mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend] (Disaggregated): consume Magma application buffers from server resources
This commit is contained in:
@@ -365,6 +365,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::Initialize() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
VkBufferManager::RegisterWireResourceOps();
|
||||
#endif
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -282,6 +282,68 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return cache;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// The verb's handles identify server stores. Readback orders GPU-produced
|
||||
// command/count bytes before CPU tier expansion; never inspect a client binding.
|
||||
void DrawWireIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
||||
GLsizei stride, Bool indexed, Bool counted = false, GLintptr countOffset = 0) {
|
||||
if (drawcount <= 0) return;
|
||||
auto& buffers = pVulkanRenderer->GetWireBufferManager();
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
if (counted) {
|
||||
Uint32 count = 0;
|
||||
if (countOffset < 0 || !buffers.ReadWireBuffer(state.VerbIndirectParameterBuffer,
|
||||
static_cast<Uint64>(countOffset), sizeof(count), &count)) return;
|
||||
drawcount = static_cast<GLsizei>(std::min<Uint32>(count, static_cast<Uint32>(drawcount)));
|
||||
if (!drawcount) return;
|
||||
}
|
||||
const SizeT commandSize = indexed ? sizeof(DrawElementsIndirectCommand) : sizeof(DrawArraysIndirectCommand);
|
||||
if (stride == 0) stride = static_cast<GLsizei>(commandSize);
|
||||
if (stride < static_cast<GLsizei>(commandSize)) return;
|
||||
const Uint64 byteCount = static_cast<Uint64>(stride) * (drawcount - 1) + commandSize;
|
||||
if (byteCount > std::numeric_limits<SizeT>::max()) return;
|
||||
Vector<Uint8> bytes(static_cast<SizeT>(byteCount));
|
||||
if (!buffers.ReadWireBuffer(state.VerbIndirectBuffer, reinterpret_cast<Uint64>(indirect),
|
||||
byteCount, bytes.data())) return;
|
||||
if (indexed) {
|
||||
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
|
||||
if (indexSize != 1 && indexSize != 2 && indexSize != 4) return;
|
||||
Vector<DrawIndexedCmdParam> params(static_cast<SizeT>(drawcount));
|
||||
MultiDrawIndexedCmd payload{};
|
||||
payload.mode = mode;
|
||||
payload.indexBufferView.indexType = type;
|
||||
payload.drawCount = static_cast<Uint32>(drawcount);
|
||||
payload.pParams = params.data();
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawElementsIndirectCommand command{};
|
||||
Memcpy(&command, bytes.data() + static_cast<SizeT>(i) * stride, sizeof(command));
|
||||
params[i] = {command.count, command.instanceCount, command.firstIndex,
|
||||
command.baseVertex, command.baseInstance};
|
||||
const Uint64 end = (static_cast<Uint64>(command.firstIndex) + command.count) * indexSize;
|
||||
if (end > std::numeric_limits<SizeT>::max()) return;
|
||||
payload.indexBufferView.indexByteSize = std::max<SizeT>(payload.indexBufferView.indexByteSize,
|
||||
static_cast<SizeT>(end));
|
||||
}
|
||||
pVulkanRenderer->MultiDrawElements(payload);
|
||||
} else {
|
||||
Vector<DrawCmdParam> params(static_cast<SizeT>(drawcount));
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawArraysIndirectCommand command{};
|
||||
Memcpy(&command, bytes.data() + static_cast<SizeT>(i) * stride, sizeof(command));
|
||||
params[i].vertexCount = command.count;
|
||||
params[i].instanceCount = command.instanceCount;
|
||||
params[i].firstVertex = command.first;
|
||||
params[i].firstInstance = command.baseInstance;
|
||||
}
|
||||
MultiDrawCmd payload{};
|
||||
payload.mode = mode;
|
||||
payload.drawCount = static_cast<Uint32>(drawcount);
|
||||
payload.pParams = params.data();
|
||||
pVulkanRenderer->MultiDrawArrays(payload);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
MG_State::GLState::ProgramObject* TryGetDirectVulkanProgram(GLuint program) {
|
||||
if (!MGB_CTX->ValidateProgramName(program)) {
|
||||
return nullptr;
|
||||
@@ -402,7 +464,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RejectWireLegacyBuffer();
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
DrawWireIndirect(mode, type, indirect, drawcount, stride, true);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirect called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "DirectVulkan::MultiDrawElementsIndirect called with null GL context");
|
||||
@@ -410,7 +475,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RejectWireLegacyBuffer();
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
DrawWireIndirect(mode, 0, indirect, drawcount, stride, false);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirect called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "DirectVulkan::MultiDrawArraysIndirect called with null GL context");
|
||||
@@ -464,7 +532,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RejectWireLegacyBuffer();
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
DrawWireIndirect(mode, type, indirect, maxdrawcount, stride, true, true, drawcount);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirectCount called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "DirectVulkan::MultiDrawElementsIndirectCount called with null GL context");
|
||||
@@ -473,7 +544,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RejectWireLegacyBuffer();
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
DrawWireIndirect(mode, 0, indirect, maxdrawcount, stride, false, true, drawcount);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirectCount called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "DirectVulkan::MultiDrawArraysIndirectCount called with null GL context");
|
||||
@@ -548,7 +622,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RejectWireLegacyBuffer();
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
DrawWireIndirect(mode, type, indirect, 1, 0, true);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsIndirect called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "DirectVulkan::DrawElementsIndirect called with null GL context");
|
||||
@@ -610,7 +687,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RejectWireLegacyBuffer();
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
DrawWireIndirect(mode, 0, indirect, 1, 0, false);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArraysIndirect called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "DirectVulkan::DrawArraysIndirect called with null GL context");
|
||||
@@ -681,7 +761,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
void DispatchComputeIndirect(GLintptr indirect) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RejectWireLegacyBuffer();
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
Uint32 groups[3]{};
|
||||
if (indirect >= 0 && pVulkanRenderer->GetWireBufferManager().ReadWireBuffer(
|
||||
MG_Pipe::MGPipeApplier().VerbDispatchIndirectBuffer,
|
||||
static_cast<Uint64>(indirect), sizeof(groups), groups))
|
||||
pVulkanRenderer->DispatchCompute(groups[0], groups[1], groups[2]);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DispatchComputeIndirect called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "DirectVulkan::DispatchComputeIndirect called with null GL context");
|
||||
@@ -966,9 +1053,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// vkCmdDrawIndexed all carry natively.
|
||||
static void MultiDrawElementsImpl(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RejectWireLegacyBuffer();
|
||||
#endif
|
||||
|
||||
if (drawcount <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -983,8 +1068,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// MultiDrawIndexedCmd left the client-memory shape addressing a view whose byte
|
||||
// offset is a hardcoded 0, so UploadAndBindIndexBuffer saw a null client pointer,
|
||||
// declined the whole batch and painted nothing.)
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const Bool noIndexBuffer = MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
? MG_Pipe::MGPipeHandleIsNull(MG_Pipe::MGPipeApplier().IndexBuffer.Res)
|
||||
: MGB_CTX->GetBoundVertexArray()->GetIndexBufferBindingSlot().GetBoundObject() == nullptr;
|
||||
if (noIndexBuffer) {
|
||||
#else
|
||||
const auto& vao = *MGB_CTX->GetBoundVertexArray();
|
||||
if (vao.GetIndexBufferBindingSlot().GetBoundObject() == nullptr) {
|
||||
#endif
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (count[i] <= 0) {
|
||||
continue;
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "BufferArena.h"
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <Config.h>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool BufferArena::Initialize(const BufferArenaDesc& desc) {
|
||||
@@ -125,6 +128,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
bufferDesc.usage = m_desc.usage;
|
||||
bufferDesc.memoryUsage = m_desc.memoryUsage;
|
||||
bufferDesc.allocationFlags = m_desc.allocationFlags;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Split staging may also be the destination of a GPU UBO range copy.
|
||||
// Persistent host writes must be coherent: Upload deliberately writes
|
||||
// the mapped pointer without a per-range VMA flush.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && m_desc.persistentlyMapped) {
|
||||
bufferDesc.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
}
|
||||
#endif
|
||||
if (!buffer.Create(bufferDesc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -46,9 +46,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
[[noreturn]] static void WireBufferDescriptorDebt() {
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} - Magma buffer descriptors remain P7");
|
||||
std::abort();
|
||||
static Bool ResolveWireRange(Uint64 offset, Uint64 declaredSize, VkDeviceSize bufferSize,
|
||||
VkDeviceSize& start, VkDeviceSize& size) {
|
||||
if (offset >= bufferSize) return false;
|
||||
start = static_cast<VkDeviceSize>(offset);
|
||||
const VkDeviceSize remaining = bufferSize - start;
|
||||
size = declaredSize == MG_Pipe::kMGPipeWholeBuffer
|
||||
? remaining : std::min<VkDeviceSize>(declaredSize, remaining);
|
||||
return size != 0;
|
||||
}
|
||||
|
||||
static VkImageViewType WireViewType(MG_Pipe::MGPipeResourceTarget target) {
|
||||
@@ -474,6 +479,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_bufferManager = bufferManager;
|
||||
m_programFactory = programFactory;
|
||||
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
vkGetPhysicalDeviceProperties(m_physicalDevice, &properties);
|
||||
m_wireStorageOffsetAlignment = std::max<VkDeviceSize>(1, properties.limits.minStorageBufferOffsetAlignment);
|
||||
m_wireTexelOffsetAlignment = std::max<VkDeviceSize>(1, properties.limits.minTexelBufferOffsetAlignment);
|
||||
m_wireMaxUniformRange = properties.limits.maxUniformBufferRange;
|
||||
m_wireMaxStorageRange = properties.limits.maxStorageBufferRange;
|
||||
m_wireMaxTexelElements = properties.limits.maxTexelBufferElements;
|
||||
}
|
||||
#endif
|
||||
m_frameCount = frameCount;
|
||||
m_maxBindings = maxBindings;
|
||||
m_samplerResolveMemo.assign(m_maxBindings, SamplerResolveMemo{});
|
||||
@@ -1052,27 +1068,75 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return texture;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Bool UniformManager::ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 frameIndex,
|
||||
Bool storage, VkBufferView& out) {
|
||||
out = VK_NULL_HANDLE;
|
||||
if (binding >= programObj.samplerUniformLocationByBinding.size() ||
|
||||
binding >= programObj.samplerNumericDomainByBinding.size() || frameIndex >= m_frames.size()) return false;
|
||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||
if (location < 0) return false;
|
||||
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
if (unit < 0 || static_cast<Uint32>(unit) >=
|
||||
(storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) return false;
|
||||
if (storage && binding >= programObj.storageImageFormatByBinding.size()) return false;
|
||||
const VkFormat declaredFormat = storage ? programObj.storageImageFormatByBinding[binding] : VK_FORMAT_UNDEFINED;
|
||||
const auto placeholder = [&] {
|
||||
out = AcquireUnboundTexelBufferView(declaredFormat,
|
||||
programObj.samplerNumericDomainByBinding[binding], storage);
|
||||
return out != VK_NULL_HANDLE;
|
||||
};
|
||||
const auto texture = storage ? state.BoundShaderImages[unit].Res : state.BoundSamplerViews[unit].Texture;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(texture)) return placeholder();
|
||||
if (texture.Slot >= state.TextureResources.size()) WireDescriptorFatal("texel-buffer-record");
|
||||
const auto& record = state.TextureResources[texture.Slot];
|
||||
if (!record.Live || record.Gen != texture.Gen) WireDescriptorFatal("texel-buffer-generation");
|
||||
if (record.Desc.Target != static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::TexBuffer))
|
||||
WireDescriptorFatal("texel-buffer-target");
|
||||
const auto buffer = record.Desc.BufferForTexBuffer;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(buffer)) return placeholder();
|
||||
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager->AcquireWireSlice(BufferKind::TextureBuffer, buffer, slice) || !slice.IsValid()) return false;
|
||||
const auto internalFormat = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
|
||||
VkFormat format = declaredFormat;
|
||||
if (storage && format == VK_FORMAT_UNDEFINED && state.BoundShaderImages[unit].InternalFormat != 0)
|
||||
format = MG_Util::ConvertTextureInternalFormatToVkEnum(MG_Util::ConvertGLEnumToTextureInternalFormat(
|
||||
state.BoundShaderImages[unit].InternalFormat));
|
||||
if (format == VK_FORMAT_UNDEFINED) format = MG_Util::ConvertTextureInternalFormatToVkEnum(internalFormat);
|
||||
const auto feature = storage ? VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT : VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT;
|
||||
if (format == VK_FORMAT_UNDEFINED || !BufferFormatSupportsFeature(m_physicalDevice, format, feature))
|
||||
WireDescriptorFatal("texel-buffer-native-format@P7");
|
||||
const VkDeviceSize texelSize = MG_Util::GetSizedInternalFormatSizeInBytes(internalFormat);
|
||||
VkDeviceSize start = 0, size = 0;
|
||||
if (texelSize == 0 || !ResolveWireRange(record.Desc.BufOffset, record.Desc.BufSize, slice.size, start, size)) return false;
|
||||
size = std::min(size / texelSize, static_cast<VkDeviceSize>(m_wireMaxTexelElements)) * texelSize;
|
||||
if (size == 0) return false;
|
||||
if (start > std::numeric_limits<VkDeviceSize>::max() - slice.offset ||
|
||||
(slice.offset + start) % m_wireTexelOffsetAlignment != 0)
|
||||
WireDescriptorFatal("unaligned-texel-buffer-range@P7");
|
||||
VkBufferViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO};
|
||||
viewInfo.buffer = slice.buffer;
|
||||
viewInfo.format = format;
|
||||
viewInfo.offset = slice.offset + start;
|
||||
viewInfo.range = size;
|
||||
if (vkCreateBufferView(m_device, &viewInfo, nullptr, &out) != VK_SUCCESS || out == VK_NULL_HANDLE) return false;
|
||||
m_frames[frameIndex].texelBufferViews.push_back(out);
|
||||
if (storage && state.BoundShaderImages[unit].Access != kMGPipeImageAccessReadOnly)
|
||||
m_bufferManager->MarkWireBufferGpuWritten(buffer, start, size);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
Bool UniformManager::ResolveTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj,
|
||||
Uint32 binding, Uint32 frameIndex,
|
||||
VkBufferView& outBufferView) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) {
|
||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||
const Int unit = location < 0 ? 0 : program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
if (unit < 0 || static_cast<Uint32>(unit) >= MG_Pipe::kMGPipeMaxTextureUnits) return false;
|
||||
const auto handle = state.BoundSamplerViews[unit].Texture;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
if (handle.Slot >= state.TextureResources.size()) WireDescriptorFatal("texel-buffer-record");
|
||||
const auto& record = state.TextureResources[handle.Slot];
|
||||
if (!record.Live || record.Gen != handle.Gen) WireDescriptorFatal("texel-buffer-generation");
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(record.Desc.BufferForTexBuffer)) WireBufferDescriptorDebt();
|
||||
}
|
||||
outBufferView = AcquireUnboundTexelBufferView(VK_FORMAT_UNDEFINED,
|
||||
programObj.samplerNumericDomainByBinding[binding], false);
|
||||
return outBufferView != VK_NULL_HANDLE;
|
||||
}
|
||||
if (program.IsWire())
|
||||
return ResolveWireTexelBufferDescriptor(program, programObj, binding, frameIndex, false, outBufferView);
|
||||
#endif
|
||||
outBufferView = VK_NULL_HANDLE;
|
||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "ResolveTexelBufferDescriptor: buffer manager is null");
|
||||
@@ -1199,22 +1263,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 binding, Uint32 frameIndex,
|
||||
VkBufferView& outBufferView) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) {
|
||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||
const Int unit = location < 0 ? 0 : program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
if (unit < 0 || static_cast<Uint32>(unit) >= MG_Pipe::kMGPipeMaxImageUnits) return false;
|
||||
const auto handle = state.BoundShaderImages[unit].Res;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
if (handle.Slot >= state.TextureResources.size()) WireDescriptorFatal("texel-buffer-record");
|
||||
const auto& record = state.TextureResources[handle.Slot];
|
||||
if (!record.Live || record.Gen != handle.Gen) WireDescriptorFatal("texel-buffer-generation");
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(record.Desc.BufferForTexBuffer)) WireBufferDescriptorDebt();
|
||||
}
|
||||
outBufferView = AcquireUnboundTexelBufferView(programObj.storageImageFormatByBinding[binding],
|
||||
programObj.samplerNumericDomainByBinding[binding], true);
|
||||
return outBufferView != VK_NULL_HANDLE;
|
||||
}
|
||||
if (program.IsWire())
|
||||
return ResolveWireTexelBufferDescriptor(program, programObj, binding, frameIndex, true, outBufferView);
|
||||
#endif
|
||||
outBufferView = VK_NULL_HANDLE;
|
||||
MOBILEGL_ASSERT(m_bufferManager != nullptr, "ResolveStorageTexelBufferDescriptor: buffer manager is null");
|
||||
@@ -1387,10 +1437,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MOBILEGL_ASSERT(MGB_CTX_LIVE, "ResolveStorageBufferDescriptor: GL context is null");
|
||||
MOBILEGL_ASSERT(binding < programObj.storageBlockIndexByBinding.size(),
|
||||
"ResolveStorageBufferDescriptor: binding %u out of range", binding);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire() && (binding >= programObj.storageBlockIndexByBinding.size() ||
|
||||
binding >= programObj.storageBlockNameByBinding.size()))
|
||||
WireDescriptorFatal("storage-buffer-reflection-binding");
|
||||
#endif
|
||||
|
||||
const Int blockIndex = programObj.storageBlockIndexByBinding[binding];
|
||||
MOBILEGL_ASSERT(blockIndex >= 0, "ResolveStorageBufferDescriptor: no SSBO block mapped to binding %u",
|
||||
binding);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire() && blockIndex < 0) WireDescriptorFatal("storage-buffer-reflection-block");
|
||||
#endif
|
||||
// An atomic counter is not an SSBO the application ever declared: glslang lowers every
|
||||
// atomic_uint onto a synthesized gl_AtomicCounterBlock_<N> storage block, where N is the
|
||||
// GL ATOMIC-COUNTER binding. That block arrives here auto-mapped to an arbitrary
|
||||
@@ -1429,13 +1487,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire()) {
|
||||
const auto& state = MG_Pipe::MGPipeApplier();
|
||||
const Uint32 cls = isAtomicCounterBlock ? 2u : 1u;
|
||||
if (frontendBinding < state.ShaderBufferCount[cls] &&
|
||||
!MG_Pipe::MGPipeHandleIsNull(state.BoundShaderBuffers[cls][frontendBinding].Res))
|
||||
WireBufferDescriptorDebt();
|
||||
const BufferSlice placeholder = m_bufferManager->AcquireUnboundStorageDescriptor();
|
||||
if (!placeholder.IsValid()) return false;
|
||||
outBufferInfo = {placeholder.buffer, placeholder.offset, placeholder.size};
|
||||
const Uint32 cls = isAtomicCounterBlock ? MG_Pipe::kMGPipeShaderBufferClassAtomicCounter
|
||||
: MG_Pipe::kMGPipeShaderBufferClassShaderStorage;
|
||||
if (frontendBinding >= MG_Pipe::kMGPipeMaxBufferBindingPoints)
|
||||
WireDescriptorFatal("storage-buffer-binding-point");
|
||||
// The applier retains entries outside the last partial update window. Count
|
||||
// describes that update, not the upper bound of the complete binding table.
|
||||
const auto& range = state.BoundShaderBuffers[cls][frontendBinding];
|
||||
if (MG_Pipe::MGPipeHandleIsNull(range.Res)) {
|
||||
const BufferSlice placeholder = m_bufferManager->AcquireUnboundStorageDescriptor();
|
||||
if (!placeholder.IsValid()) return false;
|
||||
outBufferInfo = {placeholder.buffer, placeholder.offset, placeholder.size};
|
||||
return true;
|
||||
}
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager->AcquireWireSlice(BufferKind::ShaderStorage, range.Res, slice) || !slice.IsValid()) return false;
|
||||
VkDeviceSize start = 0, size = 0;
|
||||
if (!ResolveWireRange(range.Offset, range.Size, slice.size, start, size)) return false;
|
||||
if (start > std::numeric_limits<VkDeviceSize>::max() - slice.offset ||
|
||||
(slice.offset + start) % m_wireStorageOffsetAlignment != 0)
|
||||
WireDescriptorFatal(isAtomicCounterBlock ? "unaligned-atomic-buffer-range@P7"
|
||||
: "unaligned-storage-buffer-range@P7");
|
||||
if (size > m_wireMaxStorageRange) WireDescriptorFatal("storage-buffer-native-range@P7");
|
||||
outBufferInfo = {slice.buffer, slice.offset + start, size};
|
||||
// Shader writes stay in the canonical server VkBuffer. The event marks the
|
||||
// client's shadow stale; neither this path nor later acquires seed it back.
|
||||
m_bufferManager->MarkWireBufferGpuWritten(range.Res, start, size);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -2234,6 +2311,59 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Bool UniformManager::ResolveWireUniformBufferPayload(const MagmaProgramSource& program,
|
||||
Uint32 blockIndex, Uint32 bindingPoint, UboBindResult& out) const {
|
||||
if (bindingPoint >= MG_Pipe::kMGPipeMaxBufferBindingPoints)
|
||||
WireDescriptorFatal("uniform-buffer-binding-point");
|
||||
const VkDeviceSize blockSize = program.GetUBOSizeAt(blockIndex);
|
||||
if (blockSize == 0) return false;
|
||||
if (blockSize > m_wireMaxUniformRange) WireDescriptorFatal("uniform-block-native-range@P7");
|
||||
const auto& range = MG_Pipe::MGPipeApplier().BoundShaderBuffers[
|
||||
MG_Pipe::kMGPipeShaderBufferClassUniform][bindingPoint];
|
||||
BufferSlice source{};
|
||||
VkDeviceSize start = 0, available = 0;
|
||||
const Bool bound = !MG_Pipe::MGPipeHandleIsNull(range.Res);
|
||||
if (bound) {
|
||||
if (!m_bufferManager->AcquireWireSlice(BufferKind::Uniform, range.Res, source) || !source.IsValid()) return false;
|
||||
if (!ResolveWireRange(range.Offset, range.Size, source.size, start, available)) return false;
|
||||
if (start > std::numeric_limits<VkDeviceSize>::max() - source.offset)
|
||||
WireDescriptorFatal("uniform-buffer-offset-overflow");
|
||||
const VkDeviceSize absoluteOffset = source.offset + start;
|
||||
if (available >= blockSize && absoluteOffset % m_minDynamicOffsetAlignment == 0 &&
|
||||
absoluteOffset <= std::numeric_limits<Uint32>::max()) {
|
||||
out.directBindable = true;
|
||||
out.buffer = source.buffer;
|
||||
out.range = blockSize;
|
||||
out.dynamicOffset = absoluteOffset;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve the existing short-range compatibility policy without sampling the
|
||||
// client's shadow: zero the complete reflected block, then GPU-copy only bytes
|
||||
// inside the GL binding range. A readback here could retire the command buffer
|
||||
// whose value BindProgramUniformBuffers is currently holding.
|
||||
const VkDeviceSize copied = std::min(available, blockSize);
|
||||
if (bound && ((start & 3u) != 0 || ((source.offset + start) & 3u) != 0 || (copied & 3u) != 0))
|
||||
WireDescriptorFatal("uniform-buffer-byte-tail@P7");
|
||||
static thread_local Vector<Uint8> zero;
|
||||
zero.assign(static_cast<SizeT>(blockSize), 0);
|
||||
BufferSlice padded{};
|
||||
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, m_wireFrameIndex, zero.data(), blockSize,
|
||||
std::max<VkDeviceSize>(4, m_minDynamicOffsetAlignment), padded) || !padded.IsValid()) return false;
|
||||
if (bound && copied != 0 &&
|
||||
!m_bufferManager->CopyWireBufferRangeToSlice(range.Res, start, copied, padded)) return false;
|
||||
if (padded.offset > std::numeric_limits<Uint32>::max())
|
||||
WireDescriptorFatal("uniform-buffer-dynamic-offset@P7");
|
||||
out.directBindable = true;
|
||||
out.buffer = padded.buffer;
|
||||
out.range = blockSize;
|
||||
out.dynamicOffset = padded.offset;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
Bool UniformManager::ResolveUniformBufferPayload(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 arrayElement, UboBindResult& out) const {
|
||||
@@ -2260,13 +2390,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return outData != nullptr && outSize > 0;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Global uniforms above are record bytes; named UBOs retain P7's buffer boundary.
|
||||
if (program.IsWire()) WireBufferDescriptorDebt();
|
||||
#endif
|
||||
|
||||
MOBILEGL_ASSERT(binding < programObj.uniformBlockIndexByBinding.size(),
|
||||
"ResolveUniformBufferPayload: UBO mapping binding %u out of range", binding);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire() && binding >= programObj.uniformBlockIndexByBinding.size())
|
||||
WireDescriptorFatal("uniform-buffer-reflection-binding");
|
||||
#endif
|
||||
Int blockIndex = programObj.uniformBlockIndexByBinding[binding];
|
||||
if (arrayElement > 0) {
|
||||
const auto arrayIt = programObj.arrayedUniformBlockIndicesByBinding.find(binding);
|
||||
@@ -2287,8 +2416,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MOBILEGL_ASSERT(static_cast<Uint32>(blockIndex) < activeUniformBlockCount,
|
||||
"ResolveUniformBufferPayload: uniform block index %d out of range (count=%u)", blockIndex,
|
||||
activeUniformBlockCount);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire() && (blockIndex < 0 || static_cast<Uint32>(blockIndex) >= activeUniformBlockCount))
|
||||
WireDescriptorFatal("uniform-buffer-reflection-block");
|
||||
#endif
|
||||
|
||||
const Uint32 frontendBinding = program.GetUniformBlockBinding(static_cast<Uint32>(blockIndex));
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire())
|
||||
return ResolveWireUniformBufferPayload(program, static_cast<Uint32>(blockIndex), frontendBinding, out);
|
||||
#endif
|
||||
const Uint32 uniformBindingPointCount =
|
||||
static_cast<Uint32>(MGB_CTX->GetBufferBindingPointCount(BufferTarget::Uniform));
|
||||
MOBILEGL_ASSERT(frontendBinding < uniformBindingPointCount,
|
||||
@@ -2535,6 +2672,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32& outDynamicOffset) {
|
||||
UboBindResult ubo{};
|
||||
const Bool hasPayload = ResolveUniformBufferPayload(program, programObj, binding, arrayElement, ubo);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (program.IsWire() && !hasPayload) return false;
|
||||
#endif
|
||||
MOBILEGL_ASSERT(hasPayload && (ubo.directBindable || (ubo.payload != nullptr && ubo.payloadSize > 0)),
|
||||
"UniformDescriptorBinder::ResolveDynamicUboDescriptor failed: missing UBO payload on binding %u element %u",
|
||||
binding, arrayElement);
|
||||
|
||||
@@ -236,7 +236,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 element, Bool storage, VkDescriptorImageInfo& out) const;
|
||||
Bool ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 frameIndex, Bool storage, VkBufferView& out);
|
||||
Uint32 m_wireFrameIndex = 0;
|
||||
VkDeviceSize m_wireStorageOffsetAlignment = 1;
|
||||
VkDeviceSize m_wireTexelOffsetAlignment = 1;
|
||||
VkDeviceSize m_wireMaxUniformRange = ~VkDeviceSize{0};
|
||||
VkDeviceSize m_wireMaxStorageRange = ~VkDeviceSize{0};
|
||||
Uint32 m_wireMaxTexelElements = ~Uint32{0};
|
||||
#endif
|
||||
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
|
||||
VkDescriptorImageInfo& outImageInfo) const;
|
||||
@@ -273,6 +281,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const void* payload = nullptr; // fallback UploadTransient path
|
||||
VkDeviceSize payloadSize = 0;
|
||||
};
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Bool ResolveWireUniformBufferPayload(const MagmaProgramSource& program, Uint32 blockIndex,
|
||||
Uint32 bindingPoint, UboBindResult& out) const;
|
||||
#endif
|
||||
Bool ResolveUniformBufferPayload(const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
|
||||
Uint32 arrayElement, UboBindResult& out) const;
|
||||
|
||||
@@ -435,6 +435,78 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return entry;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Bool VertexInputStateFactory::BuildWireVertexInput(const MG_Pipe::MGPipeVertexElementsRecord& elements,
|
||||
const MG_Pipe::MGPipeApplierState& state, Uint32 activeMask, BackendVertexInputState& out) const {
|
||||
out = BackendVertexInputState{};
|
||||
for (Uint32 location = 0; location < elements.AttributeCount; ++location) {
|
||||
const auto& attr = elements.Attributes[location];
|
||||
if (!attr.Enabled || !(activeMask & (1u << location))) continue;
|
||||
// set_vertex_buffers is flattened PER ATTRIBUTE (VertexInputEmit.h), not
|
||||
// indexed by the original ARB binding point in attr.BindingIndex.
|
||||
if (location < state.VertexBufferStart ||
|
||||
location - state.VertexBufferStart >= state.VertexBufferCount) return false;
|
||||
const auto& buffer = state.VertexBuffers[location];
|
||||
const auto type = static_cast<DataType>(attr.Type);
|
||||
if (attr.Stride < 0 || attr.Size < 1 || attr.Size > 4) return false;
|
||||
VkFormat format = ToVkVertexFormat(type, attr.Size, attr.Normalized, attr.IsInteger,
|
||||
attr.IsBgra, attr.IsLong);
|
||||
auto conversion = VertexStreamConversion::None;
|
||||
if (format == VK_FORMAT_UNDEFINED && type == DataType::Float64) {
|
||||
const auto* backend = MG_Remote::Server::ServerLoopInstance().Backend();
|
||||
if (backend && backend->GetDynamicParameters().SupportsFloat64VertexAttributes) return false;
|
||||
format = ToFloat32VertexFormat(attr.Size);
|
||||
conversion = VertexStreamConversion::Float64ToFloat32;
|
||||
}
|
||||
if (format == VK_FORMAT_UNDEFINED) return false;
|
||||
if (!SupportsVertexBufferFormat(format)) {
|
||||
if (!IsScaledIntegerVertexFormat(format)) return false;
|
||||
format = ToFloat32VertexFormat(attr.Size);
|
||||
conversion = VertexStreamConversion::ScaledIntegerToFloat32;
|
||||
if (!SupportsVertexBufferFormat(format)) return false;
|
||||
}
|
||||
const SizeT elementSize = GetAttributeByteSize(type, attr.Size, attr.IsBgra);
|
||||
if (!elementSize) return false;
|
||||
const Uint64 offset = attr.Offset + buffer.Offset;
|
||||
if (offset < attr.Offset) return false;
|
||||
const SizeT alignment = (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev)
|
||||
? elementSize : GetComponentSize(type);
|
||||
if (conversion == VertexStreamConversion::None && alignment > 1 &&
|
||||
(offset % alignment || static_cast<Uint32>(attr.Stride) % alignment))
|
||||
conversion = VertexStreamConversion::Repack;
|
||||
Uint32 stride = static_cast<Uint32>(attr.Stride);
|
||||
if (stride && conversion != VertexStreamConversion::None)
|
||||
stride = conversion == VertexStreamConversion::Repack ? static_cast<Uint32>(elementSize)
|
||||
: static_cast<Uint32>(attr.Size) * sizeof(Float);
|
||||
const Uint32 binding = static_cast<Uint32>(out.bindings.size());
|
||||
out.bindings.push_back({binding, stride,
|
||||
buffer.Divisor ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX});
|
||||
out.attributes.push_back({location, binding, format, 0});
|
||||
out.bindingAttributeLocations.push_back(location);
|
||||
out.bindingBaseOffsets.push_back(static_cast<SizeT>(offset));
|
||||
out.bindingConversions.push_back(conversion);
|
||||
if (buffer.Divisor > 1) out.bindingDivisors.push_back({binding, buffer.Divisor});
|
||||
out.attributeLocationMask |= 1u << location;
|
||||
}
|
||||
// Native handles/offsets do not decide the pipeline layout. Include only the
|
||||
// resolved binding/attribute/divisor values, including a legal zero stride.
|
||||
Uint64 hash = XXH64(out.bindings.data(), out.bindings.size() * sizeof(out.bindings[0]), 0);
|
||||
hash = XXH64(out.attributes.data(), out.attributes.size() * sizeof(out.attributes[0]), hash);
|
||||
out.layoutHash = XXH64(out.bindingDivisors.data(),
|
||||
out.bindingDivisors.size() * sizeof(out.bindingDivisors[0]), hash);
|
||||
out.state.vertexBindingDescriptionCount = static_cast<Uint32>(out.bindings.size());
|
||||
out.state.pVertexBindingDescriptions = out.bindings.data();
|
||||
out.state.vertexAttributeDescriptionCount = static_cast<Uint32>(out.attributes.size());
|
||||
out.state.pVertexAttributeDescriptions = out.attributes.data();
|
||||
if (!out.bindingDivisors.empty()) {
|
||||
out.divisorState.vertexBindingDivisorCount = static_cast<Uint32>(out.bindingDivisors.size());
|
||||
out.divisorState.pVertexBindingDivisors = out.bindingDivisors.data();
|
||||
out.state.pNext = &out.divisorState;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void VertexInputStateFactory::OnFrameBoundary() {
|
||||
++m_frameBoundaryCounter;
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
// MG_Pipe::MGPipeHandle for the P2 D12.5 memo table below. A header of constexpr constants,
|
||||
// so the pull build gains nothing from it.
|
||||
#include <MG_Pipe/MGPipeHandles.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#endif
|
||||
|
||||
#include "Config.h"
|
||||
#include "MagmaPipeArms.h"
|
||||
@@ -125,6 +128,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// normal types, and 4 (one packed word) for the 2_10_10_10 types and GL_BGRA. Returns 0 for
|
||||
// an unknown/unsupported type.
|
||||
static SizeT GetAttributeByteSize(DataType type, Int size, Bool isBgra);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// A wire VAO is a layout record plus a separate per-attribute buffer window.
|
||||
// No frontend VAO identity, address or memo participates in this layout.
|
||||
Bool BuildWireVertexInput(const MG_Pipe::MGPipeVertexElementsRecord& elements,
|
||||
const MG_Pipe::MGPipeApplierState& state, Uint32 activeMask,
|
||||
BackendVertexInputState& out) const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false,
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
#include "VulkanRenderer.h"
|
||||
|
||||
#include "MG_Util/Metrics/PipeStats.h"
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include "MG_Pipe/MGPipeCallbacks.h"
|
||||
#include "MG_Pipe/PipeApply.h"
|
||||
#include "MG_Remote/Server/StagedShadow.h"
|
||||
#include <Config.h>
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
@@ -119,8 +126,272 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||
.ReadbackFromGpu = Ops_ReadbackFromGpu,
|
||||
};
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
VkBufferManager& WireManager() {
|
||||
if (g_activeBufferManager == nullptr) {
|
||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"wire-buffer-manager\"}");
|
||||
std::abort();
|
||||
}
|
||||
return *g_activeBufferManager;
|
||||
}
|
||||
|
||||
const MG_Pipe::MGPipeResourceOps g_vulkanWireResourceOps = {
|
||||
.Create = [](auto res, const auto& desc) { WireManager().CreateWireBuffer(res, desc); },
|
||||
.Respecify = [](auto res, const auto& desc, const void* bytes) {
|
||||
WireManager().RespecifyWireBuffer(res, desc, bytes);
|
||||
},
|
||||
.SubData = [](auto res, const auto& record, const void* bytes) {
|
||||
WireManager().WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
|
||||
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
|
||||
},
|
||||
.SubDataResident = [](auto res, const auto& record, const void* bytes) {
|
||||
WireManager().WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
|
||||
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
|
||||
},
|
||||
.FlushRange = [](auto res, const auto& record, const void* bytes) {
|
||||
WireManager().FlushWireBuffer(res, record.Offset, record.Size, bytes);
|
||||
},
|
||||
.Readback = [](auto res, const auto& record) {
|
||||
WireManager().ReadbackWireBuffer(res, record.Offset, record.Size);
|
||||
},
|
||||
.Destroy = [](auto res) { WireManager().DestroyWireBuffer(res); },
|
||||
// Split mapping remains T2: the client owns its map and pushes exact
|
||||
// modified ranges. A server pointer is never donated across the wire.
|
||||
.MapPersistent = [](MG_Pipe::MGPipeHandle, Uint64, const void*) -> void* { return nullptr; },
|
||||
.UnmapPersistent = [](MG_Pipe::MGPipeHandle) {},
|
||||
};
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
void VkBufferManager::RegisterWireResourceOps() {
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
MG_Pipe::MGPipeSetResourceOps(&g_vulkanWireResourceOps);
|
||||
}
|
||||
}
|
||||
|
||||
VkBufferManager::WireBufferResource* VkBufferManager::FindWireBuffer(MG_Pipe::MGPipeHandle res) {
|
||||
const auto found = m_wireBuffers.find(WireBufferKey(res));
|
||||
return found == m_wireBuffers.end() ? nullptr : &found->second;
|
||||
}
|
||||
|
||||
void VkBufferManager::CreateWireBuffer(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc) {
|
||||
if (FindWireBuffer(res) != nullptr) {
|
||||
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"duplicate-buffer-create\"} {slot=%u, gen=%u}",
|
||||
res.Slot, res.Gen);
|
||||
std::abort();
|
||||
}
|
||||
m_wireBuffers.try_emplace(WireBufferKey(res));
|
||||
RespecifyWireBuffer(res, desc, nullptr);
|
||||
}
|
||||
|
||||
void VkBufferManager::RespecifyWireBuffer(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc,
|
||||
const void* initialBytes) {
|
||||
auto* resource = FindWireBuffer(res);
|
||||
if (!resource) {
|
||||
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"unknown-buffer-respecify\"} {slot=%u, gen=%u}",
|
||||
res.Slot, res.Gen);
|
||||
std::abort();
|
||||
}
|
||||
DeferRelease(std::move(resource->buffer));
|
||||
resource->size = desc.Width;
|
||||
resource->lastUseSerial = 0;
|
||||
resource->gpuWritesPending = false;
|
||||
resource->stagedCoverage.clear();
|
||||
++m_sliceEpochCounter;
|
||||
if (resource->size == 0) return;
|
||||
|
||||
VkBufferUsageFlags usage = kPersistentBackedUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
if (m_initInfo.transformFeedbackUsageEnabled) usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
||||
if (!resource->buffer.Create({
|
||||
.allocator = m_initInfo.allocator,
|
||||
.size = resource->size,
|
||||
.usage = usage,
|
||||
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
|
||||
.allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,
|
||||
.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
}) || resource->buffer.Map() == nullptr) {
|
||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-storage\"} {slot=%u, gen=%u, size=%llu}",
|
||||
res.Slot, res.Gen, static_cast<unsigned long long>(resource->size));
|
||||
std::abort();
|
||||
}
|
||||
// Under transport, defined bytes follow as resource_subdata records. An
|
||||
// undefined store has no shadow to upload and no implicit zero snapshot.
|
||||
if (initialBytes != nullptr && desc.HasDefinedContent) {
|
||||
WriteWireBuffer(res, 0, resource->size, initialBytes);
|
||||
}
|
||||
}
|
||||
|
||||
Bool VkBufferManager::WaitForWireBufferHostAccess(WireBufferResource& resource) {
|
||||
if (resource.lastUseSerial <= GetCompletedSerial() && !resource.gpuWritesPending) return true;
|
||||
if (!m_copyProvider || !pVulkanRenderer) return false;
|
||||
const VkCommandBuffer commands = m_copyProvider->AcquireBufferCopyCommandBuffer();
|
||||
if (commands == VK_NULL_HANDLE) return false;
|
||||
VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_HOST_BIT,
|
||||
0, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
if (!pVulkanRenderer->WaitForSubmitIndex(pVulkanRenderer->GetSyncPointSubmitIndex(), UINT64_MAX, true)) {
|
||||
return false;
|
||||
}
|
||||
resource.lastUseSerial = 0;
|
||||
resource.gpuWritesPending = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBufferManager::WriteWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
|
||||
const void* bytes) {
|
||||
auto* resource = FindWireBuffer(res);
|
||||
if (!resource || offset > resource->size || size > resource->size - offset || (size && !bytes)) {
|
||||
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"buffer-write-range\"} {slot=%u, gen=%u}",
|
||||
res.Slot, res.Gen);
|
||||
std::abort();
|
||||
}
|
||||
if (size == 0) return;
|
||||
Bool uploaded = false;
|
||||
if (resource->lastUseSerial > GetCompletedSerial()) {
|
||||
// Vulkan buffer copies require four-byte aligned ranges. An odd GL
|
||||
// byte update waits, then writes only the exact range; rounding it
|
||||
// from a CPU shadow could overwrite neighbouring GPU-written bytes.
|
||||
if (((offset | size) & 3u) == 0) {
|
||||
uploaded = StagedWireRangeCopy(*resource, bytes, static_cast<SizeT>(offset),
|
||||
static_cast<SizeT>(size));
|
||||
}
|
||||
if (!uploaded && !WaitForWireBufferHostAccess(*resource)) {
|
||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-write-sync\"}");
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (!uploaded) {
|
||||
uploaded = resource->buffer.Upload(bytes, size, offset);
|
||||
if (uploaded && MG_Util::PipeStats::Enabled()) {
|
||||
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, size);
|
||||
}
|
||||
}
|
||||
if (!uploaded) {
|
||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-upload\"}");
|
||||
std::abort();
|
||||
}
|
||||
MG_Remote::Server::StagedShadowStore::CoverageAdd(resource->stagedCoverage,
|
||||
static_cast<SizeT>(offset),
|
||||
static_cast<SizeT>(offset + size));
|
||||
++m_sliceEpochCounter;
|
||||
}
|
||||
|
||||
void VkBufferManager::FlushWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
|
||||
const void* bytes) {
|
||||
if (bytes != nullptr) {
|
||||
WriteWireBuffer(res, offset, size, bytes);
|
||||
return;
|
||||
}
|
||||
const auto* resource = FindWireBuffer(res);
|
||||
if (!resource || offset > resource->size || size > resource->size - offset ||
|
||||
!MG_Remote::Server::StagedShadowStore::CoverageHas(resource->stagedCoverage,
|
||||
static_cast<SizeT>(offset), static_cast<SizeT>(offset + size))) {
|
||||
MGLOG_F("Magma: Fatal{StageSnapshotTooNarrow, \"buffer-flush\"} {slot=%u, gen=%u}",
|
||||
res.Slot, res.Gen);
|
||||
std::abort();
|
||||
}
|
||||
// SubData has already copied the owned record bytes into GPU storage,
|
||||
// ordered after earlier uses. There is no stale shadow to replay here.
|
||||
}
|
||||
|
||||
Bool VkBufferManager::AcquireWireSlice(BufferKind kind, MG_Pipe::MGPipeHandle res, BufferSlice& outSlice) {
|
||||
(void)kind; // The store carries every buffer usage, so changing roles never orphans it.
|
||||
outSlice = {};
|
||||
auto* resource = FindWireBuffer(res);
|
||||
if (!resource || !resource->buffer.IsValid() || resource->size == 0) return false;
|
||||
resource->lastUseSerial = m_frameSerial;
|
||||
outSlice = resource->buffer.GetSlice(0, resource->size);
|
||||
outSlice.mapped = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkBufferManager::ReadWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, void* dst) {
|
||||
auto* resource = FindWireBuffer(res);
|
||||
if (!resource || offset > resource->size || size > resource->size - offset || (size && !dst)) return false;
|
||||
if (size == 0) return true;
|
||||
// Concurrent GPU reads do not prevent a CPU read. Only staged copies or
|
||||
// shader writes need a host visibility barrier and a submission wait;
|
||||
// ordinary index/vertex inspection must not stall once per draw.
|
||||
if (resource->gpuWritesPending && !WaitForWireBufferHostAccess(*resource)) return false;
|
||||
if (!resource->buffer.Invalidate(size, offset)) return false;
|
||||
Memcpy(dst, static_cast<const Uint8*>(resource->buffer.GetMappedData()) + offset, static_cast<SizeT>(size));
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VkBufferManager::CopyWireBufferRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
|
||||
const BufferSlice& dst) {
|
||||
auto* resource = FindWireBuffer(res);
|
||||
if (!resource || offset > resource->size || size > resource->size - offset || size > dst.size) return false;
|
||||
if (size == 0) return true;
|
||||
if (!m_copyProvider || dst.buffer == VK_NULL_HANDLE || ((offset | size | dst.offset) & 3u) != 0) return false;
|
||||
const VkCommandBuffer commands = m_copyProvider->AcquireBufferCopyCommandBuffer();
|
||||
if (commands == VK_NULL_HANDLE) return false;
|
||||
VkMemoryBarrier before{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
before.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT;
|
||||
before.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &before, 0, nullptr, 0, nullptr);
|
||||
const VkBufferCopy copy{offset, dst.offset, size};
|
||||
vkCmdCopyBuffer(commands, resource->buffer.GetHandle(), dst.buffer, 1, ©);
|
||||
VkMemoryBarrier after{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
after.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
after.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
0, 1, &after, 0, nullptr, 0, nullptr);
|
||||
resource->lastUseSerial = m_frameSerial;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBufferManager::ReadbackWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size) {
|
||||
Vector<Uint8> bytes(static_cast<SizeT>(size));
|
||||
if (!ReadWireBuffer(res, offset, size, bytes.data())) {
|
||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-readback\"} {slot=%u, gen=%u}", res.Slot, res.Gen);
|
||||
std::abort();
|
||||
}
|
||||
if (MG_Pipe::gMGPipeCallbacks.OnBufferWriteback == nullptr) {
|
||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-writeback-callback\"}");
|
||||
std::abort();
|
||||
}
|
||||
// The reverse producer synchronously copies into SEG_EVENT before
|
||||
// this owned vector is released; no mapped Vulkan pointer crosses roles.
|
||||
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(res, offset,
|
||||
{reinterpret_cast<Uint64>(bytes.data()), size, MG_Pipe::kMGHostSpanSegNone, 0});
|
||||
++m_sliceEpochCounter;
|
||||
}
|
||||
|
||||
void VkBufferManager::MarkWireBufferGpuWritten(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size) {
|
||||
auto* resource = FindWireBuffer(res);
|
||||
if (!resource || offset > resource->size) return;
|
||||
if (size == MG_Pipe::kMGPipeWholeBuffer) size = resource->size - offset;
|
||||
if (size == 0 || size > resource->size - offset) return;
|
||||
resource->lastUseSerial = m_frameSerial;
|
||||
resource->stagedCoverage.clear();
|
||||
resource->gpuWritesPending = true;
|
||||
++m_sliceEpochCounter;
|
||||
// The existing client dirty-state protocol accepts one whole-store
|
||||
// notification. The store remains on the GPU and exact later SubData
|
||||
// only overwrites its own range, preserving every other GPU-written byte.
|
||||
const MG_Pipe::MGPRange range{0, MG_Pipe::kMGPipeWholeBuffer};
|
||||
if (MG_Pipe::gMGPipeCallbacks.OnGpuWritten == nullptr) {
|
||||
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-gpu-written-callback\"}");
|
||||
std::abort();
|
||||
}
|
||||
MG_Pipe::gMGPipeCallbacks.OnGpuWritten(res, 1, &range);
|
||||
}
|
||||
|
||||
void VkBufferManager::DestroyWireBuffer(MG_Pipe::MGPipeHandle res) {
|
||||
const auto found = m_wireBuffers.find(WireBufferKey(res));
|
||||
if (found == m_wireBuffers.end()) return;
|
||||
DeferRelease(std::move(found->second.buffer));
|
||||
m_wireBuffers.erase(found);
|
||||
++m_sliceEpochCounter;
|
||||
}
|
||||
#endif
|
||||
|
||||
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
|
||||
Shutdown();
|
||||
|
||||
@@ -138,12 +409,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
g_activeBufferManager = this;
|
||||
MG_State::GLState::SetBufferBackendOps(&g_vulkanBufferBackendOps);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
RegisterWireResourceOps();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkBufferManager::Shutdown() {
|
||||
if (g_activeBufferManager == this) {
|
||||
g_activeBufferManager = nullptr;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Pipe::MGPipeGetResourceOps() == &g_vulkanWireResourceOps) {
|
||||
MG_Pipe::MGPipeSetResourceOps(nullptr);
|
||||
}
|
||||
#endif
|
||||
if (MG_State::GLState::GetBufferBackendOps() == &g_vulkanBufferBackendOps) {
|
||||
MG_State::GLState::SetBufferBackendOps(nullptr);
|
||||
}
|
||||
@@ -153,6 +432,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_unboundTexelBuffer.Destroy();
|
||||
DestroyAllDeferredReleases();
|
||||
ReleaseAllLiveResources();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
m_wireBuffers.clear();
|
||||
#endif
|
||||
m_copyProvider = nullptr;
|
||||
m_initInfo = {};
|
||||
m_currentFrameIndex = 0;
|
||||
@@ -279,6 +561,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.frameCount = m_initInfo.frameCount,
|
||||
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
(MG_Config::Transport != MG_Config::TransportMode::Monolith ? VK_BUFFER_USAGE_TRANSFER_DST_BIT : 0) |
|
||||
#endif
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
.memoryUsage = m_initInfo.transientMemoryUsage,
|
||||
.allocationFlags = m_initInfo.transientAllocationFlags,
|
||||
@@ -431,6 +716,55 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Bool VkBufferManager::StagedWireRangeCopy(WireBufferResource& resource, const void* data,
|
||||
SizeT offset, SizeT size) {
|
||||
if (!m_copyProvider) {
|
||||
return false;
|
||||
}
|
||||
BufferSlice staging{};
|
||||
if (!m_transientUploadArena.Upload(m_currentFrameIndex, data,
|
||||
static_cast<VkDeviceSize>(size), 16, staging)) {
|
||||
return false;
|
||||
}
|
||||
if (MG_Util::PipeStats::Enabled()) {
|
||||
// The staging fill is the host copy; the vkCmdCopyBuffer below is the device
|
||||
// half of the same bytes and is not counted twice.
|
||||
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, static_cast<Uint64>(size));
|
||||
}
|
||||
VkCommandBuffer commandBuffer = m_copyProvider->AcquireBufferCopyCommandBuffer();
|
||||
if (commandBuffer == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Order the copy after every prior read/write of this buffer, both from
|
||||
// in-flight frames (submission order) and from commands already recorded
|
||||
// in this frame's command buffer.
|
||||
VkMemoryBarrier beforeBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
beforeBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
beforeBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1,
|
||||
&beforeBarrier, 0, nullptr, 0, nullptr);
|
||||
|
||||
VkBufferCopy region{};
|
||||
region.srcOffset = staging.offset;
|
||||
region.dstOffset = static_cast<VkDeviceSize>(offset);
|
||||
region.size = static_cast<VkDeviceSize>(size);
|
||||
vkCmdCopyBuffer(commandBuffer, staging.buffer, resource.buffer.GetHandle(), 1, ®ion);
|
||||
|
||||
VkMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
afterBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
afterBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1,
|
||||
&afterBarrier, 0, nullptr, 0, nullptr);
|
||||
|
||||
resource.lastUseSerial = m_frameSerial;
|
||||
resource.gpuWritesPending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void VkBufferManager::OnRespecify(MG_State::GLState::BufferObject& bufferObject) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
if (!resource) {
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
#include "../VkIncludes.h"
|
||||
#include <Includes.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include "MG_Pipe/MGPipeTypes.h"
|
||||
#include <unordered_map>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
enum class BufferKind : Uint8 {
|
||||
@@ -99,6 +103,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool Initialize(const VkBufferManagerInitInfo& initInfo);
|
||||
void Shutdown();
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Registered before caps publication; the initialized renderer owns the storage.
|
||||
static void RegisterWireResourceOps();
|
||||
// Transport resources are owned by their complete wire handle, never by a
|
||||
// frontend BufferObject. Acquires expose the full GPU store, without a CPU
|
||||
// pointer: CPU consumers must use ReadWireBuffer for ordered, current bytes.
|
||||
Bool AcquireWireSlice(BufferKind kind, MG_Pipe::MGPipeHandle res, BufferSlice& outSlice);
|
||||
Bool ReadWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, void* dst);
|
||||
Bool CopyWireBufferRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
|
||||
const BufferSlice& dst);
|
||||
void MarkWireBufferGpuWritten(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size);
|
||||
|
||||
// Resource-op entry points. All run on the server apply owner.
|
||||
void CreateWireBuffer(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc);
|
||||
void RespecifyWireBuffer(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc,
|
||||
const void* initialBytes);
|
||||
void WriteWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, const void* bytes);
|
||||
void FlushWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, const void* bytes);
|
||||
void ReadbackWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size);
|
||||
void DestroyWireBuffer(MG_Pipe::MGPipeHandle res);
|
||||
#endif
|
||||
|
||||
// Recreate all per-frame transient arenas
|
||||
Bool RecreateTransientArenas(Uint32 frameCount);
|
||||
void BeginFrame(Uint32 frameIndex);
|
||||
@@ -180,6 +206,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool IsResourceBusy(const VkBufferResource& resource) const;
|
||||
|
||||
private:
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
struct WireBufferResource {
|
||||
VkBufferObject buffer;
|
||||
Uint64 size = 0;
|
||||
Uint64 lastUseSerial = 0;
|
||||
Bool gpuWritesPending = false;
|
||||
// Only ranges actually submitted by resource_subdata are covered. No
|
||||
// shadow is retained: flush cannot replay stale bytes over GPU writes.
|
||||
Vector<Range1D> stagedCoverage;
|
||||
};
|
||||
static Uint64 WireBufferKey(MG_Pipe::MGPipeHandle res) {
|
||||
return (static_cast<Uint64>(res.Gen) << 32) | res.Slot;
|
||||
}
|
||||
WireBufferResource* FindWireBuffer(MG_Pipe::MGPipeHandle res);
|
||||
Bool WaitForWireBufferHostAccess(WireBufferResource& resource);
|
||||
#endif
|
||||
Bool InitializeTransientArenas();
|
||||
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
|
||||
VkBufferResource* GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
|
||||
@@ -192,6 +234,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// in-flight and already-recorded GPU work.
|
||||
Bool StagedRangeCopy(VkBufferResource& resource, const void* data,
|
||||
SizeT offset, SizeT size);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Bool StagedWireRangeCopy(WireBufferResource& resource, const void* data, SizeT offset, SizeT size);
|
||||
#endif
|
||||
void DeferRelease(VkBufferObject&& buffer);
|
||||
void CollectDeferredReleases(Uint32 frameIndex);
|
||||
void DestroyAllDeferredReleases();
|
||||
@@ -211,6 +256,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
|
||||
Vector<WeakPtr<VkBufferResource>> m_liveResources;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
std::unordered_map<Uint64, WireBufferResource> m_wireBuffers;
|
||||
#endif
|
||||
// Size m_liveResources had just after the last sweep; the next sweep waits for it to double.
|
||||
SizeT m_liveResourcesLastPruned = 0;
|
||||
Uint32 m_currentFrameIndex = 0;
|
||||
|
||||
@@ -141,6 +141,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// IBufferCopyCommandProvider: recording command buffer, outside any
|
||||
// render pass, for immediate staged buffer copies.
|
||||
VkCommandBuffer AcquireBufferCopyCommandBuffer() override;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
VkBufferManager& GetWireBufferManager() { return m_bufferManager; }
|
||||
#endif
|
||||
|
||||
// FrameContext::IRecordingObserver: prepares the frame's timer-query
|
||||
// pool (harvest + reset) right after the frame command buffer begins
|
||||
|
||||
@@ -16,6 +16,42 @@
|
||||
std::abort();
|
||||
}
|
||||
|
||||
static Bool ConvertWireVertexStream(const MGPVertexAttribWire& attr,
|
||||
VertexInputStateFactory::VertexStreamConversion conversion, const Uint8* source,
|
||||
SizeT stride, SizeT count, Vector<Uint8>& output) {
|
||||
using Conversion = VertexInputStateFactory::VertexStreamConversion;
|
||||
const auto type = static_cast<DataType>(attr.Type);
|
||||
const SizeT elementBytes = VertexInputStateFactory::GetAttributeByteSize(type, attr.Size, attr.IsBgra);
|
||||
if (conversion == Conversion::Repack) {
|
||||
output.resize(count * elementBytes);
|
||||
for (SizeT i = 0; i < count; ++i)
|
||||
Memcpy(output.data() + i * elementBytes, source + i * stride, elementBytes);
|
||||
return true;
|
||||
}
|
||||
output.resize(count * attr.Size * sizeof(Float));
|
||||
const auto convert = [&]<typename T>() {
|
||||
for (SizeT i = 0; i < count; ++i) {
|
||||
for (SizeT component = 0; component < attr.Size; ++component) {
|
||||
T value{};
|
||||
Memcpy(&value, source + i * stride + component * sizeof(T), sizeof(T));
|
||||
const Float converted = static_cast<Float>(value);
|
||||
Memcpy(output.data() + (i * attr.Size + component) * sizeof(Float), &converted, sizeof(Float));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
switch (type) {
|
||||
case DataType::Float64: return convert.template operator()<Double>();
|
||||
case DataType::Int8: return convert.template operator()<Int8>();
|
||||
case DataType::Uint8: return convert.template operator()<Uint8>();
|
||||
case DataType::Int16: return convert.template operator()<Int16>();
|
||||
case DataType::Uint16: return convert.template operator()<Uint16>();
|
||||
case DataType::Int32: return convert.template operator()<Int32>();
|
||||
case DataType::Uint32: return convert.template operator()<Uint32>();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::DestroyWireDrawPass() {
|
||||
// FlushPendingCommands only submits. These objects may still be referenced by
|
||||
// that submission, so retire this temporary pass after the queue completes.
|
||||
@@ -30,17 +66,17 @@
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const auto program = WireProgramSource(state.BoundShaderCso);
|
||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) return false;
|
||||
if (((aspects & DrawSetupAspect::IndexBuffer) && (!indices || !indices->forceClientMemory)) ||
|
||||
aspects & DrawSetupAspect::IndirectDrawBuffer ||
|
||||
MGB_CTX->IsTransformFeedbackActive()) WireBufferLegacyFatal();
|
||||
if (aspects & DrawSetupAspect::IndirectDrawBuffer || MGB_CTX->IsTransformFeedbackActive())
|
||||
WireBufferLegacyFatal();
|
||||
const auto* fbo = state.DrawFramebuffer();
|
||||
if (!fbo) MagmaWireFatal("draw-framebuffer-record");
|
||||
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("draw-flush");
|
||||
DestroyWireDrawPass();
|
||||
|
||||
ProgramFactory::CompileOptionFlags flags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
|
||||
ProgramFactory::CompileOptionFlags flags =
|
||||
ProgramFactory::CompileOptionFlags(GetBaseTransformFlagsRaw(fbo->IsDefault));
|
||||
const auto* programObject = &m_programFactory->GetOrCreateProgram(program, flags);
|
||||
if (programObject->readsBaseVertexBuiltin) {
|
||||
if (!(aspects & DrawSetupAspect::IndexBuffer) && programObject->readsBaseVertexBuiltin) {
|
||||
flags |= ProgramFactory::CompileOptionBit::ZeroBaseVertex;
|
||||
programObject = &m_programFactory->GetOrCreateProgram(program, flags);
|
||||
}
|
||||
@@ -48,13 +84,99 @@
|
||||
const auto& programObj = *programObject;
|
||||
const auto vaoHandle = state.BoundVertexElements;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(vaoHandle)) MagmaWireFatal("vertex-elements-record");
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(vaoHandle)) {
|
||||
if (vaoHandle.Slot >= state.VertexElementsCsos.size()) MagmaWireFatal("vertex-elements-record");
|
||||
const auto& vao = state.VertexElementsCsos[vaoHandle.Slot];
|
||||
if (!vao.Live || vao.Gen != vaoHandle.Gen) MagmaWireFatal("vertex-elements-generation");
|
||||
for (Uint32 i = 0; i < vao.AttributeCount; ++i)
|
||||
if (vao.Attributes[i].Enabled && (programObj.activeVertexInputLocationMask & (1u << i)))
|
||||
WireBufferLegacyFatal();
|
||||
if (vaoHandle.Slot >= state.VertexElementsCsos.size()) MagmaWireFatal("vertex-elements-record");
|
||||
const auto& vao = state.VertexElementsCsos[vaoHandle.Slot];
|
||||
if (!vao.Live || vao.Gen != vaoHandle.Gen) MagmaWireFatal("vertex-elements-generation");
|
||||
VertexInputStateFactory::BackendVertexInputState input{};
|
||||
if (!m_vertexInputStateFactory->BuildWireVertexInput(vao, state,
|
||||
programObj.activeVertexInputLocationMask, input)) MagmaWireFatal("vertex-layout@P7");
|
||||
Vector<BufferSlice> vertexSlices(input.bindings.size());
|
||||
for (SizeT binding = 0; binding < input.bindings.size(); ++binding) {
|
||||
const Uint32 location = input.bindingAttributeLocations[binding];
|
||||
const auto& attr = vao.Attributes[location];
|
||||
const auto& resource = state.VertexBuffers[location];
|
||||
if (MG_Pipe::MGPipeHandleIsNull(resource.Res)) MagmaWireFatal("client-vertex-array@P8");
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager.AcquireWireSlice(BufferKind::Vertex, resource.Res, slice))
|
||||
MagmaWireFatal("vertex-buffer-resource");
|
||||
const VkDeviceSize offset = input.bindingBaseOffsets[binding];
|
||||
const auto type = static_cast<DataType>(attr.Type);
|
||||
const SizeT elementSize = VertexInputStateFactory::GetAttributeByteSize(type, attr.Size, attr.IsBgra);
|
||||
if (offset > slice.size || elementSize > slice.size - offset) return false;
|
||||
const auto conversion = input.bindingConversions[binding];
|
||||
if (conversion != VertexInputStateFactory::VertexStreamConversion::None) {
|
||||
const SizeT stride = static_cast<SizeT>(attr.Stride);
|
||||
SizeT count = stride ? 1 + (static_cast<SizeT>(slice.size - offset) - elementSize) / stride : 1;
|
||||
// Direct non-indexed draws bound the conversion; indexed/multi-draw may
|
||||
// name arbitrary vertices, so preserve the entire valid source stream.
|
||||
if (stride && !(aspects & DrawSetupAspect::IndexBuffer) && !resource.Divisor)
|
||||
count = std::min<SizeT>(count, static_cast<SizeT>(drawParams.firstVertex) + drawParams.vertexCount);
|
||||
if (!count) return false;
|
||||
Vector<Uint8> source((count - 1) * stride + elementSize);
|
||||
if (!m_bufferManager.ReadWireBuffer(resource.Res, offset, source.size(), source.data())) return false;
|
||||
Vector<Uint8> converted;
|
||||
if (!ConvertWireVertexStream(attr, conversion, source.data(), stride, count, converted))
|
||||
MagmaWireFatal("vertex-format-conversion@P7");
|
||||
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
|
||||
converted.data(), converted.size(), 16, vertexSlices[binding])) return false;
|
||||
} else {
|
||||
slice.offset += offset;
|
||||
slice.size -= offset;
|
||||
vertexSlices[binding] = slice;
|
||||
}
|
||||
}
|
||||
|
||||
BufferSlice indexSlice{};
|
||||
VkIndexType indexType = VK_INDEX_TYPE_MAX_ENUM;
|
||||
if (aspects & DrawSetupAspect::IndexBuffer) {
|
||||
if (!indices) return false;
|
||||
const SizeT width = MG_Util::GetGLTypeSize(indices->indexType);
|
||||
if (width != 1 && width != 2 && width != 4) return false;
|
||||
indexType = width == 4 ? VK_INDEX_TYPE_UINT32 : width == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT8;
|
||||
const auto& restart = MGB_CTX->GetRenderStateParameters();
|
||||
const Uint32 fixed = MG_Util::FixedRestartIndexForGLType(indices->indexType);
|
||||
const Bool replaceRestart = restart.PrimitiveRestartEnabled && !restart.PrimitiveRestartFixedIndexEnabled &&
|
||||
restart.PrimitiveRestartIndex < fixed;
|
||||
const Bool expandByte = width == 1 && !m_indexTypeUint8ExtensionEnabled;
|
||||
const Bool cpuIndices = indices->forceClientMemory || MG_Pipe::MGPipeHandleIsNull(state.IndexBuffer.Res);
|
||||
Vector<Uint8> copied;
|
||||
const void* bytes = nullptr;
|
||||
if (cpuIndices) bytes = reinterpret_cast<const void*>(indices->indexByteOffset);
|
||||
else {
|
||||
if (!m_bufferManager.AcquireWireSlice(BufferKind::Index, state.IndexBuffer.Res, indexSlice))
|
||||
MagmaWireFatal("index-buffer-resource");
|
||||
const Uint64 offset = state.IndexBuffer.Offset + indices->indexByteOffset;
|
||||
if (offset < state.IndexBuffer.Offset || offset > indexSlice.size ||
|
||||
indices->indexByteSize > indexSlice.size - offset) return false;
|
||||
if (replaceRestart || expandByte || offset % width) {
|
||||
copied.resize(indices->indexByteSize);
|
||||
if (!m_bufferManager.ReadWireBuffer(state.IndexBuffer.Res, offset, copied.size(), copied.data())) return false;
|
||||
bytes = copied.data();
|
||||
}
|
||||
indexSlice.offset += offset;
|
||||
indexSlice.size = indices->indexByteSize;
|
||||
}
|
||||
if (bytes) {
|
||||
Vector<Uint8> rewritten;
|
||||
SizeT uploadSize = indices->indexByteSize;
|
||||
if (replaceRestart || expandByte) {
|
||||
const SizeT outWidth = expandByte ? 2 : width;
|
||||
rewritten.resize(uploadSize / width * outWidth);
|
||||
for (SizeT i = 0; i < uploadSize / width; ++i) {
|
||||
Uint32 value = 0;
|
||||
Memcpy(&value, static_cast<const Uint8*>(bytes) + i * width, width);
|
||||
if (replaceRestart && value == restart.PrimitiveRestartIndex) value = fixed;
|
||||
if (expandByte && ResolvePrimitiveRestartEnable(aspects, indices) && value == fixed) value = 0xffffu;
|
||||
Memcpy(rewritten.data() + i * outWidth, &value, outWidth);
|
||||
}
|
||||
bytes = rewritten.data();
|
||||
uploadSize = rewritten.size();
|
||||
if (expandByte) indexType = VK_INDEX_TYPE_UINT16;
|
||||
}
|
||||
if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(),
|
||||
bytes, uploadSize, 16, indexSlice)) return false;
|
||||
}
|
||||
if (!indexSlice.IsValid()) return false;
|
||||
}
|
||||
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
@@ -150,12 +272,15 @@
|
||||
pass.extent = {static_cast<Int>(extent.width), static_cast<Int>(extent.height)};
|
||||
pass.layers = layers;
|
||||
pass.hash = pass.compatibilityHash = XXH64(colors.data(), colors.size() * sizeof(colors[0]), passHash);
|
||||
VertexInputStateFactory::BackendVertexInputState input{};
|
||||
const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass, false);
|
||||
const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass,
|
||||
ResolvePrimitiveRestartEnable(aspects, indices));
|
||||
if (pipeline == VK_NULL_HANDLE) return false;
|
||||
Uint32 binding = 0;
|
||||
for (Uint32 i = 0; i < vertexSlices.size(); ++i)
|
||||
vkCmdBindVertexBuffers(frame.commandBuffer, i, 1, &vertexSlices[i].buffer, &vertexSlices[i].offset);
|
||||
Uint32 binding = static_cast<Uint32>(vertexSlices.size());
|
||||
for (Uint32 location = 0; location < kMaxVertexAttribs; ++location) {
|
||||
if (!(programObj.activeVertexInputLocationMask & (1u << location))) continue;
|
||||
if (!(programObj.activeVertexInputLocationMask & (1u << location)) ||
|
||||
(input.attributeLocationMask & (1u << location))) continue;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
const void* data = nullptr;
|
||||
VkDeviceSize size = 0;
|
||||
@@ -166,19 +291,12 @@
|
||||
data, size, 16, slice)) return false;
|
||||
vkCmdBindVertexBuffers(frame.commandBuffer, binding++, 1, &slice.buffer, &slice.offset);
|
||||
}
|
||||
if (indices) {
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(),
|
||||
reinterpret_cast<const void*>(indices->indexByteOffset), indices->indexByteSize, 16, slice)) return false;
|
||||
const VkIndexType type = indices->indexType == GL_UNSIGNED_INT ? VK_INDEX_TYPE_UINT32 :
|
||||
indices->indexType == GL_UNSIGNED_SHORT ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT8;
|
||||
vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, slice.offset, type);
|
||||
}
|
||||
if (indexSlice.IsValid()) vkCmdBindIndexBuffer(frame.commandBuffer, indexSlice.buffer, indexSlice.offset, indexType);
|
||||
if (!VkRenderPassManager::BeginRenderPass(frame.commandBuffer, pass)) return false;
|
||||
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
ApplyDynamicDrawStateTail(frame, pass.extent, fbo->IsDefault,
|
||||
ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin));
|
||||
(void)drawParams;
|
||||
if (MG_Util::PipeStats::Enabled()) MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::Draws, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,8 +113,7 @@ namespace MobileGL::MG_Backend {
|
||||
switch (type) {
|
||||
case BackendType::DirectGLES: return MG_Pipe::kMGPipeSubsystemsMigratedAtP5e;
|
||||
case BackendType::DirectVulkan:
|
||||
return (MG_Pipe::kMGPipeSubsystemsMigratedAtP4a | MG_Pipe::kMGPipeSubsystemBufferBindings) &
|
||||
~MG_Pipe::kMGPipeSubsystemResources;
|
||||
return MG_Pipe::kMGPipeSubsystemsMigratedAtP4a | MG_Pipe::kMGPipeSubsystemBufferBindings;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user