floooh / sokol

minimal cross-platform standalone C headers
https://floooh.github.io/sokol-html5
zlib License
6.55k stars 469 forks source link

sokol_gfx: Issue creating render target #936

Closed leiradel closed 6 months ago

leiradel commented 8 months ago

I'm having trouble creating a render target on Windows with the D3D11 backend.

I had code working fine on Android using the GLES3 backend, but it refused to create the render target on Windows:

SokolRenderTarget(long const width, long const height) : rm::RenderTarget(width, height) {
    sg_desc sgdesc = sg_query_desc();

    {
        sg_image_desc desc = {};
        desc.render_target = true;
        desc.width = width;
        desc.height = height;
        desc.pixel_format = SG_PIXELFORMAT_RGBA8;
        desc.sample_count = sgdesc.context.sample_count;
        _target = sg_make_image(&desc);

        if (sg_query_image_state(_target) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer image");
            return;
        }

        RM_INFO("Created color attachment");

        desc.pixel_format = sgdesc.context.depth_format;
        desc.sample_count = sgdesc.context.sample_count;
        _depth = sg_make_image(&desc);

        if (sg_query_image_state(_depth) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer depth image");
            return;
        }

        RM_INFO("Created depth buffer");
    }

    {
        sg_pass_desc desc = {};
        desc.color_attachments[0].image = _target;
        desc.depth_stencil_attachment.image = _depth;
        _pass = sg_make_pass(&desc);

        if (sg_query_pass_state(_pass) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer pass");
            return;
        }
    }
}

There's a validation error when creating the depth buffer:

[ERROR] sokol_gfx.h:14771: VALIDATE_IMAGEDESC_RT_PIXELFORMAT: invalid pixel format for render-target image
[FATAL] sokol_gfx.h:14683: VALIDATION_FAILED: validation layer checks failed

The best I could come up with is this:

#if defined(_WIN32)
                desc.pixel_format = SG_PIXELFORMAT_DEPTH;
#else
                desc.pixel_format = sgdesc.context.depth_format;
#endif

It's a red flag for me, the fact that I'm testing the platform there tells me that I'd doing something wrong, but that got me past the render target creation. However, I then I get this error when rendering:

[ERROR] sokol_gfx.h:15318: VALIDATE_ABND_FS_IMAGE_MSAA: sg_apply_bindings: cannot bind image with sample_count>1 to fragment stage
[FATAL] sokol_gfx.h:14683: VALIDATION_FAILED: validation layer checks failed

I'm using sokol_gp to render the game since it's 2D. I recon that the issue may be in it, but I'd appreciate some assurance that I'm correctly creating the render target.

Thanks in advance!

leiradel commented 8 months ago

Oh, I think I should say that I tried many different combinations of values in pixel_format for both the color attachment and the depth buffer, after looking at the sokol examples. Unfortunately, nothing worked.

floooh commented 8 months ago

I think these are several issues. The first one I need to investigate (e.g. why does creating a rendering target with pixel format sgdesc.context.depth_format fail, which is most likely SG_PIXELFORMAT_DEPTH_STENCIL, this is definitely weird.

The second problem regarding VALIDATE_ABND_FS_IMAGE_MSAA was a breaking change from May 2023:

https://github.com/floooh/sokol/blob/master/CHANGELOG.md#19-may-2023

(an MSAA render target now needs to be explicitly resolved into a non-MSAA render target, and this resolved image needs to be used as texture).

floooh commented 8 months ago

As for the first error, can you check these two things:

if (sg_query_pixelformat(SG_PIXELFORMAT_DEPTH_STENCIL).render) {
    printf("depth stencil is renderable\n");
} else {
    printf("depth stencil is not renderable!\n");
}

...if this already ends up in the else branch, then there's something weird about your D3D11 driver, because in this case the D3D11 driver says that SG_PIXELFORMAT_DEPTH_STENCIL isn't renderable (this check happens here: https://github.com/floooh/sokol/blob/bd7fa9300dc2e2edca087cf436d1c5c07bd754fc/sokol_gfx.h#L9816)

...second can you check that the pixel format in sgdesc.context.depth_format is indeed SG_PIXELFORMAT_DEPTH_STENCIL (numeric value 43)?

floooh commented 8 months ago

PS: the closest sokol sample for your use case is this:

https://github.com/floooh/sokol-samples/blob/master/sapp/offscreen-msaa-sapp.c

...specifically this part where the pass object is created:

https://github.com/floooh/sokol-samples/blob/3e746d86cd87f634f4f7793c2e8d0ef71a2067ca/sapp/offscreen-msaa-sapp.c#L70-L113

Note how there's now 3 image objects required instead of two:

...to access the render target as texture in a pixel shader you would need the resolve-image now

leiradel commented 8 months ago

As for the first error, can you check these two things:

if (sg_query_pixelformat(SG_PIXELFORMAT_DEPTH_STENCIL).render) {
    printf("depth stencil is renderable\n");
} else {
    printf("depth stencil is not renderable!\n");
}

...if this already ends up in the else branch, then there's something weird about your D3D11 driver, because in this case the D3D11 driver says that SG_PIXELFORMAT_DEPTH_STENCIL isn't renderable (this check happens here:

https://github.com/floooh/sokol/blob/bd7fa9300dc2e2edca087cf436d1c5c07bd754fc/sokol_gfx.h#L9816 )

It ends up in the else branch:

[INFO ] Initializing platform services
[INFO ] Initializing GDI+
[INFO ] Initializing time
[INFO ] Initializing renderer
[INFO ] depth stencil is not renderable!
leiradel commented 8 months ago

...second can you check that the pixel format in sgdesc.context.depth_format is indeed SG_PIXELFORMAT_DEPTH_STENCIL (numeric value 43)?

Thread 1 hit Breakpoint 1, (anonymous namespace)::SokolRenderTarget::SokolRenderTarget (this=0x1484c3189f0, width=1280, height=888) at Platform/Sokol/Renderer.cpp:117
117                     desc.sample_count = sgdesc.context.sample_count;
(gdb) p sgdesc.context.depth_format
$2 = SG_PIXELFORMAT_DEPTH_STENCIL
(gdb) p (int)SG_PIXELFORMAT_DEPTH_STENCIL
$4 = 43

This is when creating the render target.

leiradel commented 8 months ago

PS: the closest sokol sample for your use case is this:

https://github.com/floooh/sokol-samples/blob/master/sapp/offscreen-msaa-sapp.c

I'm not using MSAA, maybe it's something from sokol_gp? Also, I'm using sokol_app on Windows, but not on Android.

floooh commented 8 months ago

[INFO ] depth stencil is not renderable!

Wait, it's the same here on my laptop! I need to investigate this. In the meantime, please use SG_PIXELFORMAT_DEPTH as workaround for offscreen render targets (unless stencil is needed, in that case you'd need to wait for a fix).

I'm not using MSAA

Hmm, the error VALIDATE_ABND_FS_IMAGE_MSAA happens when trying to bind a render target image with .sample_count > 1 to a shader stage, and since in your code a render target image is initialized like this:

        sg_image_desc desc = {};
        desc.render_target = true;
        desc.width = width;
        desc.height = height;
        desc.pixel_format = SG_PIXELFORMAT_RGBA8;
        desc.sample_count = sgdesc.context.sample_count;  <===================
        _target = sg_make_image(&desc);

...I was assuming that sgdesc.context.sample_count is set to a value > 1. If no MSAA is intended for offscreen rendering, it's better to set desc.sample_count explicitly to 1 (also in the pipeline objects used for offscreen rendering!)

I'll check what's up with the non-renderable SG_PIXELFORMAT_DEPTH_STENCIL next.

floooh commented 8 months ago

I'm preparing a fix for the non-renderable SG_PIXELFORMAT_DEPTH_STENCIL images in this PR:

https://github.com/floooh/sokol/pull/937

...the problem was that all depth pixel formats should also be reported as renderable (which didn't happen in the d3d11 backend). I also cleaned up the texture vs resource-view pixel formats for SG_PIXELFORMAT_DEPTH_STENCIL in the D3D11 backend (similar how it was already done for SG_PIXELFORMAT_DEPTH). I tested in the offscreen-sapp.c sample by switching all occurances of SG_PIXELFORMAT_DEPTH to SG_PIXELFORMAT_DEPTH_STENCIL.

The multisample validation error is something else though (please check that your rendertarget image really isn't created with a sample_count > 1.

floooh commented 8 months ago

Ok, PR https://github.com/floooh/sokol/pull/937 has been merged, which should fix the first part of the problem (not being able to create a depth-stencil render-target image.

Let me know if this works, and let's look into the multisample validation error next :)

leiradel commented 8 months ago

Ok, I've updated sokol_gfx, and changed sokol_gp at one place to replace SG_SAMPLERTYPE_SAMPLE with SG_SAMPLERTYPE_FILTERING. My code to create a render target now looks like this:

SokolRenderTarget(long const width, long const height) : rm::RenderTarget(width, height) {
    sg_desc sgdesc = sg_query_desc();

    {
        sg_image_desc desc = {};
        desc.render_target = true;
        desc.width = width;
        desc.height = height;
        desc.pixel_format = SG_PIXELFORMAT_RGBA8;
        desc.sample_count = 1;
        _target = sg_make_image(&desc);

        if (sg_query_image_state(_target) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer image");
            return;
        }

        RM_INFO("Created color attachment");

        desc.pixel_format = SG_PIXELFORMAT_DEPTH;
        desc.sample_count = 1;
        _depth = sg_make_image(&desc);

        if (sg_query_image_state(_depth) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer depth image");
            return;
        }

        RM_INFO("Created depth buffer");
    }

    {
        sg_pass_desc desc = {};
        desc.color_attachments[0].image = _target;
        desc.depth_stencil_attachment.image = _depth;
        _pass = sg_make_pass(&desc);

        if (sg_query_pass_state(_pass) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer pass");
            return;
        }
    }
}

The render target is created but now I get an error when trying to render into it:

[ERROR] sokol_gfx.h:16131: VALIDATE_APIP_COLOR_FORMAT: sg_apply_pipeline: pipeline color attachment pixel format doesn't match pass color attachment pixel format
[ERROR] sokol_gfx.h:16132: VALIDATE_APIP_SAMPLE_COUNT: sg_apply_pipeline: pipeline MSAA sample count doesn't match render pass attachment sample count
[ERROR] sokol_gfx.h:16136: VALIDATE_APIP_DEPTH_FORMAT: sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format
[FATAL] sokol_gfx.h:15603: VALIDATION_FAILED: validation layer checks failed

On Android, with the same code, I'm also getting a validation failure when trying to render into the render target:

sokol_gfx.h:16136: VALIDATE_APIP_DEPTH_FORMAT: sg_apply_pipeline: pipeline depth pixel_format doesn't match pass depth attachment pixel format
sokol_gfx.h:15603: VALIDATION_FAILED: validation layer checks failed

Thanks for the time you're putting into this issue, and please let me know if you need any other tests.

floooh commented 8 months ago

Ah ok, now it's clashing with sokol_gp.h pipeline creation code.

It looks like sokol_gp.h expects the depth-pixel-format to be identical with the default framebuffer, and doesn't have enough options to inject a completely custom pipeline object (e.g. this is all you can change: https://github.com/edubart/sokol_gp/blob/f3a4ec845c782157c4182c39e5a13c49f4858a60/sokol_gp.h#L530-L535).

On the other hand you can inject "any" pipeline here as long as it is compatible with what sokol_gp.h expects (e.g. you don't have to use sgp_make_pipeline():

https://github.com/edubart/sokol_gp/blob/f3a4ec845c782157c4182c39e5a13c49f4858a60/sokol_gp.h#L2075-L2088

There are two options now, either make your render pass compatible, with the default framebuffer, and switch off multisampling in the default framebuffer:

...or you create your own pipeline object with sg_make_pipeline() (NOT sgp_make_pipeline()) which is compatible with your render pass and a duplicate of the sokol_gp.h shader.

The third option is to ask the sokol_gp.h author (or to patch this in your own copy) to add more configuration options to sgp_make_pipeline(), at least the depth pixel format and sample count.

leiradel commented 8 months ago

I can now create a render target and use it without any issues on Android using this:

SokolRenderTarget(long const width, long const height) : rm::RenderTarget(width, height) {
    sg_desc sgdesc = sg_query_desc();

    {
        sg_image_desc desc = {};
        desc.render_target = true;
        desc.width = width;
        desc.height = height;
        desc.pixel_format = sgdesc.context.color_format;
        desc.sample_count = sgdesc.context.sample_count;
        _target = sg_make_image(&desc);

        if (sg_query_image_state(_target) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer image");
            return;
        }

        RM_INFO("Created color attachment");

        desc.pixel_format = sgdesc.context.depth_format;
        _depth = sg_make_image(&desc);

        if (sg_query_image_state(_depth) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer depth image");
            return;
        }

        RM_INFO("Created depth buffer");
    }

    {
        sg_pass_desc desc = {};
        desc.color_attachments[0].image = _target;
        desc.depth_stencil_attachment.image = _depth;
        _pass = sg_make_pass(&desc);

        if (sg_query_pass_state(_pass) != SG_RESOURCESTATE_VALID) {
            RM_FATAL("Failed to create frame buffer pass");
            return;
        }
    }
}

On Windows, the render target is correctly created, but all I get is a gray screen. This is on me though, since I can't even render a colored quad. I'm probably doing something wrong.

Thanks for all the help!

edubart commented 7 months ago

The third option is to ask the sokol_gp.h author (or to patch this in your own copy) to add more configuration options to sgp_make_pipeline(), at least the depth pixel format and sample count.

sokol_gp.h author here, I will probably expose both later to help with this issue.

But about the depth pixel format, the library is intended for 2D, so it does no care about depth-stencil format, that's why I created the issue https://github.com/floooh/sokol/issues/632 in the past. Ideally he should be not be using depth stencil for 2D stuff, maybe Sokol could have a "dont care" pixel format for depth-stencil, and then I advise users of Sokol GP to use that everywhere.

edubart commented 6 months ago

I updated sokol_gp.h to be able to specify depth_format and sample_count in its pipelines, and also on context creation. So I think this issue can be closed.

floooh commented 6 months ago

Thanks!

leiradel commented 3 months ago

I forgot to thank you guys for solving this, so thank you!