mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 00:12:40 +00:00
refactor(graphics): split renderer and recompiler into focused modules
This commit is contained in:
1 parent
2b9cba457e
commit
a6b61d7aa0
108 files changed
+245
-237
No files matched your search
@@ -0,0 +1,231 @@
|
||||
#include "graphics/host_gpu/renderer/image/blitHelper.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "gpu_blit_shaders/gpu_blit_color_to_ms_depth_spv.h"
|
||||
#include "gpu_blit_shaders/gpu_blit_fs_triangle_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
: m_graphics(graphics), m_scheduler(scheduler) {
|
||||
vk::DescriptorSetLayoutBinding texture_binding {};
|
||||
texture_binding.binding = 0;
|
||||
texture_binding.descriptorType = vk::DescriptorType::eSampledImage;
|
||||
texture_binding.descriptorCount = 1;
|
||||
texture_binding.stageFlags = vk::ShaderStageFlagBits::eFragment;
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptor_info.bindingCount = 1;
|
||||
descriptor_info.pBindings = &texture_binding;
|
||||
RequireVulkanSuccess(m_graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
|
||||
&m_descriptor_layout),
|
||||
"create BlitHelper descriptor layout");
|
||||
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_descriptor_layout;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
|
||||
"create BlitHelper pipeline layout");
|
||||
|
||||
m_vertex_shader = CreateShader(GPU_BLIT_FS_TRIANGLE_SPV, std::size(GPU_BLIT_FS_TRIANGLE_SPV));
|
||||
m_fragment_shader =
|
||||
CreateShader(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, std::size(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV));
|
||||
}
|
||||
|
||||
BlitHelper::~BlitHelper() {
|
||||
for (const auto& pipeline: m_pipelines) {
|
||||
m_graphics.device.destroyPipeline(pipeline.handle, nullptr);
|
||||
}
|
||||
if (m_fragment_shader != nullptr) {
|
||||
m_graphics.device.destroyShaderModule(m_fragment_shader, nullptr);
|
||||
}
|
||||
if (m_vertex_shader != nullptr) {
|
||||
m_graphics.device.destroyShaderModule(m_vertex_shader, nullptr);
|
||||
}
|
||||
if (m_pipeline_layout != nullptr) {
|
||||
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
|
||||
}
|
||||
if (m_descriptor_layout != nullptr) {
|
||||
m_graphics.device.destroyDescriptorSetLayout(m_descriptor_layout, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
|
||||
EXIT_IF(code == nullptr || words == 0);
|
||||
vk::ShaderModuleCreateInfo create {};
|
||||
create.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
create.codeSize = words * sizeof(uint32_t);
|
||||
create.pCode = code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&create, nullptr, &module),
|
||||
"create BlitHelper shader module");
|
||||
return module;
|
||||
}
|
||||
|
||||
vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
|
||||
const auto cached = std::ranges::find(m_pipelines, key, &Pipeline::key);
|
||||
if (cached != m_pipelines.end()) {
|
||||
return cached->handle;
|
||||
}
|
||||
|
||||
const auto samples = vulkan_sample_count(key.samples);
|
||||
EXIT_IF(samples == vk::SampleCountFlagBits {} || key.format == vk::Format::eUndefined);
|
||||
|
||||
std::array<vk::PipelineShaderStageCreateInfo, 2> stages {};
|
||||
stages[0].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
|
||||
stages[0].module = m_vertex_shader;
|
||||
stages[0].pName = "main";
|
||||
stages[1].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
|
||||
stages[1].module = m_fragment_shader;
|
||||
stages[1].pName = "main";
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input {};
|
||||
vertex_input.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
|
||||
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
|
||||
input_assembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
|
||||
input_assembly.topology = vk::PrimitiveTopology::eTriangleList;
|
||||
vk::PipelineViewportStateCreateInfo viewport {};
|
||||
viewport.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
|
||||
viewport.viewportCount = 1;
|
||||
viewport.scissorCount = 1;
|
||||
vk::PipelineRasterizationStateCreateInfo rasterization {};
|
||||
rasterization.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
||||
rasterization.polygonMode = vk::PolygonMode::eFill;
|
||||
rasterization.cullMode = vk::CullModeFlagBits::eNone;
|
||||
rasterization.lineWidth = 1.0f;
|
||||
vk::PipelineMultisampleStateCreateInfo multisample {};
|
||||
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
|
||||
multisample.rasterizationSamples = samples;
|
||||
vk::PipelineDepthStencilStateCreateInfo depth {};
|
||||
depth.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
|
||||
depth.depthTestEnable = VK_TRUE;
|
||||
depth.depthWriteEnable = VK_TRUE;
|
||||
depth.depthCompareOp = vk::CompareOp::eAlways;
|
||||
vk::PipelineColorBlendStateCreateInfo color_blend {};
|
||||
color_blend.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
||||
const std::array dynamic_states {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
|
||||
vk::PipelineDynamicStateCreateInfo dynamic {};
|
||||
dynamic.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
|
||||
dynamic.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
|
||||
dynamic.pDynamicStates = dynamic_states.data();
|
||||
|
||||
vk::PipelineRenderingCreateInfo rendering {};
|
||||
rendering.sType = vk::StructureType::ePipelineRenderingCreateInfo;
|
||||
rendering.depthAttachmentFormat = key.format;
|
||||
|
||||
vk::GraphicsPipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
|
||||
create.pNext = &rendering;
|
||||
create.stageCount = static_cast<uint32_t>(stages.size());
|
||||
create.pStages = stages.data();
|
||||
create.pVertexInputState = &vertex_input;
|
||||
create.pInputAssemblyState = &input_assembly;
|
||||
create.pViewportState = &viewport;
|
||||
create.pRasterizationState = &rasterization;
|
||||
create.pMultisampleState = &multisample;
|
||||
create.pDepthStencilState = &depth;
|
||||
create.pColorBlendState = &color_blend;
|
||||
create.pDynamicState = &dynamic;
|
||||
create.layout = m_pipeline_layout;
|
||||
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createGraphicsPipelines(nullptr, 1, &create, nullptr, &pipeline),
|
||||
"create color-to-MS-depth pipeline");
|
||||
m_pipelines.push_back({key, pipeline});
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
|
||||
const auto& source_info = source.info;
|
||||
const auto& destination_info = destination.info;
|
||||
EXIT_IF(DepthAspectTransferFormat(source_info.pixel_format) != vk::Format::eUndefined ||
|
||||
DepthAspectTransferFormat(destination_info.pixel_format) == vk::Format::eUndefined ||
|
||||
source_info.samples != 1 || destination_info.samples <= 1 ||
|
||||
destination_info.samples > 4 || source.backing.image_type != vk::ImageType::e2D ||
|
||||
destination.backing.image_type != vk::ImageType::e2D ||
|
||||
source_info.extent.width != destination_info.extent.width ||
|
||||
source_info.extent.height != destination_info.extent.height ||
|
||||
source_info.extent.depth != 1 || destination_info.extent.depth != 1 ||
|
||||
source.backing.image == nullptr || destination.backing.image == nullptr);
|
||||
m_scheduler.EndRendering();
|
||||
|
||||
ImageViewInfo source_view_info {};
|
||||
source_view_info.format = source_info.pixel_format;
|
||||
source_view_info.type = vk::ImageViewType::e2D;
|
||||
source_view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
source_view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
||||
const auto source_view = source.FindView(source_view_info);
|
||||
|
||||
ImageViewInfo destination_view_info {};
|
||||
destination_view_info.format = destination_info.pixel_format;
|
||||
destination_view_info.type = vk::ImageViewType::e2D;
|
||||
destination_view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
destination_view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
const auto destination_view = destination.FindView(destination_view_info);
|
||||
|
||||
auto& command_buffer = m_scheduler.Current();
|
||||
auto command = command_buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
|
||||
command);
|
||||
destination.Transit(ColorToMsDepthLayout,
|
||||
vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {}, command);
|
||||
|
||||
vk::RenderingAttachmentInfo depth_attachment {};
|
||||
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
depth_attachment.imageView = destination_view;
|
||||
depth_attachment.imageLayout = ColorToMsDepthLayout;
|
||||
depth_attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
depth_attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
depth_attachment.clearValue.depthStencil = {0.0f, 0};
|
||||
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.sType = vk::StructureType::eRenderingInfo;
|
||||
rendering.renderArea.extent = {destination_info.extent.width, destination_info.extent.height};
|
||||
rendering.layerCount = 1;
|
||||
rendering.pDepthAttachment = &depth_attachment;
|
||||
command.beginRendering(&rendering);
|
||||
|
||||
vk::DescriptorImageInfo descriptor_image {};
|
||||
descriptor_image.imageView = source_view;
|
||||
descriptor_image.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
vk::WriteDescriptorSet descriptor_write {};
|
||||
descriptor_write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
descriptor_write.dstBinding = 0;
|
||||
descriptor_write.descriptorCount = 1;
|
||||
descriptor_write.descriptorType = vk::DescriptorType::eSampledImage;
|
||||
descriptor_write.pImageInfo = &descriptor_image;
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, m_pipeline_layout, 0, 1,
|
||||
&descriptor_write);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eGraphics,
|
||||
GetPipeline({destination_info.samples, destination_info.pixel_format}));
|
||||
|
||||
const vk::Viewport viewport {0.0f,
|
||||
0.0f,
|
||||
static_cast<float>(destination_info.extent.width),
|
||||
static_cast<float>(destination_info.extent.height),
|
||||
0.0f,
|
||||
1.0f};
|
||||
const vk::Rect2D scissor {{0, 0},
|
||||
{destination_info.extent.width, destination_info.extent.height}};
|
||||
command.setViewport(0, 1, &viewport);
|
||||
command.setScissor(0, 1, &scissor);
|
||||
command.draw(3, 1, 0, 0);
|
||||
command.endRendering();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <compare>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandScheduler;
|
||||
class Image;
|
||||
struct GraphicContext;
|
||||
|
||||
class BlitHelper final {
|
||||
public:
|
||||
inline static constexpr auto ColorToMsDepthLayout =
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
|
||||
BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler);
|
||||
~BlitHelper();
|
||||
KYTY_CLASS_NO_COPY(BlitHelper);
|
||||
|
||||
void ReinterpretColorAsMsDepth(Image& source, Image& destination);
|
||||
|
||||
private:
|
||||
struct PipelineKey {
|
||||
uint32_t samples = 1;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
|
||||
auto operator<=>(const PipelineKey&) const = default;
|
||||
};
|
||||
|
||||
struct Pipeline {
|
||||
PipelineKey key;
|
||||
vk::Pipeline handle = nullptr;
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::ShaderModule CreateShader(const uint32_t* code, size_t words) const;
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
vk::DescriptorSetLayout m_descriptor_layout = nullptr;
|
||||
vk::PipelineLayout m_pipeline_layout = nullptr;
|
||||
vk::ShaderModule m_vertex_shader = nullptr;
|
||||
vk::ShaderModule m_fragment_shader = nullptr;
|
||||
std::vector<Pipeline> m_pipelines;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
|
||||
@@ -0,0 +1,782 @@
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] vk::ImageType HostImageType(Prospero::ImageType type) {
|
||||
switch (type) {
|
||||
case Prospero::ImageType::kColor1D: return vk::ImageType::e1D;
|
||||
case Prospero::ImageType::kColor3D: return vk::ImageType::e3D;
|
||||
case Prospero::ImageType::kColor2D: return vk::ImageType::e2D;
|
||||
default: EXIT("non-base image type: %u\n", static_cast<uint32_t>(type));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const ImageInfo& info) {
|
||||
vk::ImageCreateFlags flags {};
|
||||
if (DepthAspectTransferFormat(info.pixel_format) == vk::Format::eUndefined) {
|
||||
flags |= vk::ImageCreateFlagBits::eMutableFormat;
|
||||
flags |= vk::ImageCreateFlagBits::eExtendedUsage;
|
||||
if (Prospero::BlockCompressedBytesPerBlock(info.guest_format) != 0) {
|
||||
flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible;
|
||||
}
|
||||
}
|
||||
if (info.IsVolume()) {
|
||||
flags |= vk::ImageCreateFlagBits::e2DArrayCompatible;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasFormatFeature(vk::FormatProperties properties,
|
||||
vk::FormatFeatureFlagBits feature) {
|
||||
return static_cast<bool>(properties.optimalTilingFeatures & feature);
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::ImageUsageFlags ImageUsageFlags(GraphicContext& graphics, const ImageInfo& info) {
|
||||
const auto properties = graphics.GetFormatProperties(info.pixel_format);
|
||||
auto usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eSampledImage)) {
|
||||
usage |= vk::ImageUsageFlagBits::eSampled;
|
||||
}
|
||||
if (DepthAspectTransferFormat(info.pixel_format) != vk::Format::eUndefined) {
|
||||
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
return usage;
|
||||
}
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
|
||||
usage |= vk::ImageUsageFlagBits::eColorAttachment;
|
||||
}
|
||||
if (info.samples == 1 &&
|
||||
HasFormatFeature(properties, vk::FormatFeatureFlagBits::eStorageImage)) {
|
||||
usage |= vk::ImageUsageFlagBits::eStorage;
|
||||
} else if (info.samples == 1) {
|
||||
const auto compatible = SrgbStorageViewFormat(info.pixel_format);
|
||||
if (compatible != vk::Format::eUndefined &&
|
||||
HasFormatFeature(graphics.GetFormatProperties(compatible),
|
||||
vk::FormatFeatureFlagBits::eStorageImage)) {
|
||||
usage |= vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
void ValidateRange(GuestRange range, const char* name) {
|
||||
if ((range.address == 0) != (range.size == 0) ||
|
||||
(range.address != 0 && (range.address >= TRACKER_ADDRESS_SIZE ||
|
||||
range.size > TRACKER_ADDRESS_SIZE - range.address))) {
|
||||
EXIT("invalid %s image range: address=0x%016llx size=0x%016llx\n", name,
|
||||
static_cast<unsigned long long>(range.address),
|
||||
static_cast<unsigned long long>(range.size));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::ImageAspectFlags Image::FullAspectMask(vk::Format format) noexcept {
|
||||
switch (format) {
|
||||
case vk::Format::eD16Unorm:
|
||||
case vk::Format::eX8D24UnormPack32:
|
||||
case vk::Format::eD32Sfloat: return vk::ImageAspectFlagBits::eDepth;
|
||||
case vk::Format::eS8Uint: return vk::ImageAspectFlagBits::eStencil;
|
||||
case vk::Format::eD16UnormS8Uint:
|
||||
case vk::Format::eD24UnormS8Uint:
|
||||
case vk::Format::eD32SfloatS8Uint:
|
||||
return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
||||
default: return vk::ImageAspectFlagBits::eColor;
|
||||
}
|
||||
}
|
||||
|
||||
Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
|
||||
vk::AccessFlags2 destination_access,
|
||||
vk::PipelineStageFlags2 destination_stage,
|
||||
std::optional<ImageSubresourceRange> range) {
|
||||
auto& state = backing.state;
|
||||
auto& subresource_states = backing.subresource_states;
|
||||
|
||||
const bool partial =
|
||||
range && (range->base_level != 0 || range->level_count != info.resources.levels ||
|
||||
range->base_layer != 0 || range->layer_count != info.resources.layers);
|
||||
const bool has_subresource_states = !subresource_states.empty();
|
||||
|
||||
Barriers barriers;
|
||||
if (partial || has_subresource_states) {
|
||||
if (!has_subresource_states) {
|
||||
subresource_states.resize(info.resources.levels * info.resources.layers, state);
|
||||
}
|
||||
|
||||
const uint32_t base_level = partial ? range->base_level : 0;
|
||||
const uint32_t level_count = partial ? range->level_count : info.resources.levels;
|
||||
const uint32_t base_layer = partial ? range->base_layer : 0;
|
||||
const uint32_t layer_count = partial ? range->layer_count : info.resources.layers;
|
||||
for (uint32_t level = base_level; level < base_level + level_count; level++) {
|
||||
for (uint32_t layer = base_layer; layer < base_layer + layer_count; layer++) {
|
||||
const auto index = level * info.resources.layers + layer;
|
||||
EXIT_IF(index >= subresource_states.size());
|
||||
auto& subresource_state = subresource_states[index];
|
||||
|
||||
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
|
||||
vk::AccessFlagBits2::eShaderWrite |
|
||||
vk::AccessFlagBits2::eMemoryWrite;
|
||||
const bool repeated_write =
|
||||
static_cast<bool>(subresource_state.access_mask & write_access);
|
||||
if (subresource_state.layout != destination_layout ||
|
||||
subresource_state.access_mask != destination_access || repeated_write) {
|
||||
vk::ImageMemoryBarrier2 barrier {};
|
||||
barrier.srcStageMask = subresource_state.pl_stage;
|
||||
barrier.srcAccessMask = subresource_state.access_mask;
|
||||
barrier.dstStageMask = destination_stage;
|
||||
barrier.dstAccessMask = destination_access;
|
||||
barrier.oldLayout = subresource_state.layout;
|
||||
barrier.newLayout = destination_layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = backing.image;
|
||||
barrier.subresourceRange.aspectMask = FullAspectMask(backing.format);
|
||||
barrier.subresourceRange.baseMipLevel = level;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.baseArrayLayer = layer;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
barriers.push_back(barrier);
|
||||
subresource_state = {destination_stage, destination_access, destination_layout};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!partial) {
|
||||
subresource_states.clear();
|
||||
}
|
||||
} else {
|
||||
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
|
||||
vk::AccessFlagBits2::eShaderWrite |
|
||||
vk::AccessFlagBits2::eMemoryWrite;
|
||||
const bool repeated_write = static_cast<bool>(state.access_mask & write_access);
|
||||
if (state.layout == destination_layout && state.access_mask == destination_access &&
|
||||
!repeated_write) {
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier2 barrier {};
|
||||
barrier.srcStageMask = state.pl_stage;
|
||||
barrier.srcAccessMask = state.access_mask;
|
||||
barrier.dstStageMask = destination_stage;
|
||||
barrier.dstAccessMask = destination_access;
|
||||
barrier.oldLayout = state.layout;
|
||||
barrier.newLayout = destination_layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = backing.image;
|
||||
barrier.subresourceRange.aspectMask = FullAspectMask(backing.format);
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
|
||||
barriers.push_back(barrier);
|
||||
}
|
||||
|
||||
state = {destination_stage, destination_access, destination_layout};
|
||||
return barriers;
|
||||
}
|
||||
|
||||
void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
|
||||
std::optional<ImageSubresourceRange> range,
|
||||
vk::CommandBuffer command_buffer) {
|
||||
const auto transfer_access =
|
||||
vk::AccessFlagBits2::eTransferRead | vk::AccessFlagBits2::eTransferWrite;
|
||||
vk::PipelineStageFlags2 destination_stage {};
|
||||
if (static_cast<bool>(destination_access & transfer_access)) {
|
||||
destination_stage |= vk::PipelineStageFlagBits2::eTransfer;
|
||||
}
|
||||
if (!destination_access ||
|
||||
static_cast<bool>(destination_access & ~vk::AccessFlags2 {transfer_access})) {
|
||||
destination_stage |= vk::PipelineStageFlagBits2::eAllGraphics |
|
||||
vk::PipelineStageFlagBits2::eComputeShader;
|
||||
}
|
||||
const auto barriers =
|
||||
GetBarriers(destination_layout, destination_access, destination_stage, range);
|
||||
if (barriers.empty()) {
|
||||
return;
|
||||
}
|
||||
if (m_scheduler != nullptr) {
|
||||
m_scheduler->EndRendering();
|
||||
}
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(barriers.size());
|
||||
dependency.pImageMemoryBarriers = barriers.data();
|
||||
command_buffer.pipelineBarrier2(dependency);
|
||||
}
|
||||
|
||||
void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
|
||||
uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr ||
|
||||
size == 0);
|
||||
m_scheduler->EndRendering();
|
||||
vk::BufferMemoryBarrier2 buffer_barrier {};
|
||||
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
|
||||
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer;
|
||||
buffer_barrier.dstAccessMask = vk::AccessFlagBits2::eTransferRead;
|
||||
buffer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
buffer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
buffer_barrier.buffer = buffer;
|
||||
buffer_barrier.offset = offset;
|
||||
buffer_barrier.size = size;
|
||||
const auto image_barriers =
|
||||
GetBarriers(vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite,
|
||||
vk::PipelineStageFlagBits2::eCopy, {});
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
dependency.bufferMemoryBarrierCount = 1;
|
||||
dependency.pBufferMemoryBarriers = &buffer_barrier;
|
||||
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
|
||||
dependency.pImageMemoryBarriers = image_barriers.data();
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
command.pipelineBarrier2(dependency);
|
||||
command.copyBufferToImage(buffer, backing.image, vk::ImageLayout::eTransferDstOptimal,
|
||||
static_cast<uint32_t>(copies.size()), copies.data());
|
||||
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer;
|
||||
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead;
|
||||
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
buffer_barrier.dstAccessMask =
|
||||
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
dependency.imageMemoryBarrierCount = 0;
|
||||
dependency.pImageMemoryBarriers = nullptr;
|
||||
command.pipelineBarrier2(dependency);
|
||||
Transit(vk::ImageLayout::eGeneral,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
}
|
||||
|
||||
void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
|
||||
uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
|
||||
m_scheduler->EndRendering();
|
||||
vk::BufferMemoryBarrier2 buffer_barrier {};
|
||||
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
buffer_barrier.srcAccessMask =
|
||||
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eCopy;
|
||||
buffer_barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
||||
buffer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
buffer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
buffer_barrier.buffer = buffer;
|
||||
buffer_barrier.offset = offset;
|
||||
buffer_barrier.size = size;
|
||||
const auto image_barriers =
|
||||
GetBarriers(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead,
|
||||
vk::PipelineStageFlagBits2::eCopy, {});
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
dependency.bufferMemoryBarrierCount = 1;
|
||||
dependency.pBufferMemoryBarriers = &buffer_barrier;
|
||||
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
|
||||
dependency.pImageMemoryBarriers = image_barriers.data();
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
command.pipelineBarrier2(dependency);
|
||||
command.copyImageToBuffer(backing.image, vk::ImageLayout::eTransferSrcOptimal, buffer,
|
||||
static_cast<uint32_t>(copies.size()), copies.data());
|
||||
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eCopy;
|
||||
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
||||
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
buffer_barrier.dstAccessMask =
|
||||
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
dependency.imageMemoryBarrierCount = 0;
|
||||
dependency.pImageMemoryBarriers = nullptr;
|
||||
command.pipelineBarrier2(dependency);
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t>
|
||||
Image::SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth) {
|
||||
const auto source_type = source.backing.image_type;
|
||||
const auto destination_type = destination.backing.image_type;
|
||||
uint32_t source_layers = source.backing.layers;
|
||||
uint32_t destination_layers = destination.backing.layers;
|
||||
if (source_type == vk::ImageType::e3D) {
|
||||
source_layers = 1;
|
||||
}
|
||||
if (destination_type == vk::ImageType::e3D) {
|
||||
destination_layers = 1;
|
||||
}
|
||||
if (source_type == destination_type) {
|
||||
source_layers = destination_layers =
|
||||
std::min(source_layers, destination_layers);
|
||||
} else if (source_type == vk::ImageType::e2D &&
|
||||
destination_type == vk::ImageType::e3D) {
|
||||
source_layers = depth;
|
||||
} else if (source_type == vk::ImageType::e3D &&
|
||||
destination_type == vk::ImageType::e2D) {
|
||||
destination_layers = depth;
|
||||
}
|
||||
return {source_layers, destination_layers};
|
||||
}
|
||||
|
||||
void Image::CopyImage(Image& source) {
|
||||
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples);
|
||||
m_scheduler->EndRendering();
|
||||
const uint32_t levels =
|
||||
std::min(source.backing.mip_levels, backing.mip_levels);
|
||||
const uint32_t base_depth = backing.image_type == vk::ImageType::e3D
|
||||
? backing.extent.depth
|
||||
: source.backing.extent.depth;
|
||||
const auto source_aspect =
|
||||
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
|
||||
const auto destination_aspect =
|
||||
FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil;
|
||||
std::vector<vk::ImageCopy> copies;
|
||||
copies.reserve(levels);
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
const auto width = std::max(source.backing.extent.width >> level, 1u);
|
||||
const auto height = std::max(source.backing.extent.height >> level, 1u);
|
||||
const auto depth = std::max(base_depth >> level, 1u);
|
||||
const auto [source_layers, destination_layers] =
|
||||
SanitizeCopyLayers(source, *this, depth);
|
||||
vk::ImageCopy copy {};
|
||||
copy.srcSubresource = {source_aspect, level, 0, 1};
|
||||
copy.dstSubresource = {destination_aspect, level, 0, 1};
|
||||
if (source.backing.image_type == backing.image_type) {
|
||||
if (source.backing.image_type == vk::ImageType::e3D) {
|
||||
copy.extent = {width, height, depth};
|
||||
} else {
|
||||
copy.srcSubresource.layerCount =
|
||||
std::min(source_layers, destination_layers);
|
||||
copy.dstSubresource.layerCount = copy.srcSubresource.layerCount;
|
||||
copy.extent = {width, height, 1};
|
||||
}
|
||||
} else if (source.backing.image_type == vk::ImageType::e2D) {
|
||||
copy.srcSubresource.layerCount = source_layers;
|
||||
copy.extent = {width, height, source_layers};
|
||||
} else {
|
||||
copy.dstSubresource.layerCount = destination_layers;
|
||||
copy.extent = {width, height, destination_layers};
|
||||
}
|
||||
copies.push_back(copy);
|
||||
}
|
||||
if (copies.empty()) {
|
||||
return;
|
||||
}
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {}, command);
|
||||
Transit(vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite, {}, command);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
backing.image, vk::ImageLayout::eTransferDstOptimal,
|
||||
static_cast<uint32_t>(copies.size()), copies.data());
|
||||
Transit(vk::ImageLayout::eGeneral,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
}
|
||||
|
||||
void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
|
||||
const ImageSubresourceRange& destination_range) {
|
||||
EXIT_IF(m_scheduler == nullptr || backing.samples != 1 ||
|
||||
source.backing.image_type != vk::ImageType::e2D ||
|
||||
backing.image_type != vk::ImageType::e2D ||
|
||||
source_range.level_count != 1 || destination_range.level_count != 1 ||
|
||||
source_range.base_level >= source.backing.mip_levels ||
|
||||
destination_range.base_level >= backing.mip_levels ||
|
||||
source_range.base_layer >= source.backing.layers ||
|
||||
destination_range.base_layer >= backing.layers);
|
||||
const auto layers = std::min(
|
||||
{source_range.layer_count, destination_range.layer_count,
|
||||
source.backing.layers - source_range.base_layer,
|
||||
backing.layers - destination_range.base_layer});
|
||||
const auto source_width =
|
||||
std::max(source.backing.extent.width >> source_range.base_level, 1u);
|
||||
const auto source_height =
|
||||
std::max(source.backing.extent.height >> source_range.base_level, 1u);
|
||||
const auto destination_width =
|
||||
std::max(backing.extent.width >> destination_range.base_level, 1u);
|
||||
const auto destination_height =
|
||||
std::max(backing.extent.height >> destination_range.base_level, 1u);
|
||||
const bool copy = source.backing.samples == 1;
|
||||
EXIT_IF(layers == 0 || info.extent.width > source_width ||
|
||||
info.extent.height > source_height || info.extent.width > destination_width ||
|
||||
info.extent.height > destination_height ||
|
||||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
|
||||
: source.backing.format != backing.format));
|
||||
auto resolved_source_range = source_range;
|
||||
auto resolved_destination_range = destination_range;
|
||||
resolved_source_range.layer_count = layers;
|
||||
resolved_destination_range.layer_count = layers;
|
||||
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
|
||||
|
||||
m_scheduler->EndRendering();
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, resolved_source_range, command);
|
||||
Transit(vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite, resolved_destination_range, command);
|
||||
if (copy) {
|
||||
vk::ImageCopy region {};
|
||||
region.srcSubresource = {vk::ImageAspectFlagBits::eColor,
|
||||
resolved_source_range.base_level,
|
||||
resolved_source_range.base_layer, layers};
|
||||
region.dstSubresource = {vk::ImageAspectFlagBits::eColor,
|
||||
resolved_destination_range.base_level,
|
||||
resolved_destination_range.base_layer, layers};
|
||||
region.extent = resolve_extent;
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
backing.image, vk::ImageLayout::eTransferDstOptimal, region);
|
||||
} else {
|
||||
vk::ImageResolve region {};
|
||||
region.srcSubresource = {vk::ImageAspectFlagBits::eColor,
|
||||
resolved_source_range.base_level,
|
||||
resolved_source_range.base_layer, layers};
|
||||
region.dstSubresource = {vk::ImageAspectFlagBits::eColor,
|
||||
resolved_destination_range.base_level,
|
||||
resolved_destination_range.base_layer, layers};
|
||||
region.extent = resolve_extent;
|
||||
command.resolveImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
backing.image, vk::ImageLayout::eTransferDstOptimal, region);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Image::CopyRows(uint64_t row_size, uint32_t rows, uint64_t capacity) noexcept {
|
||||
if (row_size == 0 || rows == 0 || row_size > capacity) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<uint32_t>(std::min<uint64_t>(rows, capacity / row_size));
|
||||
}
|
||||
|
||||
void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
|
||||
EXIT_IF(m_scheduler == nullptr || buffer.Handle() == nullptr ||
|
||||
source.backing.samples != 1 || backing.samples != 1);
|
||||
m_scheduler->EndRendering();
|
||||
const uint32_t levels =
|
||||
std::min(source.backing.mip_levels, backing.mip_levels);
|
||||
const auto source_aspect =
|
||||
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
|
||||
const auto destination_aspect =
|
||||
FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil;
|
||||
const auto source_bytes = DepthAspectTransferBytes(source.backing.format) != 0
|
||||
? DepthAspectTransferBytes(source.backing.format)
|
||||
: source.info.bytes_per_block;
|
||||
const auto destination_bytes = DepthAspectTransferBytes(backing.format) != 0
|
||||
? DepthAspectTransferBytes(backing.format)
|
||||
: info.bytes_per_block;
|
||||
const uint32_t source_block = source.info.IsBlock() ? 4u : 1u;
|
||||
const uint32_t destination_block = info.IsBlock() ? 4u : 1u;
|
||||
EXIT_IF(levels == 0 || source_bytes == 0 || source_bytes != destination_bytes ||
|
||||
source_block != destination_block);
|
||||
|
||||
vk::BufferMemoryBarrier2 barrier {};
|
||||
barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead;
|
||||
barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = buffer.Handle();
|
||||
barrier.offset = 0;
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
dependency.bufferMemoryBarrierCount = 1;
|
||||
dependency.pBufferMemoryBarriers = &barrier;
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {}, command);
|
||||
Transit(vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite, {}, command);
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
const auto width = std::max(source.backing.extent.width >> level, 1u);
|
||||
const auto height = std::max(source.backing.extent.height >> level, 1u);
|
||||
const auto source_depth = source.backing.image_type == vk::ImageType::e3D
|
||||
? std::max(source.backing.extent.depth >> level, 1u)
|
||||
: source.backing.layers;
|
||||
const auto destination_depth = backing.image_type == vk::ImageType::e3D
|
||||
? std::max(backing.extent.depth >> level, 1u)
|
||||
: backing.layers;
|
||||
const auto slices = std::min(source_depth, destination_depth);
|
||||
const auto block_rows = (height + source_block - 1) / source_block;
|
||||
const auto row_size =
|
||||
static_cast<uint64_t>((width + source_block - 1) / source_block) * source_bytes;
|
||||
const auto rows_per_copy = CopyRows(row_size, block_rows, buffer.Size());
|
||||
EXIT_IF(slices == 0 || rows_per_copy == 0);
|
||||
for (uint32_t slice = 0; slice < slices; slice++) {
|
||||
for (uint32_t block_row = 0; block_row < block_rows;
|
||||
block_row += rows_per_copy) {
|
||||
const auto copy_rows = std::min(rows_per_copy, block_rows - block_row);
|
||||
const auto y = block_row * source_block;
|
||||
const auto copy_height =
|
||||
std::min(copy_rows * source_block, height - y);
|
||||
const auto copy_size = row_size * copy_rows;
|
||||
vk::BufferImageCopy source_copy {};
|
||||
source_copy.imageSubresource = {
|
||||
source_aspect, level,
|
||||
source.backing.image_type == vk::ImageType::e3D ? 0u : slice, 1};
|
||||
source_copy.imageOffset = {
|
||||
0, static_cast<int32_t>(y),
|
||||
source.backing.image_type == vk::ImageType::e3D
|
||||
? static_cast<int32_t>(slice)
|
||||
: 0};
|
||||
source_copy.imageExtent = {width, copy_height, 1};
|
||||
auto destination_copy = source_copy;
|
||||
destination_copy.imageSubresource = {
|
||||
destination_aspect, level,
|
||||
backing.image_type == vk::ImageType::e3D ? 0u : slice, 1};
|
||||
destination_copy.imageOffset.z =
|
||||
backing.image_type == vk::ImageType::e3D
|
||||
? static_cast<int32_t>(slice)
|
||||
: 0;
|
||||
barrier.size = copy_size;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
||||
command.pipelineBarrier2(dependency);
|
||||
command.copyImageToBuffer(source.backing.image,
|
||||
vk::ImageLayout::eTransferSrcOptimal,
|
||||
buffer.Handle(), source_copy);
|
||||
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferRead;
|
||||
command.pipelineBarrier2(dependency);
|
||||
command.copyBufferToImage(buffer.Handle(), backing.image,
|
||||
vk::ImageLayout::eTransferDstOptimal,
|
||||
destination_copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
Transit(vk::ImageLayout::eGeneral,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
}
|
||||
|
||||
void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
|
||||
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples ||
|
||||
mip >= backing.mip_levels || layer >= backing.layers);
|
||||
m_scheduler->EndRendering();
|
||||
const auto width = std::max(backing.extent.width >> mip, 1u);
|
||||
const auto height = std::max(backing.extent.height >> mip, 1u);
|
||||
const auto depth = std::max(backing.extent.depth >> mip, 1u);
|
||||
EXIT_IF(width != source.backing.extent.width ||
|
||||
height != source.backing.extent.height);
|
||||
const auto [source_layers, destination_layers] =
|
||||
SanitizeCopyLayers(source, *this, depth);
|
||||
const auto aspects = FullAspectMask(source.backing.format);
|
||||
EXIT_IF(aspects != FullAspectMask(backing.format));
|
||||
std::array<vk::ImageCopy, 2> copies {};
|
||||
uint32_t copy_count = 0;
|
||||
for (const auto aspect: {vk::ImageAspectFlagBits::eColor, vk::ImageAspectFlagBits::eDepth,
|
||||
vk::ImageAspectFlagBits::eStencil}) {
|
||||
if (!static_cast<bool>(aspects & aspect)) {
|
||||
continue;
|
||||
}
|
||||
auto& copy = copies[copy_count++];
|
||||
copy.srcSubresource = {aspect, 0, 0, source_layers};
|
||||
copy.dstSubresource = {aspect, mip, layer, destination_layers};
|
||||
copy.extent = {width, height, depth};
|
||||
}
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
Transit(vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite, {}, command);
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {}, command);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
backing.image, vk::ImageLayout::eTransferDstOptimal, copy_count,
|
||||
copies.data());
|
||||
Transit(vk::ImageLayout::eGeneral,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
}
|
||||
|
||||
namespace ImageOps {
|
||||
|
||||
void Validate(const ImageInfo& info) {
|
||||
ValidateRange(info.data, "data");
|
||||
ValidateRange(info.stencil, "stencil");
|
||||
|
||||
if (info.pixel_format == vk::Format::eUndefined) {
|
||||
const bool metadata_empty =
|
||||
info.metadata.range.address == 0 && info.metadata.range.size == 0 &&
|
||||
info.metadata.kind == ImageMetadataKind::None &&
|
||||
info.metadata.control == 0 &&
|
||||
info.metadata.compression == VideoOutCompression::Uncompressed &&
|
||||
!info.metadata.stencil_compressed;
|
||||
if (info.data.Empty() || info.HasStencil() || !metadata_empty || info.extent.width == 0 ||
|
||||
info.extent.height == 0 || info.extent.depth == 0 || info.resources.levels != 1 ||
|
||||
info.resources.layers != 1 || info.samples != 1 || info.pitch != 0 ||
|
||||
info.bytes_per_block != 0) {
|
||||
EXIT("invalid stencil association image\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.extent.width == 0 || info.extent.height == 0 || info.extent.depth == 0 ||
|
||||
info.resources.levels == 0 ||
|
||||
info.resources.levels > info.mip_layout.size() || info.resources.layers == 0 ||
|
||||
info.samples == 0 || vulkan_sample_count(info.samples) == vk::SampleCountFlagBits {} ||
|
||||
info.bytes_per_block == 0 || (info.data.address != 0 && info.pitch == 0)) {
|
||||
EXIT("invalid image geometry or format\n");
|
||||
}
|
||||
|
||||
switch (info.type) {
|
||||
case Prospero::ImageType::kColor1D:
|
||||
if (info.extent.height != 1 || info.extent.depth != 1) {
|
||||
EXIT("invalid 1D image shape\n");
|
||||
}
|
||||
break;
|
||||
case Prospero::ImageType::kColor3D:
|
||||
if (info.resources.layers != 1) {
|
||||
EXIT("3D images cannot have array layers\n");
|
||||
}
|
||||
break;
|
||||
case Prospero::ImageType::kColor2D:
|
||||
if (info.extent.depth != 1) {
|
||||
EXIT("invalid 2D image shape\n");
|
||||
}
|
||||
break;
|
||||
default: EXIT("non-base image type: %u\n", static_cast<uint32_t>(info.type));
|
||||
}
|
||||
if (info.samples > 1 && info.resources.levels != 1) {
|
||||
EXIT("multisampled images cannot have mip levels\n");
|
||||
}
|
||||
|
||||
if (info.metadata.stencil_compressed && !info.HasStencil()) {
|
||||
EXIT("compressed stencil metadata requires a stencil plane\n");
|
||||
}
|
||||
switch (info.metadata.kind) {
|
||||
case ImageMetadataKind::None:
|
||||
if (info.metadata.range.address != 0 || info.metadata.range.size != 0 ||
|
||||
info.metadata.control != 0 ||
|
||||
info.metadata.compression != VideoOutCompression::Uncompressed ||
|
||||
info.metadata.stencil_compressed) {
|
||||
EXIT("metadata-free image has metadata state\n");
|
||||
}
|
||||
break;
|
||||
case ImageMetadataKind::Htile:
|
||||
ValidateRange(info.metadata.range, "HTILE");
|
||||
if (info.metadata.range.Empty() ||
|
||||
info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
EXIT("invalid HTILE metadata\n");
|
||||
}
|
||||
break;
|
||||
case ImageMetadataKind::Dcc:
|
||||
if (info.metadata.range.address == 0 ||
|
||||
info.metadata.range.address >= TRACKER_ADDRESS_SIZE ||
|
||||
(info.metadata.range.size != 0 &&
|
||||
info.metadata.range.size > TRACKER_ADDRESS_SIZE - info.metadata.range.address) ||
|
||||
info.metadata.compression == VideoOutCompression::Unsupported) {
|
||||
EXIT("invalid DCC metadata\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element) {
|
||||
switch (bytes_per_element) {
|
||||
case 1: return Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm);
|
||||
case 2: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm);
|
||||
case 4: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
|
||||
case 8: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float);
|
||||
case 16: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float);
|
||||
default: EXIT("unsupported render-target element size: %u\n", bytes_per_element);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ImageOps
|
||||
|
||||
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
const ImageInfo& image_info)
|
||||
: info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
ImageOps::Validate(info);
|
||||
if (info.pixel_format == vk::Format::eUndefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
backing.format = info.pixel_format;
|
||||
backing.image_type = HostImageType(info.type);
|
||||
backing.extent = info.extent;
|
||||
backing.guest_pitch = info.pitch;
|
||||
backing.layers = info.IsVolume() ? 1u : info.resources.layers;
|
||||
backing.mip_levels = info.resources.levels;
|
||||
backing.samples = info.samples;
|
||||
backing.flags = ImageCreateFlags(info);
|
||||
backing.usage = ImageUsageFlags(graphics, info);
|
||||
|
||||
vk::ImageCreateInfo create {};
|
||||
create.sType = vk::StructureType::eImageCreateInfo;
|
||||
create.flags = backing.flags;
|
||||
create.imageType = backing.image_type;
|
||||
create.extent = backing.extent;
|
||||
create.mipLevels = backing.mip_levels;
|
||||
create.arrayLayers = backing.layers;
|
||||
create.format = backing.format;
|
||||
create.tiling = vk::ImageTiling::eOptimal;
|
||||
create.initialLayout = backing.state.layout;
|
||||
create.usage = backing.usage;
|
||||
create.sharingMode = vk::SharingMode::eExclusive;
|
||||
create.samples = vulkan_sample_count(backing.samples);
|
||||
|
||||
vk::ImageFormatProperties properties {};
|
||||
if (graphics.GetImageFormatProperties(create.format, create.imageType, create.tiling,
|
||||
create.usage, create.flags,
|
||||
&properties) != vk::Result::eSuccess ||
|
||||
!static_cast<bool>(properties.sampleCounts & create.samples)) {
|
||||
EXIT("image format does not support required usage: format=%d type=%d usage=0x%x "
|
||||
"flags=0x%x samples=%u\n",
|
||||
static_cast<int>(create.format), static_cast<int>(create.imageType),
|
||||
static_cast<vk::ImageUsageFlags::MaskType>(create.usage),
|
||||
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), backing.samples);
|
||||
}
|
||||
|
||||
backing.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
if (!graphics.CreateImage(create, backing)) {
|
||||
EXIT("failed to create image: extent=%ux%ux%u format=%d layers=%u levels=%u\n",
|
||||
create.extent.width, create.extent.height, create.extent.depth,
|
||||
static_cast<int>(create.format), create.arrayLayers, create.mipLevels);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Image::HashGuestEdges() const {
|
||||
constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1;
|
||||
std::array<uint8_t, TRACKER_PAGE_SIZE * 2> bytes {};
|
||||
const auto range = info.data;
|
||||
const uint64_t head_end = std::min(range.End(), (range.address + page_mask) & ~page_mask);
|
||||
const uint64_t tail_begin = std::max(range.address, range.End() & ~page_mask);
|
||||
const uint64_t head_size = head_end - range.address;
|
||||
const uint64_t tail_address = tail_begin < head_end ? head_end : tail_begin;
|
||||
const uint64_t tail_size = range.End() - tail_address;
|
||||
if ((head_size != 0 &&
|
||||
!LibKernel::Memory::TryReadBacking(range.address, bytes.data(), head_size)) ||
|
||||
(tail_size != 0 &&
|
||||
!LibKernel::Memory::TryReadBacking(tail_address, bytes.data() + head_size, tail_size))) {
|
||||
EXIT("Image: failed to hash guest backing\n");
|
||||
}
|
||||
return XXH3_64bits(bytes.data(), static_cast<size_t>(head_size + tail_size));
|
||||
}
|
||||
|
||||
Image::~Image() {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
if (m_graphics == nullptr) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(views.mutex);
|
||||
for (auto& cached: views.views) {
|
||||
if (cached.view != nullptr) {
|
||||
m_graphics->device.destroyImageView(cached.view, nullptr);
|
||||
cached.view = nullptr;
|
||||
}
|
||||
}
|
||||
views.views.clear();
|
||||
}
|
||||
if (backing.image != nullptr) {
|
||||
m_graphics->DeleteImage(backing);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,188 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
|
||||
#include <compare>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class Buffer;
|
||||
class CommandScheduler;
|
||||
struct ImageTestAccess;
|
||||
|
||||
struct ImageId {
|
||||
uint32_t index = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t generation = 0;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return index != std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
auto operator<=>(const ImageId&) const = default;
|
||||
};
|
||||
|
||||
struct CachedImageView {
|
||||
ImageViewInfo info;
|
||||
vk::ImageView view = nullptr;
|
||||
};
|
||||
|
||||
struct ImageViewCache {
|
||||
ImageViewCache() = default;
|
||||
KYTY_CLASS_NO_COPY(ImageViewCache);
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<CachedImageView> views;
|
||||
};
|
||||
|
||||
struct ImageUsage {
|
||||
bool texture = false;
|
||||
bool storage = false;
|
||||
bool render_target = false;
|
||||
bool depth_target = false;
|
||||
bool video_out = false;
|
||||
};
|
||||
|
||||
struct ImageBinding {
|
||||
bool is_bound = false;
|
||||
bool is_target = false;
|
||||
bool needs_rebind = false;
|
||||
bool force_general = false;
|
||||
};
|
||||
|
||||
class Image final {
|
||||
public:
|
||||
Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& info);
|
||||
~Image();
|
||||
KYTY_CLASS_NO_COPY(Image);
|
||||
|
||||
[[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info);
|
||||
void AssociateDepth(ImageId image_id) { depth_id = image_id; }
|
||||
using Barriers = std::vector<vk::ImageMemoryBarrier2>;
|
||||
[[nodiscard]] Barriers
|
||||
GetBarriers(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
|
||||
vk::PipelineStageFlags2 destination_stage,
|
||||
std::optional<ImageSubresourceRange> range);
|
||||
void Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
|
||||
std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer);
|
||||
void Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
|
||||
uint64_t offset, uint64_t size);
|
||||
void Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
|
||||
uint64_t offset, uint64_t size);
|
||||
void CopyImage(Image& source);
|
||||
void Resolve(Image& source, const ImageSubresourceRange& source_range,
|
||||
const ImageSubresourceRange& destination_range);
|
||||
void CopyImageWithBuffer(Image& source, Buffer& buffer);
|
||||
void CopyMip(Image& source, uint32_t mip, uint32_t layer);
|
||||
|
||||
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
|
||||
if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) {
|
||||
m_cpu_dirty = true;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
} else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) {
|
||||
m_maybe_cpu_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
|
||||
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; }
|
||||
[[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; }
|
||||
[[nodiscard]] bool NeedsMaybeCpuHash() const {
|
||||
return m_maybe_cpu_dirty && !m_maybe_hash_valid;
|
||||
}
|
||||
void SetMaybeCpuHash(uint64_t hash) {
|
||||
if (!NeedsMaybeCpuHash()) {
|
||||
EXIT("image cannot initialize maybe-dirty hash\n");
|
||||
}
|
||||
m_maybe_cpu_hash = hash;
|
||||
m_maybe_hash_valid = true;
|
||||
}
|
||||
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
|
||||
if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) {
|
||||
EXIT("image cannot resolve maybe-dirty hash\n");
|
||||
}
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
m_cpu_dirty |= hash != m_maybe_cpu_hash;
|
||||
return m_cpu_dirty;
|
||||
}
|
||||
|
||||
void RefreshComplete() {
|
||||
if (!IsCpuDirty()) {
|
||||
EXIT("clean image cannot complete a refresh\n");
|
||||
}
|
||||
m_cpu_dirty = false;
|
||||
m_maybe_cpu_dirty = false;
|
||||
m_maybe_hash_valid = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; }
|
||||
void MarkGpuModified() noexcept { m_gpu_modified = true; }
|
||||
void ClearGpuModified() noexcept { m_gpu_modified = false; }
|
||||
|
||||
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
|
||||
void MarkBufferModified() noexcept { m_buffer_modified = true; }
|
||||
void ClearBufferModified() noexcept { m_buffer_modified = false; }
|
||||
|
||||
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size, bool pages = false) const noexcept {
|
||||
return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size)
|
||||
: ImageRangeOverlaps(info.data.address, info.data.size, address, size);
|
||||
}
|
||||
[[nodiscard]] bool GpuOverlaps(uint64_t address, uint64_t size) const noexcept {
|
||||
return IsGpuModified() && Overlaps(address, size);
|
||||
}
|
||||
[[nodiscard]] bool SafeToDownload() const noexcept {
|
||||
return IsGpuModified() && !IsBufferModified() && !IsCpuDirty();
|
||||
}
|
||||
[[nodiscard]] uint64_t AccountedSize() const noexcept {
|
||||
return backing.image == nullptr ? 0 : (info.data.size + 1023) & ~uint64_t {1023};
|
||||
}
|
||||
[[nodiscard]] uint64_t HashGuestEdges() const;
|
||||
|
||||
ImageInfo info;
|
||||
VulkanImage backing;
|
||||
ImageViewCache views;
|
||||
ImageUsage usage;
|
||||
ImageBinding binding;
|
||||
bool registered = false;
|
||||
ImageId depth_id {};
|
||||
uint64_t tick_accessed_last = 0;
|
||||
size_t lru_id = 0;
|
||||
|
||||
private:
|
||||
friend struct ImageTestAccess;
|
||||
|
||||
[[nodiscard]] static vk::ImageAspectFlags FullAspectMask(vk::Format format) noexcept;
|
||||
[[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows,
|
||||
uint64_t capacity) noexcept;
|
||||
[[nodiscard]] static std::pair<uint32_t, uint32_t>
|
||||
SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth);
|
||||
|
||||
GraphicContext* m_graphics = nullptr;
|
||||
CommandScheduler* m_scheduler = nullptr;
|
||||
uint64_t m_maybe_cpu_hash = 0;
|
||||
bool m_cpu_dirty = false;
|
||||
bool m_maybe_cpu_dirty = false;
|
||||
bool m_maybe_hash_valid = false;
|
||||
bool m_gpu_modified = false;
|
||||
bool m_buffer_modified = false;
|
||||
};
|
||||
|
||||
namespace ImageOps {
|
||||
|
||||
void Validate(const ImageInfo& info);
|
||||
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
|
||||
|
||||
} // namespace ImageOps
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
|
||||
@@ -0,0 +1,580 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GuestRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
|
||||
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 || size == 0; }
|
||||
[[nodiscard]] constexpr bool Valid() const noexcept {
|
||||
return !Empty() && address < TRACKER_ADDRESS_SIZE &&
|
||||
size <= TRACKER_ADDRESS_SIZE - address;
|
||||
}
|
||||
[[nodiscard]] constexpr uint64_t End() const noexcept { return address + size; }
|
||||
auto operator<=>(const GuestRange&) const = default;
|
||||
};
|
||||
|
||||
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported };
|
||||
|
||||
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc };
|
||||
|
||||
struct ImageMetadataInfo {
|
||||
GuestRange range;
|
||||
ImageMetadataKind kind = ImageMetadataKind::None;
|
||||
uint32_t control = 0;
|
||||
VideoOutCompression compression = VideoOutCompression::Uncompressed;
|
||||
bool stencil_compressed = false;
|
||||
};
|
||||
|
||||
struct ImageSubresources {
|
||||
uint32_t levels = 1;
|
||||
uint32_t layers = 1;
|
||||
auto operator<=>(const ImageSubresources&) const = default;
|
||||
};
|
||||
|
||||
struct ImageSubresourceRange {
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 1;
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
auto operator<=>(const ImageSubresourceRange&) const = default;
|
||||
};
|
||||
|
||||
struct ImageMipInfo {
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t height = 0;
|
||||
auto operator<=>(const ImageMipInfo&) const = default;
|
||||
};
|
||||
|
||||
struct ImageInfo {
|
||||
GuestRange data;
|
||||
GuestRange stencil;
|
||||
ImageMetadataInfo metadata;
|
||||
uint32_t htile_clear_mask = UINT32_MAX;
|
||||
vk::Format pixel_format = vk::Format::eUndefined;
|
||||
uint32_t guest_format = 0;
|
||||
Prospero::ImageType type = Prospero::ImageType::kColor2D;
|
||||
vk::Extent3D extent = {1, 1, 1};
|
||||
ImageSubresources resources;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t bytes_per_block = 0;
|
||||
uint32_t samples = 1;
|
||||
uint32_t tile_mode = 0;
|
||||
bool bgra16 = false;
|
||||
std::array<ImageMipInfo, 16> mip_layout {};
|
||||
|
||||
[[nodiscard]] constexpr bool HasStencil() const noexcept { return !stencil.Empty(); }
|
||||
[[nodiscard]] constexpr bool HasMetadata() const noexcept {
|
||||
return metadata.kind != ImageMetadataKind::None;
|
||||
}
|
||||
[[nodiscard]] bool IsDepth() const noexcept;
|
||||
[[nodiscard]] bool IsBlock() const noexcept {
|
||||
return Prospero::BlockCompressedBytesPerBlock(guest_format) != 0;
|
||||
}
|
||||
[[nodiscard]] bool IsTiled() const noexcept {
|
||||
return tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
|
||||
}
|
||||
[[nodiscard]] constexpr bool IsVolume() const noexcept {
|
||||
return type == Prospero::ImageType::kColor3D;
|
||||
}
|
||||
[[nodiscard]] constexpr bool IsLayered() const noexcept {
|
||||
return !IsVolume() && resources.layers > 1;
|
||||
}
|
||||
[[nodiscard]] constexpr uint32_t TransferLayers() const noexcept {
|
||||
return IsVolume() ? extent.depth : resources.layers;
|
||||
}
|
||||
[[nodiscard]] vk::Extent2D BlockExtent() const noexcept {
|
||||
const auto shift = Prospero::BlockCompressedBytesPerBlock(guest_format) != 0 ? 2u : 0u;
|
||||
return {pitch >> shift, extent.height >> shift};
|
||||
}
|
||||
[[nodiscard]] bool IsCompatible(const ImageInfo& other) const noexcept {
|
||||
return pixel_format == other.pixel_format && samples == other.samples &&
|
||||
bytes_per_block == other.bytes_per_block;
|
||||
}
|
||||
[[nodiscard]] int32_t MipOf(const ImageInfo& container) const noexcept {
|
||||
if (!IsCompatible(container) || tile_mode != container.tile_mode || resources.levels != 1 ||
|
||||
container.resources.layers == 0 ||
|
||||
container.resources.levels > container.mip_layout.size()) {
|
||||
return -1;
|
||||
}
|
||||
if (HasStencil() != container.HasStencil() ||
|
||||
(HasStencil() && (stencil.address < container.stencil.address ||
|
||||
stencil.End() > container.stencil.End()))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t mip = -1;
|
||||
for (uint32_t level = 0; level < container.resources.levels; level++) {
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
|
||||
container.data.address > UINT64_MAX - layout.offset) {
|
||||
continue;
|
||||
}
|
||||
const auto mip_base = container.data.address + layout.offset;
|
||||
const auto slice_size = layout.size / container.resources.layers;
|
||||
if (slice_size == 0 || mip_base > UINT64_MAX - layout.size) {
|
||||
continue;
|
||||
}
|
||||
const auto mip_end = mip_base + layout.size;
|
||||
if (data.address >= mip_base && data.address < mip_end &&
|
||||
(data.address - mip_base) % slice_size == 0) {
|
||||
mip = static_cast<int32_t>(level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mip < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u)) {
|
||||
return -1;
|
||||
}
|
||||
const auto mip_depth = std::max(container.extent.depth >> level, 1u);
|
||||
if (container.type == Prospero::ImageType::kColor3D &&
|
||||
type == Prospero::ImageType::kColor2D) {
|
||||
if (resources.layers != mip_depth) {
|
||||
return -1;
|
||||
}
|
||||
} else if (type != container.type) {
|
||||
return -1;
|
||||
}
|
||||
return mip;
|
||||
}
|
||||
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
|
||||
if (!IsCompatible(container) || type != container.type || mip < 0 ||
|
||||
static_cast<uint32_t>(mip) >= container.resources.levels ||
|
||||
container.resources.levels > container.mip_layout.size() ||
|
||||
container.resources.layers == 0 || data.size == 0) {
|
||||
return -1;
|
||||
}
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u)) {
|
||||
return -1;
|
||||
}
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
|
||||
container.data.address > UINT64_MAX - layout.offset) {
|
||||
return -1;
|
||||
}
|
||||
const auto slice_size = layout.size / container.resources.layers;
|
||||
if (slice_size == 0 || data.size % slice_size != 0) {
|
||||
return -1;
|
||||
}
|
||||
const auto mip_base = container.data.address + layout.offset;
|
||||
if (data.address < mip_base) {
|
||||
return -1;
|
||||
}
|
||||
const auto address_delta = data.address - mip_base;
|
||||
if (address_delta % data.size != 0 || address_delta / data.size > INT32_MAX) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int32_t>(address_delta / data.size);
|
||||
}
|
||||
};
|
||||
|
||||
struct ImageViewInfo {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::ImageViewType type = vk::ImageViewType::e2D;
|
||||
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor;
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 1;
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
vk::ComponentMapping mapping = {};
|
||||
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
|
||||
|
||||
[[nodiscard]] bool operator==(const ImageViewInfo& rhs) const noexcept {
|
||||
return format == rhs.format && type == rhs.type && aspect == rhs.aspect &&
|
||||
base_level == rhs.base_level && level_count == rhs.level_count &&
|
||||
base_layer == rhs.base_layer && layer_count == rhs.layer_count &&
|
||||
mapping.r == rhs.mapping.r && mapping.g == rhs.mapping.g &&
|
||||
mapping.b == rhs.mapping.b && mapping.a == rhs.mapping.a && usage == rhs.usage;
|
||||
}
|
||||
};
|
||||
|
||||
struct DepthFormatPolicy {
|
||||
Prospero::DepthFormat depth_format;
|
||||
Prospero::BufferFormat guest_format;
|
||||
uint32_t bytes_per_element;
|
||||
vk::Format sampled_view_format;
|
||||
vk::Format depth_attachment_format;
|
||||
std::array<vk::Format, 3> stencil_attachment_formats;
|
||||
};
|
||||
|
||||
inline constexpr std::array<DepthFormatPolicy, 2> DEPTH_FORMAT_POLICIES {{
|
||||
{Prospero::DepthFormat::kZ16,
|
||||
Prospero::BufferFormat::k16UNorm,
|
||||
2,
|
||||
vk::Format::eR16Unorm,
|
||||
vk::Format::eD16Unorm,
|
||||
{vk::Format::eD16UnormS8Uint, vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint}},
|
||||
{Prospero::DepthFormat::kZ32F,
|
||||
Prospero::BufferFormat::k32Float,
|
||||
4,
|
||||
vk::Format::eR32Sfloat,
|
||||
vk::Format::eD32Sfloat,
|
||||
{vk::Format::eD32SfloatS8Uint, vk::Format::eUndefined, vk::Format::eUndefined}},
|
||||
}};
|
||||
|
||||
[[nodiscard]] inline constexpr const DepthFormatPolicy*
|
||||
FindDepthFormatPolicy(uint32_t depth_format) noexcept {
|
||||
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
|
||||
if (Prospero::GpuEnumValue(policy.depth_format) == depth_format) {
|
||||
return &policy;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr const DepthFormatPolicy*
|
||||
FindGuestDepthFormatPolicy(uint32_t guest_format) noexcept {
|
||||
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
|
||||
if (Prospero::GpuEnumValue(policy.guest_format) == guest_format) {
|
||||
return &policy;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool IsStencilAttachmentFormat(const DepthFormatPolicy& policy,
|
||||
vk::Format format) noexcept {
|
||||
for (const auto candidate: policy.stencil_attachment_formats) {
|
||||
if (candidate != vk::Format::eUndefined && candidate == format) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr vk::Format DepthAttachmentFormat(const DepthFormatPolicy& policy,
|
||||
bool has_stencil) noexcept {
|
||||
return has_stencil ? policy.stencil_attachment_formats.front() : policy.depth_attachment_format;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr vk::Format DepthAttachmentFormat(uint32_t depth_format,
|
||||
uint32_t stencil_format) noexcept {
|
||||
bool has_stencil = false;
|
||||
switch (static_cast<Prospero::StencilFormat>(stencil_format)) {
|
||||
case Prospero::StencilFormat::kInvalid: break;
|
||||
case Prospero::StencilFormat::k8UInt: has_stencil = true; break;
|
||||
default: return vk::Format::eUndefined;
|
||||
}
|
||||
const auto* policy = FindDepthFormatPolicy(depth_format);
|
||||
return policy == nullptr ? vk::Format::eUndefined : DepthAttachmentFormat(*policy, has_stencil);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr vk::ImageUsageFlags DepthTargetImageUsage() noexcept {
|
||||
return vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled |
|
||||
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr vk::Format DepthAspectTransferFormat(vk::Format format) noexcept {
|
||||
switch (format) {
|
||||
case vk::Format::eD16Unorm:
|
||||
case vk::Format::eD16UnormS8Uint: return vk::Format::eD16Unorm;
|
||||
case vk::Format::eD24UnormS8Uint: return vk::Format::eX8D24UnormPack32;
|
||||
case vk::Format::eD32Sfloat:
|
||||
case vk::Format::eD32SfloatS8Uint: return vk::Format::eD32Sfloat;
|
||||
default: return vk::Format::eUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool ImageInfo::IsDepth() const noexcept {
|
||||
return DepthAspectTransferFormat(pixel_format) != vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr uint32_t DepthAspectTransferBytes(vk::Format format) noexcept {
|
||||
switch (DepthAspectTransferFormat(format)) {
|
||||
case vk::Format::eD16Unorm: return 2;
|
||||
case vk::Format::eX8D24UnormPack32:
|
||||
case vk::Format::eD32Sfloat: return 4;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr uint32_t EncodeD16AsD24(uint16_t value) noexcept {
|
||||
return static_cast<uint32_t>((static_cast<uint64_t>(value) * 0x00ffffffu + 0x7fffu) / 0xffffu);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint32_t EncodeD16AsD32(uint16_t value) noexcept {
|
||||
return std::bit_cast<uint32_t>(static_cast<float>(value) / 65535.0f);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
|
||||
uint32_t guest_format,
|
||||
vk::Format view_format) noexcept {
|
||||
const auto* policy = FindGuestDepthFormatPolicy(guest_format);
|
||||
return policy != nullptr && view_format == policy->sampled_view_format &&
|
||||
(image_format == policy->depth_attachment_format ||
|
||||
IsStencilAttachmentFormat(*policy, image_format));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
|
||||
vk::Format view_format) noexcept {
|
||||
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
|
||||
if (IsSupportedSampledDepthFormat(image_format, Prospero::GpuEnumValue(policy.guest_format),
|
||||
view_format)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool IsSupportedDepthTargetFormat(const ImageInfo& info) {
|
||||
const auto* policy = FindGuestDepthFormatPolicy(info.guest_format);
|
||||
return policy != nullptr && info.bytes_per_block == policy->bytes_per_element &&
|
||||
(info.HasStencil() ? IsStencilAttachmentFormat(*policy, info.pixel_format)
|
||||
: info.pixel_format == policy->depth_attachment_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool IsSupportedDepthPlaneReadback(const ImageInfo& info) {
|
||||
if (!IsSupportedDepthTargetFormat(info)) {
|
||||
return false;
|
||||
}
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
|
||||
return transfer_bytes == info.bytes_per_block ||
|
||||
(info.bytes_per_block == sizeof(uint16_t) &&
|
||||
transfer_bytes == sizeof(uint32_t));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline VideoOutCompression
|
||||
ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control,
|
||||
uint64_t dcc_clear_color) noexcept {
|
||||
constexpr uint32_t DCC_256_256_0 = 0x00000048u;
|
||||
constexpr uint32_t DCC_256_64_64 = 0x00000208u;
|
||||
if (!compressed) {
|
||||
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
|
||||
? VideoOutCompression::Uncompressed
|
||||
: VideoOutCompression::Unsupported;
|
||||
}
|
||||
if (metadata_address == 0 || (metadata_address & 0xffu) != 0 || dcc_clear_color != 0) {
|
||||
return VideoOutCompression::Unsupported;
|
||||
}
|
||||
switch (dcc_control) {
|
||||
case DCC_256_256_0: return VideoOutCompression::Dcc256_256_0;
|
||||
case DCC_256_64_64: return VideoOutCompression::Dcc256_64_64;
|
||||
default: return VideoOutCompression::Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool
|
||||
CanUseVideoOutNativeWithoutUpload(VideoOutCompression compression, bool render_target,
|
||||
bool gpu_modified, bool guest_modified) noexcept {
|
||||
return compression != VideoOutCompression::Uncompressed &&
|
||||
compression != VideoOutCompression::Unsupported && !guest_modified &&
|
||||
(render_target || gpu_modified);
|
||||
}
|
||||
|
||||
struct VideoOutPixelFormatInfo {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
uint32_t guest_format = 0;
|
||||
uint32_t bytes_per_element = 0;
|
||||
bool bgra16 = false;
|
||||
};
|
||||
|
||||
struct VideoOutFormatPolicy {
|
||||
uint64_t pixel_format;
|
||||
VideoOutPixelFormatInfo info;
|
||||
};
|
||||
|
||||
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{
|
||||
{0x8000000022000000ull,
|
||||
{vk::Format::eR8G8B8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
|
||||
false}},
|
||||
{0x8000000000000000ull,
|
||||
{vk::Format::eB8G8R8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
|
||||
false}},
|
||||
{0x8100000022000000ull,
|
||||
{vk::Format::eA2B10G10R10UnormPack32,
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
|
||||
{0x8100000000000000ull,
|
||||
{vk::Format::eA2R10G10B10UnormPack32,
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
|
||||
{0xc001000622000000ull,
|
||||
{vk::Format::eR16G16B16A16Sfloat,
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, false}},
|
||||
{0xc001000600000000ull,
|
||||
{vk::Format::eR16G16B16A16Sfloat,
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, true}},
|
||||
}};
|
||||
|
||||
[[nodiscard]] inline bool DecodeVideoOutPixelFormat(uint64_t pixel_format,
|
||||
VideoOutPixelFormatInfo& info) {
|
||||
for (const auto& policy: VIDEO_OUT_FORMAT_POLICIES) {
|
||||
if (policy.pixel_format == pixel_format) {
|
||||
info = policy.info;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsSupportedVideoOutFormat(const ImageInfo& info) {
|
||||
for (const auto& policy: VIDEO_OUT_FORMAT_POLICIES) {
|
||||
if (info.pixel_format == policy.info.format &&
|
||||
info.guest_format == policy.info.guest_format &&
|
||||
info.bytes_per_block == policy.info.bytes_per_element &&
|
||||
info.bgra16 == policy.info.bgra16) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool
|
||||
IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
|
||||
return tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool IsSupportedStandard64RenderTarget(const ImageInfo& info) {
|
||||
if (info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB) ||
|
||||
info.data.address == 0 || (info.data.address & 0xffffu) != 0 || info.extent.width == 0 ||
|
||||
info.extent.height == 0 || info.bytes_per_block != 4 || info.resources.levels != 1 ||
|
||||
info.resources.layers != 1 || info.samples != 1) {
|
||||
return false;
|
||||
}
|
||||
const auto expected_pitch =
|
||||
(static_cast<uint64_t>(info.extent.width) + 127u) & ~uint64_t {127u};
|
||||
const auto padded_height =
|
||||
(static_cast<uint64_t>(info.extent.height) + 127u) & ~uint64_t {127u};
|
||||
return expected_pitch <= UINT32_MAX && info.pitch == expected_pitch &&
|
||||
expected_pitch <= UINT64_MAX / padded_height / info.bytes_per_block &&
|
||||
info.data.size == expected_pitch * padded_height * info.bytes_per_block;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool IsTiledRenderTarget(const ImageInfo& info) noexcept {
|
||||
return info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
|
||||
IsSupportedStandard64RenderTarget(info);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed,
|
||||
vk::ClearColorValue& clear) {
|
||||
vk::ClearColorValue next {};
|
||||
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
|
||||
const auto srgb8 = [](uint32_t value) {
|
||||
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
|
||||
return encoded <= 0.04045f ? encoded / 12.92f
|
||||
: std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
};
|
||||
switch (format) {
|
||||
case vk::Format::eR32Uint:
|
||||
next.uint32[0] = packed;
|
||||
break;
|
||||
case vk::Format::eR32Sint:
|
||||
next.int32[0] = static_cast<int32_t>(packed);
|
||||
break;
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
next.float32[0] = srgb8(packed);
|
||||
next.float32[1] = srgb8(packed >> 8u);
|
||||
next.float32[2] = srgb8(packed >> 16u);
|
||||
next.float32[3] = unorm8(packed >> 24u);
|
||||
break;
|
||||
case vk::Format::eB8G8R8A8Srgb:
|
||||
next.float32[0] = srgb8(packed >> 16u);
|
||||
next.float32[1] = srgb8(packed >> 8u);
|
||||
next.float32[2] = srgb8(packed);
|
||||
next.float32[3] = unorm8(packed >> 24u);
|
||||
break;
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
next.float32[0] = unorm8(packed);
|
||||
next.float32[1] = unorm8(packed >> 8u);
|
||||
next.float32[2] = unorm8(packed >> 16u);
|
||||
next.float32[3] = unorm8(packed >> 24u);
|
||||
break;
|
||||
case vk::Format::eB8G8R8A8Unorm:
|
||||
next.float32[0] = unorm8(packed >> 16u);
|
||||
next.float32[1] = unorm8(packed >> 8u);
|
||||
next.float32[2] = unorm8(packed);
|
||||
next.float32[3] = unorm8(packed >> 24u);
|
||||
break;
|
||||
case vk::Format::eA2B10G10R10UnormPack32:
|
||||
next.float32[0] = static_cast<float>(packed & 0x3ffu) / 1023.0f;
|
||||
next.float32[1] = static_cast<float>((packed >> 10u) & 0x3ffu) / 1023.0f;
|
||||
next.float32[2] = static_cast<float>((packed >> 20u) & 0x3ffu) / 1023.0f;
|
||||
next.float32[3] = static_cast<float>((packed >> 30u) & 0x3u) / 3.0f;
|
||||
break;
|
||||
case vk::Format::eA2R10G10B10UnormPack32:
|
||||
next.float32[0] = static_cast<float>((packed >> 20u) & 0x3ffu) / 1023.0f;
|
||||
next.float32[1] = static_cast<float>((packed >> 10u) & 0x3ffu) / 1023.0f;
|
||||
next.float32[2] = static_cast<float>(packed & 0x3ffu) / 1023.0f;
|
||||
next.float32[3] = static_cast<float>((packed >> 30u) & 0x3u) / 3.0f;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
clear = next;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool DecodePackedStencilClear(uint32_t packed, uint8_t& clear) {
|
||||
const auto value = static_cast<uint8_t>(packed);
|
||||
if (packed != static_cast<uint32_t>(value) * 0x01010101u) {
|
||||
return false;
|
||||
}
|
||||
clear = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool DecodePackedDepthClear(vk::Format format, uint32_t packed, float& clear) {
|
||||
if (format != vk::Format::eD32Sfloat && format != vk::Format::eD32SfloatS8Uint) {
|
||||
return false;
|
||||
}
|
||||
const auto value = std::bit_cast<float>(packed);
|
||||
if (!std::isfinite(value) || value < 0.0f || value > 1.0f) {
|
||||
return false;
|
||||
}
|
||||
clear = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool ImageRangeOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
|
||||
uint64_t right_size) {
|
||||
if (left_size == 0 || right_size == 0 || left > UINT64_MAX - left_size ||
|
||||
right > UINT64_MAX - right_size) {
|
||||
EXIT("invalid image overlap range\n");
|
||||
}
|
||||
return left < right + right_size && right < left + left_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool ImageRangeOverlaps(GuestRange left, GuestRange right) {
|
||||
return ImageRangeOverlaps(left.address, left.size, right.address, right.size);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool ImagePageRangesOverlap(uint64_t left, uint64_t left_size, uint64_t right,
|
||||
uint64_t right_size) {
|
||||
if (left_size == 0 || right_size == 0 || left > UINT64_MAX - left_size ||
|
||||
right > UINT64_MAX - right_size) {
|
||||
EXIT("invalid image page-overlap range\n");
|
||||
}
|
||||
const auto left_first = left / TRACKER_PAGE_SIZE;
|
||||
const auto left_last = (left + left_size - 1) / TRACKER_PAGE_SIZE;
|
||||
const auto right_first = right / TRACKER_PAGE_SIZE;
|
||||
const auto right_last = (right + right_size - 1) / TRACKER_PAGE_SIZE;
|
||||
return left_first <= right_last && right_first <= left_last;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool ImagePageRangesOverlap(GuestRange left, GuestRange right) {
|
||||
return ImagePageRangesOverlap(left.address, left.size, right.address, right.size);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
|
||||
@@ -0,0 +1,416 @@
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool IsComponentSwizzle(vk::ComponentSwizzle swizzle) {
|
||||
switch (swizzle) {
|
||||
case vk::ComponentSwizzle::eIdentity:
|
||||
case vk::ComponentSwizzle::eZero:
|
||||
case vk::ComponentSwizzle::eOne:
|
||||
case vk::ComponentSwizzle::eR:
|
||||
case vk::ComponentSwizzle::eG:
|
||||
case vk::ComponentSwizzle::eB:
|
||||
case vk::ComponentSwizzle::eA: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCompatibleViewFormat(vk::Format image_format, vk::Format view_format) {
|
||||
return ImageViewOps::FormatsCompatible(image_format, view_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsStencilViewFormat(vk::Format format) {
|
||||
switch (format) {
|
||||
case vk::Format::eS8Uint:
|
||||
case vk::Format::eR8Uint:
|
||||
case vk::Format::eR8Unorm: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsDepthViewFormat(vk::Format format) {
|
||||
switch (format) {
|
||||
case vk::Format::eD16Unorm:
|
||||
case vk::Format::eR16Unorm:
|
||||
case vk::Format::eD32Sfloat:
|
||||
case vk::Format::eR32Sfloat:
|
||||
case vk::Format::eR32Uint: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsValidViewType(const VulkanImage& image, const ImageViewInfo& info) {
|
||||
switch (image.image_type) {
|
||||
case vk::ImageType::e1D:
|
||||
if (info.type != vk::ImageViewType::e1D && info.type != vk::ImageViewType::e1DArray) {
|
||||
return false;
|
||||
}
|
||||
return info.type != vk::ImageViewType::e1D || info.layer_count == 1;
|
||||
case vk::ImageType::e2D:
|
||||
switch (info.type) {
|
||||
case vk::ImageViewType::e2D: return info.layer_count == 1;
|
||||
case vk::ImageViewType::e2DArray: return true;
|
||||
case vk::ImageViewType::eCube:
|
||||
return static_cast<bool>(image.flags &
|
||||
vk::ImageCreateFlagBits::eCubeCompatible) &&
|
||||
info.base_layer % 6 == 0 && info.layer_count == 6;
|
||||
case vk::ImageViewType::eCubeArray:
|
||||
return static_cast<bool>(image.flags &
|
||||
vk::ImageCreateFlagBits::eCubeCompatible) &&
|
||||
info.base_layer % 6 == 0 && info.layer_count % 6 == 0;
|
||||
default: return false;
|
||||
}
|
||||
case vk::ImageType::e3D:
|
||||
switch (info.type) {
|
||||
case vk::ImageViewType::e3D:
|
||||
return info.base_layer == 0 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2D:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2DArray:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1;
|
||||
default: return false;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsValidAspect(const VulkanImage& image, vk::ImageAspectFlags aspect) {
|
||||
const auto depth_format = DepthAspectTransferFormat(image.format);
|
||||
if (depth_format == vk::Format::eUndefined) {
|
||||
return aspect == vk::ImageAspectFlagBits::eColor;
|
||||
}
|
||||
const auto supported = ImageViewOps::DepthAspectMask(image.format);
|
||||
return static_cast<bool>(aspect) && !(aspect & ~supported);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ImageViewOps {
|
||||
|
||||
namespace {
|
||||
|
||||
enum CompatibilityClass : uint32_t {
|
||||
None = 0,
|
||||
Bit8 = 1u << 0,
|
||||
Bit16 = 1u << 1,
|
||||
Bit24 = 1u << 2,
|
||||
Bit32 = 1u << 3,
|
||||
Bit48 = 1u << 4,
|
||||
Bit64 = 1u << 5,
|
||||
Bit96 = 1u << 6,
|
||||
Bit128 = 1u << 7,
|
||||
Bit192 = 1u << 8,
|
||||
Bit256 = 1u << 9,
|
||||
Bc1Rgb = 1u << 10,
|
||||
Bc1Rgba = 1u << 11,
|
||||
Bc2 = 1u << 12,
|
||||
Bc3 = 1u << 13,
|
||||
Bc4 = 1u << 14,
|
||||
Bc5 = 1u << 15,
|
||||
Bc6h = 1u << 16,
|
||||
Bc7 = 1u << 17,
|
||||
D16 = 1u << 18,
|
||||
D16S8 = 1u << 19,
|
||||
D24 = 1u << 20,
|
||||
D24S8 = 1u << 21,
|
||||
D32 = 1u << 22,
|
||||
D32S8 = 1u << 23,
|
||||
S8 = 1u << 24,
|
||||
};
|
||||
|
||||
[[nodiscard]] uint32_t FormatClass(vk::Format format) noexcept {
|
||||
switch (format) {
|
||||
case vk::Format::eR4G4UnormPack8:
|
||||
case vk::Format::eR8Sint:
|
||||
case vk::Format::eR8Snorm:
|
||||
case vk::Format::eR8Srgb:
|
||||
case vk::Format::eR8Sscaled:
|
||||
case vk::Format::eR8Uint:
|
||||
case vk::Format::eR8Unorm:
|
||||
case vk::Format::eR8Uscaled: return Bit8;
|
||||
|
||||
case vk::Format::eA1R5G5B5UnormPack16:
|
||||
case vk::Format::eA4B4G4R4UnormPack16:
|
||||
case vk::Format::eA4R4G4B4UnormPack16:
|
||||
case vk::Format::eB4G4R4A4UnormPack16:
|
||||
case vk::Format::eB5G5R5A1UnormPack16:
|
||||
case vk::Format::eB5G6R5UnormPack16:
|
||||
case vk::Format::eR10X6UnormPack16:
|
||||
case vk::Format::eR12X4UnormPack16:
|
||||
case vk::Format::eR16Sfloat:
|
||||
case vk::Format::eR16Sint:
|
||||
case vk::Format::eR16Snorm:
|
||||
case vk::Format::eR16Sscaled:
|
||||
case vk::Format::eR16Uint:
|
||||
case vk::Format::eR16Unorm:
|
||||
case vk::Format::eR16Uscaled:
|
||||
case vk::Format::eR4G4B4A4UnormPack16:
|
||||
case vk::Format::eR5G5B5A1UnormPack16:
|
||||
case vk::Format::eR5G6B5UnormPack16:
|
||||
case vk::Format::eR8G8Sint:
|
||||
case vk::Format::eR8G8Snorm:
|
||||
case vk::Format::eR8G8Srgb:
|
||||
case vk::Format::eR8G8Sscaled:
|
||||
case vk::Format::eR8G8Uint:
|
||||
case vk::Format::eR8G8Unorm:
|
||||
case vk::Format::eR8G8Uscaled: return Bit16;
|
||||
|
||||
case vk::Format::eB8G8R8Sint:
|
||||
case vk::Format::eB8G8R8Snorm:
|
||||
case vk::Format::eB8G8R8Srgb:
|
||||
case vk::Format::eB8G8R8Sscaled:
|
||||
case vk::Format::eB8G8R8Uint:
|
||||
case vk::Format::eB8G8R8Unorm:
|
||||
case vk::Format::eB8G8R8Uscaled:
|
||||
case vk::Format::eR8G8B8Sint:
|
||||
case vk::Format::eR8G8B8Snorm:
|
||||
case vk::Format::eR8G8B8Srgb:
|
||||
case vk::Format::eR8G8B8Sscaled:
|
||||
case vk::Format::eR8G8B8Uint:
|
||||
case vk::Format::eR8G8B8Unorm:
|
||||
case vk::Format::eR8G8B8Uscaled: return Bit24;
|
||||
|
||||
case vk::Format::eA2B10G10R10SintPack32:
|
||||
case vk::Format::eA2B10G10R10SnormPack32:
|
||||
case vk::Format::eA2B10G10R10SscaledPack32:
|
||||
case vk::Format::eA2B10G10R10UintPack32:
|
||||
case vk::Format::eA2B10G10R10UnormPack32:
|
||||
case vk::Format::eA2B10G10R10UscaledPack32:
|
||||
case vk::Format::eA2R10G10B10SintPack32:
|
||||
case vk::Format::eA2R10G10B10SnormPack32:
|
||||
case vk::Format::eA2R10G10B10SscaledPack32:
|
||||
case vk::Format::eA2R10G10B10UintPack32:
|
||||
case vk::Format::eA2R10G10B10UnormPack32:
|
||||
case vk::Format::eA2R10G10B10UscaledPack32:
|
||||
case vk::Format::eA8B8G8R8SintPack32:
|
||||
case vk::Format::eA8B8G8R8SnormPack32:
|
||||
case vk::Format::eA8B8G8R8SrgbPack32:
|
||||
case vk::Format::eA8B8G8R8SscaledPack32:
|
||||
case vk::Format::eA8B8G8R8UintPack32:
|
||||
case vk::Format::eA8B8G8R8UnormPack32:
|
||||
case vk::Format::eA8B8G8R8UscaledPack32:
|
||||
case vk::Format::eB10G11R11UfloatPack32:
|
||||
case vk::Format::eB8G8R8A8Sint:
|
||||
case vk::Format::eB8G8R8A8Snorm:
|
||||
case vk::Format::eB8G8R8A8Srgb:
|
||||
case vk::Format::eB8G8R8A8Sscaled:
|
||||
case vk::Format::eB8G8R8A8Uint:
|
||||
case vk::Format::eB8G8R8A8Unorm:
|
||||
case vk::Format::eB8G8R8A8Uscaled:
|
||||
case vk::Format::eE5B9G9R9UfloatPack32:
|
||||
case vk::Format::eR10X6G10X6Unorm2Pack16:
|
||||
case vk::Format::eR12X4G12X4Unorm2Pack16:
|
||||
case vk::Format::eR16G16Sfloat:
|
||||
case vk::Format::eR16G16Sint:
|
||||
case vk::Format::eR16G16Snorm:
|
||||
case vk::Format::eR16G16Sscaled:
|
||||
case vk::Format::eR16G16Uint:
|
||||
case vk::Format::eR16G16Unorm:
|
||||
case vk::Format::eR16G16Uscaled:
|
||||
case vk::Format::eR32Sfloat:
|
||||
case vk::Format::eR32Sint:
|
||||
case vk::Format::eR32Uint:
|
||||
case vk::Format::eR8G8B8A8Sint:
|
||||
case vk::Format::eR8G8B8A8Snorm:
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
case vk::Format::eR8G8B8A8Sscaled:
|
||||
case vk::Format::eR8G8B8A8Uint:
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Uscaled: return Bit32;
|
||||
|
||||
case vk::Format::eR16G16B16Sfloat:
|
||||
case vk::Format::eR16G16B16Sint:
|
||||
case vk::Format::eR16G16B16Snorm:
|
||||
case vk::Format::eR16G16B16Sscaled:
|
||||
case vk::Format::eR16G16B16Uint:
|
||||
case vk::Format::eR16G16B16Unorm:
|
||||
case vk::Format::eR16G16B16Uscaled: return Bit48;
|
||||
|
||||
case vk::Format::eR16G16B16A16Sfloat:
|
||||
case vk::Format::eR16G16B16A16Sint:
|
||||
case vk::Format::eR16G16B16A16Snorm:
|
||||
case vk::Format::eR16G16B16A16Sscaled:
|
||||
case vk::Format::eR16G16B16A16Uint:
|
||||
case vk::Format::eR16G16B16A16Unorm:
|
||||
case vk::Format::eR16G16B16A16Uscaled:
|
||||
case vk::Format::eR32G32Sfloat:
|
||||
case vk::Format::eR32G32Sint:
|
||||
case vk::Format::eR32G32Uint:
|
||||
case vk::Format::eR64Sfloat:
|
||||
case vk::Format::eR64Sint:
|
||||
case vk::Format::eR64Uint: return Bit64;
|
||||
|
||||
case vk::Format::eR32G32B32Sfloat:
|
||||
case vk::Format::eR32G32B32Sint:
|
||||
case vk::Format::eR32G32B32Uint: return Bit96;
|
||||
|
||||
case vk::Format::eR32G32B32A32Sfloat:
|
||||
case vk::Format::eR32G32B32A32Sint:
|
||||
case vk::Format::eR32G32B32A32Uint:
|
||||
case vk::Format::eR64G64Sfloat:
|
||||
case vk::Format::eR64G64Sint:
|
||||
case vk::Format::eR64G64Uint: return Bit128;
|
||||
|
||||
case vk::Format::eR64G64B64Sfloat:
|
||||
case vk::Format::eR64G64B64Sint:
|
||||
case vk::Format::eR64G64B64Uint: return Bit192;
|
||||
|
||||
case vk::Format::eR64G64B64A64Sfloat:
|
||||
case vk::Format::eR64G64B64A64Sint:
|
||||
case vk::Format::eR64G64B64A64Uint: return Bit256;
|
||||
|
||||
case vk::Format::eBc1RgbSrgbBlock:
|
||||
case vk::Format::eBc1RgbUnormBlock: return Bc1Rgb | Bit64;
|
||||
case vk::Format::eBc1RgbaSrgbBlock:
|
||||
case vk::Format::eBc1RgbaUnormBlock: return Bc1Rgba | Bit64;
|
||||
case vk::Format::eBc2SrgbBlock:
|
||||
case vk::Format::eBc2UnormBlock: return Bc2 | Bit128;
|
||||
case vk::Format::eBc3SrgbBlock:
|
||||
case vk::Format::eBc3UnormBlock: return Bc3 | Bit128;
|
||||
case vk::Format::eBc4SnormBlock:
|
||||
case vk::Format::eBc4UnormBlock: return Bc4 | Bit64;
|
||||
case vk::Format::eBc5SnormBlock:
|
||||
case vk::Format::eBc5UnormBlock: return Bc5 | Bit128;
|
||||
case vk::Format::eBc6HSfloatBlock:
|
||||
case vk::Format::eBc6HUfloatBlock: return Bc6h | Bit128;
|
||||
case vk::Format::eBc7SrgbBlock:
|
||||
case vk::Format::eBc7UnormBlock: return Bc7 | Bit128;
|
||||
|
||||
case vk::Format::eD16Unorm: return D16;
|
||||
case vk::Format::eD16UnormS8Uint: return D16S8;
|
||||
case vk::Format::eX8D24UnormPack32: return D24;
|
||||
case vk::Format::eD24UnormS8Uint: return D24S8;
|
||||
case vk::Format::eD32Sfloat: return D32;
|
||||
case vk::Format::eD32SfloatS8Uint: return D32S8;
|
||||
case vk::Format::eS8Uint: return S8;
|
||||
default: return None;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::ImageAspectFlags DepthAspectMask(vk::Format format) {
|
||||
switch (format) {
|
||||
case vk::Format::eD16Unorm:
|
||||
case vk::Format::eD32Sfloat: return vk::ImageAspectFlagBits::eDepth;
|
||||
case vk::Format::eD16UnormS8Uint:
|
||||
case vk::Format::eD24UnormS8Uint:
|
||||
case vk::Format::eD32SfloatS8Uint:
|
||||
return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
||||
default: EXIT("unsupported depth/stencil image format: %d\n", static_cast<int>(format));
|
||||
}
|
||||
}
|
||||
|
||||
bool FormatsCompatible(vk::Format base, vk::Format view) noexcept {
|
||||
if (base == view) {
|
||||
return true;
|
||||
}
|
||||
const auto base_class = FormatClass(base);
|
||||
const auto view_class = FormatClass(view);
|
||||
return view_class != None && (base_class & view_class) == view_class;
|
||||
}
|
||||
|
||||
} // namespace ImageViewOps
|
||||
|
||||
vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
const auto& image = backing;
|
||||
auto normalized = view_info;
|
||||
const bool is_storage =
|
||||
static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
|
||||
normalized.aspect = FullAspectMask(image.format);
|
||||
if (normalized.aspect & vk::ImageAspectFlagBits::eDepth &&
|
||||
IsDepthViewFormat(normalized.format)) {
|
||||
normalized.format = image.format;
|
||||
normalized.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
}
|
||||
if (normalized.aspect & vk::ImageAspectFlagBits::eStencil &&
|
||||
IsStencilViewFormat(normalized.format)) {
|
||||
normalized.format = image.format;
|
||||
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
|
||||
}
|
||||
normalized.usage =
|
||||
is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
|
||||
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
|
||||
IsCompatibleViewFormat(image.format, normalized.format);
|
||||
const bool slice_view = image.image_type == vk::ImageType::e3D &&
|
||||
(normalized.type == vk::ImageViewType::e2D ||
|
||||
normalized.type == vk::ImageViewType::e2DArray);
|
||||
const bool levels_valid = normalized.level_count != 0 &&
|
||||
normalized.base_level < image.mip_levels &&
|
||||
normalized.level_count <= image.mip_levels - normalized.base_level;
|
||||
const auto view_layers = slice_view && levels_valid
|
||||
? std::max(image.extent.depth >> normalized.base_level, 1u)
|
||||
: image.layers;
|
||||
const bool ranges_valid = levels_valid &&
|
||||
normalized.layer_count != 0 && normalized.base_layer < view_layers &&
|
||||
normalized.layer_count <= view_layers - normalized.base_layer;
|
||||
const bool mapping_valid =
|
||||
IsComponentSwizzle(normalized.mapping.r) && IsComponentSwizzle(normalized.mapping.g) &&
|
||||
IsComponentSwizzle(normalized.mapping.b) && IsComponentSwizzle(normalized.mapping.a);
|
||||
if (image.image == nullptr || !format_compatible || !ranges_valid || !mapping_valid ||
|
||||
!IsValidViewType(image, normalized) ||
|
||||
!IsValidAspect(image, normalized.aspect)) {
|
||||
EXIT("invalid image view: image_format=%d view_format=%d type=%d aspect=0x%x "
|
||||
"mip=%u+%u layer=%u+%u usage=0x%x image_levels=%u image_layers=%u\n",
|
||||
static_cast<int>(image.format), static_cast<int>(normalized.format),
|
||||
static_cast<int>(normalized.type),
|
||||
static_cast<vk::ImageAspectFlags::MaskType>(normalized.aspect), normalized.base_level,
|
||||
normalized.level_count, normalized.base_layer, normalized.layer_count,
|
||||
static_cast<vk::ImageUsageFlags::MaskType>(normalized.usage), image.mip_levels,
|
||||
image.layers);
|
||||
}
|
||||
|
||||
std::lock_guard lock(views.mutex);
|
||||
for (const auto& cached: views.views) {
|
||||
if (cached.info == normalized) {
|
||||
return cached.view;
|
||||
}
|
||||
}
|
||||
|
||||
vk::ImageViewUsageCreateInfo usage {};
|
||||
usage.sType = vk::StructureType::eImageViewUsageCreateInfo;
|
||||
usage.usage = image.usage;
|
||||
if (!is_storage) {
|
||||
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
vk::ImageViewCreateInfo create {};
|
||||
create.sType = vk::StructureType::eImageViewCreateInfo;
|
||||
create.pNext = &usage;
|
||||
create.image = image.image;
|
||||
create.viewType = normalized.type;
|
||||
create.format = normalized.format;
|
||||
create.components = normalized.mapping;
|
||||
create.subresourceRange.aspectMask = normalized.aspect;
|
||||
create.subresourceRange.baseMipLevel = normalized.base_level;
|
||||
create.subresourceRange.levelCount = normalized.level_count;
|
||||
create.subresourceRange.baseArrayLayer = normalized.base_layer;
|
||||
create.subresourceRange.layerCount = normalized.layer_count;
|
||||
|
||||
vk::ImageView view = nullptr;
|
||||
const auto result = m_graphics->device.createImageView(&create, nullptr, &view);
|
||||
if (result != vk::Result::eSuccess || view == nullptr) {
|
||||
EXIT("failed to create image view: result=%d image_format=%d view_format=%d type=%d "
|
||||
"aspect=0x%x mip=%u+%u layer=%u+%u usage=0x%x\n",
|
||||
static_cast<int>(result), static_cast<int>(image.format),
|
||||
static_cast<int>(view_info.format), static_cast<int>(view_info.type),
|
||||
static_cast<vk::ImageAspectFlags::MaskType>(view_info.aspect), view_info.base_level,
|
||||
view_info.level_count, view_info.base_layer, view_info.layer_count,
|
||||
static_cast<vk::ImageUsageFlags::MaskType>(view_info.usage));
|
||||
}
|
||||
views.views.push_back({normalized, view});
|
||||
return view;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,141 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace ImageViewOps {
|
||||
|
||||
[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
|
||||
[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept;
|
||||
} // namespace ImageViewOps
|
||||
|
||||
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle) noexcept {
|
||||
if ((swizzle & ~0xfffu) != 0) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t channel = 0; channel < 4; channel++) {
|
||||
switch (GetDstSel(swizzle, channel)) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7: break;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
|
||||
vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
|
||||
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format SrgbStorageViewFormat(vk::Format image_format) noexcept {
|
||||
switch (image_format) {
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
case vk::Format::eB8G8R8A8Srgb: return vk::Format::eR8G8B8A8Unorm;
|
||||
default: return vk::Format::eUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsSupportedSampledColorView(vk::Format image_format,
|
||||
vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
return IsValidImageSwizzle(swizzle) &&
|
||||
ImageViewOps::FormatsCompatible(image_format, view_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint32_t
|
||||
SelectSampledColorView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
if (IsSupportedSampledColorView(image_format, view_format, swizzle)) {
|
||||
return swizzle;
|
||||
}
|
||||
UnsupportedColorView("sampled", image_format, view_format, swizzle);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsSupportedSampledDepthView(vk::Format image_format,
|
||||
vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
if (!IsSupportedSampledDepthFormat(image_format, view_format)) {
|
||||
return false;
|
||||
}
|
||||
switch (swizzle) {
|
||||
case DstSel(4, 4, 4, 4):
|
||||
case DstSel(4, 0, 0, 0):
|
||||
case DstSel(4, 0, 0, 1): return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint32_t
|
||||
SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
if (IsSupportedSampledDepthView(image_format, view_format, swizzle)) {
|
||||
return swizzle;
|
||||
}
|
||||
EXIT("unsupported sampled depth image view: image_format=%d view_format=%d swizzle=0x%03x\n",
|
||||
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
return resource.kind == ShaderRecompiler::IR::ResourceKind::Image &&
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
|
||||
!resource.written && !resource.atomic;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
return resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint &&
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
|
||||
!resource.written && !resource.atomic && !resource.depth_compare;
|
||||
}
|
||||
|
||||
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
const auto srgb_view = SrgbStorageViewFormat(image_format);
|
||||
const bool srgb_storage_view =
|
||||
srgb_view != vk::Format::eUndefined && view_format == srgb_view;
|
||||
if ((image_format != view_format && !srgb_storage_view) ||
|
||||
!IsValidImageSwizzle(swizzle)) {
|
||||
UnsupportedColorView("storage", image_format, view_format, swizzle);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
|
||||
!resource.atomic && !resource.depth_compare;
|
||||
}
|
||||
|
||||
inline void
|
||||
ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
if (!IsSupportedStorageImageResource(resource)) {
|
||||
EXIT("unsupported storage color image resource: kind=%u dimension=%u mip=%u "
|
||||
"read=%d written=%d atomic=%d depth_compare=%d\n",
|
||||
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
|
||||
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written,
|
||||
resource.atomic, resource.depth_compare);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
|
||||
@@ -0,0 +1,633 @@
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/host_gpu/renderer/image/tiler.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
struct RenderTargetFormatMapping {
|
||||
Prospero::ChannelLayout layout;
|
||||
Prospero::ChannelType type;
|
||||
Prospero::ChannelOrder order;
|
||||
RenderTargetFormatInfo info;
|
||||
};
|
||||
|
||||
constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
|
||||
{Prospero::ChannelLayout::k8_8,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR8G8Unorm, 2}},
|
||||
{Prospero::ChannelLayout::k8_8_8_8,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR8G8B8A8Unorm, 4}},
|
||||
{Prospero::ChannelLayout::k8_8_8_8,
|
||||
Prospero::ChannelType::kSNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR8G8B8A8Snorm, 4}},
|
||||
{Prospero::ChannelLayout::k8_8_8_8,
|
||||
Prospero::ChannelType::kSrgb,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR8G8B8A8Srgb, 4}},
|
||||
{Prospero::ChannelLayout::k8_8_8_8,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kAlt,
|
||||
{vk::Format::eB8G8R8A8Unorm, 4}},
|
||||
{Prospero::ChannelLayout::k8_8_8_8,
|
||||
Prospero::ChannelType::kSNorm,
|
||||
Prospero::ChannelOrder::kAlt,
|
||||
{vk::Format::eB8G8R8A8Snorm, 4}},
|
||||
{Prospero::ChannelLayout::k8_8_8_8,
|
||||
Prospero::ChannelType::kSrgb,
|
||||
Prospero::ChannelOrder::kAlt,
|
||||
{vk::Format::eB8G8R8A8Srgb, 4}},
|
||||
{Prospero::ChannelLayout::k5_5_5_1,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR5G5B5A1UnormPack16, 2}},
|
||||
{Prospero::ChannelLayout::k4_4_4_4,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kReversed,
|
||||
{vk::Format::eB4G4R4A4UnormPack16, 2}},
|
||||
{Prospero::ChannelLayout::k10_10_10_2,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eA2B10G10R10UnormPack32, 4}},
|
||||
{Prospero::ChannelLayout::k10_10_10_2,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kAlt,
|
||||
{vk::Format::eA2R10G10B10UnormPack32, 4}},
|
||||
{Prospero::ChannelLayout::k11_11_10,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eB10G11R11UfloatPack32, 4}},
|
||||
{Prospero::ChannelLayout::k16,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16Unorm, 2}},
|
||||
{Prospero::ChannelLayout::k16,
|
||||
Prospero::ChannelType::kUInt,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16Uint, 2}},
|
||||
{Prospero::ChannelLayout::k16,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16Sfloat, 2}},
|
||||
{Prospero::ChannelLayout::k16_16,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16G16Unorm, 4}},
|
||||
{Prospero::ChannelLayout::k16_16,
|
||||
Prospero::ChannelType::kSNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16G16Snorm, 4}},
|
||||
{Prospero::ChannelLayout::k16_16,
|
||||
Prospero::ChannelType::kUInt,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16G16Uint, 4}},
|
||||
{Prospero::ChannelLayout::k16_16,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16G16Sfloat, 4}},
|
||||
{Prospero::ChannelLayout::k16_16_16_16,
|
||||
Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16G16B16A16Unorm, 8}},
|
||||
{Prospero::ChannelLayout::k16_16_16_16,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR16G16B16A16Sfloat, 8}},
|
||||
{Prospero::ChannelLayout::k16_16_16_16,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kAlt,
|
||||
{vk::Format::eR16G16B16A16Sfloat, 8, Prospero::ColorMappingBgra}},
|
||||
{Prospero::ChannelLayout::k16_16_16_16,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kReversed,
|
||||
{vk::Format::eR16G16B16A16Sfloat, 8, Prospero::ColorMappingAbgr}},
|
||||
{Prospero::ChannelLayout::k32,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR32Sfloat, 4}},
|
||||
{Prospero::ChannelLayout::k32_32,
|
||||
Prospero::ChannelType::kUInt,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR32G32Uint, 8}},
|
||||
{Prospero::ChannelLayout::k32_32,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR32G32Sfloat, 8}},
|
||||
{Prospero::ChannelLayout::k32_32_32_32,
|
||||
Prospero::ChannelType::kFloat,
|
||||
Prospero::ChannelOrder::kStandard,
|
||||
{vk::Format::eR32G32B32A32Sfloat, 16}},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO: cleanup!
|
||||
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_t raw_type,
|
||||
uint32_t raw_order) {
|
||||
const auto layout = static_cast<Prospero::ChannelLayout>(raw_layout);
|
||||
const auto type = static_cast<Prospero::ChannelType>(raw_type);
|
||||
const auto order = static_cast<Prospero::ChannelOrder>(raw_order);
|
||||
|
||||
if (layout == Prospero::ChannelLayout::k8 && type == Prospero::ChannelType::kUNorm &&
|
||||
raw_order <= Prospero::GpuEnumValue(Prospero::ChannelOrder::kAltReversed)) {
|
||||
return {vk::Format::eR8Unorm, 1};
|
||||
}
|
||||
for (const auto& mapping: kRenderTargetFormats) {
|
||||
if (mapping.layout == layout && mapping.type == type && mapping.order == order) {
|
||||
return mapping.info;
|
||||
}
|
||||
}
|
||||
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n", raw_layout,
|
||||
raw_type, raw_order);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static uint64_t GetLevelSrcOffset(const TileSizeOffset& level_size) {
|
||||
return (level_size.src_size != 0 ? level_size.src_offset : level_size.offset);
|
||||
}
|
||||
|
||||
static uint64_t GetLevelSrcSize(const TileSizeOffset& level_size) {
|
||||
return (level_size.src_size != 0 ? level_size.src_size : level_size.size);
|
||||
}
|
||||
|
||||
static uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
|
||||
return volume_texture ? std::max(depth >> level, 1u) : depth;
|
||||
}
|
||||
|
||||
static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume_texture) {
|
||||
size_t count = 0;
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
count += GetTextureLevelDepth(depth, level, volume_texture);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
|
||||
uint32_t slice) {
|
||||
if (level >= 16 || layout.level_sizes[level].size == 0) {
|
||||
EXIT("invalid texture upload slice source, level=%u slice=%u\n", level, slice);
|
||||
}
|
||||
const auto level_offset = GetLevelSrcOffset(layout.level_sizes[level]);
|
||||
const auto slice_stride =
|
||||
layout.source_slice_stride != 0 ? layout.source_slice_stride : layout.slice_stride;
|
||||
if (slice_stride != 0 && slice > (UINT64_MAX - level_offset) / slice_stride) {
|
||||
EXIT("texture upload slice source offset overflow, level=%u slice=%u\n", level, slice);
|
||||
}
|
||||
return level_offset + static_cast<uint64_t>(slice) * slice_stride;
|
||||
}
|
||||
|
||||
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
|
||||
switch (static_cast<Prospero::CompSwizzle>(s)) {
|
||||
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
|
||||
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
|
||||
case Prospero::CompSwizzle::kRed: return vk::ComponentSwizzle::eR;
|
||||
case Prospero::CompSwizzle::kGreen: return vk::ComponentSwizzle::eG;
|
||||
case Prospero::CompSwizzle::kBlue: return vk::ComponentSwizzle::eB;
|
||||
case Prospero::CompSwizzle::kAlpha: return vk::ComponentSwizzle::eA;
|
||||
default: EXIT("unknown swizzle: %d\n", static_cast<int>(s));
|
||||
}
|
||||
return vk::ComponentSwizzle::eIdentity;
|
||||
}
|
||||
|
||||
static uint32_t TextureGetDstSel(uint32_t swizzle, uint32_t channel) {
|
||||
return (swizzle >> (channel * 3u)) & 0x7u;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
|
||||
vk::ComponentMapping components {};
|
||||
components.r = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 0)));
|
||||
components.g = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 1)));
|
||||
components.b = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 2)));
|
||||
components.a = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 3)));
|
||||
return components;
|
||||
}
|
||||
|
||||
vk::Format TextureGetFormat(uint32_t fmt) {
|
||||
const auto vk_format = VulkanFormat(fmt);
|
||||
if (vk_format != vk::Format::eUndefined) {
|
||||
return vk_format;
|
||||
}
|
||||
EXIT("unknown format: fmt = %u\n", fmt);
|
||||
return vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels,
|
||||
uint64_t total_size, uint32_t depth) {
|
||||
uint64_t stride = 0;
|
||||
for (uint32_t i = 0; i < levels; i++) {
|
||||
stride =
|
||||
std::max(stride, static_cast<uint64_t>(level_sizes[i].offset) + level_sizes[i].size);
|
||||
}
|
||||
|
||||
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
|
||||
const auto guest_stride = total_size / depth;
|
||||
if (guest_stride >= stride) {
|
||||
stride = guest_stride;
|
||||
}
|
||||
}
|
||||
|
||||
return stride;
|
||||
}
|
||||
|
||||
uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
|
||||
if (const uint32_t bytes_per_element = Prospero::NumBytesPerElement(fmt);
|
||||
bytes_per_element != 0) {
|
||||
return static_cast<uint64_t>(pitch) * height * bytes_per_element;
|
||||
}
|
||||
|
||||
if (const uint32_t bytes_per_block = Prospero::BlockCompressedBytesPerBlock(fmt);
|
||||
bytes_per_block != 0) {
|
||||
const uint32_t blocks_w = std::max((pitch + 3u) / 4u, 1u);
|
||||
const uint32_t blocks_h = std::max((height + 3u) / 4u, 1u);
|
||||
return static_cast<uint64_t>(blocks_w) * blocks_h * bytes_per_block;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64_t height,
|
||||
uint64_t levels, uint32_t base_pitch) {
|
||||
uint64_t offset = 0;
|
||||
auto pitch = base_pitch;
|
||||
auto h = static_cast<uint32_t>(height);
|
||||
|
||||
for (uint32_t i = 0; i < levels; i++) {
|
||||
const auto size = CalcLinearUploadLevelSize(fmt, pitch, h);
|
||||
EXIT_NOT_IMPLEMENTED(size > 0xffffffffull);
|
||||
EXIT_NOT_IMPLEMENTED(offset > 0xffffffffull);
|
||||
|
||||
level_sizes[i].size = static_cast<uint32_t>(size);
|
||||
level_sizes[i].offset = static_cast<uint32_t>(offset);
|
||||
level_sizes[i].src_size = 0;
|
||||
level_sizes[i].src_offset = 0;
|
||||
level_sizes[i].x = 0;
|
||||
level_sizes[i].y = 0;
|
||||
|
||||
offset += size;
|
||||
if (pitch > 1) {
|
||||
pitch /= 2;
|
||||
}
|
||||
if (h > 1) {
|
||||
h /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
|
||||
uint64_t levels, uint32_t depth, uint64_t pitch,
|
||||
uint64_t tile, uint64_t upload_size,
|
||||
bool allow_depth_tile, bool volume_texture,
|
||||
const char* owner) {
|
||||
TextureUploadLayout layout {};
|
||||
layout.tile = static_cast<uint32_t>(tile);
|
||||
layout.pitch = static_cast<uint32_t>(pitch);
|
||||
layout.texel_block = Prospero::BlockCompressedBytesPerBlock(fmt) != 0 ? 4u : 1u;
|
||||
layout.volume_texture = volume_texture;
|
||||
|
||||
if (fmt != 0) {
|
||||
if (layout.tile != 0) {
|
||||
const auto tile_mode = static_cast<Prospero::TileMode>(layout.tile);
|
||||
switch (tile_mode) {
|
||||
case Prospero::TileMode::kStandard256B:
|
||||
if (TileIsStandard256BTextureSupported(fmt)) {
|
||||
layout.tile_family = TileBlockFamily::Standard256B;
|
||||
}
|
||||
break;
|
||||
case Prospero::TileMode::kStandard4KB:
|
||||
if (TileIsStandard4KBTextureSupported(fmt)) {
|
||||
layout.tile_family = TileBlockFamily::Standard4KB;
|
||||
}
|
||||
break;
|
||||
case Prospero::TileMode::kStandard64KB:
|
||||
if (TileIsStandard64KBTextureSupported(fmt)) {
|
||||
layout.tile_family = TileBlockFamily::Standard64KB;
|
||||
}
|
||||
break;
|
||||
case Prospero::TileMode::kPrt:
|
||||
if (TileIsStandard64KBTextureSupported(fmt)) {
|
||||
layout.tile_family = TileBlockFamily::Prt64KB;
|
||||
}
|
||||
break;
|
||||
case Prospero::TileMode::kRenderTarget:
|
||||
if (Prospero::RenderTargetBytesPerElement(fmt) != 0) {
|
||||
layout.tile_family = TileBlockFamily::RenderTarget64KB;
|
||||
}
|
||||
break;
|
||||
case Prospero::TileMode::kDepth:
|
||||
if (allow_depth_tile && Prospero::RenderTargetBytesPerElement(fmt) != 0) {
|
||||
layout.tile_family = TileBlockFamily::Depth64KB;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
if (layout.tile_family == TileBlockFamily::Count) {
|
||||
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
|
||||
"size=%" PRIu64 " extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64
|
||||
" levels=%" PRIu64 "\n",
|
||||
owner, static_cast<uint32_t>(fmt), layout.tile, upload_size, width, height,
|
||||
pitch, levels);
|
||||
}
|
||||
}
|
||||
|
||||
layout.pitch = TileGetTexturePitch(fmt, width, levels, layout.tile);
|
||||
|
||||
TileGetTextureSize(fmt, width, height, layout.pitch, levels, layout.tile, nullptr,
|
||||
layout.level_sizes, layout.padded_sizes);
|
||||
|
||||
if (static_cast<Prospero::TileMode>(layout.tile) != Prospero::TileMode::kLinear) {
|
||||
if (layout.volume_texture) {
|
||||
layout.slice_stride = SetLinearUploadLevels(layout.level_sizes, fmt, height, levels,
|
||||
static_cast<uint32_t>(width));
|
||||
} else {
|
||||
TileSizeOffset tiled_levels[16] {};
|
||||
std::copy_n(layout.level_sizes, levels, tiled_levels);
|
||||
layout.source_slice_stride =
|
||||
CalcTextureSliceStride(tiled_levels, levels, upload_size, depth);
|
||||
SetLinearUploadLevels(layout.level_sizes, fmt, height, levels, layout.pitch);
|
||||
for (uint32_t i = 0; i < levels; ++i) {
|
||||
if (tiled_levels[i].src_size > tiled_levels[i].size) {
|
||||
layout.first_tail_level = std::min(layout.first_tail_level, i);
|
||||
}
|
||||
layout.level_sizes[i].src_offset = GetLevelSrcOffset(tiled_levels[i]);
|
||||
layout.level_sizes[i].src_size = GetLevelSrcSize(tiled_levels[i]);
|
||||
layout.level_sizes[i].x = tiled_levels[i].x;
|
||||
layout.level_sizes[i].y = tiled_levels[i].y;
|
||||
}
|
||||
}
|
||||
} else if (layout.volume_texture) {
|
||||
layout.slice_stride =
|
||||
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
|
||||
}
|
||||
} else {
|
||||
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
|
||||
" extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64 " levels=%" PRIu64 "\n",
|
||||
owner, layout.tile, upload_size, width, height, pitch, levels);
|
||||
}
|
||||
|
||||
if (!layout.volume_texture) {
|
||||
layout.slice_stride =
|
||||
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
std::vector<vk::BufferImageCopy>
|
||||
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture) {
|
||||
uint32_t mip_width = width;
|
||||
uint32_t mip_height = height;
|
||||
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) !=
|
||||
Prospero::TileMode::kLinear
|
||||
? width
|
||||
: layout.pitch;
|
||||
|
||||
std::vector<vk::BufferImageCopy> regions;
|
||||
regions.reserve(GetTextureRegionCount(depth, levels, volume_texture));
|
||||
for (uint32_t i = 0; i < levels; i++) {
|
||||
EXIT_NOT_IMPLEMENTED(layout.level_sizes[i].size == 0);
|
||||
|
||||
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
|
||||
|
||||
for (uint32_t z = 0; z < mip_depth; z++) {
|
||||
const auto slice_offset = z * layout.slice_stride;
|
||||
vk::BufferImageCopy region {};
|
||||
region.bufferOffset =
|
||||
layout.level_sizes[i].offset + slice_offset;
|
||||
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i,
|
||||
array_texture ? z : 0, 1};
|
||||
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
|
||||
region.imageExtent = {mip_width, mip_height, 1};
|
||||
const bool linear =
|
||||
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear;
|
||||
if (linear) {
|
||||
region.bufferRowLength = layout.padded_sizes[i].width;
|
||||
region.bufferImageHeight = layout.padded_sizes[i].height;
|
||||
} else {
|
||||
const auto align = [](uint32_t value, uint32_t block) {
|
||||
return ((value + block - 1u) / block) * block;
|
||||
};
|
||||
const auto pitch = align(mip_pitch, layout.texel_block);
|
||||
region.bufferRowLength =
|
||||
pitch > align(mip_width, layout.texel_block) ? pitch : 0;
|
||||
}
|
||||
regions.push_back(region);
|
||||
}
|
||||
|
||||
if (mip_width > 1) {
|
||||
mip_width /= 2;
|
||||
}
|
||||
if (mip_height > 1) {
|
||||
mip_height /= 2;
|
||||
}
|
||||
if (mip_pitch > 1) {
|
||||
mip_pitch /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
struct GpuTileElementLayout {
|
||||
uint32_t bytes = 0;
|
||||
uint32_t wide = 1;
|
||||
uint32_t tall = 1;
|
||||
};
|
||||
|
||||
static bool GetGpuTileElementLayout(uint32_t fmt, GpuTileElementLayout& out) {
|
||||
if (const auto bytes = Prospero::NumBytesPerElement(fmt); bytes != 0) {
|
||||
out = {bytes, 1, 1};
|
||||
return true;
|
||||
}
|
||||
if (const auto bytes = Prospero::BlockCompressedBytesPerBlock(fmt); bytes != 0) {
|
||||
out = {bytes, 4, 4};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity, uint64_t& size) {
|
||||
if (offset > capacity || length > capacity - offset) {
|
||||
return false;
|
||||
}
|
||||
size = length;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
|
||||
uint64_t levels, std::vector<GpuTileInfo>& out_infos) {
|
||||
if (size == 0 || levels == 0 || levels > 16 || depth == 0 ||
|
||||
regions.size() != GetTextureRegionCount(depth, levels, layout.volume_texture) ||
|
||||
Prospero::IsFmaskTextureFormat(fmt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GpuTileElementLayout element {};
|
||||
if (layout.tile_family == TileBlockFamily::RenderTarget64KB ||
|
||||
layout.tile_family == TileBlockFamily::Depth64KB) {
|
||||
element.bytes = Prospero::RenderTargetBytesPerElement(fmt);
|
||||
} else if (!GetGpuTileElementLayout(fmt, element)) {
|
||||
return false;
|
||||
}
|
||||
if (element.bytes == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<GpuTileInfo> infos;
|
||||
infos.reserve(regions.size());
|
||||
if (layout.volume_texture) {
|
||||
TileVolumeLayout volume {};
|
||||
if (!TileGetTextureVolumeLayout(fmt, regions[0].imageExtent.width,
|
||||
regions[0].imageExtent.height, depth,
|
||||
static_cast<uint32_t>(levels), layout.tile, volume)) {
|
||||
return false;
|
||||
}
|
||||
element = {volume.bytes_per_element, volume.texel_width, volume.texel_height};
|
||||
TileBlockLayout block {};
|
||||
if (!TileGetBlockLayout(volume.family, element.bytes, block)) return false;
|
||||
|
||||
size_t region_base = 0;
|
||||
for (uint32_t level = 0; level < levels; ++level) {
|
||||
const uint32_t mip_depth = GetTextureLevelDepth(depth, level, true);
|
||||
const bool tail = level >= volume.first_tail_level;
|
||||
const uint64_t linear_stride = layout.slice_stride;
|
||||
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
|
||||
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
|
||||
const auto& region = regions[region_base + z];
|
||||
const auto pitch = region.bufferRowLength != 0
|
||||
? region.bufferRowLength
|
||||
: region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
GpuTileInfo info {};
|
||||
info.family = block.family;
|
||||
info.bytes_per_element = block.bytes_per_element;
|
||||
info.linear_offset = region.bufferOffset;
|
||||
info.tiled_offset =
|
||||
static_cast<uint64_t>(z / block.block_depth) * volume.block_slice_size +
|
||||
volume.level_offsets[level];
|
||||
const uint64_t linear_span =
|
||||
static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
|
||||
layout.level_sizes[level].size;
|
||||
if (!SetGpuTileSize(info.linear_offset, linear_span, size, info.linear_size) ||
|
||||
!SetGpuTileSize(info.tiled_offset, volume.level_sizes[level], size,
|
||||
info.tiled_size)) {
|
||||
return false;
|
||||
}
|
||||
info.linear_slice_stride = linear_stride;
|
||||
info.width = std::max(
|
||||
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max(
|
||||
(logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.depth = copy_depth;
|
||||
info.surface_z = block.block_depth == 1
|
||||
? static_cast<uint32_t>(region.imageOffset.z)
|
||||
: 0;
|
||||
info.pitch =
|
||||
std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail_x = tail ? volume.tail_x[level] : 0;
|
||||
info.tail_y = tail ? volume.tail_y[level] : 0;
|
||||
info.tail = tail;
|
||||
info.tiled_width = volume.level_widths[level];
|
||||
info.tiled_height = volume.level_heights[level];
|
||||
infos.push_back(info);
|
||||
}
|
||||
region_base += mip_depth;
|
||||
}
|
||||
} else {
|
||||
const auto base_family = layout.tile_family;
|
||||
if (base_family == TileBlockFamily::Count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t region_index = 0;
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
const auto& level_size = layout.level_sizes[level];
|
||||
const bool tail = level >= layout.first_tail_level;
|
||||
const auto family = base_family;
|
||||
TileBlockLayout block {};
|
||||
if (!TileGetBlockLayout(family, element.bytes, block)) {
|
||||
return false;
|
||||
}
|
||||
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
|
||||
for (uint32_t z = 0; z < level_depth; z++) {
|
||||
const auto& region = regions[region_index++];
|
||||
const auto pitch = region.bufferRowLength != 0
|
||||
? region.bufferRowLength
|
||||
: region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
GpuTileInfo info {};
|
||||
info.family = block.family;
|
||||
info.bytes_per_element = block.bytes_per_element;
|
||||
info.linear_offset = region.bufferOffset;
|
||||
info.tiled_offset = TextureUploadSliceSourceOffset(layout, level, z);
|
||||
if (!SetGpuTileSize(info.linear_offset, level_size.size, size, info.linear_size) ||
|
||||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), size,
|
||||
info.tiled_size)) {
|
||||
return false;
|
||||
}
|
||||
info.width = std::max(
|
||||
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max(
|
||||
(logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB ||
|
||||
base_family == TileBlockFamily::Depth64KB
|
||||
? region.imageSubresource.baseArrayLayer
|
||||
: 0;
|
||||
info.pitch =
|
||||
std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail = tail;
|
||||
info.tail_x = tail ? level_size.x : 0;
|
||||
info.tail_y = tail ? level_size.y : 0;
|
||||
info.tiled_width =
|
||||
layout.padded_sizes[level].width != 0
|
||||
? std::max((layout.padded_sizes[level].width + element.wide - 1u) /
|
||||
element.wide,
|
||||
1u)
|
||||
: info.pitch;
|
||||
info.tiled_height =
|
||||
layout.padded_sizes[level].height != 0
|
||||
? std::max((layout.padded_sizes[level].height + element.tall - 1u) /
|
||||
element.tall,
|
||||
1u)
|
||||
: info.height;
|
||||
infos.push_back(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (infos.empty()) {
|
||||
return false;
|
||||
}
|
||||
out_infos = std::move(infos);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GpuTileInfo;
|
||||
|
||||
struct RenderTargetFormatInfo {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
uint32_t bytes_per_element = 0;
|
||||
Prospero::ColorComponentMapping export_mapping;
|
||||
};
|
||||
|
||||
struct TextureUploadLayout {
|
||||
uint32_t tile = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t texel_block = 1;
|
||||
uint64_t slice_stride = 0;
|
||||
uint64_t source_slice_stride = 0;
|
||||
uint32_t first_tail_level = 16;
|
||||
TileBlockFamily tile_family = TileBlockFamily::Count;
|
||||
bool volume_texture = false;
|
||||
TileSizeOffset level_sizes[16] = {};
|
||||
TilePaddedSize padded_sizes[16] = {};
|
||||
};
|
||||
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
|
||||
vk::Format TextureGetFormat(uint32_t fmt);
|
||||
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
|
||||
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
|
||||
uint64_t levels, uint32_t depth, uint64_t pitch,
|
||||
uint64_t tile, uint64_t upload_size,
|
||||
bool allow_depth_tile, bool volume_texture,
|
||||
const char* owner);
|
||||
std::vector<vk::BufferImageCopy>
|
||||
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture);
|
||||
bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
|
||||
uint64_t levels, std::vector<GpuTileInfo>& infos);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_ */
|
||||
@@ -0,0 +1,718 @@
|
||||
#include "graphics/host_gpu/renderer/image/tiler.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_demote_d16_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_depth_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_promote_d16_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_prt_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_prt_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_render_target_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard256_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard4_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard4_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_swap_bgra16_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
StreamBuffer& stream_buffer)
|
||||
: m_graphics(graphics), m_scheduler(scheduler), m_stream_buffer(stream_buffer) {
|
||||
static_assert(FamilyCount == 9);
|
||||
static_assert(sizeof(Push) == 52);
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> bindings {};
|
||||
for (uint32_t index = 0; index < 2; index++) {
|
||||
bindings[index] = {index, vk::DescriptorType::eStorageBuffer, 1,
|
||||
vk::ShaderStageFlagBits::eCompute, nullptr};
|
||||
}
|
||||
bindings[2] = {2, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute,
|
||||
nullptr};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptor_info.bindingCount = static_cast<uint32_t>(bindings.size());
|
||||
descriptor_info.pBindings = bindings.data();
|
||||
RequireVulkanSuccess(m_graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
|
||||
&m_descriptor_layout),
|
||||
"create TileManager descriptor layout");
|
||||
|
||||
const vk::PushConstantRange push_range {vk::ShaderStageFlagBits::eCompute, 0, sizeof(Push)};
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_descriptor_layout;
|
||||
layout_info.pushConstantRangeCount = 1;
|
||||
layout_info.pPushConstantRanges = &push_range;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
|
||||
"create TileManager pipeline layout");
|
||||
}
|
||||
|
||||
TileManager::~TileManager() {
|
||||
for (auto pipeline: m_pipelines) {
|
||||
if (pipeline != nullptr) {
|
||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_d16_to_d24 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d24, nullptr);
|
||||
}
|
||||
if (m_d16_to_d32 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d32, nullptr);
|
||||
}
|
||||
if (m_d24_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d24_to_d16, nullptr);
|
||||
}
|
||||
if (m_d32_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d32_to_d16, nullptr);
|
||||
}
|
||||
if (m_swap_bgra16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_swap_bgra16, nullptr);
|
||||
}
|
||||
if (m_pipeline_layout != nullptr) {
|
||||
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
|
||||
}
|
||||
if (m_descriptor_layout != nullptr) {
|
||||
m_graphics.device.destroyDescriptorSetLayout(m_descriptor_layout, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TileManager::Scratch TileManager::AllocateScratch(uint64_t size) {
|
||||
EXIT_IF(size == 0);
|
||||
vk::BufferCreateInfo create {};
|
||||
create.sType = vk::StructureType::eBufferCreateInfo;
|
||||
create.size = size;
|
||||
create.usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferSrc |
|
||||
vk::BufferUsageFlagBits::eTransferDst;
|
||||
create.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
VmaAllocationCreateInfo allocate {};
|
||||
allocate.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VmaAllocation memory = nullptr;
|
||||
const auto raw = static_cast<VkBufferCreateInfo>(create);
|
||||
RequireVulkanSuccess(static_cast<vk::Result>(vmaCreateBuffer(
|
||||
m_graphics.allocator, &raw, &allocate, &buffer, &memory, nullptr)),
|
||||
"allocate TileManager scratch buffer");
|
||||
return {buffer, memory, size};
|
||||
}
|
||||
|
||||
void TileManager::DeferDestroy(Scratch scratch) {
|
||||
auto allocator = m_graphics.allocator;
|
||||
m_scheduler.DeferOperation(
|
||||
[allocator, scratch] { vmaDestroyBuffer(allocator, scratch.buffer, scratch.allocation); });
|
||||
}
|
||||
|
||||
void TileManager::Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, uint64_t source_base,
|
||||
uint64_t target_base, std::vector<Dispatch>& dispatches) {
|
||||
EXIT_IF(infos.empty() || tiled_capacity == 0 || linear_capacity == 0);
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
EXIT_NOT_IMPLEMENTED(tiled_capacity > UINT32_MAX || linear_capacity > UINT32_MAX);
|
||||
|
||||
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
|
||||
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
|
||||
};
|
||||
const auto checked_add = [](uint64_t left, uint64_t right, uint64_t& result) {
|
||||
return right <= UINT64_MAX - left && (result = left + right, true);
|
||||
};
|
||||
const auto valid_range = [](uint64_t offset, uint64_t size, uint64_t capacity) {
|
||||
return size != 0 && offset <= capacity && size <= capacity - offset;
|
||||
};
|
||||
|
||||
dispatches.clear();
|
||||
dispatches.reserve(infos.size());
|
||||
for (const auto& info: infos) {
|
||||
TileBlockLayout block {};
|
||||
const uint32_t tiled_width = info.tiled_width != 0 ? info.tiled_width : info.pitch;
|
||||
const uint32_t tiled_height = info.tiled_height != 0 ? info.tiled_height : info.height;
|
||||
const uint64_t groups_x = (static_cast<uint64_t>(info.width) + 7u) / 8u;
|
||||
const uint64_t groups_y = (static_cast<uint64_t>(info.height) + 7u) / 8u;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!TileGetBlockLayout(info.family, info.bytes_per_element, block) || info.width == 0 ||
|
||||
info.height == 0 || info.depth == 0 || info.pitch < info.width ||
|
||||
groups_x > limits.maxComputeWorkGroupCount[0] ||
|
||||
groups_y > limits.maxComputeWorkGroupCount[1] ||
|
||||
info.depth > limits.maxComputeWorkGroupCount[2] ||
|
||||
(!info.tail && (tiled_width < info.width || tiled_height < info.height)) ||
|
||||
!valid_range(info.linear_offset, info.linear_size, linear_capacity) ||
|
||||
!valid_range(info.tiled_offset, info.tiled_size, tiled_capacity) ||
|
||||
(block.block_depth == 1 && info.depth != 1));
|
||||
|
||||
uint64_t pitch_bytes = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(info.pitch, info.bytes_per_element, pitch_bytes) ||
|
||||
pitch_bytes > UINT32_MAX);
|
||||
uint64_t slice_bytes = info.linear_slice_stride;
|
||||
uint64_t minimum_slice = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(pitch_bytes, info.height, minimum_slice));
|
||||
if (slice_bytes == 0) {
|
||||
slice_bytes = minimum_slice;
|
||||
}
|
||||
uint64_t linear_used = 0;
|
||||
uint64_t bytes = 0;
|
||||
EXIT_NOT_IMPLEMENTED((info.depth > 1 && slice_bytes < minimum_slice) ||
|
||||
!checked_multiply(info.depth - 1u, slice_bytes, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
!checked_multiply(info.height - 1u, pitch_bytes, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
!checked_multiply(info.width, info.bytes_per_element, bytes) ||
|
||||
!checked_add(linear_used, bytes, linear_used) ||
|
||||
linear_used > info.linear_size || slice_bytes > UINT32_MAX);
|
||||
|
||||
const uint64_t columns =
|
||||
(static_cast<uint64_t>(tiled_width) + block.block_width - 1u) / block.block_width;
|
||||
const uint64_t rows =
|
||||
(static_cast<uint64_t>(tiled_height) + block.block_height - 1u) / block.block_height;
|
||||
uint64_t blocks_per_slice = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(columns, rows, blocks_per_slice) ||
|
||||
columns > UINT32_MAX || blocks_per_slice > UINT32_MAX);
|
||||
if (info.tail) {
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
info.family == TileBlockFamily::Standard256B || info.depth > block.block_depth ||
|
||||
info.tail_x >= block.block_width || info.width > block.block_width - info.tail_x ||
|
||||
info.tail_y >= block.block_height ||
|
||||
info.height > block.block_height - info.tail_y ||
|
||||
info.tiled_size < block.block_size);
|
||||
} else {
|
||||
const uint64_t slices =
|
||||
(static_cast<uint64_t>(info.depth) + block.block_depth - 1u) / block.block_depth;
|
||||
uint64_t tiled_used = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!checked_multiply(blocks_per_slice, slices, tiled_used) ||
|
||||
!checked_multiply(tiled_used, block.block_size, tiled_used) ||
|
||||
tiled_used > info.tiled_size);
|
||||
}
|
||||
|
||||
const uint32_t alignment = std::min(info.bytes_per_element, 4u);
|
||||
EXIT_NOT_IMPLEMENTED(((info.linear_offset | info.tiled_offset | pitch_bytes | slice_bytes) &
|
||||
(alignment - 1u)) != 0);
|
||||
const uint64_t src = source_base + (tile ? info.linear_offset : info.tiled_offset);
|
||||
const uint64_t dst = target_base + (tile ? info.tiled_offset : info.linear_offset);
|
||||
EXIT_NOT_IMPLEMENTED(src > UINT32_MAX || dst > UINT32_MAX);
|
||||
|
||||
const uint32_t family_index = static_cast<uint32_t>(info.family);
|
||||
const uint32_t element_index = std::countr_zero(info.bytes_per_element);
|
||||
EXIT_NOT_IMPLEMENTED(family_index >= FamilyCount || element_index >= BytesPerElementCount);
|
||||
Dispatch dispatch {};
|
||||
dispatch.pipeline_slot =
|
||||
((tile ? FamilyCount : 0u) + family_index) * BytesPerElementCount + element_index;
|
||||
dispatch.push.src_base = static_cast<uint32_t>(src);
|
||||
dispatch.push.dst_base = static_cast<uint32_t>(dst);
|
||||
dispatch.push.width = info.width;
|
||||
dispatch.push.height = info.height;
|
||||
dispatch.push.depth = info.depth;
|
||||
dispatch.push.surface_z = info.surface_z;
|
||||
dispatch.push.pitch_bytes = static_cast<uint32_t>(pitch_bytes);
|
||||
dispatch.push.slice_bytes = static_cast<uint32_t>(slice_bytes);
|
||||
dispatch.push.blocks_per_row = static_cast<uint32_t>(columns);
|
||||
dispatch.push.blocks_per_slice = static_cast<uint32_t>(blocks_per_slice);
|
||||
dispatch.push.tail_x = info.tail_x;
|
||||
dispatch.push.tail_y = info.tail_y;
|
||||
dispatch.push.tail = info.tail;
|
||||
dispatches.push_back(dispatch);
|
||||
}
|
||||
|
||||
const uint64_t uniform_alignment =
|
||||
std::max<uint64_t>(limits.minUniformBufferOffsetAlignment, 1);
|
||||
const uint64_t stride = (sizeof(Push) + uniform_alignment - 1) & ~(uniform_alignment - 1);
|
||||
EXIT_NOT_IMPLEMENTED(dispatches.size() > UINT64_MAX / stride);
|
||||
const uint64_t bytes = dispatches.size() * stride;
|
||||
auto [mapped, offset] = m_stream_buffer.Map(bytes, uniform_alignment);
|
||||
EXIT_IF(mapped == nullptr);
|
||||
for (size_t index = 0; index < dispatches.size(); index++) {
|
||||
std::memcpy(mapped + index * stride, &dispatches[index].push, sizeof(Push));
|
||||
dispatches[index].params_offset = offset + index * stride;
|
||||
}
|
||||
m_stream_buffer.Commit();
|
||||
}
|
||||
|
||||
vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
EXIT_IF(slot >= m_pipelines.size());
|
||||
if (m_pipelines[slot] != nullptr) {
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
struct Shader {
|
||||
const uint32_t* code;
|
||||
size_t words;
|
||||
};
|
||||
static constexpr std::array<Shader, FamilyCount> shaders {{
|
||||
{GPU_TILER_STANDARD256_SPV, std::size(GPU_TILER_STANDARD256_SPV)},
|
||||
{GPU_TILER_STANDARD4_SPV, std::size(GPU_TILER_STANDARD4_SPV)},
|
||||
{GPU_TILER_STANDARD4_3D_SPV, std::size(GPU_TILER_STANDARD4_3D_SPV)},
|
||||
{GPU_TILER_STANDARD64_SPV, std::size(GPU_TILER_STANDARD64_SPV)},
|
||||
{GPU_TILER_STANDARD64_3D_SPV, std::size(GPU_TILER_STANDARD64_3D_SPV)},
|
||||
{GPU_TILER_PRT_SPV, std::size(GPU_TILER_PRT_SPV)},
|
||||
{GPU_TILER_PRT_3D_SPV, std::size(GPU_TILER_PRT_3D_SPV)},
|
||||
{GPU_TILER_RENDER_TARGET_SPV, std::size(GPU_TILER_RENDER_TARGET_SPV)},
|
||||
{GPU_TILER_DEPTH_SPV, std::size(GPU_TILER_DEPTH_SPV)},
|
||||
}};
|
||||
const uint32_t element_index = slot % BytesPerElementCount;
|
||||
const uint32_t direction_index = slot / (FamilyCount * BytesPerElementCount);
|
||||
const uint32_t family_index = (slot / BytesPerElementCount) % FamilyCount;
|
||||
const uint32_t values[] {1u << element_index, direction_index};
|
||||
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
|
||||
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = shaders[family_index].words * sizeof(uint32_t);
|
||||
module_info.pCode = shaders[family_index].code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create TileManager shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_pipelines[slot]);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create TileManager pipeline");
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
|
||||
void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
|
||||
uint64_t source_capacity, vk::Buffer target, uint64_t target_offset,
|
||||
uint64_t target_capacity, std::span<Dispatch> dispatches,
|
||||
bool clear_target) {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t source_descriptor_offset = source_offset & ~(descriptor_alignment - 1);
|
||||
const uint64_t target_descriptor_offset = target_offset & ~(descriptor_alignment - 1);
|
||||
const uint64_t source_base = source_offset - source_descriptor_offset;
|
||||
const uint64_t target_base = target_offset - target_descriptor_offset;
|
||||
const uint64_t source_range = (source_base + source_capacity + 3u) & ~uint64_t {3};
|
||||
const uint64_t target_range = (target_base + target_capacity + 3u) & ~uint64_t {3};
|
||||
EXIT_NOT_IMPLEMENTED(source_range > limits.maxStorageBufferRange ||
|
||||
target_range > limits.maxStorageBufferRange || target_offset % 4 != 0 ||
|
||||
target_capacity % 4 != 0);
|
||||
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::BufferMemoryBarrier barriers[3] {};
|
||||
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].buffer = source;
|
||||
barriers[0].offset = source_offset;
|
||||
barriers[0].size = source_capacity;
|
||||
barriers[1] = barriers[0];
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
|
||||
barriers[1].dstAccessMask =
|
||||
clear_target ? vk::AccessFlagBits::eTransferWrite
|
||||
: vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].buffer = target;
|
||||
barriers[1].offset = target_offset;
|
||||
barriers[1].size = target_capacity;
|
||||
barriers[2] = barriers[0];
|
||||
barriers[2].srcAccessMask = vk::AccessFlagBits::eHostWrite;
|
||||
barriers[2].dstAccessMask = vk::AccessFlagBits::eUniformRead;
|
||||
barriers[2].buffer = m_stream_buffer.Handle();
|
||||
barriers[2].offset = dispatches.front().params_offset;
|
||||
barriers[2].size =
|
||||
dispatches.back().params_offset - dispatches.front().params_offset + sizeof(Push);
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer, {}, 0,
|
||||
nullptr, 3, barriers, 0, nullptr);
|
||||
if (clear_target) {
|
||||
command.fillBuffer(target, target_offset, target_capacity, 0);
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barriers[1].dstAccessMask =
|
||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 1,
|
||||
&barriers[1], 0, nullptr);
|
||||
}
|
||||
|
||||
const vk::DescriptorBufferInfo source_info {source, source_descriptor_offset, source_range};
|
||||
const vk::DescriptorBufferInfo target_info {target, target_descriptor_offset, target_range};
|
||||
for (auto& dispatch: dispatches) {
|
||||
const vk::DescriptorBufferInfo params_info {m_stream_buffer.Handle(),
|
||||
dispatch.params_offset, sizeof(Push)};
|
||||
const vk::DescriptorBufferInfo infos[] {source_info, target_info, params_info};
|
||||
std::array<vk::WriteDescriptorSet, 3> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = index == 2 ? vk::DescriptorType::eUniformBuffer
|
||||
: vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, GetPipeline(dispatch.pipeline_slot));
|
||||
command.dispatch((dispatch.push.width + 7u) / 8u, (dispatch.push.height + 7u) / 8u,
|
||||
dispatch.push.depth);
|
||||
}
|
||||
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eMemoryRead;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllCommands, {}, 0, nullptr, 1,
|
||||
&barriers[1], 0, nullptr);
|
||||
}
|
||||
|
||||
TileManager::Result TileManager::Detile(vk::Buffer tiled, uint64_t tiled_offset,
|
||||
uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos) {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t source_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
Prepare(false, tiled_capacity, linear_capacity, infos, source_base, 0, dispatches);
|
||||
auto scratch = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
|
||||
DeferDestroy(scratch);
|
||||
Record(false, tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
|
||||
true);
|
||||
return {scratch.buffer, 0, linear_capacity};
|
||||
}
|
||||
|
||||
void TileManager::Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linear_capacity,
|
||||
vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
std::span<const GpuTileInfo> infos) {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t source_base = linear_offset & (descriptor_alignment - 1);
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
Prepare(true, tiled_capacity, linear_capacity, infos, source_base, target_base, dispatches);
|
||||
Record(true, linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
dispatches, false);
|
||||
}
|
||||
|
||||
void TileManager::TileImage(Image& image, std::span<const vk::BufferImageCopy> regions,
|
||||
vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
|
||||
ColorTransform transform) {
|
||||
EXIT_IF(regions.empty());
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const uint64_t descriptor_alignment =
|
||||
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
// Reserve all stream parameters before creating a scheduler-lived scratch dependency:
|
||||
// StreamBuffer::Map is allowed to submit the current tick when it wraps.
|
||||
Prepare(true, tiled_capacity, linear_capacity, infos, 0, target_base, dispatches);
|
||||
auto linear = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
|
||||
DeferDestroy(linear);
|
||||
image.Download(regions, linear.buffer, 0, linear.size);
|
||||
Result source {linear.buffer, 0, linear.size};
|
||||
if (transform == ColorTransform::SwapBgra16) {
|
||||
source = SwapBgra16(source);
|
||||
}
|
||||
Record(true, source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
dispatches, false);
|
||||
}
|
||||
|
||||
TileManager::Result TileManager::GetScratchBuffer(uint64_t size) {
|
||||
auto scratch = AllocateScratch((size + 3u) & ~uint64_t {3});
|
||||
DeferDestroy(scratch);
|
||||
return {scratch.buffer, 0, scratch.size};
|
||||
}
|
||||
|
||||
TileManager::StorageBinding TileManager::BindStorage(Result buffer, uint64_t size) const {
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const auto alignment = std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const auto descriptor_offset = buffer.offset - buffer.offset % alignment;
|
||||
const auto base = buffer.offset - descriptor_offset;
|
||||
EXIT_IF(buffer.buffer == nullptr || size == 0 || buffer.size < size || base > UINT32_MAX ||
|
||||
size > UINT64_MAX - base || base + size > UINT64_MAX - 3);
|
||||
const auto range = (base + size + 3) & ~uint64_t {3};
|
||||
EXIT_IF(range > limits.maxStorageBufferRange || range > UINT32_MAX);
|
||||
return {{buffer.buffer, descriptor_offset, range}, static_cast<uint32_t>(base)};
|
||||
}
|
||||
|
||||
uint32_t TileManager::ConversionRows(uint64_t offset, uint64_t row_stride, uint64_t active,
|
||||
uint32_t remaining, uint64_t alignment, uint64_t max_range,
|
||||
uint32_t max_groups) noexcept {
|
||||
if (row_stride == 0 || active == 0 || remaining == 0 || alignment == 0 || max_groups == 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto prefix = offset % alignment;
|
||||
if (prefix >= max_range || active > max_range - prefix) {
|
||||
return 0;
|
||||
}
|
||||
const auto descriptor_rows = 1 + (max_range - prefix - active) / row_stride;
|
||||
return static_cast<uint32_t>(std::min<uint64_t>({remaining, descriptor_rows, max_groups}));
|
||||
}
|
||||
|
||||
void TileManager::ConvertD16(Result source, Result target, D16Direction direction, bool d32,
|
||||
const D16Layout& layout) {
|
||||
vk::Pipeline* pipeline_pointer = nullptr;
|
||||
if (direction == D16Direction::Promote) {
|
||||
pipeline_pointer = d32 ? &m_d16_to_d32 : &m_d16_to_d24;
|
||||
} else {
|
||||
pipeline_pointer = d32 ? &m_d32_to_d16 : &m_d24_to_d16;
|
||||
}
|
||||
auto& pipeline = *pipeline_pointer;
|
||||
if (pipeline == nullptr) {
|
||||
const uint32_t value = d32 ? 1u : 0u;
|
||||
const vk::SpecializationMapEntry entry {0, 0, sizeof(value)};
|
||||
const vk::SpecializationInfo specialization {1, &entry, sizeof(value), &value};
|
||||
const uint32_t* code = nullptr;
|
||||
size_t words = 0;
|
||||
if (direction == D16Direction::Promote) {
|
||||
code = GPU_TILER_PROMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_PROMOTE_D16_SPV);
|
||||
} else {
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
|
||||
}
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = words * sizeof(uint32_t);
|
||||
module_info.pCode = code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create D16 conversion shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create D16 conversion pipeline");
|
||||
}
|
||||
|
||||
const uint64_t source_element =
|
||||
direction == D16Direction::Promote ? sizeof(uint16_t) : sizeof(uint32_t);
|
||||
const uint64_t target_element =
|
||||
direction == D16Direction::Promote ? sizeof(uint32_t) : sizeof(uint16_t);
|
||||
const uint64_t source_active = static_cast<uint64_t>(layout.width) * source_element;
|
||||
const uint64_t target_active = static_cast<uint64_t>(layout.width) * target_element;
|
||||
const auto required = [](uint32_t height, uint32_t layers, uint64_t row_stride,
|
||||
uint64_t slice_stride, uint64_t active) {
|
||||
EXIT_IF(height == 0 || layers == 0 || row_stride < active ||
|
||||
(height - 1) > (UINT64_MAX - active) / row_stride);
|
||||
const auto slice = static_cast<uint64_t>(height - 1) * row_stride + active;
|
||||
EXIT_IF(slice_stride < slice || (layers - 1) > (UINT64_MAX - slice) / slice_stride);
|
||||
return static_cast<uint64_t>(layers - 1) * slice_stride + slice;
|
||||
};
|
||||
EXIT_IF(layout.width == 0 || layout.source_row_stride > UINT32_MAX ||
|
||||
layout.target_row_stride > UINT32_MAX);
|
||||
const auto source_required = required(layout.height, layout.layers, layout.source_row_stride,
|
||||
layout.source_slice_stride, source_active);
|
||||
const auto target_required = required(layout.height, layout.layers, layout.target_row_stride,
|
||||
layout.target_slice_stride, target_active);
|
||||
EXIT_IF(source_required > UINT64_MAX - 3 || target_required > UINT64_MAX - 3);
|
||||
const auto source_barrier_size = (source_required + 3) & ~uint64_t {3};
|
||||
const auto target_barrier_size = (target_required + 3) & ~uint64_t {3};
|
||||
EXIT_IF(source.size < source_barrier_size || target.size < target_barrier_size);
|
||||
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::BufferMemoryBarrier barriers[2] {};
|
||||
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].buffer = source.buffer;
|
||||
barriers[0].offset = source.offset;
|
||||
barriers[0].size = source_barrier_size;
|
||||
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite |
|
||||
vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barriers[1].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[1].buffer = target.buffer;
|
||||
barriers[1].offset = target.offset;
|
||||
barriers[1].size = target_barrier_size;
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite |
|
||||
vk::AccessFlagBits::eHostWrite |
|
||||
vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline);
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
const auto descriptor_alignment = std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
|
||||
const auto rows_for = [&](Result buffer, uint64_t relative, uint64_t stride, uint64_t active,
|
||||
uint32_t remaining) {
|
||||
EXIT_IF(relative > buffer.size || buffer.offset > UINT64_MAX - relative);
|
||||
const auto offset = buffer.offset + relative;
|
||||
return ConversionRows(offset, stride, active, remaining, descriptor_alignment,
|
||||
limits.maxStorageBufferRange, limits.maxComputeWorkGroupCount[1]);
|
||||
};
|
||||
const auto groups_x = (static_cast<uint64_t>(layout.width) + 63u) / 64u;
|
||||
EXIT_IF(groups_x == 0 || groups_x > limits.maxComputeWorkGroupCount[0]);
|
||||
for (uint32_t layer = 0; layer < layout.layers; layer++) {
|
||||
for (uint32_t row = 0; row < layout.height;) {
|
||||
const auto source_relative =
|
||||
layout.source_slice_stride * layer + layout.source_row_stride * row;
|
||||
const auto target_relative =
|
||||
layout.target_slice_stride * layer + layout.target_row_stride * row;
|
||||
const auto remaining = layout.height - row;
|
||||
const auto rows = std::min(rows_for(source, source_relative, layout.source_row_stride,
|
||||
source_active, remaining),
|
||||
rows_for(target, target_relative, layout.target_row_stride,
|
||||
target_active, remaining));
|
||||
EXIT_IF(rows == 0);
|
||||
const auto source_span =
|
||||
static_cast<uint64_t>(rows - 1) * layout.source_row_stride + source_active;
|
||||
const auto target_span =
|
||||
static_cast<uint64_t>(rows - 1) * layout.target_row_stride + target_active;
|
||||
const auto source_binding = BindStorage(
|
||||
{source.buffer, source.offset + source_relative, source.size - source_relative},
|
||||
source_span);
|
||||
const auto target_binding = BindStorage(
|
||||
{target.buffer, target.offset + target_relative, target.size - target_relative},
|
||||
target_span);
|
||||
const vk::DescriptorBufferInfo infos[] {
|
||||
source_binding.info,
|
||||
target_binding.info,
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
Push push {};
|
||||
push.src_base = source_binding.base;
|
||||
push.dst_base = target_binding.base;
|
||||
push.width = layout.width;
|
||||
push.height = rows;
|
||||
push.pitch_bytes = static_cast<uint32_t>(layout.source_row_stride);
|
||||
push.slice_bytes = static_cast<uint32_t>(layout.target_row_stride);
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(push), &push);
|
||||
command.dispatch(static_cast<uint32_t>(groups_x), rows, 1);
|
||||
row += rows;
|
||||
}
|
||||
}
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eMemoryRead;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllCommands, {}, 0, nullptr, 1,
|
||||
&barriers[1], 0, nullptr);
|
||||
}
|
||||
|
||||
void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
|
||||
if (m_swap_bgra16 == nullptr) {
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = std::size(GPU_TILER_SWAP_BGRA16_SPV) * sizeof(uint32_t);
|
||||
module_info.pCode = GPU_TILER_SWAP_BGRA16_SPV;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create BGRA16 swap shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_swap_bgra16);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create BGRA16 swap pipeline");
|
||||
}
|
||||
const uint64_t bytes = static_cast<uint64_t>(pixels) * 8u;
|
||||
EXIT_IF(pixels == 0);
|
||||
const auto input_binding = BindStorage(input, bytes);
|
||||
const auto output_binding = BindStorage(output, bytes);
|
||||
|
||||
const vk::DescriptorBufferInfo infos[] {
|
||||
input_binding.info,
|
||||
output_binding.info,
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
vk::BufferMemoryBarrier barriers[2] {};
|
||||
for (uint32_t index = 0; index < 2; index++) {
|
||||
barriers[index].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[index].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[index].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[index].buffer = infos[index].buffer;
|
||||
barriers[index].offset = infos[index].offset;
|
||||
barriers[index].size = infos[index].range;
|
||||
}
|
||||
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryRead;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_swap_bgra16);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
Push push {};
|
||||
push.src_base = input_binding.base;
|
||||
push.dst_base = output_binding.base;
|
||||
push.width = pixels;
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
|
||||
&push);
|
||||
command.dispatch((pixels + 63u) / 64u, 1, 1);
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 1, &barriers[1],
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
TileManager::Result TileManager::SwapBgra16(Result input) {
|
||||
EXIT_NOT_IMPLEMENTED(input.size == 0 || input.size % 8u != 0 || input.size / 8u > UINT32_MAX);
|
||||
auto output = AllocateScratch(input.size);
|
||||
DeferDestroy(output);
|
||||
Result result {output.buffer, 0, output.size};
|
||||
SwapBgra16(input, result, static_cast<uint32_t>(input.size / 8u));
|
||||
return result;
|
||||
}
|
||||
|
||||
void TileManager::SwapBgra16(Result input, Result output) {
|
||||
EXIT_NOT_IMPLEMENTED(input.size == 0 || input.size % 8u != 0 || input.size / 8u > UINT32_MAX ||
|
||||
output.size < input.size);
|
||||
SwapBgra16(input, output, static_cast<uint32_t>(input.size / 8u));
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,149 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TILER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TILER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandScheduler;
|
||||
class Image;
|
||||
class StreamBuffer;
|
||||
struct GraphicContext;
|
||||
struct TileManagerTestAccess;
|
||||
|
||||
struct GpuTileInfo {
|
||||
TileBlockFamily family = TileBlockFamily::Count;
|
||||
uint32_t bytes_per_element = 0;
|
||||
uint64_t linear_offset = 0;
|
||||
uint64_t linear_size = 0;
|
||||
uint64_t tiled_offset = 0;
|
||||
uint64_t tiled_size = 0;
|
||||
uint64_t linear_slice_stride = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t depth = 1;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t tail_x = 0;
|
||||
uint32_t tail_y = 0;
|
||||
bool tail = false;
|
||||
uint32_t tiled_width = 0;
|
||||
uint32_t tiled_height = 0;
|
||||
uint32_t surface_z = 0;
|
||||
};
|
||||
|
||||
class TileManager final {
|
||||
public:
|
||||
enum class D16Direction { Promote, Demote };
|
||||
enum class ColorTransform { None, SwapBgra16 };
|
||||
|
||||
struct Result {
|
||||
vk::Buffer buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
struct D16Layout {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t layers = 0;
|
||||
uint64_t source_row_stride = 0;
|
||||
uint64_t target_row_stride = 0;
|
||||
uint64_t source_slice_stride = 0;
|
||||
uint64_t target_slice_stride = 0;
|
||||
};
|
||||
|
||||
TileManager(GraphicContext& graphics, CommandScheduler& scheduler, StreamBuffer& stream_buffer);
|
||||
~TileManager();
|
||||
KYTY_CLASS_NO_COPY(TileManager);
|
||||
|
||||
// The returned device-local buffer remains alive through the current scheduler tick.
|
||||
[[nodiscard]] Result Detile(vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
|
||||
uint64_t linear_capacity, std::span<const GpuTileInfo> infos);
|
||||
void Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linear_capacity, vk::Buffer tiled,
|
||||
uint64_t tiled_offset, uint64_t tiled_capacity, std::span<const GpuTileInfo> infos);
|
||||
void TileImage(Image& image, std::span<const vk::BufferImageCopy> regions, vk::Buffer tiled,
|
||||
uint64_t tiled_offset, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos,
|
||||
ColorTransform transform = ColorTransform::None);
|
||||
[[nodiscard]] Result GetScratchBuffer(uint64_t size);
|
||||
void ConvertD16(Result source, Result target, D16Direction direction, bool d32,
|
||||
const D16Layout& layout);
|
||||
[[nodiscard]] Result SwapBgra16(Result input);
|
||||
void SwapBgra16(Result input, Result output);
|
||||
|
||||
private:
|
||||
friend struct TileManagerTestAccess;
|
||||
|
||||
static constexpr uint32_t FamilyCount = static_cast<uint32_t>(TileBlockFamily::Count);
|
||||
static constexpr uint32_t BytesPerElementCount = 5;
|
||||
static constexpr uint32_t DirectionCount = 2;
|
||||
static constexpr uint32_t PipelineCount = FamilyCount * BytesPerElementCount * DirectionCount;
|
||||
|
||||
struct Push {
|
||||
uint32_t src_base;
|
||||
uint32_t dst_base;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t depth;
|
||||
uint32_t surface_z;
|
||||
uint32_t pitch_bytes;
|
||||
uint32_t slice_bytes;
|
||||
uint32_t blocks_per_row;
|
||||
uint32_t blocks_per_slice;
|
||||
uint32_t tail_x;
|
||||
uint32_t tail_y;
|
||||
uint32_t tail;
|
||||
};
|
||||
struct Dispatch {
|
||||
Push push {};
|
||||
uint32_t pipeline_slot = 0;
|
||||
uint64_t params_offset = 0;
|
||||
};
|
||||
struct Scratch {
|
||||
vk::Buffer buffer = nullptr;
|
||||
VmaAllocation allocation = nullptr;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
struct StorageBinding {
|
||||
vk::DescriptorBufferInfo info;
|
||||
uint32_t base = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] Scratch AllocateScratch(uint64_t size);
|
||||
[[nodiscard]] StorageBinding BindStorage(Result buffer, uint64_t size) const;
|
||||
[[nodiscard]] static uint32_t ConversionRows(uint64_t offset, uint64_t row_stride,
|
||||
uint64_t active, uint32_t remaining,
|
||||
uint64_t alignment, uint64_t max_range,
|
||||
uint32_t max_groups) noexcept;
|
||||
void DeferDestroy(Scratch scratch);
|
||||
void Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, uint64_t source_base, uint64_t target_base,
|
||||
std::vector<Dispatch>& dispatches);
|
||||
void Record(bool tile, vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
std::span<Dispatch> dispatches, bool clear_target);
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(uint32_t slot);
|
||||
void SwapBgra16(Result input, Result output, uint32_t pixels);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
StreamBuffer& m_stream_buffer;
|
||||
vk::DescriptorSetLayout m_descriptor_layout = nullptr;
|
||||
vk::PipelineLayout m_pipeline_layout = nullptr;
|
||||
std::array<vk::Pipeline, PipelineCount> m_pipelines {};
|
||||
vk::Pipeline m_d16_to_d24 = nullptr;
|
||||
vk::Pipeline m_d16_to_d32 = nullptr;
|
||||
vk::Pipeline m_d24_to_d16 = nullptr;
|
||||
vk::Pipeline m_d32_to_d16 = nullptr;
|
||||
vk::Pipeline m_swap_bgra16 = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TILER_H_
|
||||
Reference in new issue
Block a user