mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 00:58:30 +09:00
[Merge] (ShaderTranspiler, GLState, DirectGLES): land dev GL43 wave2/wave3 under the translation cache
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/BufferEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToStr/BufferEnumConverter.h>
|
||||
#include <MG_Util/ShaderTranspiler/Types.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
||||
Bool ValidateBufferTarget(BufferTarget target) {
|
||||
@@ -67,6 +68,13 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
||||
// binding points in GL 3.3 (no ARB_transform_feedback3).
|
||||
pointCount = std::min<SizeT>(pointCount, 4);
|
||||
}
|
||||
if (target == BufferTarget::AtomicCounter) {
|
||||
// GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, which is NOT the state layer's array
|
||||
// size: a counter buffer reaches a shader only as a lowered storage block, so the
|
||||
// reserved range is the ceiling, and glGetIntegerv advertises the same number.
|
||||
pointCount = std::min<SizeT>(
|
||||
pointCount, static_cast<SizeT>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFER_BINDINGS));
|
||||
}
|
||||
return pointCount;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -326,10 +326,23 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
|
||||
// GL 4.6 core 10.9: inside a conditional block whose predicate did not pass, the drawing
|
||||
// commands, Clear, ClearBuffer* and the compute dispatches are DISCARDED. The gate sits on the
|
||||
// wrappers that ISSUE the backend call rather than at the top of each entry point, so that
|
||||
// everything a real driver would still do inside the block - argument validation and the
|
||||
// errors it raises - happens exactly as it does outside one, and only the command itself is
|
||||
// dropped. It is deliberately not on the frontend's transform-feedback accounting either:
|
||||
// that mirrors what the capture stage would have written, and a conditional block around a
|
||||
// capturing draw has no test coverage in either direction.
|
||||
static Bool ConditionalRenderDiscardsCommand() {
|
||||
return MG_State::pGLContext->ConditionalRenderDiscardsCommands();
|
||||
}
|
||||
|
||||
void Clear_Backend(GLbitfield mask) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.Clear(mask);
|
||||
}
|
||||
|
||||
@@ -337,6 +350,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawElements(mode, count, type, indices);
|
||||
}
|
||||
|
||||
@@ -345,6 +359,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElements(mode, count, type, indices, drawcount);
|
||||
}
|
||||
|
||||
@@ -353,6 +368,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsBaseVertex(mode, count, type, indices, drawcount,
|
||||
basevertex);
|
||||
}
|
||||
@@ -361,6 +377,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawArrays(mode, first, count);
|
||||
}
|
||||
|
||||
@@ -368,6 +385,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArrays(mode, first, count, drawcount);
|
||||
}
|
||||
|
||||
@@ -376,6 +394,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawElementsBaseVertex(mode, count, type, indices, basevertex);
|
||||
}
|
||||
|
||||
@@ -384,6 +403,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
|
||||
}
|
||||
|
||||
@@ -391,6 +411,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirect(mode, indirect, drawcount, stride);
|
||||
}
|
||||
|
||||
@@ -399,6 +420,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount(mode, type, indirect, drawcount,
|
||||
maxdrawcount, stride);
|
||||
}
|
||||
@@ -408,6 +430,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount(mode, indirect, drawcount, maxdrawcount,
|
||||
stride);
|
||||
}
|
||||
@@ -417,6 +440,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawRangeElementsBaseVertex(mode, start, end, count, type, indices,
|
||||
basevertex);
|
||||
}
|
||||
@@ -426,6 +450,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawRangeElements(mode, start, end, count, type, indices);
|
||||
}
|
||||
|
||||
@@ -435,6 +460,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseVertexBaseInstance(
|
||||
mode, count, type, indices, instancecount, basevertex, baseinstance);
|
||||
}
|
||||
@@ -444,6 +470,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseVertex(mode, count, type, indices, instancecount,
|
||||
basevertex);
|
||||
}
|
||||
@@ -453,6 +480,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseInstance(mode, count, type, indices,
|
||||
instancecount, baseinstance);
|
||||
}
|
||||
@@ -462,6 +490,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstanced(mode, count, type, indices, instancecount);
|
||||
}
|
||||
|
||||
@@ -469,6 +498,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawElementsIndirect(mode, type, indirect);
|
||||
}
|
||||
void DrawArraysInstancedBaseInstance_Backend(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
@@ -476,6 +506,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawArraysInstancedBaseInstance(mode, first, count, instancecount,
|
||||
baseinstance);
|
||||
}
|
||||
@@ -484,6 +515,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawArraysInstanced(mode, first, count, instancecount);
|
||||
}
|
||||
|
||||
@@ -491,6 +523,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawArraysIndirect(mode, indirect);
|
||||
}
|
||||
|
||||
@@ -519,6 +552,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// GL 4.3 added both dispatches to the conditional-render set (GL 4.6 core 10.9), which is
|
||||
// exactly what KHR-GL43.compute_shader.conditional-dispatching checks.
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
dispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
|
||||
}
|
||||
|
||||
@@ -570,6 +606,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||
if (ConditionalRenderDiscardsCommand()) return;
|
||||
dispatchComputeIndirect(indirect);
|
||||
}
|
||||
|
||||
|
||||
@@ -725,8 +725,8 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadName, GLuint name) DECLARE_GL_FUNCTION_S
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PushName, GLuint name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushName, name)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PopName) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PopName)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ClampColor, GLenum target, GLenum clamp) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClampColor, target, clamp)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginConditionalRender, GLuint id, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginConditionalRender, id, mode)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndConditionalRender, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndConditionalRender)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BeginConditionalRender, GLuint id, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginConditionalRender, id, mode)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, EndConditionalRender) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndConditionalRender)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1i, GLuint index, GLint x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1i, index, x)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2i, GLuint index, GLint x, GLint y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2i, index, x, y)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3i, GLuint index, GLint x, GLint y, GLint z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3i, index, x, y, z)
|
||||
@@ -982,7 +982,7 @@ DECLARE_GL_FUNCTION_HEAD(void, GetDoublei_v, GLenum target, GLuint index, GLdoub
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawArraysInstancedBaseInstance, GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawArraysInstancedBaseInstance, mode, first, count, instancecount, baseinstance)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseInstance, mode, count, type, indices, instancecount, baseinstance)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseVertexBaseInstance, mode, count, type, indices, instancecount, basevertex, baseinstance)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data)
|
||||
|
||||
@@ -2613,18 +2613,26 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
void ClearBufferfi_Backend(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
|
||||
// GL 4.6 core 10.9 makes ClearBuffer* conditional alongside the drawing commands.
|
||||
if (MG_State::pGLContext->ConditionalRenderDiscardsCommands()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.ClearBufferfi(buffer, drawbuffer, depth, stencil);
|
||||
}
|
||||
|
||||
void ClearBufferfv_Backend(GLenum buffer, GLint drawbuffer, const GLfloat* value) {
|
||||
// GL 4.6 core 10.9 makes ClearBuffer* conditional alongside the drawing commands.
|
||||
if (MG_State::pGLContext->ConditionalRenderDiscardsCommands()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.ClearBufferfv(buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearBufferuiv_Backend(GLenum buffer, GLint drawbuffer, const GLuint* value) {
|
||||
// GL 4.6 core 10.9 makes ClearBuffer* conditional alongside the drawing commands.
|
||||
if (MG_State::pGLContext->ConditionalRenderDiscardsCommands()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.ClearBufferuiv(buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearBufferiv_Backend(GLenum buffer, GLint drawbuffer, const GLint* value) {
|
||||
// GL 4.6 core 10.9 makes ClearBuffer* conditional alongside the drawing commands.
|
||||
if (MG_State::pGLContext->ConditionalRenderDiscardsCommands()) return;
|
||||
MG_Backend::gBackendFunctionsTable.GL.ClearBufferiv(buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||
#include <MG_Util/Texture/TextureFormatProcessor.h>
|
||||
#include <MG_Util/Async/ShaderCompilePool.h>
|
||||
#include <MG_Util/ShaderTranspiler/Types.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
@@ -46,13 +47,29 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr GLint kFrontendMaxComputeUniformComponents = 1024;
|
||||
constexpr GLint kFrontendMaxComputeAtomicCounters = 8;
|
||||
constexpr GLint kFrontendMaxComputeAtomicCounterBuffers = 8;
|
||||
// Shared with the glslang resource table for the same reason as the atomic-counter
|
||||
// limits below: gl_MaxComputeUniformComponents expands from BuildTBuiltInResource.
|
||||
constexpr GLint kFrontendMaxComputeUniformComponents =
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_COMPUTE_UNIFORM_COMPONENTS);
|
||||
// Every atomic-counter limit is shared with the glslang resource table
|
||||
// (BuildTBuiltInResource) through MG_Util/ShaderTranspiler/Types.h: GL 4.6 requires
|
||||
// glGetIntegerv and the gl_MaxAtomicCounter* built-in constants to agree, and the two
|
||||
// used to be independent tables that disagreed on both the binding count and the buffer
|
||||
// size. Never move one of these without the other.
|
||||
constexpr GLint kFrontendMaxComputeAtomicCounters =
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTERS_PER_STAGE);
|
||||
constexpr GLint kFrontendMaxComputeAtomicCounterBuffers =
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE);
|
||||
constexpr GLint kFrontendMaxComputeSharedMemorySize = 32768;
|
||||
constexpr GLint kFrontendMaxComputeWorkGroupInvocations = 1024;
|
||||
constexpr GLint kFrontendMaxCombinedAtomicCounters = 8;
|
||||
constexpr GLint kFrontendMaxFragmentAtomicCounters = 8;
|
||||
constexpr GLint kFrontendMaxCombinedAtomicCounters =
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTERS_PER_STAGE);
|
||||
constexpr GLint kFrontendMaxCombinedAtomicCounterBuffers =
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE);
|
||||
constexpr GLint kFrontendMaxFragmentAtomicCounters =
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTERS_PER_STAGE);
|
||||
constexpr GLint kFrontendMaxFragmentAtomicCounterBuffers =
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE);
|
||||
constexpr GLint kFrontendMaxGeometryAtomicCounters = 0;
|
||||
constexpr GLint kFrontendMaxTessControlAtomicCounters = 0;
|
||||
constexpr GLint kFrontendMaxTessEvaluationAtomicCounters = 0;
|
||||
@@ -66,10 +83,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
constexpr GLint kFrontendMaxTessControlAtomicCounterBuffers = 0;
|
||||
constexpr GLint kFrontendMaxTessEvaluationAtomicCounterBuffers = 0;
|
||||
constexpr GLint kFrontendMaxVertexAtomicCounterBuffers = 0;
|
||||
// One atomic counter is a uint, and a buffer never has to hold more counters than the
|
||||
// combined limit the frontend advertises. GL 4.6 table 23.63 floors this at 32 bytes.
|
||||
// GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE: the byte offset ceiling a counter may be declared
|
||||
// at. The matching binding count is applied in GetIndexedBufferQueryPointCount, so that
|
||||
// the getter, the indexed queries and glBindBufferBase all share one ceiling.
|
||||
constexpr GLint kFrontendMaxAtomicCounterBufferSize =
|
||||
kFrontendMaxCombinedAtomicCounters * static_cast<GLint>(sizeof(GLuint));
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFER_SIZE);
|
||||
// KHR_debug minima (GL 4.6 table 23.66); the debug entry points are stubs, but the
|
||||
// limits they advertise still have to be legal.
|
||||
constexpr GLint kFrontendMaxDebugGroupStackDepth = 64;
|
||||
@@ -103,12 +121,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
constexpr GLint kFrontendSubpixelBits = 4;
|
||||
constexpr GLint kFrontendMaxSamples = 4;
|
||||
|
||||
// The floors under GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE. Shared with the compile
|
||||
// pipeline (CaptureCompileEnv floors the same driver answers at them, and
|
||||
// BuildTBuiltInResource expands gl_MaxComputeWorkGroup* from the result), because a
|
||||
// shader is allowed to compare the built-in constant against this query.
|
||||
constexpr GLint GetMinComputeWorkGroupCount(GLuint index) {
|
||||
return index < 3 ? 65535 : 0;
|
||||
return index < 3 ? static_cast<GLint>(MG_Util::ShaderTranspiler::MIN_COMPUTE_WORK_GROUP_COUNT[index]) : 0;
|
||||
}
|
||||
|
||||
constexpr GLint GetMinComputeWorkGroupSize(GLuint index) {
|
||||
return index < 2 ? 1024 : (index == 2 ? 64 : 0);
|
||||
return index < 3 ? static_cast<GLint>(MG_Util::ShaderTranspiler::MIN_COMPUTE_WORK_GROUP_SIZE[index]) : 0;
|
||||
}
|
||||
|
||||
GLint GetMaxCombinedUniformComponents(GLint maxDefaultUniformComponents, GLint maxUniformBlocks,
|
||||
@@ -186,6 +208,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings;
|
||||
return std::min(frontendCount, static_cast<SizeT>(std::max(backendCount, 0)));
|
||||
}
|
||||
if (bufferTarget == BufferTarget::AtomicCounter) {
|
||||
// The counter family's binding count is NOT the state layer's array size: a
|
||||
// counter buffer only reaches a shader as a lowered storage block, so what an
|
||||
// implementation can serve is the reserved range, and that number is also what
|
||||
// glslang compiles a layout(binding = N) atomic_uint against. Clamped here so
|
||||
// GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, the indexed getters' index check and
|
||||
// glBindBufferBase's all report the same ceiling.
|
||||
return std::min(frontendCount,
|
||||
static_cast<SizeT>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFER_BINDINGS));
|
||||
}
|
||||
return frontendCount;
|
||||
}
|
||||
|
||||
@@ -213,6 +245,23 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return ClampBlockCountToBindingPoints(blockCount, BufferTarget::ShaderStorage);
|
||||
}
|
||||
|
||||
// The per-stage GL_MAX_*_SHADER_STORAGE_BLOCKS answers. Backend-derived, and NOT a
|
||||
// constant to be "restored" - these used to return a flat 16 for vertex, geometry and
|
||||
// both tessellation stages, which is wrong on any host that does not serve storage
|
||||
// blocks in those stages. Zero is a legal answer: GL 4.6 table 23.64 and ES 3.2 table
|
||||
// 21.44 both set the minimum at 0 for every graphics stage except fragment, which is
|
||||
// why the conformance suite gates each such test on the query instead of assuming it.
|
||||
// ARM's GLES driver reports 0 for all four (a Mali-G925 does), and advertising 16 there
|
||||
// bought nothing: the program still failed to link inside the backend, the frontend
|
||||
// still reported LINK_STATUS as true, and every draw with it silently rendered nothing.
|
||||
GLint StageStorageBlockCount(Int MG_Backend::DynamicBackendParameters::*stageLimit) {
|
||||
static const MG_Backend::DynamicBackendParameters kBackendlessDefaults{};
|
||||
const MG_Backend::DynamicBackendParameters& parameters =
|
||||
MG_Backend::pActiveBackendObject ? MG_Backend::pActiveBackendObject->GetDynamicParameters()
|
||||
: kBackendlessDefaults;
|
||||
return ClampStorageBlockCount(static_cast<GLint>(parameters.*stageLimit));
|
||||
}
|
||||
|
||||
bool TryDecodeDrawBufferQuery(GLenum pname, SizeT& drawBufferIndex) {
|
||||
if (pname == GL_DRAW_BUFFER) {
|
||||
drawBufferIndex = 0;
|
||||
@@ -1532,6 +1581,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_MAX_COMBINED_ATOMIC_COUNTERS:
|
||||
*params = kFrontendMaxCombinedAtomicCounters;
|
||||
return;
|
||||
case GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS:
|
||||
*params = kFrontendMaxCombinedAtomicCounterBuffers;
|
||||
return;
|
||||
case GL_MAX_COMBINED_UNIFORM_BLOCKS:
|
||||
*params = ClampUniformBlockCount(kFrontendMaxCombinedUniformBlocks);
|
||||
return;
|
||||
@@ -1547,8 +1599,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_MAX_FRAGMENT_ATOMIC_COUNTERS:
|
||||
*params = kFrontendMaxFragmentAtomicCounters;
|
||||
return;
|
||||
case GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS:
|
||||
*params = kFrontendMaxFragmentAtomicCounterBuffers;
|
||||
return;
|
||||
case GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS:
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
*params = StageStorageBlockCount(&MG_Backend::DynamicBackendParameters::MaxFragmentShaderStorageBlocks);
|
||||
return;
|
||||
case GL_MAX_FRAGMENT_INPUT_COMPONENTS:
|
||||
*params = kFrontendMaxFragmentInputComponents;
|
||||
@@ -1574,7 +1629,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = kFrontendMaxGeometryAtomicCounterBuffers;
|
||||
return;
|
||||
case GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS:
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
*params = StageStorageBlockCount(&MG_Backend::DynamicBackendParameters::MaxGeometryShaderStorageBlocks);
|
||||
return;
|
||||
case GL_MAX_GEOMETRY_INPUT_COMPONENTS:
|
||||
*params = kFrontendMaxGeometryInputComponents;
|
||||
@@ -1645,16 +1700,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = 0;
|
||||
return;
|
||||
case GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS:
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
*params = StageStorageBlockCount(&MG_Backend::DynamicBackendParameters::MaxTessControlShaderStorageBlocks);
|
||||
return;
|
||||
case GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS:
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
*params =
|
||||
StageStorageBlockCount(&MG_Backend::DynamicBackendParameters::MaxTessEvaluationShaderStorageBlocks);
|
||||
return;
|
||||
case GL_MAX_TEXTURE_LOD_BIAS:
|
||||
*params = 15; // TODO
|
||||
return;
|
||||
case GL_MAX_UNIFORM_LOCATIONS:
|
||||
*params = 1024 * 4; // TODO
|
||||
// The same constant the link's location allocator enforces - see ProgramObject.
|
||||
*params = MG_State::GLState::ProgramObject::MAX_UNIFORM_LOCATIONS;
|
||||
return;
|
||||
case GL_MAX_VARYING_COMPONENTS:
|
||||
*params = kFrontendMaxVaryingComponents;
|
||||
@@ -1674,7 +1731,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
: MG_Backend::DynamicBackendParameters{}.MaxVertexImageUniforms;
|
||||
return;
|
||||
case GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS:
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
*params = StageStorageBlockCount(&MG_Backend::DynamicBackendParameters::MaxVertexShaderStorageBlocks);
|
||||
return;
|
||||
case GL_MAX_VERTEX_UNIFORM_COMPONENTS:
|
||||
*params = kFrontendMaxVertexUniformComponents;
|
||||
@@ -1984,6 +2041,24 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_UNIFORM_BUFFER_START:
|
||||
RecordIndexedOnlyGetterError(__func__, pname);
|
||||
return;
|
||||
// glBindBufferBase/Range set the GENERIC binding point too (GL 4.6 core 6.1.1), and this
|
||||
// is the one indexed-buffer family whose non-indexed query was never answered - so it
|
||||
// fell through to INVALID_ENUM and left the caller's variable holding whatever was in its
|
||||
// stack slot. _START/_SIZE stay indexed-only, exactly like their uniform-buffer siblings.
|
||||
case GL_ATOMIC_COUNTER_BUFFER_BINDING:
|
||||
if (const auto& obj =
|
||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::AtomicCounter).GetBoundObject()) {
|
||||
*params = static_cast<GLint>(obj->GetExternalIndex());
|
||||
} else {
|
||||
*params = 0;
|
||||
}
|
||||
return;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_START:
|
||||
RecordIndexedOnlyGetterError(__func__, pname);
|
||||
return;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_SIZE:
|
||||
RecordIndexedOnlyGetterError(__func__, pname);
|
||||
return;
|
||||
case GL_UNPACK_ALIGNMENT:
|
||||
*params = MG_State::pGLContext->GetPixelStoreParam(PixelStoreParam::UnpackAlignment);
|
||||
return;
|
||||
@@ -2219,18 +2294,19 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
static_cast<Uint64>(INT32_MAX)));
|
||||
break;
|
||||
case GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS:
|
||||
// NOT the frontend's binding-point array size: GetIndexedBufferQueryPointCount
|
||||
// clamps this family to the range a lowered counter block can actually be served
|
||||
// from, which is the same number glslang compiles a layout(binding = N) atomic_uint
|
||||
// against and the same one glBindBufferBase validates an index against.
|
||||
*params = static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::AtomicCounter));
|
||||
break;
|
||||
case GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE:
|
||||
// The conformance suite splits this evenly across every advertised binding point and
|
||||
// binds all of them in one glBindBuffersRange
|
||||
// (KHR-GL44.multi_bind.functional_bind_buffers_range), so the pair has to divide:
|
||||
// 32 bytes over 36 binding points is a zero-sized range, which BindBufferRange
|
||||
// rejects with INVALID_VALUE before it binds anything. Floor the advertised size at
|
||||
// one counter per binding point.
|
||||
*params = std::max<GLint>(
|
||||
kFrontendMaxAtomicCounterBufferSize,
|
||||
static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::AtomicCounter) * sizeof(GLuint)));
|
||||
// (KHR-GL44.multi_bind.functional_bind_buffers_range), so the pair has to divide -
|
||||
// a zero-sized range is INVALID_VALUE before BindBufferRange binds anything. The
|
||||
// shared constant is 16384 over 8 binding points, which divides.
|
||||
*params = kFrontendMaxAtomicCounterBufferSize;
|
||||
break;
|
||||
case GL_MAX_TEXTURE_BUFFER_SIZE:
|
||||
*params = dynamicParameters.MaxTextureBufferSize;
|
||||
|
||||
@@ -645,7 +645,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
break;
|
||||
}
|
||||
case GL_ACTIVE_ATOMIC_COUNTER_BUFFERS:
|
||||
*params = programObject->GetActiveAtomicCounterCount();
|
||||
// Counter BUFFERS, not counters, and glslang's own getNumAtomicCounters() answers
|
||||
// neither: the relaxed parse has already turned every atomic_uint into a plain uint
|
||||
// member of a synthesized storage block by the time it builds its reflection, so it
|
||||
// reports zero. The interface-query model recovers the buffers from those blocks and
|
||||
// is what glGetProgramInterfaceiv(GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES)
|
||||
// already answers - the two queries are required to agree.
|
||||
*params = ProgramInterface::GetActiveResourceCount(*programObject, GL_ATOMIC_COUNTER_BUFFER);
|
||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||
break;
|
||||
case GL_ACTIVE_ATTRIBUTES:
|
||||
@@ -2838,6 +2844,73 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return ProgramInterface::GetResourceLocationIndex(*programObject, programInterface, name);
|
||||
}
|
||||
|
||||
// GL 4.6 §7.7. Every property this reports is one the GL_ATOMIC_COUNTER_BUFFER interface
|
||||
// already carries, so this is a rename of glGetProgramResourceiv's props onto the older
|
||||
// entry point's - and the two are required to agree, which is only true while both read the
|
||||
// same model. It was a silent stub: it wrote nothing, raised nothing, and left every probe
|
||||
// reading its own uninitialised output.
|
||||
static Bool TryMapActiveAtomicCounterBufferProp(GLenum pname, GLenum& outProp) {
|
||||
switch (pname) {
|
||||
case GL_ATOMIC_COUNTER_BUFFER_BINDING:
|
||||
outProp = GL_BUFFER_BINDING;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE:
|
||||
outProp = GL_BUFFER_DATA_SIZE;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS:
|
||||
outProp = GL_NUM_ACTIVE_VARIABLES;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES:
|
||||
outProp = GL_ACTIVE_VARIABLES;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER:
|
||||
outProp = GL_REFERENCED_BY_VERTEX_SHADER;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER:
|
||||
outProp = GL_REFERENCED_BY_TESS_CONTROL_SHADER;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER:
|
||||
outProp = GL_REFERENCED_BY_TESS_EVALUATION_SHADER;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER:
|
||||
outProp = GL_REFERENCED_BY_GEOMETRY_SHADER;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER:
|
||||
outProp = GL_REFERENCED_BY_FRAGMENT_SHADER;
|
||||
return true;
|
||||
case GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_COMPUTE_SHADER:
|
||||
outProp = GL_REFERENCED_BY_COMPUTE_SHADER;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GetActiveAtomicCounterBufferiv(GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) {
|
||||
auto& programObject = TryToGetProgramForInterfaceQuery(program, __func__);
|
||||
if (!programObject) return;
|
||||
GLenum prop = GL_NONE;
|
||||
if (!TryMapActiveAtomicCounterBufferProp(pname, prop)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"pname is not an active atomic counter buffer property."));
|
||||
return;
|
||||
}
|
||||
Vector<GLint> values;
|
||||
if (!ProgramInterface::GetResourceProp(*programObject, GL_ATOMIC_COUNTER_BUFFER, bufferIndex, prop, values)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"bufferIndex is not an active atomic counter buffer index."));
|
||||
return;
|
||||
}
|
||||
if (params == nullptr) return;
|
||||
// GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES is the only multi-value property
|
||||
// here, and the caller sized its array from _ACTIVE_ATOMIC_COUNTERS.
|
||||
for (SizeT i = 0; i < values.size(); ++i) params[i] = values[i];
|
||||
}
|
||||
|
||||
// GL 4.6 §7.6.2: <storageBlockIndex> is an active shader storage block index of <program>
|
||||
// - that is, exactly what glGetProgramResourceIndex(GL_SHADER_STORAGE_BLOCK) returned.
|
||||
// Since wave 2 that index is the interface-query layer's, so this is where the one index
|
||||
|
||||
@@ -140,6 +140,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
|
||||
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void GetActiveAtomicCounterBufferiv(GLuint program, GLuint bufferIndex, GLenum pname, GLint* params);
|
||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||
void Uniform1d(GLint location, GLdouble v0);
|
||||
void Uniform1dv(GLint location, GLsizei count, const GLdouble* value);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface {
|
||||
// "<getAtomicCounterBlockName()>_<binding>" (ParseContextBase.cpp), one per GL
|
||||
// atomic-counter binding point. That block IS the GL_ATOMIC_COUNTER_BUFFER resource
|
||||
// and its trailing number IS GL_BUFFER_BINDING; its members stay GL_UNIFORMs.
|
||||
constexpr const char* kAtomicCounterBlockPrefix = "gl_AtomicCounterBlock";
|
||||
constexpr const char* kAtomicCounterBlockPrefix = MG_Util::ShaderTranspiler::ATOMIC_COUNTER_BLOCK_PREFIX;
|
||||
|
||||
enum class BlockKind {
|
||||
Uniform, // a real GL uniform block
|
||||
|
||||
@@ -565,6 +565,75 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
queryObject->ended = true;
|
||||
}
|
||||
|
||||
void BeginConditionalRender(GLuint id, GLenum mode) {
|
||||
// GL 4.6 core 10.9's eight modes. The _INVERTED half flips the sense of the predicate;
|
||||
// the BY_REGION half only narrows WHERE an implementation is permitted to discard, so
|
||||
// treating it as its whole-framebuffer sibling is what an implementation without region
|
||||
// granularity does. The _NO_WAIT half is a permission to render rather than stall, not an
|
||||
// obligation - see the resolve below.
|
||||
Bool inverted = false;
|
||||
switch (mode) {
|
||||
case GL_QUERY_WAIT:
|
||||
case GL_QUERY_NO_WAIT:
|
||||
case GL_QUERY_BY_REGION_WAIT:
|
||||
case GL_QUERY_BY_REGION_NO_WAIT:
|
||||
inverted = false;
|
||||
break;
|
||||
case GL_QUERY_WAIT_INVERTED:
|
||||
case GL_QUERY_NO_WAIT_INVERTED:
|
||||
case GL_QUERY_BY_REGION_WAIT_INVERTED:
|
||||
case GL_QUERY_BY_REGION_NO_WAIT_INVERTED:
|
||||
inverted = true;
|
||||
break;
|
||||
default:
|
||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "mode is not a conditional render mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (MG_State::pGLContext->IsConditionalRenderActive()) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Conditional rendering is already active.");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
const auto* queryObject = FindQueryObjectLocked(id);
|
||||
// A generated NAME is not yet a query object; it becomes one at its first use with a
|
||||
// target (the same rule glIsQuery answers by).
|
||||
if (!queryObject || (!queryObject->created && queryObject->target == 0)) {
|
||||
RecordQueryError(ErrorCode::InvalidValue, __FUNCTION__, "id is not the name of a query object.");
|
||||
return;
|
||||
}
|
||||
if (queryObject->active) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "The query object is still active.");
|
||||
return;
|
||||
}
|
||||
if (queryObject->target != GL_SAMPLES_PASSED && queryObject->target != GL_ANY_SAMPLES_PASSED &&
|
||||
queryObject->target != GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||
"Conditional rendering requires an occlusion query object.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolved ONCE, here, and by WAITING even for the _NO_WAIT modes: the spec lets those
|
||||
// render instead of stalling, so always waiting is conforming and is the only choice that
|
||||
// gives the whole block one deterministic verdict. Reading it per command instead would
|
||||
// let a result that lands mid-block change the answer half way through.
|
||||
Uint64 samplesPassed = 0;
|
||||
if (!GetQueryObjectValue(id, GL_QUERY_RESULT, __FUNCTION__, samplesPassed)) return;
|
||||
const Bool passed = samplesPassed != 0;
|
||||
MG_State::pGLContext->BeginConditionalRender(id, mode, inverted ? passed : !passed);
|
||||
}
|
||||
|
||||
void EndConditionalRender() {
|
||||
if (!MG_State::pGLContext->IsConditionalRenderActive()) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Conditional rendering is not active.");
|
||||
return;
|
||||
}
|
||||
MG_State::pGLContext->EndConditionalRender();
|
||||
}
|
||||
|
||||
void GetQueryiv(GLenum target, GLenum pname, GLint* params) {
|
||||
if (!params) {
|
||||
return;
|
||||
|
||||
@@ -29,6 +29,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||
void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||
void QueryCounter(GLuint id, GLenum target);
|
||||
// Conditional rendering (GL 4.6 core 10.9). Implemented here rather than beside the drawing
|
||||
// entry points because the predicate is a QUERY OBJECT's result, and the object registry -
|
||||
// with the lock that guards it - lives in this file.
|
||||
void BeginConditionalRender(GLuint id, GLenum mode);
|
||||
void EndConditionalRender();
|
||||
// Destroys every still-registered query object exactly as DeleteQueries would.
|
||||
// GL requires queries to die with their context; called only from full library
|
||||
// teardown (DestroyImpl), where no context survives on any thread, so the
|
||||
|
||||
@@ -661,21 +661,42 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
"Compressed texture formats are not supported."));
|
||||
}
|
||||
|
||||
// glGetTexLevelParameter{i,f}v answers WIDTH/HEIGHT/DEPTH out of the mipmap chain. The only
|
||||
// other storage type the state layer knows is GL_TEXTURE_BUFFER (TextureStorageType is
|
||||
// {Mipmap, Buffer}), whose level geometry this stack does not track yet. Report that instead
|
||||
// of throwing: THROW_UNIMPL_EXCEPTION unwinds a C++ exception through the C GL ABI and takes
|
||||
// the process down, which is never an acceptable answer to a query - see the same reasoning
|
||||
// above for the compressed-format path.
|
||||
// GL_TEXTURE_WIDTH of a buffer texture: how many texels of the texture's internal format fit
|
||||
// in the buffer range it addresses, CLAMPED to GL_MAX_TEXTURE_BUFFER_SIZE. Attaching a larger
|
||||
// buffer is legal (GL 4.6 core 8.9) - the texture simply addresses the first
|
||||
// MAX_TEXTURE_BUFFER_SIZE texels of it, and that clamped count is what WIDTH reports.
|
||||
//
|
||||
// GL_TEXTURE_BUFFER_SIZE is deliberately NOT clamped the same way: it reports the range in
|
||||
// basic machine units exactly as glTexBuffer/glTexBufferRange were given it. Swapping the two
|
||||
// fails KHR-GL43.texture_buffer.texture_buffer_max_size in the opposite direction.
|
||||
GLint GetBufferTextureTexelWidth(const MG_State::GLState::ITextureObject* textureObject) {
|
||||
const SizeT texelByteSize = MG_Util::GetSizedInternalFormatSizeInBytes(textureObject->GetFormat());
|
||||
// A format with no known footprint has no texel count to report; answering 0 beats
|
||||
// dividing by it.
|
||||
if (texelByteSize == 0) return 0;
|
||||
const auto* bufferTextureObject =
|
||||
static_cast<const MG_State::GLState::TextureObjectBuffer*>(textureObject);
|
||||
const SizeT texelCount = bufferTextureObject->GetBufferRangeSizeInBytes() / texelByteSize;
|
||||
const SizeT maxTexelCount = static_cast<SizeT>(
|
||||
std::max(0, MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxTextureBufferSize));
|
||||
return static_cast<GLint>(std::min(texelCount, maxTexelCount));
|
||||
}
|
||||
|
||||
// glGetTexLevelParameter{i,f}v answers WIDTH/HEIGHT/DEPTH out of the mipmap chain, and (since
|
||||
// the buffer-texture arms above) out of the attached buffer range for GL_TEXTURE_BUFFER. This
|
||||
// is what is left: a storage class with no level geometry at all. Report it instead of
|
||||
// throwing - THROW_UNIMPL_EXCEPTION unwinds a C++ exception through the C GL ABI and takes the
|
||||
// process down, which is never an acceptable answer to a query - see the same reasoning above
|
||||
// for the compressed-format path.
|
||||
void RecordUnsupportedLevelQueryStorage(const char* caller, GLenum pname) {
|
||||
MGLOG_W_ONCE("%s: glGetTexLevelParameter(pname=%s) is not implemented for texture-buffer "
|
||||
"storage; recording GL_INVALID_OPERATION instead of terminating",
|
||||
MGLOG_W_ONCE("%s: glGetTexLevelParameter(pname=%s) is not implemented for this texture's "
|
||||
"storage class; recording GL_INVALID_OPERATION instead of terminating",
|
||||
caller, MG_Util::ConvertGLEnumToString(pname).c_str());
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", caller,
|
||||
"Level queries are not supported for texture-buffer storage."));
|
||||
"Level queries are not supported for this texture's storage class."));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -691,6 +712,34 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return textureObject;
|
||||
}
|
||||
|
||||
// Whether a raw internalformat enum names a compressed format - the question GL asks whenever an
|
||||
// entry point is forbidden on a compressed image: glTexStorage3D on TEXTURE_3D (no
|
||||
// block-compressed format is defined for a three-dimensional image, so it is INVALID_OPERATION
|
||||
// rather than the INVALID_ENUM an unknown sized format gets - GL 4.6 core 8.19 / Khronos bug
|
||||
// 11239, KHR-GLxx.texture_storage.compressed_data) and the clear-texture pair (8.19 again).
|
||||
// Written against the enum ranges rather than a name list because the families are contiguous
|
||||
// and MobileGL's own internal-format enum drops the ones it cannot carry, which would make this
|
||||
// check silently narrower than the API surface.
|
||||
static Bool IsCompressedGLInternalFormat(GLenum internalformat) {
|
||||
switch (internalformat) {
|
||||
case 0x8225: // GL_COMPRESSED_RED
|
||||
case 0x8226: // GL_COMPRESSED_RG
|
||||
case 0x84ED: // GL_COMPRESSED_RGB
|
||||
case 0x84EE: // GL_COMPRESSED_RGBA
|
||||
case 0x8C48: // GL_COMPRESSED_SRGB
|
||||
case 0x8C49: // GL_COMPRESSED_SRGB_ALPHA
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (internalformat >= 0x83F0 && internalformat <= 0x83F3) || // S3TC / DXT
|
||||
(internalformat >= 0x8DBB && internalformat <= 0x8DBE) || // RGTC
|
||||
(internalformat >= 0x8E8C && internalformat <= 0x8E8F) || // BPTC
|
||||
(internalformat >= 0x9270 && internalformat <= 0x9279) || // ETC2 / EAC
|
||||
(internalformat >= 0x93B0 && internalformat <= 0x93BD) || // ASTC LDR
|
||||
(internalformat >= 0x93D0 && internalformat <= 0x93DD); // ASTC sRGB
|
||||
}
|
||||
|
||||
namespace {
|
||||
void RecordClearTextureError(const char* caller, ErrorCode code, const String& message) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
@@ -726,6 +775,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
std::format("Texture level {} is not defined.", level));
|
||||
return nullptr;
|
||||
}
|
||||
// GL 4.6 core 8.19: a compressed internal format is INVALID_OPERATION for both clear
|
||||
// entry points. Two tags to ask, because they answer different questions: the stored
|
||||
// one covers a level glCompressedTexImage* or a SPECIFIC compressed internalformat
|
||||
// defined, the requested one covers the six generic GL_COMPRESSED_* enums that MobileGL
|
||||
// deliberately backs with uncompressed storage (see MipmapStorage) and that would
|
||||
// otherwise look like an ordinary RGBA8 image by the time the clear runs.
|
||||
const auto& uploadTargets = mipmapTexture->GetUploadTargets();
|
||||
if (!uploadTargets.empty() &&
|
||||
(mipmapTexture->GetMipmapCompressedFormat(uploadTargets[0], static_cast<Uint>(level)) != GL_NONE ||
|
||||
mipmapTexture->GetMipmapRequestedCompressedFormat(uploadTargets[0], static_cast<Uint>(level)) !=
|
||||
GL_NONE)) {
|
||||
RecordClearTextureError(caller, ErrorCode::InvalidOperation,
|
||||
"Compressed textures cannot be cleared.");
|
||||
return nullptr;
|
||||
}
|
||||
return mipmapTexture;
|
||||
}
|
||||
|
||||
@@ -2197,6 +2261,26 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
} else {
|
||||
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
|
||||
// The same specific-compressed-format tag glTexImage2D records (see TexImage2D_State):
|
||||
// GL 4.6 core 8.5 commits the level to that format, so GL_TEXTURE_COMPRESSED and
|
||||
// GL_TEXTURE_INTERNAL_FORMAT must report it - and, less obviously, glCopyImageSubData
|
||||
// sizes the level's texel BLOCK from it. Without the tag a GL_COMPRESSED_RG_RGTC2
|
||||
// array level measured as the RG8 storage it resolved to, 2 bytes instead of 16, and
|
||||
// the copy-compatibility rule refused a pairing 18.3.2 requires. AllocateStorage above
|
||||
// clears the tag, so this has to follow it.
|
||||
const auto compressedInfo = MG_Util::GetCompressedFormatInfo(static_cast<GLenum>(internalformat));
|
||||
if (compressedInfo.blockWidth != 0) {
|
||||
textureMipmapObject->SetMipmapCompressedImage(
|
||||
textureUploadTarget, level, static_cast<GLenum>(internalformat), nullptr,
|
||||
MG_Util::CalculateCompressedTextureImageSize(compressedInfo, {width, height, depth}));
|
||||
}
|
||||
// Also after AllocateStorage, which clears it. Records the generic GL_COMPRESSED_*
|
||||
// enums too, which the tag above deliberately skips - glClearTexImage has to refuse
|
||||
// them all (GL 4.6 core 8.19).
|
||||
if (IsCompressedGLInternalFormat(static_cast<GLenum>(internalformat))) {
|
||||
textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget, level,
|
||||
static_cast<GLenum>(internalformat));
|
||||
}
|
||||
}
|
||||
|
||||
if (!originalPixels) {
|
||||
@@ -2343,6 +2427,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
textureUploadTarget, level, static_cast<GLenum>(internalformat), nullptr,
|
||||
MG_Util::CalculateCompressedTextureImageSize(compressedInfo, {width, height, 1}));
|
||||
}
|
||||
// Also after AllocateStorage, which clears it. Records the generic GL_COMPRESSED_*
|
||||
// enums too, which the tag above deliberately skips - glClearTexImage has to refuse
|
||||
// them all (GL 4.6 core 8.19).
|
||||
if (IsCompressedGLInternalFormat(static_cast<GLenum>(internalformat))) {
|
||||
textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget, level,
|
||||
static_cast<GLenum>(internalformat));
|
||||
}
|
||||
}
|
||||
|
||||
if (!originalPixels) {
|
||||
@@ -2431,6 +2522,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (!isProxy) {
|
||||
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, 1, 1}, internalBytes});
|
||||
// After AllocateStorage, which clears the tag. No block-compressed format has a 1D
|
||||
// layout, so only the specific-format tag the 2D/3D paths record is skipped here - the
|
||||
// request itself still has to be remembered for glClearTexImage (GL 4.6 core 8.19).
|
||||
if (IsCompressedGLInternalFormat(static_cast<GLenum>(internalFormat))) {
|
||||
textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget, level,
|
||||
static_cast<GLenum>(internalFormat));
|
||||
}
|
||||
}
|
||||
|
||||
if (!originalPixels) {
|
||||
@@ -2982,6 +3080,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = textureObject->GetSamplerObject()->GetMaxAnisotropy();
|
||||
}
|
||||
break;
|
||||
// GL 4.6 core 8.11 lists this among the parameters EVERY GetTexParameter form answers.
|
||||
// It was handled by the iv/Iiv/Iuiv getters and missed by this one, so the float query
|
||||
// raised GL_INVALID_ENUM and left the caller's float untouched - which is what
|
||||
// KHR-GL4x.shader_image_load_store.basic-api-texParam reads back.
|
||||
case GL_IMAGE_FORMAT_COMPATIBILITY_TYPE:
|
||||
if (params) {
|
||||
*params = static_cast<GLfloat>(GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE);
|
||||
}
|
||||
break;
|
||||
case GL_DEPTH_STENCIL_TEXTURE_MODE:
|
||||
if (params) {
|
||||
*params = static_cast<GLfloat>(textureObject->GetDepthStencilTextureMode());
|
||||
@@ -3031,6 +3138,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).x();
|
||||
break;
|
||||
}
|
||||
case TextureStorageType::Buffer:
|
||||
*params = GetBufferTextureTexelWidth(textureObject.get());
|
||||
break;
|
||||
default:
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname);
|
||||
break;
|
||||
@@ -3046,6 +3156,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).y();
|
||||
break;
|
||||
}
|
||||
case TextureStorageType::Buffer:
|
||||
*params = 1; // a buffer texture is one-dimensional
|
||||
break;
|
||||
default:
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname);
|
||||
break;
|
||||
@@ -3061,6 +3174,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).z();
|
||||
break;
|
||||
}
|
||||
case TextureStorageType::Buffer:
|
||||
*params = 1; // a buffer texture is one-dimensional
|
||||
break;
|
||||
default:
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname);
|
||||
break;
|
||||
@@ -3130,6 +3246,31 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GL_TEXTURE_BUFFER_SIZE:
|
||||
case GL_TEXTURE_BUFFER_OFFSET: {
|
||||
// GL 4.6 core 8.9: both describe the window of the attached buffer a GL_TEXTURE_BUFFER
|
||||
// texture addresses, so there is nothing to report for any other storage - which is
|
||||
// INVALID_OPERATION, the same shape GL_TEXTURE_COMPRESSED_IMAGE_SIZE guards itself with
|
||||
// above.
|
||||
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", "GetTexLevelParameteriv_State",
|
||||
"GL_TEXTURE_BUFFER_SIZE / GL_TEXTURE_BUFFER_OFFSET need a buffer texture."));
|
||||
return;
|
||||
}
|
||||
if (params) {
|
||||
const auto* bufferTextureObject =
|
||||
static_cast<MG_State::GLState::TextureObjectBuffer*>(textureObject.get());
|
||||
// Basic machine units, and UNCLAMPED - see GetBufferTextureTexelWidth for why this
|
||||
// half does not take the GL_MAX_TEXTURE_BUFFER_SIZE clamp that WIDTH does.
|
||||
*params = static_cast<GLint>(pname == GL_TEXTURE_BUFFER_SIZE
|
||||
? bufferTextureObject->GetBufferRangeSizeInBytes()
|
||||
: bufferTextureObject->GetBufferRangeOffset());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexLevelParameteriv_State",
|
||||
@@ -3169,6 +3310,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).x();
|
||||
break;
|
||||
}
|
||||
case TextureStorageType::Buffer:
|
||||
*params = (GLfloat)GetBufferTextureTexelWidth(textureObject.get());
|
||||
break;
|
||||
default:
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname);
|
||||
break;
|
||||
@@ -3184,6 +3328,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).y();
|
||||
break;
|
||||
}
|
||||
case TextureStorageType::Buffer:
|
||||
*params = 1.0f; // a buffer texture is one-dimensional
|
||||
break;
|
||||
default:
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname);
|
||||
break;
|
||||
@@ -3199,6 +3346,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).z();
|
||||
break;
|
||||
}
|
||||
case TextureStorageType::Buffer:
|
||||
*params = 1.0f; // a buffer texture is one-dimensional
|
||||
break;
|
||||
default:
|
||||
RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname);
|
||||
break;
|
||||
@@ -3266,6 +3416,27 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GL_TEXTURE_BUFFER_SIZE:
|
||||
case GL_TEXTURE_BUFFER_OFFSET: {
|
||||
// See GetTexLevelParameteriv_State: both describe the attached buffer range of a
|
||||
// GL_TEXTURE_BUFFER texture, so any other storage makes the query INVALID_OPERATION.
|
||||
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", "GetTexLevelParameterfv_State",
|
||||
"GL_TEXTURE_BUFFER_SIZE / GL_TEXTURE_BUFFER_OFFSET need a buffer texture."));
|
||||
return;
|
||||
}
|
||||
if (params) {
|
||||
const auto* bufferTextureObject =
|
||||
static_cast<MG_State::GLState::TextureObjectBuffer*>(textureObject.get());
|
||||
*params = static_cast<GLfloat>(pname == GL_TEXTURE_BUFFER_SIZE
|
||||
? bufferTextureObject->GetBufferRangeSizeInBytes()
|
||||
: bufferTextureObject->GetBufferRangeOffset());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexLevelParameterfv_State",
|
||||
@@ -3412,9 +3583,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Backend::gBackendFunctionsTable.GL.CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
|
||||
}
|
||||
|
||||
void CopyImageSubData_Backend(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
void CopyImageSubData_Backend(const MG_Backend::CopyImageEndpoint& src,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
const MG_Backend::CopyImageEndpoint& dst,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
||||
auto copyImageSubData = MG_Backend::gBackendFunctionsTable.GL.CopyImageSubData;
|
||||
@@ -3425,7 +3596,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
"Backend does not support image-to-image copies."));
|
||||
return;
|
||||
}
|
||||
copyImageSubData(srcTexture, srcTarget, srcLevel, srcX, srcY, srcZ, dstTexture, dstTarget, dstLevel, dstX,
|
||||
copyImageSubData(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, dstLevel, dstX,
|
||||
dstY, dstZ, srcWidth, srcHeight, srcDepth);
|
||||
}
|
||||
|
||||
@@ -3472,9 +3643,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// the ~30 entry points that reach it through a BOUND object (where the name was never
|
||||
// in question and the fault is the binding), so this is a local rule rather than a
|
||||
// change to the helper.
|
||||
Bool ValidateCopyImageObjectExists(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||
Bool ValidateCopyImageObjectExists(const MG_Backend::CopyImageEndpoint& endpoint,
|
||||
const char* endpointName) {
|
||||
if (textureObject) return true;
|
||||
if (endpoint.Exists()) return true;
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
@@ -3498,21 +3669,106 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Util::ConvertTextureTargetToString(textureObject->GetTarget()))));
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool ValidateCopyImageSubData_State(const SharedPtr<MG_State::GLState::ITextureObject>& srcTexture,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& dstTexture,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
||||
if (!ValidateCopyImageObjectExists(srcTexture, "source") ||
|
||||
!ValidateCopyImageObjectExists(dstTexture, "destination")) {
|
||||
// ---- The questions ValidateCopyImageSubData_State asks of one endpoint. ---------------
|
||||
// A renderbuffer answers all of them directly: it has exactly one image, no mip chain and
|
||||
// no sampler state, and it carries its own internal format and extent.
|
||||
|
||||
Int GetCopyImageEndpointSamples(const MG_Backend::CopyImageEndpoint& endpoint) {
|
||||
if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetSamples();
|
||||
return endpoint.Texture->GetSamples();
|
||||
}
|
||||
|
||||
TextureInternalFormat GetCopyImageEndpointFormat(const MG_Backend::CopyImageEndpoint& endpoint) {
|
||||
if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetInternalFormat();
|
||||
return endpoint.Texture->GetFormat();
|
||||
}
|
||||
|
||||
// A renderbuffer has level 0 and nothing else, and the failure is the same INVALID_VALUE
|
||||
// ValidateTextureLevelExists records for a level a texture does not have.
|
||||
Bool ValidateCopyImageEndpointLevelExists(const MG_Backend::CopyImageEndpoint& endpoint, GLint level,
|
||||
const char* caller) {
|
||||
if (!endpoint.IsRenderbuffer()) {
|
||||
return TextureImpl::ValidateTextureLevelExists(endpoint.Texture, level, caller);
|
||||
}
|
||||
if (level == 0) return true;
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "A renderbuffer has only level 0."));
|
||||
return false;
|
||||
}
|
||||
const auto srcTextureTarget = MG_Util::ConvertGLEnumToTextureTarget(srcTarget);
|
||||
const auto dstTextureTarget = MG_Util::ConvertGLEnumToTextureTarget(dstTarget);
|
||||
if (!TextureImpl::ValidateTextureTarget(srcTextureTarget) ||
|
||||
!TextureImpl::ValidateTextureTarget(dstTextureTarget)) {
|
||||
|
||||
// Targets with no mip chain have q == level_base by definition (GL 4.6 core 8.17), so no
|
||||
// minification filter can make them mipmap incomplete - while the shared predicate derives
|
||||
// q from the base level's size alone and would call a 16x16 multisample image incomplete.
|
||||
Bool CopyImageTargetHasMipmapChain(TextureTarget target) {
|
||||
switch (target) {
|
||||
case TextureTarget::TextureRectangle:
|
||||
case TextureTarget::TextureBuffer:
|
||||
case TextureTarget::Texture2DMultisample:
|
||||
case TextureTarget::Texture2DMultisampleArray:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Bool IsCopyImageEndpointComplete(const MG_Backend::CopyImageEndpoint& endpoint) {
|
||||
// A renderbuffer is complete exactly when it has storage - there is nothing else it
|
||||
// could be missing.
|
||||
if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->IsAllocated();
|
||||
const auto* texture = endpoint.Texture.get();
|
||||
if (!texture) return false;
|
||||
// 18.3.2 asks for TEXTURE completeness, which GL 4.6 core 8.17 defines to include the
|
||||
// MIP CHAIN whenever the minification filter samples it - and ITextureObject::
|
||||
// IsComplete() only answers the storage half (an internal format, and no zero-size
|
||||
// level in the middle of the chain). A texture with level 0 alone and the default
|
||||
// NEAREST_MIPMAP_LINEAR filter is incomplete, which is exactly how
|
||||
// KHR-GL43.copy_image.incomplete_tex builds its subject.
|
||||
//
|
||||
// The filter is the texture's OWN: copy-image never goes through a texture unit, so no
|
||||
// sampler object is in play. An immutable texture is unaffected - glTexStorage clamps
|
||||
// TEXTURE_MAX_LEVEL to levels-1, which is what makes a single-level immutable texture
|
||||
// mipmap complete under any filter.
|
||||
const auto& sampler = texture->GetSamplerObject();
|
||||
const Bool mipmapped = CopyImageTargetHasMipmapChain(texture->GetTarget()) && sampler &&
|
||||
sampler->GetMipmapMode() != SamplerMipmapMode::None;
|
||||
return MG_State::GLState::IsMipmapCompleteForFilter(texture, mipmapped);
|
||||
}
|
||||
|
||||
GLenum GetCopyImageEndpointCompressedFormat(const MG_Backend::CopyImageEndpoint& endpoint,
|
||||
TextureUploadTarget uploadTarget, GLint level) {
|
||||
if (endpoint.IsRenderbuffer()) return GL_NONE;
|
||||
return GetCompressedLevelFormat(endpoint.Texture, uploadTarget, level);
|
||||
}
|
||||
|
||||
IntVec3 GetCopyImageEndpointLevelSize(const MG_Backend::CopyImageEndpoint& endpoint,
|
||||
TextureUploadTarget uploadTarget, GLint level) {
|
||||
if (endpoint.IsRenderbuffer()) {
|
||||
return {endpoint.Renderbuffer->GetWidth(), endpoint.Renderbuffer->GetHeight(), 1};
|
||||
}
|
||||
return GetCopyImageLevelSize(endpoint.Texture, uploadTarget, level);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool ValidateCopyImageSubData_State(const MG_Backend::CopyImageEndpoint& src,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY,
|
||||
const MG_Backend::CopyImageEndpoint& dst,
|
||||
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
||||
if (!ValidateCopyImageObjectExists(src, "source") ||
|
||||
!ValidateCopyImageObjectExists(dst, "destination")) {
|
||||
return false;
|
||||
}
|
||||
// GL_RENDERBUFFER has no TextureTarget to convert to, and it needs none: it is its own
|
||||
// whole-image target, and the endpoint that carries it was resolved from the renderbuffer
|
||||
// namespace, so it matches its object by construction.
|
||||
const auto srcTextureTarget =
|
||||
src.IsRenderbuffer() ? TextureTarget::Unknown : MG_Util::ConvertGLEnumToTextureTarget(srcTarget);
|
||||
const auto dstTextureTarget =
|
||||
dst.IsRenderbuffer() ? TextureTarget::Unknown : MG_Util::ConvertGLEnumToTextureTarget(dstTarget);
|
||||
if ((!src.IsRenderbuffer() && !TextureImpl::ValidateTextureTarget(srcTextureTarget)) ||
|
||||
(!dst.IsRenderbuffer() && !TextureImpl::ValidateTextureTarget(dstTextureTarget))) {
|
||||
return false;
|
||||
}
|
||||
// GL_TEXTURE_BUFFER and the cube FACE enums convert to a target this frontend knows, but
|
||||
@@ -3520,8 +3776,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (!ValidateCopyImageTarget(srcTarget, "source") || !ValidateCopyImageTarget(dstTarget, "destination")) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidateCopyImageTargetMatchesObject(srcTexture, srcTextureTarget, "source") ||
|
||||
!ValidateCopyImageTargetMatchesObject(dstTexture, dstTextureTarget, "destination")) {
|
||||
if (!ValidateCopyImageTargetMatchesObject(src.Texture, srcTextureTarget, "source") ||
|
||||
!ValidateCopyImageTargetMatchesObject(dst.Texture, dstTextureTarget, "destination")) {
|
||||
return false;
|
||||
}
|
||||
if (!TextureImpl::ValidateTextureLevelNumber(srcLevel) ||
|
||||
@@ -3535,8 +3791,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// driver as an out-of-range mip index - on Adreno that is a SIGSEGV inside
|
||||
// vkCmdCopyImage, which is what KHR-GL43.copy_image.non_existent_mipmap used to do to
|
||||
// the whole glcts process. The answer the spec asks for is GL_INVALID_VALUE.
|
||||
if (!TextureImpl::ValidateTextureLevelExists(srcTexture, srcLevel, __func__) ||
|
||||
!TextureImpl::ValidateTextureLevelExists(dstTexture, dstLevel, __func__)) {
|
||||
if (!ValidateCopyImageEndpointLevelExists(src, srcLevel, __func__) ||
|
||||
!ValidateCopyImageEndpointLevelExists(dst, dstLevel, __func__)) {
|
||||
return false;
|
||||
}
|
||||
if (srcWidth < 0 || srcHeight < 0 || srcDepth < 0) {
|
||||
@@ -3552,37 +3808,41 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// A multisample image can only be copied to one with the same sample count, and a
|
||||
// single-sample image reports zero - so this one comparison is also what rejects
|
||||
// copying between a multisample target and a non-multisample one.
|
||||
if (srcTexture->GetSamples() != dstTexture->GetSamples()) {
|
||||
const Int srcSamples = GetCopyImageEndpointSamples(src);
|
||||
const Int dstSamples = GetCopyImageEndpointSamples(dst);
|
||||
if (srcSamples != dstSamples) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", __func__,
|
||||
std::format("The two images have different sample counts ({} vs. {}).",
|
||||
srcTexture->GetSamples(), dstTexture->GetSamples())));
|
||||
srcSamples, dstSamples)));
|
||||
return false;
|
||||
}
|
||||
// 18.3.2: both images must be complete. An incomplete one has no defined texels to copy
|
||||
// and no defined storage to copy into.
|
||||
if (!srcTexture->IsComplete() || !dstTexture->IsComplete()) {
|
||||
const Bool srcComplete = IsCopyImageEndpointComplete(src);
|
||||
const Bool dstComplete = IsCopyImageEndpointComplete(dst);
|
||||
if (!srcComplete || !dstComplete) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", __func__,
|
||||
std::format("A copied image is incomplete (source complete: {}, destination complete: {}).",
|
||||
srcTexture->IsComplete(), dstTexture->IsComplete())));
|
||||
srcComplete, dstComplete)));
|
||||
return false;
|
||||
}
|
||||
const auto srcUploadTarget = GetPrimaryUploadTarget(srcTexture);
|
||||
const auto dstUploadTarget = GetPrimaryUploadTarget(dstTexture);
|
||||
const auto srcUploadTarget = GetPrimaryUploadTarget(src.Texture);
|
||||
const auto dstUploadTarget = GetPrimaryUploadTarget(dst.Texture);
|
||||
const auto srcBlock = TextureImpl::ResolveCopyImageTexelBlock(
|
||||
srcTexture->GetFormat(), GetCompressedLevelFormat(srcTexture, srcUploadTarget, srcLevel));
|
||||
GetCopyImageEndpointFormat(src), GetCopyImageEndpointCompressedFormat(src, srcUploadTarget, srcLevel));
|
||||
const auto dstBlock = TextureImpl::ResolveCopyImageTexelBlock(
|
||||
dstTexture->GetFormat(), GetCompressedLevelFormat(dstTexture, dstUploadTarget, dstLevel));
|
||||
GetCopyImageEndpointFormat(dst), GetCopyImageEndpointCompressedFormat(dst, dstUploadTarget, dstLevel));
|
||||
if (!TextureImpl::ValidateCopyImageFormatCompatibility(srcBlock, dstBlock)) {
|
||||
return false;
|
||||
}
|
||||
const IntVec3 srcLevelSize = GetCopyImageLevelSize(srcTexture, srcUploadTarget, srcLevel);
|
||||
const IntVec3 dstLevelSize = GetCopyImageLevelSize(dstTexture, dstUploadTarget, dstLevel);
|
||||
const IntVec3 srcLevelSize = GetCopyImageEndpointLevelSize(src, srcUploadTarget, srcLevel);
|
||||
const IntVec3 dstLevelSize = GetCopyImageEndpointLevelSize(dst, dstUploadTarget, dstLevel);
|
||||
if (!TextureImpl::ValidateCopyImageBlockAlignment(srcBlock, srcX, srcY, srcWidth, srcHeight,
|
||||
srcLevelSize.x(), srcLevelSize.y(), "source") ||
|
||||
!TextureImpl::ValidateCopyImageBlockAlignment(dstBlock, dstX, dstY, srcWidth, srcHeight,
|
||||
@@ -4098,8 +4358,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a cube map this is exactly cube completeness: IsComplete() wants all six faces.
|
||||
if (!textureObject->IsComplete()) {
|
||||
// GL 4.6 core 8.11.4 names cube completeness as the only completeness a readback requires,
|
||||
// and for a cube map that is exactly what IsComplete() answers (all six faces defined at
|
||||
// every level). It must not speak for any other target: on a mip chain it also rejects
|
||||
// "level N defined, the levels below it not", which is a perfectly readable texture at
|
||||
// level N - and the shape glClearTexImage's conformance cases build, since they define
|
||||
// only the level they clear. The requested level's own existence is checked below.
|
||||
if ((target == TextureTarget::TextureCubeMap || target == TextureTarget::TextureCubeMapArray) &&
|
||||
!textureObject->IsComplete()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture is incomplete"));
|
||||
@@ -4131,8 +4397,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
// Shared format/type/internal-format matrix (packed-type pairing, depth-vs-color mismatch,
|
||||
// integer-ness). Also rejects STENCIL_INDEX readback, which needs GL_ARB_texture_stencil8
|
||||
// (not advertised by MobileGL).
|
||||
// integer-ness). Also rejects a STENCIL_INDEX readback of anything but stencil-only
|
||||
// storage, which is the only pairing GL 4.4 / ARB_texture_stencil8 ever made legal.
|
||||
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(
|
||||
textureInputFormat, textureObject->GetFormat(), texturePixelDataType)) {
|
||||
return false;
|
||||
@@ -4158,33 +4424,48 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const auto* textureMipmapObject =
|
||||
static_cast<const MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
||||
const auto& uploadTargets = textureObject->GetUploadTargets();
|
||||
if (!uploadTargets.empty() && static_cast<Uint>(level) < textureMipmapObject->GetMipmapLevelCount()) {
|
||||
// Tightly packed, and summed over every face because a cube map query returns all
|
||||
// six. Pack pixel-store state only ever grows this, so a request rejected here
|
||||
// could not have fit under any packing.
|
||||
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(uploadTargets[0], level);
|
||||
const SizeT required = MG_Util::CalculateInputTextureImageSize(textureInputFormat,
|
||||
texturePixelDataType, texelSize) *
|
||||
uploadTargets.size();
|
||||
// The half of the completeness gate above that GL does keep: the REQUESTED level has
|
||||
// to hold an image. A name that was never given one carries no levels at all (which is
|
||||
// also what an Unknown internal format answers), and a chain grown to reach level N
|
||||
// leaves every level below it at {0, 0, 0}.
|
||||
if (uploadTargets.empty() || static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture level has no image to read back."));
|
||||
return false;
|
||||
}
|
||||
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(uploadTargets[0], level);
|
||||
if (texelSize.x() <= 0 || texelSize.y() <= 0 || texelSize.z() <= 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture level has no image to read back."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bufSize >= 0 && static_cast<SizeT>(bufSize) < required) {
|
||||
// Tightly packed, and summed over every face because a cube map query returns all
|
||||
// six. Pack pixel-store state only ever grows this, so a request rejected here
|
||||
// could not have fit under any packing.
|
||||
const SizeT required = MG_Util::CalculateInputTextureImageSize(textureInputFormat,
|
||||
texturePixelDataType, texelSize) *
|
||||
uploadTargets.size();
|
||||
|
||||
if (bufSize >= 0 && static_cast<SizeT>(bufSize) < required) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Destination buffer is too small."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pixelPackBufferObject) {
|
||||
const SizeT bufferSize = pixelPackBufferObject->GetSize();
|
||||
const SizeT offset = reinterpret_cast<SizeT>(pixels);
|
||||
if (offset > bufferSize || required > bufferSize - offset) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Destination buffer is too small."));
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||
"Packing would write past the end of the pixel pack buffer."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pixelPackBufferObject) {
|
||||
const SizeT bufferSize = pixelPackBufferObject->GetSize();
|
||||
const SizeT offset = reinterpret_cast<SizeT>(pixels);
|
||||
if (offset > bufferSize || required > bufferSize - offset) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||
"Packing would write past the end of the pixel pack buffer."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4419,6 +4700,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const SizeT byteSize = ComputeTextureStorageByteSize(textureInternalFormat, levelWidth, 1, 1);
|
||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, 1, 1}, byteSize});
|
||||
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
||||
if (IsCompressedGLInternalFormat(internalformat)) {
|
||||
// After AllocateStorage, which clears the tag. See TexImage1D_State: no compressed
|
||||
// format has a 1D block layout, but glClearTexImage still has to refuse the request.
|
||||
textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget,
|
||||
static_cast<Uint>(level), internalformat);
|
||||
}
|
||||
}
|
||||
// Immutable storage defines exactly `levels` levels; AllocateStorage only grows, so a
|
||||
// longer pre-existing chain has to be dropped explicitly.
|
||||
@@ -4487,6 +4774,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Util::CalculateCompressedTextureImageSize(compressedInfo,
|
||||
{levelWidth, levelHeight, 1}));
|
||||
}
|
||||
if (IsCompressedGLInternalFormat(internalformat)) {
|
||||
// Also after AllocateStorage. The generic enums land here and nowhere above,
|
||||
// and glClearTexImage has to refuse them too (GL 4.6 core 8.19).
|
||||
textureMipmapObject->SetMipmapRequestedCompressedFormat(uploadTarget,
|
||||
static_cast<Uint>(level), internalformat);
|
||||
}
|
||||
}
|
||||
// See TextureStorage1D.
|
||||
textureMipmapObject->TruncateMipmapLevels(uploadTarget, static_cast<Uint>(levels));
|
||||
@@ -4494,32 +4787,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
||||
}
|
||||
|
||||
// No block-compressed format is defined for a three-dimensional image, so glTexStorage3D on
|
||||
// TEXTURE_3D must reject one - and with INVALID_OPERATION, not the INVALID_ENUM an unknown
|
||||
// sized format gets (GL 4.6 core 8.19 / Khronos bug 11239, KHR-GLxx.texture_storage
|
||||
// .compressed_data). Written against the enum ranges rather than a name list because the
|
||||
// families are contiguous and MobileGL's own internal-format enum drops the ones it cannot
|
||||
// carry, which would make this check silently narrower than the API surface.
|
||||
static Bool IsCompressedGLInternalFormat(GLenum internalformat) {
|
||||
switch (internalformat) {
|
||||
case 0x8225: // GL_COMPRESSED_RED
|
||||
case 0x8226: // GL_COMPRESSED_RG
|
||||
case 0x84ED: // GL_COMPRESSED_RGB
|
||||
case 0x84EE: // GL_COMPRESSED_RGBA
|
||||
case 0x8C48: // GL_COMPRESSED_SRGB
|
||||
case 0x8C49: // GL_COMPRESSED_SRGB_ALPHA
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (internalformat >= 0x83F0 && internalformat <= 0x83F3) || // S3TC / DXT
|
||||
(internalformat >= 0x8DBB && internalformat <= 0x8DBE) || // RGTC
|
||||
(internalformat >= 0x8E8C && internalformat <= 0x8E8F) || // BPTC
|
||||
(internalformat >= 0x9270 && internalformat <= 0x9279) || // ETC2 / EAC
|
||||
(internalformat >= 0x93B0 && internalformat <= 0x93BD) || // ASTC LDR
|
||||
(internalformat >= 0x93D0 && internalformat <= 0x93DD); // ASTC sRGB
|
||||
}
|
||||
|
||||
void TextureStorage3D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height,
|
||||
GLsizei depth) {
|
||||
auto textureObject = GetTextureObjectByName(texture, __func__);
|
||||
@@ -4562,6 +4829,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// Array targets keep their layer count constant across levels; only true 3D
|
||||
// textures halve depth per level (GL 3.3 §3.9 glTexStorage3D).
|
||||
const Bool depthMips = DepthParticipatesInMipmapping(textureObject->GetTarget());
|
||||
// The same specific-compressed-format tag glTexStorage2D records, for the array targets a
|
||||
// compressed glTexStorage3D is legal on (GL_TEXTURE_3D was refused above). Zero width means
|
||||
// a generic format, which MobileGL answers with uncompressed storage, so it is not tagged.
|
||||
const auto compressedInfo = MG_Util::GetCompressedFormatInfo(internalformat);
|
||||
for (GLsizei level = 0; level < levels; ++level) {
|
||||
const GLsizei levelWidth = std::max<GLsizei>(1, width >> level);
|
||||
const GLsizei levelHeight = std::max<GLsizei>(1, height >> level);
|
||||
@@ -4571,6 +4842,19 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level,
|
||||
{{levelWidth, levelHeight, levelDepth}, byteSize});
|
||||
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
||||
if (compressedInfo.blockWidth != 0) {
|
||||
// After AllocateStorage, which clears the tag.
|
||||
textureMipmapObject->SetMipmapCompressedImage(
|
||||
textureUploadTarget, static_cast<Uint>(level), internalformat, nullptr,
|
||||
MG_Util::CalculateCompressedTextureImageSize(compressedInfo,
|
||||
{levelWidth, levelHeight, levelDepth}));
|
||||
}
|
||||
if (IsCompressedGLInternalFormat(internalformat)) {
|
||||
// Also after AllocateStorage. The generic enums land here and nowhere above,
|
||||
// and glClearTexImage has to refuse them too (GL 4.6 core 8.19).
|
||||
textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget,
|
||||
static_cast<Uint>(level), internalformat);
|
||||
}
|
||||
}
|
||||
// See TextureStorage1D.
|
||||
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
|
||||
@@ -5780,17 +6064,29 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
||||
// A missing name is INVALID_VALUE here, where GetTextureObjectByName's own diagnostic is
|
||||
// INVALID_OPERATION - so resolve through the plain lookup, which answers a null
|
||||
// INVALID_OPERATION - so resolve through the plain lookups, which answer a null
|
||||
// SharedPtr, and let the validator record the error this entry point owes.
|
||||
const SharedPtr<MG_State::GLState::ITextureObject> srcTexture =
|
||||
MG_State::pGLContext->GetTextureObject(srcName);
|
||||
const SharedPtr<MG_State::GLState::ITextureObject> dstTexture =
|
||||
MG_State::pGLContext->GetTextureObject(dstName);
|
||||
if (!ValidateCopyImageSubData_State(srcTexture, srcTarget, srcLevel, srcX, srcY, dstTexture, dstTarget,
|
||||
//
|
||||
// The TARGET picks the namespace: GL 4.6 core 18.3.2 accepts GL_RENDERBUFFER, and a
|
||||
// renderbuffer name has nothing to do with a texture name. Resolving both through
|
||||
// GetTextureObject made every renderbuffer endpoint INVALID_VALUE - or, when the number
|
||||
// happened to collide with a live texture, INVALID_ENUM from the target check.
|
||||
const auto resolveEndpoint = [](GLuint name, GLenum target) {
|
||||
MG_Backend::CopyImageEndpoint endpoint{};
|
||||
if (target == GL_RENDERBUFFER) {
|
||||
endpoint.Renderbuffer = MG_State::pGLContext->GetRenderbufferObject(name);
|
||||
} else {
|
||||
endpoint.Texture = MG_State::pGLContext->GetTextureObject(name);
|
||||
}
|
||||
return endpoint;
|
||||
};
|
||||
const MG_Backend::CopyImageEndpoint src = resolveEndpoint(srcName, srcTarget);
|
||||
const MG_Backend::CopyImageEndpoint dst = resolveEndpoint(dstName, dstTarget);
|
||||
if (!ValidateCopyImageSubData_State(src, srcTarget, srcLevel, srcX, srcY, dst, dstTarget,
|
||||
dstLevel, dstX, dstY, srcWidth, srcHeight, srcDepth)) {
|
||||
return;
|
||||
}
|
||||
CopyImageSubData_Backend(srcTexture, srcTarget, srcLevel, srcX, srcY, srcZ, dstTexture, dstTarget, dstLevel,
|
||||
CopyImageSubData_Backend(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, dstLevel,
|
||||
dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth);
|
||||
}
|
||||
|
||||
|
||||
@@ -313,9 +313,13 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TexImage in core 3.3 has no stencil-only upload path (that arrived with GL 4.4).
|
||||
if (format == TextureInputFormat::StencilIndex) {
|
||||
return recordInvalidOperation("STENCIL_INDEX is not a valid texture upload format");
|
||||
// The stencil-only transfer path arrived with GL 4.4 / ARB_texture_stencil8, and only ever
|
||||
// pairs with stencil-only storage: against a depth, depth-stencil or colour internal format
|
||||
// STENCIL_INDEX keeps the pre-4.4 answer (GL CTS packed_pixels feeds exactly that pairing
|
||||
// and expects INVALID_OPERATION).
|
||||
if (format == TextureInputFormat::StencilIndex &&
|
||||
internalFormat != TextureInternalFormat::StencilIndex8) {
|
||||
return recordInvalidOperation("STENCIL_INDEX requires a stencil-only internal format");
|
||||
}
|
||||
|
||||
if (IsDepthLikeInputFormat(format) != IsDepthLikeInternalFormat(internalFormat)) {
|
||||
|
||||
@@ -514,10 +514,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// recorded DataType is always Float64 - what IsLong adds is that this is the *unconverted* form,
|
||||
// as opposed to VertexAttribFormat(GL_DOUBLE), which asks for a float conversion.
|
||||
//
|
||||
// Whether the backend can feed it is detected, not assumed: DirectVulkan needs shaderFloat64,
|
||||
// and DirectGLES can never have it at all. A backend without it declines here, loudly - GL error
|
||||
// plus a log line naming the reason - rather than accepting state no draw could honour and
|
||||
// rendering garbage. The matching startup POST row is in MG_Util/SelfTest/DriverPost.cpp.
|
||||
// Whether the backend can FEED it is detected, not assumed: DirectVulkan needs shaderFloat64,
|
||||
// and DirectGLES can never have it at all. What that costs is the ARRAY, not the call: GL 4.6
|
||||
// core 10.3.2 defines no error for a well-formed glVertexAttribLFormat, and a GL 4.3 context
|
||||
// has 64-bit attributes in core, so declining the call would be non-conformant and would make
|
||||
// the four pure state queries (VERTEX_ATTRIB_ARRAY_SIZE / _TYPE / _LONG / _RELATIVE_OFFSET)
|
||||
// unanswerable (KHR-GL43.vertex_attrib_binding.basic-state1/3). The format is therefore
|
||||
// RECORDED here and the enabled array is dropped at draw instead - loudly, once, naming the
|
||||
// reason. The matching startup POST row is in MG_Util/SelfTest/DriverPost.cpp; the draw-side
|
||||
// drop is DirectGLES/Managers.cpp and, on DirectVulkan, VertexInputStateFactory's Float64 case.
|
||||
static void VertexAttribLFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
|
||||
GLuint attribindex, GLint size, GLenum type,
|
||||
GLuint relativeoffset) {
|
||||
@@ -528,14 +533,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (!MG_Backend::pActiveBackendObject ||
|
||||
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) {
|
||||
MGLOG_W_ONCE("VertexAttribLFormat: attribute %u asked for a 64-bit (GL_DOUBLE) format, but this "
|
||||
"backend has no double-precision vertex attribute support - see the "
|
||||
"\"64-bit vertex attributes\" / \"shaderFloat64\" POST row for what that costs",
|
||||
"backend has no double-precision vertex attribute support - the format is recorded "
|
||||
"and queryable, but the array will be DROPPED at draw and the attribute will read "
|
||||
"its generic current value; see the \"64-bit vertex attributes\" / \"shaderFloat64\" "
|
||||
"POST row for what that costs",
|
||||
attribindex);
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribLFormat",
|
||||
"64-bit vertex attributes are not supported by this backend."));
|
||||
return;
|
||||
}
|
||||
|
||||
vao->SetAttributeFormatSeparate(attribindex, size, MG_Util::ConvertGLEnumToDataType(type),
|
||||
|
||||
Reference in New Issue
Block a user