Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sokol_gfx.h gl: explicitely set GL_TEXTURE_MAX_LEVEL #924

Merged
merged 7 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
## Updates

#### 27-Oct-2023

Fix broken render-to-mipmap in the sokol_gfx.h GL backend.

There was a subtle bug / "feature gap" lurking in sokol_gfx.h GL backend: trying
to render to any mipmap except the top-level mipmap resulted in a black screen
because of an incomplete-framebuffer error. This is fixed now. The changes in detail:

- creating a texture in the GL backend now sets the GL_TEXTURE_MAX_LEVEL property
(this is the fix to make everything work)
- the framebuffer completeness check in the GL backend now has more detailed error logging
- in the validation layer, the requirement that a sampler that's used with a
single-mipmap-texture must use `.mipmap_filter = SG_FILTER_NONE` has been
relaxed (a later update will remove SG_FILTER_NONE entirely since it's not needed anymore
and the concept of a "none" mipmap filter only exists in GL and Metal, but not D3D, WebGPU
and Vulkan)

Ticket: https://github.com/floooh/sokol/issues/923

PR: https://github.com/floooh/sokol/pull/924

There's also a new render-to-mipmap sample which covers to close this 'feature gap':

https://floooh.github.io/sokol-html5/miprender-sapp.html

A couple of similar samples will follow over the next few days
(rendering to texture array layers and 3d texture slices).

#### 26-Oct-2023

- sokol_app.h gl: fix a regression introduced in https://github.com/floooh/sokol/pull/916
Expand Down
104 changes: 60 additions & 44 deletions sokol_gfx.h
Original file line number Diff line number Diff line change
Expand Up @@ -1920,13 +1920,6 @@ typedef enum sg_primitive_type {
For min_filter and mag_filter the default is SG_FILTER_NEAREST.

For mipmap_filter the default is SG_FILTER_NONE.

The following restrictions apply:

- an image object with (num_mipmaps == 1) must use SG_FILTER_NONE
- min_filter and mag_filter cannot be SG_FILTER_NONE

Those restrictions are checked in the validation layer.
*/
typedef enum sg_filter {
_SG_FILTER_DEFAULT, // value 0 reserved for default-init
Expand Down Expand Up @@ -3213,8 +3206,12 @@ typedef struct sg_frame_stats {
_SG_LOGITEM_XMACRO(GL_SHADER_LINKING_FAILED, "shader linking failed (gl)") \
_SG_LOGITEM_XMACRO(GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER, "vertex attribute not found in shader (gl)") \
_SG_LOGITEM_XMACRO(GL_TEXTURE_NAME_NOT_FOUND_IN_SHADER, "texture name not found in shader (gl)") \
_SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_INCOMPLETE, "framebuffer completeness check failed (gl)") \
_SG_LOGITEM_XMACRO(GL_MSAA_FRAMEBUFFER_INCOMPLETE, "completeness check failed for msaa resolve framebuffer (gl)") \
_SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNDEFINED, "framebuffer completeness check failed with GL_FRAMEBUFFER_UNDEFINED (gl)") \
_SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT (gl)") \
_SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT (gl)") \
_SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNSUPPORTED, "framebuffer completeness check failed with GL_FRAMEBUFFER_UNSUPPORTED (gl)") \
_SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE, "framebuffer completeness check failed with GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE (gl)") \
_SG_LOGITEM_XMACRO(GL_FRAMEBUFFER_STATUS_UNKNOWN, "framebuffer completeness check failed (unknown reason) (gl)") \
_SG_LOGITEM_XMACRO(D3D11_CREATE_BUFFER_FAILED, "CreateBuffer() failed (d3d11)") \
_SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_UNSUPPORTED_PIXEL_FORMAT, "pixel format not supported for depth-stencil texture (d3d11)") \
_SG_LOGITEM_XMACRO(D3D11_CREATE_DEPTH_TEXTURE_FAILED, "CreateTexture2D() failed for depth-stencil texture (d3d11)") \
Expand Down Expand Up @@ -3433,7 +3430,6 @@ typedef struct sg_frame_stats {
_SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_EXPECTED_NONFILTERING_SAMPLER, "sg_apply_bindings: shader expected SG_SAMPLERTYPE_NONFILTERING on vertex stage, but sampler has SG_FILTER_LINEAR filters") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_UNEXPECTED_SAMPLER_BINDING, "sg_apply_bindings: unexpected sampler binding on vertex stage") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_SMP_EXISTS, "sg_apply_bindings: sampler bound to vertex stage no longer alive") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMG_SMP_MIPMAPS, "sg_apply_bindings: image bound to vertex stage has mipmap_count == 1, but associated sampler mipmap filer is not SG_MIPMAPFILTER_NONE") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_EXPECTED_IMAGE_BINDING, "sg_apply_bindings: image binding on fragment stage is missing or the image handle is invalid") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMG_EXISTS, "sg_apply_bindings: image bound to fragment stage no longer alive") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMAGE_TYPE_MISMATCH, "sg_apply_bindings: type of image bound to fragment stage doesn't match shader desc") \
Expand All @@ -3447,7 +3443,6 @@ typedef struct sg_frame_stats {
_SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_EXPECTED_NONFILTERING_SAMPLER, "sg_apply_bindings: shader expected SG_SAMPLERTYPE_NONFILTERING on fragment stage, but sampler has SG_FILTER_LINEAR filters") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_UNEXPECTED_SAMPLER_BINDING, "sg_apply_bindings: unexpected sampler binding on fragment stage") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_SMP_EXISTS, "sg_apply_bindings: sampler bound to fragment stage no longer alive") \
_SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMG_SMP_MIPMAPS, "sg_apply_bindings: image bound to fragment stage has mipmap_count == 1, but associated sampler mipmap filer is not SG_MIPMAPFILTER_NONE") \
_SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_PIPELINE, "sg_apply_uniforms: must be called after sg_apply_pipeline()") \
_SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_UB_AT_SLOT, "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot") \
_SG_LOGITEM_XMACRO(VALIDATE_AUB_SIZE, "sg_apply_uniforms: data size doesn't match declared uniform block size") \
Expand Down Expand Up @@ -4304,6 +4299,12 @@ inline int sg_append_buffer(sg_buffer buf_id, const sg_range& data) { return sg_
#define GL_TEXTURE_COMPARE_FUNC 0x884D
#define GL_COMPARE_REF_TO_TEXTURE 0x884E
#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F
#define GL_TEXTURE_MAX_LEVEL 0x813D
#define GL_FRAMEBUFFER_UNDEFINED 0x8219
#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
#endif

#ifndef GL_UNSIGNED_INT_2_10_10_10_REV
Expand Down Expand Up @@ -7689,6 +7690,7 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_image(_sg_image_t* img, const sg_
SOKOL_ASSERT(img->gl.tex[slot]);
_sg_gl_cache_store_texture_sampler_binding(0);
_sg_gl_cache_bind_texture_sampler(0, img->gl.target, img->gl.tex[slot], 0);
glTexParameteri(img->gl.target, GL_TEXTURE_MAX_LEVEL, img->cmn.num_mipmaps - 1);
const int num_faces = img->cmn.type == SG_IMAGETYPE_CUBE ? 6 : 1;
int data_index = 0;
for (int face_index = 0; face_index < num_faces; face_index++) {
Expand Down Expand Up @@ -8127,9 +8129,31 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_
}

// check if framebuffer is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
_SG_ERROR(GL_FRAMEBUFFER_INCOMPLETE);
return SG_RESOURCESTATE_FAILED;
{
const GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
switch (fb_status) {
case GL_FRAMEBUFFER_UNDEFINED:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_UNDEFINED);
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT);
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT);
break;
case GL_FRAMEBUFFER_UNSUPPORTED:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_UNSUPPORTED);
break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE);
break;
default:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_UNKNOWN);
break;
}
return SG_RESOURCESTATE_FAILED;
}
}

// setup color attachments for the framebuffer
Expand All @@ -8151,8 +8175,28 @@ _SOKOL_PRIVATE sg_resource_state _sg_gl_create_pass(_sg_pass_t* pass, _sg_image_
glBindFramebuffer(GL_FRAMEBUFFER, pass->gl.msaa_resolve_framebuffer[i]);
_sg_gl_fb_attach_texture(gl_resolve_att, cmn_resolve_att, GL_COLOR_ATTACHMENT0);
// check if framebuffer is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
_SG_ERROR(GL_MSAA_FRAMEBUFFER_INCOMPLETE);
const GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
switch (fb_status) {
case GL_FRAMEBUFFER_UNDEFINED:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_UNDEFINED);
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_ATTACHMENT);
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MISSING_ATTACHMENT);
break;
case GL_FRAMEBUFFER_UNSUPPORTED:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_UNSUPPORTED);
break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_INCOMPLETE_MULTISAMPLE);
break;
default:
_SG_ERROR(GL_FRAMEBUFFER_STATUS_UNKNOWN);
break;
}
return SG_RESOURCESTATE_FAILED;
}
// setup color attachments for the framebuffer
Expand Down Expand Up @@ -16108,34 +16152,6 @@ _SOKOL_PRIVATE bool _sg_validate_apply_bindings(const sg_bindings* bindings) {
_SG_VALIDATE(bindings->fs.samplers[i].id == SG_INVALID_ID, VALIDATE_ABND_FS_UNEXPECTED_SAMPLER_BINDING);
}
}

// if image-sampler-pair info was provided in shader desc, check that that the mipmap filter matches image num mipmaps
for (int img_smp_index = 0; img_smp_index < pip->shader->cmn.stage[SG_SHADERSTAGE_VS].num_image_samplers; img_smp_index++) {
const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_VS];
const int img_index = stage->image_samplers[img_smp_index].image_slot;
const int smp_index = stage->image_samplers[img_smp_index].sampler_slot;
const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->vs.images[img_index].id);
const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, bindings->vs.samplers[smp_index].id);
if (img && smp) {
if (img->cmn.num_mipmaps == 1) {
_SG_VALIDATE(smp->cmn.mipmap_filter == SG_FILTER_NONE, VALIDATE_ABND_VS_IMG_SMP_MIPMAPS);
}
}
}
for (int img_smp_index = 0; img_smp_index < pip->shader->cmn.stage[SG_SHADERSTAGE_FS].num_image_samplers; img_smp_index++) {
const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_FS];
const int img_index = stage->image_samplers[img_smp_index].image_slot;
const int smp_index = stage->image_samplers[img_smp_index].sampler_slot;
SOKOL_ASSERT(img_index < stage->num_images);
SOKOL_ASSERT(smp_index < stage->num_samplers);
const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->fs.images[img_index].id);
const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, bindings->fs.samplers[smp_index].id);
if (img && smp) {
if (img->cmn.num_mipmaps == 1) {
_SG_VALIDATE(smp->cmn.mipmap_filter == SG_FILTER_NONE, VALIDATE_ABND_FS_IMG_SMP_MIPMAPS);
}
}
}
return _sg_validate_end();
#endif
}
Expand Down
Loading