mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-10-11 21:49:59 +00:00
video_core: Renderer optimizations pt1 (#5112)
* vk_runtime: Revert to interval sets for barrier tracking Perf capture shows they are faster in almost all cases due to not needing rebalancing after every insert * buffer_cache: Dont use copysparsememory for streaming copies * fetch_shader: Optimize fetch shader parsing * shader_recompiler: Optimize common case of sharp fetch Almost always the sharp will be contigious in flatbuf so just use a single load op to load it * remove zlib * vk_rasterizer: Check dst region not in sync batch before doing cpu fast path * address_space: Maintain sparse page table of backing pointers Can be used to implement CopySparseMemory lock free and cheaply * vk_rasterizer: Proper gds barriers * texture_cache: Bring back lock * fetch_shader: Handle v_mov_b32 * address_space: Avoid out of bounds access with higher than 40bit addresses so stephen doesnt kill me * core: Add setting to control inlining of fetch shader
This commit is contained in:
+1
-3
@@ -840,6 +840,7 @@ set(COMMON src/common/logging/classes.cpp
|
||||
src/common/number_utils.cpp
|
||||
src/common/memory_patcher.h
|
||||
src/common/memory_patcher.cpp
|
||||
src/common/multi_level_page_table.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp
|
||||
src/common/scm_rev.h
|
||||
src/common/key_manager.cpp
|
||||
@@ -1118,8 +1119,6 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
|
||||
src/video_core/buffer_cache/region_manager.h
|
||||
src/video_core/renderer_vulkan/liverpool_to_vk.cpp
|
||||
src/video_core/renderer_vulkan/liverpool_to_vk.h
|
||||
src/video_core/renderer_vulkan/vk_barrier_tracker.cpp
|
||||
src/video_core/renderer_vulkan/vk_barrier_tracker.h
|
||||
src/video_core/renderer_vulkan/vk_common.cpp
|
||||
src/video_core/renderer_vulkan/vk_common.h
|
||||
src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
|
||||
@@ -1181,7 +1180,6 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
|
||||
src/video_core/cache_storage.h
|
||||
src/video_core/page_manager.cpp
|
||||
src/video_core/page_manager.h
|
||||
src/video_core/multi_level_page_table.h
|
||||
src/video_core/renderdoc.cpp
|
||||
src/video_core/renderdoc.h
|
||||
)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
zlib License
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "common/object_pool.h"
|
||||
#include "common/spin_lock.h"
|
||||
|
||||
namespace VideoCore {
|
||||
namespace Common {
|
||||
|
||||
template <class Traits>
|
||||
class MultiLevelPageTable final {
|
||||
@@ -87,4 +87,4 @@ private:
|
||||
Common::ObjectPool<L1Page> page_alloc;
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
||||
} // namespace Common
|
||||
@@ -85,6 +85,18 @@ public:
|
||||
reset();
|
||||
}
|
||||
|
||||
bool operator==(const SmallVector& other) const {
|
||||
if (m_size != other.m_size) {
|
||||
return false;
|
||||
}
|
||||
for (size_type i = 0; i < m_size; ++i) {
|
||||
if (!(m_data[i] == other.m_data[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr reference operator[](size_type i) noexcept {
|
||||
return m_data[i];
|
||||
}
|
||||
|
||||
@@ -828,6 +828,14 @@ AddressSpace::AddressSpace() : impl{std::make_unique<Impl>()} {
|
||||
AddressSpace::~AddressSpace() = default;
|
||||
|
||||
void* AddressSpace::Map(VAddr virtual_addr, u64 size, PAddr phys_addr, bool is_exec) {
|
||||
const VAddr end_addr = virtual_addr + size - 1;
|
||||
if (phys_addr != -1 && std::bit_width(end_addr) <= Traits::ADDRESS_SPACE_BITS) {
|
||||
const u64 base_page = virtual_addr >> Traits::PAGE_BITS;
|
||||
for (u64 offset = 0; offset < size; offset += 16_KB) {
|
||||
backing_pages[base_page + (offset >> Traits::PAGE_BITS)] =
|
||||
backing_base + phys_addr + offset;
|
||||
}
|
||||
}
|
||||
#if ARCH_X86_64
|
||||
const auto prot = is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
#else
|
||||
@@ -849,6 +857,13 @@ void* AddressSpace::MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot,
|
||||
}
|
||||
|
||||
VAddr AddressSpace::Unmap(VAddr virtual_addr, u64* size) {
|
||||
const VAddr end_addr = virtual_addr + *size - 1;
|
||||
if (std::bit_width(end_addr) <= Traits::ADDRESS_SPACE_BITS) {
|
||||
const u64 base_page = virtual_addr >> Traits::PAGE_BITS;
|
||||
for (u64 offset = 0; offset < *size; offset += 16_KB) {
|
||||
backing_pages[base_page + (offset >> Traits::PAGE_BITS)] = nullptr;
|
||||
}
|
||||
}
|
||||
return impl->Unmap(virtual_addr, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <boost/icl/separate_interval_set.hpp>
|
||||
#include "common/arch.h"
|
||||
#include "common/enum.h"
|
||||
#include "common/multi_level_page_table.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -29,9 +30,21 @@ public:
|
||||
explicit AddressSpace();
|
||||
~AddressSpace();
|
||||
|
||||
struct Traits {
|
||||
using Entry = u8*;
|
||||
static constexpr size_t ADDRESS_SPACE_BITS = 40;
|
||||
static constexpr size_t L1_BITS = 16;
|
||||
static constexpr size_t PAGE_BITS = 14;
|
||||
static constexpr bool NULL_CHECK = true;
|
||||
};
|
||||
using BackingPageTable = Common::MultiLevelPageTable<Traits>;
|
||||
|
||||
[[nodiscard]] u8* BackingBase() const noexcept {
|
||||
return backing_base;
|
||||
}
|
||||
[[nodiscard]] const BackingPageTable& BackingPages() const noexcept {
|
||||
return backing_pages;
|
||||
}
|
||||
|
||||
[[nodiscard]] VAddr SystemManagedVirtualBase() noexcept {
|
||||
return reinterpret_cast<VAddr>(system_managed_base);
|
||||
@@ -100,6 +113,7 @@ private:
|
||||
u64 system_reserved_size{};
|
||||
u8* user_base{};
|
||||
u64 user_size{};
|
||||
BackingPageTable backing_pages;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -438,6 +438,7 @@ struct GPUSettings {
|
||||
Setting<bool> rcas_enabled{true};
|
||||
Setting<int> rcas_attenuation{250};
|
||||
Setting<bool> userfaultfd{false};
|
||||
Setting<bool> inline_fetch_shader{false};
|
||||
// TODO add overrides
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
return std::vector<OverrideItem>{
|
||||
@@ -460,6 +461,7 @@ struct GPUSettings {
|
||||
make_override<GPUSettings>("direct_memory_access_enabled",
|
||||
&GPUSettings::direct_memory_access_enabled),
|
||||
make_override<GPUSettings>("vblank_frequency", &GPUSettings::vblank_frequency),
|
||||
make_override<GPUSettings>("inline_fetch_shader", &GPUSettings::inline_fetch_shader),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -743,6 +745,7 @@ public:
|
||||
SETTING_FORWARD_BOOL(m_gpu, DirectMemoryAccessEnabled, direct_memory_access_enabled)
|
||||
SETTING_FORWARD_BOOL_READONLY(m_gpu, PatchShaders, patch_shaders)
|
||||
SETTING_FORWARD_BOOL(m_gpu, UserfaultfdTracking, userfaultfd)
|
||||
SETTING_FORWARD_BOOL_READONLY(m_gpu, InlineFetchShader, inline_fetch_shader)
|
||||
|
||||
u32 GetVblankFrequency() {
|
||||
if (m_gpu.vblank_frequency.value < 30) {
|
||||
|
||||
+6
-14
@@ -145,27 +145,19 @@ void MemoryManager::SetPrtArea(u32 id, VAddr address, u64 size) {
|
||||
}
|
||||
|
||||
void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) {
|
||||
std::shared_lock lk{mutex};
|
||||
ASSERT_MSG(IsValidMapping(virtual_addr), "Attempted to access invalid address {:#x}",
|
||||
virtual_addr);
|
||||
|
||||
auto vma = FindVMA(virtual_addr);
|
||||
const auto& backing_pages = impl.BackingPages();
|
||||
while (size) {
|
||||
const u64 offset = virtual_addr - vma->first;
|
||||
const u64 copy_size = std::min<u64>(vma->second.size - (virtual_addr - vma->first), size);
|
||||
if (vma->second.IsMapped()) {
|
||||
u8* out = dest;
|
||||
vma->second.ForEachPhysArea(offset, copy_size, [&](PAddr paddr, u32 chunk_size) {
|
||||
std::memcpy(out, impl.BackingBase() + paddr, chunk_size);
|
||||
out += chunk_size;
|
||||
});
|
||||
const u64 page = virtual_addr >> 14;
|
||||
const u64 offset_in_page = virtual_addr % 16_KB;
|
||||
const u64 copy_size = std::min<u64>(16_KB - offset_in_page, size);
|
||||
if (auto* entry = backing_pages.find(page); entry && *entry) {
|
||||
std::memcpy(dest, *entry + offset_in_page, copy_size);
|
||||
} else {
|
||||
std::memset(dest, 0, copy_size);
|
||||
}
|
||||
size -= copy_size;
|
||||
virtual_addr += copy_size;
|
||||
dest += copy_size;
|
||||
++vma;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -332,21 +332,22 @@ void EmitContext::DefineInputs() {
|
||||
DefineVariable(U32[1], spv::BuiltIn::BaseInstance, spv::StorageClass::Input);
|
||||
}
|
||||
|
||||
const auto fetch_shader = Gcn::ParseFetchShader(info);
|
||||
if (!fetch_shader) {
|
||||
Shader::Gcn::FetchShaderData fetch_shader;
|
||||
if (!Gcn::ParseFetchShader(info, fetch_shader)) {
|
||||
break;
|
||||
}
|
||||
for (const auto& attrib : fetch_shader->attributes) {
|
||||
ASSERT(attrib.semantic < IR::NumParams);
|
||||
ASSERT(fetch_shader.attributes.size() <= IR::NumParams);
|
||||
for (u32 semantic = 0; semantic < fetch_shader.attributes.size(); ++semantic) {
|
||||
const auto& attrib = fetch_shader.attributes[semantic];
|
||||
const auto sharp = attrib.GetSharp(info);
|
||||
const Id type{GetAttributeType(*this, sharp.GetNumberFmt())[4]};
|
||||
Id id{DefineInput(type, attrib.semantic)};
|
||||
Id id{DefineInput(type, semantic)};
|
||||
if (attrib.GetStepRate() != Gcn::VertexAttribute::InstanceIdType::None) {
|
||||
Name(id, fmt::format("vs_instance_attr{}", attrib.semantic));
|
||||
Name(id, fmt::format("vs_instance_attr{}", semantic));
|
||||
} else {
|
||||
Name(id, fmt::format("vs_in_attr{}", attrib.semantic));
|
||||
Name(id, fmt::format("vs_in_attr{}", semantic));
|
||||
}
|
||||
input_params[attrib.semantic] = GetAttributeInfo(sharp.GetNumberFmt(), id, 4, false);
|
||||
input_params[semantic] = GetAttributeInfo(sharp.GetNumberFmt(), id, 4, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <utility>
|
||||
#include "common/assert.h"
|
||||
#include "shader_recompiler/frontend/decode.h"
|
||||
#include "shader_recompiler/frontend/fetch_shader.h"
|
||||
#include "shader_recompiler/frontend/opcodes.h"
|
||||
|
||||
namespace Shader::Gcn {
|
||||
|
||||
@@ -32,28 +34,18 @@ namespace Shader::Gcn {
|
||||
* We take the reverse way, extract the original input semantics from these instructions.
|
||||
**/
|
||||
|
||||
static bool IsTypedBufferLoad(const Gcn::GcnInst& inst) {
|
||||
return inst.opcode == Opcode::TBUFFER_LOAD_FORMAT_X ||
|
||||
inst.opcode == Opcode::TBUFFER_LOAD_FORMAT_XY ||
|
||||
inst.opcode == Opcode::TBUFFER_LOAD_FORMAT_XYZ ||
|
||||
inst.opcode == Opcode::TBUFFER_LOAD_FORMAT_XYZW;
|
||||
}
|
||||
|
||||
const u32* GetFetchShaderCode(const Info& info, u32 sgpr_base) {
|
||||
const u32* code;
|
||||
std::memcpy(&code, &info.user_data[sgpr_base], sizeof(code));
|
||||
return code;
|
||||
}
|
||||
|
||||
std::optional<FetchShaderData> ParseFetchShader(const Shader::Info& info) {
|
||||
bool ParseFetchShader(const Shader::Info& info, FetchShaderData& out_fetch_data) {
|
||||
if (!info.has_fetch_shader) {
|
||||
return std::nullopt;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* code = GetFetchShaderCode(info, info.fetch_shader_sgpr_base);
|
||||
FetchShaderData data{};
|
||||
GcnCodeSlice code_slice(code, code + std::numeric_limits<u32>::max());
|
||||
GcnDecodeContext decoder;
|
||||
out_fetch_data.attributes.clear();
|
||||
|
||||
struct VsharpLoad {
|
||||
u32 dword_offset{};
|
||||
@@ -61,54 +53,86 @@ std::optional<FetchShaderData> ParseFetchShader(const Shader::Info& info) {
|
||||
};
|
||||
std::array<VsharpLoad, 104> loads{};
|
||||
|
||||
u32 semantic_index = 0;
|
||||
while (!code_slice.atEnd()) {
|
||||
const auto inst = decoder.decodeInstruction(code_slice);
|
||||
data.size += inst.length;
|
||||
const auto* code = GetFetchShaderCode(info, info.fetch_shader_sgpr_base);
|
||||
const u32* ptr = code;
|
||||
while (true) {
|
||||
const u32 word0 = *(ptr++);
|
||||
const auto encoding = GetInstructionEncoding(word0);
|
||||
|
||||
if (inst.opcode == Opcode::S_SETPC_B64) {
|
||||
if (encoding == InstEncoding::SOP1) {
|
||||
const auto opcode = OpcodeSOP1((word0 >> 8) & 0xff);
|
||||
ASSERT(opcode == OpcodeSOP1::S_SETPC_B64);
|
||||
break;
|
||||
}
|
||||
|
||||
if (inst.inst_class == InstClass::ScalarMemRd) {
|
||||
loads[inst.dst[0].code] = VsharpLoad{inst.control.smrd.offset, inst.src[0].code * 2};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst.opcode == Opcode::V_ADD_I32) {
|
||||
const auto vgpr = inst.dst[0].code;
|
||||
const auto sgpr = s8(inst.src[0].code);
|
||||
switch (vgpr) {
|
||||
case 0: // V0 is always the vertex offset
|
||||
data.vertex_offset_sgpr = sgpr;
|
||||
break;
|
||||
case 3: // V3 is always the instance offset
|
||||
data.instance_offset_sgpr = sgpr;
|
||||
break;
|
||||
default:
|
||||
} else if (encoding == InstEncoding::SMRD) {
|
||||
const auto opcode = OpcodeSMRD((word0 >> 22) & 0x1f);
|
||||
const u32 sdst = (word0 >> 15) & 0x7f;
|
||||
const u32 sbase = (word0 >> 9) & 0x3f;
|
||||
const u32 offset = word0 & 0xff;
|
||||
ASSERT(opcode == OpcodeSMRD::S_LOAD_DWORDX4);
|
||||
loads[sdst] = VsharpLoad{offset, sbase * 2};
|
||||
} else if (encoding == InstEncoding::VOP2) {
|
||||
const auto opcode = OpcodeVOP2((word0 >> 25) & 0x3f);
|
||||
const u32 src0 = word0 & 0x1ff;
|
||||
const u32 vsrc1 = (word0 >> 9) & 0xff;
|
||||
const u32 vdst = (word0 >> 17) & 0xff;
|
||||
ASSERT(opcode == OpcodeVOP2::V_ADD_I32 && src0 < 16 && vsrc1 == vdst);
|
||||
if (vdst == 0) {
|
||||
// V0 is always the vertex offset
|
||||
out_fetch_data.vertex_offset_sgpr = src0;
|
||||
} else if (vdst == 3) {
|
||||
// V3 is always the instance offset
|
||||
out_fetch_data.instance_offset_sgpr = src0;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
if (inst.inst_class == InstClass::VectorMemBufFmt) {
|
||||
} else if (encoding == InstEncoding::MUBUF || encoding == InstEncoding::MTBUF) {
|
||||
// SRSRC is in units of 4 SPGRs while SBASE is in pairs of SGPRs
|
||||
const u32 base_sgpr = inst.src[2].code * 4;
|
||||
auto& attrib = data.attributes.emplace_back();
|
||||
attrib.semantic = semantic_index++;
|
||||
attrib.dest_vgpr = inst.src[1].code;
|
||||
attrib.num_elements = inst.control.mubuf.count;
|
||||
attrib.sgpr_base = loads[base_sgpr].base_sgpr;
|
||||
attrib.dword_offset = loads[base_sgpr].dword_offset;
|
||||
attrib.inst_offset = inst.control.mtbuf.offset;
|
||||
attrib.instance_data = inst.src[0].code;
|
||||
if (IsTypedBufferLoad(inst)) {
|
||||
attrib.data_format = inst.control.mtbuf.dfmt;
|
||||
attrib.num_format = inst.control.mtbuf.nfmt;
|
||||
const u32 word1 = *(ptr++);
|
||||
const u32 srsrc = ((word1 >> 16) & 0x1f) * 4;
|
||||
auto& attrib = out_fetch_data.attributes.emplace_back();
|
||||
attrib.dest_vgpr = (word1 >> 8) & 0xff;
|
||||
if (encoding == InstEncoding::MUBUF) {
|
||||
const auto opcode = OpcodeMUBUF((word0 >> 18) & 0x7f);
|
||||
ASSERT(opcode >= OpcodeMUBUF::BUFFER_LOAD_FORMAT_X &&
|
||||
opcode <= OpcodeMUBUF::BUFFER_LOAD_FORMAT_XYZW);
|
||||
attrib.num_elements = (std::to_underlying(opcode) -
|
||||
std::to_underlying(OpcodeMUBUF::BUFFER_LOAD_FORMAT_X)) +
|
||||
1;
|
||||
} else {
|
||||
const auto opcode = OpcodeMTBUF((word0 >> 16) & 0x7);
|
||||
ASSERT(opcode >= OpcodeMTBUF::TBUFFER_LOAD_FORMAT_X &&
|
||||
opcode <= OpcodeMTBUF::TBUFFER_LOAD_FORMAT_XYZW);
|
||||
attrib.num_elements = (std::to_underlying(opcode) -
|
||||
std::to_underlying(OpcodeMTBUF::TBUFFER_LOAD_FORMAT_X)) +
|
||||
1;
|
||||
}
|
||||
attrib.sgpr_base = loads[srsrc].base_sgpr;
|
||||
attrib.dword_offset = loads[srsrc].dword_offset;
|
||||
attrib.inst_offset = word0 & 0xfff;
|
||||
attrib.instance_data = word1 & 0xff;
|
||||
if (encoding == InstEncoding::MTBUF) {
|
||||
attrib.data_format = (word0 >> 19) & 0xf;
|
||||
attrib.num_format = (word0 >> 23) & 0x7;
|
||||
} else {
|
||||
attrib.data_format = 0;
|
||||
attrib.num_format = 0;
|
||||
}
|
||||
} else if (encoding == InstEncoding::SOPP) {
|
||||
const auto opcode = OpcodeSOPP((word0 >> 16) & 0x7f);
|
||||
ASSERT(opcode == OpcodeSOPP::S_WAITCNT);
|
||||
} else if (encoding == InstEncoding::VOP1) {
|
||||
const auto opcode = OpcodeVOP1((word0 >> 9) & 0xff);
|
||||
const u32 src0 = word0 & 0x1ff;
|
||||
const u32 vdst = (word0 >> 17) & 0xff;
|
||||
ASSERT(opcode == OpcodeVOP1::V_MOV_B32 && src0 == 242);
|
||||
LOG_WARNING(Render_Recompiler, "Fetch shader has V{} = 1.0 which is ignored", vdst);
|
||||
} else {
|
||||
UNREACHABLE_MSG("Unexpected instruction encoding in fetch shader {}", u32(encoding));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
out_fetch_data.size = (ptr - code) * sizeof(u32);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Shader::Gcn
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "common/small_vector.h"
|
||||
#include "common/types.h"
|
||||
#include "shader_recompiler/info.h"
|
||||
|
||||
@@ -22,15 +21,14 @@ struct VertexAttribute {
|
||||
Plain = 3,
|
||||
};
|
||||
|
||||
u8 semantic; ///< Semantic index of the attribute
|
||||
u8 dest_vgpr; ///< Destination VGPR to load first component.
|
||||
u8 num_elements; ///< Number of components to load
|
||||
u8 sgpr_base; ///< SGPR that contains the pointer to the list of vertex V#
|
||||
u8 dword_offset; ///< The dword offset of the V# that describes this attribute.
|
||||
u8 instance_data; ///< Indicates that the buffer will be accessed in instance rate
|
||||
u8 inst_offset; ///< Instruction offset applied on the formatted buffer loads
|
||||
u8 data_format{}; ///< Data format override when typed buffer loads are used
|
||||
u8 num_format{}; ///< Number format override when typed buffer loads are used
|
||||
u8 data_format; ///< Data format override when typed buffer loads are used
|
||||
u8 num_format; ///< Number format override when typed buffer loads are used
|
||||
|
||||
InstanceIdType GetStepRate() const {
|
||||
return static_cast<InstanceIdType>(instance_data);
|
||||
@@ -46,16 +44,12 @@ struct VertexAttribute {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool operator==(const VertexAttribute& other) const {
|
||||
return semantic == other.semantic && dest_vgpr == other.dest_vgpr &&
|
||||
num_elements == other.num_elements && sgpr_base == other.sgpr_base &&
|
||||
dword_offset == other.dword_offset && instance_data == other.instance_data;
|
||||
}
|
||||
bool operator==(const VertexAttribute& other) const = default;
|
||||
};
|
||||
|
||||
struct FetchShaderData {
|
||||
u32 size = 0;
|
||||
std::vector<VertexAttribute> attributes;
|
||||
SmallVector<VertexAttribute, 8> attributes;
|
||||
s8 vertex_offset_sgpr = -1; ///< SGPR of vertex offset from VADDR
|
||||
s8 instance_offset_sgpr = -1; ///< SGPR of instance offset from VADDR
|
||||
|
||||
@@ -64,12 +58,16 @@ struct FetchShaderData {
|
||||
instance_offset_sgpr == other.instance_offset_sgpr;
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& buffer);
|
||||
};
|
||||
|
||||
const u32* GetFetchShaderCode(const Info& info, u32 sgpr_base);
|
||||
|
||||
std::optional<FetchShaderData> ParseFetchShader(const Shader::Info& info);
|
||||
bool ParseFetchShader(const Shader::Info& info, FetchShaderData& out_fetch_data);
|
||||
|
||||
} // namespace Shader::Gcn
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/io_file.h"
|
||||
#include "common/path_util.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "shader_recompiler/frontend/decode.h"
|
||||
#include "shader_recompiler/frontend/fetch_shader.h"
|
||||
#include "shader_recompiler/frontend/translate/translate.h"
|
||||
#include "shader_recompiler/info.h"
|
||||
@@ -112,10 +113,10 @@ void Translator::EmitPrologue(IR::Block* first_block) {
|
||||
// v0: vertex ID, always present
|
||||
IR::U32 vertex_id = ir.GetAttributeU32(IR::Attribute::VertexId);
|
||||
if (base_vertex_sgpr != -1) {
|
||||
if (!fetch_data || fetch_data->vertex_offset_sgpr == -1) {
|
||||
if (!fetch_data.Empty() || fetch_data.vertex_offset_sgpr == -1) {
|
||||
vertex_id = ir.ISub(vertex_id, ir.GetAttributeU32(IR::Attribute::BaseVertex));
|
||||
} else {
|
||||
ASSERT_MSG(fetch_data->vertex_offset_sgpr == base_vertex_sgpr,
|
||||
ASSERT_MSG(fetch_data.vertex_offset_sgpr == base_vertex_sgpr,
|
||||
"Fetch shader in indirect draw uses wrong base vertex");
|
||||
}
|
||||
}
|
||||
@@ -157,11 +158,11 @@ void Translator::EmitPrologue(IR::Block* first_block) {
|
||||
if (runtime_info.props.num_input_vgprs > 2) {
|
||||
IR::U32 instance_id = ir.GetAttributeU32(IR::Attribute::InstanceId);
|
||||
if (base_instance_sgpr != -1) {
|
||||
if (!fetch_data || fetch_data->instance_offset_sgpr == -1) {
|
||||
if (!fetch_data.Empty() || fetch_data.instance_offset_sgpr == -1) {
|
||||
instance_id =
|
||||
ir.ISub(instance_id, ir.GetAttributeU32(IR::Attribute::BaseInstance));
|
||||
} else {
|
||||
ASSERT_MSG(fetch_data->instance_offset_sgpr == base_instance_sgpr,
|
||||
ASSERT_MSG(fetch_data.instance_offset_sgpr == base_instance_sgpr,
|
||||
"Fetch shader in indirect draw uses wrong base instance");
|
||||
}
|
||||
}
|
||||
@@ -1134,29 +1135,27 @@ template void Translator::SetDstPk<IR::F32, false>(const InstOperand& operand,
|
||||
void Translator::EmitFetch(const GcnInst& inst) {
|
||||
const auto code_sgpr_base = inst.src[0].code;
|
||||
|
||||
#if 0
|
||||
// Translate fetch shader inline using regular buffer bindings; useful for debugging.
|
||||
const auto* code = GetFetchShaderCode(info, code_sgpr_base);
|
||||
GcnCodeSlice slice(code, code + std::numeric_limits<u32>::max());
|
||||
GcnDecodeContext decoder;
|
||||
if (EmulatorSettings.IsInlineFetchShader()) {
|
||||
// Translate fetch shader inline using regular buffer bindings; useful for debugging.
|
||||
const auto* code = GetFetchShaderCode(info, code_sgpr_base);
|
||||
GcnCodeSlice slice(code, code + std::numeric_limits<u32>::max());
|
||||
GcnDecodeContext decoder;
|
||||
|
||||
// Decode and save instructions
|
||||
while (!slice.atEnd()) {
|
||||
const auto sub_inst = decoder.decodeInstruction(slice);
|
||||
if (sub_inst.opcode == Opcode::S_SETPC_B64) {
|
||||
// Assume we're swapping back to the main shader.
|
||||
break;
|
||||
// Decode and save instructions
|
||||
while (!slice.atEnd()) {
|
||||
const auto sub_inst = decoder.decodeInstruction(slice);
|
||||
if (sub_inst.opcode == Opcode::S_SETPC_B64) {
|
||||
// Assume we're swapping back to the main shader.
|
||||
break;
|
||||
}
|
||||
TranslateInstruction(sub_inst);
|
||||
}
|
||||
TranslateInstruction(sub_inst);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
#endif
|
||||
|
||||
info.has_fetch_shader = true;
|
||||
info.fetch_shader_sgpr_base = code_sgpr_base;
|
||||
|
||||
fetch_data = ParseFetchShader(info);
|
||||
ASSERT(fetch_data.has_value());
|
||||
ASSERT(ParseFetchShader(info, fetch_data));
|
||||
|
||||
if (EmulatorSettings.IsDumpShaders()) {
|
||||
using namespace Common::FS;
|
||||
@@ -1167,11 +1166,12 @@ void Translator::EmitFetch(const GcnInst& inst) {
|
||||
const auto filename = fmt::format("vs_{:#018x}.fetch.bin", info.pgm_hash);
|
||||
const auto file = IOFile{dump_dir / filename, FileAccessMode::Create};
|
||||
const auto* code = GetFetchShaderCode(info, code_sgpr_base);
|
||||
file.WriteRaw<u8>(code, fetch_data->size);
|
||||
file.WriteRaw<u8>(code, fetch_data.size);
|
||||
}
|
||||
|
||||
for (const auto& attrib : fetch_data->attributes) {
|
||||
const IR::Attribute attr{IR::Attribute::Param0 + attrib.semantic};
|
||||
for (u32 semantic = 0; semantic < fetch_data.attributes.size(); ++semantic) {
|
||||
const auto& attrib = fetch_data.attributes[semantic];
|
||||
const IR::Attribute attr{IR::Attribute::Param0 + semantic};
|
||||
IR::VectorReg dst_reg{attrib.dest_vgpr};
|
||||
|
||||
// Read the V# of the attribute to figure out component number and type.
|
||||
@@ -1182,7 +1182,7 @@ void Translator::EmitFetch(const GcnInst& inst) {
|
||||
const auto converted =
|
||||
IR::ApplyReadNumberConversionVec4(ir, values, buffer.GetNumberConversion());
|
||||
const auto swizzled = ApplySwizzle(ir, converted, buffer.DstSelect());
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
for (u32 i = 0; i < attrib.num_elements; i++) {
|
||||
ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(swizzled, i)});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ private:
|
||||
u32 next_vgpr_num;
|
||||
std::unordered_map<u32, IR::VectorReg> vgpr_map;
|
||||
std::array<IR::Attribute, MaxInterpVgpr> vgpr_to_interp{};
|
||||
std::optional<FetchShaderData> fetch_data{};
|
||||
FetchShaderData fetch_data{};
|
||||
bool opcode_missing = false;
|
||||
u32 pc{};
|
||||
};
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
#include <limits>
|
||||
#include "shader_recompiler/info.h"
|
||||
#include "shader_recompiler/ir/basic_block.h"
|
||||
#include "shader_recompiler/ir/breadth_first_search.h"
|
||||
#include "shader_recompiler/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/ir/opcodes.h"
|
||||
#include "shader_recompiler/ir/operand_helper.h"
|
||||
#include "shader_recompiler/ir/passes/ir_passes.h"
|
||||
#include "shader_recompiler/ir/passes/resource_pass.h"
|
||||
#include "shader_recompiler/ir/program.h"
|
||||
#include "shader_recompiler/ir/reinterpret.h"
|
||||
#include "shader_recompiler/profile.h"
|
||||
#include "shader_recompiler/resource.h"
|
||||
#include "video_core/amdgpu/resource.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
@@ -181,6 +180,7 @@ SharpLocation SharpLocationFromSource(const IR::Inst* inst) {
|
||||
|
||||
template <typename T>
|
||||
SharpFetch<T> ConstructSharpFetch(const SharpReference& sharp) {
|
||||
using Summary = SharpFetch<T>::Summary;
|
||||
SharpFetch<T> sharp_fetch{};
|
||||
for (u32 i = 0; i < sharp.num_dwords; i++) {
|
||||
auto dword = sharp.dwords[i];
|
||||
@@ -189,8 +189,20 @@ SharpFetch<T> ConstructSharpFetch(const SharpReference& sharp) {
|
||||
} else {
|
||||
sharp_fetch.offsets[i] = SharpLocationFromSource(dword.Inst());
|
||||
sharp_fetch.load_mask |= (1 << i);
|
||||
if (sharp_fetch.offsets[i] == UNKNOWN_LOCATION) {
|
||||
sharp_fetch.summary = Summary::Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sharp_fetch.summary != Summary::Invalid) {
|
||||
const u32 base = sharp_fetch.offsets[0];
|
||||
for (u32 i = 1; i < sharp.num_dwords; ++i) {
|
||||
if (sharp_fetch.offsets[i] - base != i) {
|
||||
return sharp_fetch;
|
||||
}
|
||||
}
|
||||
}
|
||||
sharp_fetch.summary = Summary::SingleLoad;
|
||||
return sharp_fetch;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,13 @@ struct SharpFetch {
|
||||
static constexpr std::size_t N = sizeof(T) / sizeof(u32);
|
||||
static_assert(N <= 8);
|
||||
|
||||
enum class Summary : u8 {
|
||||
SingleLoad,
|
||||
MultiLoad,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
Summary summary{Summary::MultiLoad};
|
||||
std::array<u32, N> immediates;
|
||||
std::array<SharpLocation, N> offsets;
|
||||
u8 load_mask;
|
||||
@@ -35,12 +42,13 @@ struct SharpFetch {
|
||||
template <u32 num_dwords = N>
|
||||
requires(num_dwords <= N)
|
||||
constexpr bool Fetch(const u32* flatbuf, T* out) const {
|
||||
u8 mask = load_mask;
|
||||
for (u32 i = 0; i < num_dwords; i++) {
|
||||
if (offsets[i] == UNKNOWN_LOCATION) {
|
||||
return false;
|
||||
}
|
||||
if (summary == Summary::SingleLoad) [[likely]] {
|
||||
std::memcpy(out, flatbuf + offsets[0], num_dwords * sizeof(u32));
|
||||
return true;
|
||||
} else if (summary == Summary::Invalid) [[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
u8 mask = load_mask;
|
||||
std::array<u32, num_dwords> out_dw;
|
||||
for (u32 i = 0; i < num_dwords; i++) {
|
||||
out_dw[i] = (mask & 1) ? flatbuf[offsets[i]] : immediates[i];
|
||||
|
||||
@@ -83,22 +83,21 @@ struct StageSpecialization {
|
||||
const Info* info{};
|
||||
RuntimeInfo runtime_info{};
|
||||
std::bitset<MaxStageResources> bitset{};
|
||||
std::optional<Gcn::FetchShaderData> fetch_shader_data{};
|
||||
boost::container::small_vector<VsAttribSpecialization, 32> vs_attribs;
|
||||
boost::container::small_vector<BufferSpecialization, 16> buffers;
|
||||
boost::container::small_vector<ImageSpecialization, 16> images;
|
||||
boost::container::small_vector<FMaskSpecialization, 8> fmasks;
|
||||
boost::container::small_vector<SamplerSpecialization, 16> samplers;
|
||||
Gcn::FetchShaderData fetch_shader_data{};
|
||||
SmallVector<VsAttribSpecialization, 32> vs_attribs;
|
||||
SmallVector<BufferSpecialization, 16> buffers;
|
||||
SmallVector<ImageSpecialization, 16> images;
|
||||
SmallVector<FMaskSpecialization, 8> fmasks;
|
||||
SmallVector<SamplerSpecialization, 16> samplers;
|
||||
Backend::Bindings start{};
|
||||
|
||||
StageSpecialization() = default;
|
||||
StageSpecialization(const Info& info_, RuntimeInfo runtime_info_, const Profile& profile_,
|
||||
Backend::Bindings start_)
|
||||
: info{&info_}, runtime_info{runtime_info_}, start{start_} {
|
||||
fetch_shader_data = Gcn::ParseFetchShader(info_);
|
||||
if (info_.sw_stage == SwStage::Vertex && fetch_shader_data) {
|
||||
if (info_.sw_stage == SwStage::Vertex && Gcn::ParseFetchShader(info_, fetch_shader_data)) {
|
||||
// Specialize shader on VS input number types to follow spec.
|
||||
ForEachSharp(vs_attribs, fetch_shader_data->attributes,
|
||||
ForEachSharp(vs_attribs, fetch_shader_data.attributes,
|
||||
[this](auto& spec, const auto& desc, AmdGpu::Buffer sharp) {
|
||||
using InstanceIdType = Shader::Gcn::VertexAttribute::InstanceIdType;
|
||||
if (const auto step_rate = desc.GetStepRate();
|
||||
@@ -183,7 +182,7 @@ struct StageSpecialization {
|
||||
binding++;
|
||||
continue;
|
||||
}
|
||||
bitset.set(binding++);
|
||||
bitset[binding++] = true;
|
||||
func(spec, desc, sharp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/debug.h"
|
||||
#include "common/div_ceil.h"
|
||||
#include "common/error.h"
|
||||
#include "common/multi_level_page_table.h"
|
||||
#include "common/signal_context.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/emulator_settings.h"
|
||||
@@ -260,7 +261,7 @@ struct PageManager::Impl {
|
||||
static constexpr size_t PAGE_BITS = PM_PAGE_BITS;
|
||||
static constexpr bool NULL_CHECK = false;
|
||||
};
|
||||
MultiLevelPageTable<PageTraits> cached_pages;
|
||||
Common::MultiLevelPageTable<PageTraits> cached_pages;
|
||||
struct MutexTraits {
|
||||
#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
|
||||
using Entry = Common::AdaptiveMutex;
|
||||
@@ -272,7 +273,7 @@ struct PageManager::Impl {
|
||||
static constexpr size_t PAGE_BITS = PM_PAGE_BITS;
|
||||
static constexpr bool NULL_CHECK = false;
|
||||
};
|
||||
MultiLevelPageTable<MutexTraits> locks;
|
||||
Common::MultiLevelPageTable<MutexTraits> locks;
|
||||
};
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright DXVK Project
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#include <bit>
|
||||
#include "video_core/renderer_vulkan/vk_barrier_tracker.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
BarrierTracker::BarrierTracker() {
|
||||
// Having an accessible 0 node makes certain things easier to
|
||||
// implement and allows us to use 0 as an invalid node index.
|
||||
nodes.emplace_back();
|
||||
|
||||
// Pre-allocate root nodes for the implicit hash table
|
||||
for (u32 i = 0; i < 2u * HashTableSize; i++) {
|
||||
AllocateNode();
|
||||
}
|
||||
}
|
||||
|
||||
bool BarrierTracker::FindRange(const AddressRange& range, Access access_type) const {
|
||||
u32 root_index = ComputeRootIndex(range, access_type);
|
||||
u32 node_index = FindNode(range, root_index);
|
||||
return node_index;
|
||||
}
|
||||
|
||||
void BarrierTracker::InsertRange(const AddressRange& range, Access access_type) {
|
||||
// If we can just insert the node with no conflicts, we don't have to do anything.
|
||||
u32 root_index = ComputeRootIndex(range, access_type);
|
||||
u32 node_index = InsertNode(range, root_index);
|
||||
|
||||
if (!node_index) [[likely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's an existing node and it contains the entire range we want to add already, also
|
||||
// don't do anything. If there are conflicting access ops, reset it.
|
||||
auto& node = nodes[node_index];
|
||||
if (node.address_range.Contains(range)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, check if there are any other overlapping ranges.
|
||||
// If that is not the case, simply update the range we found.
|
||||
bool has_overlap = false;
|
||||
|
||||
if (range.range_start < node.address_range.range_start) {
|
||||
AddressRange test_range;
|
||||
test_range.resource = range.resource;
|
||||
test_range.range_start = range.range_start;
|
||||
test_range.range_end = node.address_range.range_start - 1;
|
||||
has_overlap = FindNode(test_range, root_index);
|
||||
}
|
||||
|
||||
if (range.range_end > node.address_range.range_end && !has_overlap) {
|
||||
AddressRange test_range;
|
||||
test_range.resource = range.resource;
|
||||
test_range.range_start = node.address_range.range_end + 1;
|
||||
test_range.range_end = range.range_end;
|
||||
has_overlap = FindNode(test_range, root_index);
|
||||
}
|
||||
|
||||
if (!has_overlap) {
|
||||
node.address_range.range_start =
|
||||
std::min(node.address_range.range_start, range.range_start);
|
||||
node.address_range.range_end = std::max(node.address_range.range_end, range.range_end);
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are multiple ranges overlapping the one being inserted, remove them all and insert
|
||||
// the merged range.
|
||||
AddressRange merged_range = range;
|
||||
while (node_index) {
|
||||
auto& node = nodes[node_index];
|
||||
merged_range.range_start =
|
||||
std::min(merged_range.range_start, node.address_range.range_start);
|
||||
merged_range.range_end = std::max(merged_range.range_end, node.address_range.range_end);
|
||||
|
||||
RemoveNode(node_index, root_index);
|
||||
node_index = FindNode(range, root_index);
|
||||
}
|
||||
InsertNode(merged_range, root_index);
|
||||
}
|
||||
|
||||
void BarrierTracker::Clear() {
|
||||
root_mask_valid = 0;
|
||||
while (root_mask_subtree) {
|
||||
// Free subtrees if any, but keep the root node intact
|
||||
const u32 root_index = std::countr_zero(root_mask_subtree) + 1;
|
||||
auto& root = nodes[root_index];
|
||||
if (root.header) {
|
||||
FreeNode(root.Child(0));
|
||||
FreeNode(root.Child(1));
|
||||
root.header = 0;
|
||||
}
|
||||
root_mask_subtree &= root_mask_subtree - 1;
|
||||
}
|
||||
}
|
||||
|
||||
u32 BarrierTracker::AllocateNode() {
|
||||
if (!free.empty()) {
|
||||
const u32 node_index = free.back();
|
||||
free.pop_back();
|
||||
|
||||
// Free any subtree that the node might still have
|
||||
auto& node = nodes[node_index];
|
||||
FreeNode(node.Child(0));
|
||||
FreeNode(node.Child(1));
|
||||
|
||||
node.header = 0;
|
||||
return node_index;
|
||||
} else {
|
||||
// Allocate entirely new node in the array
|
||||
const u32 node_index = nodes.size();
|
||||
nodes.emplace_back();
|
||||
return node_index;
|
||||
}
|
||||
}
|
||||
|
||||
void BarrierTracker::FreeNode(u32 node) {
|
||||
if (node) {
|
||||
free.push_back(node);
|
||||
}
|
||||
}
|
||||
|
||||
u32 BarrierTracker::FindNode(const AddressRange& range, u32 root_index) const {
|
||||
// Check if the given root is valid at all
|
||||
const u64 root_bit = 1ULL << (root_index - 1);
|
||||
if (!(root_mask_valid & root_bit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Traverse search tree normally
|
||||
u32 node_index = root_index;
|
||||
while (node_index) {
|
||||
auto& node = nodes[node_index];
|
||||
if (node.address_range.Overlaps(range)) {
|
||||
return node_index;
|
||||
}
|
||||
node_index = node.Child(node.address_range < range);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 BarrierTracker::InsertNode(const AddressRange& range, u32 root_index) {
|
||||
// Check if the given root is valid at all
|
||||
const u64 root_bit = 1ULL << (root_index - 1);
|
||||
|
||||
if (!(root_mask_valid & root_bit)) {
|
||||
// Update root node as necessary. Also reset its red-ness if we set it during deletion.
|
||||
root_mask_valid |= root_bit;
|
||||
auto& node = nodes[root_index];
|
||||
node.header = 0;
|
||||
node.address_range = range;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Traverse tree and abort if we find any range overlapping the one we're trying to insert.
|
||||
u32 parent_index = root_index;
|
||||
u32 child_index = 0;
|
||||
|
||||
while (true) {
|
||||
auto& parent = nodes[parent_index];
|
||||
if (parent.address_range.Overlaps(range)) {
|
||||
return parent_index;
|
||||
}
|
||||
child_index = parent.address_range < range;
|
||||
if (!parent.Child(child_index)) {
|
||||
break;
|
||||
}
|
||||
parent_index = parent.Child(child_index);
|
||||
}
|
||||
|
||||
// Create and insert new node into the tree
|
||||
const u32 node_index = AllocateNode();
|
||||
|
||||
auto& parent = nodes[parent_index];
|
||||
parent.SetChild(child_index, node_index);
|
||||
|
||||
auto& node = nodes[node_index];
|
||||
node.SetRed(true);
|
||||
node.SetParent(parent_index);
|
||||
node.address_range = range;
|
||||
|
||||
// Only do the fixup to maintain red-black properties if
|
||||
// we haven't marked the root node as red in a deletion.
|
||||
if (parent_index != root_index && !nodes[root_index].IsRed()) {
|
||||
RebalancePostInsert(node_index, root_index);
|
||||
}
|
||||
|
||||
root_mask_subtree |= root_bit;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BarrierTracker::RemoveNode(u32 node_index, u32 root_index) {
|
||||
auto& node = nodes[node_index];
|
||||
|
||||
u32 l = node.Child(0);
|
||||
u32 r = node.Child(1);
|
||||
|
||||
if (l && r) {
|
||||
// Both children are valid. Take the payload from the smallest
|
||||
// node in the right subtree and delete that node instead.
|
||||
u32 child_index = r;
|
||||
while (nodes[child_index].Child(0)) {
|
||||
child_index = nodes[child_index].Child(0);
|
||||
}
|
||||
|
||||
node.address_range = nodes[child_index].address_range;
|
||||
RemoveNode(child_index, root_index);
|
||||
return;
|
||||
}
|
||||
|
||||
// Deletion is expected to be exceptionally rare, to the point of
|
||||
// being irrelevant in practice since it can only ever happen if an
|
||||
// app reads multiple disjoint blocks of a resource and then reads
|
||||
// another range covering multiple of those blocks again. Instead
|
||||
// of implementing a complex post-delete fixup, mark the root as
|
||||
// red and allow the tree to go unbalanced until the next reset.
|
||||
if (!node.IsRed() && (node_index != root_index)) {
|
||||
nodes[root_index].SetRed(true);
|
||||
}
|
||||
|
||||
// We're deleting the a node with one or no children. To avoid
|
||||
// special-casing the root node, copy the child node to it and
|
||||
// update links as necessary.
|
||||
u32 child_index = std::max(l, r);
|
||||
u32 parent_index = node.Parent();
|
||||
|
||||
if (child_index) {
|
||||
auto& child = nodes[child_index];
|
||||
|
||||
u32 cl = child.Child(0);
|
||||
u32 cr = child.Child(1);
|
||||
|
||||
node.SetChild(0, cl);
|
||||
node.SetChild(1, cr);
|
||||
|
||||
if (node_index != root_index)
|
||||
node.SetRed(child.IsRed());
|
||||
|
||||
node.address_range = child.address_range;
|
||||
|
||||
if (cl) {
|
||||
nodes[cl].SetParent(node_index);
|
||||
}
|
||||
if (cr) {
|
||||
nodes[cr].SetParent(node_index);
|
||||
}
|
||||
|
||||
child.header = 0;
|
||||
FreeNode(child_index);
|
||||
} else if (node_index != root_index) {
|
||||
// Removing leaf node, update parent link and move on.
|
||||
auto& parent = nodes[parent_index];
|
||||
|
||||
const u32 which = parent.Child(1) == node_index;
|
||||
parent.SetChild(which, 0);
|
||||
|
||||
node.header = 0;
|
||||
FreeNode(node_index);
|
||||
} else {
|
||||
// Removing root with no children, mark tree as invalid
|
||||
const u64 root_bit = 1ULL << (root_index - 1u);
|
||||
root_mask_subtree &= ~root_bit;
|
||||
root_mask_valid &= ~root_bit;
|
||||
}
|
||||
}
|
||||
|
||||
void BarrierTracker::RebalancePostInsert(u32 node_index, u32 root_index) {
|
||||
while (node_index != root_index) {
|
||||
auto& node = nodes[node_index];
|
||||
auto& p = nodes[node.Parent()];
|
||||
|
||||
if (!p.IsRed()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto& g = nodes[p.Parent()];
|
||||
|
||||
if (g.Child(1) == node.Parent()) {
|
||||
auto& u = nodes[g.Child(0)];
|
||||
|
||||
if (g.Child(0) && u.IsRed()) {
|
||||
g.SetRed(true);
|
||||
u.SetRed(false);
|
||||
p.SetRed(false);
|
||||
node_index = p.Parent();
|
||||
} else {
|
||||
if (p.Child(0) == node_index) {
|
||||
RotateRight(node.Parent(), root_index);
|
||||
}
|
||||
p.SetRed(false);
|
||||
g.SetRed(true);
|
||||
RotateLeft(p.Parent(), root_index);
|
||||
}
|
||||
} else {
|
||||
auto& u = nodes[g.Child(1)];
|
||||
if (g.Child(1) && u.IsRed()) {
|
||||
g.SetRed(true);
|
||||
u.SetRed(false);
|
||||
p.SetRed(false);
|
||||
node_index = p.Parent();
|
||||
} else {
|
||||
if (p.Child(1) == node_index) {
|
||||
RotateLeft(node.Parent(), root_index);
|
||||
}
|
||||
p.SetRed(false);
|
||||
g.SetRed(true);
|
||||
RotateRight(p.Parent(), root_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes[root_index].SetRed(false);
|
||||
}
|
||||
|
||||
void BarrierTracker::RotateLeft(u32 node_index, u32 root_index) {
|
||||
// This implements rotations in such a way that the node to
|
||||
// rotate around does not move. This is important to avoid
|
||||
// having a special case for the root node, and avoids having
|
||||
// to access the parent or special-case the root node.
|
||||
auto& node = nodes[node_index];
|
||||
|
||||
u32 l = node.Child(0);
|
||||
u32 r = node.Child(1);
|
||||
u32 rl = nodes[r].Child(0);
|
||||
u32 rr = nodes[r].Child(1);
|
||||
|
||||
nodes[l].SetParent(r);
|
||||
|
||||
const bool is_red = nodes[r].IsRed();
|
||||
nodes[r].SetRed(node.IsRed());
|
||||
nodes[r].SetChild(0, l);
|
||||
nodes[r].SetChild(1, rl);
|
||||
|
||||
nodes[rr].SetParent(node_index);
|
||||
|
||||
node.SetRed(is_red && node_index != root_index);
|
||||
node.SetChild(0, r);
|
||||
node.SetChild(1, rr);
|
||||
|
||||
std::swap(node.address_range, nodes[r].address_range);
|
||||
}
|
||||
|
||||
void BarrierTracker::RotateRight(u32 node_index, u32 root_index) {
|
||||
auto& node = nodes[node_index];
|
||||
|
||||
u32 l = node.Child(0);
|
||||
u32 r = node.Child(1);
|
||||
u32 ll = nodes[l].Child(0);
|
||||
u32 lr = nodes[l].Child(1);
|
||||
|
||||
nodes[r].SetParent(l);
|
||||
|
||||
const bool is_red = nodes[l].IsRed();
|
||||
nodes[l].SetRed(node.IsRed());
|
||||
nodes[l].SetChild(0, lr);
|
||||
nodes[l].SetChild(1, r);
|
||||
|
||||
nodes[ll].SetParent(node_index);
|
||||
|
||||
node.SetRed(is_red && node_index != root_index);
|
||||
node.SetChild(0, ll);
|
||||
node.SetChild(1, l);
|
||||
|
||||
std::swap(node.address_range, nodes[l].address_range);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -1,137 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright DXVK Project
|
||||
// SPDX-License-Identifier: Zlib
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
enum class Access : u32 {
|
||||
None = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
};
|
||||
|
||||
struct AddressRange {
|
||||
u64 resource{};
|
||||
u64 range_start{};
|
||||
u64 range_end{};
|
||||
|
||||
bool Contains(const AddressRange& other) const {
|
||||
return resource == other.resource && range_start <= other.range_start &&
|
||||
range_end >= other.range_end;
|
||||
}
|
||||
|
||||
bool Overlaps(const AddressRange& other) const {
|
||||
return resource == other.resource && range_end >= other.range_start &&
|
||||
range_start <= other.range_end;
|
||||
}
|
||||
|
||||
bool operator<(const AddressRange& other) const {
|
||||
return (resource < other.resource) ||
|
||||
(resource == other.resource && range_start < other.range_start);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Node of a red-black tree, consisting of a packed node header as well as a resource address range.
|
||||
* GCC generates weird code with bitfields here, so pack manually.
|
||||
*/
|
||||
struct BarrierTreeNode {
|
||||
constexpr static u64 NodeIndexMask = (1u << 21) - 1u;
|
||||
|
||||
// Packed header with node indices and the node color.
|
||||
// [0:0]: Set if the node is red, clear otherwise.
|
||||
// [21:1]: Index of the left child node, may be 0.
|
||||
// [42:22]: Index of the right child node, may be 0.
|
||||
// [43:63]: Index of the parent node, may be 0 for the root.
|
||||
u64 header{};
|
||||
|
||||
// Address range of the node
|
||||
AddressRange address_range{};
|
||||
|
||||
void SetRed(bool red) {
|
||||
header &= ~1ULL;
|
||||
header |= u64(red);
|
||||
}
|
||||
|
||||
bool IsRed() const {
|
||||
return header & 1u;
|
||||
}
|
||||
|
||||
void SetParent(u32 node) {
|
||||
header &= ~(NodeIndexMask << 43);
|
||||
header |= u64(node) << 43;
|
||||
}
|
||||
|
||||
void SetChild(u32 index, u32 node) {
|
||||
const u32 shift = (index ? 22 : 1);
|
||||
header &= ~(NodeIndexMask << shift);
|
||||
header |= u64(node) << shift;
|
||||
}
|
||||
|
||||
u32 Parent() const {
|
||||
return (header >> 43) & NodeIndexMask;
|
||||
}
|
||||
|
||||
u32 Child(u32 index) const {
|
||||
const u32 shift = (index ? 22 : 1);
|
||||
return (header >> shift) & NodeIndexMask;
|
||||
}
|
||||
|
||||
bool IsRoot() const {
|
||||
return Parent() == 0u;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides a two-part hash table for read and written resource ranges, which is
|
||||
* backed by binary trees to handle individual address ranges as well as collisions.
|
||||
*/
|
||||
class BarrierTracker {
|
||||
constexpr static u32 HashTableSize = 32u;
|
||||
|
||||
public:
|
||||
explicit BarrierTracker();
|
||||
~BarrierTracker() = default;
|
||||
|
||||
/// Checks whether there is a pending access of a given type
|
||||
bool FindRange(const AddressRange& range, Access access_type) const;
|
||||
|
||||
/// Inserts address range for a given access type
|
||||
void InsertRange(const AddressRange& range, Access access_type);
|
||||
|
||||
/// Clears the entire structure
|
||||
void Clear();
|
||||
|
||||
/// Checks whether any resources are dirty
|
||||
bool Empty() const {
|
||||
return !root_mask_valid;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 AllocateNode();
|
||||
void FreeNode(u32 node);
|
||||
u32 FindNode(const AddressRange& range, u32 root_index) const;
|
||||
u32 InsertNode(const AddressRange& range, u32 root_index);
|
||||
void RemoveNode(u32 node_index, u32 root_index);
|
||||
void RebalancePostInsert(u32 node_index, u32 root_index);
|
||||
void RotateLeft(u32 node_index, u32 root_index);
|
||||
void RotateRight(u32 node_index, u32 root_index);
|
||||
|
||||
constexpr static u32 ComputeRootIndex(const AddressRange& range, Access access) {
|
||||
u64 hash = range.resource * 93887;
|
||||
hash ^= (hash >> 16);
|
||||
return 1 + (hash % HashTableSize) + (access == Access::Write ? HashTableSize : 0);
|
||||
}
|
||||
|
||||
private:
|
||||
u64 root_mask_valid{};
|
||||
u64 root_mask_subtree{};
|
||||
std::vector<BarrierTreeNode> nodes;
|
||||
std::vector<u32> free;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -32,10 +32,13 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
const Shader::Profile& profile, const GraphicsPipelineKey& key_,
|
||||
vk::PipelineCache pipeline_cache, std::span<const Shader::Info*, MaxShaderStages> infos,
|
||||
std::span<const Shader::RuntimeInfo, MaxShaderStages> runtime_infos,
|
||||
std::optional<const Shader::Gcn::FetchShaderData> fetch_shader_,
|
||||
std::span<const vk::ShaderModule> modules, SerializationSupport& sdata, bool preloading)
|
||||
: Pipeline{instance, scheduler, desc_heap, profile, pipeline_cache}, key{key_},
|
||||
fetch_shader{std::move(fetch_shader_)} {
|
||||
const Shader::Gcn::FetchShaderData* fetch_shader_, std::span<const vk::ShaderModule> modules,
|
||||
SerializationSupport& sdata, bool preloading)
|
||||
: Pipeline{instance, scheduler, desc_heap, profile, pipeline_cache}, key{key_} {
|
||||
if (fetch_shader_) {
|
||||
fetch_shader = *fetch_shader_;
|
||||
}
|
||||
|
||||
const vk::Device device = instance.GetDevice();
|
||||
std::ranges::copy(infos, stages.begin());
|
||||
BuildDescSetLayout(preloading);
|
||||
@@ -436,21 +439,22 @@ void GraphicsPipeline::GetVertexInputs(
|
||||
VertexInputs<vk::VertexInputBindingDivisorDescriptionEXT>& divisors,
|
||||
VertexInputs<AmdGpu::Buffer>& guest_buffers, u32 step_rate_0, u32 step_rate_1) const {
|
||||
using InstanceIdType = Shader::Gcn::VertexAttribute::InstanceIdType;
|
||||
if (!fetch_shader || fetch_shader->attributes.empty()) {
|
||||
if (fetch_shader.Empty() || fetch_shader.attributes.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto& vs_info = GetStage(Shader::SwStage::Vertex);
|
||||
for (const auto& attrib : fetch_shader->attributes) {
|
||||
for (u32 semantic = 0; semantic < fetch_shader.attributes.size(); ++semantic) {
|
||||
const auto& attrib = fetch_shader.attributes[semantic];
|
||||
const auto step_rate = attrib.GetStepRate();
|
||||
const auto buffer = attrib.GetSharp(vs_info);
|
||||
attributes.push_back(Attribute{
|
||||
.location = attrib.semantic,
|
||||
.binding = attrib.semantic,
|
||||
.location = semantic,
|
||||
.binding = semantic,
|
||||
.format = LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()),
|
||||
.offset = 0,
|
||||
});
|
||||
bindings.push_back(Binding{
|
||||
.binding = attrib.semantic,
|
||||
.binding = semantic,
|
||||
.stride = buffer.GetStride(),
|
||||
.inputRate = step_rate == InstanceIdType::None ? vk::VertexInputRate::eVertex
|
||||
: vk::VertexInputRate::eInstance,
|
||||
@@ -462,7 +466,7 @@ void GraphicsPipeline::GetVertexInputs(
|
||||
bindings.back().divisor = divisor;
|
||||
} else if (step_rate != InstanceIdType::None) {
|
||||
divisors.push_back(vk::VertexInputBindingDivisorDescriptionEXT{
|
||||
.binding = attrib.semantic,
|
||||
.binding = semantic,
|
||||
.divisor = divisor,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,12 +88,12 @@ public:
|
||||
vk::PipelineCache pipeline_cache,
|
||||
std::span<const Shader::Info*, MaxShaderStages> stages,
|
||||
std::span<const Shader::RuntimeInfo, MaxShaderStages> runtime_infos,
|
||||
std::optional<const Shader::Gcn::FetchShaderData> fetch_shader,
|
||||
const Shader::Gcn::FetchShaderData* fetch_shader,
|
||||
std::span<const vk::ShaderModule> modules, SerializationSupport& sdata,
|
||||
bool preloading);
|
||||
~GraphicsPipeline();
|
||||
|
||||
const std::optional<const Shader::Gcn::FetchShaderData>& GetFetchShader() const noexcept {
|
||||
const Shader::Gcn::FetchShaderData& GetFetchShader() const noexcept {
|
||||
return fetch_shader;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ private:
|
||||
|
||||
private:
|
||||
GraphicsPipelineKey key;
|
||||
std::optional<const Shader::Gcn::FetchShaderData> fetch_shader{};
|
||||
Shader::Gcn::FetchShaderData fetch_shader{};
|
||||
};
|
||||
|
||||
struct ClipDistanceShaderKey {
|
||||
|
||||
@@ -356,7 +356,6 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline(const DrawIndirectPar
|
||||
}
|
||||
}
|
||||
}
|
||||
fetch_shader.reset();
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
@@ -489,7 +488,7 @@ bool PipelineCache::RefreshGraphicsKey() {
|
||||
bool PipelineCache::RefreshGraphicsStages() {
|
||||
const auto& regs = liverpool->regs;
|
||||
auto& key = graphics_key;
|
||||
fetch_shader = std::nullopt;
|
||||
fetch_shader = nullptr;
|
||||
|
||||
Shader::Backend::Bindings binding{};
|
||||
const auto bind_stage = [&](HwStage stage_in, SwStage stage_out) -> bool {
|
||||
@@ -509,13 +508,8 @@ bool PipelineCache::RefreshGraphicsStages() {
|
||||
}
|
||||
|
||||
const auto params = AmdGpu::GetParams(*pgm);
|
||||
std::optional<Shader::Gcn::FetchShaderData> fetch_shader_;
|
||||
std::tie(infos[stage_out_idx], modules[stage_out_idx], fetch_shader_,
|
||||
key.stage_hashes[stage_out_idx]) =
|
||||
std::tie(infos[stage_out_idx], modules[stage_out_idx], key.stage_hashes[stage_out_idx]) =
|
||||
GetProgram(stage_in, stage_out, params, binding);
|
||||
if (fetch_shader_) {
|
||||
fetch_shader = fetch_shader_;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -614,7 +608,7 @@ bool PipelineCache::RefreshComputeKey() {
|
||||
Shader::Backend::Bindings binding{};
|
||||
const auto& cs_pgm = liverpool->GetCsRegs();
|
||||
const auto cs_params = AmdGpu::GetParams(cs_pgm);
|
||||
std::tie(infos[0], modules[0], fetch_shader, compute_key.value) =
|
||||
std::tie(infos[0], modules[0], compute_key.value) =
|
||||
GetProgram(HwStage::Compute, SwStage::Compute, cs_params, binding);
|
||||
return true;
|
||||
}
|
||||
@@ -667,8 +661,10 @@ PipelineCache::Result PipelineCache::GetProgram(HwStage hw_stage, SwStage sw_sta
|
||||
|
||||
RegisterShaderMeta(program->info, spec.fetch_shader_data, spec, perm_hash, 0);
|
||||
program->AddPermut(module, std::move(spec));
|
||||
return std::make_tuple(&program->info, module, program->modules[0].spec.fetch_shader_data,
|
||||
perm_hash);
|
||||
if (auto& fetch = program->modules[0].spec.fetch_shader_data; !fetch.Empty()) {
|
||||
fetch_shader = &fetch;
|
||||
}
|
||||
return std::make_tuple(&program->info, module, perm_hash);
|
||||
}
|
||||
|
||||
auto& program = it_pgm.value();
|
||||
@@ -696,8 +692,10 @@ PipelineCache::Result PipelineCache::GetProgram(HwStage hw_stage, SwStage sw_sta
|
||||
perm_idx = std::distance(program->modules.begin(), it);
|
||||
perm_hash = HashCombine(params.hash, perm_idx);
|
||||
}
|
||||
return std::make_tuple(&program->info, module,
|
||||
program->modules[perm_idx].spec.fetch_shader_data, perm_hash);
|
||||
if (auto& fetch = program->modules[perm_idx].spec.fetch_shader_data; !fetch.Empty()) {
|
||||
fetch_shader = &fetch;
|
||||
}
|
||||
return std::make_tuple(&program->info, module, perm_hash);
|
||||
}
|
||||
|
||||
std::optional<vk::ShaderModule> PipelineCache::ReplaceShader(vk::ShaderModule module,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
|
||||
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
#include "vulkan/vulkan.hpp"
|
||||
|
||||
template <>
|
||||
struct std::hash<vk::ShaderModule> {
|
||||
@@ -85,9 +86,8 @@ public:
|
||||
|
||||
const ComputePipeline* GetComputePipeline();
|
||||
|
||||
using Result = std::tuple<const Shader::Info*, vk::ShaderModule,
|
||||
std::optional<Shader::Gcn::FetchShaderData>, u64>;
|
||||
Result GetProgram(Shader::HwStage stage, Shader::SwStage l_stage,
|
||||
using Result = std::tuple<const Shader::Info*, vk::ShaderModule, u64>;
|
||||
Result GetProgram(Shader::HwStage hw_stage, Shader::SwStage sw_stage,
|
||||
const Shader::ShaderParams& params, Shader::Backend::Bindings& binding);
|
||||
|
||||
std::optional<vk::ShaderModule> ReplaceShader(vk::ShaderModule module,
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
std::array<Shader::RuntimeInfo, MaxShaderStages> runtime_infos{};
|
||||
std::array<const Shader::Info*, MaxShaderStages> infos{};
|
||||
std::array<vk::ShaderModule, MaxShaderStages> modules{};
|
||||
std::optional<Shader::Gcn::FetchShaderData> fetch_shader{};
|
||||
Shader::Gcn::FetchShaderData* fetch_shader{};
|
||||
GraphicsPipelineKey graphics_key{};
|
||||
ComputePipelineKey compute_key{};
|
||||
u32 num_new_pipelines{}; // new pipelines added to the cache since the game start
|
||||
|
||||
@@ -92,7 +92,6 @@ void RegisterShaderBinary(std::vector<u32>&& spv, u64 pgm_hash, size_t perm_idx)
|
||||
}
|
||||
|
||||
bool LoadShaderMeta(Serialization::Archive& ar, Shader::Info& info,
|
||||
std::optional<Shader::Gcn::FetchShaderData>& fetch_shader_data,
|
||||
Shader::StageSpecialization& spec, size_t& perm_idx) {
|
||||
Serialization::Reader meta{ar};
|
||||
|
||||
@@ -114,8 +113,6 @@ bool LoadShaderMeta(Serialization::Archive& ar, Shader::Info& info,
|
||||
|
||||
spec.Deserialize(ar);
|
||||
info.Deserialize(ar);
|
||||
|
||||
fetch_shader_data = spec.fetch_shader_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -243,7 +240,7 @@ bool PipelineCache::LoadGraphicsPipeline(Serialization::Archive& ar) {
|
||||
|
||||
infos.fill(nullptr);
|
||||
modules.fill(nullptr);
|
||||
fetch_shader.reset();
|
||||
fetch_shader = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -253,7 +250,7 @@ bool PipelineCache::LoadPipelineStage(Serialization::Archive& ar, size_t stage)
|
||||
Shader::StageSpecialization spec{};
|
||||
spec.info = &program->info;
|
||||
size_t perm_idx{};
|
||||
if (!LoadShaderMeta(ar, program->info, fetch_shader, spec, perm_idx)) {
|
||||
if (!LoadShaderMeta(ar, program->info, spec, perm_idx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -297,6 +294,9 @@ bool PipelineCache::LoadPipelineStage(Serialization::Archive& ar, size_t stage)
|
||||
|
||||
infos[stage] = &it_pgm.value()->info;
|
||||
modules[stage] = module;
|
||||
if (auto& fetch = it_pgm.value()->modules[perm_idx].spec.fetch_shader_data; !fetch.Empty()) {
|
||||
fetch_shader = &fetch;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -453,9 +453,9 @@ void StageSpecialization::Serialize(Serialization::Archive& ar) const {
|
||||
|
||||
spec.Write(bitset.to_string());
|
||||
|
||||
if (fetch_shader_data) {
|
||||
spec.Write(sizeof(*fetch_shader_data));
|
||||
fetch_shader_data->Serialize(ar);
|
||||
if (!fetch_shader_data.Empty()) {
|
||||
spec.Write(sizeof(fetch_shader_data));
|
||||
fetch_shader_data.Serialize(ar);
|
||||
} else {
|
||||
spec.Write(size_t{0});
|
||||
}
|
||||
|
||||
@@ -143,17 +143,16 @@ void Rasterizer::PrepareRenderState(const GraphicsPipeline* pipeline) {
|
||||
}
|
||||
}
|
||||
|
||||
static std::pair<u32, u32> GetDrawOffsets(
|
||||
const AmdGpu::Regs& regs, const Shader::Info& info,
|
||||
const std::optional<Shader::Gcn::FetchShaderData>& fetch_shader) {
|
||||
static std::pair<u32, u32> GetDrawOffsets(const AmdGpu::Regs& regs, const Shader::Info& info,
|
||||
const Shader::Gcn::FetchShaderData& fetch_shader) {
|
||||
u32 vertex_offset = regs.index_offset;
|
||||
u32 instance_offset = 0;
|
||||
if (fetch_shader) {
|
||||
if (vertex_offset == 0 && fetch_shader->vertex_offset_sgpr != -1) {
|
||||
vertex_offset = info.user_data[fetch_shader->vertex_offset_sgpr];
|
||||
if (!fetch_shader.Empty()) {
|
||||
if (vertex_offset == 0 && fetch_shader.vertex_offset_sgpr != -1) {
|
||||
vertex_offset = info.user_data[fetch_shader.vertex_offset_sgpr];
|
||||
}
|
||||
if (fetch_shader->instance_offset_sgpr != -1) {
|
||||
instance_offset = info.user_data[fetch_shader->instance_offset_sgpr];
|
||||
if (fetch_shader.instance_offset_sgpr != -1) {
|
||||
instance_offset = info.user_data[fetch_shader.instance_offset_sgpr];
|
||||
}
|
||||
}
|
||||
return {vertex_offset, instance_offset};
|
||||
@@ -729,7 +728,7 @@ void Rasterizer::BindBuffers(const Shader::Info& stage, Shader::Backend::Binding
|
||||
if (desc.buffer_type == Shader::BufferType::GdsBuffer) {
|
||||
const auto* gds_buf = buffer_cache.GetGdsBuffer();
|
||||
buffer_infos.emplace_back(gds_buf->Handle(), 0, gds_buf->SizeBytes());
|
||||
needs_barrier |= runtime.IsBufferAccessed(gds_buf, 0, gds_buf->SizeBytes());
|
||||
bound_buffers.emplace_back(gds_buf, 0, gds_buf->SizeBytes(), desc.is_written);
|
||||
} else if (desc.buffer_type == Shader::BufferType::Flatbuf) {
|
||||
auto& vk_buffer = buffer_cache.GetStreamBuffer();
|
||||
const u32 ubo_size = stage.flattened_ud_buf.size() * sizeof(u32);
|
||||
@@ -908,7 +907,8 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin
|
||||
} else {
|
||||
needs_barrier |= runtime.Transit(
|
||||
&image, vk::ImageLayout::eGeneral, vk::PipelineStageFlagBits2::eAllCommands,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite);
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
|
||||
desc.view_info.range);
|
||||
}
|
||||
} else {
|
||||
if (is_storage) {
|
||||
@@ -1166,7 +1166,8 @@ void Rasterizer::FillBuffer(VAddr address, u32 num_bytes, u32 value, bool is_gds
|
||||
}
|
||||
|
||||
void Rasterizer::CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds) {
|
||||
if (!dst_gds && !buffer_cache.IsRegionGpuModified(dst, num_bytes)) {
|
||||
if (!dst_gds && !buffer_cache.IsRegionGpuModified(dst, num_bytes) &&
|
||||
!buffer_cache.IsRegionInSyncBatch(dst, num_bytes)) {
|
||||
if (!src_gds && !buffer_cache.IsRegionGpuModified(src, num_bytes) &&
|
||||
!texture_cache.FindImageFromRange(src, num_bytes)) {
|
||||
// Both buffers were not transferred to GPU yet. Can safely copy in host memory.
|
||||
|
||||
@@ -679,24 +679,21 @@ void Runtime::SetBackingSamples(VideoCore::Image* image, u32 num_samples, bool c
|
||||
|
||||
bool Runtime::IsBufferAccessed(const VideoCore::Buffer* handle, u64 offset, u64 size,
|
||||
bool check_read_access) {
|
||||
const AddressRange range = {
|
||||
.resource = reinterpret_cast<u64>(handle),
|
||||
.range_start = offset,
|
||||
.range_end = offset + size - 1,
|
||||
};
|
||||
bool has_access = barrier_tracker.FindRange(range, Access::Write);
|
||||
MakeCurrent(handle);
|
||||
bool has_access = resource->write_ranges.Overlaps(offset, offset + size);
|
||||
if (check_read_access && !has_access) {
|
||||
has_access |= barrier_tracker.FindRange(range, Access::Read);
|
||||
has_access |= resource->read_ranges.Overlaps(offset, offset + size);
|
||||
}
|
||||
return has_access;
|
||||
}
|
||||
|
||||
void Runtime::AccessBuffer(const VideoCore::Buffer* handle, u64 offset, u64 size,
|
||||
vk::PipelineStageFlags2 src_stage, vk::AccessFlags2 src_access) {
|
||||
const AddressRange range = {
|
||||
.resource = reinterpret_cast<u64>(handle),
|
||||
.range_start = offset,
|
||||
.range_end = offset + size - 1,
|
||||
MakeCurrent(handle);
|
||||
|
||||
const Interval range = {
|
||||
.start = offset,
|
||||
.end = offset + size,
|
||||
};
|
||||
|
||||
constexpr static vk::AccessFlags2 READ_MASK =
|
||||
@@ -712,10 +709,10 @@ void Runtime::AccessBuffer(const VideoCore::Buffer* handle, u64 offset, u64 size
|
||||
vk::AccessFlagBits2::eMemoryWrite | vk::AccessFlagBits2::eTransformFeedbackWriteEXT;
|
||||
|
||||
if (src_access & WRITE_MASK) {
|
||||
barrier_tracker.InsertRange(range, Access::Write);
|
||||
resource->write_ranges.Add(range);
|
||||
}
|
||||
if (src_access & READ_MASK) {
|
||||
barrier_tracker.InsertRange(range, Access::Read);
|
||||
resource->read_ranges.Add(range);
|
||||
}
|
||||
|
||||
memory_barrier.srcStageMask |= src_stage;
|
||||
@@ -746,7 +743,24 @@ void Runtime::FlushBarriers() {
|
||||
memory_barrier.srcAccessMask = vk::AccessFlagBits2::eNone;
|
||||
|
||||
image_barriers.clear();
|
||||
barrier_tracker.Clear();
|
||||
for (auto& resource : resources) {
|
||||
resource.read_ranges.Clear();
|
||||
resource.write_ranges.Clear();
|
||||
}
|
||||
resources.clear();
|
||||
resource = nullptr;
|
||||
}
|
||||
|
||||
void Runtime::MakeCurrent(const VideoCore::Buffer* handle) {
|
||||
if (resource && resource->handle == handle) {
|
||||
return;
|
||||
}
|
||||
auto it = std::ranges::find(resources, handle, &BufferBarriers::handle);
|
||||
if (it != resources.end()) {
|
||||
resource = std::addressof(*it);
|
||||
return;
|
||||
}
|
||||
resource = &resources.emplace_back(handle);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/interval_set.h"
|
||||
#include "common/types.h"
|
||||
#include "video_core/buffer_cache/buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_barrier_tracker.h"
|
||||
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
|
||||
#include "video_core/texture_cache/image.h"
|
||||
#include "video_core/texture_cache/types.h"
|
||||
@@ -76,12 +76,22 @@ public:
|
||||
|
||||
void FlushBarriers();
|
||||
|
||||
private:
|
||||
void MakeCurrent(const VideoCore::Buffer* handle);
|
||||
|
||||
private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
std::unique_ptr<VideoCore::BlitHelper> blit_helper;
|
||||
StagingBufferPool staging_pool;
|
||||
BarrierTracker barrier_tracker;
|
||||
struct BufferBarriers {
|
||||
const VideoCore::Buffer* handle;
|
||||
using AccessList = IntervalList<Interval>;
|
||||
AccessList read_ranges;
|
||||
AccessList write_ranges;
|
||||
};
|
||||
BufferBarriers* resource{};
|
||||
std::vector<BufferBarriers> resources;
|
||||
VideoCore::Image::Barriers image_barriers;
|
||||
vk::MemoryBarrier2 memory_barrier{};
|
||||
};
|
||||
|
||||
@@ -259,11 +259,7 @@ void Image::GetBarriers(Barriers& barriers, vk::ImageLayout dst_layout, vk::Acce
|
||||
ASSERT(subres_idx < subresource_states.size());
|
||||
auto& state = subresource_states[subres_idx];
|
||||
|
||||
constexpr auto write_flags = vk::AccessFlagBits2::eTransferWrite |
|
||||
vk::AccessFlagBits2::eShaderWrite |
|
||||
vk::AccessFlagBits2::eMemoryWrite;
|
||||
const bool is_write = static_cast<bool>(state.access_mask & write_flags);
|
||||
if (state.layout != dst_layout || state.access_mask != dst_mask || is_write) {
|
||||
if (state.layout != dst_layout || state.access_mask != dst_mask) {
|
||||
barriers.emplace_back(vk::ImageMemoryBarrier2{
|
||||
.srcStageMask = state.pl_stage,
|
||||
.srcAccessMask = state.access_mask,
|
||||
@@ -293,14 +289,9 @@ void Image::GetBarriers(Barriers& barriers, vk::ImageLayout dst_layout, vk::Acce
|
||||
subresource_states.clear();
|
||||
}
|
||||
} else { // Full resource transition
|
||||
constexpr auto write_flags = vk::AccessFlagBits2::eTransferWrite |
|
||||
vk::AccessFlagBits2::eShaderWrite |
|
||||
vk::AccessFlagBits2::eMemoryWrite;
|
||||
const bool is_write = static_cast<bool>(last_state.access_mask & write_flags);
|
||||
if (last_state.layout == dst_layout && last_state.access_mask == dst_mask && !is_write) {
|
||||
if (last_state.layout == dst_layout && last_state.access_mask == dst_mask) {
|
||||
return;
|
||||
}
|
||||
|
||||
barriers.emplace_back(vk::ImageMemoryBarrier2{
|
||||
.srcStageMask = last_state.pl_stage,
|
||||
.srcAccessMask = last_state.access_mask,
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include "common/lru_cache.h"
|
||||
#include "common/multi_level_page_table.h"
|
||||
#include "common/slot_vector.h"
|
||||
#include "shader_recompiler/resource.h"
|
||||
#include "video_core/multi_level_page_table.h"
|
||||
#include "video_core/texture_cache/blit_helper.h"
|
||||
#include "video_core/texture_cache/image.h"
|
||||
#include "video_core/texture_cache/image_view.h"
|
||||
@@ -49,7 +49,7 @@ class TextureCache {
|
||||
static constexpr size_t PAGE_BITS = 20;
|
||||
static constexpr bool NULL_CHECK = true;
|
||||
};
|
||||
using PageTable = MultiLevelPageTable<Traits>;
|
||||
using PageTable = Common::MultiLevelPageTable<Traits>;
|
||||
|
||||
public:
|
||||
enum class BindingType : u32 {
|
||||
@@ -119,7 +119,7 @@ public:
|
||||
|
||||
/// Updates image contents if it was modified by CPU.
|
||||
void UpdateImage(ImageId image_id) {
|
||||
std::scoped_lock lock{mutex};
|
||||
std::scoped_lock lk{mutex};
|
||||
Image& image = slot_images[image_id];
|
||||
TrackImage(image_id);
|
||||
TouchImage(image);
|
||||
|
||||
Reference in New Issue
Block a user