Initial commit

This commit is contained in:
nmzik
2026-07-16 10:21:41 +02:00
commit 147188b143
427 changed files with 205923 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include <map>
#include <memory>
#include <mutex>
#include <vector>
namespace Libs::Graphics {
struct GraphicContext;
struct VulkanBuffer;
class CommandBuffer;
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;
};
struct BufferCacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
[[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange* merged,
BufferCacheRange candidate) noexcept;
[[nodiscard]] bool CanMergeBufferCacheQueueMask(uint64_t queue_mask, uint32_t queue) noexcept;
[[nodiscard]] bool CanReadbackBufferCacheQueueMask(uint64_t queue_mask, uint32_t queue) noexcept;
class BufferCache {
public:
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
static constexpr uint64_t GetBufferOffset(uint64_t vaddr) {
return vaddr & (CACHING_PAGE_SIZE - 1);
}
BufferCache(PageManager& page_manager, ResourceMutex& resource_mutex);
~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache);
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] std::pair<VulkanBuffer*, uint64_t>
ObtainBuffer(CommandBuffer* command, GraphicContext* ctx, 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, GraphicContext* ctx, const void* src,
uint64_t size, uint64_t alignment,
VulkanBuffer** out_buffer, uint64_t* out_offset,
uint64_t* out_range);
[[nodiscard]] VulkanBuffer* ObtainNullBuffer(CommandBuffer* command,
GraphicContext* ctx);
[[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(CommandBuffer* command, GraphicContext* ctx, uint64_t vaddr, uint64_t size,
uint32_t value);
void CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64_t dst_vaddr,
uint64_t src_vaddr, uint64_t size);
[[nodiscard]] bool HasPageOverlap(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 ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
void SetTextureCache(TextureCache& texture_cache);
void RegisterForDelete(VulkanBuffer* buffer);
void DeleteAll(GraphicContext* ctx);
private:
struct CachedBuffer;
struct ReadbackWorker;
struct CommandProcessorReadbackResources;
void RequestCommandProcessorReadback(PageFaultAccess access, uint64_t vaddr, uint64_t size);
[[nodiscard]] bool CompleteCommandProcessorReadback(PageFaultAccess access, uint64_t vaddr,
uint64_t size) noexcept;
[[nodiscard]] bool ReleaseCommandProcessorReadback(PageFaultAccess access, uint64_t vaddr,
uint64_t size) noexcept;
Common::Mutex m_mutex;
std::vector<VulkanBuffer*> m_delete_later;
std::shared_ptr<VulkanBuffer> m_null_buffer;
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
std::unique_ptr<ReadbackWorker> m_readback;
std::unique_ptr<CommandProcessorReadbackResources> m_cp_readback_resources;
RangeSet m_gpu_modified_ranges;
MemoryTracker m_memory_tracker;
PageManager& m_page_manager;
TextureCache* m_texture_cache = nullptr;
ResourceMutex& m_resource_mutex;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
@@ -0,0 +1,515 @@
#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/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/renderer/renderState.h"
#include "graphics/host_gpu/utils.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <limits>
#include <vulkan/vk_enum_string_helper.h>
namespace Libs::Graphics {
static std::atomic<uint32_t> g_render_color_log_count = 0;
static float ColorClearUnorm(uint32_t value, uint32_t bits) {
return static_cast<float>(value) / static_cast<float>((1u << bits) - 1u);
}
static float ColorClearSnorm(uint32_t value, uint32_t bits) {
const uint32_t mask = (1u << bits) - 1u;
const uint32_t sign_bit = 1u << (bits - 1u);
value &= mask;
auto signed_value = static_cast<int32_t>(value);
if ((value & sign_bit) != 0) {
signed_value = static_cast<int32_t>(value | ~mask);
}
const auto denominator = static_cast<float>((1u << (bits - 1u)) - 1u);
return std::max(-1.0f, static_cast<float>(signed_value) / denominator);
}
static float ColorClearF16(uint32_t value) {
const uint32_t sign = (value >> 15u) & 0x1u;
const uint32_t exponent = (value >> 10u) & 0x1fu;
const uint32_t mantissa = value & 0x3ffu;
const float sign_mul = (sign != 0 ? -1.0f : 1.0f);
if (exponent == 0) {
if (mantissa == 0) {
return sign_mul * 0.0f;
}
return sign_mul * std::ldexp(static_cast<float>(mantissa), -24);
}
if (exponent == 0x1fu) {
return mantissa == 0 ? sign_mul * std::numeric_limits<float>::infinity()
: std::numeric_limits<float>::quiet_NaN();
}
return sign_mul *
std::ldexp(static_cast<float>(0x400u | mantissa), static_cast<int>(exponent) - 25);
}
[[maybe_unused]] static VkClearColorValue ColorClearValue(const HW::RenderTarget& rt) {
const uint32_t c0 = rt.clear_word0.word0;
const uint32_t c1 = rt.clear_word1.word1;
VkClearColorValue color {};
switch (static_cast<Prospero::ChannelType>(rt.info.channel_type)) {
case Prospero::ChannelType::kUNorm:
case Prospero::ChannelType::kSrgb:
// SRGB clear words are still encoded as normalized component values.
switch (static_cast<Prospero::ChannelLayout>(rt.info.format)) {
case Prospero::ChannelLayout::k8:
color.float32[0] = ColorClearUnorm(c0 & 0xffu, 8);
color.float32[1] = 0.0f;
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
case Prospero::ChannelLayout::k8_8:
color.float32[0] = ColorClearUnorm(c0 & 0xffu, 8);
color.float32[1] = ColorClearUnorm((c0 >> 8u) & 0xffu, 8);
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
case Prospero::ChannelLayout::k8_8_8_8:
color.float32[0] = ColorClearUnorm(c0 & 0xffu, 8);
color.float32[1] = ColorClearUnorm((c0 >> 8u) & 0xffu, 8);
color.float32[2] = ColorClearUnorm((c0 >> 16u) & 0xffu, 8);
color.float32[3] = ColorClearUnorm((c0 >> 24u) & 0xffu, 8);
if (rt.info.channel_order ==
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)) {
std::swap(color.float32[0], color.float32[2]);
}
break;
case Prospero::ChannelLayout::k11_11_10:
color.float32[0] = 0.0f;
color.float32[1] = 0.0f;
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
case Prospero::ChannelLayout::k10_10_10_2:
color.float32[0] = ColorClearUnorm(c0 & 0x3ffu, 10);
color.float32[1] = ColorClearUnorm((c0 >> 10u) & 0x3ffu, 10);
color.float32[2] = ColorClearUnorm((c0 >> 20u) & 0x3ffu, 10);
color.float32[3] = ColorClearUnorm((c0 >> 30u) & 0x3u, 2);
if (rt.info.channel_order ==
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)) {
std::swap(color.float32[0], color.float32[2]);
}
break;
case Prospero::ChannelLayout::k16_16:
color.float32[0] = 0.0f;
color.float32[1] = 0.0f;
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
case Prospero::ChannelLayout::k32_32_32_32:
color.float32[0] = 0.0f;
color.float32[1] = 0.0f;
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
default:
EXIT("%s\n unsupported color clear format\n",
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
}
break;
case Prospero::ChannelType::kSNorm:
switch (static_cast<Prospero::ChannelLayout>(rt.info.format)) {
case Prospero::ChannelLayout::k16_16:
color.float32[0] = ColorClearSnorm(c0 & 0xffffu, 16);
color.float32[1] = ColorClearSnorm((c0 >> 16u) & 0xffffu, 16);
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
case Prospero::ChannelLayout::k8_8_8_8:
color.float32[0] = ColorClearSnorm(c0 & 0xffu, 8);
color.float32[1] = ColorClearSnorm((c0 >> 8u) & 0xffu, 8);
color.float32[2] = ColorClearSnorm((c0 >> 16u) & 0xffu, 8);
color.float32[3] = ColorClearSnorm((c0 >> 24u) & 0xffu, 8);
if (rt.info.channel_order ==
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)) {
std::swap(color.float32[0], color.float32[2]);
}
break;
default:
EXIT("%s\n unsupported snorm color clear format\n",
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
}
break;
case Prospero::ChannelType::kFloat:
switch (static_cast<Prospero::ChannelLayout>(rt.info.format)) {
case Prospero::ChannelLayout::k16:
color.float32[0] = ColorClearF16(c0 & 0xffffu);
color.float32[1] = 0.0f;
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
case Prospero::ChannelLayout::k16_16:
color.float32[0] = ColorClearF16(c0 & 0xffffu);
color.float32[1] = ColorClearF16((c0 >> 16u) & 0xffffu);
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
case Prospero::ChannelLayout::k16_16_16_16:
color.float32[0] = ColorClearF16(c0 & 0xffffu);
color.float32[1] = ColorClearF16((c0 >> 16u) & 0xffffu);
color.float32[2] = ColorClearF16(c1 & 0xffffu);
color.float32[3] = ColorClearF16((c1 >> 16u) & 0xffffu);
break;
case Prospero::ChannelLayout::k32:
case Prospero::ChannelLayout::k11_11_10:
case Prospero::ChannelLayout::k32_32_32_32:
color.float32[0] = 0.0f;
color.float32[1] = 0.0f;
color.float32[2] = 0.0f;
color.float32[3] = 1.0f;
break;
default:
EXIT("%s\n unsupported float color clear format\n",
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
}
break;
case Prospero::ChannelType::kUInt:
case Prospero::ChannelType::kSInt:
switch (static_cast<Prospero::ChannelLayout>(rt.info.format)) {
case Prospero::ChannelLayout::k8:
color.uint32[0] = c0;
color.uint32[1] = 0;
color.uint32[2] = 0;
color.uint32[3] = 1;
break;
case Prospero::ChannelLayout::k16:
color.uint32[0] = c0 & 0xffffu;
color.uint32[1] = 0;
color.uint32[2] = 0;
color.uint32[3] = 1;
break;
case Prospero::ChannelLayout::k16_16:
color.uint32[0] = c0 & 0xffffu;
color.uint32[1] = (c0 >> 16u) & 0xffffu;
color.uint32[2] = 0;
color.uint32[3] = 1;
break;
case Prospero::ChannelLayout::k8_8_8_8:
color.uint32[0] = c0 & 0xffu;
color.uint32[1] = (c0 >> 8u) & 0xffu;
color.uint32[2] = (c0 >> 16u) & 0xffu;
color.uint32[3] = c0 >> 24u;
if (rt.info.channel_order ==
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)) {
std::swap(color.uint32[0], color.uint32[2]);
}
break;
case Prospero::ChannelLayout::k32_32:
color.uint32[0] = c0;
color.uint32[1] = c1;
color.uint32[2] = 0;
color.uint32[3] = 1;
break;
default:
EXIT("%s\n unsupported integer color clear format\n",
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
}
break;
default:
EXIT("%s\n unsupported color clear number type\n",
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
}
EXIT_NOT_IMPLEMENTED(
c1 != 0 &&
!(rt.info.channel_type == Prospero::GpuEnumValue(Prospero::ChannelType::kFloat) &&
rt.info.format == Prospero::GpuEnumValue(Prospero::ChannelLayout::k16_16_16_16)) &&
!((rt.info.channel_type == Prospero::GpuEnumValue(Prospero::ChannelType::kUInt) ||
rt.info.channel_type == Prospero::GpuEnumValue(Prospero::ChannelType::kSInt)) &&
rt.info.format == Prospero::GpuEnumValue(Prospero::ChannelLayout::k32_32)));
return color;
}
static uint32_t RenderGetColorBpp(uint32_t dfmt) {
switch (static_cast<Prospero::ChannelLayout>(dfmt)) {
case Prospero::ChannelLayout::k8: return 8;
case Prospero::ChannelLayout::k16:
case Prospero::ChannelLayout::k8_8: return 16;
case Prospero::ChannelLayout::k32:
case Prospero::ChannelLayout::k16_16:
case Prospero::ChannelLayout::k11_11_10:
case Prospero::ChannelLayout::k10_10_10_2:
case Prospero::ChannelLayout::k8_8_8_8: return 32;
case Prospero::ChannelLayout::k32_32:
case Prospero::ChannelLayout::k16_16_16_16: return 64;
case Prospero::ChannelLayout::k32_32_32_32: return 128;
case Prospero::ChannelLayout::k5_5_5_1:
case Prospero::ChannelLayout::k4_4_4_4: return 16;
default: EXIT("unsupported render-target channel layout: %u\n", dfmt);
}
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
RenderColorInfo* r, uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
bool reuse_existing_render_texture) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(r == nullptr);
const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(hw)
: render_target_slot);
const auto& rt = hw.GetRenderTarget(rt_slot);
auto mask = render_target_mask_slot(hw.GetRenderTargetMask(), rt_slot);
if (ignore_target_mask && rt.base.addr != 0 && mask == 0) {
mask = 0x0f;
}
r->target_slot = rt_slot;
if (rt.base.addr == 0 || mask == 0) {
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("RenderColorTarget: no color output slot=%" PRIu32 " base=0x%010" PRIx64
" slot_mask=0x%01" PRIx32 " target_mask=0x%08" PRIx32
" rt_slice_offset=%" PRIu32 "\n",
rt_slot, rt.base.addr, mask, hw.GetRenderTargetMask(),
render_target_slice_offset);
}
}
// No color output
r->type = RenderColorType::NoColorOutput;
r->base_addr = 0;
r->vulkan_buffer = nullptr;
r->vulkan_view = nullptr;
r->format = VK_FORMAT_UNDEFINED;
r->extent = {};
r->base_mip_level = 0;
r->buffer_size = 0;
r->color_clear_enable = false;
r->color_clear_value = {};
return;
}
const bool msaa_compat =
color_msaa_single_sample_compatible(rt.attrib.num_samples, rt.attrib.num_fragments);
if ((!msaa_compat && (rt.attrib.num_samples != 0 || rt.attrib.num_fragments != 0)) ||
rt.view.base_array_slice_index != 0 || render_target_slice_offset != 0) {
EXIT("multisampled or sliced render targets are unsupported\n");
}
const uint32_t levels = rt.attrib2.num_mip_levels + 1u;
if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) {
EXIT("unsupported render-target mip range: current=%u levels=%u\n",
rt.view.current_mip_level, levels);
}
if (msaa_compat) {
static std::atomic<uint32_t> logged_fragments = 0;
const uint32_t bit = 1u << rt.attrib.num_fragments;
if ((logged_fragments.fetch_or(bit, std::memory_order_relaxed) & bit) == 0) {
LOGF("RenderColorTarget: compatibility: rendering PS5 %ux samples/fragments as "
"single-sample\n",
bit);
}
}
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("RenderColorTarget: inspect slot=%" PRIu32 " base=0x%010" PRIx64
" mask=0x%01" PRIx32 " attrib2_width=%" PRIu32 " attrib2_height=%" PRIu32
" attrib3_tile=0x%08" PRIx32 " attrib3_dim=0x%08" PRIx32 " fmt=0x%08" PRIx32
" nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 "\n",
rt_slot, rt.base.addr, mask, rt.attrib2.width, rt.attrib2.height,
rt.attrib3.tile_mode, rt.attrib3.dimension, rt.info.format, rt.info.channel_type,
rt.info.channel_order);
}
}
// CB_COLOR_CONTROL describes the color-buffer operation / ROP3 logic op.
// ROP3 Copy is the normal color write path, not a render-target clear.
// Fast color clears are metadata driven and must be handled explicitly when
// that metadata path is implemented; render-pass load must preserve contents.
r->color_clear_enable = false;
r->color_clear_value = {};
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
switch (rt.attrib3.tile_mode) {
case Prospero::GpuEnumValue(Prospero::TileMode::kLinear):
case Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget):
tile = !RenderIsColorTileModeLinear(rt.attrib3.tile_mode);
break;
default: EXIT("unknown tile mode: %u\n", rt.attrib3.tile_mode);
}
if (!tile && levels > 1) {
EXIT("linear mipmapped render targets are unsupported\n");
}
width = rt.attrib2.width + 1;
height = rt.attrib2.height + 1;
const auto bytes_per_element = RenderGetColorBpp(rt.info.format) / 8u;
if (bytes_per_element == 0) {
EXIT("render-target format has no valid element size\n");
}
if (rt.pitch.pitch_div8_minus1 != 0) {
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
} else if (tile) {
pitch = TileGetRenderTargetPitch(width, bytes_per_element);
if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
}
} else {
pitch = width;
}
if (tile) {
TileSizeAlign layout {};
const bool valid_layout =
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element, &layout)
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
levels, &layout, nullptr, nullptr);
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;
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
"\n",
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
}
} else {
size = static_cast<uint64_t>(pitch) * height * bytes_per_element;
}
if (size == 0 || size > TRACKER_ADDRESS_SIZE - rt.base.addr) {
EXIT("render-target memory footprint is invalid\n");
}
const auto effective_slice = rt.view.base_array_slice_index + render_target_slice_offset;
const auto effective_base = rt.base.addr + static_cast<uint64_t>(size) * effective_slice;
auto video_image = Presentation::DisplayBufferFind(effective_base, true);
bool render_to_texture = (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 VkExtent2D 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) {
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
" tile=%s target=%s video_size=0x%016" PRIx64 " video_pitch=%" PRIu64 "\n",
rt_slot, effective_base, 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, tile ? "tiled" : "linear",
render_to_texture ? "RenderTexture" : "DisplayBuffer", video_image.size,
video_image.pitch);
}
if (render_to_texture) {
const auto rt_format = TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type,
rt.info.channel_order);
(void)reuse_existing_render_texture;
RenderTargetInfo target {};
target.address = effective_base;
target.size = size;
target.format = rt_format.format;
target.width = width;
target.height = height;
target.pitch = pitch;
target.bytes_per_element = rt_format.bytes_per_element;
target.tile_mode = rt.attrib3.tile_mode;
target.levels = levels;
auto* texture_cache = g_render_ctx->GetTextureCache();
auto* buffer_vulkan =
texture_cache->FindRenderTarget(buffer, g_render_ctx->GetGraphicCtx(), target);
r->type = RenderColorType::RenderTexture;
r->base_addr = effective_base;
r->vulkan_buffer = buffer_vulkan;
r->vulkan_view = texture_cache->GetRenderTargetAttachmentView(
g_render_ctx->GetGraphicCtx(), buffer_vulkan, target.format, rt.view.current_mip_level);
r->format = target.format;
r->extent = view_extent;
r->base_mip_level = rt.view.current_mip_level;
r->buffer_size = size;
} else {
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));
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 = effective_base;
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;
}
}
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) {
g_render_ctx->GetTextureCache()->MarkGpuWritten(target.vulkan_buffer);
} else {
EXIT("unknown writable render-color resource type\n");
}
}
}
} // namespace Libs::Graphics
+674
View File
@@ -0,0 +1,674 @@
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.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/renderer/renderState.h"
#include "graphics/host_gpu/utils.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/presentation/window.h"
#include <algorithm>
#include <atomic>
#include <cstring>
#include <memory>
#include <vulkan/vk_enum_string_helper.h>
namespace Libs::Graphics {
static std::atomic<uint64_t> g_command_buffer_submit_seq = 0;
class CommandPool {
public:
CommandPool() = default;
~CommandPool() // NOLINT
{
// TODO(): check if destructor is called from std::_Exit()
// DeleteAll();
}
KYTY_CLASS_NO_COPY(CommandPool);
VulkanCommandPool* GetPool(int id) {
if (m_pool[id] == nullptr) {
Create(id);
}
return m_pool[id];
}
private:
void Create(int id);
void DeleteAll();
VulkanCommandPool* m_pool[GraphicContext::QUEUES_NUM] = {};
};
RenderContext* g_render_ctx = nullptr;
static thread_local CommandPool g_command_pool;
FenceResourceRetainer::~FenceResourceRetainer() {
if (!m_resources.empty()) {
EXIT("fence resource retainer destroyed before release\n");
}
}
void FenceResourceRetainer::Retain(std::shared_ptr<void> resource) {
if (resource == nullptr) {
EXIT("cannot retain a null fence resource\n");
}
if (std::ranges::none_of(m_resources, [&resource](const auto& retained) {
return retained.get() == resource.get();
})) {
m_resources.push_back(std::move(resource));
}
}
void FenceResourceRetainer::ReleaseAfterFence() noexcept {
m_resources.clear();
}
void GraphicsRenderInit() {
EXIT_IF(g_render_ctx != nullptr);
g_render_ctx = new RenderContext;
}
void GraphicsRenderCreateContext() {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->SetGraphicCtx(WindowGetGraphicContext());
}
void CommandPool::Create(int id) {
auto* ctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(id < 0 || id >= GraphicContext::QUEUES_NUM);
auto*& pool = m_pool[id];
EXIT_IF(pool != nullptr);
EXIT_IF(ctx == nullptr);
EXIT_IF(ctx->device == nullptr);
EXIT_IF(ctx->queues[id].family == static_cast<uint32_t>(-1));
pool = new VulkanCommandPool;
VkCommandPoolCreateInfo pool_info {};
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pool_info.pNext = nullptr;
pool_info.queueFamilyIndex = ctx->queues[id].family;
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
vkCreateCommandPool(ctx->device, &pool_info, nullptr, &pool->pool);
EXIT_NOT_IMPLEMENTED(pool->pool == nullptr);
pool->buffers_count = 8;
pool->buffers = std::make_unique<VkCommandBuffer[]>(pool->buffers_count);
pool->fences = std::make_unique<VkFence[]>(pool->buffers_count);
pool->semaphores = std::make_unique<VkSemaphore[]>(pool->buffers_count);
pool->busy = std::make_unique<bool[]>(pool->buffers_count);
VkCommandBufferAllocateInfo alloc_info {};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_info.commandPool = pool->pool;
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
alloc_info.commandBufferCount = pool->buffers_count;
if (vkAllocateCommandBuffers(ctx->device, &alloc_info, pool->buffers.get()) != VK_SUCCESS) {
EXIT("Can't allocate command buffers");
}
for (uint32_t i = 0; i < pool->buffers_count; i++) {
pool->busy[i] = false;
VkFenceCreateInfo fence_info {};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.pNext = nullptr;
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
if (vkCreateFence(ctx->device, &fence_info, nullptr, &pool->fences[i]) != VK_SUCCESS) {
EXIT("Can't create fence");
}
VkSemaphoreCreateInfo semaphore_info {};
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphore_info.pNext = nullptr;
semaphore_info.flags = 0;
if (vkCreateSemaphore(ctx->device, &semaphore_info, nullptr, &pool->semaphores[i]) !=
VK_SUCCESS) {
EXIT("Can't create semaphore");
}
EXIT_IF(pool->buffers[i] == nullptr);
EXIT_IF(pool->fences[i] == nullptr);
EXIT_IF(pool->semaphores[i] == nullptr);
}
}
void CommandPool::DeleteAll() {
auto* ctx = g_render_ctx->GetGraphicCtx();
for (auto& pool: m_pool) {
if (pool != nullptr) {
EXIT_IF(ctx == nullptr);
EXIT_IF(ctx->device == nullptr);
for (uint32_t i = 0; i < pool->buffers_count; i++) {
vkDestroySemaphore(ctx->device, pool->semaphores[i], nullptr);
vkDestroyFence(ctx->device, pool->fences[i], nullptr);
}
vkFreeCommandBuffers(ctx->device, pool->pool, pool->buffers_count, pool->buffers.get());
vkDestroyCommandPool(ctx->device, pool->pool, nullptr);
delete pool;
pool = nullptr;
}
}
}
bool CommandBuffer::IsInvalid() const {
EXIT_IF(g_render_ctx == nullptr);
if (m_pool != nullptr) {
Common::LockGuard lock(m_pool->mutex);
return (m_index == static_cast<uint32_t>(-1) || m_index >= m_pool->buffers_count);
}
return true;
}
void CommandBuffer::Allocate() {
EXIT_IF(!IsInvalid());
m_pool = g_command_pool.GetPool(m_queue);
Common::LockGuard lock(m_pool->mutex);
for (uint32_t i = 0; i < m_pool->buffers_count; i++) {
if (!m_pool->busy[i]) {
m_pool->busy[i] = true;
vkResetCommandBuffer(m_pool->buffers[i], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
m_index = i;
break;
}
}
EXIT_NOT_IMPLEMENTED(IsInvalid());
}
void CommandBuffer::Free() {
EXIT_IF(IsInvalid());
Common::LockGuard lock(m_pool->mutex);
WaitForFence();
m_host_stream.Release();
m_pool->busy[m_index] = false;
vkResetCommandBuffer(m_pool->buffers[m_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
RecycleDescriptorsAfterFence();
m_fence_resources.ReleaseAfterFence();
m_index = static_cast<uint32_t>(-1);
EXIT_NOT_IMPLEMENTED(!IsInvalid());
}
void CommandBuffer::DeleteAfterFence(VulkanBuffer* buffer) {
EXIT_IF(buffer == nullptr);
m_delete_after_fence.push_back(buffer);
}
void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
if (IsInvalid() || m_execute) {
EXIT("cannot retain a resource on an invalid or submitted command buffer\n");
}
m_fence_resources.Retain(std::move(resource));
}
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet* set) {
EXIT_IF(set == nullptr);
m_descriptor_sets_after_fence.push_back(set);
}
void CommandBuffer::RecycleDescriptorsAfterFence() {
for (auto* set: m_descriptor_sets_after_fence) {
g_render_ctx->GetDescriptorCache()->Recycle(set);
}
m_descriptor_sets_after_fence.clear();
}
void CommandBuffer::Begin() const {
EXIT_IF(IsInvalid());
auto* buffer = m_pool->buffers[m_index];
VkCommandBufferBeginInfo begin_info {};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.pNext = nullptr;
begin_info.flags = 0;
begin_info.pInheritanceInfo = nullptr;
auto result = vkBeginCommandBuffer(buffer, &begin_info);
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
}
void CommandBuffer::End() const {
EXIT_IF(IsInvalid());
auto* buffer = m_pool->buffers[m_index];
auto result = vkEndCommandBuffer(buffer);
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
}
void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0, uint32_t arg1,
uint32_t arg2, uint32_t arg3, uint64_t arg4) {
m_debug_op = op;
m_debug_submit_id = submit_id;
m_debug_arg0 = arg0;
m_debug_arg1 = arg1;
m_debug_arg2 = arg2;
m_debug_arg3 = arg3;
m_debug_arg4 = arg4;
}
void CommandBuffer::Execute() {
EXIT_IF(IsInvalid());
EXIT_IF(m_execute);
auto* buffer = m_pool->buffers[m_index];
auto* fence = m_pool->fences[m_index];
VkSubmitInfo submit_info {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.pNext = nullptr;
submit_info.waitSemaphoreCount = 0;
submit_info.pWaitSemaphores = nullptr;
submit_info.pWaitDstStageMask = nullptr;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = 0;
submit_info.pSignalSemaphores = nullptr;
EXIT_IF(m_queue < 0 || m_queue >= GraphicContext::QUEUES_NUM);
const auto& queue = g_render_ctx->GetGraphicCtx()->queues[m_queue];
auto result = vkResetFences(g_render_ctx->GetGraphicCtx()->device, 1, &fence);
if (result != VK_SUCCESS) {
LOGF("vkResetFences failed before submit: %s (%d)\n", string_VkResult(result),
static_cast<int>(result));
}
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
if (queue.mutex != nullptr) {
queue.mutex->Lock();
}
if (Config::GraphicsDebugDumpEnabled()) {
LOGF("vkQueueSubmit begin: queue=%d index=%u debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_queue, m_index, m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1,
m_debug_arg2, m_debug_arg3, m_debug_arg4);
}
result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
if (queue.mutex != nullptr) {
queue.mutex->Unlock();
}
m_execute = true;
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
if (result != VK_SUCCESS) {
LOGF("vkQueueSubmit failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
}
void CommandBuffer::ExecuteWithSemaphore(VkSemaphore signal_semaphore) {
EXIT_IF(IsInvalid());
EXIT_IF(m_execute);
auto* buffer = m_pool->buffers[m_index];
auto* fence = m_pool->fences[m_index];
if (signal_semaphore == nullptr) {
signal_semaphore = m_pool->semaphores[m_index];
}
VkSubmitInfo submit_info {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.pNext = nullptr;
submit_info.waitSemaphoreCount = 0;
submit_info.pWaitSemaphores = nullptr;
submit_info.pWaitDstStageMask = nullptr;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &signal_semaphore;
EXIT_IF(m_queue < 0 || m_queue >= GraphicContext::QUEUES_NUM);
const auto& queue = g_render_ctx->GetGraphicCtx()->queues[m_queue];
auto result = vkResetFences(g_render_ctx->GetGraphicCtx()->device, 1, &fence);
if (result != VK_SUCCESS) {
LOGF("vkResetFences failed before submit: %s (%d)\n", string_VkResult(result),
static_cast<int>(result));
}
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
if (queue.mutex != nullptr) {
queue.mutex->Lock();
}
if (Config::GraphicsDebugDumpEnabled()) {
LOGF("vkQueueSubmit begin: queue=%d index=%u signal_semaphore=%p debug_op=%u"
" debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_queue, m_index, static_cast<void*>(signal_semaphore), m_debug_op, m_debug_submit_id,
m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3, m_debug_arg4);
}
result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
if (queue.mutex != nullptr) {
queue.mutex->Unlock();
}
m_execute = true;
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
if (result != VK_SUCCESS) {
LOGF("vkQueueSubmit failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
}
void CommandBuffer::ExecuteWithSemaphore(VkSemaphore wait_semaphore,
VkPipelineStageFlags wait_stage,
VkSemaphore signal_semaphore) {
EXIT_IF(IsInvalid());
EXIT_IF(m_execute);
EXIT_IF(wait_semaphore == nullptr);
auto* buffer = m_pool->buffers[m_index];
auto* fence = m_pool->fences[m_index];
if (signal_semaphore == nullptr) {
signal_semaphore = m_pool->semaphores[m_index];
}
VkSubmitInfo submit_info {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.pNext = nullptr;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &wait_semaphore;
submit_info.pWaitDstStageMask = &wait_stage;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &signal_semaphore;
EXIT_IF(m_queue < 0 || m_queue >= GraphicContext::QUEUES_NUM);
const auto& queue = g_render_ctx->GetGraphicCtx()->queues[m_queue];
auto result = vkResetFences(g_render_ctx->GetGraphicCtx()->device, 1, &fence);
if (result != VK_SUCCESS) {
LOGF("vkResetFences failed before submit: %s (%d)\n", string_VkResult(result),
static_cast<int>(result));
}
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
if (queue.mutex != nullptr) {
queue.mutex->Lock();
}
if (Config::GraphicsDebugDumpEnabled()) {
LOGF("vkQueueSubmit begin: queue=%d index=%u wait_semaphore=%p signal_semaphore=%p"
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_queue, m_index, 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, m_debug_arg4);
}
result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
if (queue.mutex != nullptr) {
queue.mutex->Unlock();
}
m_execute = true;
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
if (result != VK_SUCCESS) {
LOGF("vkQueueSubmit failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
}
void CommandBuffer::WaitForFence() {
EXIT_IF(IsInvalid());
const bool was_executed = m_execute;
if (m_execute) {
auto* device = g_render_ctx->GetGraphicCtx()->device;
auto result = vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX);
if (result != VK_SUCCESS) {
LOGF("vkWaitForFences failed: %s (%d), wait=WaitForFence queue=%d index=%u "
"submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
m_debug_arg3, m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
m_execute = false;
}
if (was_executed) {
RecycleDescriptorsAfterFence();
m_fence_resources.ReleaseAfterFence();
}
for (auto* buffer: m_delete_after_fence) {
VulkanDeleteBuffer(g_render_ctx->GetGraphicCtx(), buffer);
delete buffer;
}
m_delete_after_fence.clear();
}
void CommandBuffer::WaitForFenceAndReset() {
EXIT_IF(IsInvalid());
const bool was_executed = m_execute;
if (m_execute) {
auto* device = g_render_ctx->GetGraphicCtx()->device;
auto result = vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX);
if (result != VK_SUCCESS) {
LOGF("vkWaitForFences failed: %s (%d), wait=WaitForFenceAndReset queue=%d index=%u "
"submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
m_debug_arg3, m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
m_execute = false;
vkResetCommandBuffer(m_pool->buffers[m_index],
VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
}
m_host_stream.Reset();
if (was_executed) {
RecycleDescriptorsAfterFence();
m_fence_resources.ReleaseAfterFence();
}
for (auto* buffer: m_delete_after_fence) {
VulkanDeleteBuffer(g_render_ctx->GetGraphicCtx(), buffer);
delete buffer;
}
m_delete_after_fence.clear();
}
void CommandBuffer::BeginRenderPass(VulkanFramebuffer* framebuffer, RenderColorInfo* colors,
uint32_t requested_color_count, RenderDepthInfo* depth) const {
EXIT_IF(IsInvalid());
auto* buffer = m_pool->buffers[m_index];
EXIT_IF(framebuffer == nullptr);
EXIT_IF(colors == nullptr);
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
bool with_depth = (depth->format != VK_FORMAT_UNDEFINED && 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++;
}
bool with_color = (color_count != 0);
EXIT_NOT_IMPLEMENTED(!with_depth && !with_color);
VkClearValue 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};
VkExtent2D extent = (with_color ? colors[0].extent : depth->vulkan_buffer->extent);
VkRenderPassBeginInfo render_pass_info {};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
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, reinterpret_cast<void*>(colors[i].vulkan_buffer->image),
colors[i].vulkan_buffer->memory.unique_id,
string_VkImageLayout(colors[i].vulkan_buffer->layout),
string_VkImageLayout(color_initial_layout));
}
VkImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = 0;
image_memory_barrier.dstAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
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_IMAGE_ASPECT_COLOR_BIT;
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 = 1;
vkCmdPipelineBarrier(buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 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,
reinterpret_cast<void*>(colors[i].vulkan_buffer->image),
colors[i].vulkan_buffer->memory.unique_id,
string_VkImageLayout(colors[i].vulkan_buffer->layout));
}
}
const auto depth_layout =
(with_depth && framebuffer != nullptr ? framebuffer->depth_layout
: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
if (with_depth && depth->vulkan_buffer->layout != depth_layout) {
VkImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
image_memory_barrier.dstAccessMask =
(depth_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
? VK_ACCESS_MEMORY_READ_BIT
: VK_ACCESS_MEMORY_WRITE_BIT);
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 =
DepthStencilAspectMask(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 = 1;
vkCmdPipelineBarrier(
buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0,
nullptr, 0, nullptr, 1, &image_memory_barrier);
depth->vulkan_buffer->layout = image_memory_barrier.newLayout;
}
vkCmdBeginRenderPass(buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
for (uint32_t i = 0; i < color_count; i++) {
colors[i].vulkan_buffer->layout = RENDER_COLOR_IMAGE_LAYOUT;
}
}
void CommandBuffer::EndRenderPass() const {
EXIT_IF(IsInvalid());
auto* buffer = m_pool->buffers[m_index];
vkCmdEndRenderPass(buffer);
}
} // namespace Libs::Graphics
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,357 @@
#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/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/renderer/renderState.h"
#include "graphics/host_gpu/utils.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <limits>
#include <vulkan/vk_enum_string_helper.h>
namespace Libs::Graphics {
[[noreturn]] static void DepthFatal(const char* format, ...) {
std::fputs("Depth target fatal: ", stderr);
va_list args;
va_start(args, format);
std::vfprintf(stderr, format, args);
va_end(args);
std::fputc('\n', stderr);
std::fflush(stderr);
EXIT("unsupported render state; details were printed above\n");
}
static VkStencilOp ConvertStencilOp(uint8_t value) {
switch (static_cast<Prospero::StencilOp>(value)) {
case Prospero::StencilOp::kKeep: return VK_STENCIL_OP_KEEP;
case Prospero::StencilOp::kZero: return VK_STENCIL_OP_ZERO;
case Prospero::StencilOp::kReplaceTest:
case Prospero::StencilOp::kReplaceOp: return VK_STENCIL_OP_REPLACE;
case Prospero::StencilOp::kAddClamp: return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
case Prospero::StencilOp::kSubClamp: return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
case Prospero::StencilOp::kInvert: return VK_STENCIL_OP_INVERT;
case Prospero::StencilOp::kAddWrap: return VK_STENCIL_OP_INCREMENT_AND_WRAP;
case Prospero::StencilOp::kSubWrap: return VK_STENCIL_OP_DECREMENT_AND_WRAP;
default: DepthFatal("unsupported stencil operation");
}
}
static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
return fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp) ||
pass == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp) ||
depth_fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
RenderDepthInfo* r) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(r == nullptr);
(void)submit_id;
(void)buffer;
const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool depth_active =
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
if (!depth_active && !stencil_active) {
return;
}
const bool attachment_unbound =
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kInvalid) &&
z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid) &&
z.z_info.tile_mode_index == 0 && z.z_info.num_samples == 0 &&
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.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 &&
!z.stencil_info.expclear_enabled && !z.stencil_info.texture_compatible_stencil &&
!z.stencil_info.partially_resident && z.depth_view.slice_start == 0 &&
z.depth_view.slice_max == 0 && z.depth_view.current_mip_level == 0 &&
!z.depth_view.depth_write_disable && !z.depth_view.stencil_write_disable &&
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 && 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 &&
z.z_read_base_addr == 0 && z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
z.stencil_write_base_addr == 0 && z.htile_data_base_addr == 0 &&
!z.z_info.tile_surface_enable && !z.size.valid && !z.width_height_valid &&
!z.pitch_height_valid && z.size.x_max == 0 && z.size.y_max == 0 &&
z.pitch_div8_minus1 == 0 && z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0 &&
z.width == 0 && z.height == 0;
if (attachment_unbound) {
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: ignoring enabled depth/stencil state without a bound attachment\n");
}
return;
}
const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const bool has_htile = z.z_info.tile_surface_enable;
const bool msaa_compat = depth_msaa_single_sample_compatible(z.z_info.num_samples);
const bool htile_stencil_compat = depth_htile_stencil_acceleration_compatible(
has_stencil, has_htile, z.stencil_info.tile_stencil_disable);
// Prospero defines the compression-disable bits as tile writeback policy. Vulkan attachments
// expose the same logical depth/stencil values regardless of the driver's backing compression.
if ((stencil_active && !has_stencil) || rc.resummarize_enable || rc.copy_centroid ||
rc.copy_sample != 0 || z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
z.z_info.embedded_sample_locations || z.z_info.partially_resident ||
z.stencil_info.partially_resident || z.z_info.plane_compression != 0 ||
(z.z_info.num_samples != 0 && !msaa_compat) || z.z_info.num_mip_levels != 0 ||
z.z_info.tile_mode_index != 0 || z.z_info.zrange_precision > 1 ||
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 ||
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 || z.z_read_base_addr == 0 ||
z.z_write_base_addr != z.z_read_base_addr || (z.z_read_base_addr & 0xffffu) != 0 ||
dc.zfunc > static_cast<uint8_t>(VK_COMPARE_OP_ALWAYS)) {
DepthFatal("unsupported depth register state");
}
if (msaa_compat) {
static std::atomic<uint32_t> logged_fragments = 0;
const uint32_t bit = 1u << z.z_info.num_samples;
if ((logged_fragments.fetch_or(bit, std::memory_order_relaxed) & bit) == 0) {
LOGF("DepthTarget: compatibility: rendering PS5 %ux depth fragments as single-sample\n",
bit);
}
}
if (has_stencil) {
// Prospero defines Hi-Stencil as HTile-backed acceleration of the logical stencil plane.
// Keep the plane native in Vulkan while tracking HTile separately.
if (z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) ||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
!htile_stencil_compat || z.stencil_info.texture_compatible_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.depth_view.stencil_write_disable) {
DepthFatal("unsupported stencil attachment state");
}
if (!z.stencil_info.tile_stencil_disable) {
static std::atomic_bool logged = false;
if (!logged.load(std::memory_order_relaxed) &&
!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: compatibility: using native stencil with PS5 HTILE "
"acceleration\n");
}
}
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0 ||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil) {
DepthFatal("stencil state without an active stencil attachment");
}
if (has_htile) {
if (z.htile_data_base_addr == 0 || (z.htile_data_base_addr & 0x7fffu) != 0) {
DepthFatal("invalid HTile metadata address");
}
} else if (z.htile_data_base_addr != 0) {
DepthFatal("HTile address without an enabled tile surface");
}
const bool size_xy_valid = z.size.valid && (z.size.x_max != 0 || z.size.y_max != 0);
const bool wh_valid = z.width_height_valid && z.width != 0 && z.height != 0;
if (!size_xy_valid && !wh_valid) {
DepthFatal("missing depth extent");
}
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))) {
DepthFatal("inconsistent depth extent or encoded layout");
}
uint32_t guest_format = 0;
uint32_t bytes = 0;
switch (static_cast<Prospero::DepthFormat>(z.z_info.format)) {
case Prospero::DepthFormat::kZ16:
if (has_stencil) {
DepthFatal("Z16 plus stencil is unsupported");
}
r->format = VK_FORMAT_D16_UNORM;
guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm);
bytes = 2;
break;
case Prospero::DepthFormat::kZ32F:
r->format = has_stencil ? VK_FORMAT_D32_SFLOAT_S8_UINT : VK_FORMAT_D32_SFLOAT;
guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
bytes = 4;
break;
default: DepthFatal("unsupported depth format");
}
const auto pitch = TileGetTexturePitch(guest_format, width, 1,
Prospero::GpuEnumValue(Prospero::TileMode::kDepth));
if (z.pitch_height_valid && ((static_cast<uint64_t>(z.pitch_div8_minus1) + 1u) * 8u != pitch ||
(static_cast<uint64_t>(z.height_div8_minus1) + 1u) * 8u !=
((static_cast<uint64_t>(height) + 7u) & ~7ull))) {
DepthFatal("encoded depth pitch or height mismatch");
}
const uint32_t block_width = bytes == 2 ? 256u : 128u;
const uint64_t padded_width =
(static_cast<uint64_t>(pitch) + block_width - 1u) & ~(block_width - 1u);
const uint64_t padded_height =
(static_cast<uint64_t>(height) + 127u) & ~static_cast<uint64_t>(127u);
if (padded_width > UINT64_MAX / padded_height ||
padded_width * padded_height > UINT64_MAX / bytes) {
DepthFatal("depth footprint overflow");
}
const uint64_t expected_size = padded_width * padded_height * bytes;
TileSizeAlign depth_size {};
TileSizeAlign stencil_size {};
TileSizeAlign htile_size {};
if (has_stencil || has_htile) {
if (!TileGetDepthSize(width, height, 0, z.z_info.format, z.stencil_info.format, has_htile,
&stencil_size, &htile_size, &depth_size) ||
depth_size.align != 65536 || depth_size.size != expected_size ||
(has_stencil != (stencil_size.align == 65536 && stencil_size.size != 0)) ||
(has_htile != (htile_size.align == 32768 && htile_size.size != 0))) {
DepthFatal("unsupported depth/stencil/HTile footprint");
}
if (has_htile) {
g_render_ctx->GetTextureCache()->RegisterMeta(z.htile_data_base_addr, htile_size.size);
}
} else {
TileGetTextureTotalSize(guest_format, width, height, 1, pitch, 1,
Prospero::GpuEnumValue(Prospero::TileMode::kDepth), false,
&depth_size);
}
if (expected_size == 0 || expected_size > UINT32_MAX || depth_size.align != 65536 ||
depth_size.size != expected_size ||
(z.pitch_height_valid &&
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u != expected_size)) {
DepthFatal("depth footprint mismatch: extent=%ux%u pitch=%u expected=0x%016" PRIx64
" calculated=0x%016" PRIx64 "/0x%016" PRIx64
" encoded_valid=%u encoded=0x%016" PRIx64,
width, height, pitch, expected_size, depth_size.size, depth_size.align,
z.pitch_height_valid ? 1u : 0u,
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u);
}
r->htile = has_htile;
r->width = width;
r->height = height;
r->depth_buffer_size = depth_size.size;
r->depth_buffer_vaddr = z.z_read_base_addr;
r->stencil_buffer_size = has_stencil ? stencil_size.size : 0;
r->stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
r->htile_buffer_size = has_htile ? htile_size.size : 0;
r->htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
auto* cache = g_render_ctx->GetTextureCache();
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_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;
r->depth_compare_op = static_cast<VkCompareOp>(dc.zfunc);
r->depth_bounds_test_enable = dc.depth_bounds_enable;
r->depth_min_bounds = hw.GetDepthBoundsMin();
r->depth_max_bounds = hw.GetDepthBoundsMax();
r->stencil_clear_enable = rc.stencil_clear_enable;
r->stencil_clear_value = hw.GetStencilClearValue();
r->stencil_test_enable = dc.stencil_enable;
if (dc.stencil_enable) {
if (dc.stencilfunc > static_cast<uint8_t>(VK_COMPARE_OP_ALWAYS) ||
(dc.backface_enable &&
dc.stencilfunc_bf > static_cast<uint8_t>(VK_COMPARE_OP_ALWAYS)) ||
(UsesStencilOpValue(sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail) &&
sm.stencil_opval != sm.stencil_testval) ||
(dc.backface_enable &&
UsesStencilOpValue(sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf) &&
sm.stencil_opval_bf != sm.stencil_testval_bf)) {
DepthFatal("unsupported stencil compare or replacement state");
}
r->stencil_static_front = {
ConvertStencilOp(sc.stencil_fail), ConvertStencilOp(sc.stencil_zpass),
ConvertStencilOp(sc.stencil_zfail), static_cast<VkCompareOp>(dc.stencilfunc)};
r->stencil_dynamic_front = {sm.stencil_mask,
rc.stencil_clear_enable ? 0u : sm.stencil_writemask,
sm.stencil_testval};
if (dc.backface_enable) {
r->stencil_static_back = {
ConvertStencilOp(sc.stencil_fail_bf), ConvertStencilOp(sc.stencil_zpass_bf),
ConvertStencilOp(sc.stencil_zfail_bf), static_cast<VkCompareOp>(dc.stencilfunc_bf)};
r->stencil_dynamic_back = {sm.stencil_mask_bf,
rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf,
sm.stencil_testval_bf};
} else {
r->stencil_static_back = r->stencil_static_front;
r->stencil_dynamic_back = r->stencil_dynamic_front;
}
}
r->vaddr_num = has_stencil ? 2 : 1;
r->vaddr[0] = r->depth_buffer_vaddr;
r->size[0] = r->depth_buffer_size;
if (has_stencil) {
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.depth_load_clear = r->depth_load_clear_enable;
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)));
r->vulkan_buffer = cache->FindDepthTarget(buffer, g_render_ctx->GetGraphicCtx(), info);
if (meta_clear && !cache->TouchMeta(z.htile_data_base_addr, z.depth_view.slice_start, false)) {
DepthFatal("failed to consume HTile clear state");
}
}
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target) {
const bool with_depth = target.format != VK_FORMAT_UNDEFINED && target.vulkan_buffer != nullptr;
if (with_depth && !depth_attachment_read_only(&target)) {
g_render_ctx->GetTextureCache()->MarkGpuWritten(target.vulkan_buffer);
}
}
} // namespace Libs::Graphics
@@ -0,0 +1,303 @@
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include <array>
namespace Libs::Graphics {
namespace {
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
VkShaderStageFlags StageFlags(DescriptorCache::Stage stage) {
switch (stage) {
case DescriptorCache::Stage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT;
case DescriptorCache::Stage::Pixel: return VK_SHADER_STAGE_FRAGMENT_BIT;
case DescriptorCache::Stage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT;
default: EXIT("unknown descriptor stage\n");
}
}
bool IsSampledImage(BindingKind kind) {
switch (kind) {
case BindingKind::Sampled2D:
case BindingKind::Sampled2DArray:
case BindingKind::Sampled3D:
case BindingKind::SampledUint2D:
case BindingKind::SampledUint2DArray:
case BindingKind::SampledUint3D: return true;
default: return false;
}
}
bool IsStorageImage(BindingKind kind) {
switch (kind) {
case BindingKind::Storage2D:
case BindingKind::Storage2DArray:
case BindingKind::Storage3D:
case BindingKind::StorageUint2D:
case BindingKind::StorageUint2DArray:
case BindingKind::StorageUint3D: return true;
default: return false;
}
}
VkDescriptorType DescriptorType(BindingKind kind) {
if (kind == BindingKind::Samplers) {
return VK_DESCRIPTOR_TYPE_SAMPLER;
}
if (IsSampledImage(kind)) {
return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
}
if (IsStorageImage(kind)) {
return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
}
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
}
uint32_t DescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
}
std::vector<uint32_t> LayoutKey(DescriptorCache::Stage stage,
const ShaderRecompiler::IR::Program& program) {
std::vector<uint32_t> key;
key.reserve(1u + program.bindings.descriptors.size() * 4u);
key.push_back(static_cast<uint32_t>(stage));
for (const auto& binding: program.bindings.descriptors) {
key.push_back(static_cast<uint32_t>(binding.kind));
key.push_back(binding.binding);
key.push_back(DescriptorCount(binding));
key.push_back(static_cast<uint32_t>(DescriptorType(binding.kind)));
}
return key;
}
VkImageLayout SampledLayout(const VulkanImage* image) {
EXIT_IF(image == nullptr);
switch (image->layout) {
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: return VK_IMAGE_LAYOUT_GENERAL;
default: return image->layout;
}
}
VkDescriptorBufferInfo BufferInfo(const BufferView& view) {
EXIT_IF(view.buffer == nullptr || view.buffer->buffer == nullptr);
return {view.buffer->buffer, view.offset, view.range};
}
VkDescriptorImageInfo MakeDescriptorImageInfo(const DescriptorCache::TextureBinding& texture,
VkImageLayout 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};
}
} // namespace
void DescriptorCache::Init() {
m_initialized = true;
}
VkDescriptorSetLayout
DescriptorCache::GetDescriptorSetLayoutInternal(GraphicContext* gctx, Stage stage,
const ShaderRecompiler::IR::Program& program) {
EXIT_IF(gctx == nullptr);
Init();
const auto key = LayoutKey(stage, program);
if (const auto found = m_descriptor_set_layouts.find(key);
found != m_descriptor_set_layouts.end()) {
return found->second;
}
std::vector<VkDescriptorSetLayoutBinding> bindings;
bindings.reserve(program.bindings.descriptors.size());
for (const auto& descriptor: program.bindings.descriptors) {
bindings.push_back({descriptor.binding, DescriptorType(descriptor.kind),
DescriptorCount(descriptor), StageFlags(stage), nullptr});
}
if (bindings.empty()) {
return nullptr;
}
VkDescriptorSetLayoutCreateInfo info {};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
info.bindingCount = static_cast<uint32_t>(bindings.size());
info.pBindings = bindings.data();
VkDescriptorSetLayout layout = nullptr;
vkCreateDescriptorSetLayout(gctx->device, &info, nullptr, &layout);
EXIT_NOT_IMPLEMENTED(layout == nullptr);
m_descriptor_set_layouts.emplace(key, layout);
return layout;
}
void DescriptorCache::CreatePool(GraphicContext* gctx) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(gctx == nullptr);
constexpr uint32_t MaxSets = 512;
const VkDescriptorPoolSize sizes[] = {
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
MaxSets * (ShaderRecompiler::IR::ShaderInfo::MaxBuffers +
ShaderRecompiler::IR::ShaderInfo::MaxAddresses + 3u)},
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
{VK_DESCRIPTOR_TYPE_SAMPLER, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxSamplers},
};
VkDescriptorPoolCreateInfo info {};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
info.poolSizeCount = static_cast<uint32_t>(std::size(sizes));
info.pPoolSizes = sizes;
info.maxSets = MaxSets;
const auto pool_id = static_cast<int>(m_pools.size());
auto& pool = m_pools.emplace_back();
vkCreateDescriptorPool(gctx->device, &info, nullptr, &pool.pool);
EXIT_NOT_IMPLEMENTED(pool.pool == nullptr);
pool.next_free_pool = m_first_free_pool;
m_first_free_pool = pool_id;
}
VulkanDescriptorSet* DescriptorCache::Allocate(Stage stage,
const ShaderRecompiler::IR::Program& program) {
KYTY_PROFILER_FUNCTION();
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
Common::LockGuard lock(m_mutex);
const auto layout = GetDescriptorSetLayoutInternal(gctx, stage, program);
EXIT_IF(layout == nullptr);
auto& free_sets = m_free_sets_by_layout[layout];
if (!free_sets.empty()) {
auto* result = free_sets.back();
free_sets.pop_back();
return result;
}
constexpr uint32_t Batch = 32;
for (int attempt = 0; attempt < 2; attempt++) {
for (int pool_id = m_first_free_pool; pool_id != -1;
pool_id = m_pools[pool_id].next_free_pool) {
auto& pool = m_pools[pool_id];
std::array<VkDescriptorSetLayout, Batch> layouts;
layouts.fill(layout);
std::array<VkDescriptorSet, Batch> sets {};
VkDescriptorSetAllocateInfo info {};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
info.descriptorPool = pool.pool;
info.descriptorSetCount = Batch;
info.pSetLayouts = layouts.data();
if (vkAllocateDescriptorSets(gctx->device, &info, sets.data()) == VK_SUCCESS) {
free_sets.reserve(free_sets.size() + Batch - 1u);
for (uint32_t i = 0; i + 1u < Batch; i++) {
free_sets.push_back(new VulkanDescriptorSet {sets[i], layout, pool_id});
}
return new VulkanDescriptorSet {sets.back(), layout, pool_id};
}
pool.free = false;
m_first_free_pool = pool.next_free_pool;
break;
}
CreatePool(gctx);
}
return nullptr;
}
void DescriptorCache::Recycle(VulkanDescriptorSet* set) {
EXIT_IF(set == nullptr || set->set == nullptr || set->layout == nullptr);
Common::LockGuard lock(m_mutex);
m_free_sets_by_layout[set->layout].push_back(set);
}
VulkanDescriptorSet* DescriptorCache::GetDescriptor(Stage stage,
const ShaderRecompiler::IR::Program& program,
const NativeDescriptors& data) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(data.buffers.size() != program.info.buffers.size() ||
data.images.size() != program.info.images.size() ||
data.samplers.size() != program.info.samplers.size() ||
data.addresses.size() != program.info.addresses.size());
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
auto* set = Allocate(stage, program);
EXIT_NOT_IMPLEMENTED(set == nullptr);
const auto descriptor_count = program.info.buffers.size() + program.info.images.size() +
program.info.samplers.size() + program.info.addresses.size() + 3u;
std::vector<VkDescriptorBufferInfo> buffer_infos;
std::vector<VkDescriptorImageInfo> image_infos;
std::vector<VkWriteDescriptorSet> writes;
buffer_infos.reserve(descriptor_count);
image_infos.reserve(descriptor_count);
writes.reserve(program.bindings.descriptors.size());
for (const auto& binding: program.bindings.descriptors) {
VkWriteDescriptorSet write {};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = set->set;
write.dstBinding = binding.binding;
write.descriptorType = DescriptorType(binding.kind);
write.descriptorCount = DescriptorCount(binding);
const auto buffer_start = buffer_infos.size();
const auto image_start = image_infos.size();
switch (binding.kind) {
case BindingKind::Buffers:
for (const auto resource: binding.resources) {
buffer_infos.push_back(BufferInfo(data.buffers.at(resource)));
}
break;
case BindingKind::AddressMemory:
for (const auto resource: binding.resources) {
buffer_infos.push_back(BufferInfo(data.addresses.at(resource)));
}
break;
case BindingKind::FlattenedSrt:
buffer_infos.push_back(BufferInfo(data.flattened_srt));
break;
case BindingKind::UserData: buffer_infos.push_back(BufferInfo(data.user_data)); break;
case BindingKind::Gds: buffer_infos.push_back(BufferInfo(data.gds)); break;
case BindingKind::Samplers:
for (const auto resource: binding.resources) {
const auto sampler = data.samplers.at(resource);
EXIT_IF(sampler == nullptr);
image_infos.push_back({sampler, nullptr, VK_IMAGE_LAYOUT_UNDEFINED});
}
break;
default: {
const auto layout = IsStorageImage(binding.kind) ? VK_IMAGE_LAYOUT_GENERAL
: VK_IMAGE_LAYOUT_UNDEFINED;
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));
}
break;
}
}
if (buffer_infos.size() != buffer_start) {
write.pBufferInfo = buffer_infos.data() + buffer_start;
}
if (image_infos.size() != image_start) {
write.pImageInfo = image_infos.data() + image_start;
}
writes.push_back(write);
}
vkUpdateDescriptorSets(gctx->device, static_cast<uint32_t>(writes.size()), writes.data(), 0,
nullptr);
return set;
}
VkDescriptorSetLayout
DescriptorCache::GetDescriptorSetLayout(Stage stage, const ShaderRecompiler::IR::Program& program) {
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
Common::LockGuard lock(m_mutex);
return GetDescriptorSetLayoutInternal(gctx, stage, program);
}
} // namespace Libs::Graphics
@@ -0,0 +1,118 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_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/shader/shaderBindings.h"
#include <map>
#include <unordered_map>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct Program;
}
class CommandBuffer;
struct ShaderStageRuntime;
struct VulkanDescriptor {
VkDescriptorSet descriptor_set = nullptr;
};
struct VulkanDescriptorSet {
VkDescriptorSet set = nullptr;
VkDescriptorSetLayout layout = nullptr;
int pool_id = -1;
};
struct BufferView {
VulkanBuffer* buffer = nullptr;
VkDeviceSize offset = 0;
VkDeviceSize range = VK_WHOLE_SIZE;
};
struct ShaderAddressWriteRange {
VulkanBuffer* buffer = nullptr;
uint64_t address = 0;
uint64_t size = 0;
};
class DescriptorCache {
public:
enum class Stage { Unknown, Vertex, Pixel, Compute };
struct TextureBinding {
VulkanImage* image = nullptr;
int view = VulkanImage::VIEW_DEFAULT;
VkImageView image_view = nullptr;
};
enum class TextureVariant : int {
Float2D = 0,
Uint2D,
FloatArray,
UintArray,
Float3D,
Uint3D,
};
struct NativeDescriptors {
std::vector<BufferView> buffers;
std::vector<TextureBinding> images;
std::vector<VkSampler> samplers;
std::vector<BufferView> addresses;
BufferView gds;
BufferView flattened_srt;
BufferView user_data;
};
DescriptorCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
virtual ~DescriptorCache() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(DescriptorCache);
VkDescriptorSetLayout GetDescriptorSetLayout(Stage stage,
const ShaderRecompiler::IR::Program& program);
void Recycle(VulkanDescriptorSet* set);
VulkanDescriptorSet* GetDescriptor(Stage stage, const ShaderRecompiler::IR::Program& program,
const NativeDescriptors& descriptors);
private:
struct Pool {
VkDescriptorPool pool = nullptr;
int next_free_pool = -1;
bool free = true;
};
void Init();
void CreatePool(GraphicContext* gctx);
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
VkDescriptorSetLayout
GetDescriptorSetLayoutInternal(GraphicContext* gctx, Stage stage,
const ShaderRecompiler::IR::Program& program);
Common::Mutex m_mutex;
std::vector<Pool> m_pools;
int m_first_free_pool = -1;
std::unordered_map<VkDescriptorSetLayout, std::vector<VulkanDescriptorSet*>>
m_free_sets_by_layout;
std::map<std::vector<uint32_t>, VkDescriptorSetLayout> m_descriptor_set_layouts;
bool m_initialized = false;
};
const char* storage_usage_name(ShaderStorageUsage usage);
VkImageAspectFlags DepthStencilAspectMask(VkFormat format);
std::vector<ShaderAddressWriteRange>
BindDescriptors(uint64_t submit_id, CommandBuffer* buffer, VkPipelineBindPoint pipeline_bind_point,
VkPipelineLayout layout, const ShaderStageRuntime& runtime,
VkShaderStageFlags vk_stage, DescriptorCache::Stage stage);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
@@ -0,0 +1,698 @@
#include "graphics/host_gpu/renderer/descriptors.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/file.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/gpu_format.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/hostMemory.h"
#include "graphics/host_gpu/objects/textureCommon.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/renderer/renderState.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/utils.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/presentation/displayBuffer.h"
#include "graphics/shader/recompiler/BindingLayout.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shader.h"
#include <algorithm>
#include <atomic>
#include <cstring>
#include <fmt/format.h>
#include <limits>
#include <vulkan/vk_enum_string_helper.h>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
namespace Libs::Graphics {
using TextureVariant = DescriptorCache::TextureVariant;
static void BindNullStorageBuffer(CommandBuffer* cmd_buffer, BufferView* dst) {
EXIT_IF(cmd_buffer == nullptr || dst == nullptr);
dst->buffer =
g_render_ctx->GetBufferCache()->ObtainNullBuffer(cmd_buffer, g_render_ctx->GetGraphicCtx());
dst->offset = 0;
dst->range = 16;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
static const char* VulkanImageTypeName(VulkanImageType type) {
switch (type) {
case VulkanImageType::VideoOut: return "VideoOut";
case VulkanImageType::DepthStencil: return "DepthStencil";
case VulkanImageType::Texture: return "Texture";
case VulkanImageType::StorageTexture: return "StorageTexture";
case VulkanImageType::RenderTexture: return "RenderTexture";
case VulkanImageType::Unknown:
default: return "Unknown";
}
}
VkImageAspectFlags DepthStencilAspectMask(VkFormat format) {
auto ret = static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT);
if (format == VK_FORMAT_D24_UNORM_S8_UINT || format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
ret |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
return ret;
}
static int SampledArrayViewIndex(const VulkanImage* image, int view_index) {
EXIT_IF(image == nullptr);
switch (view_index) {
case VulkanImage::VIEW_DEFAULT: return VulkanImage::VIEW_DEFAULT_ARRAY;
case VulkanImage::VIEW_BGRA: return VulkanImage::VIEW_BGRA_ARRAY;
case VulkanImage::VIEW_DEPTH_TEXTURE: return VulkanImage::VIEW_DEPTH_TEXTURE_ARRAY;
case VulkanImage::VIEW_STENCIL_TEXTURE: return VulkanImage::VIEW_STENCIL_TEXTURE_ARRAY;
case VulkanImage::VIEW_R001: return VulkanImage::VIEW_R001_ARRAY;
case VulkanImage::VIEW_RGB1: return VulkanImage::VIEW_RGB1_ARRAY;
case VulkanImage::VIEW_R000: return VulkanImage::VIEW_R000_ARRAY;
case VulkanImage::VIEW_RG01: return VulkanImage::VIEW_RG01_ARRAY;
case VulkanImage::VIEW_000R: return VulkanImage::VIEW_000R_ARRAY;
default: return view_index;
}
}
static int SelectSampledTextureArrayView(const VulkanImage* image, int base_view) {
const int array_view = SampledArrayViewIndex(image, base_view);
if (image->image_view[array_view] == nullptr) {
EXIT("missing sampled array image view: image_type=%s base_view=%d array_view=%d "
"layers=%u\n",
VulkanImageTypeName(image->type), base_view, array_view, image->layers);
}
return array_view;
}
static bool TextureVariantIsUint(TextureVariant variant) {
return variant == TextureVariant::Uint2D || variant == TextureVariant::UintArray ||
variant == TextureVariant::Uint3D;
}
static bool TextureVariantIsArray(TextureVariant variant) {
return variant == TextureVariant::FloatArray || variant == TextureVariant::UintArray;
}
static bool TextureVariantIs3D(TextureVariant variant) {
return variant == TextureVariant::Float3D || variant == TextureVariant::Uint3D;
}
static int TextureVariantDefaultView(TextureVariant variant) {
return TextureVariantIsArray(variant) ? VulkanImage::VIEW_DEFAULT_ARRAY
: VulkanImage::VIEW_DEFAULT;
}
static VulkanImage* GetDummySampledTexture(TextureVariant variant) {
return g_render_ctx->GetTextureCache()->GetDummySampledTexture(TextureVariantIsUint(variant),
TextureVariantIs3D(variant));
}
static VulkanImage* GetDummyStorageTexture(TextureVariant variant) {
return g_render_ctx->GetTextureCache()->GetDummyStorageTexture(TextureVariantIsUint(variant),
TextureVariantIs3D(variant));
}
static ShaderBufferResource
NativeBufferDescriptor(const ShaderRecompiler::IR::DescriptorValue& descriptor) {
ShaderBufferResource result;
std::copy_n(descriptor.dwords.begin(), std::size(result.fields), result.fields);
return result;
}
static ShaderTextureResource
NativeTextureDescriptor(const ShaderRecompiler::IR::DescriptorValue& descriptor) {
ShaderTextureResource result;
std::copy_n(descriptor.dwords.begin(), std::size(result.fields), result.fields);
return result;
}
static ShaderSamplerResource
NativeSamplerDescriptor(const ShaderRecompiler::IR::DescriptorValue& descriptor) {
ShaderSamplerResource result;
std::copy_n(descriptor.dwords.begin(), std::size(result.fields), result.fields);
return result;
}
static BufferView NativeStorageBuffer(uint64_t submit_id, CommandBuffer* command_buffer,
const ShaderBufferResource& descriptor,
const ShaderRecompiler::IR::BufferResource& resource) {
BufferView result;
const auto address = descriptor.Base48();
const auto stride = descriptor.Stride();
const auto records = descriptor.NumRecords();
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("storage buffer descriptor footprint overflow\n");
}
const auto size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || size == 0) {
BindNullStorageBuffer(command_buffer, &result);
return result;
}
auto* const ctx = g_render_ctx->GetGraphicCtx();
const auto alignment = ctx->StorageMinAlignment();
if (alignment == 0 || size > ctx->GetPhysicalDeviceProperties().limits.maxStorageBufferRange ||
BufferCache::CACHING_PAGE_SIZE % alignment != 0) {
EXIT("storage buffer range or device alignment is unsupported\n");
}
(void)submit_id;
auto binding = g_render_ctx->GetBufferCache()->ObtainBuffer(
command_buffer, ctx, address, size, resource.written, resource.read, resource.formatted);
if (binding.second % alignment != 0) {
EXIT("storage buffer binding is not device-aligned\n");
}
result.buffer = binding.first;
result.offset = binding.second;
result.range = static_cast<VkDeviceSize>(size);
return result;
}
static BufferView
NativeAddressBuffer(uint64_t submit_id, CommandBuffer* command_buffer,
const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::ResourceSnapshot::Address& address) {
BufferView result;
if (address.binding_base == 0) {
BindNullStorageBuffer(command_buffer, &result);
return result;
}
if (resource.written) {
EXIT("writable address resources are unsupported\n");
}
const auto limit = resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
? ShaderRecompiler::IR::FlatAddressWindowSize
: static_cast<uint64_t>(g_render_ctx->GetGraphicCtx()
->GetPhysicalDeviceProperties()
.limits.maxStorageBufferRange);
uint64_t size = 0;
const auto access = HostMemoryAccess::Read;
if (!HostMemoryQueryRange(address.binding_base, limit, access, &size)) {
EXIT("address resource is not host-accessible: base=0x%016" PRIx64 "\n",
address.binding_base);
}
auto* const ctx = g_render_ctx->GetGraphicCtx();
const auto alignment = ctx->StorageMinAlignment();
if (alignment == 0 || size > ctx->GetPhysicalDeviceProperties().limits.maxStorageBufferRange ||
BufferCache::GetBufferOffset(address.binding_base) % alignment != 0) {
EXIT("address resource range or alignment is unsupported\n");
}
(void)submit_id;
auto binding = g_render_ctx->GetBufferCache()->ObtainBuffer(command_buffer, ctx,
address.binding_base, size);
result.buffer = binding.first;
result.offset = binding.second;
result.range = static_cast<VkDeviceSize>(size);
return result;
}
static TextureVariant NativeTextureVariant(const ShaderRecompiler::IR::ImageResource& resource) {
const bool uint_image = resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
switch (resource.dimension) {
case ShaderRecompiler::Decoder::ImageDimension::Dim3D:
return uint_image ? TextureVariant::Uint3D : TextureVariant::Float3D;
case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray:
return uint_image ? TextureVariant::UintArray : TextureVariant::FloatArray;
default: return uint_image ? TextureVariant::Uint2D : TextureVariant::Float2D;
}
}
static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageResource& resource) {
return resource.kind == ShaderRecompiler::IR::ResourceKind::Image &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic && !resource.depth_compare;
}
static bool IsSupportedSampledDepthFormat(VkFormat image_format, uint32_t guest_format,
VkFormat view_format) {
const bool d16 = image_format == VK_FORMAT_D16_UNORM &&
guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm) &&
view_format == VK_FORMAT_R16_UNORM;
const bool d32 =
(image_format == VK_FORMAT_D32_SFLOAT || image_format == VK_FORMAT_D32_SFLOAT_S8_UINT) &&
guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float) &&
view_format == VK_FORMAT_R32_SFLOAT;
return d16 || d32;
}
bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
const VulkanImage& image) {
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto pitch = TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode());
return image.type == VulkanImageType::DepthStencil && image.layers == 1 &&
width == image.extent.width && height == image.extent.height &&
descriptor.Depth() == 0 && descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 &&
descriptor.MaxMip() == 0 && descriptor.MinLod() == 0 && descriptor.BaseArray5() == 0 &&
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth() && pitch >= width &&
pitch == image.guest_pitch;
}
static bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor) {
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
constexpr uint32_t field3_base = 0x91800000u;
constexpr uint32_t field5_expected = 0x00700000u;
return (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == (field3_base | descriptor.DstSelXYZW()) &&
descriptor.fields[4] == 0 && descriptor.fields[5] == field5_expected &&
descriptor.fields[6] == 0 && descriptor.fields[7] == 0;
}
static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor,
const VulkanImage* image, VkFormat view_format,
uint64_t size) {
const bool resource_ok = IsSupportedSampledDepthResource(resource);
const bool descriptor_ok =
image != nullptr && IsSupportedDepthTargetDescriptor(descriptor, *image);
const bool encoding_ok = IsSupportedDepthTextureEncoding(descriptor);
const bool format_ok = image != nullptr && IsSupportedSampledDepthFormat(
image->format, descriptor.Format(), view_format);
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return;
}
const auto descriptor_pitch =
TileGetTexturePitch(descriptor.Format(), static_cast<uint32_t>(descriptor.Width5()) + 1u, 1,
descriptor.TileMode());
EXIT("unsupported sampled depth target: resource=%d descriptor=%d encoding=%d format=%d "
"image_format=%d view_format=%d descriptor_pitch=%u target_pitch=%u "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
resource_ok, descriptor_ok, encoding_ok, format_ok,
image == nullptr ? static_cast<int>(VK_FORMAT_UNDEFINED) : static_cast<int>(image->format),
static_cast<int>(view_format), descriptor_pitch,
image == nullptr ? 0u : image->guest_pitch, descriptor.Base40(), size);
}
static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor) {
const auto tile = descriptor.TileMode();
const bool is_2d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
descriptor.Depth() == 0;
const bool is_2d_array =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) &&
descriptor.BaseArray5() <= descriptor.Depth();
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D);
const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) ||
tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
const bool supported_swizzle =
IsSupportedStorageSwizzle(descriptor.Format(), descriptor.DstSelXYZW()) &&
(descriptor.DstSelXYZW() == DstSel(4, 5, 6, 7) || !resource.read);
return (is_2d || is_2d_array || is_3d) && supported_tile && descriptor.BaseLevel() == 0 &&
descriptor.LastLevel() == 0 && descriptor.MaxMip() == 0 && descriptor.MinLod() == 0 &&
descriptor.BaseArray5() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
!descriptor.MsaaDepth();
}
static bool IsSupportedStorageTextureEncoding(const ShaderTextureResource& descriptor) {
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
constexpr uint32_t field5_expected = 0x00700000u;
const uint32_t expected_field3 = descriptor.DstSelXYZW() |
(static_cast<uint32_t>(descriptor.TileMode()) << 20u) |
(static_cast<uint32_t>(descriptor.Type()) << 28u);
return (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == expected_field3 && descriptor.fields[4] == descriptor.Depth() &&
descriptor.fields[5] == field5_expected && descriptor.fields[6] == 0 &&
descriptor.fields[7] == 0;
}
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size) {
const auto format = descriptor.Format();
const bool resource_ok = IsSupportedStorageImageResource(resource);
const bool descriptor_ok = IsSupportedStorageTextureDescriptor(resource, descriptor);
const bool encoding_ok = IsSupportedStorageTextureEncoding(descriptor);
const bool uint_resource =
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
const bool format_ok = Prospero::IsSupportedTextureFormat(format) &&
uint_resource == Prospero::IsUintTextureFormat(format);
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return;
}
EXIT("unsupported storage texture: resource=%d descriptor=%d encoding=%d format=%d "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64
" extent=%ux%ux%u type=%u format=%u tile=%u swizzle=0x%03x read=%d written=%d\n",
resource_ok, descriptor_ok, encoding_ok, format_ok, descriptor.Base40(), size,
static_cast<uint32_t>(descriptor.Width5()) + 1u,
static_cast<uint32_t>(descriptor.Height5()) + 1u,
static_cast<uint32_t>(descriptor.Depth()) + 1u, descriptor.Type(), format,
descriptor.TileMode(), descriptor.DstSelXYZW(), resource.read, resource.written);
}
void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size) {
constexpr uint32_t field1_reserved = 0x200fff00u;
constexpr uint32_t field2_reserved = 0xf0003000u;
const auto format = descriptor.Format();
if (!IsSupportedSampledColorResource(resource) || size == 0 ||
(descriptor.fields[1] & field1_reserved) != 0 ||
(descriptor.fields[2] & field2_reserved) != 0 || descriptor.fields[3] != 0x90500facu ||
descriptor.fields[4] != 0 || descriptor.fields[5] != 0x00700000u ||
descriptor.fields[6] != 0 || descriptor.fields[7] != 0 ||
!Prospero::IsSupportedTextureFormat(format) || Prospero::IsUintTextureFormat(format)) {
EXIT("unsupported storage texture descriptor encoding\n");
}
}
static DescriptorCache::TextureBinding
NativeTexture(uint64_t submit_id, CommandBuffer* command_buffer,
const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value) {
auto descriptor = NativeTextureDescriptor(value);
const bool storage = resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
const auto variant = NativeTextureVariant(resource);
if (storage) {
ValidateStorageImageResource(resource);
}
if (descriptor.IsNull()) {
return storage ? DescriptorCache::TextureBinding {GetDummyStorageTexture(variant),
TextureVariantDefaultView(variant)}
: DescriptorCache::TextureBinding {GetDummySampledTexture(variant),
TextureVariantDefaultView(variant)};
}
const auto address = descriptor.Base40();
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto base_level = descriptor.BaseLevel();
const auto last_level = descriptor.LastLevel();
const auto levels = static_cast<uint32_t>(descriptor.MaxMip()) + 1u;
if (base_level > last_level || last_level >= levels) {
EXIT("unsupported texture mip view: base=%u last=%u levels=%u\n", base_level, last_level,
levels);
}
const auto view_levels = last_level - base_level + 1u;
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
const auto tile = descriptor.TileMode();
const auto format = descriptor.Format();
const auto view_format = TextureGetFormat(format, storage ? TextureFormatUsage::Storage
: TextureFormatUsage::Sampled);
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
const auto pitch = TileGetTexturePitch(format, width, levels, tile);
const auto swizzle = descriptor.DstSelXYZW();
TileSizeAlign size;
TileGetTextureTotalSize(format, width, height, depth, pitch, levels, tile,
type == Prospero::ImageType::kColor3D, &size);
EXIT_NOT_IMPLEMENTED(size.size == 0 ||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
if (storage) {
ValidateStorageTexture(resource, descriptor, size.size);
g_render_ctx->GetBufferCache()->ValidateGpuAccess(address, size.size, resource.read,
resource.written);
}
VulkanImage* image = nullptr;
int view = VulkanImage::VIEW_DEFAULT;
VkImageView image_view = nullptr;
const bool check_depth = static_cast<Prospero::TileMode>(tile) == Prospero::TileMode::kDepth ||
descriptor.MsaaDepth();
if (image == nullptr) {
if (check_depth) {
image = g_render_ctx->GetTextureCache()->FindDepthTargetByRange(address, size.size);
} else {
image = g_render_ctx->GetTextureCache()->FindRenderTargetByRange(command_buffer,
address, size.size);
}
if (image != nullptr) {
if (check_depth) {
ValidateDepthTargetBinding(resource, descriptor, image, view_format, size.size);
view = SelectSampledDepthView(image->format, view_format, swizzle);
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 64) {
LOGF("SampledDepthTarget: bind addr=0x%016" PRIx64 " size=0x%016" PRIx64
" format=%d view=%d\n",
address, size.size, static_cast<int>(image->format), view);
}
}
} else {
if (!(storage ? IsSupportedStorageImageResource(resource)
: IsSupportedSampledColorResource(resource)) ||
image->type != VulkanImageType::RenderTexture || image->layers != 1 ||
width != image->extent.width || height != image->extent.height || depth != 1 ||
(storage ? levels != 1 || base_level != 0
: levels != image->mip_levels || base_level >= levels) ||
descriptor.BaseArray5() != 0 || type != Prospero::ImageType::kColor2D) {
EXIT("unsupported cached render-target image view: storage=%d resource=%u"
" image_type=%u layers=%u extent=%ux%u/%ux%u depth=%u"
" levels=%u/%u base_level=%u base_array=%u descriptor_type=%u\n",
storage, static_cast<uint32_t>(resource.kind),
static_cast<uint32_t>(image->type), image->layers, width, height,
image->extent.width, image->extent.height, depth, levels,
image->mip_levels, base_level, descriptor.BaseArray5(),
static_cast<uint32_t>(type));
}
view = storage ? SelectStorageColorView(image->format, view_format, swizzle)
: SelectSampledColorView(image->format, view_format, swizzle);
if (storage) {
image_view = g_render_ctx->GetTextureCache()->GetRenderTargetStorageView(
g_render_ctx->GetGraphicCtx(),
static_cast<RenderTextureVulkanImage*>(image), view_format, base_level,
view_levels);
} else {
image_view = g_render_ctx->GetTextureCache()->GetRenderTargetSampledView(
g_render_ctx->GetGraphicCtx(),
static_cast<RenderTextureVulkanImage*>(image), view_format, view,
base_level, view_levels);
}
}
if (image_view == nullptr && image->image_view[view] == nullptr) {
EXIT("required cached texture image view is missing\n");
}
if (storage) {
g_render_ctx->GetTextureCache()->MarkGpuWritten(image);
}
}
}
if (image == nullptr) {
const auto video = Presentation::DisplayBufferFind(address);
if (video.image != nullptr) {
if (storage) {
EXIT("storage access to a video-out surface is unsupported\n");
}
image = video.image;
if (!storage && swizzle == DstSel(6, 5, 4, 7)) {
view = VulkanImage::VIEW_BGRA;
}
}
}
if (image == nullptr) {
auto* texture_cache = g_render_ctx->GetTextureCache();
const bool metadata_read = texture_cache->HasMetaOverlap(address, size.size);
if (storage && metadata_read) {
EXIT("storage texture overlaps surface metadata\n");
}
if (!storage && metadata_read) {
ValidateMetadataReuseTexture(resource, descriptor, size.size);
}
(void)submit_id;
(void)command_buffer;
ImageInfo info {};
info.address = address;
info.size = size.size;
info.format = format;
info.width = width;
info.height = height;
info.pitch = pitch;
info.base_level = base_level;
info.levels = levels;
info.view_levels = view_levels;
info.tile = tile;
info.swizzle = swizzle;
info.depth = depth;
info.type = descriptor.Type();
info.base_array = descriptor.BaseArray5();
if (storage) {
image = texture_cache->FindStorageTexture(command_buffer, g_render_ctx->GetGraphicCtx(),
info);
view = VulkanImage::VIEW_DEFAULT;
} else {
image = texture_cache->FindTexture(command_buffer, g_render_ctx->GetGraphicCtx(), info,
metadata_read);
if (image->type == VulkanImageType::StorageTexture) {
image_view = texture_cache->GetStorageTextureSampledView(
g_render_ctx->GetGraphicCtx(), static_cast<StorageTextureVulkanImage*>(image),
info);
}
}
}
EXIT_NOT_IMPLEMENTED(image == nullptr);
if (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) {
view = SelectSampledTextureArrayView(image, view);
}
return {image, view, image_view};
}
static VkSampler NativeSampler(const ShaderRecompiler::IR::Program& program, uint32_t index,
const ShaderRecompiler::IR::DescriptorValue& value) {
auto descriptor = NativeSamplerDescriptor(value);
const bool depth_compare = std::any_of(program.info.sampled_pairs.begin(),
program.info.sampled_pairs.end(), [&](const auto& pair) {
return pair.sampler == index &&
pair.image < program.info.images.size() &&
program.info.images[pair.image].depth_compare;
});
if (!depth_compare) {
descriptor.fields[0] &= ~(0x7u << 12u);
}
return g_render_ctx->GetSamplerCache()->GetSampler(descriptor);
}
static BufferView NativeUpload(CommandBuffer* command_buffer, std::span<const uint32_t> data) {
EXIT_IF(data.empty());
BufferView result;
EXIT_IF(!g_render_ctx->GetBufferCache()->UploadHostData(
command_buffer, g_render_ctx->GetGraphicCtx(), data.data(), data.size_bytes(), 256,
&result.buffer, &result.offset, &result.range));
return result;
}
std::vector<ShaderAddressWriteRange>
BindDescriptors(uint64_t submit_id, CommandBuffer* buffer, VkPipelineBindPoint pipeline_bind_point,
VkPipelineLayout layout, const ShaderStageRuntime& runtime,
VkShaderStageFlags vk_stage, DescriptorCache::Stage stage) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer == nullptr || !runtime);
const auto& program = *runtime.program;
const auto& snapshot = *runtime.resources;
std::string error;
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) {
EXIT("invalid native shader runtime snapshot: %s\n", error.c_str());
}
auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()];
const auto shader_stages = ShaderPipelineStages(vk_stage);
DescriptorCache::NativeDescriptors descriptors;
descriptors.buffers.reserve(program.info.buffers.size());
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
descriptors.buffers.push_back(
NativeStorageBuffer(submit_id, buffer, NativeBufferDescriptor(snapshot.buffers[i]),
program.info.buffers[i]));
}
descriptors.images.reserve(program.info.images.size());
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto kind = program.info.images[i].kind;
if ((kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
vk_stage != VK_SHADER_STAGE_COMPUTE_BIT) {
EXIT("storage images are unsupported outside compute shaders\n");
}
descriptors.images.push_back(
NativeTexture(submit_id, buffer, program.info.images[i], snapshot.images[i]));
}
descriptors.samplers.reserve(program.info.samplers.size());
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
descriptors.samplers.push_back(NativeSampler(program, i, snapshot.samplers[i]));
}
descriptors.addresses.reserve(program.info.addresses.size());
std::vector<ShaderAddressWriteRange> address_writes;
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
auto view = NativeAddressBuffer(submit_id, buffer, program.info.addresses[i],
snapshot.addresses[i]);
descriptors.addresses.push_back(view);
if (program.info.addresses[i].written && snapshot.addresses[i].binding_base != 0) {
EXIT_IF(view.buffer == nullptr || view.offset != 0 || view.range == VK_WHOLE_SIZE ||
view.range == 0);
address_writes.push_back({view.buffer, snapshot.addresses[i].binding_base,
static_cast<uint64_t>(view.range)});
}
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) !=
nullptr) {
descriptors.flattened_srt = NativeUpload(buffer, snapshot.flattened_srt);
}
std::vector<uint32_t> user_data;
user_data.reserve(program.bindings.user_data_registers.size());
for (const auto reg: program.bindings.user_data_registers) {
user_data.push_back(snapshot.user_data[reg - program.user_data_base]);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
descriptors.user_data = NativeUpload(buffer, user_data);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
descriptors.gds.buffer =
g_render_ctx->GetGdsBuffer()->GetBuffer(g_render_ctx->GetGraphicCtx());
const auto barrier = MakeGdsDependency(*descriptors.gds.buffer);
vkCmdPipelineBarrier(vk_buffer,
VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT |
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
shader_stages, 0, 0, nullptr, 1, &barrier, 0, nullptr);
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto* image = descriptors.images[i].image;
const auto& resource = program.info.images[i];
if (resource.kind == ShaderRecompiler::IR::ResourceKind::Image ||
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) {
switch (image->type) {
case VulkanImageType::DepthStencil:
GraphicsRenderDepthStencilBarrier(vk_buffer, image);
break;
case VulkanImageType::RenderTexture:
case VulkanImageType::StorageTexture:
GraphicsRenderTextureBarrier(vk_buffer, image);
break;
case VulkanImageType::VideoOut:
GraphicsRenderColorImageBarrier(vk_buffer, image, RENDER_COLOR_IMAGE_LAYOUT);
break;
default: break;
}
} else {
const auto barrier =
MakeStorageImageDependency(*image, resource.read, resource.written);
vkCmdPipelineBarrier(vk_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, shader_stages, 0, 0,
nullptr, 0, nullptr, 1, &barrier);
image->layout = VK_IMAGE_LAYOUT_GENERAL;
}
}
if (!program.bindings.descriptors.empty()) {
auto* set = g_render_ctx->GetDescriptorCache()->GetDescriptor(stage, program, descriptors);
EXIT_IF(set == nullptr);
vkCmdBindDescriptorSets(vk_buffer, pipeline_bind_point, layout,
program.bindings.descriptor_set, 1, &set->set, 0, nullptr);
buffer->RecycleDescriptorAfterFence(set);
}
if (program.bindings.push_constant_size != 0) {
EXIT_IF(program.bindings.push_constant_size != user_data.size() * sizeof(uint32_t));
vkCmdPushConstants(vk_buffer, layout, vk_stage, program.bindings.push_constant_offset,
program.bindings.push_constant_size, user_data.data());
}
return address_writes;
}
} // namespace Libs::Graphics
@@ -0,0 +1,22 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shaderBindings.h"
#include <cstdint>
namespace Libs::Graphics {
struct VulkanImage;
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
const VulkanImage& image);
void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size);
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
@@ -0,0 +1,422 @@
#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/renderContext.h"
#include "graphics/host_gpu/utils.h"
#include <algorithm>
#include <atomic>
#include <vulkan/vk_enum_string_helper.h>
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(depth == nullptr);
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
bool with_depth = (depth->format != VK_FORMAT_UNDEFINED && depth->vulkan_buffer != nullptr);
bool with_color[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
VulkanImage* first_color = nullptr;
VkExtent2D first_color_extent = {};
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;
} 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;
}
color_count++;
}
VkImageLayout 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_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
auto depth_read_only =
(with_depth && depth_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL);
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,
string_VkFormat(depth->format));
}
depth->format = VK_FORMAT_UNDEFINED;
depth->vulkan_buffer = 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_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
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_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;
}
}
auto* framebuffer = new VulkanFramebuffer;
framebuffer->render_pass = nullptr;
framebuffer->framebuffer = nullptr;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
framebuffer->color_layout[i] = color_layout[i];
}
framebuffer->depth_layout = depth_layout;
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
if (!with_depth && color_count == 0) {
LOGF("Framebuffer: warning: no color or depth attachment\n");
return nullptr;
}
EXIT_NOT_IMPLEMENTED(!with_depth && color_count == 0);
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 = CreateDummyBuffer(VK_FORMAT_B8G8R8A8_SRGB, depth->vulkan_buffer->extent.width,
depth->vulkan_buffer->extent.height);
first_color_extent = first_color->extent;
}
VkAttachmentDescription attachments[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
attachments[i].flags = 0;
attachments[i].format = colors[i].format;
attachments[i].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[i].loadOp = (colors[i].color_clear_enable ? VK_ATTACHMENT_LOAD_OP_CLEAR
: VK_ATTACHMENT_LOAD_OP_LOAD);
attachments[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[i].initialLayout = color_layout[i];
attachments[i].finalLayout = RENDER_COLOR_IMAGE_LAYOUT;
}
const uint32_t depth_attachment = color_count;
attachments[depth_attachment].flags = 0;
attachments[depth_attachment].format = depth->format;
attachments[depth_attachment].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[depth_attachment].loadOp =
(depth->depth_load_clear_enable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD);
attachments[depth_attachment].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[depth_attachment].stencilLoadOp =
(depth->stencil_clear_enable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD);
attachments[depth_attachment].stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[depth_attachment].initialLayout = depth_layout;
attachments[depth_attachment].finalLayout = depth_layout;
VkAttachmentReference 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;
}
VkAttachmentReference depth_attachment_ref {};
depth_attachment_ref.attachment = depth_attachment;
depth_attachment_ref.layout = depth_layout;
VkSubpassDescription subpass {};
subpass.flags = 0;
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
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<VkPipelineStageFlags>(
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT);
const auto attachment_access_mask = static_cast<VkAccessFlags>(
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
VkSubpassDependency dependencies[2] = {};
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
dependencies[0].dstStageMask = attachment_stage_mask;
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
dependencies[0].dstAccessMask = attachment_access_mask;
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].srcStageMask = attachment_stage_mask;
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
dependencies[1].srcAccessMask = attachment_access_mask;
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
VkRenderPassCreateInfo render_pass_info {};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_info.pNext = nullptr;
render_pass_info.flags = 0;
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 =
vkCreateRenderPass(gctx->device, &render_pass_info, nullptr, &framebuffer->render_pass);
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
VkFormat 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);
EXIT_NOT_IMPLEMENTED(framebuffer->render_pass == nullptr);
VkImageView 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) {
views[color_count] = depth->vulkan_buffer->image_view[VulkanImage::VIEW_DEFAULT];
}
VkFramebufferCreateInfo framebuffer_info {};
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_info.pNext = nullptr;
framebuffer_info.flags = 0;
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 =
vkCreateFramebuffer(gctx->device, &framebuffer_info, nullptr, &framebuffer->framebuffer);
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
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_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) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(image == nullptr);
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) {
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
vkDestroyFramebuffer(gctx->device, f.framebuffer->framebuffer, nullptr);
vkDestroyRenderPass(gctx->device, f.framebuffer->render_pass, nullptr);
delete f.framebuffer;
f.framebuffer = nullptr;
}
}
}
void FramebufferCache::FreeFramebufferByDepth(DepthStencilVulkanImage* image) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(image == nullptr);
Common::LockGuard lock(m_mutex);
for (auto& f: m_framebuffers) {
if (f.framebuffer != nullptr && f.depth_id == image->memory.unique_id) {
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
vkDestroyFramebuffer(gctx->device, f.framebuffer->framebuffer, nullptr);
vkDestroyRenderPass(gctx->device, f.framebuffer->render_pass, nullptr);
delete f.framebuffer;
f.framebuffer = nullptr;
}
}
}
VideoOutVulkanImage* FramebufferCache::CreateDummyBuffer(VkFormat format, uint32_t width,
uint32_t height) {
for (auto* b: m_dummy_buffers) {
if (b->extent.width == width && b->extent.height == height && b->format == format) {
return b;
}
}
auto* ctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(ctx == nullptr);
auto* vk_obj = new VideoOutVulkanImage;
vk_obj->extent.width = width;
vk_obj->extent.height = height;
vk_obj->format = format;
vk_obj->image = nullptr;
vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED;
UtilResetImageViews(vk_obj);
VkImageCreateInfo image_info {};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = nullptr;
image_info.flags = 0;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.extent.width = vk_obj->extent.width;
image_info.extent.height = vk_obj->extent.height;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.format = vk_obj->format;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.initialLayout = vk_obj->layout;
image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
VulkanMemory mem;
mem.property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
bool created = VulkanCreateImage(ctx, &image_info, vk_obj, &mem);
EXIT_NOT_IMPLEMENTED(!created);
vk_obj->memory = mem;
VkImageViewCreateInfo create_info {};
create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.image = vk_obj->image;
create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
create_info.format = vk_obj->format;
create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
create_info.subresourceRange.baseArrayLayer = 0;
create_info.subresourceRange.baseMipLevel = 0;
create_info.subresourceRange.layerCount = 1;
create_info.subresourceRange.levelCount = 1;
vkCreateImageView(ctx->device, &create_info, nullptr,
&vk_obj->image_view[VulkanImage::VIEW_DEFAULT]);
EXIT_NOT_IMPLEMENTED(vk_obj->image_view[VulkanImage::VIEW_DEFAULT] == nullptr);
UtilSetImageLayoutOptimal(vk_obj);
m_dummy_buffers.push_back(vk_obj);
return vk_obj;
}
} // namespace Libs::Graphics
@@ -0,0 +1,80 @@
#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/renderState.h"
#include <vector>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
static constexpr VkImageLayout RENDER_COLOR_IMAGE_LAYOUT = VK_IMAGE_LAYOUT_GENERAL;
struct VulkanFramebuffer {
VkRenderPass render_pass = nullptr;
uint64_t render_pass_id = 0;
VkFramebuffer framebuffer = nullptr;
VkImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
VkImageLayout depth_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
};
inline uint64_t render_pass_compat_id(uint32_t color_count, const VkFormat* color_formats,
bool with_depth, VkFormat depth_format,
VkImageLayout depth_layout) {
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(VK_SAMPLE_COUNT_1_BIT);
return id;
}
class FramebufferCache {
public:
FramebufferCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
virtual ~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:
VideoOutVulkanImage* CreateDummyBuffer(VkFormat format, uint32_t width, uint32_t height);
struct Framebuffer {
VulkanFramebuffer* framebuffer = nullptr;
uint64_t image_id[RENDER_COLOR_ATTACHMENTS_MAX] = {};
VkImageView color_view[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint64_t depth_id = 0;
bool color_clear_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
VkImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool depth_clear_enable = false;
bool stencil_clear_enable = false;
bool depth_read_only = false;
};
Common::Mutex m_mutex;
std::vector<Framebuffer> m_framebuffers;
std::vector<VideoOutVulkanImage*> m_dummy_buffers;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
@@ -0,0 +1,70 @@
#include "graphics/host_gpu/renderer/gdsBuffer.h"
#include "common/assert.h"
#include "graphics/host_gpu/utils.h"
#include "graphics/host_gpu/vma.h"
namespace Libs::Graphics {
void GdsBuffer::Init(GraphicContext* ctx) {
if (m_buffer == nullptr) {
m_buffer = std::make_unique<VulkanBuffer>();
m_buffer->usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
m_buffer->memory.property = static_cast<uint32_t>(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
VulkanCreateBuffer(ctx, DW_SIZE * 4, m_buffer.get());
}
}
void GdsBuffer::Clear(GraphicContext* ctx, uint64_t dw_offset, uint32_t dw_num,
uint32_t clear_value) {
EXIT_IF(ctx == nullptr);
Common::LockGuard lock(m_mutex);
Init(ctx);
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
EXIT_NOT_IMPLEMENTED(dw_offset + dw_num > DW_SIZE);
void* data = nullptr;
VulkanMapMemory(ctx, &m_buffer->memory, &data);
for (uint32_t i = 0; i < dw_num; i++) {
static_cast<uint32_t*>(data)[dw_offset + i] = clear_value;
}
VulkanUnmapMemory(ctx, &m_buffer->memory);
}
void GdsBuffer::Read(GraphicContext* ctx, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
EXIT_IF(dst == nullptr);
Common::LockGuard lock(m_mutex);
Init(ctx);
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
EXIT_NOT_IMPLEMENTED(dw_offset + dw_size > DW_SIZE);
void* data = nullptr;
VulkanMapMemory(ctx, &m_buffer->memory, &data);
for (uint32_t i = 0; i < dw_size; i++) {
dst[i] = static_cast<uint32_t*>(data)[dw_offset + i];
}
VulkanUnmapMemory(ctx, &m_buffer->memory);
}
VulkanBuffer* GdsBuffer::GetBuffer(GraphicContext* ctx) {
Common::LockGuard lock(m_mutex);
Init(ctx);
return m_buffer.get();
}
} // namespace Libs::Graphics
@@ -0,0 +1,36 @@
#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:
GdsBuffer() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
virtual ~GdsBuffer() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(GdsBuffer);
void Clear(GraphicContext* ctx, uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value);
void Read(GraphicContext* ctx, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
VulkanBuffer* GetBuffer(GraphicContext* ctx);
private:
static constexpr uint64_t DW_SIZE = 0x3000;
void Init(GraphicContext* ctx);
Common::Mutex m_mutex;
std::unique_ptr<VulkanBuffer> m_buffer;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
@@ -0,0 +1,120 @@
#include "graphics/host_gpu/renderer/gpuResourceManager.h"
#include "common/assert.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"
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager()
: m_page_manager(FaultThunk, this), m_buffer_cache(m_page_manager, m_resource_mutex),
m_texture_cache(m_page_manager, m_buffer_cache, m_resource_mutex) {
m_buffer_cache.SetTextureCache(m_texture_cache);
}
GpuResourceManager::~GpuResourceManager() = default;
bool GpuResourceManager::FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr,
uint64_t size, PageFaultPhase phase) noexcept {
return static_cast<GpuResourceManager*>(context)->InvalidateMemory(access, vaddr, size, phase);
}
bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept {
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;
}
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
if (!m_page_manager.IsMapped(fault_vaddr, 1)) {
return false;
}
if (LabelInCallback()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU label callback, "
"addr=0x%016" PRIx64 " access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
if (!GraphicsRunIsCommandProcessorGuestAccess()) {
EXIT("unsupported page fault from a pre-owned resource transaction, addr=0x%016" PRIx64
" access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
ResourceMutex::FaultScope fault(m_resource_mutex);
return m_page_manager.HandleFault(access, fault_vaddr);
}
// 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);
}
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
return m_page_manager.IsMapped(vaddr, size);
}
std::unique_lock<ResourceMutex> GpuResourceManager::LockCommandProcessorGuestAccess(
uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("invalid command-processor guest-memory access, addr=0x%016" PRIx64
" size=0x%016" PRIx64 "\n",
vaddr, size);
}
return std::unique_lock(m_resource_mutex);
}
std::unique_lock<ResourceMutex> GpuResourceManager::LockAsynchronousGuestWrite(uint64_t vaddr,
uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("invalid asynchronous guest-memory write, addr=0x%016" PRIx64
" size=0x%016" PRIx64 "\n",
vaddr, size);
}
std::unique_lock transaction(m_resource_mutex);
if (m_buffer_cache.HasPageOverlap(vaddr, size) || m_texture_cache.HasPageOverlap(vaddr, size) ||
m_texture_cache.HasMetaOverlap(vaddr, size)) {
EXIT("asynchronous guest-memory write to a cached buffer/image range is unsupported, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
return transaction;
}
void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
m_page_manager.OnGpuMap(vaddr, size, access);
}
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
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);
}
void GpuResourceManager::FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size,
uint32_t value) {
if (g_render_ctx == nullptr || command == nullptr || command->IsInvalid()) {
EXIT("cannot fill a buffer without a valid render command context\n");
}
Common::LockGuard lock(g_render_ctx->GetMutex());
m_buffer_cache.FillBuffer(command, g_render_ctx->GetGraphicCtx(), vaddr, size, value);
}
void GpuResourceManager::CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_t src_vaddr,
uint64_t size) {
if (g_render_ctx == nullptr || command == nullptr || command->IsInvalid()) {
EXIT("cannot copy a buffer without a valid render command context\n");
}
Common::LockGuard lock(g_render_ctx->GetMutex());
m_buffer_cache.CopyBuffer(command, g_render_ctx->GetGraphicCtx(), dst_vaddr, src_vaddr, size);
}
} // namespace Libs::Graphics
@@ -0,0 +1,56 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/resourceMutex.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include <cstdint>
#include <mutex>
namespace Libs::Graphics {
class CommandBuffer;
class GpuResourceManager {
public:
GpuResourceManager();
~GpuResourceManager();
KYTY_CLASS_NO_COPY(GpuResourceManager);
[[nodiscard]] BufferCache* GetBufferCache() { return &m_buffer_cache; }
[[nodiscard]] TextureCache* GetTextureCache() { return &m_texture_cache; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
// Serializes synchronous command-processor guest-memory access against buffer/image ownership
// publication. Address metadata keeps this seam ready for a future page-range lock.
[[nodiscard]] std::unique_lock<ResourceMutex>
LockCommandProcessorGuestAccess(uint64_t vaddr, uint64_t size);
// Keeps an asynchronous host callback's validation and guest write in one cache transaction.
// Cached buffer/image destinations are unsupported and fail before the callback can fault.
[[nodiscard]] std::unique_lock<ResourceMutex> LockAsynchronousGuestWrite(uint64_t vaddr,
uint64_t size);
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);
private:
static bool FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
PageManager m_page_manager;
ResourceMutex m_resource_mutex;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
+74
View File
@@ -0,0 +1,74 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include <cstddef>
#include <span>
namespace Libs::Graphics {
struct Image final: ImageInfo {
Image& operator=(const ImageInfo& value) {
if (m_cpu_dirty) {
EXIT("dirty sampled image cannot be reassigned\n");
}
static_cast<ImageInfo&>(*this) = value;
return *this;
}
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
if (ImagePageRangesOverlap(address, this->size, vaddr, size)) {
m_cpu_dirty = true;
}
}
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty; }
void RefreshComplete() {
if (!m_cpu_dirty) {
EXIT("clean sampled image cannot complete a refresh\n");
}
m_cpu_dirty = false;
}
private:
bool m_cpu_dirty = false;
};
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 (ImagePageRangesOverlap(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_
+487
View File
@@ -0,0 +1,487 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include <bit>
#include <cmath>
#include <cstdint>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
struct ImageInfo {
uint64_t address = 0;
uint64_t size = 0;
uint32_t format = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t base_level = 0;
uint32_t levels = 1;
uint32_t view_levels = 1;
uint32_t tile = 0;
uint32_t swizzle = 0;
uint32_t depth = 1;
uint32_t type = 0;
uint32_t base_array = 0;
};
struct RenderTargetInfo {
uint64_t address = 0;
uint64_t size = 0;
VkFormat format = VK_FORMAT_UNDEFINED;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t bytes_per_element = 0;
uint32_t tile_mode = 0;
uint32_t levels = 1;
};
// Common image-to-buffer copy description. Storage images and render targets keep distinct
// cache records today, but downloads should consume one normalized layout just as uploads do.
struct ColorImageTransferInfo {
uint64_t address = 0;
uint64_t size = 0;
VkFormat format = VK_FORMAT_UNDEFINED;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t bytes_per_element = 0;
uint32_t tile_mode = 0;
uint32_t levels = 1;
};
[[nodiscard]] inline ColorImageTransferInfo
MakeColorImageTransferInfo(const ImageInfo& info, VkFormat format,
uint32_t bytes_per_element) noexcept {
return {info.address, info.size, format, info.width, info.height,
info.pitch, bytes_per_element, info.tile, info.levels};
}
[[nodiscard]] inline ColorImageTransferInfo
MakeColorImageTransferInfo(const RenderTargetInfo& info) noexcept {
return {
info.address, info.size, info.format, info.width, info.height, info.pitch,
info.bytes_per_element, info.tile_mode, info.levels};
}
struct DepthTargetInfo {
uint64_t address = 0;
uint64_t size = 0;
uint64_t stencil_address = 0;
uint64_t stencil_size = 0;
uint64_t htile_address = 0;
uint64_t htile_size = 0;
VkFormat format = VK_FORMAT_UNDEFINED;
uint32_t guest_format = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t bytes_per_element = 0;
uint32_t tile_mode = 0;
bool depth_load_clear = false;
bool stencil_load_clear = false;
bool stencil_access = false;
};
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_64_64, Unsupported };
struct VideoOutInfo {
uint64_t address = 0;
uint64_t size = 0;
uint64_t metadata_address = 0;
VkFormat format = VK_FORMAT_UNDEFINED;
uint32_t guest_format = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint32_t bytes_per_element = 0;
uint32_t tile_mode = 0;
uint32_t dcc_control = 0;
VideoOutCompression compression = VideoOutCompression::Unsupported;
};
[[nodiscard]] inline VideoOutCompression
ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control,
uint64_t dcc_clear_color) noexcept {
constexpr uint32_t VIDEO_OUT_DCC_CONTROL_256_64_64 = 0x00000208u;
if (!compressed) {
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
? VideoOutCompression::Uncompressed
: VideoOutCompression::Unsupported;
}
return metadata_address != 0 && (metadata_address & 0xffu) == 0 &&
dcc_control == VIDEO_OUT_DCC_CONTROL_256_64_64 && dcc_clear_color == 0
? VideoOutCompression::Dcc256_64_64
: VideoOutCompression::Unsupported;
}
[[nodiscard]] inline constexpr bool
CanUseVideoOutNativeWithoutUpload(VideoOutCompression compression, bool render_target,
bool gpu_modified, bool guest_modified) noexcept {
return compression == VideoOutCompression::Dcc256_64_64 && !guest_modified &&
(render_target || gpu_modified);
}
struct VideoOutPixelFormatInfo {
VkFormat format = VK_FORMAT_UNDEFINED;
uint32_t guest_format = 0;
};
[[nodiscard]] inline bool DecodeVideoOutPixelFormat(uint64_t pixel_format,
VideoOutPixelFormatInfo* info) {
if (info == nullptr) {
return false;
}
VideoOutPixelFormatInfo next {};
switch (pixel_format) {
case 0x8000000022000000ull: // SCE_VIDEO_OUT_PIXEL_FORMAT2_R8_G8_B8_A8_SRGB
next.format = VK_FORMAT_R8G8B8A8_SRGB;
next.guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb);
break;
case 0x8000000000000000ull: // SCE_VIDEO_OUT_PIXEL_FORMAT2_B8_G8_R8_A8_SRGB
next.format = VK_FORMAT_B8G8R8A8_SRGB;
next.guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb);
break;
case 0x8100000022000000ull: // SCE_VIDEO_OUT_PIXEL_FORMAT2_R10_G10_B10_A2_SRGB
next.format = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
next.guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm);
break;
case 0x8100000000000000ull: // SCE_VIDEO_OUT_PIXEL_FORMAT2_B10_G10_R10_A2_SRGB
next.format = VK_FORMAT_A2R10G10B10_UNORM_PACK32;
next.guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm);
break;
default: return false;
}
*info = next;
return true;
}
[[nodiscard]] inline bool IsSupportedVideoOutFormat(uint32_t guest_format, VkFormat format) {
const bool rgba8 = guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb) &&
(format == VK_FORMAT_R8G8B8A8_SRGB || format == VK_FORMAT_B8G8R8A8_SRGB);
const bool rgb10 = guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm) &&
(format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 ||
format == VK_FORMAT_A2R10G10B10_UNORM_PACK32);
return rgba8 || rgb10;
}
enum class DepthOverlap : uint8_t { None, RetireSampled, Unsupported };
enum class RenderTargetOverlap : uint8_t { None, RetireSampled, RetireTarget, Unsupported };
enum class SampledOverlap : uint8_t { None, ReadOnlyAlias, Unsupported };
enum class StorageSampledOverlap : uint8_t { None, ExactImage, Unsupported };
enum class HostWriteOverlap : uint8_t { None, InvalidateImage, Unsupported };
enum class BufferImageAlias : uint8_t { None, VideoOutWrite, RenderTargetWrite, Unsupported };
enum class MetaImageOverlap : uint8_t { RetainSampled, RetireTarget, Unsupported };
[[nodiscard]] inline MetaImageOverlap ClassifyMetaImageOverlap(bool sampled, bool render_target,
bool gpu_modified,
bool buffer_modified) {
if (sampled && !gpu_modified) {
return MetaImageOverlap::RetainSampled;
}
if (render_target && !gpu_modified && !buffer_modified) {
return MetaImageOverlap::RetireTarget;
}
return MetaImageOverlap::Unsupported;
}
[[nodiscard]] inline float DecodeSrgbClearComponent(uint32_t value) {
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
}
[[nodiscard]] inline bool DecodePackedColorClear(VkFormat format, uint32_t packed,
VkClearColorValue* clear) {
if (clear == nullptr) {
return false;
}
VkClearColorValue next {};
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
switch (format) {
case VK_FORMAT_R8G8B8A8_SRGB:
next.float32[0] = DecodeSrgbClearComponent(packed);
next.float32[1] = DecodeSrgbClearComponent(packed >> 8u);
next.float32[2] = DecodeSrgbClearComponent(packed >> 16u);
next.float32[3] = unorm8(packed >> 24u);
break;
case VK_FORMAT_B8G8R8A8_SRGB:
next.float32[0] = DecodeSrgbClearComponent(packed >> 16u);
next.float32[1] = DecodeSrgbClearComponent(packed >> 8u);
next.float32[2] = DecodeSrgbClearComponent(packed);
next.float32[3] = unorm8(packed >> 24u);
break;
case VK_FORMAT_R8G8B8A8_UNORM:
next.float32[0] = unorm8(packed);
next.float32[1] = unorm8(packed >> 8u);
next.float32[2] = unorm8(packed >> 16u);
next.float32[3] = unorm8(packed >> 24u);
break;
case VK_FORMAT_B8G8R8A8_UNORM:
next.float32[0] = unorm8(packed >> 16u);
next.float32[1] = unorm8(packed >> 8u);
next.float32[2] = unorm8(packed);
next.float32[3] = unorm8(packed >> 24u);
break;
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
next.float32[0] = static_cast<float>(packed & 0x3ffu) / 1023.0f;
next.float32[1] = static_cast<float>((packed >> 10u) & 0x3ffu) / 1023.0f;
next.float32[2] = static_cast<float>((packed >> 20u) & 0x3ffu) / 1023.0f;
next.float32[3] = static_cast<float>((packed >> 30u) & 0x3u) / 3.0f;
break;
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
next.float32[0] = static_cast<float>((packed >> 20u) & 0x3ffu) / 1023.0f;
next.float32[1] = static_cast<float>((packed >> 10u) & 0x3ffu) / 1023.0f;
next.float32[2] = static_cast<float>(packed & 0x3ffu) / 1023.0f;
next.float32[3] = static_cast<float>((packed >> 30u) & 0x3u) / 3.0f;
break;
default: return false;
}
*clear = next;
return true;
}
[[nodiscard]] inline bool DecodePackedStencilClear(uint32_t packed, uint8_t* clear) {
if (clear == nullptr) {
return false;
}
const auto value = static_cast<uint8_t>(packed);
if (packed != static_cast<uint32_t>(value) * 0x01010101u) {
return false;
}
*clear = value;
return true;
}
[[nodiscard]] inline bool DecodePackedDepthClear(VkFormat format, uint32_t packed, float* clear) {
if (clear == nullptr ||
(format != VK_FORMAT_D32_SFLOAT && format != VK_FORMAT_D32_SFLOAT_S8_UINT)) {
return false;
}
const auto value = std::bit_cast<float>(packed);
if (!std::isfinite(value) || value < 0.0f || value > 1.0f) {
return false;
}
*clear = value;
return true;
}
[[nodiscard]] inline bool CanNativeClearDepthFromBuffer(const DepthTargetInfo& target,
uint64_t address, uint64_t size) {
const bool d32 =
target.format == VK_FORMAT_D32_SFLOAT || target.format == VK_FORMAT_D32_SFLOAT_S8_UINT;
return address == target.address && size == target.size && d32 &&
target.guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float) &&
target.bytes_per_element == 4 && target.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
target.htile_address == 0 && target.htile_size == 0;
}
[[nodiscard]] inline bool CanLoadStencilAttachment(const DepthTargetInfo& target,
bool stencil_initialized) {
const bool has_stencil = target.stencil_address != 0 || target.stencil_size != 0;
return !has_stencil || !target.stencil_access || target.stencil_load_clear ||
stencil_initialized;
}
[[nodiscard]] inline bool IsDepthTargetRangeCompatible(const DepthTargetInfo& target,
uint64_t address, uint64_t size) {
if (address == 0 || size == 0 || address > UINT64_MAX - size) {
EXIT("invalid depth-target view range\n");
}
const bool depth = address == target.address && size <= target.size;
const bool stencil = target.stencil_address != 0 && address == target.stencil_address &&
size == target.stencil_size;
return depth || stencil;
}
[[nodiscard]] inline bool ImageRangeOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) {
if (left_size == 0 || right_size == 0 || left > UINT64_MAX - left_size ||
right > UINT64_MAX - right_size) {
EXIT("invalid image overlap range\n");
}
return left < right + right_size && right < left + left_size;
}
[[nodiscard]] inline bool ImagePageRangesOverlap(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) {
if (left_size == 0 || right_size == 0 || left > UINT64_MAX - left_size ||
right > UINT64_MAX - right_size) {
EXIT("invalid image page-overlap range\n");
}
const auto left_first = left / TRACKER_PAGE_SIZE;
const auto left_last = (left + left_size - 1) / TRACKER_PAGE_SIZE;
const auto right_first = right / TRACKER_PAGE_SIZE;
const auto right_last = (right + right_size - 1) / TRACKER_PAGE_SIZE;
return left_first <= right_last && right_first <= left_last;
}
[[nodiscard]] inline SampledOverlap ClassifySampledOverlap(const ImageInfo& requested,
const ImageInfo& cached,
bool cached_gpu_modified,
bool same_context) {
if (!ImagePageRangesOverlap(requested.address, requested.size, cached.address, cached.size)) {
return SampledOverlap::None;
}
return !cached_gpu_modified && same_context ? SampledOverlap::ReadOnlyAlias
: SampledOverlap::Unsupported;
}
[[nodiscard]] inline bool IsRgba8SrgbReinterpretation(VkFormat cached, VkFormat requested) noexcept;
[[nodiscard]] inline StorageSampledOverlap
ClassifyStorageSampledOverlap(const ImageInfo& requested, const ImageInfo& cached,
VkFormat requested_view_format, VkFormat cached_image_format,
bool cached_gpu_modified, bool cached_cpu_dirty, bool same_context) {
if (!ImagePageRangesOverlap(requested.address, requested.size, cached.address, cached.size)) {
return StorageSampledOverlap::None;
}
const bool same_backing =
requested.address == cached.address && requested.size == cached.size &&
requested.width == cached.width && requested.height == cached.height &&
requested.pitch == cached.pitch && requested.base_level == cached.base_level &&
requested.levels == cached.levels && requested.view_levels == cached.view_levels &&
requested.tile == cached.tile && requested.depth == cached.depth &&
requested.type == cached.type && requested.base_array == cached.base_array;
const bool compatible_format =
(requested.format == cached.format && requested_view_format == cached_image_format) ||
IsRgba8SrgbReinterpretation(cached_image_format, requested_view_format);
return same_backing && compatible_format && cached_gpu_modified && !cached_cpu_dirty &&
same_context
? StorageSampledOverlap::ExactImage
: StorageSampledOverlap::Unsupported;
}
[[nodiscard]] inline HostWriteOverlap
ClassifyHostWriteOverlap(uint64_t write_address, uint64_t write_size, uint64_t image_address,
uint64_t image_size, bool host_refreshable, bool gpu_modified,
bool metadata_overlap) {
if (!ImagePageRangesOverlap(write_address, write_size, image_address, image_size)) {
return HostWriteOverlap::None;
}
return host_refreshable && !gpu_modified && !metadata_overlap
? HostWriteOverlap::InvalidateImage
: HostWriteOverlap::Unsupported;
}
[[nodiscard]] inline bool IsSampledBufferWriteTransition(uint64_t buffer_address,
uint64_t buffer_size,
uint64_t image_address,
uint64_t image_size,
bool image_gpu_modified) {
if (!ImagePageRangesOverlap(buffer_address, buffer_size, image_address, image_size)) {
return false;
}
const bool exact = buffer_address == image_address && buffer_size == image_size;
const bool page_aligned = ((buffer_address | buffer_size) & (TRACKER_PAGE_SIZE - 1)) == 0;
return exact && page_aligned && !image_gpu_modified;
}
[[nodiscard]] inline BufferImageAlias
ClassifyBufferImageAlias(uint64_t buffer_address, uint64_t buffer_size, uint64_t image_address,
uint64_t image_size, bool video_out, bool render_target,
bool image_gpu_modified, bool buffer_written, bool buffer_formatted) {
if (!ImagePageRangesOverlap(buffer_address, buffer_size, image_address, image_size)) {
return BufferImageAlias::None;
}
const bool exact = buffer_address == image_address && buffer_size == image_size;
if (!exact || !buffer_written || !buffer_formatted) {
return BufferImageAlias::Unsupported;
}
if (video_out && !render_target && !image_gpu_modified) {
return BufferImageAlias::VideoOutWrite;
}
const bool page_aligned = ((buffer_address | buffer_size) & (TRACKER_PAGE_SIZE - 1)) == 0;
if (render_target && !video_out && image_gpu_modified && page_aligned) {
return BufferImageAlias::RenderTargetWrite;
}
return BufferImageAlias::Unsupported;
}
[[nodiscard]] inline DepthOverlap ClassifyDepthOverlap(const ImageInfo& sampled,
bool sampled_gpu_modified,
const DepthTargetInfo& depth) {
const bool depth_overlap =
ImageRangeOverlaps(sampled.address, sampled.size, depth.address, depth.size);
const bool stencil_overlap =
depth.stencil_address != 0 && ImageRangeOverlaps(sampled.address, sampled.size,
depth.stencil_address, depth.stencil_size);
if (!depth_overlap && !stencil_overlap) {
return DepthOverlap::None;
}
const bool has_stencil = depth.stencil_address != 0 || depth.stencil_size != 0;
if (sampled.address == depth.address && !sampled_gpu_modified && depth.depth_load_clear &&
(!has_stencil ||
(depth.stencil_address != 0 && depth.stencil_size != 0 && depth.stencil_load_clear))) {
return DepthOverlap::RetireSampled;
}
return DepthOverlap::Unsupported;
}
[[nodiscard]] inline RenderTargetOverlap
ClassifyRenderTargetOverlap(const ImageInfo& sampled, bool sampled_gpu_modified, bool same_context,
const RenderTargetInfo& target) {
if (!ImagePageRangesOverlap(sampled.address, sampled.size, target.address, target.size)) {
return RenderTargetOverlap::None;
}
const bool exact = sampled.address == target.address && sampled.size == target.size;
const bool page_isolated_overlap =
sampled.address % TRACKER_PAGE_SIZE == 0 && sampled.size % TRACKER_PAGE_SIZE == 0 &&
target.address % TRACKER_PAGE_SIZE == 0 && target.size % TRACKER_PAGE_SIZE == 0 &&
ImageRangeOverlaps(sampled.address, sampled.size, target.address, target.size);
return !sampled_gpu_modified && same_context && (exact || page_isolated_overlap)
? RenderTargetOverlap::RetireSampled
: RenderTargetOverlap::Unsupported;
}
[[nodiscard]] inline bool IsRgba8SrgbViewFormat(VkFormat format) noexcept {
return format == VK_FORMAT_R8G8B8A8_UNORM || format == VK_FORMAT_R8G8B8A8_SRGB ||
format == VK_FORMAT_B8G8R8A8_UNORM || format == VK_FORMAT_B8G8R8A8_SRGB;
}
[[nodiscard]] inline bool IsRgba8SrgbReinterpretation(VkFormat cached,
VkFormat requested) noexcept {
return (cached == VK_FORMAT_R8G8B8A8_UNORM && requested == VK_FORMAT_R8G8B8A8_SRGB) ||
(cached == VK_FORMAT_R8G8B8A8_SRGB && requested == VK_FORMAT_R8G8B8A8_UNORM) ||
(cached == VK_FORMAT_B8G8R8A8_UNORM && requested == VK_FORMAT_B8G8R8A8_SRGB) ||
(cached == VK_FORMAT_B8G8R8A8_SRGB && requested == VK_FORMAT_B8G8R8A8_UNORM);
}
[[nodiscard]] inline bool IsCompatibleRenderTargetView(const RenderTargetInfo& cached,
const RenderTargetInfo& requested) noexcept {
return cached.address == requested.address && cached.size == requested.size &&
cached.width == requested.width && cached.height == requested.height &&
cached.pitch == requested.pitch &&
cached.bytes_per_element == requested.bytes_per_element &&
cached.tile_mode == requested.tile_mode && cached.levels == requested.levels &&
IsRgba8SrgbReinterpretation(cached.format, requested.format);
}
[[nodiscard]] inline RenderTargetOverlap
ClassifyRenderTargetOverlap(const RenderTargetInfo& cached, bool cached_gpu_modified,
bool cached_buffer_modified, bool same_context,
const RenderTargetInfo& requested) {
if (!ImagePageRangesOverlap(cached.address, cached.size, requested.address, requested.size)) {
return RenderTargetOverlap::None;
}
// For an equal-address allocation-pool entry, a changed block raster or block size is a new
// image allocation, not a view of the old image. In Kyty we can only retire an already-published target with page-isolated storage :)
const bool page_isolated =
cached.address % TRACKER_PAGE_SIZE == 0 && cached.size % TRACKER_PAGE_SIZE == 0 &&
requested.address % TRACKER_PAGE_SIZE == 0 && requested.size % TRACKER_PAGE_SIZE == 0;
const bool pool_storage_shape_changed = cached.pitch != requested.pitch ||
cached.height != requested.height ||
cached.bytes_per_element != requested.bytes_per_element;
return cached.address == requested.address && page_isolated && pool_storage_shape_changed &&
!cached_gpu_modified && !cached_buffer_modified && same_context
? RenderTargetOverlap::RetireTarget
: RenderTargetOverlap::Unsupported;
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
+141
View File
@@ -0,0 +1,141 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.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_uint = format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UInt) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt);
return swizzle == DstSel(4, 5, 6, 7) ||
(single_channel_uint &&
(swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1))) ||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm) &&
(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));
}
[[noreturn]] inline void UnsupportedColorView(const char* usage, VkFormat image_format,
VkFormat view_format, uint32_t swizzle) noexcept {
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
}
[[nodiscard]] inline VkFormat BgraToRgbaSampledViewFormat(VkFormat image_format) noexcept {
switch (image_format) {
case VK_FORMAT_B8G8R8A8_UNORM: return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_R8G8B8A8_SRGB;
default: return VK_FORMAT_UNDEFINED;
}
}
[[nodiscard]] inline bool IsBgraToRgba8SampledView(VkFormat image_format,
VkFormat view_format) noexcept {
const bool bgra8 =
image_format == VK_FORMAT_B8G8R8A8_UNORM || image_format == VK_FORMAT_B8G8R8A8_SRGB;
const bool rgba8 =
view_format == VK_FORMAT_R8G8B8A8_UNORM || view_format == VK_FORMAT_R8G8B8A8_SRGB;
return bgra8 && rgba8;
}
[[nodiscard]] inline VkFormat BgraSrgbStorageViewFormat(VkFormat image_format) noexcept {
return image_format == VK_FORMAT_B8G8R8A8_SRGB ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_UNDEFINED;
}
[[nodiscard]] inline bool IsBgraSrgbStorageView(VkFormat image_format, VkFormat view_format,
uint32_t swizzle) noexcept {
return view_format == BgraSrgbStorageViewFormat(image_format) && swizzle == DstSel(6, 5, 4, 7);
}
[[nodiscard]] inline int SelectSampledColorView(VkFormat image_format, VkFormat view_format,
uint32_t swizzle) noexcept {
if (image_format == view_format) {
switch (swizzle) {
case DstSel(4, 5, 6, 7): return VulkanImage::VIEW_DEFAULT;
case DstSel(4, 0, 0, 1): return VulkanImage::VIEW_R001;
case DstSel(4, 5, 0, 1): return VulkanImage::VIEW_RG01;
case DstSel(4, 5, 6, 1): return VulkanImage::VIEW_RGB1;
default: break;
}
}
if (IsBgraToRgba8SampledView(image_format, view_format) && swizzle == DstSel(6, 5, 4, 7)) {
return VulkanImage::VIEW_RGBA8_BGRA;
}
UnsupportedColorView("sampled", image_format, view_format, swizzle);
}
[[nodiscard]] inline int SelectSampledDepthView(VkFormat image_format, VkFormat view_format,
uint32_t swizzle) noexcept {
const bool d16 = image_format == VK_FORMAT_D16_UNORM && view_format == VK_FORMAT_R16_UNORM;
const bool d32 =
(image_format == VK_FORMAT_D32_SFLOAT || image_format == VK_FORMAT_D32_SFLOAT_S8_UINT) &&
view_format == VK_FORMAT_R32_SFLOAT;
if (d16 || d32) {
switch (swizzle) {
case DstSel(4, 4, 4, 4): return VulkanImage::VIEW_DEPTH_TEXTURE;
case DstSel(4, 0, 0, 0): return VulkanImage::VIEW_R000;
default: break;
}
}
EXIT("unsupported sampled depth image view: image_format=%d view_format=%d swizzle=0x%03x\n",
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
}
[[nodiscard]] inline bool
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return resource.kind == ShaderRecompiler::IR::ResourceKind::Image &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic;
}
[[nodiscard]] inline int SelectStorageColorView(VkFormat image_format, VkFormat view_format,
uint32_t swizzle) noexcept {
const bool single_channel_uint = view_format == VK_FORMAT_R8_UINT ||
view_format == VK_FORMAT_R16_UINT ||
view_format == VK_FORMAT_R32_UINT;
const bool swizzle_ok =
swizzle == DstSel(4, 5, 6, 7) ||
(single_channel_uint && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1))) ||
(view_format == VK_FORMAT_R8G8B8A8_UNORM &&
(swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) ||
(view_format == VK_FORMAT_R32G32B32A32_SFLOAT && swizzle == DstSel(5, 6, 7, 4));
if ((image_format != view_format &&
!IsBgraSrgbStorageView(image_format, view_format, swizzle)) ||
!swizzle_ok) {
UnsupportedColorView("storage", image_format, view_format, swizzle);
}
return VulkanImage::VIEW_STORAGE;
}
[[nodiscard]] inline bool
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
!resource.atomic && !resource.depth_compare;
}
inline void
ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
if (!IsSupportedStorageImageResource(resource)) {
EXIT("unsupported storage color image resource: kind=%u dimension=%u mip=%u "
"read=%d written=%d atomic=%d depth_compare=%d\n",
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written,
resource.atomic, resource.depth_compare);
}
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
+12
View File
@@ -0,0 +1,12 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_INTERNAL_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_INTERNAL_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/pipelineCache.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderState.h"
#include "graphics/host_gpu/renderer/samplerCache.h"
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_INTERNAL_H_
@@ -0,0 +1,296 @@
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/utils.h"
#include <atomic>
#include <cstring>
#include <fmt/format.h>
#include <span>
#include <utility>
namespace Libs::Graphics {
namespace {
void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_params) {
static_params.viewport_scale[0] = 0.5f;
static_params.viewport_scale[1] = 0.5f;
static_params.viewport_scale[2] = 1.0f;
static_params.viewport_offset[0] = 0.5f;
static_params.viewport_offset[1] = 0.5f;
static_params.viewport_offset[2] = 0.0f;
static_params.scissor_ltrb[0] = 0;
static_params.scissor_ltrb[1] = 0;
static_params.scissor_ltrb[2] = 1;
static_params.scissor_ltrb[3] = 1;
}
} // namespace
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
return std::memcmp(this, &other, sizeof(*this)) == 0;
}
void PipelineCache::DeletePipelineInternal(Pipeline* p) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(p == nullptr);
EXIT_IF(p->pipeline == nullptr);
EXIT_IF(p->pipeline_layout == nullptr);
DumpPipeline("delete", *p);
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
VulkanDeviceWaitIdle(gctx);
vkDestroyPipeline(gctx->device, p->pipeline, nullptr);
vkDestroyPipelineLayout(gctx->device, p->pipeline_layout, nullptr);
p->pipeline = nullptr;
p->pipeline_layout = nullptr;
}
PipelineCache::GraphicsPipeline* PipelineCache::CreateGraphicsPipeline(
VulkanFramebuffer* framebuffer, RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo* depth, ShaderVertexInputInfo* vs_input_info, HW::Context* ctx,
HW::Shader* sh_ctx, ShaderPixelInputInfo* ps_input_info, VkPrimitiveTopology 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);
EXIT_IF(framebuffer == nullptr);
EXIT_IF(depth == nullptr);
EXIT_IF(colors == nullptr);
EXIT_IF(color_count > RENDER_COLOR_ATTACHMENTS_MAX);
EXIT_IF(vs_spirv.empty());
EXIT_IF(ps_active && ps_spirv.empty());
Common::LockGuard lock(m_mutex);
const auto& vertex_info = sh_ctx->GetVs();
const auto& ps_regs = sh_ctx->GetPs();
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
? render_target_mask_slot(ctx->GetRenderTargetMask(), colors[i].target_slot)
: 0);
}
const HW::ModeControl& mc = ctx->GetModeControl();
auto vs_id = ShaderGetIdVS(&vertex_info, vs_input_info, true);
ShaderId ps_id {};
if (ps_active) {
ps_id = ShaderGetIdPS(&ps_regs, ps_input_info, true);
}
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;
if (ps_active && depth->depth_test_enable && ps_input_info->ps_execute_on_noop) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("Pipeline: temporary: accepting EXEC_ON_NOOP with depth test enabled\n");
}
}
static_params.negative_one_to_one = !ctx->GetClipControl().dx_clip_space;
static_params.topology = topology;
static_params.with_depth =
(depth->format != VK_FORMAT_UNDEFINED && depth->vulkan_buffer != nullptr);
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;
static_params.depth_bounds_test_enable = depth->depth_bounds_test_enable;
static_params.depth_min_bounds = depth->depth_min_bounds;
static_params.depth_max_bounds = depth->depth_max_bounds;
static_params.stencil_test_enable = depth->stencil_test_enable;
static_params.stencil_front = depth->stencil_static_front;
static_params.stencil_back = depth->stencil_static_back;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
static_params.color_mask[i] = color_mask[i];
}
static_params.cull_back = mc.cull_back;
static_params.cull_front = mc.cull_front;
static_params.face = mc.face;
for (uint32_t i = 0; i < color_count; i++) {
const auto& rt = ctx->GetRenderTarget(colors[i].target_slot);
const auto& bc = ctx->GetBlendControl(colors[i].target_slot);
static_params.color_srcblend[i] = bc.color_srcblend;
static_params.color_comb_fcn[i] = bc.color_comb_fcn;
static_params.color_destblend[i] = bc.color_destblend;
static_params.alpha_srcblend[i] = bc.alpha_srcblend;
static_params.alpha_comb_fcn[i] = bc.alpha_comb_fcn;
static_params.alpha_destblend[i] = bc.alpha_destblend;
static_params.separate_alpha_blend[i] = bc.separate_alpha_blend;
static_params.blend_enable[i] = bc.enable;
static_params.blend_bypass[i] = rt.info.blend_bypass;
}
static_params.blend_color_red = bclr.red;
static_params.blend_color_green = bclr.green;
static_params.blend_color_blue = bclr.blue;
static_params.blend_color_alpha = bclr.alpha;
NormalizeStaticParamsForDynamicState(static_params);
GraphicsPipelineKey key {};
key.render_pass_id = p.render_pass_id;
key.vs_shader_id = p.vs_shader_id;
key.ps_shader_id = p.ps_shader_id;
key.static_params = static_params;
if (auto iter = m_graphics_pipelines.find(key); iter != m_graphics_pipelines.end()) {
return iter->second.get();
}
if (graphics_debug_dump_enabled()) {
ShaderDbgDumpInputInfo(vs_input_info);
if (ps_active) {
ShaderDbgDumpInputInfo(ps_input_info);
}
LOGF("PipelineTrace: shader binaries VS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64 "\n",
vs_id.hash0, vs_id.crc32, static_cast<uint64_t>(vs_spirv.size()), ps_id.hash0,
ps_id.crc32, static_cast<uint64_t>(ps_spirv.size()));
}
auto cached = std::make_unique<GraphicsPipeline>(p);
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
CreatePipelineInternal(cached.get(), framebuffer->render_pass, 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,
ps_id.crc32);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
auto [iter, inserted] = m_graphics_pipelines.emplace(std::move(key), std::move(cached));
EXIT_IF(!inserted);
DumpPipeline("create", *iter->second);
return iter->second.get();
}
PipelineCache::ComputePipeline*
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo* input_info,
const HW::ComputeShaderInfo* cs_regs,
std::span<const uint32_t> cs_spirv) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Compute)", profiler::colors::RedA100);
EXIT_IF(cs_regs == nullptr);
EXIT_IF(cs_spirv.empty());
Common::LockGuard lock(m_mutex);
auto cs_id = ShaderGetIdCS(cs_regs, input_info, true);
ComputePipeline p {};
p.cs_shader_id = cs_id;
ComputePipelineKey key {};
key.cs_shader_id = p.cs_shader_id;
if (auto iter = m_compute_pipelines.find(key); iter != m_compute_pipelines.end()) {
return iter->second.get();
}
if (graphics_debug_dump_enabled()) {
ShaderDbgDumpInputInfo(input_info);
}
auto cached = std::make_unique<ComputePipeline>(p);
CreatePipelineInternal(cached.get(), input_info, cs_spirv);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
auto [iter, inserted] = m_compute_pipelines.emplace(std::move(key), std::move(cached));
EXIT_IF(!inserted);
return iter->second.get();
}
void PipelineCache::DeletePipeline(Pipeline* pipeline) {
Common::LockGuard lock(m_mutex);
EXIT_IF(pipeline == nullptr);
for (auto iter = m_graphics_pipelines.begin(); iter != m_graphics_pipelines.end(); iter++) {
if (iter->second.get() == pipeline) {
DeletePipelineInternal(iter->second.get());
m_graphics_pipelines.erase(iter);
return;
}
}
for (auto iter = m_compute_pipelines.begin(); iter != m_compute_pipelines.end(); iter++) {
if (iter->second.get() == pipeline) {
DeletePipelineInternal(iter->second.get());
m_compute_pipelines.erase(iter);
return;
}
}
EXIT_IF(true);
}
void PipelineCache::DeleteAllPipelines() {
Common::LockGuard lock(m_mutex);
for (auto& item: m_graphics_pipelines) {
DeletePipelineInternal(item.second.get());
}
m_graphics_pipelines.clear();
for (auto& item: m_compute_pipelines) {
DeletePipelineInternal(item.second.get());
}
m_compute_pipelines.clear();
}
void PipelineCache::DumpToFile(Common::File* f, const Pipeline& p) {}
void PipelineCache::DumpPipeline(const char* action, const Pipeline& p) {
EXIT_IF(action == nullptr);
static std::atomic_int dump_id = 0;
if (!Config::PipelineDumpEnabled()) {
return;
}
Common::File f;
const auto file_name =
Config::GetPipelineDumpFolder() /
fmt::format("{:04d}_{:04d}_pipeline_{}.log", GraphicsRunGetFrameNum(), dump_id++, action);
Common::File::CreateDirectories(file_name.parent_path());
f.Create(file_name);
if (f.IsInvalid()) {
auto file_name_text = Common::PathToString(file_name);
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", file_name_text.c_str());
return;
}
DumpToFile(&f, p);
f.Close();
}
} // namespace Libs::Graphics
@@ -0,0 +1,152 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINECACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINECACHE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/file.h"
#include "common/threads.h"
#include "graphics/host_gpu/renderer/renderState.h"
#include "graphics/shader/shader.h"
#include <cstddef>
#include <memory>
#include <span>
#include <unordered_map>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
struct VulkanFramebuffer;
namespace HW {
class Context;
class Shader;
struct ComputeShaderInfo;
} // namespace HW
class PipelineCache {
public:
PipelineCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
virtual ~PipelineCache() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(PipelineCache);
struct Pipeline {
VkPipelineLayout pipeline_layout = nullptr;
VkPipeline pipeline = nullptr;
};
struct GraphicsPipeline: Pipeline {
uint64_t render_pass_id = 0;
ShaderId vs_shader_id;
ShaderId ps_shader_id;
};
struct ComputePipeline: Pipeline {
ShaderId cs_shader_id;
};
GraphicsPipeline* CreateGraphicsPipeline(
VulkanFramebuffer* framebuffer, RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo* depth, ShaderVertexInputInfo* vs_input_info, HW::Context* ctx,
HW::Shader* sh_ctx, ShaderPixelInputInfo* ps_input_info, VkPrimitiveTopology topology,
bool ps_active, std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
ComputePipeline* CreateComputePipeline(ShaderComputeInputInfo* input_info,
const HW::ComputeShaderInfo* cs_regs,
std::span<const uint32_t> cs_spirv);
void DeletePipeline(Pipeline* pipeline);
void DeleteAllPipelines();
private:
struct GraphicsPipelineKey {
uint64_t render_pass_id = 0;
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 &&
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
}
};
struct ComputePipelineKey {
ShaderId cs_shader_id;
bool operator==(const ComputePipelineKey& other) const {
return cs_shader_id == other.cs_shader_id;
}
};
struct PipelineKeyHash {
static void Mix(std::size_t* hash, std::size_t value) {
*hash ^= value + static_cast<std::size_t>(0x9e3779b97f4a7c15ull) + (*hash << 6u) +
(*hash >> 2u);
}
static void MixShaderId(std::size_t* hash, const ShaderId& id) {
Mix(hash, id.hash0);
Mix(hash, id.crc32);
Mix(hash, id.ids.size());
for (auto value: id.ids) {
Mix(hash, value);
}
}
static void MixStaticParams(std::size_t* hash, const PipelineStaticParameters& params) {
const auto* bytes = reinterpret_cast<const uint8_t*>(&params);
for (std::size_t i = 0; i < sizeof(params); i++) {
Mix(hash, bytes[i]);
}
}
};
struct GraphicsPipelineKeyHash {
std::size_t operator()(const GraphicsPipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::Mix(&hash, key.render_pass_id);
PipelineKeyHash::MixShaderId(&hash, key.vs_shader_id);
PipelineKeyHash::MixShaderId(&hash, key.ps_shader_id);
PipelineKeyHash::MixStaticParams(&hash, key.static_params);
return hash;
}
};
struct ComputePipelineKeyHash {
std::size_t operator()(const ComputePipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixShaderId(&hash, key.cs_shader_id);
return hash;
}
};
void DeletePipelineInternal(Pipeline* p);
void DumpToFile(Common::File* f, const Pipeline& p);
void DumpPipeline(const char* action, const Pipeline& p);
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
std::unordered_map<ComputePipelineKey, std::unique_ptr<ComputePipeline>, ComputePipelineKeyHash>
m_compute_pipelines;
Common::Mutex m_mutex;
};
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, VkRenderPass render_pass,
const ShaderVertexInputInfo* vs_input_info,
std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info,
std::span<const uint32_t> ps_shader,
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,
const ShaderComputeInputInfo* input_info,
std::span<const uint32_t> cs_shader);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINECACHE_H_
+191
View File
@@ -0,0 +1,191 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRENDER_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRENDER_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "kernel/eventQueue.h"
#include <memory>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
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 VulkanCommandPool;
struct VulkanBuffer;
struct VulkanDescriptorSet;
struct VulkanFramebuffer;
struct RenderDepthInfo;
struct RenderColorInfo;
class BufferCache;
struct HtileClearTarget {
uint64_t address = 0;
uint64_t size = 0;
};
class FenceResourceRetainer {
public:
FenceResourceRetainer() = default;
~FenceResourceRetainer();
KYTY_CLASS_NO_COPY(FenceResourceRetainer);
void Retain(std::shared_ptr<void> resource);
void ReleaseAfterFence() noexcept;
[[nodiscard]] bool Empty() const noexcept { return m_resources.empty(); }
private:
std::vector<std::shared_ptr<void>> m_resources;
};
class CommandBuffer {
public:
explicit CommandBuffer(int queue): m_queue(queue) { Allocate(); }
virtual ~CommandBuffer() { Free(); }
KYTY_CLASS_NO_COPY(CommandBuffer);
[[nodiscard]] bool IsInvalid() const;
void Allocate();
void Free();
void Begin() const;
void End() const;
void Execute();
void ExecuteWithSemaphore(VkSemaphore signal_semaphore = nullptr);
void ExecuteWithSemaphore(VkSemaphore wait_semaphore, VkPipelineStageFlags wait_stage,
VkSemaphore signal_semaphore);
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 WaitForFence();
void WaitForFenceAndReset();
void DeleteAfterFence(VulkanBuffer* buffer);
void RetainResourceUntilFence(std::shared_ptr<void> resource);
void RecycleDescriptorAfterFence(VulkanDescriptorSet* set);
[[nodiscard]] uint32_t GetIndex() const { return m_index; }
[[nodiscard]] int GetQueue() const { return m_queue; }
VulkanCommandPool* GetPool() { return m_pool; }
[[nodiscard]] bool IsExecute() const { return m_execute; }
private:
friend class BufferCache;
void RecycleDescriptorsAfterFence();
VulkanCommandPool* m_pool = nullptr;
uint32_t m_index = static_cast<uint32_t>(-1);
int m_queue = -1;
bool m_execute = false;
uint64_t m_submit_seq = 0;
uint32_t m_debug_op = 0;
uint64_t m_debug_submit_id = 0;
uint32_t m_debug_arg0 = 0;
uint32_t m_debug_arg1 = 0;
uint32_t m_debug_arg2 = 0;
uint32_t m_debug_arg3 = 0;
uint64_t m_debug_arg4 = 0;
std::vector<VulkanBuffer*> m_delete_after_fence;
FenceResourceRetainer m_fence_resources;
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
HostStreamBuffer m_host_stream;
};
void RenderDrawIndex(uint64_t submit_id, CommandBuffer* buffer, HW::Context* ctx,
HW::UserConfig* ucfg, HW::Shader* sh_ctx, 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, CommandBuffer* buffer, HW::Context* ctx,
HW::UserConfig* ucfg, HW::Shader* sh_ctx, 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, CommandBuffer* buffer, HW::Context* ctx,
HW::Shader* sh_ctx, uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode);
void GraphicsRenderInit();
void GraphicsRenderCreateContext();
[[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 GraphicsRenderWriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer* buffer,
uint64_t* dst_gpu_addr, uint64_t value);
void GraphicsRenderWriteAtEndOfPipeClockCounter(uint64_t submit_id, CommandBuffer* buffer,
uint64_t* dst_gpu_addr);
void GraphicsRenderWriteAtEndOfPipeClockCounterWithWriteBack(uint64_t submit_id,
CommandBuffer* buffer,
uint64_t* dst_gpu_addr);
void GraphicsRenderWriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value);
void GraphicsRenderWriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t dw_offset,
uint32_t dw_num);
void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBackFlip32(
uint64_t submit_id, CommandBuffer* buffer, uint32_t* dst_gpu_addr, uint32_t value, int handle,
int index, int flip_mode, int64_t flip_arg);
void GraphicsRenderWriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value, int handle,
int index, int flip_mode, int64_t flip_arg);
void GraphicsRenderWriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer* buffer, int handle,
int index, int flip_mode, int64_t flip_arg);
void GraphicsRenderWriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer* buffer,
uint64_t* dst_gpu_addr, uint64_t value);
void GraphicsRenderWriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value);
void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id,
CommandBuffer* buffer,
uint64_t* dst_gpu_addr, uint64_t value,
uint32_t context_id = 0);
void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBack32(uint64_t submit_id,
CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value,
uint32_t context_id = 0);
void GraphicsRenderWriteAtEndOfPipeWithInterrupt64(uint64_t submit_id, CommandBuffer* buffer,
uint64_t* dst_gpu_addr, uint64_t value,
uint32_t context_id = 0);
void GraphicsRenderWriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value,
uint32_t context_id = 0);
void GraphicsRenderMemoryBarrier(CommandBuffer* buffer);
void GraphicsRenderTextureBarrier(CommandBuffer* buffer, uint64_t vaddr, uint64_t size);
void GraphicsRenderDepthStencilBarrier(CommandBuffer* buffer, uint64_t vaddr, uint64_t size);
void GraphicsRenderDeleteBuffers();
void GraphicsRenderTriggerEopEventAtEndOfPipe(CommandBuffer* buffer, uint32_t context_id);
int GraphicsRenderAddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata);
int GraphicsRenderDeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id);
void GraphicsRenderTriggerAgcUserInterrupt();
void GraphicsRenderTriggerEopEvent(uint32_t context_id);
void GraphicsRenderClearGds(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value);
void GraphicsRenderReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRENDER_H_ */
@@ -0,0 +1,577 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderInternal.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/file.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/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/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderState.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/host_gpu/utils.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shader.h"
#include "kernel/eventQueue.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstring>
#include <limits>
#include <span>
#include <unordered_map>
#include <vector>
#include <vulkan/vk_enum_string_helper.h>
// IWYU pragma: no_forward_declare VkImageView_T
namespace Libs::Graphics {
static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
const uint64_t records = descriptor.NumRecords();
const uint64_t stride = descriptor.Stride();
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("compute buffer descriptor footprint overflow\n");
}
return stride == 0 ? records : records * stride;
}
bool ResolveHtileClearTarget(const HW::DepthRenderTarget& z, uint64_t descriptor_size,
HtileClearTarget* resolved) {
if (resolved == nullptr) {
return false;
}
*resolved = {};
const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const bool depth_format_supported =
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kZ16) ||
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kZ32F);
const bool msaa_compat = depth_msaa_single_sample_compatible(z.z_info.num_samples);
const bool supported_depth_state =
z.z_info.tile_surface_enable && depth_format_supported &&
!(z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kZ16) && has_stencil) &&
z.z_info.tile_mode_index == 0 && (z.z_info.num_samples == 0 || msaa_compat) &&
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;
}
if (msaa_compat) {
static std::atomic<uint32_t> logged_fragments = 0;
const uint32_t bit = 1u << z.z_info.num_samples;
if ((logged_fragments.fetch_or(bit, std::memory_order_relaxed) & bit) == 0) {
LOGF("HTileClear: compatibility: treating PS5 %ux depth fragments as single-sample\n",
bit);
}
}
const bool size_xy_valid = z.size.valid && (z.size.x_max != 0 || z.size.y_max != 0);
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;
}
const bool z16 = z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kZ16);
const uint32_t guest_format = Prospero::GpuEnumValue(z16 ? Prospero::BufferFormat::k16UNorm
: Prospero::BufferFormat::k32Float);
const uint32_t bytes = z16 ? 2u : 4u;
const uint32_t pitch = TileGetTexturePitch(guest_format, width, 1,
Prospero::GpuEnumValue(Prospero::TileMode::kDepth));
if (z.pitch_height_valid && ((static_cast<uint64_t>(z.pitch_div8_minus1) + 1u) * 8u != pitch ||
(static_cast<uint64_t>(z.height_div8_minus1) + 1u) * 8u !=
((static_cast<uint64_t>(height) + 7u) & ~7ull))) {
return false;
}
const uint32_t block_width = z16 ? 256u : 128u;
const uint64_t padded_width =
(static_cast<uint64_t>(pitch) + block_width - 1u) & ~(block_width - 1u);
const uint64_t padded_height = (static_cast<uint64_t>(height) + 127u) & ~127ull;
if (padded_width > UINT64_MAX / padded_height ||
padded_width * padded_height > UINT64_MAX / bytes) {
return false;
}
const uint64_t expected_depth_size = padded_width * padded_height * bytes;
TileSizeAlign depth_size {};
TileSizeAlign stencil_size {};
TileSizeAlign htile_size {};
if (!TileGetDepthSize(width, height, 0, z.z_info.format, z.stencil_info.format, true,
&stencil_size, &htile_size, &depth_size) ||
expected_depth_size == 0 || expected_depth_size > UINT32_MAX || depth_size.align != 65536 ||
depth_size.size != expected_depth_size ||
(has_stencil != (stencil_size.align == 65536 && stencil_size.size != 0)) ||
htile_size.align != 32768 || htile_size.size == 0 || htile_size.size != descriptor_size ||
(z.pitch_height_valid &&
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u != expected_depth_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 HW::Context& ctx,
uint32_t group_x, uint32_t group_y, uint32_t group_z,
uint32_t mode) {
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 = g_render_ctx->GetTextureCache();
uint32_t current_references = 0;
uint32_t registered_writes = 0;
uint64_t described_meta_size = 0;
HtileClearTarget registered_target {};
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.
if (resource.written && cache->IsMetaRange(descriptor.Base48(), descriptor_size)) {
registered_writes++;
registered_target = {.address = descriptor.Base48(), .size = descriptor_size};
}
}
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\n");
}
cache->RegisterMeta(target.address, target.size);
} else {
target = registered_target;
}
g_render_ctx->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");
}
metadata_descriptor = descriptor;
metadata_writes++;
continue;
}
if (resource.written || !resource.read || resource.atomic || descriptor.Base48() == 0 ||
descriptor_size == 0 || cache->HasMetaOverlap(descriptor.Base48(), descriptor_size)) {
EXIT("unsupported HTile clear side-buffer access\n");
}
g_render_ctx->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);
if (!cache->ClearMeta(target.address)) {
EXIT("failed to record HTile compute clear\n");
}
const bool descriptor_backed =
current_references != 0 && !z.size.valid && !z.width_height_valid && !z.pitch_height_valid;
const uint32_t source_bit = current_references == 0 ? 1u : descriptor_backed ? 2u : 4u;
static std::atomic<uint32_t> logged_sources {0};
if ((logged_sources.fetch_or(source_bit, std::memory_order_relaxed) & source_bit) == 0) {
LOGF("HTileClear: native virtual clear source=%s shader=0x%016" PRIx64 " addr=0x%016" PRIx64
" size=0x%016" PRIx64 "\n",
current_references == 0 ? "registered"
: descriptor_backed ? "descriptor"
: "derived",
program.shader_hash, target.address, target.size);
}
return true;
}
bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode,
ShaderBufferResource* resolved_descriptor, uint32_t* resolved_clear,
uint64_t* resolved_size) {
if (resolved_descriptor == nullptr || resolved_clear == nullptr || resolved_size == nullptr) {
return false;
}
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
!program.info.images.empty() || !program.info.samplers.empty() ||
!program.info.addresses.empty() || !resources.images.empty() ||
!resources.samplers.empty() || !resources.addresses.empty()) {
return false;
}
const auto& resource = program.info.buffers.front();
const auto& raw = resources.buffers.front();
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(raw);
if (!resource.formatted || !resource.written || resource.read || resource.atomic ||
resource.scalar || resource.max_byte_extent != 16 || descriptor.Stride() != 16 ||
descriptor.Format() != Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32UInt) ||
descriptor.SwizzleEnabled() || descriptor.IndexStride() != 0 || descriptor.AddTid() ||
resource.packed_stride != descriptor.PackedStride() || raw.dword_count != 4 ||
program.user_data_base != 0 || resources.user_data.size() != 8) {
return false;
}
for (uint32_t i = 0; i < raw.dword_count; i++) {
if (raw.dwords[i] != resources.user_data[i]) {
return false;
}
}
const uint32_t clear = resources.user_data[4];
if (resources.user_data[5] != clear || resources.user_data[6] != clear ||
resources.user_data[7] != clear) {
return false;
}
const bool full_dispatch =
input.dispatch_thread_dimensions && 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.dispatch_threads_num[0] == group_x &&
input.dispatch_threads_num[1] == 1 && input.dispatch_threads_num[2] == 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 == 0x61u &&
group_x % input.threads_num[0] == 0 && descriptor.NumRecords() == group_x;
const auto size = BufferDescriptorSize(descriptor);
if (!full_dispatch || size == 0) {
return false;
}
*resolved_descriptor = descriptor;
*resolved_clear = clear;
*resolved_size = size;
return true;
}
static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, CommandBuffer* command,
uint32_t group_x, uint32_t group_y, uint32_t group_z,
uint32_t mode) {
ShaderBufferResource descriptor;
uint32_t packed_clear = 0;
uint64_t size = 0;
if (!ResolveComputeImageClear(input, group_x, group_y, group_z, mode, &descriptor,
&packed_clear, &size)) {
return false;
}
auto* cache = g_render_ctx->GetTextureCache();
if (!cache->ClearColorImageFromBuffer(command, descriptor.Base48(), size, packed_clear) &&
!cache->ClearDepthImageFromBuffer(command, descriptor.Base48(), size, packed_clear) &&
!cache->ClearStencilImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
return false;
}
static std::atomic<uint32_t> logged_clears {0};
if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: compute image clear shader=0x%016" PRIx64
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
}
return true;
}
void RenderDispatchDirect(uint64_t submit_id, CommandBuffer* buffer, HW::Context* ctx,
HW::Shader* sh_ctx, uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode) {
EXIT_IF(ctx == nullptr);
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DispatchDirect), submit_id,
thread_group_x, thread_group_y, thread_group_z, mode,
sh_ctx != nullptr ? sh_ctx->GetCs().cs_regs.data_addr : 0);
Common::LockGuard lock(g_render_ctx->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",
thread_group_x, thread_group_y, thread_group_z, mode);
return;
}
if (!ShaderAddressValid(sh_ctx->GetCs().cs_regs.data_addr)) {
return;
}
constexpr uint32_t DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS = 1u << 5u;
constexpr uint32_t DISPATCH_INITIATOR_BASE_BITS = 0x41u;
constexpr uint32_t DISPATCH_INITIATOR_MODIFIER_BITS = 0xa038u;
constexpr uint32_t DISPATCH_INITIATOR_KNOWN_MASK =
DISPATCH_INITIATOR_BASE_BITS | DISPATCH_INITIATOR_MODIFIER_BITS;
const uint32_t unknown_mode_bits = mode & ~DISPATCH_INITIATOR_KNOWN_MASK;
if (unknown_mode_bits != 0) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: unknown dispatch initiator bits "
"mode=0x%08" PRIx32 " unknown=0x%08" PRIx32 " shader=0x%016" PRIx64
" groups=%ux%ux%u\n",
mode, unknown_mode_bits, sh_ctx->GetCs().cs_regs.data_addr, thread_group_x,
thread_group_y, thread_group_z);
}
}
const auto& cs_regs = sh_ctx->GetCs();
const auto& sh_regs = ctx->GetShaderRegisters();
ShaderComputeInputInfo input_info {};
std::span<const uint32_t> cs_shader;
if (!ShaderCompileInfoCS(&cs_regs, &sh_regs, &input_info, &cs_shader)) {
EXIT("ShaderCompileInfoCS failed for dispatch with CS shader 0x%016" PRIx64 "\n",
cs_regs.cs_regs.data_addr);
}
const bool use_thread_dimensions = (mode & DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0;
if (use_thread_dimensions) {
input_info.dispatch_thread_dimensions = true;
input_info.dispatch_threads_num[0] = thread_group_x;
input_info.dispatch_threads_num[1] = thread_group_y;
input_info.dispatch_threads_num[2] = thread_group_z;
}
const uint32_t frame_num = GraphicsRunGetFrameNum();
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, *ctx, thread_group_x, thread_group_y, thread_group_z,
mode)) {
return;
}
if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
return;
}
const auto sampled_images = std::count_if(
program.info.images.begin(), program.info.images.end(), [](const auto& image) {
return image.kind == ShaderRecompiler::IR::ResourceKind::Image ||
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint;
});
const bool has_sampler = !program.info.samplers.empty();
static std::atomic<uint32_t> dispatch_log_count {0};
if ((large_workgroup || has_sampler) &&
dispatch_log_count.fetch_add(1, std::memory_order_relaxed) < 512) {
LOGF("GraphicsRenderDispatchDirect: frame=%u shader=0x%016" PRIx64
" groups=%ux%ux%u mode=0x%08" PRIx32 " local=%ux%ux%u "
"buffers=%zu textures=%zu sampled=%zu storage=%zu samplers=%zu push=%u\n",
frame_num, sh_ctx->GetCs().cs_regs.data_addr, thread_group_x, thread_group_y,
thread_group_z, mode, input_info.threads_num[0], input_info.threads_num[1],
input_info.threads_num[2], program.info.buffers.size(), program.info.images.size(),
sampled_images, program.info.images.size() - sampled_images,
program.info.samplers.size(), program.bindings.push_constant_size);
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& buffer = program.info.buffers[i];
const auto r = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
LOGF(" CS buffer[%u]: source=%u usage=%s addr=0x%012" PRIx64
" stride=%u records=%u format=%u\n",
i, buffer.source, buffer.written ? "read-write" : "read-only", r.Base48(),
r.Stride(), r.NumRecords(), r.Format());
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto& image = program.info.images[i];
const auto r = DecodeNativeDescriptor<ShaderTextureResource>(resources.images[i]);
LOGF(" CS texture[%u]: source=%u usage=%s sampled=%s addr=0x%010" PRIx64
" type=%u fmt=%u extent=%ux%u depth=%u levels=%u tile=%u\n",
i, image.source, image.written ? "read-write" : "read-only",
(image.kind == ShaderRecompiler::IR::ResourceKind::Image ||
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint)
? "true"
: "false",
r.Base40(), static_cast<uint32_t>(r.Type()), r.Format(),
static_cast<uint32_t>(r.Width5()) + 1u, static_cast<uint32_t>(r.Height5()) + 1u,
static_cast<uint32_t>(r.Depth()) + 1u,
std::max<uint32_t>(static_cast<uint32_t>(r.LastLevel()),
static_cast<uint32_t>(r.MaxMip())) +
1u,
r.TileMode());
}
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
const auto r = DecodeNativeDescriptor<ShaderSamplerResource>(resources.samplers[i]);
LOGF(" CS sampler[%u]: source=%u clamp=%u/%u/%u filter=%u/%u/%u mip=%u "
"lod=%u-%u bias=%d\n",
i, program.info.samplers[i].source, static_cast<uint32_t>(r.ClampX()),
static_cast<uint32_t>(r.ClampY()), static_cast<uint32_t>(r.ClampZ()),
static_cast<uint32_t>(r.XyMagFilter()), static_cast<uint32_t>(r.XyMinFilter()),
static_cast<uint32_t>(r.ZFilter()), static_cast<uint32_t>(r.MipFilter()),
static_cast<uint32_t>(r.MinLod()), static_cast<uint32_t>(r.MaxLod()),
static_cast<int32_t>(r.LodBias()));
}
}
if (use_thread_dimensions) {
auto groups_from_threads = [](uint32_t threads, uint32_t group_size) {
return (threads == 0
? 0u
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
};
const uint32_t old_x = thread_group_x;
const uint32_t old_y = thread_group_y;
const uint32_t old_z = thread_group_z;
thread_group_x = groups_from_threads(thread_group_x, cs_regs.cs_regs.num_thread_x);
thread_group_y = groups_from_threads(thread_group_y, cs_regs.cs_regs.num_thread_y);
thread_group_z = groups_from_threads(thread_group_z, cs_regs.cs_regs.num_thread_z);
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: use-thread-dimensions %ux%ux%u / %ux%ux%u -> "
"groups %ux%ux%u\n",
old_x, old_y, old_z, std::max(cs_regs.cs_regs.num_thread_x, 1u),
std::max(cs_regs.cs_regs.num_thread_y, 1u),
std::max(cs_regs.cs_regs.num_thread_z, 1u), thread_group_x, thread_group_y,
thread_group_z);
}
}
if (thread_group_x == 0 || thread_group_y == 0 || thread_group_z == 0) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: skipping zero-sized dispatch groups=%ux%ux%u "
"mode=0x%08" PRIx32 " shader=0x%016" PRIx64 "\n",
thread_group_x, thread_group_y, thread_group_z, mode,
sh_ctx->GetCs().cs_regs.data_addr);
}
return;
}
auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()];
auto* pipeline = g_render_ctx->GetPipelineCache()->CreateComputePipeline(
&input_info, &sh_ctx->GetCs(), cs_shader);
vkCmdBindPipeline(vk_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->pipeline);
const auto address_writes = BindDescriptors(
submit_id, buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->pipeline_layout,
input_info.stage, VK_SHADER_STAGE_COMPUTE_BIT, DescriptorCache::Stage::Compute);
vkCmdDispatch(vk_buffer, thread_group_x, thread_group_y, thread_group_z);
bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
has_storage_writes = MarkShaderAddressWrites(address_writes) || has_storage_writes;
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);
}) ||
has_storage_writes;
if (has_storage_writes) {
ShaderWriteBarrier(vk_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
}
} // namespace Libs::Graphics
@@ -0,0 +1,97 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/presentation/window.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include <algorithm>
namespace Libs::Graphics {
void RenderContext::SetGraphicCtx(GraphicContext* ctx) {
EXIT_IF(ctx == nullptr);
Common::LockGuard lock(m_graphic_ctx_mutex);
m_graphic_ctx = ctx;
}
GraphicContext* RenderContext::GetGraphicCtx() {
EXIT_IF(g_render_ctx == nullptr);
{
Common::LockGuard lock(m_graphic_ctx_mutex);
if (m_graphic_ctx != nullptr) {
return m_graphic_ctx;
}
}
WindowWaitForGraphicInitialized();
auto* ctx = WindowGetGraphicContext();
LOGF("Graphic context initialized\n ctx = %p\n", static_cast<void*>(ctx));
EXIT_IF(ctx == nullptr);
GraphicContext* ret = nullptr;
{
Common::LockGuard lock(m_graphic_ctx_mutex);
if (m_graphic_ctx == nullptr) {
m_graphic_ctx = ctx;
}
ret = m_graphic_ctx;
}
return ret;
}
void RenderContext::AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
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});
}
void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
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()) {
return;
}
if (--it->count == 0) {
m_eop_eqs.erase(it);
}
}
void RenderContext::TriggerEopEvent(uint32_t context_id) {
Common::LockGuard lock(m_eop_mutex);
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);
}
}
auto tsc = LibKernel::KernelReadTsc();
auto result = LibKernel::EventQueue::KernelTriggerUserEventForAll(AGC_USER_INTERRUPT_EVENT,
reinterpret_cast<void*>(tsc));
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
}
} // namespace Libs::Graphics
@@ -0,0 +1,72 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERCONTEXT_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERCONTEXT_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/renderer/bufferCache.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 <vector>
namespace Libs::Graphics {
constexpr int AGC_USER_INTERRUPT_EVENT = 0x1800;
class RenderContext {
public:
RenderContext() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
virtual ~RenderContext() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(RenderContext);
void SetGraphicCtx(GraphicContext* ctx);
GraphicContext* GetGraphicCtx();
Common::Mutex& GetMutex() { return m_mutex; }
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(); }
void AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
void DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
void TriggerEopEvent(uint32_t context_id);
private:
struct EopEqRegistration {
LibKernel::EventQueue::KernelEqueue eq = nullptr;
int id = 0;
uint32_t count = 0;
};
Common::Mutex m_mutex;
Common::Mutex m_graphic_ctx_mutex;
PipelineCache m_pipeline_cache;
DescriptorCache m_descriptor_cache;
FramebufferCache m_framebuffer_cache;
SamplerCache m_sampler_cache;
GraphicContext* m_graphic_ctx = nullptr;
GdsBuffer m_gds_buffer;
GpuResourceManager m_gpu_resources;
Common::Mutex m_eop_mutex;
std::vector<EopEqRegistration> m_eop_eqs;
};
extern RenderContext* g_render_ctx;
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERCONTEXT_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,21 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERINTERNAL_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERINTERNAL_H_
#include "common/assert.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include <cstring>
namespace Libs::Graphics {
template <typename T>
T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
T result {};
EXIT_IF(value.dword_count < sizeof(result) / sizeof(uint32_t));
std::memcpy(&result, value.dwords.data(), sizeof(result));
return result;
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERINTERNAL_H_
@@ -0,0 +1,281 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERSTATE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERSTATE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include <cstdint>
#include <string>
#include <type_traits>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
class CommandBuffer;
struct DepthStencilVulkanImage;
struct RenderTextureVulkanImage;
struct ShaderVertexInputInfo;
struct VulkanImage;
namespace HW {
class Context;
class Shader;
class UserConfig;
struct RenderTarget;
struct ScanModeControl;
struct ScreenViewport;
} // namespace HW
static constexpr uint32_t RENDER_COLOR_ATTACHMENTS_MAX = 8;
inline constexpr bool depth_msaa_single_sample_compatible(uint32_t encoded_fragments) {
return encoded_fragments == 1 || encoded_fragments == 2;
}
inline constexpr bool color_msaa_single_sample_compatible(uint32_t encoded_samples,
uint32_t encoded_fragments) {
return encoded_samples == encoded_fragments &&
depth_msaa_single_sample_compatible(encoded_fragments);
}
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
bool acceleration_disabled) {
return acceleration_disabled || (has_stencil && has_htile);
}
// Pack structs to guarantee the uniqueness of object representation.
#pragma pack(push, 1)
struct PipelineStencilStaticState {
VkStencilOp failOp = VK_STENCIL_OP_KEEP;
VkStencilOp passOp = VK_STENCIL_OP_KEEP;
VkStencilOp depthFailOp = VK_STENCIL_OP_KEEP;
VkCompareOp compareOp = VK_COMPARE_OP_NEVER;
};
struct PipelineStencilDynamicState {
uint32_t compareMask = 0;
uint32_t writeMask = 0;
uint32_t reference = 0;
};
inline constexpr bool stencil_face_accesses_attachment(const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
return state.compareOp != VK_COMPARE_OP_ALWAYS ||
(dynamic.writeMask != 0 &&
(state.failOp != VK_STENCIL_OP_KEEP || state.passOp != VK_STENCIL_OP_KEEP ||
state.depthFailOp != VK_STENCIL_OP_KEEP));
}
struct PipelineStaticParameters {
float viewport_scale[3] = {};
float viewport_offset[3] = {};
bool negative_one_to_one = false;
int scissor_ltrb[4] = {0};
VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
bool with_depth = false;
bool depth_test_enable = false;
bool depth_write_enable = false;
VkCompareOp depth_compare_op = VK_COMPARE_OP_NEVER;
bool depth_bounds_test_enable = false;
float depth_min_bounds = 0.0f;
float depth_max_bounds = 0.0f;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_front;
PipelineStencilStaticState stencil_back;
uint32_t color_count = 1;
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool cull_front = false;
bool cull_back = false;
bool face = false;
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t color_comb_fcn[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t color_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t alpha_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t alpha_comb_fcn[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t alpha_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool blend_bypass[RENDER_COLOR_ATTACHMENTS_MAX] = {};
float blend_color_red = 0.0f;
float blend_color_green = 0.0f;
float blend_color_blue = 0.0f;
float blend_color_alpha = 0.0f;
bool operator==(const PipelineStaticParameters& other) const noexcept;
};
struct PipelineDynamicParameters {
bool stencil_test_enable = false;
float viewport_scale[3] = {};
float viewport_offset[3] = {};
int scissor_ltrb[4] = {0};
float line_width = 1.0f;
uint32_t color_write_count = 1;
bool color_write_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {true, true};
PipelineStencilDynamicState stencil_front;
PipelineStencilDynamicState stencil_back;
};
#pragma pack(pop)
static_assert(std::is_trivially_copyable_v<PipelineStencilStaticState>);
static_assert(std::is_standard_layout_v<PipelineStencilStaticState>);
static_assert(alignof(PipelineStencilStaticState) == 1);
static_assert(sizeof(PipelineStencilStaticState) == sizeof(VkStencilOp) * 3 + sizeof(VkCompareOp));
static_assert(std::is_trivially_copyable_v<PipelineStencilDynamicState>);
static_assert(std::is_standard_layout_v<PipelineStencilDynamicState>);
static_assert(alignof(PipelineStencilDynamicState) == 1);
static_assert(sizeof(PipelineStencilDynamicState) == sizeof(uint32_t) * 3);
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) ==
sizeof(float[3]) + sizeof(float[3]) + sizeof(bool) + sizeof(int[4]) +
sizeof(VkPrimitiveTopology) + sizeof(bool) * 3 + sizeof(VkCompareOp) +
sizeof(bool) + sizeof(float) * 2 + sizeof(bool) +
sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
sizeof(uint32_t[RENDER_COLOR_ATTACHMENTS_MAX]) + sizeof(bool) * 3 +
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
static_assert(std::is_trivially_copyable_v<PipelineDynamicParameters>);
static_assert(std::is_standard_layout_v<PipelineDynamicParameters>);
static_assert(alignof(PipelineDynamicParameters) == 1);
static_assert(sizeof(PipelineDynamicParameters) ==
sizeof(bool) + sizeof(float[3]) + sizeof(float[3]) + sizeof(int[4]) +
sizeof(float) + sizeof(uint32_t) + sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) +
sizeof(PipelineStencilDynamicState) * 2);
struct RenderDepthInfo {
VkFormat format = VK_FORMAT_UNDEFINED;
uint32_t width = 0;
uint32_t height = 0;
bool htile = false;
uint64_t depth_buffer_size = 0;
uint64_t depth_buffer_vaddr = 0;
uint64_t depth_tile_swizzle = 0;
uint64_t stencil_buffer_size = 0;
uint64_t stencil_buffer_vaddr = 0;
uint64_t stencil_tile_swizzle = 0;
uint64_t htile_buffer_size = 0;
uint64_t htile_buffer_vaddr = 0;
uint64_t htile_tile_swizzle = 0;
bool depth_clear_enable = false;
bool depth_load_clear_enable = false;
bool depth_meta_clear_enable = false;
float depth_clear_value = 0.0f;
bool depth_test_enable = false;
bool depth_write_enable = false;
VkCompareOp depth_compare_op = VK_COMPARE_OP_NEVER;
bool depth_bounds_test_enable = false;
float depth_min_bounds = 0.0f;
float depth_max_bounds = 0.0f;
bool stencil_clear_enable = false;
uint8_t stencil_clear_value = 0;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_static_front;
PipelineStencilStaticState stencil_static_back;
PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back;
DepthStencilVulkanImage* vulkan_buffer = nullptr;
uint64_t vaddr[3] = {};
uint64_t size[3] = {};
int vaddr_num = 0;
};
inline bool depth_attachment_read_only(const RenderDepthInfo* depth) {
EXIT_IF(depth == nullptr);
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;
}
inline VkImageLayout depth_attachment_layout(const RenderDepthInfo* depth) {
return depth_attachment_read_only(depth) ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
}
enum class RenderColorType {
NoColorOutput,
DisplayBuffer,
RenderTexture,
};
struct RenderColorInfo {
RenderColorType type = RenderColorType::NoColorOutput;
VulkanImage* vulkan_buffer = nullptr;
VkImageView vulkan_view = nullptr;
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent = {};
uint32_t base_mip_level = 0;
uint64_t base_addr = 0;
uint64_t buffer_size = 0;
uint32_t target_slot = 0;
bool color_clear_enable = false;
VkClearColorValue color_clear_value {};
};
struct ScissorRect {
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
};
enum class CommandBufferDebugOp : uint32_t {
DispatchDirect,
DrawIndex,
DrawIndexAuto,
EopWrite,
EopInterrupt,
EopWriteBack,
EopFlip,
EopWriteBackFlip,
EopOnlyFlip,
Unknown,
};
uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot);
uint32_t render_target_first_bound_slot(const HW::Context& hw);
bool graphics_debug_dump_enabled();
void uc_print(const char* func, const HW::UserConfig& uc);
void uc_check(const HW::UserConfig& uc);
void sh_print(const char* func, const HW::Shader& uc);
void sh_check(const HW::Shader& uc);
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
bool RenderIsColorTileModeLinear(uint32_t tile_mode);
void hw_print(const HW::Context& hw);
void hw_check(const HW::Context& hw);
void LogDrawPhase(const char* draw_name, const char* phase);
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
VkExtent2D extent);
int32_t ResolveVertexOffset(uint32_t index_offset, const ShaderVertexInputInfo& vs_input_info);
void GraphicsRenderTextureBarrier(VkCommandBuffer vk_buffer, VulkanImage* image);
void GraphicsRenderColorImageBarrier(VkCommandBuffer vk_buffer, VulkanImage* image,
VkImageLayout new_layout);
void GraphicsRenderDepthStencilImageBarrier(VkCommandBuffer vk_buffer, VulkanImage* image,
VkImageLayout new_layout);
void GraphicsRenderDepthStencilBarrier(VkCommandBuffer vk_buffer, VulkanImage* image);
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
RenderDepthInfo* r);
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
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);
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERSTATE_H_
@@ -0,0 +1,226 @@
#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/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/renderer/renderState.h"
#include "graphics/host_gpu/utils.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <limits>
#include <vulkan/vk_enum_string_helper.h>
namespace Libs::Graphics {
void GraphicsRenderMemoryBarrier(CommandBuffer* buffer) {
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
Common::LockGuard lock(g_render_ctx->GetMutex());
auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()];
VkMemoryBarrier mem_barrier {};
mem_barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
mem_barrier.pNext = nullptr;
mem_barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
mem_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
vkCmdPipelineBarrier(vk_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &mem_barrier, 0, nullptr, 0,
nullptr);
}
void GraphicsRenderTextureBarrier(VkCommandBuffer vk_buffer, VulkanImage* image) {
EXIT_IF(image == nullptr);
VkImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_MEMORY_READ_BIT;
image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
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_IMAGE_ASPECT_COLOR_BIT;
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;
vkCmdPipelineBarrier(
vk_buffer,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
image->layout = image_memory_barrier.newLayout;
}
void GraphicsRenderColorImageBarrier(VkCommandBuffer vk_buffer, VulkanImage* image,
VkImageLayout new_layout) {
EXIT_IF(image == nullptr);
if (image->layout != new_layout) {
if (image->type == VulkanImageType::VideoOut &&
image->layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
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",
reinterpret_cast<void*>(image->image), static_cast<int>(image->type),
string_VkImageLayout(image->layout), string_VkImageLayout(new_layout));
}
}
VkImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT;
image_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT;
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_IMAGE_ASPECT_COLOR_BIT;
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<VkPipelineStageFlags>(VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT);
vkCmdPipelineBarrier(vk_buffer, stages, stages, 0, 0, nullptr, 0, nullptr, 1,
&image_memory_barrier);
image->layout = image_memory_barrier.newLayout;
}
}
VkImageAspectFlags DepthStencilAspectMask(VkFormat format);
void GraphicsRenderDepthStencilImageBarrier(VkCommandBuffer vk_buffer, VulkanImage* image,
VkImageLayout new_layout) {
EXIT_IF(image == nullptr);
EXIT_IF(image->type != VulkanImageType::DepthStencil);
if (new_layout == VK_IMAGE_LAYOUT_UNDEFINED || new_layout == VK_IMAGE_LAYOUT_PREINITIALIZED) {
EXIT("invalid destination depth/stencil image layout: %s\n",
string_VkImageLayout(new_layout));
}
if (image->layout == new_layout) {
return;
}
VkImageMemoryBarrier barrier {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
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 = {DepthStencilAspectMask(image->format), 0, VK_REMAINING_MIP_LEVELS,
0, image->layers};
const auto stages = static_cast<VkPipelineStageFlags>(VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT);
vkCmdPipelineBarrier(vk_buffer, stages, stages, 0, 0, nullptr, 0, nullptr, 1, &barrier);
image->layout = new_layout;
}
void GraphicsRenderDepthStencilBarrier(VkCommandBuffer vk_buffer, VulkanImage* image) {
EXIT_IF(image == nullptr);
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",
string_VkFormat(depth->format), depth->extent.width, depth->extent.height);
}
depth->compressed = false;
}
if (image->layout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL) {
VkImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
image_memory_barrier.oldLayout = image->layout;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
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 = DepthStencilAspectMask(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;
vkCmdPipelineBarrier(
vk_buffer,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 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 == nullptr);
EXIT_IF(buffer->IsInvalid());
Common::LockGuard lock(g_render_ctx->GetMutex());
auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()];
auto* native = g_render_ctx->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 == nullptr);
EXIT_IF(buffer->IsInvalid());
Common::LockGuard lock(g_render_ctx->GetMutex());
auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()];
auto* native = g_render_ctx->GetTextureCache()->FindDepthTargetByRange(vaddr, size);
if (native == nullptr) {
EXIT("depth-target barrier range has no cached image\n");
}
GraphicsRenderDepthStencilBarrier(vk_buffer, native);
}
} // namespace Libs::Graphics
@@ -0,0 +1,28 @@
#include "common/assert.h"
#include "graphics/host_gpu/renderer/renderState.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shader.h"
namespace Libs::Graphics {
int32_t ResolveVertexOffset(uint32_t index_offset, const ShaderVertexInputInfo& vs_input_info) {
if (index_offset != 0 || !vs_input_info.fetch_embedded) {
return static_cast<int32_t>(index_offset);
}
EXIT_IF(!vs_input_info.stage);
const auto& program = *vs_input_info.stage.program;
const auto& resources = *vs_input_info.stage.resources;
if (program.info.vertex_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
const auto index =
static_cast<uint32_t>(program.info.vertex_offset_sgpr) - program.user_data_base;
if (index < resources.user_data.size()) {
return static_cast<int32_t>(resources.user_data[index]);
}
}
return 0;
}
} // namespace Libs::Graphics
@@ -0,0 +1,93 @@
#include "graphics/host_gpu/renderer/resourceMutex.h"
#include "common/assert.h"
namespace Libs::Graphics {
ResourceMutex::FaultScope::FaultScope(ResourceMutex& mutex): m_mutex(mutex) {
m_resource_preowned = m_mutex.BeginFault();
}
ResourceMutex::FaultScope::~FaultScope() {
m_mutex.EndFault(m_resource_preowned);
}
ResourceMutex::~ResourceMutex() {
std::lock_guard state(m_state);
if (m_resource_owner != std::thread::id {} || m_fault_owner != std::thread::id {}) {
EXIT("ResourceMutex destroyed with an active owner or fault transaction\n");
}
}
void ResourceMutex::lock() {
const auto current = std::this_thread::get_id();
{
std::lock_guard state(m_state);
if (m_resource_owner == current) {
EXIT("recursive resource transaction\n");
}
if (m_fault_owner == current) {
EXIT("resource transaction re-entered from a page-fault callback\n");
}
}
m_resource.lock();
std::lock_guard state(m_state);
if (m_resource_owner != std::thread::id {}) {
EXIT("resource mutex acquired with a stale owner\n");
}
m_resource_owner = current;
}
void ResourceMutex::unlock() {
const auto current = std::this_thread::get_id();
{
std::lock_guard state(m_state);
if (m_resource_owner != current) {
EXIT("resource transaction released without ownership\n");
}
m_resource_owner = {};
}
m_resource.unlock();
}
bool ResourceMutex::IsOwnedByCurrentThread() {
std::lock_guard state(m_state);
return m_resource_owner == std::this_thread::get_id();
}
bool ResourceMutex::BeginFault() {
const auto current = std::this_thread::get_id();
{
std::lock_guard state(m_state);
if (m_fault_owner == current) {
EXIT("nested resource page-fault transaction\n");
}
if (m_resource_owner == current) {
m_fault_owner = current;
return true;
}
}
lock();
std::lock_guard state(m_state);
if (m_resource_owner != current || m_fault_owner != std::thread::id {}) {
EXIT("resource fault transaction acquired inconsistent ownership\n");
}
m_fault_owner = current;
return false;
}
void ResourceMutex::EndFault(bool resource_preowned) {
const auto current = std::this_thread::get_id();
{
std::lock_guard state(m_state);
if (m_fault_owner != current || m_resource_owner != current) {
EXIT("resource page-fault transaction ended with inconsistent ownership\n");
}
m_fault_owner = {};
}
if (!resource_preowned) {
unlock();
}
}
} // namespace Libs::Graphics
@@ -0,0 +1,48 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RESOURCEMUTEX_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RESOURCEMUTEX_H_
#include "common/common.h"
#include <mutex>
#include <thread>
namespace Libs::Graphics {
// Owner-tracked shared buffer/image transaction. External faults pause GPU submissions before
// taking this mutex; synchronous command-processor accesses reuse their already-owned transaction.
class ResourceMutex final {
public:
class FaultScope final {
public:
explicit FaultScope(ResourceMutex& mutex);
~FaultScope();
KYTY_CLASS_NO_COPY(FaultScope);
private:
ResourceMutex& m_mutex;
bool m_resource_preowned = false;
};
ResourceMutex() = default;
~ResourceMutex();
KYTY_CLASS_NO_COPY(ResourceMutex);
void lock();
void unlock();
[[nodiscard]] bool IsOwnedByCurrentThread();
private:
friend class FaultScope;
[[nodiscard]] bool BeginFault();
void EndFault(bool resource_preowned);
std::mutex m_resource;
std::mutex m_state;
std::thread::id m_resource_owner;
std::thread::id m_fault_owner;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RESOURCEMUTEX_H_
@@ -0,0 +1,146 @@
#include "graphics/host_gpu/renderer/samplerCache.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/renderer/renderContext.h"
namespace Libs::Graphics {
VkSampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
Common::LockGuard lock(m_mutex);
const SamplerKey key {r.fields[0], r.fields[1], r.fields[2], r.fields[3]};
if (auto iter = m_samplers.find(key); iter != m_samplers.end()) {
return iter->second;
}
float aniso_ratio = 1.0f;
const auto mag_filter = r.XyMagFilter();
const auto min_filter = r.XyMinFilter();
auto is_aniso_filter = [](uint8_t filter) {
switch (static_cast<Prospero::SamplerFilter>(filter)) {
case Prospero::SamplerFilter::kAnisoPoint:
case Prospero::SamplerFilter::kAnisoLinear: return true;
case Prospero::SamplerFilter::kPoint:
case Prospero::SamplerFilter::kBilinear: return false;
default: EXIT("unknown sampler filter: %u\n", filter);
}
return false;
};
auto to_vk_filter = [](uint8_t filter) {
switch (static_cast<Prospero::SamplerFilter>(filter)) {
case Prospero::SamplerFilter::kPoint:
case Prospero::SamplerFilter::kAnisoPoint: return VK_FILTER_NEAREST;
case Prospero::SamplerFilter::kBilinear:
case Prospero::SamplerFilter::kAnisoLinear: return VK_FILTER_LINEAR;
default: EXIT("unknown sampler filter: %u\n", filter);
}
return VK_FILTER_NEAREST;
};
const bool aniso = is_aniso_filter(mag_filter) || is_aniso_filter(min_filter);
if (aniso) {
switch (static_cast<Prospero::SamplerAnisoRatio>(r.MaxAnisoRatio())) {
case Prospero::SamplerAnisoRatio::kOne: aniso_ratio = 1.0f; break;
case Prospero::SamplerAnisoRatio::kTwo: aniso_ratio = 2.0f; break;
case Prospero::SamplerAnisoRatio::kFour: aniso_ratio = 4.0f; break;
case Prospero::SamplerAnisoRatio::kEight: aniso_ratio = 8.0f; break;
case Prospero::SamplerAnisoRatio::kSixteen: aniso_ratio = 16.0f; break;
default: EXIT("unknown ratio: %d\n", static_cast<int>(r.MaxAnisoRatio()));
}
}
const auto mip_filter = r.MipFilter();
float min_lod = 0.0f;
float max_lod = 0.0f;
if (static_cast<Prospero::SamplerMipFilter>(mip_filter) != Prospero::SamplerMipFilter::kNone) {
min_lod = static_cast<float>(r.MinLod()) / 256.0f;
max_lod = static_cast<float>(r.MaxLod()) / 256.0f;
}
VkSamplerCreateInfo sampler_info {};
auto to_vk_address_mode = [](uint8_t clamp) {
switch (static_cast<Prospero::SamplerClampMode>(clamp)) {
case Prospero::SamplerClampMode::kWrap: return VK_SAMPLER_ADDRESS_MODE_REPEAT;
case Prospero::SamplerClampMode::kMirror: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case Prospero::SamplerClampMode::kClampLastTexel: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case Prospero::SamplerClampMode::kMirrorOnceLastTexel:
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
case Prospero::SamplerClampMode::kClampHalfBorder:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
case Prospero::SamplerClampMode::kMirrorOnceHalfBorder:
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
case Prospero::SamplerClampMode::kClampBorder: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
case Prospero::SamplerClampMode::kMirrorOnceBorder:
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
default: EXIT("unknown clamp: %u\n", clamp);
}
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
};
VkBorderColor border = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
switch (static_cast<Prospero::SamplerBorderColor>(r.BorderColorType())) {
case Prospero::SamplerBorderColor::kTransBlack:
border = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
break;
case Prospero::SamplerBorderColor::kOpaqueBlack: border = VK_BORDER_COLOR_INT_OPAQUE_BLACK; break;
case Prospero::SamplerBorderColor::kOpaqueWhite: border = VK_BORDER_COLOR_INT_OPAQUE_WHITE; break;
case Prospero::SamplerBorderColor::kFromTable:
LOGF(
"temporary: approximating table border color as transparent black, index = %" PRIu16
"\n",
r.BorderColorPtr());
border = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
break;
default: EXIT("unknown border color: %d", static_cast<int>(r.BorderColorType()));
}
sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
sampler_info.pNext = nullptr;
sampler_info.flags = 0;
sampler_info.magFilter = to_vk_filter(mag_filter);
sampler_info.minFilter = to_vk_filter(min_filter);
sampler_info.mipmapMode =
(static_cast<Prospero::SamplerMipFilter>(mip_filter) == Prospero::SamplerMipFilter::kLinear
? VK_SAMPLER_MIPMAP_MODE_LINEAR
: VK_SAMPLER_MIPMAP_MODE_NEAREST);
sampler_info.addressModeU = to_vk_address_mode(r.ClampX());
sampler_info.addressModeV = to_vk_address_mode(r.ClampY());
sampler_info.addressModeW = to_vk_address_mode(r.ClampZ());
sampler_info.mipLodBias =
static_cast<float>(static_cast<int16_t>((r.LodBias() ^ 0x2000u) - 0x2000u)) / 256.0f;
sampler_info.anisotropyEnable = (aniso ? VK_TRUE : VK_FALSE);
sampler_info.maxAnisotropy = aniso_ratio;
sampler_info.compareEnable = (r.DepthCompareFunc() != 0 ? VK_TRUE : VK_FALSE);
sampler_info.compareOp = static_cast<VkCompareOp>(r.DepthCompareFunc());
sampler_info.minLod = min_lod;
sampler_info.maxLod = max_lod;
sampler_info.borderColor = border;
sampler_info.unnormalizedCoordinates = (r.ForceUnormCoords() ? VK_TRUE : VK_FALSE);
if (r.ForceUnormCoords()) {
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler_info.minLod = 0.0f;
sampler_info.maxLod = 0.0f;
sampler_info.anisotropyEnable = VK_FALSE;
sampler_info.maxAnisotropy = 1.0f;
sampler_info.compareEnable = VK_FALSE;
sampler_info.mipLodBias = 0.0f;
}
VkSampler vk_sampler = nullptr;
vkCreateSampler(g_render_ctx->GetGraphicCtx()->device, &sampler_info, nullptr, &vk_sampler);
EXIT_NOT_IMPLEMENTED(vk_sampler == nullptr);
m_samplers.emplace(key, vk_sampler);
return vk_sampler;
}
} // namespace Libs::Graphics
@@ -0,0 +1,46 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SAMPLERCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SAMPLERCACHE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/shader/shaderBindings.h"
#include <array>
#include <cstddef>
#include <unordered_map>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
class SamplerCache {
public:
SamplerCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
virtual ~SamplerCache() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(SamplerCache);
VkSampler GetSampler(const ShaderSamplerResource& r);
private:
using SamplerKey = std::array<uint32_t, 4>;
struct SamplerKeyHash {
std::size_t operator()(const SamplerKey& key) const {
std::size_t hash = 0;
for (auto value: key) {
hash ^= static_cast<std::size_t>(value) +
static_cast<std::size_t>(0x9e3779b97f4a7c15ull) + (hash << 6u) +
(hash >> 2u);
}
return hash;
}
};
Common::Mutex m_mutex;
std::unordered_map<SamplerKey, VkSampler, SamplerKeyHash> m_samplers;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SAMPLERCACHE_H_
@@ -0,0 +1,127 @@
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "common/assert.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/shader/shaderBindings.h"
#include "graphics/shader/shader.h"
#include <cstring>
namespace Libs::Graphics {
VkPipelineStageFlags ShaderPipelineStages(VkShaderStageFlags stages) {
VkPipelineStageFlags result = 0;
if ((stages & VK_SHADER_STAGE_VERTEX_BIT) != 0) {
result |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
}
if ((stages & VK_SHADER_STAGE_FRAGMENT_BIT) != 0) {
result |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
}
if ((stages & VK_SHADER_STAGE_COMPUTE_BIT) != 0) {
result |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
}
EXIT_IF(result == 0);
return result;
}
VkMemoryBarrier MakeShaderWriteDependency() {
VkMemoryBarrier barrier {};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
return barrier;
}
VkImageMemoryBarrier MakeStorageImageDependency(const VulkanImage& image, bool read, bool written) {
EXIT_IF(image.image == nullptr || image.type == VulkanImageType::DepthStencil);
VkImageMemoryBarrier barrier {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
barrier.dstAccessMask =
(read ? VK_ACCESS_SHADER_READ_BIT : 0) | (written ? VK_ACCESS_SHADER_WRITE_BIT : 0);
barrier.oldLayout = image.layout;
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image.image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = image.layers;
EXIT_IF(barrier.dstAccessMask == 0 || barrier.subresourceRange.layerCount == 0);
return barrier;
}
VkBufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer) {
EXIT_IF(buffer.buffer == nullptr);
VkBufferMemoryBarrier barrier {};
barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
barrier.srcAccessMask =
VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = buffer.buffer;
barrier.offset = 0;
barrier.size = VK_WHOLE_SIZE;
return barrier;
}
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources) {
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
std::vector<ShaderBufferWriteRange> writes;
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
if (!program.info.buffers[i].written) {
continue;
}
const auto& value = resources.buffers[i];
EXIT_IF(value.dword_count < 4);
ShaderBufferResource descriptor;
std::memcpy(descriptor.fields, value.dwords.data(), sizeof(descriptor.fields));
const auto address = descriptor.Base48();
const auto records = static_cast<uint64_t>(descriptor.NumRecords());
const auto stride = static_cast<uint64_t>(descriptor.Stride());
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("shader resource barrier buffer footprint overflow\n");
}
const auto size = stride == 0 ? records : stride * records;
if (address != 0 && size != 0) {
writes.push_back({address, size});
}
}
return writes;
}
bool MarkShaderAddressWrites(const std::vector<ShaderAddressWriteRange>& writes) {
if (!writes.empty()) {
EXIT("shader address writes are unsupported\n");
}
return false;
}
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
EXIT_IF(!runtime);
return !CollectShaderBufferWrites(*runtime.program, *runtime.resources).empty();
}
void ShaderWriteBarrier(VkCommandBuffer vk_buffer, VkPipelineStageFlags source_stages) {
EXIT_IF(vk_buffer == nullptr || source_stages == 0);
const auto barrier = MakeShaderWriteDependency();
vkCmdPipelineBarrier(
vk_buffer, source_stages,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 1, &barrier, 0, nullptr, 0, nullptr);
}
} // namespace Libs::Graphics
@@ -0,0 +1,34 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include <vector>
namespace Libs::Graphics {
struct ShaderAddressWriteRange;
struct ShaderStageRuntime;
struct ShaderBufferWriteRange {
uint64_t address = 0;
uint64_t size = 0;
bool operator==(const ShaderBufferWriteRange& other) const = default;
};
VkPipelineStageFlags ShaderPipelineStages(VkShaderStageFlags stages);
VkMemoryBarrier MakeShaderWriteDependency();
VkImageMemoryBarrier MakeStorageImageDependency(const VulkanImage& image, bool read, bool written);
VkBufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer);
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources);
bool MarkShaderAddressWrites(const std::vector<ShaderAddressWriteRange>& writes);
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime);
void ShaderWriteBarrier(VkCommandBuffer vk_buffer, VkPipelineStageFlags source_stages);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
@@ -0,0 +1,50 @@
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/shader/recompiler/SpirvEmitter.h"
namespace Libs::Graphics {
ShaderLaneMaskMode SelectGraphicsLaneMaskMode(const GraphicContext& context,
uint32_t guest_wave_size) {
return context.subgroup_size == 32u && guest_wave_size == 64u
? ShaderLaneMaskMode::PerInvocation
: ShaderLaneMaskMode::NativeWave;
}
ShaderSubgroupConfiguration ConfigureShaderSubgroup(const GraphicContext& context,
VkShaderStageFlagBits stage,
const ShaderRecompiler::IR::Program& program) {
const auto guest_wave_size = program.wave_size;
if (guest_wave_size != 32u && guest_wave_size != 64u) {
return {};
}
if (stage != VK_SHADER_STAGE_COMPUTE_BIT) {
const auto expected = SelectGraphicsLaneMaskMode(context, guest_wave_size);
if (program.lane_mask_mode != expected || (context.subgroup_size != guest_wave_size &&
expected != ShaderLaneMaskMode::PerInvocation)) {
return {};
}
return {expected == ShaderLaneMaskMode::NativeWave
? ShaderSubgroupMode::Natural
: ShaderSubgroupMode::PerInvocationGraphics,
0};
}
if (program.lane_mask_mode != ShaderLaneMaskMode::NativeWave) {
return {};
}
if (context.subgroup_size == guest_wave_size) {
return {ShaderSubgroupMode::Natural, 0};
}
if (context.subgroup_size_control_enabled &&
(context.required_subgroup_size_stages & stage) != 0 &&
guest_wave_size >= context.min_subgroup_size &&
guest_wave_size <= context.max_subgroup_size) {
return {ShaderSubgroupMode::Controlled, guest_wave_size};
}
if (!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(program)) {
return {ShaderSubgroupMode::FlattenedMasks, 0};
}
return {};
}
} // namespace Libs::Graphics
@@ -0,0 +1,31 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ShaderIR.h"
namespace Libs::Graphics {
enum class ShaderSubgroupMode {
Natural,
Controlled,
PerInvocationGraphics,
FlattenedMasks,
Unsupported
};
struct ShaderSubgroupConfiguration {
ShaderSubgroupMode mode = ShaderSubgroupMode::Unsupported;
uint32_t required_size = 0;
};
ShaderLaneMaskMode SelectGraphicsLaneMaskMode(const GraphicContext& context,
uint32_t guest_wave_size);
ShaderSubgroupConfiguration ConfigureShaderSubgroup(const GraphicContext& context,
VkShaderStageFlagBits stage,
const ShaderRecompiler::IR::Program& program);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,99 @@
#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/utils.h"
#include "graphics/host_gpu/vma.h"
#include <atomic>
#include <cstring>
namespace Libs::Graphics {
namespace {
std::atomic<uint64_t> g_stream_buffer_count {0};
} // namespace
HostStreamBuffer::HostStreamBuffer() = default;
HostStreamBuffer::~HostStreamBuffer() {
Release();
}
void HostStreamBuffer::EnsureBuffer(GraphicContext* ctx) {
if (m_buffer != nullptr) {
if (m_ctx != ctx || m_mapped == nullptr) {
EXIT("host stream buffer changed graphics context\n");
}
return;
}
KYTY_PROFILER_BLOCK("HostStreamBuffer::create");
m_ctx = ctx;
m_buffer = std::make_unique<VulkanBuffer>();
m_buffer->usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT;
m_buffer->memory.property = static_cast<uint32_t>(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
VulkanCreateBuffer(ctx, CAPACITY, m_buffer.get());
VulkanMapMemory(ctx, &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);
}
bool HostStreamBuffer::Copy(GraphicContext* ctx, const void* src, uint64_t size,
uint64_t alignment, VulkanBuffer** out_buffer, uint64_t* out_offset,
uint64_t* out_range) {
KYTY_PROFILER_FUNCTION();
if (ctx == nullptr || src == nullptr || out_buffer == nullptr || out_offset == nullptr ||
out_range == nullptr) {
EXIT("invalid host stream upload\n");
}
std::lock_guard lock(m_mutex);
if (size == 0) {
return false;
}
if (alignment == 0) {
alignment = 1;
}
if ((alignment & (alignment - 1)) != 0 || m_offset > UINT64_MAX - (alignment - 1)) {
return false;
}
const auto offset = (m_offset + alignment - 1) & ~(alignment - 1);
if (offset > CAPACITY || size > CAPACITY - offset) {
return false;
}
EnsureBuffer(ctx);
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;
return true;
}
void HostStreamBuffer::Reset() noexcept {
std::lock_guard lock(m_mutex);
m_offset = 0;
}
void HostStreamBuffer::Release() noexcept {
std::lock_guard lock(m_mutex);
if (m_buffer != nullptr) {
if (m_ctx == nullptr || m_mapped == nullptr) {
EXIT("invalid host stream buffer release state\n");
}
VulkanUnmapMemory(m_ctx, &m_buffer->memory);
VulkanDeleteBuffer(m_ctx, m_buffer.get());
m_buffer.reset();
}
m_ctx = nullptr;
m_mapped = nullptr;
m_offset = 0;
}
} // namespace Libs::Graphics
@@ -0,0 +1,48 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_STREAMBUFFER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_STREAMBUFFER_H_
#include "common/abi.h"
#include "common/common.h"
#include <cstdint>
#include <memory>
#include <mutex>
namespace Libs::Graphics {
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:
HostStreamBuffer();
~HostStreamBuffer();
KYTY_CLASS_NO_COPY(HostStreamBuffer);
[[nodiscard]] bool Copy(GraphicContext* ctx, 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;
private:
void EnsureBuffer(GraphicContext* ctx);
// 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_ctx = nullptr;
std::unique_ptr<VulkanBuffer> m_buffer;
void* m_mapped = nullptr;
uint64_t m_offset = 0;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_STREAMBUFFER_H_
+483
View File
@@ -0,0 +1,483 @@
#include "common/assert.h"
#include "common/common.h"
#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/host_gpu/renderer/renderState.h"
#include "graphics/presentation/displayBuffer.h"
#include "kernel/eventQueue.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include <cstring>
namespace Libs::Graphics {
constexpr int GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT = 0x00;
constexpr int GRAPHICS_EVENT_EOP = 0x40;
static void SubmitLabel32(CommandBuffer* buffer, uint32_t* dst, uint32_t value,
LabelCallback callback_1 = nullptr, LabelCallback callback_2 = nullptr,
const uint64_t* args = nullptr) {
auto* label =
LabelCreate32(g_render_ctx->GetGraphicCtx(), dst, value, callback_1, callback_2, args);
LabelSet(buffer, label);
LabelDelete(label);
}
static void SubmitLabel64(CommandBuffer* buffer, uint64_t* dst, uint64_t value,
LabelCallback callback_1 = nullptr, LabelCallback callback_2 = nullptr,
const uint64_t* args = nullptr) {
auto* label =
LabelCreate64(g_render_ctx->GetGraphicCtx(), dst, value, callback_1, callback_2, args);
LabelSet(buffer, label);
LabelDelete(label);
}
template <typename T>
static void PublishImmediateFence(T* dst, T value) {
static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t));
EXIT_IF(dst == nullptr);
// EOP/EOS fence payloads are constant and published while parsing the packet. Kyty performs the
// direct mapped write under CommandProcessorGuestAccessScope: a protected access is first resolved by
// BufferCache inline, then this instruction retries so the EOP payload wins over prior GPU bytes.
std::memcpy(dst, &value, sizeof(value));
}
static void SubmitDisplayBufferFlip(const uint64_t* args) {
if (g_render_ctx == nullptr || args == nullptr) {
EXIT("GPU flip submission has invalid state, render_ctx=%p args=%p\n",
static_cast<const void*>(g_render_ctx), static_cast<const void*>(args));
}
const auto handle = static_cast<int>(args[0]);
const auto index = static_cast<int>(args[1]);
const auto flip_mode = static_cast<int>(args[2]);
const auto flip_arg = static_cast<int64_t>(args[3]);
const auto result =
Presentation::DisplayBufferSubmitFlipFromGpu(handle, index, flip_mode, flip_arg);
if (result != 0) {
EXIT("GPU flip submission failed, result=%d handle=%d index=%d mode=%d arg=%" PRId64 "\n",
result, handle, index, flip_mode, flip_arg);
}
}
static bool CompleteDisplayBufferFlip(const uint64_t* args) {
if (g_render_ctx == nullptr || args == nullptr) {
EXIT("GPU flip completion has invalid state, render_ctx=%p args=%p\n",
static_cast<const void*>(g_render_ctx), static_cast<const void*>(args));
}
Presentation::DisplayBufferCompleteFlipFromGpu(static_cast<int>(args[0]),
static_cast<int>(args[1]));
return true;
}
void GraphicsRenderTriggerAgcUserInterrupt() {
auto tsc = LibKernel::KernelReadTsc();
auto result = LibKernel::EventQueue::KernelTriggerUserEventForAll(AGC_USER_INTERRUPT_EVENT,
reinterpret_cast<void*>(tsc));
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
}
void GraphicsRenderTriggerEopEvent(uint32_t context_id) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->TriggerEopEvent(context_id);
}
void GraphicsRenderWriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWrite), submit_id, 4, value,
0, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
PublishImmediateFence(dst_gpu_addr, value);
}
void GraphicsRenderWriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t dw_offset,
uint32_t dw_num) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWrite), submit_id,
dw_offset, dw_num, 0, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
uint64_t args[LABEL_ARGS_MAX] = {static_cast<uint64_t>(dw_offset),
static_cast<uint64_t>(dw_num),
reinterpret_cast<uint64_t>(dst_gpu_addr), 0};
SubmitLabel32(
buffer, dst_gpu_addr, 0,
[](const uint64_t* args) {
auto dw_offset = static_cast<uint32_t>(args[0]);
auto dw_num = static_cast<uint32_t>(args[1]);
auto* dst_gpu_addr = reinterpret_cast<uint32_t*>(args[2]);
if (dst_gpu_addr == nullptr || dw_num == 0) {
EXIT("invalid asynchronous EOP GDS write\n");
}
const auto size = static_cast<uint64_t>(dw_num) * sizeof(uint32_t);
auto transaction = g_render_ctx->GetGpuResources()->LockAsynchronousGuestWrite(
reinterpret_cast<uint64_t>(dst_gpu_addr), size);
g_render_ctx->GetGdsBuffer()->Read(g_render_ctx->GetGraphicCtx(), dst_gpu_addr,
dw_offset, dw_num);
return false;
},
nullptr, args);
}
void GraphicsRenderWriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer* buffer,
uint64_t* dst_gpu_addr, uint64_t value) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWrite), submit_id, 8,
static_cast<uint32_t>(value), static_cast<uint32_t>(value >> 32u), 0,
reinterpret_cast<uint64_t>(dst_gpu_addr));
PublishImmediateFence(dst_gpu_addr, value);
}
void GraphicsRenderWriteAtEndOfPipeClockCounter(uint64_t submit_id, CommandBuffer* buffer,
uint64_t* dst_gpu_addr) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWrite), submit_id, 8, 0, 0,
0, reinterpret_cast<uint64_t>(dst_gpu_addr));
const auto value = LibKernel::KernelReadTsc();
PublishImmediateFence(dst_gpu_addr, value);
LOGF_COLOR(Log::Color::BrightGreen,
"EndOfPipe Signal!!! [0x%016" PRIx64 "] <- Clock: 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
}
void GraphicsRenderWriteAtEndOfPipeClockCounterWithWriteBack(uint64_t submit_id,
CommandBuffer* buffer,
uint64_t* dst_gpu_addr) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBack), submit_id, 8, 0,
0, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
const auto value = LibKernel::KernelReadTsc();
PublishImmediateFence(dst_gpu_addr, value);
LOGF_COLOR(Log::Color::BrightGreen,
"EndOfPipe Signal!!! [0x%016" PRIx64 "] <- Clock: 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
}
void GraphicsRenderWriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer* buffer,
uint64_t* dst_gpu_addr, uint64_t value) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBack), submit_id, 8,
static_cast<uint32_t>(value), static_cast<uint32_t>(value >> 32u), 0,
reinterpret_cast<uint64_t>(dst_gpu_addr));
PublishImmediateFence(dst_gpu_addr, value);
}
void GraphicsRenderWriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBack), submit_id, 4,
value, 0, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
PublishImmediateFence(dst_gpu_addr, value);
}
void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id,
CommandBuffer* buffer,
uint64_t* dst_gpu_addr, uint64_t value,
uint32_t context_id) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBack), submit_id, 8,
context_id, static_cast<uint32_t>(value),
static_cast<uint32_t>(value >> 32u),
reinterpret_cast<uint64_t>(dst_gpu_addr));
PublishImmediateFence(dst_gpu_addr, value);
uint64_t args[LABEL_ARGS_MAX] = {context_id != 0 ? context_id : value};
SubmitLabel64(
buffer, nullptr, 0, nullptr,
[](const uint64_t* args) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->TriggerEopEvent(static_cast<uint32_t>(args[0]));
return true;
},
args);
}
void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBack32(uint64_t submit_id,
CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value,
uint32_t context_id) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBack), submit_id, 4,
context_id, value, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
PublishImmediateFence(dst_gpu_addr, value);
uint64_t args[LABEL_ARGS_MAX] = {context_id != 0 ? context_id : value};
SubmitLabel32(
buffer, nullptr, 0, nullptr,
[](const uint64_t* args) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->TriggerEopEvent(static_cast<uint32_t>(args[0]));
return true;
},
args);
}
void GraphicsRenderWriteAtEndOfPipeWithInterrupt64(uint64_t submit_id, CommandBuffer* buffer,
uint64_t* dst_gpu_addr, uint64_t value,
uint32_t context_id) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopInterrupt), submit_id, 8,
context_id, static_cast<uint32_t>(value),
static_cast<uint32_t>(value >> 32u),
reinterpret_cast<uint64_t>(dst_gpu_addr));
PublishImmediateFence(dst_gpu_addr, value);
uint64_t args[LABEL_ARGS_MAX] = {context_id != 0 ? context_id : value};
SubmitLabel64(
buffer, nullptr, 0, nullptr,
[](const uint64_t* args) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->TriggerEopEvent(static_cast<uint32_t>(args[0]));
return true;
},
args);
}
void GraphicsRenderWriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value,
uint32_t context_id) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopInterrupt), submit_id, 4,
context_id, value, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
PublishImmediateFence(dst_gpu_addr, value);
uint64_t args[LABEL_ARGS_MAX] = {context_id != 0 ? context_id : value};
SubmitLabel32(
buffer, nullptr, 0, nullptr,
[](const uint64_t* args) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->TriggerEopEvent(static_cast<uint32_t>(args[0]));
return true;
},
args);
}
void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBackFlip32(
uint64_t submit_id, CommandBuffer* buffer, uint32_t* dst_gpu_addr, uint32_t value, int handle,
int index, int flip_mode, int64_t flip_arg) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBackFlip), submit_id,
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), value, static_cast<uint64_t>(flip_arg));
PublishImmediateFence(dst_gpu_addr, value);
uint64_t args[LABEL_ARGS_MAX] = {static_cast<uint64_t>(handle), static_cast<uint64_t>(index),
static_cast<uint64_t>(flip_mode),
static_cast<uint64_t>(flip_arg)};
SubmitDisplayBufferFlip(args);
SubmitLabel32(
buffer, nullptr, 0, CompleteDisplayBufferFlip,
[](const uint64_t* /*args*/) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->TriggerEopEvent(0);
return true;
},
args);
}
void GraphicsRenderWriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer* buffer,
uint32_t* dst_gpu_addr, uint32_t value, int handle,
int index, int flip_mode, int64_t flip_arg) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(dst_gpu_addr == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopFlip), submit_id,
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), value, static_cast<uint64_t>(flip_arg));
PublishImmediateFence(dst_gpu_addr, value);
uint64_t args[LABEL_ARGS_MAX] = {static_cast<uint64_t>(handle), static_cast<uint64_t>(index),
static_cast<uint64_t>(flip_mode),
static_cast<uint64_t>(flip_arg)};
SubmitDisplayBufferFlip(args);
SubmitLabel32(buffer, nullptr, 0, CompleteDisplayBufferFlip, nullptr, args);
}
void GraphicsRenderWriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer* buffer, int handle,
int index, int flip_mode, int64_t flip_arg) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
buffer->SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopOnlyFlip), submit_id,
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), 0, static_cast<uint64_t>(flip_arg));
uint64_t args[LABEL_ARGS_MAX] = {static_cast<uint64_t>(handle), static_cast<uint64_t>(index),
static_cast<uint64_t>(flip_mode),
static_cast<uint64_t>(flip_arg)};
SubmitDisplayBufferFlip(args);
SubmitLabel32(buffer, nullptr, 0, CompleteDisplayBufferFlip, nullptr, args);
}
void GraphicsRenderTriggerEopEventAtEndOfPipe(CommandBuffer* buffer, uint32_t context_id) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
uint64_t args[LABEL_ARGS_MAX] = {static_cast<uint64_t>(context_id)};
SubmitLabel32(
buffer, nullptr, 0, nullptr,
[](const uint64_t* args) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->TriggerEopEvent(static_cast<uint32_t>(args[0]));
return true;
},
args);
}
static void EopEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
EXIT_IF(event == nullptr);
event->triggered = false;
event->event.fflags = 0;
event->event.data = 0;
}
static void EopEventDeleteFunc(LibKernel::EventQueue::KernelEqueue eq,
LibKernel::EventQueue::KernelEqueueEvent* event) {
EXIT_IF(event == nullptr);
EXIT_IF(g_render_ctx == nullptr);
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) {
g_render_ctx->DeleteEopEq(eq, static_cast<int>(event->event.ident));
}
}
static void EopEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
void* trigger_data) {
EXIT_IF(event == nullptr);
auto triggered_event = event->event;
triggered_event.fflags++;
triggered_event.data = reinterpret_cast<intptr_t>(trigger_data);
if (event->triggered) {
event->pending_events.push_back(triggered_event);
} else {
event->event = triggered_event;
event->triggered = true;
}
}
int GraphicsRenderAddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata) {
EXIT_IF(g_render_ctx == nullptr);
LibKernel::EventQueue::KernelEqueueEvent event;
event.triggered = false;
event.event.ident = static_cast<uintptr_t>(id);
event.event.filter = LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS;
event.event.udata = udata;
event.event.fflags = 0;
event.event.data = id;
event.filter.delete_event_func = EopEventDeleteFunc;
event.filter.reset_func = EopEventResetFunc;
event.filter.trigger_func = EopEventTriggerFunc;
event.filter.data = nullptr;
int result = LibKernel::EventQueue::KernelAddEvent(eq, event);
if (id == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT || id == GRAPHICS_EVENT_EOP) {
g_render_ctx->AddEopEq(eq, id);
}
return result;
}
int GraphicsRenderDeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id) {
EXIT_IF(g_render_ctx == nullptr);
int result = LibKernel::EventQueue::KernelDeleteEvent(
eq, static_cast<uintptr_t>(id), LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS);
return result;
}
void GraphicsRenderClearGds(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->GetGdsBuffer()->Clear(g_render_ctx->GetGraphicCtx(), dw_offset, dw_num,
clear_value);
}
void GraphicsRenderReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->GetGdsBuffer()->Read(g_render_ctx->GetGraphicCtx(), dst, dw_offset, dw_size);
}
void GraphicsRenderDeleteBuffers() {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->GetBufferCache()->DeleteAll(g_render_ctx->GetGraphicCtx());
}
} // namespace Libs::Graphics
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,143 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TEXTURECACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TEXTURECACHE_H_
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/host_gpu/renderer/tiler.h"
#include <array>
#include <map>
#include <memory>
#include <mutex>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
struct GraphicContext;
struct RenderTextureVulkanImage;
struct StorageTextureVulkanImage;
struct VideoOutVulkanImage;
struct VulkanImage;
struct VulkanMemory;
class BufferCache;
class CommandBuffer;
class ResourceMutex;
class TextureCache {
public:
TextureCache(PageManager& page_manager, BufferCache& buffer_cache, ResourceMutex& resource_mutex);
~TextureCache();
KYTY_CLASS_NO_COPY(TextureCache);
[[nodiscard]] VulkanImage* FindTexture(CommandBuffer* command, GraphicContext* ctx,
const ImageInfo& info, bool metadata_read);
[[nodiscard]] StorageTextureVulkanImage*
FindStorageTexture(CommandBuffer* command, GraphicContext* ctx, const ImageInfo& info);
[[nodiscard]] RenderTextureVulkanImage*
FindRenderTarget(CommandBuffer* command, GraphicContext* ctx, const RenderTargetInfo& info);
[[nodiscard]] DepthStencilVulkanImage*
FindDepthTarget(CommandBuffer* command, GraphicContext* ctx, const DepthTargetInfo& info);
[[nodiscard]] std::vector<VideoOutVulkanImage*>
RegisterVideoOutSurfaces(GraphicContext* ctx, const std::vector<VideoOutInfo>& infos);
void RefreshVideoOut(VideoOutVulkanImage* image, bool render_target = false);
void UnregisterVideoOutSurfaces(const std::vector<VideoOutVulkanImage*>& images);
[[nodiscard]] bool ClearColorImageFromBuffer(CommandBuffer* command, uint64_t vaddr,
uint64_t size, uint32_t packed_clear);
[[nodiscard]] bool ClearDepthImageFromBuffer(CommandBuffer* command, uint64_t vaddr,
uint64_t size, uint32_t packed_clear);
[[nodiscard]] bool ClearStencilImageFromBuffer(CommandBuffer* command, uint64_t vaddr,
uint64_t size, uint32_t packed_clear);
void MarkGpuWritten(VulkanImage* image);
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
void InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
bool formatted_buffer_write = false);
[[nodiscard]] RenderTextureVulkanImage* FindRenderTargetByRange(CommandBuffer* command,
uint64_t vaddr, uint64_t size);
[[nodiscard]] VkImageView GetRenderTargetAttachmentView(GraphicContext* ctx,
RenderTextureVulkanImage* image,
VkFormat format, uint32_t level);
[[nodiscard]] VkImageView GetRenderTargetSampledView(GraphicContext* ctx,
RenderTextureVulkanImage* image,
VkFormat view_format, int variant,
uint32_t base_level, uint32_t level_count);
[[nodiscard]] VkImageView GetRenderTargetStorageView(GraphicContext* ctx,
RenderTextureVulkanImage* image,
VkFormat view_format, uint32_t base_level,
uint32_t level_count);
[[nodiscard]] VkImageView GetStorageTextureSampledView(GraphicContext* ctx,
StorageTextureVulkanImage* image,
const ImageInfo& info);
[[nodiscard]] DepthStencilVulkanImage* FindDepthTargetByRange(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasRangeOverlap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasGpuModifiedRangeOverlap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasGpuTargetPageOverlap(uint64_t vaddr, uint64_t size);
void RegisterMeta(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t vaddr);
[[nodiscard]] bool IsMetaRange(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasMetaOverlap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMetaGpuModified(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);
static void DeleteGpuTexture(GraphicContext* ctx, GpuTextureVulkanImage* image,
VulkanMemory* mem);
static void DeleteRenderTexture(GraphicContext* ctx, RenderTextureVulkanImage* image,
VulkanMemory* mem);
static void DeleteDepthStencil(GraphicContext* ctx, DepthStencilVulkanImage* image,
VulkanMemory* mem);
static void DeleteVideoOut(GraphicContext* ctx, VideoOutVulkanImage* image, VulkanMemory* mem);
VulkanImage* GetDummySampledTexture(bool uint_format, bool image_3d);
VulkanImage* GetDummyStorageTexture(bool uint_format, bool image_3d);
private:
struct CachedImage;
struct ReadbackWorker;
struct MetaDataInfo {
uint64_t size = 0;
uint32_t clear_mask = 0;
bool gpu_modified = false;
};
static void DeleteImageViews(GraphicContext* ctx, VulkanImage* image);
[[nodiscard]] bool HasMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
[[nodiscard]] CachedImage* FindGpuReadbackPageCandidateLocked(uint64_t vaddr,
uint64_t size) const;
void RequireNoMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
void MarkSampledAliasesCpuDirtyLocked(uint64_t vaddr, uint64_t size);
void RetireImages(const std::vector<CachedImage*>& retire);
void SynchronizeRenderTargetToBufferLocked(CachedImage& cached);
Common::Mutex m_dummy_mutex;
std::array<VulkanImage*, 4> m_dummy_sampled_textures {};
std::array<VulkanImage*, 4> m_dummy_storage_textures {};
std::array<VulkanMemory*, 4> m_dummy_sampled_memory {};
std::array<VulkanMemory*, 4> m_dummy_storage_memory {};
GraphicContext* m_dummy_ctx = nullptr;
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;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
std::unique_ptr<ReadbackWorker> m_readback;
std::vector<uint8_t> m_buffer_transition_linear;
std::vector<uint8_t> m_buffer_transition_guest;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TEXTURECACHE_H_
+124
View File
@@ -0,0 +1,124 @@
#include "graphics/host_gpu/renderer/tiler.h"
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.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/image.h"
#include "graphics/host_gpu/utils.h"
namespace Libs::Graphics {
void Tiler::DetileImage(GraphicContext* ctx, GpuTextureVulkanImage* image,
const ImageInfo& info, const BufferImageCopySource& source,
bool refresh, bool storage) const {
if (ctx == nullptr || image == nullptr || info.address == 0 || info.size == 0 ||
info.width == 0 || info.height == 0 || info.depth == 0 || info.levels == 0 ||
info.levels >= 16 || !source.cpu_current || source.address != info.address ||
source.size != info.size || (source.buffer == nullptr && source.offset != 0)) {
EXIT("Tiler: unsupported sampled-image detile, ctx=%p image=%p source_buffer=%p "
"source=0x%016" PRIx64 "+0x%016" PRIx64 " offset=0x%016" PRIx64
" current=%d image=0x%016" PRIx64 "+0x%016" PRIx64
" extent=%ux%ux%u levels=%u tile=%u storage=%d\n",
static_cast<const void*>(ctx), static_cast<const void*>(image),
static_cast<const void*>(source.buffer), source.address, source.size, source.offset,
source.cpu_current, info.address, info.size, info.width, info.height, info.depth,
info.levels, info.tile, storage);
}
if (refresh) {
VulkanDeviceWaitIdle(ctx);
}
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, false, volume_texture, storage ? "StorageTextureCache" : "TextureCache");
const auto slice_layout = TextureUploadSliceLayout::MipChainPerSlice;
auto regions = TextureBuildUploadRegions(layout, image->format, info.width, info.height,
info.depth, info.levels, array_texture, volume_texture,
TextureUploadDestination::MipLevels, slice_layout);
// Keep the buffer owner explicit at the detiling seam. The current PS5
// backend consumes the coherent guest publication; a GPU detiler can consume source.buffer and
// source.offset here without changing TextureCache ownership or alias classification.
TextureUploadGuestImage(
ctx, image, reinterpret_cast<const void*>(source.address), info.size, regions, layout,
info.format, info.width, info.height, info.depth, info.levels, slice_layout,
storage ? "StorageTextureCache" : "TextureCache",
static_cast<uint64_t>(storage ? VK_IMAGE_LAYOUT_GENERAL
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL));
}
void Tiler::DetileImage(GraphicContext* ctx, DepthStencilVulkanImage* image,
const DepthTargetInfo& info, const BufferImageCopySource& source,
bool refresh) const {
const bool d16 = info.guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm) &&
info.format == VK_FORMAT_D16_UNORM && info.bytes_per_element == 2;
const bool d32 =
info.guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float) &&
(info.format == VK_FORMAT_D32_SFLOAT || info.format == VK_FORMAT_D32_SFLOAT_S8_UINT) &&
info.bytes_per_element == 4;
if (ctx == nullptr || image == nullptr || info.address == 0 || info.size == 0 ||
info.width == 0 || info.height == 0 || info.pitch < info.width ||
info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kDepth) || (!d16 && !d32) ||
!source.cpu_current || source.address != info.address || source.size != info.size ||
(source.buffer == nullptr && source.offset != 0)) {
EXIT("Tiler: unsupported depth detile, ctx=%p image=%p source_buffer=%p "
"source=0x%016" PRIx64 "+0x%016" PRIx64 " offset=0x%016" PRIx64
" current=%d depth=0x%016" PRIx64 "+0x%016" PRIx64
" extent=%ux%u pitch=%u tile=%u format=%d guest_format=%u bpe=%u\n",
static_cast<const void*>(ctx), static_cast<const void*>(image),
static_cast<const void*>(source.buffer), source.address, source.size, source.offset,
source.cpu_current, info.address, info.size, info.width, info.height, info.pitch,
info.tile_mode, static_cast<int>(info.format), info.guest_format,
info.bytes_per_element);
}
if (refresh) {
VulkanDeviceWaitIdle(ctx);
}
UtilScratchBuffer linear(info.size);
TileConvertTiledToLinearDepth(linear.Data(), reinterpret_cast<const void*>(source.address),
info.guest_format, info.width, info.height, info.pitch,
info.size);
UtilFillDepthImage(ctx, image, linear.Data(), info.size, info.pitch);
}
void Tiler::TileImage(void* dst, const void* src, const RenderTargetInfo& info) const {
const bool supported_element = info.bytes_per_element == 1 || info.bytes_per_element == 2 ||
info.bytes_per_element == 4 || info.bytes_per_element == 8;
if (dst == nullptr || src == nullptr || info.address == 0 || info.size == 0 ||
info.width == 0 || info.height == 0 || info.pitch < info.width ||
info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) || info.levels != 1 ||
!supported_element) {
EXIT("Tiler: unsupported render-target tile, dst=%p src=%p "
"addr=0x%016" PRIx64 "+0x%016" PRIx64
" extent=%ux%u pitch=%u levels=%u tile=%u bpe=%u\n",
dst, src, info.address, info.size, info.width, info.height, info.pitch, info.levels,
info.tile_mode, info.bytes_per_element);
}
TileConvertLinearToTiledRenderTarget(dst, src, info.width, info.height, info.pitch,
info.bytes_per_element, info.size);
}
void Tiler::TileImage(void* dst, const void* src, const DepthTargetInfo& info) const {
const bool supported_format =
(info.guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm) &&
info.format == VK_FORMAT_D16_UNORM && info.bytes_per_element == 2) ||
(info.guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float) &&
info.format == VK_FORMAT_D32_SFLOAT && info.bytes_per_element == 4);
if (dst == nullptr || src == nullptr || info.address == 0 || info.size == 0 ||
info.stencil_address != 0 || info.stencil_size != 0 || info.width == 0 ||
info.height == 0 || info.pitch < info.width ||
info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kDepth) || !supported_format) {
EXIT("Tiler: unsupported depth-target tile, dst=%p src=%p "
"depth=0x%016" PRIx64 "+0x%016" PRIx64 " stencil=0x%016" PRIx64 "+0x%016" PRIx64
" extent=%ux%u pitch=%u tile=%u format=%d guest_format=%u bpe=%u\n",
dst, src, info.address, info.size, info.stencil_address, info.stencil_size, info.width,
info.height, info.pitch, info.tile_mode, static_cast<int>(info.format),
info.guest_format, info.bytes_per_element);
}
TileConvertLinearToTiledDepth(dst, src, info.guest_format, info.width, info.height, info.pitch,
info.size);
}
} // namespace Libs::Graphics
+30
View File
@@ -0,0 +1,30 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TILER_H_
#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"
namespace Libs::Graphics {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
struct GraphicContext;
class Tiler final {
public:
Tiler() = default;
KYTY_CLASS_NO_COPY(Tiler);
void DetileImage(GraphicContext* ctx, GpuTextureVulkanImage* image, const ImageInfo& info,
const BufferImageCopySource& source, bool refresh, bool storage) const;
void DetileImage(GraphicContext* ctx, DepthStencilVulkanImage* image,
const DepthTargetInfo& info, const BufferImageCopySource& source,
bool refresh) const;
void TileImage(void* dst, const void* src, const RenderTargetInfo& info) const;
void TileImage(void* dst, const void* src, const DepthTargetInfo& info) const;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TILER_H_