mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend] (Disaggregated): generate packed float mips with native shader blits
This commit is contained in:
@@ -478,6 +478,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool BlitWireColorToDefault(WireImage source, WireImage destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter);
|
||||
Bool BlitWireColorImage(WireImage source, WireImage destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter, Bool mipmap);
|
||||
struct WireColorBlitResources;
|
||||
WireColorBlitResources* m_wireColorBlitResources = nullptr;
|
||||
void DestroyWireColorBlitResources();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
struct VulkanRenderer::WireColorBlitResources {
|
||||
struct Target {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkRenderPass renderPass = VK_NULL_HANDLE;
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
};
|
||||
@@ -38,15 +39,25 @@
|
||||
Bool VulkanRenderer::BlitWireColorToDefault(WireImage source, WireImage destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter) {
|
||||
if (!destination.isDefault || source.isDefault || source.image == destination.image ||
|
||||
if (!destination.isDefault) return false;
|
||||
return BlitWireColorImage(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, filter, false);
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::BlitWireColorImage(WireImage source, WireImage destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter, Bool mipmap) {
|
||||
const Bool sameImage = source.image == destination.image;
|
||||
if (source.isDefault || (sameImage && source.level == destination.level && source.layer == destination.layer) ||
|
||||
source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT ||
|
||||
source.is3D || (source.viewType != VK_IMAGE_VIEW_TYPE_2D &&
|
||||
source.is3D || destination.is3D || (source.viewType != VK_IMAGE_VIEW_TYPE_2D &&
|
||||
source.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) ||
|
||||
(destination.viewType != VK_IMAGE_VIEW_TYPE_2D && destination.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) ||
|
||||
!vkuFormatIsSampledFloat(source.format)) return false;
|
||||
if (sx0 == sx1 || sy0 == sy1 || dx0 == dx1 || dy0 == dy1) return true;
|
||||
if (!source.extent.width || !source.extent.height ||
|
||||
!destination.extent.width || !destination.extent.height) return false;
|
||||
const auto transform = m_swapchainObject.GetPreTransform();
|
||||
const auto transform = destination.isDefault ? m_swapchainObject.GetPreTransform() :
|
||||
VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR &&
|
||||
transform != VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR &&
|
||||
transform != VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR &&
|
||||
@@ -56,6 +67,15 @@
|
||||
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
|
||||
(filter == GL_LINEAR ? VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT : 0);
|
||||
if ((sourceProperties.optimalTilingFeatures & required) != required) return false;
|
||||
// Mip generation samples and writes the texture's own color space;
|
||||
// GL_FRAMEBUFFER_SRGB only controls framebuffer operations.
|
||||
if (!mipmap) destination.format = ResolveSrgbAttachmentWriteFormat(destination.format,
|
||||
MGB_CTX->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb));
|
||||
VkFormatProperties destinationProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, destination.format, &destinationProperties);
|
||||
if (!(destinationProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) return false;
|
||||
const VkImageLayout sourceLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
const VkImageLayout destinationLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
struct Constants {
|
||||
float srcRect[4];
|
||||
@@ -97,17 +117,15 @@
|
||||
VK_VERIFY(vkCreateSampler(m_device, &sampler, nullptr, &cache.linear));
|
||||
}
|
||||
auto& cache = *m_wireColorBlitResources;
|
||||
// Honor the same sRGB write policy as wire draw attachments.
|
||||
destination.format = ResolveSrgbAttachmentWriteFormat(destination.format,
|
||||
MGB_CTX->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb));
|
||||
WireColorBlitResources::Target* target = nullptr;
|
||||
for (auto& entry : cache.targets) {
|
||||
if (entry.format == destination.format) { target = &entry; break; }
|
||||
if (entry.format == destination.format && entry.layout == destinationLayout) { target = &entry; break; }
|
||||
}
|
||||
if (!target) {
|
||||
cache.targets.emplace_back();
|
||||
target = &cache.targets.back();
|
||||
target->format = destination.format;
|
||||
target->layout = destinationLayout;
|
||||
VkAttachmentDescription attachment{};
|
||||
attachment.format = destination.format;
|
||||
attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
@@ -115,8 +133,8 @@
|
||||
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
const VkAttachmentReference color{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
attachment.initialLayout = attachment.finalLayout = destinationLayout;
|
||||
const VkAttachmentReference color{0, destinationLayout};
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
@@ -174,8 +192,12 @@
|
||||
}
|
||||
|
||||
m_textureManager->FlushPendingUploads();
|
||||
TransitionWireImage(source, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
TransitionWireImage(destination, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
// Distinct mip/layer views of the same image are legal shader input and
|
||||
// output. GENERAL keeps the whole-image layout tracker truthful; the
|
||||
// transition also orders the previous mip's attachment write.
|
||||
TransitionWireImage(source, sourceLayout);
|
||||
if (sameImage) destination.layout = source.layout;
|
||||
else TransitionWireImage(destination, destinationLayout);
|
||||
const auto makeView = [&](const WireImage& image) {
|
||||
VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
view.image = image.image;
|
||||
@@ -213,7 +235,7 @@
|
||||
alloc.pSetLayouts = &cache.descriptorLayout;
|
||||
VK_VERIFY(vkAllocateDescriptorSets(m_device, &alloc, &descriptorSet));
|
||||
const VkDescriptorImageInfo sampled{filter == GL_LINEAR ? cache.linear : cache.nearest,
|
||||
sourceView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
sourceView, sourceLayout};
|
||||
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
write.dstSet = descriptorSet;
|
||||
write.dstBinding = 0;
|
||||
@@ -227,6 +249,12 @@
|
||||
transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
|
||||
const float dw = static_cast<float>(quarterTurn ? destination.extent.height : destination.extent.width);
|
||||
const float dh = static_cast<float>(quarterTurn ? destination.extent.width : destination.extent.height);
|
||||
// The shared vertex shader flips window Y. Offscreen images keep the
|
||||
// source row order, so undo that flip in the destination rectangle.
|
||||
if (!destination.isDefault) {
|
||||
dy0 = static_cast<GLint>(destination.extent.height) - dy0;
|
||||
dy1 = static_cast<GLint>(destination.extent.height) - dy1;
|
||||
}
|
||||
const Constants constants{{sx0 / sw, sy0 / sh, (static_cast<float>(sx1) - sx0) / sw,
|
||||
(static_cast<float>(sy1) - sy0) / sh},
|
||||
{dx0 / dw, dy0 / dh, (static_cast<float>(dx1) - dx0) / dw,
|
||||
@@ -260,7 +288,7 @@
|
||||
retired.imageViews = {sourceView, destinationView};
|
||||
retired.descriptorPools.push_back(descriptorPool);
|
||||
RetireWireObjects(Move(retired));
|
||||
m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true);
|
||||
if (destination.isDefault) m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -661,8 +661,9 @@
|
||||
RetireWireDrawPass();
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
Uint32 base = state.VerbMipBaseLevel, baseLayer = 0, layerCount = 1;
|
||||
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
|
||||
const auto handle = m_textureManager->ResolveWireTextureStorage(state.VerbMipRes, base, baseLayer,
|
||||
nullptr, &layerCount);
|
||||
&viewFormat, &layerCount);
|
||||
auto* resource = m_textureManager->SyncTextureResourceByHandle(handle);
|
||||
if (!resource) MagmaWireFatal("mipmap-resource");
|
||||
m_textureManager->FlushPendingUploads();
|
||||
@@ -676,38 +677,74 @@
|
||||
if (resource->aspect != VK_IMAGE_ASPECT_COLOR_BIT && resource->aspect != VK_IMAGE_ASPECT_DEPTH_BIT)
|
||||
MagmaWireFatal("depth-stencil-mipmap@P7");
|
||||
VkFormatProperties props{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,resource->format,&props);
|
||||
const VkFormat format = viewFormat == VK_FORMAT_UNDEFINED ? resource->format : viewFormat;
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,format,&props);
|
||||
const auto required = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
|
||||
if ((props.optimalTilingFeatures & required) != required) MagmaWireFatal("mipmap-native-format@P7");
|
||||
const Bool nativeBlit = format == resource->format && (props.optimalTilingFeatures & required) == required;
|
||||
const VkFilter filter = props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT ?
|
||||
VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
||||
VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags access = 0;
|
||||
GetImageTransitionSourceState(resource->layout,stage,access);
|
||||
if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,resource->layout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,stage,VK_PIPELINE_STAGE_TRANSFER_BIT,access,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,0,resource->mipLevels)) MagmaWireFatal("mipmap-transition");
|
||||
for (Uint32 level=base+1;level<base+count;++level) {
|
||||
VkImageLayout previous = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT,resource->aspect,level-1,1);
|
||||
VkImageBlit blit{};
|
||||
blit.srcSubresource = {resource->aspect,level-1,baseLayer,layerCount};
|
||||
blit.dstSubresource = {resource->aspect,level,baseLayer,layerCount};
|
||||
blit.srcOffsets[1] = {static_cast<Int32>(std::max(resource->extent.width>>(level-1),1u)),
|
||||
static_cast<Int32>(std::max(resource->extent.height>>(level-1),1u)),
|
||||
static_cast<Int32>(std::max(resource->depth>>(level-1),1u))};
|
||||
blit.dstOffsets[1] = {static_cast<Int32>(std::max(resource->extent.width>>level,1u)),
|
||||
static_cast<Int32>(std::max(resource->extent.height>>level,1u)),
|
||||
static_cast<Int32>(std::max(resource->depth>>level,1u))};
|
||||
vkCmdBlitImage(frame.commandBuffer,resource->image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
resource->image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&blit,filter);
|
||||
VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,level-1,1);
|
||||
if (!nativeBlit) {
|
||||
// Packed floating-point color formats may support sampling and
|
||||
// rendering without BLIT_DST. Generate from actual GPU mip contents
|
||||
// through the native shader pass; no client or staged texels enter.
|
||||
if (resource->aspect != VK_IMAGE_ASPECT_COLOR_BIT ||
|
||||
resource->viewType == VK_IMAGE_VIEW_TYPE_1D || resource->viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY ||
|
||||
resource->viewType == VK_IMAGE_VIEW_TYPE_3D ||
|
||||
!(resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) ||
|
||||
baseLayer >= resource->arrayLayers || layerCount > resource->arrayLayers - baseLayer)
|
||||
MagmaWireFatal("mipmap-shader-format-or-shape");
|
||||
for (Uint32 level = base + 1; level < base + count; ++level) {
|
||||
for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) {
|
||||
WireImage source{};
|
||||
source.image = resource->image;
|
||||
source.format = format;
|
||||
source.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
source.layout = resource->layout;
|
||||
source.trackedLayout = &resource->layout;
|
||||
source.levels = resource->mipLevels;
|
||||
source.level = level - 1;
|
||||
source.layer = layer;
|
||||
source.extent = {std::max(resource->extent.width >> (level - 1), 1u),
|
||||
std::max(resource->extent.height >> (level - 1), 1u)};
|
||||
WireImage destination = source;
|
||||
destination.level = level;
|
||||
destination.extent = {std::max(resource->extent.width >> level, 1u),
|
||||
std::max(resource->extent.height >> level, 1u)};
|
||||
if (!BlitWireColorImage(source, destination, 0, 0, source.extent.width, source.extent.height,
|
||||
0, 0, destination.extent.width, destination.extent.height,
|
||||
filter == VK_FILTER_LINEAR ? GL_LINEAR : GL_NEAREST, true))
|
||||
MagmaWireFatal("mipmap-shader-blit");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags access = 0;
|
||||
GetImageTransitionSourceState(resource->layout,stage,access);
|
||||
if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,resource->layout,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,stage,VK_PIPELINE_STAGE_TRANSFER_BIT,access,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,0,resource->mipLevels)) MagmaWireFatal("mipmap-transition");
|
||||
for (Uint32 level=base+1;level<base+count;++level) {
|
||||
VkImageLayout previous = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT,resource->aspect,level-1,1);
|
||||
VkImageBlit blit{};
|
||||
blit.srcSubresource = {resource->aspect,level-1,baseLayer,layerCount};
|
||||
blit.dstSubresource = {resource->aspect,level,baseLayer,layerCount};
|
||||
blit.srcOffsets[1] = {static_cast<Int32>(std::max(resource->extent.width>>(level-1),1u)),
|
||||
static_cast<Int32>(std::max(resource->extent.height>>(level-1),1u)),
|
||||
static_cast<Int32>(std::max(resource->depth>>(level-1),1u))};
|
||||
blit.dstOffsets[1] = {static_cast<Int32>(std::max(resource->extent.width>>level,1u)),
|
||||
static_cast<Int32>(std::max(resource->extent.height>>level,1u)),
|
||||
static_cast<Int32>(std::max(resource->depth>>level,1u))};
|
||||
vkCmdBlitImage(frame.commandBuffer,resource->image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
resource->image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&blit,filter);
|
||||
VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,level-1,1);
|
||||
}
|
||||
resource->layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
}
|
||||
resource->layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
auto& record = state.TextureResources[handle.Slot];
|
||||
record.Desc.Levels = std::max<Uint16>(record.Desc.Levels,static_cast<Uint16>(base+count));
|
||||
resource->syncedWireSerial = ++record.Serial;
|
||||
|
||||
@@ -460,6 +460,25 @@ TEST_F(F1WireScenario, GenerateMipmapPackedFloatPixels) {
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
const GLfloat expected[4] = {0.25f, 0.5f, 0.75f, 1.0f};
|
||||
for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 0.01f);
|
||||
|
||||
// Change the base on the GPU after its client upload. The regenerated mip
|
||||
// must read those new pixels, and offscreen shader blits must keep row order.
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClearColor(0.875f, 0.375f, 0.125f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(0, 4, 8, 4);
|
||||
glClearColor(0.125f, 0.75f, 0.5f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2);
|
||||
GLfloat rows[8]{};
|
||||
glReadPixels(1, 0, 1, 2, GL_RGBA, GL_FLOAT, rows);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
const GLfloat generated[8] = {0.875f, 0.375f, 0.125f, 1.0f, 0.125f, 0.75f, 0.5f, 1.0f};
|
||||
for (int i = 0; i < 8; ++i) EXPECT_NEAR(rows[i], generated[i], 0.01f);
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, GenerateMipmapDepthPixels) {
|
||||
|
||||
Reference in New Issue
Block a user