Handle GLES stupid limitations of color-rendering properly in emulation

* GLES doesn't handle rendering to formats like a normal API - it has an odd
  list of exceptions and extensions to check. If we don't have support for
  blitting in our texture copy emulation we fall back to CPU read and upload.
* We also can't use framebuffer blits for copying MSAA textures so we have to
  fall back to clearing to black. Emulating this would require a shader write
  with sample shading of some kind which is not worthwhile since this emulation
  path is only hit for initial contents where MSAA texture initial contents are
  very unlikely to be needed.
This commit is contained in:
baldurk
2025-07-01 12:21:24 +01:00
parent 6be083f66a
commit 5e846479c7
3 changed files with 229 additions and 86 deletions
+113 -79
View File
@@ -1556,97 +1556,131 @@ GLuint GetBoundVertexBuffer(GLuint i)
return buffer;
}
void SafeBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)
struct SafeClearBlitState
{
// only viewport 0's scissor should be used for blits, but Intel seems to require all scissors to
// be disabled. Since it's only a bit more work to disable them all, we push/pop all of them
SafeClearBlitState()
{
// only viewport 0's scissor should be used for blits, but Intel seems to require all scissors
// to be disabled. Since it's only a bit more work to disable them all, we push/pop all of them
GL.glGetIntegerv(eGL_MAX_VIEWPORTS, &maxViews);
// fetch current state
{
if(HasExt[ARB_viewport_array])
{
for(GLint v = 0; v < maxViews; v++)
scissorEnabled[v] = GL.glIsEnabledi(eGL_SCISSOR_TEST, v) != 0;
}
else
{
scissorEnabled[0] = GL.glIsEnabled(eGL_SCISSOR_TEST) != 0;
}
if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
GL.glGetBooleani_v(eGL_COLOR_WRITEMASK, 0, ColorMask);
else
GL.glGetBooleanv(eGL_COLOR_WRITEMASK, ColorMask);
GL.glGetBooleanv(eGL_DEPTH_WRITEMASK, &DepthMask);
GL.glGetIntegerv(eGL_STENCIL_WRITEMASK, &StencilMask);
GL.glGetIntegerv(eGL_STENCIL_BACK_WRITEMASK, &StencilBackMask);
}
GL.glGetFloatv(eGL_COLOR_CLEAR_VALUE, ClearColor);
GL.glGetFloatv(eGL_DEPTH_CLEAR_VALUE, &ClearDepth);
GL.glGetIntegerv(eGL_STENCIL_CLEAR_VALUE, &ClearStencil);
// apply safe state
{
if(HasExt[ARB_viewport_array])
{
for(GLint v = 0; v < maxViews; v++)
GL.glDisablei(eGL_SCISSOR_TEST, v);
}
else
{
GL.glDisable(eGL_SCISSOR_TEST);
}
if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
GL.glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
else
GL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
GL.glDepthMask(GL_TRUE);
GL.glStencilMaskSeparate(eGL_FRONT, 0xff);
GL.glStencilMaskSeparate(eGL_BACK, 0xff);
}
}
~SafeClearBlitState()
{
// restore original state
{
if(HasExt[ARB_viewport_array])
{
for(GLint v = 0; v < maxViews; v++)
{
if(scissorEnabled[v])
GL.glEnablei(eGL_SCISSOR_TEST, v);
else
GL.glDisablei(eGL_SCISSOR_TEST, v);
}
}
else
{
if(scissorEnabled[0])
GL.glEnable(eGL_SCISSOR_TEST);
else
GL.glDisable(eGL_SCISSOR_TEST);
}
if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
GL.glColorMaski(0, ColorMask[0], ColorMask[1], ColorMask[2], ColorMask[3]);
else
GL.glColorMask(ColorMask[0], ColorMask[1], ColorMask[2], ColorMask[3]);
GL.glDepthMask(DepthMask);
GL.glStencilMaskSeparate(eGL_FRONT, StencilMask);
GL.glStencilMaskSeparate(eGL_BACK, StencilBackMask);
}
GL.glClearColor(ClearColor[0], ClearColor[1], ClearColor[2], ClearColor[3]);
GL.glClearDepthf(ClearDepth);
GL.glClearStencil(ClearStencil);
}
bool scissorEnabled[16] = {};
GLboolean ColorMask[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE};
GLboolean DepthMask = GL_TRUE;
GLint StencilMask = 0xff, StencilBackMask = 0xff;
GLfloat ClearColor[4] = {};
GLfloat ClearDepth = 1.0f;
GLint ClearStencil = 0;
GLint maxViews = 0;
GL.glGetIntegerv(eGL_MAX_VIEWPORTS, &maxViews);
};
// fetch current state
{
if(HasExt[ARB_viewport_array])
{
for(GLint v = 0; v < maxViews; v++)
scissorEnabled[v] = GL.glIsEnabledi(eGL_SCISSOR_TEST, v) != 0;
}
else
{
scissorEnabled[0] = GL.glIsEnabled(eGL_SCISSOR_TEST) != 0;
}
if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
GL.glGetBooleani_v(eGL_COLOR_WRITEMASK, 0, ColorMask);
else
GL.glGetBooleanv(eGL_COLOR_WRITEMASK, ColorMask);
GL.glGetBooleanv(eGL_DEPTH_WRITEMASK, &DepthMask);
GL.glGetIntegerv(eGL_STENCIL_WRITEMASK, &StencilMask);
GL.glGetIntegerv(eGL_STENCIL_BACK_WRITEMASK, &StencilBackMask);
}
// apply safe state
{
if(HasExt[ARB_viewport_array])
{
for(GLint v = 0; v < maxViews; v++)
GL.glDisablei(eGL_SCISSOR_TEST, v);
}
else
{
GL.glDisable(eGL_SCISSOR_TEST);
}
if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
GL.glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
else
GL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
GL.glDepthMask(GL_TRUE);
GL.glStencilMaskSeparate(eGL_FRONT, 0xff);
GL.glStencilMaskSeparate(eGL_BACK, 0xff);
}
void SafeBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)
{
SafeClearBlitState safe;
GL.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
// restore original state
{
if(HasExt[ARB_viewport_array])
{
for(GLint v = 0; v < maxViews; v++)
{
if(scissorEnabled[v])
GL.glEnablei(eGL_SCISSOR_TEST, v);
else
GL.glDisablei(eGL_SCISSOR_TEST, v);
}
}
else
{
if(scissorEnabled[0])
GL.glEnable(eGL_SCISSOR_TEST);
else
GL.glDisable(eGL_SCISSOR_TEST);
}
void SafeClearFramebuffer(GLbitfield clearMask, GLfloat rgba[4], GLfloat depth, GLint stencil)
{
SafeClearBlitState safe;
if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
GL.glColorMaski(0, ColorMask[0], ColorMask[1], ColorMask[2], ColorMask[3]);
else
GL.glColorMask(ColorMask[0], ColorMask[1], ColorMask[2], ColorMask[3]);
GL.glDepthMask(DepthMask);
GL.glStencilMaskSeparate(eGL_FRONT, StencilMask);
GL.glStencilMaskSeparate(eGL_BACK, StencilBackMask);
}
GL.glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]);
GL.glClearDepthf(0.0f);
GL.glClearStencil(0);
GL.glClear(clearMask);
}
BufferCategory MakeBufferCategory(GLenum bufferTarget)
+3
View File
@@ -630,6 +630,7 @@ void GetCurrentBinding(GLuint curProg, ShaderReflection *refl, const ConstantBlo
// pops state for only a single drawbuffer!
void SafeBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
void SafeClearFramebuffer(GLbitfield clearMask, GLfloat rgba[4], GLfloat depth, GLint stencil);
enum UniformType
{
@@ -790,6 +791,8 @@ extern bool IsGLES;
EXT_TO_CHECK(99, 99, EXT_texture_buffer) \
/* OpenGL ES extensions */ \
EXT_TO_CHECK(99, 32, EXT_color_buffer_float) \
EXT_TO_CHECK(99, 99, EXT_color_buffer_half_float) \
EXT_TO_CHECK(99, 99, EXT_render_snorm) \
EXT_TO_CHECK(99, 32, EXT_primitive_bounding_box) \
EXT_TO_CHECK(99, 32, OES_primitive_bounding_box) \
EXT_TO_CHECK(99, 32, OES_texture_border_color) \
+113 -7
View File
@@ -1187,6 +1187,7 @@ void APIENTRY _glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLev
GLenum attach = eGL_COLOR_ATTACHMENT0;
bool layered = false;
bool msaa = false;
bool cpuTransfer = false;
if(srcTarget == eGL_TEXTURE_CUBE_MAP || srcTarget == eGL_TEXTURE_CUBE_MAP_ARRAY ||
@@ -1206,8 +1207,87 @@ void APIENTRY _glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLev
GLenum fmt = eGL_NONE;
GL.glGetTexLevelParameteriv(levelQueryType, 0, eGL_TEXTURE_INTERNAL_FORMAT, (GLint *)&fmt);
bool colorRenderable = true;
if(IsCompressedFormat(fmt))
colorRenderable = false;
if(fmt == eGL_RGB9_E5)
colorRenderable = false;
if(colorRenderable && IsGLES)
{
// before GLES 3 framebuffers basically weren't supported for the lack of formats you could
// use. We can turn some on based on extensions below
if(GLCoreVersion < 30)
colorRenderable = false;
// GLES randomly excludes an inconsistent set of formats
switch(fmt)
{
// case eGL_RGB8: // valid?!
case eGL_SRGB8:
case eGL_RGB8I:
case eGL_RGB8UI:
case eGL_RGB8_SNORM:
case eGL_RGB9_E5:
case eGL_RGB10:
case eGL_RGB16I:
case eGL_RGB16UI:
case eGL_RGB32F:
case eGL_RGB32I:
case eGL_RGB32UI: colorRenderable = false; break;
default: break;
}
// SRGB 1- and 2- channel formats aren't renderable
if(fmt == eGL_SR8_EXT || fmt == eGL_SRG8_EXT)
colorRenderable = false;
// some extensions are required for other formats
switch(fmt)
{
case eGL_R8_SNORM:
case eGL_RG8_SNORM:
case eGL_RGBA8_SNORM: colorRenderable = HasExt[EXT_render_snorm];
case eGL_R16_SNORM:
case eGL_RG16_SNORM:
case eGL_RGBA16_SNORM: colorRenderable = HasExt[EXT_render_snorm];
case eGL_R16F:
case eGL_RG16F:
case eGL_RGBA16F:
colorRenderable = HasExt[EXT_color_buffer_half_float] || HasExt[EXT_color_buffer_float];
case eGL_RGB16F: colorRenderable = HasExt[EXT_color_buffer_half_float];
case eGL_R32F:
case eGL_RG32F:
case eGL_RGBA32F:
case eGL_R11F_G11F_B10F: colorRenderable = HasExt[EXT_color_buffer_float];
case eGL_RGB10_A2:
case eGL_RGB10_A2UI: colorRenderable = (GLCoreVersion >= 30);
default: break;
}
}
// can't blit or CPU-read MSAA textures. we treat these as 'color-renderable' but we will just clear to black
if(srcTarget == eGL_TEXTURE_2D_MULTISAMPLE || srcTarget == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
colorRenderable = true;
msaa = true;
RDCDEBUG("Can't support image copy emulation on MSAA images, clearing to black");
}
// non-color-renderable formats have to go through this path even though they are not compressed
if(IsCompressedFormat(fmt) || fmt == eGL_RGB9_E5)
if(!colorRenderable)
{
// have to do this via CPU readback, there's no alternative for GPU copies
cpuTransfer = true;
@@ -1332,6 +1412,8 @@ void APIENTRY _glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLev
}
}
GLfloat black[4] = {};
if(cpuTransfer)
{
// nothing to do!
@@ -1348,8 +1430,16 @@ void APIENTRY _glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLev
if(status != eGL_FRAMEBUFFER_COMPLETE)
RDCERR("glCopyImageSubData emulation read FBO is %s", ToStr(status).c_str());
SafeBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, dstX, dstY,
dstX + srcWidth, dstY + srcHeight, mask, eGL_NEAREST);
if(msaa)
{
SafeClearFramebuffer(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
black, 0.0f, 0);
}
else
{
SafeBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, dstX, dstY,
dstX + srcWidth, dstY + srcHeight, mask, eGL_NEAREST);
}
}
else if(srcTarget == eGL_TEXTURE_CUBE_MAP)
{
@@ -1381,8 +1471,16 @@ void APIENTRY _glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLev
RDCERR("glCopyImageSubData emulation read FBO is %s for slice 0", ToStr(status).c_str());
}
SafeBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, dstX, dstY,
dstX + srcWidth, dstY + srcHeight, mask, eGL_NEAREST);
if(msaa)
{
SafeClearFramebuffer(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
black, 0.0f, 0);
}
else
{
SafeBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, dstX, dstY,
dstX + srcWidth, dstY + srcHeight, mask, eGL_NEAREST);
}
}
}
else
@@ -1405,8 +1503,16 @@ void APIENTRY _glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLev
RDCERR("glCopyImageSubData emulation read FBO is %s for slice 0", ToStr(status).c_str());
}
SafeBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, dstX, dstY,
dstX + srcWidth, dstY + srcHeight, mask, eGL_NEAREST);
if(msaa)
{
SafeClearFramebuffer(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
black, 0.0f, 0);
}
else
{
SafeBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, dstX, dstY,
dstX + srcWidth, dstY + srcHeight, mask, eGL_NEAREST);
}
}
}
}