diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 38c211fb..134e247d 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -236,6 +236,13 @@ namespace MobileGL { // can still see the capture program and buffer bindings. void (*BeginTransformFeedback)(GLenum primitiveMode); void (*EndTransformFeedback)(); + // ARB_transform_feedback2. A backend that leaves these null keeps the single + // implicit capture span the frontend has always modelled; the frontend state + // (paused flag, per-object bindings) is tracked either way. + void (*PauseTransformFeedback)(); + void (*ResumeTransformFeedback)(); + void (*BindTransformFeedback)(GLuint name); + void (*DeleteTransformFeedback)(GLuint name); Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported }; struct GlobalBackendFunctionsTable { diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 98c0d163..4116fa66 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -1032,6 +1032,10 @@ namespace MobileGL::MG_Backend::DirectGLES { // span boundaries over. funcsTable.GL.BeginTransformFeedback = XfbImpl::BeginTransformFeedback; funcsTable.GL.EndTransformFeedback = XfbImpl::EndTransformFeedback; + funcsTable.GL.PauseTransformFeedback = XfbImpl::PauseTransformFeedback; + funcsTable.GL.ResumeTransformFeedback = XfbImpl::ResumeTransformFeedback; + funcsTable.GL.BindTransformFeedback = XfbImpl::BindTransformFeedback; + funcsTable.GL.DeleteTransformFeedback = XfbImpl::DeleteTransformFeedback; funcsTableInitialized = true; } return funcsTable; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index e5d1028c..614a87fe 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -407,6 +407,11 @@ namespace MobileGL::MG_Backend::DirectGLES { // the capture buffers bound when Begin is issued, and both of those only become true // once PrepareForDraw has run. A span that never draws therefore never touches the // driver at all, which is also what the GL semantics amount to. + // + // Transform feedback objects (ARB_transform_feedback2) are ES 3.0 core, so each + // frontend object gets one of the driver's: a paused span lives inside the ES object, + // which is the only way several of them can be paused at once - and the only reason + // the default object alone would not do. namespace XfbImpl { namespace { struct XfbCaptureTarget { @@ -416,10 +421,161 @@ namespace MobileGL::MG_Backend::DirectGLES { SizeT end = 0; }; - Bool g_xfbPending = false; // frontend Begin seen, driver capture not started yet - Bool g_xfbStarted = false; // driver capture running - GLenum g_xfbPrimitiveMode = GL_POINTS; - Vector g_xfbTargets; + // Per frontend transform feedback object. The default object (name 0) maps to + // the driver's default object (id 0) and is always present. + struct XfbObjectState { + GLuint esId = 0; + Bool pending = false; // frontend Begin seen, driver capture not started yet + Bool started = false; // driver capture running + Bool paused = false; // frontend Pause seen and not yet resumed + GLenum primitiveMode = GL_POINTS; + Vector targets; + // Set for a layout ES cannot express (gl_SkipComponents / gl_NextBuffer): + // the driver captures gap-free records into the scratch buffer below and + // End scatters them into `targets`. + Bool scattered = false; + SharedPtr scatterProgram; + SizeT scatterCapacityVertices = 0; + }; + + // One scratch ES buffer serves every scattered capture: only one span can be + // recording at a time (the driver would reject a second Begin), so its contents + // are consumed by the End that follows. + GLuint g_scatterBufferId = 0; + SizeT g_scatterBufferSize = 0; + + UnorderedMap g_xfbObjects; + GLuint g_currentXfbName = 0; + + XfbObjectState& CurrentXfb() { + return g_xfbObjects[g_currentXfbName]; + } + + Bool AreTransformFeedbackObjectsSupported() { + return g_GLESFuncs.glGenTransformFeedbacks != nullptr && + g_GLESFuncs.glBindTransformFeedback != nullptr && + g_GLESFuncs.glDeleteTransformFeedbacks != nullptr && + g_GLESFuncs.glPauseTransformFeedback != nullptr && + g_GLESFuncs.glResumeTransformFeedback != nullptr; + } + + // Mirrors one capture span's results into the frontend CPU shadows. The GPU wrote + // the capture buffers behind the frontend's back, so the shadows that back + // MapBuffer/GetBufferSubData still hold the pre-draw bytes. Buffers whose storage + // the backend already owns (coherent persistent map) need nothing: reads resolve + // against that storage directly. + void ReadbackCapturedRanges(Vector& targets) { + if (g_GLESFuncs.glMapBufferRange != nullptr && g_GLESFuncs.glUnmapBuffer != nullptr) { + for (const auto& target : targets) { + if (!target.buffer || target.buffer->IsBackendPersistentMapped()) continue; + const SizeT size = target.end - target.start; + BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId); + void* mapped = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget, + static_cast(target.start), + static_cast(size), GL_MAP_READ_BIT); + if (mapped == nullptr) { + MGLOG_E("EndTransformFeedback: failed to map backend buffer %u for capture readback", + target.backendId); + continue; + } + target.buffer->WritebackFromBackend({mapped, size}, target.start); + g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget); + } + } + targets.clear(); + } + + // Binds a scratch buffer, sized for `capacityVertices` gap-free records, to + // capture point 0 in place of the application's buffers. Returns false when the + // scratch storage cannot be provided, in which case the caller falls back to the + // direct binding (which produces a wrong layout, but is what happened before). + Bool BindScatterCaptureBuffer(SizeT packedStride, SizeT capacityVertices) { + if (packedStride == 0 || capacityVertices == 0) return false; + if (g_GLESFuncs.glGenBuffers == nullptr || g_GLESFuncs.glBufferData == nullptr) return false; + const SizeT required = packedStride * capacityVertices; + if (g_scatterBufferId == 0) { + g_GLESFuncs.glGenBuffers(1, &g_scatterBufferId); + if (g_scatterBufferId == 0) return false; + g_scatterBufferSize = 0; + } + if (g_scatterBufferSize < required) { + BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_scatterBufferId); + g_GLESFuncs.glBufferData(BufferImpl::TempBufferTarget, static_cast(required), nullptr, + GL_DYNAMIC_COPY); + g_scatterBufferSize = required; + } + // Point 0 carries every captured varying (the ES capture is INTERLEAVED); the + // other points must be cleared or the driver would still write the app's buffers. + BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, 0, g_scatterBufferId, 0, + static_cast(required)); + const SizeT pointCount = + MG_State::pGLContext->GetTouchedBufferBindingPointCount(BufferTarget::TransformFeedback); + for (SizeT i = 1; i < pointCount; ++i) { + BufferImpl::BindBufferBaseCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(i), 0); + } + return true; + } + + // Distributes the gap-free records the driver captured into the application's + // buffers at the offsets the GL layout asks for. Only the bytes a varying actually + // occupies are written, so the holes gl_SkipComponents asks for keep whatever the + // application had put there - which is the whole point of the feature. + void ScatterCapturedRecords(XfbObjectState& xfb) { + const auto& program = xfb.scatterProgram; + if (!program || xfb.targets.empty()) return; + if (g_GLESFuncs.glMapBufferRange == nullptr || g_GLESFuncs.glUnmapBuffer == nullptr) return; + + const SizeT packedStride = program->GetTransformFeedbackPackedStride(); + const SizeT vertices = std::min( + static_cast(MG_State::pGLContext->GetTransformFeedbackCapturedVertices()), + xfb.scatterCapacityVertices); + if (packedStride == 0 || vertices == 0) return; + + BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_scatterBufferId); + const void* packed = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget, 0, + static_cast(packedStride * vertices), + GL_MAP_READ_BIT); + if (packed == nullptr) { + MGLOG_E("EndTransformFeedback: failed to map the scatter capture buffer"); + return; + } + + // One staged copy per destination buffer: start from what the application had + // (the shadow is authoritative - uploads go shadow -> ES, and previous captures + // were mirrored back into it), patch the captured varyings in, then push the + // whole range down once. + for (SizeT targetIndex = 0; targetIndex < xfb.targets.size(); ++targetIndex) { + const auto& target = xfb.targets[targetIndex]; + if (!target.buffer) continue; + const SizeT stride = program->GetTransformFeedbackStride(static_cast(targetIndex)); + if (stride == 0) continue; + const SizeT rangeBytes = target.end - target.start; + Vector staged(rangeBytes); + Memcpy(staged.data(), target.buffer->MappedData() + target.start, rangeBytes); + + for (const auto& varying : program->GetTransformFeedbackVaryings()) { + if (varying.bufferIndex != targetIndex) continue; + for (SizeT v = 0; v < vertices; ++v) { + const SizeT dstOffset = v * stride + varying.offsetBytes; + if (dstOffset + varying.byteSize > rangeBytes) break; + Memcpy(staged.data() + dstOffset, + static_cast(packed) + v * packedStride + varying.packedOffsetBytes, + varying.byteSize); + } + } + + target.buffer->WritebackFromBackend({staged.data(), rangeBytes}, target.start); + if (g_GLESFuncs.glBufferSubData != nullptr) { + BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId); + g_GLESFuncs.glBufferSubData(BufferImpl::TempBufferTarget, + static_cast(target.start), + static_cast(rangeBytes), staged.data()); + } + BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_scatterBufferId); + } + g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget); + xfb.targets.clear(); + } } // namespace Bool AreTransformFeedbacksSupported() { @@ -430,17 +586,23 @@ namespace MobileGL::MG_Backend::DirectGLES { void BeginTransformFeedback(GLenum primitiveMode) { if (!AreTransformFeedbacksSupported()) return; - g_xfbPrimitiveMode = primitiveMode; - g_xfbPending = true; - g_xfbStarted = false; - g_xfbTargets.clear(); + auto& xfb = CurrentXfb(); + xfb.primitiveMode = primitiveMode; + xfb.pending = true; + xfb.started = false; + xfb.paused = false; + xfb.targets.clear(); } // Tail of PrepareForDraw: the program is bound and every buffer the draw needs // is up to date, so the capture buffers can be bound and the span opened. void StartPendingTransformFeedback() { - if (!g_xfbPending) return; - g_xfbPending = false; + auto& xfb = CurrentXfb(); + // A span that was paused before its first draw must not open here: the draw is + // not captured, and opening the span would also subject it to the capture + // primitive-mode rule the paused draw is exempt from. + if (!xfb.pending || xfb.paused) return; + xfb.pending = false; const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram(); if (!program) return; @@ -459,51 +621,103 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT start = std::min(range.start, bufferObject->GetSize()); const SizeT end = std::min(range.end, bufferObject->GetSize()); if (end <= start) continue; - g_xfbTargets.push_back({bufferObject, backendResource->id, start, end}); + xfb.targets.push_back({bufferObject, backendResource->id, start, end}); } BufferImpl::SyncBufferBindingPoints(BufferTarget::TransformFeedback, GL_TRANSFORM_FEEDBACK_BUFFER); - g_GLESFuncs.glBeginTransformFeedback(g_xfbPrimitiveMode); - g_xfbStarted = true; + + // A layout with holes or several interleaved buffers is not expressible on ES: + // capture gap-free into scratch storage and place the records at End instead. + xfb.scattered = false; + xfb.scatterProgram.reset(); + xfb.scatterCapacityVertices = 0; + if (program->NeedsScatteredTransformFeedbackCapture()) { + SizeT capacityVertices = ~SizeT(0); + for (SizeT i = 0; i < xfb.targets.size(); ++i) { + const SizeT stride = program->GetTransformFeedbackStride(static_cast(i)); + if (stride == 0) continue; + capacityVertices = + std::min(capacityVertices, (xfb.targets[i].end - xfb.targets[i].start) / stride); + } + if (capacityVertices == ~SizeT(0)) capacityVertices = 0; + if (BindScatterCaptureBuffer(program->GetTransformFeedbackPackedStride(), capacityVertices)) { + xfb.scattered = true; + xfb.scatterProgram = program; + xfb.scatterCapacityVertices = capacityVertices; + } + } + + g_GLESFuncs.glBeginTransformFeedback(xfb.primitiveMode); + xfb.started = true; } void EndTransformFeedback() { - g_xfbPending = false; - if (!g_xfbStarted) return; - g_xfbStarted = false; + auto& xfb = CurrentXfb(); + xfb.pending = false; + xfb.paused = false; + if (!xfb.started) return; + xfb.started = false; g_GLESFuncs.glEndTransformFeedback(); - - // The GPU wrote the capture buffers behind the frontend's back, so the CPU - // shadows that back MapBuffer/GetBufferSubData still hold the pre-draw bytes. - // Mirror the captured ranges into them. Buffers whose storage the backend - // already owns (coherent persistent map) need nothing: reads resolve against - // that storage directly. - if (g_GLESFuncs.glMapBufferRange != nullptr && g_GLESFuncs.glUnmapBuffer != nullptr) { - for (const auto& target : g_xfbTargets) { - if (!target.buffer || target.buffer->IsBackendPersistentMapped()) continue; - const SizeT size = target.end - target.start; - BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId); - void* mapped = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget, - static_cast(target.start), - static_cast(size), GL_MAP_READ_BIT); - if (mapped == nullptr) { - MGLOG_E("EndTransformFeedback: failed to map backend buffer %u for capture readback", - target.backendId); - continue; - } - target.buffer->WritebackFromBackend({mapped, size}, target.start); - g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget); - } + if (xfb.scattered) { + ScatterCapturedRecords(xfb); + xfb.scattered = false; + xfb.scatterProgram.reset(); + } else { + ReadbackCapturedRanges(xfb.targets); } - g_xfbTargets.clear(); } - // The ES context went away (or is being torn down): the span, its buffer ids and - // the frontend objects it pinned all belonged to it. + void PauseTransformFeedback() { + auto& xfb = CurrentXfb(); + xfb.paused = true; + // A span the driver never opened (paused before the first draw) has nothing to + // pause; the flag above is what holds the deferred Begin back until the resume. + if (!xfb.started || g_GLESFuncs.glPauseTransformFeedback == nullptr) return; + g_GLESFuncs.glPauseTransformFeedback(); + } + + void ResumeTransformFeedback() { + auto& xfb = CurrentXfb(); + xfb.paused = false; + if (!xfb.started || g_GLESFuncs.glResumeTransformFeedback == nullptr) return; + g_GLESFuncs.glResumeTransformFeedback(); + } + + void BindTransformFeedback(GLuint name) { + if (!AreTransformFeedbackObjectsSupported()) { + // Without driver objects there is only the default span; keep the frontend + // name so the bookkeeping below stays consistent. + g_currentXfbName = name; + return; + } + auto& xfb = g_xfbObjects[name]; + if (name != 0 && xfb.esId == 0) { + g_GLESFuncs.glGenTransformFeedbacks(1, &xfb.esId); + } + g_GLESFuncs.glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb.esId); + g_currentXfbName = name; + } + + void DeleteTransformFeedback(GLuint name) { + const auto it = g_xfbObjects.find(name); + if (it == g_xfbObjects.end()) return; + if (it->second.esId != 0 && g_GLESFuncs.glDeleteTransformFeedbacks != nullptr) { + g_GLESFuncs.glDeleteTransformFeedbacks(1, &it->second.esId); + } + g_xfbObjects.erase(it); + // The frontend reverts to the default object when the bound one is deleted. + if (g_currentXfbName == name) { + BindTransformFeedback(0); + } + } + + // The ES context went away (or is being torn down): the spans, their buffer ids, the + // driver objects and the frontend objects they pinned all belonged to it. void OnBackendContextDestroyed() { - g_xfbPending = false; - g_xfbStarted = false; - g_xfbTargets.clear(); + g_xfbObjects.clear(); + g_currentXfbName = 0; + g_scatterBufferId = 0; + g_scatterBufferSize = 0; } } // namespace XfbImpl diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 99a011cc..fa2eaf14 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -173,6 +173,10 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool AreTransformFeedbacksSupported(); void BeginTransformFeedback(GLenum primitiveMode); void EndTransformFeedback(); + void PauseTransformFeedback(); + void ResumeTransformFeedback(); + void BindTransformFeedback(GLuint name); + void DeleteTransformFeedback(GLuint name); void OnBackendContextDestroyed(); } // namespace XfbImpl diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 2f91ea24..50686af3 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -72,6 +72,9 @@ namespace MobileGL::MG_Impl::GLImpl { // Geometry amplification is not modelled here. static void AccountTransformFeedbackPrimitives(GLenum mode, GLsizei count) { if (!MG_State::pGLContext->IsTransformFeedbackActive()) return; + // A paused span captures nothing, so a draw made while paused contributes to + // PRIMITIVES_GENERATED but not to TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN. + if (MG_State::pGLContext->IsTransformFeedbackPaused()) return; Uint64 primitives = CountPrimitivesForDraw(mode, count); if (primitives == 0) return; MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives); @@ -202,8 +205,11 @@ namespace MobileGL::MG_Impl::GLImpl { // While transform feedback is active the draw's primitive type must match // the feedback primitive mode (GL 3.3 core 13.2.2). With a geometry shader // the constraint moves to the shader's output primitive type instead, so - // the draw mode itself is unconstrained here. + // the draw mode itself is unconstrained here. A paused span is exempt: it + // captures nothing, so there is nothing for the mode to be incompatible with + // (GL 4.6 core 13.2.3). if (MG_State::pGLContext->IsTransformFeedbackActive() && + !MG_State::pGLContext->IsTransformFeedbackPaused() && !(MG_State::pGLContext->GetTransformFeedbackProgram() && MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) { const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode(); @@ -684,9 +690,12 @@ namespace MobileGL::MG_Impl::GLImpl { "No program with transform feedback varyings is active.")); return; } - // Every capture buffer slot the program's mode uses must have a buffer bound. + // Every capture buffer slot the program's mode uses must have a buffer bound. A slot + // of stride 0 - two consecutive gl_NextBuffer entries - captures nothing and so needs + // no binding. const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount(); for (SizeT i = 0; i < usedBufferCount; ++i) { + if (program->GetTransformFeedbackStride(static_cast(i)) == 0) continue; const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(i)); if (point.GetBoundObject() == nullptr) { @@ -800,4 +809,174 @@ namespace MobileGL::MG_Impl::GLImpl { FixupGsStripCaptureOrder(capturedProgram, inputPrimitives); } + void PauseTransformFeedback(void) { + if (!MG_State::pGLContext->IsTransformFeedbackActive() || + MG_State::pGLContext->IsTransformFeedbackPaused()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "Transform feedback is not active, or is already paused.")); + return; + } + MG_State::pGLContext->SetTransformFeedbackPaused(true); + if (const auto pauseXfb = MG_Backend::gBackendFunctionsTable.GL.PauseTransformFeedback) { + pauseXfb(); + } + } + + void ResumeTransformFeedback(void) { + if (!MG_State::pGLContext->IsTransformFeedbackActive() || + !MG_State::pGLContext->IsTransformFeedbackPaused()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is not paused.")); + return; + } + MG_State::pGLContext->SetTransformFeedbackPaused(false); + if (const auto resumeXfb = MG_Backend::gBackendFunctionsTable.GL.ResumeTransformFeedback) { + resumeXfb(); + } + } + + void GenTransformFeedbacks(GLsizei n, GLuint* ids) { + if (n < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "n must be non-negative.")); + return; + } + if (n == 0 || ids == nullptr) return; + Vector names; + MG_State::pGLContext->GenTransformFeedbackNames(static_cast(n), names); + Memcpy(ids, names.data(), static_cast(n) * sizeof(GLuint)); + } + + void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) { + if (n < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "n must be non-negative.")); + return; + } + if (ids == nullptr) return; + for (GLsizei i = 0; i < n; ++i) { + const GLuint id = ids[i]; + // Unknown names and 0 are silently ignored; an object whose capture span is + // still open is not (GL 4.6 core 13.2.1). + if (id == 0 || !MG_State::pGLContext->ValidateTransformFeedbackName(id)) continue; + if (id == MG_State::pGLContext->GetBoundTransformFeedbackName() && + MG_State::pGLContext->IsTransformFeedbackActive()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "Cannot delete a transform feedback object whose capture is active.")); + continue; + } + if (const auto deleteXfb = MG_Backend::gBackendFunctionsTable.GL.DeleteTransformFeedback) { + deleteXfb(id); + } + MG_State::pGLContext->MarkTransformFeedbackObjectForDeletion(id); + } + } + + void BindTransformFeedback(GLenum target, GLuint id) { + if (target != GL_TRANSFORM_FEEDBACK) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, "target must be GL_TRANSFORM_FEEDBACK.")); + return; + } + // A running capture pins its object; only a paused one may be swapped out. + if (MG_State::pGLContext->IsTransformFeedbackActive() && + !MG_State::pGLContext->IsTransformFeedbackPaused()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "Transform feedback is active and not paused.")); + return; + } + if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + std::to_string(id) + " is not a transform feedback object name.")); + return; + } + MG_State::pGLContext->BindTransformFeedbackObject(id); + if (const auto bindXfb = MG_Backend::gBackendFunctionsTable.GL.BindTransformFeedback) { + bindXfb(id); + } + } + + GLboolean IsTransformFeedback(GLuint id) { + // Name 0 is the default object, which glIsTransformFeedback reports as not an object. + return (id != 0 && MG_State::pGLContext->ValidateTransformFeedbackName(id)) ? GL_TRUE : GL_FALSE; + } + + // glDrawTransformFeedback[Stream][Instanced]: replays the vertices the named object + // captured in its last completed span, as if by glDrawArraysInstanced with that count + // (GL 4.6 core 10.3.7). + static void DrawTransformFeedbackImpl(const char* functionName, GLenum mode, GLuint id, GLuint stream, + GLsizei instancecount) { + if (!ValidateCurrentProgramForExecution(functionName)) return; + if (!ValidatePrimitiveModeForBackend(functionName, mode)) return; + if (instancecount < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", functionName, "instancecount must be non-negative.")); + return; + } + if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", functionName, + std::to_string(id) + " is not a transform feedback object name.")); + return; + } + // GL_MAX_VERTEX_STREAMS is 1, so stream 0 is the only one that exists. + if (stream != 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", functionName, + "stream must be less than GL_MAX_VERTEX_STREAMS.")); + return; + } + // Drawing from an object whose capture is currently open is legal and deliberate: + // it is how a transform feedback result is fed straight back into the next span + // (ARB_transform_feedback2 lists no such restriction). + if (!MG_State::pGLContext->HasTransformFeedbackCompletedSpan(id)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", functionName, + "glEndTransformFeedback has never been called for this object.")); + return; + } + + const Uint64 vertices = MG_State::pGLContext->GetTransformFeedbackRecordedVertices(id); + if (vertices == 0) return; + const auto count = static_cast(vertices); + AccountTransformFeedbackPrimitives(mode, count); + if (instancecount == 1) { + DrawArrays_Backend(mode, 0, count); + } else { + DrawArraysInstanced_Backend(mode, 0, count, instancecount); + } + } + + void DrawTransformFeedback(GLenum mode, GLuint id) { + DrawTransformFeedbackImpl(__func__, mode, id, 0, 1); + } + + void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount) { + DrawTransformFeedbackImpl(__func__, mode, id, 0, instancecount); + } + + void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream) { + DrawTransformFeedbackImpl(__func__, mode, id, stream, 1); + } + + void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) { + DrawTransformFeedbackImpl(__func__, mode, id, stream, instancecount); + } + } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index cc6c8e2e..688d27bd 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -13,6 +13,16 @@ namespace MobileGL::MG_Impl::GLImpl { /* @INSERTION_POINT:FUNCTION_DECLARATION@ */ void BeginTransformFeedback(GLenum primitiveMode); void EndTransformFeedback(void); + void PauseTransformFeedback(void); + void ResumeTransformFeedback(void); + void GenTransformFeedbacks(GLsizei n, GLuint* ids); + void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids); + void BindTransformFeedback(GLenum target, GLuint id); + GLboolean IsTransformFeedback(GLuint id); + void DrawTransformFeedback(GLenum mode, GLuint id); + void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount); + void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream); + void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); void DispatchComputeIndirect(GLintptr indirect); void MemoryBarrier(GLbitfield barriers); diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 949d5711..86eb9fcc 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -292,18 +292,12 @@ DECLARE_GL_FUNCTION_HEAD(void, SamplerParameterfv, GLuint sampler, GLenum pname, DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameteriv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameteriv, sampler, pname, params) DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterfv, GLuint sampler, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterfv, sampler, pname, params) DECLARE_GL_FUNCTION_HEAD(void, VertexAttribDivisor, GLuint index, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribDivisor, index, divisor) -DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedback, target, id) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacks, n, ids) -// Transform feedback objects are not implemented, so no name is ever a live object. The shared -// stub returns (type)1, telling a probing caller that every id it invents already exists; GL_FALSE -// is both truthful and what the spec requires for a name that was never generated. -MOBILEGL_GL_API GLboolean glIsTransformFeedback(GLuint id) { - MGLOG_W("Stub function: %s(...)", __FUNCTION__); - return GL_FALSE; -} -DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedback) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedback) +DECLARE_GL_FUNCTION_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindTransformFeedback, target, id) +DECLARE_GL_FUNCTION_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids) +DECLARE_GL_FUNCTION_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenTransformFeedbacks, n, ids) +DECLARE_GL_FUNCTION_HEAD(GLboolean, IsTransformFeedback, GLuint id) DECLARE_GL_FUNCTION_END(GLboolean, IsTransformFeedback, id) +DECLARE_GL_FUNCTION_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PauseTransformFeedback) +DECLARE_GL_FUNCTION_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary) DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramBinary, GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramBinary, program, binaryFormat, binary, length) DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramParameteri, GLuint program, GLenum pname, GLint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramParameteri, program, pname, value) @@ -942,11 +936,11 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformSubroutinesuiv, GLenum shadertype, GL DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformSubroutineuiv, GLenum shadertype, GLint location, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformSubroutineuiv, shadertype, location, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramStageiv, GLuint program, GLenum shadertype, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramStageiv, program, shadertype, pname, values) DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameterfv, GLenum pname, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameterfv, pname, values) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedback, mode, id) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStream, mode, id, stream) -DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginQueryIndexed, target, index, id) -DECLARE_GL_FUNCTION_STUB_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndQueryIndexed, target, index) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryIndexediv, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryIndexediv, target, index, pname, params) +DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedback, mode, id) +DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStream, mode, id, stream) +DECLARE_GL_FUNCTION_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginQueryIndexed, target, index, id) +DECLARE_GL_FUNCTION_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndQueryIndexed, target, index) +DECLARE_GL_FUNCTION_HEAD(void, GetQueryIndexediv, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryIndexediv, target, index, pname, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform1d, GLuint program, GLint location, GLdouble v0) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform1d, program, location, v0) DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform1dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform1dv, program, location, count, value) DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform2d, GLuint program, GLint location, GLdouble v0, GLdouble v1) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform2d, program, location, v0, v1) @@ -988,8 +982,8 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawArraysInstancedBaseInstance, GLenum mode, GLi 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_STUB_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount) +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_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params) diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 3458df5f..c70b3c07 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -1952,7 +1952,10 @@ namespace MobileGL::MG_Impl::GLImpl { *params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0; break; case GL_TRANSFORM_FEEDBACK_PAUSED: - *params = 0; + *params = MG_State::pGLContext->IsTransformFeedbackPaused() ? 1 : 0; + break; + case GL_TRANSFORM_FEEDBACK_BINDING: + *params = static_cast(MG_State::pGLContext->GetBoundTransformFeedbackName()); break; case GL_MAX_TEXTURE_IMAGE_UNITS: *params = dynamicParameters.MaxTextureImageUnits; diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index d2dc7a92..98bc303e 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -920,9 +920,11 @@ namespace MobileGL::MG_Impl::GLImpl { void UseProgram_State(GLuint program) { MGLOG_D("UseProgram_State: program=%u", program); - // GL 3.3 core 2.11.3: the program in use may not change while transform - // feedback is active (there is no pause in 3.3). - if (MG_State::pGLContext->IsTransformFeedbackActive()) { + // The program in use may not change while transform feedback is active - unless + // the capture is paused, which is exactly what ARB_transform_feedback2 added the + // pause for (GL 4.6 core 7.3). + if (MG_State::pGLContext->IsTransformFeedbackActive() && + !MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, @@ -2284,6 +2286,31 @@ namespace MobileGL::MG_Impl::GLImpl { for (GLsizei i = 0; i < count; ++i) { names.emplace_back(varyings != nullptr && varyings[i] != nullptr ? varyings[i] : ""); } + // ARB_transform_feedback3's special names only mean anything in an interleaved + // capture, and gl_NextBuffer cannot advance past the last capture buffer. + constexpr Uint maxTransformFeedbackBuffers = 4; + Uint nextBufferCount = 0; + for (const String& name : names) { + const Bool isNextBuffer = name == "gl_NextBuffer"; + const Bool isSkipComponents = name.size() == 18 && name.compare(0, 17, "gl_SkipComponents") == 0 && + name[17] >= '1' && name[17] <= '4'; + if (!isNextBuffer && !isSkipComponents) continue; + if (bufferMode != GL_INTERLEAVED_ATTRIBS) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "'" + name + "' requires GL_INTERLEAVED_ATTRIBS.")); + return; + } + if (isNextBuffer && ++nextBufferCount >= maxTransformFeedbackBuffers) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "More gl_NextBuffer entries than " + "GL_MAX_TRANSFORM_FEEDBACK_BUFFERS allows.")); + return; + } + } programObject->SetTransformFeedbackVaryings(Move(names), bufferMode); } diff --git a/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp b/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp index d123d187..1676722f 100644 --- a/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp +++ b/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp @@ -7,6 +7,7 @@ // End of Source File Header #include "GL_Query.h" +#include "../Getter/GL_Getter.h" #include #include #include @@ -466,4 +467,42 @@ namespace MobileGL::MG_Impl::GLImpl { } *params = static_cast(value); } + + namespace { + // The indexed query entry points differ from the plain ones only in the vertex + // stream they address (GL 4.6 core 4.2.1): index must be below GL_MAX_VERTEX_STREAMS + // for the two transform feedback targets and zero for every other target. With a + // single vertex stream both bounds are 1, so a valid call is always index 0 and + // forwards to the unindexed implementation. + Bool ValidateQueryStreamIndex(const char* function, GLenum target, GLuint index) { + const Bool perStreamTarget = + target == GL_PRIMITIVES_GENERATED || target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN; + GLint maxVertexStreams = 1; + if (perStreamTarget) { + GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams); + } + if (index < static_cast(std::max(maxVertexStreams, 1))) { + return true; + } + RecordQueryError(ErrorCode::InvalidValue, function, + perStreamTarget ? "index is not less than GL_MAX_VERTEX_STREAMS." + : "index must be zero for this query target."); + return false; + } + } // namespace + + void BeginQueryIndexed(GLenum target, GLuint index, GLuint id) { + if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return; + BeginQuery(target, id); + } + + void EndQueryIndexed(GLenum target, GLuint index) { + if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return; + EndQuery(target); + } + + void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params) { + if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return; + GetQueryiv(target, pname, params); + } } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Query/GL_Query.h b/MobileGL/MG_Impl/GLImpl/Query/GL_Query.h index 604f196f..841e6443 100644 --- a/MobileGL/MG_Impl/GLImpl/Query/GL_Query.h +++ b/MobileGL/MG_Impl/GLImpl/Query/GL_Query.h @@ -16,6 +16,9 @@ namespace MobileGL::MG_Impl::GLImpl { void BeginQuery(GLenum target, GLuint id); void EndQuery(GLenum target); void GetQueryiv(GLenum target, GLenum pname, GLint* params); + void BeginQueryIndexed(GLenum target, GLuint index, GLuint id); + void EndQueryIndexed(GLenum target, GLuint index); + void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params); void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params); void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params); void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params); diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index b8602c31..edb7eb74 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -745,6 +745,83 @@ namespace MobileGL::MG_State { Bool GLContext::ValidateRenderbufferObject(Uint index) const { return m_renderbufferState.ValidateRenderbufferObject(index); } + + void GLContext::SaveBoundTransformFeedbackState() { + auto& object = m_transformFeedbackObjects[m_boundTransformFeedback]; + for (Uint i = 0; i < MAX_TRANSFORM_FEEDBACK_BUFFERS; ++i) { + const auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, i); + object.bindings[i] = {point.GetBoundObject(), point.GetRange(), point.HasExplicitRange()}; + } + object.active = m_transformFeedbackActive; + object.paused = m_transformFeedbackPaused; + object.primitiveMode = m_transformFeedbackPrimitiveMode; + object.program = m_transformFeedbackProgram; + object.capturedVertices = m_transformFeedbackCapturedVertices; + object.inputPrimitives = m_transformFeedbackInputPrimitives; + } + + void GLContext::RestoreBoundTransformFeedbackState() { + const auto& object = m_transformFeedbackObjects[m_boundTransformFeedback]; + for (Uint i = 0; i < MAX_TRANSFORM_FEEDBACK_BUFFERS; ++i) { + auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, i); + point.Bind(object.bindings[i].buffer); + if (object.bindings[i].buffer) { + point.SetRange(object.bindings[i].range, object.bindings[i].hasExplicitRange); + } else { + point.ClearRange(); + } + } + m_transformFeedbackActive = object.active; + m_transformFeedbackPaused = object.paused; + m_transformFeedbackPrimitiveMode = object.primitiveMode; + m_transformFeedbackProgram = object.program; + m_transformFeedbackCapturedVertices = object.capturedVertices; + m_transformFeedbackInputPrimitives = object.inputPrimitives; + } + + void GLContext::GenTransformFeedbackNames(Uint number, Vector& ids) { + ids.resize(number); + if (number == 0) return; + m_transformFeedbackNames.Generate(number, ids.data()); + // A generated name already denotes an object with the default state, so that a + // bind never has to distinguish "first use" from any later one. + for (const Uint id : ids) { + m_transformFeedbackObjects[id] = {}; + } + } + + Bool GLContext::ValidateTransformFeedbackName(Uint index) const { + return index == 0 || m_transformFeedbackNames.IsValid(index); + } + + void GLContext::BindTransformFeedbackObject(Uint index) { + if (index == m_boundTransformFeedback) return; + SaveBoundTransformFeedbackState(); + m_boundTransformFeedback = index; + RestoreBoundTransformFeedbackState(); + } + + void GLContext::MarkTransformFeedbackObjectForDeletion(Uint index) { + if (index == 0 || !m_transformFeedbackNames.IsValid(index)) return; + // Deleting the bound object reverts to the default one (GL 4.6 core 13.2.1); + // its state is dropped rather than saved back into the dying object. + if (index == m_boundTransformFeedback) { + m_boundTransformFeedback = 0; + RestoreBoundTransformFeedbackState(); + } + m_transformFeedbackObjects.erase(index); + m_transformFeedbackNames.Delete(index); + } + + Uint64 GLContext::GetTransformFeedbackRecordedVertices(Uint index) const { + const auto it = m_transformFeedbackObjects.find(index); + return it == m_transformFeedbackObjects.end() ? 0 : it->second.recordedVertices; + } + + Bool GLContext::HasTransformFeedbackCompletedSpan(Uint index) const { + const auto it = m_transformFeedbackObjects.find(index); + return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan; + } } // namespace GLState // Leak-at-exit storage; see GlobalObjects.cpp. diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index b4df3edb..376aa799 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -211,9 +211,12 @@ namespace MobileGL { void SetScissorBox(IntVec4 box); // x, y, width, height const IntVec4& GetScissorBox() const; // x, y, width, height - // Transform feedback (GL 3.0 core Begin/End; no feedback objects yet) + // Transform feedback. The fields below are the state of the transform + // feedback object currently bound to GL_TRANSFORM_FEEDBACK; see the object + // block further down for how a bind swaps them. void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr& program) { m_transformFeedbackActive = true; + m_transformFeedbackPaused = false; m_transformFeedbackPrimitiveMode = primitiveMode; m_transformFeedbackProgram = program; ++m_transformFeedbackGeneration; @@ -222,9 +225,16 @@ namespace MobileGL { } void EndTransformFeedback() { m_transformFeedbackActive = false; + m_transformFeedbackPaused = false; m_transformFeedbackProgram.reset(); + // What glDrawTransformFeedback on this object replays from now on. + auto& object = m_transformFeedbackObjects[m_boundTransformFeedback]; + object.recordedVertices = m_transformFeedbackCapturedVertices; + object.hasCompletedSpan = true; } Bool IsTransformFeedbackActive() const { return m_transformFeedbackActive; } + Bool IsTransformFeedbackPaused() const { return m_transformFeedbackPaused; } + void SetTransformFeedbackPaused(Bool paused) { m_transformFeedbackPaused = paused; } GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; } const SharedPtr& GetTransformFeedbackProgram() const { return m_transformFeedbackProgram; @@ -252,6 +262,29 @@ namespace MobileGL { } Uint64 GetTransformFeedbackInputPrimitives() const { return m_transformFeedbackInputPrimitives; } + // Transform feedback objects (ARB_transform_feedback2 / GL 4.0 core). + // The capture state above and the indexed GL_TRANSFORM_FEEDBACK_BUFFER + // binding points are object state, but the context keeps exactly one live + // copy of both so that every existing reader - the backends' per-draw sync, + // the drawing and getter paths - needs no notion of which object owns them. + // A bind therefore saves the live copy into the outgoing object and restores + // the incoming one's. Object 0 is the default object and always exists. + static constexpr Uint MAX_TRANSFORM_FEEDBACK_BUFFERS = 4; + void GenTransformFeedbackNames(Uint number, Vector& ids); + // A name glGenTransformFeedbacks handed out and glDeleteTransformFeedbacks + // has not taken back. Name 0 is always valid. + Bool ValidateTransformFeedbackName(Uint index) const; + void BindTransformFeedbackObject(Uint index); + void MarkTransformFeedbackObjectForDeletion(Uint index); + Uint GetBoundTransformFeedbackName() const { return m_boundTransformFeedback; } + // Vertices the object captured in its last completed span; the vertex count + // glDrawTransformFeedback replays. + Uint64 GetTransformFeedbackRecordedVertices(Uint index) const; + // Whether the object has ever completed a capture span. glDrawTransformFeedback + // on an object that has not is INVALID_OPERATION, which a zero vertex count + // cannot express: an empty completed span is legal and draws nothing. + Bool HasTransformFeedbackCompletedSpan(Uint index) const; + // Framebuffer void GenFramebufferNames(Uint number, Vector& framebuffers); const SharedPtr& GetFramebufferObject(Uint index); @@ -285,12 +318,40 @@ namespace MobileGL { VertexArrayState m_vertexArrayState; Array m_currentVertexAttributes{}; Bool m_transformFeedbackActive = false; + Bool m_transformFeedbackPaused = false; GLenum m_transformFeedbackPrimitiveMode = GL_POINTS; SharedPtr m_transformFeedbackProgram; Uint64 m_transformFeedbackGeneration = 0; + // Not object state: the transform feedback queries snapshot it at BeginQuery + // and take the delta at EndQuery, which spans whatever objects were used. Uint64 m_transformFeedbackPrimitiveCounter = 0; Uint64 m_transformFeedbackCapturedVertices = 0; Uint64 m_transformFeedbackInputPrimitives = 0; + + // Everything a transform feedback object owns while it is NOT the bound one. + struct TransformFeedbackObjectState { + struct SavedBufferBinding { + SharedPtr buffer; + Range1D range; + Bool hasExplicitRange = false; + }; + Array bindings; + Bool active = false; + Bool paused = false; + GLenum primitiveMode = GL_POINTS; + SharedPtr program; + Uint64 capturedVertices = 0; + Uint64 inputPrimitives = 0; + Uint64 recordedVertices = 0; + Bool hasCompletedSpan = false; + }; + void SaveBoundTransformFeedbackState(); + void RestoreBoundTransformFeedbackState(); + // operator[] materialises an entry with the default state on first touch, so + // the default object (name 0) needs no seeding here. + UnorderedMap m_transformFeedbackObjects; + IndexGenerator m_transformFeedbackNames; + Uint m_boundTransformFeedback = 0; TextureState m_textureState; ProgramState m_programState; RenderState m_renderState; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 11db3fa0..a3557444 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -174,6 +174,8 @@ namespace MobileGL::MG_State::GLState { m_xfbStrides.clear(); m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; m_xfbVaryingNameMaxLength = 0; + m_xfbNeedsScatteredCapture = false; + m_xfbPackedStride = 0; m_gsInputPrimitive = GL_NONE; m_linkStatus = false; } @@ -223,6 +225,8 @@ namespace MobileGL::MG_State::GLState { m_xfbStrides.clear(); m_xfbBufferMode = m_requestedXfbBufferMode; m_xfbVaryingNameMaxLength = 0; + m_xfbNeedsScatteredCapture = false; + m_xfbPackedStride = 0; if (m_requestedXfbVaryings.empty()) { return true; } @@ -244,8 +248,27 @@ namespace MobileGL::MG_State::GLState { const Bool interleaved = m_xfbBufferMode == GL_INTERLEAVED_ATTRIBS; Uint32 interleavedOffset = 0; + // ARB_transform_feedback3 lets an interleaved capture leave holes (gl_SkipComponents1..4) + // and move on to the next buffer (gl_NextBuffer). Both only affect where the following + // varyings land, so they are consumed here and never become XfbVaryings of their own - + // which also keeps them out of the name list a backend declares on its own driver. + Uint32 interleavedBufferIndex = 0; + Vector interleavedStrides; for (SizeT i = 0; i < m_requestedXfbVaryings.size(); ++i) { const String& name = m_requestedXfbVaryings[i]; + if (interleaved && name == "gl_NextBuffer") { + interleavedStrides.push_back(interleavedOffset); + interleavedOffset = 0; + ++interleavedBufferIndex; + m_xfbNeedsScatteredCapture = true; + continue; + } + if (interleaved && name.size() == 18 && name.compare(0, 17, "gl_SkipComponents") == 0 && + name[17] >= '1' && name[17] <= '4') { + interleavedOffset += static_cast(name[17] - '0') * 4; + m_xfbNeedsScatteredCapture = true; + continue; + } for (SizeT j = 0; j < i; ++j) { if (m_requestedXfbVaryings[j] == name) { m_infoLog = "Transform feedback varying '" + name + "' is specified more than once."; @@ -286,12 +309,14 @@ namespace MobileGL::MG_State::GLState { } varying.byteSize = bytesPerElement * static_cast(varying.size); + varying.packedOffsetBytes = m_xfbPackedStride; + m_xfbPackedStride += varying.byteSize; if (interleaved) { - varying.bufferIndex = 0; + varying.bufferIndex = interleavedBufferIndex; varying.offsetBytes = interleavedOffset; interleavedOffset += varying.byteSize; } else { - varying.bufferIndex = static_cast(i); + varying.bufferIndex = static_cast(m_xfbVaryings.size()); varying.offsetBytes = 0; } m_xfbVaryingNameMaxLength = @@ -302,13 +327,22 @@ namespace MobileGL::MG_State::GLState { constexpr Uint32 kMaxSeparateAttribs = 4; constexpr Uint32 kMaxSeparateComponents = 4; constexpr Uint32 kMaxInterleavedComponents = 64; + constexpr Uint32 kMaxTransformFeedbackBuffers = 4; if (interleaved) { - if (interleavedOffset > kMaxInterleavedComponents * 4) { - m_infoLog = "Transform feedback interleaved capture exceeds " - "GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS."; + interleavedStrides.push_back(interleavedOffset); + if (interleavedStrides.size() > kMaxTransformFeedbackBuffers) { + m_infoLog = "Transform feedback capture uses more buffers than " + "GL_MAX_TRANSFORM_FEEDBACK_BUFFERS."; return false; } - m_xfbStrides.assign(1, interleavedOffset); + for (const Uint32 stride : interleavedStrides) { + if (stride > kMaxInterleavedComponents * 4) { + m_infoLog = "Transform feedback interleaved capture exceeds " + "GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS."; + return false; + } + } + m_xfbStrides = Move(interleavedStrides); } else { if (m_xfbVaryings.size() > kMaxSeparateAttribs) { m_infoLog = "Transform feedback separate capture exceeds " diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index e8dadf3c..582b5a83 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -474,6 +474,9 @@ namespace MobileGL::MG_State::GLState { Uint32 bufferIndex = 0; // capture buffer slot Uint32 offsetBytes = 0; // offset within the capture buffer Uint32 byteSize = 0; // bytes captured per vertex for this varying + // Offset within the gap-free record a backend that cannot express the GL + // layout captures into; see NeedsScatteredTransformFeedbackCapture. + Uint32 packedOffsetBytes = 0; }; void SetTransformFeedbackVaryings(Vector&& names, GLenum bufferMode) { m_requestedXfbVaryings = Move(names); @@ -491,6 +494,15 @@ namespace MobileGL::MG_State::GLState { } SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); } Int GetTransformFeedbackVaryingMaxLength() const { return m_xfbVaryingNameMaxLength; } + // True when the capture layout uses gl_SkipComponents / gl_NextBuffer + // (ARB_transform_feedback3), which no ES driver can express: it can only pack every + // captured varying into one record with no gaps. A backend that captures through + // such a driver has to capture into scratch storage and scatter the records into the + // application's buffers itself, using packedOffsetBytes as the source offset and + // (bufferIndex, offsetBytes, stride) as the destination. + Bool NeedsScatteredTransformFeedbackCapture() const { return m_xfbNeedsScatteredCapture; } + // Bytes one gap-free captured record occupies. + Uint32 GetTransformFeedbackPackedStride() const { return m_xfbPackedStride; } // True when the capture stage is a triangle-strip geometry shader with a // statically-known emit sequence: the Vulkan capture order then needs the GL // odd-triangle vertex swap after EndTransformFeedback. @@ -611,5 +623,7 @@ namespace MobileGL::MG_State::GLState { GLenum m_gsInputPrimitive = GL_NONE; GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; Int m_xfbVaryingNameMaxLength = 0; + Bool m_xfbNeedsScatteredCapture = false; + Uint32 m_xfbPackedStride = 0; }; } // namespace MobileGL::MG_State::GLState