|
|
|
|
@@ -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
|
|
|
|
|
@@ -485,8 +485,8 @@ void Vic::Blend(const ConfigStruct& config, const SlotStruct& slot, VideoPixelFo
|
|
|
|
|
source_bottom = (std::min)(source_bottom, out_surface_height);
|
|
|
|
|
source_right = (std::min)(source_right, out_surface_width);
|
|
|
|
|
|
|
|
|
|
auto const work_width = u32((std::max)(0, s32(source_right) - s32(source_left)));
|
|
|
|
|
auto const work_height = u32((std::max)(0, s32(source_bottom) - s32(source_top)));
|
|
|
|
|
[[maybe_unused]] auto const work_width = u32((std::max)(0, s32(source_right) - s32(source_left)));
|
|
|
|
|
[[maybe_unused]] auto const work_height = u32((std::max)(0, s32(source_bottom) - s32(source_top)));
|
|
|
|
|
|
|
|
|
|
// TODO Alpha blending. No games I've seen use more than a single surface or supply an alpha
|
|
|
|
|
// below max, so it's ignored for now.
|
|
|
|
|
@@ -630,42 +630,49 @@ void Vic::Blend(const ConfigStruct& config, const SlotStruct& slot, VideoPixelFo
|
|
|
|
|
// | r1c0 r1c1 r1c2 r1c3 | * | G | = | G |
|
|
|
|
|
// | r2c0 r2c1 r2c2 r2c3 | | B | | B |
|
|
|
|
|
// | 1 |
|
|
|
|
|
auto const shift = s32(slot.color_matrix.matrix_r_shift.Value());
|
|
|
|
|
|
|
|
|
|
struct AliasedMatrixType { u64 m[4]; };
|
|
|
|
|
static_assert(sizeof(AliasedMatrixType) == sizeof(slot.color_matrix));
|
|
|
|
|
u64 const mat_mask = (1 << 20) - 1;
|
|
|
|
|
auto const* amt = reinterpret_cast<AliasedMatrixType const*>(&slot.color_matrix);
|
|
|
|
|
|
|
|
|
|
constexpr s32 shifts[4] = { 0, 20, 40, 60 };
|
|
|
|
|
s32 mr[4][4];
|
|
|
|
|
for (u32 j = 0; j < 3; ++j)
|
|
|
|
|
for (u32 i = 0; i < 4; ++i)
|
|
|
|
|
mr[j][i] = s32(s64(((amt->m[i] >> shifts[j]) & mat_mask) << (64 - 20)) >> (64 - 20));
|
|
|
|
|
|
|
|
|
|
auto const clamp_min = s32(slot.config.soft_clamp_low.Value());
|
|
|
|
|
auto const clamp_max = s32(slot.config.soft_clamp_high.Value());
|
|
|
|
|
for (u32 y = 0; y < work_height; ++y) {
|
|
|
|
|
auto const src = (y + source_top) * in_surface_width + source_left;
|
|
|
|
|
auto const dst = (y + source_top) * out_surface_width + rect_left;
|
|
|
|
|
for (u32 x = 0; x < work_width; ++x) {
|
|
|
|
|
auto const& in_pixel = slot_surface[src + x];
|
|
|
|
|
auto& out_pixel = output_surface[dst + x];
|
|
|
|
|
s32 const mul_values[4] = {
|
|
|
|
|
in_pixel.r * mr[0][0] + in_pixel.g * mr[1][1] + in_pixel.b * mr[0][2],
|
|
|
|
|
in_pixel.r * mr[1][0] + in_pixel.g * mr[1][1] + in_pixel.b * mr[1][2],
|
|
|
|
|
in_pixel.r * mr[2][0] + in_pixel.g * mr[2][1] + in_pixel.b * mr[2][2],
|
|
|
|
|
s32(in_pixel.a)
|
|
|
|
|
};
|
|
|
|
|
s32 const mul_clamp[4] = {
|
|
|
|
|
std::clamp(((mul_values[0] >> shift) + mr[0][3]) >> 8, clamp_min, clamp_max),
|
|
|
|
|
std::clamp(((mul_values[1] >> shift) + mr[1][3]) >> 8, clamp_min, clamp_max),
|
|
|
|
|
std::clamp(((mul_values[2] >> shift) + mr[2][3]) >> 8, clamp_min, clamp_max),
|
|
|
|
|
std::clamp(mul_values[3], clamp_min, clamp_max)
|
|
|
|
|
};
|
|
|
|
|
out_pixel = format == VideoPixelFormat::A8R8G8B8
|
|
|
|
|
? Pixel(u16(mul_clamp[2]), u16(mul_clamp[1]), u16(mul_clamp[0]), u16(mul_clamp[3]))
|
|
|
|
|
: Pixel(u16(mul_clamp[0]), u16(mul_clamp[1]), u16(mul_clamp[2]), u16(mul_clamp[3]));
|
|
|
|
|
const auto r0c0 = s32(slot.color_matrix.matrix_coeff00.Value());
|
|
|
|
|
const auto r0c1 = s32(slot.color_matrix.matrix_coeff01.Value());
|
|
|
|
|
const auto r0c2 = s32(slot.color_matrix.matrix_coeff02.Value());
|
|
|
|
|
const auto r0c3 = s32(slot.color_matrix.matrix_coeff03.Value());
|
|
|
|
|
const auto r1c0 = s32(slot.color_matrix.matrix_coeff10.Value());
|
|
|
|
|
const auto r1c1 = s32(slot.color_matrix.matrix_coeff11.Value());
|
|
|
|
|
const auto r1c2 = s32(slot.color_matrix.matrix_coeff12.Value());
|
|
|
|
|
const auto r1c3 = s32(slot.color_matrix.matrix_coeff13.Value());
|
|
|
|
|
const auto r2c0 = s32(slot.color_matrix.matrix_coeff20.Value());
|
|
|
|
|
const auto r2c1 = s32(slot.color_matrix.matrix_coeff21.Value());
|
|
|
|
|
const auto r2c2 = s32(slot.color_matrix.matrix_coeff22.Value());
|
|
|
|
|
const auto r2c3 = s32(slot.color_matrix.matrix_coeff23.Value());
|
|
|
|
|
const auto shift = s32(slot.color_matrix.matrix_r_shift.Value());
|
|
|
|
|
const auto clamp_min = s32(slot.config.soft_clamp_low.Value());
|
|
|
|
|
const auto clamp_max = s32(slot.config.soft_clamp_high.Value());
|
|
|
|
|
auto MatMul = [&](const Pixel& in_pixel) -> std::tuple<s32, s32, s32, s32> {
|
|
|
|
|
auto r = s32(in_pixel.r);
|
|
|
|
|
auto g = s32(in_pixel.g);
|
|
|
|
|
auto b = s32(in_pixel.b);
|
|
|
|
|
r = in_pixel.r * r0c0 + in_pixel.g * r0c1 + in_pixel.b * r0c2;
|
|
|
|
|
g = in_pixel.r * r1c0 + in_pixel.g * r1c1 + in_pixel.b * r1c2;
|
|
|
|
|
b = in_pixel.r * r2c0 + in_pixel.g * r2c1 + in_pixel.b * r2c2;
|
|
|
|
|
r >>= shift;
|
|
|
|
|
g >>= shift;
|
|
|
|
|
b >>= shift;
|
|
|
|
|
r += r0c3;
|
|
|
|
|
g += r1c3;
|
|
|
|
|
b += r2c3;
|
|
|
|
|
r >>= 8;
|
|
|
|
|
g >>= 8;
|
|
|
|
|
b >>= 8;
|
|
|
|
|
return {r, g, b, s32(in_pixel.a)};
|
|
|
|
|
};
|
|
|
|
|
for (u32 y = source_top; y < source_bottom; y++) {
|
|
|
|
|
const auto src{y * in_surface_width + source_left};
|
|
|
|
|
const auto dst{y * out_surface_width + rect_left};
|
|
|
|
|
for (u32 x = source_left; x < source_right; x++) {
|
|
|
|
|
auto [r, g, b, a] = MatMul(slot_surface[src + x]);
|
|
|
|
|
r = std::clamp(r, clamp_min, clamp_max);
|
|
|
|
|
g = std::clamp(g, clamp_min, clamp_max);
|
|
|
|
|
b = std::clamp(b, clamp_min, clamp_max);
|
|
|
|
|
a = std::clamp(a, clamp_min, clamp_max);
|
|
|
|
|
output_surface[dst + x] = {u16(r), u16(g), u16(b), u16(a)};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|