MonoGame icon indicating copy to clipboard operation
MonoGame copied to clipboard

GetData() corrupts data when an element size is not a multiple of 16 bytes

Open k4G17eYWI opened this issue 1 year ago • 6 comments

Prerequisites

  • [X] I have verified this issue is present in the develop branch
  • [X] I have searched open and closed issues to ensure it has not already been reported.

MonoGame Version

MonoGame.Framework.Compute.DesktopGL 3.8.3

Which MonoGame platform are you using?

MonoGame Cross-Platform Desktop Application (mgdesktopgl)

Operating System

Windows

Description

I have a struct of 3 floats. It takes three bytes. When I generate elements {111,222,333} in a shader and get it back through GetData() data becomes corrupted. Looks like it tries to extend stride to 16 bytes. If I add another float to my structure it works well (I think because of a stride becomes a multiple of 16 bytes)

image

Steps to Reproduce

shader code:

RWStructuredBuffer<particle> buf;

struct particle2
{
    float3 position;
    // float w;
};

[numthreads(1,1,1)]
void EmitParticles(uint3 id : SV_DispatchThreadID)
{
    particle2 p;
    p.position = float3(111,222,333);  
    buf[id.x] = p;
}

c# code:

public struct Particle2
{
    public float X;
    public float Y;
    public float Z;
    // public float W;
}

_shader.DispatchIndirect(0, 0, _argsBuffer);
var data = new Particle2[100];
_testBuffer.GetData(data);

If I uncomment a third component in both shader and c# it works as it should.

Minimal Example Repo

No response

Expected Behavior

GetData() should not corrupt data. If it is impossible to operate structs which stride is not a multiple of 16 an exception should be thrown.

Resulting Behavior

I have a struct of 3 floats. It takes three bytes. When I generate elements {111,222,333} in a shader and get it back through GetData() data becomes corrupted. Looks like it tries to extend stride to 16 bytes. If I add another float to my structure it works well (I think because of a stride becomes a multiple of 16 bytes)

Files

No response

k4G17eYWI avatar Apr 29 '24 07:04 k4G17eYWI

Update: my theory about setting element size multiple of 16 bytes turned out to be wrong. When a float3 hits a border of 16-byte blocks data become corrupted.

corrupted:

struct particle
{
    float3 pos;
    float3 pos2;    
    float _1;
    float _2;
};

works well:

struct particle
{
    float3 pos;
    float _1;
    // -------- 16-byte block
    float3 pos2;    
    float _2;
    // -------- 16-byte block
};

k4G17eYWI avatar Apr 29 '24 12:04 k4G17eYWI

Yes, you want to pad your data to be 16-byte aligned. GPU's are vector processors. They can process 4 floats in a single instruction, hence the 16 byte alignment.

cpt-max avatar May 09 '24 18:05 cpt-max

But Unity somehow deals with that on my android device... So what's the proper way to pass a struct containing float3-data? Pad them with a single float to get 64 bit?

k4G17eYWI avatar May 09 '24 18:05 k4G17eYWI

Yes exactly, you pad it with one float. I guess Unity does that for you.

cpt-max avatar May 09 '24 19:05 cpt-max

In this sample I'm padding the particle struct to be 8-byte aligned, which works fine: Particle So I guess 8 byte is the minimum alignement, not 16.

cpt-max avatar May 09 '24 19:05 cpt-max

Whenever you bind a structured buffer to a shader in a debug build, a check is performed that verifies that the C# struct and the shader struct are of the same size. You get an exception when they are not. With the standard Nugets this doesn't work though, because they are release builds. I pushed a fix for this now, so the check is also performed in release builds, but I didn't publish new Nugets yet.

cpt-max avatar May 20 '24 19:05 cpt-max