refactor renderer

This commit is contained in:
nmzik committed 2026-07-27 02:27:29 +02:00
1 parent a720b28b7f
commit a20cf5d298
112 files changed
+18269 -18368

No files matched your search

@@ -0,0 +1,231 @@
#include "graphics/host_gpu/renderer/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.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_
File diff suppressed because it is too large. Load diff
+87 -44
View File
@@ -6,44 +6,34 @@
#include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include <map>
#include <memory>
#include <mutex>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
struct GraphicContext;
struct VulkanBuffer;
class CommandBuffer;
class CommandScheduler;
class TextureCache;
class ResourceMutex;
struct BufferImageCopySource {
VulkanBuffer* buffer = nullptr;
uint64_t offset = 0;
uint64_t address = 0;
uint64_t size = 0;
bool cpu_current = false;
// True when the guest range was CPU-dirty before coherence resolution. ObtainBufferForImage
// may consume that tracker state while publishing the same bytes to a cached buffer.
bool cpu_dirty = false;
};
struct BufferCacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
struct BufferBinding {
std::shared_ptr<VulkanBuffer> buffer;
uint64_t offset = 0;
std::vector<uint8_t> host_data;
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
uint64_t offset = 0;
};
[[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange& merged,
BufferCacheRange candidate) noexcept;
struct ImageBufferSource {
Buffer* buffer = nullptr;
uint64_t offset = 0;
bool gpu_owned = false;
};
class BufferCache {
public:
@@ -52,7 +42,8 @@ public:
return vaddr & (CACHING_PAGE_SIZE - 1);
}
BufferCache(GraphicContext& graphics, PageManager& page_manager, ResourceMutex& resource_mutex);
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache, ResourceMutex& resource_mutex);
~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache);
@@ -62,39 +53,91 @@ public:
[[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool is_written = false, bool is_read = true,
bool is_formatted = false);
// Emulator-owned, CPU-current scratch only. Guest ranges must use ObtainBuffer so page
// ownership is resolved before any CPU access.
[[nodiscard]] bool UploadHostData(CommandBuffer& command, const void* src, uint64_t size,
uint64_t alignment, VulkanBuffer*& out_buffer,
uint64_t& out_offset, uint64_t& out_range);
[[nodiscard]] std::shared_ptr<VulkanBuffer> ObtainNullBuffer();
[[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size, uint32_t value);
void CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size);
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept;
[[nodiscard]] Buffer& GetGdsBuffer() noexcept { return m_gds_buffer; }
[[nodiscard]] const Buffer& GetGdsBuffer() const noexcept { return m_gds_buffer; }
[[nodiscard]] BufferBinding UploadTransient(const void* data, uint64_t size,
uint64_t alignment);
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer();
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
[[nodiscard]] std::pair<std::shared_ptr<Buffer>, uint64_t>
ObtainBufferForImageWrite(uint64_t vaddr, uint64_t size);
void DiscardGpuDirtyBytes(uint64_t vaddr, uint64_t size);
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
bool src_gds = false);
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
void PublishImageBacking(uint64_t vaddr, uint64_t size);
void InvalidateImageAliases(uint64_t vaddr, uint64_t size);
void BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
void CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
[[nodiscard]] bool SynchronizeBacking(uint64_t vaddr, uint64_t size);
void PublishImageBuffer(uint64_t vaddr, uint64_t size);
void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
void SetTextureCache(TextureCache& texture_cache);
void ResetNullBuffer();
void RunGarbageCollector();
private:
struct CachedBuffer;
struct ReadbackWorker;
friend struct BufferCacheTestAccess;
GraphicContext& m_graphics;
Common::Mutex m_mutex;
std::shared_ptr<VulkanBuffer> m_null_buffer;
// TODO: add LRU cache
struct CacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
struct CachedBuffer;
struct DownloadCopy;
struct DownloadRange;
struct RetiredBuffer;
struct FaultReadback;
struct PendingBackingPublication;
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
}
[[nodiscard]] static bool PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) noexcept;
[[nodiscard]] static std::pair<uint64_t, uint64_t>
DownloadEnvelope(const DownloadCopy& copy);
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
const void* source, uint64_t size);
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
uint64_t size);
[[nodiscard]] std::vector<DownloadRange>
RecordDownloads(std::span<const DownloadCopy> copies);
void PublishDownloads(std::span<const DownloadRange> downloads);
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
void RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached, uint64_t vaddr,
uint64_t size, bool upload);
void DiscardGpuDirtyBytesLocked(uint64_t vaddr, uint64_t size, const char* operation);
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
Buffer m_gds_buffer;
Common::Mutex m_mutex;
std::shared_ptr<Buffer> m_null_buffer;
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
std::unique_ptr<ReadbackWorker> m_readback;
std::unique_ptr<FaultReadback> m_fault_readback;
RangeSet m_gpu_modified_ranges;
RangeSet m_image_invalidated_ranges;
std::mutex m_publication_mutex;
std::vector<PendingBackingPublication> m_pending_backing_publications;
MemoryTracker m_memory_tracker;
StreamBuffer m_staging_buffer;
StreamBuffer m_stream_buffer;
StreamBuffer m_download_buffer;
StreamBuffer m_device_buffer;
PageManager& m_page_manager;
TextureCache* m_texture_cache = nullptr;
TextureCache& m_texture_cache;
ResourceMutex& m_resource_mutex;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024;
uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
};
} // namespace Libs::Graphics
@@ -10,12 +10,9 @@
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
@@ -25,9 +22,11 @@ namespace Libs::Graphics {
static std::atomic<uint32_t> g_render_color_log_count = 0;
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderColorInfo& r,
uint32_t render_target_slice_offset, uint32_t render_target_slot,
bool ignore_target_mask, bool reuse_existing_render_texture) {
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
bool exact_format) {
KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
@@ -57,14 +56,17 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
// No color output
r.type = RenderColorType::NoColorOutput;
r.desc = {};
r.base_addr = 0;
r.vulkan_buffer = nullptr;
r.vulkan_view = nullptr;
r.image_id = {};
r.image_view = nullptr;
r.format = vk::Format::eUndefined;
r.extent = {};
r.base_mip_level = 0;
r.base_array_layer = 0;
r.buffer_size = 0;
r.samples = 1;
r.export_mapping = {};
r.color_clear_enable = false;
r.color_clear_value = {};
return;
@@ -176,26 +178,33 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
pitch = width;
}
TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {};
if (tile) {
TileSizeAlign layout {};
bool valid_layout = false;
if (standard64) {
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
height, pitch, levels, rt.attrib3.tile_mode, &layout, nullptr,
nullptr);
height, pitch, levels, rt.attrib3.tile_mode, &layout, mip_sizes,
mip_padded);
valid_layout = layout.size != 0 && layout.align == 65536;
} else {
valid_layout =
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
layout, rt.attrib.num_fragments)
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
levels, layout, nullptr, nullptr);
levels, layout, mip_sizes, mip_padded);
}
if (!valid_layout) {
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
height, pitch, bytes_per_element, levels);
}
size = layout.size;
EXIT_IF(size > UINT32_MAX);
if (levels == 1) {
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (rt.slice.slice_div64_minus1 != 0 &&
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
@@ -204,6 +213,11 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
}
} else {
size = static_cast<uint64_t>(pitch) * height * bytes_per_element * samples;
if (size > UINT32_MAX) {
EXIT("linear render-target slice exceeds the supported layout size\n");
}
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (size == 0 || size > UINT64_MAX / view.image_layers) {
EXIT("render-target memory footprint is invalid\n");
@@ -213,119 +227,69 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
EXIT("render-target backing range is invalid\n");
}
auto video_image = Presentation::DisplayBufferFind(rt.base.addr, true);
if (video_image.image != nullptr &&
!IsSupportedDisplayRenderTargetTileMode(rt.attrib3.tile_mode)) {
EXIT("unsupported display render-target tile mode: tile=%u expected=%u addr=0x%010" PRIx64
" backing_size=0x%016" PRIx64 " video_size=0x%016" PRIx64 "\n",
rt.attrib3.tile_mode, Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget),
rt.base.addr, backing_size, video_image.size);
}
bool render_to_texture = view.base_layer != 0 || video_image.image == nullptr;
if (!render_to_texture && (levels != 1 || rt.view.current_mip_level != 0)) {
EXIT("mipmapped display render targets are unsupported\n");
}
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
std::max(height >> rt.view.current_mip_level, 1u)};
auto decision_log_id = g_render_color_log_count.fetch_add(1);
if (decision_log_id < 128 || !render_to_texture) {
if (decision_log_id < 128) {
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
" extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32
" samples=%u tile=%s target=%s video_size=0x%016" PRIx64 " video_pitch=%" PRIu64 "\n",
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
rt_slot, rt.base.addr, backing_size, width, height, rt.view.current_mip_level,
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear",
render_to_texture ? "RenderTexture" : "DisplayBuffer", video_image.size,
video_image.pitch);
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
}
if (render_to_texture) {
(void)reuse_existing_render_texture;
RenderTargetInfo target {};
target.address = rt.base.addr;
target.size = backing_size;
target.format = target_format.format;
target.width = width;
target.height = height;
target.pitch = pitch;
target.bytes_per_element = target_format.bytes_per_element;
target.tile_mode = rt.attrib3.tile_mode;
target.levels = levels;
target.layers = view.image_layers;
target.samples = samples;
auto& texture_cache = GetRenderContext().GetTextureCache();
auto& buffer_vulkan = texture_cache.FindRenderTarget(buffer, target);
r.type = RenderColorType::RenderTexture;
r.base_addr = rt.base.addr;
r.vulkan_buffer = &buffer_vulkan;
r.vulkan_view = texture_cache.GetRenderTargetAttachmentView(
buffer_vulkan, target.format, rt.view.current_mip_level, view.base_layer,
view.layer_count);
r.format = target.format;
r.extent = view_extent;
r.base_mip_level = rt.view.current_mip_level;
r.buffer_size = backing_size;
r.samples = samples;
r.export_mapping = target_format.export_mapping;
r.color_clear_enable = buffer_vulkan.initial_clear_pending;
r.color_clear_value = {};
} else {
if (samples != 1) {
EXIT("multisampled display render targets are unsupported\n");
}
const auto layout = static_cast<Prospero::ChannelLayout>(rt.info.format);
const auto type = static_cast<Prospero::ChannelType>(rt.info.channel_type);
const auto order = static_cast<Prospero::ChannelOrder>(rt.info.channel_order);
bool supported_display_format =
(layout == Prospero::ChannelLayout::k8_8_8_8 &&
(type == Prospero::ChannelType::kSrgb || type == Prospero::ChannelType::kUNorm) &&
(order == Prospero::ChannelOrder::kStandard ||
order == Prospero::ChannelOrder::kAlt)) ||
(layout == Prospero::ChannelLayout::k10_10_10_2 &&
type == Prospero::ChannelType::kUNorm &&
(order == Prospero::ChannelOrder::kStandard ||
order == Prospero::ChannelOrder::kAlt)) ||
(layout == Prospero::ChannelLayout::k16_16_16_16 &&
type == Prospero::ChannelType::kFloat &&
(order == Prospero::ChannelOrder::kStandard || order == Prospero::ChannelOrder::kAlt));
EXIT_NOT_IMPLEMENTED(!supported_display_format);
// Display buffer
if (video_image.size != size) {
LOGF("RenderColorTarget: display buffer size differs from render target span, "
"video_size=0x%016" PRIx64 " render_size=0x%016" PRIx64 "\n",
video_image.size, size);
}
EXIT_NOT_IMPLEMENTED(video_image.size < size);
EXIT_NOT_IMPLEMENTED(video_image.pitch != pitch);
r.type = RenderColorType::DisplayBuffer;
r.base_addr = rt.base.addr;
r.vulkan_buffer = video_image.image;
r.vulkan_view = video_image.image->image_view[VulkanImage::VIEW_DEFAULT];
r.format = video_image.image->format;
r.extent = video_image.image->extent;
r.base_mip_level = 0;
r.buffer_size = video_image.size;
r.samples = 1;
r.export_mapping = target_format.export_mapping;
}
}
void MarkRenderTargetGpuWritten(const RenderColorInfo& target) {
const bool with_color = target.vulkan_buffer != nullptr;
if (with_color) {
if (target.type == RenderColorType::RenderTexture ||
target.type == RenderColorType::DisplayBuffer) {
GetRenderContext().GetTextureCache().MarkGpuWritten(*target.vulkan_buffer);
} else {
EXIT("unknown writable render-color resource type\n");
}
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::RenderTarget;
desc.info.data = {rt.base.addr, backing_size};
desc.info.pixel_format = target_format.format;
desc.info.guest_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {levels, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes_per_element;
desc.info.samples = samples;
desc.info.tile_mode = rt.attrib3.tile_mode;
for (uint32_t level = 0; level < levels; level++) {
const auto level_offset =
mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
const auto level_size =
static_cast<uint64_t>(mip_sizes[level].src_size != 0 ? mip_sizes[level].src_size
: mip_sizes[level].size) *
view.image_layers;
desc.info.mip_layout[level] = {
level_offset,
level_size,
mip_padded[level].width,
mip_padded[level].height,
};
}
desc.view_info.format = target_format.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
desc.view_info.base_level = rt.view.current_mip_level;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
auto& texture_cache = m_context.GetTextureCache();
r.desc = std::move(desc);
r.image_id = texture_cache.FindImage(r.desc, exact_format);
r.type = RenderColorType::RenderTexture;
r.base_addr = rt.base.addr;
r.image_view = nullptr;
r.format = r.desc.view_info.format;
r.extent = view_extent;
r.base_mip_level = rt.view.current_mip_level;
r.buffer_size = backing_size;
r.samples = samples;
r.export_mapping = target_format.export_mapping;
r.color_clear_enable = false;
r.color_clear_value = {};
BindRenderTarget(r.image_id);
}
} // namespace Libs::Graphics
@@ -3,6 +3,7 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
@@ -10,18 +11,17 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
struct VulkanImage;
enum class RenderColorType {
NoColorOutput,
DisplayBuffer,
RenderTexture,
};
struct RenderColorInfo {
RenderColorType type = RenderColorType::NoColorOutput;
VulkanImage* vulkan_buffer = nullptr;
vk::ImageView vulkan_view = nullptr;
RenderColorType type = RenderColorType::NoColorOutput;
TextureCache::ImageDesc desc;
ImageId image_id;
vk::ImageView image_view = nullptr;
vk::Format format = vk::Format::eUndefined;
vk::Extent2D extent = {};
uint32_t base_mip_level = 0;
@@ -35,13 +35,6 @@ struct RenderColorInfo {
vk::ClearColorValue color_clear_value {};
};
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderColorInfo& r,
uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
bool ignore_target_mask = false,
bool reuse_existing_render_texture = false);
void MarkRenderTargetGpuWritten(const RenderColorInfo& target);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_
@@ -1,18 +1,161 @@
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include <algorithm>
namespace Libs::Graphics {
static thread_local CommandScheduler* g_deferred_callback_scheduler = nullptr;
void CommandSlot::Reset() {
EXIT_IF(buffer == nullptr);
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
if (result != vk::Result::eSuccess) {
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
}
CommandScheduler::CommandPool::~CommandPool() {
Destroy();
}
void CommandScheduler::CommandPool::Create(GraphicContext& graphics) {
EXIT_IF(m_pool != nullptr || m_graphics != nullptr ||
graphics.queue_family == static_cast<uint32_t>(-1));
m_graphics = &graphics;
vk::CommandPoolCreateInfo create {};
create.sType = vk::StructureType::eCommandPoolCreateInfo;
create.queueFamilyIndex = graphics.queue_family;
create.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
const auto result = graphics.device.createCommandPool(&create, nullptr, &m_pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
}
CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
EXIT_IF(m_graphics == nullptr);
auto& graphics = *m_graphics;
vk::CommandBufferAllocateInfo allocate {};
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
allocate.commandPool = m_pool;
allocate.level = vk::CommandBufferLevel::ePrimary;
allocate.commandBufferCount = 1;
vk::CommandBuffer buffer = nullptr;
EXIT_IF(graphics.device.allocateCommandBuffers(&allocate, &buffer) != vk::Result::eSuccess);
vk::FenceCreateInfo fence_create {};
fence_create.sType = vk::StructureType::eFenceCreateInfo;
fence_create.flags = vk::FenceCreateFlagBits::eSignaled;
vk::Fence fence = nullptr;
if (graphics.device.createFence(&fence_create, nullptr, &fence) != vk::Result::eSuccess) {
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
EXIT("failed to create command-buffer fence\n");
}
auto& slot = m_slots.emplace_back();
slot.pool_mutex = &m_mutex;
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
slot.buffer = buffer;
slot.fence = fence;
return &slot;
}
CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
Create(graphics);
}
EXIT_IF(m_graphics != &graphics);
auto found = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
auto* slot = found != m_slots.end() ? &*found : CreateSlot();
slot->busy = true;
slot->Reset();
return slot;
}
void CommandScheduler::CommandPool::Destroy() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
return;
}
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
EXIT_IF(m_graphics == nullptr);
for (const auto& slot: m_slots) {
m_graphics->device.destroyFence(slot.fence, nullptr);
}
m_graphics->device.destroyCommandPool(m_pool, nullptr);
m_slots.clear();
m_pool = nullptr;
m_graphics = nullptr;
}
bool CommandScheduler::InDeferredOperation() noexcept {
return g_deferred_callback_scheduler != nullptr;
}
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
: m_master(graphics), m_context(context), m_graphics(graphics),
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {}
CommandScheduler::~CommandScheduler() {
Shutdown();
}
void CommandScheduler::Shutdown() {
{
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Closed) {
return;
}
if (g_deferred_callback_scheduler == this) {
EXIT_IF(m_operation_state == OperationState::Open);
// A priority callback cannot join its own runner, while a normal callback can be
// executing inside the shutdown owner's final PopPendingOperations. The owning
// thread will finish shutdown after this callback returns.
return;
}
if (m_operation_state == OperationState::Draining) {
m_operation_available.wait(
lock, [this] { return m_operation_state == OperationState::Closed; });
return;
}
m_operation_state = OperationState::Draining;
}
if (Active() && m_recording) {
Finish();
}
DrainPriorityOperations();
m_priority_thread.request_stop();
m_operation_available.notify_all();
if (m_priority_thread.joinable()) {
m_priority_thread.join();
}
{
std::lock_guard lock(m_operation_mutex);
EXIT_IF(!m_pending_operations.empty() || !m_priority_operations.empty() ||
m_priority_active);
m_operation_state = OperationState::Closed;
}
m_operation_available.notify_all();
}
void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config,
HW::Shader& shaders) {
{
std::lock_guard lock(m_operation_mutex);
EXIT_IF(m_operation_state != OperationState::Open);
}
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
if (!Active()) {
for (auto& buffer: m_buffers) {
buffer = std::make_unique<RenderCommandBuffer>();
buffer = std::make_unique<RenderCommandBuffer>(*this);
}
m_current = 0;
}
@@ -24,37 +167,201 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
}
}
void CommandScheduler::BeginRendering(const RenderState& state) {
Current().BeginRendering(state);
}
void CommandScheduler::EndRendering() {
if (Active() && m_recording) {
Current().EndRendering();
}
}
void CommandScheduler::Flush() {
SubmitCurrent();
SubmitInfo submit;
Flush(submit);
}
void CommandScheduler::Flush(SubmitInfo& submit) {
SubmitCurrent(submit);
BeginNext();
}
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
auto& submitted = SubmitCurrent();
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
BeginNext();
return submitted;
}
void CommandScheduler::Finish() {
CheckActive();
const auto tick = CurrentTick();
if (m_recording) {
SubmitCurrent();
SubmitInfo submit;
SubmitCurrent(submit);
}
for (auto& buffer: m_buffers) {
buffer->WaitForFenceAndReset();
}
m_master.Wait(tick);
PopPendingOperations();
BindCurrent();
Current().Begin();
m_recording = true;
}
void CommandScheduler::FinishCurrent() {
auto& submitted = SubmitCurrent();
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
submitted.WaitForFenceAndReset();
m_master.Refresh();
PopPendingOperations();
submitted.Begin();
m_recording = true;
}
void CommandScheduler::Wait(uint64_t tick) {
CheckActive();
EXIT_IF(tick > CurrentTick());
if (tick >= CurrentTick()) {
// A stream-buffer wrap can wait while a draw is being prepared through a reference to
// Current(). Recycle the same command object so that reference remains valid.
FinishCurrent();
return;
}
m_master.Wait(tick);
PopPendingOperations();
}
void CommandScheduler::PopPendingOperations() {
m_master.Refresh();
for (;;) {
Common::UniqueFunction<void> callback;
{
std::lock_guard lock(m_operation_mutex);
if (m_pending_operations.empty() ||
!m_master.IsFree(m_pending_operations.front().tick)) {
return;
}
callback = std::move(m_pending_operations.front().callback);
m_pending_operations.pop();
}
RunOperation(std::move(callback));
}
}
void CommandScheduler::DeferOperation(Common::UniqueFunction<void>&& operation) {
CheckActive();
EXIT_IF(!operation);
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Open) {
m_pending_operations.push({std::move(operation), CurrentTick()});
return;
}
if (g_deferred_callback_scheduler == this) {
lock.unlock();
operation();
return;
}
m_operation_available.wait(lock,
[this] { return m_operation_state == OperationState::Closed; });
lock.unlock();
operation();
}
void CommandScheduler::DeferPriorityOperation(Common::UniqueFunction<void>&& operation) {
CheckActive();
EXIT_IF(!operation);
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Open) {
m_priority_operations.push({std::move(operation), CurrentTick()});
lock.unlock();
m_operation_available.notify_one();
return;
}
if (g_deferred_callback_scheduler == this) {
lock.unlock();
operation();
return;
}
m_operation_available.wait(lock,
[this] { return m_operation_state == OperationState::Closed; });
lock.unlock();
operation();
}
void CommandScheduler::PriorityOperationsThread(std::stop_token stop) {
while (!stop.stop_requested()) {
PendingOperation operation;
{
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(lock, [this, &stop] {
return stop.stop_requested() || !m_priority_operations.empty();
});
if (stop.stop_requested()) {
return;
}
operation = std::move(m_priority_operations.front());
m_priority_operations.pop();
m_priority_active = true;
m_priority_active_tick = operation.tick;
}
m_master.Wait(operation.tick);
if (!stop.stop_requested()) {
RunOperation(std::move(operation.callback));
}
{
std::lock_guard lock(m_operation_mutex);
m_priority_active = false;
m_priority_active_tick = 0;
}
m_operation_available.notify_all();
}
}
void CommandScheduler::DrainPriorityOperations() {
EXIT_IF(g_deferred_callback_scheduler == this);
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(
lock, [this] { return m_priority_operations.empty() && !m_priority_active; });
}
void CommandScheduler::WaitPriorityOperations(uint64_t tick) {
EXIT_IF(g_deferred_callback_scheduler == this);
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(lock, [this, tick] {
const bool active_before_or_at =
m_priority_active && m_priority_active_tick <= tick;
const bool queued_before_or_at =
!m_priority_operations.empty() && m_priority_operations.front().tick <= tick;
return !active_before_or_at && !queued_before_or_at;
});
}
void CommandScheduler::RunOperation(Common::UniqueFunction<void>&& operation) {
auto* previous = g_deferred_callback_scheduler;
g_deferred_callback_scheduler = this;
operation();
g_deferred_callback_scheduler = previous;
}
bool CommandScheduler::IsFree(uint64_t tick) {
if (m_master.IsFree(tick)) {
return true;
}
m_master.Refresh();
return m_master.IsFree(tick);
}
CommandSlot* CommandScheduler::AllocateCommandBuffer() {
return m_command_pool.Allocate(m_graphics);
}
uint64_t CommandScheduler::NextSubmitSequence() noexcept {
return m_submit_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
}
void CommandScheduler::CheckActive() const {
EXIT_IF(!Active() || m_current >= BufferCount);
}
@@ -70,12 +377,14 @@ void CommandScheduler::BindCurrent() const {
Current().Bind(*m_registers, *m_user_config, *m_shaders);
}
CommandBuffer& CommandScheduler::SubmitCurrent() {
CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
CheckActive();
EXIT_IF(!m_recording);
auto& submitted = Current();
submitted.End();
submitted.Execute();
const auto signal_tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), signal_tick);
submitted.Execute(submit);
m_recording = false;
return submitted;
}
@@ -84,6 +393,7 @@ void CommandScheduler::BeginNext() {
EXIT_IF(m_recording);
m_current = (m_current + 1) % BufferCount;
Current().WaitForFenceAndReset();
PopPendingOperations();
BindCurrent();
Current().Begin();
m_recording = true;
@@ -2,42 +2,124 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
#include "common/common.h"
#include "common/uniqueFunction.h"
#include "graphics/host_gpu/renderer/masterSemaphore.h"
#include "graphics/host_gpu/renderer/render.h"
#include <array>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
namespace Libs::Graphics {
struct CommandSlot {
Common::Mutex* pool_mutex = nullptr;
uint32_t id = 0;
vk::CommandBuffer buffer = nullptr;
vk::Fence fence = nullptr;
bool busy = false;
void Reset();
};
class CommandScheduler {
public:
static constexpr int BufferCount = 8;
CommandScheduler() = default;
~CommandScheduler() = default;
CommandScheduler(RenderContext& context, GraphicContext& graphics);
~CommandScheduler();
KYTY_CLASS_NO_COPY(CommandScheduler);
void Begin(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders);
void BeginRendering(const RenderState& state);
void EndRendering();
void Flush();
void Flush(SubmitInfo& submit);
CommandBuffer& FlushAndGetSubmitted();
void Finish();
void FinishCurrent();
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
// the priority runner cannot join itself.
void Shutdown();
void Wait(uint64_t tick);
void PopPendingOperations();
void DrainPriorityOperations();
void WaitPriorityOperations(uint64_t tick);
void DeferOperation(Common::UniqueFunction<void>&& operation);
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] static bool InDeferredOperation() noexcept;
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
[[nodiscard]] bool IsFree(uint64_t tick);
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
private:
void BindCurrent() const;
CommandBuffer& SubmitCurrent();
void BeginNext();
class CommandPool {
public:
CommandPool() = default;
~CommandPool();
KYTY_CLASS_NO_COPY(CommandPool);
CommandSlot* Allocate(GraphicContext& graphics);
private:
void Create(GraphicContext& graphics);
CommandSlot* CreateSlot();
void Destroy();
GraphicContext* m_graphics = nullptr;
Common::Mutex m_mutex;
vk::CommandPool m_pool = nullptr;
std::deque<CommandSlot> m_slots;
};
enum class OperationState { Open, Draining, Closed };
struct PendingOperation {
Common::UniqueFunction<void> callback;
uint64_t tick = 0;
};
void BindCurrent() const;
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
void BeginNext();
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
std::atomic<uint64_t> m_submit_sequence = 0;
friend class CommandBuffer;
};
} // namespace Libs::Graphics
+87 -291
View File
@@ -9,63 +9,17 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <atomic>
#include <bit>
#include <cstring>
#include <deque>
#include <memory>
namespace Libs::Graphics {
static std::atomic<uint64_t> g_command_buffer_submit_seq = 0;
static void ResetNativeCommandBuffer(vk::CommandBuffer buffer) {
EXIT_IF(buffer == nullptr);
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
if (result != vk::Result::eSuccess) {
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
}
struct CommandSlot {
Common::Mutex* pool_mutex = nullptr;
uint32_t id = 0;
vk::CommandBuffer buffer = nullptr;
vk::Fence fence = nullptr;
bool busy = false;
};
class ThreadCommandPool {
public:
ThreadCommandPool() = default;
KYTY_CLASS_NO_COPY(ThreadCommandPool);
CommandSlot* Allocate();
void Destroy();
private:
void Create();
CommandSlot* CreateSlot();
Common::Mutex m_mutex;
vk::CommandPool m_pool = nullptr;
std::deque<CommandSlot> m_slots;
};
static RenderContext* g_render_ctx = nullptr;
static thread_local ThreadCommandPool g_command_pool;
RenderContext& GetRenderContext() noexcept {
return *g_render_ctx;
}
FenceResourceRetainer::~FenceResourceRetainer() {
if (!m_resources.empty()) {
@@ -88,93 +42,14 @@ void FenceResourceRetainer::ReleaseAfterFence() noexcept {
m_resources.clear();
}
void GraphicsRenderInit(GraphicContext& graphics) {
g_render_ctx = new RenderContext(graphics);
}
void GraphicsRenderReleaseThreadCommandPool() {
g_command_pool.Destroy();
}
CommandBuffer::CommandBuffer()
: m_graphics(GetRenderContext().GetGraphics()), m_slot(g_command_pool.Allocate()),
m_host_stream(m_graphics) {}
CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
m_slot(scheduler.AllocateCommandBuffer()) {}
CommandBuffer::~CommandBuffer() {
Release();
}
void ThreadCommandPool::Create() {
auto& graphics = GetRenderContext().GetGraphics();
EXIT_IF(m_pool != nullptr || graphics.queue_family == static_cast<uint32_t>(-1));
vk::CommandPoolCreateInfo pool_info {};
pool_info.sType = vk::StructureType::eCommandPoolCreateInfo;
pool_info.pNext = nullptr;
pool_info.queueFamilyIndex = graphics.queue_family;
pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
const auto result = graphics.device.createCommandPool(&pool_info, nullptr, &m_pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
}
CommandSlot* ThreadCommandPool::CreateSlot() {
auto& graphics = GetRenderContext().GetGraphics();
vk::CommandBufferAllocateInfo alloc_info {};
alloc_info.sType = vk::StructureType::eCommandBufferAllocateInfo;
alloc_info.commandPool = m_pool;
alloc_info.level = vk::CommandBufferLevel::ePrimary;
alloc_info.commandBufferCount = 1;
vk::CommandBuffer buffer = nullptr;
if (graphics.device.allocateCommandBuffers(&alloc_info, &buffer) != vk::Result::eSuccess) {
EXIT("Can't allocate command buffers");
}
vk::FenceCreateInfo fence_info {};
fence_info.sType = vk::StructureType::eFenceCreateInfo;
fence_info.flags = vk::FenceCreateFlagBits::eSignaled;
vk::Fence fence = nullptr;
if (graphics.device.createFence(&fence_info, nullptr, &fence) != vk::Result::eSuccess) {
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
EXIT("Can't create fence");
}
auto& slot = m_slots.emplace_back();
slot.pool_mutex = &m_mutex;
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
slot.buffer = buffer;
slot.fence = fence;
return &slot;
}
CommandSlot* ThreadCommandPool::Allocate() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
Create();
}
auto it = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
auto* slot = it != m_slots.end() ? &*it : CreateSlot();
slot->busy = true;
ResetNativeCommandBuffer(slot->buffer);
return slot;
}
void ThreadCommandPool::Destroy() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
return;
}
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
auto& graphics = GetRenderContext().GetGraphics();
for (const auto& slot: m_slots) {
graphics.device.destroyFence(slot.fence, nullptr);
}
graphics.device.destroyCommandPool(m_pool, nullptr);
m_slots.clear();
m_pool = nullptr;
}
bool CommandBuffer::IsInvalid() const {
return m_slot == nullptr;
}
@@ -194,10 +69,8 @@ void CommandBuffer::Release() {
WaitForFence();
m_host_stream.Release();
m_slot->busy = false;
ResetNativeCommandBuffer(m_slot->buffer);
m_slot->Reset();
ReleaseResourcesAfterFence();
m_slot = nullptr;
@@ -224,12 +97,13 @@ void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
void CommandBuffer::RecycleDescriptorsAfterFence() {
for (auto* set: m_descriptor_sets_after_fence) {
GetRenderContext().GetDescriptorCache().Recycle(*set);
m_context.GetDescriptorCache().Recycle(*set);
}
m_descriptor_sets_after_fence.clear();
}
void CommandBuffer::Begin() const {
EXIT_IF(m_rendering);
auto buffer = Handle();
vk::CommandBufferBeginInfo begin_info {};
@@ -244,6 +118,7 @@ void CommandBuffer::Begin() const {
}
void CommandBuffer::End() const {
EndRendering();
auto buffer = Handle();
auto result = buffer.end();
@@ -262,39 +137,34 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
m_debug_arg4 = arg4;
}
void CommandBuffer::Execute() {
Submit(nullptr, {}, nullptr);
}
void CommandBuffer::ExecuteWithSemaphore(vk::Semaphore wait_semaphore,
vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore) {
EXIT_IF(wait_semaphore == nullptr || signal_semaphore == nullptr);
Submit(wait_semaphore, wait_stage, signal_semaphore);
}
void CommandBuffer::Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore) {
void CommandBuffer::Execute(const SubmitInfo& submit) {
EXIT_IF(IsInvalid());
EXIT_IF(m_execute);
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
submit.num_signal_semaphores > SubmitInfo::MaxSemaphores);
const bool has_wait = wait_semaphore != nullptr;
const bool has_signal = signal_semaphore != nullptr;
auto buffer = Handle();
auto fence = m_slot->fence;
auto buffer = Handle();
auto fence = m_slot->fence;
vk::TimelineSemaphoreSubmitInfo timeline_info {};
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
vk::SubmitInfo submit_info {};
submit_info.sType = vk::StructureType::eSubmitInfo;
submit_info.pNext = nullptr;
submit_info.waitSemaphoreCount = has_wait ? 1u : 0u;
submit_info.pWaitSemaphores = has_wait ? &wait_semaphore : nullptr;
submit_info.pWaitDstStageMask = has_wait ? &wait_stage : nullptr;
submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
submit_info.pWaitDstStageMask = submit.wait_stages.data();
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = has_signal ? 1u : 0u;
submit_info.pSignalSemaphores = has_signal ? &signal_semaphore : nullptr;
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
auto& graphics = GetRenderContext().GetGraphics();
auto& graphics = m_graphics;
EXIT_IF(graphics.queue == nullptr);
auto result = graphics.device.resetFences(1, &fence);
@@ -305,16 +175,16 @@ void CommandBuffer::Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
if (Config::GraphicsDebugDumpEnabled()) {
LOGF("vkQueueSubmit begin: slot=%u wait_semaphore=%p signal_semaphore=%p"
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_slot->id, static_cast<void*>(wait_semaphore), static_cast<void*>(signal_semaphore),
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
LOGF("vkQueueSubmit begin: slot=%u waits=%u signals=%u debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_slot->id, submit.num_wait_semaphores, submit.num_signal_semaphores, m_debug_op,
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
{
Common::LockGuard lock(graphics.queue_mutex);
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
m_submit_seq = m_scheduler.NextSubmitSequence();
result = graphics.queue.submit(1, &submit_info, fence);
}
@@ -340,7 +210,7 @@ void CommandBuffer::WaitForFenceOnly() {
if (!m_execute || m_fence_waited) {
return;
}
auto device = GetRenderContext().GetGraphics().device;
auto device = m_graphics.device;
auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
if (result != vk::Result::eSuccess) {
LOGF("vkWaitForFences failed: %s (%d), slot=%u submit_seq=%" PRIu64
@@ -364,12 +234,10 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
m_execute = false;
m_fence_waited = false;
if (reset_recording) {
ResetNativeCommandBuffer(m_slot->buffer);
Common::LockGuard lock(*m_slot->pool_mutex);
m_slot->Reset();
}
}
if (reset_recording) {
m_host_stream.Reset();
}
if (was_executed) {
ReleaseResourcesAfterFence();
}
@@ -383,142 +251,70 @@ void CommandBuffer::ReleaseResourcesAfterFence() {
void CommandBuffer::DeleteBuffersAfterFence() {
for (const auto& buffer: m_retired_buffers) {
GetRenderContext().GetGraphics().DeleteBuffer(*buffer);
m_graphics.DeleteBuffer(*buffer);
}
m_retired_buffers.clear();
}
void CommandBuffer::BeginRenderPass(VulkanFramebuffer& framebuffer, RenderColorInfo* colors,
uint32_t requested_color_count, RenderDepthInfo& depth) const {
auto buffer = Handle();
EXIT_IF(colors == nullptr);
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
bool with_depth = (depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
uint32_t color_count = 0;
for (uint32_t i = 0; i < requested_color_count; i++) {
if (colors[i].vulkan_buffer == nullptr) {
break;
}
color_count++;
void CommandBuffer::BeginRendering(const RenderState& state) const {
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
if (m_rendering && m_render_state == state) {
return;
}
bool with_color = (color_count != 0);
EndRendering();
EXIT_NOT_IMPLEMENTED(!with_depth && !with_color);
vk::ClearValue clears[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
clears[i].color = colors[i].color_clear_value;
}
clears[color_count].depthStencil = {depth.depth_clear_value, depth.stencil_clear_value};
vk::Extent2D extent = (with_color ? colors[0].extent : depth.vulkan_buffer->extent);
vk::RenderPassBeginInfo render_pass_info {};
render_pass_info.sType = vk::StructureType::eRenderPassBeginInfo;
render_pass_info.pNext = nullptr;
render_pass_info.renderPass = framebuffer.render_pass;
render_pass_info.framebuffer = framebuffer.framebuffer;
render_pass_info.renderArea.offset = {0, 0};
render_pass_info.renderArea.extent = extent;
render_pass_info.clearValueCount = color_count + (with_depth ? 1u : 0u);
render_pass_info.pClearValues = clears;
for (uint32_t i = 0; i < color_count; i++) {
const auto color_initial_layout = framebuffer.color_layout[i];
if (colors[i].vulkan_buffer->layout != color_initial_layout) {
if (graphics_debug_dump_enabled()) {
LOGF("BeginRenderPass: color%u initial barrier image=%p mem=%" PRIu64 " %s -> %s\n",
i, VulkanHandleToPointer(colors[i].vulkan_buffer->image),
colors[i].vulkan_buffer->memory.unique_id,
VulkanToString(colors[i].vulkan_buffer->layout).c_str(),
VulkanToString(color_initial_layout).c_str());
}
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = {};
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead |
vk::AccessFlagBits::eColorAttachmentWrite;
image_memory_barrier.oldLayout = colors[i].vulkan_buffer->layout;
image_memory_barrier.newLayout = color_initial_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = colors[i].vulkan_buffer->image;
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = colors[i].vulkan_buffer->layers;
buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
&image_memory_barrier);
colors[i].vulkan_buffer->layout = image_memory_barrier.newLayout;
} else if (graphics_debug_dump_enabled()) {
LOGF("BeginRenderPass: color%u initial image=%p mem=%" PRIu64 " layout=%s\n", i,
VulkanHandleToPointer(colors[i].vulkan_buffer->image),
colors[i].vulkan_buffer->memory.unique_id,
VulkanToString(colors[i].vulkan_buffer->layout).c_str());
}
std::array<vk::RenderingAttachmentInfo, RENDER_COLOR_ATTACHMENTS_MAX> colors {};
for (uint32_t i = 0; i < state.num_color_attachments; i++) {
const auto& attachment = state.color_attachments[i];
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
colors[i].imageView = attachment.image_view;
colors[i].imageLayout = attachment.image_layout;
colors[i].loadOp = attachment.is_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
colors[i].clearValue.color.uint32 = attachment.clear_value;
}
const auto depth_layout =
with_depth ? framebuffer.depth_layout : vk::ImageLayout::eDepthStencilAttachmentOptimal;
const auto& depth_stencil = state.depth_stencil_attachment;
vk::RenderingAttachmentInfo depth {};
depth.sType = vk::StructureType::eRenderingAttachmentInfo;
depth.imageView = depth_stencil.image_view;
depth.imageLayout = depth_stencil.image_layout;
depth.loadOp = depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
depth.storeOp = vk::AttachmentStoreOp::eStore;
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
if (with_depth && depth.vulkan_buffer->layout != depth_layout) {
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
image_memory_barrier.dstAccessMask =
(depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal
? vk::AccessFlagBits::eMemoryRead
: vk::AccessFlagBits::eMemoryWrite);
image_memory_barrier.oldLayout = depth.vulkan_buffer->layout;
image_memory_barrier.newLayout = depth_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = depth.vulkan_buffer->image;
image_memory_barrier.subresourceRange.aspectMask =
ImageViewOps::DepthAspectMask(depth.vulkan_buffer->format);
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = depth.vulkan_buffer->layers;
vk::RenderingAttachmentInfo stencil {};
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
stencil.imageView = depth_stencil.image_view;
stencil.imageLayout = depth_stencil.image_layout;
stencil.loadOp = depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
stencil.storeOp = vk::AttachmentStoreOp::eStore;
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
depth.vulkan_buffer->layout = image_memory_barrier.newLayout;
}
buffer.beginRenderPass(&render_pass_info, vk::SubpassContents::eInline);
for (uint32_t i = 0; i < color_count; i++) {
colors[i].vulkan_buffer->layout = RENDER_COLOR_IMAGE_LAYOUT;
if (colors[i].vulkan_buffer->type == VulkanImageType::RenderTexture) {
static_cast<RenderTextureVulkanImage*>(colors[i].vulkan_buffer)->initial_clear_pending =
false;
}
}
if (with_depth) {
depth.vulkan_buffer->initial_depth_clear_pending = false;
depth.vulkan_buffer->initial_stencil_clear_pending = false;
}
vk::RenderingInfo rendering {};
rendering.sType = vk::StructureType::eRenderingInfo;
rendering.renderArea.extent = {state.width, state.height};
rendering.layerCount = state.num_layers;
rendering.colorAttachmentCount = state.num_color_attachments;
rendering.pColorAttachments = colors.data();
rendering.pDepthAttachment = depth_stencil.has_depth ? &depth : nullptr;
rendering.pStencilAttachment = depth_stencil.has_stencil ? &stencil : nullptr;
Handle().beginRendering(rendering);
m_render_state = state;
m_rendering = true;
}
void CommandBuffer::EndRenderPass() const {
auto buffer = Handle();
buffer.endRenderPass();
void CommandBuffer::EndRendering() const {
if (!m_rendering) {
return;
}
Handle().endRendering();
m_rendering = false;
m_render_state = {};
}
} // namespace Libs::Graphics
@@ -13,12 +13,10 @@
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
@@ -88,7 +86,8 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderDepthInfo& r) {
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
(void)submit_id;
const auto& hw = buffer.GetRegisters();
@@ -282,18 +281,9 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
auto& cache = GetRenderContext().GetTextureCache();
if (has_htile) {
cache.RegisterMeta(r.htile_buffer_vaddr, r.htile_buffer_size, view.image_layers);
}
if (has_htile && rc.depth_clear_enable && !cache.ClearMeta(z.htile_data_base_addr)) {
DepthFatal("failed to acquire HTile metadata for a depth clear");
}
const bool meta_clear =
has_htile && cache.IsMetaCleared(z.htile_data_base_addr, z.depth_view.slice_start);
r.depth_clear_enable = rc.depth_clear_enable;
r.depth_meta_clear_enable = meta_clear;
r.depth_load_clear_enable = r.depth_clear_enable || r.depth_meta_clear_enable;
r.depth_meta_clear_enable = false;
r.depth_load_clear_enable = r.depth_clear_enable;
r.depth_clear_value = hw.GetDepthClearValue();
r.depth_test_enable = dc.z_enable;
r.depth_write_enable = dc.z_write_enable && !z.depth_view.depth_write_disable;
@@ -343,55 +333,94 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
r.vaddr[1] = r.stencil_buffer_vaddr;
r.size[1] = r.stencil_buffer_size;
}
DepthTargetInfo info {};
info.address = r.depth_buffer_vaddr;
info.size = r.depth_buffer_size;
info.stencil_address = r.stencil_buffer_vaddr;
info.stencil_size = r.stencil_buffer_size;
info.htile_address = r.htile_buffer_vaddr;
info.htile_size = r.htile_buffer_size;
info.format = r.format;
info.guest_format = guest_format;
info.width = width;
info.height = height;
info.pitch = pitch;
info.bytes_per_element = bytes;
info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
info.layers = view.image_layers;
info.samples = samples;
info.depth_load_clear = r.depth_load_clear_enable;
info.depth_access = depth_active;
info.stencil_load_clear = rc.stencil_clear_enable;
info.stencil_access =
r.stencil_clear_enable ||
(r.stencil_test_enable &&
(stencil_face_accesses_attachment(r.stencil_static_front, r.stencil_dynamic_front) ||
stencil_face_accesses_attachment(r.stencil_static_back, r.stencil_dynamic_back)));
info.stencil_htile_compressed =
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::DepthTarget;
desc.info.data = {r.depth_buffer_vaddr, r.depth_buffer_size};
desc.info.stencil = {r.stencil_buffer_vaddr, r.stencil_buffer_size};
desc.info.pixel_format = r.format;
desc.info.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
desc.info.mip_layout[0] = {0, r.depth_buffer_size, pitch, height};
desc.info.metadata.range = {r.htile_buffer_vaddr, r.htile_buffer_size};
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.tile_stencil_disable;
r.vulkan_buffer = &cache.FindDepthTarget(buffer, info);
r.vulkan_view =
cache.GetDepthTargetAttachmentView(*r.vulkan_buffer, view.base_layer, view.layer_count);
if (r.vulkan_buffer->initial_depth_clear_pending) {
r.depth_load_clear_enable = true;
r.depth_clear_value = 0.0f;
}
if (r.vulkan_buffer->initial_stencil_clear_pending) {
r.stencil_clear_enable = true;
r.stencil_clear_value = 0;
}
if (meta_clear && !cache.TouchMeta(z.htile_data_base_addr, z.depth_view.slice_start, false)) {
DepthFatal("failed to consume HTile clear state");
}
desc.view_info.format = r.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = ImageViewOps::DepthAspectMask(r.format);
desc.view_info.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
r.desc = std::move(desc);
auto& cache = m_context.GetTextureCache();
r.image_id = cache.FindImage(r.desc);
r.image_view = nullptr;
BindRenderTarget(r.image_id);
}
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target) {
const bool with_depth =
target.format != vk::Format::eUndefined && target.vulkan_buffer != nullptr;
if (with_depth && !depth_attachment_read_only(target)) {
GetRenderContext().GetTextureCache().MarkGpuWritten(*target.vulkan_buffer);
vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
if (format == vk::Format::eUndefined) {
return {};
}
const auto available = ImageViewOps::DepthAspectMask(format);
vk::ImageAspectFlags writes {};
if ((available & vk::ImageAspectFlagBits::eDepth) &&
(depth_load_clear_enable || (depth_test_enable && depth_write_enable))) {
writes |= vk::ImageAspectFlagBits::eDepth;
}
if (!(available & vk::ImageAspectFlagBits::eStencil)) {
return writes;
}
const auto face_writes = [&](const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
if (dynamic.writeMask == 0) {
return false;
}
bool can_pass = state.compareOp != vk::CompareOp::eNever;
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
if (dynamic.compareMask == 0) {
switch (state.compareOp) {
case vk::CompareOp::eEqual:
case vk::CompareOp::eLessOrEqual:
case vk::CompareOp::eGreaterOrEqual:
case vk::CompareOp::eAlways:
can_pass = true;
can_fail = false;
break;
case vk::CompareOp::eNever:
case vk::CompareOp::eLess:
case vk::CompareOp::eGreater:
case vk::CompareOp::eNotEqual:
can_pass = false;
can_fail = true;
break;
default: break;
}
}
const bool depth_pass = !depth_test_enable || depth_compare_op != vk::CompareOp::eNever;
const bool depth_fail = depth_test_enable && depth_compare_op != vk::CompareOp::eAlways;
return (can_fail && state.failOp != vk::StencilOp::eKeep) ||
(can_pass && depth_pass && state.passOp != vk::StencilOp::eKeep) ||
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
};
if (stencil_clear_enable ||
(stencil_test_enable &&
(face_writes(stencil_static_front, stencil_dynamic_front) ||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
writes |= vk::ImageAspectFlagBits::eStencil;
}
return writes;
}
} // namespace Libs::Graphics
@@ -2,7 +2,9 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
@@ -10,7 +12,6 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
struct DepthStencilVulkanImage;
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
bool acceleration_disabled) {
@@ -18,6 +19,7 @@ inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stenc
}
struct RenderDepthInfo {
TextureCache::ImageDesc desc;
vk::Format format = vk::Format::eUndefined;
uint32_t width = 0;
uint32_t height = 0;
@@ -49,29 +51,46 @@ struct RenderDepthInfo {
PipelineStencilStaticState stencil_static_back;
PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back;
DepthStencilVulkanImage* vulkan_buffer = nullptr;
vk::ImageView vulkan_view = nullptr;
uint64_t vaddr[3] = {};
uint64_t size[3] = {};
int vaddr_num = 0;
ImageId image_id;
vk::ImageView image_view = nullptr;
uint64_t vaddr[3] = {};
uint64_t size[3] = {};
int vaddr_num = 0;
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
};
inline bool depth_attachment_read_only(const RenderDepthInfo& depth) {
const bool stencil_write =
depth.stencil_test_enable &&
(depth.stencil_dynamic_front.writeMask != 0 || depth.stencil_dynamic_back.writeMask != 0);
return !depth.depth_load_clear_enable && !depth.stencil_clear_enable &&
!depth.depth_write_enable && !stencil_write;
return !depth.AttachmentWriteAspects();
}
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
return depth_attachment_read_only(depth) ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
: vk::ImageLayout::eDepthStencilAttachmentOptimal;
const auto available = ImageViewOps::DepthAspectMask(depth.format);
const auto writes = depth.AttachmentWriteAspects();
const bool has_depth = static_cast<bool>(available & vk::ImageAspectFlagBits::eDepth);
const bool has_stencil = static_cast<bool>(available & vk::ImageAspectFlagBits::eStencil);
const bool depth_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eDepth);
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
if (!has_stencil) {
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
: vk::ImageLayout::eDepthReadOnlyOptimal;
}
if (!has_depth) {
return stencil_write ? vk::ImageLayout::eStencilAttachmentOptimal
: vk::ImageLayout::eStencilReadOnlyOptimal;
}
if (depth_write && stencil_write) {
return vk::ImageLayout::eDepthStencilAttachmentOptimal;
}
if (depth_write) {
return vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal;
}
if (stencil_write) {
return vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal;
}
return vk::ImageLayout::eDepthStencilReadOnlyOptimal;
}
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderDepthInfo& r);
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
@@ -2,7 +2,6 @@
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include <array>
@@ -76,31 +75,35 @@ std::vector<uint32_t> LayoutKey(DescriptorCache::Stage stage,
return key;
}
vk::ImageLayout SampledLayout(const VulkanImage& image) {
switch (image.layout) {
case vk::ImageLayout::eTransferDstOptimal:
case vk::ImageLayout::eTransferSrcOptimal:
case vk::ImageLayout::eColorAttachmentOptimal: return vk::ImageLayout::eGeneral;
default: return image.layout;
}
}
vk::DescriptorBufferInfo BufferInfo(const BufferView& view) {
EXIT_IF(view.buffer == nullptr || view.buffer->buffer == nullptr);
return {view.buffer->buffer, view.offset, view.range};
}
vk::DescriptorImageInfo MakeDescriptorImageInfo(const DescriptorCache::TextureBinding& texture,
vk::ImageLayout layout) {
EXIT_IF(texture.image == nullptr || texture.view < 0 || texture.view >= VulkanImage::VIEW_MAX);
const auto view = texture.image_view != nullptr ? texture.image_view
: texture.image->image_view[texture.view];
EXIT_IF(view == nullptr);
return {nullptr, view, layout};
EXIT_IF(view.buffer == nullptr);
return {view.buffer, view.offset, view.range};
}
} // namespace
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture) {
EXIT_IF(!texture.image_id || texture.image_view == nullptr ||
texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, texture.image_view, texture.layout};
}
DescriptorCache::~DescriptorCache() {
for (auto& [layout, sets]: m_free_sets_by_layout) {
(void)layout;
for (auto* set: sets) {
delete set;
}
}
for (const auto& [key, layout]: m_descriptor_set_layouts) {
(void)key;
m_graphics.device.destroyDescriptorSetLayout(layout, nullptr);
}
for (const auto& pool: m_pools) {
m_graphics.device.destroyDescriptorPool(pool.pool, nullptr);
}
}
vk::DescriptorSetLayout
DescriptorCache::GetDescriptorSetLayoutInternal(Stage stage,
const ShaderRecompiler::IR::Program& program) {
@@ -256,13 +259,9 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
}
break;
default: {
const auto layout = IsStorageImage(binding.kind) ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eUndefined;
for (const auto resource: binding.resources) {
const auto& texture = data.images.at(resource);
image_infos.push_back(MakeDescriptorImageInfo(
texture,
IsSampledImage(binding.kind) ? SampledLayout(*texture.image) : layout));
image_infos.push_back(MakeImageInfo(texture));
}
break;
}
@@ -6,6 +6,7 @@
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shaderBindings.h"
@@ -18,11 +19,14 @@
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct DescriptorValue;
struct ImageResource;
struct Program;
struct ResourceSnapshot;
}
} // namespace ShaderRecompiler::IR
class CommandBuffer;
struct DescriptorCacheTestAccess;
struct ShaderStageRuntime;
struct VulkanDescriptorSet {
@@ -32,11 +36,10 @@ struct VulkanDescriptorSet {
};
struct BufferView {
std::shared_ptr<VulkanBuffer> owner;
VulkanBuffer* buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
std::vector<uint8_t> host_data;
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
};
class DescriptorCache {
@@ -44,19 +47,10 @@ public:
enum class Stage { Unknown, Vertex, Pixel, Compute };
struct TextureBinding {
VulkanImage* image = nullptr;
int view = VulkanImage::VIEW_DEFAULT;
vk::ImageView image_view = nullptr;
std::shared_ptr<void> owner;
};
enum class TextureVariant : int {
Float2D = 0,
Uint2D,
FloatArray,
UintArray,
Float3D,
Uint3D,
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
};
struct NativeDescriptors {
@@ -70,20 +64,20 @@ public:
};
struct PreparedBindings {
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot;
NativeDescriptors resources;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
vk::ShaderStageFlags shader_stage;
Stage stage = Stage::Unknown;
bool committed = false;
NativeDescriptors resources;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
vk::ShaderStageFlags shader_stage;
Stage stage = Stage::Unknown;
bool committed = false;
};
explicit DescriptorCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~DescriptorCache() { KYTY_NOT_IMPLEMENTED; }
~DescriptorCache();
KYTY_CLASS_NO_COPY(DescriptorCache);
vk::DescriptorSetLayout GetDescriptorSetLayout(Stage stage,
@@ -93,11 +87,14 @@ public:
const NativeDescriptors& descriptors);
private:
friend struct DescriptorCacheTestAccess;
struct Pool {
vk::DescriptorPool pool = nullptr;
int next_free_pool = -1;
};
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture);
void CreatePool();
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
vk::DescriptorSetLayout
@@ -112,15 +109,6 @@ private:
std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts;
};
[[nodiscard]] DescriptorCache::PreparedBindings
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage);
void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void ActivateImageWrites(std::span<DescriptorCache::PreparedBindings*> bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
File diff suppressed because it is too large. Load diff
+3 -12
View File
@@ -2,6 +2,7 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shaderBindings.h"
@@ -12,14 +13,6 @@
namespace Libs::Graphics {
struct VulkanImage;
enum class VulkanImageType;
enum class ImageWriteActivation { Unsupported, Implicit, Explicit };
[[nodiscard]] ImageWriteActivation
ClassifyImageWriteActivation(VulkanImageType type) noexcept;
template <typename T>
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
static_assert(std::is_trivially_copyable_v<T>);
@@ -41,13 +34,11 @@ ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
Prospero::ImageType type, uint32_t base_layer, uint32_t image_layers);
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
const VulkanImage& image);
const Image& image);
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor);
[[nodiscard]] bool
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const VulkanImage& image);
void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size);
const ShaderTextureResource& descriptor, const Image& image);
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size);
@@ -1,50 +0,0 @@
#include "graphics/host_gpu/renderer/dummyTextureCache.h"
#include "common/assert.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/transfer.h"
#include <algorithm>
namespace Libs::Graphics {
namespace {
[[nodiscard]] constexpr size_t DummyTextureIndex(bool uint_format, bool image_3d) noexcept {
return (image_3d ? 2u : 0u) + (uint_format ? 1u : 0u);
}
} // namespace
DummyTextureCache::~DummyTextureCache() {
Common::LockGuard lock(m_mutex);
const auto populated = [](const auto& slots) {
return std::ranges::any_of(slots, [](const auto& slot) { return slot.image != nullptr; });
};
if (!populated(m_sampled) && !populated(m_storage)) {
return;
}
Transfer::WaitForQueueIdle();
const auto destroy = [](auto& slots) {
for (auto& slot: slots) {
if (slot.image != nullptr) {
ImageOps::Destroy(*slot.image);
slot.image = nullptr;
}
}
};
destroy(m_sampled);
destroy(m_storage);
}
VulkanImage& DummyTextureCache::Get(Usage usage, bool uint_format, bool image_3d) {
Common::LockGuard lock(m_mutex);
auto& slots = usage == Usage::Storage ? m_storage : m_sampled;
auto& slot = slots[DummyTextureIndex(uint_format, image_3d)];
if (slot.image == nullptr) {
slot.image = ImageOps::CreateDummyTexture(uint_format, image_3d, usage == Usage::Storage);
}
return *slot.image;
}
} // namespace Libs::Graphics
@@ -1,35 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
#include "common/abi.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include <array>
#include <cstdint>
namespace Libs::Graphics {
class DummyTextureCache final {
public:
enum class Usage : uint8_t { Sampled, Storage };
DummyTextureCache() = default;
~DummyTextureCache();
KYTY_CLASS_NO_COPY(DummyTextureCache);
[[nodiscard]] VulkanImage& Get(Usage usage, bool uint_format, bool image_3d);
private:
struct Slot {
GpuTextureVulkanImage* image = nullptr;
};
Common::Mutex m_mutex;
std::array<Slot, 4> m_sampled {};
std::array<Slot, 4> m_storage {};
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
@@ -1,357 +0,0 @@
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <atomic>
namespace Libs::Graphics {
VulkanFramebuffer* FramebufferCache::CreateFramebuffer(RenderColorInfo* colors,
uint32_t requested_color_count,
RenderDepthInfo& depth) {
KYTY_PROFILER_FUNCTION();
Common::LockGuard lock(m_mutex);
EXIT_IF(colors == nullptr);
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
bool with_depth = (depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
bool with_color[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
VulkanImage* first_color = nullptr;
vk::Extent2D first_color_extent = {};
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < requested_color_count; i++) {
with_color[i] = (colors[i].vulkan_buffer != nullptr);
if (!with_color[i]) {
break;
}
if (first_color == nullptr) {
first_color = colors[i].vulkan_buffer;
first_color_extent = colors[i].extent;
attachment_samples = colors[i].samples;
} else if (colors[i].extent.width != first_color_extent.width ||
colors[i].extent.height != first_color_extent.height) {
LOGF("Framebuffer: temporary: dropping mismatched MRT%u attachment color0=%ux%u "
"color%u=%ux%u\n",
i, first_color_extent.width, first_color_extent.height, i, colors[i].extent.width,
colors[i].extent.height);
with_color[i] = false;
break;
}
if (colors[i].samples != attachment_samples ||
colors[i].vulkan_buffer->samples != colors[i].samples) {
EXIT("Framebuffer: mismatched color attachment samples at slot %u, expected=%u "
"requested=%u image=%u\n",
i, attachment_samples, colors[i].samples, colors[i].vulkan_buffer->samples);
}
color_count++;
}
if (with_depth) {
if (depth.samples != depth.vulkan_buffer->samples) {
EXIT("Framebuffer: depth attachment sample identity mismatch, requested=%u image=%u\n",
depth.samples, depth.vulkan_buffer->samples);
}
if (attachment_samples == 0) {
attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) {
EXIT(
"Framebuffer: mixed color/depth sample counts are unsupported, color=%u depth=%u\n",
attachment_samples, depth.samples);
}
}
if (!with_depth && color_count == 0) {
LOGF("Framebuffer: warning: no color or depth attachment\n");
return nullptr;
}
if (vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
EXIT("Framebuffer: invalid attachment sample count %u\n", attachment_samples);
}
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (auto& layout: color_layout) {
layout = RENDER_COLOR_IMAGE_LAYOUT;
}
auto depth_layout = (with_depth ? depth_attachment_layout(depth)
: vk::ImageLayout::eDepthStencilAttachmentOptimal);
auto depth_read_only =
(with_depth && depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal);
if (with_depth && first_color != nullptr &&
(first_color_extent.width != depth.vulkan_buffer->extent.width ||
first_color_extent.height != depth.vulkan_buffer->extent.height)) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("Framebuffer: temporary: dropping mismatched PS5 depth attachment color=%ux%u "
"depth=%ux%u format=%s\n",
first_color_extent.width, first_color_extent.height,
depth.vulkan_buffer->extent.width, depth.vulkan_buffer->extent.height,
VulkanToString(depth.format).c_str());
}
depth.format = vk::Format::eUndefined;
depth.vulkan_buffer = nullptr;
depth.vulkan_view = nullptr;
depth.depth_test_enable = false;
depth.depth_write_enable = false;
depth.depth_bounds_test_enable = false;
depth.stencil_test_enable = false;
depth.depth_clear_enable = false;
depth.depth_load_clear_enable = false;
depth.stencil_clear_enable = false;
with_depth = false;
depth_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
depth_read_only = false;
}
for (auto& f: m_framebuffers) {
bool color_match = (f.framebuffer != nullptr);
for (uint32_t i = 0; color_match && i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
const uint64_t image_id =
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
color_match =
color_match && f.image_id[i] == image_id &&
f.color_view[i] ==
(i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr) &&
f.color_clear_enable[i] ==
(i < color_count && with_color[i] && colors[i].color_clear_enable) &&
f.color_layout[i] == color_layout[i];
}
if (color_match && f.depth_id == (with_depth ? depth.vulkan_buffer->memory.unique_id : 0) &&
f.depth_view == (with_depth ? depth.vulkan_view : nullptr) &&
f.depth_clear_enable == depth.depth_load_clear_enable &&
f.stencil_clear_enable == depth.stencil_clear_enable &&
f.depth_read_only == depth_read_only) {
return f.framebuffer;
}
}
EXIT_NOT_IMPLEMENTED(with_depth && first_color != nullptr &&
(first_color_extent.width != depth.vulkan_buffer->extent.width ||
first_color_extent.height != depth.vulkan_buffer->extent.height));
if (first_color == nullptr) {
first_color_extent = depth.vulkan_buffer->extent;
}
auto* framebuffer = new VulkanFramebuffer;
framebuffer->render_pass = nullptr;
framebuffer->framebuffer = nullptr;
framebuffer->samples = attachment_samples;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
framebuffer->color_layout[i] = color_layout[i];
}
framebuffer->depth_layout = depth_layout;
vk::AttachmentDescription attachments[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
attachments[i].flags = {};
attachments[i].format = colors[i].format;
attachments[i].samples = vulkan_sample_count(attachment_samples);
attachments[i].loadOp = (colors[i].color_clear_enable ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad);
attachments[i].storeOp = vk::AttachmentStoreOp::eStore;
attachments[i].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
attachments[i].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
attachments[i].initialLayout = color_layout[i];
attachments[i].finalLayout = RENDER_COLOR_IMAGE_LAYOUT;
}
const uint32_t depth_attachment = color_count;
attachments[depth_attachment].flags = {};
attachments[depth_attachment].format = depth.format;
attachments[depth_attachment].samples = vulkan_sample_count(attachment_samples);
attachments[depth_attachment].loadOp =
(depth.depth_load_clear_enable ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad);
attachments[depth_attachment].storeOp = vk::AttachmentStoreOp::eStore;
attachments[depth_attachment].stencilLoadOp =
(depth.stencil_clear_enable ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad);
attachments[depth_attachment].stencilStoreOp = vk::AttachmentStoreOp::eStore;
attachments[depth_attachment].initialLayout = depth_layout;
attachments[depth_attachment].finalLayout = depth_layout;
vk::AttachmentReference color_attachment_ref[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
color_attachment_ref[i].attachment = (i < color_count ? i : VK_ATTACHMENT_UNUSED);
color_attachment_ref[i].layout = RENDER_COLOR_IMAGE_LAYOUT;
}
vk::AttachmentReference depth_attachment_ref {};
depth_attachment_ref.attachment = depth_attachment;
depth_attachment_ref.layout = depth_layout;
vk::SubpassDescription subpass {};
subpass.flags = {};
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = nullptr;
subpass.colorAttachmentCount = color_count;
subpass.pColorAttachments = (color_count > 0 ? color_attachment_ref : nullptr);
subpass.pResolveAttachments = nullptr;
subpass.pDepthStencilAttachment = (with_depth ? &depth_attachment_ref : nullptr);
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = nullptr;
const auto attachment_stage_mask =
static_cast<vk::PipelineStageFlags>(vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests);
const auto attachment_access_mask = static_cast<vk::AccessFlags>(
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eDepthStencilAttachmentRead |
vk::AccessFlagBits::eDepthStencilAttachmentWrite);
vk::SubpassDependency dependencies[2] = {};
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eAllCommands;
dependencies[0].dstStageMask = attachment_stage_mask;
dependencies[0].srcAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
dependencies[0].dstAccessMask = attachment_access_mask;
dependencies[0].dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].srcStageMask = attachment_stage_mask;
dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eAllCommands;
dependencies[1].srcAccessMask = attachment_access_mask;
dependencies[1].dstAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
dependencies[1].dependencyFlags = vk::DependencyFlagBits::eByRegion;
vk::RenderPassCreateInfo render_pass_info {};
render_pass_info.sType = vk::StructureType::eRenderPassCreateInfo;
render_pass_info.pNext = nullptr;
render_pass_info.flags = {};
render_pass_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
render_pass_info.pAttachments = attachments;
render_pass_info.subpassCount = 1;
render_pass_info.pSubpasses = &subpass;
render_pass_info.dependencyCount = 2;
render_pass_info.pDependencies = dependencies;
auto result =
m_graphics.device.createRenderPass(&render_pass_info, nullptr, &framebuffer->render_pass);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
vk::Format color_formats[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
color_formats[i] = colors[i].format;
}
framebuffer->render_pass_id = render_pass_compat_id(
color_count, color_formats, with_depth, depth.format, depth_layout, attachment_samples);
EXIT_NOT_IMPLEMENTED(framebuffer->render_pass == nullptr);
vk::ImageView views[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
if (colors[i].vulkan_view == nullptr) {
EXIT("Framebuffer: color attachment view is missing at slot %u\n", i);
}
views[i] = colors[i].vulkan_view;
}
if (with_depth) {
if (depth.vulkan_view == nullptr) {
EXIT("Framebuffer: depth attachment view is missing\n");
}
views[color_count] = depth.vulkan_view;
}
vk::FramebufferCreateInfo framebuffer_info {};
framebuffer_info.sType = vk::StructureType::eFramebufferCreateInfo;
framebuffer_info.pNext = nullptr;
framebuffer_info.flags = {};
framebuffer_info.renderPass = framebuffer->render_pass;
framebuffer_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
framebuffer_info.pAttachments = views;
framebuffer_info.width = first_color_extent.width;
framebuffer_info.height = first_color_extent.height;
framebuffer_info.layers = 1;
result =
m_graphics.device.createFramebuffer(&framebuffer_info, nullptr, &framebuffer->framebuffer);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(framebuffer->framebuffer == nullptr);
Framebuffer fnew;
fnew.framebuffer = framebuffer;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
fnew.image_id[i] =
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
fnew.color_view[i] = (i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr);
fnew.color_clear_enable[i] =
(i < color_count && with_color[i] && colors[i].color_clear_enable);
fnew.color_layout[i] = color_layout[i];
}
fnew.depth_id = (with_depth ? depth.vulkan_buffer->memory.unique_id : 0);
fnew.depth_view = (with_depth ? depth.vulkan_view : nullptr);
fnew.depth_clear_enable = depth.depth_load_clear_enable;
fnew.stencil_clear_enable = depth.stencil_clear_enable;
fnew.depth_read_only = depth_read_only;
bool updated = false;
for (auto& f: m_framebuffers) {
if (f.framebuffer == nullptr) {
f = fnew;
updated = true;
break;
}
}
if (!updated) {
m_framebuffers.push_back(fnew);
}
return framebuffer;
}
void FramebufferCache::FreeFramebufferByColor(VulkanImage& image) {
Common::LockGuard lock(m_mutex);
for (auto& f: m_framebuffers) {
bool uses_image = false;
for (auto image_id: f.image_id) {
if (image_id == image.memory.unique_id) {
uses_image = true;
break;
}
}
if (f.framebuffer != nullptr && uses_image) {
m_graphics.device.destroyFramebuffer(f.framebuffer->framebuffer, nullptr);
m_graphics.device.destroyRenderPass(f.framebuffer->render_pass, nullptr);
delete f.framebuffer;
f.framebuffer = nullptr;
}
}
}
void FramebufferCache::FreeFramebufferByDepth(DepthStencilVulkanImage& image) {
Common::LockGuard lock(m_mutex);
for (auto& f: m_framebuffers) {
if (f.framebuffer != nullptr && f.depth_id == image.memory.unique_id) {
m_graphics.device.destroyFramebuffer(f.framebuffer->framebuffer, nullptr);
m_graphics.device.destroyRenderPass(f.framebuffer->render_pass, nullptr);
delete f.framebuffer;
f.framebuffer = nullptr;
}
}
}
} // namespace Libs::Graphics
@@ -1,85 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <vector>
namespace Libs::Graphics {
struct RenderColorInfo;
struct RenderDepthInfo;
static constexpr vk::ImageLayout RENDER_COLOR_IMAGE_LAYOUT = vk::ImageLayout::eGeneral;
struct VulkanFramebuffer {
vk::RenderPass render_pass = nullptr;
uint64_t render_pass_id = 0;
vk::Framebuffer framebuffer = nullptr;
uint32_t samples = 1;
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageLayout depth_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
};
inline uint64_t render_pass_compat_id(uint32_t color_count, const vk::Format* color_formats,
bool with_depth, vk::Format depth_format,
vk::ImageLayout depth_layout, uint32_t samples) {
uint64_t id = 0xcbf29ce484222325ull;
auto mix = [&id](uint64_t v) {
id ^= v;
id *= 0x100000001b3ull;
};
mix(color_count);
for (uint32_t i = 0; i < color_count; i++) {
mix(static_cast<uint32_t>(color_formats[i]));
}
mix(with_depth ? 1u : 0u);
mix(static_cast<uint32_t>(depth_format));
mix(static_cast<uint32_t>(depth_layout));
mix(samples);
return id;
}
class FramebufferCache {
public:
explicit FramebufferCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~FramebufferCache() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(FramebufferCache);
VulkanFramebuffer* CreateFramebuffer(RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo& depth);
void FreeFramebufferByColor(VulkanImage& image);
void FreeFramebufferByDepth(DepthStencilVulkanImage& image);
private:
struct Framebuffer {
VulkanFramebuffer* framebuffer = nullptr;
uint64_t image_id[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageView color_view[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint64_t depth_id = 0;
vk::ImageView depth_view = nullptr;
bool color_clear_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool depth_clear_enable = false;
bool stencil_clear_enable = false;
bool depth_read_only = false;
};
GraphicContext& m_graphics;
Common::Mutex m_mutex;
std::vector<Framebuffer> m_framebuffers;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
@@ -1,67 +0,0 @@
#include "graphics/host_gpu/renderer/gdsBuffer.h"
#include "common/assert.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
namespace Libs::Graphics {
void GdsBuffer::Init() {
if (m_buffer == nullptr) {
m_buffer = std::make_unique<VulkanBuffer>();
m_buffer->usage = vk::BufferUsageFlagBits::eStorageBuffer;
m_buffer->memory.property = vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent |
vk::MemoryPropertyFlagBits::eHostCached;
m_graphics.CreateBuffer(DW_SIZE * 4, *m_buffer);
}
}
void GdsBuffer::Clear(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value) {
Common::LockGuard lock(m_mutex);
Init();
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
EXIT_NOT_IMPLEMENTED(dw_offset + dw_num > DW_SIZE);
void* data = nullptr;
m_graphics.MapMemory(m_buffer->memory, data);
for (uint32_t i = 0; i < dw_num; i++) {
static_cast<uint32_t*>(data)[dw_offset + i] = clear_value;
}
m_graphics.UnmapMemory(m_buffer->memory);
}
void GdsBuffer::Read(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
EXIT_IF(dst == nullptr);
Common::LockGuard lock(m_mutex);
Init();
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
EXIT_NOT_IMPLEMENTED(dw_offset + dw_size > DW_SIZE);
void* data = nullptr;
m_graphics.MapMemory(m_buffer->memory, data);
for (uint32_t i = 0; i < dw_size; i++) {
dst[i] = static_cast<uint32_t*>(data)[dw_offset + i];
}
m_graphics.UnmapMemory(m_buffer->memory);
}
VulkanBuffer& GdsBuffer::GetBuffer() {
Common::LockGuard lock(m_mutex);
Init();
return *m_buffer;
}
} // namespace Libs::Graphics
@@ -1,39 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include <memory>
namespace Libs::Graphics {
class GdsBuffer {
public:
explicit GdsBuffer(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~GdsBuffer() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(GdsBuffer);
void Clear(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value);
void Read(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
VulkanBuffer& GetBuffer();
private:
static constexpr uint64_t DW_SIZE = 0x3000;
void Init();
GraphicContext& m_graphics;
Common::Mutex m_mutex;
std::unique_ptr<VulkanBuffer> m_buffer;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
@@ -3,17 +3,14 @@
#include "common/assert.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager(GraphicContext& graphics)
: m_page_manager(FaultThunk, this), m_buffer_cache(graphics, m_page_manager, m_resource_mutex),
m_texture_cache(graphics, m_page_manager, m_buffer_cache, m_resource_mutex) {
m_buffer_cache.SetTextureCache(m_texture_cache);
}
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
: m_page_manager(FaultThunk, this),
m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
GpuResourceManager::~GpuResourceManager() = default;
@@ -24,6 +21,15 @@ bool GpuResourceManager::FaultThunk(void* context, PageFaultAccess access, uint6
bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept {
// Let the authoritative image materialize first. A clean overlapping buffer marks a write
// fault CPU-dirty when it begins ownership transfer; doing that before image preflight would
// make the image appear to race a real CPU write. Completion and release retain buffer-first
// ordering so its pending fault is gone before TextureCache publishes the downloaded backing.
if (phase == PageFaultPhase::Invalidate) {
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
return buffer_handled || image_handled;
}
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
return buffer_handled || image_handled;
@@ -33,19 +39,23 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
if (!m_page_manager.IsMapped(fault_vaddr, 1)) {
return false;
}
if (LabelInCallback()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU label callback, "
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
if (auto* cp = GraphicsRunCurrentCommandProcessor(); cp != nullptr) {
cp->BeginReadbackTransaction();
bool handled = false;
bool handled = false;
const auto resolve = [this, access, fault_vaddr, &handled](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
(void)m_buffer_cache.SynchronizeBacking(fault_vaddr, 1);
{
ResourceMutex::FaultScope fault(m_resource_mutex);
handled = m_page_manager.HandleFault(access, fault_vaddr);
}
cp->EndReadbackTransaction();
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return handled;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
@@ -53,36 +63,36 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
" access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
// Stop command-processor jobs before taking the shared cache transaction. External readback
// workers inherit this paused state and therefore never form resource -> submission lock
// inversion.
GraphicsRunSubmissionLock submissions;
ResourceMutex::FaultScope fault(m_resource_mutex);
return m_page_manager.HandleFault(access, fault_vaddr);
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
return handled;
}
void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) {
if (!m_page_manager.HasAnyMapping(vaddr, size)) {
return;
}
if (LabelInCallback()) {
EXIT("unsupported host write from an asynchronous GPU label callback, addr=0x%016" PRIx64
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported host write from an asynchronous GPU completion, addr=0x%016" PRIx64
" size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto handle_range = [this, vaddr, size]() {
const auto handle_range = [this, vaddr, size] {
if (!m_page_manager.HandleWriteRange(vaddr, size)) {
EXIT("failed to prepare host write, addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
};
if (auto* cp = GraphicsRunCurrentCommandProcessor(); cp != nullptr) {
cp->BeginReadbackTransaction();
const auto resolve = [this, &handle_range](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
{
ResourceMutex::FaultScope fault(m_resource_mutex);
handle_range();
}
cp->EndReadbackTransaction();
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
@@ -90,9 +100,8 @@ void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) {
" size=0x%016" PRIx64 "\n",
vaddr, size);
}
GraphicsRunSubmissionLock submissions;
ResourceMutex::FaultScope fault(m_resource_mutex);
handle_range();
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
}
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
@@ -107,27 +116,26 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess ac
if (!IsMapped(vaddr, size)) {
EXIT("cannot unmap an unmapped GPU resource range\n");
}
m_texture_cache.UnmapMemory(vaddr, size);
m_buffer_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size, access);
const auto unmap = [this, vaddr, size, access] {
m_texture_cache.UnmapMemory(vaddr, size);
m_buffer_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size, access);
};
if (m_gpu == nullptr) {
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("cannot synchronously unmap from a resource transaction\n");
}
unmap();
return;
}
Gpu::SubmissionLock submissions(*m_gpu);
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::FillBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
uint32_t value) {
if (command.IsInvalid()) {
EXIT("cannot fill a buffer without a valid render command context\n");
}
Common::LockGuard lock(GetRenderContext().GetMutex());
m_buffer_cache.FillBuffer(&command, vaddr, size, value);
}
void GpuResourceManager::CopyBuffer(CommandBuffer& command, uint64_t dst_vaddr, uint64_t src_vaddr,
uint64_t size) {
if (command.IsInvalid()) {
EXIT("cannot copy a buffer without a valid render command context\n");
}
Common::LockGuard lock(GetRenderContext().GetMutex());
m_buffer_cache.CopyBuffer(&command, dst_vaddr, src_vaddr, size);
void GpuResourceManager::RunGarbageCollector() {
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
}
} // namespace Libs::Graphics
@@ -12,24 +12,25 @@
namespace Libs::Graphics {
class CommandBuffer;
class CommandScheduler;
class Gpu;
class GpuResourceManager {
public:
explicit GpuResourceManager(GraphicContext& graphics);
GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler);
~GpuResourceManager();
KYTY_CLASS_NO_COPY(GpuResourceManager);
[[nodiscard]] BufferCache& GetBufferCache() { return m_buffer_cache; }
[[nodiscard]] TextureCache& GetTextureCache() { return m_texture_cache; }
void SetGpu(Gpu* gpu) noexcept { m_gpu = gpu; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
void FillBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size, uint32_t value);
void CopyBuffer(CommandBuffer& command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size);
void RunGarbageCollector();
private:
static bool FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr, uint64_t size,
@@ -41,6 +42,7 @@ private:
ResourceMutex m_resource_mutex;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
Gpu* m_gpu = nullptr;
};
} // namespace Libs::Graphics
File diff suppressed because it is too large. Load diff
+141 -121
View File
@@ -2,49 +2,93 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include <algorithm>
#include <cstddef>
#include <compare>
#include <limits>
#include <mutex>
#include <optional>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
struct GraphicContext;
struct RenderTextureVulkanImage;
struct VideoOutVulkanImage;
struct VulkanImage;
class Buffer;
class CommandScheduler;
struct ImageTestAccess;
struct Image final: ImageInfo {
Image& operator=(const ImageInfo& value) {
if (IsCpuDirty()) {
EXIT("dirty sampled image cannot be reassigned\n");
}
static_cast<ImageInfo&>(*this) = value;
m_track_begin = address;
m_track_end = address + size;
m_maybe_cpu_hash_valid = false;
return *this;
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(address, this->size, vaddr, size)) {
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_track_begin = m_track_end;
} else if (ImagePageRangesOverlap(address, this->size, vaddr, size)) {
constexpr uint64_t page_mask = 4096 - 1;
if (vaddr + size <= address) {
const auto next_page = (address + page_mask) & ~page_mask;
m_track_begin = std::min(m_track_end, std::max(m_track_begin, next_page));
} else if (vaddr >= address + this->size) {
const auto page = (address + this->size) & ~page_mask;
m_track_end = std::max(m_track_begin, std::min(m_track_end, page));
}
m_maybe_cpu_dirty = m_track_begin == m_track_end;
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;
}
}
@@ -52,117 +96,93 @@ struct Image final: ImageInfo {
[[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_cpu_hash_valid;
}
[[nodiscard]] bool IsCpuTrackingComplete() const {
return m_track_begin == address && m_track_end == address + size;
return m_maybe_cpu_dirty && !m_maybe_hash_valid;
}
void SetMaybeCpuHash(uint64_t hash) {
if (!NeedsMaybeCpuHash()) {
EXIT("sampled image cannot initialize maybe-dirty hash\n");
EXIT("image cannot initialize maybe-dirty hash\n");
}
m_maybe_cpu_hash = hash;
m_maybe_cpu_hash_valid = true;
m_maybe_cpu_hash = hash;
m_maybe_hash_valid = true;
}
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
if (!m_maybe_cpu_dirty || !m_maybe_cpu_hash_valid || m_cpu_dirty) {
EXIT("sampled image cannot resolve maybe-dirty hash\n");
}
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_cpu_dirty = hash != m_maybe_cpu_hash;
if (!m_cpu_dirty) {
m_track_begin = address;
m_track_end = address + size;
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 sampled image cannot complete a refresh\n");
EXIT("clean image cannot complete a refresh\n");
}
m_cpu_dirty = false;
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_track_begin = address;
m_track_end = address + size;
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:
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_maybe_cpu_hash_valid = false;
uint64_t m_track_begin = 0;
uint64_t m_track_end = 0;
uint64_t m_maybe_cpu_hash = 0;
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 {
[[nodiscard]] GpuTextureVulkanImage* CreateTexture(const ImageInfo& info,
bool storage, vk::ComponentMapping& components);
void CreateTextureViews(GpuTextureVulkanImage& image,
const ImageInfo& info, bool storage, vk::ComponentMapping components);
[[nodiscard]] RenderTextureVulkanImage* CreateRenderTarget(
const RenderTargetInfo& info);
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
void UploadRenderTargetLayers(RenderTextureVulkanImage& image,
const RenderTargetInfo& info, uint32_t base_layer,
uint32_t layer_count, bool refresh);
void UploadRenderTarget(RenderTextureVulkanImage& image,
const RenderTargetInfo& info, bool refresh);
[[nodiscard]] DepthStencilVulkanImage* CreateDepthTarget(
const DepthTargetInfo& info);
void ValidateVideoOut(const VideoOutInfo& info);
[[nodiscard]] VideoOutVulkanImage* CreateVideoOut(
const VideoOutInfo& info);
void SwapVideoOutBgra16(void* data, uint64_t size);
void UploadVideoOut(VideoOutVulkanImage& image, const VideoOutInfo& info,
bool refresh);
[[nodiscard]] GpuTextureVulkanImage* CreateDummyTexture(bool uint_format,
bool image_3d, bool storage);
void Destroy(VulkanImage& image);
void Validate(const ImageInfo& info);
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
} // namespace ImageOps
struct ImageRetirementRange {
uint64_t address = 0;
uint64_t size = 0;
bool retire = false;
};
struct ImageRetirementConflict {
size_t retired = SIZE_MAX;
size_t retained = SIZE_MAX;
[[nodiscard]] bool Exists() const { return retired != SIZE_MAX; }
};
[[nodiscard]] inline ImageRetirementConflict
FindImageRetirementConflict(std::span<const ImageRetirementRange> ranges) {
for (size_t retired = 0; retired < ranges.size(); retired++) {
if (!ranges[retired].retire) {
continue;
}
for (size_t retained = 0; retained < ranges.size(); retained++) {
if (ranges[retained].retire) {
continue;
}
if (ImageRangeOverlaps(ranges[retired].address, ranges[retired].size,
ranges[retained].address, ranges[retained].size)) {
return {retired, retained};
}
}
}
return {};
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
File diff suppressed because it is too large. Load diff
+343 -337
View File
@@ -1,9 +1,8 @@
#include "graphics/host_gpu/renderer/imageView.h"
#include "common/assert.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image.h"
#include <mutex>
@@ -11,62 +10,286 @@ namespace Libs::Graphics {
namespace {
void CreateView(VulkanImage& image, int view_index,
vk::ImageViewType view_type, vk::ImageAspectFlags aspect_mask,
vk::ComponentMapping components, uint32_t base_array_layer, uint32_t base_mip_level,
uint32_t layer_count, uint32_t level_count,
vk::Format view_format = vk::Format::eUndefined,
vk::ImageUsageFlags view_usage = {}) {
auto& graphics = GetRenderContext().GetGraphics();
if (view_index < 0 || view_index >= VulkanImage::VIEW_MAX ||
image.image_view[view_index] != nullptr) {
EXIT("invalid image-view creation target: image=%p index=%d current_view=%d\n",
static_cast<const void*>(&image), view_index,
view_index >= 0 && view_index < VulkanImage::VIEW_MAX &&
image.image_view[view_index] != nullptr);
}
vk::ImageViewUsageCreateInfo usage_info {};
usage_info.sType = vk::StructureType::eImageViewUsageCreateInfo;
usage_info.usage = view_usage;
vk::ImageViewCreateInfo create_info {};
create_info.sType = vk::StructureType::eImageViewCreateInfo;
create_info.pNext = view_usage ? &usage_info : nullptr;
create_info.image = image.image;
create_info.viewType = view_type;
create_info.format = view_format != vk::Format::eUndefined ? view_format : image.format;
create_info.components = components;
create_info.subresourceRange.aspectMask = aspect_mask;
create_info.subresourceRange.baseArrayLayer = base_array_layer;
create_info.subresourceRange.baseMipLevel = base_mip_level;
create_info.subresourceRange.layerCount = layer_count;
create_info.subresourceRange.levelCount = level_count;
const auto result =
graphics.device.createImageView(&create_info, nullptr, &image.image_view[view_index]);
if (result != vk::Result::eSuccess || image.image_view[view_index] == nullptr) {
EXIT("failed to create image view: result=%d image_format=%d view_format=%d index=%d\n",
static_cast<int>(result), static_cast<int>(image.format),
static_cast<int>(create_info.format), view_index);
[[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;
}
}
void CreateRenderTargetView(VulkanImage& image, int index,
vk::ComponentSwizzle r, vk::ComponentSwizzle g, vk::ComponentSwizzle b,
vk::ComponentSwizzle a, vk::ImageViewType type = vk::ImageViewType::e2D,
vk::Format view_format = vk::Format::eUndefined,
vk::ImageUsageFlags view_usage = {}, uint32_t level_count = 0) {
const auto layer_count = type == vk::ImageViewType::e2DArray ? image.layers : 1u;
CreateView(image, index, type, vk::ImageAspectFlagBits::eColor, {r, g, b, a}, 0, 0,
layer_count, level_count == 0 ? image.mip_levels : level_count, view_format,
view_usage);
[[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:
return info.type == vk::ImageViewType::e3D && info.base_layer == 0 &&
info.layer_count == 1;
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:
@@ -79,314 +302,97 @@ vk::ImageAspectFlags DepthAspectMask(vk::Format format) {
}
}
bool FormatSupportsStorage(vk::Format format) {
auto& graphics = GetRenderContext().GetGraphics();
const auto properties = graphics.GetFormatProperties(format);
return static_cast<bool>(properties.optimalTilingFeatures &
vk::FormatFeatureFlagBits::eStorageImage);
}
void CreateRenderTargetViews(RenderTextureVulkanImage& image) {
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity);
if (image.layers > 1) {
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT_ARRAY,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ImageViewType::e2DArray);
}
if (image.samples == 1 && FormatSupportsStorage(image.format)) {
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ImageViewType::e2D, vk::Format::eUndefined,
vk::ImageUsageFlags {}, 1);
if (image.layers > 1) {
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE_ARRAY,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ImageViewType::e2DArray, vk::Format::eUndefined,
vk::ImageUsageFlags {}, 1);
}
}
}
void CreateDepthViews(DepthStencilVulkanImage& image) {
CreateView(image, VulkanImage::VIEW_DEFAULT, vk::ImageViewType::e2D,
DepthAspectMask(image.format),
{vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity},
0, 0, 1, 1);
}
void CreateVideoOutViews(VideoOutVulkanImage& image) {
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity);
if ((image.format == vk::Format::eR8G8B8A8Srgb || image.format == vk::Format::eB8G8R8A8Srgb) &&
FormatSupportsStorage(vk::Format::eR8G8B8A8Uint)) {
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ImageViewType::e2D, vk::Format::eR8G8B8A8Uint,
vk::ImageUsageFlagBits::eStorage, 1);
}
}
void DestroyViews(VulkanImage& image) {
auto& graphics = GetRenderContext().GetGraphics();
for (auto& cached: image.view_cache.views) {
if (cached.view != nullptr) {
graphics.device.destroyImageView(cached.view, nullptr);
cached.view = nullptr;
}
}
image.view_cache.views.clear();
for (auto& view: image.image_view) {
if (view != nullptr) {
graphics.device.destroyImageView(view, nullptr);
view = nullptr;
}
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 TextureCache::GetRenderTargetAttachmentView(RenderTextureVulkanImage& image,
vk::Format format, uint32_t level,
uint32_t base_layer,
uint32_t layer_count) {
if (image.format == vk::Format::eUndefined || level >= image.mip_levels || level >= 16 ||
layer_count == 0 || base_layer >= image.layers || layer_count > image.layers - base_layer) {
EXIT("TextureCache: invalid render-target attachment view, image=%p format=%d"
" level=%u image_levels=%u base_layer=%u layer_count=%u image_layers=%u\n",
static_cast<const void*>(&image), static_cast<int>(format), level, image.mip_levels,
base_layer, layer_count, image.layers);
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 (format != image.format && !IsRgba8SrgbReinterpretation(image.format, format)) {
EXIT("TextureCache: incompatible render-target attachment view, image_format=%d"
" view_format=%d level=%u\n",
static_cast<int>(image.format), static_cast<int>(format), level);
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 ranges_valid = normalized.level_count != 0 &&
normalized.base_level < image.mip_levels &&
normalized.level_count <= image.mip_levels - normalized.base_level &&
normalized.layer_count != 0 && normalized.base_layer < image.layers &&
normalized.layer_count <= image.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);
}
return GetImageView(
image, {format, layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray,
vk::ImageAspectFlagBits::eColor, level, 1, base_layer, layer_count,
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eColorAttachment});
}
vk::ImageView TextureCache::GetDepthTargetAttachmentView(DepthStencilVulkanImage& image,
uint32_t base_layer,
uint32_t layer_count) {
if (layer_count == 0 || base_layer >= image.layers || layer_count > image.layers - base_layer) {
EXIT("TextureCache: invalid depth-target attachment view, image=%p base_layer=%u "
"layer_count=%u image_layers=%u\n",
static_cast<const void*>(&image), base_layer, layer_count, image.layers);
}
return GetImageView(image,
{image.format,
layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray,
ImageViewOps::DepthAspectMask(image.format), 0, 1, base_layer, layer_count,
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eDepthStencilAttachment});
}
vk::ImageView TextureCache::GetImageView(VulkanImage& image, const ImageViewInfo& info) {
const bool supported_type = info.type == vk::ImageViewType::e2D ||
info.type == vk::ImageViewType::e2DArray ||
info.type == vk::ImageViewType::e3D;
const bool supported_usage = info.usage == vk::ImageUsageFlagBits::eSampled ||
info.usage == vk::ImageUsageFlagBits::eStorage ||
info.usage == vk::ImageUsageFlagBits::eColorAttachment ||
info.usage == vk::ImageUsageFlagBits::eDepthStencilAttachment;
const bool valid_shape =
(info.type == vk::ImageViewType::e2D && info.layer_count == 1) ||
info.type == vk::ImageViewType::e2DArray ||
(info.type == vk::ImageViewType::e3D && info.base_layer == 0 && info.layer_count == 1);
if (info.format == vk::Format::eUndefined || !info.aspect || info.level_count == 0 ||
info.base_level >= (image.mip_levels) ||
info.level_count > image.mip_levels - info.base_level || info.layer_count == 0 ||
info.base_layer >= image.layers || info.layer_count > image.layers - info.base_layer ||
!supported_type || !valid_shape || !supported_usage) {
EXIT("TextureCache: invalid dynamic image view, image=%p format=%d aspect=0x%x"
" swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d usage=0x%x"
" image_levels=%u image_layers=%u\n",
static_cast<const void*>(&image), static_cast<int>(info.format),
static_cast<vk::ImageAspectFlags::MaskType>(info.aspect), info.swizzle,
info.base_level, info.level_count, info.base_layer, info.layer_count,
static_cast<int>(info.type), static_cast<vk::ImageUsageFlags::MaskType>(info.usage),
image.mip_levels, image.layers);
}
auto& cache = image.view_cache;
std::lock_guard lock(cache.mutex);
for (const auto& cached: cache.views) {
if (cached.info == info) {
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 = info.usage;
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 = info.type;
create.format = info.format;
create.components = info.usage == vk::ImageUsageFlagBits::eSampled
? TextureGetComponentMapping(info.swizzle)
: vk::ComponentMapping {};
create.subresourceRange.aspectMask = info.aspect;
create.subresourceRange.baseMipLevel = info.base_level;
create.subresourceRange.levelCount = info.level_count;
create.subresourceRange.baseArrayLayer = info.base_layer;
create.subresourceRange.layerCount = info.layer_count;
vk::ImageView view = nullptr;
const auto result = m_graphics.device.createImageView(&create, nullptr, &view);
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("TextureCache: failed to create dynamic image view, result=%d format=%d"
" aspect=0x%x swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d usage=0x%x\n",
static_cast<int>(result), static_cast<int>(info.format),
static_cast<vk::ImageAspectFlags::MaskType>(info.aspect), info.swizzle,
info.base_level, info.level_count, info.base_layer, info.layer_count,
static_cast<int>(info.type), static_cast<vk::ImageUsageFlags::MaskType>(info.usage));
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));
}
cache.views.push_back({info, view});
views.views.push_back({normalized, view});
return view;
}
vk::ImageView TextureCache::GetDepthTargetSampledView(DepthStencilVulkanImage& image,
vk::Format view_format, uint32_t swizzle,
uint32_t base_level, uint32_t level_count,
vk::ImageViewType type, uint32_t base_layer,
uint32_t layer_count) {
if (view_format == vk::Format::eUndefined ||
!IsSupportedSampledDepthView(image.format, view_format, swizzle)) {
EXIT("TextureCache: invalid sampled depth-target view, image=%p image_format=%d"
" view_format=%d swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d"
" image_levels=%u image_layers=%u\n",
static_cast<const void*>(&image), static_cast<int>(image.format),
static_cast<int>(view_format), swizzle, base_level, level_count, base_layer,
layer_count, static_cast<int>(type), image.mip_levels, image.layers);
}
return GetImageView(image, {image.format, type, vk::ImageAspectFlagBits::eDepth, base_level,
level_count, base_layer, layer_count, swizzle});
}
vk::ImageView TextureCache::GetSampledColorView(VulkanImage& image, vk::Format view_format,
uint32_t swizzle, uint32_t base_level,
uint32_t level_count, vk::ImageViewType type,
uint32_t base_layer, uint32_t layer_count) {
if (view_format == vk::Format::eUndefined || base_level >= 16 ||
(type != vk::ImageViewType::e2D && type != vk::ImageViewType::e2DArray) ||
!IsSupportedSampledColorView(image.format, view_format, swizzle)) {
EXIT("TextureCache: invalid sampled color view, image=%p swizzle=0x%03x"
" view_format=%d mip=%u+%u layer=%u+%u type=%d image_levels=%u image_layers=%u\n",
static_cast<const void*>(&image), swizzle, static_cast<int>(view_format), base_level,
level_count, base_layer, layer_count, static_cast<int>(type), image.mip_levels,
image.layers);
}
const auto precreated_view = type == vk::ImageViewType::e2DArray
? VulkanImage::VIEW_DEFAULT_ARRAY
: VulkanImage::VIEW_DEFAULT;
const bool full_view = base_level == 0 && level_count == image.mip_levels && base_layer == 0 &&
layer_count == (type == vk::ImageViewType::e2DArray ? image.layers : 1u);
if (view_format == image.format && swizzle == DstSel(4, 5, 6, 7) && full_view &&
image.image_view[precreated_view] != nullptr) {
return image.image_view[precreated_view];
}
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, base_level,
level_count, base_layer, layer_count, swizzle});
}
vk::ImageView TextureCache::GetRenderTargetStorageView(RenderTextureVulkanImage& image,
vk::Format view_format, uint32_t base_level,
uint32_t level_count, vk::ImageViewType type,
uint32_t base_layer, uint32_t layer_count) {
if (view_format == vk::Format::eUndefined ||
(type != vk::ImageViewType::e2D && type != vk::ImageViewType::e2DArray)) {
EXIT("TextureCache: invalid render-target storage view, image=%p view_format=%d"
" mip=%u+%u layer=%u+%u type=%d image_levels=%u image_layers=%u\n",
static_cast<const void*>(&image), static_cast<int>(view_format), base_level,
level_count, base_layer, layer_count, static_cast<int>(type), image.mip_levels,
image.layers);
}
const bool exact = view_format == image.format;
const bool compatible = view_format == BgraSrgbStorageViewFormat(image.format);
if (!exact && !compatible) {
EXIT("TextureCache: incompatible render-target storage view, image_format=%d"
" view_format=%d base=%u count=%u\n",
static_cast<int>(image.format), static_cast<int>(view_format), base_level,
level_count);
}
if (exact) {
const auto index = type == vk::ImageViewType::e2DArray ? VulkanImage::VIEW_STORAGE_ARRAY
: VulkanImage::VIEW_STORAGE;
const bool full_view =
base_level == 0 && level_count == 1 && base_layer == 0 &&
layer_count == (type == vk::ImageViewType::e2DArray ? image.layers : 1u);
if (full_view && image.image_view[index] != nullptr) {
return image.image_view[index];
}
}
if (compatible && !ImageViewOps::FormatSupportsStorage(view_format)) {
EXIT("TextureCache: compatible render-target storage format lacks storage support,"
" image_format=%d view_format=%d base=%u count=%u\n",
static_cast<int>(image.format), static_cast<int>(view_format), base_level,
level_count);
}
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, base_level,
level_count, base_layer, layer_count, DstSel(4, 5, 6, 7),
vk::ImageUsageFlagBits::eStorage});
}
vk::ImageView TextureCache::GetStorageTextureSampledView(StorageTextureVulkanImage& image,
const ImageInfo& info) {
const auto shape = SelectStorageSampledViewShape(info.type, info.depth, image.layers);
if (image.image == nullptr || shape == StorageSampledViewShape::Unsupported ||
info.base_array != 0 || info.levels != image.mip_levels || info.base_level >= info.levels ||
info.view_levels == 0 || info.base_level + info.view_levels > info.levels) {
EXIT("TextureCache: invalid sampled view of storage texture, image=%p type=%u depth=%u"
" base=%u levels=%u view_levels=%u image_levels=%u base_array=%u\n",
static_cast<const void*>(&image), info.type, info.depth, info.base_level, info.levels,
info.view_levels, image.mip_levels, info.base_array);
}
const auto view_format = TextureGetFormat(info.format);
if (view_format != image.format && !IsRgba8SrgbReinterpretation(image.format, view_format) &&
!IsR32UintFloatReinterpretation(image.format, view_format)) {
EXIT("TextureCache: incompatible sampled view of storage texture, image_format=%d"
" view_format=%d swizzle=0x%03x\n",
static_cast<int>(image.format), static_cast<int>(view_format), info.swizzle);
}
vk::ImageViewType type = static_cast<vk::ImageViewType>(VK_IMAGE_VIEW_TYPE_MAX_ENUM);
switch (shape) {
case StorageSampledViewShape::Image2D: type = vk::ImageViewType::e2D; break;
case StorageSampledViewShape::Image2DArray: type = vk::ImageViewType::e2DArray; break;
case StorageSampledViewShape::Image3D: type = vk::ImageViewType::e3D; break;
case StorageSampledViewShape::Unsupported:
EXIT("TextureCache: unsupported sampled storage-image view shape\n");
}
const auto layer_count = shape == StorageSampledViewShape::Image2DArray ? info.depth : 1u;
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, info.base_level,
info.view_levels, 0, layer_count, info.swizzle});
}
vk::ImageView TextureCache::GetStorageTextureStorageView(StorageTextureVulkanImage& image,
uint32_t base_level) {
if (image.image == nullptr || base_level >= (image.mip_levels)) {
EXIT("TextureCache: invalid storage-texture mip view, image=%p level=%u levels=%u\n",
static_cast<const void*>(&image), base_level, image.mip_levels);
}
if (base_level == 0) {
return image.image_view[VulkanImage::VIEW_DEFAULT];
}
return GetImageView(image, {image.format, vk::ImageViewType::e2D,
vk::ImageAspectFlagBits::eColor, base_level, 1, 0, 1,
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eStorage});
}
} // namespace Libs::Graphics
+23 -77
View File
@@ -2,42 +2,28 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shader.h"
namespace Libs::Graphics {
[[nodiscard]] inline bool IsSupportedStorageSwizzle(uint32_t format, uint32_t swizzle) noexcept {
const bool single_channel =
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UInt) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16Float) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
return swizzle == DstSel(4, 5, 6, 7) ||
(single_channel && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1) ||
swizzle == DstSel(4, 4, 4, 4))) ||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32UInt) &&
swizzle == DstSel(4, 5, 0, 1)) ||
((format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UInt)) &&
(swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) ||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float) &&
swizzle == DstSel(5, 6, 7, 4));
}
[[nodiscard]] inline bool IsSupportedStorageDepthTile(uint32_t format, uint32_t type,
uint32_t width, uint32_t height,
uint32_t depth) noexcept {
return (format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) &&
type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) && width != 0 &&
height != 0 && depth == 1) ||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) &&
type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) && width != 0 &&
height != 0 && depth == 1);
[[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,
@@ -60,11 +46,8 @@ namespace Libs::Graphics {
switch (image_format) {
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
switch (view_format) {
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb: return true;
default: return false;
}
return view_format == vk::Format::eR8G8B8A8Unorm ||
view_format == vk::Format::eR8G8B8A8Srgb;
case vk::Format::eA2R10G10B10UnormPack32:
return view_format == vk::Format::eA2B10G10R10UnormPack32;
default: return false;
@@ -86,28 +69,10 @@ namespace Libs::Graphics {
return view_format == BgraSrgbStorageViewFormat(image_format) && swizzle == DstSel(6, 5, 4, 7);
}
[[nodiscard]] inline bool IsValidSampledColorSwizzle(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;
}
[[nodiscard]] inline bool IsSupportedSampledColorView(vk::Format image_format,
vk::Format view_format,
uint32_t swizzle) noexcept {
if (!IsValidSampledColorSwizzle(swizzle)) {
if (!IsValidImageSwizzle(swizzle)) {
return false;
}
if (image_format == view_format || IsRgba8SrgbReinterpretation(image_format, view_format)) {
@@ -169,26 +134,13 @@ IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& r
!resource.written && !resource.atomic && !resource.depth_compare;
}
[[nodiscard]] inline int SelectStorageColorView(vk::Format image_format, vk::Format view_format,
uint32_t swizzle) noexcept {
const bool single_channel =
view_format == vk::Format::eR8Unorm || view_format == vk::Format::eR8Uint ||
view_format == vk::Format::eR16Uint || view_format == vk::Format::eR32Uint ||
view_format == vk::Format::eR16Sfloat || view_format == vk::Format::eR32Sfloat;
const bool swizzle_ok =
swizzle == DstSel(4, 5, 6, 7) ||
(single_channel && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1) ||
swizzle == DstSel(4, 4, 4, 4))) ||
(view_format == vk::Format::eR32G32Uint && swizzle == DstSel(4, 5, 0, 1)) ||
((view_format == vk::Format::eR8G8B8A8Unorm || view_format == vk::Format::eR8G8B8A8Uint) &&
(swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) ||
(view_format == vk::Format::eR32G32B32A32Sfloat && swizzle == DstSel(5, 6, 7, 4));
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
uint32_t swizzle) noexcept {
if ((image_format != view_format &&
!IsBgraSrgbStorageView(image_format, view_format, swizzle)) ||
!swizzle_ok) {
!IsValidImageSwizzle(swizzle)) {
UnsupportedColorView("storage", image_format, view_format, swizzle);
}
return VulkanImage::VIEW_STORAGE;
}
[[nodiscard]] inline bool
@@ -216,13 +168,7 @@ ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource
namespace ImageViewOps {
[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
[[nodiscard]] bool FormatSupportsStorage(vk::Format format);
void CreateRenderTargetViews(RenderTextureVulkanImage& image);
void CreateDepthViews(DepthStencilVulkanImage& image);
void CreateVideoOutViews(VideoOutVulkanImage& image);
void DestroyViews(VulkanImage& image);
[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept;
} // namespace ImageViewOps
} // namespace Libs::Graphics
@@ -0,0 +1,60 @@
#include "graphics/host_gpu/renderer/masterSemaphore.h"
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
namespace Libs::Graphics {
MasterSemaphore::MasterSemaphore(GraphicContext& graphics): m_graphics(graphics) {
vk::SemaphoreTypeCreateInfo type_info {};
type_info.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
type_info.semaphoreType = vk::SemaphoreType::eTimeline;
type_info.initialValue = 0;
vk::SemaphoreCreateInfo create_info {};
create_info.sType = vk::StructureType::eSemaphoreCreateInfo;
create_info.pNext = &type_info;
const auto result = m_graphics.device.createSemaphore(&create_info, nullptr, &m_semaphore);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_semaphore == nullptr);
}
MasterSemaphore::~MasterSemaphore() {
if (m_semaphore != nullptr) {
m_graphics.device.destroySemaphore(m_semaphore, nullptr);
}
}
void MasterSemaphore::Refresh() {
uint64_t counter = 0;
const auto result = m_graphics.device.getSemaphoreCounterValue(m_semaphore, &counter);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
auto known = m_gpu_tick.load(std::memory_order_acquire);
while (known < counter &&
!m_gpu_tick.compare_exchange_weak(known, counter, std::memory_order_release,
std::memory_order_relaxed)) {
}
}
void MasterSemaphore::Wait(uint64_t tick) {
if (IsFree(tick)) {
return;
}
Refresh();
if (IsFree(tick)) {
return;
}
vk::SemaphoreWaitInfo wait_info {};
wait_info.sType = vk::StructureType::eSemaphoreWaitInfo;
wait_info.semaphoreCount = 1;
wait_info.pSemaphores = &m_semaphore;
wait_info.pValues = &tick;
const auto result = m_graphics.device.waitSemaphores(&wait_info, UINT64_MAX);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
Refresh();
}
} // namespace Libs::Graphics
@@ -0,0 +1,43 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <atomic>
namespace Libs::Graphics {
struct GraphicContext;
class MasterSemaphore {
public:
explicit MasterSemaphore(GraphicContext& graphics);
~MasterSemaphore();
KYTY_CLASS_NO_COPY(MasterSemaphore);
[[nodiscard]] uint64_t CurrentTick() const noexcept {
return m_current_tick.load(std::memory_order_acquire);
}
[[nodiscard]] uint64_t KnownGpuTick() const noexcept {
return m_gpu_tick.load(std::memory_order_acquire);
}
[[nodiscard]] bool IsFree(uint64_t tick) const noexcept { return KnownGpuTick() >= tick; }
[[nodiscard]] uint64_t NextTick() noexcept {
return m_current_tick.fetch_add(1, std::memory_order_release);
}
[[nodiscard]] vk::Semaphore Handle() const noexcept { return m_semaphore; }
void Refresh();
void Wait(uint64_t tick);
private:
GraphicContext& m_graphics;
vk::Semaphore m_semaphore = nullptr;
std::atomic<uint64_t> m_gpu_tick {0};
std::atomic<uint64_t> m_current_tick {1};
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
@@ -7,7 +7,7 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
@@ -36,13 +36,25 @@ void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_param
} // namespace
PipelineCache::~PipelineCache() {
auto destroy = [this](const auto& pipelines) {
for (const auto& [key, pipeline]: pipelines) {
(void)key;
m_graphics.device.destroyPipeline(pipeline->pipeline, nullptr);
m_graphics.device.destroyPipelineLayout(pipeline->pipeline_layout, nullptr);
}
};
destroy(m_graphics_pipelines);
destroy(m_compute_pipelines);
}
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
return std::memcmp(this, &other, sizeof(*this)) == 0;
}
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
VulkanFramebuffer& framebuffer, RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo& depth, ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
@@ -61,10 +73,10 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
const HW::BlendColor& bclr = ctx.GetBlendColor();
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
color_mask[i] = (colors[i].vulkan_buffer != nullptr
? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
ctx.GetRenderTargetMask(), colors[i].target_slot))
: 0);
color_mask[i] =
(colors[i].image_id ? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
ctx.GetRenderTargetMask(), colors[i].target_slot))
: 0);
}
const HW::ModeControl& mc = ctx.GetModeControl();
@@ -76,11 +88,40 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
PipelineStaticParameters static_params {};
GraphicsPipeline p {};
p.render_pass_id = framebuffer.render_pass_id;
p.ps_shader_id = ps_id;
p.vs_shader_id = vs_id;
static_params.color_count = color_count;
PipelineRenderingState rendering {};
rendering.color_count = color_count;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
EXIT_IF(!colors[i].image_id || colors[i].format == vk::Format::eUndefined);
rendering.color_formats[i] = colors[i].format;
if (attachment_samples == 0) {
attachment_samples = colors[i].samples;
} else if (attachment_samples != colors[i].samples) {
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, colors[i].samples);
}
}
const bool with_depth =
depth.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
if (with_depth) {
const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
rendering.depth_format =
aspects & vk::ImageAspectFlagBits::eDepth ? depth.format : vk::Format::eUndefined;
rendering.stencil_format =
aspects & vk::ImageAspectFlagBits::eStencil ? depth.format : vk::Format::eUndefined;
if (attachment_samples == 0) {
attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
attachment_samples, depth.samples);
}
}
EXIT_IF(attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {});
if (ps_active && depth.depth_test_enable && ps_input_info->ps_execute_on_noop) {
static std::atomic<uint32_t> log_count {0};
@@ -94,14 +135,13 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
static_params.negative_one_to_one = !clip_control.dx_clip_space;
static_params.depth_clip_enable = clip_control.IsZClipEnabled();
static_params.topology = topology;
static_params.samples = framebuffer.samples;
static_params.samples = attachment_samples;
static_params.sample_shading_enable =
ps_active && framebuffer.samples > 1 && ps_input_info->ps_sample_shading;
ps_active && attachment_samples > 1 && ps_input_info->ps_sample_shading;
if (static_params.sample_shading_enable && !m_graphics.sample_rate_shading_enabled) {
EXIT("Pipeline: sample-rate shading is required but unsupported by the host\n");
}
static_params.with_depth =
(depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
static_params.with_depth = with_depth;
static_params.depth_test_enable = depth.depth_test_enable;
static_params.depth_write_enable = (depth.depth_write_enable && !depth.depth_clear_enable);
static_params.depth_compare_op = depth.depth_compare_op;
@@ -139,7 +179,7 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
NormalizeStaticParamsForDynamicState(static_params);
GraphicsPipelineKey key {};
key.render_pass_id = p.render_pass_id;
key.rendering = rendering;
key.vs_shader_id = p.vs_shader_id;
key.ps_shader_id = p.ps_shader_id;
key.static_params = static_params;
@@ -162,7 +202,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
auto cached = std::make_unique<GraphicsPipeline>(p);
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
CreatePipelineInternal(*cached, framebuffer.render_pass, vs_input_info, vs_spirv, ps_input_info,
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, rendering, vs_input_info,
vs_spirv, ps_input_info,
ps_spirv, static_params, vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32, ps_active);
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
@@ -204,7 +245,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
}
auto cached = std::make_unique<ComputePipeline>(p);
CreatePipelineInternal(*cached, input_info, cs_spirv);
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, input_info, cs_spirv);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
+33 -11
View File
@@ -20,8 +20,8 @@ namespace Libs::Graphics {
struct GraphicContext;
struct RenderColorInfo;
struct RenderDepthInfo;
struct VulkanFramebuffer;
class RenderCommandBuffer;
class DescriptorCache;
namespace HW {
class Context;
@@ -86,12 +86,22 @@ static_assert(sizeof(PipelineStaticParameters) ==
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
vk::Format depth_format = vk::Format::eUndefined;
vk::Format stencil_format = vk::Format::eUndefined;
uint32_t color_count = 0;
bool operator==(const PipelineRenderingState&) const = default;
};
class PipelineCache {
public:
explicit PipelineCache(GraphicContext& graphics): m_graphics(graphics) {
PipelineCache(GraphicContext& graphics, DescriptorCache& descriptor_cache)
: m_graphics(graphics), m_descriptor_cache(descriptor_cache) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~PipelineCache() { KYTY_NOT_IMPLEMENTED; }
~PipelineCache();
KYTY_CLASS_NO_COPY(PipelineCache);
struct Pipeline {
@@ -100,7 +110,6 @@ public:
};
struct GraphicsPipeline: Pipeline {
uint64_t render_pass_id = 0;
ShaderId vs_shader_id;
ShaderId ps_shader_id;
};
@@ -110,8 +119,8 @@ public:
};
GraphicsPipeline& CreateGraphicsPipeline(
VulkanFramebuffer& framebuffer, RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo& depth, ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
@@ -120,13 +129,13 @@ public:
private:
struct GraphicsPipelineKey {
uint64_t render_pass_id = 0;
PipelineRenderingState rendering;
ShaderId vs_shader_id;
ShaderId ps_shader_id;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
return render_pass_id == other.render_pass_id && vs_shader_id == other.vs_shader_id &&
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
}
};
@@ -160,12 +169,21 @@ private:
Mix(hash, bytes[i]);
}
}
static void MixRendering(std::size_t& hash, const PipelineRenderingState& rendering) {
Mix(hash, rendering.color_count);
for (uint32_t i = 0; i < rendering.color_count; i++) {
Mix(hash, static_cast<uint32_t>(rendering.color_formats[i]));
}
Mix(hash, static_cast<uint32_t>(rendering.depth_format));
Mix(hash, static_cast<uint32_t>(rendering.stencil_format));
}
};
struct GraphicsPipelineKeyHash {
std::size_t operator()(const GraphicsPipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::Mix(hash, key.render_pass_id);
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::MixShaderId(hash, key.vs_shader_id);
PipelineKeyHash::MixShaderId(hash, key.ps_shader_id);
PipelineKeyHash::MixStaticParams(hash, key.static_params);
@@ -182,6 +200,7 @@ private:
};
GraphicContext& m_graphics;
DescriptorCache& m_descriptor_cache;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
@@ -192,7 +211,9 @@ private:
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
uint32_t ps_crc32);
void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::RenderPass render_pass,
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering,
const ShaderVertexInputInfo& vs_input_info,
std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info,
@@ -200,7 +221,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
bool ps_active);
void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader);
+114 -45
View File
@@ -2,10 +2,13 @@
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRENDER_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <memory>
#include <vector>
@@ -15,29 +18,23 @@ namespace HW {
class Context;
class UserConfig;
class Shader;
struct DepthRenderTarget;
} // namespace HW
struct GraphicContext;
struct ShaderBufferResource;
struct ShaderComputeInputInfo;
struct VideoOutVulkanImage;
struct DepthStencilVulkanImage;
struct TextureVulkanImage;
struct StorageTextureVulkanImage;
struct RenderTextureVulkanImage;
struct CommandSlot;
struct VulkanBuffer;
struct VulkanDescriptorSet;
struct VulkanFramebuffer;
struct RenderDepthInfo;
struct RenderColorInfo;
class BufferCache;
struct HtileClearTarget {
uint64_t address = 0;
uint64_t size = 0;
};
struct DrawCallInfo;
struct DrawEmitInfo;
struct DrawIndexBufferSource;
struct DrawRenderState;
class RenderContext;
class CommandScheduler;
struct RenderExecutorTestAccess;
enum class CommandBufferDebugOp : uint32_t {
DispatchDirect,
@@ -66,9 +63,35 @@ private:
std::vector<std::shared_ptr<void>> m_resources;
};
struct SubmitInfo {
static constexpr uint32_t MaxSemaphores = 3;
std::array<vk::Semaphore, MaxSemaphores> wait_semaphores {};
std::array<uint64_t, MaxSemaphores> wait_ticks {};
std::array<vk::PipelineStageFlags, MaxSemaphores> wait_stages {};
std::array<vk::Semaphore, MaxSemaphores> signal_semaphores {};
std::array<uint64_t, MaxSemaphores> signal_ticks {};
uint32_t num_wait_semaphores = 0;
uint32_t num_signal_semaphores = 0;
void AddWait(vk::Semaphore semaphore, uint64_t tick = 1,
vk::PipelineStageFlags stage = vk::PipelineStageFlagBits::eAllCommands) {
EXIT_IF(semaphore == nullptr || num_wait_semaphores >= MaxSemaphores);
wait_semaphores[num_wait_semaphores] = semaphore;
wait_ticks[num_wait_semaphores] = tick;
wait_stages[num_wait_semaphores++] = stage;
}
void AddSignal(vk::Semaphore semaphore, uint64_t tick = 1) {
EXIT_IF(semaphore == nullptr || num_signal_semaphores >= MaxSemaphores);
signal_semaphores[num_signal_semaphores] = semaphore;
signal_ticks[num_signal_semaphores++] = tick;
}
};
class CommandBuffer {
public:
CommandBuffer();
explicit CommandBuffer(CommandScheduler& scheduler);
~CommandBuffer();
KYTY_CLASS_NO_COPY(CommandBuffer);
@@ -77,14 +100,11 @@ public:
void Begin() const;
void End() const;
void Execute();
void ExecuteWithSemaphore(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore);
void Execute(const SubmitInfo& submit = {});
void SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0 = 0, uint32_t arg1 = 0,
uint32_t arg2 = 0, uint32_t arg3 = 0, uint64_t arg4 = 0);
void BeginRenderPass(VulkanFramebuffer& framebuffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth) const;
void EndRenderPass() const;
void BeginRendering(const RenderState& state) const;
void EndRendering() const;
void WaitForFenceOnly();
void WaitForFence();
void WaitForFenceAndReset();
@@ -94,19 +114,18 @@ public:
[[nodiscard]] vk::CommandBuffer Handle() const;
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
[[nodiscard]] RenderContext& GetContext() const noexcept { return m_context; }
[[nodiscard]] bool IsExecute() const { return m_execute; }
private:
friend class BufferCache;
void Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore);
void Release();
void FinalizeFence(bool reset_recording);
void ReleaseResourcesAfterFence();
void DeleteBuffersAfterFence();
void RecycleDescriptorsAfterFence();
RenderContext& m_context;
CommandScheduler& m_scheduler;
GraphicContext& m_graphics;
CommandSlot* m_slot = nullptr;
bool m_execute = false;
@@ -122,12 +141,13 @@ private:
std::vector<std::unique_ptr<VulkanBuffer>> m_retired_buffers;
FenceResourceRetainer m_fence_resources;
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
HostStreamBuffer m_host_stream;
mutable RenderState m_render_state;
mutable bool m_rendering = false;
};
class RenderCommandBuffer final: public CommandBuffer {
public:
RenderCommandBuffer() = default;
explicit RenderCommandBuffer(CommandScheduler& scheduler): CommandBuffer(scheduler) {}
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
m_registers = &registers;
@@ -145,30 +165,79 @@ private:
HW::Shader* m_shaders = nullptr;
};
void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset = 0,
uint32_t instance_count = 1, uint32_t first_vertex = 0,
uint32_t first_instance = 0);
void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
class RenderExecutor {
public:
explicit RenderExecutor(RenderContext& context): m_context(context) {}
KYTY_CLASS_NO_COPY(RenderExecutor);
void GraphicsRenderInit(GraphicContext& graphics);
void GraphicsRenderReleaseThreadCommandPool();
void DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
void DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset = 0,
uint32_t instance_count = 1, uint32_t first_vertex = 0,
uint32_t first_instance = 0);
void DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
[[nodiscard]] DescriptorCache::PreparedBindings
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage);
void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
private:
struct GraphicsBindings {
DescriptorCache::PreparedBindings vertex;
std::optional<DescriptorCache::PreparedBindings> pixel;
};
[[nodiscard]] DescriptorCache::TextureBinding
ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(CommandBuffer& buffer,
const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
bool ignore_target_mask = false, bool exact_format = false);
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderDepthInfo& target);
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state);
void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug);
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth);
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id);
void TrackImageBinding(ImageId id);
void ResetBindings();
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer);
RenderContext& m_context;
std::vector<std::shared_ptr<Image>> m_bound_images;
friend struct RenderExecutorTestAccess;
};
[[nodiscard]] bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode,
ShaderBufferResource& descriptor,
uint32_t& packed_clear, uint64_t& size);
[[nodiscard]] bool ResolveHtileClearTarget(const HW::DepthRenderTarget& target,
uint64_t descriptor_size, HtileClearTarget& resolved);
void GraphicsRenderMemoryBarrier(CommandBuffer& buffer);
void GraphicsRenderTextureBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size);
void GraphicsRenderDepthStencilBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size);
} // namespace Libs::Graphics
+35 -223
View File
@@ -9,20 +9,15 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/descriptors.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h"
@@ -50,217 +45,35 @@ static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
return stride == 0 ? records : records * stride;
}
bool ResolveHtileClearTarget(const HW::DepthRenderTarget& z, uint64_t descriptor_size,
HtileClearTarget& resolved) {
resolved = {};
const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const auto* depth_policy = FindDepthFormatPolicy(z.z_info.format);
const bool supported_depth_state =
z.z_info.tile_surface_enable && depth_policy != nullptr && z.z_info.tile_mode_index == 0 &&
z.z_info.num_samples <= 3 && z.z_info.zrange_precision <= 1 && !z.z_info.expclear_enabled &&
!z.z_info.embedded_sample_locations && !z.z_info.partially_resident &&
z.z_info.num_mip_levels == 0 && z.z_info.plane_compression == 0 &&
z.depth_view.current_mip_level == 0 && z.depth_view.slice_start == 0 &&
z.depth_view.slice_max == 0 && z.depth_info.addr5_swizzle_mask == 0 &&
z.depth_info.array_mode == 0 && z.depth_info.pipe_config == 0 &&
z.depth_info.bank_width == 0 && z.depth_info.bank_height == 0 &&
z.depth_info.macro_tile_aspect == 0 && z.depth_info.num_banks == 0;
const bool supported_stencil_state =
z.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 &&
!z.stencil_info.expclear_enabled &&
depth_htile_stencil_acceleration_compatible(has_stencil, true,
z.stencil_info.tile_stencil_disable) &&
!z.stencil_info.texture_compatible_stencil && !z.stencil_info.partially_resident &&
(!has_stencil ||
(z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) &&
!z.depth_view.stencil_write_disable));
const bool supported_htile_state =
z.htile_surface.linear == 0 && z.htile_surface.full_cache == 0 &&
z.htile_surface.htile_uses_preload_win == 0 && z.htile_surface.preload == 0 &&
z.htile_surface.prefetch_width == 0 && z.htile_surface.prefetch_height == 0 &&
z.htile_surface.dst_outside_zero_to_one == 0;
const bool supported_addresses =
z.z_read_base_addr != 0 && z.z_write_base_addr == z.z_read_base_addr &&
(z.z_read_base_addr & 0xffffu) == 0 &&
(has_stencil ? z.stencil_read_base_addr != 0 &&
z.stencil_write_base_addr == z.stencil_read_base_addr &&
(z.stencil_read_base_addr & 0xffffu) == 0
: z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0) &&
z.htile_data_base_addr != 0 && (z.htile_data_base_addr & 0x7fffu) == 0 &&
descriptor_size != 0 && (descriptor_size & 0x7fffu) == 0 &&
z.htile_data_base_addr < TRACKER_ADDRESS_SIZE &&
descriptor_size <= TRACKER_ADDRESS_SIZE - z.htile_data_base_addr;
if (!supported_depth_state || !supported_stencil_state || !supported_htile_state ||
!supported_addresses) {
return false;
}
const bool size_xy_valid = z.size.valid;
const bool wh_valid = z.width_height_valid && z.width != 0 && z.height != 0;
if (!size_xy_valid && !wh_valid) {
// Prospero emits an exact full-surface metadata descriptor even
// when the compute context omits depth extent registers. Admit only that wholly absent
// layout state; partially programmed layouts remain unsupported.
const bool descriptor_backed_state =
!z.size.valid && z.size.x_max == 0 && z.size.y_max == 0 && !z.width_height_valid &&
z.width == 0 && z.height == 0 && !z.pitch_height_valid && z.pitch_div8_minus1 == 0 &&
z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0;
if (!descriptor_backed_state) {
return false;
}
resolved = {.address = z.htile_data_base_addr, .size = descriptor_size};
return true;
}
const uint32_t width = size_xy_valid ? static_cast<uint32_t>(z.size.x_max) + 1u : z.width;
const uint32_t height = size_xy_valid ? static_cast<uint32_t>(z.size.y_max) + 1u : z.height;
if (width > 16384 || height > 16384 ||
(size_xy_valid && wh_valid && (width != z.width || height != z.height)) ||
(!z.pitch_height_valid &&
(z.pitch_div8_minus1 != 0 || z.height_div8_minus1 != 0 || z.slice_div64_minus1 != 0))) {
return false;
}
TileSizeAlign htile_size {};
if (!TileGetHtileSize(width, height, htile_size) || htile_size.size != descriptor_size) {
return false;
}
resolved = {.address = z.htile_data_base_addr, .size = htile_size.size};
return true;
}
static void ValidateFullHtileClearDispatch(const ShaderComputeInputInfo& input,
const ShaderBufferResource& metadata, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode) {
const bool thread_dimensions = input.dispatch_thread_dimensions;
const bool supported_shape =
input.threads_num[0] == 64 && input.threads_num[1] == 1 && input.threads_num[2] == 1 &&
group_x != 0 && group_y == 1 && group_z == 1 && input.group_id[0] && !input.group_id[1] &&
!input.group_id[2] && input.thread_ids_num == 1 && input.wave_size == 32 &&
!input.tg_size_en && mode == (thread_dimensions ? 0x61u : 0x41u);
const bool dimensions_match =
thread_dimensions
? input.dispatch_threads_num[0] == group_x && input.dispatch_threads_num[1] == 1 &&
input.dispatch_threads_num[2] == 1 && group_x % input.threads_num[0] == 0
: input.dispatch_threads_num[0] == 0 && input.dispatch_threads_num[1] == 0 &&
input.dispatch_threads_num[2] == 0;
const uint64_t launched_threads =
thread_dimensions ? group_x : static_cast<uint64_t>(group_x) * input.threads_num[0];
if (!supported_shape || !dimensions_match || metadata.Stride() == 0 ||
launched_threads != metadata.NumRecords()) {
EXIT("HTile compute clear does not cover the complete metadata surface\n");
}
}
static bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode) {
const auto& ctx = buffer.GetRegisters();
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
if (resources.buffers.size() != program.info.buffers.size()) {
EXIT("compute runtime buffer count does not match shader metadata\n");
}
const auto& z = ctx.GetDepthRenderTarget();
const uint64_t meta_addr = z.htile_data_base_addr;
auto& cache = GetRenderContext().GetTextureCache();
uint32_t current_references = 0;
uint32_t registered_writes = 0;
uint64_t described_meta_size = 0;
HtileClearTarget registered_target {};
TextureCache::MetaRangeInfo registered_meta {};
auto& cache = buffer.GetContext().GetTextureCache();
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i];
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
const auto descriptor_size = BufferDescriptorSize(descriptor);
if (meta_addr != 0 && descriptor.Base48() == meta_addr) {
current_references++;
described_meta_size = descriptor_size;
}
// An exact registered metadata range remains
// identifiable even when it is no longer the currently bound depth target.
TextureCache::MetaRangeInfo resolved_meta {};
if (resource.written &&
cache.ResolveMetaRange(descriptor.Base48(), descriptor_size, resolved_meta)) {
registered_writes++;
registered_target = {.address = descriptor.Base48(), .size = descriptor_size};
registered_meta = resolved_meta;
if (!resource.written && cache.IsMeta(descriptor.Base48())) {
return false;
}
}
if (current_references == 0 && registered_writes == 0) {
return false;
}
if (current_references > 1 || (current_references == 0 && registered_writes > 1)) {
EXIT("HTile clear has ambiguous metadata descriptors: current=%u registered=%u\n",
current_references, registered_writes);
}
HtileClearTarget target {};
if (current_references != 0) {
if (!ResolveHtileClearTarget(z, described_meta_size, target)) {
EXIT("unsupported HTile compute-clear target state: current=%u registered=%u "
"meta=0x%016" PRIx64 "+0x%016" PRIx64 " depth=0x%016" PRIx64 "/0x%016" PRIx64
" stencil=0x%016" PRIx64 "/0x%016" PRIx64
" extent=%d:%ux%u wh=%d:%ux%u pitch=%d:%u/%u/%u zfmt=%u sfmt=%u samples=%u\n",
current_references, registered_writes, meta_addr, described_meta_size,
z.z_read_base_addr, z.z_write_base_addr, z.stencil_read_base_addr,
z.stencil_write_base_addr, z.size.valid, static_cast<uint32_t>(z.size.x_max) + 1u,
static_cast<uint32_t>(z.size.y_max) + 1u, z.width_height_valid, z.width, z.height,
z.pitch_height_valid, z.pitch_div8_minus1, z.height_div8_minus1,
z.slice_div64_minus1, z.z_info.format, z.stencil_info.format,
z.z_info.num_samples);
}
cache.RegisterMeta(target.address, target.size);
if (!cache.ResolveMetaRange(target.address, target.size, registered_meta)) {
EXIT("failed to resolve registered HTile compute-clear range\n");
}
} else {
target = registered_target;
}
GetRenderContext().GetBufferCache().ValidateGpuAccess(target.address, target.size, false, true);
uint32_t metadata_writes = 0;
ShaderBufferResource metadata_descriptor {};
if (!program.info.images.empty() || !program.info.samplers.empty() ||
!program.info.addresses.empty()) {
EXIT("HTile clear with non-buffer resources is unsupported: images=%zu samplers=%zu "
"addresses=%zu\n",
program.info.images.size(), program.info.samplers.size(),
program.info.addresses.size());
}
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i];
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
const auto descriptor_size = BufferDescriptorSize(descriptor);
if (descriptor.Base48() == target.address) {
if (!resource.written || resource.read || resource.atomic ||
descriptor_size != target.size || descriptor.SwizzleEnabled() ||
descriptor.IndexStride() != 0 || descriptor.AddTid() ||
resource.packed_stride != descriptor.PackedStride()) {
EXIT("unsupported HTile compute metadata access\n");
if (!program.info.has_bitwise_xor) {
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i];
if (resource.written) {
const auto descriptor =
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
if (cache.ClearMeta(descriptor.Base48())) {
return true;
}
}
metadata_descriptor = descriptor;
metadata_writes++;
continue;
}
if (resource.written || !resource.read || resource.atomic || descriptor.Base48() == 0 ||
descriptor_size == 0 ||
cache.QueryRegion(descriptor.Base48(), descriptor_size).metadata_pages) {
EXIT("unsupported HTile clear side-buffer access\n");
}
GetRenderContext().GetBufferCache().ValidateGpuAccess(descriptor.Base48(), descriptor_size,
true, false);
}
if (metadata_writes != 1) {
EXIT("HTile clear requires exactly one write-only metadata buffer, writes=%u\n",
metadata_writes);
}
ValidateFullHtileClearDispatch(input, metadata_descriptor, group_x, group_y, group_z, mode);
const bool recorded = registered_meta.full ? cache.ClearMeta(registered_meta.metadata_address)
: cache.TouchMeta(registered_meta.metadata_address,
registered_meta.slice, true);
if (!recorded) {
EXIT("failed to record HTile compute clear\n");
}
return true;
return false;
}
bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
@@ -324,7 +137,7 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
size)) {
return false;
}
auto& cache = GetRenderContext().GetTextureCache();
auto& cache = command.GetContext().GetTextureCache();
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
return false;
}
@@ -337,8 +150,9 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
return true;
}
void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode) {
void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode) {
EXIT_IF(buffer.IsInvalid());
auto& ctx = buffer.GetRegisters();
auto& sh_ctx = buffer.GetShaders();
@@ -347,8 +161,7 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
thread_group_x, thread_group_y, thread_group_z, mode,
sh_ctx.GetCs().cs_regs.data_addr);
Common::LockGuard lock(GetRenderContext().GetMutex());
Common::LockGuard lock(m_context.GetMutex());
if (sh_ctx.GetCs().cs_regs.data_addr == 0) {
LOGF("GraphicsRenderDispatchDirect: temporary: ignoring dispatch with null CS shader, "
"groups=%ux%ux%u mode=%u\n",
@@ -396,17 +209,18 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
input_info.dispatch_threads_num[2] = thread_group_z;
}
const uint32_t frame_num = GraphicsRunGetFrameNum();
const uint32_t frame_num = static_cast<uint32_t>(m_context.GetGpu().GetFrameNum());
const bool large_workgroup =
(input_info.threads_num[0] * input_info.threads_num[1] * input_info.threads_num[2] >= 512);
const auto& program = *input_info.stage.program;
const auto& resources = *input_info.stage.resources;
if (TryConsumeComputeMetaClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
if (TryConsumeComputeMetaClear(input_info, buffer)) {
ResetBindings();
return;
}
if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
ResetBindings();
return;
}
const auto sampled_images = std::count_if(
@@ -501,15 +315,13 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
return;
}
auto& pipeline = GetRenderContext().GetPipelineCache().CreateComputePipeline(
input_info, sh_ctx.GetCs(), cs_shader);
auto bindings = PrepareBindings(buffer, input_info.stage,
vk::ShaderStageFlagBits::eCompute,
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
auto bindings = PrepareBindings(buffer, input_info.stage, vk::ShaderStageFlagBits::eCompute,
DescriptorCache::Stage::Compute);
RebindBuffers(buffer, bindings);
RebindImages(buffer, bindings);
DescriptorCache::PreparedBindings* binding_sets[] = {&bindings};
ActivateImageWrites(binding_sets);
auto vk_buffer = buffer.Handle();
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
@@ -518,17 +330,17 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
has_storage_writes =
std::any_of(
program.info.images.begin(), program.info.images.end(),
[](const auto& image) {
return image.written &&
(image.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
image.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint);
}) ||
std::any_of(program.info.images.begin(), program.info.images.end(),
[](const auto& image) {
return image.written &&
(image.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
image.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint);
}) ||
has_storage_writes;
if (has_storage_writes) {
ShaderWriteBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
}
ResetBindings();
}
} // namespace Libs::Graphics
@@ -2,6 +2,8 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/presentation/videoOut.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
@@ -10,24 +12,66 @@
namespace Libs::Graphics {
RenderContext::RenderContext(GraphicContext& graphics)
: m_graphics(graphics), m_pipeline_cache(graphics), m_descriptor_cache(graphics),
m_framebuffer_cache(graphics), m_sampler_cache(graphics), m_gds_buffer(graphics),
m_gpu_resources(graphics) {
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
m_descriptor_cache(graphics), m_pipeline_cache(graphics, m_descriptor_cache),
m_sampler_cache(graphics),
m_gpu_resources(graphics, m_command_scheduler) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
RenderContext::~RenderContext() {
ShutdownGpu();
m_command_scheduler.Shutdown();
}
void RenderContext::InitializeGpu(VideoOut::VideoOutDriver* video_out) {
EXIT_IF(m_gpu != nullptr);
m_video_out = video_out;
m_gpu = std::make_unique<Gpu>(*this);
m_gpu_resources.SetGpu(m_gpu.get());
}
void RenderContext::ShutdownGpu() {
if (m_gpu != nullptr) {
m_gpu_resources.SetGpu(nullptr);
m_gpu->Shutdown();
m_gpu.reset();
}
if (m_video_out != nullptr) {
if (m_command_scheduler.Active()) {
m_command_scheduler.FinishCurrent();
}
m_command_scheduler.DrainPriorityOperations();
m_video_out = nullptr;
}
}
Gpu& RenderContext::GetGpu() const {
EXIT_IF(m_gpu == nullptr);
return *m_gpu;
}
VideoOut::VideoOutDriver& RenderContext::GetVideoOut() const {
EXIT_IF(m_video_out == nullptr);
return *m_video_out;
}
void RenderContext::AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
auto queue = LibKernel::EventQueue::KernelPinEqueue(eq);
if (!queue) {
return;
}
Common::LockGuard lock(m_eop_mutex);
auto it = std::find_if(m_eop_eqs.begin(), m_eop_eqs.end(), [eq, id](const auto& entry) {
return entry.eq == eq && entry.id == id;
});
if (it != m_eop_eqs.end()) {
it->count++;
return;
}
m_eop_eqs.push_back({eq, id, 1});
m_eop_eqs.push_back({eq, std::move(queue), id});
}
void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
@@ -40,22 +84,27 @@ void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id)
return;
}
if (--it->count == 0) {
m_eop_eqs.erase(it);
}
m_eop_eqs.erase(it);
}
void RenderContext::TriggerEopEvent(uint32_t context_id) {
Common::LockGuard lock(m_eop_mutex);
std::vector<EopEqRegistration> registrations;
{
Common::LockGuard lock(m_eop_mutex);
registrations = m_eop_eqs;
}
for (auto& eop_entry: m_eop_eqs) {
if (eop_entry.eq != nullptr) {
const auto id = static_cast<uintptr_t>(eop_entry.id);
auto result = LibKernel::EventQueue::KernelTriggerEvent(
eop_entry.eq, id, LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
for (const auto& registration: registrations) {
const auto result = LibKernel::EventQueue::KernelTriggerEvent(
registration.eq, static_cast<uintptr_t>(registration.id),
LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
if (result == LibKernel::KERNEL_ERROR_EBADF ||
result == LibKernel::KERNEL_ERROR_ENOENT) {
DeleteEopEq(registration.eq, registration.id);
continue;
}
EXIT_NOT_IMPLEMENTED(result != OK);
}
auto tsc = LibKernel::KernelReadTsc();
+25 -19
View File
@@ -8,38 +8,45 @@
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/gdsBuffer.h"
#include "graphics/host_gpu/renderer/gpuResourceManager.h"
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "graphics/host_gpu/renderer/samplerCache.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "kernel/eventQueue.h"
#include <memory>
#include <vector>
namespace Libs::VideoOut {
class VideoOutDriver;
}
namespace Libs::Graphics {
constexpr int AGC_USER_INTERRUPT_EVENT = 0x1800;
class Gpu;
class RenderContext {
public:
explicit RenderContext(GraphicContext& graphics);
~RenderContext() { KYTY_NOT_IMPLEMENTED; }
~RenderContext();
KYTY_CLASS_NO_COPY(RenderContext);
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
void InitializeGpu(VideoOut::VideoOutDriver* video_out);
void ShutdownGpu();
[[nodiscard]] Gpu& GetGpu() const;
[[nodiscard]] VideoOut::VideoOutDriver& GetVideoOut() const;
Common::Mutex& GetMutex() { return m_mutex; }
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
DescriptorCache& GetDescriptorCache() { return m_descriptor_cache; }
FramebufferCache& GetFramebufferCache() { return m_framebuffer_cache; }
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
GdsBuffer& GetGdsBuffer() { return m_gds_buffer; }
GpuResourceManager& GetGpuResources() { return m_gpu_resources; }
BufferCache& GetBufferCache() { return m_gpu_resources.GetBufferCache(); }
TextureCache& GetTextureCache() { return m_gpu_resources.GetTextureCache(); }
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
void AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
void DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
@@ -47,27 +54,26 @@ public:
private:
struct EopEqRegistration {
LibKernel::EventQueue::KernelEqueue eq = nullptr;
int id = 0;
uint32_t count = 0;
LibKernel::EventQueue::KernelEqueue eq = LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
LibKernel::EventQueue::KernelEqueueRef queue;
int id = 0;
};
GraphicContext& m_graphics;
Common::Mutex m_mutex;
CommandScheduler m_command_scheduler;
PipelineCache m_pipeline_cache;
DescriptorCache m_descriptor_cache;
FramebufferCache m_framebuffer_cache;
SamplerCache m_sampler_cache;
GdsBuffer m_gds_buffer;
GpuResourceManager m_gpu_resources;
GraphicContext& m_graphics;
Common::Mutex m_mutex;
RenderExecutor m_render_executor;
CommandScheduler m_command_scheduler;
DescriptorCache m_descriptor_cache;
PipelineCache m_pipeline_cache;
SamplerCache m_sampler_cache;
GpuResourceManager m_gpu_resources;
std::unique_ptr<Gpu> m_gpu;
VideoOut::VideoOutDriver* m_video_out = nullptr;
Common::Mutex m_eop_mutex;
std::vector<EopEqRegistration> m_eop_eqs;
};
[[nodiscard]] RenderContext& GetRenderContext() noexcept;
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERCONTEXT_H_
+271 -264
View File
@@ -13,18 +13,15 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h"
@@ -36,6 +33,7 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <cmath>
#include <cstring>
#include <limits>
@@ -72,13 +70,9 @@ static std::atomic<uint32_t> g_mrt_state_log_count = 0;
static std::atomic<uint32_t> g_shader_stage_log_count = 0;
static std::atomic<uint32_t> g_framebuffer_skip_log_count = 0;
static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset);
static const char* RenderColorTypeName(RenderColorType type) {
switch (type) {
case RenderColorType::NoColorOutput: return "NoColorOutput";
case RenderColorType::DisplayBuffer: return "DisplayBuffer";
case RenderColorType::RenderTexture: return "RenderTexture";
default: return "Unknown";
}
@@ -107,8 +101,8 @@ static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& col
" color_image=%s depth_format=%s depth_image=%s depth_vaddr_num=%d target_mask=0x%08" PRIx32
" prim=%u index_count=%u flags=0x%08" PRIx32 "\n",
log_id, draw_name, RenderColorTypeName(color.type), color.base_addr, color.buffer_size,
color.vulkan_buffer != nullptr ? "yes" : "no", VulkanToString(depth.format).c_str(),
depth.vulkan_buffer != nullptr ? "yes" : "no", depth.vaddr_num, ctx.GetRenderTargetMask(),
color.image_id ? "yes" : "no", VulkanToString(depth.format).c_str(),
depth.image_id ? "yes" : "no", depth.vaddr_num, ctx.GetRenderTargetMask(),
ucfg.GetPrimType(), index_count, flags);
}
@@ -183,7 +177,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
}
auto log_id = g_draw_state_log_count.fetch_add(1);
if (log_id >= 192 && color.type != RenderColorType::DisplayBuffer) {
if (log_id >= 192) {
return;
}
@@ -199,7 +193,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint;
});
vk::Extent2D extent = color.vulkan_buffer != nullptr ? color.extent : vk::Extent2D {};
vk::Extent2D extent = color.image_id ? color.extent : vk::Extent2D {};
auto sc = calc_final_scissor(vp, ctx.GetScanModeControl(), extent);
LOGF(
@@ -209,12 +203,13 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
" blend=%s src=%u dst=%u comb=%u ps_tex=%d sampled=%d storage=%d ps_kill=%s target_mode0=%u"
" depth_test=%s depth_write=%s depth_func=%u depth_clear=%s viewport=(%.1f,%.1f %.1fx%.1f) "
"scissor=(%d,%d)-(%d,%d)\n",
log_id, GraphicsRunGetFrameNum(), draw_name, RenderColorTypeName(color.type),
color.base_addr, extent.width, extent.height, ucfg.GetPrimType(), index_count, flags,
ctx.GetRenderTargetMask(), color.color_clear_enable ? "true" : "false",
color.color_clear_value.float32[0], color.color_clear_value.float32[1],
color.color_clear_value.float32[2], color.color_clear_value.float32[3], cc.mode, cc.op,
bc.enable ? "true" : "false", bc.color_srcblend, bc.color_destblend, bc.color_comb_fcn,
log_id, buffer.GetContext().GetGpu().GetFrameNum(), draw_name,
RenderColorTypeName(color.type), color.base_addr, extent.width, extent.height,
ucfg.GetPrimType(), index_count, flags, ctx.GetRenderTargetMask(),
color.color_clear_enable ? "true" : "false", color.color_clear_value.float32[0],
color.color_clear_value.float32[1], color.color_clear_value.float32[2],
color.color_clear_value.float32[3], cc.mode, cc.op, bc.enable ? "true" : "false",
bc.color_srcblend, bc.color_destblend, bc.color_comb_fcn,
static_cast<int>(ps_resources.images.size()), static_cast<int>(sampled_images),
static_cast<int>(ps_resources.images.size() - sampled_images),
ps_input_info.ps_pixel_kill_enable ? "true" : "false", ps_input_info.target_output_mode[0],
@@ -226,20 +221,21 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
LogMrtState(draw_name, buffer, ps_input_info);
}
static void LogDrawInputState(const RenderColorInfo& color,
static void LogDrawInputState(const RenderCommandBuffer& buffer,
const RenderColorInfo& color,
const ShaderVertexInputInfo& vs_input_info,
uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr) {
auto log_id = g_draw_input_log_count.fetch_add(1);
if (log_id >= 64 && color.type != RenderColorType::DisplayBuffer) {
if (log_id >= 64) {
return;
}
LOGF("DrawInputState[%u]: frame=%d target=%s addr=0x%010" PRIx64
" index_type=%u index_count=%u index_addr=0x%016" PRIx64
" vs_resources=%d vs_buffers=%d\n",
log_id, GraphicsRunGetFrameNum(), RenderColorTypeName(color.type), color.base_addr,
index_type_and_size, index_count, reinterpret_cast<uint64_t>(index_addr),
log_id, buffer.GetContext().GetGpu().GetFrameNum(), RenderColorTypeName(color.type),
color.base_addr, index_type_and_size, index_count, reinterpret_cast<uint64_t>(index_addr),
vs_input_info.resources_num, vs_input_info.buffers_num);
for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
@@ -305,15 +301,6 @@ static void LogDrawInputState(const RenderColorInfo& color,
}
}
static void VulkanCmdSetColorWriteEnableEXT(vk::CommandBuffer command_buffer,
uint32_t attachment_count,
const vk::Bool32* p_color_write_enables) {
if (VULKAN_HPP_DEFAULT_DISPATCHER.vkCmdSetColorWriteEnableEXT == nullptr) {
EXIT("vkCmdSetColorWriteEnableEXT not present\n");
}
command_buffer.setColorWriteEnableEXT(attachment_count, p_color_write_enables);
}
static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandBuffer& buffer,
const RenderColorInfo* colors,
uint32_t color_count,
@@ -327,10 +314,10 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
const auto& vp = ctx.GetScreenViewport();
vk::Extent2D framebuffer_extent {};
if (color_count > 0 && colors[0].vulkan_buffer != nullptr) {
if (color_count > 0 && colors[0].image_id) {
framebuffer_extent = colors[0].extent;
} else if (depth.vulkan_buffer != nullptr) {
framebuffer_extent = depth.vulkan_buffer->extent;
} else if (depth.image_id) {
framebuffer_extent = {depth.width, depth.height};
}
const auto final_scissor = calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent);
@@ -412,7 +399,9 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
for (uint32_t i = 0; i < dynamic_params.color_write_count; i++) {
enable[i] = (dynamic_params.color_write_enable[i] ? VK_TRUE : VK_FALSE);
}
VulkanCmdSetColorWriteEnableEXT(vk_buffer, dynamic_params.color_write_count, enable);
if (dynamic_params.color_write_count != 0) {
vk_buffer.setColorWriteEnableEXT(dynamic_params.color_write_count, enable);
}
}
static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) {
@@ -488,12 +477,11 @@ struct DrawRenderState {
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
bool ps_active = true;
VulkanFramebuffer* framebuffer = nullptr;
RenderState rendering;
ShaderVertexInputInfo vs_input_info;
ShaderPixelInputInfo ps_input_info;
std::span<const uint32_t> vs_shader;
std::span<const uint32_t> ps_shader;
std::vector<std::shared_ptr<void>> target_owners;
};
struct DrawCallInfo {
@@ -505,6 +493,123 @@ struct DrawCallInfo {
uint32_t first_instance = 0;
};
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth) {
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
auto& cache = m_context.GetTextureCache();
RenderState state {};
state.width = std::numeric_limits<uint32_t>::max();
state.height = std::numeric_limits<uint32_t>::max();
state.num_layers = std::numeric_limits<uint32_t>::max();
state.num_color_attachments = color_count;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
EXIT_IF(!target.image_id);
const auto old_image = cache.ResolveOwner(target.image_id);
if (old_image == nullptr ||
(!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id);
}
target.image_view = cache.FindRenderTarget(target.image_id, target.desc);
auto& image = cache.GetImage(target.image_id);
EXIT_IF(image.backing.samples != target.samples || target.image_view == nullptr);
if (attachment_samples == 0) {
attachment_samples = target.samples;
} else if (attachment_samples != target.samples) {
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, target.samples);
}
const auto& view = target.desc.view_info;
const auto layout =
image.binding.is_bound ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eColorAttachmentOptimal;
image.Transit(layout,
vk::AccessFlagBits2::eColorAttachmentRead |
vk::AccessFlagBits2::eColorAttachmentWrite,
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
view.layer_count},
buffer.Handle());
state.width = std::min(state.width, target.extent.width);
state.height = std::min(state.height, target.extent.height);
state.num_layers = std::min(state.num_layers, view.layer_count);
auto& attachment = state.color_attachments[i];
attachment.image_view = target.image_view;
attachment.image_layout = layout;
attachment.clear_value = target.color_clear_value.uint32;
attachment.is_clear = target.color_clear_enable;
}
if (depth.image_id) {
const auto owner = cache.ResolveOwner(depth.image_id);
if (owner == nullptr || !owner->registered || owner->binding.needs_rebind) {
EXIT("depth target changed after render-state discovery\n");
}
depth.image_view = cache.FindDepthTarget(depth.image_id, depth.desc);
if (depth.htile && depth.depth_clear_enable && !cache.ClearMeta(depth.htile_buffer_vaddr)) {
EXIT("failed to acquire HTile metadata for a depth clear\n");
}
depth.depth_meta_clear_enable =
depth.htile &&
cache.IsMetaCleared(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer);
depth.depth_load_clear_enable =
depth.depth_clear_enable || depth.depth_meta_clear_enable;
if (depth.depth_meta_clear_enable &&
!cache.TouchMeta(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer, false)) {
EXIT("failed to consume HTile clear state\n");
}
auto& image = cache.GetImage(depth.image_id);
EXIT_IF(depth.image_view == nullptr || image.backing.samples != depth.samples);
if (attachment_samples == 0) {
attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
attachment_samples, depth.samples);
}
const auto layout = depth_attachment_layout(depth);
const auto writes = depth.AttachmentWriteAspects();
auto access = vk::AccessFlags2 {vk::AccessFlagBits2::eDepthStencilAttachmentRead};
if (writes) {
access |= vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
}
const auto& view = depth.desc.view_info;
image.Transit(layout, access,
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
view.layer_count},
buffer.Handle());
state.width = std::min(state.width, depth.width);
state.height = std::min(state.height, depth.height);
state.num_layers = std::min(state.num_layers, view.layer_count);
const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
auto& attachment = state.depth_stencil_attachment;
attachment.image_view = depth.image_view;
attachment.image_layout = layout;
attachment.clear_value[0] = std::bit_cast<uint32_t>(depth.depth_clear_value);
attachment.clear_value[1] = depth.stencil_clear_value;
attachment.has_depth =
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth);
attachment.depth_clear = depth.depth_load_clear_enable;
attachment.has_stencil =
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
attachment.stencil_clear = depth.stencil_clear_enable;
}
if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
EXIT("render state has no valid attachments\n");
}
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
state.num_layers = 1;
}
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.width == std::numeric_limits<uint32_t>::max() ||
state.height == std::numeric_limits<uint32_t>::max());
return state;
}
static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer,
const DrawRenderState& state, const DrawCallInfo& draw) {
EXIT_IF(draw.name == nullptr);
@@ -512,7 +617,7 @@ static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer,
const auto& sh_ctx = buffer.GetShaders();
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
state.depth_info.vulkan_buffer != nullptr);
static_cast<bool>(state.depth_info.image_id));
if (state.color_count != 0 || !with_depth) {
return true;
}
@@ -561,13 +666,13 @@ struct DrawIndexBufferSource {
};
struct PreparedIndexBuffer {
std::shared_ptr<VulkanBuffer> buffer;
const void* host_data = nullptr;
std::vector<uint8_t> owned_data;
uint64_t address = 0;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
uint64_t address = 0;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
bool streamed = false;
};
static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer) {
@@ -625,26 +730,16 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use
return true;
}
static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, uint32_t render_target_slice_offset,
bool skip_null_framebuffer, bool log_setup_phases,
DrawRenderState& state) {
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state) {
EXIT_IF(draw.name == nullptr);
auto& ctx = buffer.GetRegisters();
if (log_setup_phases) {
LogDrawPhase(draw.name, "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
if (ResolveColorTargets(submit_id, buffer, render_target_slice_offset)) {
return false;
}
if (state.depth_info.vulkan_buffer != nullptr) {
state.target_owners.push_back(
GetRenderContext().GetTextureCache().GetImageOwner(*state.depth_info.vulkan_buffer));
}
if (log_setup_phases) {
LogDrawPhase(draw.name, "ResolveRenderColorTarget");
}
@@ -653,20 +748,18 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
ctx.GetRenderTarget(slot).base.addr != 0)) {
ResolveRenderColorTarget(submit_id, buffer, state.color_info[state.color_count],
render_target_slice_offset, slot);
if (state.color_info[state.color_count].vulkan_buffer != nullptr) {
if (state.color_info[state.color_count].type == RenderColorType::RenderTexture) {
state.target_owners.push_back(GetRenderContext()
.GetTextureCache()
.GetImageOwner(*state.color_info[state.color_count]
.vulkan_buffer));
}
if (state.color_info[state.color_count].image_id) {
state.color_count++;
}
}
}
if (log_setup_phases) {
LogDrawPhase(draw.name, "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
state.depth_info.vulkan_buffer != nullptr);
static_cast<bool>(state.depth_info.image_id));
if (state.color_count == 0 && !with_depth) {
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
draw.index_count, draw.flags);
@@ -674,20 +767,6 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
}
state.ps_active = DrawHasActivePixelShader(buffer, state, draw);
if (log_setup_phases) {
LogDrawPhase(draw.name, "CreateFramebuffer");
}
state.framebuffer = GetRenderContext().GetFramebufferCache().CreateFramebuffer(
state.color_info, state.color_count, state.depth_info);
if (state.framebuffer == nullptr && skip_null_framebuffer) {
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
draw.index_count, draw.flags);
return false;
}
EXIT_NOT_IMPLEMENTED(state.framebuffer == nullptr);
EXIT_NOT_IMPLEMENTED(state.framebuffer->render_pass == nullptr);
return true;
}
@@ -731,9 +810,10 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
}
}
static std::vector<BufferBinding>
PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer, const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) {
static std::vector<BufferBinding> PrepareVertexBuffers(uint64_t submit_id,
RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(draw.name == nullptr);
(void)submit_id;
@@ -744,100 +824,80 @@ PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer, const Draw
const auto& b = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(b);
if (size == 0) {
bindings.push_back({GetRenderContext().GetBufferCache().ObtainNullBuffer(), 0});
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
bindings.push_back({owner, owner->Handle(), 0});
} else {
bindings.push_back(
GetRenderContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
}
}
return bindings;
}
static void RebindVertexBuffers(RenderCommandBuffer& buffer,
static void RebindVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info,
std::vector<BufferBinding>& bindings) {
std::vector<BufferBinding>& bindings) {
EXIT_IF(bindings.size() != static_cast<size_t>(vs_input_info.buffers_num));
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
bindings[i] = size == 0
? BufferBinding {GetRenderContext().GetBufferCache().ObtainNullBuffer(), 0}
: GetRenderContext().GetBufferCache().ObtainBuffer(buffer, vertex.addr, size);
if (size == 0) {
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
bindings[i] = {owner, owner->Handle(), 0};
} else {
bindings[i] =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, vertex.addr, size);
}
}
}
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
const DrawIndexBufferSource& source) {
PreparedIndexBuffer prepared;
if (!source.enabled) {
return prepared;
}
EXIT_IF(source.size == 0);
prepared.host_data = source.host_data;
prepared.address = source.address;
prepared.size = source.size;
prepared.type = source.type;
prepared.address = source.address;
prepared.size = source.size;
prepared.type = source.type;
if (source.host_data != nullptr) {
return prepared;
auto binding =
buffer.GetContext().GetBufferCache().UploadTransient(source.host_data, source.size, 16);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
prepared.streamed = true;
} else {
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(
buffer, source.address, source.size);
prepared.buffer = std::move(binding.buffer);
prepared.owned_data = std::move(binding.host_data);
prepared.offset = binding.offset;
auto binding =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
}
return prepared;
}
static void RebindIndexBuffer(RenderCommandBuffer& buffer, PreparedIndexBuffer& prepared) {
if (prepared.size == 0 || prepared.host_data != nullptr) {
if (prepared.size == 0 || prepared.streamed) {
return;
}
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(
buffer, prepared.address, prepared.size);
prepared.buffer = std::move(binding.buffer);
prepared.owned_data = std::move(binding.host_data);
prepared.offset = binding.offset;
}
static void RevalidateDrawTargets(DrawRenderState& state) {
auto old_owners = std::move(state.target_owners);
std::vector<std::shared_ptr<void>> current_owners;
auto validate = [&](VulkanImage& image) {
auto owner = GetRenderContext().GetTextureCache().GetImageOwner(image);
const auto found = std::find_if(old_owners.begin(), old_owners.end(), [&](const auto& old) {
return old.get() == owner.get();
});
EXIT_IF(found == old_owners.end());
current_owners.push_back(std::move(owner));
};
if (state.depth_info.vulkan_buffer != nullptr) {
validate(*state.depth_info.vulkan_buffer);
}
for (uint32_t i = 0; i < state.color_count; i++) {
if (state.color_info[i].type == RenderColorType::RenderTexture) {
validate(*state.color_info[i].vulkan_buffer);
}
}
state.target_owners = std::move(current_owners);
auto binding =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, prepared.address, prepared.size);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
}
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
std::vector<BufferBinding>& bindings) {
for (uint32_t slot = 0; slot < bindings.size(); slot++) {
auto& binding = bindings[slot];
VulkanBuffer* vertex_buffer = binding.buffer.get();
vk::DeviceSize vertex_offset = binding.offset;
if (!binding.host_data.empty()) {
vk::DeviceSize range = 0;
EXIT_IF(!GetRenderContext().GetBufferCache().UploadHostData(
buffer, binding.host_data.data(), binding.host_data.size(), 16, vertex_buffer,
vertex_offset, range));
} else {
buffer.RetainResourceUntilFence(binding.buffer);
auto& binding = bindings[slot];
if (binding.owner != nullptr) {
buffer.RetainResourceUntilFence(binding.owner);
}
EXIT_IF(vertex_buffer == nullptr);
vk_buffer.bindVertexBuffers(slot, 1, &vertex_buffer->buffer, &vertex_offset);
EXIT_IF(binding.buffer == nullptr);
vk_buffer.bindVertexBuffers(slot, 1, &binding.buffer, &binding.offset);
}
}
@@ -846,23 +906,11 @@ static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_
if (prepared.size == 0) {
return;
}
VulkanBuffer* index_buffer = prepared.buffer.get();
vk::DeviceSize index_offset = prepared.offset;
const void* host_data = prepared.host_data;
if (host_data == nullptr && !prepared.owned_data.empty()) {
host_data = prepared.owned_data.data();
if (prepared.owner != nullptr) {
buffer.RetainResourceUntilFence(prepared.owner);
}
if (host_data != nullptr) {
vk::DeviceSize range = 0;
if (!GetRenderContext().GetBufferCache().UploadHostData(
buffer, host_data, prepared.size, 16, index_buffer, index_offset, range)) {
EXIT("failed to upload host index buffer\n");
}
} else {
buffer.RetainResourceUntilFence(prepared.buffer);
}
EXIT_IF(index_buffer == nullptr);
vk_buffer.bindIndexBuffer(index_buffer->buffer, index_offset, prepared.type);
EXIT_IF(prepared.buffer == nullptr);
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
}
static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw,
@@ -883,7 +931,7 @@ static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCa
LogDrawTargetState(draw.name, state.color_info[0], state.depth_info, buffer,
state.ps_input_info, draw.index_count, draw.flags);
}
LogDrawInputState(state.color_info[0], state.vs_input_info, index_type_and_size,
LogDrawInputState(buffer, state.color_info[0], state.vs_input_info, index_type_and_size,
draw.index_count, index_addr);
// LogDrawTextureState(draw.name, state.color_info[0], state.ps_input_info);
}
@@ -957,63 +1005,35 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
}
}
static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug) {
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source,
bool log_pipeline_phase, bool set_bind_debug,
bool set_auto_debug) {
EXIT_IF(draw.name == nullptr);
auto& ucfg = buffer.GetUserConfig();
LogDrawPhase(draw.name, "PrepareBindings");
auto bindings = PrepareGraphicsBindings(buffer, state.vs_input_info.stage,
state.ps_input_info.stage, state.ps_active);
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source);
RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings);
RebindIndexBuffer(buffer, index_binding);
state.rendering =
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info);
if (log_pipeline_phase) {
LogDrawPhase(draw.name, "CreatePipeline");
}
auto& pipeline = GetRenderContext().GetPipelineCache().CreateGraphicsPipeline(
*state.framebuffer, state.color_info, state.color_count, state.depth_info,
state.vs_input_info, buffer, &state.ps_input_info, topology, state.ps_active,
state.vs_shader, state.ps_shader);
LogDrawPhase(draw.name, "PrepareBindingsVS");
auto vs_bindings = PrepareBindings(buffer, state.vs_input_info.stage,
vk::ShaderStageFlagBits::eVertex,
DescriptorCache::Stage::Vertex);
std::optional<DescriptorCache::PreparedBindings> ps_bindings;
if (state.ps_active) {
LogDrawPhase(draw.name, "PrepareBindingsPS");
ps_bindings.emplace(PrepareBindings(buffer, state.ps_input_info.stage,
vk::ShaderStageFlagBits::eFragment,
DescriptorCache::Stage::Pixel));
}
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source);
// The discovery pass above can merge cache allocations. Rebind every buffer only after the
// complete draw resource set is known.
RebindBuffers(buffer, vs_bindings);
if (ps_bindings.has_value()) {
RebindBuffers(buffer, *ps_bindings);
}
RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings);
RebindIndexBuffer(buffer, index_binding);
RebindImages(buffer, vs_bindings);
if (ps_bindings.has_value()) {
RebindImages(buffer, *ps_bindings);
}
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
state.color_info, state.color_count, state.depth_info, state.vs_input_info, buffer,
&state.ps_input_info, topology, state.ps_active, state.vs_shader, state.ps_shader);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
// memory.
RevalidateDrawTargets(state);
DescriptorCache::PreparedBindings* binding_sets[] = {
&vs_bindings, ps_bindings.has_value() ? &*ps_bindings : nullptr};
ActivateImageWrites(binding_sets);
MarkRenderTargetGpuWritten(state.depth_info);
for (uint32_t i = 0; i < state.color_count; i++) {
MarkRenderTargetGpuWritten(state.color_info[i]);
}
for (const auto& owner: state.target_owners) {
buffer.RetainResourceUntilFence(owner);
}
auto vk_buffer = buffer.Handle();
if (set_bind_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x100u);
@@ -1023,26 +1043,25 @@ static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
}
CommitVertexBuffers(buffer, vk_buffer, vertex_bindings);
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
vs_bindings);
if (ps_bindings.has_value()) {
bindings.vertex);
if (bindings.pixel.has_value()) {
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
*ps_bindings);
*bindings.pixel);
}
CommitIndexBuffer(buffer, vk_buffer, index_binding);
const auto dynamic_params = BuildGraphicsDynamicParams(buffer, state.color_info,
state.color_count, state.depth_info);
const auto dynamic_params =
BuildGraphicsDynamicParams(buffer, state.color_info, state.color_count, state.depth_info);
SetDynamicParams(buffer, vk_buffer, dynamic_params);
LogDrawPhase(draw.name, "BeginRenderPass");
LogDrawPhase(draw.name, "BeginRendering");
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x400u);
}
buffer.BeginRenderPass(*state.framebuffer, state.color_info, state.color_count,
state.depth_info);
m_context.GetCommandScheduler().BeginRendering(state.rendering);
vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
@@ -1052,7 +1071,6 @@ static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
}
buffer.EndRenderPass();
vk::PipelineStageFlags shader_write_stages = {};
if (HasShaderBufferWrites(state.vs_input_info.stage)) {
shader_write_stages |= vk::PipelineStageFlagBits::eVertexShader;
@@ -1061,18 +1079,20 @@ static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
}
if (shader_write_stages) {
m_context.GetCommandScheduler().EndRendering();
ShaderWriteBarrier(vk_buffer, shader_write_stages);
}
LogDrawPhase(draw.name, "EndRenderPass");
LogDrawPhase(draw.name, "DrawComplete");
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x700u);
}
}
void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count, uint32_t render_target_slice_offset,
int32_t vertex_offset_add, uint32_t first_instance) {
void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count, uint32_t render_target_slice_offset,
int32_t vertex_offset_add, uint32_t first_instance) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid());
@@ -1083,13 +1103,13 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
index_count, flags, type, instance_count,
reinterpret_cast<uint64_t>(index_addr));
Common::LockGuard lock(GetRenderContext().GetMutex());
Common::LockGuard lock(m_context.GetMutex());
if (index_count == 0) {
return;
}
if (ConsumeMetadataColorOperation(buffer)) {
ResetBindings();
return;
}
@@ -1181,8 +1201,8 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
index_source.type = index_type;
DrawRenderState state {};
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false, true,
state)) {
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, true, state)) {
ResetBindings();
return;
}
@@ -1199,12 +1219,15 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, true, true,
false);
ResetBindings();
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset,
uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset,
uint32_t instance_count, uint32_t first_vertex,
uint32_t first_instance) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid());
@@ -1214,13 +1237,13 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DrawIndexAuto), submit_id,
index_count, flags, first_vertex, instance_count, first_instance);
Common::LockGuard lock(GetRenderContext().GetMutex());
Common::LockGuard lock(m_context.GetMutex());
if (index_count == 0) {
return;
}
if (ConsumeMetadataColorOperation(buffer)) {
ResetBindings();
return;
}
@@ -1262,8 +1285,8 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
instance_count, first_instance};
DrawRenderState state {};
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, true, false,
state)) {
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false, state)) {
ResetBindings();
return;
}
@@ -1271,6 +1294,7 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
const bool use_ngg_rectlist_draw = Config::NggRectlistDrawEnabled();
if (!GetDrawTopology(ucfg, true, use_ngg_rectlist_draw, topology)) {
ResetBindings();
return;
}
const bool draw_prim7_as_ngg =
@@ -1287,6 +1311,7 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
state.ps_input_info.input_num, sh_ctx.GetPs().ps_regs.chksum,
sh_ctx.GetVs().es_regs.data_addr, sh_ctx.GetVs().gs_regs.data_addr);
}
ResetBindings();
return;
}
@@ -1306,27 +1331,11 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
DrawIndexBufferSource index_source {};
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false,
true);
ResetBindings();
}
bool IsSameColorResolveSubresource(const RenderColorInfo& src, const RenderColorInfo& dst) {
return src.base_addr == dst.base_addr && src.base_mip_level == dst.base_mip_level &&
src.base_array_layer == dst.base_array_layer;
}
ImageImageCopy MakeColorResolveCopy(const RenderColorInfo& src, const RenderColorInfo& dst,
uint32_t width, uint32_t height) {
ImageImageCopy region {*src.vulkan_buffer};
region.src_level = src.base_mip_level;
region.dst_level = dst.base_mip_level;
region.width = width;
region.height = height;
region.src_layer = src.base_array_layer;
region.dst_layer = dst.base_array_layer;
return region;
}
static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset) {
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset) {
const auto& hw = buffer.GetRegisters();
if (hw.GetColorControl().mode != 3) {
return false;
@@ -1341,25 +1350,23 @@ static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo src {};
RenderColorInfo dst {};
ResolveRenderColorTarget(submit_id, buffer, src, render_target_slice_offset, 0, true, true);
ResolveRenderColorTarget(submit_id, buffer, dst, render_target_slice_offset, 1, true);
if (src.vulkan_buffer == nullptr || dst.vulkan_buffer == nullptr ||
src.type == RenderColorType::NoColorOutput || dst.type == RenderColorType::NoColorOutput) {
ResolveRenderColorTarget(submit_id, buffer, dst, render_target_slice_offset, 1, true, true);
if (!src.image_id || !dst.image_id || src.type == RenderColorType::NoColorOutput ||
dst.type == RenderColorType::NoColorOutput) {
return false;
}
if (IsSameColorResolveSubresource(src, dst)) {
if (src.base_addr == dst.base_addr && src.base_mip_level == dst.base_mip_level &&
src.base_array_layer == dst.base_array_layer) {
return true;
}
const uint32_t width = std::min(src.extent.width, dst.extent.width);
const uint32_t height = std::min(src.extent.height, dst.extent.height);
if (width == 0 || height == 0) {
return false;
}
const std::array regions {MakeColorResolveCopy(src, dst, width, height)};
MarkRenderTargetGpuWritten(dst);
Transfer::CopyImage(buffer, regions, *dst.vulkan_buffer, dst.vulkan_buffer->layout);
auto& cache = m_context.GetTextureCache();
cache.MarkGpuWritten(dst.image_id);
auto& source = cache.GetImage(src.image_id);
auto& destination = cache.GetImage(dst.image_id);
destination.Resolve(source,
{src.base_mip_level, 1, src.base_array_layer, 1},
{dst.base_mip_level, 1, dst.base_array_layer, 1});
return true;
}
+2 -8
View File
@@ -8,7 +8,6 @@
namespace Libs::Graphics {
struct ImageImageCopy;
struct RenderColorInfo;
struct ShaderVertexInputInfo;
@@ -39,13 +38,8 @@ static_assert(sizeof(PipelineDynamicParameters) ==
sizeof(uint32_t) + sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) +
sizeof(PipelineStencilDynamicState) * 2);
[[nodiscard]] bool IsSameColorResolveSubresource(const RenderColorInfo& src,
const RenderColorInfo& dst);
[[nodiscard]] ImageImageCopy MakeColorResolveCopy(const RenderColorInfo& src,
const RenderColorInfo& dst, uint32_t width,
uint32_t height);
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
const ShaderVertexInputInfo& vs_input_info);
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
const ShaderVertexInputInfo& vs_input_info);
} // namespace Libs::Graphics
@@ -3,6 +3,7 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <cstdint>
#include <type_traits>
@@ -10,6 +11,30 @@ namespace Libs::Graphics {
static constexpr uint32_t RENDER_COLOR_ATTACHMENTS_MAX = 8;
struct RenderAttachment {
vk::ImageView image_view = nullptr;
vk::ImageLayout image_layout = vk::ImageLayout::eUndefined;
std::array<uint32_t, 4> clear_value = {};
bool is_clear = false;
bool has_depth = false;
bool depth_clear = false;
bool has_stencil = false;
bool stencil_clear = false;
bool operator==(const RenderAttachment&) const = default;
};
struct RenderState {
std::array<RenderAttachment, RENDER_COLOR_ATTACHMENTS_MAX> color_attachments;
RenderAttachment depth_stencil_attachment;
uint32_t width = 0;
uint32_t height = 0;
uint32_t num_layers = 1;
uint32_t num_color_attachments = 0;
bool operator==(const RenderState&) const = default;
};
[[nodiscard]] inline constexpr uint32_t render_sample_count(uint32_t encoded_samples) {
return encoded_samples <= 3 ? 1u << encoded_samples : 0;
}
@@ -1,221 +0,0 @@
#include "graphics/host_gpu/renderer/renderTargetBarriers.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/threads.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <limits>
namespace Libs::Graphics {
void GraphicsRenderMemoryBarrier(CommandBuffer& buffer) {
EXIT_IF(buffer.IsInvalid());
Common::LockGuard lock(GetRenderContext().GetMutex());
auto vk_buffer = buffer.Handle();
VulkanMemoryBarrier mem_barrier {};
mem_barrier.sType = vk::StructureType::eMemoryBarrier;
mem_barrier.pNext = nullptr;
mem_barrier.srcAccessMask = vk::AccessFlagBits::eMemoryWrite;
mem_barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags {}, 1,
&mem_barrier, 0, nullptr, 0, nullptr);
}
void GraphicsRenderTextureBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image) {
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask =
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eMemoryRead;
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
image_memory_barrier.oldLayout = image.layout;
image_memory_barrier.newLayout = RENDER_COLOR_IMAGE_LAYOUT;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image.image;
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = image.layers;
vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eFragmentShader,
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eComputeShader,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
image.layout = image_memory_barrier.newLayout;
}
void GraphicsRenderColorImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
vk::ImageLayout new_layout) {
if (image.layout != new_layout) {
if (image.type == VulkanImageType::VideoOut &&
image.layout == vk::ImageLayout::eTransferDstOptimal) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 128) {
LOGF("GraphicsRenderColorImageBarrier: image=%p type=%d layout=%s -> %s\n",
VulkanHandleToPointer(image.image), static_cast<int>(image.type),
VulkanToString(image.layout).c_str(), VulkanToString(new_layout).c_str());
}
}
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask =
vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead;
image_memory_barrier.dstAccessMask =
vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead;
image_memory_barrier.oldLayout = image.layout;
image_memory_barrier.newLayout = new_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image.image;
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = image.layers;
const auto stages = static_cast<vk::PipelineStageFlags>(
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
vk::PipelineStageFlagBits::eTransfer);
vk_buffer.pipelineBarrier(stages, stages, vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
&image_memory_barrier);
image.layout = image_memory_barrier.newLayout;
}
}
void GraphicsRenderDepthStencilImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
vk::ImageLayout new_layout) {
EXIT_IF(image.type != VulkanImageType::DepthStencil);
if (new_layout == vk::ImageLayout::eUndefined ||
new_layout == vk::ImageLayout::ePreinitialized) {
EXIT("invalid destination depth/stencil image layout: %s\n",
VulkanToString(new_layout).c_str());
}
if (image.layout == new_layout) {
return;
}
vk::ImageMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eImageMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
barrier.oldLayout = image.layout;
barrier.newLayout = new_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image.image;
barrier.subresourceRange = {ImageViewOps::DepthAspectMask(image.format), 0,
VK_REMAINING_MIP_LEVELS, 0, image.layers};
const auto stages = static_cast<vk::PipelineStageFlags>(
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
vk::PipelineStageFlagBits::eTransfer);
vk_buffer.pipelineBarrier(stages, stages, vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
&barrier);
image.layout = new_layout;
}
void GraphicsRenderDepthStencilBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image) {
EXIT_IF(image.type != VulkanImageType::DepthStencil);
auto& depth = static_cast<DepthStencilVulkanImage&>(image);
if (depth.compressed) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF(
"DepthTexture: decompressing depth target for shader read format=%s extent=%ux%u\n",
VulkanToString(depth.format).c_str(), depth.extent.width, depth.extent.height);
}
depth.compressed = false;
}
if (image.layout != vk::ImageLayout::eDepthStencilReadOnlyOptimal) {
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
image_memory_barrier.oldLayout = image.layout;
image_memory_barrier.newLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image.image;
image_memory_barrier.subresourceRange.aspectMask =
ImageViewOps::DepthAspectMask(image.format);
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = image.layers;
vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eComputeShader,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
image.layout = image_memory_barrier.newLayout;
}
}
void GraphicsRenderTextureBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size) {
EXIT_IF(buffer.IsInvalid());
Common::LockGuard lock(GetRenderContext().GetMutex());
auto vk_buffer = buffer.Handle();
auto* native = GetRenderContext().GetTextureCache().FindRenderTargetByRange(buffer, vaddr, size);
if (native == nullptr) {
EXIT("render-target barrier range has no cached image\n");
}
GraphicsRenderTextureBarrier(vk_buffer, *native);
}
void GraphicsRenderDepthStencilBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size) {
EXIT_IF(buffer.IsInvalid());
Common::LockGuard lock(GetRenderContext().GetMutex());
auto vk_buffer = buffer.Handle();
auto* native = GetRenderContext().GetTextureCache().FindDepthTargetByRange(buffer, vaddr, size);
if (native == nullptr) {
EXIT("depth-target barrier range has no cached image\n");
}
GraphicsRenderDepthStencilBarrier(vk_buffer, *native);
}
} // namespace Libs::Graphics
@@ -1,19 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
#include "graphics/host_gpu/vulkanCommon.h"
namespace Libs::Graphics {
struct VulkanImage;
void GraphicsRenderTextureBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image);
void GraphicsRenderColorImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
vk::ImageLayout new_layout);
void GraphicsRenderDepthStencilImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
vk::ImageLayout new_layout);
void GraphicsRenderDepthStencilBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
@@ -7,6 +7,13 @@
namespace Libs::Graphics {
SamplerCache::~SamplerCache() {
for (const auto& [key, sampler]: m_samplers) {
(void)key;
m_graphics.device.destroySampler(sampler, nullptr);
}
}
vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
Common::LockGuard lock(m_mutex);
@@ -21,7 +21,7 @@ public:
explicit SamplerCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~SamplerCache() { KYTY_NOT_IMPLEMENTED; }
~SamplerCache();
KYTY_CLASS_NO_COPY(SamplerCache);
vk::Sampler GetSampler(const ShaderSamplerResource& r);
@@ -36,36 +36,8 @@ VulkanMemoryBarrier MakeShaderWriteDependency() {
return barrier;
}
vk::ImageMemoryBarrier MakeStorageImageDependency(const VulkanImage& image, bool read,
bool written) {
EXIT_IF(image.image == nullptr || image.type == VulkanImageType::DepthStencil);
vk::ImageMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eImageMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryWrite;
barrier.dstAccessMask = {};
if (read) {
barrier.dstAccessMask |= vk::AccessFlagBits::eShaderRead;
}
if (written) {
barrier.dstAccessMask |= vk::AccessFlagBits::eShaderWrite;
}
barrier.oldLayout = image.layout;
barrier.newLayout = vk::ImageLayout::eGeneral;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image.image;
barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = image.layers;
EXIT_IF(!barrier.dstAccessMask || barrier.subresourceRange.layerCount == 0);
return barrier;
}
vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer) {
EXIT_IF(buffer.buffer == nullptr);
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
EXIT_IF(buffer == nullptr);
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
@@ -74,7 +46,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer) {
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = buffer.buffer;
barrier.buffer = buffer;
barrier.offset = 0;
barrier.size = VK_WHOLE_SIZE;
return barrier;
@@ -19,9 +19,7 @@ struct ShaderBufferWriteRange {
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
VulkanMemoryBarrier MakeShaderWriteDependency();
vk::ImageMemoryBarrier MakeStorageImageDependency(const VulkanImage& image, bool read,
bool written);
vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer);
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources);
+31 -20
View File
@@ -385,7 +385,9 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
return vk::BlendOp::eAdd;
}
static void CreateLayout(std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
static void CreateLayout(DescriptorCache& descriptor_cache,
std::span<vk::DescriptorSetLayout> set_layouts,
uint32_t& set_layouts_num,
std::span<vk::PushConstantRange> push_constant_info,
uint32_t& push_constant_info_num,
const ShaderRecompiler::IR::Program& program,
@@ -405,17 +407,16 @@ static void CreateLayout(std::span<vk::DescriptorSetLayout> set_layouts, uint32_
if (need_descriptor) {
EXIT_IF(bindings.descriptor_set != set_layouts_num);
set_layouts[set_layouts_num] =
GetRenderContext().GetDescriptorCache().GetDescriptorSetLayout(stage, program);
set_layouts[set_layouts_num] = descriptor_cache.GetDescriptorSetLayout(stage, program);
set_layouts_num++;
}
}
static void ConfigureSubgroupSize(vk::ShaderStageFlagBits vk_stage,
static void ConfigureSubgroupSize(const GraphicContext& graphics,
vk::ShaderStageFlagBits vk_stage,
const ShaderRecompiler::IR::Program& program,
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo& required,
vk::PipelineShaderStageCreateInfo& stage) {
const auto& graphics = GetRenderContext().GetGraphics();
const auto config =
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {graphics}, vk_stage, program);
switch (config.mode) {
@@ -455,7 +456,9 @@ static void ConfigureSubgroupSize(vk::ShaderStageFlagBits
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::RenderPass render_pass,
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering,
const ShaderVertexInputInfo& vs_input_info,
std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info,
@@ -463,11 +466,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
bool ps_active) {
EXIT_IF(render_pass == nullptr);
EXIT_IF(ps_active && ps_input_info == nullptr);
auto& graphics = GetRenderContext().GetGraphics();
vk::ShaderModule vert_shader_module = nullptr;
vk::ShaderModule frag_shader_module = nullptr;
@@ -511,7 +511,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
vert_shader_stage_info.pName = "main";
vert_shader_stage_info.pSpecializationInfo = nullptr;
EXIT_IF(!vs_input_info.stage);
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eVertex, *vs_input_info.stage.program,
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex,
*vs_input_info.stage.program,
vert_subgroup_size, vert_shader_stage_info);
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
@@ -525,7 +526,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
frag_shader_stage_info.pSpecializationInfo = nullptr;
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eFragment, *ps_input_info->stage.program,
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eFragment,
*ps_input_info->stage.program,
frag_subgroup_size, frag_shader_stage_info);
}
@@ -823,12 +825,14 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
uint32_t push_constant_info_num = 0;
EXIT_IF(!vs_input_info.stage);
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num,
*vs_input_info.stage.program, vk::ShaderStageFlagBits::eVertex,
DescriptorCache::Stage::Vertex);
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num,
*ps_input_info->stage.program, vk::ShaderStageFlagBits::eFragment,
DescriptorCache::Stage::Pixel);
}
@@ -902,8 +906,14 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
dynamic_state.pDynamicStates = dynamic_states;
vk::GraphicsPipelineCreateInfo pipeline_info {};
vk::PipelineRenderingCreateInfo rendering_info {};
rendering_info.sType = vk::StructureType::ePipelineRenderingCreateInfo;
rendering_info.colorAttachmentCount = rendering.color_count;
rendering_info.pColorAttachmentFormats = rendering.color_formats.data();
rendering_info.depthAttachmentFormat = rendering.depth_format;
rendering_info.stencilAttachmentFormat = rendering.stencil_format;
pipeline_info.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
pipeline_info.pNext = nullptr;
pipeline_info.pNext = &rendering_info;
pipeline_info.flags = {};
pipeline_info.stageCount = shader_stage_count;
pipeline_info.pStages = shader_stages;
@@ -917,7 +927,7 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_state;
pipeline_info.layout = pipeline.pipeline_layout;
pipeline_info.renderPass = render_pass;
pipeline_info.renderPass = nullptr;
pipeline_info.subpass = 0;
pipeline_info.basePipelineHandle = nullptr;
pipeline_info.basePipelineIndex = -1;
@@ -952,11 +962,10 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader) {
auto& graphics = GetRenderContext().GetGraphics();
vk::ShaderModule comp_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
@@ -985,7 +994,8 @@ void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
comp_shader_stage_info.pName = "main";
comp_shader_stage_info.pSpecializationInfo = nullptr;
EXIT_IF(!input_info.stage);
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eCompute, *input_info.stage.program,
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute,
*input_info.stage.program,
comp_subgroup_size, comp_shader_stage_info);
vk::DescriptorSetLayout set_layouts[1] = {};
@@ -995,7 +1005,8 @@ void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
uint32_t push_constant_info_num = 0;
EXIT_IF(!input_info.stage);
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num,
*input_info.stage.program, vk::ShaderStageFlagBits::eCompute,
DescriptorCache::Stage::Compute);
+328 -63
View File
@@ -1,95 +1,360 @@
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/vma.h"
#include <atomic>
#include <cstring>
#include <limits>
#include <numeric>
#include <vk_mem_alloc.h>
namespace Libs::Graphics {
namespace {
std::atomic<uint64_t> g_stream_buffer_count {0};
} // namespace
HostStreamBuffer::HostStreamBuffer(GraphicContext& graphics): m_graphics(graphics) {}
constexpr size_t WATCHES_INITIAL_RESERVE = 0x4000;
constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
HostStreamBuffer::~HostStreamBuffer() {
Release();
[[nodiscard]] VmaAllocationCreateFlags AllocationFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal: return {};
}
return {};
}
void HostStreamBuffer::EnsureBuffer() {
if (m_buffer != nullptr) {
if (m_mapped == nullptr) {
EXIT("host stream buffer lost its mapped storage\n");
}
return;
[[nodiscard]] VmaMemoryUsage AllocationUsage(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
case MemoryUsage::Stream: return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MemoryUsage::Upload:
case MemoryUsage::Download: return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
}
KYTY_PROFILER_BLOCK("HostStreamBuffer::create");
m_buffer = std::make_unique<VulkanBuffer>();
m_buffer->usage = vk::BufferUsageFlagBits::eStorageBuffer |
vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eIndexBuffer |
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst;
m_buffer->memory.property = vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent |
vk::MemoryPropertyFlagBits::eHostCached;
m_graphics.CreateBuffer(CAPACITY, *m_buffer);
m_graphics.MapMemory(m_buffer->memory, m_mapped);
const auto count = g_stream_buffer_count.fetch_add(1, std::memory_order_relaxed) + 1;
LOGF("HostStreamBuffer: created serial=%" PRIu64 " capacity=%" PRIu64 "\n", count, CAPACITY);
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
}
bool HostStreamBuffer::Copy(const void* src, uint64_t size, uint64_t alignment,
VulkanBuffer*& out_buffer, uint64_t& out_offset, uint64_t& out_range) {
KYTY_PROFILER_FUNCTION();
if (src == nullptr) {
EXIT("invalid host stream upload\n");
}
std::lock_guard lock(m_mutex);
if (size == 0) {
return false;
}
[[nodiscard]] bool AlignUp(uint64_t value, uint64_t alignment, uint64_t& result) {
if (alignment == 0) {
alignment = 1;
result = value;
return true;
}
if ((alignment & (alignment - 1)) != 0 || m_offset > UINT64_MAX - (alignment - 1)) {
const auto remainder = value % alignment;
if (remainder == 0) {
result = value;
return true;
}
const auto increment = alignment - remainder;
if (value > std::numeric_limits<uint64_t>::max() - increment) {
return false;
}
const auto offset = (m_offset + alignment - 1) & ~(alignment - 1);
if (offset > CAPACITY || size > CAPACITY - offset) {
return false;
}
EnsureBuffer();
std::memcpy(static_cast<uint8_t*>(m_mapped) + offset, src, static_cast<size_t>(size));
m_offset = offset + size;
out_buffer = m_buffer.get();
out_offset = offset;
out_range = size;
result = value + increment;
return true;
}
void HostStreamBuffer::Reset() noexcept {
std::lock_guard lock(m_mutex);
m_offset = 0;
} // namespace
Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size)
: m_graphics(&graphics), m_scheduler(&scheduler), m_usage(usage), m_cpu_address(cpu_address),
m_size(size), m_buffer(std::make_unique<VulkanBuffer>()) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(graphics.allocator == nullptr || size == 0);
vk::BufferCreateInfo buffer_info {};
buffer_info.size = size;
buffer_info.usage = flags;
buffer_info.sharingMode = vk::SharingMode::eExclusive;
VmaAllocationCreateInfo allocation_info {};
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
allocation_info.usage = AllocationUsage(usage);
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
? VkMemoryPropertyFlags {}
: VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
VmaAllocationInfo allocation_result {};
VkBuffer native_buffer = VK_NULL_HANDLE;
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer_info), &allocation_info,
&native_buffer, &m_buffer->memory.allocation, &allocation_result));
if (result != vk::Result::eSuccess) {
graphics.LogMemoryBudget();
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_buffer->buffer = native_buffer;
m_buffer->usage = flags;
m_buffer->buffer_size = size;
m_buffer->memory.allocation_info = allocation_result;
m_buffer->memory.memory = allocation_result.deviceMemory;
m_buffer->memory.offset = allocation_result.offset;
m_buffer->memory.type = allocation_result.memoryType;
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
VkMemoryPropertyFlags properties = 0;
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
m_buffer->memory.property = vk::MemoryPropertyFlags(properties);
m_is_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
if (allocation_result.pMappedData != nullptr) {
m_mapped = {static_cast<uint8_t*>(allocation_result.pMappedData),
static_cast<size_t>(size)};
}
VulkanTrackAllocation(m_buffer->memory);
}
void HostStreamBuffer::Release() noexcept {
std::lock_guard lock(m_mutex);
if (m_buffer != nullptr) {
if (m_mapped == nullptr) {
EXIT("invalid host stream buffer release state\n");
}
m_graphics.UnmapMemory(m_buffer->memory);
m_graphics.DeleteBuffer(*m_buffer);
m_buffer.reset();
Buffer::~Buffer() {
if (m_buffer->buffer != nullptr) {
VulkanUntrackAllocation(m_buffer->memory);
vmaDestroyBuffer(m_graphics->allocator, m_buffer->buffer, m_buffer->memory.allocation);
}
m_mapped = nullptr;
m_offset = 0;
}
vk::Buffer Buffer::Handle() const noexcept {
return m_buffer->buffer;
}
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
}
void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
EXIT_IF(source == nullptr || m_mapped.empty() || offset > m_size || size > m_size - offset);
std::memcpy(m_mapped.data() + offset, source, static_cast<size_t>(size));
Flush(offset, size);
}
void Buffer::Flush(uint64_t offset, uint64_t size) {
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
if (!m_is_coherent && size != 0) {
const auto result = vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation,
offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
vk::AccessFlags destination) const {
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64
" size=0x%016" PRIx64 " capacity=0x%016" PRIx64 "\n",
static_cast<const void*>(Handle()), offset, size, m_size);
}
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = source;
barrier.dstAccessMask = destination;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = Handle();
barrier.offset = offset;
barrier.size = size;
return barrier;
}
void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
uint64_t destination_offset, uint64_t size, vk::AccessFlags source_before,
vk::AccessFlags destination_before, vk::AccessFlags source_after,
vk::AccessFlags destination_after) {
if (size == 0 || source_offset > source.m_size || size > source.m_size - source_offset ||
destination_offset > m_size || size > m_size - destination_offset) {
EXIT("Buffer: invalid copy range\n");
}
if (source.Handle() == Handle() && source_offset < destination_offset + size &&
destination_offset < source_offset + size) {
EXIT("Buffer: overlapping self-copy\n");
}
command.EndRendering();
const vk::BufferMemoryBarrier before[] = {
source.Barrier(source_offset, size, source_before, vk::AccessFlagBits::eTransferRead),
Barrier(destination_offset, size, destination_before,
vk::AccessFlagBits::eTransferWrite),
};
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
auto before_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
if (static_cast<bool>((source_before | destination_before) & host_access)) {
before_stage |= vk::PipelineStageFlagBits::eHost;
}
const auto native = command.Handle();
native.pipelineBarrier(before_stage, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, before, 0, nullptr);
const vk::BufferCopy copy {source_offset, destination_offset, size};
native.copyBuffer(source.Handle(), Handle(), 1, &copy);
const vk::BufferMemoryBarrier after[] = {
source.Barrier(source_offset, size, vk::AccessFlagBits::eTransferRead, source_after),
Barrier(destination_offset, size, vk::AccessFlagBits::eTransferWrite, destination_after),
};
auto after_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
if (static_cast<bool>((source_after | destination_after) & host_access)) {
after_stage |= vk::PipelineStageFlagBits::eHost;
}
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, after_stage,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, after, 0, nullptr);
}
void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
if (((offset | size) & 3u) != 0) {
EXIT("Buffer: fill range must be dword aligned\n");
}
auto& command = Scheduler().Current();
command.EndRendering();
const auto before =
Barrier(offset, size, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eTransferWrite);
const auto native = command.Handle();
native.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
0, nullptr, 1, &before, 0, nullptr);
native.fillBuffer(Handle(), offset, size, value);
const auto after =
Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &after, 0, nullptr);
}
StreamBuffer::StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t size)
: Buffer(graphics, scheduler, usage, 0, AllFlags, size) {
ReserveWatches(m_current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(m_previous_watches, WATCHES_INITIAL_RESERVE);
}
bool StreamBuffer::NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment) {
if (coherent) {
return true;
}
if (!AlignUp(size, atom, size)) {
return false;
}
const auto divisor = std::gcd(alignment, atom);
if (alignment != 0 && alignment / divisor > UINT64_MAX / atom) {
return false;
}
alignment = alignment == 0 ? atom : alignment / divisor * atom;
return true;
}
std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignment,
bool allow_wait) {
if (Mapped().empty()) {
return {nullptr, 0};
}
uint64_t mapped_size = size;
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
if (!NormalizeReservation(IsCoherent(), atom, mapped_size, alignment)) {
return {nullptr, 0};
}
if (mapped_size > Size()) {
return {nullptr, 0};
}
uint64_t aligned_offset = 0;
if (!AlignUp(m_offset, alignment, aligned_offset)) {
return {nullptr, 0};
}
const bool wrap = aligned_offset > Size() - mapped_size;
if (wrap) {
aligned_offset = 0;
}
auto wait_cursor = wrap ? size_t {0} : m_wait_cursor;
auto wait_bound = wrap ? uint64_t {0} : m_wait_bound;
auto invalidation_mark =
wrap ? std::optional<size_t> {m_current_watch_cursor} : m_invalidation_mark;
auto& pending_watches = wrap ? m_current_watches : m_previous_watches;
if (!WaitPendingOperations(pending_watches, invalidation_mark, aligned_offset + mapped_size,
allow_wait, wait_cursor, wait_bound)) {
return {nullptr, 0};
}
if (wrap) {
m_invalidation_mark = invalidation_mark;
m_current_watch_cursor = 0;
std::swap(m_previous_watches, m_current_watches);
}
m_wait_cursor = wait_cursor;
m_wait_bound = wait_bound;
m_offset = aligned_offset;
m_mapped_size = mapped_size;
return {Mapped().data() + m_offset, m_offset};
}
void StreamBuffer::Commit() {
if (!IsCoherent() && Usage() != MemoryUsage::Download && m_mapped_size != 0) {
const auto result = vmaFlushAllocation(
Graphics().allocator, NativeBuffer().memory.allocation, m_offset, m_mapped_size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
m_offset += m_mapped_size;
const auto tick = Scheduler().CurrentTick();
if (m_current_watch_cursor != 0 && m_current_watches[m_current_watch_cursor - 1].tick == tick) {
m_current_watches[m_current_watch_cursor - 1].upper_bound = m_offset;
return;
}
if (m_current_watch_cursor + 1 >= m_current_watches.size()) {
ReserveWatches(m_current_watches, WATCHES_RESERVE_CHUNK);
}
auto& watch = m_current_watches[m_current_watch_cursor++];
watch.upper_bound = m_offset;
watch.tick = tick;
}
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
if (IsCoherent() || size == 0) {
return;
}
const auto result = vmaInvalidateAllocation(Graphics().allocator,
NativeBuffer().memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
EXIT_IF(source == nullptr);
const auto [data, offset] = Map(size, alignment);
EXIT_IF(data == nullptr);
std::memcpy(data, source, static_cast<size_t>(size));
Commit();
return offset;
}
void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, size_t grow_size) {
watches.resize(watches.size() + grow_size);
}
bool StreamBuffer::WaitPendingOperations(const std::vector<Watch>& watches,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
size_t& wait_cursor, uint64_t& wait_bound) {
if (!invalidation_mark.has_value()) {
return true;
}
while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) {
const auto& watch = watches[wait_cursor];
if (!Scheduler().IsFree(watch.tick) && !allow_wait) {
return false;
}
Scheduler().Wait(watch.tick);
if (Usage() == MemoryUsage::Download) {
Scheduler().WaitPriorityOperations(watch.tick);
}
wait_bound = watch.upper_bound;
++wait_cursor;
}
return true;
}
} // namespace Libs::Graphics
+106 -23
View File
@@ -3,43 +3,126 @@
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
class CommandBuffer;
class CommandScheduler;
struct StreamBufferTestAccess;
struct GraphicContext;
struct VulkanBuffer;
// Host-only mapped stream mechanism. BufferCache owns the upload policy/API; one backend remains
// attached to each CommandBuffer so Reset is provably after that command's fence. Replacing this
// backend with scheduler watches will not change descriptor/render call sites.
class HostStreamBuffer final {
public:
explicit HostStreamBuffer(GraphicContext& graphics);
~HostStreamBuffer();
KYTY_CLASS_NO_COPY(HostStreamBuffer);
enum class MemoryUsage : uint8_t {
DeviceLocal,
Upload,
Download,
Stream,
};
[[nodiscard]] bool Copy(const void* src, uint64_t size, uint64_t alignment,
VulkanBuffer*& out_buffer, uint64_t& out_offset, uint64_t& out_range);
void Reset() noexcept;
void Release() noexcept;
inline constexpr vk::BufferUsageFlags ReadFlags =
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eUniformBuffer |
vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eIndirectBuffer;
inline constexpr vk::BufferUsageFlags AllFlags =
ReadFlags | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer;
class Buffer {
public:
Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size);
~Buffer();
KYTY_CLASS_NO_COPY(Buffer);
[[nodiscard]] vk::Buffer Handle() const noexcept;
[[nodiscard]] uint64_t Size() const noexcept { return m_size; }
[[nodiscard]] std::span<uint8_t> Mapped() const noexcept { return m_mapped; }
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
[[nodiscard]] uint64_t Offset(uint64_t address) const noexcept {
return address - m_cpu_address;
}
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
void Write(uint64_t offset, const void* source, uint64_t size);
void Flush(uint64_t offset, uint64_t size);
void CopyFrom(
CommandBuffer& command, const Buffer& source, uint64_t source_offset,
uint64_t destination_offset, uint64_t size,
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_before =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags source_after =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_after =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
void Fill(uint64_t offset, uint64_t size, uint32_t value);
protected:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
[[nodiscard]] VulkanBuffer& NativeBuffer() noexcept { return *m_buffer; }
private:
void EnsureBuffer();
[[nodiscard]] vk::BufferMemoryBarrier Barrier(uint64_t offset, uint64_t size,
vk::AccessFlags source,
vk::AccessFlags destination) const;
// Interim fence-scoped backend: one command processor's eight live command buffers reserve up
// to 128 MiB lazily. Additional active processors scale that aggregate until this backend is
// replaced by a single 64 MiB scheduler-watched ring; the BufferCache seam stays fixed.
static constexpr uint64_t CAPACITY = 16ull * 1024ull * 1024ull;
mutable std::mutex m_mutex;
GraphicContext& m_graphics;
GraphicContext* m_graphics = nullptr;
CommandScheduler* m_scheduler = nullptr;
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
uint64_t m_cpu_address = 0;
uint64_t m_size = 0;
std::unique_ptr<VulkanBuffer> m_buffer;
void* m_mapped = nullptr;
uint64_t m_offset = 0;
std::span<uint8_t> m_mapped;
bool m_is_coherent = false;
};
class StreamBuffer final: public Buffer {
public:
StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t size);
[[nodiscard]] std::pair<uint8_t*, uint64_t> Map(uint64_t size, uint64_t alignment = 0,
bool allow_wait = true);
void Commit();
// Download mappings become visible to the CPU only after their GPU completion tick is free.
// Call this from the scheduler's deferred completion operation before reading Mapped().
void Invalidate(uint64_t offset, uint64_t size);
[[nodiscard]] uint64_t Copy(const void* source, uint64_t size, uint64_t alignment = 0);
private:
friend struct StreamBufferTestAccess;
struct Watch {
uint64_t tick = 0;
uint64_t upper_bound = 0;
};
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
[[nodiscard]] static bool NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment);
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
size_t& wait_cursor, uint64_t& wait_bound);
uint64_t m_offset = 0;
uint64_t m_mapped_size = 0;
std::vector<Watch> m_current_watches;
size_t m_current_watch_cursor = 0;
std::optional<size_t> m_invalidation_mark;
std::vector<Watch> m_previous_watches;
size_t m_wait_cursor = 0;
uint64_t m_wait_bound = 0;
};
} // namespace Libs::Graphics
+47 -55
View File
@@ -5,16 +5,16 @@
#include "common/logging/log.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/presentation/displayBuffer.h"
#include "graphics/presentation/videoOut.h"
#include "kernel/eventQueue.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include <array>
#include <cstring>
#include <limits>
#include <optional>
@@ -57,19 +57,6 @@ uint64_t ReadReferenceClock() {
return value;
}
static void SubmitLabel(CommandBuffer& buffer, LabelCallback callback_1 = nullptr,
LabelCallback callback_2 = nullptr, const uint64_t* args = nullptr) {
auto* label = LabelCreate(callback_1, callback_2, args);
LabelSet(buffer, *label);
LabelDelete(*label);
}
static bool CompleteDisplayBufferFlip(const uint64_t* args) {
EXIT_IF(args == nullptr);
Presentation::DisplayBufferCompleteFlipFromGpu(args[0]);
return true;
}
enum class EndOfPipeCompletion { None, Interrupt, Flip, FlipAndInterrupt };
struct EndOfPipeSignal {
@@ -86,17 +73,6 @@ struct EndOfPipeSignal {
enum class EndOfPipeWriteSize : uint32_t { Dword = 4, Qword = 8 };
enum class EndOfPipeWriteAction { Write, WriteBack, Interrupt, InterruptWriteBack };
static bool TriggerEopEventCallback(const uint64_t* args) {
EXIT_IF(args == nullptr);
GetRenderContext().TriggerEopEvent(static_cast<uint32_t>(args[0]));
return true;
}
static bool TriggerDefaultEopEventCallback(const uint64_t* /*args*/) {
GetRenderContext().TriggerEopEvent(0);
return true;
}
static void ValidateEndOfPipeSignal(const EndOfPipeSignal& signal) {
if (signal.destination.has_value()) {
EXIT_IF(*signal.destination == 0);
@@ -111,19 +87,33 @@ static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
signal.debug_args[0], signal.debug_args[1], signal.debug_args[2],
signal.debug_args[3], signal.debug_data);
const uint64_t args[LABEL_ARGS_MAX] = {signal.completion_data};
auto& renderer = signal.buffer->GetContext();
auto& scheduler = renderer.GetCommandScheduler();
if (signal.completion != EndOfPipeCompletion::None) {
EXIT_IF(!scheduler.Active() || signal.buffer != &scheduler.Current());
}
switch (signal.completion) {
case EndOfPipeCompletion::None: return;
case EndOfPipeCompletion::Interrupt:
SubmitLabel(*signal.buffer, nullptr, TriggerEopEventCallback, args);
case EndOfPipeCompletion::Interrupt: {
const auto context_id = static_cast<uint32_t>(signal.completion_data);
scheduler.DeferPriorityOperation(
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
return;
case EndOfPipeCompletion::Flip:
SubmitLabel(*signal.buffer, CompleteDisplayBufferFlip, nullptr, args);
}
case EndOfPipeCompletion::Flip: {
const auto request_id = signal.completion_data;
scheduler.DeferPriorityOperation(
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
return;
case EndOfPipeCompletion::FlipAndInterrupt:
SubmitLabel(*signal.buffer, CompleteDisplayBufferFlip, TriggerDefaultEopEventCallback,
args);
}
case EndOfPipeCompletion::FlipAndInterrupt: {
const auto request_id = signal.completion_data;
scheduler.DeferPriorityOperation([&renderer, request_id] {
renderer.GetVideoOut().CompleteFlip(request_id);
renderer.TriggerEopEvent(0);
});
return;
}
}
}
@@ -172,10 +162,6 @@ void TriggerAgcUserInterrupt() {
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
}
void TriggerEopEvent(uint32_t context_id) {
GetRenderContext().TriggerEopEvent(context_id);
}
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
uint32_t value) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
@@ -261,11 +247,12 @@ void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Interrupt, context_id);
}
uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
int64_t flip_arg) {
uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
int64_t flip_arg) {
for (;;) {
uint64_t request_id = 0;
const auto result = Presentation::DisplayBufferSubmitFlipFromGpu(
auto& video_out = buffer.GetContext().GetVideoOut();
const auto result = video_out.SubmitFlipFromGpu(
buffer, handle, index, flip_mode, flip_arg, request_id);
if (result == OK) {
EXIT_IF(request_id == 0);
@@ -276,7 +263,7 @@ uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index,
"\n",
result, handle, index, flip_mode, flip_arg);
}
Presentation::DisplayBufferWaitForFlipQueueSlot();
video_out.WaitForSubmitSlot();
}
}
@@ -331,9 +318,11 @@ void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int han
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id) {
ValidateEndOfPipeSignal({.buffer = &buffer});
uint64_t args[LABEL_ARGS_MAX] = {static_cast<uint64_t>(context_id)};
SubmitLabel(buffer, nullptr, TriggerEopEventCallback, args);
auto& renderer = buffer.GetContext();
auto& scheduler = renderer.GetCommandScheduler();
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
scheduler.DeferPriorityOperation(
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
}
static void EopEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
@@ -349,7 +338,9 @@ static void EopEventDeleteFunc(LibKernel::EventQueue::KernelEqueue eq,
EXIT_NOT_IMPLEMENTED(event->event.filter != LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS);
if (event->event.ident == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT ||
event->event.ident == GRAPHICS_EVENT_EOP) {
GetRenderContext().DeleteEopEq(eq, static_cast<int>(event->event.ident));
auto* renderer = static_cast<RenderContext*>(event->filter.data);
EXIT_IF(renderer == nullptr);
renderer->DeleteEopEq(eq, static_cast<int>(event->event.ident));
}
}
@@ -368,7 +359,8 @@ static void EopEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
}
}
int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata) {
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
void* udata) {
LibKernel::EventQueue::KernelEqueueEvent event;
event.triggered = false;
event.event.ident = static_cast<uintptr_t>(id);
@@ -379,13 +371,13 @@ int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata) {
event.filter.delete_event_func = EopEventDeleteFunc;
event.filter.reset_func = EopEventResetFunc;
event.filter.trigger_func = EopEventTriggerFunc;
event.filter.data = nullptr;
event.filter.data = &renderer;
int result = LibKernel::EventQueue::KernelAddEvent(eq, event);
if (result == 0 &&
(id == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT || id == GRAPHICS_EVENT_EOP)) {
GetRenderContext().AddEopEq(eq, id);
renderer.AddEopEq(eq, id);
}
return result;
@@ -398,12 +390,12 @@ int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id) {
return result;
}
void ReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
GetRenderContext().GetGdsBuffer().Read(dst, dw_offset, dw_size);
}
void DeleteBuffers() {
GetRenderContext().GetBufferCache().ResetNullBuffer();
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
const auto offset = uint64_t {dw_offset} * sizeof(uint32_t);
const auto size = uint64_t {dw_size} * sizeof(uint32_t);
EXIT_IF(dst == nullptr || offset > gds.Size() || size > gds.Size() - offset ||
gds.Mapped().empty());
std::memcpy(dst, gds.Mapped().data() + offset, static_cast<size_t>(size));
}
} // namespace Libs::Graphics::Sync
+7 -6
View File
@@ -8,6 +8,8 @@
namespace Libs::Graphics {
class CommandBuffer;
class Buffer;
class RenderContext;
namespace Sync {
@@ -16,7 +18,6 @@ namespace Sync {
[[nodiscard]] uint64_t ReadReferenceClock();
void TriggerAgcUserInterrupt();
void TriggerEopEvent(uint32_t context_id);
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id);
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
@@ -46,8 +47,8 @@ void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer&
uint64_t* dst_gpu_addr, uint64_t value,
uint32_t context_id = 0);
[[nodiscard]] uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index,
int flip_mode, int64_t flip_arg);
[[nodiscard]] uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index,
int flip_mode, int64_t flip_arg);
void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int handle, int index,
int flip_mode, int64_t flip_arg, uint64_t request_id);
void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
@@ -58,10 +59,10 @@ void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuf
int handle, int index, int flip_mode,
int64_t flip_arg, uint64_t request_id);
int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata);
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
void* udata);
int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id);
void ReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
void DeleteBuffers();
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
} // namespace Sync
} // namespace Libs::Graphics
File diff suppressed because it is too large. Load diff
+153 -143
View File
@@ -3,174 +3,184 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "common/lruCache.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/host_gpu/renderer/blitHelper.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/renderer/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <compare>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
struct GraphicContext;
struct ImageViewInfo;
struct RenderTextureVulkanImage;
struct StorageTextureVulkanImage;
struct VideoOutVulkanImage;
struct VulkanImage;
struct VulkanMemory;
class Buffer;
class BufferCache;
class CommandBuffer;
class DummyTextureCache;
class CommandScheduler;
class ResourceMutex;
[[nodiscard]] bool IsExactRenderTargetMipStorage(const ImageInfo& sampled, const ImageInfo& storage,
vk::Format sampled_view_format,
vk::Format storage_image_format) noexcept;
class RenderExecutor;
class StreamBuffer;
class TileManager;
struct TextureCacheTestAccess;
class TextureCache {
public:
struct RegionInfo {
bool image_pages = false;
bool image_bytes = false;
bool gpu_image_bytes = false;
bool non_sampled_pages = false;
bool metadata_pages = false;
bool metadata_bytes = false;
bool gpu_metadata_bytes = false;
};
struct MetaRangeInfo {
uint64_t metadata_address = 0;
uint64_t metadata_size = 0;
uint32_t slice = 0;
bool full = false;
enum class BindingType : uint8_t { Texture, Storage, RenderTarget, DepthTarget, VideoOut };
struct ImageDesc {
ImageInfo info;
ImageViewInfo view_info;
BindingType type = BindingType::Texture;
};
TextureCache(GraphicContext& graphics, PageManager& page_manager, BufferCache& buffer_cache,
ResourceMutex& resource_mutex);
struct RegionInfo {
bool image_pages = false;
bool image_bytes = false;
bool gpu_image_bytes = false;
};
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
BufferCache& buffer_cache, ResourceMutex& resource_mutex);
~TextureCache();
KYTY_CLASS_NO_COPY(TextureCache);
[[nodiscard]] VulkanImage& FindTexture(CommandBuffer& command, const ImageInfo& info,
bool metadata_read);
[[nodiscard]] StorageTextureVulkanImage& FindStorageTexture(CommandBuffer& command,
const ImageInfo& info);
[[nodiscard]] RenderTextureVulkanImage& FindRenderTarget(CommandBuffer& command,
const RenderTargetInfo& info);
[[nodiscard]] DepthStencilVulkanImage& FindDepthTarget(CommandBuffer& command,
const DepthTargetInfo& info);
[[nodiscard]] std::vector<VideoOutVulkanImage*>
RegisterVideoOutSurfaces(const std::vector<VideoOutInfo>& infos);
void RefreshVideoOut(VideoOutVulkanImage& image, bool render_target = false);
void UnregisterVideoOutSurfaces(const std::vector<VideoOutVulkanImage*>& images);
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
uint32_t packed_clear);
void MarkGpuWritten(VulkanImage& image);
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
bool formatted_buffer_write = false);
[[nodiscard]] RenderTextureVulkanImage* FindRenderTargetByRange(CommandBuffer& command,
uint64_t vaddr, uint64_t size);
[[nodiscard]] vk::ImageView GetRenderTargetAttachmentView(RenderTextureVulkanImage& image,
vk::Format format, uint32_t level,
uint32_t base_layer,
uint32_t layer_count);
[[nodiscard]] vk::ImageView GetDepthTargetAttachmentView(DepthStencilVulkanImage& image,
uint32_t base_layer,
uint32_t layer_count);
[[nodiscard]] vk::ImageView
GetDepthTargetSampledView(DepthStencilVulkanImage& image, vk::Format view_format,
uint32_t swizzle, uint32_t base_level, uint32_t level_count,
vk::ImageViewType type, uint32_t base_layer, uint32_t layer_count);
[[nodiscard]] vk::ImageView GetSampledColorView(VulkanImage& image, vk::Format view_format,
uint32_t swizzle, uint32_t base_level,
uint32_t level_count, vk::ImageViewType type,
uint32_t base_layer, uint32_t layer_count);
[[nodiscard]] vk::ImageView
GetRenderTargetStorageView(RenderTextureVulkanImage& image, vk::Format view_format,
uint32_t base_level, uint32_t level_count, vk::ImageViewType type,
uint32_t base_layer, uint32_t layer_count);
[[nodiscard]] vk::ImageView GetStorageTextureSampledView(StorageTextureVulkanImage& image,
const ImageInfo& info);
[[nodiscard]] vk::ImageView GetStorageTextureStorageView(StorageTextureVulkanImage& image,
uint32_t base_level);
[[nodiscard]] DepthStencilVulkanImage*
FindDepthTargetByRange(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool allow_containing_sampled = false);
[[nodiscard]] RegionInfo QueryRegion(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool ResolveMetaRange(uint64_t vaddr, uint64_t size, MetaRangeInfo& info);
void RegisterMeta(uint64_t vaddr, uint64_t size, uint32_t layers = 1);
[[nodiscard]] bool IsMeta(uint64_t vaddr);
[[nodiscard]] bool IsMetaRange(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMetaCleared(uint64_t vaddr, uint32_t slice);
[[nodiscard]] bool ClearMeta(uint64_t vaddr);
[[nodiscard]] bool TouchMeta(uint64_t vaddr, uint32_t slice, bool is_clear);
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] ImageId FindImage(ImageDesc& desc, bool exact_format = false);
[[nodiscard]] ImageId FindImageFromRange(uint64_t address, uint64_t size,
bool ensure_valid = true);
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindRenderTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindDepthTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] Image& GetImage(ImageId id);
[[nodiscard]] const Image& GetImage(ImageId id) const;
void MarkGpuWritten(ImageId id);
VulkanImage& GetDummySampledTexture(bool uint_format, bool image_3d);
VulkanImage& GetDummyStorageTexture(bool uint_format, bool image_3d);
[[nodiscard]] std::shared_ptr<void> GetImageOwner(const VulkanImage& image);
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
uint32_t packed_clear);
void PrepareHostWrite(uint64_t address, uint64_t size);
[[nodiscard]] bool SynchronizeImageToBuffer(uint64_t address, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
bool formatted_buffer_write = false);
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
[[nodiscard]] bool ClearMeta(uint64_t address);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t address, uint64_t size,
PageFaultPhase phase) noexcept;
void UnmapMemory(uint64_t address, uint64_t size);
void ProcessDownloadImages();
void RunGarbageCollector();
private:
struct CachedImage;
using ImageOwnerIndex = MultiRangePageOwnerIndex<CachedImage*>;
struct ReadbackWorker;
struct MetaDataInfo {
uint64_t size = 0;
uint32_t layers = 1;
uint32_t clear_mask = 0;
bool gpu_modified = false;
};
[[nodiscard]] vk::ImageView GetImageView(VulkanImage& image, const ImageViewInfo& info);
[[nodiscard]] bool HasMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
[[nodiscard]] CachedImage* FindGpuReadbackPageCandidateLocked(uint64_t vaddr, uint64_t size);
void RequireNoMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
void ResolveImageMetadataOverlapsLocked(uint64_t vaddr, uint64_t size);
void MarkSampledAliasesCpuDirtyLocked(uint64_t vaddr, uint64_t size);
void RetireSampledTargetAliases(const ImageInfo& requested);
void ResolveStorageImageOverlaps(const ImageInfo& requested);
void RetireStorageDepthAliasLocked(const ImageInfo& requested);
void RegisterImageLocked(CachedImage& image);
void UnregisterImageLocked(CachedImage& image, bool release_tracking);
[[nodiscard]] VulkanImage& PublishImage(CommandBuffer& command,
std::shared_ptr<CachedImage> image);
[[nodiscard]] std::vector<CachedImage*> FindImagesInRegionLocked(uint64_t vaddr, uint64_t size,
bool page_overlap);
void RequireRetirementIsolation(const std::vector<CachedImage*>& retire, const char* operation,
uint64_t address, uint64_t size) const;
void RetireImages(const std::vector<CachedImage*>& retire,
const CachedImage* native_image_source = nullptr);
void RetireDepthMetadataLocked(const std::vector<CachedImage*>& retire,
uint64_t preserve_address = 0);
void MaterializeImagesToGuestLocked(const std::vector<std::shared_ptr<CachedImage>>& images);
void SynchronizeColorImageToBufferLocked(CachedImage& cached, uint64_t write_address,
uint64_t write_size);
void SynchronizeDepthImageToBufferLocked(CachedImage& cached, uint64_t write_address,
uint64_t write_size);
enum class TransferDirection { Upload, Download };
struct ColorTransferPlan;
struct DownloadPlan;
GraphicContext& m_graphics;
std::unique_ptr<DummyTextureCache> m_dummy_textures;
TrackingSpinLock m_lock;
std::mutex m_fault_mutex;
MemoryTracker m_memory_tracker;
MemoryTracker m_metadata_tracker;
Tiler m_tiler;
BufferCache& m_buffer_cache;
ResourceMutex& m_resource_mutex;
std::vector<std::shared_ptr<CachedImage>> m_images;
ImageOwnerIndex m_image_owner_index;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
std::unique_ptr<ReadbackWorker> m_readback;
std::vector<uint8_t> m_buffer_transition_guest;
struct Slot {
std::shared_ptr<Image> image;
uint32_t generation = 1;
};
struct MetaDataInfo {
uint32_t clear_mask = 0;
};
struct OverlapResult {
ImageId image;
int32_t mip = -1;
int32_t layer = -1;
};
using ImageOwnerIndex = MultiRangePageOwnerIndex<ImageId>;
[[nodiscard]] Image& ResolveImage(ImageId id);
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
[[nodiscard]] std::shared_ptr<Image> ResolveOwner(ImageId id) const;
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
void UnregisterImage(ImageId id, bool release_tracking);
void DeleteImage(ImageId id, bool release_tracking = true);
void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {});
void RetainImage(CommandBuffer& command, ImageId id);
void TouchImage(Image& image);
void TrackImageDownload(ImageId id);
void TrackImageDownloadLocked(ImageId id, Image& image);
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
bool exact_format);
[[nodiscard]] static BindingType UploadBinding(const Image& image);
[[nodiscard]] bool SafeToDownload(const Image& image);
[[nodiscard]] std::vector<ImageId> FindImagesInRegion(uint64_t address, uint64_t size,
bool page_overlap) const;
[[nodiscard]] OverlapResult ResolveOverlap(const ImageInfo& requested, BindingType binding,
ImageId cached, ImageId merged);
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
ImageId cached);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
void RefreshImage(ImageId id, const ImageDesc& desc);
void InitializeImage(ImageId id, const ImageDesc& desc);
[[nodiscard]] ColorTransferPlan BuildColorTransfer(const Image& image, BindingType binding,
TransferDirection direction) const;
[[nodiscard]] DownloadPlan BuildDownload(const Image& image) const;
void UploadImage(Image& image, const ImageDesc& desc, Buffer& source, uint64_t source_offset);
void DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
DownloadPlan plan);
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
void PrepareImageCopy(Image& image);
void RefreshCopySource(ImageId id);
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
void CopyImage(ImageId destination, ImageId source);
void AssociateStencil(ImageId depth, GuestRange stencil);
void AssociateStencilLocked(ImageId depth, GuestRange stencil);
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size);
void RestoreGpuTracking(const Image& image);
void ReleaseGpuTracking(ImageId id);
[[nodiscard]] bool SynchronizeImageToBuffer(ImageId id);
void DownloadImage(ImageId id);
[[nodiscard]] bool TryDownloadImage(ImageId id);
[[nodiscard]] std::pair<uint8_t*, uint64_t> MapDownload(uint64_t size, uint64_t alignment);
void QueueDownload(GuestRange range, StreamBuffer& download, uint8_t* mapped, uint64_t offset);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
TrackingSpinLock m_lock;
MemoryTracker m_memory_tracker;
BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler;
BufferCache& m_buffer_cache;
ResourceMutex& m_resource_mutex;
std::vector<Slot> m_slots;
std::vector<uint32_t> m_free_slots;
ImageOwnerIndex m_image_owner_index;
std::map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::set<ImageId> m_download_images;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 0;
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
bool m_readback_linear_images = false;
friend struct TextureCacheTestAccess;
friend class RenderExecutor;
};
} // namespace Libs::Graphics
+698 -141
View File
@@ -1,161 +1,718 @@
#include "graphics/host_gpu/renderer/tiler.h"
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/gpuTiler.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/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include <vector>
#include <algorithm>
#include <array>
#include <bit>
#include <cstring>
#include <limits>
namespace Libs::Graphics {
namespace {
struct DepthTransfer {
std::vector<GpuTileInfo> infos;
std::vector<BufferImageCopy> regions;
};
DepthTransfer MakeDepthTransfer(uint64_t size, uint32_t layers, uint32_t format,
uint32_t bytes_per_element, uint32_t width, uint32_t height,
uint32_t pitch, uint32_t base_layer, vk::ImageAspectFlags aspect) {
EXIT_IF(size == 0 || layers == 0 || size % layers != 0);
TileBlockLayout block {};
EXIT_NOT_IMPLEMENTED(
!TileGetBlockLayout(TileBlockFamily::Depth64KB, bytes_per_element, block) ||
Prospero::NumBytesPerElement(format) != bytes_per_element);
const uint64_t slice_size = size / layers;
DepthTransfer transfer;
transfer.infos.reserve(layers);
transfer.regions.reserve(layers);
for (uint32_t layer = 0; layer < layers; layer++) {
const uint64_t offset = slice_size * layer;
GpuTileInfo info {block.family,
block.bytes_per_element,
offset,
slice_size,
offset,
slice_size,
0,
width,
height,
1,
pitch};
info.surface_z = base_layer + layer;
transfer.infos.push_back(info);
BufferImageCopy region {};
region.offset = static_cast<uint32_t>(offset);
region.pitch = pitch;
region.width = width;
region.height = height;
region.dst_layer = base_layer + layer;
region.aspect = aspect;
transfer.regions.push_back(region);
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};
}
return transfer;
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");
}
void UploadDepth(DepthStencilVulkanImage& image, uint64_t source_address, uint64_t size,
uint32_t layers, uint32_t format, uint32_t bytes_per_element, uint32_t width,
uint32_t height, uint32_t pitch, uint32_t base_layer,
vk::ImageAspectFlags aspect) {
auto transfer = MakeDepthTransfer(size, layers, format, bytes_per_element, width, height, pitch,
base_layer, aspect);
Transfer::UploadTiledImage(image, reinterpret_cast<const void*>(source_address), size, size,
transfer.infos, transfer.regions,
vk::ImageLayout::eDepthStencilAttachmentOptimal);
}
template <uint32_t (*Encode)(uint16_t)>
void UploadPromotedD16Depth(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, uint32_t base_layer) {
const uint64_t guest_slice_size = info.size / info.layers;
const uint64_t texels = static_cast<uint64_t>(info.pitch) * info.height;
const uint64_t host_slice_size = texels * sizeof(uint32_t);
const uint64_t host_upload_size = host_slice_size * info.layers;
EXIT_IF(host_upload_size > UINT32_MAX);
auto transfer = MakeDepthTransfer(info.size, info.layers, info.guest_format,
info.bytes_per_element, info.width, info.height, info.pitch,
base_layer, vk::ImageAspectFlagBits::eDepth);
std::vector<uint16_t> guest_linear(info.size / sizeof(uint16_t));
GpuDetile(reinterpret_cast<const void*>(source.address), guest_linear.data(), info.size,
info.size, transfer.infos);
Transfer::ScratchBuffer host_linear(host_upload_size);
for (uint32_t layer = 0; layer < info.layers; layer++) {
const auto* guest = guest_linear.data() + guest_slice_size / sizeof(uint16_t) * layer;
auto* host = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(host_linear.Data()) +
host_slice_size * layer);
for (uint64_t texel = 0; texel < texels; texel++) {
host[texel] = Encode(guest[texel]);
}
transfer.regions[layer].offset = static_cast<uint32_t>(host_slice_size * layer);
}
Transfer::UploadImage(image, host_linear.Data(), host_upload_size, transfer.regions,
vk::ImageLayout::eDepthStencilAttachmentOptimal);
}
} // namespace
void Tiler::DetileImage(GpuTextureVulkanImage& image, const ImageInfo& info,
const BufferImageCopySource& source, bool refresh, bool storage) const {
if (refresh) Transfer::WaitForQueueIdle();
const bool array_texture = TextureIsLayeredTexture(info.type);
const bool volume_texture = TextureIs3DTexture(info.type);
auto layout = TextureCalcUploadLayout(
info.format, info.width, info.height, info.levels, info.depth, info.pitch, info.tile,
info.size, true, volume_texture, storage ? "StorageTextureCache" : "TextureCache");
auto regions = TextureBuildUploadRegions(layout, image.format, info.width, info.height,
info.depth, info.levels, array_texture, volume_texture,
TextureUploadDestination::MipLevels);
TextureUploadGuestImage(image, reinterpret_cast<const void*>(source.address), info.size,
regions, layout, info.format, info.width, info.height, info.depth,
info.levels, storage ? "StorageTextureCache" : "TextureCache",
storage ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eShaderReadOnlyOptimal);
}
void Tiler::DetileImage(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, bool refresh,
uint32_t base_layer) const {
EXIT_NOT_IMPLEMENTED(info.samples != 1 || image.samples != 1);
if (refresh) Transfer::WaitForQueueIdle();
if (DepthAspectTransferBytes(info.format) != info.bytes_per_element) {
switch (info.format) {
case vk::Format::eD24UnormS8Uint:
UploadPromotedD16Depth<EncodeD16AsD24>(image, info, source, base_layer);
return;
case vk::Format::eD32SfloatS8Uint:
UploadPromotedD16Depth<EncodeD16AsD32>(image, info, source, base_layer);
return;
default: EXIT_NOT_IMPLEMENTED(true);
TileManager::~TileManager() {
for (auto pipeline: m_pipelines) {
if (pipeline != nullptr) {
m_graphics.device.destroyPipeline(pipeline, nullptr);
}
}
UploadDepth(image, source.address, info.size, info.layers, info.guest_format,
info.bytes_per_element, info.width, info.height, info.pitch, base_layer,
vk::ImageAspectFlagBits::eDepth);
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);
}
}
void Tiler::DetileStencil(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, bool refresh,
uint32_t base_layer) const {
EXIT_NOT_IMPLEMENTED(info.samples != 1 || image.samples != 1);
if (refresh) Transfer::WaitForQueueIdle();
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;
const auto format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt);
const auto pitch = TileGetTexturePitch(format, info.width, 1,
Prospero::GpuEnumValue(Prospero::TileMode::kDepth));
UploadDepth(image, source.address, info.stencil_size, info.layers, format, 1, info.width,
info.height, pitch, base_layer, vk::ImageAspectFlagBits::eStencil);
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
+136 -16
View File
@@ -2,26 +2,146 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TILER_H_
#include "common/common.h"
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/imageInfo.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 {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
class Tiler final {
public:
Tiler() = default;
KYTY_CLASS_NO_COPY(Tiler);
class CommandScheduler;
class Image;
class StreamBuffer;
struct GraphicContext;
struct TileManagerTestAccess;
void DetileImage(GpuTextureVulkanImage& image, const ImageInfo& info,
const BufferImageCopySource& source, bool refresh, bool storage) const;
void DetileImage(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, bool refresh,
uint32_t base_layer = 0) const;
void DetileStencil(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, bool refresh,
uint32_t base_layer = 0) const;
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