mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 12:48:32 +09:00
[Feat] (MG_Backend/DirectVulkan): GenerateMipmap WIP
This commit is contained in:
@@ -66,7 +66,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::CopyTexSubImage2D called with null GL context");
|
||||
pVulkanRenderer->CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
|
||||
}
|
||||
void GenerateMipmap(GLenum target) {}
|
||||
void GenerateMipmap(GLenum target) {
|
||||
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GenerateMipmap called with null VulkanRenderer");
|
||||
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::GenerateMipmap called with null GL context");
|
||||
pVulkanRenderer->GenerateMipmap(target);
|
||||
}
|
||||
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {}
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {}
|
||||
|
||||
|
||||
@@ -69,6 +69,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (att.IsTexture()) {
|
||||
const Int textureLevel = att.GetTextureLevel();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel)));
|
||||
|
||||
Uint64 imageIdentity = 0;
|
||||
auto* texture = att.GetTexture().get();
|
||||
auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
|
||||
if (resource != nullptr) {
|
||||
imageIdentity = reinterpret_cast<Uint64>(resource->image);
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity)));
|
||||
}
|
||||
|
||||
if (includePendingClear && att.IsTexture()) {
|
||||
@@ -94,9 +102,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
currentLayout = m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex);
|
||||
}
|
||||
} else {
|
||||
auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
|
||||
if (resource != nullptr) {
|
||||
currentLayout = resource->layout;
|
||||
auto* textureResource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
|
||||
if (textureResource != nullptr) {
|
||||
currentLayout = textureResource->layout;
|
||||
}
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout)));
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static constexpr VkPipelineStageFlags kGraphicsSampledReadStages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
|
||||
|
||||
@@ -43,6 +45,188 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
static void GetImageTransitionSourceState(VkImageLayout oldLayout,
|
||||
VkPipelineStageFlags& outSrcStageMask,
|
||||
VkAccessFlags& outSrcAccessMask) {
|
||||
switch (oldLayout) {
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
outSrcAccessMask = 0;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_GENERAL:
|
||||
outSrcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||
outSrcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
outSrcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
outSrcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
outSrcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
outSrcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
outSrcStageMask = kGraphicsSampledReadStages;
|
||||
outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
outSrcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
outSrcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
return;
|
||||
default:
|
||||
MOBILEGL_ASSERT(false, "GetImageTransitionSourceState: unsupported layout=%d", static_cast<Int>(oldLayout));
|
||||
outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
outSrcAccessMask = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void GetImageTransitionDestinationState(VkImageLayout newLayout,
|
||||
VkPipelineStageFlags& outDstStageMask,
|
||||
VkAccessFlags& outDstAccessMask) {
|
||||
switch (newLayout) {
|
||||
case VK_IMAGE_LAYOUT_GENERAL:
|
||||
outDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||
outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
outDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
outDstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
outDstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
outDstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
outDstStageMask = kGraphicsSampledReadStages;
|
||||
outDstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
outDstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
return;
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
outDstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
return;
|
||||
default:
|
||||
MOBILEGL_ASSERT(false, "GetImageTransitionDestinationState: unsupported layout=%d", static_cast<Int>(newLayout));
|
||||
outDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
outDstAccessMask = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static Bool PreserveTextureContentsOnRecreate(VkDevice device,
|
||||
VkCommandPool commandPool,
|
||||
VkQueue graphicsQueue,
|
||||
const VkTextureManager::TextureResource& oldResource,
|
||||
VkTextureManager::TextureResource& newResource) {
|
||||
MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: device is null");
|
||||
MOBILEGL_ASSERT(commandPool != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: commandPool is null");
|
||||
MOBILEGL_ASSERT(graphicsQueue != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: graphicsQueue is null");
|
||||
MOBILEGL_ASSERT(oldResource.image != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: old image is null");
|
||||
MOBILEGL_ASSERT(newResource.image != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: new image is null");
|
||||
|
||||
const Uint32 preservedMipLevels = std::min(oldResource.mipLevels, newResource.mipLevels);
|
||||
if (preservedMipLevels == 0 || oldResource.layout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
VkCommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = commandPool;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer),
|
||||
"vkAllocateCommandBuffers(texture preserve)");
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer(texture preserve)");
|
||||
|
||||
Bool ok = VkTextureManager::TransitionImageLayout(
|
||||
commandBuffer, newResource.image, newResource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, VK_ACCESS_TRANSFER_WRITE_BIT, newResource.aspect, 0, newResource.mipLevels);
|
||||
MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare destination image");
|
||||
|
||||
VkImageLayout srcTrackedLayout = oldResource.layout;
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags srcAccessMask = 0;
|
||||
GetImageTransitionSourceState(srcTrackedLayout, srcStageMask, srcAccessMask);
|
||||
ok = VkTextureManager::TransitionImageLayout(
|
||||
commandBuffer, oldResource.image, srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, oldResource.aspect, 0, preservedMipLevels);
|
||||
MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare source image");
|
||||
|
||||
Vector<VkImageCopy> copyRegions;
|
||||
copyRegions.reserve(preservedMipLevels);
|
||||
for (Uint32 level = 0; level < preservedMipLevels; ++level) {
|
||||
VkImageCopy copy{};
|
||||
copy.srcSubresource.aspectMask = oldResource.aspect;
|
||||
copy.srcSubresource.mipLevel = level;
|
||||
copy.srcSubresource.baseArrayLayer = 0;
|
||||
copy.srcSubresource.layerCount = oldResource.arrayLayers;
|
||||
copy.dstSubresource.aspectMask = newResource.aspect;
|
||||
copy.dstSubresource.mipLevel = level;
|
||||
copy.dstSubresource.baseArrayLayer = 0;
|
||||
copy.dstSubresource.layerCount = newResource.arrayLayers;
|
||||
copy.extent.width = std::max(oldResource.extent.width >> level, 1u);
|
||||
copy.extent.height = std::max(oldResource.extent.height >> level, 1u);
|
||||
copy.extent.depth = std::max(oldResource.depth >> level, 1u);
|
||||
copyRegions.push_back(copy);
|
||||
}
|
||||
|
||||
vkCmdCopyImage(commandBuffer,
|
||||
oldResource.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
newResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
static_cast<Uint32>(copyRegions.size()), copyRegions.data());
|
||||
|
||||
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags dstAccessMask = 0;
|
||||
GetImageTransitionDestinationState(oldResource.layout, dstStageMask, dstAccessMask);
|
||||
ok = VkTextureManager::TransitionImageLayout(
|
||||
commandBuffer, newResource.image, newResource.layout, oldResource.layout,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, newResource.aspect, 0, newResource.mipLevels);
|
||||
MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to restore destination layout");
|
||||
|
||||
VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture preserve)");
|
||||
|
||||
VkSubmitInfo submitInfo{};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
|
||||
VkFenceCreateInfo fenceInfo{};
|
||||
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateFence(device, &fenceInfo, nullptr, &fence), "vkCreateFence(texture preserve)");
|
||||
VK_VERIFY(vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence), "vkQueueSubmit(texture preserve)");
|
||||
VK_VERIFY(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX), "vkWaitForFences(texture preserve)");
|
||||
|
||||
vkDestroyFence(device, fence, nullptr);
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static TextureFormatInfo ResolveTextureFormatInfo(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::RGB:
|
||||
@@ -446,7 +630,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
resource.Reset();
|
||||
const Bool preserveExistingContent =
|
||||
resource.image != VK_NULL_HANDLE &&
|
||||
resource.format == format &&
|
||||
resource.extent.width == static_cast<Uint32>(texelSize.x()) &&
|
||||
resource.extent.height == static_cast<Uint32>(texelSize.y()) &&
|
||||
resource.depth == shapeInfo.depth &&
|
||||
resource.arrayLayers == shapeInfo.arrayLayers &&
|
||||
resource.viewType == shapeInfo.viewType &&
|
||||
resource.mipLevels < mipLevels &&
|
||||
resource.layout != VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
std::unique_ptr<TextureResource> preservedResource;
|
||||
if (preserveExistingContent) {
|
||||
preservedResource = std::make_unique<TextureResource>(Move(resource));
|
||||
} else {
|
||||
resource.Reset();
|
||||
}
|
||||
|
||||
auto aspect = GetAspectMaskForFormat(format);
|
||||
|
||||
@@ -489,6 +689,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
resource.aspect = aspect;
|
||||
resource.viewType = shapeInfo.viewType;
|
||||
resource.syncedTextureParamsVersion = 0;
|
||||
|
||||
if (preservedResource) {
|
||||
const Bool preserved = PreserveTextureContentsOnRecreate(
|
||||
m_device, m_commandPool, m_graphicsQueue, *preservedResource, resource);
|
||||
MOBILEGL_ASSERT(preserved,
|
||||
"SyncTextureResource: failed to preserve texture contents while growing mip chain");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "MG_State/GLState/ProgramState/ProgramObject.h"
|
||||
#include "MG_State/GLState/ProgramState/ShaderObject.h"
|
||||
#include "MG_State/GLState/SamplerState/SamplerObject.h"
|
||||
#include "MG_State/GLState/TextureState/TextureObject.h"
|
||||
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
|
||||
#include "MG_Util/Converters/GLToMG/TextureEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h"
|
||||
@@ -133,6 +134,68 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static constexpr Uint kHiddenBlitNearestSamplerId = 0xFFFFFFF3u;
|
||||
static constexpr Uint kHiddenBlitLinearSamplerId = 0xFFFFFFF4u;
|
||||
|
||||
static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) {
|
||||
Int maxDimension = std::max<Int>(
|
||||
baseTexelSize.x(),
|
||||
std::max<Int>(baseTexelSize.y(), std::max<Int>(baseTexelSize.z(), 1)));
|
||||
Uint32 mipLevelCount = 1;
|
||||
while (maxDimension > 1) {
|
||||
maxDimension = std::max<Int>(maxDimension / 2, 1);
|
||||
++mipLevelCount;
|
||||
}
|
||||
return mipLevelCount;
|
||||
}
|
||||
|
||||
static IntVec3 ComputeMipTexelSize(const IntVec3& baseTexelSize, Uint32 relativeMipLevel) {
|
||||
const Int width = std::max<Int>(baseTexelSize.x() >> static_cast<Int>(relativeMipLevel), 1);
|
||||
const Int height = std::max<Int>(baseTexelSize.y() >> static_cast<Int>(relativeMipLevel), 1);
|
||||
const Int depth = std::max<Int>(baseTexelSize.z() >> static_cast<Int>(relativeMipLevel), 1);
|
||||
return {width, height, depth};
|
||||
}
|
||||
|
||||
static Bool EnsureGenerateMipmapStorageAllocated(::MobileGL::MG_State::GLState::TextureObjectMipmap& texture,
|
||||
::MobileGL::TextureUploadTarget uploadTarget,
|
||||
Uint32 baseMipLevel) {
|
||||
const Uint32 existingMipLevelCount = static_cast<Uint32>(texture.GetMipmapLevelCount());
|
||||
if (existingMipLevelCount <= baseMipLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const IntVec3 baseTexelSize = texture.GetMipmapTexelSize(uploadTarget, baseMipLevel);
|
||||
const SizeT baseByteSize = texture.GetMipmapByteSize(uploadTarget, baseMipLevel);
|
||||
if (baseTexelSize.x() <= 0 || baseTexelSize.y() <= 0 || baseTexelSize.z() <= 0 || baseByteSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const SizeT baseTexelCount = static_cast<SizeT>(baseTexelSize.x()) * static_cast<SizeT>(baseTexelSize.y()) *
|
||||
static_cast<SizeT>(baseTexelSize.z());
|
||||
if (baseTexelCount == 0 || (baseByteSize % baseTexelCount) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const SizeT bytesPerTexel = baseByteSize / baseTexelCount;
|
||||
const Uint32 requiredMipLevelCount = baseMipLevel + ComputeFullMipLevelCount(baseTexelSize);
|
||||
if (existingMipLevelCount >= requiredMipLevelCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Uint32 level = existingMipLevelCount; level < requiredMipLevelCount; ++level) {
|
||||
const IntVec3 levelTexelSize = ComputeMipTexelSize(baseTexelSize, level - baseMipLevel);
|
||||
const SizeT levelByteSize = bytesPerTexel * static_cast<SizeT>(levelTexelSize.x()) *
|
||||
static_cast<SizeT>(levelTexelSize.y()) *
|
||||
static_cast<SizeT>(levelTexelSize.z());
|
||||
texture.AllocateStorage(uploadTarget, level, {levelTexelSize, levelByteSize});
|
||||
texture.MarkStorageDirty(uploadTarget, level, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static VkImageLayout ResolveGenerateMipmapFinalLayout(VkImageAspectFlags aspectMask) {
|
||||
return (aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0
|
||||
? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
|
||||
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
|
||||
enum class BlitSurfaceTransform : Uint32 {
|
||||
Identity = 0,
|
||||
Rotate90 = 1,
|
||||
@@ -1843,6 +1906,181 @@ void main() {
|
||||
MOBILEGL_ASSERT(dstRestored, "%s: failed to restore destination image layout", __func__);
|
||||
}
|
||||
|
||||
void VulkanRenderer::GenerateMipmap(GLenum target) {
|
||||
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||
const auto uploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
||||
MOBILEGL_ASSERT(textureTarget == TextureTarget::Texture2D || textureTarget == TextureTarget::Texture3D,
|
||||
"GenerateMipmap currently only supports GL_TEXTURE_2D and GL_TEXTURE_3D.");
|
||||
|
||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
||||
auto texture = textureUnit.GetBindingSlot(textureTarget).GetBoundObject();
|
||||
MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture.");
|
||||
MOBILEGL_ASSERT(texture->IsComplete(), "GenerateMipmap requires a complete texture.");
|
||||
|
||||
auto* mipmapTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(texture.get());
|
||||
MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires a mipmapped texture object.");
|
||||
|
||||
const Uint32 currentMipLevelCount = static_cast<Uint32>(mipmapTexture->GetMipmapLevelCount());
|
||||
MOBILEGL_ASSERT(currentMipLevelCount > 0, "GenerateMipmap requires level 0 storage.");
|
||||
|
||||
const Uint32 baseMipLevel = std::min(static_cast<Uint32>(texture->GetLevelRange().x()), currentMipLevelCount - 1);
|
||||
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (!frame.isCommandRecording) {
|
||||
m_frameContext.BeginCommandRecording();
|
||||
m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
|
||||
}
|
||||
|
||||
if (VkRenderPassManager::GetActiveRenderPass() != nullptr) {
|
||||
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
|
||||
}
|
||||
|
||||
const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *texture);
|
||||
MOBILEGL_ASSERT(clearReady,
|
||||
"GenerateMipmap: failed to materialize pending clear for textureId=%d",
|
||||
texture->GetExternalIndex());
|
||||
|
||||
auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
|
||||
MOBILEGL_ASSERT(resource != nullptr && resource->image != VK_NULL_HANDLE,
|
||||
"GenerateMipmap failed to sync the backend texture.");
|
||||
|
||||
VkFormatProperties formatProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, resource->format, &formatProperties);
|
||||
const VkFormatFeatureFlags optimalTilingFeatures = formatProperties.optimalTilingFeatures;
|
||||
if ((optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0 ||
|
||||
(optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0) {
|
||||
MGLOG_W("GenerateMipmap skipped for textureId=%d because Vulkan format %d does not support blit-based mip generation",
|
||||
texture->GetExternalIndex(), static_cast<Int>(resource->format));
|
||||
return;
|
||||
}
|
||||
|
||||
MOBILEGL_ASSERT(EnsureGenerateMipmapStorageAllocated(*mipmapTexture, uploadTarget, baseMipLevel),
|
||||
"GenerateMipmap could not allocate a full mip chain for this texture.");
|
||||
|
||||
resource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
|
||||
MOBILEGL_ASSERT(resource != nullptr && resource->image != VK_NULL_HANDLE,
|
||||
"GenerateMipmap failed to resync the backend texture after allocating mip storage.");
|
||||
MOBILEGL_ASSERT(resource->layout != VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
"GenerateMipmap requires initialized base-level image data.");
|
||||
|
||||
const IntVec3 storageBaseTexelSize = {
|
||||
static_cast<Int>(resource->extent.width),
|
||||
static_cast<Int>(resource->extent.height),
|
||||
static_cast<Int>(resource->depth),
|
||||
};
|
||||
const IntVec3 baseTexelSize = ComputeMipTexelSize(storageBaseTexelSize, baseMipLevel);
|
||||
const Uint32 requiredMipLevelCount = baseMipLevel + ComputeFullMipLevelCount(baseTexelSize);
|
||||
const Uint32 generateMipLevelCount = std::min(requiredMipLevelCount, resource->mipLevels);
|
||||
if (generateMipLevelCount <= baseMipLevel + 1) {
|
||||
resource->layout = ResolveGenerateMipmapFinalLayout(resource->aspect);
|
||||
return;
|
||||
}
|
||||
|
||||
const VkImageLayout originalLayout = resource->layout;
|
||||
const VkImageLayout finalLayout = ResolveGenerateMipmapFinalLayout(resource->aspect);
|
||||
const VkFilter blitFilter = (optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0
|
||||
? VK_FILTER_LINEAR
|
||||
: VK_FILTER_NEAREST;
|
||||
|
||||
VkPipelineStageFlags originalSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags originalSrcAccessMask = 0;
|
||||
GetImageTransitionSourceState(originalLayout, originalSrcStageMask, originalSrcAccessMask);
|
||||
|
||||
VkPipelineStageFlags finalDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags finalDstAccessMask = 0;
|
||||
GetImageTransitionDestinationState(finalLayout, finalDstStageMask, finalDstAccessMask);
|
||||
|
||||
if (originalLayout != finalLayout) {
|
||||
if (baseMipLevel > 0) {
|
||||
VkImageLayout lowerMipLayout = originalLayout;
|
||||
const Bool lowerReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, resource->image, lowerMipLayout, finalLayout,
|
||||
originalSrcStageMask, finalDstStageMask,
|
||||
originalSrcAccessMask, finalDstAccessMask,
|
||||
resource->aspect, 0, baseMipLevel);
|
||||
MOBILEGL_ASSERT(lowerReady, "%s: failed to transition lower untouched mip levels", __func__);
|
||||
}
|
||||
|
||||
if (generateMipLevelCount < resource->mipLevels) {
|
||||
VkImageLayout upperMipLayout = originalLayout;
|
||||
const Bool upperReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, resource->image, upperMipLayout, finalLayout,
|
||||
originalSrcStageMask, finalDstStageMask,
|
||||
originalSrcAccessMask, finalDstAccessMask,
|
||||
resource->aspect, generateMipLevelCount, resource->mipLevels - generateMipLevelCount);
|
||||
MOBILEGL_ASSERT(upperReady, "%s: failed to transition upper untouched mip levels", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
VkImageLayout srcMipLayout = originalLayout;
|
||||
Bool srcReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, resource->image, srcMipLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
originalSrcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
originalSrcAccessMask, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
resource->aspect, baseMipLevel, 1);
|
||||
MOBILEGL_ASSERT(srcReady, "%s: failed to transition base mip level to transfer source", __func__);
|
||||
|
||||
for (Uint32 level = baseMipLevel + 1; level < generateMipLevelCount; ++level) {
|
||||
VkImageLayout dstMipLayout = originalLayout;
|
||||
Bool dstReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, resource->image, dstMipLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
originalSrcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
originalSrcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
resource->aspect, level, 1);
|
||||
MOBILEGL_ASSERT(dstReady, "%s: failed to transition mip level %u to transfer destination", __func__, level);
|
||||
|
||||
const IntVec3 srcTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level - 1);
|
||||
const IntVec3 dstTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level);
|
||||
|
||||
VkImageBlit blitRegion{};
|
||||
blitRegion.srcSubresource.aspectMask = resource->aspect;
|
||||
blitRegion.srcSubresource.mipLevel = level - 1;
|
||||
blitRegion.srcSubresource.baseArrayLayer = 0;
|
||||
blitRegion.srcSubresource.layerCount = 1;
|
||||
blitRegion.srcOffsets[0] = {0, 0, 0};
|
||||
blitRegion.srcOffsets[1] = {srcTexelSize.x(), srcTexelSize.y(), srcTexelSize.z()};
|
||||
blitRegion.dstSubresource.aspectMask = resource->aspect;
|
||||
blitRegion.dstSubresource.mipLevel = level;
|
||||
blitRegion.dstSubresource.baseArrayLayer = 0;
|
||||
blitRegion.dstSubresource.layerCount = 1;
|
||||
blitRegion.dstOffsets[0] = {0, 0, 0};
|
||||
blitRegion.dstOffsets[1] = {dstTexelSize.x(), dstTexelSize.y(), dstTexelSize.z()};
|
||||
|
||||
vkCmdBlitImage(frame.commandBuffer,
|
||||
resource->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, &blitRegion, blitFilter);
|
||||
|
||||
VkImageLayout finishedSrcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
Bool srcRestored = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, resource->image, finishedSrcLayout, finalLayout,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, finalDstStageMask,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, finalDstAccessMask,
|
||||
resource->aspect, level - 1, 1);
|
||||
MOBILEGL_ASSERT(srcRestored, "%s: failed to transition mip level %u to final layout", __func__, level - 1);
|
||||
|
||||
if (level + 1 < generateMipLevelCount) {
|
||||
VkImageLayout nextSrcLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
Bool nextSrcReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, resource->image, nextSrcLayout, 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);
|
||||
MOBILEGL_ASSERT(nextSrcReady, "%s: failed to prepare mip level %u as next transfer source", __func__, level);
|
||||
} else {
|
||||
VkImageLayout lastMipLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
Bool lastMipReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, resource->image, lastMipLayout, finalLayout,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, finalDstStageMask,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, finalDstAccessMask,
|
||||
resource->aspect, level, 1);
|
||||
MOBILEGL_ASSERT(lastMipReady, "%s: failed to transition last mip level to final layout", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
resource->layout = finalLayout;
|
||||
}
|
||||
|
||||
void VulkanRenderer::DrawArrays(const DrawCmd& payload) {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
GLbitfield mask, GLenum filter);
|
||||
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
void GenerateMipmap(GLenum target);
|
||||
void DrawArrays(const DrawCmd& payload);
|
||||
void DrawElements(const DrawIndexedCmd& payload);
|
||||
void MultiDrawElements(const MultiDrawIndexedCmd& payloads);
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace MobileGL {
|
||||
case TextureInternalFormat::RG8Snorm:
|
||||
case TextureInternalFormat::RG8I:
|
||||
case TextureInternalFormat::RG8UI:
|
||||
case TextureInternalFormat::DepthComponent16:
|
||||
return 2;
|
||||
|
||||
case TextureInternalFormat::RGB4:
|
||||
@@ -39,6 +40,7 @@ namespace MobileGL {
|
||||
case TextureInternalFormat::SRGB8:
|
||||
case TextureInternalFormat::RGB8I:
|
||||
case TextureInternalFormat::RGB8UI:
|
||||
case TextureInternalFormat::DepthComponent24:
|
||||
return 3;
|
||||
|
||||
case TextureInternalFormat::RGBA2:
|
||||
@@ -52,6 +54,11 @@ namespace MobileGL {
|
||||
case TextureInternalFormat::RGB10A2:
|
||||
case TextureInternalFormat::RGB10A2UI:
|
||||
case TextureInternalFormat::R32F:
|
||||
case TextureInternalFormat::DepthComponent:
|
||||
case TextureInternalFormat::DepthComponent32:
|
||||
case TextureInternalFormat::DepthComponent32F:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
|
||||
case TextureInternalFormat::RG16:
|
||||
case TextureInternalFormat::RG16Snorm:
|
||||
@@ -83,6 +90,7 @@ namespace MobileGL {
|
||||
case TextureInternalFormat::RGBA32F:
|
||||
case TextureInternalFormat::RGBA32I:
|
||||
case TextureInternalFormat::RGBA32UI:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
return 16;
|
||||
|
||||
case TextureInternalFormat::R11FG11FB10F:
|
||||
@@ -125,7 +133,13 @@ namespace MobileGL {
|
||||
SizeT GetBaseInternalFormatComponentCount(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::DepthComponent:
|
||||
case TextureInternalFormat::DepthComponent16:
|
||||
case TextureInternalFormat::DepthComponent24:
|
||||
case TextureInternalFormat::DepthComponent32:
|
||||
case TextureInternalFormat::DepthComponent32F:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
// Depth stencil is actually 2 components
|
||||
// tho real formats always gives byte size in whole
|
||||
// so we count this as one here
|
||||
|
||||
Reference in New Issue
Block a user