mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Magma] provide native placeholders for unbound wire image descriptors
This commit is contained in:
@@ -118,9 +118,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (unit < 0 || static_cast<Uint32>(unit) >=
|
||||
(storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) return false;
|
||||
const auto handle = storage ? state.BoundShaderImages[unit].Res : state.BoundSamplerViews[unit].Texture;
|
||||
// The old placeholder constructor enters the guarded client allocator. Keep that
|
||||
// P7 boundary explicit instead of constructing a frontend texture on the server.
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) WireDescriptorFatal("unbound-image-placeholder@P7");
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle))
|
||||
return ResolveWirePlaceholderImage(commandBuffer, program, programObj, binding, storage, out);
|
||||
if (handle.Slot >= state.TextureResources.size()) WireDescriptorFatal("image-record");
|
||||
const auto& record = state.TextureResources[handle.Slot];
|
||||
if (!record.Live || record.Gen != handle.Gen) WireDescriptorFatal("image-record-generation");
|
||||
@@ -402,6 +401,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
#include "WirePlaceholderImages.inc"
|
||||
|
||||
static Bool FindFramebufferAttachmentForTexture(const MG_State::GLState::FramebufferObject& framebuffer,
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
FramebufferAttachmentType& outAttachment, Int& outLevel) {
|
||||
@@ -598,6 +599,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
frame.peakAllocatedSetsThisFrame = 0;
|
||||
}
|
||||
m_frames.clear();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
for (auto& entry : m_wirePlaceholderImages) DestroyWirePlaceholderImage(entry.second);
|
||||
m_wirePlaceholderImages.clear();
|
||||
#endif
|
||||
|
||||
m_bufferManager = nullptr;
|
||||
m_programFactory = nullptr;
|
||||
@@ -2825,7 +2830,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
(storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) return false;
|
||||
const auto handle = storage ? state.BoundShaderImages[unit].Res
|
||||
: state.BoundSamplerViews[unit].Texture;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) WireDescriptorFatal("unbound-image-placeholder@P7");
|
||||
// Null is the wire's unbound/incomplete binding. Its native
|
||||
// placeholder needs no resource upload or frontend allocation.
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) continue;
|
||||
if (!m_textureManager->SyncTextureResourceByHandle(handle, false, storage)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
struct WireImageViewKeyHash {
|
||||
SizeT operator()(const WireImageViewKey& key) const;
|
||||
};
|
||||
|
||||
struct WirePlaceholderImage {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
VkRenderPass clearPass = VK_NULL_HANDLE;
|
||||
VkFramebuffer clearFramebuffer = VK_NULL_HANDLE;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
Uint32 layers = 1;
|
||||
};
|
||||
mutable UnorderedMap<Uint64, WirePlaceholderImage> m_wirePlaceholderImages;
|
||||
Bool ResolveWirePlaceholderImage(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Bool storage,
|
||||
VkDescriptorImageInfo& out) const;
|
||||
void DestroyWirePlaceholderImage(WirePlaceholderImage& image) const;
|
||||
#endif
|
||||
|
||||
struct FrameResources {
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
// UniformManager-owned native descriptors for wire bindings with no image.
|
||||
// No GL object is constructed or inserted into the client's resource namespace.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
void UniformManager::DestroyWirePlaceholderImage(WirePlaceholderImage& image) const {
|
||||
if (m_device != VK_NULL_HANDLE) {
|
||||
if (image.clearFramebuffer) vkDestroyFramebuffer(m_device, image.clearFramebuffer, nullptr);
|
||||
if (image.clearPass) vkDestroyRenderPass(m_device, image.clearPass, nullptr);
|
||||
if (image.sampler) vkDestroySampler(m_device, image.sampler, nullptr);
|
||||
if (image.view) vkDestroyImageView(m_device, image.view, nullptr);
|
||||
if (image.image) vkDestroyImage(m_device, image.image, nullptr);
|
||||
if (image.memory) vkFreeMemory(m_device, image.memory, nullptr);
|
||||
}
|
||||
image = {};
|
||||
}
|
||||
|
||||
Bool UniformManager::ResolveWirePlaceholderImage(VkCommandBuffer commands, const MagmaProgramSource& program,
|
||||
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Bool storage,
|
||||
VkDescriptorImageInfo& out) const {
|
||||
if (binding >= programObj.samplerTextureTargetByBinding.size() ||
|
||||
binding >= programObj.samplerNumericDomainByBinding.size()) return false;
|
||||
const auto target = programObj.samplerTextureTargetByBinding[binding];
|
||||
const auto domain = programObj.samplerNumericDomainByBinding[binding];
|
||||
const Bool multisample = target == TextureTarget::Texture2DMultisample ||
|
||||
target == TextureTarget::Texture2DMultisampleArray;
|
||||
Bool shadow = false;
|
||||
if (!storage) {
|
||||
const Int location = programObj.samplerUniformLocationByBinding[binding];
|
||||
switch (program.GetUniformType(static_cast<Uint>(location))) {
|
||||
case GL_SAMPLER_1D_SHADOW: case GL_SAMPLER_2D_SHADOW: case GL_SAMPLER_CUBE_SHADOW:
|
||||
case GL_SAMPLER_1D_ARRAY_SHADOW: case GL_SAMPLER_2D_ARRAY_SHADOW:
|
||||
case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW: case GL_SAMPLER_2D_RECT_SHADOW: shadow = true; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
if (storage) {
|
||||
if (binding >= programObj.storageImageFormatByBinding.size()) return false;
|
||||
format = programObj.storageImageFormatByBinding[binding];
|
||||
if (format == VK_FORMAT_UNDEFINED) format = PlaceholderFormatForNumericDomain(domain);
|
||||
} else if (shadow) format = VK_FORMAT_D16_UNORM;
|
||||
else if (domain == SamplerNumericDomain::Float) format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
else if (domain == SamplerNumericDomain::SignedInteger) format = VK_FORMAT_R8G8B8A8_SINT;
|
||||
else if (domain == SamplerNumericDomain::UnsignedInteger) format = VK_FORMAT_R8G8B8A8_UINT;
|
||||
if (format == VK_FORMAT_UNDEFINED) return false;
|
||||
VkImageType imageType = VK_IMAGE_TYPE_2D;
|
||||
VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
VkImageCreateFlags flags = 0;
|
||||
Uint32 layers = 1;
|
||||
switch (target) {
|
||||
case TextureTarget::Texture1D: imageType = VK_IMAGE_TYPE_1D; viewType = VK_IMAGE_VIEW_TYPE_1D; break;
|
||||
case TextureTarget::Texture1DArray: imageType = VK_IMAGE_TYPE_1D; viewType = VK_IMAGE_VIEW_TYPE_1D_ARRAY; break;
|
||||
case TextureTarget::Texture2D: case TextureTarget::TextureRectangle:
|
||||
case TextureTarget::Texture2DMultisample: break;
|
||||
case TextureTarget::Texture2DArray: case TextureTarget::Texture2DMultisampleArray:
|
||||
viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; break;
|
||||
case TextureTarget::Texture3D: imageType = VK_IMAGE_TYPE_3D; viewType = VK_IMAGE_VIEW_TYPE_3D; break;
|
||||
case TextureTarget::TextureCubeMap: case TextureTarget::TextureCubeMapArray:
|
||||
flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; layers = 6;
|
||||
viewType = target == TextureTarget::TextureCubeMap ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
const Uint64 key = static_cast<Uint32>(format) | (static_cast<Uint64>(target) << 32) |
|
||||
(static_cast<Uint64>(storage) << 48) | (static_cast<Uint64>(shadow) << 49);
|
||||
auto found = m_wirePlaceholderImages.find(key);
|
||||
if (found == m_wirePlaceholderImages.end()) {
|
||||
WirePlaceholderImage image{};
|
||||
image.format = format; image.layers = layers;
|
||||
image.aspect = shadow ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const auto fail = [&] {
|
||||
MGLOG_E_ONCE("Magma wire placeholder allocation failed: target=%d format=%d storage=%d multisample=%d",
|
||||
static_cast<Int>(target), static_cast<Int>(format), static_cast<Int>(storage), static_cast<Int>(multisample));
|
||||
DestroyWirePlaceholderImage(image);
|
||||
return false;
|
||||
};
|
||||
const VkImageUsageFlags usage = (storage ? VK_IMAGE_USAGE_STORAGE_BIT : VK_IMAGE_USAGE_SAMPLED_BIT) |
|
||||
(multisample ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : VK_IMAGE_USAGE_TRANSFER_DST_BIT);
|
||||
VkImageFormatProperties properties{};
|
||||
if (vkGetPhysicalDeviceImageFormatProperties(m_physicalDevice, format, imageType,
|
||||
VK_IMAGE_TILING_OPTIMAL, usage, flags, &properties) != VK_SUCCESS) return fail();
|
||||
if (multisample) {
|
||||
// MS=1 in SPIR-V cannot name a single-sampled image, even when
|
||||
// the shader's runtime branch does not fetch that descriptor.
|
||||
image.samples = VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM;
|
||||
for (const auto sample : {VK_SAMPLE_COUNT_2_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_8_BIT,
|
||||
VK_SAMPLE_COUNT_16_BIT, VK_SAMPLE_COUNT_32_BIT, VK_SAMPLE_COUNT_64_BIT}) {
|
||||
if (properties.sampleCounts & sample) { image.samples = sample; break; }
|
||||
}
|
||||
if (image.samples == VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM) return fail();
|
||||
}
|
||||
VkImageCreateInfo create{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
create.flags = flags; create.imageType = imageType; create.format = format;
|
||||
create.extent = {1, 1, 1}; create.mipLevels = 1; create.arrayLayers = layers;
|
||||
create.samples = image.samples; create.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
create.usage = usage; create.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
if (vkCreateImage(m_device, &create, nullptr, &image.image) != VK_SUCCESS) return fail();
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetImageMemoryRequirements(m_device, image.image, &requirements);
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
|
||||
Uint32 memoryType = ~0u;
|
||||
for (Uint32 i = 0; i < memoryProperties.memoryTypeCount; ++i) {
|
||||
if (!(requirements.memoryTypeBits & (1u << i))) continue;
|
||||
if (memoryType == ~0u) memoryType = i;
|
||||
if (memoryProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
|
||||
memoryType = i; break;
|
||||
}
|
||||
}
|
||||
if (memoryType == ~0u) return fail();
|
||||
VkMemoryAllocateInfo allocate{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocate.allocationSize = requirements.size; allocate.memoryTypeIndex = memoryType;
|
||||
if (vkAllocateMemory(m_device, &allocate, nullptr, &image.memory) != VK_SUCCESS ||
|
||||
vkBindImageMemory(m_device, image.image, image.memory, 0) != VK_SUCCESS) return fail();
|
||||
VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
view.image = image.image; view.viewType = viewType; view.format = format;
|
||||
view.subresourceRange = {image.aspect, 0, 1, 0, layers};
|
||||
if (vkCreateImageView(m_device, &view, nullptr, &image.view) != VK_SUCCESS) return fail();
|
||||
if (!storage) {
|
||||
VkSamplerCreateInfo sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
||||
sampler.magFilter = sampler.minFilter = VK_FILTER_NEAREST;
|
||||
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
sampler.addressModeU = sampler.addressModeV = sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler.compareEnable = shadow; sampler.compareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
sampler.maxLod = 0.0f;
|
||||
if (vkCreateSampler(m_device, &sampler, nullptr, &image.sampler) != VK_SUCCESS) return fail();
|
||||
}
|
||||
if (multisample) {
|
||||
VkAttachmentDescription attachment{};
|
||||
attachment.format = format; attachment.samples = image.samples;
|
||||
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachment.finalLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
VkAttachmentReference color{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color;
|
||||
VkRenderPassCreateInfo pass{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
pass.attachmentCount = 1; pass.pAttachments = &attachment;
|
||||
pass.subpassCount = 1; pass.pSubpasses = &subpass;
|
||||
VkSubpassDependency dependencies[2]{};
|
||||
dependencies[0] = {VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0};
|
||||
dependencies[1] = {0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0};
|
||||
pass.dependencyCount = 2; pass.pDependencies = dependencies;
|
||||
if (vkCreateRenderPass(m_device, &pass, nullptr, &image.clearPass) != VK_SUCCESS) return fail();
|
||||
VkFramebufferCreateInfo framebuffer{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebuffer.renderPass = image.clearPass; framebuffer.attachmentCount = 1;
|
||||
framebuffer.pAttachments = &image.view; framebuffer.width = framebuffer.height = 1; framebuffer.layers = layers;
|
||||
if (vkCreateFramebuffer(m_device, &framebuffer, nullptr, &image.clearFramebuffer) != VK_SUCCESS) return fail();
|
||||
}
|
||||
found = m_wirePlaceholderImages.emplace(key, image).first;
|
||||
}
|
||||
auto& image = found->second;
|
||||
// Sampled incomplete textures remain immutable opaque black. A storage
|
||||
// placeholder is reset before each use, so a prior discarded store cannot
|
||||
// leak into an unrelated invocation's unbound image loads.
|
||||
if (storage || image.layout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
VkClearValue clear{};
|
||||
if (!storage) {
|
||||
if (domain == SamplerNumericDomain::SignedInteger) clear.color.int32[3] = 1;
|
||||
else if (domain == SamplerNumericDomain::UnsignedInteger) clear.color.uint32[3] = 1;
|
||||
else clear.color.float32[3] = 1.0f;
|
||||
}
|
||||
if (multisample) {
|
||||
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
begin.renderPass = image.clearPass; begin.framebuffer = image.clearFramebuffer;
|
||||
begin.renderArea.extent = {1, 1}; begin.clearValueCount = 1; begin.pClearValues = &clear;
|
||||
vkCmdBeginRenderPass(commands, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdEndRenderPass(commands);
|
||||
image.layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
} else {
|
||||
if (!VkTextureManager::TransitionImageLayout(commands, image.image, image.layout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, image.aspect)) return false;
|
||||
const VkImageSubresourceRange range{image.aspect, 0, 1, 0, image.layers};
|
||||
if (shadow) {
|
||||
const VkClearDepthStencilValue depth{0.0f, 0};
|
||||
vkCmdClearDepthStencilImage(commands, image.image, image.layout, &depth, 1, &range);
|
||||
} else vkCmdClearColorImage(commands, image.image, image.layout, &clear.color, 1, &range);
|
||||
if (!VkTextureManager::TransitionImageLayout(commands, image.image, image.layout,
|
||||
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
image.aspect)) return false;
|
||||
}
|
||||
VkMemoryBarrier visible{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
visible.srcAccessMask = multisample ? VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
visible.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commands, multisample ? VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &visible, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
out = {image.sampler, image.view, VK_IMAGE_LAYOUT_GENERAL};
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user