mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-15 21:19:47 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71cf0e0daa | |||
| 59444d5c48 | |||
| 3aa4fb867c | |||
| ab1ad1dc4a | |||
| 0dc64e2a48 | |||
| 420b002448 | |||
| 413cebca20 | |||
| a2d830e51b | |||
| 47d6360038 | |||
| e0daa0d83d | |||
| c487a5cbc4 | |||
| 6b7e54e115 | |||
| 8bbbd28f48 | |||
| ca7c1c7230 | |||
| db10193a16 | |||
| 8c045a47d2 | |||
| 616e0a93c7 | |||
| 78e7037ccc | |||
| 635335fc85 | |||
| e35efd3db1 | |||
| 901f556af5 | |||
| ef730ed490 | |||
| c188b6a819 | |||
| 476f035672 | |||
| b4a1207dab | |||
| 59c41f0746 | |||
| c2f0a1c786 | |||
| a711a4e6ab | |||
| 5aa9ed4dc0 | |||
| 1b8f4c2062 | |||
| 1eaf8bb28f | |||
| 28bf78db3a |
+1
-2
@@ -25,8 +25,7 @@ void AssertFailSoftImpl();
|
||||
|
||||
#define ASSERT_MSG(_a_, ...) \
|
||||
([&]() YUZU_NO_INLINE { \
|
||||
auto&& _a_eval_ = (_a_); \
|
||||
if (!(_a_eval_)) [[unlikely]] { \
|
||||
if (!(_a_)) [[unlikely]] { \
|
||||
LOG_CRITICAL(Debug, __FILE__ ": assert " __VA_ARGS__); \
|
||||
AssertFailSoftImpl(); \
|
||||
} \
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/div_ceil.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename AddressType, u32 PageBits, u64 MaxAddress>
|
||||
class PageBitsetRangeSet {
|
||||
public:
|
||||
PageBitsetRangeSet() : m_words(kWordCount) {
|
||||
static_assert((MaxAddress % kPageSize) == 0, "MaxAddress must be page-aligned.");
|
||||
}
|
||||
|
||||
void Add(AddressType base_address, size_t size) {
|
||||
Modify(base_address, size, true);
|
||||
}
|
||||
|
||||
void Subtract(AddressType base_address, size_t size) {
|
||||
Modify(base_address, size, false);
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
std::fill(m_words.begin(), m_words.end(), 0);
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return std::none_of(m_words.begin(), m_words.end(), [](u64 word) { return word != 0; });
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void ForEachInRange(AddressType base_address, size_t size, Func&& func) const {
|
||||
if (size == 0 || m_words.empty()) {
|
||||
return;
|
||||
}
|
||||
const AddressType end_address = base_address + static_cast<AddressType>(size);
|
||||
const u64 start_page = static_cast<u64>(base_address) >> PageBits;
|
||||
const u64 end_page = Common::DivCeil(static_cast<u64>(end_address), kPageSize);
|
||||
const u64 clamped_start = (std::min)(start_page, kPageCount);
|
||||
const u64 clamped_end = (std::min)(end_page, kPageCount);
|
||||
if (clamped_start >= clamped_end) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool in_run = false;
|
||||
u64 run_start_page = 0;
|
||||
for (u64 page = clamped_start; page < clamped_end; ++page) {
|
||||
if (Test(page)) {
|
||||
if (!in_run) {
|
||||
in_run = true;
|
||||
run_start_page = page;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (in_run) {
|
||||
EmitRun(run_start_page, page, base_address, end_address, func);
|
||||
in_run = false;
|
||||
}
|
||||
}
|
||||
if (in_run) {
|
||||
EmitRun(run_start_page, clamped_end, base_address, end_address, func);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr u64 kPageSize = u64{1} << PageBits;
|
||||
static constexpr u64 kPageCount = MaxAddress / kPageSize;
|
||||
static constexpr u64 kWordCount = (kPageCount + 63) / 64;
|
||||
|
||||
void Modify(AddressType base_address, size_t size, bool set_bits) {
|
||||
if (size == 0 || m_words.empty()) {
|
||||
return;
|
||||
}
|
||||
const AddressType end_address = base_address + static_cast<AddressType>(size);
|
||||
const u64 start_page = static_cast<u64>(base_address) >> PageBits;
|
||||
const u64 end_page = Common::DivCeil(static_cast<u64>(end_address), kPageSize);
|
||||
const u64 clamped_start = (std::min)(start_page, kPageCount);
|
||||
const u64 clamped_end = (std::min)(end_page, kPageCount);
|
||||
if (clamped_start >= clamped_end) {
|
||||
return;
|
||||
}
|
||||
const u64 start_word = clamped_start / 64;
|
||||
const u64 end_word = (clamped_end - 1) / 64;
|
||||
const u64 start_mask = ~0ULL << (clamped_start % 64);
|
||||
const u64 end_mask = (clamped_end % 64) == 0 ? ~0ULL : ((1ULL << (clamped_end % 64)) - 1);
|
||||
|
||||
if (start_word == end_word) {
|
||||
const u64 mask = start_mask & end_mask;
|
||||
if (set_bits) {
|
||||
m_words[start_word] |= mask;
|
||||
} else {
|
||||
m_words[start_word] &= ~mask;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (set_bits) {
|
||||
m_words[start_word] |= start_mask;
|
||||
m_words[end_word] |= end_mask;
|
||||
std::fill(m_words.begin() + static_cast<std::ptrdiff_t>(start_word + 1),
|
||||
m_words.begin() + static_cast<std::ptrdiff_t>(end_word), ~0ULL);
|
||||
} else {
|
||||
m_words[start_word] &= ~start_mask;
|
||||
m_words[end_word] &= ~end_mask;
|
||||
std::fill(m_words.begin() + static_cast<std::ptrdiff_t>(start_word + 1),
|
||||
m_words.begin() + static_cast<std::ptrdiff_t>(end_word), 0ULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool Test(u64 page) const {
|
||||
const u64 word = page / 64;
|
||||
const u64 bit = page % 64;
|
||||
return (m_words[word] & (1ULL << bit)) != 0;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
static void EmitRun(u64 run_start_page, u64 run_end_page, AddressType base_address,
|
||||
AddressType end_address, Func&& func) {
|
||||
AddressType run_start = static_cast<AddressType>(run_start_page * kPageSize);
|
||||
AddressType run_end = static_cast<AddressType>(run_end_page * kPageSize);
|
||||
if (run_start < base_address) {
|
||||
run_start = base_address;
|
||||
}
|
||||
if (run_end > end_address) {
|
||||
run_end = end_address;
|
||||
}
|
||||
if (run_start < run_end) {
|
||||
func(run_start, run_end);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u64> m_words;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
+18
-18
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -116,28 +119,25 @@ struct OverlapRangeSet<AddressType>::OverlapRangeSetImpl {
|
||||
}
|
||||
AddressType end_address = base_address + static_cast<AddressType>(size);
|
||||
IntervalType interval{base_address, end_address};
|
||||
bool any_removals = false;
|
||||
m_split_ranges_set += std::make_pair(interval, -amount);
|
||||
do {
|
||||
any_removals = false;
|
||||
auto it = m_split_ranges_set.lower_bound(interval);
|
||||
if (it == m_split_ranges_set.end()) {
|
||||
return;
|
||||
}
|
||||
auto end_it = m_split_ranges_set.upper_bound(interval);
|
||||
for (; it != end_it; it++) {
|
||||
if (it->second <= 0) {
|
||||
if constexpr (has_on_delete) {
|
||||
if (it->second == 0) {
|
||||
on_delete(it->first.lower(), it->first.upper());
|
||||
}
|
||||
auto it = m_split_ranges_set.lower_bound(interval);
|
||||
if (it == m_split_ranges_set.end()) {
|
||||
return;
|
||||
}
|
||||
auto end_it = m_split_ranges_set.upper_bound(interval);
|
||||
while (it != end_it) {
|
||||
if (it->second <= 0) {
|
||||
if constexpr (has_on_delete) {
|
||||
if (it->second == 0) {
|
||||
on_delete(it->first.lower(), it->first.upper());
|
||||
}
|
||||
any_removals = true;
|
||||
m_split_ranges_set.erase(it);
|
||||
break;
|
||||
}
|
||||
auto to_erase = it++;
|
||||
m_split_ranges_set.erase(to_erase);
|
||||
continue;
|
||||
}
|
||||
} while (any_removals);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
|
||||
@@ -127,6 +127,11 @@ public:
|
||||
void UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta);
|
||||
|
||||
private:
|
||||
struct TranslationEntry {
|
||||
DAddr guest_page{};
|
||||
u8* host_ptr{};
|
||||
};
|
||||
|
||||
// Internal helper that performs the update assuming the caller already holds the necessary lock.
|
||||
void UpdatePagesCachedCountNoLock(DAddr addr, size_t size, s32 delta);
|
||||
|
||||
@@ -195,6 +200,8 @@ private:
|
||||
}
|
||||
|
||||
Common::VirtualBuffer<VAddr> cpu_backing_address;
|
||||
std::array<TranslationEntry, 4> t_slot{};
|
||||
u32 cache_cursor = 0;
|
||||
using CounterType = u8;
|
||||
using CounterAtomicType = std::atomic_uint8_t;
|
||||
static constexpr size_t subentries = 8 / sizeof(CounterType);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -247,6 +247,7 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size
|
||||
}
|
||||
impl->multi_dev_address.Register(new_dev, start_id);
|
||||
}
|
||||
t_slot = {};
|
||||
if (track) {
|
||||
TrackContinuityImpl(address, virtual_address, size, asid);
|
||||
}
|
||||
@@ -278,6 +279,7 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) {
|
||||
compressed_device_addr[phys_addr - 1] = new_start | MULTI_FLAG;
|
||||
}
|
||||
}
|
||||
t_slot = {};
|
||||
}
|
||||
template <typename Traits>
|
||||
void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtual_address,
|
||||
@@ -417,6 +419,26 @@ void DeviceMemoryManager<Traits>::WalkBlock(DAddr addr, std::size_t size, auto o
|
||||
template <typename Traits>
|
||||
void DeviceMemoryManager<Traits>::ReadBlock(DAddr address, void* dest_pointer, size_t size) {
|
||||
device_inter->FlushRegion(address, size);
|
||||
const std::size_t page_offset = address & Memory::YUZU_PAGEMASK;
|
||||
if (size <= Memory::YUZU_PAGESIZE - page_offset) {
|
||||
const DAddr guest_page = address & ~static_cast<DAddr>(Memory::YUZU_PAGEMASK);
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
if (t_slot[i].guest_page == guest_page && t_slot[i].host_ptr != nullptr) {
|
||||
std::memcpy(dest_pointer, t_slot[i].host_ptr + page_offset, size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t page_index = address >> Memory::YUZU_PAGEBITS;
|
||||
const auto phys_addr = compressed_physical_ptr[page_index];
|
||||
if (phys_addr != 0) {
|
||||
auto* const mem_ptr = GetPointerFromRaw<u8>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS));
|
||||
t_slot[cache_cursor % t_slot.size()] = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr};
|
||||
cache_cursor = (cache_cursor + 1) & 3U;
|
||||
std::memcpy(dest_pointer, mem_ptr + page_offset, size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
WalkBlock(
|
||||
address, size,
|
||||
[&](size_t copy_amount, DAddr current_vaddr) {
|
||||
@@ -455,6 +477,26 @@ void DeviceMemoryManager<Traits>::WriteBlock(DAddr address, const void* src_poin
|
||||
|
||||
template <typename Traits>
|
||||
void DeviceMemoryManager<Traits>::ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size) {
|
||||
const std::size_t page_offset = address & Memory::YUZU_PAGEMASK;
|
||||
if (size <= Memory::YUZU_PAGESIZE - page_offset) {
|
||||
const DAddr guest_page = address & ~static_cast<DAddr>(Memory::YUZU_PAGEMASK);
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
if (t_slot[i].guest_page == guest_page && t_slot[i].host_ptr != nullptr) {
|
||||
std::memcpy(dest_pointer, t_slot[i].host_ptr + page_offset, size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t page_index = address >> Memory::YUZU_PAGEBITS;
|
||||
const auto phys_addr = compressed_physical_ptr[page_index];
|
||||
if (phys_addr != 0) {
|
||||
auto* const mem_ptr = GetPointerFromRaw<u8>((PAddr(phys_addr - 1) << Memory::YUZU_PAGEBITS));
|
||||
t_slot[cache_cursor % t_slot.size()] = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr};
|
||||
cache_cursor = (cache_cursor + 1) & 3U;
|
||||
std::memcpy(dest_pointer, mem_ptr + page_offset, size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
WalkBlock(
|
||||
address, size,
|
||||
[&](size_t copy_amount, DAddr current_vaddr) {
|
||||
|
||||
@@ -206,16 +206,10 @@ std::string_view FormatStorage(ImageFormat format) {
|
||||
return "S16";
|
||||
case ImageFormat::R32_UINT:
|
||||
return "U32";
|
||||
case ImageFormat::R32_SINT:
|
||||
return "S32";
|
||||
case ImageFormat::R32G32_UINT:
|
||||
return "U32X2";
|
||||
case ImageFormat::R32G32_SINT:
|
||||
return "S32X2";
|
||||
case ImageFormat::R32G32B32A32_UINT:
|
||||
return "U32X4";
|
||||
case ImageFormat::R32G32B32A32_SINT:
|
||||
return "S32X4";
|
||||
}
|
||||
throw InvalidArgument("Invalid image format {}", format);
|
||||
}
|
||||
|
||||
@@ -148,16 +148,10 @@ std::string_view ImageFormatString(ImageFormat format) {
|
||||
return ",r16i";
|
||||
case ImageFormat::R32_UINT:
|
||||
return ",r32ui";
|
||||
case ImageFormat::R32_SINT:
|
||||
return ",r32i";
|
||||
case ImageFormat::R32G32_UINT:
|
||||
return ",rg32ui";
|
||||
case ImageFormat::R32G32_SINT:
|
||||
return ",rg32i";
|
||||
case ImageFormat::R32G32B32A32_UINT:
|
||||
return ",rgba32ui";
|
||||
case ImageFormat::R32G32B32A32_SINT:
|
||||
return ",rgba32i";
|
||||
default:
|
||||
throw NotImplementedException("Image format: {}", format);
|
||||
}
|
||||
|
||||
@@ -142,22 +142,15 @@ Id GetCbuf(EmitContext& ctx, Id result_type, Id UniformDefinitions::*member_ptr,
|
||||
|
||||
const auto is_float = UniformDefinitions::IsFloat(member_ptr);
|
||||
const auto num_elements = UniformDefinitions::NumElements(member_ptr);
|
||||
const Id zero_element = is_float ? ctx.Const(0.0f) : ctx.Const(0u);
|
||||
|
||||
const std::array zero_vec{
|
||||
is_float ? ctx.Const(0.0f) : ctx.Const(0u),
|
||||
is_float ? ctx.Const(0.0f) : ctx.Const(0u),
|
||||
is_float ? ctx.Const(0.0f) : ctx.Const(0u),
|
||||
is_float ? ctx.Const(0.0f) : ctx.Const(0u),
|
||||
};
|
||||
const Id cond = ctx.OpULessThanEqual(ctx.TypeBool(), buffer_offset, ctx.Const(0xFFFFu));
|
||||
|
||||
// OpSelect with vector result requires vector condition, scalar uses scalar directly
|
||||
if (num_elements > 1) {
|
||||
const std::array zero_vec{zero_element, zero_element, zero_element, zero_element};
|
||||
const Id zero = ctx.OpCompositeConstruct(result_type, std::span(zero_vec.data(), num_elements));
|
||||
|
||||
const Id bool_vector_type = ctx.TypeVector(ctx.U1, num_elements);
|
||||
const std::array cond_vec{cond, cond, cond, cond};
|
||||
const Id vector_cond = ctx.OpCompositeConstruct(bool_vector_type, std::span(cond_vec.data(), num_elements));
|
||||
return ctx.OpSelect(result_type, vector_cond, val, zero);
|
||||
} else {
|
||||
return ctx.OpSelect(result_type, cond, val, zero_element);
|
||||
}
|
||||
const Id zero = ctx.OpCompositeConstruct(result_type, std::span(zero_vec.data(), num_elements));
|
||||
return ctx.OpSelect(result_type, cond, val, zero);
|
||||
}
|
||||
|
||||
Id GetCbufU32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
|
||||
|
||||
@@ -205,7 +205,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
|
||||
if (def.count > 1) {
|
||||
throw NotImplementedException("Indirect texture sample");
|
||||
}
|
||||
return ctx.OpLoad(def.image_type, def.id);
|
||||
return ctx.OpLoad(ctx.image_buffer_type, def.id);
|
||||
} else {
|
||||
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
|
||||
if (def.count > 1) {
|
||||
@@ -215,22 +215,16 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageInfo {
|
||||
Id image;
|
||||
bool is_integer;
|
||||
bool is_signed;
|
||||
};
|
||||
|
||||
ImageInfo Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) {
|
||||
std::pair<Id, bool> Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) {
|
||||
if (!index.IsImmediate() || index.U32() != 0) {
|
||||
throw NotImplementedException("Indirect image indexing");
|
||||
}
|
||||
if (info.type == TextureType::Buffer) {
|
||||
const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
|
||||
return {ctx.OpLoad(def.image_type, def.id), def.is_integer, def.is_signed};
|
||||
return {ctx.OpLoad(def.image_type, def.id), def.is_integer};
|
||||
} else {
|
||||
const ImageDefinition def{ctx.images.at(info.descriptor_index)};
|
||||
return {ctx.OpLoad(def.image_type, def.id), def.is_integer, def.is_signed};
|
||||
return {ctx.OpLoad(def.image_type, def.id), def.is_integer};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,25 +550,8 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
|
||||
lod = Id{};
|
||||
}
|
||||
const ImageOperands operands(lod, ms);
|
||||
bool is_integer = false;
|
||||
bool is_signed = false;
|
||||
Id result_type{ctx.F32[4]};
|
||||
if (info.type == TextureType::Buffer) {
|
||||
const TextureBufferDefinition& def{ctx.texture_buffers.at(info.descriptor_index)};
|
||||
is_integer = def.is_integer;
|
||||
is_signed = def.is_signed;
|
||||
if (is_integer) {
|
||||
result_type = is_signed ? ctx.S32[4] : ctx.U32[4];
|
||||
}
|
||||
}
|
||||
Id fetched = Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst,
|
||||
result_type, TextureImage(ctx, info, index), coords,
|
||||
operands.MaskOptional(), operands.Span());
|
||||
if (is_integer) {
|
||||
// IR expects F32x4 from ImageFetch; bitcast integer results to float vector.
|
||||
fetched = ctx.OpBitcast(ctx.F32[4], fetched);
|
||||
}
|
||||
return fetched;
|
||||
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
|
||||
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
|
||||
}
|
||||
|
||||
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
|
||||
@@ -635,25 +612,21 @@ Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id co
|
||||
LOG_WARNING(Shader_SPIRV, "Typeless image read not supported by host");
|
||||
return ctx.ConstantNull(ctx.U32[4]);
|
||||
}
|
||||
const auto [image, is_integer, is_signed] = Image(ctx, index, info);
|
||||
const Id result_type{is_integer ? (is_signed ? ctx.S32[4] : ctx.U32[4]) : ctx.F32[4]};
|
||||
const auto [image, is_integer] = Image(ctx, index, info);
|
||||
const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]};
|
||||
Id color{Emit(&EmitContext::OpImageSparseRead, &EmitContext::OpImageRead, ctx, inst,
|
||||
result_type, image, coords, std::nullopt, std::span<const Id>{})};
|
||||
if (!is_integer) {
|
||||
color = ctx.OpBitcast(ctx.U32[4], color);
|
||||
} else if (is_signed) {
|
||||
color = ctx.OpBitcast(ctx.U32[4], color);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id color) {
|
||||
const auto info{inst->Flags<IR::TextureInstInfo>()};
|
||||
const auto [image, is_integer, is_signed] = Image(ctx, index, info);
|
||||
const auto [image, is_integer] = Image(ctx, index, info);
|
||||
if (!is_integer) {
|
||||
color = ctx.OpBitcast(ctx.F32[4], color);
|
||||
} else if (is_signed) {
|
||||
color = ctx.OpBitcast(ctx.S32[4], color);
|
||||
}
|
||||
ctx.OpImageWrite(image, coords, color);
|
||||
}
|
||||
|
||||
@@ -7,18 +7,13 @@
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
namespace {
|
||||
struct ImageInfo {
|
||||
Id id;
|
||||
bool is_signed;
|
||||
};
|
||||
|
||||
ImageInfo Image(EmitContext& ctx, IR::TextureInstInfo info) {
|
||||
Id Image(EmitContext& ctx, IR::TextureInstInfo info) {
|
||||
if (info.type == TextureType::Buffer) {
|
||||
const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
|
||||
return {def.id, def.is_signed};
|
||||
return def.id;
|
||||
} else {
|
||||
const ImageDefinition def{ctx.images.at(info.descriptor_index)};
|
||||
return {def.id, def.is_signed};
|
||||
return def.id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,66 +24,42 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
|
||||
}
|
||||
|
||||
Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id), bool value_signed) {
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
|
||||
if (!index.IsImmediate() || index.U32() != 0) {
|
||||
// TODO: handle layers
|
||||
throw NotImplementedException("Image indexing");
|
||||
}
|
||||
const auto info{inst->Flags<IR::TextureInstInfo>()};
|
||||
const auto image_info{Image(ctx, info)};
|
||||
Id pointer_type{image_info.is_signed ? ctx.image_s32 : ctx.image_u32};
|
||||
if (!Sirit::ValidId(pointer_type)) {
|
||||
const Id element_type{image_info.is_signed ? ctx.S32[1] : ctx.U32[1]};
|
||||
pointer_type = ctx.TypePointer(spv::StorageClass::Image, element_type);
|
||||
}
|
||||
const Id image{image_info.id};
|
||||
const Id pointer{ctx.OpImageTexelPointer(pointer_type, image, coords, ctx.Const(0U))};
|
||||
const Id image{Image(ctx, info)};
|
||||
const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))};
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
const Id result_type{image_info.is_signed ? ctx.S32[1] : ctx.U32[1]};
|
||||
|
||||
// Ensure value type matches result_type's pointee type
|
||||
Id cast_value{value};
|
||||
if (image_info.is_signed) {
|
||||
// Result type is signed s32, ensure value is also s32
|
||||
cast_value = ctx.OpBitcast(ctx.S32[1], value);
|
||||
} else {
|
||||
// Result type is unsigned u32, ensure value is also u32
|
||||
cast_value = ctx.OpBitcast(ctx.U32[1], value);
|
||||
}
|
||||
|
||||
Id result{(ctx.*atomic_func)(result_type, pointer, scope, semantics, cast_value)};
|
||||
|
||||
// Convert result back to u32 for IR compatibility
|
||||
if (image_info.is_signed) {
|
||||
result = ctx.OpBitcast(ctx.U32[1], result);
|
||||
}
|
||||
return result;
|
||||
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
Id EmitImageAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicIAdd, false);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicIAdd);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicSMin32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicSMin, true);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicSMin);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicUMin32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicUMin, false);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicUMin);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicSMax32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicSMax, true);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicSMax);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicUMax32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicUMax, false);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicUMax);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicInc32(EmitContext&, IR::Inst*, const IR::Value&, Id, Id) {
|
||||
@@ -103,22 +74,22 @@ Id EmitImageAtomicDec32(EmitContext&, IR::Inst*, const IR::Value&, Id, Id) {
|
||||
|
||||
Id EmitImageAtomicAnd32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicAnd, false);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicAnd);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicOr32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicOr, false);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicOr);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicXor32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicXor, false);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicXor);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
|
||||
Id value) {
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicExchange, false);
|
||||
return ImageAtomicU32(ctx, inst, index, coords, value, &Sirit::Module::OpAtomicExchange);
|
||||
}
|
||||
|
||||
Id EmitBindlessImageAtomicIAdd32(EmitContext&) {
|
||||
|
||||
@@ -69,16 +69,10 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
|
||||
return spv::ImageFormat::R16i;
|
||||
case ImageFormat::R32_UINT:
|
||||
return spv::ImageFormat::R32ui;
|
||||
case ImageFormat::R32_SINT:
|
||||
return spv::ImageFormat::R32i;
|
||||
case ImageFormat::R32G32_UINT:
|
||||
return spv::ImageFormat::Rg32ui;
|
||||
case ImageFormat::R32G32_SINT:
|
||||
return spv::ImageFormat::Rg32i;
|
||||
case ImageFormat::R32G32B32A32_UINT:
|
||||
return spv::ImageFormat::Rgba32ui;
|
||||
case ImageFormat::R32G32B32A32_SINT:
|
||||
return spv::ImageFormat::Rgba32i;
|
||||
}
|
||||
throw InvalidArgument("Invalid image format {}", format);
|
||||
}
|
||||
@@ -619,10 +613,7 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
|
||||
const Id element_pointer{TypePointer(spv::StorageClass::Workgroup, element_type)};
|
||||
const Id variable{AddGlobalVariable(pointer, spv::StorageClass::Workgroup)};
|
||||
Decorate(variable, spv::Decoration::Aliased);
|
||||
// Workgroup variables in EntryPoint interfaces are only supported in SPIR-V 1.4+
|
||||
if (profile.supported_spirv >= 0x00010400) {
|
||||
interfaces.push_back(variable);
|
||||
}
|
||||
interfaces.push_back(variable);
|
||||
|
||||
return std::make_tuple(variable, element_pointer, pointer);
|
||||
}};
|
||||
@@ -651,10 +642,7 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
|
||||
|
||||
shared_u32 = TypePointer(spv::StorageClass::Workgroup, U32[1]);
|
||||
shared_memory_u32 = AddGlobalVariable(shared_memory_u32_type, spv::StorageClass::Workgroup);
|
||||
// Workgroup variables in EntryPoint interfaces are only supported in SPIR-V 1.4+
|
||||
if (profile.supported_spirv >= 0x00010400) {
|
||||
interfaces.push_back(shared_memory_u32);
|
||||
}
|
||||
interfaces.push_back(shared_memory_u32);
|
||||
|
||||
const Id func_type{TypeFunction(void_id, U32[1], U32[1])};
|
||||
const auto make_function{[&](u32 mask, u32 size) {
|
||||
@@ -1317,25 +1305,20 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
|
||||
return;
|
||||
}
|
||||
const spv::ImageFormat format{spv::ImageFormat::Unknown};
|
||||
image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
|
||||
|
||||
const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
|
||||
texture_buffers.reserve(info.texture_buffer_descriptors.size());
|
||||
for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
|
||||
if (desc.count != 1) {
|
||||
throw NotImplementedException("Array of texture buffers");
|
||||
}
|
||||
// Use the correct sampled type based on the descriptor's data format
|
||||
const Id sampled_type{desc.is_integer ? (desc.is_signed ? S32[1] : U32[1]) : F32[1]};
|
||||
image_buffer_type = TypeImage(sampled_type, spv::Dim::Buffer, 0U, false, false, 1, format);
|
||||
const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
|
||||
const Id id{AddGlobalVariable(type, spv::StorageClass::UniformConstant)};
|
||||
Decorate(id, spv::Decoration::Binding, binding);
|
||||
Decorate(id, spv::Decoration::DescriptorSet, 0U);
|
||||
Name(id, NameOf(stage, desc, "texbuf"));
|
||||
texture_buffers.push_back({
|
||||
.id = id,
|
||||
.image_type = image_buffer_type,
|
||||
.is_integer = desc.is_integer,
|
||||
.is_signed = desc.is_signed,
|
||||
.count = desc.count,
|
||||
});
|
||||
if (profile.supported_spirv >= 0x00010400) {
|
||||
@@ -1352,23 +1335,19 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
|
||||
throw NotImplementedException("Array of image buffers");
|
||||
}
|
||||
const spv::ImageFormat format{GetImageFormat(desc.format)};
|
||||
const Id sampled_type{desc.is_integer ? (desc.is_signed ? S32[1] : U32[1]) : F32[1]};
|
||||
const Id sampled_type{desc.is_integer ? U32[1] : F32[1]};
|
||||
const Id image_type{
|
||||
TypeImage(sampled_type, spv::Dim::Buffer, false, false, false, 2, format)};
|
||||
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
|
||||
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};
|
||||
Decorate(id, spv::Decoration::Binding, binding);
|
||||
Decorate(id, spv::Decoration::DescriptorSet, 0U);
|
||||
if (format == spv::ImageFormat::Unknown) {
|
||||
Decorate(id, spv::Decoration::NonReadable);
|
||||
}
|
||||
Name(id, NameOf(stage, desc, "imgbuf"));
|
||||
image_buffers.push_back({
|
||||
.id = id,
|
||||
.image_type = image_type,
|
||||
.count = desc.count,
|
||||
.is_integer = desc.is_integer,
|
||||
.is_signed = desc.is_signed,
|
||||
});
|
||||
if (profile.supported_spirv >= 0x00010400) {
|
||||
interfaces.push_back(id);
|
||||
@@ -1405,9 +1384,6 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in
|
||||
if (info.uses_atomic_image_u32) {
|
||||
image_u32 = TypePointer(spv::StorageClass::Image, U32[1]);
|
||||
}
|
||||
if (info.uses_atomic_s32_min || info.uses_atomic_s32_max) {
|
||||
image_s32 = TypePointer(spv::StorageClass::Image, S32[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitContext::DefineImages(const Info& info, u32& binding, u32& scaling_index) {
|
||||
@@ -1416,23 +1392,18 @@ void EmitContext::DefineImages(const Info& info, u32& binding, u32& scaling_inde
|
||||
if (desc.count != 1) {
|
||||
throw NotImplementedException("Array of images");
|
||||
}
|
||||
const Id sampled_type{desc.is_integer ? (desc.is_signed ? S32[1] : U32[1]) : F32[1]};
|
||||
const Id sampled_type{desc.is_integer ? U32[1] : F32[1]};
|
||||
const Id image_type{ImageType(*this, desc, sampled_type)};
|
||||
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
|
||||
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};
|
||||
Decorate(id, spv::Decoration::Binding, binding);
|
||||
Decorate(id, spv::Decoration::DescriptorSet, 0U);
|
||||
const spv::ImageFormat format{GetImageFormat(desc.format)};
|
||||
if (format == spv::ImageFormat::Unknown) {
|
||||
Decorate(id, spv::Decoration::NonReadable);
|
||||
}
|
||||
Name(id, NameOf(stage, desc, "img"));
|
||||
images.push_back({
|
||||
.id = id,
|
||||
.image_type = image_type,
|
||||
.count = desc.count,
|
||||
.is_integer = desc.is_integer,
|
||||
.is_signed = desc.is_signed,
|
||||
});
|
||||
if (profile.supported_spirv >= 0x00010400) {
|
||||
interfaces.push_back(id);
|
||||
|
||||
@@ -45,9 +45,6 @@ struct TextureDefinition {
|
||||
|
||||
struct TextureBufferDefinition {
|
||||
Id id;
|
||||
Id image_type; // Stores the correct buffer image type (F32 or U32 based on descriptor)
|
||||
bool is_integer;
|
||||
bool is_signed;
|
||||
u32 count;
|
||||
};
|
||||
|
||||
@@ -56,7 +53,6 @@ struct ImageBufferDefinition {
|
||||
Id image_type;
|
||||
u32 count;
|
||||
bool is_integer;
|
||||
bool is_signed;
|
||||
};
|
||||
|
||||
struct ImageDefinition {
|
||||
@@ -64,7 +60,6 @@ struct ImageDefinition {
|
||||
Id image_type;
|
||||
u32 count;
|
||||
bool is_integer;
|
||||
bool is_signed;
|
||||
};
|
||||
|
||||
struct UniformDefinitions {
|
||||
@@ -255,7 +250,6 @@ public:
|
||||
|
||||
Id image_buffer_type{};
|
||||
Id image_u32{};
|
||||
Id image_s32{};
|
||||
|
||||
std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};
|
||||
std::array<StorageDefinitions, Info::MAX_SSBOS> ssbos{};
|
||||
|
||||
@@ -204,65 +204,6 @@ static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
|
||||
return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf));
|
||||
}
|
||||
|
||||
static inline bool IsTexturePixelFormatSignedCached(Environment& env,
|
||||
const ConstBufferAddr& cbuf) {
|
||||
switch (ReadTexturePixelFormatCached(env, cbuf)) {
|
||||
case TexturePixelFormat::A8B8G8R8_SINT:
|
||||
case TexturePixelFormat::R8_SINT:
|
||||
case TexturePixelFormat::R8G8_SINT:
|
||||
case TexturePixelFormat::R16_SINT:
|
||||
case TexturePixelFormat::R16G16_SINT:
|
||||
case TexturePixelFormat::R16G16B16A16_SINT:
|
||||
case TexturePixelFormat::R32_SINT:
|
||||
case TexturePixelFormat::R32G32_SINT:
|
||||
case TexturePixelFormat::R32G32B32A32_SINT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool IsImageFormatSigned(ImageFormat format) {
|
||||
switch (format) {
|
||||
case ImageFormat::R8_SINT:
|
||||
case ImageFormat::R16_SINT:
|
||||
case ImageFormat::R32_SINT:
|
||||
case ImageFormat::R32G32_SINT:
|
||||
case ImageFormat::R32G32B32A32_SINT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::optional<ImageFormat> BufferImageFormatFromPixelFormat(
|
||||
TexturePixelFormat pixel_format) {
|
||||
switch (pixel_format) {
|
||||
case TexturePixelFormat::R8_UINT:
|
||||
return ImageFormat::R8_UINT;
|
||||
case TexturePixelFormat::R8_SINT:
|
||||
return ImageFormat::R8_SINT;
|
||||
case TexturePixelFormat::R16_UINT:
|
||||
return ImageFormat::R16_UINT;
|
||||
case TexturePixelFormat::R16_SINT:
|
||||
return ImageFormat::R16_SINT;
|
||||
case TexturePixelFormat::R32_UINT:
|
||||
return ImageFormat::R32_UINT;
|
||||
case TexturePixelFormat::R32_SINT:
|
||||
return ImageFormat::R32_SINT;
|
||||
case TexturePixelFormat::R32G32_UINT:
|
||||
return ImageFormat::R32G32_UINT;
|
||||
case TexturePixelFormat::R32G32_SINT:
|
||||
return ImageFormat::R32G32_SINT;
|
||||
case TexturePixelFormat::R32G32B32A32_UINT:
|
||||
return ImageFormat::R32G32B32A32_UINT;
|
||||
case TexturePixelFormat::R32G32B32A32_SINT:
|
||||
return ImageFormat::R32G32B32A32_SINT;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env);
|
||||
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env) {
|
||||
@@ -711,21 +652,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
|
||||
const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead};
|
||||
const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite};
|
||||
const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)};
|
||||
ImageFormat image_format = flags.image_format;
|
||||
if (flags.type == TextureType::Buffer) {
|
||||
const auto pixel_format = ReadTexturePixelFormatCached(env, cbuf);
|
||||
if (const auto mapped = BufferImageFormatFromPixelFormat(pixel_format)) {
|
||||
image_format = *mapped;
|
||||
}
|
||||
}
|
||||
const bool is_signed{IsImageFormatSigned(image_format)};
|
||||
if (flags.type == TextureType::Buffer) {
|
||||
index = descriptors.Add(ImageBufferDescriptor{
|
||||
.format = image_format,
|
||||
.format = flags.image_format,
|
||||
.is_written = is_written,
|
||||
.is_read = is_read,
|
||||
.is_integer = is_integer,
|
||||
.is_signed = is_signed,
|
||||
.cbuf_index = cbuf.index,
|
||||
.cbuf_offset = cbuf.offset,
|
||||
.count = cbuf.count,
|
||||
@@ -734,26 +666,22 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
|
||||
} else {
|
||||
index = descriptors.Add(ImageDescriptor{
|
||||
.type = flags.type,
|
||||
.format = image_format,
|
||||
.format = flags.image_format,
|
||||
.is_written = is_written,
|
||||
.is_read = is_read,
|
||||
.is_integer = is_integer,
|
||||
.is_signed = is_signed,
|
||||
.cbuf_index = cbuf.index,
|
||||
.cbuf_offset = cbuf.offset,
|
||||
.count = cbuf.count,
|
||||
.size_shift = DESCRIPTOR_SIZE_SHIFT,
|
||||
});
|
||||
}
|
||||
flags.image_format.Assign(image_format);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (flags.type == TextureType::Buffer) {
|
||||
index = descriptors.Add(TextureBufferDescriptor{
|
||||
.has_secondary = cbuf.has_secondary,
|
||||
.is_integer = IsTexturePixelFormatIntegerCached(env, cbuf),
|
||||
.is_signed = IsTexturePixelFormatSignedCached(env, cbuf),
|
||||
.cbuf_index = cbuf.index,
|
||||
.cbuf_offset = cbuf.offset,
|
||||
.shift_left = cbuf.shift_left,
|
||||
|
||||
@@ -150,11 +150,8 @@ enum class ImageFormat : u32 {
|
||||
R16_UINT,
|
||||
R16_SINT,
|
||||
R32_UINT,
|
||||
R32_SINT,
|
||||
R32G32_UINT,
|
||||
R32G32_SINT,
|
||||
R32G32B32A32_UINT,
|
||||
R32G32B32A32_SINT,
|
||||
};
|
||||
|
||||
enum class Interpolation {
|
||||
@@ -181,8 +178,6 @@ struct StorageBufferDescriptor {
|
||||
|
||||
struct TextureBufferDescriptor {
|
||||
bool has_secondary;
|
||||
bool is_integer; // True if data is SINT/UINT (from R_type in TIC), false if FLOAT
|
||||
bool is_signed; // True if integer data is signed
|
||||
u32 cbuf_index;
|
||||
u32 cbuf_offset;
|
||||
u32 shift_left;
|
||||
@@ -201,7 +196,6 @@ struct ImageBufferDescriptor {
|
||||
bool is_written;
|
||||
bool is_read;
|
||||
bool is_integer;
|
||||
bool is_signed;
|
||||
u32 cbuf_index;
|
||||
u32 cbuf_offset;
|
||||
u32 count;
|
||||
@@ -235,7 +229,6 @@ struct ImageDescriptor {
|
||||
bool is_written;
|
||||
bool is_read;
|
||||
bool is_integer;
|
||||
bool is_signed;
|
||||
u32 cbuf_index;
|
||||
u32 cbuf_offset;
|
||||
u32 count;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -39,7 +42,8 @@ public:
|
||||
static constexpr u64 BASE_PAGE_SIZE = 1ULL << BASE_PAGE_BITS;
|
||||
|
||||
explicit BufferBase(VAddr cpu_addr_, u64 size_bytes_)
|
||||
: cpu_addr{cpu_addr_}, size_bytes{size_bytes_} {}
|
||||
: cpu_addr_cached{static_cast<DAddr>(cpu_addr_)}, cpu_addr{cpu_addr_},
|
||||
size_bytes{size_bytes_} {}
|
||||
|
||||
explicit BufferBase(NullBufferParams) {}
|
||||
|
||||
@@ -97,6 +101,8 @@ public:
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
DAddr cpu_addr_cached = 0;
|
||||
|
||||
/// Returns the offset relative to the given CPU address
|
||||
/// @pre IsInBounds returns true
|
||||
[[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
@@ -382,6 +382,10 @@ void BufferCache<P>::BindHostComputeBuffers() {
|
||||
BindHostComputeUniformBuffers();
|
||||
BindHostComputeStorageBuffers();
|
||||
BindHostComputeTextureBuffers();
|
||||
if (any_buffer_uploaded) {
|
||||
runtime.PostCopyBarrier();
|
||||
any_buffer_uploaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -458,14 +462,14 @@ void BufferCache<P>::UnbindGraphicsTextureBuffers(size_t stage) {
|
||||
template <class P>
|
||||
void BufferCache<P>::BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr,
|
||||
u32 size, PixelFormat format, bool is_written,
|
||||
bool is_image, bool is_integer, bool is_signed) {
|
||||
bool is_image) {
|
||||
channel_state->enabled_texture_buffers[stage] |= 1U << tbo_index;
|
||||
channel_state->written_texture_buffers[stage] |= (is_written ? 1U : 0U) << tbo_index;
|
||||
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
||||
channel_state->image_texture_buffers[stage] |= (is_image ? 1U : 0U) << tbo_index;
|
||||
}
|
||||
channel_state->texture_buffers[stage][tbo_index] =
|
||||
GetTextureBufferBinding(gpu_addr, size, format, is_integer, is_signed);
|
||||
GetTextureBufferBinding(gpu_addr, size, format);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -532,8 +536,7 @@ void BufferCache<P>::UnbindComputeTextureBuffers() {
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size,
|
||||
PixelFormat format, bool is_written, bool is_image,
|
||||
bool is_integer, bool is_signed) {
|
||||
PixelFormat format, bool is_written, bool is_image) {
|
||||
if (tbo_index >= channel_state->compute_texture_buffers.size()) [[unlikely]] {
|
||||
LOG_ERROR(HW_GPU, "Texture buffer index {} exceeds maximum texture buffer count",
|
||||
tbo_index);
|
||||
@@ -545,7 +548,7 @@ void BufferCache<P>::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_add
|
||||
channel_state->image_compute_texture_buffers |= (is_image ? 1U : 0U) << tbo_index;
|
||||
}
|
||||
channel_state->compute_texture_buffers[tbo_index] =
|
||||
GetTextureBufferBinding(gpu_addr, size, format, is_integer, is_signed);
|
||||
GetTextureBufferBinding(gpu_addr, size, format);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -681,6 +684,7 @@ void BufferCache<P>::PopAsyncBuffers() {
|
||||
auto& async_buffer = async_buffers.front();
|
||||
u8* base = async_buffer->mapped_span.data();
|
||||
const size_t base_offset = async_buffer->offset;
|
||||
Common::RangeSet<DAddr> ranges_to_remove;
|
||||
for (const auto& copy : downloads) {
|
||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||
const u64 dst_offset = copy.dst_offset - base_offset;
|
||||
@@ -690,9 +694,11 @@ void BufferCache<P>::PopAsyncBuffers() {
|
||||
end - start);
|
||||
});
|
||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
||||
gpu_modified_ranges.Subtract(start, end - start);
|
||||
ranges_to_remove.Add(start, end - start);
|
||||
});
|
||||
}
|
||||
ranges_to_remove.ForEach(
|
||||
[&](DAddr start, DAddr end) { gpu_modified_ranges.Subtract(start, end - start); });
|
||||
async_buffers_death_ring.emplace_back(*async_buffer);
|
||||
async_buffers.pop_front();
|
||||
pending_downloads.pop_front();
|
||||
@@ -764,45 +770,85 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size,
|
||||
u32 stride) {
|
||||
if constexpr (IS_OPENGL) {
|
||||
runtime.BindVertexBuffer(index, buffer, offset, size, stride);
|
||||
} else {
|
||||
runtime.BindVertexBuffer(index, buffer.Handle(), offset, size, stride);
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
Binding& BufferCache<P>::VertexBufferSlot(u32 index) {
|
||||
ASSERT(index < NUM_VERTEX_BUFFERS);
|
||||
return v_buffer[index];
|
||||
}
|
||||
|
||||
template <class P>
|
||||
const Binding& BufferCache<P>::VertexBufferSlot(u32 index) const {
|
||||
ASSERT(index < NUM_VERTEX_BUFFERS);
|
||||
return v_buffer[index];
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::UpdateVertexBufferSlot(u32 index, const Binding& binding) {
|
||||
Binding& slot = VertexBufferSlot(index);
|
||||
if (slot.device_addr != binding.device_addr || slot.size != binding.size) {
|
||||
++vertex_buffers_serial;
|
||||
}
|
||||
slot = binding;
|
||||
if (binding.buffer_id != NULL_BUFFER_ID && binding.size != 0) {
|
||||
enabled_vertex_buffers_mask |= (1u << index);
|
||||
} else {
|
||||
enabled_vertex_buffers_mask &= ~(1u << index);
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::BindHostVertexBuffers() {
|
||||
HostBindings<typename P::Buffer> host_bindings;
|
||||
bool any_valid{false};
|
||||
auto& flags = maxwell3d->dirty.flags;
|
||||
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
||||
const Binding& binding = channel_state->vertex_buffers[index];
|
||||
u32 enabled_mask = enabled_vertex_buffers_mask;
|
||||
HostBindings<Buffer> bindings{};
|
||||
u32 last_index = std::numeric_limits<u32>::max();
|
||||
const auto flush_bindings = [&]() {
|
||||
if (bindings.buffers.empty()) {
|
||||
return;
|
||||
}
|
||||
bindings.max_index = bindings.min_index + static_cast<u32>(bindings.buffers.size());
|
||||
runtime.BindVertexBuffers(bindings);
|
||||
bindings = HostBindings<Buffer>{};
|
||||
last_index = std::numeric_limits<u32>::max();
|
||||
};
|
||||
while (enabled_mask != 0) {
|
||||
const u32 index = std::countr_zero(enabled_mask);
|
||||
enabled_mask &= (enabled_mask - 1);
|
||||
const Binding& binding = VertexBufferSlot(index);
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
|
||||
if (!flags[Dirty::VertexBuffer0 + index]) {
|
||||
flush_bindings();
|
||||
continue;
|
||||
}
|
||||
flags[Dirty::VertexBuffer0 + index] = false;
|
||||
|
||||
host_bindings.min_index = (std::min)(host_bindings.min_index, index);
|
||||
host_bindings.max_index = (std::max)(host_bindings.max_index, index);
|
||||
any_valid = true;
|
||||
}
|
||||
|
||||
if (any_valid) {
|
||||
host_bindings.max_index++;
|
||||
for (u32 index = host_bindings.min_index; index < host_bindings.max_index; index++) {
|
||||
flags[Dirty::VertexBuffer0 + index] = false;
|
||||
|
||||
const Binding& binding = channel_state->vertex_buffers[index];
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
|
||||
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
buffer.MarkUsage(offset, binding.size);
|
||||
|
||||
host_bindings.buffers.push_back(&buffer);
|
||||
host_bindings.offsets.push_back(offset);
|
||||
host_bindings.sizes.push_back(binding.size);
|
||||
host_bindings.strides.push_back(stride);
|
||||
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
buffer.MarkUsage(offset, binding.size);
|
||||
if (!bindings.buffers.empty() && index != last_index + 1) {
|
||||
flush_bindings();
|
||||
}
|
||||
runtime.BindVertexBuffers(host_bindings);
|
||||
if (bindings.buffers.empty()) {
|
||||
bindings.min_index = index;
|
||||
}
|
||||
bindings.buffers.push_back(&buffer);
|
||||
bindings.offsets.push_back(offset);
|
||||
bindings.sizes.push_back(binding.size);
|
||||
bindings.strides.push_back(stride);
|
||||
last_index = index;
|
||||
}
|
||||
flush_bindings();
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -956,17 +1002,15 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
|
||||
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
const PixelFormat format = binding.format;
|
||||
const bool is_integer = binding.is_integer;
|
||||
const bool is_signed = binding.is_signed;
|
||||
buffer.MarkUsage(offset, size);
|
||||
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
||||
if (((channel_state->image_texture_buffers[stage] >> index) & 1) != 0) {
|
||||
runtime.BindImageBuffer(buffer, offset, size, format);
|
||||
} else {
|
||||
runtime.BindTextureBuffer(buffer, offset, size, format, is_integer, is_signed);
|
||||
runtime.BindTextureBuffer(buffer, offset, size, format);
|
||||
}
|
||||
} else {
|
||||
runtime.BindTextureBuffer(buffer, offset, size, format, is_integer, is_signed);
|
||||
runtime.BindTextureBuffer(buffer, offset, size, format);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1093,17 +1137,15 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
|
||||
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
const PixelFormat format = binding.format;
|
||||
const bool is_integer = binding.is_integer;
|
||||
const bool is_signed = binding.is_signed;
|
||||
buffer.MarkUsage(offset, size);
|
||||
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
||||
if (((channel_state->image_compute_texture_buffers >> index) & 1) != 0) {
|
||||
runtime.BindImageBuffer(buffer, offset, size, format);
|
||||
} else {
|
||||
runtime.BindTextureBuffer(buffer, offset, size, format, is_integer, is_signed);
|
||||
runtime.BindTextureBuffer(buffer, offset, size, format);
|
||||
}
|
||||
} else {
|
||||
runtime.BindTextureBuffer(buffer, offset, size, format, is_integer, is_signed);
|
||||
runtime.BindTextureBuffer(buffer, offset, size, format);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1210,17 +1252,20 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
|
||||
u32 size = address_size; // TODO: Analyze stride and number of vertices
|
||||
if (array.enable == 0 || size == 0 || !device_addr) {
|
||||
channel_state->vertex_buffers[index] = NULL_BINDING;
|
||||
UpdateVertexBufferSlot(index, NULL_BINDING);
|
||||
return;
|
||||
}
|
||||
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
|
||||
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
|
||||
}
|
||||
const BufferId buffer_id = FindBuffer(*device_addr, size);
|
||||
channel_state->vertex_buffers[index] = Binding{
|
||||
const Binding binding{
|
||||
.device_addr = *device_addr,
|
||||
.size = size,
|
||||
.buffer_id = buffer_id,
|
||||
};
|
||||
channel_state->vertex_buffers[index] = binding;
|
||||
UpdateVertexBufferSlot(index, binding);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -1533,12 +1578,12 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 size) {
|
||||
boost::container::small_vector<BufferCopy, 4> copies;
|
||||
upload_copies.clear();
|
||||
u64 total_size_bytes = 0;
|
||||
u64 largest_copy = 0;
|
||||
DAddr buffer_start = buffer.CpuAddr();
|
||||
const DAddr buffer_start = buffer.cpu_addr_cached;
|
||||
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
||||
copies.push_back(BufferCopy{
|
||||
upload_copies.push_back(BufferCopy{
|
||||
.src_offset = total_size_bytes,
|
||||
.dst_offset = device_addr_out - buffer_start,
|
||||
.size = range_size,
|
||||
@@ -1549,8 +1594,9 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
|
||||
if (total_size_bytes == 0) {
|
||||
return true;
|
||||
}
|
||||
const std::span<BufferCopy> copies_span(copies.data(), copies.size());
|
||||
const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size());
|
||||
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
|
||||
any_buffer_uploaded = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1740,6 +1786,7 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
|
||||
auto& binding = channel_state->vertex_buffers[index];
|
||||
if (binding.buffer_id == buffer_id) {
|
||||
binding.buffer_id = BufferId{};
|
||||
UpdateVertexBufferSlot(index, binding);
|
||||
dirty_vertex_buffers.push_back(index);
|
||||
}
|
||||
}
|
||||
@@ -1838,8 +1885,7 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
|
||||
|
||||
template <class P>
|
||||
TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
|
||||
PixelFormat format, bool is_integer,
|
||||
bool is_signed) {
|
||||
PixelFormat format) {
|
||||
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
|
||||
TextureBufferBinding binding;
|
||||
if (!device_addr || size == 0) {
|
||||
@@ -1847,15 +1893,11 @@ TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(GPUVAddr gpu_addr,
|
||||
binding.size = 0;
|
||||
binding.buffer_id = NULL_BUFFER_ID;
|
||||
binding.format = PixelFormat::Invalid;
|
||||
binding.is_integer = false;
|
||||
binding.is_signed = false;
|
||||
} else {
|
||||
binding.device_addr = *device_addr;
|
||||
binding.size = size;
|
||||
binding.buffer_id = BufferId{};
|
||||
binding.format = format;
|
||||
binding.is_integer = is_integer;
|
||||
binding.is_signed = is_signed;
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "common/div_ceil.h"
|
||||
#include "common/literals.h"
|
||||
#include "common/lru_cache.h"
|
||||
#include "common/page_bitset_range_set.h"
|
||||
#include "common/range_sets.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
@@ -84,8 +85,6 @@ struct Binding {
|
||||
|
||||
struct TextureBufferBinding : Binding {
|
||||
PixelFormat format;
|
||||
bool is_integer{}; // True if data is SINT/UINT, false if FLOAT
|
||||
bool is_signed{}; // True if integer data is signed
|
||||
};
|
||||
|
||||
static constexpr Binding NULL_BINDING{
|
||||
@@ -253,8 +252,7 @@ public:
|
||||
void UnbindGraphicsTextureBuffers(size_t stage);
|
||||
|
||||
void BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr, u32 size,
|
||||
PixelFormat format, bool is_written, bool is_image,
|
||||
bool is_integer, bool is_signed);
|
||||
PixelFormat format, bool is_written, bool is_image);
|
||||
|
||||
void UnbindComputeStorageBuffers();
|
||||
|
||||
@@ -264,7 +262,7 @@ public:
|
||||
void UnbindComputeTextureBuffers();
|
||||
|
||||
void BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size, PixelFormat format,
|
||||
bool is_written, bool is_image, bool is_integer, bool is_signed);
|
||||
bool is_written, bool is_image);
|
||||
|
||||
[[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size,
|
||||
ObtainBufferSynchronize sync_info,
|
||||
@@ -323,6 +321,7 @@ public:
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
Runtime& runtime;
|
||||
bool any_buffer_uploaded = false;
|
||||
|
||||
private:
|
||||
template <typename Func>
|
||||
@@ -375,6 +374,8 @@ private:
|
||||
|
||||
void BindHostTransformFeedbackBuffers();
|
||||
|
||||
void BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
|
||||
|
||||
void BindHostComputeUniformBuffers();
|
||||
|
||||
void BindHostComputeStorageBuffers();
|
||||
@@ -448,8 +449,7 @@ private:
|
||||
bool is_written) const;
|
||||
|
||||
[[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
|
||||
PixelFormat format, bool is_integer,
|
||||
bool is_signed);
|
||||
PixelFormat format);
|
||||
|
||||
[[nodiscard]] std::span<const u8> ImmediateBufferWithData(DAddr device_addr, size_t size);
|
||||
|
||||
@@ -457,6 +457,12 @@ private:
|
||||
|
||||
[[nodiscard]] bool HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept;
|
||||
|
||||
[[nodiscard]] Binding& VertexBufferSlot(u32 index);
|
||||
|
||||
[[nodiscard]] const Binding& VertexBufferSlot(u32 index) const;
|
||||
|
||||
void UpdateVertexBufferSlot(u32 index, const Binding& binding);
|
||||
|
||||
void ClearDownload(DAddr base_addr, u64 size);
|
||||
|
||||
void InlineMemoryImplementation(DAddr dest_address, size_t copy_size,
|
||||
@@ -476,9 +482,15 @@ private:
|
||||
|
||||
u32 last_index_count = 0;
|
||||
|
||||
u32 enabled_vertex_buffers_mask = 0;
|
||||
u64 vertex_buffers_serial = 0;
|
||||
std::array<Binding, 32> v_buffer{};
|
||||
|
||||
boost::container::small_vector<BufferCopy, 4> upload_copies;
|
||||
|
||||
MemoryTracker memory_tracker;
|
||||
Common::RangeSet<DAddr> uncommitted_gpu_modified_ranges;
|
||||
Common::RangeSet<DAddr> gpu_modified_ranges;
|
||||
Common::PageBitsetRangeSet<DAddr, CACHING_PAGEBITS, (1ULL << 34)> gpu_modified_ranges;
|
||||
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
|
||||
|
||||
// Async Buffers
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -89,7 +92,7 @@ struct CommandDataContainer {
|
||||
|
||||
/// Struct used to synchronize the GPU thread
|
||||
struct SynchState final {
|
||||
using CommandQueue = Common::MPSCQueue<CommandDataContainer>;
|
||||
using CommandQueue = Common::SPSCQueue<CommandDataContainer>;
|
||||
std::mutex write_lock;
|
||||
CommandQueue queue;
|
||||
u64 last_fence{};
|
||||
|
||||
+28
-27
@@ -89,7 +89,7 @@ public:
|
||||
: HLEMacroImpl(maxwell3d_)
|
||||
{}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]);
|
||||
if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
|
||||
Fallback(parameters);
|
||||
@@ -120,7 +120,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void Fallback(const std::vector<u32>& parameters) {
|
||||
void Fallback(std::span<const u32> parameters) {
|
||||
SCOPE_EXIT {
|
||||
if (extended) {
|
||||
maxwell3d.engine_state = Maxwell3D::EngineHint::None;
|
||||
@@ -167,7 +167,7 @@ class HLE_DrawIndexedIndirect final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_DrawIndexedIndirect(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]);
|
||||
if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
|
||||
Fallback(parameters);
|
||||
@@ -207,7 +207,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void Fallback(const std::vector<u32>& parameters) {
|
||||
void Fallback(std::span<const u32> parameters) {
|
||||
maxwell3d.RefreshParameters();
|
||||
const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
|
||||
const u32 element_base = parameters[4];
|
||||
@@ -238,7 +238,7 @@ class HLE_MultiLayerClear final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_MultiLayerClear(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
maxwell3d.RefreshParameters();
|
||||
ASSERT(parameters.size() == 1);
|
||||
|
||||
@@ -256,7 +256,7 @@ class HLE_MultiDrawIndexedIndirectCount final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_MultiDrawIndexedIndirectCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]);
|
||||
if (!IsTopologySafe(topology)) {
|
||||
Fallback(parameters);
|
||||
@@ -301,7 +301,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void Fallback(const std::vector<u32>& parameters) {
|
||||
void Fallback(std::span<const u32> parameters) {
|
||||
SCOPE_EXIT {
|
||||
// Clean everything.
|
||||
maxwell3d.regs.vertex_id_base = 0x0;
|
||||
@@ -347,7 +347,7 @@ class HLE_DrawIndirectByteCount final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback();
|
||||
|
||||
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0xFFFFU);
|
||||
@@ -372,7 +372,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void Fallback(const std::vector<u32>& parameters) {
|
||||
void Fallback(std::span<const u32> parameters) {
|
||||
maxwell3d.RefreshParameters();
|
||||
|
||||
maxwell3d.regs.draw.begin = parameters[0];
|
||||
@@ -381,7 +381,8 @@ private:
|
||||
|
||||
maxwell3d.draw_manager->DrawArray(
|
||||
maxwell3d.regs.draw.topology, 0,
|
||||
maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1);
|
||||
maxwell3d.regs.draw_auto_stride > 0 ? maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride : 0,
|
||||
0, 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -389,7 +390,7 @@ class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
maxwell3d.RefreshParameters();
|
||||
const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2;
|
||||
const u32 address = maxwell3d.regs.shadow_scratch[24];
|
||||
@@ -405,7 +406,7 @@ class HLE_D7333D26E0A93EDE final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_D7333D26E0A93EDE(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
maxwell3d.RefreshParameters();
|
||||
const size_t index = parameters[0];
|
||||
const u32 address = maxwell3d.regs.shadow_scratch[42 + index];
|
||||
@@ -421,7 +422,7 @@ class HLE_BindShader final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_BindShader(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
maxwell3d.RefreshParameters();
|
||||
auto& regs = maxwell3d.regs;
|
||||
const u32 index = parameters[0];
|
||||
@@ -451,7 +452,7 @@ class HLE_SetRasterBoundingBox final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_SetRasterBoundingBox(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
maxwell3d.RefreshParameters();
|
||||
const u32 raster_mode = parameters[0];
|
||||
auto& regs = maxwell3d.regs;
|
||||
@@ -467,7 +468,7 @@ class HLE_ClearConstBuffer final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_ClearConstBuffer(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
maxwell3d.RefreshParameters();
|
||||
static constexpr std::array<u32, base_size> zeroes{};
|
||||
auto& regs = maxwell3d.regs;
|
||||
@@ -483,7 +484,7 @@ class HLE_ClearMemory final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_ClearMemory(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
maxwell3d.RefreshParameters();
|
||||
|
||||
const u32 needed_memory = parameters[2] / sizeof(u32);
|
||||
@@ -507,7 +508,7 @@ class HLE_TransformFeedbackSetup final : public HLEMacroImpl {
|
||||
public:
|
||||
explicit HLE_TransformFeedbackSetup(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
|
||||
void Execute(std::span<const u32> parameters, [[maybe_unused]] u32 method) override {
|
||||
maxwell3d.RefreshParameters();
|
||||
|
||||
auto& regs = maxwell3d.regs;
|
||||
@@ -560,12 +561,12 @@ std::unique_ptr<CachedMacro> HLEMacro::GetHLEProgram(u64 hash) const {
|
||||
namespace {
|
||||
class MacroInterpreterImpl final : public CachedMacro {
|
||||
public:
|
||||
explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_)
|
||||
explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, std::span<const u32> code_)
|
||||
: CachedMacro(maxwell3d_)
|
||||
, code{code_}
|
||||
{}
|
||||
|
||||
void Execute(const std::vector<u32>& params, u32 method) override;
|
||||
void Execute(std::span<const u32> params, u32 method) override;
|
||||
|
||||
private:
|
||||
/// Resets the execution engine state, zeroing registers, etc.
|
||||
@@ -630,10 +631,10 @@ private:
|
||||
u32 next_parameter_index = 0;
|
||||
|
||||
bool carry_flag = false;
|
||||
const std::vector<u32>& code;
|
||||
std::span<const u32> code;
|
||||
};
|
||||
|
||||
void MacroInterpreterImpl::Execute(const std::vector<u32>& params, u32 method) {
|
||||
void MacroInterpreterImpl::Execute(std::span<const u32> params, u32 method) {
|
||||
Reset();
|
||||
|
||||
registers[1] = params[0];
|
||||
@@ -932,7 +933,7 @@ static const auto default_cg_mode = nullptr; //Allow RWE
|
||||
|
||||
class MacroJITx64Impl final : public Xbyak::CodeGenerator, public CachedMacro {
|
||||
public:
|
||||
explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_)
|
||||
explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, std::span<const u32> code_)
|
||||
: Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode)
|
||||
, CachedMacro(maxwell3d_)
|
||||
, code{code_}
|
||||
@@ -940,7 +941,7 @@ public:
|
||||
Compile();
|
||||
}
|
||||
|
||||
void Execute(const std::vector<u32>& parameters, u32 method) override;
|
||||
void Execute(std::span<const u32> parameters, u32 method) override;
|
||||
|
||||
void Compile_ALU(Macro::Opcode opcode);
|
||||
void Compile_AddImmediate(Macro::Opcode opcode);
|
||||
@@ -992,10 +993,10 @@ private:
|
||||
bool is_delay_slot{};
|
||||
u32 pc{};
|
||||
|
||||
const std::vector<u32>& code;
|
||||
std::span<const u32> code;
|
||||
};
|
||||
|
||||
void MacroJITx64Impl::Execute(const std::vector<u32>& parameters, u32 method) {
|
||||
void MacroJITx64Impl::Execute(std::span<const u32> parameters, u32 method) {
|
||||
ASSERT_OR_EXECUTE(program != nullptr, { return; });
|
||||
JITState state{};
|
||||
state.maxwell3d = &maxwell3d;
|
||||
@@ -1591,7 +1592,7 @@ void MacroEngine::ClearCode(u32 method) {
|
||||
uploaded_macro_code.erase(method);
|
||||
}
|
||||
|
||||
void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
|
||||
void MacroEngine::Execute(u32 method, std::span<const u32> parameters) {
|
||||
auto compiled_macro = macro_cache.find(method);
|
||||
if (compiled_macro != macro_cache.end()) {
|
||||
const auto& cache_info = compiled_macro->second;
|
||||
@@ -1648,7 +1649,7 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<CachedMacro> MacroEngine::Compile(const std::vector<u32>& code) {
|
||||
std::unique_ptr<CachedMacro> MacroEngine::Compile(std::span<const u32> code) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
if (!is_interpreted)
|
||||
return std::make_unique<MacroJITx64Impl>(maxwell3d, code);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -107,7 +108,7 @@ public:
|
||||
/// Executes the macro code with the specified input parameters.
|
||||
/// @param parameters The parameters of the macro
|
||||
/// @param method The method to execute
|
||||
virtual void Execute(const std::vector<u32>& parameters, u32 method) = 0;
|
||||
virtual void Execute(std::span<const u32> parameters, u32 method) = 0;
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
};
|
||||
|
||||
@@ -134,10 +135,10 @@ public:
|
||||
void ClearCode(u32 method);
|
||||
|
||||
// Compiles the macro if its not in the cache, and executes the compiled macro
|
||||
void Execute(u32 method, const std::vector<u32>& parameters);
|
||||
void Execute(u32 method, std::span<const u32> parameters);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code);
|
||||
std::unique_ptr<CachedMacro> Compile(std::span<const u32> code);
|
||||
|
||||
private:
|
||||
struct CacheInfo {
|
||||
|
||||
@@ -88,7 +88,7 @@ void Buffer::MakeResident(GLenum access) noexcept {
|
||||
glMakeNamedBufferResidentNV(buffer.handle, access);
|
||||
}
|
||||
|
||||
GLuint Buffer::View(u32 offset, u32 size, PixelFormat format, bool is_integer, bool is_signed) {
|
||||
GLuint Buffer::View(u32 offset, u32 size, PixelFormat format) {
|
||||
const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
|
||||
return offset == view.offset && size == view.size && format == view.format;
|
||||
})};
|
||||
@@ -370,8 +370,8 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
|
||||
PixelFormat format, bool is_integer, bool is_signed) {
|
||||
*texture_handles++ = buffer.View(offset, size, format, is_integer, is_signed);
|
||||
PixelFormat format) {
|
||||
*texture_handles++ = buffer.View(offset, size, format);
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindImageBuffer(Buffer& buffer, u32 offset, u32 size, PixelFormat format) {
|
||||
|
||||
@@ -34,8 +34,7 @@ public:
|
||||
|
||||
void MarkUsage(u64 offset, u64 size) {}
|
||||
|
||||
[[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format,
|
||||
bool is_integer = false, bool is_signed = false);
|
||||
[[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
|
||||
|
||||
[[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept {
|
||||
return address;
|
||||
@@ -119,7 +118,7 @@ public:
|
||||
void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
|
||||
|
||||
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
|
||||
VideoCore::Surface::PixelFormat format, bool is_integer, bool is_signed);
|
||||
VideoCore::Surface::PixelFormat format);
|
||||
|
||||
void BindImageBuffer(Buffer& buffer, u32 offset, u32 size,
|
||||
VideoCore::Surface::PixelFormat format);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -177,8 +177,7 @@ void ComputePipeline::Configure() {
|
||||
ImageView& image_view{texture_cache.GetImageView(views[texbuf_index].id)};
|
||||
buffer_cache.BindComputeTextureBuffer(texbuf_index, image_view.GpuAddr(),
|
||||
image_view.BufferSize(), image_view.format,
|
||||
is_written, is_image, desc.is_integer,
|
||||
desc.is_signed);
|
||||
is_written, is_image);
|
||||
++texbuf_index;
|
||||
}
|
||||
}};
|
||||
@@ -190,6 +189,10 @@ void ComputePipeline::Configure() {
|
||||
buffer_cache.runtime.SetEnableStorageBuffers(use_storage_buffers);
|
||||
buffer_cache.runtime.SetImagePointers(textures.data(), images.data());
|
||||
buffer_cache.BindHostComputeBuffers();
|
||||
if (buffer_cache.any_buffer_uploaded) {
|
||||
buffer_cache.runtime.PostCopyBarrier();
|
||||
buffer_cache.any_buffer_uploaded = false;
|
||||
}
|
||||
|
||||
const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers +
|
||||
num_image_buffers};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -397,8 +397,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
|
||||
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
|
||||
image_view.BufferSize(), image_view.format,
|
||||
is_written, is_image, desc.is_integer,
|
||||
desc.is_signed);
|
||||
is_written, is_image);
|
||||
++index;
|
||||
++texture_buffer_it;
|
||||
}
|
||||
@@ -559,6 +558,10 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
if (image_binding != 0) {
|
||||
glBindImageTextures(0, image_binding, images.data());
|
||||
}
|
||||
if (buffer_cache.any_buffer_uploaded) {
|
||||
buffer_cache.runtime.PostCopyBarrier();
|
||||
buffer_cache.any_buffer_uploaded = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -433,16 +433,10 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form
|
||||
return GL_R16I;
|
||||
case Shader::ImageFormat::R32_UINT:
|
||||
return GL_R32UI;
|
||||
case Shader::ImageFormat::R32_SINT:
|
||||
return GL_R32I;
|
||||
case Shader::ImageFormat::R32G32_UINT:
|
||||
return GL_RG32UI;
|
||||
case Shader::ImageFormat::R32G32_SINT:
|
||||
return GL_RG32I;
|
||||
case Shader::ImageFormat::R32G32B32A32_UINT:
|
||||
return GL_RGBA32UI;
|
||||
case Shader::ImageFormat::R32G32B32A32_SINT:
|
||||
return GL_RGBA32I;
|
||||
}
|
||||
ASSERT_MSG(false, "Invalid image format={}", format);
|
||||
return GL_R32UI;
|
||||
|
||||
@@ -112,12 +112,15 @@ void UploadImage(const Device& device, MemoryAllocator& allocator, Scheduler& sc
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([&](vk::CommandBuffer cmdbuf) {
|
||||
TransitionImageLayout(cmdbuf, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
const VkImageLayout transfer_dst_layout = device.IsKhrUnifiedImageLayoutsSupported()
|
||||
? VK_IMAGE_LAYOUT_GENERAL
|
||||
: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
TransitionImageLayout(cmdbuf, *image, transfer_dst_layout,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
cmdbuf.CopyBufferToImage(*upload_buffer, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
cmdbuf.CopyBufferToImage(*upload_buffer, *image, transfer_dst_layout,
|
||||
regions);
|
||||
TransitionImageLayout(cmdbuf, *image, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
transfer_dst_layout);
|
||||
});
|
||||
scheduler.Finish();
|
||||
}
|
||||
|
||||
@@ -177,10 +177,6 @@ try
|
||||
|
||||
RendererVulkan::~RendererVulkan() {
|
||||
scheduler.RegisterOnSubmit([] {});
|
||||
scheduler.WaitWorker();
|
||||
// vkDeviceWaitIdle MUST be called only after all queue submissions are complete
|
||||
// to avoid threading errors on VkQueue simultaneous access
|
||||
std::scoped_lock lock{scheduler.submit_mutex};
|
||||
void(device.GetLogical().WaitIdle());
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,6 @@ BlitScreen::~BlitScreen() = default;
|
||||
void BlitScreen::WaitIdle() {
|
||||
present_manager.WaitPresent();
|
||||
scheduler.Finish();
|
||||
std::scoped_lock lock{scheduler.submit_mutex};
|
||||
// vkDeviceWaitIdle MUST be protected by submit_mutex to prevent racing with queue submissions
|
||||
// from the worker thread. This ensures no simultaneous access to VkQueue.
|
||||
device.GetLogical().WaitIdle();
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
|
||||
} // Anonymous namespace
|
||||
|
||||
Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params)
|
||||
: VideoCommon::BufferBase(null_params), runtime{&runtime}, tracker{4096} {
|
||||
: VideoCommon::BufferBase(null_params), tracker{4096} {
|
||||
if (runtime.device.HasNullDescriptor()) {
|
||||
return;
|
||||
}
|
||||
@@ -93,53 +93,14 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p
|
||||
}
|
||||
|
||||
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
|
||||
: VideoCommon::BufferBase(cpu_addr_, size_bytes_), runtime{&runtime}, device{&runtime.device},
|
||||
: VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device},
|
||||
buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} {
|
||||
if (runtime.device.HasDebuggingToolAttached()) {
|
||||
buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat SelectTexelBufferFormat(VkFormat float_format, bool is_integer, bool is_signed) {
|
||||
// If the buffer stores integer data but Vulkan reports float format,
|
||||
// we need to map to appropriate integer formats for type compatibility
|
||||
if (!is_integer) {
|
||||
// Non-integer buffer, use the original float format
|
||||
return float_format;
|
||||
}
|
||||
|
||||
// Integer buffer: map float formats to signed/unsigned equivalents
|
||||
if (is_signed) {
|
||||
// Signed integer
|
||||
switch (float_format) {
|
||||
case VK_FORMAT_R32_SFLOAT:
|
||||
return VK_FORMAT_R32_SINT;
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
return VK_FORMAT_R32G32_SINT;
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
return VK_FORMAT_R32G32B32A32_SINT;
|
||||
default:
|
||||
// For non-float formats, use as-is
|
||||
return float_format;
|
||||
}
|
||||
} else {
|
||||
// Unsigned integer
|
||||
switch (float_format) {
|
||||
case VK_FORMAT_R32_SFLOAT:
|
||||
return VK_FORMAT_R32_UINT;
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
return VK_FORMAT_R32G32_UINT;
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
return VK_FORMAT_R32G32B32A32_UINT;
|
||||
default:
|
||||
// For non-float formats, use as-is
|
||||
return float_format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format,
|
||||
bool is_integer, bool is_signed) {
|
||||
VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) {
|
||||
if (!device) {
|
||||
// Null buffer supported, return a null descriptor
|
||||
return VK_NULL_HANDLE;
|
||||
@@ -148,44 +109,25 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat
|
||||
offset = 0;
|
||||
size = 0;
|
||||
}
|
||||
const VkDeviceSize alignment = device->GetTexelBufferOffsetAlignment();
|
||||
const bool needs_alignment = alignment != 0 && (offset % alignment) != 0;
|
||||
const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
|
||||
return offset == view.offset && size == view.size && format == view.format;
|
||||
})};
|
||||
if (it != views.end()) {
|
||||
return *it->handle;
|
||||
}
|
||||
vk::Buffer backing_buffer{};
|
||||
VkBuffer view_buffer = *buffer;
|
||||
u32 view_offset = offset;
|
||||
if (needs_alignment && runtime && size != 0) {
|
||||
backing_buffer = CreateBuffer(*device, runtime->memory_allocator, size);
|
||||
VideoCommon::BufferCopy copy{
|
||||
.src_offset = offset,
|
||||
.dst_offset = 0,
|
||||
.size = size,
|
||||
};
|
||||
runtime->CopyBuffer(*backing_buffer, *buffer, std::span{©, 1}, true);
|
||||
view_buffer = *backing_buffer;
|
||||
view_offset = 0;
|
||||
}
|
||||
views.push_back({
|
||||
.offset = offset,
|
||||
.size = size,
|
||||
.format = format,
|
||||
.is_integer = is_integer,
|
||||
.is_signed = is_signed,
|
||||
.handle = device->GetLogical().CreateBufferView({
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.buffer = view_buffer,
|
||||
.buffer = *buffer,
|
||||
.format = MaxwellToVK::SurfaceFormat(*device, FormatType::Buffer, false, format).format,
|
||||
.offset = view_offset,
|
||||
.offset = offset,
|
||||
.range = size,
|
||||
}),
|
||||
.backing_buffer = std::move(backing_buffer),
|
||||
});
|
||||
return *views.back().handle;
|
||||
}
|
||||
@@ -552,7 +494,6 @@ void BufferCacheRuntime::PostCopyBarrier() {
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||
const VkPipelineStageFlags dst_stages =
|
||||
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT |
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
|
||||
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
|
||||
@@ -33,8 +33,7 @@ public:
|
||||
explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params);
|
||||
explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_);
|
||||
|
||||
[[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format,
|
||||
bool is_integer = false, bool is_signed = false);
|
||||
[[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
|
||||
|
||||
[[nodiscard]] VkBuffer Handle() const noexcept {
|
||||
return *buffer;
|
||||
@@ -61,13 +60,9 @@ private:
|
||||
u32 offset;
|
||||
u32 size;
|
||||
VideoCore::Surface::PixelFormat format;
|
||||
bool is_integer;
|
||||
bool is_signed;
|
||||
vk::BufferView handle;
|
||||
vk::Buffer backing_buffer;
|
||||
};
|
||||
|
||||
BufferCacheRuntime* runtime{};
|
||||
const Device* device{};
|
||||
vk::Buffer buffer;
|
||||
std::vector<BufferView> views;
|
||||
@@ -154,8 +149,8 @@ public:
|
||||
}
|
||||
|
||||
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
|
||||
VideoCore::Surface::PixelFormat format, bool is_integer, bool is_signed) {
|
||||
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format, is_integer, is_signed));
|
||||
VideoCore::Surface::PixelFormat format) {
|
||||
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format));
|
||||
}
|
||||
|
||||
bool ShouldLimitDynamicStorageBuffers() const {
|
||||
|
||||
@@ -916,7 +916,7 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange{aspect, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS},
|
||||
.subresourceRange = {aspect, 0, 1, 0, 1},
|
||||
};
|
||||
|
||||
// Single barrier handles both buffer and image
|
||||
@@ -950,7 +950,7 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange{aspect, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS},
|
||||
.subresourceRange = {aspect, 0, 1, 0, 1},
|
||||
};
|
||||
|
||||
cmdbuf.PipelineBarrier(
|
||||
|
||||
@@ -194,8 +194,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
ImageView& image_view = texture_cache.GetImageView(views[index].id);
|
||||
buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(),
|
||||
image_view.BufferSize(), image_view.format,
|
||||
is_written, is_image, desc.is_integer,
|
||||
desc.is_signed);
|
||||
is_written, is_image);
|
||||
++index;
|
||||
}
|
||||
}};
|
||||
@@ -204,6 +203,10 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
|
||||
buffer_cache.UpdateComputeBuffers();
|
||||
buffer_cache.BindHostComputeBuffers();
|
||||
if (buffer_cache.any_buffer_uploaded) {
|
||||
buffer_cache.runtime.PostCopyBarrier();
|
||||
buffer_cache.any_buffer_uploaded = false;
|
||||
}
|
||||
|
||||
RescalingPushConstant rescaling;
|
||||
const VideoCommon::SamplerId* samplers_it{samplers.data()};
|
||||
|
||||
@@ -422,8 +422,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
|
||||
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
|
||||
image_view.BufferSize(), image_view.format,
|
||||
is_written, is_image, desc.is_integer,
|
||||
desc.is_signed);
|
||||
is_written, is_image);
|
||||
++index;
|
||||
++texture_buffer_it;
|
||||
}
|
||||
@@ -497,6 +496,10 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
if constexpr (Spec::enabled_stages[4]) {
|
||||
prepare_stage(4);
|
||||
}
|
||||
if (buffer_cache.any_buffer_uploaded) {
|
||||
buffer_cache.runtime.PostCopyBarrier();
|
||||
buffer_cache.any_buffer_uploaded = false;
|
||||
}
|
||||
texture_cache.UpdateRenderTargets(false);
|
||||
texture_cache.CheckFeedbackLoop(views);
|
||||
ConfigureDraw(rescaling, render_area);
|
||||
|
||||
@@ -157,10 +157,10 @@ public:
|
||||
ReserveHostQuery();
|
||||
|
||||
scheduler.Record([query_pool = current_query_pool,
|
||||
query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
|
||||
query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
|
||||
const bool use_precise = Settings::IsGPULevelHigh();
|
||||
cmdbuf.BeginQuery(query_pool, static_cast<u32>(query_index),
|
||||
use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
|
||||
use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
|
||||
});
|
||||
|
||||
has_started = true;
|
||||
@@ -454,11 +454,6 @@ private:
|
||||
|
||||
void ReserveHostQuery() {
|
||||
size_t new_slot = ReserveBankSlot();
|
||||
scheduler.RecordWithUploadBuffer([query_pool = current_query_pool,
|
||||
query_index = current_bank_slot](vk::CommandBuffer,
|
||||
vk::CommandBuffer upload_cmdbuf) {
|
||||
upload_cmdbuf.ResetQueryPool(query_pool, static_cast<u32>(query_index), 1);
|
||||
});
|
||||
current_bank->AddReference(1);
|
||||
num_slots_used++;
|
||||
if (current_query) {
|
||||
|
||||
@@ -52,8 +52,7 @@ using VideoCore::Surface::SurfaceType;
|
||||
const bool has_stencil = surface_type == SurfaceType::DepthStencil ||
|
||||
surface_type == SurfaceType::Stencil;
|
||||
|
||||
// Attachments are tracked as GENERAL outside render passes; render-pass begin performs
|
||||
// the transition into attachment-optimal layout for the subpass.
|
||||
// Use optimal layouts for attachments - this allows drivers to optimize tiling and access patterns
|
||||
const VkImageLayout attachment_layout = is_depth_stencil
|
||||
? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
|
||||
: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
@@ -68,7 +67,7 @@ using VideoCore::Surface::SurfaceType;
|
||||
: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE
|
||||
: VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.initialLayout = attachment_layout,
|
||||
.finalLayout = attachment_layout,
|
||||
};
|
||||
}
|
||||
@@ -91,7 +90,7 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
||||
const bool is_valid{format != PixelFormat::Invalid};
|
||||
references[index] = VkAttachmentReference{
|
||||
.attachment = is_valid ? num_colors : VK_ATTACHMENT_UNUSED,
|
||||
.layout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
};
|
||||
if (is_valid) {
|
||||
descriptions.push_back(AttachmentDescription(*device, format, key.samples, false));
|
||||
@@ -104,7 +103,7 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
||||
if (key.depth_format != PixelFormat::Invalid) {
|
||||
depth_reference = VkAttachmentReference{
|
||||
.attachment = num_colors,
|
||||
.layout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
||||
};
|
||||
descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples, true));
|
||||
}
|
||||
|
||||
@@ -168,6 +168,10 @@ bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pipeline->UsesExtendedDynamicState() && !pipeline->HasDynamicVertexInput()) {
|
||||
state_tracker.InvalidateVertexBufferState();
|
||||
}
|
||||
|
||||
if (!pipeline->UsesExtendedDynamicState()) {
|
||||
state.needs_state_enable_refresh = true;
|
||||
} else if (state.needs_state_enable_refresh) {
|
||||
@@ -365,22 +369,20 @@ void Scheduler::EndRenderPass()
|
||||
VkImageLayout new_layout;
|
||||
|
||||
if (is_color) {
|
||||
// Keep GENERAL to match descriptor image layouts used across the renderer.
|
||||
// Color attachments can be read as textures or used as attachments again
|
||||
src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
this_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
new_layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
dst_access = VK_ACCESS_SHADER_READ_BIT
|
||||
| VK_ACCESS_SHADER_WRITE_BIT
|
||||
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
|
||||
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
} else if (is_depth_stencil) {
|
||||
// Keep GENERAL to match descriptor image layouts used across the renderer.
|
||||
// Depth attachments can be read as textures or used as attachments again
|
||||
src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
this_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
|
||||
| VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
new_layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
dst_access = VK_ACCESS_SHADER_READ_BIT
|
||||
| VK_ACCESS_SHADER_WRITE_BIT
|
||||
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
|
||||
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
} else {
|
||||
|
||||
@@ -101,6 +101,14 @@ public:
|
||||
(*flags)[Dirty::StateEnable] = true;
|
||||
}
|
||||
|
||||
void InvalidateVertexBufferState() {
|
||||
(*flags)[VideoCommon::Dirty::VertexBuffers] = true;
|
||||
for (int index = VideoCommon::Dirty::VertexBuffer0;
|
||||
index <= VideoCommon::Dirty::VertexBuffer31; ++index) {
|
||||
(*flags)[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool TouchViewports() {
|
||||
const bool dirty_viewports = Exchange(Dirty::Viewports, false);
|
||||
const bool rescale_viewports = Exchange(VideoCommon::Dirty::RescaleViewports, false);
|
||||
|
||||
@@ -47,32 +47,10 @@ using VideoCommon::SubresourceRange;
|
||||
using VideoCore::Surface::BytesPerBlock;
|
||||
using VideoCore::Surface::HasAlpha;
|
||||
using VideoCore::Surface::IsPixelFormatASTC;
|
||||
using VideoCore::Surface::IsPixelFormatBCn;
|
||||
using VideoCore::Surface::IsPixelFormatSRGB;
|
||||
using VideoCore::Surface::IsPixelFormatInteger;
|
||||
using VideoCore::Surface::SurfaceType;
|
||||
|
||||
namespace {
|
||||
PixelFormat StorageCompatibleBaseFormat(const Device& device, PixelFormat format) {
|
||||
if (!IsPixelFormatSRGB(format)) {
|
||||
return format;
|
||||
}
|
||||
PixelFormat candidate = format;
|
||||
switch (format) {
|
||||
case PixelFormat::A8B8G8R8_SRGB:
|
||||
candidate = PixelFormat::A8B8G8R8_UNORM;
|
||||
break;
|
||||
case PixelFormat::B8G8R8A8_SRGB:
|
||||
candidate = PixelFormat::B8G8R8A8_UNORM;
|
||||
break;
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
const auto base_info =
|
||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, candidate);
|
||||
return base_info.storage ? candidate : format;
|
||||
}
|
||||
|
||||
constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
if (color == std::array<float, 4>{0, 0, 0, 0}) {
|
||||
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
@@ -129,20 +107,10 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
PixelFormat format) {
|
||||
VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
const auto can_have_storage_bit = [&]() {
|
||||
if (IsPixelFormatASTC(format) || IsPixelFormatBCn(format) || IsPixelFormatSRGB(format)) {
|
||||
return false;
|
||||
}
|
||||
return info.storage &&
|
||||
VideoCore::Surface::GetFormatType(format) == SurfaceType::ColorTexture;
|
||||
};
|
||||
if (info.attachable) {
|
||||
switch (VideoCore::Surface::GetFormatType(format)) {
|
||||
case VideoCore::Surface::SurfaceType::ColorTexture:
|
||||
usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
if (can_have_storage_bit()) {
|
||||
usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
}
|
||||
break;
|
||||
case VideoCore::Surface::SurfaceType::Depth:
|
||||
case VideoCore::Surface::SurfaceType::Stencil:
|
||||
@@ -160,21 +128,9 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
return usage;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageUsageFlags ImageUsageFlags(const Device& device,
|
||||
const MaxwellToVK::FormatInfo& info,
|
||||
PixelFormat format) {
|
||||
VkImageUsageFlags usage = ImageUsageFlags(info, format);
|
||||
// ASTC recompression requires STORAGE_BIT for the GPU decoder pass
|
||||
if (IsPixelFormatASTC(format) && !device.IsOptimalAstcSupported()) {
|
||||
usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
|
||||
const PixelFormat base_format = StorageCompatibleBaseFormat(device, info.format);
|
||||
const auto format_info =
|
||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, base_format);
|
||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format);
|
||||
VkImageCreateFlags flags{};
|
||||
if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
|
||||
info.size.width == info.size.height && !device.HasBrokenCubeImageCompatibility()) {
|
||||
@@ -199,7 +155,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
.arrayLayers = static_cast<u32>(info.resources.layers),
|
||||
.samples = ConvertSampleCount(info.num_samples),
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = ImageUsageFlags(device, format_info, base_format),
|
||||
.usage = ImageUsageFlags(format_info, info.format),
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
@@ -228,45 +184,8 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
return allocator.CreateImage(image_ci);
|
||||
}
|
||||
|
||||
[[nodiscard]] VkFormat ConvertUintToUnormFormat(VkFormat format) {
|
||||
// Convert UINT formats to UNORM equivalents for sampling compatibility
|
||||
// Shaders expect FLOAT component types for samplers, not UINT
|
||||
switch (format) {
|
||||
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
|
||||
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
|
||||
return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
|
||||
case VK_FORMAT_R8_UINT:
|
||||
return VK_FORMAT_R8_UNORM;
|
||||
case VK_FORMAT_R16_UINT:
|
||||
return VK_FORMAT_R16_UNORM;
|
||||
case VK_FORMAT_R8G8_UINT:
|
||||
return VK_FORMAT_R8G8_UNORM;
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
return VK_FORMAT_R16G16_UNORM;
|
||||
case VK_FORMAT_R16G16B16A16_UINT:
|
||||
return VK_FORMAT_R16G16B16A16_UNORM;
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image,
|
||||
VkFormat format) {
|
||||
// GLSL storage images in host shaders commonly use rgba8, which maps to VK_FORMAT_R8G8B8A8_UNORM.
|
||||
// Some guest formats are represented as A8B8G8R8 in Vulkan; using that directly here triggers
|
||||
// format mismatch warnings and undefined writes on vkCmdDispatch.
|
||||
const VkFormat storage_view_format = [&] {
|
||||
switch (format) {
|
||||
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
}();
|
||||
|
||||
static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -278,7 +197,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
||||
.flags = 0,
|
||||
.image = image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
|
||||
.format = storage_view_format,
|
||||
.format = format,
|
||||
.components{
|
||||
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
@@ -605,24 +524,20 @@ struct RangedBarrierRange {
|
||||
max_layer = (std::max)(max_layer, layers.baseArrayLayer + layers.layerCount);
|
||||
}
|
||||
|
||||
VkImageSubresourceRange SubresourceRange(VkImageAspectFlags aspect_mask, bool is_3d = false) const noexcept {
|
||||
u32 layer_count = max_layer - min_layer;
|
||||
// For 3D images with 2D_ARRAY compatibility, use VK_REMAINING_ARRAY_LAYERS for single layer
|
||||
if (is_3d && layer_count == 1) {
|
||||
layer_count = VK_REMAINING_ARRAY_LAYERS;
|
||||
}
|
||||
VkImageSubresourceRange SubresourceRange(VkImageAspectFlags aspect_mask) const noexcept {
|
||||
return VkImageSubresourceRange{
|
||||
.aspectMask = aspect_mask,
|
||||
.baseMipLevel = min_mip,
|
||||
.levelCount = max_mip - min_mip,
|
||||
.baseArrayLayer = min_layer,
|
||||
.layerCount = layer_count,
|
||||
.layerCount = max_layer - min_layer,
|
||||
};
|
||||
}
|
||||
};
|
||||
void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage image,
|
||||
VkImageAspectFlags aspect_mask, bool is_initialized,
|
||||
std::span<const VkBufferImageCopy> copies, bool is_3d_image = false) {
|
||||
bool use_unified_layouts,
|
||||
std::span<const VkBufferImageCopy> copies) {
|
||||
static constexpr VkAccessFlags WRITE_ACCESS_FLAGS =
|
||||
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
@@ -635,7 +550,9 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
|
||||
for (const auto& region : copies) {
|
||||
range.AddLayers(region.imageSubresource);
|
||||
}
|
||||
const VkImageSubresourceRange subresource_range = range.SubresourceRange(aspect_mask, is_3d_image);
|
||||
const VkImageSubresourceRange subresource_range = range.SubresourceRange(aspect_mask);
|
||||
const VkImageLayout transfer_dst_layout =
|
||||
use_unified_layouts ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
|
||||
const VkImageMemoryBarrier read_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
@@ -643,7 +560,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
|
||||
.srcAccessMask = WRITE_ACCESS_FLAGS,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = transfer_dst_layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
@@ -655,7 +572,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = WRITE_ACCESS_FLAGS | READ_ACCESS_FLAGS,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.oldLayout = transfer_dst_layout,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
@@ -667,7 +584,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
read_barrier);
|
||||
cmdbuf.CopyBufferToImage(src_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copies);
|
||||
cmdbuf.CopyBufferToImage(src_buffer, image, transfer_dst_layout, copies);
|
||||
// TODO: Move this to another API
|
||||
cmdbuf.PipelineBarrier(
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
@@ -778,16 +695,10 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4
|
||||
return VK_FORMAT_R16_SINT;
|
||||
case Shader::ImageFormat::R32_UINT:
|
||||
return VK_FORMAT_R32_UINT;
|
||||
case Shader::ImageFormat::R32_SINT:
|
||||
return VK_FORMAT_R32_SINT;
|
||||
case Shader::ImageFormat::R32G32_UINT:
|
||||
return VK_FORMAT_R32G32_UINT;
|
||||
case Shader::ImageFormat::R32G32_SINT:
|
||||
return VK_FORMAT_R32G32_SINT;
|
||||
case Shader::ImageFormat::R32G32B32A32_UINT:
|
||||
return VK_FORMAT_R32G32B32A32_UINT;
|
||||
case Shader::ImageFormat::R32G32B32A32_SINT:
|
||||
return VK_FORMAT_R32G32B32A32_SINT;
|
||||
}
|
||||
ASSERT_MSG(false, "Invalid image format={}", format);
|
||||
return VK_FORMAT_R32_UINT;
|
||||
@@ -795,7 +706,7 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4
|
||||
|
||||
void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const ImageInfo& info,
|
||||
VkImageAspectFlags aspect_mask, const Settings::ResolutionScalingInfo& resolution,
|
||||
bool up_scaling = true) {
|
||||
bool use_unified_layouts, bool up_scaling = true) {
|
||||
const bool is_2d = info.type == ImageType::e2D;
|
||||
const auto resources = info.resources;
|
||||
const VkExtent2D extent{
|
||||
@@ -809,7 +720,7 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([dst_image, src_image, extent, resources, aspect_mask, resolution, is_2d,
|
||||
vk_filter, up_scaling](vk::CommandBuffer cmdbuf) {
|
||||
vk_filter, up_scaling, use_unified_layouts](vk::CommandBuffer cmdbuf) {
|
||||
const VkOffset2D src_size{
|
||||
.x = static_cast<s32>(up_scaling ? extent.width : resolution.ScaleUp(extent.width)),
|
||||
.y = static_cast<s32>(is_2d && up_scaling ? extent.height
|
||||
@@ -869,6 +780,10 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
};
|
||||
const VkImageLayout transfer_src_layout =
|
||||
use_unified_layouts ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
const VkImageLayout transfer_dst_layout =
|
||||
use_unified_layouts ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
const std::array read_barriers{
|
||||
VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
@@ -876,7 +791,7 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
||||
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
.newLayout = transfer_src_layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = src_image,
|
||||
@@ -890,7 +805,7 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, // Discard contents
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = transfer_dst_layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
@@ -904,7 +819,7 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
||||
.srcAccessMask = 0,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
.oldLayout = transfer_src_layout,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
@@ -917,7 +832,7 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.oldLayout = transfer_dst_layout,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
@@ -935,8 +850,8 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
cmdbuf.PipelineBarrier(src_stages, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, nullptr, nullptr, read_barriers);
|
||||
cmdbuf.BlitImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regions, vk_filter);
|
||||
cmdbuf.BlitImage(src_image, transfer_src_layout, dst_image,
|
||||
transfer_dst_layout, regions, vk_filter);
|
||||
// After transfer, images may be used in graphics, compute, or as attachments
|
||||
const VkPipelineStageFlags dst_stages =
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
@@ -971,29 +886,16 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
|
||||
return;
|
||||
}
|
||||
for (size_t index_a = 0; index_a < VideoCore::Surface::MaxPixelFormat; index_a++) {
|
||||
auto add_view_format = [&](VkFormat format) {
|
||||
auto& formats = view_formats[index_a];
|
||||
if (std::ranges::find(formats, format) == formats.end()) {
|
||||
formats.push_back(format);
|
||||
}
|
||||
};
|
||||
|
||||
const auto image_format = static_cast<PixelFormat>(index_a);
|
||||
if (IsPixelFormatASTC(image_format) && !device.IsOptimalAstcSupported()) {
|
||||
add_view_format(VK_FORMAT_A8B8G8R8_UNORM_PACK32);
|
||||
add_view_format(VK_FORMAT_R8G8B8A8_UNORM);
|
||||
view_formats[index_a].push_back(VK_FORMAT_A8B8G8R8_UNORM_PACK32);
|
||||
}
|
||||
for (size_t index_b = 0; index_b < VideoCore::Surface::MaxPixelFormat; index_b++) {
|
||||
const auto view_format = static_cast<PixelFormat>(index_b);
|
||||
if (VideoCore::Surface::IsViewCompatible(image_format, view_format, false, true)) {
|
||||
const auto view_info =
|
||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, view_format);
|
||||
add_view_format(view_info.format);
|
||||
|
||||
if (view_info.format == VK_FORMAT_A8B8G8R8_UNORM_PACK32 ||
|
||||
view_info.format == VK_FORMAT_A8B8G8R8_UINT_PACK32) {
|
||||
add_view_format(VK_FORMAT_R8G8B8A8_UNORM);
|
||||
}
|
||||
view_formats[index_a].push_back(view_info.format);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1128,11 +1030,9 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
|
||||
const VkBuffer copy_buffer = GetTemporaryBuffer(total_size);
|
||||
const VkImage dst_image = dst.Handle();
|
||||
const VkImage src_image = src.Handle();
|
||||
const bool dst_is_3d = dst.info.type == ImageType::e3D;
|
||||
const bool src_is_3d = src.info.type == ImageType::e3D;
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([dst_image, src_image, copy_buffer, src_aspect_mask, dst_aspect_mask,
|
||||
vk_in_copies, vk_out_copies, dst_is_3d, src_is_3d](vk::CommandBuffer cmdbuf) {
|
||||
vk_in_copies, vk_out_copies](vk::CommandBuffer cmdbuf) {
|
||||
RangedBarrierRange dst_range;
|
||||
RangedBarrierRange src_range;
|
||||
for (const VkBufferImageCopy& copy : vk_in_copies) {
|
||||
@@ -1167,7 +1067,7 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = src_image,
|
||||
.subresourceRange = src_range.SubresourceRange(src_aspect_mask, src_is_3d),
|
||||
.subresourceRange = src_range.SubresourceRange(src_aspect_mask),
|
||||
},
|
||||
};
|
||||
const std::array middle_in_barrier{
|
||||
@@ -1181,7 +1081,7 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = src_image,
|
||||
.subresourceRange = src_range.SubresourceRange(src_aspect_mask, src_is_3d),
|
||||
.subresourceRange = src_range.SubresourceRange(src_aspect_mask),
|
||||
},
|
||||
};
|
||||
const std::array middle_out_barrier{
|
||||
@@ -1197,7 +1097,7 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange = dst_range.SubresourceRange(dst_aspect_mask, dst_is_3d),
|
||||
.subresourceRange = dst_range.SubresourceRange(dst_aspect_mask),
|
||||
},
|
||||
};
|
||||
const std::array post_barriers{
|
||||
@@ -1216,7 +1116,7 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange = dst_range.SubresourceRange(dst_aspect_mask, dst_is_3d),
|
||||
.subresourceRange = dst_range.SubresourceRange(dst_aspect_mask),
|
||||
},
|
||||
};
|
||||
const VkPipelineStageFlags src_stages_transfer =
|
||||
@@ -1604,10 +1504,8 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
|
||||
});
|
||||
const VkImage dst_image = dst.Handle();
|
||||
const VkImage src_image = src.Handle();
|
||||
const bool src_is_3d = src.info.type == ImageType::e3D;
|
||||
const bool dst_is_3d = dst.info.type == ImageType::e3D;
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([dst_image, src_image, aspect_mask, vk_copies, src_is_3d, dst_is_3d](vk::CommandBuffer cmdbuf) {
|
||||
scheduler.Record([dst_image, src_image, aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) {
|
||||
RangedBarrierRange dst_range;
|
||||
RangedBarrierRange src_range;
|
||||
for (const VkImageCopy& copy : vk_copies) {
|
||||
@@ -1627,7 +1525,7 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = src_image,
|
||||
.subresourceRange = src_range.SubresourceRange(aspect_mask, src_is_3d),
|
||||
.subresourceRange = src_range.SubresourceRange(aspect_mask),
|
||||
},
|
||||
VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
@@ -1641,7 +1539,7 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange = dst_range.SubresourceRange(aspect_mask, dst_is_3d),
|
||||
.subresourceRange = dst_range.SubresourceRange(aspect_mask),
|
||||
},
|
||||
};
|
||||
const std::array post_barriers{
|
||||
@@ -1655,7 +1553,7 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = src_image,
|
||||
.subresourceRange = src_range.SubresourceRange(aspect_mask, src_is_3d),
|
||||
.subresourceRange = src_range.SubresourceRange(aspect_mask),
|
||||
},
|
||||
VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
@@ -1672,7 +1570,7 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dst_image,
|
||||
.subresourceRange = dst_range.SubresourceRange(aspect_mask, dst_is_3d),
|
||||
.subresourceRange = dst_range.SubresourceRange(aspect_mask),
|
||||
},
|
||||
};
|
||||
cmdbuf.PipelineBarrier(
|
||||
@@ -1720,10 +1618,8 @@ void TextureCacheRuntime::TickFrame() {}
|
||||
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
|
||||
VAddr cpu_addr_)
|
||||
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
|
||||
runtime{&runtime_},
|
||||
original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
|
||||
runtime->ViewFormats(StorageCompatibleBaseFormat(
|
||||
runtime_.device, info.format)))),
|
||||
runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
|
||||
runtime->ViewFormats(info.format))),
|
||||
aspect_mask(ImageAspectMask(info.format)) {
|
||||
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
|
||||
switch (Settings::values.accelerate_astc.GetValue()) {
|
||||
@@ -1752,13 +1648,14 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
||||
}
|
||||
current_image = &Image::original_image;
|
||||
storage_image_views.resize(info.resources.levels);
|
||||
|
||||
// Transition render targets to GENERAL layout
|
||||
const auto format_info =
|
||||
MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, false, info.format);
|
||||
const VkImageUsageFlags usage = ImageUsageFlags(runtime->device, format_info, info.format);
|
||||
if ((usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0) {
|
||||
runtime->TransitionImageLayout(*this);
|
||||
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() &&
|
||||
Settings::values.astc_recompression.GetValue() ==
|
||||
Settings::AstcRecompression::Uncompressed) {
|
||||
const auto& device = runtime->device.GetLogical();
|
||||
for (s32 level = 0; level < info.resources.levels; ++level) {
|
||||
storage_image_views[level] =
|
||||
MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1853,9 +1750,11 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
|
||||
const VkImage temp_vk_image = *temp_wrapper->original_image;
|
||||
const VkImageAspectFlags vk_aspect_mask = temp_wrapper->aspect_mask;
|
||||
|
||||
scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask, vk_copies,
|
||||
const bool use_unified_layouts = runtime->device.IsKhrUnifiedImageLayoutsSupported();
|
||||
scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask, vk_copies, use_unified_layouts,
|
||||
keep = temp_wrapper](vk::CommandBuffer cmdbuf) {
|
||||
CopyBufferToImage(cmdbuf, src_buffer, temp_vk_image, vk_aspect_mask, false, VideoCommon::FixSmallVectorADL(vk_copies), false);
|
||||
CopyBufferToImage(cmdbuf, src_buffer, temp_vk_image, vk_aspect_mask, false,
|
||||
use_unified_layouts, VideoCommon::FixSmallVectorADL(vk_copies));
|
||||
});
|
||||
|
||||
// Use MSAACopyPass to convert from non-MSAA to MSAA
|
||||
@@ -1891,11 +1790,12 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
|
||||
const VkImage vk_image = *original_image;
|
||||
const VkImageAspectFlags vk_aspect_mask = aspect_mask;
|
||||
const bool was_initialized = std::exchange(initialized, true);
|
||||
const bool is_3d = info.type == ImageType::e3D;
|
||||
|
||||
const bool use_unified_layouts = runtime->device.IsKhrUnifiedImageLayoutsSupported();
|
||||
scheduler->Record([src_buffer, vk_image, vk_aspect_mask, was_initialized,
|
||||
vk_copies, is_3d](vk::CommandBuffer cmdbuf) {
|
||||
CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, was_initialized, VideoCommon::FixSmallVectorADL(vk_copies), is_3d);
|
||||
vk_copies, use_unified_layouts](vk::CommandBuffer cmdbuf) {
|
||||
CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, was_initialized,
|
||||
use_unified_layouts, VideoCommon::FixSmallVectorADL(vk_copies));
|
||||
});
|
||||
|
||||
if (is_rescaled) {
|
||||
@@ -2136,15 +2036,8 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
|
||||
}
|
||||
|
||||
VkImageView Image::StorageImageView(s32 level) noexcept {
|
||||
// Storage views require the image to have been created with VK_IMAGE_USAGE_STORAGE_BIT
|
||||
if (!(original_image.UsageFlags() & VK_IMAGE_USAGE_STORAGE_BIT)) {
|
||||
// Image doesn't support storage usage, return null
|
||||
return nullptr;
|
||||
}
|
||||
auto& view = storage_image_views[level];
|
||||
if (!view) {
|
||||
// Ensure image is in VK_IMAGE_LAYOUT_GENERAL before using as storage image
|
||||
runtime->TransitionImageLayout(*this);
|
||||
const auto format_info =
|
||||
MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
|
||||
view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image),
|
||||
@@ -2177,9 +2070,6 @@ bool Image::ScaleUp(bool ignore) {
|
||||
scaled_info.size.height = scaled_height;
|
||||
scaled_image = MakeImage(runtime->device, runtime->memory_allocator, scaled_info,
|
||||
runtime->ViewFormats(info.format));
|
||||
const VkImageAspectFlags init_aspect =
|
||||
aspect_mask != 0 ? aspect_mask : ImageAspectMask(info.format);
|
||||
runtime->TransitionImageLayout(*scaled_image, init_aspect);
|
||||
ignore = false;
|
||||
}
|
||||
current_image = &Image::scaled_image;
|
||||
@@ -2192,7 +2082,8 @@ bool Image::ScaleUp(bool ignore) {
|
||||
if (NeedsScaleHelper()) {
|
||||
return BlitScaleHelper(true);
|
||||
} else {
|
||||
BlitScale(*scheduler, *original_image, *scaled_image, info, aspect_mask, resolution);
|
||||
BlitScale(*scheduler, *original_image, *scaled_image, info, aspect_mask, resolution,
|
||||
runtime->device.IsKhrUnifiedImageLayoutsSupported());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2217,7 +2108,8 @@ bool Image::ScaleDown(bool ignore) {
|
||||
if (NeedsScaleHelper()) {
|
||||
return BlitScaleHelper(false);
|
||||
} else {
|
||||
BlitScale(*scheduler, *scaled_image, *original_image, info, aspect_mask, resolution, false);
|
||||
BlitScale(*scheduler, *scaled_image, *original_image, info, aspect_mask, resolution,
|
||||
runtime->device.IsKhrUnifiedImageLayoutsSupported(), false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2299,9 +2191,6 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
|
||||
samples(ConvertSampleCount(image.info.num_samples)) {
|
||||
using Shader::TextureType;
|
||||
|
||||
// Ensure image is transitioned to GENERAL layout before creating views
|
||||
runtime.TransitionImageLayout(image);
|
||||
|
||||
const VkImageAspectFlags aspect_mask = ImageViewAspectMask(info);
|
||||
std::array<SwizzleSource, 4> swizzle{
|
||||
SwizzleSource::R,
|
||||
@@ -2317,13 +2206,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
|
||||
std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
|
||||
}
|
||||
}
|
||||
auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
|
||||
|
||||
// Convert UINT formats to UNORM for sampling compatibility when not a render target
|
||||
if (!info.IsRenderTarget()) {
|
||||
format_info.format = ConvertUintToUnormFormat(format_info.format);
|
||||
}
|
||||
|
||||
const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
|
||||
if (ImageUsageFlags(format_info, format) != image.UsageFlags()) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Image view format {} has different usage flags than image format {}", format,
|
||||
@@ -2332,7 +2215,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
|
||||
const VkImageViewUsageCreateInfo image_view_usage{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.usage = ImageUsageFlags(format_info, format) & image.UsageFlags(),
|
||||
.usage = ImageUsageFlags(format_info, format),
|
||||
};
|
||||
const VkImageViewCreateInfo create_info{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
@@ -2413,7 +2296,6 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageV
|
||||
|
||||
null_image = MakeImage(*device, runtime.memory_allocator, info, {});
|
||||
image_handle = *null_image;
|
||||
runtime.TransitionImageLayout(*null_image, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
for (u32 i = 0; i < Shader::NUM_TEXTURE_TYPES; i++) {
|
||||
image_views[i] = MakeView(VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
@@ -2459,20 +2341,13 @@ VkImageView ImageView::ColorView() {
|
||||
VkImageView ImageView::StorageView(Shader::TextureType texture_type,
|
||||
Shader::ImageFormat image_format) {
|
||||
if (image_handle) {
|
||||
if (!storage_views) {
|
||||
storage_views.emplace();
|
||||
}
|
||||
if (image_format == Shader::ImageFormat::Typeless) {
|
||||
auto& view{storage_views->typeless[size_t(texture_type)]};
|
||||
if (!view) {
|
||||
const auto& format_info =
|
||||
MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, false, format);
|
||||
view = MakeView(format_info.format, VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
}
|
||||
return *view;
|
||||
return Handle(texture_type);
|
||||
}
|
||||
const bool is_signed = image_format == Shader::ImageFormat::R8_SINT
|
||||
|| image_format == Shader::ImageFormat::R16_SINT;
|
||||
if (!storage_views)
|
||||
storage_views.emplace();
|
||||
auto& views{is_signed ? storage_views->signeds : storage_views->unsigneds};
|
||||
auto& view{views[size_t(texture_type)]};
|
||||
if (!view)
|
||||
@@ -2699,36 +2574,41 @@ void TextureCacheRuntime::AccelerateImageUpload(
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
||||
if (image.ExchangeInitialization()) {
|
||||
return;
|
||||
if (!image.ExchangeInitialization()) {
|
||||
VkImageMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_NONE,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image.Handle(),
|
||||
.subresourceRange{
|
||||
.aspectMask = image.AspectMask(),
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([barrier](vk::CommandBuffer cmdbuf) {
|
||||
// After layout transition, image may be used in shaders or as attachment
|
||||
const VkPipelineStageFlags dst_stages_layout =
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
dst_stages_layout, 0, barrier);
|
||||
});
|
||||
}
|
||||
TransitionImageLayout(image.Handle(), image.AspectMask());
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::TransitionImageLayout(VkImage image, VkImageAspectFlags aspect_mask) {
|
||||
VkImageMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_NONE,
|
||||
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect_mask,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([barrier](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -97,7 +97,6 @@ public:
|
||||
void InsertUploadMemoryBarrier() {}
|
||||
|
||||
void TransitionImageLayout(Image& image);
|
||||
void TransitionImageLayout(VkImage image, VkImageAspectFlags aspect_mask);
|
||||
|
||||
bool HasBrokenTextureViewFormats() const noexcept {
|
||||
// No known Vulkan driver has broken image views
|
||||
@@ -372,7 +371,6 @@ private:
|
||||
struct StorageViews {
|
||||
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> signeds;
|
||||
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> unsigneds;
|
||||
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> typeless;
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask);
|
||||
|
||||
@@ -291,55 +291,59 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (render_targets_serial == last_feedback_loop_serial &&
|
||||
texture_bindings_serial == last_feedback_texture_serial) {
|
||||
if (last_feedback_loop_result) {
|
||||
runtime.BarrierFeedbackLoop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (rt_active_mask == 0) {
|
||||
last_feedback_loop_serial = render_targets_serial;
|
||||
last_feedback_texture_serial = texture_bindings_serial;
|
||||
last_feedback_loop_result = false;
|
||||
return;
|
||||
}
|
||||
const u32 depth_bit = 1u << NUM_RT;
|
||||
const bool depth_active = (rt_active_mask & depth_bit) != 0;
|
||||
|
||||
const bool requires_barrier = [&] {
|
||||
for (const auto& view : views) {
|
||||
if (!view.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_render_target = false;
|
||||
|
||||
for (const auto& ct_view_id : render_targets.color_buffer_ids) {
|
||||
if (ct_view_id && ct_view_id == view.id) {
|
||||
is_render_target = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_render_target && render_targets.depth_buffer_id == view.id) {
|
||||
is_render_target = true;
|
||||
}
|
||||
|
||||
if (is_render_target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& image_view = slot_image_views[view.id];
|
||||
|
||||
for (const auto& ct_view_id : render_targets.color_buffer_ids) {
|
||||
if (!ct_view_id) {
|
||||
{
|
||||
bool is_continue = false;
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
is_continue |= (rt_active_mask & (1u << i)) && view.id == render_targets.color_buffer_ids[i];
|
||||
if (is_continue)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& ct_view = slot_image_views[ct_view_id];
|
||||
|
||||
if (image_view.image_id == ct_view.image_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (render_targets.depth_buffer_id) {
|
||||
auto& zt_view = slot_image_views[render_targets.depth_buffer_id];
|
||||
if (depth_active && view.id == render_targets.depth_buffer_id)
|
||||
continue;
|
||||
|
||||
if (image_view.image_id == zt_view.image_id) {
|
||||
return true;
|
||||
}
|
||||
const ImageId view_image_id = slot_image_views[view.id].image_id;
|
||||
{
|
||||
bool is_continue = false;
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
is_continue |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i];
|
||||
if (is_continue)
|
||||
continue;
|
||||
}
|
||||
if (depth_active && view_image_id == rt_depth_image_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}();
|
||||
|
||||
last_feedback_loop_serial = render_targets_serial;
|
||||
last_feedback_texture_serial = texture_bindings_serial;
|
||||
last_feedback_loop_result = requires_barrier;
|
||||
if (requires_barrier) {
|
||||
runtime.BarrierFeedbackLoop();
|
||||
}
|
||||
@@ -399,13 +403,19 @@ void TextureCache<P>::SynchronizeGraphicsDescriptors() {
|
||||
const bool linked_tsc = maxwell3d->regs.sampler_binding == SamplerBinding::ViaHeaderBinding;
|
||||
const u32 tic_limit = maxwell3d->regs.tex_header.limit;
|
||||
const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d->regs.tex_sampler.limit;
|
||||
bool bindings_changed = false;
|
||||
if (channel_state->graphics_sampler_table.Synchronize(maxwell3d->regs.tex_sampler.Address(),
|
||||
tsc_limit)) {
|
||||
channel_state->graphics_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
|
||||
bindings_changed = true;
|
||||
}
|
||||
if (channel_state->graphics_image_table.Synchronize(maxwell3d->regs.tex_header.Address(),
|
||||
tic_limit)) {
|
||||
channel_state->graphics_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
|
||||
bindings_changed = true;
|
||||
}
|
||||
if (bindings_changed) {
|
||||
++texture_bindings_serial;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,12 +425,18 @@ void TextureCache<P>::SynchronizeComputeDescriptors() {
|
||||
const u32 tic_limit = kepler_compute->regs.tic.limit;
|
||||
const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute->regs.tsc.limit;
|
||||
const GPUVAddr tsc_gpu_addr = kepler_compute->regs.tsc.Address();
|
||||
bool bindings_changed = false;
|
||||
if (channel_state->compute_sampler_table.Synchronize(tsc_gpu_addr, tsc_limit)) {
|
||||
channel_state->compute_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
|
||||
bindings_changed = true;
|
||||
}
|
||||
if (channel_state->compute_image_table.Synchronize(kepler_compute->regs.tic.Address(),
|
||||
tic_limit)) {
|
||||
channel_state->compute_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
|
||||
bindings_changed = true;
|
||||
}
|
||||
if (bindings_changed) {
|
||||
++texture_bindings_serial;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,6 +550,7 @@ void TextureCache<P>::UpdateRenderTargets(bool is_clear) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VideoCommon::RenderTargets previous_render_targets = render_targets;
|
||||
const bool rescaled = RescaleRenderTargets();
|
||||
if (is_rescaling != rescaled) {
|
||||
flags[Dirty::RescaleViewports] = true;
|
||||
@@ -549,6 +566,21 @@ void TextureCache<P>::UpdateRenderTargets(bool is_clear) {
|
||||
|
||||
PrepareImageView(depth_buffer_id, true, is_clear && IsFullClear(depth_buffer_id));
|
||||
|
||||
rt_active_mask = 0;
|
||||
rt_image_id = {};
|
||||
for (size_t i = 0; i < rt_image_id.size(); ++i) {
|
||||
if (ImageViewId const view = render_targets.color_buffer_ids[i]; view) {
|
||||
rt_active_mask |= 1u << i;
|
||||
rt_image_id[i] = slot_image_views[view].image_id;
|
||||
}
|
||||
}
|
||||
if (depth_buffer_id) {
|
||||
rt_active_mask |= (1u << NUM_RT);
|
||||
rt_depth_image_id = slot_image_views[depth_buffer_id].image_id;
|
||||
} else {
|
||||
rt_depth_image_id = ImageId{};
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < NUM_RT; ++index) {
|
||||
render_targets.draw_buffers[index] = static_cast<u8>(maxwell3d->regs.rt_control.Map(index));
|
||||
}
|
||||
@@ -564,12 +596,22 @@ void TextureCache<P>::UpdateRenderTargets(bool is_clear) {
|
||||
};
|
||||
render_targets.is_rescaled = is_rescaling;
|
||||
|
||||
if (render_targets != previous_render_targets) {
|
||||
++render_targets_serial;
|
||||
}
|
||||
|
||||
flags[Dirty::DepthBiasGlobal] = true;
|
||||
}
|
||||
|
||||
template <class P>
|
||||
typename P::Framebuffer* TextureCache<P>::GetFramebuffer() {
|
||||
return &slot_framebuffers[GetFramebufferId(render_targets)];
|
||||
if (last_framebuffer_id && last_framebuffer_serial == render_targets_serial) {
|
||||
return &slot_framebuffers[last_framebuffer_id];
|
||||
}
|
||||
const FramebufferId framebuffer_id = GetFramebufferId(render_targets);
|
||||
last_framebuffer_id = framebuffer_id;
|
||||
last_framebuffer_serial = render_targets_serial;
|
||||
return &slot_framebuffers[framebuffer_id];
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -2614,6 +2656,10 @@ void TextureCache<P>::RemoveFramebuffers(std::span<const ImageViewId> removed_vi
|
||||
if (it->first.Contains(removed_views)) {
|
||||
auto framebuffer_id = it->second;
|
||||
ASSERT(framebuffer_id);
|
||||
if (framebuffer_id == last_framebuffer_id) {
|
||||
last_framebuffer_id = {};
|
||||
last_framebuffer_serial = 0;
|
||||
}
|
||||
sentenced_framebuffers.Push(std::move(slot_framebuffers[framebuffer_id]));
|
||||
it = framebuffers.erase(it);
|
||||
} else {
|
||||
|
||||
@@ -455,6 +455,16 @@ private:
|
||||
std::deque<TextureCacheGPUMap> gpu_page_table_storage;
|
||||
|
||||
RenderTargets render_targets;
|
||||
u64 render_targets_serial = 0;
|
||||
u32 rt_active_mask = 0;
|
||||
std::array<ImageId, 8> rt_image_id{};
|
||||
ImageId rt_depth_image_id{};
|
||||
u64 texture_bindings_serial = 0;
|
||||
u64 last_feedback_loop_serial = 0;
|
||||
u64 last_feedback_texture_serial = 0;
|
||||
bool last_feedback_loop_result = false;
|
||||
FramebufferId last_framebuffer_id{};
|
||||
u64 last_framebuffer_serial = 0;
|
||||
|
||||
ankerl::unordered_dense::map<RenderTargets, FramebufferId> framebuffers;
|
||||
ankerl::unordered_dense::map<u64, std::vector<ImageMapId>, Common::IdentityHash<u64>> page_table;
|
||||
|
||||
@@ -924,9 +924,6 @@ bool Device::ShouldBoostClocks() const {
|
||||
}
|
||||
|
||||
bool Device::HasTimelineSemaphore() const {
|
||||
if (GetDriverID() == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
return false;
|
||||
}
|
||||
return features.timeline_semaphore.timelineSemaphore;
|
||||
}
|
||||
|
||||
@@ -935,8 +932,18 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
bool suitable = true;
|
||||
|
||||
// Configure properties.
|
||||
if (!Settings::values.renderer_debug) {
|
||||
features.features.robustBufferAccess = VK_FALSE;
|
||||
if (extensions.robustness_2) {
|
||||
features.robustness2.robustBufferAccess2 = VK_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
VkPhysicalDeviceVulkan12Features features_1_2{};
|
||||
VkPhysicalDeviceVulkan13Features features_1_3{};
|
||||
#ifdef VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES
|
||||
VkPhysicalDeviceVulkan14Features features_1_4{};
|
||||
#endif
|
||||
|
||||
// Configure properties.
|
||||
properties.properties = physical.GetProperties();
|
||||
@@ -976,6 +983,11 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
if (instance_version < VK_API_VERSION_1_3) {
|
||||
FOR_EACH_VK_FEATURE_1_3(FEATURE_EXTENSION);
|
||||
}
|
||||
#ifdef VK_API_VERSION_1_4
|
||||
if (instance_version < VK_API_VERSION_1_4) {
|
||||
FOR_EACH_VK_FEATURE_1_4(FEATURE_EXTENSION);
|
||||
}
|
||||
#endif
|
||||
|
||||
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
|
||||
FOR_EACH_VK_EXTENSION(EXTENSION);
|
||||
@@ -1011,11 +1023,16 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
// Set next pointer.
|
||||
void** next = &features2.pNext;
|
||||
|
||||
// Vulkan 1.2 and 1.3 features
|
||||
// Vulkan 1.2, 1.3 and 1.4 features
|
||||
if (instance_version >= VK_API_VERSION_1_2) {
|
||||
features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
|
||||
features_1_3.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
|
||||
|
||||
#ifdef VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES
|
||||
features_1_4.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES;
|
||||
features_1_3.pNext = &features_1_4;
|
||||
#endif
|
||||
|
||||
features_1_2.pNext = &features_1_3;
|
||||
|
||||
*next = &features_1_2;
|
||||
@@ -1047,6 +1064,13 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
} else {
|
||||
FOR_EACH_VK_FEATURE_1_3(EXT_FEATURE);
|
||||
}
|
||||
#ifdef VK_API_VERSION_1_4
|
||||
if (instance_version >= VK_API_VERSION_1_4) {
|
||||
FOR_EACH_VK_FEATURE_1_4(FEATURE);
|
||||
} else {
|
||||
FOR_EACH_VK_FEATURE_1_4(EXT_FEATURE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef EXT_FEATURE
|
||||
#undef FEATURE
|
||||
@@ -1484,16 +1508,31 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.maintenance6, features.maintenance6,
|
||||
VK_KHR_MAINTENANCE_6_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance7 (proposed for Vulkan 1.4, no features)
|
||||
// VK_KHR_maintenance7
|
||||
#ifdef VK_API_VERSION_1_4
|
||||
extensions.maintenance7 = instance_version >= VK_API_VERSION_1_4 ||
|
||||
loaded_extensions.contains(VK_KHR_MAINTENANCE_7_EXTENSION_NAME);
|
||||
#else
|
||||
extensions.maintenance7 = loaded_extensions.contains(VK_KHR_MAINTENANCE_7_EXTENSION_NAME);
|
||||
#endif
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance7, VK_KHR_MAINTENANCE_7_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance8 (proposed for Vulkan 1.4, no features)
|
||||
// VK_KHR_maintenance8
|
||||
#ifdef VK_API_VERSION_1_4
|
||||
extensions.maintenance8 = instance_version >= VK_API_VERSION_1_4 ||
|
||||
loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
|
||||
#else
|
||||
extensions.maintenance8 = loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
|
||||
#endif
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance8, VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance9 (proposed for Vulkan 1.4, no features)
|
||||
// VK_KHR_maintenance9
|
||||
#ifdef VK_API_VERSION_1_4
|
||||
extensions.maintenance9 = instance_version >= VK_API_VERSION_1_4 ||
|
||||
loaded_extensions.contains(VK_KHR_MAINTENANCE_9_EXTENSION_NAME);
|
||||
#else
|
||||
extensions.maintenance9 = loaded_extensions.contains(VK_KHR_MAINTENANCE_9_EXTENSION_NAME);
|
||||
#endif
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance9, VK_KHR_MAINTENANCE_9_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
|
||||
@@ -317,11 +317,6 @@ public:
|
||||
return properties.properties.limits.minStorageBufferOffsetAlignment;
|
||||
}
|
||||
|
||||
/// Returns texel buffer offset alignment requirement.
|
||||
VkDeviceSize GetTexelBufferOffsetAlignment() const {
|
||||
return properties.properties.limits.minTexelBufferOffsetAlignment;
|
||||
}
|
||||
|
||||
/// Returns the maximum range for storage buffers.
|
||||
VkDeviceSize GetMaxStorageBufferRange() const {
|
||||
return properties.properties.limits.maxStorageBufferRange;
|
||||
@@ -492,6 +487,11 @@ public:
|
||||
return extensions.workgroup_memory_explicit_layout;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_unified_image_layouts.
|
||||
bool IsKhrUnifiedImageLayoutsSupported() const {
|
||||
return extensions.unified_image_layouts;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_image_format_list.
|
||||
bool IsKhrImageFormatListSupported() const {
|
||||
return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -47,6 +47,14 @@ bool IsMicrosoftDozen(const char* device_name) {
|
||||
return std::strstr(device_name, "Microsoft") != nullptr;
|
||||
}
|
||||
|
||||
constexpr u32 MaxInstanceApiVersion() {
|
||||
#ifdef VK_API_VERSION_1_4
|
||||
return VK_API_VERSION_1_4;
|
||||
#else
|
||||
return VK_API_VERSION_1_3;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld) {
|
||||
// Sort by name, this will set a base and make GPUs with higher numbers appear first
|
||||
// (e.g. GTX 1650 will intentionally be listed before a GTX 1080).
|
||||
@@ -127,7 +135,6 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
||||
X(vkCmdPipelineBarrier);
|
||||
X(vkCmdPushConstants);
|
||||
X(vkCmdPushDescriptorSetWithTemplateKHR);
|
||||
X(vkCmdResetQueryPool);
|
||||
X(vkCmdSetBlendConstants);
|
||||
X(vkCmdSetDepthBias);
|
||||
X(vkCmdSetDepthBias2EXT);
|
||||
@@ -438,8 +445,8 @@ Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char
|
||||
#else
|
||||
constexpr VkFlags ci_flags{};
|
||||
#endif
|
||||
// DO NOT TOUCH, breaks RNDA3!!
|
||||
// Don't know why, but gloom + yellow line glitch appears
|
||||
// Keep application and engine tags stable for driver behavior compatibility.
|
||||
const u32 api_version = std::min(version, MaxInstanceApiVersion());
|
||||
const VkApplicationInfo application_info{
|
||||
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -447,7 +454,7 @@ Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char
|
||||
.applicationVersion = VK_MAKE_VERSION(1, 3, 0),
|
||||
.pEngineName = "yuzu Emulator",
|
||||
.engineVersion = VK_MAKE_VERSION(1, 3, 0),
|
||||
.apiVersion = VK_API_VERSION_1_3,
|
||||
.apiVersion = api_version,
|
||||
};
|
||||
const VkInstanceCreateInfo ci{
|
||||
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||
|
||||
@@ -228,7 +228,6 @@ struct DeviceDispatch : InstanceDispatch {
|
||||
PFN_vkCmdPushConstants vkCmdPushConstants{};
|
||||
PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR{};
|
||||
PFN_vkCmdResolveImage vkCmdResolveImage{};
|
||||
PFN_vkCmdResetQueryPool vkCmdResetQueryPool{};
|
||||
PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{};
|
||||
PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{};
|
||||
PFN_vkCmdSetDepthBias vkCmdSetDepthBias{};
|
||||
@@ -1165,10 +1164,6 @@ public:
|
||||
dld->vkCmdBeginQuery(handle, query_pool, query, flags);
|
||||
}
|
||||
|
||||
void ResetQueryPool(VkQueryPool query_pool, u32 first, u32 count) const noexcept {
|
||||
dld->vkCmdResetQueryPool(handle, query_pool, first, count);
|
||||
}
|
||||
|
||||
void EndQuery(VkQueryPool query_pool, u32 query) const noexcept {
|
||||
dld->vkCmdEndQuery(handle, query_pool, query);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user