mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Refactor] (P13 W6e): the backends' frontend-framebuffer clear, blit and readback entries go - the verb port owns those slots in every mode - with Magma's frontend render-pass, pending-clear and renderbuffer halves.
This commit is contained in:
@@ -1605,7 +1605,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
funcsTable.GL.DrawElements = DrawElements;
|
||||
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
||||
funcsTable.GL.MultiDrawArrays = MultiDrawArrays;
|
||||
funcsTable.GL.MultiDrawElements = MultiDrawElements;
|
||||
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
||||
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
||||
funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
|
||||
@@ -1633,18 +1632,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
funcsTable.GL.ClearBufferfv = ClearBufferfv;
|
||||
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||
funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv;
|
||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
||||
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
||||
funcsTable.GL.CopyImageSubData = CopyImageSubData;
|
||||
funcsTable.GL.GenerateMipmap = GenerateMipmap;
|
||||
funcsTable.GL.ReadPixels = ReadPixels;
|
||||
funcsTable.GL.GetTexImage = GetTexImage;
|
||||
funcsTable.GL.FenceSync = FenceSync;
|
||||
funcsTable.GL.ClientWaitSync = ClientWaitSync;
|
||||
funcsTable.GL.WaitSync = WaitSync;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,8 +46,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount);
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex);
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
@@ -71,21 +69,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
GLuint baseinstance);
|
||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height, GLint border);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||
@@ -98,7 +83,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void GenerateMipmap(GLenum target);
|
||||
const GLubyte* GetString(GLenum name);
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void DispatchComputeIndirect(GLintptr indirect);
|
||||
void MemoryBarrier(GLbitfield barriers);
|
||||
|
||||
@@ -10389,91 +10389,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_syncedAttachmentRecordHash = 0;
|
||||
}
|
||||
|
||||
static Bool SyncAttachmentObject(GLenum glFBOTarget,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachmentObject,
|
||||
GLenum glBackendAttachment) {
|
||||
if (attachmentObject.IsTexture()) {
|
||||
const auto& textureObject = attachmentObject.GetTexture();
|
||||
SharedPtr<TextureImpl::BackendTextureObject> backendTextureObject;
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on
|
||||
// a first attach), named debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
if (auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get())) {
|
||||
backendTextureObject = *backendTextureSlot;
|
||||
} else {
|
||||
auto& newTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject);
|
||||
if (!newTextureSlot) {
|
||||
newTextureSlot = MakeShared<TextureImpl::BackendTextureObject>();
|
||||
}
|
||||
backendTextureObject = newTextureSlot;
|
||||
}
|
||||
if (!backendTextureObject) {
|
||||
MGLOG_E_ONCE("%s: No backend texture found for FBO attachment, cannot bind texture.", __func__);
|
||||
return false;
|
||||
}
|
||||
backendTextureObject->SyncMipmapsToBackend(textureObject);
|
||||
// From here on every draw, clear and blit into this framebuffer can write the
|
||||
// texture behind the shadow's back.
|
||||
TextureImpl::NoteDriverSideTextureWrite(textureObject);
|
||||
if (attachmentObject.IsLayered()) {
|
||||
g_GLESFuncs.glFramebufferTexture(glFBOTarget, glBackendAttachment,
|
||||
backendTextureObject->GetBackendTextureId(),
|
||||
static_cast<GLint>(attachmentObject.GetTextureLevel()));
|
||||
} else if (const auto uploadTarget = attachmentObject.GetTextureUploadTarget();
|
||||
uploadTarget == TextureUploadTarget::Texture3D ||
|
||||
uploadTarget == TextureUploadTarget::Texture2DArray ||
|
||||
uploadTarget == TextureUploadTarget::Texture1DArray ||
|
||||
uploadTarget == TextureUploadTarget::CubeMapArray ||
|
||||
uploadTarget == TextureUploadTarget::Texture2DMultisampleArray) {
|
||||
// Single slice/layer of a 3D or array texture: ES has no
|
||||
// glFramebufferTexture3D, layers attach via glFramebufferTextureLayer.
|
||||
g_GLESFuncs.glFramebufferTextureLayer(glFBOTarget, glBackendAttachment,
|
||||
backendTextureObject->GetBackendTextureId(),
|
||||
static_cast<GLint>(attachmentObject.GetTextureLevel()),
|
||||
static_cast<GLint>(attachmentObject.GetTextureLayer()));
|
||||
} else {
|
||||
auto glTextureTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(
|
||||
attachmentObject.GetTextureUploadTarget());
|
||||
if (glTextureTarget == GL_UNKNOWN_MGL) {
|
||||
glTextureTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(textureObject->GetTarget());
|
||||
}
|
||||
// glBindTexture rejects cube-face enums (INVALID_ENUM with no
|
||||
// bind, while Bind() would still record the cube-map cache slot
|
||||
// as bound): bind via the owning cube target; the attach below
|
||||
// keeps the face target.
|
||||
const Bool isCubeFace = glTextureTarget >= GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
|
||||
glTextureTarget <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
|
||||
backendTextureObject->Bind(isCubeFace ? GL_TEXTURE_CUBE_MAP : glTextureTarget);
|
||||
g_GLESFuncs.glFramebufferTexture2D(glFBOTarget, glBackendAttachment, glTextureTarget,
|
||||
backendTextureObject->GetBackendTextureId(),
|
||||
static_cast<GLint>(attachmentObject.GetTextureLevel()));
|
||||
}
|
||||
} else if (attachmentObject.IsRenderbuffer()) {
|
||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on
|
||||
// a first attach), named debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
SharedPtr<RenderbufferImpl::BackendRenderbufferObject> backendRenderbufferObject;
|
||||
if (auto* backendRenderbufferSlot =
|
||||
RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get())) {
|
||||
backendRenderbufferObject = *backendRenderbufferSlot;
|
||||
} else {
|
||||
auto& newRenderbufferSlot =
|
||||
RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreate(renderbufferObject);
|
||||
if (!newRenderbufferSlot) {
|
||||
newRenderbufferSlot = MakeShared<RenderbufferImpl::BackendRenderbufferObject>();
|
||||
}
|
||||
backendRenderbufferObject = newRenderbufferSlot;
|
||||
}
|
||||
|
||||
backendRenderbufferObject->SyncToBackend(renderbufferObject);
|
||||
backendRenderbufferObject->Bind();
|
||||
g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment, GL_RENDERBUFFER,
|
||||
backendRenderbufferObject->GetBackendRenderbufferId());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Bool IsSnormFormat(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::R8Snorm:
|
||||
@@ -10502,53 +10417,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
static Bool IsSnormFallbackAttachment(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) {
|
||||
if (attachmentObject.IsTexture()) {
|
||||
const auto& textureObject = attachmentObject.GetTexture();
|
||||
return textureObject && IsSnormFormat(textureObject->GetFormat()) &&
|
||||
TextureImpl::ShouldUseCaveatTextureFormat(textureObject->GetFormat(), textureObject->GetTarget());
|
||||
}
|
||||
if (attachmentObject.IsRenderbuffer()) {
|
||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
||||
return renderbufferObject &&
|
||||
IsSnormFormat(renderbufferObject->GetInternalFormat()) &&
|
||||
TextureImpl::ShouldUseCaveatRenderbufferFormat(renderbufferObject->GetInternalFormat());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Bool IsUnormFallbackAttachment(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) {
|
||||
if (attachmentObject.IsTexture()) {
|
||||
const auto& textureObject = attachmentObject.GetTexture();
|
||||
return textureObject && IsUnormFormat(textureObject->GetFormat()) &&
|
||||
TextureImpl::ShouldUseCaveatTextureFormat(textureObject->GetFormat(), textureObject->GetTarget());
|
||||
}
|
||||
if (attachmentObject.IsRenderbuffer()) {
|
||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
||||
return renderbufferObject &&
|
||||
IsUnormFormat(renderbufferObject->GetInternalFormat()) &&
|
||||
TextureImpl::ShouldUseCaveatRenderbufferFormat(renderbufferObject->GetInternalFormat());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool IsAlphaWidenedColorAttachment(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) {
|
||||
if (attachmentObject.IsTexture()) {
|
||||
const auto& textureObject = attachmentObject.GetTexture();
|
||||
return textureObject && TextureImpl::BackendTextureFormatAddsAlpha(textureObject->GetFormat(),
|
||||
textureObject->GetTarget());
|
||||
}
|
||||
if (attachmentObject.IsRenderbuffer()) {
|
||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
||||
return renderbufferObject &&
|
||||
TextureImpl::BackendRenderbufferFormatAddsAlpha(renderbufferObject->GetInternalFormat());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint32 g_alphaWidenedDrawBufferMask = 0;
|
||||
Uint32 g_integerColorDrawBufferMask = 0;
|
||||
|
||||
@@ -10585,19 +10453,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
static Bool IsIntegerColorAttachment(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) {
|
||||
if (attachmentObject.IsTexture()) {
|
||||
const auto& textureObject = attachmentObject.GetTexture();
|
||||
return textureObject && IsIntegerColorFormat(textureObject->GetFormat());
|
||||
}
|
||||
if (attachmentObject.IsRenderbuffer()) {
|
||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
||||
return renderbufferObject && IsIntegerColorFormat(renderbufferObject->GetInternalFormat());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// THE FOUR CROSS-OBJECT MASKS, ANSWERED FROM THE RECORD (ID-12 DV-5, review M-3 / DV-5).
|
||||
//
|
||||
// All four reduce to (internal format, TEXTURE TARGET) for a texture attachment and to
|
||||
@@ -10850,23 +10705,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint32 ComputeAlphaWidenedDrawBufferMask(const MG_State::GLState::FramebufferObject& fbo) {
|
||||
using FBO = MG_State::GLState::FramebufferObject;
|
||||
const auto& drawBuffers = fbo.GetDrawBuffers();
|
||||
Uint32 mask = 0;
|
||||
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS && i < 32; ++i) {
|
||||
const auto frontendBuf = drawBuffers[i];
|
||||
if (frontendBuf < FramebufferAttachmentType::Color0 ||
|
||||
frontendBuf > FramebufferAttachmentType::Color31) {
|
||||
continue;
|
||||
}
|
||||
if (IsAlphaWidenedColorAttachment(fbo.GetAttachment(frontendBuf))) {
|
||||
mask |= (1u << i);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// The read attachment's storage carries an alpha channel its frontend format does not
|
||||
// (the three-channel colour-renderable widening). GL answers such a read with 1.0, but
|
||||
// the storage holds whatever the draw wrote there, so the readback has to overwrite it.
|
||||
@@ -11011,49 +10849,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ApplyReadBufferFromRecord(*record, 0);
|
||||
}
|
||||
|
||||
void BackendFramebufferObject::SyncReadBufferToBackend(
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject) {
|
||||
if (!stateFBOObject) {
|
||||
return;
|
||||
}
|
||||
// P4a (D-C2): the READ buffer is answered from the RESOLVED read surface of the READ
|
||||
// framebuffer's own record, which is what structurally closes the read-buffer
|
||||
// shared-FBO defect class. The comment two lines below - "when this is reached from
|
||||
// SyncCurrentFBO's 'same FBO as draw' skip path" - describes a hazard that becomes
|
||||
// unrepresentable: the record says which target it is, and ReadSurface was resolved
|
||||
// from the read framebuffer's own read buffer before it ever crossed.
|
||||
//
|
||||
// The record carries the resolved SURFACE and not an index, so the attachment POINT
|
||||
// is recovered by matching it against Color[] - the one place both spellings sit
|
||||
// side by side. An empty surface (Res == the null handle, which D-C1 makes the
|
||||
// spelling of "no attachment" independently of Kind's numbering) is
|
||||
// FramebufferAttachmentType::None, i.e. GL_NONE. A surface that matches no colour
|
||||
// point cannot be a legal read buffer and is refused rather than guessed.
|
||||
//
|
||||
// P5e (fb): THAT DECISION now lives in ApplyReadBufferFromRecord, which takes no
|
||||
// frontend object; what is left in this overload is the monolith glue that finds
|
||||
// the handle by identity. Under an active transport nothing reaches it -
|
||||
// SyncToBackendByHandle runs the record form directly.
|
||||
// P5c (hd): with an active transport the handle is the caller's
|
||||
// (m_pushedSyncHandle), never the client allocator's - see SyncToBackend.
|
||||
const MG_Pipe::MGPipeHandle fbo =
|
||||
m_pushedSyncHandle;
|
||||
// ID-19: the OBJECT's record, not "the record of whatever is bound to READ". A
|
||||
// framebuffer whose read buffer is being pushed need not be the read binding at
|
||||
// all - glNamedFramebufferReadBuffer and the DSA clears reach here by name - and
|
||||
// ReadSurface is resolved from THIS framebuffer's own read buffer under every
|
||||
// Target, Named included (c0e's MGPFramebufferState comment).
|
||||
const auto* record = PushedFramebufferRecord(fbo);
|
||||
if (record == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: framebuffer %u has no applier record on the handle arm, so "
|
||||
"its read buffer cannot be pushed (handle {%u, %u})",
|
||||
stateFBOObject->GetExternalIndex(), fbo.Slot, fbo.Gen);
|
||||
return;
|
||||
}
|
||||
ApplyReadBufferFromRecord(*record, stateFBOObject->GetExternalIndex());
|
||||
return;
|
||||
}
|
||||
|
||||
Bool BackendFramebufferObject::RecomputeBackendColorSlots(
|
||||
const FramebufferObject::FramebufferAttachmentArray& stateDrawBuffers) {
|
||||
// Only the first GL_MAX_COLOR_ATTACHMENTS points exist in the backend. The frontend's own
|
||||
@@ -11143,380 +10938,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return moved;
|
||||
}
|
||||
|
||||
void BackendFramebufferObject::SyncToBackend(
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject, FramebufferTarget asTarget) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (!stateFBOObject) {
|
||||
MGLOG_E_ONCE("State FBO object is null, cannot sync to backend.");
|
||||
return;
|
||||
}
|
||||
MGLOG_D("Syncing FBO with backend ID %u to backend for state ID %u, as %s FBO", m_backendFBOId,
|
||||
stateFBOObject->GetExternalIndex(), (asTarget == FramebufferTarget::Draw ? "DRAW" : "READ"));
|
||||
GLenum glFBOTarget = MG_Util::ConvertFramebufferTargetToGLEnum(asTarget);
|
||||
|
||||
// P4a (D-C2 as corrected by ID-19): THE RECORD OF THIS FRAMEBUFFER OBJECT, resolved
|
||||
// BEFORE the driver framebuffer is bound.
|
||||
//
|
||||
// The order is the whole of C-1's fix. v1 bound first and refused afterwards, so a
|
||||
// glClearNamedFramebufferfv / glBlitNamedFramebuffer on a framebuffer that is bound
|
||||
// to neither target left this twin's freshly minted driver FBO bound with NO
|
||||
// ATTACHMENTS and the caller then issued the clear or the blit against it -
|
||||
// GL_INVALID_FRAMEBUFFER_OPERATION and nothing cleared, where the legacy arm cleared
|
||||
// correctly. What the reorder buys is exactly this and no more: THE REFUSAL ITSELF
|
||||
// HAS NO DRIVER EFFECT - it no longer mints and binds a driver FBO as a side effect
|
||||
// of declining. It does NOT decide the driver's binding (review N-1): every caller
|
||||
// binds immediately afterwards and none of them looks at a return value -
|
||||
// SyncAndBindFramebufferObject is SyncToBackend then an unconditional Bind(target)
|
||||
// (DirectGLES.cpp:3292-3293, the path all five DSA entry points take), and
|
||||
// BindCurrentFBO binds the current FBO's twin whether or not SyncCurrentFBO synced
|
||||
// it. So an unconfigured framebuffer can still end up bound; what the log line
|
||||
// promises is only that this function did not put it there.
|
||||
//
|
||||
// The record is per FRAMEBUFFER OBJECT and is found by handle, so it describes this
|
||||
// object whether it is bound to Draw, to Read, to both or to neither, and its own
|
||||
// Target is NOT compared against `asTarget`: a Named record legitimately names no
|
||||
// binding at all. What is still gated on `asTarget` is what reaches the DRIVER's
|
||||
// bound target - glDrawBuffers and the four cross-object masks below, which stay
|
||||
// Draw-only for the reason the OIT comment gives.
|
||||
const MG_Pipe::MGPFramebufferState* pushedRecord = nullptr;
|
||||
FramebufferObject::FramebufferAttachmentArray pushedDrawBuffers{};
|
||||
// MONOLITH GLUE, named as such: the Framebuffer handle of an object this backend
|
||||
// still arrives holding. A framebuffer has a handle but NO wire lifetime (D-I2) -
|
||||
// there is no framebuffer create or destroy in the catalogue and none is invented
|
||||
// - so the handle exists purely to key set_framebuffer_state, which is exactly
|
||||
// what it is used for here.
|
||||
//
|
||||
// P5c (hd): with an active transport the handle is the one the caller is applying
|
||||
// (m_pushedSyncHandle - the record-driven sync set it), and the client allocator
|
||||
// is never probed (T2). A null there means a caller reached this arm without a
|
||||
// record, which the null-record refusal below names.
|
||||
const MG_Pipe::MGPipeHandle fbo =
|
||||
m_pushedSyncHandle;
|
||||
pushedRecord = PushedFramebufferRecord(fbo);
|
||||
if (pushedRecord == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: framebuffer %u has no applier record on the handle arm, so it "
|
||||
"is not configured here; nothing is bound by this refusal, but the "
|
||||
"caller's own bind still targets it (handle {%u, %u})",
|
||||
stateFBOObject->GetExternalIndex(), fbo.Slot, fbo.Gen);
|
||||
return;
|
||||
}
|
||||
DecodePushedDrawBuffers(*pushedRecord, pushedDrawBuffers.data());
|
||||
Bind(asTarget);
|
||||
|
||||
// -------------------- Connect attachments (set buffers) -----------------------
|
||||
// 1. Remap draw buffers
|
||||
const FramebufferObject::FramebufferAttachmentArray& stateDrawBuffers =
|
||||
pushedRecord != nullptr ? pushedDrawBuffers : stateFBOObject->GetDrawBuffers();
|
||||
Bool drawBufferClean = false;
|
||||
if (memcmp(m_frontendDrawBuffers, stateDrawBuffers.data(),
|
||||
FramebufferObject::MAX_DRAW_BUFFERS * sizeof(FramebufferAttachmentType)) == 0) {
|
||||
drawBufferClean = true;
|
||||
}
|
||||
|
||||
// glDrawBuffers writes the state of the FBO bound to GL_DRAW_FRAMEBUFFER.
|
||||
// When this object is only bound as the READ target the call would land on
|
||||
// whatever framebuffer is draw-bound AND falsely stamp this object's memo,
|
||||
// so the later draw-target sync skips as "clean" while the real state is
|
||||
// stale (Minecraft 26.x OIT: the scratch clear-FBO kept draw buffers NONE
|
||||
// from its blit-destination configuration, silently dropping every
|
||||
// offscreen color clear).
|
||||
if (!drawBufferClean && asTarget == FramebufferTarget::Draw) {
|
||||
memcpy(m_frontendDrawBuffers, stateDrawBuffers.data(),
|
||||
FramebufferObject::MAX_DRAW_BUFFERS * sizeof(FramebufferAttachmentType));
|
||||
std::fill(m_backendDrawBuffers, m_backendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS, GL_NONE);
|
||||
int nEffectiveBuffers = 0;
|
||||
for (GLint i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS; ++i) {
|
||||
auto& frontendBuf = stateDrawBuffers[i];
|
||||
if (frontendBuf == FramebufferAttachmentType::None) {
|
||||
m_backendDrawBuffers[i] = GL_NONE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create compacted mapping
|
||||
if (frontendBuf == FramebufferAttachmentType::FrontLeft ||
|
||||
frontendBuf == FramebufferAttachmentType::FrontRight ||
|
||||
frontendBuf == FramebufferAttachmentType::BackLeft ||
|
||||
frontendBuf == FramebufferAttachmentType::BackRight) {
|
||||
MGLOG_D("%s: frontend buf token found for default fbo, shouldn't remap", __func__);
|
||||
m_backendDrawBuffers[i] = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendBuf);
|
||||
} else {
|
||||
m_backendDrawBuffers[i] = GL_COLOR_ATTACHMENT0 + i;
|
||||
}
|
||||
nEffectiveBuffers = i + 1;
|
||||
}
|
||||
g_GLESFuncs.glDrawBuffers(nEffectiveBuffers, m_backendDrawBuffers);
|
||||
// The line above pinned backend point s to draw-buffer slot s, so the images have to
|
||||
// be moved under those points. Rebuild the whole colour map and, when anything moved,
|
||||
// also drop the read-buffer memo: SyncReadBufferToBackend keys it on the frontend
|
||||
// enum alone, which does not change when the point under it does.
|
||||
if (RecomputeBackendColorSlots(stateDrawBuffers)) {
|
||||
m_frontendReadBuffer = FramebufferAttachmentType::Unknown;
|
||||
}
|
||||
MGLOG_D("DBAPPLY beFbo=%u target=%d n=%d db0=0x%x feDb0=%d", m_backendFBOId, (int)asTarget,
|
||||
nEffectiveBuffers, m_backendDrawBuffers[0], (int)stateDrawBuffers[0]);
|
||||
}
|
||||
|
||||
if (asTarget == FramebufferTarget::Draw) {
|
||||
Uint32 snormClampOutputMask = 0;
|
||||
Uint32 unormClampOutputMask = 0;
|
||||
Uint32 alphaWidenedMask = 0;
|
||||
Uint32 integerColorMask = 0;
|
||||
for (Uint i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS && i < 32; ++i) {
|
||||
const auto frontendBuf = stateDrawBuffers[i];
|
||||
if (frontendBuf < FramebufferAttachmentType::Color0 ||
|
||||
frontendBuf > FramebufferAttachmentType::Color31) {
|
||||
continue;
|
||||
}
|
||||
// ID-12 DV-5 / M-3: on the handle arm the four masks are answered from the
|
||||
// record's own surface for the point this draw buffer names, and the frontend
|
||||
// attachment object below is never touched - the whole arm returns here.
|
||||
// A draw buffer that names a point the record cannot describe is D-C3's
|
||||
// refusal arriving too late to refuse, so it is loud and contributes no bit.
|
||||
if (pushedRecord != nullptr) {
|
||||
const MG_Pipe::MGPSurface* pushedSurface =
|
||||
PushedSurfaceForAttachment(*pushedRecord, frontendBuf);
|
||||
if (pushedSurface == nullptr) {
|
||||
MGLOG_E_ONCE("MGPipe: framebuffer %u's draw buffer %u names colour point %d, "
|
||||
"which its record does not describe (the record carries %u "
|
||||
"colour points) - contributing no fallback mask bit",
|
||||
stateFBOObject->GetExternalIndex(), i,
|
||||
static_cast<Int>(frontendBuf) -
|
||||
static_cast<Int>(FramebufferAttachmentType::Color0),
|
||||
MG_Pipe::kMGPipeMaxColorAttachments);
|
||||
continue;
|
||||
}
|
||||
if (IsSnormFallbackSurface(*pushedSurface)) {
|
||||
snormClampOutputMask |= (1u << i);
|
||||
} else if (IsUnormFallbackSurface(*pushedSurface)) {
|
||||
unormClampOutputMask |= (1u << i);
|
||||
}
|
||||
if (IsAlphaWidenedColorSurface(*pushedSurface)) {
|
||||
alphaWidenedMask |= (1u << i);
|
||||
}
|
||||
if (IsIntegerColorSurface(*pushedSurface)) {
|
||||
integerColorMask |= (1u << i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto& attachmentObject = stateFBOObject->GetAttachment(frontendBuf);
|
||||
if (IsSnormFallbackAttachment(attachmentObject)) {
|
||||
snormClampOutputMask |= (1u << i);
|
||||
} else if (IsUnormFallbackAttachment(attachmentObject)) {
|
||||
unormClampOutputMask |= (1u << i);
|
||||
}
|
||||
// Independent of the two above: a widened attachment can be SNORM
|
||||
// (GL_RGB8_SNORM -> GL_RGBA16F, which also clamps) or not (GL_SRGB8 ->
|
||||
// GL_SRGB8_ALPHA8, which does not), so it gets its own bit rather than an
|
||||
// `else if` branch of theirs.
|
||||
if (IsAlphaWidenedColorAttachment(attachmentObject)) {
|
||||
alphaWidenedMask |= (1u << i);
|
||||
}
|
||||
if (IsIntegerColorAttachment(attachmentObject)) {
|
||||
integerColorMask |= (1u << i);
|
||||
}
|
||||
}
|
||||
PrgramImpl::g_snormFallbackClampOutputMask = snormClampOutputMask;
|
||||
PrgramImpl::g_unormFallbackClampOutputMask = unormClampOutputMask;
|
||||
g_alphaWidenedDrawBufferMask = alphaWidenedMask;
|
||||
g_integerColorDrawBufferMask = integerColorMask;
|
||||
}
|
||||
|
||||
// 2. Remap read buffer. glReadBuffer writes the READ-bound FBO's state, so
|
||||
// only apply (and stamp the memo) when this object is bound as READ.
|
||||
if (asTarget == FramebufferTarget::Read) {
|
||||
SyncReadBufferToBackend(stateFBOObject);
|
||||
}
|
||||
|
||||
// -------------------- Attach texture to backend FBO -----------------------
|
||||
// A backend texture id was re-minted since this twin's last walk
|
||||
// (RecreateBackendTexture): any point here may still hold the dead id while
|
||||
// its frontend attachment version is unchanged, so the memo below would skip
|
||||
// exactly the attachment that needs repair. Re-arm every point first.
|
||||
if (m_syncedBackendIdGeneration != g_attachmentBackendIdGeneration) {
|
||||
std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(),
|
||||
static_cast<Uint16>(~0u));
|
||||
m_syncedBackendIdGeneration = g_attachmentBackendIdGeneration;
|
||||
}
|
||||
// P4a (D-C4): the record's ContentHash is what says this framebuffer's resolved state
|
||||
// moved, and it REPLACES the frontend attachment versions AS A KEY. It covers every
|
||||
// field the record carries, so an attachment set that moved, a draw-buffer array
|
||||
// that moved, an extent that moved and a recycled Fbo whose successor happens to
|
||||
// carry an identical attachment set are all one compare - and it moves in ONE place
|
||||
// rather than in an array of 41 the twin had to walk.
|
||||
//
|
||||
// The re-arm is expressed through m_syncedFrontendAttachmentVersions rather than
|
||||
// around it, so the attachment loop below is unchanged on both arms: a moved record
|
||||
// arms every point and the loop then does exactly what it does today, including the
|
||||
// empty-colour-point detach that keeps m_backendColorSlots a permutation of the
|
||||
// PHYSICAL layout.
|
||||
//
|
||||
// OVER-FIRING IS FREE AND UNDER-FIRING IS FATAL, so the per-attachment versions stay
|
||||
// as a second, narrower gate underneath: a frontend attachment that moved without
|
||||
// the record moving still re-attaches. That direction is the safe one and it is the
|
||||
// reason the array is re-armed rather than retired here. It is what the array is FOR
|
||||
// now: the walk below resolves each attachment from the record (SyncAttachmentSurface),
|
||||
// so the versions no longer say WHICH object a point holds - they say only "this point
|
||||
// may have moved since I last looked", and they retire with the frontend attachment
|
||||
// array itself, which is a later phase's.
|
||||
if (pushedRecord != nullptr && m_syncedRecordHashes[SizeT(asTarget)] != pushedRecord->ContentHash) {
|
||||
std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(),
|
||||
static_cast<Uint16>(~0u));
|
||||
m_syncedRecordHashes[SizeT(asTarget)] = pushedRecord->ContentHash;
|
||||
}
|
||||
const auto& attachments = stateFBOObject->GetAllAttachmentObjects();
|
||||
const auto& attachmentVersions = stateFBOObject->GetAllFramebufferAttachmentVersions();
|
||||
for (SizeT i = 0; i < attachments.size(); ++i) {
|
||||
const auto& attachmentObject = attachments[i];
|
||||
auto frontendType = static_cast<FramebufferAttachmentType>(i);
|
||||
GLenum glBackendAttachment = GL_NONE;
|
||||
if (frontendType >= FramebufferAttachmentType::Color0 &&
|
||||
frontendType <= FramebufferAttachmentType::Color31)
|
||||
glBackendAttachment = GetBackendAttachmentType(frontendType);
|
||||
else
|
||||
glBackendAttachment = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendType);
|
||||
|
||||
// relevant FRONTEND!!! version should be checked and updated
|
||||
if (m_syncedFrontendAttachmentVersions[i] != attachmentVersions[i]) {
|
||||
// SyncAttachmentObject only ever attaches: for an empty frontend attachment it
|
||||
// returns true and issues nothing, so the point keeps whatever was there. That is
|
||||
// what makes m_backendColorSlots a permutation of the PHYSICAL layout rather than
|
||||
// a claim about one - a point handed to an attachment with no image would
|
||||
// otherwise still hold the previous owner's image and glReadBuffer would return
|
||||
// it. Bounded by GL_MAX_COLOR_ATTACHMENTS because GL_COLOR_ATTACHMENTn above the
|
||||
// driver's limit is INVALID_ENUM, and restricted to colour points because
|
||||
// FRONT_LEFT/BACK_LEFT and co. are not ES attachment points at all.
|
||||
const Bool isColorPoint =
|
||||
frontendType >= FramebufferAttachmentType::Color0 &&
|
||||
frontendType <= FramebufferAttachmentType::Color31 &&
|
||||
(static_cast<Int>(frontendType) - static_cast<Int>(FramebufferAttachmentType::Color0)) <
|
||||
g_GLESCapabilities.MaxColorAttachments;
|
||||
// P5e (fb, §5.4): ON THE RECORD ARM THE EMPTY POINT IS THE RECORD'S, and it
|
||||
// is the SAME test SyncAttachmentSurface attaches on - `Kind == None ||
|
||||
// Res null`. Asking the frontend here and the record there is what left
|
||||
// review N-6's one-directional hole; one test closes it by construction.
|
||||
const Bool pointIsEmpty =
|
||||
pushedRecord != nullptr
|
||||
? [&] {
|
||||
const MG_Pipe::MGPSurface* s =
|
||||
PushedSurfaceForAttachment(*pushedRecord, frontendType);
|
||||
return s == nullptr || s->Kind == MG_Pipe::kMGPipeSurfaceKindNone ||
|
||||
MG_Pipe::MGPipeHandleIsNull(s->Res);
|
||||
}()
|
||||
: attachmentObject.IsEmpty();
|
||||
if (isColorPoint && pointIsEmpty && glBackendAttachment != GL_NONE) {
|
||||
g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment, GL_RENDERBUFFER, 0);
|
||||
}
|
||||
// M-3: the ATTACHMENT RESOLUTION is the record's on the handle arm. The walk
|
||||
// itself still runs over the frontend's 41 points, because that is what says
|
||||
// which points EXIST and it is also what carries the per-attachment version
|
||||
// memo underneath the record hash; what the record answers is WHICH object
|
||||
// each point holds and in what shape.
|
||||
//
|
||||
// A point the record does not describe (Color8..Color31; the FRONT/BACK
|
||||
// tokens) is only reachable with a non-empty attachment if D-C3's refusal
|
||||
// failed to fire, so an empty one is silently fine and a live one is loud.
|
||||
Bool attachmentSynced;
|
||||
if (pushedRecord != nullptr) {
|
||||
const MG_Pipe::MGPSurface* pushedSurface =
|
||||
PushedSurfaceForAttachment(*pushedRecord, frontendType);
|
||||
if (pushedSurface == nullptr) {
|
||||
if (attachmentObject.IsEmpty()) {
|
||||
attachmentSynced = true;
|
||||
} else {
|
||||
MGLOG_E_ONCE("MGPipe: framebuffer %u holds an attachment at point %s, which "
|
||||
"its record does not describe - refusing to attach it",
|
||||
stateFBOObject->GetExternalIndex(),
|
||||
MG_Util::ConvertFramebufferAttachmentTypeToString(frontendType).c_str());
|
||||
attachmentSynced = false;
|
||||
}
|
||||
} else {
|
||||
attachmentSynced =
|
||||
SyncAttachmentSurface(glFBOTarget, *pushedSurface, glBackendAttachment,
|
||||
&attachmentObject);
|
||||
}
|
||||
} else {
|
||||
attachmentSynced = SyncAttachmentObject(glFBOTarget, attachmentObject, glBackendAttachment);
|
||||
}
|
||||
if (attachmentSynced) {
|
||||
m_syncedFrontendAttachmentVersions[i] = attachmentVersions[i];
|
||||
}
|
||||
}
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||
else {
|
||||
MGLOG_D("%s: Skipped SyncAttachmentObject(target=%s, frontendObj=(%dx%dx%d, %s), backendAtt=%s), "
|
||||
"version = %u",
|
||||
__func__, MG_Util::ConvertGLEnumToString(glFBOTarget).c_str(),
|
||||
attachmentObject.GetSize().x(), attachmentObject.GetSize().y(),
|
||||
attachmentObject.GetSize().z(),
|
||||
MG_Util::ConvertFramebufferAttachmentTypeToString(frontendType).c_str(),
|
||||
MG_Util::ConvertGLEnumToString(glBackendAttachment).c_str(),
|
||||
m_syncedFrontendAttachmentVersions[i]);
|
||||
if (!attachmentObject.IsTexture() && !attachmentObject.IsRenderbuffer()) {
|
||||
continue;
|
||||
}
|
||||
GLint objectType = GL_NONE;
|
||||
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(
|
||||
glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &objectType);
|
||||
MOBILEGL_ASSERT((objectType == GL_NONE) ||
|
||||
(attachmentObject.IsTexture() && objectType == GL_TEXTURE) ||
|
||||
(attachmentObject.IsRenderbuffer() && objectType == GL_RENDERBUFFER),
|
||||
"Attachment type not match!");
|
||||
GLint objectName = 0;
|
||||
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(
|
||||
glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &objectName);
|
||||
// Verify that the backend object's name and parameters match the frontend attachment state
|
||||
if (attachmentObject.IsTexture()) {
|
||||
const auto& textureObject = attachmentObject.GetTexture();
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named
|
||||
// debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
|
||||
MOBILEGL_ASSERT(backendTextureSlot != nullptr && *backendTextureSlot != nullptr,
|
||||
"No backend texture found while framebuffer reports texture attachment.");
|
||||
GLuint backendTexId = (*backendTextureSlot)->GetBackendTextureId();
|
||||
MOBILEGL_ASSERT(static_cast<GLint>(backendTexId) == objectName,
|
||||
"Attachment texture name mismatch between GLES (%d) and backend texture object "
|
||||
"(%d), frontend texture object ID=%d.",
|
||||
objectName, backendTexId, textureObject->GetExternalIndex());
|
||||
|
||||
GLint texLevel = 0;
|
||||
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(
|
||||
glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &texLevel);
|
||||
MOBILEGL_ASSERT(texLevel == static_cast<GLint>(attachmentObject.GetTextureLevel()),
|
||||
"Attachment texture level mismatch between GLES and state object.");
|
||||
} else if (attachmentObject.IsRenderbuffer()) {
|
||||
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
|
||||
// P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named
|
||||
// debt inside the scope - P3b/P4b rekeys the registry.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
auto* backendRboSlot =
|
||||
RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get());
|
||||
MOBILEGL_ASSERT(
|
||||
backendRboSlot != nullptr && *backendRboSlot != nullptr,
|
||||
"No backend renderbuffer found while framebuffer reports renderbuffer attachment.");
|
||||
GLuint backendRboId = (*backendRboSlot)->GetBackendRenderbufferId();
|
||||
MOBILEGL_ASSERT(static_cast<GLint>(backendRboId) == objectName,
|
||||
"Attachment renderbuffer name mismatch between GLES and state object.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// The walk itself can re-mint an id (SyncAttachmentObject ->
|
||||
// SyncMipmapsToBackend -> RecreateBackendTexture), invalidating points this
|
||||
// walk already attached or version-skipped - e.g. one texture attached at two
|
||||
// points. Re-enter until the generation is quiescent: every pass syncs each
|
||||
// dirty texture clean, so each repeat finds strictly fewer re-mints and the
|
||||
// common case (no re-mint) never takes a second pass. The head's draw/read-
|
||||
// buffer syncs are memoized against their own shadows, so a repeat re-walks
|
||||
// only the attachments.
|
||||
if (m_syncedBackendIdGeneration != g_attachmentBackendIdGeneration) {
|
||||
SyncToBackend(stateFBOObject, asTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- P5e (fb, CONTRACT-P5E.md §5.4): the reverse index, texture -> framebuffers ------
|
||||
//
|
||||
// WHY IT EXISTS. ScopedDetachedTextureFramebufferAttachments used to answer "which
|
||||
|
||||
@@ -2324,8 +2324,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
~BackendFramebufferObject();
|
||||
BackendFramebufferObject(const BackendFramebufferObject&) = delete;
|
||||
BackendFramebufferObject& operator=(const BackendFramebufferObject&) = delete;
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
||||
FramebufferTarget asTarget);
|
||||
// P5e SEAM (MG_Remote/CONTRACT-P5E.md §5.4; declared by c0e, bodied by fb): the same
|
||||
// sync keyed on the framebuffer HANDLE. The record's eleven surfaces ARE the point
|
||||
// set - the emitter refuses a point at or above the wire width - so the attachment
|
||||
@@ -2333,10 +2331,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// handle is the argument. An OVERLOAD rather than a changed signature, so the
|
||||
// monolith arm and the pull build see no token move.
|
||||
void SyncToBackendByHandle(MG_Pipe::MGPipeHandle fbo, FramebufferTarget asTarget);
|
||||
// Apply only this FBO's read buffer (glReadBuffer) to the backend. Split out so it can
|
||||
// still run when SyncCurrentFBO skips the READ-target sync because the same GL FBO is
|
||||
// bound as both draw and read (otherwise glReadBuffer changes would be silently dropped).
|
||||
void SyncReadBufferToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject);
|
||||
// P5e (fb, §5.4): the same read-buffer push keyed on the handle, for the one path
|
||||
// that applies a read buffer without doing the rest of the sync - SyncCurrentFBO's
|
||||
// "one object is bound to BOTH bindings" skip, where the DRAW pass already did the
|
||||
@@ -2514,15 +2508,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// GL_CLAMP_READ_COLOR rule the clamp above implements.
|
||||
Bool IsAlphaWidenedFallbackReadAttachment();
|
||||
|
||||
// True when this attachment's storage carries an alpha channel its frontend format does
|
||||
// not (the three-channel colour-renderable widening).
|
||||
Bool IsAlphaWidenedColorAttachment(const MG_State::GLState::FramebufferAttachmentObject& attachmentObject);
|
||||
|
||||
// Bit i set = DRAW BUFFER i of `fbo` resolves to a colour attachment the backend widened
|
||||
// from three channels to four. Indexed by draw-buffer slot, not by attachment point,
|
||||
// because that is what glColorMaski / glClearBufferfv address.
|
||||
Uint32 ComputeAlphaWidenedDrawBufferMask(const MG_State::GLState::FramebufferObject& fbo);
|
||||
|
||||
// The same mask for whatever is currently bound to GL_DRAW_FRAMEBUFFER, recomputed by
|
||||
// SyncCurrentFBO (BackendFramebufferObject::SyncToBackend for the DRAW target, and reset
|
||||
// to 0 on the default framebuffer). Read by the draw/clear state sync, so it is only
|
||||
|
||||
@@ -987,7 +987,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
funcsTable.GL.DrawElements = DrawElements;
|
||||
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
||||
funcsTable.GL.MultiDrawArrays = MultiDrawArrays;
|
||||
funcsTable.GL.MultiDrawElements = MultiDrawElements;
|
||||
funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex;
|
||||
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
||||
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
||||
@@ -1008,19 +1007,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
funcsTable.GL.ClearBufferfv = ClearBufferfv;
|
||||
funcsTable.GL.ClearBufferuiv = ClearBufferuiv;
|
||||
funcsTable.GL.ClearBufferiv = ClearBufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv;
|
||||
funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi;
|
||||
funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv;
|
||||
funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv;
|
||||
funcsTable.GL.BlitFramebuffer = BlitFramebuffer;
|
||||
funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer;
|
||||
funcsTable.GL.CopyTexImage2D = CopyTexImage2D;
|
||||
funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D;
|
||||
funcsTable.GL.CopyImageSubData = CopyImageSubData;
|
||||
funcsTable.GL.GenerateMipmap = GenerateMipmap;
|
||||
funcsTable.GL.ReadPixels = ReadPixels;
|
||||
funcsTable.GL.GetTexImage = GetTexImage;
|
||||
funcsTable.GL.GetTextureImage = GetTextureImage;
|
||||
funcsTable.GL.DispatchCompute = DispatchCompute;
|
||||
funcsTable.GL.DispatchComputeIndirect = DispatchComputeIndirect;
|
||||
funcsTable.GL.MemoryBarrier = MemoryBarrier;
|
||||
|
||||
@@ -312,34 +312,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
pVulkanRenderer->ClearBufferiv(buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLfloat* value) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfv called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ClearNamedFramebufferfv called with null GL context");
|
||||
pVulkanRenderer->ClearNamedFramebufferfv(framebuffer, buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLint* value) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferiv called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ClearNamedFramebufferiv called with null GL context");
|
||||
pVulkanRenderer->ClearNamedFramebufferiv(framebuffer, buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLuint* value) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferuiv called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ClearNamedFramebufferuiv called with null GL context");
|
||||
pVulkanRenderer->ClearNamedFramebufferuiv(framebuffer, buffer, drawbuffer, value);
|
||||
}
|
||||
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, GLfloat depth, GLint stencil) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfi called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ClearNamedFramebufferfi called with null GL context");
|
||||
pVulkanRenderer->ClearNamedFramebufferfi(framebuffer, buffer, drawbuffer, depth, stencil);
|
||||
}
|
||||
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||
DrawWireIndirect(mode, type, indirect, drawcount, stride, true);
|
||||
return;
|
||||
@@ -560,18 +532,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ReadPixels called with null GL context");
|
||||
pVulkanRenderer->ReadPixels(x, y, width, height, format, type, pixels);
|
||||
}
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GetTexImage called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::GetTexImage called with null GL context");
|
||||
pVulkanRenderer->GetTexImage(target, level, format, type, pixels);
|
||||
}
|
||||
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget uploadTarget,
|
||||
GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GetTextureImage called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::GetTextureImage called with null GL context");
|
||||
pVulkanRenderer->GetTextureImage(texture, uploadTarget, level, format, type, bufSize, pixels);
|
||||
}
|
||||
|
||||
void Clear(GLbitfield mask) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::Clear called with null GL context");
|
||||
@@ -780,13 +740,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
pVulkanRenderer->MultiDrawElements(payload);
|
||||
}
|
||||
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawElements called with null GL context");
|
||||
MultiDrawElementsImpl(mode, count, type, indices, drawcount, nullptr);
|
||||
}
|
||||
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsBaseVertex called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DrawElementsBaseVertex called with null GL context");
|
||||
@@ -824,15 +777,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
pVulkanRenderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||
}
|
||||
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
||||
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitNamedFramebuffer called with null VulkanRenderer");
|
||||
pVulkanRenderer->BlitNamedFramebuffer(readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0,
|
||||
dstY0, dstX1, dstY1, mask, filter);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Backend fence handle: the queue-submission index captured at fence
|
||||
// creation (see VulkanRenderer::GetSyncPointSubmitIndex). The fence is
|
||||
|
||||
@@ -65,21 +65,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, const GLuint* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer, GLenum buffer,
|
||||
GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void Clear(GLbitfield mask);
|
||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
||||
void DrawArrays(GLenum mode, GLint first, GLsizei count);
|
||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex);
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount);
|
||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex);
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
@@ -105,11 +95,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
||||
GLsizei height, GLint border);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
|
||||
@@ -129,9 +114,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
|
||||
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding);
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget uploadTarget,
|
||||
GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels);
|
||||
// GL fence sync objects, mapped onto the renderer's frame-serial busy
|
||||
// tracking: a fence captures the frame serial current at creation and is
|
||||
// signaled once every command recorded under that serial has completed on
|
||||
|
||||
@@ -602,7 +602,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
for (auto& entry : m_descriptorReuseMemo) {
|
||||
entry.valid = false;
|
||||
}
|
||||
m_fastRebindMemo.valid = false;
|
||||
m_lastBindValid = false;
|
||||
}
|
||||
|
||||
@@ -689,60 +688,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
for (auto& entry : m_descriptorReuseMemo) {
|
||||
entry.valid = false;
|
||||
}
|
||||
// The rebind memo's set may be among the freed ones.
|
||||
m_fastRebindMemo.valid = false;
|
||||
MGLOG_D("UniformDescriptorBinder: freed %zu descriptor sets for destroyed layout", purgedSets);
|
||||
}
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 element,
|
||||
VkDescriptorImageInfo& outImageInfo,
|
||||
Bool trustUnchangedHint) const {
|
||||
return ResolveWireImageDescriptor(commandBuffer, program, programObj, binding, element, false, outImageInfo);
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveSamplerDescriptorOverride(
|
||||
const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const {
|
||||
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptorOverride: texture manager is null");
|
||||
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptorOverride: sampler manager is null");
|
||||
MOBILEGL_ASSERT(samplerBindingOverride.texture != nullptr,
|
||||
"ResolveSamplerDescriptorOverride: override texture is null for binding %u",
|
||||
samplerBindingOverride.binding);
|
||||
MOBILEGL_ASSERT(samplerBindingOverride.sampler != nullptr,
|
||||
"ResolveSamplerDescriptorOverride: override sampler is null for binding %u",
|
||||
samplerBindingOverride.binding);
|
||||
|
||||
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*samplerBindingOverride.texture);
|
||||
MOBILEGL_ASSERT(resource != nullptr,
|
||||
"ResolveSamplerDescriptorOverride: failed to sync override texture resource for binding %u textureId=%d",
|
||||
samplerBindingOverride.binding, samplerBindingOverride.texture->GetExternalIndex());
|
||||
MOBILEGL_ASSERT(IsValidSampledImageLayout(resource->layout),
|
||||
"ResolveSamplerDescriptorOverride: invalid layout %d for binding %u textureId=%d",
|
||||
static_cast<Int>(resource->layout), samplerBindingOverride.binding,
|
||||
samplerBindingOverride.texture->GetExternalIndex());
|
||||
|
||||
outImageInfo = {
|
||||
.sampler = m_samplerManager->GetOrCreateSampler(*samplerBindingOverride.sampler,
|
||||
*samplerBindingOverride.texture,
|
||||
samplerBindingOverride.forceNearestFiltering,
|
||||
resource->sampledLevelCount),
|
||||
.imageView = samplerBindingOverride.imageView != VK_NULL_HANDLE ?
|
||||
samplerBindingOverride.imageView :
|
||||
// Same reason as in ResolveSamplerDescriptor: the resource's own views describe
|
||||
// the storage texture, so a view has to be asked for its own.
|
||||
(samplerBindingOverride.texture->IsTextureView()
|
||||
? m_textureManager->GetOrCreateSampledImageView(*samplerBindingOverride.texture,
|
||||
VK_FORMAT_UNDEFINED)
|
||||
: (resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView)),
|
||||
.imageLayout = samplerBindingOverride.imageLayout != VK_IMAGE_LAYOUT_UNDEFINED ?
|
||||
samplerBindingOverride.imageLayout : resource->layout,
|
||||
};
|
||||
return outImageInfo.sampler != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 frameIndex,
|
||||
Bool storage, VkBufferView& out) {
|
||||
@@ -1553,10 +1502,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 frameIndex,
|
||||
VkPipelineBindPoint bindPoint,
|
||||
const SamplerBindingOverride* samplerBindingOverride,
|
||||
Bool samplerDescriptorsUnchangedHint,
|
||||
const Vector<SamplerBindingOverride>* samplerBindingOverrides) {
|
||||
VkPipelineBindPoint bindPoint) {
|
||||
// This program has a descriptor MobileGL could not resolve (see
|
||||
// VkProgramObject::declinedDescriptors). Refusing here is the whole of the decline: the
|
||||
// binding is still declared in the layout, so the pipeline is consistent with the shader
|
||||
@@ -1577,39 +1523,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
frame.activeDescriptorPoolIndex = 0;
|
||||
}
|
||||
|
||||
// Dynamic-offset-only rebind (see FastRebindMemo in the header): the last
|
||||
// cacheable walk of this exact program selected a set whose contents are
|
||||
// provably still what this walk would write - the hint covers every
|
||||
// sampler binding, and an unchanged (buffer, range) for the single
|
||||
// dynamic UBO covers the rest - except the dynamic offset, which rebinding
|
||||
// the SAME set delivers without any descriptor write.
|
||||
const Bool cacheable = samplerBindingOverride == nullptr &&
|
||||
(samplerBindingOverrides == nullptr || samplerBindingOverrides->empty());
|
||||
if (cacheable && samplerDescriptorsUnchangedHint && m_fastRebindMemo.valid &&
|
||||
m_fastRebindMemo.frameIndex == frameIndex &&
|
||||
// A wire store destroyed since the memo was taken may have handed its handle
|
||||
// value to a later mint (see FastRebindMemo): "same VkBuffer" then names a
|
||||
// different store, so the memo is refused and the full walk re-records it.
|
||||
m_fastRebindMemo.wireStoreDestroyEpoch == m_bufferManager->GetWireStoreDestroyEpoch() &&
|
||||
m_fastRebindMemo.programLifetimeId == program.GetLifetimeId() &&
|
||||
m_fastRebindMemo.programHash == programObj.hash) {
|
||||
VkBuffer uboBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceSize uboRange = 0;
|
||||
Uint32 uboDynamicOffset = 0;
|
||||
if (ResolveDynamicUboDescriptor(program, programObj, m_fastRebindMemo.uboBinding, 0, frameIndex,
|
||||
uboBuffer, uboRange, uboDynamicOffset) &&
|
||||
uboBuffer == m_fastRebindMemo.uboBuffer && uboRange == m_fastRebindMemo.uboRange) {
|
||||
auto& fastOffsets = m_dynamicOffsetsScratch;
|
||||
fastOffsets.clear();
|
||||
fastOffsets.push_back(uboDynamicOffset);
|
||||
BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout,
|
||||
m_fastRebindMemo.set, fastOffsets);
|
||||
return true;
|
||||
}
|
||||
// Any mismatch (arena wrap or growth, direct-bind retarget, upload
|
||||
// failure) falls through to the full walk, which re-records the memo.
|
||||
}
|
||||
|
||||
// The descriptor set is chosen AFTER the writes are built (below), so a draw
|
||||
// whose resolved descriptor content matches the previous draw can reuse that
|
||||
// set and skip both AcquireDescriptorSet and vkUpdateDescriptorSets.
|
||||
@@ -1660,13 +1573,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
texelBufferViews.reserve(m_maxBindings);
|
||||
dynamicOffsets.reserve(programObj.dynamicBindings.size() + uboArrayExtra);
|
||||
|
||||
// Eligibility probe for FastRebindMemo, filled by this walk: exactly one
|
||||
// dynamic-UBO descriptor (no arrayed elements) and otherwise only
|
||||
// combined-image samplers, so the whole set's content is pinned by the
|
||||
// sampler hint plus one (buffer, range) compare.
|
||||
Uint32 dynamicUboDescriptorCount = 0;
|
||||
Uint32 fastRebindUboBinding = 0;
|
||||
Bool fastRebindKindsEligible = true;
|
||||
|
||||
// Iterate only the bindings this program declares. The old walk covered all 256 slots of
|
||||
// bindingKinds on every draw to find the 1-8 a real program uses.
|
||||
@@ -1685,8 +1591,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) {
|
||||
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||
dynamicUboDescriptorCount += descriptorCount;
|
||||
fastRebindUboBinding = binding;
|
||||
const SizeT firstBufferInfoIndex = bufferInfos.size();
|
||||
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||
VkDescriptorBufferInfo bufferInfo{};
|
||||
@@ -1719,7 +1623,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
texelBufferViews.push_back(bufferView);
|
||||
fastRebindKindsEligible = false;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
|
||||
write.pTexelBufferView = &texelBufferViews.back();
|
||||
writes.push_back(write);
|
||||
@@ -1738,7 +1641,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
texelBufferViews.push_back(bufferView);
|
||||
fastRebindKindsEligible = false;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
|
||||
write.pTexelBufferView = &texelBufferViews.back();
|
||||
writes.push_back(write);
|
||||
@@ -1760,7 +1662,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
bufferInfos.push_back(bufferInfo);
|
||||
}
|
||||
|
||||
fastRebindKindsEligible = false;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
write.descriptorCount = descriptorCount;
|
||||
write.pBufferInfo = &bufferInfos[firstBufferInfoIndex];
|
||||
@@ -1786,7 +1687,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
imageInfos.push_back(imageInfo);
|
||||
}
|
||||
fastRebindKindsEligible = false;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
write.descriptorCount = descriptorCount;
|
||||
write.pImageInfo = &imageInfos[firstImageInfoIndex];
|
||||
@@ -1798,37 +1698,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// so a shader indexing them sampled a descriptor nobody had filled in
|
||||
// (KHR-GL42.shading_language_420pack.binding_sampler_array).
|
||||
const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding);
|
||||
// Overrides come only from MobileGL's own blit and depth-mipmap programs, whose
|
||||
// samplers are scalars; the override replaces THE descriptor at its binding, so
|
||||
// there is no element for it to mean on an arrayed one.
|
||||
const Bool overrideThisBinding = samplerBindingOverride != nullptr &&
|
||||
samplerBindingOverride->binding == binding &&
|
||||
samplerBindingOverride->texture != nullptr &&
|
||||
samplerBindingOverride->sampler != nullptr;
|
||||
MOBILEGL_ASSERT(
|
||||
!overrideThisBinding || descriptorCount == 1,
|
||||
"BindProgramUniformBuffers: sampler override targets arrayed binding %u (%u descriptors)",
|
||||
binding, descriptorCount);
|
||||
const SizeT firstImageInfoIndex = imageInfos.size();
|
||||
for (Uint32 element = 0; element < descriptorCount; ++element) {
|
||||
VkDescriptorImageInfo imageInfo{};
|
||||
const SamplerBindingOverride* overrideForElement =
|
||||
overrideThisBinding && element == 0 ? samplerBindingOverride : nullptr;
|
||||
if (overrideForElement == nullptr && samplerBindingOverrides != nullptr) {
|
||||
const auto overrideIt = std::find_if(
|
||||
samplerBindingOverrides->begin(), samplerBindingOverrides->end(),
|
||||
[binding, element](const SamplerBindingOverride& candidate) {
|
||||
return candidate.binding == binding && candidate.element == element;
|
||||
});
|
||||
if (overrideIt != samplerBindingOverrides->end()) {
|
||||
overrideForElement = &*overrideIt;
|
||||
}
|
||||
}
|
||||
const Bool hasImage = overrideForElement != nullptr
|
||||
? ResolveSamplerDescriptorOverride(*overrideForElement, imageInfo)
|
||||
: ResolveSamplerDescriptor(commandBuffer, program, programObj, binding,
|
||||
element, imageInfo,
|
||||
samplerDescriptorsUnchangedHint);
|
||||
const Bool hasImage = ResolveWireImageDescriptor(commandBuffer, program, programObj, binding,
|
||||
element, false, imageInfo);
|
||||
if (!hasImage) {
|
||||
MGLOG_E_ONCE(
|
||||
"UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u "
|
||||
@@ -1845,14 +1719,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
imageInfos.push_back(imageInfo);
|
||||
}
|
||||
if (descriptorCount > 1) {
|
||||
// The dynamic-offset-only rebind replays a whole descriptor set on the
|
||||
// strength of the sampler hint alone, and its eligibility probe was written
|
||||
// for bindings that carry one descriptor each. An arrayed sampler binding
|
||||
// also bypasses the per-binding descriptor memo, so there is nothing for it
|
||||
// to win here either.
|
||||
fastRebindKindsEligible = false;
|
||||
}
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
write.descriptorCount = descriptorCount;
|
||||
write.pImageInfo = &imageInfos[firstImageInfoIndex];
|
||||
@@ -1906,12 +1772,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
VkDescriptorSet reusedSet = VK_NULL_HANDLE;
|
||||
if (cacheable) {
|
||||
for (const auto& entry : m_descriptorReuseMemo) {
|
||||
if (entry.valid && entry.signature == signature) {
|
||||
reusedSet = entry.set;
|
||||
break;
|
||||
}
|
||||
for (const auto& entry : m_descriptorReuseMemo) {
|
||||
if (entry.valid && entry.signature == signature) {
|
||||
reusedSet = entry.set;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (reusedSet != VK_NULL_HANDLE) {
|
||||
@@ -1929,31 +1793,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (!writes.empty()) {
|
||||
vkUpdateDescriptorSets(m_device, static_cast<Uint32>(writes.size()), writes.data(), 0, nullptr);
|
||||
}
|
||||
if (cacheable) {
|
||||
m_descriptorReuseMemo[m_descriptorReuseMemoNext] =
|
||||
DescriptorReuseEntry{signature, descriptorSet, true};
|
||||
m_descriptorReuseMemoNext = (m_descriptorReuseMemoNext + 1) % kDescriptorReuseMemoSize;
|
||||
} else {
|
||||
for (auto& entry : m_descriptorReuseMemo) {
|
||||
entry.valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (Re)record the dynamic-offset-only rebind memo. Recording on every
|
||||
// cacheable walk (allocated or reused set alike - both hold exactly the
|
||||
// content just computed) keeps the single slot tracking the most recent
|
||||
// program; a non-cacheable override walk drops it alongside the reuse
|
||||
// memo above.
|
||||
if (cacheable && fastRebindKindsEligible && dynamicUboDescriptorCount == 1) {
|
||||
m_fastRebindMemo = FastRebindMemo{
|
||||
/*valid=*/true, frameIndex, program.GetLifetimeId(), programObj.hash,
|
||||
fastRebindUboBinding, bufferInfos[0].buffer,
|
||||
bufferInfos[0].range, descriptorSet,
|
||||
m_bufferManager->GetWireStoreDestroyEpoch(),
|
||||
};
|
||||
} else {
|
||||
m_fastRebindMemo.valid = false;
|
||||
m_descriptorReuseMemo[m_descriptorReuseMemoNext] =
|
||||
DescriptorReuseEntry{signature, descriptorSet, true};
|
||||
m_descriptorReuseMemoNext = (m_descriptorReuseMemoNext + 1) % kDescriptorReuseMemoSize;
|
||||
}
|
||||
|
||||
BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, descriptorSet,
|
||||
|
||||
@@ -27,16 +27,6 @@ namespace MobileGL::MG_State::GLState {
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
class UniformManager {
|
||||
public:
|
||||
struct SamplerBindingOverride {
|
||||
Uint32 binding = 0;
|
||||
Uint32 element = 0;
|
||||
MG_State::GLState::ITextureObject* texture = nullptr;
|
||||
const MG_State::GLState::SamplerObject* sampler = nullptr;
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkImageLayout imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
Bool forceNearestFiltering = false;
|
||||
};
|
||||
|
||||
struct SamplerImageFeedbackBinding {
|
||||
Uint32 samplerBinding = 0;
|
||||
Uint32 samplerElement = 0;
|
||||
@@ -107,20 +97,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// submit older work, but descriptor recording must never rotate it.
|
||||
Bool PrepareWireTextureResources(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj);
|
||||
// samplerDescriptorsUnchangedHint: the caller (SetupDraw fast path) proved that
|
||||
// every input of every combined-image-sampler resolution is unchanged since the
|
||||
// previous draw's resolve - same (texture, sampler) per binding, texture params
|
||||
// sum, sampling-resolution generation (sampler params + texture shape), image
|
||||
// epochs AND per-resource layout values - so the per-binding cached
|
||||
// VkDescriptorImageInfo may be reused without re-running the resolve chain.
|
||||
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
|
||||
const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 frameIndex,
|
||||
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
const SamplerBindingOverride* samplerBindingOverride = nullptr,
|
||||
Bool samplerDescriptorsUnchangedHint = false,
|
||||
const Vector<SamplerBindingOverride>* samplerBindingOverrides = nullptr);
|
||||
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS);
|
||||
|
||||
// Pure format-policy helper kept public for host regression tests. Formatted storage
|
||||
// images use their shader qualifier; transformed float images use glBindImageTexture's
|
||||
@@ -277,18 +258,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// class when the device cannot use the declared one as a texel buffer.
|
||||
VkBufferView AcquireUnboundTexelBufferView(VkFormat declaredFormat, SamplerNumericDomain numericDomain,
|
||||
Bool storage);
|
||||
// `element` indexes a sampler ARRAY inside one binding; each element carries its own
|
||||
// independently assigned GL texture unit, so it selects the texture, the sampler
|
||||
// override and the fallback separately from its neighbours.
|
||||
//
|
||||
// trustUnchangedHint: reuse this binding's cached VkDescriptorImageInfo outright
|
||||
// (see BindProgramUniformBuffers' samplerDescriptorsUnchangedHint for the proof
|
||||
// obligations the caller carries). The cache is keyed by binding alone, so it is
|
||||
// used ONLY for single-descriptor bindings - see m_samplerResolveMemo.
|
||||
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 element, VkDescriptorImageInfo& outImageInfo,
|
||||
Bool trustUnchangedHint = false) const;
|
||||
Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 element, Bool storage, VkDescriptorImageInfo& out) const;
|
||||
@@ -301,8 +270,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkDeviceSize m_wireMaxUniformRange = ~VkDeviceSize{0};
|
||||
VkDeviceSize m_wireMaxStorageRange = ~VkDeviceSize{0};
|
||||
Uint32 m_wireMaxTexelElements = ~Uint32{0};
|
||||
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
||||
VkDescriptorImageInfo& outImageInfo) const;
|
||||
Bool ResolveTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 frameIndex, VkBufferView& outBufferView);
|
||||
@@ -440,47 +407,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
DescriptorReuseEntry m_descriptorReuseMemo[kDescriptorReuseMemoSize];
|
||||
Uint32 m_descriptorReuseMemoNext = 0;
|
||||
|
||||
// Dynamic-offset-only rebind (see BindProgramUniformBuffers): records the
|
||||
// descriptor set selected by the last cacheable full walk of a program
|
||||
// whose active bindings are exactly one dynamic UBO (single descriptor)
|
||||
// plus combined-image samplers. When the next call proves every sampler
|
||||
// descriptor input unchanged (samplerDescriptorsUnchangedHint) and the
|
||||
// UBO re-resolves to the SAME VkBuffer+range - only the dynamic offset
|
||||
// moved, the per-draw glUniform case - the walk collapses to: resolve one
|
||||
// offset, rebind the recorded set with new pDynamicOffsets (Vulkan allows
|
||||
// rebinding the same set with different dynamic offsets).
|
||||
// Invalidation inventory: BeginFrame clears it (the frame's sets are
|
||||
// recycled) and the frameIndex field guards cross-frame confusion on top;
|
||||
// OnDescriptorSetLayoutDestroyed clears it (the set may be freed); a
|
||||
// sampler-override walk clears it (mirrors m_descriptorReuseMemo); a
|
||||
// program relink bumps the backend state version and thus programObj.hash
|
||||
// so the key misses; the program lifetime id is never reused, so a
|
||||
// deleted-and-recreated program misses; a texture/sampler/binding change
|
||||
// drops the hint upstream; an arena wrap or growth resolves a different
|
||||
// VkBuffer and misses. AcquireDescriptorSet's per-frame cursor only
|
||||
// advances, so the recorded set is never re-written within its frame.
|
||||
// P7 M2 round 2 (ID-P7-43): a WIRE store can die MID-FRAME on the reclaim
|
||||
// path (VkBufferManager::DeferredWireRelease), and the next mint can hand
|
||||
// its VkBuffer handle value back - a heap pointer under lavapipe - so "the
|
||||
// same VkBuffer+range" is no longer proof of the same store. The memo
|
||||
// therefore records VkBufferManager::GetWireStoreDestroyEpoch() and misses
|
||||
// once any wire store has been destroyed since; the descriptor-reuse
|
||||
// signature below folds the same epoch in. On the wire arm this memo is
|
||||
// never consulted (SetupWireDraw passes no sampler hint), so the field is
|
||||
// the defensive half; the signature is the half the wire arm reaches.
|
||||
struct FastRebindMemo {
|
||||
Bool valid = false;
|
||||
Uint32 frameIndex = 0;
|
||||
Uint64 programLifetimeId = 0;
|
||||
ProgramFactory::HashType programHash = 0;
|
||||
Uint32 uboBinding = 0;
|
||||
VkBuffer uboBuffer = VK_NULL_HANDLE;
|
||||
VkDeviceSize uboRange = 0;
|
||||
VkDescriptorSet set = VK_NULL_HANDLE;
|
||||
Uint64 wireStoreDestroyEpoch = 0;
|
||||
};
|
||||
FastRebindMemo m_fastRebindMemo;
|
||||
|
||||
// vkCmdBindDescriptorSets dedup: consecutive draws with a static uniform
|
||||
// block resolve to the same set AND the same dynamic offsets, so the
|
||||
// driver call can be skipped outright. Command-buffer-scope state; reset
|
||||
|
||||
@@ -68,24 +68,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
toLinear(payload.color.z()), payload.color.w());
|
||||
}
|
||||
|
||||
void ForceOpaqueClearAlpha(ClearAttachmentPayload& payload) {
|
||||
switch (payload.colorEncoding) {
|
||||
case ClearColorEncoding::Int:
|
||||
payload.colorInt = IntVec4(payload.colorInt.x(), payload.colorInt.y(), payload.colorInt.z(), 1);
|
||||
break;
|
||||
case ClearColorEncoding::Uint:
|
||||
payload.colorUint = UintVec4(payload.colorUint.x(), payload.colorUint.y(), payload.colorUint.z(), 1u);
|
||||
break;
|
||||
case ClearColorEncoding::Float:
|
||||
payload.color = FloatVec4(payload.color.x(), payload.color.y(), payload.color.z(), 1.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Bool PendingClearMatchesTextureIdentity(const PendingClearKey& key, const TextureIdentity& identity) {
|
||||
return key.texture == identity.texture && key.textureLifetimeId == identity.lifetimeId;
|
||||
}
|
||||
|
||||
static Uint32 ResolveAttachmentBaseArrayLayer(TextureUploadTarget target) {
|
||||
if (!IsCubeMapFaceUploadTarget(target)) {
|
||||
return 0;
|
||||
@@ -93,18 +75,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return static_cast<Uint32>(target) - static_cast<Uint32>(TextureUploadTarget::CubeMapPositiveX);
|
||||
}
|
||||
|
||||
static Uint32 ResolveAttachmentBaseArrayLayer(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (attachment.IsLayered()) {
|
||||
return 0;
|
||||
}
|
||||
const TextureUploadTarget uploadTarget = attachment.GetTextureUploadTarget();
|
||||
if (!IsCubeMapFaceUploadTarget(uploadTarget)) {
|
||||
return static_cast<Uint32>(std::max(attachment.GetTextureLayer(), 0));
|
||||
}
|
||||
return ResolveAttachmentBaseArrayLayer(uploadTarget);
|
||||
}
|
||||
|
||||
// ResolveAttachmentLayerCount used to be duplicated here, reading attachment.GetSize().z()
|
||||
// raw - no ToVulkanLevelExtent remap for a 1D array, no six-faces arm for a cube map. That is
|
||||
// not a cosmetic difference: the count below is not key-only, it is written straight into
|
||||
@@ -113,65 +83,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// for good, while the very same queued clear cleared all six through the render pass's
|
||||
// LOAD_OP_CLEAR. The helper now lives once, in VkTextureManager.h beside ToVulkanLevelExtent.
|
||||
|
||||
static const MG_State::GLState::FramebufferAttachmentObject* GetClearableAttachment(
|
||||
const MG_State::GLState::FramebufferObject& drawFbo, FramebufferAttachmentType attachmentType) {
|
||||
if (attachmentType == FramebufferAttachmentType::None) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& attachment = drawFbo.GetAttachment(attachmentType);
|
||||
if (!attachment.IsTexture() || attachment.IsRenderbuffer()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &attachment;
|
||||
}
|
||||
|
||||
// The texture a pending clear is actually ABOUT. A clear issued through a GL texture view
|
||||
// (ARB_texture_view) targets the storage it views, so it must queue against - and be found
|
||||
// by - the storage texture; keying it on the view instead left the clear invisible to every
|
||||
// materialisation done through the parent's name (and vice versa), so the image stayed in
|
||||
// VK_IMAGE_LAYOUT_UNDEFINED and the readback was dropped as unreadable.
|
||||
static MG_State::GLState::ITextureObject* ClearStorageTextureOf(MG_State::GLState::ITextureObject* texture) {
|
||||
if (texture == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto& storageOwner = texture->GetViewStorageOwner();
|
||||
return storageOwner ? storageOwner.get() : texture;
|
||||
}
|
||||
|
||||
PendingClearKey VkClearManager::MakePendingClearKey(MG_State::GLState::ITextureObject* rawTexture, Uint32 mipLevel,
|
||||
Uint32 baseArrayLayer, Uint32 layerCount) {
|
||||
MG_State::GLState::ITextureObject* texture = ClearStorageTextureOf(rawTexture);
|
||||
if (rawTexture != nullptr && texture != rawTexture) {
|
||||
// The caller named a level and a layer of the VIEW; the key describes the STORAGE, so
|
||||
// both have to be shifted into its numbering (GL 4.6 core 8.18). Without this a clear
|
||||
// of a view's level 0 would collide with a clear of the storage's level 0 even when
|
||||
// the view opened onto level 1.
|
||||
mipLevel += static_cast<Uint32>(rawTexture->GetViewMinLevel());
|
||||
baseArrayLayer += static_cast<Uint32>(rawTexture->GetViewMinLayer());
|
||||
}
|
||||
return PendingClearKey {
|
||||
.texture = texture,
|
||||
.textureLifetimeId = texture ? texture->GetLifetimeId() : 0,
|
||||
.mipLevel = mipLevel,
|
||||
.baseArrayLayer = baseArrayLayer,
|
||||
.layerCount = layerCount,
|
||||
};
|
||||
}
|
||||
|
||||
PendingClearKey VkClearManager::MakePendingClearKey(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
MOBILEGL_ASSERT(attachment.IsTexture() && !attachment.IsRenderbuffer(),
|
||||
"MakePendingClearKey requires a texture framebuffer attachment");
|
||||
auto* texture = attachment.GetTexture().get();
|
||||
MOBILEGL_ASSERT(texture != nullptr, "MakePendingClearKey: texture attachment resolved to null");
|
||||
const Uint32 mipLevel = static_cast<Uint32>(std::max(attachment.GetTextureLevel(), 0));
|
||||
const Uint32 baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment);
|
||||
const Uint32 layerCount = ResolveAttachmentLayerCount(attachment);
|
||||
return MakePendingClearKey(texture, mipLevel, baseArrayLayer, layerCount);
|
||||
}
|
||||
|
||||
Bool VkClearManager::Initialize() {
|
||||
return true;
|
||||
}
|
||||
@@ -183,355 +94,4 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
TextureIdentity VkClearManager::MakeTextureIdentity(MG_State::GLState::ITextureObject* texture) {
|
||||
// Same rule as VkTextureManager::MakeTextureIdentity: a GL texture view is identified by
|
||||
// the storage it views. A clear posted against a view and one posted against its parent
|
||||
// target the same image, so they have to coalesce rather than queue independently.
|
||||
if (texture != nullptr) {
|
||||
const auto& storageOwner = texture->GetViewStorageOwner();
|
||||
if (storageOwner) {
|
||||
texture = storageOwner.get();
|
||||
}
|
||||
}
|
||||
return TextureIdentity {
|
||||
.texture = texture,
|
||||
.lifetimeId = texture ? texture->GetLifetimeId() : 0,
|
||||
};
|
||||
}
|
||||
|
||||
void VkClearManager::MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src) {
|
||||
dst.mask |= src.mask;
|
||||
if ((src.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
// The whole colour story travels together (same rule as
|
||||
// VkRenderPassManager::QueueRenderbufferClear): a glClearBufferiv/uiv
|
||||
// payload carries its value in colorInt/colorUint and its branch selector
|
||||
// in colorEncoding - dropping them here would leave the pending clear
|
||||
// reading as an all-zero float one.
|
||||
dst.color = src.color;
|
||||
dst.colorEncoding = src.colorEncoding;
|
||||
dst.colorInt = src.colorInt;
|
||||
dst.colorUint = src.colorUint;
|
||||
}
|
||||
if ((src.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
dst.depth = src.depth;
|
||||
}
|
||||
if ((src.mask & GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
dst.stencil = src.stencil;
|
||||
}
|
||||
}
|
||||
|
||||
void VkClearManager::ErasePendingClearsForTextureLocked(const TextureIdentity& identity) {
|
||||
Vector<PendingClearKey> keysToErase;
|
||||
keysToErase.reserve(m_pendingClears.size());
|
||||
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||
if (PendingClearMatchesTextureIdentity(it->first, identity)) {
|
||||
keysToErase.emplace_back(it->first);
|
||||
}
|
||||
}
|
||||
for (const auto& key : keysToErase) {
|
||||
m_pendingClears.erase(key);
|
||||
}
|
||||
m_aliveObjects.erase(identity);
|
||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Bool VkClearManager::LockTextureIdentityLocked(const TextureIdentity& identity,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||
outTexture.reset();
|
||||
if (identity.texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto aliveIt = m_aliveObjects.find(identity);
|
||||
if (aliveIt == m_aliveObjects.end()) {
|
||||
ErasePendingClearsForTextureLocked(identity);
|
||||
return false;
|
||||
}
|
||||
|
||||
outTexture = aliveIt->second.lock();
|
||||
if (!outTexture || outTexture.get() != identity.texture || outTexture->GetLifetimeId() != identity.lifetimeId) {
|
||||
ErasePendingClearsForTextureLocked(identity);
|
||||
outTexture.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkClearManager::LockTextureLocked(const PendingClearKey& key,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||
return LockTextureIdentityLocked(TextureIdentity{
|
||||
.texture = key.texture,
|
||||
.lifetimeId = key.textureLifetimeId,
|
||||
}, outTexture);
|
||||
}
|
||||
|
||||
void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferObject& drawFbo) {
|
||||
if (mask & GL_COLOR_BUFFER_BIT) {
|
||||
auto& drawbufs = drawFbo.GetDrawBuffers();
|
||||
// This should automatically work on default & offscreen FBO
|
||||
for (auto drawbuf: drawbufs) {
|
||||
const auto* attachment = GetClearableAttachment(drawFbo, drawbuf);
|
||||
if (!attachment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QueueClear({
|
||||
.mask = GL_COLOR_BUFFER_BIT,
|
||||
.color = clearPayload.color
|
||||
}, *attachment);
|
||||
|
||||
MGLOG_D("%s: %s (texture %d) - color = (%.2f, %.2f, %.2f, %.2f)", __func__,
|
||||
MG_Util::ConvertFramebufferAttachmentTypeToString(drawbuf).c_str(),
|
||||
attachment->GetTexture()->GetExternalIndex(),
|
||||
clearPayload.color[0], clearPayload.color[1], clearPayload.color[2], clearPayload.color[3]);
|
||||
}
|
||||
}
|
||||
|
||||
if (mask & GL_DEPTH_BUFFER_BIT) {
|
||||
const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Depth);
|
||||
if (attachment) {
|
||||
QueueClear({
|
||||
.mask = GL_DEPTH_BUFFER_BIT,
|
||||
.depth = clearPayload.depth,
|
||||
}, *attachment);
|
||||
|
||||
MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__,
|
||||
attachment->GetTexture()->GetExternalIndex(), clearPayload.depth);
|
||||
}
|
||||
}
|
||||
|
||||
if (mask & GL_STENCIL_BUFFER_BIT) {
|
||||
const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Stencil);
|
||||
if (attachment) {
|
||||
QueueClear({
|
||||
.mask = GL_STENCIL_BUFFER_BIT,
|
||||
.stencil = clearPayload.stencil,
|
||||
}, *attachment);
|
||||
|
||||
MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__,
|
||||
attachment->GetTexture()->GetExternalIndex(), clearPayload.stencil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||
if (clearPayload.mask == 0 || !texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& storageOwner = texture->GetViewStorageOwner();
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& storageTexture = storageOwner ? storageOwner : texture;
|
||||
const PendingClearKey key = MakePendingClearKey(storageTexture.get());
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_aliveObjects[MakeTextureIdentity(storageTexture.get())] = storageTexture;
|
||||
auto& pending = m_pendingClears[key];
|
||||
MergeClearPayload(pending, clearPayload);
|
||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (clearPayload.mask == 0 || !attachment.IsTexture() || attachment.IsRenderbuffer()) {
|
||||
return;
|
||||
}
|
||||
const auto texture = attachment.GetTexture();
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PendingClearKey key = MakePendingClearKey(attachment);
|
||||
// The alive entry must hold the STORAGE object, because the key names it:
|
||||
// LockTextureIdentityLocked cross-checks the two, and registering a view here under its
|
||||
// storage's identity made every lookup of this clear fail that check and silently report
|
||||
// "nothing pending" - which is how a clear issued through a view's framebuffer vanished.
|
||||
const auto& storageOwner = texture->GetViewStorageOwner();
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& storageTexture = storageOwner ? storageOwner : texture;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_aliveObjects[MakeTextureIdentity(storageTexture.get())] = storageTexture;
|
||||
auto& pending = m_pendingClears[key];
|
||||
MergeClearPayload(pending, clearPayload);
|
||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
|
||||
if (texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||
return false; // per-draw hot path: nothing pending anywhere
|
||||
}
|
||||
|
||||
texture = ClearStorageTextureOf(texture);
|
||||
const Uint64 lifetimeId = texture->GetLifetimeId();
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||
if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) {
|
||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||
return LockTextureLocked(it->first, liveTexture);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool VkClearManager::HasPendingClear(const PendingClearKey& key) {
|
||||
if (key.texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||
return false; // per-draw hot path: nothing pending anywhere
|
||||
}
|
||||
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_pendingClears.find(key) == m_pendingClears.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||
return LockTextureLocked(key, liveTexture);
|
||||
}
|
||||
|
||||
Bool VkClearManager::HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) {
|
||||
return false;
|
||||
}
|
||||
return HasPendingClear(MakePendingClearKey(attachment));
|
||||
}
|
||||
|
||||
Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload) {
|
||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||
return GetPendingClear(key, outPayload, liveTexture);
|
||||
}
|
||||
|
||||
Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
|
||||
if (key.texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||
return false; // per-draw hot path: nothing pending anywhere
|
||||
}
|
||||
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!LockTextureLocked(key, outTexture)) {
|
||||
return false;
|
||||
}
|
||||
auto it = m_pendingClears.find(key);
|
||||
if (it == m_pendingClears.end()) {
|
||||
outTexture.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
outPayload = it->second;
|
||||
MGLOG_D("%s: Got pending clear for texture@%p lifetime=%llu, mip=%u layer=%u count=%u mask=0x%x clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__,
|
||||
static_cast<void*>(key.texture),
|
||||
static_cast<unsigned long long>(key.textureLifetimeId),
|
||||
key.mipLevel, key.baseArrayLayer, key.layerCount,
|
||||
static_cast<Uint32>(outPayload.mask),
|
||||
outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3],
|
||||
outPayload.depth,
|
||||
outPayload.stencil);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkClearManager::GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
||||
ClearAttachmentPayload& outPayload) {
|
||||
if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) {
|
||||
MGLOG_D("%s: Failed getting pending clear for non-texture framebuffer attachment", __func__);
|
||||
return false;
|
||||
}
|
||||
return GetPendingClear(MakePendingClearKey(attachment), outPayload);
|
||||
}
|
||||
|
||||
Bool VkClearManager::GetPendingClears(MG_State::GLState::ITextureObject* texture,
|
||||
Vector<PendingClearEntry>& outEntries) {
|
||||
outEntries.clear();
|
||||
if (texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||
return false; // per-draw hot path: nothing pending anywhere
|
||||
}
|
||||
|
||||
texture = ClearStorageTextureOf(texture);
|
||||
const Uint64 lifetimeId = texture->GetLifetimeId();
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||
if (!LockTextureIdentityLocked(MakeTextureIdentity(texture), liveTexture)) {
|
||||
return false;
|
||||
}
|
||||
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
|
||||
if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) {
|
||||
outEntries.emplace_back(PendingClearEntry{.key = it->first, .payload = it->second});
|
||||
}
|
||||
}
|
||||
return !outEntries.empty();
|
||||
}
|
||||
|
||||
void VkClearManager::PopPendingClear(MG_State::GLState::ITextureObject* texture) {
|
||||
if (texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pendingCount.load(std::memory_order_relaxed) == 0) {
|
||||
return; // per-draw hot path: nothing pending anywhere
|
||||
}
|
||||
const TextureIdentity identity = MakeTextureIdentity(texture);
|
||||
MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex());
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
ErasePendingClearsForTextureLocked(identity);
|
||||
}
|
||||
|
||||
void VkClearManager::PopPendingClear(const PendingClearKey& key) {
|
||||
if (key.texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
auto it = m_pendingClears.find(key);
|
||||
if (it != m_pendingClears.end()) {
|
||||
m_pendingClears.erase(it);
|
||||
m_pendingCount.store(static_cast<Uint32>(m_pendingClears.size()), std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
MGLOG_D("%s: Pop pending clear for texture@%p lifetime=%llu mip=%u layer=%u count=%u", __func__,
|
||||
static_cast<void*>(key.texture), static_cast<unsigned long long>(key.textureLifetimeId),
|
||||
key.mipLevel, key.baseArrayLayer, key.layerCount);
|
||||
}
|
||||
|
||||
void VkClearManager::PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
|
||||
if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) {
|
||||
return;
|
||||
}
|
||||
PopPendingClear(MakePendingClearKey(attachment));
|
||||
}
|
||||
|
||||
SizeT VkClearManager::CollectGarbage() {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_gcCounter++;
|
||||
if (m_gcCounter != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vector<TextureIdentity> expiredTextures;
|
||||
expiredTextures.reserve(m_aliveObjects.size());
|
||||
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
|
||||
if (it->second.expired()) {
|
||||
expiredTextures.emplace_back(it->first);
|
||||
}
|
||||
}
|
||||
if (expiredTextures.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& identity : expiredTextures) {
|
||||
ErasePendingClearsForTextureLocked(identity);
|
||||
}
|
||||
return expiredTextures.size();
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -46,10 +46,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// expressed in whichever type matches (GL 4.6 core 15.2.3).
|
||||
VkClearColorValue MakeVkClearColorValue(const ClearAttachmentPayload& payload, Bool formatLacksAlpha);
|
||||
|
||||
// Applies that same rule in place, for the paths that have to bake it into the payload before
|
||||
// the destination is known.
|
||||
void ForceOpaqueClearAlpha(ClearAttachmentPayload& payload);
|
||||
|
||||
// vkCmdClearColorImage names the image, so the driver applies the destination format's transfer
|
||||
// function to whatever value it is handed. Every other write path in this backend goes through
|
||||
// the UNORM twin view while GL_FRAMEBUFFER_SRGB is off (ResolveSrgbAttachmentWriteFormat) and
|
||||
@@ -113,42 +109,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
class VkClearManager {
|
||||
public:
|
||||
static PendingClearKey MakePendingClearKey(const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
// Resolves a GL texture view to the storage it views before keying; see the definition.
|
||||
static PendingClearKey MakePendingClearKey(MG_State::GLState::ITextureObject* rawTexture, Uint32 mipLevel = 0,
|
||||
Uint32 baseArrayLayer = 0, Uint32 layerCount = 1);
|
||||
|
||||
Bool Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, const MG_State::GLState::FramebufferObject& drawFbo);
|
||||
void QueueClear(
|
||||
const ClearAttachmentPayload& clearPayload,
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture);
|
||||
void QueueClear(const ClearAttachmentPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
Bool HasPendingClear(MG_State::GLState::ITextureObject* texture);
|
||||
Bool HasPendingClear(const PendingClearKey& key);
|
||||
Bool HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload);
|
||||
Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||
Bool GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
||||
ClearAttachmentPayload& outPayload);
|
||||
Bool GetPendingClears(MG_State::GLState::ITextureObject* texture, Vector<PendingClearEntry>& outEntries);
|
||||
void PopPendingClear(MG_State::GLState::ITextureObject* texture);
|
||||
void PopPendingClear(const PendingClearKey& key);
|
||||
void PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
SizeT CollectGarbage();
|
||||
private:
|
||||
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
||||
static void MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src);
|
||||
void ErasePendingClearsForTextureLocked(const TextureIdentity& identity);
|
||||
Bool LockTextureIdentityLocked(const TextureIdentity& identity,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||
Bool LockTextureLocked(const PendingClearKey& key,
|
||||
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
|
||||
|
||||
Uint8 m_gcCounter = 0;
|
||||
public:
|
||||
// Lock-free probe for the consecutive-draw fast path: any pending clear
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -232,51 +232,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// targets): a default-framebuffer pass of one surface is not another's.
|
||||
void SetDefaultFramebufferTarget(Uint64 serial) {
|
||||
m_defaultFramebufferTarget = serial;
|
||||
m_rpFastValid = false;
|
||||
}
|
||||
// Destroys every cached pass and framebuffer (not the renderbuffers). The device must be
|
||||
// idle and no pass may be open: a surface target's image views are going away.
|
||||
void PurgeRenderPasses();
|
||||
|
||||
HashType ComputeHash(
|
||||
const MG_State::GLState::FramebufferObject& fbo,
|
||||
Uint32 swapchainImageIndex,
|
||||
Bool includePendingClear = true,
|
||||
Bool includeDefaultFboDepthStencil = true);
|
||||
// drawUsesDepthStencil: whether the operation about to run inside the pass
|
||||
// reads or writes the depth/stencil buffer (depth test or stencil test
|
||||
// enabled, or a depth/stencil clear). Only consulted for the DEFAULT
|
||||
// framebuffer: EGL undefines its ancillary buffers at every swap, so a
|
||||
// default-FBO pass whose draws provably never touch depth/stencil is
|
||||
// created WITHOUT the depth attachment - on a tiler that skips the whole
|
||||
// depth tile load AND store. The flavor only escalates: once a pass with
|
||||
// depth is active, later depth-less draws keep using it, and a depth-using
|
||||
// draw against a depth-less active pass resolves to a new (incompatible)
|
||||
// entry, which the caller's compatibility check turns into a pass split;
|
||||
// the new pass's depth loads DONT_CARE (content was undefined all along).
|
||||
//
|
||||
// Returns NULLPTR when this framebuffer cannot be represented as a Vulkan render pass at
|
||||
// all - a texture the texture manager declined to back (an unsupported format or sample
|
||||
// count), or an attachment view it cannot construct (a layer span the image has no room
|
||||
// for, a 3D image whose format was refused 2D-array compatibility). This used to be
|
||||
// unrepresentable: the function returned a reference, so the only thing the two fallible
|
||||
// calls it builds on could do was trip a MOBILEGL_ASSERT - which is compiled out of every
|
||||
// INFO build - and then dereference the null resource, or hand VK_NULL_HANDLE to
|
||||
// vkCreateFramebuffer. That took the whole process down (51 lost CTS records over 21
|
||||
// bodies, one runner restart each) where a declined draw is merely a wrong picture.
|
||||
//
|
||||
// EVERY caller must handle nullptr by dropping the operation, exactly as the draw path
|
||||
// already drops a draw whose sampler descriptor could not be resolved
|
||||
// (UniformManager::BindProgramUniformBuffers). The failure paths log MGLOG_E_ONCE
|
||||
// themselves, so a caller needs no message of its own.
|
||||
[[nodiscard]] RenderPassEntry* GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
||||
Uint32 swapchainImageIndex,
|
||||
Bool drawUsesDepthStencil = true);
|
||||
void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferObject& drawFbo);
|
||||
void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer);
|
||||
// Frame boundary hook: ages the render-pass cache and evicts long-unused
|
||||
// entries (their command buffers retired many frames ago).
|
||||
void OnPresent();
|
||||
@@ -308,28 +268,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
private:
|
||||
|
||||
// Per-draw fast-path memo for GetOrCreateRenderPass (dirty-flag state tracking): when the
|
||||
// framebuffer state is provably unchanged since the last resolution, the active render pass
|
||||
// is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch /
|
||||
// version change, swapchain rotation, any attachment image recreation (the two epochs),
|
||||
// or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed).
|
||||
Uint64 m_defaultFramebufferTarget = 0;
|
||||
Bool m_rpFastValid = false;
|
||||
const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr;
|
||||
// The FBO's never-reused lifetime id joins the raw pointer + Uint16 version:
|
||||
// a deleted FBO reallocated at the same address whose fresh setup performed
|
||||
// the same number of version bumps would otherwise compare equal (both count
|
||||
// from 0), serving the dead framebuffer's pass to the new object.
|
||||
Uint64 m_rpFastFboLifetimeId = 0;
|
||||
Uint16 m_rpFastFboVersion = 0;
|
||||
Uint32 m_rpFastSwapchainIndex = 0;
|
||||
Uint64 m_rpFastTexEpoch = 0;
|
||||
Uint64 m_rpFastRbEpoch = 0;
|
||||
Uint64 m_rpFastRenderPassHash = 0;
|
||||
// Whether the memoized entry carries a depth/stencil attachment; a
|
||||
// default-FBO resolution whose effective depth request differs must
|
||||
// miss the memo (the depth-less/depth-full flavors hash differently).
|
||||
Bool m_rpFastHadDepthStencil = false;
|
||||
|
||||
public:
|
||||
struct RenderbufferResource {
|
||||
@@ -359,13 +298,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void Destroy(VkDevice device, VmaAllocator allocator);
|
||||
};
|
||||
|
||||
// Public so the renderer's blit/copy/readback bindings can source renderbuffer
|
||||
// attachments the same way texture attachments go through the texture manager.
|
||||
RenderbufferResource* GetOrCreateRenderbufferResource(
|
||||
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||
Bool GetPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer,
|
||||
ClearAttachmentPayload& outPayload) const;
|
||||
|
||||
private:
|
||||
struct PendingRenderbufferClear {
|
||||
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||
@@ -414,8 +346,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// do not repeat vkGetPhysicalDeviceImageFormatProperties.
|
||||
UnorderedMap<VkFormat, VkSampleCountFlags> m_attachmentSampleCountsByFormat;
|
||||
|
||||
Bool HasPendingRenderbufferClear(
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment) const;
|
||||
void CollectRenderbufferGarbage();
|
||||
// Frame-boundary margin after which a resource last referenced by a retired
|
||||
// GL object (or superseded backing) is provably past every in-flight frame.
|
||||
|
||||
@@ -440,12 +440,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return vkSampler;
|
||||
}
|
||||
|
||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering, Uint32 viewLevelCount) {
|
||||
return GetOrCreateSamplerImpl(sampler, texture.GetFormat(), forceNearestFiltering, viewLevelCount);
|
||||
}
|
||||
|
||||
VkSampler VkSamplerManager::GetOrCreateSamplerFromParameters(const SamplerParameters& parameters,
|
||||
TextureInternalFormat format, Bool forceNearestFiltering, Uint32 viewLevelCount) {
|
||||
return GetOrCreateSamplerImpl(SamplerParametersSource{parameters}, format,
|
||||
|
||||
@@ -40,12 +40,6 @@ public:
|
||||
Bool Initialize(const InitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
// viewLevelCount is the mip-level count of the image view this sampler will be paired
|
||||
// with; 0 means "unknown, do not narrow". See GetOrCreateSampler for why it matters.
|
||||
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering = false,
|
||||
Uint32 viewLevelCount = 0);
|
||||
// The applier owns these values: effective bound/builtin sampler parameters and
|
||||
// the sampled texture view's format/range. No frontend object is constructed.
|
||||
VkSampler GetOrCreateSamplerFromParameters(const SamplerParameters& parameters,
|
||||
|
||||
@@ -866,271 +866,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return resourcePtr;
|
||||
}
|
||||
|
||||
VkImageView VkTextureManager::GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel) {
|
||||
TextureResource* resource = SyncTextureAndGetDescriptor(texture);
|
||||
if (resource == nullptr || resource->image == VK_NULL_HANDLE) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
// A GL texture view shares this resource with the texture it views, so it must not touch
|
||||
// perMipViews: that vector is indexed by mip level alone and holds views built with the
|
||||
// STORAGE texture's format and full layer range. Route it through the keyed attachment
|
||||
// cache instead, where its own window is part of the key.
|
||||
if (texture.IsTextureView()) {
|
||||
const TextureViewWindow window = ResolveTextureViewWindow(texture, *resource);
|
||||
return GetOrCreateAttachmentViewAtMipLevel(texture, mipLevel, window.baseArrayLayer, window.layerCount,
|
||||
window.viewType);
|
||||
}
|
||||
if (mipLevel >= resource->mipLevels) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (resource->perMipViews.size() != resource->mipLevels) {
|
||||
resource->perMipViews.resize(resource->mipLevels, VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
VkImageView& perMipView = resource->perMipViews[mipLevel];
|
||||
if (perMipView != VK_NULL_HANDLE) {
|
||||
return perMipView;
|
||||
}
|
||||
|
||||
perMipView = CreateImageView(resource->image, resource->format, resource->aspect, resource->viewType,
|
||||
mipLevel, 1, 0, resource->arrayLayers);
|
||||
if (perMipView == VK_NULL_HANDLE) {
|
||||
MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u", __func__, texture.GetExternalIndex(), mipLevel);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
return perMipView;
|
||||
}
|
||||
|
||||
VkImageView VkTextureManager::GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture,
|
||||
Uint32 mipLevel, Uint32 baseArrayLayer,
|
||||
Uint32 layerCount,
|
||||
VkImageViewType viewType) {
|
||||
TextureResource* resource = SyncTextureAndGetDescriptor(texture);
|
||||
if (resource == nullptr || resource->image == VK_NULL_HANDLE) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
// mipLevel and baseArrayLayer arrive in STORAGE space - every caller runs them through
|
||||
// ToStorageMipLevel / ToStorageArrayLayer at the GL attachment boundary. What a GL texture
|
||||
// view still contributes here is its own internal format, which may reinterpret the
|
||||
// storage's (GL 4.6 core table 8.21) and is what the attachment must actually be written
|
||||
// through.
|
||||
VkFormat viewFormatOverride = VK_FORMAT_UNDEFINED;
|
||||
if (texture.IsTextureView()) {
|
||||
viewFormatOverride = ResolveTextureViewWindow(texture, *resource).format;
|
||||
}
|
||||
if (mipLevel >= resource->mipLevels) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
// A 3D image has arrayLayers == 1 and keeps its GL layers on the z axis, so an attachment
|
||||
// view over it addresses SLICES through baseArrayLayer/layerCount: one slice for a
|
||||
// non-layered attachment (a 2D view) and the whole span for a layered one (a 2D_ARRAY view,
|
||||
// which is what a layered GL_TEXTURE_3D attachment plus a gl_Layer-writing geometry shader
|
||||
// means). BOTH spellings are legal only on a 2D-array-compatible image
|
||||
// (VUID-VkImageViewCreateInfo-image-04970 / -06723), which SyncTextureResource asks for and
|
||||
// may have had refused per format.
|
||||
//
|
||||
// The span is validated against the MIP's slice count, never against arrayLayers: a 3D
|
||||
// image's arrayLayers is 1 by construction, so measuring a layered span against it rejected
|
||||
// every layered 3D attachment - the null view that used to reach vkCreateFramebuffer.
|
||||
if (resource->viewType == VK_IMAGE_VIEW_TYPE_3D &&
|
||||
(viewType == VK_IMAGE_VIEW_TYPE_2D || viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY)) {
|
||||
const Uint32 sliceCount = std::max(resource->depth >> mipLevel, 1u);
|
||||
if ((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) == 0 ||
|
||||
layerCount == 0 || baseArrayLayer >= sliceCount || baseArrayLayer + layerCount > sliceCount) {
|
||||
// Not an error line: the render-pass builder turns the null view into one
|
||||
// MGLOG_E_ONCE and a skipped draw, which is the level this belongs at.
|
||||
MGLOG_D("%s: cannot name slice span [%u, %u) of 3D textureId=%d as viewType=%d (mip %u has %u "
|
||||
"slices, 2D-array-compatible=%d)",
|
||||
__func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(),
|
||||
static_cast<Int>(viewType), mipLevel, sliceCount,
|
||||
(int)((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0));
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
} else if (layerCount == 0 || baseArrayLayer >= resource->arrayLayers ||
|
||||
baseArrayLayer + layerCount > resource->arrayLayers) {
|
||||
MGLOG_D("%s: invalid layer span [%u, %u) for textureId=%d arrayLayers=%u",
|
||||
__func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(),
|
||||
resource->arrayLayers);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
const Bool framebufferSrgbEnabled =
|
||||
MG_Pipe::gPipeInputs.IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb);
|
||||
const VkFormat baseAttachmentFormat =
|
||||
viewFormatOverride != VK_FORMAT_UNDEFINED ? viewFormatOverride : resource->format;
|
||||
const VkFormat attachmentFormat =
|
||||
ResolveSrgbAttachmentWriteFormat(baseAttachmentFormat, framebufferSrgbEnabled);
|
||||
|
||||
// The shortcut back to the per-mip vector is only sound for the storage texture itself;
|
||||
// for a view every field below is part of what distinguishes it from its parent.
|
||||
if (viewFormatOverride == VK_FORMAT_UNDEFINED && attachmentFormat == resource->format &&
|
||||
baseArrayLayer == 0 && layerCount == resource->arrayLayers && viewType == resource->viewType) {
|
||||
return GetOrCreateViewAtMipLevel(texture, mipLevel);
|
||||
}
|
||||
|
||||
const TextureResource::AttachmentViewKey key{
|
||||
.mipLevel = mipLevel,
|
||||
.baseArrayLayer = baseArrayLayer,
|
||||
.layerCount = layerCount,
|
||||
.viewType = viewType,
|
||||
.viewFormat = attachmentFormat,
|
||||
};
|
||||
auto it = resource->attachmentViews.find(key);
|
||||
if (it == resource->attachmentViews.end()) {
|
||||
it = resource->attachmentViews.emplace(key, VK_NULL_HANDLE).first;
|
||||
}
|
||||
VkImageView& attachmentView = it->second;
|
||||
if (attachmentView != VK_NULL_HANDLE) {
|
||||
return attachmentView;
|
||||
}
|
||||
|
||||
attachmentView = CreateImageView(resource->image, attachmentFormat, resource->aspect, viewType,
|
||||
mipLevel, 1, baseArrayLayer, layerCount);
|
||||
if (attachmentView == VK_NULL_HANDLE) {
|
||||
MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u baseArrayLayer=%u layerCount=%u viewType=%d",
|
||||
__func__, texture.GetExternalIndex(), mipLevel, baseArrayLayer, layerCount, static_cast<Int>(viewType));
|
||||
resource->attachmentViews.erase(it);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
return attachmentView;
|
||||
}
|
||||
|
||||
// Builds (and caches) one sampled VkImageView over `resource`'s image for an arbitrary
|
||||
// window - the shared back end of every GL-texture-view sampled path. Keyed by the whole
|
||||
// window, which is what keeps a D24S8's depth-aspect view and its stencil-aspect view apart
|
||||
// in the same cache while both name the same image, the same levels and the same layers.
|
||||
VkImageView VkTextureManager::GetOrCreateWindowedSampledView(MG_State::GLState::ITextureObject& texture,
|
||||
TextureResource& resource,
|
||||
const TextureViewWindow& window) {
|
||||
const TextureResource::SampledImageViewKey key{
|
||||
.baseMipLevel = window.baseMipLevel,
|
||||
.levelCount = window.levelCount,
|
||||
.baseArrayLayer = window.baseArrayLayer,
|
||||
.layerCount = window.layerCount,
|
||||
.viewType = window.viewType,
|
||||
.format = window.format,
|
||||
.aspect = window.sampledAspect,
|
||||
.componentSwizzle = PackComponentSwizzle(window.components),
|
||||
};
|
||||
const auto existing = resource.alternateSampledViews.find(key);
|
||||
if (existing != resource.alternateSampledViews.end()) {
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
if (window.format != resource.format &&
|
||||
(resource.imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0) {
|
||||
MGLOG_E_ONCE("%s: textureId=%d needs a mutable-format image to be viewed as format=%d "
|
||||
"(image format=%d)",
|
||||
__func__, texture.GetExternalIndex(), static_cast<Int>(window.format),
|
||||
static_cast<Int>(resource.format));
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
const VkImageView view =
|
||||
CreateImageView(resource.image, window.format, window.sampledAspect, window.viewType,
|
||||
window.baseMipLevel, window.levelCount, window.baseArrayLayer, window.layerCount,
|
||||
&window.components);
|
||||
if (view == VK_NULL_HANDLE) {
|
||||
MGLOG_E_ONCE("%s: failed to create sampled view for textureId=%d format=%d aspect=0x%x "
|
||||
"mips=[%u,%u) layers=[%u,%u)",
|
||||
__func__, texture.GetExternalIndex(), static_cast<Int>(window.format),
|
||||
static_cast<Uint32>(window.sampledAspect), window.baseMipLevel,
|
||||
window.baseMipLevel + window.levelCount, window.baseArrayLayer,
|
||||
window.baseArrayLayer + window.layerCount);
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
resource.alternateSampledViews.emplace(key, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
VkImageView VkTextureManager::GetOrCreateSampledImageView(MG_State::GLState::ITextureObject& texture,
|
||||
VkFormat format) {
|
||||
TextureResource* resource = SyncTextureAndGetDescriptor(texture);
|
||||
if (resource == nullptr || resource->image == VK_NULL_HANDLE) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// A GL texture view never has a sampledView of its own on this resource - that one
|
||||
// belongs to the storage texture, with the storage texture's format, level range and
|
||||
// depth/stencil aspect. The window is the view's whole identity, so it always goes to the
|
||||
// keyed cache, even when the requested format happens to match the image's.
|
||||
if (texture.IsTextureView()) {
|
||||
TextureViewWindow window = ResolveTextureViewWindow(texture, *resource);
|
||||
if (format != VK_FORMAT_UNDEFINED) {
|
||||
window.format = format;
|
||||
}
|
||||
return GetOrCreateWindowedSampledView(texture, *resource, window);
|
||||
}
|
||||
|
||||
if (resource->sampledView == VK_NULL_HANDLE) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
if (format == VK_FORMAT_UNDEFINED || format == resource->format) {
|
||||
return resource->sampledView;
|
||||
}
|
||||
if (!AreSampledImageViewFormatsCompatible(resource->format, format)) {
|
||||
MGLOG_E_ONCE("%s: incompatible sampled image view format=%d for textureId=%d imageFormat=%d",
|
||||
__func__, static_cast<Int>(format), texture.GetExternalIndex(),
|
||||
static_cast<Int>(resource->format));
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
if ((resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0) {
|
||||
MGLOG_E_ONCE("%s: textureId=%d needs mutable image format=%d for sampled view format=%d",
|
||||
__func__, texture.GetExternalIndex(), static_cast<Int>(resource->format),
|
||||
static_cast<Int>(format));
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
const TextureResource::SampledImageViewKey key{
|
||||
.baseMipLevel = resource->sampledBaseMipLevel,
|
||||
.levelCount = resource->sampledLevelCount,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = resource->arrayLayers,
|
||||
.viewType = resource->viewType,
|
||||
.format = format,
|
||||
.aspect = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.componentSwizzle = PackComponentSwizzle(
|
||||
ResolveSampledViewComponents(texture, ResolveTextureFormatInfo(texture.GetFormat()))),
|
||||
};
|
||||
const auto existing = resource->alternateSampledViews.find(key);
|
||||
if (existing != resource->alternateSampledViews.end()) {
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
VkFormatProperties formatProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
|
||||
if ((formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == 0) {
|
||||
MGLOG_E_ONCE("%s: sampled image view format=%d lacks VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT "
|
||||
"for textureId=%d (available=0x%x)",
|
||||
__func__, static_cast<Int>(format), texture.GetExternalIndex(),
|
||||
static_cast<Uint32>(formatProperties.optimalTilingFeatures));
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
|
||||
const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo);
|
||||
const VkImageView view = CreateImageView(
|
||||
resource->image, format, VK_IMAGE_ASPECT_COLOR_BIT, resource->viewType,
|
||||
resource->sampledBaseMipLevel, resource->sampledLevelCount, 0, resource->arrayLayers,
|
||||
&sampledComponents, VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||
if (view == VK_NULL_HANDLE) {
|
||||
MGLOG_E_ONCE("%s: failed to create sampled image view textureId=%d imageFormat=%d viewFormat=%d",
|
||||
__func__, texture.GetExternalIndex(), static_cast<Int>(resource->format),
|
||||
static_cast<Int>(format));
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
resource->alternateSampledViews.emplace(key, view);
|
||||
MGLOG_D("%s: created sampled image view textureId=%d imageFormat=%d viewFormat=%d mip=[%u,%u)",
|
||||
__func__, texture.GetExternalIndex(), static_cast<Int>(resource->format),
|
||||
static_cast<Int>(format), resource->sampledBaseMipLevel,
|
||||
resource->sampledBaseMipLevel + resource->sampledLevelCount);
|
||||
return view;
|
||||
}
|
||||
|
||||
void VkTextureManager::StampTextureRecordingUse(MG_State::GLState::ITextureObject* texture) {
|
||||
if (texture == nullptr) {
|
||||
return;
|
||||
@@ -1141,32 +876,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
VkTextureManager::TextureResource* VkTextureManager::FindTextureResource(
|
||||
MG_State::GLState::ITextureObject& textureOrView) {
|
||||
auto it = m_textureResources.find(MakeTextureIdentity(&StorageTextureOf(textureOrView)));
|
||||
return it != m_textureResources.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
Bool VkTextureManager::HasLiveImage(MG_State::GLState::ITextureObject& textureOrView) const {
|
||||
auto it = m_textureResources.find(MakeTextureIdentity(&StorageTextureOf(textureOrView)));
|
||||
return it != m_textureResources.end() && it->second.image != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Bool VkTextureManager::HasPendingTexelUpload(MG_State::GLState::ITextureObject& textureOrView) const {
|
||||
auto& storage = StorageTextureOf(textureOrView);
|
||||
auto it = m_textureResources.find(MakeTextureIdentity(&storage));
|
||||
if (it == m_textureResources.end() || it->second.image == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
return it->second.syncedShapeVersion == storage.GetShapeVersion() &&
|
||||
it->second.syncedContentVersion != storage.GetContentVersion();
|
||||
}
|
||||
|
||||
Bool VkTextureManager::WasTextureWrittenThisRecording(MG_State::GLState::ITextureObject& textureOrView) const {
|
||||
auto it = m_textureResources.find(MakeTextureIdentity(&StorageTextureOf(textureOrView)));
|
||||
return it != m_textureResources.end() && it->second.lastRecordingWriteGeneration == m_recordingGeneration;
|
||||
}
|
||||
|
||||
void VkTextureManager::StampTextureRecordingWrite(MG_State::GLState::ITextureObject* texture) {
|
||||
if (texture == nullptr) {
|
||||
return;
|
||||
@@ -3598,90 +3307,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return storageOwner ? *storageOwner : texture;
|
||||
}
|
||||
|
||||
// The VkImageViewType a GL texture view's own target asks for. Deliberately derived from the
|
||||
// GL target rather than inherited from the storage image: a 2D view of a 2D-array texture is
|
||||
// a VK_IMAGE_VIEW_TYPE_2D over one layer, and a cube view of the same image is a
|
||||
// VK_IMAGE_VIEW_TYPE_CUBE over six - which is the whole reason table 8.20 lists those pairs.
|
||||
static VkImageViewType ResolveTextureViewImageViewType(TextureTarget target,
|
||||
VkImageViewType storageViewType) {
|
||||
switch (target) {
|
||||
case TextureTarget::Texture1D:
|
||||
return VK_IMAGE_VIEW_TYPE_1D;
|
||||
case TextureTarget::Texture1DArray:
|
||||
return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
|
||||
case TextureTarget::Texture2D:
|
||||
case TextureTarget::TextureRectangle:
|
||||
case TextureTarget::Texture2DMultisample:
|
||||
return VK_IMAGE_VIEW_TYPE_2D;
|
||||
case TextureTarget::Texture2DArray:
|
||||
case TextureTarget::Texture2DMultisampleArray:
|
||||
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
case TextureTarget::TextureCubeMap:
|
||||
return VK_IMAGE_VIEW_TYPE_CUBE;
|
||||
case TextureTarget::TextureCubeMapArray:
|
||||
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
||||
default:
|
||||
return storageViewType;
|
||||
}
|
||||
}
|
||||
|
||||
VkTextureManager::TextureViewWindow VkTextureManager::ResolveTextureViewWindow(
|
||||
MG_State::GLState::ITextureObject& texture, const TextureResource& resource) const {
|
||||
TextureViewWindow window{};
|
||||
window.format = resource.format;
|
||||
window.viewType = resource.viewType;
|
||||
window.baseArrayLayer = 0;
|
||||
window.layerCount = resource.arrayLayers;
|
||||
window.sampledAspect =
|
||||
ResolveSampledImageViewAspectMask(resource.aspect, texture.GetDepthStencilTextureMode());
|
||||
window.components = ResolveSampledViewComponents(texture, ResolveTextureFormatInfo(texture.GetFormat()));
|
||||
ResolveViewMipRange(texture, resource.mipLevels, window.baseMipLevel, window.levelCount);
|
||||
if (!texture.IsTextureView()) {
|
||||
return window;
|
||||
}
|
||||
|
||||
window.isTextureView = true;
|
||||
// GL 4.6 core 8.18: the view's TEXTURE_BASE_LEVEL / TEXTURE_MAX_LEVEL are relative to the
|
||||
// view, so ResolveViewMipRange above already clamped them against the view's own level
|
||||
// count (TextureObjectView reports it); shifting by TEXTURE_VIEW_MIN_LEVEL puts them back
|
||||
// into the storage image's numbering.
|
||||
window.baseMipLevel += static_cast<Uint32>(texture.GetViewMinLevel());
|
||||
window.baseArrayLayer = static_cast<Uint32>(texture.GetViewMinLayer());
|
||||
window.layerCount = static_cast<Uint32>(texture.GetViewNumLayers());
|
||||
window.viewType = ResolveTextureViewImageViewType(texture.GetTarget(), resource.viewType);
|
||||
// The view's OWN internalformat, which may reinterpret the storage's (table 8.21).
|
||||
const VkFormat viewFormat = ResolveTextureFormatInfo(texture.GetFormat()).format;
|
||||
if (viewFormat != VK_FORMAT_UNDEFINED) {
|
||||
window.format = viewFormat;
|
||||
}
|
||||
// Recomputed against the view's own format: a depth/stencil storage viewed as
|
||||
// depth/stencil still has to honour the VIEW's DEPTH_STENCIL_TEXTURE_MODE, which is the
|
||||
// one parameter Better Clouds deliberately sets differently on the two names.
|
||||
window.sampledAspect =
|
||||
ResolveSampledImageViewAspectMask(GetAspectMaskForFormat(window.format) != VK_IMAGE_ASPECT_NONE
|
||||
? GetAspectMaskForFormat(window.format)
|
||||
: resource.aspect,
|
||||
texture.GetDepthStencilTextureMode());
|
||||
|
||||
// Clamp to what the image actually has; a malformed view must degrade to an empty range
|
||||
// rather than reach vkCreateImageView with an out-of-bounds subresource.
|
||||
if (window.baseMipLevel >= resource.mipLevels) {
|
||||
window.baseMipLevel = resource.mipLevels - 1;
|
||||
window.levelCount = 1;
|
||||
} else {
|
||||
window.levelCount = std::min(window.levelCount, resource.mipLevels - window.baseMipLevel);
|
||||
}
|
||||
if (window.levelCount == 0) window.levelCount = 1;
|
||||
if (window.baseArrayLayer >= resource.arrayLayers) {
|
||||
window.baseArrayLayer = resource.arrayLayers - 1;
|
||||
window.layerCount = 1;
|
||||
} else {
|
||||
window.layerCount = std::min(window.layerCount, resource.arrayLayers - window.baseArrayLayer);
|
||||
}
|
||||
if (window.layerCount == 0) window.layerCount = 1;
|
||||
return window;
|
||||
}
|
||||
|
||||
// The extra VkImageCreateFlags a GL texture view needs on the image it views. Recorded
|
||||
// BEFORE the storage texture is synced (see SyncTextureAndGetDescriptor) so the very first
|
||||
// resolve of a view already creates - or recreates and copies forward - an image the view can
|
||||
|
||||
@@ -565,14 +565,6 @@ public:
|
||||
Bool isTextureView = false;
|
||||
};
|
||||
|
||||
// The four component swizzles packed into one value, for the sampled-view cache key.
|
||||
static Uint32 PackComponentSwizzle(const VkComponentMapping& components) {
|
||||
return (static_cast<Uint32>(components.r) & 0xFFu) | ((static_cast<Uint32>(components.g) & 0xFFu) << 8) |
|
||||
((static_cast<Uint32>(components.b) & 0xFFu) << 16) |
|
||||
((static_cast<Uint32>(components.a) & 0xFFu) << 24);
|
||||
}
|
||||
TextureViewWindow ResolveTextureViewWindow(MG_State::GLState::ITextureObject& texture,
|
||||
const TextureResource& resource) const;
|
||||
// Records what a GL texture view needs of the image it views, so the next sync of the
|
||||
// STORAGE texture creates (or recreates and copies forward) an image the view can be built
|
||||
// over. See m_viewRequestedImageFlags for why this is lazy rather than unconditional.
|
||||
@@ -583,11 +575,6 @@ public:
|
||||
// VkImageFormatListCreateInfo the image is created with.
|
||||
void AppendViewRequestedFormats(const MG_State::GLState::ITextureObject& storageTexture,
|
||||
Vector<VkFormat>& outFormats) const;
|
||||
// Builds (and caches, keyed by the whole window) one sampled VkImageView over a storage
|
||||
// image. Shared back end of every GL-texture-view sampled path.
|
||||
VkImageView GetOrCreateWindowedSampledView(MG_State::GLState::ITextureObject& texture,
|
||||
TextureResource& resource, const TextureViewWindow& window);
|
||||
|
||||
TextureResource* SyncTextureAndGetDescriptor(
|
||||
MG_State::GLState::ITextureObject& texture);
|
||||
// P5f (fm): THE HANDLE-KEYED ARM of SyncTextureAndGetDescriptor. Under an active transport
|
||||
@@ -664,11 +651,6 @@ public:
|
||||
const auto it = m_wireTextureResources.find(key);
|
||||
return it != m_wireTextureResources.end() && it->second.image != VK_NULL_HANDLE ? &it->second : nullptr;
|
||||
}
|
||||
VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
|
||||
VkImageView GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
Uint32 baseArrayLayer, Uint32 layerCount,
|
||||
VkImageViewType viewType);
|
||||
VkImageView GetOrCreateSampledImageView(MG_State::GLState::ITextureObject& texture, VkFormat format);
|
||||
void UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject* texture,
|
||||
Uint32 writtenMipLevel,
|
||||
@@ -693,20 +675,6 @@ public:
|
||||
resource.lastRecordingWriteGeneration = m_recordingGeneration;
|
||||
}
|
||||
void StampTextureRecordingWrite(MG_State::GLState::ITextureObject* texture);
|
||||
// Upload-ordering queries (VulkanRenderer::OrderPendingUploadAfterRecording). None of them
|
||||
// syncs, so nothing pending is uploaded by asking. All answer for the STORAGE texture.
|
||||
//
|
||||
// Uploads are recorded into a batch submitted AHEAD of the frame's recording
|
||||
// (FlushPendingUploads), so an upload into an image the open recording already WROTE lands
|
||||
// before that write, which then overwrites the newer texels. (An image the recording only
|
||||
// read keeps the old trade: its earlier reads see the newer texels.)
|
||||
Bool HasLiveImage(MG_State::GLState::ITextureObject& textureOrView) const;
|
||||
// The next sync would record client texels into the image that is live now: same shape (a
|
||||
// shape change mints a new image instead) and content moved since the last sync.
|
||||
Bool HasPendingTexelUpload(MG_State::GLState::ITextureObject& textureOrView) const;
|
||||
Bool WasTextureWrittenThisRecording(MG_State::GLState::ITextureObject& textureOrView) const;
|
||||
// The live resource, or null.
|
||||
TextureResource* FindTextureResource(MG_State::GLState::ITextureObject& textureOrView);
|
||||
// `depthStencilTextureMode` is the texture's GL_DEPTH_STENCIL_TEXTURE_MODE; it only decides
|
||||
// anything for an image that carries both aspects. Defaulted so the call sites that have no
|
||||
// texture in hand keep the depth-aspect answer they have always given.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -243,38 +243,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
|
||||
const DrawCmdParam& drawParams,
|
||||
const IndexBufferView* pIndexBufferView = nullptr);
|
||||
void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer,
|
||||
const RenderPassEntry& compatibleRenderPassEntry);
|
||||
|
||||
enum class ScissoredClearPrep {
|
||||
NotNeeded, // scissor covers the whole target — take the deferred whole-surface path instead
|
||||
NoOp, // nothing to clear (degenerate target or empty scissor rect)
|
||||
Ready, // a render pass is active; record vkCmdClearAttachments with the returned rect
|
||||
};
|
||||
ScissoredClearPrep PrepareScissoredClear(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
VkClearRect& outClearRect);
|
||||
|
||||
void Clear(GLbitfield mask);
|
||||
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
||||
void ClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLint* value);
|
||||
void ClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
||||
void ClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void BlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFbo,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFbo,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
void CopyImageSubData(const CopyImageEndpoint& srcEndpoint,
|
||||
@@ -311,16 +292,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 dstMipLevel, Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX,
|
||||
GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout,
|
||||
VkImageLayout dstRestoreLayout, Bool stencilAspect);
|
||||
// One depth/stencil copy whose source and destination are the same format and whose region
|
||||
// is a same-size, same-layer rectangle: the aspect named by `aspect` is staged through a
|
||||
// device-local buffer so the destination's other aspect cannot be overwritten by the
|
||||
// packed native word a whole-image copy moves.
|
||||
Bool CopyDepthStencilAspectThroughBuffer(FrameContext::FrameData& frame, VkImage srcImage,
|
||||
VkImage dstImage, VkFormat format, Uint32 srcMipLevel,
|
||||
Uint32 srcBaseArrayLayer, Uint32 dstMipLevel,
|
||||
Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX,
|
||||
GLint dstY, GLsizei width, GLsizei height,
|
||||
VkImageAspectFlagBits aspect);
|
||||
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
|
||||
// Map a GL bottom-left-origin rectangle into the display-oriented swapchain image.
|
||||
// Quarter-turn surface transforms swap the copy extent's axes.
|
||||
@@ -338,10 +309,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
GLsizei width, GLsizei height, GLenum destinationFormat,
|
||||
GLenum destinationType, SizeT destinationRowStride,
|
||||
Uint8* destinationPixels);
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
||||
void GetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
||||
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
||||
GLsizei bufSize, GLvoid* pixels);
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void DispatchComputeIndirect(GLintptr indirect);
|
||||
void MemoryBarrier(GLbitfield barriers);
|
||||
@@ -354,8 +321,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
const PhysicalDevice& GetPhysicalDevice() const;
|
||||
VkInstance GetInstance() const;
|
||||
Bool IsDrawIndirectCountExtensionEnabled() const;
|
||||
|
||||
// GL fence support, expressed in queue-submission indices backed by
|
||||
// real VkFences. A GL fence captures GetSyncPointSubmitIndex() at
|
||||
// creation: the index of the submission that will carry the commands
|
||||
@@ -578,11 +543,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
};
|
||||
MultisampleResolveScratchImage m_msResolveScratch;
|
||||
// Returns a scratch image at least `extent` in size with exactly `format`, transitioned to
|
||||
// TRANSFER_DST and ready to be resolved into. Null image on failure (the caller then falls
|
||||
// back to the direct resolve).
|
||||
Bool AcquireMultisampleResolveScratchImage(VkCommandBuffer commandBuffer, VkFormat format,
|
||||
VkExtent2D extent);
|
||||
void DestroyMultisampleResolveScratchImage();
|
||||
|
||||
struct DeferredDepthMipmapCleanup {
|
||||
@@ -881,14 +841,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
void CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload);
|
||||
void RecordScissoredClearBuffer(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
GLenum buffer, GLint drawbuffer,
|
||||
const ClearAttachmentPayload& clearPayload,
|
||||
const VkClearRect& clearRect);
|
||||
|
||||
// ---- Submission fence tracking (GL sync objects) ----
|
||||
// One record per vkQueueSubmit still in flight, in ascending submit
|
||||
// order. Present/readback submissions reference the frame slot's
|
||||
@@ -1588,74 +1540,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ShutdownDepthMipmapResources();
|
||||
void CollectDeferredDepthMipmapCleanup(Uint32 frameIndex);
|
||||
void DestroyDeferredDepthMipmapCleanup();
|
||||
Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame,
|
||||
MG_State::GLState::FramebufferObject& readFbo,
|
||||
MG_State::GLState::FramebufferObject& drawFbo,
|
||||
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLenum filter);
|
||||
// Clears one layer of a colour image through a throwaway render pass whose entire content
|
||||
// is its LOAD_OP_CLEAR. Two callers, both of which a transfer clear cannot serve: a z
|
||||
// slice of a VK_IMAGE_TYPE_3D image (vkCmdClearColorImage cannot name one), and a
|
||||
// MULTISAMPLE image (which carries no TRANSFER_DST usage at all). `finalLayout` is the
|
||||
// layout the caller already tracks for the whole image, so this never has to touch
|
||||
// resource->layout.
|
||||
Bool ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
|
||||
Uint32 depthSlice, const VkClearValue& clearValue,
|
||||
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
// Records the texture's queued clears. By default the texture is synced first, which
|
||||
// uploads any pending texels BEFORE the clear - right only for texels written before it.
|
||||
// `uploadPendingTexels = false` leaves them pending (OrderPendingUploadAfterRecording).
|
||||
Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture,
|
||||
Bool uploadPendingTexels = true);
|
||||
// Texture uploads run in a batch submitted ahead of the frame's recording, so the upload
|
||||
// the next sync of `texture` records would land BEFORE a write already recorded into the
|
||||
// same image (a render, clear, blit or copy), and before a clear still queued for it (such
|
||||
// a clear predates the texels: SettleTexelsBeforeQueuedClear). When either holds, this
|
||||
// records the queued clear without the texels and submits the recording, so the upload
|
||||
// follows both. An image the recording only READ keeps the old trade: no submit, and its
|
||||
// earlier reads see the newer texels. It may end the render pass and replace
|
||||
// frame.commandBuffer (recording again on return), so call it only where the caller holds
|
||||
// no render-pass or command-buffer state across it. Returns whether it submitted.
|
||||
Bool OrderPendingUploadAfterRecording(FrameContext::FrameData& frame,
|
||||
MG_State::GLState::ITextureObject& texture);
|
||||
// Called for each texture a glClear/glClearBuffer* is about to queue a clear on: uploads
|
||||
// whatever texels are still pending (and mints the image if there is none), so that any
|
||||
// texel pending when the clear is materialized was written after it.
|
||||
void SettleTexelsBeforeQueuedClear(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
const MG_State::GLState::FramebufferAttachmentObject& attachment);
|
||||
// The multisample arm of the above. Split out rather than branched inline because it
|
||||
// shares none of the transfer path: a multisample image carries no TRANSFER_DST usage, so
|
||||
// neither the TRANSFER_DST transition nor vkCmdClearColorImage is legal on one.
|
||||
Bool MaterializeMultisamplePendingClear(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::ITextureObject& texture,
|
||||
VkTextureManager::TextureResource& resource,
|
||||
const Vector<PendingClearEntry>& pendingClears);
|
||||
Bool MaterializePendingClearForRenderbuffer(
|
||||
VkCommandBuffer commandBuffer,
|
||||
const SharedPtr<MG_State::GLState::RenderbufferObject>& renderbuffer);
|
||||
// The default framebuffer's twin of the two above. It cannot go through
|
||||
// MaterializePendingClearForTexture: the default FBO's colour attachment is a
|
||||
// placeholder texture object, and syncing THAT would clear a texture image nobody
|
||||
// presents instead of the acquired swapchain image.
|
||||
Bool MaterializePendingClearForDefaultFramebuffer(VkCommandBuffer commandBuffer,
|
||||
MG_State::GLState::FramebufferObject& fbo,
|
||||
FramebufferAttachmentType attachmentType);
|
||||
// Its depth/stencil half: a different image (the swapchain's depth/stencil twin), a
|
||||
// different clear command and per-aspect masking.
|
||||
Bool MaterializePendingDepthStencilClearForDefaultFramebuffer(
|
||||
VkCommandBuffer commandBuffer, const MG_State::GLState::FramebufferAttachmentObject& attachment,
|
||||
const ClearAttachmentPayload& payload);
|
||||
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
||||
|
||||
public:
|
||||
// Submits whatever is recorded and waits for it. The CPU is about to read memory
|
||||
// a shader wrote (a mapped shader storage buffer), and coherent host-visible
|
||||
// storage only guarantees visibility once the work that produced it has retired.
|
||||
Bool FinishPendingGpuWork();
|
||||
|
||||
private:
|
||||
|
||||
void ShutdownSwapchain();
|
||||
|
||||
@@ -1191,39 +1191,6 @@ namespace {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(FramebufferTest, WidenedDrawBufferIsIdentifiedPerDrawBufferSlotNotPerAttachmentPoint) {
|
||||
ScopedBackendOverride backend(MakeUnique<ThreeChannelAttachmentBackend>(/*substituted=*/true));
|
||||
|
||||
// Complementary's `composite` framebuffer again: draw buffer 0 is a natively renderable
|
||||
// RGBA8, draw buffer 1 is the widened RGB8_SNORM. Only the second may be doctored.
|
||||
GLuint framebuffer = 0;
|
||||
GLuint colortex7 = 0;
|
||||
GLuint colortex1 = 0;
|
||||
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &colortex7);
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &colortex1);
|
||||
MG_Impl::GLImpl::TextureStorage2D(colortex7, 1, GL_RGBA8, 4, 4);
|
||||
MG_Impl::GLImpl::TextureStorage2D(colortex1, 1, GL_RGB8_SNORM, 4, 4);
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, colortex7, 0);
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT1, colortex1, 0);
|
||||
|
||||
auto& framebufferObject = MG_State::pGLContext->GetFramebufferObject(framebuffer);
|
||||
ASSERT_NE(framebufferObject, nullptr);
|
||||
framebufferObject->SetDrawBuffer(0, FramebufferAttachmentType::Color0);
|
||||
framebufferObject->SetDrawBuffer(1, FramebufferAttachmentType::Color1);
|
||||
|
||||
EXPECT_EQ(MG_Backend::DirectGLES::FramebufferImpl::ComputeAlphaWidenedDrawBufferMask(*framebufferObject),
|
||||
1u << 1);
|
||||
|
||||
// Swapping the draw-buffer array moves the bit with the SLOT, not with the attachment point:
|
||||
// glColorMaski and glClearBufferfv both address slots.
|
||||
framebufferObject->SetDrawBuffer(0, FramebufferAttachmentType::Color1);
|
||||
framebufferObject->SetDrawBuffer(1, FramebufferAttachmentType::Color0);
|
||||
EXPECT_EQ(MG_Backend::DirectGLES::FramebufferImpl::ComputeAlphaWidenedDrawBufferMask(*framebufferObject),
|
||||
1u << 0);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(FramebufferTest, DrawIntoAWidenedDrawBufferReachesTheDriverWithAlphaWritesMaskedOff) {
|
||||
ScopedRenderStateDriverStubs driver;
|
||||
MG_Backend::DirectGLES::FramebufferImpl::g_alphaWidenedDrawBufferMask = 1u << 1;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
// P5 b1: MGPipeResourceTrackerInstance(), so the split probe case can ask the PRODUCTION
|
||||
// tracker for a buffer's handle instead of minting one by hand.
|
||||
#include <MG_Impl/Pipe/ResourceTracker.h>
|
||||
#include <MG_Impl/Pipe/Verb/VerbPort.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/ProgramFactory.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/UniformManager.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h>
|
||||
@@ -373,12 +374,20 @@ TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensions) {
|
||||
|
||||
TEST(DirectGLESSanity, ProvidesNamedFramebufferBlitForDirectStateAccess) {
|
||||
MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
|
||||
// P13 W6: the backend applies a blit (BlitFramebuffer, which the record sink calls with the
|
||||
// verb's framebuffer handles); the named-framebuffer DSA entries are the verb port's alone -
|
||||
// they record a verb naming the framebuffer, and the backend never sees a frontend object.
|
||||
const auto& funcs = backend.GetBackendFunctions().GL;
|
||||
|
||||
EXPECT_NE(funcs.ClearNamedFramebufferfv, nullptr);
|
||||
EXPECT_NE(funcs.ClearNamedFramebufferfi, nullptr);
|
||||
EXPECT_NE(funcs.BlitFramebuffer, nullptr);
|
||||
EXPECT_NE(funcs.BlitNamedFramebuffer, nullptr);
|
||||
EXPECT_EQ(funcs.ClearNamedFramebufferfv, nullptr);
|
||||
EXPECT_EQ(funcs.BlitNamedFramebuffer, nullptr);
|
||||
|
||||
MobileGL::MG_Backend::GlobalBackendFunctionsTable table = backend.GetBackendFunctions();
|
||||
MobileGL::MG_Record::InstallMonolithVerbPort(table);
|
||||
EXPECT_NE(table.GL.ClearNamedFramebufferfv, nullptr);
|
||||
EXPECT_NE(table.GL.ClearNamedFramebufferfi, nullptr);
|
||||
EXPECT_NE(table.GL.BlitFramebuffer, nullptr);
|
||||
EXPECT_NE(table.GL.BlitNamedFramebuffer, nullptr);
|
||||
}
|
||||
|
||||
TEST(DirectGLESSanity, RewritesBaseInstanceBuiltinForEsslVertexShaders) {
|
||||
|
||||
@@ -37,20 +37,3 @@ target_link_libraries(
|
||||
|
||||
gtest_discover_tests(TextureViewTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
|
||||
add_executable(
|
||||
VkClearManagerTest
|
||||
VkClearManagerTest.cpp
|
||||
)
|
||||
|
||||
target_include_directories(VkClearManagerTest PRIVATE
|
||||
${MGL_ROOT}/include
|
||||
${MGL_ROOT}/MobileGL
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
VkClearManagerTest PRIVATE
|
||||
GTest::gtest_main
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
gtest_discover_tests(VkClearManagerTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
|
||||
Reference in New Issue
Block a user