mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-23 12:26:56 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9dcff95e80 | |||
| 719c44310a | |||
| 7bb618d41a | |||
| 87e815b250 | |||
| f9e14084d6 | |||
| 38c884e8c1 | |||
| 27dc7c281f | |||
| 79837b423d | |||
| aafe014d0c | |||
| a358b847c1 | |||
| a951a91703 | |||
| a875d1b205 | |||
| f5f4e7d48a | |||
| 86fed10b81 | |||
| 949de19bb0 | |||
| c513a4a13a | |||
| b54057942d | |||
| b6495a5b67 | |||
| 5b315a9ea7 | |||
| 5f5d6effa4 | |||
| c5016d9455 | |||
| 753f285198 |
@@ -265,6 +265,7 @@ set(gpu_tiler_shader_names
|
||||
promote_d16
|
||||
demote_d16
|
||||
color_transform
|
||||
color_clear
|
||||
)
|
||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
@@ -675,6 +676,12 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME wait_until_safe_for_rendering
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --wait-until-safe-for-rendering-only)
|
||||
add_test(NAME depth_bits
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-bits-only)
|
||||
add_test(NAME msaa_storage
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --msaa-storage-only)
|
||||
add_test(NAME pm4_context_state
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||
add_test(NAME gpu_tiler
|
||||
|
||||
@@ -46,6 +46,16 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
## Screenshots
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Cyberpunk 2077</strong><br>
|
||||
<img src="docs/screenshots/ps5-07.png" width="300" alt="Cyberpunk 2077 running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
<strong>MLB The Show 23</strong><br>
|
||||
<img src="docs/screenshots/ps5-08.png" width="300" alt="MLB The Show 23 running in KytyPS5">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Astro Bot</strong><br>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -29,6 +29,9 @@ enum class Counter {
|
||||
TextureUploadBytes,
|
||||
TextureDownloadBytes,
|
||||
TextureEvictions,
|
||||
ColorMetadataReadbacks, // CPU blocked reading back GPU-written DCC/CMASK metadata.
|
||||
ColorMetadataReadbackNs,
|
||||
ColorMetadataChecks, // GPU clear-key checks of DCC/CMASK metadata.
|
||||
BufferUploadBytes,
|
||||
BufferDownloadBytes,
|
||||
BufferEvictions,
|
||||
|
||||
@@ -154,6 +154,8 @@ private:
|
||||
uint32_t interrupt_context_id);
|
||||
void ProcessPm4(Pm4Execution& execution);
|
||||
void SuspendPm4();
|
||||
uint32_t NumInstances();
|
||||
const void* IndexAddress(uint32_t first_index) const;
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
|
||||
@@ -169,8 +171,10 @@ private:
|
||||
uint64_t m_index_base_addr = 0;
|
||||
uint64_t m_draw_indirect_args_base_addr = 0;
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
// Persistent draw state: indirect draws update it for subsequent draws.
|
||||
uint32_t m_num_instances = 1;
|
||||
// Persistent draw state. Indirect draws leave the address of their last instance count,
|
||||
// read only when a later draw inherits it.
|
||||
uint32_t m_num_instances = 1;
|
||||
uint64_t m_num_instances_addr = 0;
|
||||
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -39,21 +40,6 @@ static thread_local Pm4Execution* g_current_execution = nullptr;
|
||||
static thread_local bool g_gpu_thread = false;
|
||||
static thread_local GuestGpu* g_gpu_state = nullptr;
|
||||
|
||||
struct DrawIndirectArgs {
|
||||
uint32_t vertex_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
struct DrawIndexedIndirectArgs {
|
||||
uint32_t index_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_index_location;
|
||||
uint32_t base_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
static bool GraphicsRunDebugDumpEnabled() {
|
||||
return Config::GraphicsDebugDumpEnabled() &&
|
||||
Config::GetPrintfDirection() != Config::LogDirection::Silent;
|
||||
@@ -106,6 +92,12 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::Wake() {
|
||||
Common::LockGuard lock(m_queue_mutex);
|
||||
m_wake_pending = true;
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::ProcessCommands() {
|
||||
EXIT_IF(!IsGpuThread());
|
||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||
@@ -206,6 +198,7 @@ void CommandProcessor::Reset() {
|
||||
m_index_buffer_size = 0;
|
||||
m_index_base_addr = 0;
|
||||
m_num_instances = 1;
|
||||
m_num_instances_addr = 0;
|
||||
m_predicate_skip = false;
|
||||
m_user_data_marker = HW::UserSgprType::Unknown;
|
||||
m_draw_indirect_args_base_addr = 0;
|
||||
@@ -478,7 +471,10 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
}
|
||||
if (selected_queue < 0) {
|
||||
gpu->m_processing = false;
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
// A wake that came before this wait must not be lost.
|
||||
if (!std::exchange(gpu->m_wake_pending, false)) {
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
}
|
||||
for (auto& queue: gpu->m_queues) {
|
||||
if (!queue.empty()) {
|
||||
queue.front().blocked = false;
|
||||
@@ -784,7 +780,16 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) {
|
||||
num_instances = 1;
|
||||
}
|
||||
|
||||
m_num_instances = num_instances;
|
||||
m_num_instances = num_instances;
|
||||
m_num_instances_addr = 0;
|
||||
}
|
||||
|
||||
uint32_t CommandProcessor::NumInstances() {
|
||||
if (m_num_instances_addr != 0) {
|
||||
m_num_instances = *reinterpret_cast<const uint32_t*>(m_num_instances_addr);
|
||||
m_num_instances_addr = 0;
|
||||
}
|
||||
return m_num_instances;
|
||||
}
|
||||
|
||||
void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op,
|
||||
@@ -841,16 +846,12 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
|
||||
args.index_type_and_size = m_index_type_and_size;
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
if (GraphicsRunDebugDumpEnabled() && (args.base_vertex != 0 || args.first_instance != 0)) {
|
||||
LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n",
|
||||
args.base_vertex, args.first_instance);
|
||||
args.instance_count = NumInstances();
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
|
||||
const void* CommandProcessor::IndexAddress(uint32_t first_index) const {
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
case 0: index_size = 2; break;
|
||||
@@ -858,11 +859,11 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
case 2: index_size = 1; break;
|
||||
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
|
||||
}
|
||||
return reinterpret_cast<const void*>(m_index_base_addr + first_index * index_size);
|
||||
}
|
||||
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
|
||||
|
||||
DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
|
||||
DrawIndex({.index_count = index_count, .index_addr = IndexAddress(index_offset)});
|
||||
}
|
||||
|
||||
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
||||
@@ -883,7 +884,8 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
|
||||
const auto args_size =
|
||||
indexed ? sizeof(vk::DrawIndexedIndirectCommand) : sizeof(vk::DrawIndirectCommand);
|
||||
DrawIndirectMulti(data_offset, 1, nullptr, args_size, registers, draw_initiator, indexed);
|
||||
}
|
||||
|
||||
@@ -893,77 +895,65 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u));
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
// The guest records match Vulkan's indirect commands byte for byte.
|
||||
EXIT_NOT_IMPLEMENTED((stride_in_bytes & 3u) != 0 ||
|
||||
stride_in_bytes < (indexed ? sizeof(vk::DrawIndexedIndirectCommand)
|
||||
: sizeof(vk::DrawIndirectCommand)));
|
||||
|
||||
uint32_t draw_count = max_count_or_count;
|
||||
if (count_addr != nullptr) {
|
||||
draw_count = *count_addr;
|
||||
if (draw_count > max_count_or_count) {
|
||||
draw_count = max_count_or_count;
|
||||
}
|
||||
}
|
||||
|
||||
const auto draw_count = count_addr == nullptr
|
||||
? max_count_or_count
|
||||
: std::min(uint32_t {*count_addr}, max_count_or_count);
|
||||
if (draw_count == 0) {
|
||||
return;
|
||||
}
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::IndirectDraws, draw_count);
|
||||
|
||||
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
|
||||
EXIT_NOT_IMPLEMENTED(stride_in_bytes < args_size);
|
||||
|
||||
uint64_t index_size = 0;
|
||||
if (indexed) {
|
||||
switch (m_index_type_and_size) {
|
||||
case 0: index_size = 2; break;
|
||||
case 1: index_size = 4; break;
|
||||
case 2: index_size = 1; break;
|
||||
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
|
||||
}
|
||||
auto& executor = m_renderer.GetRenderExecutor();
|
||||
const auto args_addr = m_draw_indirect_args_base_addr + data_offset;
|
||||
// NUM_INSTANCES keeps the last draw's instance count.
|
||||
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||
// With no SGPRs to patch, the GPU reads the records if the shaders allow. Shaders apply
|
||||
// the records' offsets only through those SGPRs; Vulkan applies them, which matches
|
||||
// while they are zero.
|
||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
||||
registers.index_offset == Pm4::SH_NOP && (!indexed || m_index_buffer_size != 0) &&
|
||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = m_index_buffer_size,
|
||||
.index_addr = IndexAddress(0),
|
||||
.index_type_and_size = m_index_type_and_size,
|
||||
.indirect = records})
|
||||
: executor.DrawAuto(m_submit_id, CurrentBuffer(), {.indirect = records}))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t max_indices = m_index_buffer_size != 0 ? m_index_buffer_size : UINT32_MAX;
|
||||
for (uint32_t i = 0; i < draw_count; i++) {
|
||||
const auto args_addr = m_draw_indirect_args_base_addr + data_offset +
|
||||
static_cast<uint64_t>(i) * stride_in_bytes;
|
||||
|
||||
const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
|
||||
if (!indexed) {
|
||||
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->start_vertex_location,
|
||||
args->start_instance_location, 0);
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
|
||||
.instance_count = args->instance_count,
|
||||
.first_vertex = args->start_vertex_location,
|
||||
.first_instance = args->start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0);
|
||||
executor.DrawAuto(m_submit_id, CurrentBuffer(),
|
||||
{.vertex_count = args.vertexCount,
|
||||
.instance_count = args.instanceCount,
|
||||
.first_vertex = args.firstVertex,
|
||||
.first_instance = args.firstInstance,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->base_vertex_location,
|
||||
args->start_instance_location, args->start_index_location);
|
||||
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(args->start_index_location) * index_size);
|
||||
|
||||
const uint32_t index_count =
|
||||
(m_index_buffer_size != 0
|
||||
? std::min(args->index_count_per_instance, m_index_buffer_size)
|
||||
: args->index_count_per_instance);
|
||||
if (GraphicsRunDebugDumpEnabled() && index_count != args->index_count_per_instance) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 64) {
|
||||
LOGF("\t DrawIndexIndirectMulti: clamped index_count from %" PRIu32 " to %" PRIu32
|
||||
" using INDEX_BUFFER_SIZE\n",
|
||||
args->index_count_per_instance, index_count);
|
||||
}
|
||||
}
|
||||
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndex({.index_count = index_count,
|
||||
.index_addr = index_addr,
|
||||
.instance_count = args->instance_count,
|
||||
.base_vertex = static_cast<int32_t>(args->base_vertex_location),
|
||||
.first_instance = args->start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
||||
args.firstInstance, args.firstIndex);
|
||||
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = std::min(args.indexCount, max_indices),
|
||||
.index_addr = IndexAddress(args.firstIndex),
|
||||
.instance_count = args.instanceCount,
|
||||
.index_type_and_size = m_index_type_and_size,
|
||||
.base_vertex = args.vertexOffset,
|
||||
.first_instance = args.firstInstance,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,16 +1002,17 @@ void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
args.instance_count = NumInstances();
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
BufferFlush();
|
||||
|
||||
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index));
|
||||
// Stall only this command processor, not the host command lane or other queues.
|
||||
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index))) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
[[nodiscard]] bool IsStopping();
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
// Retries blocked submissions now instead of after the 100 ms stall timeout.
|
||||
void Wake();
|
||||
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
@@ -87,6 +89,7 @@ private:
|
||||
bool m_processing = false;
|
||||
bool m_accepting = true;
|
||||
bool m_stopping = false;
|
||||
bool m_wake_pending = false;
|
||||
bool m_shutdown_complete = false;
|
||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||
|
||||
@@ -35,6 +35,7 @@ struct GraphicContext {
|
||||
bool attachment_feedback_loop_enabled = false;
|
||||
bool provoking_vertex_last_enabled = false;
|
||||
bool maintenance8_enabled = false;
|
||||
bool conditional_rendering_enabled = false;
|
||||
bool supports_block_texel_view = false;
|
||||
bool mesh_shader_enabled = false;
|
||||
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
||||
@@ -42,6 +43,7 @@ struct GraphicContext {
|
||||
uint32_t min_subgroup_size = 0;
|
||||
uint32_t max_subgroup_size = 0;
|
||||
uint32_t max_push_descriptors = 0;
|
||||
vk::DescriptorSetLayout image_heap_layout = nullptr;
|
||||
vk::ShaderStageFlags required_subgroup_size_stages = {};
|
||||
Common::Mutex queue_mutex;
|
||||
uint32_t queue_family = static_cast<uint32_t>(-1);
|
||||
|
||||
@@ -174,6 +174,7 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
||||
total_size += Common::AlignUp(end - start, 64);
|
||||
});
|
||||
m_gpu_modified_ranges.Subtract(address, bytes);
|
||||
ForgetColorMetadataChecks(address, bytes);
|
||||
});
|
||||
if (copies.empty()) {
|
||||
return false;
|
||||
@@ -519,6 +520,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
|
||||
(void)SynchronizeBuffer(buffer, vaddr, size, is_written, is_texel_buffer);
|
||||
if (is_written) {
|
||||
m_gpu_modified_ranges.Add(vaddr, size);
|
||||
ForgetColorMetadataChecks(vaddr, size);
|
||||
}
|
||||
return {&buffer, buffer.Offset(vaddr)};
|
||||
}
|
||||
@@ -623,6 +625,29 @@ bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
|
||||
return address + m_slot_buffers[id].Size() > vaddr;
|
||||
}
|
||||
|
||||
void BufferCache::MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) {
|
||||
for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) {
|
||||
if ((keys & (1u << slot)) != 0) {
|
||||
m_checked_color_metadata[slot].Add(vaddr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const {
|
||||
for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) {
|
||||
if ((keys & (1u << slot)) != 0 && !m_checked_color_metadata[slot].Contains(vaddr, size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BufferCache::ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size) {
|
||||
for (auto& checked: m_checked_color_metadata) {
|
||||
checked.Subtract(vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
return m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
@@ -79,6 +80,10 @@ public:
|
||||
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
// Color metadata the GPU found free of the clear keys in `keys` (bits of code >> 5) stays so
|
||||
// until a GPU write or download.
|
||||
void MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys);
|
||||
[[nodiscard]] bool IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const;
|
||||
void ProcessFaultBuffer();
|
||||
void SynchronizeBda(const RangeSet& mapped);
|
||||
void QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
||||
@@ -124,6 +129,7 @@ private:
|
||||
// Synchronous downloads publish before returning; asynchronous callers wait before reuse.
|
||||
template <bool async>
|
||||
[[nodiscard]] bool DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
void ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
@@ -135,6 +141,8 @@ private:
|
||||
BufferMap m_buffers;
|
||||
PageTable m_page_table;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
// One per TileManager::ColorClearSlots key slot.
|
||||
std::array<RangeSet, 8> m_checked_color_metadata;
|
||||
MemoryTracker m_memory_tracker;
|
||||
StreamBuffer m_staging_buffer;
|
||||
StreamBuffer m_stream_buffer;
|
||||
|
||||
+118
-29
@@ -229,6 +229,7 @@ void TextureCache::UnregisterImage(ImageId id) {
|
||||
if (!image.registered) {
|
||||
return;
|
||||
}
|
||||
image.HeapChanged();
|
||||
UntrackImage(id);
|
||||
ImagePageTable::PageRange pages {};
|
||||
if (!ImagePageTable::TryGetPageRange(image.info.data.address, image.info.data.size, pages)) {
|
||||
@@ -378,6 +379,7 @@ void TextureCache::UntrackImageHead(ImageId id) {
|
||||
const auto address = Common::AlignDown(begin + TRACKER_PAGE_SIZE, TRACKER_PAGE_SIZE);
|
||||
const auto size = address - begin;
|
||||
image.track_addr = address;
|
||||
image.HeapChanged();
|
||||
if (image.track_addr == image.track_addr_end) {
|
||||
MarkAsMaybeDirty(id, image);
|
||||
}
|
||||
@@ -395,6 +397,7 @@ void TextureCache::UntrackImageTail(ImageId id) {
|
||||
const auto address = Common::AlignDown(end, TRACKER_PAGE_SIZE);
|
||||
const auto size = end - address;
|
||||
image.track_addr_end = address;
|
||||
image.HeapChanged();
|
||||
if (image.track_addr == image.track_addr_end) {
|
||||
MarkAsMaybeDirty(id, image);
|
||||
}
|
||||
@@ -795,7 +798,8 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
auto& cached = *owner;
|
||||
const auto current_tick = m_scheduler.CurrentTick();
|
||||
const bool safe_to_delete =
|
||||
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval;
|
||||
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval &&
|
||||
cached.heap_refs.load(std::memory_order_relaxed) == 0u;
|
||||
|
||||
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
|
||||
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
|
||||
@@ -1067,12 +1071,78 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
if (first >= layers || count > layers - first) {
|
||||
EXIT("TextureCache: color view exceeds its native metadata slices\n");
|
||||
}
|
||||
// Finish native metadata writes before reading backing bytes. This can submit the scheduler,
|
||||
// so discovery runs before final draw uploads and never holds the texture lock across it.
|
||||
if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) {
|
||||
m_buffer_cache.ReadMemory(range.address, range.size, false);
|
||||
const auto slice_size = range.size / layers;
|
||||
const GuestRange viewed {range.address + slice_size * first, slice_size * count};
|
||||
if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) {
|
||||
std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {};
|
||||
uint32_t keys = 0;
|
||||
for (uint32_t key = 0; key < clears.size(); key++) {
|
||||
clears[key].aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
if (DecodeColorClear(desc, static_cast<uint8_t>(key << 5u),
|
||||
clears[key].clearValue.color)) {
|
||||
keys |= 1u << key;
|
||||
}
|
||||
}
|
||||
// No selected key to clear, or none left since the last check.
|
||||
if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
// Compressed images cannot upload, so they must already hold native contents.
|
||||
const bool native_current = (image.usage.render_target || image.IsGpuModified()) &&
|
||||
!image.IsBufferModified() && !image.IsCpuDirty();
|
||||
// 2D-array attachment views select 2D layers or, of a volume, depth slices.
|
||||
if (m_graphics.conditional_rendering_enabled &&
|
||||
image.backing.image_type != vk::ImageType::e1D &&
|
||||
count <= m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers &&
|
||||
(image.backing.usage & vk::ImageUsageFlagBits::eColorAttachment) &&
|
||||
(m_graphics.GetFormatProperties(view.format).optimalTilingFeatures &
|
||||
vk::FormatFeatureFlagBits::eColorAttachment) &&
|
||||
(desc.info.metadata.compression == VideoOutCompression::Uncompressed ||
|
||||
native_current)) {
|
||||
// The GPU selects each slice's clear, so the CPU never waits for the metadata.
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::ColorMetadataChecks);
|
||||
InitializeImage(id);
|
||||
const bool expand = desc.type != BindingType::VideoOut;
|
||||
const auto [buffer, offset] =
|
||||
m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand);
|
||||
const auto predicates = m_tiler.CheckColorClear(
|
||||
{buffer->Handle(), offset, viewed.size}, count, keys, expand);
|
||||
if (expand) {
|
||||
m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys);
|
||||
}
|
||||
const auto command = m_scheduler.Current().Handle();
|
||||
const auto area = BeginColorRendering(image, view.format, view.base_level,
|
||||
image_first, count, nullptr, command);
|
||||
vk::ClearRect rect {.rect = area, .layerCount = 1};
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
rect.baseArrayLayer = slice;
|
||||
for (uint32_t key = 0; key < clears.size(); key++) {
|
||||
if ((keys & (1u << key)) != 0) {
|
||||
const vk::ConditionalRenderingBeginInfoEXT predicate {
|
||||
.buffer = predicates,
|
||||
.offset = (slice * clears.size() + key) * sizeof(uint32_t)};
|
||||
command.beginConditionalRenderingEXT(&predicate);
|
||||
command.clearAttachments(1, &clears[key], 1, &rect);
|
||||
command.endConditionalRenderingEXT();
|
||||
}
|
||||
}
|
||||
}
|
||||
command.endRendering();
|
||||
CommitGpuWrite(image);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Finish native metadata writes before reading backing bytes. This can submit the
|
||||
// scheduler, so discovery runs before final draw uploads and never holds the texture
|
||||
// lock across it.
|
||||
Common::DebugCounters::ScopedTimer timer(
|
||||
Common::DebugCounters::Counter::ColorMetadataReadbacks,
|
||||
Common::DebugCounters::Counter::ColorMetadataReadbackNs);
|
||||
m_buffer_cache.ReadMemory(viewed.address, viewed.size, false);
|
||||
}
|
||||
const auto slice_size = range.size / layers;
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
const auto address = range.address + slice_size * (first + slice);
|
||||
uint8_t code = 0;
|
||||
@@ -1574,29 +1644,8 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
|
||||
if (format != image.backing.format || (image.info.IsVolume() && !full_image)) {
|
||||
EXIT_NOT_IMPLEMENTED(range.aspectMask != vk::ImageAspectFlagBits::eColor ||
|
||||
range.levelCount != 1);
|
||||
ImageViewInfo view {};
|
||||
view.format = format;
|
||||
view.type = range.layerCount == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
view.base_level = range.baseMipLevel;
|
||||
view.base_layer = range.baseArrayLayer;
|
||||
view.layer_count = range.layerCount;
|
||||
view.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
image.Transit(vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite, {}, command.Handle());
|
||||
vk::RenderingAttachmentInfo attachment {};
|
||||
attachment.imageView = image.FindView(view);
|
||||
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachment.clearValue = clear;
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.renderArea.extent = {
|
||||
std::max(image.info.extent.width >> range.baseMipLevel, 1u),
|
||||
std::max(image.info.extent.height >> range.baseMipLevel, 1u)};
|
||||
rendering.layerCount = range.layerCount;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
command.Handle().beginRendering(&rendering);
|
||||
BeginColorRendering(image, format, range.baseMipLevel, range.baseArrayLayer,
|
||||
range.layerCount, &clear, command.Handle());
|
||||
command.Handle().endRendering();
|
||||
CommitGpuWrite(image);
|
||||
return;
|
||||
@@ -1619,6 +1668,40 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
|
||||
CommitGpuWrite(image);
|
||||
}
|
||||
|
||||
vk::Rect2D TextureCache::BeginColorRendering(Image& image, vk::Format format, uint32_t level,
|
||||
uint32_t layer, uint32_t layers,
|
||||
const vk::ClearValue* clear,
|
||||
vk::CommandBuffer command) {
|
||||
ImageViewInfo view {};
|
||||
view.format = format;
|
||||
view.type = layers == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
view.base_level = level;
|
||||
view.base_layer = layer;
|
||||
view.layer_count = layers;
|
||||
view.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
image.Transit(vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::AccessFlagBits2::eColorAttachmentRead |
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||
{}, command);
|
||||
vk::RenderingAttachmentInfo attachment {};
|
||||
attachment.imageView = image.FindView(view);
|
||||
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eLoad;
|
||||
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
if (clear != nullptr) {
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
attachment.clearValue = *clear;
|
||||
}
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.renderArea.extent = {std::max(image.info.extent.width >> level, 1u),
|
||||
std::max(image.info.extent.height >> level, 1u)};
|
||||
rendering.layerCount = layers;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
command.beginRendering(&rendering);
|
||||
return rendering.renderArea;
|
||||
}
|
||||
|
||||
void TextureCache::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid memory-invalidation range\n");
|
||||
@@ -1752,6 +1835,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
||||
copy_size != image.info.data.size)) {
|
||||
return false;
|
||||
}
|
||||
ForgetColorMetadataChecks(image.info.data.address, copy_size);
|
||||
m_texture_cache.DownloadImage(image, buffer, buf_offset, copy_size, levels);
|
||||
return true;
|
||||
}
|
||||
@@ -1935,6 +2019,11 @@ void TextureCache::RunGarbageCollector() {
|
||||
if (owner == nullptr || !owner->registered || owner->depth_id) {
|
||||
continue;
|
||||
}
|
||||
if (owner->heap_refs.load(std::memory_order_relaxed) != 0u) {
|
||||
// Resident heaps sample it without touching it.
|
||||
TouchImage(*owner);
|
||||
continue;
|
||||
}
|
||||
if (owner->IsGpuModified()) {
|
||||
const bool safe = owner->SafeToDownload();
|
||||
if (safe && owner->info.IsTiled()) {
|
||||
|
||||
@@ -150,6 +150,12 @@ private:
|
||||
// Caller holds m_lock. Volume layer ranges select depth slices.
|
||||
void ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
|
||||
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear);
|
||||
// Caller holds m_lock and ends rendering. Begins rendering `layers` layers of one color mip
|
||||
// through a `format` view, clearing them or, when `clear` is null, loading them; returns the
|
||||
// render area.
|
||||
vk::Rect2D BeginColorRendering(Image& image, vk::Format format, uint32_t level,
|
||||
uint32_t layer, uint32_t layers, const vk::ClearValue* clear,
|
||||
vk::CommandBuffer command);
|
||||
void PrepareImageCopy(Image& image);
|
||||
void RefreshCopySource(ImageId id);
|
||||
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
|
||||
@@ -185,6 +191,7 @@ private:
|
||||
friend struct TextureCacheTestAccess;
|
||||
friend class BufferCache;
|
||||
friend class RenderExecutor;
|
||||
friend class ImageHeap;
|
||||
friend bool BcAlias::Matches(const ImageInfo&, const ImageInfo&);
|
||||
friend bool BcAlias::Publish(TextureCache&, Image&);
|
||||
friend void BcAlias::Resolve(TextureCache&, ImageId, ImageId);
|
||||
|
||||
@@ -90,7 +90,8 @@ ImageCreateFlags(const GraphicContext& graphics, const ImageInfo& info, vk::Imag
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
|
||||
usage |= vk::ImageUsageFlagBits::eColorAttachment;
|
||||
}
|
||||
if (info.samples == 1) {
|
||||
if (graphics.GetPhysicalDeviceProperties().limits.storageImageSampleCounts &
|
||||
static_cast<vk::SampleCountFlagBits>(info.samples)) {
|
||||
usage |= vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
return usage;
|
||||
@@ -211,6 +212,13 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destinat
|
||||
barriers.push_back(barrier);
|
||||
}
|
||||
|
||||
// Resident heaps sample their images in the read state; only leaving it invalidates them.
|
||||
if (std::ranges::any_of(barriers, [&](const vk::ImageMemoryBarrier2& barrier) {
|
||||
return barrier.oldLayout == ReadLayout() &&
|
||||
barrier.srcAccessMask == vk::AccessFlagBits2::eShaderRead;
|
||||
})) {
|
||||
HeapChanged();
|
||||
}
|
||||
state = {destination_stage, destination_access, destination_layout};
|
||||
return barriers;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <compare>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
@@ -71,6 +72,7 @@ public:
|
||||
void MarkCpuDirty() {
|
||||
m_cpu_dirty = true;
|
||||
m_maybe_cpu_dirty = false;
|
||||
HeapChanged();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
|
||||
@@ -80,6 +82,7 @@ public:
|
||||
if (!IsCpuDirty()) {
|
||||
m_maybe_cpu_hash = HashGuestEdges();
|
||||
m_maybe_cpu_dirty = true;
|
||||
HeapChanged();
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
|
||||
@@ -104,7 +107,10 @@ public:
|
||||
void ClearGpuModified() noexcept { m_gpu_modified = false; }
|
||||
|
||||
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
|
||||
void MarkBufferModified() noexcept { m_buffer_modified = true; }
|
||||
void MarkBufferModified() noexcept {
|
||||
m_buffer_modified = true;
|
||||
HeapChanged();
|
||||
}
|
||||
void ClearBufferModified() noexcept { m_buffer_modified = false; }
|
||||
|
||||
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size,
|
||||
@@ -120,6 +126,19 @@ public:
|
||||
return backing.image == nullptr ? 0 : Common::AlignUp(info.data.size, 1024);
|
||||
}
|
||||
[[nodiscard]] uint64_t HashGuestEdges() const;
|
||||
[[nodiscard]] vk::ImageLayout ReadLayout() const noexcept {
|
||||
return info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
// Resident image heaps sample an image without rediscovering it until one changes its
|
||||
// contents, tracking, identity or layout.
|
||||
void HeapChanged() const noexcept {
|
||||
if (heap_refs.load(std::memory_order_relaxed) != 0u) {
|
||||
heap_epoch.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::atomic<uint64_t> heap_epoch {0};
|
||||
|
||||
ImageInfo info;
|
||||
VulkanImage backing;
|
||||
@@ -137,6 +156,8 @@ public:
|
||||
ImageSubresourceRange stencil_subresources;
|
||||
uint64_t tick_accessed_last = 0;
|
||||
size_t lru_id = 0;
|
||||
// Resident image heap elements that select this image.
|
||||
std::atomic<uint32_t> heap_refs {0};
|
||||
|
||||
private:
|
||||
friend struct ImageTestAccess;
|
||||
|
||||
@@ -101,6 +101,7 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_color_transform_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_color_clear_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
@@ -71,20 +72,11 @@ TileManager::~TileManager() {
|
||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_d16_to_d24 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d24, nullptr);
|
||||
}
|
||||
if (m_d16_to_d32 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d32, nullptr);
|
||||
}
|
||||
if (m_d24_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d24_to_d16, nullptr);
|
||||
}
|
||||
if (m_d32_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d32_to_d16, nullptr);
|
||||
}
|
||||
if (m_color_transform != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_color_transform, nullptr);
|
||||
for (auto pipeline: {m_d16_to_d24, m_d16_to_d32, m_d24_to_d16, m_d32_to_d16, m_color_transform,
|
||||
m_color_clear}) {
|
||||
if (pipeline != nullptr) {
|
||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_pipeline_layout != nullptr) {
|
||||
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
|
||||
@@ -220,42 +212,37 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
if (m_pipelines[slot] != nullptr) {
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
struct Shader {
|
||||
const uint32_t* code;
|
||||
size_t words;
|
||||
};
|
||||
static constexpr std::array<Shader, FamilyCount> shaders {{
|
||||
{GPU_TILER_STANDARD256_SPV, std::size(GPU_TILER_STANDARD256_SPV)},
|
||||
{GPU_TILER_STANDARD4_SPV, std::size(GPU_TILER_STANDARD4_SPV)},
|
||||
{GPU_TILER_STANDARD4_3D_SPV, std::size(GPU_TILER_STANDARD4_3D_SPV)},
|
||||
{GPU_TILER_STANDARD64_SPV, std::size(GPU_TILER_STANDARD64_SPV)},
|
||||
{GPU_TILER_STANDARD64_3D_SPV, std::size(GPU_TILER_STANDARD64_3D_SPV)},
|
||||
{GPU_TILER_PRT_SPV, std::size(GPU_TILER_PRT_SPV)},
|
||||
{GPU_TILER_PRT_3D_SPV, std::size(GPU_TILER_PRT_3D_SPV)},
|
||||
{GPU_TILER_RENDER_TARGET_SPV, std::size(GPU_TILER_RENDER_TARGET_SPV)},
|
||||
{GPU_TILER_DEPTH_SPV, std::size(GPU_TILER_DEPTH_SPV)},
|
||||
}};
|
||||
const uint32_t element_index = slot % BytesPerElementCount;
|
||||
static constexpr std::array<std::span<const uint32_t>, FamilyCount> shaders {
|
||||
GPU_TILER_STANDARD256_SPV, GPU_TILER_STANDARD4_SPV, GPU_TILER_STANDARD4_3D_SPV,
|
||||
GPU_TILER_STANDARD64_SPV, GPU_TILER_STANDARD64_3D_SPV, GPU_TILER_PRT_SPV,
|
||||
GPU_TILER_PRT_3D_SPV, GPU_TILER_RENDER_TARGET_SPV, GPU_TILER_DEPTH_SPV};
|
||||
const uint32_t element_index = slot % BytesPerElementCount;
|
||||
const uint32_t direction_index = slot / (FamilyCount * BytesPerElementCount);
|
||||
const uint32_t family_index = (slot / BytesPerElementCount) % FamilyCount;
|
||||
const uint32_t values[] {1u << element_index, direction_index};
|
||||
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
|
||||
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
|
||||
const auto module =
|
||||
CompileSPV({shaders[family_index].code, shaders[family_index].words}, m_graphics.device);
|
||||
m_pipelines[slot] = CreatePipeline(shaders[family_index], &specialization);
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
|
||||
vk::Pipeline TileManager::CreatePipeline(std::span<const uint32_t> code,
|
||||
const vk::SpecializationInfo* specialization) const {
|
||||
const auto module = CompileSPV(code, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
stage.pSpecializationInfo = specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_pipelines[slot]);
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create TileManager pipeline");
|
||||
return m_pipelines[slot];
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
void TileManager::Record(vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
@@ -434,39 +421,21 @@ uint32_t TileManager::ConversionRows(uint64_t offset, uint64_t row_stride, uint6
|
||||
|
||||
void TileManager::ConvertD16(Result source, Result target, D16Direction direction, bool d32,
|
||||
const D16Layout& layout) {
|
||||
vk::Pipeline* pipeline_pointer = nullptr;
|
||||
vk::Pipeline* pipeline_pointer = nullptr;
|
||||
std::span<const uint32_t> code;
|
||||
if (direction == D16Direction::Promote) {
|
||||
pipeline_pointer = d32 ? &m_d16_to_d32 : &m_d16_to_d24;
|
||||
code = GPU_TILER_PROMOTE_D16_SPV;
|
||||
} else {
|
||||
pipeline_pointer = d32 ? &m_d32_to_d16 : &m_d24_to_d16;
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
}
|
||||
auto& pipeline = *pipeline_pointer;
|
||||
if (pipeline == nullptr) {
|
||||
const uint32_t value = d32 ? 1u : 0u;
|
||||
const vk::SpecializationMapEntry entry {0, 0, sizeof(value)};
|
||||
const vk::SpecializationInfo specialization {1, &entry, sizeof(value), &value};
|
||||
const uint32_t* code = nullptr;
|
||||
size_t words = 0;
|
||||
if (direction == D16Direction::Promote) {
|
||||
code = GPU_TILER_PROMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_PROMOTE_D16_SPV);
|
||||
} else {
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
|
||||
}
|
||||
const auto module = CompileSPV({code, words}, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create D16 conversion pipeline");
|
||||
pipeline = CreatePipeline(code, &specialization);
|
||||
}
|
||||
|
||||
const uint64_t source_element =
|
||||
@@ -599,18 +568,7 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
|
||||
output.size < input.size);
|
||||
const auto pixels = static_cast<uint32_t>(input.size / element_bytes);
|
||||
if (m_color_transform == nullptr) {
|
||||
const auto module = CompileSPV(GPU_TILER_COLOR_TRANSFORM_SPV, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_color_transform);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create color transform pipeline");
|
||||
m_color_transform = CreatePipeline(GPU_TILER_COLOR_TRANSFORM_SPV, nullptr);
|
||||
}
|
||||
const auto input_binding = BindStorage(input, input.size);
|
||||
const auto output_binding = BindStorage(output, input.size);
|
||||
@@ -670,4 +628,60 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra
|
||||
return result;
|
||||
}
|
||||
|
||||
vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand) {
|
||||
struct Check {
|
||||
uint32_t metadata_base;
|
||||
uint32_t words;
|
||||
uint32_t keys;
|
||||
uint32_t expand;
|
||||
};
|
||||
static_assert(sizeof(Check) <= sizeof(Conversion));
|
||||
const uint32_t max_slices =
|
||||
m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers;
|
||||
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 ||
|
||||
metadata.size / 4 > UINT32_MAX);
|
||||
if (m_color_clear == nullptr) {
|
||||
m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr);
|
||||
m_clear_predicates = std::make_unique<Buffer>(
|
||||
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0,
|
||||
vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eConditionalRenderingEXT,
|
||||
uint64_t {max_slices} * ColorClearSlots * sizeof(uint32_t));
|
||||
}
|
||||
const auto source = BindStorage(metadata, metadata.size);
|
||||
const vk::DescriptorBufferInfo infos[] {source.info,
|
||||
{m_clear_predicates->Handle(), 0, VK_WHOLE_SIZE}};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
// Orders the metadata writers and the previous check's predicate reads before this check.
|
||||
vk::MemoryBarrier barrier {
|
||||
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite};
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 1, &barrier, 0, nullptr,
|
||||
0, nullptr);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
const Check push {source.base, static_cast<uint32_t>(metadata.size / 4 / slices), keys,
|
||||
expand};
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
|
||||
&push);
|
||||
command.dispatch(slices, 1, 1);
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllCommands, {}, 1, &barrier, 0, nullptr,
|
||||
0, nullptr);
|
||||
return m_clear_predicates->Handle();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -54,6 +54,13 @@ public:
|
||||
const D16Layout& layout);
|
||||
[[nodiscard]] Result TransformColor(Result input, ColorTransform transform, bool to_host);
|
||||
void TransformColor(Result input, Result output, ColorTransform transform, bool to_host);
|
||||
// Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots.
|
||||
static constexpr uint32_t ColorClearSlots = 8;
|
||||
// Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte
|
||||
// of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when
|
||||
// `expand` is set.
|
||||
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand);
|
||||
|
||||
private:
|
||||
friend struct TileManagerTestAccess;
|
||||
@@ -117,6 +124,8 @@ private:
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
const Tiling& tiling, bool clear_target);
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(uint32_t slot);
|
||||
[[nodiscard]] vk::Pipeline CreatePipeline(std::span<const uint32_t> code,
|
||||
const vk::SpecializationInfo* specialization) const;
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
@@ -130,6 +139,8 @@ private:
|
||||
vk::Pipeline m_d24_to_d16 = nullptr;
|
||||
vk::Pipeline m_d32_to_d16 = nullptr;
|
||||
vk::Pipeline m_color_transform = nullptr;
|
||||
vk::Pipeline m_color_clear = nullptr;
|
||||
std::unique_ptr<Buffer> m_clear_predicates;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -254,6 +254,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
|
||||
descriptor.Type() == Prospero::ImageType::kColor3D &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
const bool is_2d_msaa =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa &&
|
||||
descriptor.Type() == Prospero::ImageType::kColor2DMsaa && descriptor.Depth() == 0 &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
TileTextureBlockLayout tile_layout {};
|
||||
bool supported_tile = false;
|
||||
switch (tile) {
|
||||
@@ -284,8 +288,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool supported_swizzle =
|
||||
IsValidImageSwizzle(swizzle) &&
|
||||
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
|
||||
descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d || is_2d_msaa) &&
|
||||
supported_tile && descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
|
||||
!descriptor.MsaaDepth();
|
||||
}
|
||||
@@ -356,7 +360,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
}
|
||||
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
|
||||
view.mapping =
|
||||
storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
storage || resource.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
? vk::ComponentMapping {}
|
||||
: TextureGetComponentMapping(descriptor.DstSelXYZW(), surface_format.host_to_storage);
|
||||
switch (resource.dimension) {
|
||||
@@ -515,7 +519,9 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
}
|
||||
const auto samples = multisampled ? 1u << last_level : 1u;
|
||||
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
|
||||
const auto format = descriptor.Format();
|
||||
const auto format = ShaderRecompiler::IR::ReadsDepthBits(resource)
|
||||
? Prospero::BufferFormat::k16UNorm
|
||||
: descriptor.Format();
|
||||
const auto surface_format = TextureGetSurfaceFormatInfo(format);
|
||||
const bool shader_conversion =
|
||||
surface_format.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
@@ -579,6 +585,10 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
const auto alignment = desc.info.DataAlignment();
|
||||
EXIT_NOT_IMPLEMENTED(desc.info.data.size == 0 || alignment == 0 ||
|
||||
(address & (static_cast<uint64_t>(alignment) - 1u)) != 0);
|
||||
// Image heaps keep the descriptors of unmapped textures, which PS5 never samples.
|
||||
if (resource.heap && !m_context.IsMapped(address, desc.info.data.size)) {
|
||||
return {.layout = vk::ImageLayout::eGeneral};
|
||||
}
|
||||
if (storage) {
|
||||
ValidateStorageTexture(resource, descriptor, desc.info.data.size);
|
||||
}
|
||||
@@ -684,6 +694,10 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
||||
prepared.samplers.clear();
|
||||
prepared.shader_data.clear();
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
if (program.info.images[i].heap) {
|
||||
prepared.images[i] = {};
|
||||
continue;
|
||||
}
|
||||
auto binding = ResolveTexture(program.info.images[i], snapshot.images[i]);
|
||||
BindImage(binding.image_id, binding.desc.type == TextureCache::BindingType::Storage);
|
||||
binding.mip_views.swap(prepared.images[i].mip_views);
|
||||
@@ -846,14 +860,19 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
|
||||
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
|
||||
std::span<RenderColorInfo> colors) {
|
||||
bool uses_dma = false;
|
||||
bool uses_dma = false;
|
||||
bool uses_image_heap = false;
|
||||
FindBuffers(stages);
|
||||
for (auto* stage: stages) {
|
||||
uses_dma |= stage->runtime->program->info.uses_dma;
|
||||
uses_image_heap |= stage->runtime->program->info.uses_image_heap;
|
||||
}
|
||||
if (uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
if (uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
for (auto* stage: stages) {
|
||||
RebindImages(*stage);
|
||||
}
|
||||
@@ -953,6 +972,9 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pipeline.uses_image_heap) {
|
||||
m_context.GetImageHeap().Commit(buffer, pipeline_bind_point, pipeline.pipeline_layout);
|
||||
}
|
||||
m_descriptor_images.resize(image_descriptor_count);
|
||||
m_descriptor_writes.clear();
|
||||
m_descriptor_writes.reserve(write_count);
|
||||
@@ -1010,9 +1032,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
|
||||
range, vk_buffer);
|
||||
} else {
|
||||
image.Transit(image.info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
|
||||
image.Transit(image.ReadLayout(), vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
|
||||
}
|
||||
binding.layout = image.backing.state.layout;
|
||||
}
|
||||
@@ -1083,6 +1103,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
|
||||
const auto& resource = program.info.images[i];
|
||||
const auto& texture = descriptors.images[i];
|
||||
if (resource.heap) continue;
|
||||
EXIT_IF(texture.image_id &&
|
||||
resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size()));
|
||||
const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(resource);
|
||||
|
||||
@@ -0,0 +1,544 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/imageHeap.h"
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
namespace IR = ShaderRecompiler::IR;
|
||||
|
||||
// Smaller tables are cheap to materialize on each draw and are often transient.
|
||||
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
||||
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
||||
|
||||
template <typename Pred, typename Fn>
|
||||
void ForEachRun(size_t begin, size_t end, Pred&& pred, Fn&& fn) {
|
||||
while (begin < end) {
|
||||
if (!pred(begin)) {
|
||||
++begin;
|
||||
continue;
|
||||
}
|
||||
auto run = begin + 1u;
|
||||
while (run < end && pred(run)) ++run;
|
||||
fn(begin, run);
|
||||
begin = run;
|
||||
}
|
||||
}
|
||||
|
||||
// Grows [begin, end) to cover [first, last).
|
||||
void Widen(size_t& begin, size_t& end, size_t first, size_t last) {
|
||||
const bool empty = begin == end;
|
||||
begin = empty ? first : std::min(begin, first);
|
||||
end = empty ? last : std::max(end, last);
|
||||
}
|
||||
|
||||
// Guest bytes of a table that its tracker pages [begin, end) cover.
|
||||
std::pair<uint64_t, uint64_t> PageRange(uint64_t base, uint64_t size, size_t begin, size_t end) {
|
||||
const auto origin = Common::AlignDown(base, TRACKER_PAGE_SIZE);
|
||||
return {std::max(base, origin + begin * TRACKER_PAGE_SIZE),
|
||||
std::min(base + size, origin + end * TRACKER_PAGE_SIZE)};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t ImageHeap::DescriptorHash::operator()(const DescriptorValue& value) const noexcept {
|
||||
return XXH3_64bits(value.dwords.data(), sizeof(value.dwords));
|
||||
}
|
||||
|
||||
ImageHeap::ImageHeap(RenderContext& context, PageManager& page_manager)
|
||||
: m_context(context), m_page_manager(page_manager) {
|
||||
auto& graphics = context.GetGraphics();
|
||||
vk::PhysicalDeviceVulkan12Properties limits {};
|
||||
vk::PhysicalDeviceProperties2 properties {};
|
||||
properties.pNext = &limits;
|
||||
graphics.physical_device.getProperties2(&properties);
|
||||
EXIT_NOT_IMPLEMENTED(std::min({limits.maxDescriptorSetUpdateAfterBindSampledImages,
|
||||
limits.maxPerStageDescriptorUpdateAfterBindSampledImages,
|
||||
limits.maxPerStageUpdateAfterBindResources}) <
|
||||
IR::ImageHeapCapacity);
|
||||
|
||||
const vk::DescriptorBindingFlags binding_flags =
|
||||
vk::DescriptorBindingFlagBits::ePartiallyBound |
|
||||
vk::DescriptorBindingFlagBits::eUpdateAfterBind |
|
||||
vk::DescriptorBindingFlagBits::eUpdateUnusedWhilePending;
|
||||
vk::DescriptorSetLayoutBindingFlagsCreateInfo flags_info {};
|
||||
flags_info.bindingCount = 1;
|
||||
flags_info.pBindingFlags = &binding_flags;
|
||||
const vk::DescriptorSetLayoutBinding binding {0, vk::DescriptorType::eSampledImage,
|
||||
IR::ImageHeapCapacity,
|
||||
vk::ShaderStageFlagBits::eAll, nullptr};
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info {};
|
||||
layout_info.pNext = &flags_info;
|
||||
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool;
|
||||
layout_info.bindingCount = 1;
|
||||
layout_info.pBindings = &binding;
|
||||
EXIT_IF(graphics.device.createDescriptorSetLayout(&layout_info, nullptr, &m_layout) !=
|
||||
vk::Result::eSuccess);
|
||||
const vk::DescriptorPoolSize pool_size {vk::DescriptorType::eSampledImage,
|
||||
IR::ImageHeapCapacity};
|
||||
vk::DescriptorPoolCreateInfo pool_info {};
|
||||
pool_info.flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind;
|
||||
pool_info.maxSets = 1;
|
||||
pool_info.poolSizeCount = 1;
|
||||
pool_info.pPoolSizes = &pool_size;
|
||||
EXIT_IF(graphics.device.createDescriptorPool(&pool_info, nullptr, &m_pool) !=
|
||||
vk::Result::eSuccess);
|
||||
vk::DescriptorSetAllocateInfo allocate {};
|
||||
allocate.descriptorPool = m_pool;
|
||||
allocate.descriptorSetCount = 1;
|
||||
allocate.pSetLayouts = &m_layout;
|
||||
EXIT_IF(graphics.device.allocateDescriptorSets(&allocate, &m_set) != vk::Result::eSuccess);
|
||||
graphics.image_heap_layout = m_layout;
|
||||
// Slot word 0 selects element 0, which stays a null image.
|
||||
m_elements.push_back({.binding = {.layout = vk::ImageLayout::eShaderReadOnlyOptimal}});
|
||||
m_writes.push_back(0u);
|
||||
WriteDescriptors();
|
||||
}
|
||||
|
||||
ImageHeap::~ImageHeap() {
|
||||
for (const auto& memory: m_memories) Unwatch(*memory);
|
||||
auto& graphics = m_context.GetGraphics();
|
||||
graphics.image_heap_layout = nullptr;
|
||||
graphics.device.destroyDescriptorPool(m_pool, nullptr);
|
||||
graphics.device.destroyDescriptorSetLayout(m_layout, nullptr);
|
||||
}
|
||||
|
||||
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto size = request.size & ~uint64_t {3};
|
||||
if (size < MinTableBytes || request.count == 0u || request.step == 0u ||
|
||||
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||
return false;
|
||||
}
|
||||
auto memory = std::ranges::find_if(m_memories, [&](const auto& candidate) {
|
||||
return candidate->base == request.base && candidate->size == size;
|
||||
});
|
||||
if (memory == m_memories.end()) {
|
||||
if (!m_context.IsMapped(request.base, size)) return false;
|
||||
auto created = std::make_unique<Memory>();
|
||||
created->base = request.base;
|
||||
created->size = size;
|
||||
created->words.resize(size / 4u);
|
||||
created->dead.resize(size / 4u);
|
||||
created->dirty_pages.assign((Common::AlignUp(request.base + size, TRACKER_PAGE_SIZE) -
|
||||
Common::AlignDown(request.base, TRACKER_PAGE_SIZE)) /
|
||||
TRACKER_PAGE_SIZE,
|
||||
uint8_t {1});
|
||||
std::scoped_lock lock {m_mutex};
|
||||
memory = m_memories.insert(m_memories.end(), std::move(created));
|
||||
}
|
||||
// A draw refreshes a table and a view takes its changes once, so all programs of the draw see
|
||||
// one snapshot and one set of kinds.
|
||||
if ((*memory)->draw != m_draw) {
|
||||
if (!Refresh(**memory)) return false;
|
||||
(*memory)->draw = m_draw;
|
||||
}
|
||||
auto& views = (*memory)->views;
|
||||
const auto profile = IR::ImageHeapResource(*request.root, 0u);
|
||||
auto view = std::ranges::find_if(views, [&](const auto& candidate) {
|
||||
return candidate->step == request.step && candidate->first == request.first &&
|
||||
candidate->count == request.count && candidate->profile == profile;
|
||||
});
|
||||
if (view == views.end()) {
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
auto created = std::make_unique<View>(View {.memory = **memory,
|
||||
.step = request.step,
|
||||
.first = request.first,
|
||||
.count = request.count,
|
||||
.profile = profile,
|
||||
.resolve_epoch = epoch,
|
||||
.layout_epoch = epoch});
|
||||
created->slots.resize(size / 4u + 1u);
|
||||
created->buffer = std::make_unique<Buffer>(
|
||||
m_context.GetGraphics(), m_context.GetCommandScheduler(), MemoryUsage::DeviceLocal, 0,
|
||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
||||
size + 4u);
|
||||
UpdateProbes(*created, 0u, size);
|
||||
// The first upload also clears the slots no probe selects.
|
||||
created->dirty_begin = 0;
|
||||
created->dirty_end = created->slots.size();
|
||||
view = views.insert(views.end(), std::move(created));
|
||||
} else {
|
||||
// Empty after the first acquire in a draw, since the table refreshes once per draw.
|
||||
UpdateProbes(**view, (*view)->changed_begin, (*view)->changed_end);
|
||||
(*view)->changed_begin = (*view)->changed_end = 0;
|
||||
}
|
||||
if (std::ranges::find(m_active, view->get()) == m_active.end()) m_active.push_back(view->get());
|
||||
const auto& selected = **view;
|
||||
result = {.slots = selected.buffer->BufferDeviceAddress(),
|
||||
.slot_count = static_cast<uint32_t>(selected.slots.size() - 1u),
|
||||
.kinds = selected.kinds};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImageHeap::Refresh(Memory& memory) {
|
||||
// GPU writes leave no fault; reading the table downloads them.
|
||||
const bool gpu_dirty = m_context.GetBufferCache().HasGpuDirtyBytes(memory.base, memory.size);
|
||||
const auto pages = memory.dirty_pages.size();
|
||||
m_runs.clear();
|
||||
{
|
||||
std::scoped_lock lock {m_mutex};
|
||||
// Watch before reading, so a write during the read faults the page again.
|
||||
ForEachRun(
|
||||
0, pages, [&](size_t page) { return memory.dirty_pages[page] != 0u; },
|
||||
[&](size_t begin, size_t end) {
|
||||
std::fill(memory.dirty_pages.begin() + begin, memory.dirty_pages.begin() + end,
|
||||
uint8_t {0});
|
||||
Watch<true>(memory, begin, end);
|
||||
m_runs.emplace_back(begin, end);
|
||||
});
|
||||
}
|
||||
if (gpu_dirty) m_runs.assign(1u, {0u, pages});
|
||||
for (size_t run = 0; run < m_runs.size(); ++run) {
|
||||
const auto [address, end] = PageRange(memory.base, memory.size, m_runs[run].first,
|
||||
m_runs[run].second);
|
||||
m_scratch.resize((end - address) / 4u);
|
||||
if (!LibKernel::Memory::TryReadBufferBacking(address, m_scratch.data(), end - address)) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
for (; run < m_runs.size(); ++run) {
|
||||
MarkDirty(memory, m_runs[run].first, m_runs[run].second);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto words = std::span(memory.words).subspan((address - memory.base) / 4u,
|
||||
m_scratch.size());
|
||||
size_t first = 0;
|
||||
size_t last = words.size();
|
||||
while (first < last && words[first] == m_scratch[first]) ++first;
|
||||
while (first < last && words[last - 1u] == m_scratch[last - 1u]) --last;
|
||||
if (first == last) continue;
|
||||
std::copy(m_scratch.begin() + first, m_scratch.begin() + last, words.begin() + first);
|
||||
const auto offset = address - memory.base;
|
||||
for (auto& view: memory.views) {
|
||||
Widen(view->changed_begin, view->changed_end, offset + first * 4u, offset + last * 4u);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImageHeap::MarkDirty(Memory& memory, size_t begin, size_t end) {
|
||||
ForEachRun(
|
||||
begin, end, [&](size_t page) { return memory.dirty_pages[page] == 0u; },
|
||||
[&](size_t first, size_t last) {
|
||||
std::fill(memory.dirty_pages.begin() + first, memory.dirty_pages.begin() + last,
|
||||
uint8_t {1});
|
||||
Watch<false>(memory, first, last);
|
||||
});
|
||||
}
|
||||
|
||||
template <bool track>
|
||||
void ImageHeap::Watch(const Memory& memory, size_t begin, size_t end) {
|
||||
const auto [address, limit] = PageRange(memory.base, memory.size, begin, end);
|
||||
m_page_manager.UpdatePageWatchers<track>(address, limit - address);
|
||||
}
|
||||
|
||||
void ImageHeap::Unwatch(const Memory& memory) {
|
||||
ForEachRun(
|
||||
0, memory.dirty_pages.size(), [&](size_t page) { return memory.dirty_pages[page] == 0u; },
|
||||
[&](size_t begin, size_t end) { Watch<false>(memory, begin, end); });
|
||||
}
|
||||
|
||||
void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
for (auto& memory: m_memories) {
|
||||
const auto begin = std::max(address, memory->base);
|
||||
const auto end = std::min(address + size, memory->base + memory->size);
|
||||
if (begin >= end) continue;
|
||||
const auto origin = Common::AlignDown(memory->base, TRACKER_PAGE_SIZE);
|
||||
MarkDirty(*memory, (begin - origin) / TRACKER_PAGE_SIZE,
|
||||
(end - 1u - origin) / TRACKER_PAGE_SIZE + 1u);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageHeap::UpdateProbes(View& view, uint64_t begin, uint64_t end) {
|
||||
// A probe reads its descriptor from first + probe * step.
|
||||
const uint64_t bytes = view.profile.r128 ? 16u : 32u;
|
||||
auto probe = begin < view.first + bytes ? 0u : (begin - view.first - bytes) / view.step + 1u;
|
||||
for (; probe < view.count && view.first + probe * view.step < end; ++probe) {
|
||||
UpdateProbe(view, probe);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageHeap::UpdateProbe(View& view, uint64_t probe) {
|
||||
const auto& words = view.memory.words;
|
||||
const auto offset = (view.first + probe * view.step) / 4u;
|
||||
DescriptorValue value {.dword_count = 8u};
|
||||
std::copy_n(words.begin() + offset,
|
||||
std::min<size_t>(view.profile.r128 ? 4u : 8u, words.size() - offset),
|
||||
value.dwords.begin());
|
||||
// A buried word stays null in every view until its descriptor changes.
|
||||
auto& dead = view.memory.dead[offset];
|
||||
if (dead != 0u && dead != DescriptorHash {}(value)) dead = 0u;
|
||||
const auto kind = dead != 0u ? 0u : IR::ImageHeapKind(view.profile, value);
|
||||
Assign(view, offset, kind == 0u ? 0u : Intern(view, kind, value));
|
||||
}
|
||||
|
||||
void ImageHeap::Assign(View& view, size_t slot, uint32_t entry) {
|
||||
const auto previous = std::exchange(view.slots[slot], entry);
|
||||
Release(view, previous);
|
||||
if (previous != entry) Widen(view.dirty_begin, view.dirty_end, slot, slot + 1u);
|
||||
}
|
||||
|
||||
void ImageHeap::Bury() {
|
||||
if (m_unmapped.empty()) return;
|
||||
for (const auto& memory: m_memories) {
|
||||
for (const auto& view: memory->views) {
|
||||
bool buried = false;
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
const auto& binding = m_elements[index].binding;
|
||||
if (binding.image_id && std::ranges::any_of(m_unmapped, [&](GuestRange range) {
|
||||
return ImageRangeOverlaps(binding.desc.info.data, range);
|
||||
})) {
|
||||
m_elements[index].stale = buried = true;
|
||||
}
|
||||
}
|
||||
if (!buried) continue;
|
||||
for (uint64_t probe = 0; probe < view->count; ++probe) {
|
||||
const auto slot = (view->first + probe * view->step) / 4u;
|
||||
const auto& element = m_elements[view->slots[slot] & ElementMask];
|
||||
if (!element.stale) continue;
|
||||
memory->dead[slot] = DescriptorHash {}(element.value);
|
||||
Assign(*view, slot, 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_unmapped.clear();
|
||||
}
|
||||
|
||||
uint32_t ImageHeap::Intern(View& view, uint32_t kind, const DescriptorValue& value) {
|
||||
const auto arm = static_cast<uint32_t>(std::ranges::find(view.kind_keys, kind) -
|
||||
view.kind_keys.begin());
|
||||
if (arm == view.kind_keys.size()) {
|
||||
view.kind_keys.push_back(kind);
|
||||
view.kinds.push_back(value);
|
||||
}
|
||||
auto [found, inserted] = view.elements.try_emplace(value, 0u);
|
||||
if (inserted) {
|
||||
found->second = Allocate();
|
||||
auto& element = m_elements[found->second];
|
||||
element.value = value;
|
||||
element.kind = kind;
|
||||
view.pending.push_back(found->second);
|
||||
}
|
||||
++m_elements[found->second].refs;
|
||||
return arm << IR::ImageHeapKindShift | found->second;
|
||||
}
|
||||
|
||||
void ImageHeap::Release(View& view, uint32_t entry) {
|
||||
const auto index = entry & ElementMask;
|
||||
if (index == 0u || --m_elements[index].refs != 0u) return;
|
||||
auto& element = m_elements[index];
|
||||
if (const auto found = view.elements.find(element.value);
|
||||
found != view.elements.end() && found->second == index) {
|
||||
view.elements.erase(found);
|
||||
}
|
||||
Unreference(element);
|
||||
view.released.push_back(index);
|
||||
}
|
||||
|
||||
void ImageHeap::Unreference(Element& element) {
|
||||
if (auto* image = m_context.GetTextureCache().m_slot_images.try_get(element.binding.image_id)) {
|
||||
image->heap_refs.fetch_sub(1u, std::memory_order_relaxed);
|
||||
image->tick_accessed_last = m_context.GetCommandScheduler().CurrentTick();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ImageHeap::Allocate() {
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
while (!m_retired.empty() && scheduler.IsFree(m_retired.front().first)) {
|
||||
m_free.push_back(m_retired.front().second);
|
||||
m_retired.pop_front();
|
||||
}
|
||||
if (m_free.empty()) {
|
||||
if (m_elements.size() == IR::ImageHeapCapacity) {
|
||||
EXIT("image heaps exceed %u resident images\n", IR::ImageHeapCapacity);
|
||||
}
|
||||
m_elements.emplace_back();
|
||||
return static_cast<uint32_t>(m_elements.size() - 1u);
|
||||
}
|
||||
const auto index = m_free.back();
|
||||
m_free.pop_back();
|
||||
m_elements[index] = {};
|
||||
return index;
|
||||
}
|
||||
|
||||
void ImageHeap::Retire(View& view) {
|
||||
const auto tick = m_context.GetCommandScheduler().CurrentTick();
|
||||
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
||||
view.released.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto& textures = m_context.GetTextureCache();
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
for (auto* view: m_active) {
|
||||
bool stale = false;
|
||||
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
||||
if (view->resolve_epoch != epoch) {
|
||||
view->resolve_epoch = epoch;
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
const auto& binding = m_elements[index].binding;
|
||||
if (!binding.image_id) continue;
|
||||
const auto* image = textures.m_slot_images.try_get(binding.image_id);
|
||||
if (image == nullptr || !image->registered || image->depth_id ||
|
||||
image->binding.needs_rebind) {
|
||||
mark(index);
|
||||
} else {
|
||||
(void)textures.FindTexture(binding.image_id, binding.desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Discovery applies the color metadata clears written since the last draw.
|
||||
std::erase_if(view->dcc, [&](uint32_t index) {
|
||||
const auto& element = m_elements[index];
|
||||
if (element.refs == 0u || element.stale) return true;
|
||||
if (executor.ResolveTexture(IR::ImageHeapResource(view->profile, element.kind),
|
||||
element.value).image_id == element.binding.image_id) {
|
||||
return false;
|
||||
}
|
||||
mark(index);
|
||||
return true;
|
||||
});
|
||||
if (stale) {
|
||||
// A changed image needs a new element; pending draws still read the old one. The new
|
||||
// element keeps the old value, since a view takes table changes only when acquired.
|
||||
std::erase_if(view->elements,
|
||||
[&](const auto& item) { return m_elements[item.second].stale; });
|
||||
for (uint64_t probe = 0; probe < view->count; ++probe) {
|
||||
const auto slot = (view->first + probe * view->step) / 4u;
|
||||
const auto& element = m_elements[view->slots[slot] & ElementMask];
|
||||
if (!element.stale) continue;
|
||||
const auto value = element.value; // Intern may grow m_elements.
|
||||
Assign(*view, slot, Intern(*view, element.kind, value));
|
||||
}
|
||||
}
|
||||
for (const auto index: view->pending) {
|
||||
if (m_elements[index].refs == 0u) continue;
|
||||
auto binding = executor.ResolveTexture(
|
||||
IR::ImageHeapResource(view->profile, m_elements[index].kind),
|
||||
m_elements[index].value);
|
||||
if (binding.image_id) {
|
||||
auto& image = textures.GetImage(binding.image_id);
|
||||
image.heap_refs.fetch_add(1u, std::memory_order_relaxed);
|
||||
binding.layout = image.ReadLayout();
|
||||
binding.image_view = textures.FindTexture(binding.image_id, binding.desc);
|
||||
if (binding.desc.info.metadata.kind == ImageMetadataKind::Dcc) {
|
||||
view->dcc.push_back(index);
|
||||
}
|
||||
view->away.push_back(index);
|
||||
}
|
||||
m_elements[index].binding = std::move(binding);
|
||||
m_writes.push_back(index);
|
||||
}
|
||||
view->pending.clear();
|
||||
}
|
||||
WriteDescriptors();
|
||||
}
|
||||
|
||||
void ImageHeap::WriteDescriptors() {
|
||||
if (m_writes.empty()) return;
|
||||
std::vector<vk::DescriptorImageInfo> infos;
|
||||
std::vector<vk::WriteDescriptorSet> writes(m_writes.size());
|
||||
infos.reserve(m_writes.size());
|
||||
for (size_t write = 0; write < m_writes.size(); ++write) {
|
||||
const auto& binding = m_elements[m_writes[write]].binding;
|
||||
infos.push_back({nullptr, binding.image_view, binding.layout});
|
||||
writes[write].dstSet = m_set;
|
||||
writes[write].dstArrayElement = m_writes[write];
|
||||
writes[write].descriptorCount = 1;
|
||||
writes[write].descriptorType = vk::DescriptorType::eSampledImage;
|
||||
writes[write].pImageInfo = &infos.back();
|
||||
}
|
||||
m_context.GetGraphics().device.updateDescriptorSets(static_cast<uint32_t>(writes.size()),
|
||||
writes.data(), 0, nullptr);
|
||||
m_writes.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point,
|
||||
vk::PipelineLayout layout) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto& textures = m_context.GetTextureCache();
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
for (auto* view: m_active) {
|
||||
if (view->dirty_begin != view->dirty_end) {
|
||||
const auto slots = std::span(view->slots).subspan(view->dirty_begin,
|
||||
view->dirty_end - view->dirty_begin);
|
||||
auto& stream = m_context.GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
|
||||
const auto offset = stream.Copy(slots.data(), slots.size_bytes(), sizeof(uint32_t));
|
||||
view->buffer->CopyFrom(buffer, stream, offset, view->dirty_begin * sizeof(uint32_t),
|
||||
slots.size_bytes());
|
||||
view->dirty_begin = view->dirty_end = 0;
|
||||
}
|
||||
Retire(*view);
|
||||
if (view->layout_epoch != epoch) {
|
||||
view->layout_epoch = epoch;
|
||||
view->away.clear();
|
||||
for (const auto& [value, index]: view->elements) view->away.push_back(index);
|
||||
}
|
||||
// This draw's own bindings own the layouts of its attachments and storage images. An image
|
||||
// this draw's discovery replaced lives until the tick ends, and its slots still select it.
|
||||
std::erase_if(view->away, [&](uint32_t index) {
|
||||
const auto& element = m_elements[index];
|
||||
auto* image = textures.m_slot_images.try_get(element.binding.image_id);
|
||||
if (element.refs == 0u || image == nullptr) return true;
|
||||
if (image->binding.is_target || image->binding.shader_write ||
|
||||
image->binding.force_general) {
|
||||
return false;
|
||||
}
|
||||
const auto& view_info = element.binding.desc.view_info;
|
||||
image->Transit(image->ReadLayout(), vk::AccessFlagBits2::eShaderRead,
|
||||
ImageSubresourceRange {view_info.base_level, view_info.level_count,
|
||||
view_info.base_layer, view_info.layer_count},
|
||||
buffer.Handle());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
m_active.clear();
|
||||
buffer.Handle().bindDescriptorSets(bind_point, layout, IR::ImageHeapSet, 1, &m_set, 0,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
// Descriptors of textures in the range stay null even once it is mapped again.
|
||||
if (!m_unmapped.empty() && m_unmapped.back().End() == address) {
|
||||
m_unmapped.back().size += size;
|
||||
} else {
|
||||
m_unmapped.push_back({address, size});
|
||||
}
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
std::scoped_lock lock {m_mutex};
|
||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
||||
return false;
|
||||
}
|
||||
Unwatch(*memory);
|
||||
for (auto& view: memory->views) {
|
||||
std::erase(m_active, view.get());
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
Unreference(m_elements[index]);
|
||||
view->released.push_back(index);
|
||||
}
|
||||
Retire(*view);
|
||||
if (scheduler.Active()) {
|
||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,138 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class Buffer;
|
||||
class CommandBuffer;
|
||||
class PageManager;
|
||||
class RenderContext;
|
||||
class RenderExecutor;
|
||||
struct ImageHeapTestAccess;
|
||||
|
||||
// Guest image tables that shaders index by GPU-computed offsets, kept resident in one bindless
|
||||
// sampled-image array. Each view of a table owns device slot words, one per table dword, that
|
||||
// select an array element. CPU writes to a table fault in its snapshot, which each draw refreshes
|
||||
// once; a view takes the changes when a draw first acquires it. Changes to the images behind
|
||||
// elements bump Image::heap_epoch.
|
||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||
public:
|
||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||
~ImageHeap();
|
||||
KYTY_CLASS_NO_COPY(ImageHeap);
|
||||
|
||||
// Starts a draw or dispatch, before its programs acquire views.
|
||||
void BeginDraw() {
|
||||
++m_draw;
|
||||
Bury();
|
||||
}
|
||||
bool Acquire(const ShaderRecompiler::IR::ImageHeapRequest& request,
|
||||
ShaderRecompiler::IR::ImageHeapView& result) override;
|
||||
// Image discovery: resolves new elements and rediscovers changed images.
|
||||
void Rebind(RenderExecutor& executor);
|
||||
// Recording: uploads slots, moves resident images to their read layout and binds the array.
|
||||
void Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point, vk::PipelineLayout layout);
|
||||
// CPU write fault, on any thread.
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
|
||||
private:
|
||||
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
||||
|
||||
struct DescriptorHash {
|
||||
size_t operator()(const DescriptorValue& value) const noexcept;
|
||||
};
|
||||
|
||||
struct Element {
|
||||
DescriptorValue value;
|
||||
TextureBinding binding;
|
||||
uint32_t kind = 0;
|
||||
uint32_t refs = 0; // Slots of the owning view that select it.
|
||||
bool stale = false;
|
||||
};
|
||||
|
||||
struct Memory;
|
||||
|
||||
struct View {
|
||||
Memory& memory;
|
||||
uint64_t step = 0;
|
||||
uint64_t first = 0;
|
||||
uint64_t count = 0;
|
||||
ShaderRecompiler::IR::ImageResource profile;
|
||||
std::vector<uint32_t> slots; // Plus a trailing null slot.
|
||||
std::vector<uint32_t> kind_keys;
|
||||
std::vector<DescriptorValue> kinds; // One representative per kind_keys entry.
|
||||
std::unordered_map<DescriptorValue, uint32_t, DescriptorHash> elements;
|
||||
std::vector<uint32_t> pending; // Not yet resolved.
|
||||
std::vector<uint32_t> released; // No slot selects them after the next upload.
|
||||
std::vector<uint32_t> away; // Not yet moved to the read layout.
|
||||
std::vector<uint32_t> dcc; // Color metadata clears apply on each discovery.
|
||||
std::unique_ptr<Buffer> buffer;
|
||||
size_t dirty_begin = 0; // Slots to upload.
|
||||
size_t dirty_end = 0;
|
||||
size_t changed_begin = 0; // Table bytes changed since it was probed.
|
||||
size_t changed_end = 0;
|
||||
uint64_t resolve_epoch = 0;
|
||||
uint64_t layout_epoch = 0;
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
uint64_t base = 0;
|
||||
uint64_t size = 0;
|
||||
std::vector<uint32_t> words;
|
||||
std::vector<uint8_t> dirty_pages; // Unwatched, so unread since a write.
|
||||
std::vector<uint64_t> dead; // Per word, a buried descriptor's hash.
|
||||
uint64_t draw = 0; // Last draw that refreshed it.
|
||||
std::vector<std::unique_ptr<View>> views;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool Refresh(Memory& memory);
|
||||
void MarkDirty(Memory& memory, size_t begin, size_t end);
|
||||
template <bool track>
|
||||
void Watch(const Memory& memory, size_t begin, size_t end);
|
||||
void Unwatch(const Memory& memory);
|
||||
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
||||
void UpdateProbe(View& view, uint64_t probe);
|
||||
void Assign(View& view, size_t slot, uint32_t entry);
|
||||
void Bury();
|
||||
uint32_t Intern(View& view, uint32_t kind, const DescriptorValue& value);
|
||||
void Release(View& view, uint32_t entry);
|
||||
void Unreference(Element& element);
|
||||
uint32_t Allocate();
|
||||
void Retire(View& view);
|
||||
void WriteDescriptors();
|
||||
|
||||
RenderContext& m_context;
|
||||
PageManager& m_page_manager;
|
||||
std::mutex m_mutex; // Fault-visible Memory state and m_memories.
|
||||
std::vector<std::unique_ptr<Memory>> m_memories;
|
||||
std::vector<View*> m_active; // Acquired since the last commit.
|
||||
uint64_t m_draw = 1;
|
||||
std::vector<Element> m_elements;
|
||||
std::vector<uint32_t> m_free;
|
||||
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
||||
std::vector<std::pair<size_t, size_t>> m_runs;
|
||||
std::vector<uint32_t> m_scratch;
|
||||
std::vector<uint32_t> m_writes; // Elements whose descriptors changed.
|
||||
std::vector<GuestRange> m_unmapped; // Since the last Bury.
|
||||
vk::DescriptorSetLayout m_layout = nullptr;
|
||||
vk::DescriptorPool m_pool = nullptr;
|
||||
vk::DescriptorSet m_set = nullptr;
|
||||
|
||||
friend struct ImageHeapTestAccess;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
@@ -353,6 +353,7 @@ struct PipelineCache::ProgramCache {
|
||||
.user_data = user_data,
|
||||
.shader_base = params.Base(),
|
||||
.read_specialization_memory = ReadShaderGuestMemory,
|
||||
.image_heap = image_heap,
|
||||
};
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderComputeInputInfo>) {
|
||||
runtime.workgroup_counts = input_info.workgroup_counts;
|
||||
@@ -480,8 +481,8 @@ struct PipelineCache::ProgramCache {
|
||||
return permutation.handle;
|
||||
}
|
||||
|
||||
explicit ProgramCache(GraphicContext& graphics)
|
||||
: graphics(graphics),
|
||||
ProgramCache(GraphicContext& graphics, ShaderRecompiler::IR::ImageHeapSource* image_heap)
|
||||
: graphics(graphics), image_heap(image_heap),
|
||||
// AMD GPUs (vendor 0x1002) run NMin/NMax as v_min/v_max, which give the guest's NaN and
|
||||
// signed-zero results; GLSL.std.450 alone does not order -0 below +0.
|
||||
profile {.maintenance8 = graphics.maintenance8_enabled,
|
||||
@@ -501,12 +502,14 @@ struct PipelineCache::ProgramCache {
|
||||
std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs;
|
||||
ProgramKey lookup_key;
|
||||
GraphicContext& graphics;
|
||||
ShaderRecompiler::IR::ImageHeapSource* image_heap;
|
||||
const ShaderRecompiler::Spirv::Profile profile;
|
||||
uint64_t next_shader_id = 0;
|
||||
};
|
||||
|
||||
PipelineCache::PipelineCache(GraphicContext& graphics)
|
||||
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics)),
|
||||
PipelineCache::PipelineCache(GraphicContext& graphics,
|
||||
ShaderRecompiler::IR::ImageHeapSource* image_heap)
|
||||
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics, image_heap)),
|
||||
m_next_driver_cache_save(std::chrono::steady_clock::now() + DriverCacheSaveInterval) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
InitializeDriverCache();
|
||||
|
||||
@@ -30,6 +30,10 @@ class UserConfig;
|
||||
struct ComputeShaderInfo;
|
||||
} // namespace HW
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
class ImageHeapSource;
|
||||
} // namespace ShaderRecompiler::IR
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct PipelineStaticParameters {
|
||||
@@ -104,7 +108,7 @@ struct ShaderProgram {
|
||||
// The owning renderer serializes access, including saves while the GPU is running.
|
||||
class PipelineCache {
|
||||
public:
|
||||
explicit PipelineCache(GraphicContext& graphics);
|
||||
PipelineCache(GraphicContext& graphics, ShaderRecompiler::IR::ImageHeapSource* image_heap);
|
||||
~PipelineCache();
|
||||
KYTY_CLASS_NO_COPY(PipelineCache);
|
||||
void Save();
|
||||
@@ -114,6 +118,7 @@ public:
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
vk::DescriptorSetLayout descriptor_set_layout = nullptr;
|
||||
bool uses_push_descriptors = false;
|
||||
bool uses_image_heap = false;
|
||||
};
|
||||
|
||||
struct GraphicsPrograms {
|
||||
|
||||
@@ -52,6 +52,7 @@ vk::MemoryBarrier MakeShaderWriteDependency() {
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite |
|
||||
vk::AccessFlagBits::eVertexAttributeRead |
|
||||
vk::AccessFlagBits::eIndexRead | vk::AccessFlagBits::eUniformRead |
|
||||
vk::AccessFlagBits::eIndirectCommandRead |
|
||||
vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
|
||||
@@ -473,20 +473,23 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
for (const auto& stage: vertex_info) {
|
||||
const auto native_stage = NativeShaderStage(stage.logical_stage);
|
||||
AddLayoutBindings(descriptor_bindings, *stage.stage.program, native_stage);
|
||||
pipeline.uses_image_heap |= stage.stage.program->info.uses_image_heap;
|
||||
graphics_stages |= native_stage;
|
||||
}
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
pipeline.uses_image_heap |= ps_input_info->stage.program->info.uses_image_heap;
|
||||
}
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
const vk::PushConstantRange push_constants {graphics_stages, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
const std::array set_layouts {pipeline.descriptor_set_layout, graphics.image_heap_layout};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.setLayoutCount = pipeline.uses_image_heap ? 2u : 1u;
|
||||
pipeline_layout_info.pSetLayouts = set_layouts.data();
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_info.pPushConstantRanges = &push_constants;
|
||||
|
||||
@@ -615,13 +618,15 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
AddLayoutBindings(descriptor_bindings, *input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
pipeline.uses_image_heap = input_info.stage.program->info.uses_image_heap;
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
const vk::PushConstantRange push_constants {vk::ShaderStageFlagBits::eCompute, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
const std::array set_layouts {pipeline.descriptor_set_layout, graphics.image_heap_layout};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.setLayoutCount = pipeline.uses_image_heap ? 2u : 1u;
|
||||
pipeline_layout_info.pSetLayouts = set_layouts.data();
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_info.pPushConstantRanges = &push_constants;
|
||||
|
||||
|
||||
@@ -48,6 +48,13 @@ enum class DrawOffsetSource : uint8_t {
|
||||
IndirectArgs,
|
||||
};
|
||||
|
||||
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
|
||||
struct DrawIndirectArgs {
|
||||
uint64_t address = 0;
|
||||
uint32_t count = 0;
|
||||
uint32_t stride = 0;
|
||||
};
|
||||
|
||||
struct DrawIndexArgs {
|
||||
uint32_t index_count = 0;
|
||||
const void* index_addr = nullptr;
|
||||
@@ -57,6 +64,8 @@ struct DrawIndexArgs {
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
// With indirect records, index_count is INDEX_BUFFER_SIZE.
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct DrawAutoArgs {
|
||||
@@ -66,6 +75,7 @@ struct DrawAutoArgs {
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct SubmitInfo {
|
||||
@@ -161,18 +171,19 @@ public:
|
||||
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
|
||||
const PipelineCache::Pipeline& pipeline,
|
||||
std::span<PreparedBindings* const> bindings);
|
||||
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
|
||||
private:
|
||||
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
// False, with nothing drawn, when the draw's indirect records need CPU counts.
|
||||
bool DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
|
||||
bool DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
|
||||
struct GraphicsBindings {
|
||||
std::array<PreparedBindings, 3> vertex;
|
||||
std::optional<PreparedBindings> pixel;
|
||||
};
|
||||
|
||||
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
|
||||
std::span<RenderColorInfo> colors);
|
||||
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
|
||||
|
||||
@@ -311,6 +311,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
input_info.workgroup_counts[axis] = threads / size + (threads % size != 0u);
|
||||
}
|
||||
}
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
const auto compute_program =
|
||||
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
|
||||
if (!compute_program) return;
|
||||
@@ -363,6 +364,9 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
if (program.info.uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
if (program.info.uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
RebindImages(bindings);
|
||||
BindSharedMemory(m_context, input_info, bindings);
|
||||
RebindBuffers(bindings);
|
||||
@@ -497,6 +501,7 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
ShaderComputeInputInfo input_info {};
|
||||
input_info.async_compute = async_compute;
|
||||
input_info.indirect_args_address = args_addr;
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
|
||||
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
|
||||
if (!compute_program) return;
|
||||
@@ -511,6 +516,9 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
BindSharedMemory(m_context, input_info, bindings, args_addr);
|
||||
if (program.info.uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
RebindImages(bindings);
|
||||
// Acquiring arguments can merge cache buffers; finalize shader bindings afterward.
|
||||
const auto [args_buffer, args_offset] = m_context.GetBufferCache().ObtainBuffer(
|
||||
|
||||
@@ -13,9 +13,10 @@ namespace Libs::Graphics {
|
||||
RenderContext::RenderContext(GraphicContext& graphics)
|
||||
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
|
||||
m_descriptor_heap(graphics, m_command_scheduler.GetMasterSemaphore()),
|
||||
m_pipeline_cache(graphics), m_sampler_cache(graphics),
|
||||
m_pipeline_cache(graphics, &m_image_heap), m_sampler_cache(graphics),
|
||||
m_buffer_cache(graphics, m_command_scheduler, m_page_manager, m_texture_cache),
|
||||
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache) {
|
||||
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache),
|
||||
m_image_heap(*this, m_page_manager) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
|
||||
@@ -64,6 +65,7 @@ bool RenderContext::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) no
|
||||
if (access == PageFaultAccess::Write) {
|
||||
m_buffer_cache.InvalidateMemory(fault_vaddr, fault_size);
|
||||
m_texture_cache.InvalidateMemory(fault_vaddr, fault_size);
|
||||
m_image_heap.InvalidateMemory(fault_vaddr, fault_size);
|
||||
} else {
|
||||
m_buffer_cache.ReadMemory(fault_vaddr, fault_size);
|
||||
}
|
||||
@@ -76,6 +78,7 @@ bool RenderContext::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_buffer_cache.InvalidateMemory(vaddr, size);
|
||||
m_texture_cache.InvalidateMemory(vaddr, size);
|
||||
m_image_heap.InvalidateMemory(vaddr, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -112,6 +115,7 @@ void RenderContext::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_buffer_cache.InvalidateMemory(vaddr, size);
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_image_heap.UnmapMemory(vaddr, size);
|
||||
std::lock_guard lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.Subtract(vaddr, size);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/imageHeap.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
@@ -43,6 +44,7 @@ public:
|
||||
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
|
||||
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
|
||||
DescriptorHeap& GetDescriptorHeap() { return m_descriptor_heap; }
|
||||
ImageHeap& GetImageHeap() { return m_image_heap; }
|
||||
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
|
||||
BufferCache& GetBufferCache() { return m_buffer_cache; }
|
||||
TextureCache& GetTextureCache() { return m_texture_cache; }
|
||||
@@ -76,6 +78,7 @@ private:
|
||||
PageManager m_page_manager;
|
||||
BufferCache m_buffer_cache;
|
||||
TextureCache m_texture_cache;
|
||||
ImageHeap m_image_heap;
|
||||
mutable std::shared_mutex m_mapped_ranges_mutex;
|
||||
RangeSet m_mapped_ranges;
|
||||
std::unique_ptr<GuestGpu> m_gpu;
|
||||
|
||||
@@ -631,10 +631,11 @@ bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer,
|
||||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
|
||||
}
|
||||
|
||||
// vertex_offset is the first vertex of non-indexed draws.
|
||||
struct DrawEmitInfo {
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_vertex = 0;
|
||||
uint32_t first_instance = 0;
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_instance = 0;
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct DrawIndexBufferSource {
|
||||
@@ -892,6 +893,7 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
}
|
||||
}
|
||||
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
|
||||
buffer.GetContext().GetImageHeap().BeginDraw();
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
||||
}
|
||||
@@ -1043,8 +1045,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
emit.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex,
|
||||
emit.first_instance);
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count,
|
||||
static_cast<uint32_t>(emit.vertex_offset), emit.first_instance);
|
||||
}
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
@@ -1054,7 +1056,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
vk_buffer.drawIndexed(4, draw.instance_count, i, emit.vertex_offset,
|
||||
emit.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(4, draw.instance_count, i + emit.first_vertex,
|
||||
vk_buffer.draw(4, draw.instance_count,
|
||||
i + static_cast<uint32_t>(emit.vertex_offset),
|
||||
emit.first_instance);
|
||||
}
|
||||
}
|
||||
@@ -1075,7 +1078,7 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo&
|
||||
EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().maintenance8_enabled ||
|
||||
!Prospero::IsRectList(ucfg.GetPrimType()) ||
|
||||
draw.IsIndexed() || draw.index_count != 3 || draw.instance_count != 1 ||
|
||||
emit.first_vertex != 0 || emit.first_instance != 0 ||
|
||||
emit.vertex_offset != 0 || emit.first_instance != 0 ||
|
||||
state.color_count != 1 || color.target_slot != 0 || !depth.image_id ||
|
||||
color.desc.info.samples != 1 ||
|
||||
depth.desc.info.extent.width != color.Extent().width ||
|
||||
@@ -1189,6 +1192,14 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
|
||||
index_binding = PrepareIndexBuffer(buffer, index_source);
|
||||
}
|
||||
std::pair<Buffer*, uint64_t> args_buffer {};
|
||||
if (emit.indirect.address != 0) {
|
||||
const auto size = static_cast<uint64_t>(emit.indirect.count - 1) * emit.indirect.stride +
|
||||
(draw.IsIndexed() ? sizeof(vk::DrawIndexedIndirectCommand)
|
||||
: sizeof(vk::DrawIndirectCommand));
|
||||
args_buffer = m_context.GetBufferCache().ObtainBuffer(emit.indirect.address, size, false);
|
||||
EXIT_IF((args_buffer.second & 3u) != 0);
|
||||
}
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "CreatePipeline");
|
||||
}
|
||||
@@ -1225,12 +1236,12 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (draw_dwords != 0) {
|
||||
std::array<uint32_t, PushData::MeshDrawDwordCount> draw_data {};
|
||||
if (mesh_active) {
|
||||
draw_data = {
|
||||
draw.index_count,
|
||||
draw.IsIndexed() ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
|
||||
emit.first_instance, index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
draw_data = {draw.index_count,
|
||||
static_cast<uint32_t>(emit.vertex_offset),
|
||||
emit.first_instance,
|
||||
index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
} else {
|
||||
draw_data[0] = buffer.GetUserConfig().GetObjectId();
|
||||
}
|
||||
@@ -1275,8 +1286,14 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
if (mesh_active) {
|
||||
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
|
||||
} else {
|
||||
} else if (args_buffer.first == nullptr) {
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit);
|
||||
} else if (draw.IsIndexed()) {
|
||||
vk_buffer.drawIndexedIndirect(args_buffer.first->Handle(), args_buffer.second,
|
||||
emit.indirect.count, emit.indirect.stride);
|
||||
} else {
|
||||
vk_buffer.drawIndirect(args_buffer.first->Handle(), args_buffer.second,
|
||||
emit.indirect.count, emit.indirect.stride);
|
||||
}
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::Draws);
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::DrawInstances, draw.instance_count);
|
||||
@@ -1294,7 +1311,30 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need CPU counts, as
|
||||
// mesh and quad-list draws do. Indirect records carry their own offsets.
|
||||
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||
const ShaderVertexInputInfo& vs,
|
||||
DrawOffsetSource source,
|
||||
int32_t first_vertex,
|
||||
uint32_t first_instance,
|
||||
const DrawIndirectArgs& indirect) {
|
||||
if (indirect.address != 0) {
|
||||
if (vs.stage.program->stage == ShaderType::Mesh ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return DrawEmitInfo {.indirect = indirect};
|
||||
}
|
||||
if (source == DrawOffsetSource::IndirectArgs) {
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
return vs.fetch_embedded ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
|
||||
}
|
||||
const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs);
|
||||
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
|
||||
}
|
||||
|
||||
bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawIndexArgs& args) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
@@ -1309,19 +1349,19 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
reinterpret_cast<uint64_t>(args.index_addr));
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (args.index_count == 0 || args.instance_count == 0) {
|
||||
return;
|
||||
if (args.index_count == 0 || (args.instance_count == 0 && args.indirect.address == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
|
||||
DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DrawHasValidVertexShader(sh_ctx)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
@@ -1347,7 +1387,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
@@ -1386,30 +1426,23 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size,
|
||||
args.index_addr);
|
||||
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto [vertex_offset, instance_offset] =
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
|
||||
DrawEmitInfo emit {};
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.vertex_offset = native_indirect ? 0 : vertex_offset + args.base_vertex;
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
|
||||
primitive_restart);
|
||||
const auto emit = ResolveDrawEmit(ucfg, state.vertex_info[0], args.offset_source,
|
||||
args.base_vertex, args.first_instance, args.indirect);
|
||||
if (emit) {
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, *emit, index_source,
|
||||
primitive_restart);
|
||||
}
|
||||
ResetBindings();
|
||||
return emit.has_value();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
|
||||
bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
@@ -1423,19 +1456,19 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
args.first_instance);
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (args.vertex_count == 0 || args.instance_count == 0) {
|
||||
return;
|
||||
if (args.indirect.address == 0 && (args.vertex_count == 0 || args.instance_count == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
|
||||
DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DrawHasValidVertexShader(sh_ctx)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
@@ -1461,12 +1494,12 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool rect_list = Prospero::IsRectList(ucfg.GetPrimType());
|
||||
@@ -1480,24 +1513,18 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
sh_ctx.GetVs().es_regs.data_addr, sh_ctx.GetVs().gs_regs.data_addr);
|
||||
}
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
LogDrawStateIfNeeded(buffer, draw, state, 0, nullptr);
|
||||
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto [vertex_offset, instance_offset] =
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
DrawEmitInfo emit {};
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.first_vertex = native_indirect ? 0 :
|
||||
static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false);
|
||||
const auto emit = ResolveDrawEmit(ucfg, state.vertex_info[0], args.offset_source,
|
||||
static_cast<int32_t>(args.first_vertex), args.first_instance,
|
||||
args.indirect);
|
||||
if (emit) {
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, *emit, {}, false);
|
||||
}
|
||||
ResetBindings();
|
||||
return emit.has_value();
|
||||
}
|
||||
|
||||
bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_target_slice_offset) {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#version 450
|
||||
|
||||
// One workgroup per metadata slice; see TileManager::CheckColorClear.
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
layout(set = 0, binding = 0, std430) buffer Metadata { uint data[]; } metadata;
|
||||
layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[]; } predicates;
|
||||
|
||||
layout(push_constant, std430) uniform Check {
|
||||
uint metadata_base;
|
||||
uint words;
|
||||
uint keys;
|
||||
uint expand;
|
||||
} params;
|
||||
|
||||
const uint Slots = 8u; // TileManager::ColorClearSlots
|
||||
|
||||
shared uint key;
|
||||
shared uint mismatch;
|
||||
|
||||
void main() {
|
||||
const uint slice = gl_WorkGroupID.x;
|
||||
const uint base = (params.metadata_base >> 2u) + slice * params.words;
|
||||
// Each invocation reads, and may rewrite, only its own words.
|
||||
if (gl_LocalInvocationIndex == 0u) {
|
||||
key = metadata.data[base] & 0xffu;
|
||||
mismatch = 0u;
|
||||
}
|
||||
barrier();
|
||||
const uint pattern = key * 0x01010101u;
|
||||
bool same = true;
|
||||
for (uint i = gl_LocalInvocationIndex; same && i < params.words; i += gl_WorkGroupSize.x) {
|
||||
same = metadata.data[base + i] == pattern;
|
||||
}
|
||||
if (!same) {
|
||||
atomicOr(mismatch, 1u);
|
||||
}
|
||||
barrier();
|
||||
const uint slot = key >> 5u;
|
||||
const bool hit = mismatch == 0u && (key & 0x1fu) == 0u && (params.keys & (1u << slot)) != 0u;
|
||||
if (gl_LocalInvocationIndex < Slots) {
|
||||
predicates.data[slice * Slots + gl_LocalInvocationIndex] =
|
||||
uint(hit && slot == gl_LocalInvocationIndex);
|
||||
}
|
||||
if (hit && params.expand != 0u) {
|
||||
for (uint i = gl_LocalInvocationIndex; i < params.words; i += gl_WorkGroupSize.x) {
|
||||
metadata.data[base + i] = 0xffffffffu;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,6 +310,10 @@ void GpuActivityRows(const Snapshot& shown) {
|
||||
Row("CPU waiting on GPU",
|
||||
Fmt("%.0f ms/s (%.0f waits/s)", Rate(shown, Counter::GpuWaitNs) / 1e6,
|
||||
Rate(shown, Counter::GpuWaits)));
|
||||
Row("DCC/CMASK readbacks",
|
||||
Fmt("%.0f ms/s (%.0f/s)", Rate(shown, Counter::ColorMetadataReadbackNs) / 1e6,
|
||||
Rate(shown, Counter::ColorMetadataReadbacks)));
|
||||
Row("DCC/CMASK GPU checks", Fmt("%.0f/s", Rate(shown, Counter::ColorMetadataChecks)));
|
||||
}
|
||||
|
||||
void CompilationRows(const Snapshot& shown) {
|
||||
|
||||
@@ -221,7 +221,7 @@ public:
|
||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||
bool Flip(uint32_t micros);
|
||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||
void Wait(VideoOutConfig& cfg, int index);
|
||||
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
||||
|
||||
private:
|
||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||
@@ -240,6 +240,7 @@ private:
|
||||
Graphics::Presenter::Frame* frame;
|
||||
bool premultiplied_alpha;
|
||||
};
|
||||
void SignalDoneLocked();
|
||||
|
||||
Graphics::Presenter& m_presenter;
|
||||
Common::Mutex m_mutex;
|
||||
@@ -250,6 +251,7 @@ private:
|
||||
std::list<Request> m_cpu_requests;
|
||||
std::list<Request> m_cancelled_requests;
|
||||
bool m_processing = false;
|
||||
bool m_gpu_waiting = false;
|
||||
uint64_t m_next_request_id = 1;
|
||||
};
|
||||
|
||||
@@ -953,7 +955,7 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
|
||||
}
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
}
|
||||
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||
@@ -1033,7 +1035,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
||||
it = queue->erase(it);
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_submit_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
@@ -1205,19 +1207,26 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto has_request = [this, &cfg, index] {
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
||||
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
};
|
||||
while (has_request()) {
|
||||
m_done_cond_var.Wait(&m_mutex);
|
||||
void FlipQueue::SignalDoneLocked() {
|
||||
m_done_cond_var.SignalAll();
|
||||
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
|
||||
if (m_gpu_waiting) {
|
||||
m_gpu_waiting = false;
|
||||
m_presenter.Renderer().GetGpu().Wake();
|
||||
}
|
||||
}
|
||||
|
||||
// A display buffer is safe for rendering once it is neither displayed nor waiting to be.
|
||||
bool FlipQueue::IsSafeForRendering(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
const bool safe = cfg.flip_status.currentBuffer != index &&
|
||||
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
|
||||
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
m_gpu_waiting = !safe;
|
||||
return safe;
|
||||
}
|
||||
|
||||
bool FlipQueue::Flip(uint32_t micros) {
|
||||
KYTY_PROFILER_BLOCK("FlipQueue::Flip");
|
||||
|
||||
@@ -1311,7 +1320,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
it = m_requests.erase(it);
|
||||
}
|
||||
m_processing = false;
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
for (size_t i = count; i != 0; i--) {
|
||||
@@ -1694,12 +1703,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
||||
}
|
||||
|
||||
void VideoOutDriver::WaitFlipDone(int handle, int index) {
|
||||
bool VideoOutDriver::IsSafeForRendering(int handle, int index) {
|
||||
auto* ctx = m_impl->Get(handle);
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
||||
m_impl->GetFlipQueue().Wait(*ctx, index);
|
||||
return m_impl->GetFlipQueue().IsSafeForRendering(*ctx, index);
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot(int handle);
|
||||
void WaitFlipDone(int handle, int index);
|
||||
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
||||
|
||||
[[nodiscard]] Impl& State() noexcept;
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noe
|
||||
features.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
|
||||
features.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
|
||||
features.shaderStorageBufferArrayNonUniformIndexing = VK_TRUE;
|
||||
features.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE;
|
||||
features.descriptorBindingUpdateUnusedWhilePending = VK_TRUE;
|
||||
features.descriptorBindingPartiallyBound = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -295,6 +298,14 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
check_feature(features12.shaderStorageBufferArrayNonUniformIndexing,
|
||||
"shaderStorageBufferArrayNonUniformIndexing",
|
||||
required_features12.shaderStorageBufferArrayNonUniformIndexing);
|
||||
check_feature(features12.descriptorBindingSampledImageUpdateAfterBind,
|
||||
"descriptorBindingSampledImageUpdateAfterBind",
|
||||
required_features12.descriptorBindingSampledImageUpdateAfterBind);
|
||||
check_feature(features12.descriptorBindingUpdateUnusedWhilePending,
|
||||
"descriptorBindingUpdateUnusedWhilePending",
|
||||
required_features12.descriptorBindingUpdateUnusedWhilePending);
|
||||
check_feature(features12.descriptorBindingPartiallyBound, "descriptorBindingPartiallyBound",
|
||||
required_features12.descriptorBindingPartiallyBound);
|
||||
check_feature(features13.robustImageAccess, "robustImageAccess");
|
||||
check_feature(robustness2.nullDescriptor, "nullDescriptor");
|
||||
check_feature(features13.dynamicRendering, "dynamicRendering",
|
||||
@@ -328,6 +339,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
check_feature(device_features2.features.independentBlend, "independentBlend");
|
||||
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
||||
check_feature(device_features2.features.drawIndirectFirstInstance,
|
||||
"drawIndirectFirstInstance");
|
||||
check_feature(device_features2.features.multiDrawIndirect, "multiDrawIndirect");
|
||||
|
||||
if (!skip_device) {
|
||||
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
|
||||
@@ -499,6 +513,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
image_atomic_int64.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &image_atomic_int64;
|
||||
}
|
||||
const bool conditional_rendering_extension =
|
||||
HasExtension(device_extensions, VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceConditionalRenderingFeaturesEXT conditional_rendering {};
|
||||
if (conditional_rendering_extension) {
|
||||
conditional_rendering.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &conditional_rendering;
|
||||
}
|
||||
const bool barycentric_extension =
|
||||
HasExtension(device_extensions, VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_barycentric {};
|
||||
@@ -572,6 +593,8 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
|
||||
feedback_dynamic.attachmentFeedbackLoopDynamicState;
|
||||
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
|
||||
graphics.conditional_rendering_enabled =
|
||||
conditional_rendering_extension && conditional_rendering.conditionalRendering;
|
||||
LOGF("Vulkan maintenance8 support: %s\n",
|
||||
graphics.maintenance8_enabled ? "true" : "false");
|
||||
LOGF("Vulkan depth feedback support: %s\n",
|
||||
@@ -597,10 +620,14 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.depthClamp = VK_TRUE;
|
||||
#endif
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderStorageImageMultisample =
|
||||
supported_features2.features.shaderStorageImageMultisample;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.dualSrcBlend = VK_TRUE;
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
device_features.drawIndirectFirstInstance = VK_TRUE;
|
||||
device_features.multiDrawIndirect = VK_TRUE;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
device_features.depthBiasClamp = VK_TRUE;
|
||||
device_features.shaderClipDistance = VK_TRUE;
|
||||
@@ -609,6 +636,11 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.multiViewport = VK_TRUE;
|
||||
device_features.fillModeNonSolid = VK_TRUE;
|
||||
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
|
||||
// Storage image sample counts beyond 1x require the feature.
|
||||
if (!device_features.shaderStorageImageMultisample) {
|
||||
graphics.physical_device_properties.limits.storageImageSampleCounts =
|
||||
vk::SampleCountFlagBits::e1;
|
||||
}
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
device_features.shaderFloat64 =
|
||||
@@ -670,6 +702,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
maintenance8.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &maintenance8;
|
||||
}
|
||||
if (graphics.conditional_rendering_enabled) {
|
||||
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &conditional_rendering;
|
||||
}
|
||||
create_info.pQueueCreateInfos = &queue_create_info;
|
||||
create_info.queueCreateInfoCount = 1;
|
||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||
@@ -1046,7 +1082,8 @@ void WindowContext::CreateVulkan() {
|
||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
||||
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME}) {
|
||||
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
|
||||
VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME}) {
|
||||
if (HasExtension(available_extensions, extension)) {
|
||||
device_extensions.push_back(extension);
|
||||
}
|
||||
|
||||
@@ -169,6 +169,9 @@ uint32_t Builder::AddString(const char* text) {
|
||||
}
|
||||
|
||||
void Builder::AddName(uint32_t target, const char* name) {
|
||||
if (!m_emit_names) {
|
||||
return;
|
||||
}
|
||||
std::vector<uint32_t> operands = {target};
|
||||
AppendString(operands, name);
|
||||
AppendInstruction(m_debug, spv::OpName, operands);
|
||||
|
||||
@@ -67,6 +67,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t AddString(const char* text);
|
||||
void EnableNames() { m_emit_names = true; }
|
||||
void AddName(uint32_t target, const char* name);
|
||||
template <typename... Args>
|
||||
void AddAnnotation(spv::Op opcode, const Args&... operands) {
|
||||
@@ -159,6 +160,7 @@ private:
|
||||
std::map<std::string, uint32_t> m_import_ids;
|
||||
std::map<std::vector<uint32_t>, uint32_t> m_declaration_ids;
|
||||
size_t m_unpatched_phi_incomings = 0;
|
||||
bool m_emit_names = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
@@ -161,6 +161,7 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
if (!kind.has_value()) {
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
if (image.heap) continue;
|
||||
if (image.mip_count == 0u ||
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
@@ -226,6 +227,7 @@ std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo inp
|
||||
std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one",
|
||||
program.shader_hash);
|
||||
state.debug_source = state.builder.AddString(name);
|
||||
state.builder.EnableNames();
|
||||
}
|
||||
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
|
||||
state.lane_count =
|
||||
|
||||
@@ -120,7 +120,14 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
} else {
|
||||
EXIT("invalid image resource class");
|
||||
}
|
||||
if (image.dimension == ImageDimension::Dim1D || image.dimension == ImageDimension::Dim1DArray) {
|
||||
state.builder.RequireCapability(sampled == 1 ? spv::CapabilitySampled1D
|
||||
: spv::CapabilityImage1D);
|
||||
}
|
||||
const auto& info = ImageDimensionInfoFor(image.dimension);
|
||||
if (sampled == 2 && info.multisampled != 0u) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageMultisample);
|
||||
}
|
||||
const auto scalar_type =
|
||||
image.atomic64 ? TypeU64(state) : ImageScalarType(state, image.numeric_class);
|
||||
return state.builder.Type(spv::OpTypeImage, scalar_type,
|
||||
@@ -144,14 +151,20 @@ void DecorateNonUniformDescriptor(EmitterState& state, uint32_t id, spv::Capabil
|
||||
state.builder.RequireCapability(capability);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, id, spv::DecorationNonUniform);
|
||||
}
|
||||
|
||||
ImageDefinition& ImageDescriptorDefinition(EmitterState& state, const IR::ImageResource& image,
|
||||
IR::DescriptorBindingKind kind) {
|
||||
return (image.heap ? state.heap_images : state.images)[IR::ImageBindingIndex(kind)];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip,
|
||||
uint32_t array_index) {
|
||||
const auto pointer = ImageDescriptorPointer(state, resource, mip, array_index);
|
||||
const auto image = state.builder.AllocateId();
|
||||
const auto kind = *IR::DescriptorBindingForImage(state.program.info.images[resource]);
|
||||
state.builder.AddFunction(spv::OpLoad, state.images[IR::ImageBindingIndex(kind)].type,
|
||||
const auto& resource_info = state.program.info.images[resource];
|
||||
const auto kind = *IR::DescriptorBindingForImage(resource_info);
|
||||
state.builder.AddFunction(spv::OpLoad, ImageDescriptorDefinition(state, resource_info, kind).type,
|
||||
image, pointer);
|
||||
if (array_index != 0u) {
|
||||
DecorateNonUniformDescriptor(state, image,
|
||||
@@ -184,13 +197,13 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto image = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto kind = *IR::DescriptorBindingForImage(image_resource);
|
||||
auto& sampled_type = state.images[IR::ImageBindingIndex(kind)].sampled_type;
|
||||
if (sampled_type == 0) {
|
||||
sampled_type = state.builder.Type(
|
||||
spv::OpTypeSampledImage, state.images[IR::ImageBindingIndex(kind)].type);
|
||||
auto& definition = ImageDescriptorDefinition(state, image_resource,
|
||||
*IR::DescriptorBindingForImage(image_resource));
|
||||
if (definition.sampled_type == 0) {
|
||||
definition.sampled_type = state.builder.Type(spv::OpTypeSampledImage, definition.type);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
|
||||
state.builder.AddFunction(spv::OpSampledImage, definition.sampled_type, sampled_image, image,
|
||||
sampler_id);
|
||||
if (array_index != 0u || sampler_dynamic) {
|
||||
DecorateNonUniformDescriptor(state, sampled_image, spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
}
|
||||
@@ -202,26 +215,31 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(mip >= image.mip_count);
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
EXIT_IF(!kind.has_value() || (image.heap && array_index == 0u));
|
||||
if (array_index == 0u) {
|
||||
array_index = ConstantU32(state, image.descriptor_index + mip);
|
||||
}
|
||||
const auto& definition = state.images[IR::ImageBindingIndex(*kind)];
|
||||
const auto& definition = ImageDescriptorDefinition(state, image, *kind);
|
||||
return DescriptorElementPointer(state, definition.pointer_type, definition.variable,
|
||||
array_index, *kind, resource,
|
||||
"image descriptor array was not emitted");
|
||||
}
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel, uint32_t array_index) {
|
||||
uint32_t texel, uint32_t array_index, uint32_t sample) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
if (!image.atomic) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
}
|
||||
const auto EmitWrite = [&](uint32_t mip) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip, array_index),
|
||||
coord, texel);
|
||||
const auto descriptor = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
if (sample == 0u) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, descriptor, coord, texel);
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpImageWrite, descriptor, coord, texel,
|
||||
spv::ImageOperandsSampleMask, sample);
|
||||
}
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::Dynamic) {
|
||||
EmitWrite(0);
|
||||
|
||||
@@ -132,7 +132,7 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl
|
||||
enable(flags.bank_mask, 2u);
|
||||
enable(flags.row_mask, 4u);
|
||||
// Only row shifts (0x101-0x10f, 0x111-0x11f) can vacate a lane; other controls always read one.
|
||||
const bool row_shift = !flags.dpp8 && flags.control > 0x100u && flags.control < 0x120u &&
|
||||
const bool row_shift = flags.control > 0x100u && flags.control < 0x120u &&
|
||||
flags.control != 0x110u;
|
||||
if (!flags.bound_control && row_shift) {
|
||||
writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable,
|
||||
|
||||
@@ -389,6 +389,7 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
|
||||
const auto format = image.conversion_format;
|
||||
if (format == Prospero::BufferFormat::kInvalid) return {};
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
if (IR::ReadsDepthBits(image)) return info;
|
||||
const bool raw_signed = IR::RawSignedStorageFormat(image, format) != Prospero::BufferFormat::kInvalid;
|
||||
EXIT_IF((Prospero::RemapTextureFormat(format) == format && !raw_signed) || info.component_count == 0u ||
|
||||
info.component_count > 4u);
|
||||
@@ -424,6 +425,7 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
|
||||
const auto numeric_class = ctx.state.program.info.images[mem.resource].numeric_class;
|
||||
const auto scalar_type = ImageScalarType(ctx.state, numeric_class);
|
||||
const bool integer = info.packed_bitfield || info.type == Format::ComponentType::Uint;
|
||||
uint32_t packed = 0;
|
||||
if (info.packed_bitfield) {
|
||||
packed = ctx.state.builder.AllocateId();
|
||||
@@ -441,8 +443,16 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type,
|
||||
components[component], texel, component);
|
||||
// UNorm backing preserves the guest's filtering; scaling precedes swizzle constants.
|
||||
const auto unorm_max = static_cast<float>((1u << info.component_bits[component]) - 1u);
|
||||
components[component] = Binary(ctx.state, spv::OpFMul, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 255.0f));
|
||||
ConstantF32Value(ctx.state, unorm_max));
|
||||
if (integer) {
|
||||
// Depth bits: round back to the stored UNorm code.
|
||||
components[component] =
|
||||
Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state),
|
||||
Binary(ctx.state, spv::OpFAdd, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 0.5f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t component = info.component_count; component < 4u; component++) {
|
||||
@@ -454,20 +464,19 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
const auto selector = (swizzle >> (component * 3u)) & 7u;
|
||||
if (selector == 1u) {
|
||||
selected[component] = info.packed_bitfield
|
||||
selected[component] = integer
|
||||
? ConstantU32(ctx.state, Format::FormattedConstantBits(info, Format::FormattedSourceKind::One))
|
||||
: ConstantF32Value(ctx.state, 1.0f);
|
||||
} else if (selector >= 4u) {
|
||||
selected[component] = components[selector - 4u];
|
||||
} else {
|
||||
selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 0u)
|
||||
: ZeroF32(ctx.state);
|
||||
selected[component] = integer ? ConstantU32(ctx.state, 0u) : ZeroF32(ctx.state);
|
||||
}
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
ImageVectorType(ctx.state, numeric_class, 4), result, selected[0],
|
||||
selected[1], selected[2], selected[3]);
|
||||
integer ? TypeU32Vector(ctx.state, 4) : TypeF32Vector(ctx.state, 4),
|
||||
result, selected[0], selected[1], selected[2], selected[3]);
|
||||
return info.type == Format::ComponentType::Unorm
|
||||
? Unary(ctx.state, spv::OpBitcast, TypeF32Vector(ctx.state, 4), result) : result;
|
||||
}
|
||||
@@ -691,6 +700,21 @@ auto EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t resul
|
||||
image.indirect_resources.size() < 2u) {
|
||||
ctx.Fail(inst, "has no indirect image runtime mapping");
|
||||
}
|
||||
if (image.heap) {
|
||||
const auto entry = EmitImageHeapEntry(state, key, image.indirect_mapping_offset);
|
||||
const auto kinds = static_cast<uint32_t>(image.indirect_resources.size()) - 1u;
|
||||
// One kind leaves every slot word a bare element.
|
||||
if (kinds == 1u) return emit(image.indirect_resources[1], entry);
|
||||
const auto element = Binary(state, spv::OpBitwiseAnd, TypeU32(state), entry,
|
||||
ConstantU32(state, (1u << IR::ImageHeapKindShift) - 1u));
|
||||
const auto EmitKind = [&](uint32_t kind) {
|
||||
return emit(image.indirect_resources[kind + 1u], element);
|
||||
};
|
||||
return EmitIndexSwitch(state,
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), entry,
|
||||
ConstantU32(state, IR::ImageHeapKindShift)),
|
||||
kinds, result_type, EmitKind);
|
||||
}
|
||||
const auto selected = EmitIndirectResourceIndex(
|
||||
state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u);
|
||||
const auto SameType = [&](uint32_t lhs, uint32_t rhs) {
|
||||
@@ -870,14 +894,19 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageQueryLod) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
const auto dimension = image.dimension;
|
||||
const auto sampled = MakeSampledImage(state, mem.resource,
|
||||
LoadSamplerDescriptor(state, mem.sampler));
|
||||
const auto lod = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components,
|
||||
image.cube));
|
||||
const auto sampler = LoadSamplerDescriptor(state, mem.sampler);
|
||||
const auto lod = EmitImageAccess(ctx, inst, TypeF32Vector(state, 2),
|
||||
[&](uint32_t resource, uint32_t array_index) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto query = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), query,
|
||||
MakeSampledImage(state, resource, sampler, 0u, array_index),
|
||||
CoordF32(ctx, mem, *address, 0,
|
||||
ImageDimensionInfoFor(candidate.dimension).spatial_components,
|
||||
candidate.cube));
|
||||
return query;
|
||||
});
|
||||
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0)};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
@@ -932,9 +961,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto mip_lod = candidate.mip_mode == IR::ImageMipMode::Dynamic
|
||||
? LodU32(ctx, mem, *address, mem.image_dimension) : 0u;
|
||||
const auto coord = CoordU32(ctx, mem, *address, candidate.dimension);
|
||||
const auto& dimension_info = ImageDimensionInfoFor(candidate.dimension);
|
||||
const auto sample = dimension_info.multisampled != 0u
|
||||
? AddressU32(ctx, mem, *address, dimension_info.coordinate_components) : 0u;
|
||||
const auto texel = StoreTexel(ctx, selected, ctx.Arg(inst, 2),
|
||||
candidate.numeric_class == Prospero::TextureNumericClass::Uint);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index, sample);
|
||||
});
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -130,6 +130,7 @@ struct EmitterState {
|
||||
uint32_t lds_u64_variable = 0;
|
||||
std::array<uint32_t, 2> scratch_variable {};
|
||||
std::array<ImageDefinition, IR::ImageBindingCount> images {};
|
||||
std::array<ImageDefinition, IR::ImageBindingCount> heap_images {};
|
||||
uint32_t sampler_variable = 0;
|
||||
uint32_t sampler_type = 0;
|
||||
uint32_t sampler_pointer_type = 0;
|
||||
@@ -339,8 +340,9 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
uint32_t mip = 0, uint32_t array_index = 0,
|
||||
bool sampler_dynamic = false);
|
||||
|
||||
// sample is 0 (no id) for single-sampled images.
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel, uint32_t array_index);
|
||||
uint32_t texel, uint32_t array_index, uint32_t sample);
|
||||
|
||||
spv::ExecutionModel ExecutionModelForStage(ShaderType stage);
|
||||
|
||||
@@ -430,6 +432,8 @@ struct MemoryResourceAccess {
|
||||
|
||||
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
|
||||
uint32_t search_iterations, uint32_t default_resource);
|
||||
// Slot word of an image heap byte offset; offsets past the heap select the null element.
|
||||
uint32_t EmitImageHeapEntry(EmitterState& state, uint32_t key, uint32_t mapping_offset);
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem,
|
||||
uint32_t array_index = 0u);
|
||||
|
||||
|
||||
@@ -38,19 +38,39 @@ uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t LoadFlattenedSrt(EmitterState& state, uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.flattened_srt_variable, ConstantU32(state, 0), index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t EmitImageHeapEntry(EmitterState& state, uint32_t key, uint32_t mapping_offset) {
|
||||
const auto Mapping = [&](uint32_t word) {
|
||||
return LoadFlattenedSrt(state, ConstantU32(state, mapping_offset + word));
|
||||
};
|
||||
// Keys past the table select its trailing null slot.
|
||||
const auto index = EmitUMin32(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), key,
|
||||
ConstantU32(state, 2u)),
|
||||
Mapping(0u));
|
||||
const auto offset = Unary(state, spv::OpUConvert, TypeU64(state),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index,
|
||||
ConstantU32(state, 2u)));
|
||||
const auto pointer = Unary(state, spv::OpConvertUToPtr, TypePhysicalU32Pointer(state),
|
||||
Binary(state, spv::OpIAdd, TypeU64(state),
|
||||
PackU64(state, Mapping(1u), Mapping(2u)), offset));
|
||||
const auto entry = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), entry, pointer,
|
||||
spv::MemoryAccessAlignedMask, 4u);
|
||||
return entry;
|
||||
}
|
||||
|
||||
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
|
||||
uint32_t search_iterations, uint32_t default_resource) {
|
||||
const auto LoadMapping = [&](uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
|
||||
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
|
||||
index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
};
|
||||
const auto mapping = ConstantU32(state, mapping_offset);
|
||||
const auto count = LoadMapping(mapping);
|
||||
const auto count = LoadFlattenedSrt(state, mapping);
|
||||
if (search_iterations == 0u) {
|
||||
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), key,
|
||||
ConstantU32(state, 2u));
|
||||
@@ -58,7 +78,7 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
const auto safe = Select(state, TypeU32(state), valid, index, ConstantU32(state, 0u));
|
||||
const auto entry = Binary(state, spv::OpIAdd, TypeU32(state), safe,
|
||||
ConstantU32(state, mapping_offset + 1u));
|
||||
return Select(state, TypeU32(state), valid, LoadMapping(entry),
|
||||
return Select(state, TypeU32(state), valid, LoadFlattenedSrt(state, entry),
|
||||
ConstantU32(state, default_resource));
|
||||
}
|
||||
const auto entry_at = [&](uint32_t index) {
|
||||
@@ -76,7 +96,7 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
ConstantU32(state, 1u));
|
||||
const auto probe = Select(state, TypeU32(state), active, mid, ConstantU32(state, 0u));
|
||||
const auto less = Binary(state, spv::OpULessThan, TypeBool(state),
|
||||
LoadMapping(entry_at(probe)), key);
|
||||
LoadFlattenedSrt(state, entry_at(probe)), key);
|
||||
low = Select(state, TypeU32(state),
|
||||
Binary(state, spv::OpLogicalAnd, TypeBool(state), active, less),
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), mid, ConstantU32(state, 1u)), low);
|
||||
@@ -87,9 +107,10 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
const auto in_range = Binary(state, spv::OpULessThan, TypeBool(state), low, count);
|
||||
const auto entry = entry_at(Select(state, TypeU32(state), in_range, low, ConstantU32(state, 0u)));
|
||||
const auto match = Binary(state, spv::OpLogicalAnd, TypeBool(state), in_range,
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), LoadMapping(entry), key));
|
||||
const auto resource = LoadMapping(Binary(state, spv::OpIAdd, TypeU32(state), entry,
|
||||
ConstantU32(state, 1u)));
|
||||
Binary(state, spv::OpIEqual, TypeBool(state),
|
||||
LoadFlattenedSrt(state, entry), key));
|
||||
const auto resource = LoadFlattenedSrt(
|
||||
state, Binary(state, spv::OpIAdd, TypeU32(state), entry, ConstantU32(state, 1u)));
|
||||
return Select(state, TypeU32(state), match, resource, ConstantU32(state, default_resource));
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +206,17 @@ uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
|
||||
return state.builder.Type(spv::OpTypeArray, TypeF32(state), ConstantU32(state, count));
|
||||
}
|
||||
|
||||
void DefineImageArray(EmitterState& state, ImageDefinition& definition, const IR::ImageResource& image,
|
||||
uint32_t count, uint32_t set, uint32_t binding) {
|
||||
definition.type = ImageType(state, image);
|
||||
const auto array = state.builder.Type(spv::OpTypeArray, definition.type, ConstantU32(state, count));
|
||||
definition.variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, spv::StorageClassUniformConstant, array), spv::StorageClassUniformConstant);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, definition.variable, spv::DecorationDescriptorSet, set);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, definition.variable, spv::DecorationBinding, binding);
|
||||
definition.pointer_type = TypePointer(state, spv::StorageClassUniformConstant, definition.type);
|
||||
}
|
||||
|
||||
void DefineDescriptors(EmitterState& state) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
auto& info = state.program.info;
|
||||
@@ -222,6 +233,14 @@ void DefineDescriptors(EmitterState& state) {
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
EXIT("shader image has an invalid descriptor type or mip count");
|
||||
}
|
||||
if (image.heap) {
|
||||
// Heap kinds alias one array; the root itself is never accessed.
|
||||
auto& definition = state.heap_images[IR::ImageBindingIndex(*kind)];
|
||||
if (image.indirect_resources.empty() && definition.variable == 0) {
|
||||
DefineImageArray(state, definition, image, IR::ImageHeapCapacity, IR::ImageHeapSet, 0u);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto& count = counts[static_cast<size_t>(*kind)];
|
||||
image.descriptor_index = count;
|
||||
if (count == 0) image_types[IR::ImageBindingIndex(*kind)] = ℑ
|
||||
@@ -331,21 +350,11 @@ void DefineDescriptors(EmitterState& state) {
|
||||
default: {
|
||||
EXIT_IF(IR::ImageBindingResourceClass(kind) ==
|
||||
IR::ImageResourceClass::None);
|
||||
const auto& image = *image_types[IR::ImageBindingIndex(kind)];
|
||||
const auto name = "image_" + std::to_string(static_cast<uint32_t>(kind));
|
||||
auto& definition = state.images[IR::ImageBindingIndex(kind)];
|
||||
definition.type = ImageType(state, image);
|
||||
definition.variable = Define(ArrayType(definition.type), name.c_str(),
|
||||
spv::StorageClassUniformConstant);
|
||||
definition.pointer_type = TypePointer(
|
||||
state, spv::StorageClassUniformConstant, definition.type);
|
||||
if (image.dimension == ImageDimension::Dim1D ||
|
||||
image.dimension == ImageDimension::Dim1DArray) {
|
||||
state.builder.RequireCapability(image.resource_class ==
|
||||
IR::ImageResourceClass::Sampled
|
||||
? spv::CapabilitySampled1D
|
||||
: spv::CapabilityImage1D);
|
||||
}
|
||||
DefineImageArray(state, definition, *image_types[IR::ImageBindingIndex(kind)],
|
||||
counts[index], 0u, IR::NativeBinding(state.program.stage, kind));
|
||||
state.builder.AddName(definition.variable,
|
||||
("image_" + std::to_string(static_cast<uint32_t>(kind))).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -752,7 +761,7 @@ void DefineModule(EmitterState& state) {
|
||||
if (state.program.info.buffer_u16) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBuffer16BitAccess);
|
||||
}
|
||||
if (state.program.info.uses_dma) {
|
||||
if (state.program.info.uses_dma || state.program.info.uses_image_heap) {
|
||||
state.builder.RequireCapability(spv::CapabilityInt64);
|
||||
state.builder.RequireCapability(spv::CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
@@ -820,7 +829,7 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
|
||||
}
|
||||
state.builder.RequireExtension("SPV_KHR_float_controls");
|
||||
state.builder.AddMemoryModel(state.program.info.uses_dma
|
||||
state.builder.AddMemoryModel(state.program.info.uses_dma || state.program.info.uses_image_heap
|
||||
? spv::AddressingModelPhysicalStorageBuffer64
|
||||
: spv::AddressingModelLogical,
|
||||
spv::MemoryModelGLSL450);
|
||||
|
||||
@@ -6,13 +6,14 @@ namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
namespace {
|
||||
|
||||
struct MemoryOpcodeInfo {
|
||||
uint32_t encoding = 0;
|
||||
Opcode decoded = Opcode::UNSUPPORTED;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
uint32_t encoding = 0;
|
||||
Opcode decoded = Opcode::UNSUPPORTED;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
uint32_t data_components = 0; // D16 formats pack two components per dword
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo SMEM_OPCODE_LIST[] = {
|
||||
@@ -73,9 +74,13 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||
{0x71u, Opcode::BUFFER_GL0_INV, 0, 0},
|
||||
{0x72u, Opcode::BUFFER_GL1_INV, 0, 0},
|
||||
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true},
|
||||
{0x81u, Opcode::BUFFER_LOAD_FORMAT_D16_XY, 1, 16, false, false, true, 2},
|
||||
{0x82u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZ, 2, 16, false, false, true, 3},
|
||||
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true, 4},
|
||||
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
{0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true},
|
||||
{0x85u, Opcode::BUFFER_STORE_FORMAT_D16_XY, 1, 16, false, false, true, 2},
|
||||
{0x86u, Opcode::BUFFER_STORE_FORMAT_D16_XYZ, 2, 16, false, false, true, 3},
|
||||
{0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true, 4},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
|
||||
@@ -158,12 +163,13 @@ void ApplyMemoryInfo(Instruction& inst, const MemoryOpcodeInfo* info) {
|
||||
inst.opcode = Opcode::UNSUPPORTED;
|
||||
return;
|
||||
}
|
||||
inst.opcode = info->decoded;
|
||||
inst.data_dwords = info->data_dwords;
|
||||
inst.data_bits = info->data_bits;
|
||||
inst.data_signed = info->data_signed;
|
||||
inst.typed = info->typed;
|
||||
inst.formatted = info->formatted;
|
||||
inst.opcode = info->decoded;
|
||||
inst.data_dwords = info->data_dwords;
|
||||
inst.data_bits = info->data_bits;
|
||||
inst.data_signed = info->data_signed;
|
||||
inst.typed = info->typed;
|
||||
inst.formatted = info->formatted;
|
||||
inst.data_components = info->data_components;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -323,12 +329,7 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_XYZW ||
|
||||
inst.opcode == Opcode::BUFFER_STORE_FORMAT_D16_XYZW) {
|
||||
inst.data_components = 4u;
|
||||
}
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 ||
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16 ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI) {
|
||||
inst.dst.sdwa_sel = inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI ? 5u : 4u;
|
||||
|
||||
@@ -636,6 +636,8 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_XY:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_XYZ:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
|
||||
case Opcode::BUFFER_STORE_FORMAT_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XY:
|
||||
@@ -643,6 +645,8 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_XY:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_XYZ:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
|
||||
case Opcode::BUFFER_LOAD_UBYTE:
|
||||
case Opcode::BUFFER_LOAD_UBYTE_D16:
|
||||
|
||||
@@ -547,6 +547,8 @@ enum class Opcode {
|
||||
BUFFER_LOAD_FORMAT_XYZ,
|
||||
BUFFER_LOAD_FORMAT_XYZW,
|
||||
BUFFER_LOAD_FORMAT_D16_X,
|
||||
BUFFER_LOAD_FORMAT_D16_XY,
|
||||
BUFFER_LOAD_FORMAT_D16_XYZ,
|
||||
BUFFER_LOAD_FORMAT_D16_XYZW,
|
||||
BUFFER_STORE_FORMAT_X,
|
||||
BUFFER_STORE_FORMAT_XY,
|
||||
@@ -554,6 +556,8 @@ enum class Opcode {
|
||||
BUFFER_STORE_FORMAT_XYZW,
|
||||
BUFFER_STORE_FORMAT_D16_X,
|
||||
BUFFER_STORE_FORMAT_D16_HI_X,
|
||||
BUFFER_STORE_FORMAT_D16_XY,
|
||||
BUFFER_STORE_FORMAT_D16_XYZ,
|
||||
BUFFER_STORE_FORMAT_D16_XYZW,
|
||||
BUFFER_LOAD_UBYTE,
|
||||
BUFFER_LOAD_UBYTE_D16,
|
||||
|
||||
@@ -455,12 +455,17 @@ void Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
if (bits != 32u) {
|
||||
WriteOperand(inst.dst, WidenSubdword(loaded, bits, sign));
|
||||
} else if (memory.data_dwords == 1u) {
|
||||
WriteOperand(inst.dst, loaded);
|
||||
} else {
|
||||
for (uint32_t component = 0; component < memory.data_dwords; component++) {
|
||||
WriteOperand(OffsetOperand(inst.dst, component),
|
||||
ir.CompositeExtract(loaded, component));
|
||||
return;
|
||||
}
|
||||
for (uint32_t component = 0; component < memory.data_dwords; component++) {
|
||||
const auto dst = OffsetOperand(inst.dst, component);
|
||||
const auto value =
|
||||
memory.data_dwords == 1u ? IR::U32(loaded) : ir.CompositeExtract(loaded, component);
|
||||
// D16 formats with an odd component count keep the high half of their final VGPR.
|
||||
if (memory.data_bits == 16u && component * 2u + 1u == memory.component_count) {
|
||||
Write16Bits(dst, value);
|
||||
} else {
|
||||
WriteOperand(dst, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -992,6 +997,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XY:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZ:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY:
|
||||
@@ -1012,6 +1019,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XY:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZ:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY:
|
||||
|
||||
@@ -319,7 +319,10 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
|
||||
case Decoder::OperandKind::Vgpr: {
|
||||
const auto reg = static_cast<IR::VectorReg>(operand.reg);
|
||||
const auto old = ir.GetVectorReg(reg);
|
||||
if (operand.dpp) {
|
||||
// DPP skips EXEC lanes only through row/bank masks, or for invalid sources without
|
||||
// BOUND_CTRL (dpp8 reads zero); otherwise it is a plain masked write.
|
||||
if (operand.dpp && (operand.dpp_row_mask != 0xfu || operand.dpp_bank_mask != 0xfu ||
|
||||
(!operand.dpp_bound_ctrl && !operand.dpp8))) {
|
||||
value = IR::U32(ir.Emit(IR::ValueOpcode::DppUpdateU32, {value, old, ir.GetExec()},
|
||||
DppFlags(operand)));
|
||||
} else {
|
||||
|
||||
@@ -487,7 +487,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
const auto access = BufferAccessOf(inst.GetOpcode());
|
||||
const bool packed_formatted =
|
||||
(access == BufferAccess::Read || access == BufferAccess::Write) && memory.formatted &&
|
||||
memory.data_bits == 16u && memory.component_count == 4u && buffer_components == 2u;
|
||||
memory.data_bits == 16u && (memory.component_count + 1u) / 2u == buffer_components;
|
||||
if (buffer_components > 1u &&
|
||||
(!vector_buffer || (memory.data_bits != 32u && !packed_formatted) ||
|
||||
memory.data_dwords != buffer_components || memory.component_index != 0u)) {
|
||||
|
||||
@@ -169,6 +169,7 @@ struct ImageResource {
|
||||
bool depth_compare = false;
|
||||
bool cube = false;
|
||||
bool r128 = false;
|
||||
bool heap = false; // Resident image heap root or kind.
|
||||
uint32_t indirect_root = NoIndirectImage;
|
||||
uint32_t indirect_mapping_offset = 0;
|
||||
uint32_t indirect_search_iterations = 0;
|
||||
@@ -188,6 +189,12 @@ RawSignedStorageFormat(const ImageResource& image, Prospero::BufferFormat format
|
||||
return Prospero::BufferFormat::kInvalid;
|
||||
}
|
||||
|
||||
// A raw load of a kDepth k16UInt T# reads a D16 surface's codes. Vulkan cannot view D16 as
|
||||
// R16Uint, so the host binds it as UNorm and the shader converts back.
|
||||
[[nodiscard]] constexpr bool ReadsDepthBits(const ImageResource& image) {
|
||||
return image.conversion_format == Prospero::BufferFormat::k16UInt;
|
||||
}
|
||||
|
||||
struct SamplerResource {
|
||||
static constexpr uint32_t NoIndirectSampler = UINT32_MAX;
|
||||
|
||||
@@ -337,7 +344,12 @@ struct StageOutput {
|
||||
inline constexpr uint32_t FirstImageBinding = 1u;
|
||||
inline constexpr uint32_t FirstComparisonImageBinding = 22u;
|
||||
inline constexpr uint32_t FirstStorageImageBinding = 29u;
|
||||
inline constexpr uint32_t ImageBindingCount = 48u;
|
||||
inline constexpr uint32_t ImageBindingCount = 50u;
|
||||
// Resident image heaps share one sampled-image array at binding 0 of this set. A heap slot
|
||||
// word is kind << ImageHeapKindShift | element; element 0 is the null descriptor.
|
||||
inline constexpr uint32_t ImageHeapSet = 1u;
|
||||
inline constexpr uint32_t ImageHeapCapacity = 65536u;
|
||||
inline constexpr uint32_t ImageHeapKindShift = 20u;
|
||||
|
||||
enum class DescriptorBindingKind : uint32_t {
|
||||
Buffers = 0u,
|
||||
@@ -351,8 +363,8 @@ enum class DescriptorBindingKind : uint32_t {
|
||||
Count,
|
||||
};
|
||||
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 49u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 56u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 51u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 58u);
|
||||
|
||||
struct PushData {
|
||||
static constexpr uint32_t DwordCount = 32;
|
||||
@@ -413,8 +425,8 @@ DescriptorBindingForImage(const ImageResource& image) {
|
||||
constexpr uint32_t SampledUintBinding = 8u;
|
||||
constexpr uint32_t SampledSintBinding = 15u;
|
||||
constexpr uint32_t StorageFloatBinding = FirstStorageImageBinding;
|
||||
constexpr uint32_t StorageUintBinding = StorageFloatBinding + 5u;
|
||||
constexpr uint32_t AtomicUintBinding = StorageUintBinding + 5u;
|
||||
constexpr uint32_t StorageUintBinding = StorageFloatBinding + 6u;
|
||||
constexpr uint32_t AtomicUintBinding = StorageUintBinding + 6u;
|
||||
|
||||
uint32_t base = 0;
|
||||
bool sampled = false;
|
||||
@@ -461,10 +473,10 @@ DescriptorBindingForImage(const ImageResource& image) {
|
||||
case Decoder::ImageDimension::Dim2D: dimension = 2u; break;
|
||||
case Decoder::ImageDimension::Dim2DArray: dimension = 3u; break;
|
||||
case Decoder::ImageDimension::Dim2DMsaa:
|
||||
if (!sampled) {
|
||||
if (image.atomic) {
|
||||
return std::nullopt;
|
||||
}
|
||||
dimension = 4u;
|
||||
dimension = sampled ? 4u : 5u;
|
||||
break;
|
||||
case Decoder::ImageDimension::Dim2DMsaaArray:
|
||||
if (!sampled) {
|
||||
@@ -523,6 +535,7 @@ struct ShaderInfo {
|
||||
bool uses_lds = false;
|
||||
bool uses_gds = false;
|
||||
bool uses_flattened_srt = false;
|
||||
bool uses_image_heap = false;
|
||||
bool uses_dispatch_threads = false;
|
||||
bool bvh = false;
|
||||
bool subgroup_ballot = false;
|
||||
|
||||
@@ -681,6 +681,32 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
const auto count = table_size >= first + 4u
|
||||
? std::min((table_size - first - 4u) / step + 1u, domain)
|
||||
: 0u;
|
||||
descriptors[resource_index] = {.dword_count = dword_count};
|
||||
if constexpr (std::is_same_v<Specialization, ResourceSpecialization::Image>) {
|
||||
const auto& image = program.info.images[resource_index];
|
||||
// A resident heap maps slot words instead of materializing its entries.
|
||||
ImageHeapView view;
|
||||
if (image_heap && runtime.image_heap != nullptr &&
|
||||
image.resource_class == ImageResourceClass::Sampled &&
|
||||
image.mip_mode != ImageMipMode::Dynamic &&
|
||||
runtime.image_heap->Acquire({table_base, table_size, step, first, count, &image},
|
||||
view)) {
|
||||
snapshot.specialization_reads.emplace_back(table_base, table_size);
|
||||
if (view.kinds.empty()) return true;
|
||||
auto root = root_resource;
|
||||
root.indirect_root = resource_index;
|
||||
root.indirect_mapping_offset = static_cast<uint32_t>(snapshot.flattened_srt.size());
|
||||
root.heap = true;
|
||||
specializations[resource_index] = root;
|
||||
snapshot.flattened_srt.insert(snapshot.flattened_srt.end(),
|
||||
{view.slot_count, static_cast<uint32_t>(view.slots), static_cast<uint32_t>(view.slots >> 32u)});
|
||||
for (const auto& kind: view.kinds) {
|
||||
descriptors.push_back(kind);
|
||||
specializations.push_back(root);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (child_count == 0u || (!image_heap && count * child_count > MaxIndirectDescriptorProbes))
|
||||
return failed("candidate count", fmt::format("rows={} limit={}", count, MaxIndirectDescriptorProbes));
|
||||
// Affine SMEM offsets wrap in U32 before adding the aligned immediate.
|
||||
@@ -692,7 +718,6 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
return read_failed("image heap read", 0u, 0u, table_base, 0u, heap_words.size() * sizeof(uint32_t));
|
||||
}
|
||||
const auto mapping_offset = snapshot.flattened_srt.size();
|
||||
descriptors[resource_index] = {.dword_count = dword_count};
|
||||
snapshot.flattened_srt.resize(mapping_offset + 2u, 0u);
|
||||
for (uint32_t word = 0; word < count; ++word) {
|
||||
const auto table_offset = affine ? first + word * step : uint64_t {word} * 4u;
|
||||
@@ -932,7 +957,12 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
return SpecializationFail("FMASK requires a direct image load");
|
||||
}
|
||||
}
|
||||
image.conversion_format = ImageConversionFormat(format);
|
||||
// See ReadsDepthBits; raw loads only, since filtering would blend the codes.
|
||||
const bool depth_bits =
|
||||
!storage && base.unfiltered_access_only && format == Prospero::BufferFormat::k16UInt &&
|
||||
static_cast<Prospero::TileMode>((descriptor.dwords[3] >> 20u) & 0x1fu) ==
|
||||
Prospero::TileMode::kDepth;
|
||||
image.conversion_format = depth_bits ? format : ImageConversionFormat(format);
|
||||
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
|
||||
}
|
||||
@@ -940,7 +970,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
const bool raw_sint_storage = raw_sint_format != Prospero::BufferFormat::kInvalid;
|
||||
if (raw_sint_format == Prospero::BufferFormat::k16UInt) image.conversion_format = format;
|
||||
image.numeric_class = Prospero::SampledTextureNumericClass(
|
||||
Prospero::RemapTextureFormat(format));
|
||||
depth_bits ? Prospero::BufferFormat::k16UNorm : Prospero::RemapTextureFormat(format));
|
||||
if (!storage && format == Prospero::BufferFormat::k11_11_10UNorm) {
|
||||
if (image.indirect_root != ImageResource::NoIndirectImage) {
|
||||
return SpecializationFail("packed UNorm sampling requires a direct image");
|
||||
@@ -1005,10 +1035,12 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
const auto key_count = root.indirect_mapping_offset < snapshot.flattened_srt.size()
|
||||
? snapshot.flattened_srt[root.indirect_mapping_offset]
|
||||
: 0u;
|
||||
// A heap maps its slot count and slot address; tables map each key.
|
||||
size_t mapping_words = 3u;
|
||||
if (!root.heap)
|
||||
mapping_words = 1u + size_t {key_count} * (root.indirect_search_iterations == 0u ? 1u : 2u);
|
||||
if (key_count == 0u ||
|
||||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
|
||||
static_cast<size_t>(key_count) * (root.indirect_search_iterations == 0u ? 1u : 2u) >
|
||||
snapshot.flattened_srt.size()) {
|
||||
root.indirect_mapping_offset + mapping_words > snapshot.flattened_srt.size()) {
|
||||
return SpecializationFail("indirect image specialization has an invalid key mapping");
|
||||
}
|
||||
uint32_t exemplar = ImageResource::NoIndirectImage;
|
||||
@@ -1744,6 +1776,43 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
return BuildResourceSpecialization(program, snapshot, specialization);
|
||||
}
|
||||
|
||||
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value) {
|
||||
// Mirrors BuildResourceSpecialization for sampled roots: entries it would reject, and
|
||||
// entries that need shader-side conversion, read as null instead of failing the draw.
|
||||
const auto format = static_cast<Prospero::BufferFormat>((value.dwords[1] >> 20u) & 0x1ffu);
|
||||
const auto tile = static_cast<Prospero::TileMode>((value.dwords[3] >> 20u) & 0x1fu);
|
||||
const auto numeric_class =
|
||||
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
|
||||
if (NullImageDescriptor(value) || !ValidImageDescriptor(value, root.r128) ||
|
||||
DescriptorDimension(value, root.dimension) != root.dimension ||
|
||||
Prospero::IsFmaskTextureFormat(format) || format == Prospero::BufferFormat::k11_11_10UNorm ||
|
||||
ImageConversionFormat(format) != Prospero::BufferFormat::kInvalid ||
|
||||
(root.unfiltered_access_only && format == Prospero::BufferFormat::k16UInt &&
|
||||
tile == Prospero::TileMode::kDepth) ||
|
||||
numeric_class == Prospero::TextureNumericClass::Unsupported ||
|
||||
(root.depth_compare && numeric_class != Prospero::TextureNumericClass::Float)) {
|
||||
value.dwords.fill(0u);
|
||||
return 0u;
|
||||
}
|
||||
return 1u + static_cast<uint32_t>(numeric_class) * 2u + (DescriptorIsCube(value) ? 1u : 0u);
|
||||
}
|
||||
|
||||
ImageResource ImageHeapResource(const ImageResource& root, uint32_t kind) {
|
||||
ImageResource resource;
|
||||
resource.resource_class = ImageResourceClass::Sampled;
|
||||
resource.read = true;
|
||||
resource.heap = true;
|
||||
resource.dimension = root.dimension;
|
||||
resource.unfiltered_access_only = root.unfiltered_access_only;
|
||||
resource.depth_compare = root.depth_compare;
|
||||
resource.r128 = root.r128;
|
||||
if (kind != 0u) {
|
||||
resource.numeric_class = static_cast<Prospero::TextureNumericClass>((kind - 1u) / 2u);
|
||||
resource.cube = ((kind - 1u) & 1u) != 0u;
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
|
||||
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete);
|
||||
EXIT_IF(program.info.buffers.size() > specialization.buffers.size() ||
|
||||
@@ -1794,6 +1863,7 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
|
||||
image.indirect_mapping_offset = source.indirect_mapping_offset;
|
||||
image.indirect_search_iterations = source.indirect_search_iterations;
|
||||
image.cube = source.cube;
|
||||
image.heap = source.heap;
|
||||
image.indirect_resources.clear();
|
||||
}
|
||||
for (uint32_t index = 0; index < images.size(); index++) {
|
||||
|
||||
@@ -31,6 +31,7 @@ struct ResourceSpecialization {
|
||||
uint32_t indirect_search_iterations = 0;
|
||||
bool cube = false;
|
||||
bool fmask = false;
|
||||
bool heap = false;
|
||||
bool operator==(const Image&) const = default;
|
||||
};
|
||||
|
||||
@@ -59,6 +60,13 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
// Applies an already-derived specialization to native IR before layout and emission.
|
||||
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization);
|
||||
|
||||
// Kind of an image heap entry as root sees it, or 0 (and a zeroed value) when root reads it as
|
||||
// null. Entries of one kind specialize identically.
|
||||
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value);
|
||||
// The sampled resource that root resolves entries of kind as; kind 0 gives the identity of
|
||||
// root's view of a heap.
|
||||
ImageResource ImageHeapResource(const ImageResource& root, uint32_t kind);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCEMATERIALIZATION_H_ */
|
||||
|
||||
@@ -594,6 +594,7 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
|
||||
};
|
||||
info.uses_flattened_srt |= std::ranges::any_of(info.buffers, uses_mapping) ||
|
||||
std::ranges::any_of(info.images, uses_mapping);
|
||||
info.uses_image_heap = std::ranges::any_of(info.images, &ImageResource::heap);
|
||||
switch (program.stage) {
|
||||
case ShaderType::Vertex:
|
||||
case ShaderType::Local: CollectVertexInputs(input_info.vertex, info, inputs.components); break;
|
||||
|
||||
@@ -12,6 +12,33 @@ class Value;
|
||||
|
||||
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, std::span<uint32_t> values);
|
||||
|
||||
// Image descriptors at base + first + k * step, k < count, within size bytes that a sampled
|
||||
// root selects by GPU-computed byte offset. The host keeps them resident instead of
|
||||
// materializing them.
|
||||
struct ImageHeapRequest {
|
||||
uint64_t base = 0;
|
||||
uint64_t size = 0;
|
||||
uint64_t step = 0;
|
||||
uint64_t first = 0;
|
||||
uint64_t count = 0;
|
||||
const ImageResource* root = nullptr;
|
||||
};
|
||||
|
||||
struct ImageHeapView {
|
||||
uint64_t slots = 0; // Device address of one slot per heap dword.
|
||||
uint32_t slot_count = 0; // Keys past it select a trailing null slot.
|
||||
std::span<const DescriptorValue> kinds; // One representative descriptor per kind.
|
||||
};
|
||||
|
||||
class ImageHeapSource {
|
||||
public:
|
||||
// False when the host does not keep the table resident.
|
||||
virtual bool Acquire(const ImageHeapRequest& request, ImageHeapView& view) = 0;
|
||||
|
||||
protected:
|
||||
~ImageHeapSource() = default;
|
||||
};
|
||||
|
||||
struct SrtRuntime {
|
||||
std::span<const uint32_t> user_data;
|
||||
uint64_t shader_base = 0;
|
||||
@@ -20,6 +47,7 @@ struct SrtRuntime {
|
||||
SrtMemoryReader read_specialization_memory = nullptr;
|
||||
std::span<const uint32_t> workgroup_counts;
|
||||
uint64_t indirect_args_address = 0;
|
||||
ImageHeapSource* image_heap = nullptr;
|
||||
};
|
||||
|
||||
class SrtReadCapture {
|
||||
|
||||
@@ -410,6 +410,11 @@ std::filesystem::path GetRealFilename(const std::string& mounted_file_name) {
|
||||
return g_mount_points->ResolvePath(mounted_file_name);
|
||||
}
|
||||
|
||||
bool IsOpenDescriptor(int d) {
|
||||
const auto* file = g_files->GetFile(d);
|
||||
return file != nullptr && file->opened;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -56,6 +56,8 @@ void Mount(const std::filesystem::path& folder, const std::stri
|
||||
void Umount(const std::string& folder_or_point);
|
||||
// Returns an empty path when the guest path has no mounted filesystem.
|
||||
std::filesystem::path GetRealFilename(const std::string& mounted_file_name);
|
||||
// Returns whether d is an open file, directory or device descriptor.
|
||||
bool IsOpenDescriptor(int d);
|
||||
|
||||
int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode);
|
||||
int KYTY_SYSV_ABI KernelClose(int d);
|
||||
|
||||
@@ -33,12 +33,27 @@ static int KYTY_SYSV_ABI UsbdHandleEventsTimeout() {
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int64_t KYTY_SYSV_ABI UsbdGetDeviceList(void*** list) {
|
||||
PRINT_NAME();
|
||||
|
||||
static void* empty_list[] = {nullptr};
|
||||
*list = empty_list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void KYTY_SYSV_ABI UsbdFreeDeviceList(void** /*list*/, int /*unref_devices*/) {
|
||||
PRINT_NAME();
|
||||
}
|
||||
|
||||
} // namespace Usbd
|
||||
|
||||
LIB_DEFINE(InitUsbd_1) {
|
||||
LIB_FUNC("TOhg7P6kTH4", Usbd::UsbdInit);
|
||||
LIB_FUNC("Fq6+0Fm55xU", Usbd::UsbdExit);
|
||||
LIB_FUNC("+wU6CGuZcWk", Usbd::UsbdHandleEventsTimeout);
|
||||
LIB_FUNC("8qB9Ar4P5nc", Usbd::UsbdGetDeviceList);
|
||||
LIB_FUNC("EQ6SCLMqzkM", Usbd::UsbdFreeDeviceList);
|
||||
}
|
||||
|
||||
} // namespace Libs
|
||||
|
||||
+21
-1
@@ -30,6 +30,7 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/threads.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
@@ -465,7 +466,7 @@ Network::Id Network::HttpCreateTemplate(Id http_ctx_id, const char* user_agent,
|
||||
HttpTemplate tn {};
|
||||
tn.used = true;
|
||||
tn.http_ver = http_ver;
|
||||
tn.user_agent = std::string(user_agent);
|
||||
tn.user_agent = user_agent != nullptr ? user_agent : "";
|
||||
tn.is_auto_proxy_conf = is_auto_proxy_conf;
|
||||
tn.http_ctx_id = http_ctx_id.GetId();
|
||||
tn.nonblock = false;
|
||||
@@ -2577,6 +2578,7 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
|
||||
std::vector<std::pair<int, NativeSocket>> descriptors;
|
||||
std::vector<std::shared_ptr<SocketTransport>> stream_reads;
|
||||
std::vector<std::pair<void*, int>> ready_files;
|
||||
int host_nfds = 0;
|
||||
for (int fd = 0; fd < nfds; fd++) {
|
||||
const bool read = GuestFdIsSet(readfds, fd);
|
||||
@@ -2585,6 +2587,16 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
if (!read && !write && !except) {
|
||||
continue;
|
||||
}
|
||||
// FreeBSD polls files and devices ready to read and write at once.
|
||||
if (!IsSocket(fd) && LibKernel::FileSystem::IsOpenDescriptor(fd)) {
|
||||
if (read) {
|
||||
ready_files.emplace_back(readfds, fd);
|
||||
}
|
||||
if (write) {
|
||||
ready_files.emplace_back(writefds, fd);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
std::shared_ptr<SocketTransport> transport;
|
||||
#if !defined(_WIN32)
|
||||
@@ -2636,6 +2648,10 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
host_timeout.tv_usec = static_cast<decltype(host_timeout.tv_usec)>(guest_timeout->tv_usec);
|
||||
host_timeout_ptr = &host_timeout;
|
||||
}
|
||||
if (!ready_files.empty()) {
|
||||
host_timeout = {};
|
||||
host_timeout_ptr = &host_timeout;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
#if defined(_WIN32)
|
||||
@@ -2672,6 +2688,10 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
result++;
|
||||
}
|
||||
}
|
||||
for (const auto& [fds, fd]: ready_files) {
|
||||
GuestFdSet(fds, fd);
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "common/abi.h"
|
||||
#include "libs/network.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <array>
|
||||
@@ -162,6 +163,17 @@ void TestBuildHonoursOption(HttpUriParse parse, HttpUriBuild build) {
|
||||
CHECK(BuildWith(build, element, 0) == url);
|
||||
}
|
||||
|
||||
// Cyberpunk 2077 (PPSA03974) creates a template with a NULL user agent.
|
||||
void TestTemplateWithoutUserAgent() {
|
||||
using namespace Libs::Network;
|
||||
Initialize();
|
||||
const int pool = Net::NetPoolCreate("http", 0x10000, 0);
|
||||
const int ssl = Ssl::SslInit(0x10000);
|
||||
const int http = Http::HttpInit(pool, ssl, 0x10000);
|
||||
CHECK(Http::HttpCreateTemplate(http, nullptr, 2, 1) > 0);
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
@@ -176,5 +188,6 @@ int main() {
|
||||
TestAbsentQuery(parse);
|
||||
TestEmptyUri(parse);
|
||||
TestPresentQuery(parse);
|
||||
TestTemplateWithoutUserAgent();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -324,6 +324,7 @@ void CheckSaveRename(const std::filesystem::path &root,
|
||||
}
|
||||
|
||||
void TestRandomDevices() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
for (const auto* path : {"/dev/urandom", "/dev/random"}) {
|
||||
const int fd = FileSystem::KernelOpen(path, 0, 0);
|
||||
FileSystem::FileStat stat {};
|
||||
@@ -333,7 +334,18 @@ void TestRandomDevices() {
|
||||
std::array<uint8_t, 32> entropy {};
|
||||
Check(FileSystem::KernelRead(fd, entropy.data(), entropy.size()) == entropy.size(),
|
||||
"character device supplies requested entropy");
|
||||
// OpenSSL RAND_poll selects the device before reading it; a null timeout must not block.
|
||||
std::array<uint64_t, 16> readable {};
|
||||
readable[fd / 64] = uint64_t {1} << (fd % 64);
|
||||
auto writable = readable;
|
||||
const auto requested = readable;
|
||||
Check(Net::Select(fd + 1, readable.data(), writable.data(), nullptr, nullptr) == 2 &&
|
||||
readable == requested && writable == requested,
|
||||
"select reports an entropy source ready at once");
|
||||
Check(FileSystem::KernelClose(fd) == OK, "close entropy source");
|
||||
Check(Net::Select(fd + 1, readable.data(), nullptr, nullptr, nullptr) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EBADF,
|
||||
"select rejects a closed descriptor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2453,8 +2465,8 @@ int main(int argc, char** argv) {
|
||||
TestAioBatches();
|
||||
CheckSaveRename(temporary.Path(), "first-save");
|
||||
CheckSaveRename(temporary.Path(), "replacement-save");
|
||||
FileSystem::Shutdown();
|
||||
CheckSocketWakeup();
|
||||
FileSystem::Shutdown();
|
||||
CheckEtherAddressFormatting();
|
||||
#if defined(__linux__)
|
||||
TestNetCtlHostIpv4();
|
||||
|
||||
+142
-12
@@ -436,6 +436,128 @@ void TestInvariantIndirectImageMaterialization(bool nested) {
|
||||
"malformed image table tracking was not transactional");
|
||||
}
|
||||
|
||||
void TestResidentImageHeap() {
|
||||
struct FakeImageHeap final : ImageHeapSource {
|
||||
bool accept = true;
|
||||
std::vector<DescriptorValue> kinds;
|
||||
std::vector<ImageHeapRequest> requests;
|
||||
bool Acquire(const ImageHeapRequest &request, ImageHeapView &view) override {
|
||||
requests.push_back(request);
|
||||
if (!accept) return false;
|
||||
view = {.slots = 0x123456789abcull, .slot_count = 36u, .kinds = kinds};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
auto fixture = MakeIndirectImageFixture(false, 48u, 0u, 16u);
|
||||
fixture->PlanAndTrack();
|
||||
const auto plan = ExtractResourcePlan(fixture->program);
|
||||
std::array<uint32_t, 8> descriptor{};
|
||||
descriptor[0] = 0x20u;
|
||||
descriptor[1] = static_cast<uint32_t>(Libs::Graphics::Prospero::BufferFormat::k32_32_32_32Float) << 20u;
|
||||
descriptor[2] = 3u | (3u << 14u);
|
||||
descriptor[3] = Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||
auto cube = descriptor;
|
||||
cube[3] = Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kCube) << 28u);
|
||||
std::array<uint32_t, 16> data{0x3000u, 4u << 16u, 0x100000u, 0u,
|
||||
0x2000u, 48u << 16u, 3u, 0u};
|
||||
std::copy_n(data.begin(), 4u, data.begin() + 12u);
|
||||
LinearTestMemory memory;
|
||||
memory.fail_address = 0x3000u;
|
||||
memory.watched_address = 0x2000u;
|
||||
std::copy(descriptor.begin(), descriptor.end(), memory.words.begin() + 0x1010u / 4u);
|
||||
FakeImageHeap heap;
|
||||
heap.kinds = {{descriptor, 8u}, {cube, 8u}};
|
||||
SrtRuntime runtime{.user_data = data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory, .image_heap = &heap};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
heap.requests.size() == 1u && heap.requests[0].base == 0x2000u &&
|
||||
heap.requests[0].size == 144u && heap.requests[0].step == 16u &&
|
||||
heap.requests[0].first == 0u && heap.requests[0].count == 9u &&
|
||||
heap.requests[0].root == &plan.info.images[0] &&
|
||||
memory.watched_reads == 0u && snapshot.images.size() == 3u,
|
||||
"resident image heap was materialized entry by entry");
|
||||
const auto &root = specialization.images[0];
|
||||
const auto mapping = root.indirect_mapping_offset;
|
||||
Check(root.heap && root.indirect_root == 0u && root.indirect_search_iterations == 0u &&
|
||||
snapshot.flattened_srt.size() == mapping + 3u &&
|
||||
snapshot.flattened_srt[mapping] == 36u &&
|
||||
snapshot.flattened_srt[mapping + 1u] == 0x56789abcu &&
|
||||
snapshot.flattened_srt[mapping + 2u] == 0x1234u &&
|
||||
specialization.images[1].heap && specialization.images[1].indirect_root == 0u &&
|
||||
!specialization.images[1].cube && specialization.images[2].heap &&
|
||||
specialization.images[2].cube && snapshot.images[2].dwords == cube &&
|
||||
std::ranges::find(snapshot.specialization_reads,
|
||||
std::pair<uint64_t, uint64_t>{0x2000u, 144u}) !=
|
||||
snapshot.specialization_reads.end(),
|
||||
"resident image heap lost its slot mapping, typed kinds or read dependency");
|
||||
auto &program = fixture->program;
|
||||
ApplyResourceSpecialization(program, specialization);
|
||||
ShaderComputeInputInfo compute{};
|
||||
CollectShaderInfo(program, {.compute = &compute});
|
||||
Check(program.info.uses_image_heap && program.info.images.size() == 3u &&
|
||||
program.info.images[0].indirect_resources == std::vector<uint32_t>({0u, 1u, 2u}) &&
|
||||
std::ranges::all_of(program.info.images, &ImageResource::heap),
|
||||
"applied heap kinds lost their root or heap flag");
|
||||
heap.kinds.clear();
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.images.size() == 1u && !specialization.images[0].heap &&
|
||||
specialization.images[0].indirect_root == ImageResource::NoIndirectImage,
|
||||
"a resident heap without kinds did not read as one null image");
|
||||
heap.accept = false;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
heap.requests.size() == 3u && memory.watched_reads == 1u &&
|
||||
snapshot.images.size() == 2u && !specialization.images[0].heap &&
|
||||
snapshot.images[1].dwords == descriptor,
|
||||
"a declined heap was not materialized entry by entry");
|
||||
}
|
||||
|
||||
void TestImageHeapKinds() {
|
||||
using Format = Libs::Graphics::Prospero::BufferFormat;
|
||||
using Type = Libs::Graphics::Prospero::ImageType;
|
||||
ImageResource root;
|
||||
root.resource_class = ImageResourceClass::Sampled;
|
||||
root.dimension = Decoder::ImageDimension::Dim2DArray;
|
||||
root.source = 3u;
|
||||
const auto descriptor = [](Format format, Type type) {
|
||||
DescriptorValue value{.dword_count = 8u};
|
||||
value.dwords[0] = 0x20u;
|
||||
value.dwords[1] = static_cast<uint32_t>(format) << 20u;
|
||||
value.dwords[2] = 3u | (3u << 14u);
|
||||
value.dwords[3] = Libs::Graphics::DstSel(4, 5, 6, 7) | (static_cast<uint32_t>(type) << 28u);
|
||||
return value;
|
||||
};
|
||||
const auto kind_of = [&](Format format, Type type) {
|
||||
auto value = descriptor(format, type);
|
||||
return ImageHeapKind(root, value);
|
||||
};
|
||||
const auto float_kind = kind_of(Format::k32_32_32_32Float, Type::kColor2DArray);
|
||||
const auto uint_kind = kind_of(Format::k32UInt, Type::kColor2DArray);
|
||||
const auto cube_kind = kind_of(Format::k32_32_32_32Float, Type::kCube);
|
||||
auto volume = descriptor(Format::k32_32_32_32Float, Type::kColor3D);
|
||||
Check(float_kind != 0u && uint_kind != 0u && cube_kind != 0u && float_kind != uint_kind &&
|
||||
float_kind != cube_kind && kind_of(Format::kInvalid, Type::kColor2DArray) == 0u &&
|
||||
ImageHeapKind(root, volume) == 0u &&
|
||||
std::ranges::all_of(volume.dwords, [](uint32_t word) { return word == 0u; }),
|
||||
"heap kinds did not separate typed entries or null mismatched ones");
|
||||
const auto resource = ImageHeapResource(root, uint_kind);
|
||||
const auto cube_resource = ImageHeapResource(root, cube_kind);
|
||||
auto other = root;
|
||||
other.source = 9u;
|
||||
Check(resource.numeric_class == Libs::Graphics::Prospero::TextureNumericClass::Uint &&
|
||||
!resource.cube && cube_resource.cube &&
|
||||
cube_resource.numeric_class == Libs::Graphics::Prospero::TextureNumericClass::Float &&
|
||||
resource.resource_class == ImageResourceClass::Sampled && resource.read &&
|
||||
ImageHeapResource(root, 0u) == ImageHeapResource(other, 0u),
|
||||
"heap kind resources lost their type or kept root identity");
|
||||
root.depth_compare = true;
|
||||
Check(kind_of(Format::k32UInt, Type::kColor2DArray) == 0u,
|
||||
"depth comparison accepted an integer heap entry");
|
||||
}
|
||||
|
||||
void TestBoundedImageViewEligibility() {
|
||||
using Type = Libs::Graphics::Prospero::ImageType;
|
||||
auto fixture = MakeIndirectImageFixture(false, 48u, 0u, 16u);
|
||||
@@ -8547,16 +8669,16 @@ void TestShaderInfoCollection() {
|
||||
void TestImageBindingAbi() {
|
||||
using NumericClass = Libs::Graphics::Prospero::TextureNumericClass;
|
||||
|
||||
Check(ImageBindingCount == 48u &&
|
||||
Check(ImageBindingCount == 50u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Buffers) == 0u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 49u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Gds) == 50u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 51u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 52u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 53u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::ShaderData) == 54u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::SharedMemory) == 55u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Count) == 56u,
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 51u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Gds) == 52u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 53u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 54u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 55u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::ShaderData) == 56u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::SharedMemory) == 57u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Count) == 58u,
|
||||
"native descriptor binding anchors changed");
|
||||
|
||||
const std::array sampled_dimensions{
|
||||
@@ -8615,6 +8737,8 @@ void TestImageBindingAbi() {
|
||||
CheckBinding(ImageResourceClass::Storage, numeric_class, dimension,
|
||||
false);
|
||||
}
|
||||
CheckBinding(ImageResourceClass::Storage, numeric_class,
|
||||
Decoder::ImageDimension::Dim2DMsaa, false);
|
||||
}
|
||||
for (const auto dimension : storage_dimensions) {
|
||||
CheckBinding(ImageResourceClass::Storage, NumericClass::Uint, dimension,
|
||||
@@ -8658,11 +8782,15 @@ void TestImageBindingAbi() {
|
||||
image.numeric_class = NumericClass::Sint;
|
||||
Check(Invalid(image), "signed storage image received a descriptor binding");
|
||||
image.numeric_class = NumericClass::Float;
|
||||
image.dimension = Decoder::ImageDimension::Dim2DMsaa;
|
||||
image.dimension = Decoder::ImageDimension::Dim2DMsaaArray;
|
||||
Check(Invalid(image),
|
||||
"multisampled storage image received a descriptor binding");
|
||||
image.dimension = Decoder::ImageDimension::Dim2D;
|
||||
"multisampled storage array received a descriptor binding");
|
||||
image.dimension = Decoder::ImageDimension::Dim2DMsaa;
|
||||
image.numeric_class = NumericClass::Uint;
|
||||
image.atomic = true;
|
||||
Check(Invalid(image), "multisampled atomic image received a descriptor binding");
|
||||
image.numeric_class = NumericClass::Float;
|
||||
image.dimension = Decoder::ImageDimension::Dim2D;
|
||||
Check(Invalid(image), "float atomic image received a descriptor binding");
|
||||
}
|
||||
|
||||
@@ -8790,6 +8918,8 @@ int main() {
|
||||
Run("dynamic storage mips", TestDynamicStorageMipTracking);
|
||||
Run("invariant indirect images", [] { TestInvariantIndirectImageMaterialization(true); });
|
||||
Run("unbounded material image keys", [] { TestInvariantIndirectImageMaterialization(false); });
|
||||
Run("resident image heap", TestResidentImageHeap);
|
||||
Run("image heap kinds", TestImageHeapKinds);
|
||||
Run("bounded image view eligibility", TestBoundedImageViewEligibility);
|
||||
Run("exact unfiltered image candidates", TestExactUnfilteredImageCandidates);
|
||||
Run("waterfall image table", TestWaterfallImageTable);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -99,6 +99,7 @@ ShaderRecompiler::CompileOptions MakeCompileOptions(ShaderType stage) {
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
options.user_data = user_data;
|
||||
options.emit_debug_info = true; // Checks find variables and functions by OpName.
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex:
|
||||
options.input_info.vertex = &vertex;
|
||||
@@ -403,6 +404,11 @@ SpirvMetrics MeasureSpirv(const std::vector<uint32_t> &binary) {
|
||||
if (word_count == 0 || i + word_count > binary.size()) {
|
||||
break;
|
||||
}
|
||||
if (opcode == spv::OpName || opcode == spv::OpString || opcode == spv::OpLine) {
|
||||
metrics.words -= word_count; // Budgets measure the module without debug info.
|
||||
i += word_count;
|
||||
continue;
|
||||
}
|
||||
metrics.instructions++;
|
||||
switch (opcode) {
|
||||
case 11u: // OpExtInstImport
|
||||
@@ -7039,6 +7045,17 @@ void TestPixelAncillaryLayerInput() {
|
||||
CheckSpirvBinaryValidates(wqm_result.spirv);
|
||||
}
|
||||
|
||||
uint32_t dpp[] = {
|
||||
EncodeVop3Word0(0x148, 6), EncodeVop3Word1(5 + 256, 128 + 16, 128 + 11),
|
||||
EncodeVop1(0x01, 5, 250), EncodeVop1Dpp(6) | (1u << 19u), // Bound-zero DPP overwrite.
|
||||
EncodeExp0(0x00, 0x1), EncodeExp1(5, 0, 0, 0), EncodeSopp(0x01),
|
||||
};
|
||||
auto dpp_result = RecompileForTest(dpp, options);
|
||||
Check(ProgramHasInput(dpp_result.program, StageInputKind::Layer) &&
|
||||
!ProgramHasInput(dpp_result.program, StageInputKind::PackedAncillary),
|
||||
"DPP write retained the old ancillary register value");
|
||||
CheckSpirvBinaryValidates(dpp_result.spirv);
|
||||
|
||||
const uint32_t loop[] = {
|
||||
EncodeSop1(0x04, 8, 126), // Save the original EXEC mask.
|
||||
EncodeSop1(0x0a, 126, 8), // Widen for helper lanes.
|
||||
@@ -7092,6 +7109,9 @@ void TestPixelAncillaryLayerInput() {
|
||||
shader[5] = EncodeExp1(6, 7, 5, 0);
|
||||
ExpectFatal([&] { (void)RecompileForTest(shader, options); },
|
||||
"direct raw ancillary export was silently replaced after field lowering");
|
||||
dpp[3] = EncodeVop1Dpp(6); // Without BOUND_CTRL, lanes with inactive sources keep the value.
|
||||
ExpectFatal([&] { (void)RecompileForTest(dpp, options); },
|
||||
"DPP write without BOUND_CTRL silently replaced the ancillary value");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -10354,7 +10374,7 @@ void TestNewShaderRecompilerPlanningOnlyLoads() {
|
||||
ir.Emit(IR::ValueOpcode::ReferenceU32, {read});
|
||||
program.shader_info_complete = false;
|
||||
IR::CollectShaderInfo(program, options.input_info);
|
||||
const auto spirv = Spirv::EmitProgram(program, options.input_info);
|
||||
const auto spirv = Spirv::EmitProgram(program, options.input_info, 0, {}, true);
|
||||
CheckSpirvBinaryValidates(spirv);
|
||||
Check(spirv == result.spirv,
|
||||
"planning-only loads or references emitted GPU operands/instructions");
|
||||
@@ -11100,6 +11120,7 @@ void TestMeshExportStorage() {
|
||||
options.stage = ShaderType::Mesh;
|
||||
options.input_info.vertex = &input;
|
||||
options.user_data = user_data;
|
||||
options.emit_debug_info = true; // The checks find push data by its OpName.
|
||||
struct Case {
|
||||
uint32_t subgroup_size, push_data_start;
|
||||
bool split;
|
||||
@@ -14352,7 +14373,8 @@ void TestNewShaderRecompilerNativeBindingPlan() {
|
||||
const auto bindings = result.program.bindings;
|
||||
result.program.bindings = {};
|
||||
result.program.info.buffers[0].descriptor_index = UINT32_MAX;
|
||||
Check(ShaderRecompiler::Spirv::EmitProgram(result.program, options.input_info) == result.spirv &&
|
||||
Check(ShaderRecompiler::Spirv::EmitProgram(result.program, options.input_info, 0, {}, true) ==
|
||||
result.spirv &&
|
||||
result.program.bindings == bindings &&
|
||||
result.program.info.buffers[0].descriptor_index == 0,
|
||||
"repeated emission did not rebuild descriptor slots deterministically");
|
||||
@@ -16068,6 +16090,40 @@ void TestCapturedBufferStoreFormatD16HiXDecode() {
|
||||
"captured BUFFER_STORE_FORMAT_D16_HI_X lost its high half, index, or coherence flags");
|
||||
}
|
||||
|
||||
void TestCapturedBufferLoadD16XyzwDecode() {
|
||||
namespace Decoder = ShaderRecompiler::Decoder;
|
||||
const uint32_t code[] = {0xe20c2000u, 0x8001100cu};
|
||||
Decoder::Instruction inst;
|
||||
Decoder::DecodeInstruction(code, 0, inst);
|
||||
Check(inst.opcode == Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZW &&
|
||||
inst.word_count == 2 && inst.data_dwords == 2 && inst.data_bits == 16 &&
|
||||
inst.data_components == 4 && inst.formatted && !inst.typed,
|
||||
"captured D16 XYZW load lost its four components in two packed VGPRs");
|
||||
Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 16 &&
|
||||
inst.dst.sdwa_sel == 6 &&
|
||||
inst.src0.kind == Decoder::OperandKind::Vgpr && inst.src0.reg == 12 &&
|
||||
inst.src1.kind == Decoder::OperandKind::Sgpr && inst.src1.reg == 4 &&
|
||||
inst.idxen && !inst.offen,
|
||||
"captured D16 XYZW load lost its full destination VGPRs or index");
|
||||
}
|
||||
|
||||
void TestCapturedBufferLoadD16XyDecode() {
|
||||
namespace Decoder = ShaderRecompiler::Decoder;
|
||||
const uint32_t code[] = {0xe2042000u, 0x80090600u};
|
||||
Decoder::Instruction inst;
|
||||
Decoder::DecodeInstruction(code, 0, inst);
|
||||
Check(inst.opcode == Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XY &&
|
||||
inst.word_count == 2 && inst.data_dwords == 1 && inst.data_bits == 16 &&
|
||||
inst.data_components == 2 && inst.formatted && !inst.typed,
|
||||
"captured D16 XY load lost its two components in one packed VGPR");
|
||||
Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 6 &&
|
||||
inst.dst.sdwa_sel == 6 &&
|
||||
inst.src0.kind == Decoder::OperandKind::Vgpr && inst.src0.reg == 0 &&
|
||||
inst.src1.kind == Decoder::OperandKind::Sgpr && inst.src1.reg == 36 &&
|
||||
inst.idxen && !inst.offen,
|
||||
"captured D16 XY load lost its destination VGPR or index");
|
||||
}
|
||||
|
||||
void TestCapturedBufferStoreD16XyzwDecode() {
|
||||
namespace Decoder = ShaderRecompiler::Decoder;
|
||||
const uint32_t code[] = {0xe21c6000u, 0x8002000eu};
|
||||
@@ -16389,11 +16445,15 @@ void TestRepeatedExportsHaveOneInterface() {
|
||||
void CheckSpirvBlockSourceMapping(TestCompileResult &result) {
|
||||
using namespace ShaderRecompiler;
|
||||
const auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, spv::OpString) == 0 &&
|
||||
SpirvInstructionOpcodeCount(result.spirv, spv::OpLine) == 0,
|
||||
"default emission unexpectedly contains source mapping");
|
||||
const auto plain = Spirv::EmitProgram(result.program, options.input_info);
|
||||
Check(SpirvInstructionOpcodeCount(plain, spv::OpString) == 0 &&
|
||||
SpirvInstructionOpcodeCount(plain, spv::OpLine) == 0 &&
|
||||
SpirvInstructionOpcodeCount(plain, spv::OpName) == 0,
|
||||
"default emission unexpectedly contains debug info");
|
||||
const auto mapped =
|
||||
Spirv::EmitProgram(result.program, options.input_info, 0, {}, true);
|
||||
Check(SpirvInstructionOpcodeCount(mapped, spv::OpName) != 0,
|
||||
"debug emission omitted names");
|
||||
CheckSpirvBinaryValidates(mapped);
|
||||
uint32_t source = 0;
|
||||
uint32_t label = 0;
|
||||
@@ -16422,7 +16482,7 @@ void CheckSpirvBlockSourceMapping(TestCompileResult &result) {
|
||||
}
|
||||
Check(source != 0 && lines >= result.program.blocks.size(),
|
||||
"debug emission omitted native block source mapping");
|
||||
Check(Spirv::EmitProgram(result.program, options.input_info) == result.spirv,
|
||||
Check(Spirv::EmitProgram(result.program, options.input_info) == plain,
|
||||
"debug re-emission changed the default binary");
|
||||
}
|
||||
|
||||
@@ -16883,6 +16943,8 @@ int main() {
|
||||
TestCapturedBufferLoadUbyteD16Decode();
|
||||
TestCapturedBufferStoreShortD16HiDecode();
|
||||
TestCapturedBufferStoreFormatD16HiXDecode();
|
||||
TestCapturedBufferLoadD16XyDecode();
|
||||
TestCapturedBufferLoadD16XyzwDecode();
|
||||
TestCapturedBufferStoreD16XyzwDecode();
|
||||
TestNewShaderRecompilerUnsupportedMemoryDecode();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user