Compare commits

..

1 Commits

Author SHA1 Message Date
xbzk 4b7570b24d [ir] resolve conditional Phi inputs when tracking bindless texture handles 2026-10-18 01:19:33 -03:00
23 changed files with 142 additions and 755 deletions
@@ -57,21 +57,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
// Set default speed limit to 100%.
*out_speed_scale = 1.0f;
// If no layers are visible, skip the logic. Acquiring and holding buffers here would
// consume the producer's queue without ever presenting.
bool any_visible = false;
for (auto& layer : display.stack.layers) {
if (layer->visible) {
any_visible = true;
break;
}
}
if (!any_visible) {
return 1;
}
// Wait for the previous composite, bounding the number of in-flight composites.
nvdisp.WaitForComposite();
this->ReleaseFramebuffersLocked(display);
// Determine the number of vsync periods to wait before composing again.
std::optional<s32> swap_interval{};
@@ -165,13 +152,6 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
// Advance by 1 frame (60 FPS compositing)
m_frame_number += 1;
// Release framebuffers once the frame has been handed to the compositor. This happens
// within the same vsync period as the acquire (release_frame_number is checked against
// the advanced counter), so the producer gets its buffers back immediately instead of
// losing a queue slot for a whole frame.
// TODO: support release fences; releasing before the composite has finished can tear.
this->ReleaseFramebuffersLocked(display);
return 1;
}
+2 -12
View File
@@ -40,10 +40,9 @@ struct HandleKey {
u32 sec_index, sec_offset, sec_shift_left;
u32 count;
bool has_secondary;
u32 mask, sec_mask;
constexpr bool operator==(const HandleKey& o) const noexcept {
return std::tie(index, offset, shift_left, sec_index, sec_offset, sec_shift_left, count, has_secondary, mask, sec_mask)
== std::tie(o.index, o.offset, o.shift_left, o.sec_index, o.sec_offset, o.sec_shift_left, o.count, o.has_secondary, o.mask, o.sec_mask);
return std::tie(index, offset, shift_left, sec_index, sec_offset, sec_shift_left, count, has_secondary)
== std::tie(o.index, o.offset, o.shift_left, o.sec_index, o.sec_offset, o.sec_shift_left, o.count, o.has_secondary);
}
};
struct HandleKeyHash {
@@ -53,7 +52,6 @@ struct HandleKeyHash {
h ^= (size_t(k.sec_index) << 33) ^ (size_t(k.sec_offset) << 2);
h ^= (size_t(k.sec_shift_left) << 3);
h ^= (size_t(k.count) << 7);
h ^= (size_t(k.mask) << 11) ^ (size_t(k.sec_mask) << 13);
return h ^ (k.has_secondary ? 0x9e3779b97f4a7c15ULL : 0ULL);
}
};
@@ -68,14 +66,6 @@ struct ConstBufferAddr {
IR::U32 dynamic_offset;
u32 count;
bool has_secondary;
/// The dynamic offset is a constant buffer value shifted left, the same for the whole draw
bool has_index_cbuf;
u32 index_cbuf_index;
u32 index_cbuf_offset;
u32 index_shift;
/// Applied after the shifts
u32 mask = 0xffffffff;
u32 secondary_mask = 0xffffffff;
};
class Environment {
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <functional>
#include <optional>
#include <type_traits>
#include <queue>
@@ -13,9 +17,10 @@
namespace Shader::IR {
template <typename Pred>
auto BreadthFirstSearch(const Value& value, Pred&& pred)
template <typename Pred, typename Resolve = std::identity>
auto BreadthFirstSearch(Value value, Pred&& pred, Resolve&& resolve = {})
-> std::invoke_result_t<Pred, const Inst*> {
value = resolve(value);
if (value.IsImmediate()) {
// Nothing to do with immediates
return std::nullopt;
@@ -36,7 +41,7 @@ auto BreadthFirstSearch(const Value& value, Pred&& pred)
}
// Visit the right most arguments first
for (size_t arg = inst->NumArgs(); arg--;) {
const Value arg_value{inst->Arg(arg)};
const Value arg_value{resolve(inst->Arg(arg))};
if (arg_value.IsImmediate()) {
continue;
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -41,9 +41,9 @@ union F2I {
BitField<39, 2, Rounding> rounding;
BitField<41, 1, u64> half;
BitField<44, 1, u64> ftz;
BitField<45, 1, u64> neg;
BitField<45, 1, u64> abs;
BitField<47, 1, u64> cc;
BitField<49, 1, u64> abs;
BitField<49, 1, u64> neg;
};
size_t BitSize(DestFormat dest_format) {
+96 -115
View File
@@ -31,6 +31,60 @@ struct TextureInst {
using TextureInstVector = boost::container::small_vector<TextureInst, 24>;
const IR::Block* ConditionalHeader(const IR::Block* body) {
const auto predecessors{body->ImmPredecessors()};
const auto successors{body->ImmSuccessors()};
if (predecessors.size() != 1 || successors.size() != 1) return nullptr;
const IR::Block* const header{predecessors[0]};
const auto targets{header->ImmSuccessors()};
if (targets.size() != 2 || targets[0] != body || targets[1] != successors[0] ||
targets[1]->ImmPredecessors().size() != 2 || header->empty() ||
header->back().GetOpcode() != IR::Opcode::ConditionRef) {
return nullptr;
}
return header;
}
std::pair<IR::Value, bool> Condition(IR::Value value) {
bool negated{};
while (const IR::Inst* inst = value.TryInstRecursive()) {
if (inst->GetOpcode() == IR::Opcode::LogicalNot) negated = !negated;
else if (inst->GetOpcode() != IR::Opcode::ConditionRef) break;
value = inst->Arg(0);
}
return {value.Resolve(), negated};
}
IR::Value ResolveConditionalPhi(IR::Value value, const IR::Block* block) {
if (!value.Resolve().IsPhi() || !(block = ConditionalHeader(block))) return value;
const auto [condition, negated]{Condition(block->back().Arg(0))};
boost::container::small_vector<const IR::Block*, 16> visited;
while (value.Resolve().IsPhi() && std::ranges::find(visited, block) == visited.end()) {
visited.push_back(block);
const IR::Inst* const phi{value.InstRecursive()};
const auto predecessors{block->ImmPredecessors()};
if (predecessors.size() == 1) {
block = predecessors[0];
continue;
}
if (predecessors.size() != 2 || phi->NumArgs() != 2) break;
const IR::Block* body{predecessors[0]};
const IR::Block* header{ConditionalHeader(body)};
if (header != predecessors[1]) {
body = predecessors[1];
header = ConditionalHeader(body);
if (header != predecessors[0]) break;
}
const auto [other_condition, other_negated]{Condition(header->back().Arg(0))};
if (condition == other_condition && (phi->PhiBlock(0) == header || phi->PhiBlock(0) == body)) {
const IR::Block* const incoming{negated == other_negated ? body : header};
value = phi->Arg(phi->PhiBlock(0) == incoming ? 0 : 1);
}
block = header;
}
return value;
}
constexpr u32 DESCRIPTOR_SIZE = 8;
constexpr u32 DESCRIPTOR_SIZE_SHIFT = u32(std::countr_zero(DESCRIPTOR_SIZE));
constexpr u32 DESCRIPTOR_MAX_COUNT = 1024;
@@ -43,8 +97,7 @@ u32 DynamicDescriptorSizeShift(const IR::U32& dynamic_offset) {
if (!shift.IsImmediate())
return DESCRIPTOR_SIZE_SHIFT;
const u32 size_shift = shift.U32();
// Separate texture and sampler handles are 4 bytes apart
return size_shift >= 2 && size_shift < 31 ? size_shift : DESCRIPTOR_SIZE_SHIFT;
return size_shift >= DESCRIPTOR_SIZE_SHIFT && size_shift < 31 ? size_shift : DESCRIPTOR_SIZE_SHIFT;
}
u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift, u32 max_descriptors) {
@@ -60,37 +113,6 @@ u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift, u32 max_descriptors)
return std::min(descriptor_limit, available_count);
}
struct UniformIndex {
u32 cbuf_index;
u32 cbuf_offset;
u32 shift;
};
/// Matches a dynamic handle offset that is a constant buffer value, optionally shifted left
std::optional<UniformIndex> TrackUniformIndex(const IR::U32& dynamic_offset) {
if (dynamic_offset.IsImmediate()) {
return std::nullopt;
}
const IR::Inst* inst = dynamic_offset.InstRecursive();
u32 shift = 0;
if (inst->GetOpcode() == IR::Opcode::ShiftLeftLogical32) {
if (!inst->Arg(1).IsImmediate() || inst->Arg(0).IsImmediate()) {
return std::nullopt;
}
shift = inst->Arg(1).U32();
inst = inst->Arg(0).InstRecursive();
}
if (inst->GetOpcode() != IR::Opcode::GetCbufU32 || !inst->Arg(0).IsImmediate() ||
!inst->Arg(1).IsImmediate() || shift >= 32) {
return std::nullopt;
}
return UniformIndex{
.cbuf_index = inst->Arg(0).U32(),
.cbuf_offset = inst->Arg(1).U32(),
.shift = shift,
};
}
u32 SaturatingSub(u32 lhs, u32 rhs) {
return lhs > rhs ? lhs - rhs : 0;
}
@@ -305,15 +327,14 @@ static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr
// If has secondary, then it will be cbuf.secondary_{index|offset}, else its 0.
// So we can just hand it out the raw variable without using sec_idx or sec_off
// because comparing 0 against 0 will yield true.
const HandleKey hk{cbuf.index, cbuf.offset, cbuf.shift_left, cbuf.secondary_index, cbuf.secondary_offset, cbuf.secondary_shift_left, cbuf.count, cbuf.has_secondary, cbuf.mask, cbuf.secondary_mask};
const HandleKey hk{cbuf.index, cbuf.offset, cbuf.shift_left, cbuf.secondary_index, cbuf.secondary_offset, cbuf.secondary_shift_left, cbuf.count, cbuf.has_secondary};
if (auto it = env.handle_cache.find(hk); it != env.handle_cache.end())
return it->second;
u32 handle = (ReadCbufCached(env, cbuf.index, cbuf.offset) << cbuf.shift_left) & cbuf.mask;
if (cbuf.has_secondary) {
handle |= (ReadCbufCached(env, cbuf.secondary_index, cbuf.secondary_offset)
<< cbuf.secondary_shift_left) &
cbuf.secondary_mask;
}
const u32 sec_idx = cbuf.has_secondary ? cbuf.secondary_index : cbuf.index;
const u32 sec_off = cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset;
const u32 lhs = ReadCbufCached(env, cbuf.index, cbuf.offset) << cbuf.shift_left;
const u32 rhs = ReadCbufCached(env, sec_idx, sec_off) << cbuf.secondary_shift_left;
const u32 handle = lhs | rhs;
env.handle_cache.emplace(hk, handle);
return handle;
}
@@ -332,23 +353,23 @@ static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
}
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info);
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env, const HostTranslateInfo& host_info) {
if (const IR::Inst* key = v.InstRecursive()) {
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block);
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) {
if (const IR::Inst* key = v.TryInstRecursive()) {
if (auto it = env.track_cache.find(key); it != env.track_cache.end()) return it->second;
auto found = Track(v, env, host_info);
auto found = Track(v, env, host_info, block);
if (found) env.track_cache.emplace(key, *found);
return found;
}
return Track(v, env, host_info);
return Track(v, env, host_info, block);
}
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info);
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block);
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info) {
return IR::BreadthFirstSearch(value, [&env, &host_info](const IR::Inst* inst) {
return TryGetConstBuffer(inst, env, host_info);
});
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) {
return IR::BreadthFirstSearch(value, [&env, &host_info, block](const IR::Inst* inst) {
return TryGetConstBuffer(inst, env, host_info, block);
}, [&](IR::Value operand) { return ResolveConditionalPhi(operand, block); });
}
std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
@@ -372,35 +393,23 @@ std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
return ReadCbufCached(env, index_number, offset_number);
}
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info) {
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) {
switch (inst->GetOpcode()) {
default:
return std::nullopt;
case IR::Opcode::BitwiseOr32: {
std::optional lhs{TrackCached(inst->Arg(0), env, host_info)};
std::optional rhs{TrackCached(inst->Arg(1), env, host_info)};
std::optional lhs{TrackCached(inst->Arg(0), env, host_info, block)};
std::optional rhs{TrackCached(inst->Arg(1), env, host_info, block)};
if (!lhs || !rhs) {
return std::nullopt;
}
if (lhs->has_secondary || rhs->has_secondary) {
return std::nullopt;
}
if (lhs->count > 1 && rhs->count > 1) {
if (lhs->count > 1 || rhs->count > 1) {
return std::nullopt;
}
if (rhs->has_index_cbuf || lhs->has_index_cbuf) {
if (rhs->has_index_cbuf && lhs->has_index_cbuf) {
return std::nullopt;
}
if (rhs->has_index_cbuf) {
std::swap(lhs, rhs);
}
} else if (rhs->count > 1) {
// An indexed handle array combined with a single handle (e.g. texture indices with
// a shared sampler), only the primary handle is indexed
std::swap(lhs, rhs);
} else if (lhs->count == 1 &&
(lhs->shift_left > 0 || lhs->index > rhs->index || lhs->offset > rhs->offset)) {
if (lhs->shift_left > 0 || lhs->index > rhs->index || lhs->offset > rhs->offset) {
std::swap(lhs, rhs);
}
return ConstBufferAddr{
@@ -410,15 +419,9 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
.secondary_index = rhs->index,
.secondary_offset = rhs->offset,
.secondary_shift_left = rhs->shift_left,
.dynamic_offset = lhs->dynamic_offset,
.count = lhs->count,
.dynamic_offset = {},
.count = 1,
.has_secondary = true,
.has_index_cbuf = lhs->has_index_cbuf,
.index_cbuf_index = lhs->index_cbuf_index,
.index_cbuf_offset = lhs->index_cbuf_offset,
.index_shift = lhs->index_shift,
.mask = lhs->mask,
.secondary_mask = rhs->mask,
};
}
case IR::Opcode::ShiftLeftLogical32: {
@@ -426,11 +429,9 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
if (!shift.IsImmediate()) {
return std::nullopt;
}
std::optional lhs{TrackCached(inst->Arg(0), env, host_info)};
std::optional lhs{TrackCached(inst->Arg(0), env, host_info, block)};
if (lhs) {
lhs->shift_left = shift.U32();
// A mask applied before the shift moves with the value
lhs->mask <<= shift.U32();
}
return lhs;
}
@@ -456,9 +457,9 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
return std::nullopt;
} while (false);
}
std::optional lhs{TrackCached(op1, env, host_info)};
std::optional lhs{TrackCached(op1, env, host_info, block)};
if (lhs) {
lhs->mask &= op2.U32();
lhs->shift_left = static_cast<u32>(std::countr_zero(op2.U32()));
}
return lhs;
break;
@@ -488,12 +489,12 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
};
}
IR::Inst* const offset_inst{offset.InstRecursive()};
if (offset_inst->GetOpcode() != IR::Opcode::IAdd32) {
return std::nullopt;
}
u32 base_offset{};
IR::U32 dynamic_offset;
if (offset_inst->GetOpcode() != IR::Opcode::IAdd32) {
// The handle array starts at the beginning of the constant buffer
dynamic_offset = IR::U32{offset};
} else if (offset_inst->Arg(0).IsImmediate()) {
if (offset_inst->Arg(0).IsImmediate()) {
base_offset = offset_inst->Arg(0).U32();
dynamic_offset = IR::U32{offset_inst->Arg(1)};
} else if (offset_inst->Arg(1).IsImmediate()) {
@@ -503,7 +504,6 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
return std::nullopt;
}
auto const size_shift = DynamicDescriptorSizeShift(dynamic_offset);
const std::optional<UniformIndex> uniform_index{TrackUniformIndex(dynamic_offset)};
auto const sampled_limit = (std::max)(1U, (std::min)(host_info.max_per_stage_descriptor_sampled_images,
host_info.max_descriptor_set_sampled_images));
auto const resource_limit = (std::max)(1U, host_info.max_per_stage_resources);
@@ -517,17 +517,13 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
.dynamic_offset = dynamic_offset,
.count = DynamicDescriptorCount(base_offset, size_shift, (std::min)({DESCRIPTOR_MAX_COUNT, sampled_limit, resource_limit})),
.has_secondary = false,
.has_index_cbuf = uniform_index.has_value(),
.index_cbuf_index = uniform_index ? uniform_index->cbuf_index : 0,
.index_cbuf_offset = uniform_index ? uniform_index->cbuf_offset : 0,
.index_shift = uniform_index ? uniform_index->shift : 0,
};
}
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const HostTranslateInfo& host_info) {
ConstBufferAddr addr;
if (IsBindless(inst)) {
const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env, host_info)};
const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env, host_info, block)};
if (!track_addr) {
throw NotImplementedException("Failed to track bindless texture constant buffer");
@@ -555,13 +551,12 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const H
}
u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) {
u32 handle{(env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left) & cbuf.mask};
if (cbuf.has_secondary) {
handle |= (env.ReadCbufValue(cbuf.secondary_index, cbuf.secondary_offset)
<< cbuf.secondary_shift_left) &
cbuf.secondary_mask;
}
return handle;
const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left};
const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)
<< cbuf.secondary_shift_left};
return lhs_raw | rhs_raw;
}
[[maybe_unused]] TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
@@ -595,8 +590,7 @@ public:
desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
desc.secondary_shift_left == existing.secondary_shift_left &&
desc.count == existing.count && desc.size_shift == existing.size_shift &&
desc.has_secondary == existing.has_secondary && desc.mask == existing.mask &&
desc.secondary_mask == existing.secondary_mask;
desc.has_secondary == existing.has_secondary;
});
}
@@ -622,12 +616,7 @@ public:
desc.secondary_cbuf_index == existing.secondary_cbuf_index &&
desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
desc.secondary_shift_left == existing.secondary_shift_left &&
desc.count == existing.count && desc.size_shift == existing.size_shift &&
desc.has_index_cbuf == existing.has_index_cbuf &&
desc.index_cbuf_index == existing.index_cbuf_index &&
desc.index_cbuf_offset == existing.index_cbuf_offset &&
desc.index_shift == existing.index_shift && desc.mask == existing.mask &&
desc.secondary_mask == existing.secondary_mask;
desc.count == existing.count && desc.size_shift == existing.size_shift;
})};
// TODO: Read this from TIC
texture_descriptors[index].is_multisample |= desc.is_multisample;
@@ -736,6 +725,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
env.track_cache.clear();
TextureInstVector to_replace;
for (IR::Block* const block : program.post_order_blocks) {
env.track_cache.clear();
for (IR::Inst& inst : block->Instructions()) {
if (!IsTextureInstruction(inst)) {
continue;
@@ -873,12 +863,9 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.secondary_shift_left = cbuf.secondary_shift_left,
.count = count,
.size_shift = size_shift,
.mask = cbuf.mask,
.secondary_mask = cbuf.secondary_mask,
});
} else {
// A uniform index is resolved when drawing, so a single descriptor is enough
count = cbuf.has_index_cbuf ? 1 : std::min(count, sampled_dynamic_cap);
count = std::min(count, sampled_dynamic_cap);
const bool is_integer{is_texture_pixel_format_integer(cbuf)};
index = descriptors.Add(TextureDescriptor{
.type = flags.type,
@@ -894,12 +881,6 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.secondary_shift_left = cbuf.secondary_shift_left,
.count = count,
.size_shift = size_shift,
.has_index_cbuf = cbuf.has_index_cbuf,
.index_cbuf_index = cbuf.index_cbuf_index,
.index_cbuf_offset = cbuf.index_cbuf_offset,
.index_shift = cbuf.index_shift,
.mask = cbuf.mask,
.secondary_mask = cbuf.secondary_mask,
});
flags.is_integer.Assign(is_integer ? 1 : 0);
}
-9
View File
@@ -186,8 +186,6 @@ struct TextureBufferDescriptor {
u32 secondary_shift_left;
u32 count;
u32 size_shift;
u32 mask = 0xffffffff;
u32 secondary_mask = 0xffffffff;
auto operator<=>(const TextureBufferDescriptor&) const = default;
};
@@ -221,13 +219,6 @@ struct TextureDescriptor {
u32 secondary_shift_left;
u32 count;
u32 size_shift;
/// The handle offset is moved by a constant buffer value read when drawing
bool has_index_cbuf;
u32 index_cbuf_index;
u32 index_cbuf_offset;
u32 index_shift;
u32 mask = 0xffffffff;
u32 secondary_mask = 0xffffffff;
auto operator<=>(const TextureDescriptor&) const = default;
};
+2 -7
View File
@@ -948,14 +948,9 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
return alignment > 1 && (offset % alignment) != 0;
}
}();
// Uniform data rewritten by the CPU between draws would need a transfer copy into the cached
// buffer, which ends the current render pass. Read it from the stream buffer instead.
static constexpr u32 MAX_STREAMED_UNIFORM_BYTES = static_cast<u32>(16_KiB);
const bool use_fast_buffer = needs_alignment_stream
|| (has_host_buffer && !memory_tracker.IsRegionGpuModified(device_addr, size)
&& (size <= channel_state->uniform_buffer_skip_cache_size
|| (size <= MAX_STREAMED_UNIFORM_BYTES
&& memory_tracker.IsRegionCpuModified(device_addr, size))));
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size
&& !memory_tracker.IsRegionGpuModified(device_addr, size));
if (use_fast_buffer) {
if constexpr (IS_OPENGL) {
if (runtime.HasFastBufferSubData()) {
@@ -259,23 +259,6 @@ public:
void BindHostComputeBuffers();
/// Calls func(buffer, offset, device_addr, size) for each storage buffer the current compute
/// dispatch writes through a single host buffer
template <typename Func>
void ForEachWrittenComputeStorageBuffer(Func&& func) {
const u32 written = channel_state->enabled_compute_storage_buffers &
channel_state->written_compute_storage_buffers;
ForEachEnabledBit(written, [&](u32 index) {
const Binding& binding = channel_state->compute_storage_buffers[index];
if (binding.buffer_id == NULL_BUFFER_ID || binding.segment_count != 0 ||
binding.size == 0) {
return;
}
Buffer& buffer = slot_buffers[binding.buffer_id];
func(buffer, buffer.Offset(binding.device_addr), binding.device_addr, binding.size);
});
}
void SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
const UniformBufferSizes* sizes);
@@ -83,7 +83,6 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.vert
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp16.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp32.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_block_linear_unswizzle_2d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_quad_indexed.comp
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_turbo_mode.comp
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_uint8.comp
@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 460 core
// Copies a block linear 2D image from a buffer into a tightly packed linear buffer, 4 bytes at a
// time. 16 byte chunks of a row are contiguous in a GOB, so any texel size works as long as the
// row size is a multiple of 4 bytes.
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(std430, set = 0, binding = 0) readonly buffer InputBuffer {
uint swizzled[];
};
layout(std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
uint linear[];
};
layout(push_constant) uniform PushConstants {
uint src_word_offset;
uint row_words;
uint height;
uint layer_stride;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
};
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
void main() {
const uint word = gl_GlobalInvocationID.x;
const uint y = gl_GlobalInvocationID.y;
const uint layer = gl_GlobalInvocationID.z;
if (word >= row_words || y >= height) {
return;
}
const uint x = word * 4;
const uint block_y = y >> GOB_SIZE_Y_SHIFT;
uint offset = layer * layer_stride;
offset += (block_y >> block_height) * block_size;
offset += (block_y & block_height_mask) << GOB_SIZE_SHIFT;
offset += (x >> GOB_SIZE_X_SHIFT) << x_shift;
offset += SwizzleTable((y & 7) * 64 + (x & 63));
linear[(layer * height + y) * row_words + word] = swizzled[src_word_offset + offset / 4];
}
@@ -107,36 +107,22 @@ void ComputePipeline::Configure() {
const auto read_handle{[&](const auto& desc, u32 index) {
ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
const u32 index_offset{index << desc.size_shift};
u32 offset{desc.cbuf_offset + index_offset};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&>) {
if (desc.has_index_cbuf) {
ASSERT(((qmd.const_buffer_enable_mask >> desc.index_cbuf_index) & 1) != 0);
offset += gpu_memory->Read<u32>(cbufs[desc.index_cbuf_index].Address() +
desc.index_cbuf_offset)
<< desc.index_shift;
}
}
const u32 offset{desc.cbuf_offset + index_offset};
const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
if (desc.has_secondary) {
ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
const u32 secondary_offset{desc.secondary_cbuf_offset};
const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
secondary_offset};
const u32 lhs_raw{(gpu_memory->Read<u32>(addr) << desc.shift_left) & desc.mask};
const u32 rhs_raw{(gpu_memory->Read<u32>(separate_addr)
<< desc.secondary_shift_left) &
desc.secondary_mask};
const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
<< desc.secondary_shift_left};
return TexturePair(lhs_raw | rhs_raw, via_header_index);
}
}
u32 raw{gpu_memory->Read<u32>(addr)};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
raw &= desc.mask;
}
return TexturePair(raw, via_header_index);
return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) {
for (u32 index = 0; index < desc.count; ++index) {
@@ -308,37 +308,23 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
const auto read_handle{[&](const auto& desc, u32 index) {
ASSERT(cbufs[desc.cbuf_index].enabled);
const u32 index_offset{index << desc.size_shift};
u32 offset{desc.cbuf_offset + index_offset};
if constexpr (std::is_same_v<decltype(desc), const TextureDescriptor&>) {
if (desc.has_index_cbuf) {
ASSERT(cbufs[desc.index_cbuf_index].enabled);
offset += gpu_memory->Read<u32>(cbufs[desc.index_cbuf_index].address +
desc.index_cbuf_offset)
<< desc.index_shift;
}
}
const u32 offset{desc.cbuf_offset + index_offset};
const GPUVAddr addr{cbufs[desc.cbuf_index].address + offset};
if constexpr (std::is_same_v<decltype(desc), const TextureDescriptor&> ||
std::is_same_v<decltype(desc), const TextureBufferDescriptor&>) {
if (desc.has_secondary) {
ASSERT(cbufs[desc.secondary_cbuf_index].enabled);
const u32 second_offset{desc.secondary_cbuf_offset};
const u32 second_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
second_offset};
const u32 lhs_raw{(gpu_memory->Read<u32>(addr) << desc.shift_left) & desc.mask};
const u32 rhs_raw{(gpu_memory->Read<u32>(separate_addr)
<< desc.secondary_shift_left) &
desc.secondary_mask};
const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
<< desc.secondary_shift_left};
const u32 raw{lhs_raw | rhs_raw};
return TexturePair(raw, via_header_index);
}
}
u32 raw{gpu_memory->Read<u32>(addr)};
if constexpr (std::is_same_v<decltype(desc), const TextureDescriptor&> ||
std::is_same_v<decltype(desc), const TextureBufferDescriptor&>) {
raw &= desc.mask;
}
return TexturePair(raw, via_header_index);
return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
for (u32 index = 0; index < desc.count; ++index) {
@@ -129,13 +129,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
topology.Assign(can_collapse_topology_class
? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast<size_t>(topology_)]
: topology_);
// Target-independent rasterization rasterizes (and depth tests) at the raster sample count
if (regs.tir_mode == Maxwell::TIRMode::RasterNTargetM &&
regs.anti_alias_raster != Maxwell::AntiAliasRaster::Mode1x1) {
msaa_mode.Assign(static_cast<Tegra::Texture::MsaaMode>(regs.anti_alias_raster));
} else {
msaa_mode.Assign(regs.anti_alias_samples_mode);
}
msaa_mode.Assign(regs.anti_alias_samples_mode);
attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0);
raw2 = 0;
@@ -20,7 +20,6 @@
#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h"
#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h"
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
#include "video_core/host_shaders/vulkan_block_linear_unswizzle_2d_buffer_comp_spv.h"
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
@@ -32,7 +31,6 @@
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/texture_cache/accelerated_swizzle.h"
#include "video_core/texture_cache/types.h"
#include "video_core/texture_cache/util.h"
#include "video_core/textures/decoders.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -642,143 +640,6 @@ struct alignas(16) BlockLinearUnswizzle3DPushConstants {
};
static_assert(sizeof(BlockLinearUnswizzle3DPushConstants) <= 128);
struct BlockLinearUnswizzle2DBufferPushConstants {
u32 src_word_offset;
u32 row_words;
u32 height;
u32 layer_stride;
u32 block_size;
u32 x_shift;
u32 block_height;
u32 block_height_mask;
};
BlockLinearUnswizzle2DBufferPass::BlockLinearUnswizzle2DBufferPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle2DBufferPushConstants)>,
VULKAN_BLOCK_LINEAR_UNSWIZZLE_2D_BUFFER_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzle2DBufferPass::~BlockLinearUnswizzle2DBufferPass() = default;
bool BlockLinearUnswizzle2DBufferPass::IsSupported(const Image& image) {
const VideoCommon::ImageInfo& info = image.info;
if (info.type != VideoCommon::ImageType::e2D || info.num_samples != 1 ||
VideoCore::Surface::GetFormatType(info.format) != VideoCore::Surface::SurfaceType::ColorTexture ||
VideoCore::Surface::DefaultBlockWidth(info.format) != 1 ||
VideoCore::Surface::DefaultBlockHeight(info.format) != 1 ||
True(image.flags & VideoCommon::ImageFlagBits::Converted) ||
True(image.flags & VideoCommon::ImageFlagBits::Rescaled)) {
return false;
}
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
for (const VideoCommon::SwizzleParameters& swizzle : VideoCommon::FullUploadSwizzles(info)) {
if ((swizzle.num_tiles.width * bytes_per_block) % 4 != 0 || swizzle.num_tiles.depth != 1) {
return false;
}
}
return true;
}
std::pair<VkBuffer, boost::container::small_vector<VkBufferImageCopy, 16>>
BlockLinearUnswizzle2DBufferPass::Unswizzle(Image& image, VkBuffer src_buffer,
VkDeviceSize src_offset) {
const VideoCommon::ImageInfo& info = image.info;
const auto swizzles = VideoCommon::FullUploadSwizzles(info);
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
const u32 layers = static_cast<u32>(info.resources.layers);
const VkDeviceSize alignment = (std::max<VkDeviceSize>)(device.GetStorageBufferAlignment(), 4);
VkDeviceSize total_size = 0;
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
total_size = Common::AlignUp(total_size, alignment);
total_size += VkDeviceSize{swizzle.num_tiles.width} * bytes_per_block *
swizzle.num_tiles.height * layers;
}
const StagingBufferRef linear = staging_buffer_pool.Request(total_size, MemoryUsage::DeviceLocal);
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([](vk::CommandBuffer cmdbuf) {
static constexpr VkMemoryBarrier READ_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, READ_BARRIER);
});
boost::container::small_vector<VkBufferImageCopy, 16> copies;
VkDeviceSize linear_offset = 0;
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
linear_offset = Common::AlignUp(linear_offset, alignment);
const auto params = VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(swizzle, info);
const u32 row_words = swizzle.num_tiles.width * bytes_per_block / 4;
const u32 height = swizzle.num_tiles.height;
const VkDeviceSize level_size = VkDeviceSize{row_words} * 4 * height * layers;
const VkDeviceSize level_src = src_offset + swizzle.buffer_offset;
const VkDeviceSize aligned_src = level_src & ~(alignment - 1);
const VkDeviceSize src_size = image.guest_size_bytes - swizzle.buffer_offset +
(level_src - aligned_src);
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(src_buffer, aligned_src, src_size);
compute_pass_descriptor_queue.AddBuffer(linear.buffer, linear.offset + linear_offset,
level_size);
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
const BlockLinearUnswizzle2DBufferPushConstants push{
.src_word_offset = static_cast<u32>((level_src - aligned_src) / 4),
.row_words = row_words,
.height = height,
.layer_stride = params.layer_stride,
.block_size = params.block_size,
.x_shift = params.x_shift,
.block_height = params.block_height,
.block_height_mask = params.block_height_mask,
};
scheduler.Record([this, descriptor_data, push, layers](vk::CommandBuffer cmdbuf) {
static constexpr u32 WORKGROUP_SIZE_X = 64;
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, push);
cmdbuf.Dispatch(Common::DivCeil(push.row_words, WORKGROUP_SIZE_X), push.height, layers);
});
copies.push_back(VkBufferImageCopy{
.bufferOffset = linear.offset + linear_offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = static_cast<u32>(swizzle.level),
.baseArrayLayer = 0,
.layerCount = layers,
},
.imageOffset{0, 0, 0},
.imageExtent{swizzle.num_tiles.width, height, 1},
});
linear_offset += level_size;
}
scheduler.Record([](vk::CommandBuffer cmdbuf) {
static constexpr VkMemoryBarrier WRITE_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, WRITE_BARRIER);
});
return {linear.buffer, std::move(copies)};
}
BlockLinearUnswizzle3DPass::BlockLinearUnswizzle3DPass(
const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
@@ -10,8 +10,6 @@
#include <span>
#include <utility>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
@@ -139,28 +137,6 @@ private:
MemoryAllocator& memory_allocator;
};
class BlockLinearUnswizzle2DBufferPass final : public ComputePass {
public:
explicit BlockLinearUnswizzle2DBufferPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzle2DBufferPass();
/// Returns true when the guest layout of the image can be unswizzled by this pass
[[nodiscard]] static bool IsSupported(const Image& image);
/// Unswizzles the block linear guest data of the image, read from a GPU buffer, into a linear
/// buffer. Returns the linear buffer and the copies that upload it into the image.
std::pair<VkBuffer, boost::container::small_vector<VkBufferImageCopy, 16>> Unswizzle(
Image& image, VkBuffer src_buffer, VkDeviceSize src_offset);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearUnswizzle3DPass final : public ComputePass {
public:
explicit BlockLinearUnswizzle3DPass(const Device& device_, Scheduler& scheduler_,
@@ -174,36 +174,21 @@ bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
const auto read_handle{[&](const auto& desc, u32 index) {
ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
const u32 index_offset{index << desc.size_shift};
u32 offset{desc.cbuf_offset + index_offset};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&>) {
if (desc.has_index_cbuf) {
ASSERT(((qmd.const_buffer_enable_mask >> desc.index_cbuf_index) & 1) != 0);
offset += gpu_memory.Read<u32>(cbufs[desc.index_cbuf_index].Address() +
desc.index_cbuf_offset)
<< desc.index_shift;
}
}
const u32 offset{desc.cbuf_offset + index_offset};
const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
if (desc.has_secondary) {
ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
const u32 secondary_offset{desc.secondary_cbuf_offset};
const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
secondary_offset};
const u32 lhs_raw{(gpu_memory.Read<u32>(addr) << desc.shift_left) & desc.mask};
const u32 rhs_raw{(gpu_memory.Read<u32>(separate_addr)
<< desc.secondary_shift_left) &
desc.secondary_mask};
const u32 lhs_raw{gpu_memory.Read<u32>(addr) << desc.shift_left};
const u32 rhs_raw{gpu_memory.Read<u32>(separate_addr) << desc.secondary_shift_left};
return TexturePair(lhs_raw | rhs_raw, via_header_index);
}
}
u32 raw{gpu_memory.Read<u32>(addr)};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
raw &= desc.mask;
}
return TexturePair(raw, via_header_index);
return TexturePair(gpu_memory.Read<u32>(addr), via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) {
for (u32 index = 0; index < desc.count; ++index) {
@@ -377,37 +377,23 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
const auto read_handle{[&](const auto& desc, u32 index) {
ASSERT(cbufs[desc.cbuf_index].enabled);
const u32 index_offset{index << desc.size_shift};
u32 offset{desc.cbuf_offset + index_offset};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&>) {
if (desc.has_index_cbuf) {
ASSERT(cbufs[desc.index_cbuf_index].enabled);
offset += gpu_memory->Read<u32>(cbufs[desc.index_cbuf_index].address +
desc.index_cbuf_offset)
<< desc.index_shift;
}
}
const u32 offset{desc.cbuf_offset + index_offset};
const GPUVAddr addr{cbufs[desc.cbuf_index].address + offset};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
if (desc.has_secondary) {
ASSERT(cbufs[desc.secondary_cbuf_index].enabled);
const u32 second_offset{desc.secondary_cbuf_offset};
const u32 second_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
second_offset};
const u32 lhs_raw{(gpu_memory->Read<u32>(addr) << desc.shift_left) & desc.mask};
const u32 rhs_raw{(gpu_memory->Read<u32>(separate_addr)
<< desc.secondary_shift_left) &
desc.secondary_mask};
const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
<< desc.secondary_shift_left};
const u32 raw{lhs_raw | rhs_raw};
return TexturePair(raw, via_header_index);
}
}
u32 raw{gpu_memory->Read<u32>(addr)};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
raw &= desc.mask;
}
return TexturePair(raw, via_header_index);
return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
for (u32 index = 0; index < desc.count; ++index) {
@@ -596,12 +596,6 @@ void RasterizerVulkan::DispatchCompute() {
return;
}
const auto update_images_written_by_buffers = [this] {
buffer_cache.ForEachWrittenComputeStorageBuffer(
[this](auto& buffer, u32 offset, DAddr device_addr, u32 size) {
texture_cache.UpdateImagesFromBuffer(device_addr, size, buffer.Handle(), offset);
});
};
const auto& qmd{kepler_compute->launch_description};
auto indirect_address = kepler_compute->GetIndirectComputeAddress();
if (indirect_address) {
@@ -618,7 +612,6 @@ void RasterizerVulkan::DispatchCompute() {
}
cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset);
});
update_images_written_by_buffers();
return;
}
const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
@@ -641,7 +634,6 @@ void RasterizerVulkan::DispatchCompute() {
}
cmdbuf.Dispatch(dim[0], dim[1], dim[2]);
});
update_images_written_by_buffers();
// Log compute dispatch
if (GPU::Logging::IsActive() &&
@@ -946,8 +946,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue, memory_allocator);
}
bl2d_buffer_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue);
if (!device.IsKhrImageFormatListSupported()) {
return;
}
@@ -1091,58 +1089,6 @@ VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, Vk
return *shadow.view;
}
VkImageView TextureCacheRuntime::GetOrCreateTirColorScratch(VkImage color_image, VkFormat format,
VkExtent2D extent, u32 layers,
VkSampleCountFlagBits samples) {
TirColorScratch& scratch = tir_color_scratch[color_image];
if (scratch.image && scratch.format == format && scratch.extent.width == extent.width &&
scratch.extent.height == extent.height && scratch.layers == layers &&
scratch.samples == samples) {
return *scratch.view;
}
if (scratch.image) {
scheduler.Finish();
}
scratch.image = memory_allocator.CreateImage(VkImageCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {extent.width, extent.height, 1},
.mipLevels = 1,
.arrayLayers = layers,
.samples = samples,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
});
scratch.view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = *scratch.image,
.viewType = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D,
.format = format,
.components{},
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = layers,
},
});
scratch.format = format;
scratch.extent = extent;
scratch.layers = layers;
scratch.samples = samples;
return *scratch.view;
}
const TextureCacheRuntime::ResolveShadow* TextureCacheRuntime::GetValidResolveShadow(
VkImage msaa_image) const {
const auto it = resolve_shadows.find(msaa_image);
@@ -2122,27 +2068,6 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
}
}
bool Image::UploadSwizzledFromBuffer(VkBuffer buffer, VkDeviceSize offset) {
if (runtime == nullptr || !runtime->bl2d_buffer_unswizzle_pass || offset % 4 != 0 ||
guest_size_bytes > runtime->device.GetMaxStorageBufferRange() ||
!BlockLinearUnswizzle2DBufferPass::IsSupported(*this)) {
return false;
}
if (ENABLE_MSAA_RESOLVE_CONSUME) {
runtime->InvalidateResolveShadow(Handle());
}
auto [linear_buffer, copies] =
runtime->bl2d_buffer_unswizzle_pass->Unswizzle(*this, buffer, offset);
const VkImage vk_image = *original_image;
const VkImageAspectFlags vk_aspect_mask = aspect_mask;
const bool was_initialized = std::exchange(initialized, true);
scheduler->Record([linear_buffer, vk_image, vk_aspect_mask, was_initialized,
copies = std::move(copies)](vk::CommandBuffer cmdbuf) {
CopyBufferToImage(cmdbuf, linear_buffer, vk_image, vk_aspect_mask, was_initialized, copies);
});
return true;
}
void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
UploadMemory(map.buffer, map.offset, copies);
}
@@ -3045,26 +2970,6 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, ImageView* color_buffer,
Framebuffer::~Framebuffer() = default;
/// Single-sampled color targets with a multisampled depth buffer, as used by target-independent
/// rasterization. Vulkan requires matching sample counts for these attachments.
static bool IsMixedSamplesTarget(std::span<ImageView*, NUM_RT> color_buffers,
const ImageView* depth_buffer) {
if (depth_buffer == nullptr || depth_buffer->Samples() == VK_SAMPLE_COUNT_1_BIT) {
return false;
}
bool has_color = false;
for (const ImageView* const color_buffer : color_buffers) {
if (color_buffer == nullptr) {
continue;
}
if (color_buffer->Samples() != VK_SAMPLE_COUNT_1_BIT) {
return false;
}
has_color = true;
}
return has_color;
}
void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
std::span<ImageView*, NUM_RT> color_buffers,
ImageView* depth_buffer, bool is_rescaled_) {
@@ -3119,13 +3024,6 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
} else {
renderpass_key.depth_format = PixelFormat::Invalid;
}
if (IsMixedSamplesTarget(color_buffers, depth_buffer)) {
const VkExtent2D extent{(std::min)(render_area.width, width),
(std::min)(render_area.height, height)};
CreateMixedSamplesFramebuffer(runtime, color_buffers, attachments, renderpass_key,
static_cast<u32>((std::max)(num_layers, 1)), extent);
return;
}
renderpass_key.samples = samples;
const bool do_resolve_color = ENABLE_MSAA_TILER_RESOLVE &&
samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0 && runtime.device.IsTiler();
@@ -3142,7 +3040,10 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
discard_msaa_depth_stencil = ENABLE_MSAA_RESOLVE_CONSUME &&
ENABLE_MSAA_DEPTH_STENCIL_DISCARD && do_resolve_depth_stencil;
UseRenderPass(runtime, renderpass_key);
renderpass = runtime.render_pass_cache.Get(renderpass_key);
render_pass_key = renderpass_key;
render_pass_cache = &runtime.render_pass_cache;
runtime_ptr = &runtime;
render_area.width = (std::min)(render_area.width, width);
render_area.height = (std::min)(render_area.height, height);
@@ -3174,53 +3075,6 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
}
num_color_buffers = static_cast<u32>(num_colors);
CreateVkFramebuffer(runtime, attachments, static_cast<u32>((std::max)(num_layers, 1)));
}
void Framebuffer::CreateMixedSamplesFramebuffer(TextureCacheRuntime& runtime,
std::span<ImageView*, NUM_RT> color_buffers,
std::span<const VkImageView> attachments,
RenderPassKey renderpass_key, u32 layers,
VkExtent2D extent) {
// attachments holds the real color targets followed by the depth buffer
const size_t num_colors = attachments.size() - 1;
boost::container::small_vector<VkImageView, NUM_RT * 2 + 2> mixed_attachments;
for (const ImageView* const color_buffer : color_buffers) {
if (!color_buffer) {
continue;
}
const VkFormat vk_format = MaxwellToVK::SurfaceFormat(runtime.device, FormatType::Optimal,
true, color_buffer->format)
.format;
mixed_attachments.push_back(runtime.GetOrCreateTirColorScratch(
color_buffer->ImageHandle(), vk_format, extent, layers, samples));
}
mixed_attachments.push_back(attachments[num_colors]);
for (size_t index = 0; index < num_colors; ++index) {
mixed_attachments.push_back(attachments[index]);
}
// Multisampled scratch color + depth, resolving color into the real single-sampled targets
renderpass_key.samples = samples;
renderpass_key.resolve_color = true;
renderpass_key.resolve_depth_stencil = false;
discard_msaa_color = false;
discard_msaa_depth_stencil = false;
UseRenderPass(runtime, renderpass_key);
render_area = extent;
num_color_buffers = static_cast<u32>(num_colors);
CreateVkFramebuffer(runtime, mixed_attachments, layers);
}
void Framebuffer::UseRenderPass(TextureCacheRuntime& runtime,
const RenderPassKey& renderpass_key) {
renderpass = runtime.render_pass_cache.Get(renderpass_key);
render_pass_key = renderpass_key;
render_pass_cache = &runtime.render_pass_cache;
runtime_ptr = &runtime;
}
void Framebuffer::CreateVkFramebuffer(TextureCacheRuntime& runtime,
std::span<const VkImageView> attachments, u32 layers) {
framebuffer = runtime.device.GetLogical().CreateFramebuffer({
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.pNext = nullptr,
@@ -3230,7 +3084,7 @@ void Framebuffer::CreateVkFramebuffer(TextureCacheRuntime& runtime,
.pAttachments = attachments.data(),
.width = render_area.width,
.height = render_area.height,
.layers = layers,
.layers = static_cast<u32>((std::max)(num_layers, 1)),
});
}
@@ -133,11 +133,6 @@ public:
[[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const;
/// Multisampled stand-in for a single-sampled color target rendered with a TIR depth buffer
[[nodiscard]] VkImageView GetOrCreateTirColorScratch(VkImage color_image, VkFormat format,
VkExtent2D extent, u32 layers,
VkSampleCountFlagBits samples);
void InvalidateResolveShadow(VkImage msaa_image);
void MarkResolveShadowUpToDate(VkImage msaa_image);
@@ -164,7 +159,6 @@ public:
std::optional<ASTCDecoderPass> astc_decoder_pass;
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
std::optional<BlockLinearUnswizzle2DBufferPass> bl2d_buffer_unswizzle_pass;
const Settings::ResolutionScalingInfo& resolution;
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
@@ -193,15 +187,6 @@ public:
std::vector<MsaaScratchImage> msaa_scratch_images;
::Common::unordered_map<VkImage, ResolveShadow> resolve_shadows;
struct TirColorScratch {
vk::Image image;
vk::ImageView view;
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent{};
u32 layers = 0;
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
};
::Common::unordered_map<VkImage, TirColorScratch> tir_color_scratch;
std::vector<std::pair<u64, ResolveShadow>> pending_resolve_shadows;
};
@@ -294,22 +279,6 @@ public:
void MarkResolveShadowsUpToDate() const;
private:
/// Builds a framebuffer for single-sampled color targets drawn with a multisampled depth
/// buffer (target-independent rasterization). Color is rendered into multisampled scratch
/// images that resolve into the real targets at the end of the pass.
void CreateMixedSamplesFramebuffer(TextureCacheRuntime& runtime,
std::span<ImageView*, NUM_RT> color_buffers,
std::span<const VkImageView> attachments,
RenderPassKey renderpass_key, u32 layers,
VkExtent2D extent);
/// Fetches the render pass for the key from the cache and remembers it for variants.
void UseRenderPass(TextureCacheRuntime& runtime, const RenderPassKey& renderpass_key);
/// Creates the Vulkan framebuffer for the current render pass and render area.
void CreateVkFramebuffer(TextureCacheRuntime& runtime,
std::span<const VkImageView> attachments, u32 layers);
static constexpr size_t NUM_MEMOIZED_RENDER_PASS_VARIANTS = 8;
vk::Framebuffer framebuffer;
@@ -356,10 +325,6 @@ public:
void UploadMemory(const StagingBufferRef& map,
std::span<const VideoCommon::BufferImageCopy> copies);
/// Replaces the image contents with its block linear guest data held in a GPU buffer.
/// Returns false when the image layout is not supported.
bool UploadSwizzledFromBuffer(VkBuffer buffer, VkDeviceSize offset);
void DownloadMemory(VkBuffer buffer, size_t offset,
std::span<const VideoCommon::BufferImageCopy> copies);
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -9,6 +6,4 @@
namespace VideoCommon {
template class VideoCommon::TextureCache<Vulkan::TextureCacheParams>;
template void VideoCommon::TextureCache<Vulkan::TextureCacheParams>::UpdateImagesFromBuffer<VkBuffer>(
DAddr, size_t, VkBuffer, u64);
}
+1 -50
View File
@@ -595,36 +595,6 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
});
}
template <class P>
template <typename BufferHandle>
void TextureCache<P>::UpdateImagesFromBuffer(DAddr cpu_addr, size_t size, BufferHandle buffer,
u64 buffer_offset) {
if constexpr (requires(Image& image) { image.UploadSwizzledFromBuffer(buffer, buffer_offset); }) {
// The guest can write a render target through a storage buffer bound over its memory
// (e.g. a compute clear). Only images the binding matches exactly are replaced, a larger
// binding may hold unrelated live images in the parts the shader does not write.
static constexpr size_t MAX_BINDING_PADDING = 64 * 1024;
ForEachImageInRegion(cpu_addr, size, [&](ImageId image_id, Image& image) {
if (image.cpu_addr != cpu_addr || size < image.guest_size_bytes ||
size - image.guest_size_bytes > MAX_BINDING_PADDING ||
True(image.flags & ImageFlagBits::Remapped) ||
True(image.flags & ImageFlagBits::Sparse)) {
return;
}
if (!image.UploadSwizzledFromBuffer(buffer, buffer_offset)) {
return;
}
if (True(image.flags & ImageFlagBits::CpuModified)) {
image.flags &= ~ImageFlagBits::CpuModified;
if (False(image.flags & ImageFlagBits::Tracked)) {
TrackImage(image, image_id);
}
}
MarkModification(image);
});
}
}
template <class P>
bool TextureCache<P>::IsDownloadable(const ImageBase& image) const noexcept {
if (!image.IsSafeGpuCopy()) {
@@ -1958,26 +1928,7 @@ ImageViewId TextureCache<P>::FindDepthBuffer() {
if (gpu_addr == 0) {
return ImageViewId{};
}
// Target-independent rasterization: the depth buffer keeps the raster sample count while
// the color targets use anti_alias_samples_mode.
Tegra::Texture::MsaaMode zeta_msaa_mode = regs.anti_alias_samples_mode;
if (regs.anti_alias_raster != Tegra::Engines::Maxwell3D::Regs::AntiAliasRaster::Mode1x1) {
const auto raster_mode = static_cast<Tegra::Texture::MsaaMode>(regs.anti_alias_raster);
bool use_raster = regs.tir_mode == Tegra::Engines::Maxwell3D::Regs::TIRMode::RasterNTargetM;
if (!use_raster && regs.anti_alias_samples_mode == Tegra::Texture::MsaaMode::Msaa1x1) {
// Clears can be issued before tir_mode is enabled. Only a depth buffer sized for the
// raster samples relative to the color target is treated as multisampled, otherwise
// it ping-pongs between a 1x and a multisampled image every frame.
const auto& rt0 = regs.rt[0];
use_raster = rt0.Address() != 0 && rt0.format != Tegra::RenderTargetFormat::NONE &&
regs.zeta_size.width == rt0.width * NumSamplesX(raster_mode) &&
regs.zeta_size.height == rt0.height * NumSamplesY(raster_mode);
}
if (use_raster) {
zeta_msaa_mode = raster_mode;
}
}
const ImageInfo info(regs.zeta, regs.zeta_size, zeta_msaa_mode);
const ImageInfo info(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode);
return FindRenderTargetView(info, gpu_addr);
}
@@ -203,12 +203,6 @@ public:
/// Mark images in a range as modified from the CPU
void WriteMemory(DAddr cpu_addr, size_t size);
/// Replaces the contents of the images fully inside a range that a GPU buffer wrote, with the
/// data of that buffer
template <typename BufferHandle>
void UpdateImagesFromBuffer(DAddr cpu_addr, size_t size, BufferHandle buffer,
u64 buffer_offset);
/// Download contents of host images to guest memory in a region
void DownloadMemory(DAddr cpu_addr, size_t size);