1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include "common/assert.h"
#include "common/logging.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/sw_blitter/blitter.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/surface.h"
#include "video_core/textures/decoders.h"
using VideoCore::Surface::BytesPerBlock;
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
namespace Tegra::Engines {
using namespace Texture;
Fermi2D::Fermi2D(MemoryManager& memory_manager_) : memory_manager{memory_manager_} {
sw_blitter = std::make_unique<Blitter::SoftwareBlitEngine>(memory_manager);
// Nvidia's OpenGL driver seems to assume these values
regs.src.depth = 1;
regs.dst.depth = 1;
execution_mask.reset();
execution_mask[FERMI2D_REG_INDEX(pixels_from_memory.src_y0) + 1] = true;
}
Fermi2D::~Fermi2D() = default;
void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
rasterizer = rasterizer_;
}
void Fermi2D::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Fermi2D register, increase the size of the Regs structure");
regs.reg_array[method] = method_argument;
if (method == FERMI2D_REG_INDEX(pixels_from_memory.src_y0) + 1) {
Blit();
}
}
void Fermi2D::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
for (u32 i = 0; i < amount; ++i) {
CallMethod(system, method, base_start[i], methods_pending - i <= 1);
}
}
void Fermi2D::ConsumeSinkImpl(Core::System& system) {
for (auto [method, value] : method_sink) {
regs.reg_array[method] = value;
}
method_sink.clear();
}
void Fermi2D::Blit() {
if (static const u64 trace_destination = [] {
const char* value = std::getenv("EDEN_NX2_TRACE_DST");
return value != nullptr ? std::strtoull(value, nullptr, 16) : 0ULL;
}();
trace_destination != 0 && regs.dst.Address() >= trace_destination &&
regs.dst.Address() < trace_destination + (32ULL << 20)) {
static u32 traced_destination{};
if (traced_destination++ < 40) {
LOG_INFO(HW_GPU, "nx2 2D blit to traced dst: {:#x} {}x{} -> {:#x} {}x{}",
regs.src.Address(), regs.src.width, regs.src.height, regs.dst.Address(),
regs.dst.width, regs.dst.height);
}
}
if (static const bool trace = std::getenv("EDEN_NX2_RT_TRACE") != nullptr; trace) {
static u32 traced{};
if (traced++ < 64) {
LOG_INFO(HW_GPU, "nx2 2D blit {:#x} {}x{} -> {:#x} {}x{}", regs.src.Address(),
regs.src.width, regs.src.height, regs.dst.Address(), regs.dst.width,
regs.dst.height);
}
}
LOG_DEBUG(HW_GPU, "called. source address={:#x}, destination address={:#x}", regs.src.Address(), regs.dst.Address());
UNIMPLEMENTED_IF_MSG(regs.operation != Operation::SrcCopy, "Operation is not copy");
UNIMPLEMENTED_IF_MSG(regs.src.layer != 0, "Source layer is not zero");
UNIMPLEMENTED_IF_MSG(regs.dst.layer != 0, "Destination layer is not zero");
UNIMPLEMENTED_IF_MSG(regs.src.depth != 1, "Source depth is not one");
UNIMPLEMENTED_IF_MSG(regs.clip_enable != 0, "Clipped blit enabled");
const auto& args = regs.pixels_from_memory;
constexpr s64 null_derivative = 1ULL << 32;
Surface src = regs.src;
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const bool delegate_to_gpu = src.width > 512 && src.height > 512 && bytes_per_pixel <= 8 &&
src.format != regs.dst.format;
auto srcX = args.src_x0;
auto srcY = args.src_y0;
if (args.sample_mode.origin == Origin::Corner) {
srcX -= (args.du_dx >> 33) << 32;
srcY -= (args.dv_dy >> 33) << 32;
}
Config config{
.operation = regs.operation,
.filter = args.sample_mode.filter,
.must_accelerate =
args.du_dx != null_derivative || args.dv_dy != null_derivative || delegate_to_gpu,
.dst_x0 = args.dst_x0,
.dst_y0 = args.dst_y0,
.dst_x1 = args.dst_x0 + args.dst_width,
.dst_y1 = args.dst_y0 + args.dst_height,
.src_x0 = static_cast<s32>(srcX >> 32),
.src_y0 = static_cast<s32>(srcY >> 32),
.src_x1 = static_cast<s32>((srcX + args.du_dx * args.dst_width) >> 32),
.src_y1 = static_cast<s32>((srcY + args.dv_dy * args.dst_height) >> 32),
};
const auto need_align_to_pitch =
src.linear == Tegra::Engines::Fermi2D::MemoryLayout::Pitch &&
static_cast<s32>(src.width) == config.src_x1 &&
config.src_x1 > static_cast<s32>(src.pitch / bytes_per_pixel) && config.src_x0 > 0;
if (need_align_to_pitch) {
auto address = src.Address() + config.src_x0 * bytes_per_pixel;
src.addr_upper = static_cast<u32>(address >> 32);
src.addr_lower = static_cast<u32>(address);
src.width -= config.src_x0;
config.src_x1 -= config.src_x0;
config.src_x0 = 0;
}
memory_manager.FlushCaching();
if (!rasterizer->AccelerateSurfaceCopy(src, regs.dst, config)) {
sw_blitter->Blit(src, regs.dst, config);
}
}
} // namespace Tegra::Engines
|