cpt-max / MonoGame

One framework for creating powerful cross-platform games.
http://www.monogame.net
Other
26 stars 5 forks source link

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

Open k4G17eYWI opened 3 months ago

k4G17eYWI commented 3 months ago

Prerequisites

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 commented 3 months ago

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
};
cpt-max commented 3 months ago

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.

k4G17eYWI commented 3 months ago

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?

cpt-max commented 3 months ago

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

cpt-max commented 3 months ago

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 commented 3 months ago

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.