Compare commits

..

32 Commits

Author SHA1 Message Date
CamilleLaVey 71cf0e0daa fix build 2026-02-14 03:41:57 -04:00
CamilleLaVey 59444d5c48 [vulkan] bump to vk 1.4 and some features to 1.4 tree 2026-02-14 03:28:00 -04:00
CamilleLaVey 3aa4fb867c [vulkan, tu] Returning timeline semaphores to turnip 2026-02-14 01:07:52 -04:00
lizzie ab1ad1dc4a [maxwell] fix divide by 0 (#3540)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3540
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-14 05:12:01 +01:00
CamilleLaVey 0dc64e2a48 fix build. 2026-02-14 03:46:23 +01:00
CamilleLaVey 420b002448 Revert "[texture_cache, buffer_cache] Rebuild modified pages in texture cache" 2026-02-14 03:46:23 +01:00
CamilleLaVey 413cebca20 Revert "smol change" 2026-02-14 03:46:23 +01:00
CamilleLaVey a2d830e51b [vulkan] BindVertexBuffers2EXT reset per pipeline configuration 2026-02-14 03:46:23 +01:00
CamilleLaVey 47d6360038 smol change 2026-02-14 03:46:23 +01:00
CamilleLaVey e0daa0d83d [texture_cache, buffer_cache] Rebuild modified pages in texture cache 2026-02-14 03:46:23 +01:00
lizzie c487a5cbc4 properly do tls + array pod (#3529)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3529
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-14 03:46:23 +01:00
CamilleLaVey 6b7e54e115 Revert "[engine, dma] Adjustment of the execution mask handling" 2026-02-14 03:46:23 +01:00
CamilleLaVey 8bbbd28f48 Revert "fix license headers" 2026-02-14 03:46:23 +01:00
CamilleLaVey ca7c1c7230 Revert "small change" 2026-02-14 03:46:23 +01:00
CamilleLaVey db10193a16 Revert "fix build" 2026-02-14 03:46:23 +01:00
CamilleLaVey 8c045a47d2 fix build 2026-02-14 03:46:23 +01:00
CamilleLaVey 616e0a93c7 small change 2026-02-14 03:46:23 +01:00
CamilleLaVey 78e7037ccc fix license headers 2026-02-14 03:46:23 +01:00
CamilleLaVey 635335fc85 [engine, dma] Adjustment of the execution mask handling 2026-02-14 03:46:23 +01:00
CamilleLaVey e35efd3db1 fix build 2026-02-14 03:46:23 +01:00
CamilleLaVey 901f556af5 [buffer_cache, memory, pipeline] Added translation entry structure and update buffer cache handling 2026-02-14 03:46:23 +01:00
CamilleLaVey ef730ed490 fix build 2026-02-14 03:46:23 +01:00
CamilleLaVey c188b6a819 [vulkan, texture_cache] Add RT's management and feedback loop tracking in texture cache 2026-02-14 03:46:23 +01:00
wildcard 476f035672 [vulkan] disable robustbufferaccess when gpu debugging is off 2026-02-14 03:46:23 +01:00
CamilleLaVey b4a1207dab Revert "[vulkan] Added dirty tracking in PushDescriptor" 2026-02-14 03:46:23 +01:00
CamilleLaVey 59c41f0746 Revert "fix building" 2026-02-14 03:46:23 +01:00
CamilleLaVey c2f0a1c786 fix building 2026-02-14 03:46:23 +01:00
CamilleLaVey a711a4e6ab [vulkan] Added dirty tracking in PushDescriptor 2026-02-14 03:46:23 +01:00
CamilleLaVey 5aa9ed4dc0 fix license headers 2026-02-14 03:46:23 +01:00
CamilleLaVey 1b8f4c2062 [common, gpu] Added a page bitset range to convert Subtract within linear operations 2026-02-14 03:46:23 +01:00
CamilleLaVey 1eaf8bb28f [buffer_cache] Adjusted PopAsyncBuffers for range management 2026-02-14 03:46:23 +01:00
CamilleLaVey 28bf78db3a [common, gpu_thread] Exchanging command queue + adjusting Subtract in the OverlapRangeSetImpl 2026-02-14 03:46:23 +01:00
45 changed files with 727 additions and 744 deletions
+1 -2
View File
@@ -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(); \
} \
+143
View File
@@ -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
View File
@@ -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>
+7
View File
@@ -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);
+43 -1
View File
@@ -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{};
+2 -74
View File
@@ -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,
-7
View File
@@ -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;
+7 -1
View File
@@ -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 {
+95 -53
View File
@@ -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
+4 -1
View File
@@ -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
View File
@@ -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);
+4 -3
View File
@@ -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{&copy, 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);
+80 -34
View File
@@ -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;
+46 -7
View File
@@ -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);
}
+5 -5
View File
@@ -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);
}