mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Magma] key wire pipelines by exact render-pass compatibility identities
This commit is contained in:
@@ -198,7 +198,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.programHash, sizeof(payload.programHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (payload.wireRenderPassCompatibilityId != 0) {
|
||||
constexpr Uint64 wireDomain = 0x5749524552504b59ull;
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wireDomain, sizeof(wireDomain)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.wireRenderPassCompatibilityId,
|
||||
sizeof(payload.wireRenderPassCompatibilityId)));
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.sampleShadingEnable, sizeof(payload.sampleShadingEnable)));
|
||||
@@ -275,7 +285,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static_cast<unsigned long long>(payload.programHash));
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
m_cache.emplace(hash, PipelineCacheEntry{pipeline, payload.programHash, payload.renderPass,
|
||||
m_cache.emplace(hash, PipelineCacheEntry{pipeline, payload.programHash,
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Wire pipelines outlive their creator pass and
|
||||
// may serve another compatible pass in flight.
|
||||
// Never evict them by a recycled native handle.
|
||||
payload.wireRenderPassCompatibilityId != 0 ? VK_NULL_HANDLE :
|
||||
#endif
|
||||
payload.renderPass,
|
||||
m_frameCounter});
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
HashType vertexInputHash = 0;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Nonzero is an exact, renderer-lifetime compatibility identity.
|
||||
// renderPass remains the valid creation handle, not the cache key.
|
||||
Uint64 wireRenderPassCompatibilityId = 0;
|
||||
#endif
|
||||
Uint32 colorAttachmentCount = 1;
|
||||
VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
// glEnable(GL_SAMPLE_SHADING) + glMinSampleShading, which Vulkan bakes into the
|
||||
|
||||
@@ -5290,7 +5290,7 @@ void main() {
|
||||
VkPipeline VulkanRenderer::GetOrCreatePipelineWithInput(GLenum mode, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, ProgramFactory::CompileOptionFlags transformFlags,
|
||||
const VertexInputStateFactory::BackendVertexInputState& vis, const RenderPassEntry& renderPassEntry,
|
||||
Bool primitiveRestartEnable) {
|
||||
Bool primitiveRestartEnable, Uint64 wireRenderPassCompatibilityId) {
|
||||
#endif
|
||||
Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip;
|
||||
if (programObj.stages.empty()) {
|
||||
@@ -5313,7 +5313,11 @@ void main() {
|
||||
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
|
||||
#endif
|
||||
const Uint64 vertexLayoutHash = vis.layoutHash;
|
||||
const Uint64 renderPassHash = renderPassEntry.hash;
|
||||
const Uint64 renderPassHash =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
wireRenderPassCompatibilityId != 0 ? 0 :
|
||||
#endif
|
||||
renderPassEntry.hash;
|
||||
// The pipeline-relevant subset only: glViewport / glScissor / glBlendColor / glStencilMask
|
||||
// and friends are dynamic state or not pipeline state at all, and keying the memo on the
|
||||
// all-state counter made any of them evict a perfectly good VkPipeline. The memo compares
|
||||
@@ -5357,6 +5361,9 @@ void main() {
|
||||
if (entry.pipeline != VK_NULL_HANDLE && entry.mode == mode &&
|
||||
entry.programHash == programObj.hash && entry.vertexInputHash == vertexLayoutHash &&
|
||||
entry.renderPassHash == renderPassHash &&
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
entry.wireRenderPassCompatibilityId == wireRenderPassCompatibilityId &&
|
||||
#endif
|
||||
entry.pipelineStateHash == pipelineStateHash &&
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
entry.renderStateCso == renderStateCso &&
|
||||
@@ -5650,6 +5657,9 @@ void main() {
|
||||
.vertexInputHash = vertexLayoutHash,
|
||||
.pipelineLayout = programObj.pipelineLayout,
|
||||
.renderPass = renderPassEntry.renderPass,
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
.wireRenderPassCompatibilityId = wireRenderPassCompatibilityId,
|
||||
#endif
|
||||
.colorAttachmentCount = renderPassEntry.colorAttachmentCount,
|
||||
.rasterizationSamples = renderPassEntry.sampleCount,
|
||||
// ARB_sample_shading. Dropped on a device without sampleRateShading rather than
|
||||
@@ -5785,7 +5795,11 @@ void main() {
|
||||
const auto& drawFboBinding =
|
||||
MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MOBILEGL_ASSERT(wireFbo != nullptr || drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null");
|
||||
#else
|
||||
MOBILEGL_ASSERT(drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null");
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const Bool isDefaultDrawFbo = wireFbo ? wireFbo->IsDefault : drawFboBinding->IsDefaultFramebuffer();
|
||||
Array<FramebufferAttachmentType, MG_Pipe::kMGPipeMaxColorAttachments> wireDrawBuffers{};
|
||||
@@ -6073,6 +6087,9 @@ void main() {
|
||||
entry.programHash = programObj.hash;
|
||||
entry.vertexInputHash = vertexLayoutHash;
|
||||
entry.renderPassHash = renderPassHash;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
entry.wireRenderPassCompatibilityId = wireRenderPassCompatibilityId;
|
||||
#endif
|
||||
entry.pipelineStateHash = pipelineStateHash;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
entry.renderStateCso = renderStateCso;
|
||||
@@ -6807,6 +6824,9 @@ void main() {
|
||||
if (entry.pipeline != VK_NULL_HANDLE && entry.mode == mode &&
|
||||
entry.programHash == programObj.hash && entry.vertexInputHash == vaoLayoutHash &&
|
||||
entry.renderPassHash == snap.renderPassHash &&
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
entry.wireRenderPassCompatibilityId == 0 &&
|
||||
#endif
|
||||
entry.pipelineStateHash == pipelineStateHash &&
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
entry.renderStateCso == renderStateCso &&
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include "VkSamplerManager.h"
|
||||
#include "VkTextureManager.h"
|
||||
#include "VkTimerQueryManager.h"
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include "WireRenderPassCompatibility.h"
|
||||
#endif
|
||||
#include "MG_Util/Math/VectorTypes.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
@@ -509,6 +512,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ClearAllWireDrawPassCaches();
|
||||
Vector<Vector<WireDrawPassCacheEntry>> m_wireDrawPassCaches;
|
||||
WireDrawPassKey m_wireDrawPassKey;
|
||||
WireRenderPassCompatibilityTable m_wireRenderPassCompatibility;
|
||||
struct WireRetiredObjects {
|
||||
Uint64 submitIndex = 0;
|
||||
UniquePtr<RenderPassEntry> drawPass;
|
||||
@@ -527,7 +531,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkPipeline GetOrCreatePipelineWithInput(GLenum mode, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, ProgramFactory::CompileOptionFlags transformFlags,
|
||||
const VertexInputStateFactory::BackendVertexInputState& vis, const RenderPassEntry& renderPassEntry,
|
||||
Bool primitiveRestartEnable);
|
||||
Bool primitiveRestartEnable, Uint64 wireRenderPassCompatibilityId = 0);
|
||||
|
||||
void CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
@@ -926,6 +930,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint64 programHash = 0;
|
||||
Uint64 vertexInputHash = 0;
|
||||
Uint64 renderPassHash = 0;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Uint64 wireRenderPassCompatibilityId = 0;
|
||||
#endif
|
||||
// The PRE-HANDLE arm's key component (P2 brief D12.1), and 0 in every entry the
|
||||
// handle arm mints. VALUE hash of the pipeline-relevant fixed-function state (see
|
||||
// ComputePipelineStateHash), not the monotonic pipeline-state version: the version
|
||||
|
||||
@@ -408,8 +408,15 @@
|
||||
created.hash = created.compatibilityHash = XXH64(colors.data(), colors.size() * sizeof(colors[0]), passHash);
|
||||
}
|
||||
auto& pass = *m_wireDrawPass;
|
||||
WireRenderPassCompatibilityKey pipelinePassKey{};
|
||||
for (const auto& attachment : attachments)
|
||||
pipelinePassKey.attachmentFormatsAndSamples.push_back(
|
||||
(static_cast<Uint64>(attachment.format) << 32) | static_cast<Uint32>(attachment.samples));
|
||||
for (const auto& color : colors) pipelinePassKey.colorReferences.push_back(color.attachment);
|
||||
pipelinePassKey.depthReference = depth.attachment;
|
||||
const Uint64 compatibilityId = m_wireRenderPassCompatibility.Intern(pipelinePassKey);
|
||||
const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass,
|
||||
ResolvePrimitiveRestartEnable(aspects, indices));
|
||||
ResolvePrimitiveRestartEnable(aspects, indices), compatibilityId);
|
||||
if (pipeline == VK_NULL_HANDLE) return false;
|
||||
for (Uint32 i = 0; i < vertexSlices.size(); ++i)
|
||||
vkCmdBindVertexBuffers(frame.commandBuffer, i, 1, &vertexSlices[i].buffer, &vertexSlices[i].offset);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include "../VkIncludes.h"
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// The wire draw pass has one graphics subpass, no input attachments,
|
||||
// multiview or extensions. Its Vulkan pipeline compatibility is formats,
|
||||
// sample counts and the ordered attachment references, not native handles,
|
||||
// image identity, dimensions, load/store operations or image layouts.
|
||||
struct WireRenderPassCompatibilityKey {
|
||||
Vector<Uint64> attachmentFormatsAndSamples;
|
||||
Vector<Uint32> colorReferences;
|
||||
Uint32 depthReference = VK_ATTACHMENT_UNUSED;
|
||||
Bool operator==(const WireRenderPassCompatibilityKey&) const = default;
|
||||
};
|
||||
|
||||
struct WireRenderPassCompatibilityHash {
|
||||
SizeT operator()(const WireRenderPassCompatibilityKey& key) const {
|
||||
SizeT hash = 0;
|
||||
const auto mix = [&](Uint64 value) {
|
||||
hash ^= static_cast<SizeT>(value) + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2);
|
||||
};
|
||||
mix(key.attachmentFormatsAndSamples.size());
|
||||
for (const auto value : key.attachmentFormatsAndSamples) mix(value);
|
||||
mix(key.colorReferences.size());
|
||||
for (const auto value : key.colorReferences) mix(value);
|
||||
mix(key.depthReference);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
class WireRenderPassCompatibilityTable {
|
||||
public:
|
||||
Uint64 Intern(const WireRenderPassCompatibilityKey& key) {
|
||||
if (const auto found = m_ids.find(key); found != m_ids.end()) return found->second;
|
||||
// No reset/eviction: an ID cannot be reused while a pipeline factory
|
||||
// or a renderer memo can still hold its previous meaning.
|
||||
MOBILEGL_ASSERT(m_nextId != 0, "wire render-pass compatibility ID exhausted");
|
||||
const Uint64 id = m_nextId++;
|
||||
m_ids.emplace(key, id);
|
||||
return id;
|
||||
}
|
||||
private:
|
||||
UnorderedMap<WireRenderPassCompatibilityKey, Uint64, WireRenderPassCompatibilityHash> m_ids;
|
||||
Uint64 m_nextId = 1; // zero selects the native-handle pipeline domain
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -11,6 +11,9 @@
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/PipelineFactory.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/ProgramFactory.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireRenderPassCompatibility.h>
|
||||
#endif
|
||||
|
||||
using namespace MobileGL;
|
||||
using MobileGL::MG_Backend::DirectVulkan::PipelineFactory;
|
||||
@@ -457,3 +460,58 @@ TEST(ReflectedReadsInstanceIndexBuiltin, FalseForAnEmptyModule) {
|
||||
SpvReflectShaderModule emptyModule{};
|
||||
EXPECT_FALSE(ProgramFactory::ReflectedReadsInstanceIndexBuiltin(emptyModule));
|
||||
}
|
||||
|
||||
|
||||
TEST(WirePipelineCompatibility, ExactCompatibilitySurvivesNativeObjectTurnover) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
using namespace MobileGL::MG_Backend::DirectVulkan;
|
||||
WireRenderPassCompatibilityTable table;
|
||||
WireRenderPassCompatibilityKey a{{(Uint64(VK_FORMAT_R8G8B8A8_UNORM) << 32) | VK_SAMPLE_COUNT_1_BIT}, {0},
|
||||
VK_ATTACHMENT_UNUSED};
|
||||
const Uint64 first = table.Intern(a);
|
||||
ASSERT_NE(first, 0u);
|
||||
auto b = a;
|
||||
b.attachmentFormatsAndSamples[0] = (Uint64(VK_FORMAT_R8G8B8A8_SRGB) << 32) | VK_SAMPLE_COUNT_1_BIT;
|
||||
EXPECT_NE(table.Intern(b), first);
|
||||
b = a;
|
||||
b.attachmentFormatsAndSamples[0] = (Uint64(VK_FORMAT_R8G8B8A8_UNORM) << 32) | VK_SAMPLE_COUNT_4_BIT;
|
||||
EXPECT_NE(table.Intern(b), first);
|
||||
b = a;
|
||||
b.colorReferences = {VK_ATTACHMENT_UNUSED, 0};
|
||||
EXPECT_NE(table.Intern(b), first);
|
||||
b = a;
|
||||
b.depthReference = 0;
|
||||
EXPECT_NE(table.Intern(b), first);
|
||||
// Native pass/image turnover and swapchain changes never reset this table.
|
||||
EXPECT_EQ(table.Intern(a), first);
|
||||
#else
|
||||
GTEST_SKIP() << "wire pipeline compatibility requires disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(WirePipelineCompatibility, HashUsesCompatibilityIdentityAndSeparatesNativeDomain) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
using namespace MobileGL::MG_Backend::DirectVulkan;
|
||||
VulkanRendererConfig config{};
|
||||
config.DisablePipelineCache = true;
|
||||
// Hash-only unit: DisablePipelineCache prevents all device calls, and no
|
||||
// pipeline is created. These tokens are never passed to a Vulkan driver.
|
||||
PipelineFactory factory(reinterpret_cast<VkDevice>(uintptr_t{1}), config);
|
||||
PipelineFactory::PipelineCreatePayload payload{};
|
||||
payload.renderPass = (VkRenderPass)(uintptr_t{1});
|
||||
payload.wireRenderPassCompatibilityId = 1;
|
||||
const auto a = factory.ComputeHash(payload);
|
||||
payload.renderPass = (VkRenderPass)(uintptr_t{2});
|
||||
EXPECT_EQ(factory.ComputeHash(payload), a) << "compatible passes must reuse the pipeline across native handles";
|
||||
payload.wireRenderPassCompatibilityId = 2;
|
||||
EXPECT_NE(factory.ComputeHash(payload), a) << "a recycled native handle must not alias incompatible passes";
|
||||
payload.renderPass = (VkRenderPass)(uintptr_t{1});
|
||||
payload.wireRenderPassCompatibilityId = 0;
|
||||
EXPECT_NE(factory.ComputeHash(payload), a) << "native handles and wire identities are different domains";
|
||||
const auto native = factory.ComputeHash(payload);
|
||||
payload.renderPass = (VkRenderPass)(uintptr_t{2});
|
||||
EXPECT_NE(factory.ComputeHash(payload), native);
|
||||
#else
|
||||
GTEST_SKIP() << "wire pipeline compatibility requires disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user