|
|
|
@ -18,10 +18,10 @@
|
|
|
|
|
#include "core/hw/gpu.h"
|
|
|
|
|
|
|
|
|
|
#include "video_core/command_processor.h"
|
|
|
|
|
#include "video_core/utils.h"
|
|
|
|
|
#include "video_core/video_core.h"
|
|
|
|
|
#include <video_core/color.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace GPU {
|
|
|
|
|
|
|
|
|
|
Regs g_regs;
|
|
|
|
@ -116,24 +116,64 @@ inline void Write(u32 addr, const T data) {
|
|
|
|
|
u8* source_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalInputAddress()));
|
|
|
|
|
u8* dest_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalOutputAddress()));
|
|
|
|
|
|
|
|
|
|
// Cheap emulation of horizontal scaling: Just skip each second pixel of the
|
|
|
|
|
// input framebuffer. We keep track of this in the pixel_skip variable.
|
|
|
|
|
unsigned pixel_skip = (config.scale_horizontally != 0) ? 2 : 1;
|
|
|
|
|
unsigned horizontal_scale = (config.scale_horizontally != 0) ? 2 : 1;
|
|
|
|
|
unsigned vertical_scale = (config.scale_vertically != 0) ? 2 : 1;
|
|
|
|
|
|
|
|
|
|
u32 output_width = config.output_width / pixel_skip;
|
|
|
|
|
u32 output_width = config.output_width / horizontal_scale;
|
|
|
|
|
u32 output_height = config.output_height / vertical_scale;
|
|
|
|
|
|
|
|
|
|
for (u32 y = 0; y < config.output_height; ++y) {
|
|
|
|
|
// TODO: Why does the register seem to hold twice the framebuffer width?
|
|
|
|
|
if (config.raw_copy) {
|
|
|
|
|
// Raw copies do not perform color conversion nor tiled->linear / linear->tiled conversions
|
|
|
|
|
// TODO(Subv): Verify if raw copies perform scaling
|
|
|
|
|
memcpy(dest_pointer, source_pointer, config.output_width * config.output_height *
|
|
|
|
|
GPU::Regs::BytesPerPixel(config.output_format));
|
|
|
|
|
|
|
|
|
|
LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), flags 0x%08X, Raw copy",
|
|
|
|
|
config.output_height * output_width * GPU::Regs::BytesPerPixel(config.output_format),
|
|
|
|
|
config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(),
|
|
|
|
|
config.GetPhysicalOutputAddress(), config.output_width.Value(), config.output_height.Value(),
|
|
|
|
|
config.output_format.Value(), config.flags);
|
|
|
|
|
|
|
|
|
|
GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(Subv): Blend the pixels when horizontal / vertical scaling is enabled,
|
|
|
|
|
// right now we're just skipping the extra pixels.
|
|
|
|
|
for (u32 y = 0; y < output_height; ++y) {
|
|
|
|
|
for (u32 x = 0; x < output_width; ++x) {
|
|
|
|
|
struct {
|
|
|
|
|
int r, g, b, a;
|
|
|
|
|
} source_color = { 0, 0, 0, 0 };
|
|
|
|
|
|
|
|
|
|
u32 scaled_x = x * horizontal_scale;
|
|
|
|
|
u32 scaled_y = y * vertical_scale;
|
|
|
|
|
|
|
|
|
|
u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format);
|
|
|
|
|
u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format);
|
|
|
|
|
u32 src_offset;
|
|
|
|
|
u32 dst_offset;
|
|
|
|
|
|
|
|
|
|
if (config.output_tiled) {
|
|
|
|
|
// Interpret the input as linear and the output as tiled
|
|
|
|
|
u32 coarse_y = y & ~7;
|
|
|
|
|
u32 stride = output_width * dst_bytes_per_pixel;
|
|
|
|
|
|
|
|
|
|
src_offset = (scaled_x + scaled_y * config.input_width) * src_bytes_per_pixel;
|
|
|
|
|
dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride;
|
|
|
|
|
} else {
|
|
|
|
|
// Interpret the input as tiled and the output as linear
|
|
|
|
|
u32 coarse_y = scaled_y & ~7;
|
|
|
|
|
u32 stride = config.input_width * src_bytes_per_pixel;
|
|
|
|
|
|
|
|
|
|
src_offset = VideoCore::GetMortonOffset(scaled_x, scaled_y, src_bytes_per_pixel) + coarse_y * stride;
|
|
|
|
|
dst_offset = (x + y * output_width) * dst_bytes_per_pixel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (config.input_format) {
|
|
|
|
|
case Regs::PixelFormat::RGBA8:
|
|
|
|
|
{
|
|
|
|
|
u8* srcptr = source_pointer + (x * pixel_skip + y * config.input_width) * 4;
|
|
|
|
|
u8* srcptr = source_pointer + src_offset;
|
|
|
|
|
source_color.r = srcptr[3]; // red
|
|
|
|
|
source_color.g = srcptr[2]; // green
|
|
|
|
|
source_color.b = srcptr[1]; // blue
|
|
|
|
@ -143,7 +183,7 @@ inline void Write(u32 addr, const T data) {
|
|
|
|
|
|
|
|
|
|
case Regs::PixelFormat::RGB5A1:
|
|
|
|
|
{
|
|
|
|
|
u16 srcval = *(u16*)(source_pointer + x * 4 * pixel_skip + y * config.input_width * 4 * pixel_skip);
|
|
|
|
|
u16 srcval = *(u16*)(source_pointer + src_offset);
|
|
|
|
|
source_color.r = Color::Convert5To8((srcval >> 11) & 0x1F); // red
|
|
|
|
|
source_color.g = Color::Convert5To8((srcval >> 6) & 0x1F); // green
|
|
|
|
|
source_color.b = Color::Convert5To8((srcval >> 1) & 0x1F); // blue
|
|
|
|
@ -153,7 +193,7 @@ inline void Write(u32 addr, const T data) {
|
|
|
|
|
|
|
|
|
|
case Regs::PixelFormat::RGBA4:
|
|
|
|
|
{
|
|
|
|
|
u16 srcval = *(u16*)(source_pointer + x * 4 * pixel_skip + y * config.input_width * 4 * pixel_skip);
|
|
|
|
|
u16 srcval = *(u16*)(source_pointer + src_offset);
|
|
|
|
|
source_color.r = Color::Convert4To8((srcval >> 12) & 0xF); // red
|
|
|
|
|
source_color.g = Color::Convert4To8((srcval >> 8) & 0xF); // green
|
|
|
|
|
source_color.b = Color::Convert4To8((srcval >> 4) & 0xF); // blue
|
|
|
|
@ -169,7 +209,7 @@ inline void Write(u32 addr, const T data) {
|
|
|
|
|
switch (config.output_format) {
|
|
|
|
|
case Regs::PixelFormat::RGBA8:
|
|
|
|
|
{
|
|
|
|
|
u8* dstptr = dest_pointer + (x * pixel_skip + y * config.output_width) * 4;
|
|
|
|
|
u8* dstptr = dest_pointer + dst_offset;
|
|
|
|
|
dstptr[3] = source_color.r;
|
|
|
|
|
dstptr[2] = source_color.g;
|
|
|
|
|
dstptr[1] = source_color.b;
|
|
|
|
@ -179,7 +219,7 @@ inline void Write(u32 addr, const T data) {
|
|
|
|
|
|
|
|
|
|
case Regs::PixelFormat::RGB8:
|
|
|
|
|
{
|
|
|
|
|
u8* dstptr = dest_pointer + (x + y * output_width) * 3;
|
|
|
|
|
u8* dstptr = dest_pointer + dst_offset;
|
|
|
|
|
dstptr[2] = source_color.r; // red
|
|
|
|
|
dstptr[1] = source_color.g; // green
|
|
|
|
|
dstptr[0] = source_color.b; // blue
|
|
|
|
@ -188,7 +228,7 @@ inline void Write(u32 addr, const T data) {
|
|
|
|
|
|
|
|
|
|
case Regs::PixelFormat::RGB5A1:
|
|
|
|
|
{
|
|
|
|
|
u16* dstptr = (u16*)(dest_pointer + x * 2 + y * config.output_width * 2);
|
|
|
|
|
u16* dstptr = (u16*)(dest_pointer + dst_offset);
|
|
|
|
|
*dstptr = ((source_color.r >> 3) << 11) | ((source_color.g >> 3) << 6)
|
|
|
|
|
| ((source_color.b >> 3) << 1) | ( source_color.a >> 7);
|
|
|
|
|
break;
|
|
|
|
@ -196,7 +236,7 @@ inline void Write(u32 addr, const T data) {
|
|
|
|
|
|
|
|
|
|
case Regs::PixelFormat::RGBA4:
|
|
|
|
|
{
|
|
|
|
|
u16* dstptr = (u16*)(dest_pointer + x * 2 + y * config.output_width * 2);
|
|
|
|
|
u16* dstptr = (u16*)(dest_pointer + dst_offset);
|
|
|
|
|
*dstptr = ((source_color.r >> 4) << 12) | ((source_color.g >> 4) << 8)
|
|
|
|
|
| ((source_color.b >> 4) << 4) | ( source_color.a >> 4);
|
|
|
|
|
break;
|
|
|
|
@ -209,11 +249,11 @@ inline void Write(u32 addr, const T data) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x",
|
|
|
|
|
config.output_height * output_width * 4,
|
|
|
|
|
config.GetPhysicalInputAddress(), (u32)config.input_width, (u32)config.input_height,
|
|
|
|
|
config.GetPhysicalOutputAddress(), (u32)output_width, (u32)config.output_height,
|
|
|
|
|
config.output_format.Value());
|
|
|
|
|
LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x, flags 0x%08X",
|
|
|
|
|
config.output_height * output_width * GPU::Regs::BytesPerPixel(config.output_format),
|
|
|
|
|
config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(),
|
|
|
|
|
config.GetPhysicalOutputAddress(), output_width, output_height,
|
|
|
|
|
config.output_format.Value(), config.flags);
|
|
|
|
|
|
|
|
|
|
GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF);
|
|
|
|
|
}
|
|
|
|
|