mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Perf] (DirectVulkan): drop per-draw weak-ptr locks, re-resolves and rebuilt masks from the sampled-texture and vertex paths
This commit is contained in:
@@ -209,6 +209,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &unsupportedAttribMask, sizeof(unsupportedAttribMask)));
|
||||
entry.layoutHash = XXH64_digest(m_hashState);
|
||||
entry.attributeLocationMask = 0;
|
||||
for (const auto& attribute : entry.attributes) {
|
||||
if (attribute.location < 32u) {
|
||||
entry.attributeLocationMask |= (1u << attribute.location);
|
||||
}
|
||||
}
|
||||
entry.bindingBufferKeys = std::move(bindingBufferKeys);
|
||||
entry.bindingBaseOffsets = std::move(bindingBaseOffsets);
|
||||
entry.bindingAttributeLocations = std::move(bindingAttributeLocations);
|
||||
|
||||
@@ -50,6 +50,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// absent from `attributes`, so without this mask the draw path cannot tell them apart from
|
||||
// a genuinely disabled array and would silently feed the shader the current attribute value.
|
||||
Uint32 unsupportedAttribMask = 0;
|
||||
// Bitmask of `attributes[i].location` - the draw path needs it up to
|
||||
// three times per draw, so it is baked once at build time.
|
||||
Uint32 attributeLocationMask = 0;
|
||||
VkPipelineVertexInputStateCreateInfo state{
|
||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
|
||||
};
|
||||
|
||||
@@ -966,6 +966,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
|
||||
.target = TrackedAttachmentTarget::Texture,
|
||||
.texture = att.GetTexture(),
|
||||
.textureRaw = att.GetTexture().get(),
|
||||
.textureMipLevel = attachmentMipLevel,
|
||||
.finalLayout = desc.finalLayout,
|
||||
});
|
||||
@@ -1142,6 +1143,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
|
||||
.target = TrackedAttachmentTarget::Texture,
|
||||
.texture = selectedDepthStencilAttachment->GetTexture(),
|
||||
.textureRaw = selectedDepthStencilAttachment->GetTexture().get(),
|
||||
.textureMipLevel = attachmentMipLevel,
|
||||
.finalLayout = depthAttachmentDescription.finalLayout,
|
||||
});
|
||||
|
||||
@@ -42,6 +42,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct TrackedAttachmentLayoutInfo {
|
||||
TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture;
|
||||
WeakPtr<MG_State::GLState::ITextureObject> texture;
|
||||
// Identity-compare shortcut for the per-draw "does the active pass use
|
||||
// this sampled texture" probe: comparing this against a LIVE texture's
|
||||
// address needs no weak_ptr::lock (two refcount atomics per probe).
|
||||
// May dangle once the texture dies - compare only, never dereference.
|
||||
MG_State::GLState::ITextureObject* textureRaw = nullptr;
|
||||
WeakPtr<MG_State::GLState::RenderbufferObject> renderbuffer;
|
||||
Uint32 textureMipLevel = 0;
|
||||
Uint32 swapchainImageIndex = 0;
|
||||
|
||||
@@ -835,16 +835,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static_assert(kMaxVertexAttribs <= ProgramFactory::VkProgramObject::kMaxVertexInputLocations,
|
||||
"vertexInputTypes is indexed by vertex attribute location");
|
||||
|
||||
static Uint32 BuildVertexInputAttributeMask(const Vector<VkVertexInputAttributeDescription>& attributes) {
|
||||
Uint32 attributeMask = 0;
|
||||
for (const auto& attribute : attributes) {
|
||||
if (attribute.location < kMaxVertexAttribs) {
|
||||
attributeMask |= (1u << attribute.location);
|
||||
}
|
||||
}
|
||||
return attributeMask;
|
||||
}
|
||||
|
||||
static Bool TryGetCurrentVertexAttributeFormat(GLenum glType, VkFormat& outFormat) {
|
||||
switch (glType) {
|
||||
case GL_FLOAT:
|
||||
@@ -995,8 +985,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (trackedAttachment.target != TrackedAttachmentTarget::Texture) {
|
||||
continue;
|
||||
}
|
||||
const auto trackedTexture = trackedAttachment.texture.lock();
|
||||
if (trackedTexture && trackedTexture.get() == &texture) {
|
||||
// Raw identity compare (see textureRaw): the caller's texture is
|
||||
// live, so a dangling tracked pointer can never equal its address
|
||||
// unless the allocator reused it - and that false positive merely
|
||||
// ends the render pass early, never misses a genuine use.
|
||||
if (trackedAttachment.textureRaw == &texture) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2931,7 +2924,7 @@ void main() {
|
||||
// the GetCurrentProgram + GetOrCreateProgram hash lookup every draw.
|
||||
auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
|
||||
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
|
||||
const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vertexInputState.attributes);
|
||||
const Uint32 vertexInputAttribMask = vertexInputState.attributeLocationMask;
|
||||
const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask;
|
||||
|
||||
const auto bindingCount = vertexInputState.bindings.size() + static_cast<SizeT>(std::popcount(missingAttribMask));
|
||||
@@ -3923,7 +3916,7 @@ void main() {
|
||||
}
|
||||
#endif
|
||||
|
||||
const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vis.attributes);
|
||||
const Uint32 vertexInputAttribMask = vis.attributeLocationMask;
|
||||
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
|
||||
const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask;
|
||||
auto& patchedAttributes = m_patchedAttributesScratch;
|
||||
@@ -4468,16 +4461,52 @@ void main() {
|
||||
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
|
||||
const auto& program = *MG_State::pGLContext->GetCurrentProgram();
|
||||
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
|
||||
const auto* programObjPtr = &m_programFactory->GetOrCreateProgram(program, transformFlags);
|
||||
// Sampling a colour render target through the driver's implicit-LOD path faults the GPU on
|
||||
// Adreno 650 (see ForceExplicitLod0SamplePass); ask for the explicit-LOD variant when doing
|
||||
// so cannot change a texel, i.e. when every sampler this program reads is pinned to a
|
||||
// single mip level.
|
||||
if (UniformManager::ProgramSamplesOnlySingleLevelTextures(program, *programObjPtr)) {
|
||||
transformFlags |= ProgramFactory::CompileOptionBit::ExplicitLod0Sampling;
|
||||
programObjPtr = &m_programFactory->GetOrCreateProgram(program, transformFlags);
|
||||
// single mip level. The probe walks every sampler binding, so its verdict is memoized
|
||||
// under the sampled-set memo's key plus the sampled textures' params-version sum (level
|
||||
// range and filter changes live there); the previous draw's texture list is valid for the
|
||||
// sum exactly when that key matches (same program, same binds).
|
||||
{
|
||||
const Uint64 lodProgramLifetimeId = program.GetLifetimeId();
|
||||
const Uint32 lodProgramVersion = program.GetBackendStateVersion();
|
||||
const Uint64 lodBindGeneration = MG_State::pGLContext->GetTextureBindGeneration();
|
||||
Bool lodMemoHit = false;
|
||||
if (m_lastLodDecisionValid && m_lastSampledSetValid &&
|
||||
m_lastLodProgramLifetimeId == lodProgramLifetimeId &&
|
||||
m_lastLodProgramVersion == lodProgramVersion &&
|
||||
m_lastLodBindGeneration == lodBindGeneration && m_lastLodBaseFlags == transformFlags &&
|
||||
m_lastSampledSetProgramLifetimeId == lodProgramLifetimeId &&
|
||||
m_lastSampledSetProgramVersion == lodProgramVersion &&
|
||||
m_lastSampledSetBindGeneration == lodBindGeneration) {
|
||||
Uint64 paramsSum = 0;
|
||||
for (const auto* sampledTexture : m_sampledTexturesScratch) {
|
||||
if (sampledTexture != nullptr) {
|
||||
paramsSum += sampledTexture->GetTextureParamsVersion();
|
||||
}
|
||||
}
|
||||
if (paramsSum == m_lastLodParamsSum) {
|
||||
transformFlags = m_lastLodResultFlags;
|
||||
lodMemoHit = true;
|
||||
}
|
||||
}
|
||||
if (!lodMemoHit) {
|
||||
const ProgramFactory::CompileOptionFlags baseFlags = transformFlags;
|
||||
const auto& baseProgramObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
|
||||
if (UniformManager::ProgramSamplesOnlySingleLevelTextures(program, baseProgramObj)) {
|
||||
transformFlags |= ProgramFactory::CompileOptionBit::ExplicitLod0Sampling;
|
||||
}
|
||||
m_lastLodDecisionValid = true;
|
||||
m_lastLodProgramLifetimeId = lodProgramLifetimeId;
|
||||
m_lastLodProgramVersion = lodProgramVersion;
|
||||
m_lastLodBindGeneration = lodBindGeneration;
|
||||
m_lastLodBaseFlags = baseFlags;
|
||||
m_lastLodResultFlags = transformFlags;
|
||||
m_lastLodParamsSum = 0; // filled below once the sampled set is known
|
||||
}
|
||||
}
|
||||
const auto& programObj = *programObjPtr;
|
||||
const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
|
||||
|
||||
// Begin command recording if not yet
|
||||
if (!frame.isCommandRecording) {
|
||||
@@ -4522,6 +4551,18 @@ void main() {
|
||||
m_lastSampledSetTransformFlags = transformFlags;
|
||||
m_lastSampledSetBindGeneration = bindGeneration;
|
||||
}
|
||||
// Complete a freshly-made LOD decision (see above): its params sum
|
||||
// can only be taken once the sampled set is known. A genuine
|
||||
// all-zero sum merely re-probes next draw.
|
||||
if (m_lastLodDecisionValid && m_lastLodParamsSum == 0) {
|
||||
Uint64 paramsSum = 0;
|
||||
for (const auto* sampledTexture : sampledTextures) {
|
||||
if (sampledTexture != nullptr) {
|
||||
paramsSum += sampledTexture->GetTextureParamsVersion();
|
||||
}
|
||||
}
|
||||
m_lastLodParamsSum = paramsSum;
|
||||
}
|
||||
}
|
||||
MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s",
|
||||
program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(),
|
||||
@@ -4545,7 +4586,10 @@ void main() {
|
||||
activeRenderPass = nullptr;
|
||||
}
|
||||
Bool needSampledTextureTransitions = false;
|
||||
for (auto* sampledTexture : sampledTextures) {
|
||||
auto& sampledResources = m_sampledResourcesScratch;
|
||||
sampledResources.assign(sampledTextures.size(), nullptr);
|
||||
for (SizeT sampledIndex = 0; sampledIndex < sampledTextures.size(); ++sampledIndex) {
|
||||
auto* sampledTexture = sampledTextures[sampledIndex];
|
||||
if (!sampledTexture) {
|
||||
continue;
|
||||
}
|
||||
@@ -4554,6 +4598,7 @@ void main() {
|
||||
MOBILEGL_ASSERT(textureResource != nullptr,
|
||||
"%s: SyncTextureAndGetDescriptor failed for textureId=%d",
|
||||
__func__, sampledTexture->GetExternalIndex());
|
||||
sampledResources[sampledIndex] = textureResource;
|
||||
MGLOG_D("SetupDraw: sampled textureId=%d layout(before)=%s(%d)",
|
||||
sampledTexture->GetExternalIndex(), VkImageLayoutToString(textureResource->layout),
|
||||
static_cast<Int>(textureResource->layout));
|
||||
@@ -4592,10 +4637,23 @@ void main() {
|
||||
activeRenderPass = nullptr;
|
||||
}
|
||||
|
||||
for (auto* sampledTexture : sampledTextures) {
|
||||
for (SizeT sampledIndex = 0; sampledIndex < sampledTextures.size(); ++sampledIndex) {
|
||||
auto* sampledTexture = sampledTextures[sampledIndex];
|
||||
if (!sampledTexture) {
|
||||
continue;
|
||||
}
|
||||
// Fast path: the first loop already resolved this texture, nothing
|
||||
// is pending against it, and its layout is still sampleable (the
|
||||
// layout re-check covers an EndRenderPass between the loops having
|
||||
// rewritten an attachment's layout). Skipping the materialize +
|
||||
// transition + re-resolve chain here is the difference between one
|
||||
// pointer read and three calls per sampled texture per draw.
|
||||
if (auto* fastResource = sampledResources[sampledIndex];
|
||||
fastResource != nullptr && !m_clearManager->HasPendingClear(sampledTexture) &&
|
||||
IsValidSampledImageLayout(fastResource->layout)) {
|
||||
m_textureManager->StampResourceRecordingUse(*fastResource);
|
||||
continue;
|
||||
}
|
||||
const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sampledTexture);
|
||||
MOBILEGL_ASSERT(clearReady, "%s: MaterializePendingClearForTexture failed for textureId=%d",
|
||||
__func__, sampledTexture->GetExternalIndex());
|
||||
@@ -4659,7 +4717,7 @@ void main() {
|
||||
// Every genuinely disabled attribute the shader reads must have a current-value type we can
|
||||
// synthesize a binding for; otherwise the upload below would push a null payload.
|
||||
const Uint32 missingAttribMask =
|
||||
activeAttribMask & ~BuildVertexInputAttributeMask(vertexInputState.attributes);
|
||||
activeAttribMask & ~vertexInputState.attributeLocationMask;
|
||||
for (Uint32 location = 0; location < kMaxVertexAttribs; ++location) {
|
||||
if ((missingAttribMask & (1u << location)) == 0) continue;
|
||||
|
||||
|
||||
@@ -507,9 +507,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
ProgramFactory::CompileOptionFlags m_lastSampledSetTransformFlags = {};
|
||||
Uint64 m_lastSampledSetBindGeneration = 0;
|
||||
|
||||
// Memo for the per-draw explicit-LOD-0 eligibility probe
|
||||
// (ProgramSamplesOnlySingleLevelTextures): same key family as the
|
||||
// sampled-set memo, plus the sampled textures' params-version sum so a
|
||||
// level-range or filter change re-probes. On a hit the resolved
|
||||
// transform flags are reused, which also collapses the two
|
||||
// GetOrCreateProgram lookups into one.
|
||||
Bool m_lastLodDecisionValid = false;
|
||||
Uint64 m_lastLodProgramLifetimeId = 0;
|
||||
Uint32 m_lastLodProgramVersion = 0;
|
||||
Uint64 m_lastLodBindGeneration = 0;
|
||||
Uint64 m_lastLodParamsSum = 0;
|
||||
ProgramFactory::CompileOptionFlags m_lastLodBaseFlags = {};
|
||||
ProgramFactory::CompileOptionFlags m_lastLodResultFlags = {};
|
||||
|
||||
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
||||
// draw call and must not allocate.
|
||||
Vector<MG_State::GLState::ITextureObject*> m_sampledTexturesScratch;
|
||||
// Parallel to m_sampledTexturesScratch, refilled by every SetupDraw's
|
||||
// first sampled-texture loop: the resolved backend resources, so the
|
||||
// post-transition loop can skip re-resolving textures whose layout is
|
||||
// already sampleable.
|
||||
Vector<VkTextureManager::TextureResource*> m_sampledResourcesScratch;
|
||||
Vector<MG_State::GLState::ITextureObject*> m_storageImageTexturesScratch;
|
||||
Vector<VkBuffer> m_vertexBuffersScratch;
|
||||
Vector<VkDeviceSize> m_vertexOffsetsScratch;
|
||||
|
||||
Reference in New Issue
Block a user