mirror of
https://github.com/PabloMK7/citra
synced 2024-11-16 21:58:23 +00:00
9c781a6c76
While it was some nice and fancy template usage, it ultimately had many practical issues regarding length of involved expressions under regular usage as well as common code completion tools not being able to handle the structures. Instead, we now use a more conventional approach which is a lot more clean to use.
305 lines
11 KiB
C++
305 lines
11 KiB
C++
// Copyright 2014 Citra Emulator Project
|
|
// Licensed under GPLv2
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "common/common_types.h"
|
|
#include "common/log.h"
|
|
|
|
#include "core/core.h"
|
|
#include "core/mem_map.h"
|
|
|
|
#include "core/hle/hle.h"
|
|
#include "core/hle/kernel/thread.h"
|
|
#include "core/hle/service/gsp.h"
|
|
|
|
#include "core/hw/gpu.h"
|
|
|
|
#include "video_core/video_core.h"
|
|
|
|
|
|
namespace GPU {
|
|
|
|
Regs g_regs;
|
|
|
|
u32 g_cur_line = 0; ///< Current vertical screen line
|
|
u64 g_last_line_ticks = 0; ///< CPU tick count from last vertical screen line
|
|
|
|
/**
|
|
* Sets whether the framebuffers are in the GSP heap (FCRAM) or VRAM
|
|
* @param
|
|
*/
|
|
void SetFramebufferLocation(const FramebufferLocation mode) {
|
|
switch (mode) {
|
|
case FRAMEBUFFER_LOCATION_FCRAM:
|
|
{
|
|
auto& framebuffer_top = g_regs.framebuffer_config[0];
|
|
auto& framebuffer_sub = g_regs.framebuffer_config[1];
|
|
|
|
framebuffer_top.address_left1 = PADDR_TOP_LEFT_FRAME1;
|
|
framebuffer_top.address_left2 = PADDR_TOP_LEFT_FRAME2;
|
|
framebuffer_top.address_right1 = PADDR_TOP_RIGHT_FRAME1;
|
|
framebuffer_top.address_right2 = PADDR_TOP_RIGHT_FRAME2;
|
|
framebuffer_sub.address_left1 = PADDR_SUB_FRAME1;
|
|
//framebuffer_sub.address_left2 = unknown;
|
|
framebuffer_sub.address_right1 = PADDR_SUB_FRAME2;
|
|
//framebuffer_sub.address_right2 = unknown;
|
|
break;
|
|
}
|
|
|
|
case FRAMEBUFFER_LOCATION_VRAM:
|
|
{
|
|
auto& framebuffer_top = g_regs.framebuffer_config[0];
|
|
auto& framebuffer_sub = g_regs.framebuffer_config[1];
|
|
|
|
framebuffer_top.address_left1 = PADDR_VRAM_TOP_LEFT_FRAME1;
|
|
framebuffer_top.address_left2 = PADDR_VRAM_TOP_LEFT_FRAME2;
|
|
framebuffer_top.address_right1 = PADDR_VRAM_TOP_RIGHT_FRAME1;
|
|
framebuffer_top.address_right2 = PADDR_VRAM_TOP_RIGHT_FRAME2;
|
|
framebuffer_sub.address_left1 = PADDR_VRAM_SUB_FRAME1;
|
|
//framebuffer_sub.address_left2 = unknown;
|
|
framebuffer_sub.address_right1 = PADDR_VRAM_SUB_FRAME2;
|
|
//framebuffer_sub.address_right2 = unknown;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the location of the framebuffers
|
|
* @return Location of framebuffers as FramebufferLocation enum
|
|
*/
|
|
FramebufferLocation GetFramebufferLocation(u32 address) {
|
|
if ((address & ~Memory::VRAM_MASK) == Memory::VRAM_PADDR) {
|
|
return FRAMEBUFFER_LOCATION_VRAM;
|
|
} else if ((address & ~Memory::FCRAM_MASK) == Memory::FCRAM_PADDR) {
|
|
return FRAMEBUFFER_LOCATION_FCRAM;
|
|
} else {
|
|
ERROR_LOG(GPU, "unknown framebuffer location!");
|
|
}
|
|
return FRAMEBUFFER_LOCATION_UNKNOWN;
|
|
}
|
|
|
|
u32 GetFramebufferAddr(const u32 address) {
|
|
switch (GetFramebufferLocation(address)) {
|
|
case FRAMEBUFFER_LOCATION_FCRAM:
|
|
return Memory::VirtualAddressFromPhysical_FCRAM(address);
|
|
case FRAMEBUFFER_LOCATION_VRAM:
|
|
return Memory::VirtualAddressFromPhysical_VRAM(address);
|
|
default:
|
|
ERROR_LOG(GPU, "unknown framebuffer location");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Gets a read-only pointer to a framebuffer in memory
|
|
* @param address Physical address of framebuffer
|
|
* @return Returns const pointer to raw framebuffer
|
|
*/
|
|
const u8* GetFramebufferPointer(const u32 address) {
|
|
u32 addr = GetFramebufferAddr(address);
|
|
return (addr != 0) ? Memory::GetPointer(addr) : nullptr;
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Read(T &var, const u32 raw_addr) {
|
|
u32 addr = raw_addr - 0x1EF00000;
|
|
int index = addr / 4;
|
|
|
|
// Reads other than u32 are untested, so I'd rather have them abort than silently fail
|
|
if (index >= Regs::NumIds() || !std::is_same<T,u32>::value) {
|
|
ERROR_LOG(GPU, "unknown Read%d @ 0x%08X", sizeof(var) * 8, addr);
|
|
return;
|
|
}
|
|
|
|
var = g_regs[addr / 4];
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Write(u32 addr, const T data) {
|
|
addr -= 0x1EF00000;
|
|
int index = addr / 4;
|
|
|
|
// Writes other than u32 are untested, so I'd rather have them abort than silently fail
|
|
if (index >= Regs::NumIds() || !std::is_same<T,u32>::value) {
|
|
ERROR_LOG(GPU, "unknown Write%d 0x%08X @ 0x%08X", sizeof(data) * 8, data, addr);
|
|
return;
|
|
}
|
|
|
|
g_regs[index] = data;
|
|
|
|
switch (index) {
|
|
|
|
// Memory fills are triggered once the fill value is written.
|
|
// NOTE: This is not verified.
|
|
case GPU_REG_INDEX_WORKAROUND(memory_fill_config[0].value, 0x00004 + 0x3):
|
|
case GPU_REG_INDEX_WORKAROUND(memory_fill_config[1].value, 0x00008 + 0x3):
|
|
{
|
|
const bool is_second_filler = (index != GPU_REG_INDEX(memory_fill_config[0].value));
|
|
const auto& config = g_regs.memory_fill_config[is_second_filler];
|
|
|
|
// TODO: Not sure if this check should be done at GSP level instead
|
|
if (config.address_start) {
|
|
// TODO: Not sure if this algorithm is correct, particularly because it doesn't use the size member at all
|
|
u32* start = (u32*)Memory::GetPointer(config.GetStartAddress());
|
|
u32* end = (u32*)Memory::GetPointer(config.GetEndAddress());
|
|
for (u32* ptr = start; ptr < end; ++ptr)
|
|
*ptr = bswap32(config.value); // TODO: This is just a workaround to missing framebuffer format emulation
|
|
|
|
DEBUG_LOG(GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GPU_REG_INDEX(display_transfer_config.trigger):
|
|
{
|
|
const auto& config = g_regs.display_transfer_config;
|
|
if (config.trigger & 1) {
|
|
u8* source_pointer = Memory::GetPointer(config.GetPhysicalInputAddress());
|
|
u8* dest_pointer = Memory::GetPointer(config.GetPhysicalOutputAddress());
|
|
|
|
for (int y = 0; y < config.output_height; ++y) {
|
|
// TODO: Why does the register seem to hold twice the framebuffer width?
|
|
for (int x = 0; x < config.output_width / 2; ++x) {
|
|
struct {
|
|
int r, g, b, a;
|
|
} source_color = { 0, 0, 0, 0 };
|
|
|
|
switch (config.input_format) {
|
|
case Regs::FramebufferFormat::RGBA8:
|
|
{
|
|
// TODO: Most likely got the component order messed up.
|
|
u8* srcptr = source_pointer + x * 4 + y * config.input_width * 4 / 2;
|
|
source_color.r = srcptr[0]; // blue
|
|
source_color.g = srcptr[1]; // green
|
|
source_color.b = srcptr[2]; // red
|
|
source_color.a = srcptr[3]; // alpha
|
|
break;
|
|
}
|
|
|
|
default:
|
|
ERROR_LOG(GPU, "Unknown source framebuffer format %x", config.input_format.Value());
|
|
break;
|
|
}
|
|
|
|
switch (config.output_format) {
|
|
/*case Regs::FramebufferFormat::RGBA8:
|
|
{
|
|
// TODO: Untested
|
|
u8* dstptr = (u32*)(dest_pointer + x * 4 + y * config.output_width * 4);
|
|
dstptr[0] = source_color.r;
|
|
dstptr[1] = source_color.g;
|
|
dstptr[2] = source_color.b;
|
|
dstptr[3] = source_color.a;
|
|
break;
|
|
}*/
|
|
|
|
case Regs::FramebufferFormat::RGB8:
|
|
{
|
|
// TODO: Most likely got the component order messed up.
|
|
u8* dstptr = dest_pointer + x * 3 + y * config.output_width * 3 / 2;
|
|
dstptr[0] = source_color.r; // blue
|
|
dstptr[1] = source_color.g; // green
|
|
dstptr[2] = source_color.b; // red
|
|
break;
|
|
}
|
|
|
|
default:
|
|
ERROR_LOG(GPU, "Unknown destination framebuffer format %x", config.output_format.Value());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_LOG(GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%dx%d)-> 0x%08x(%dx%d), dst format %x",
|
|
config.output_height * config.output_width * 4,
|
|
config.GetPhysicalInputAddress(), (int)config.input_width, (int)config.input_height,
|
|
config.GetPhysicalOutputAddress(), (int)config.output_width, (int)config.output_height,
|
|
config.output_format.Value());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GPU_REG_INDEX(command_processor_config.trigger):
|
|
{
|
|
const auto& config = g_regs.command_processor_config;
|
|
if (config.trigger & 1)
|
|
{
|
|
// u32* buffer = (u32*)Memory::GetPointer(config.GetPhysicalAddress());
|
|
ERROR_LOG(GPU, "Beginning 0x%08x bytes of commands from address 0x%08x", config.size, config.GetPhysicalAddress());
|
|
// TODO: Process command list!
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Explicitly instantiate template functions because we aren't defining this in the header:
|
|
|
|
template void Read<u64>(u64 &var, const u32 addr);
|
|
template void Read<u32>(u32 &var, const u32 addr);
|
|
template void Read<u16>(u16 &var, const u32 addr);
|
|
template void Read<u8>(u8 &var, const u32 addr);
|
|
|
|
template void Write<u64>(u32 addr, const u64 data);
|
|
template void Write<u32>(u32 addr, const u32 data);
|
|
template void Write<u16>(u32 addr, const u16 data);
|
|
template void Write<u8>(u32 addr, const u8 data);
|
|
|
|
/// Update hardware
|
|
void Update() {
|
|
auto& framebuffer_top = g_regs.framebuffer_config[0];
|
|
u64 current_ticks = Core::g_app_core->GetTicks();
|
|
|
|
// Synchronize line...
|
|
if ((current_ticks - g_last_line_ticks) >= GPU::kFrameTicks / framebuffer_top.height) {
|
|
GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0);
|
|
g_cur_line++;
|
|
g_last_line_ticks = current_ticks;
|
|
}
|
|
|
|
// Synchronize frame...
|
|
if (g_cur_line >= framebuffer_top.height) {
|
|
g_cur_line = 0;
|
|
GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1);
|
|
VideoCore::g_renderer->SwapBuffers();
|
|
Kernel::WaitCurrentThread(WAITTYPE_VBLANK);
|
|
HLE::Reschedule(__func__);
|
|
}
|
|
}
|
|
|
|
/// Initialize hardware
|
|
void Init() {
|
|
g_cur_line = 0;
|
|
g_last_line_ticks = Core::g_app_core->GetTicks();
|
|
|
|
// SetFramebufferLocation(FRAMEBUFFER_LOCATION_FCRAM);
|
|
SetFramebufferLocation(FRAMEBUFFER_LOCATION_VRAM);
|
|
|
|
auto& framebuffer_top = g_regs.framebuffer_config[0];
|
|
auto& framebuffer_sub = g_regs.framebuffer_config[1];
|
|
// TODO: Width should be 240 instead?
|
|
framebuffer_top.width = 480;
|
|
framebuffer_top.height = 400;
|
|
framebuffer_top.stride = 480*3;
|
|
framebuffer_top.color_format = Regs::FramebufferFormat::RGB8;
|
|
framebuffer_top.active_fb = 0;
|
|
|
|
framebuffer_sub.width = 480;
|
|
framebuffer_sub.height = 400;
|
|
framebuffer_sub.stride = 480*3;
|
|
framebuffer_sub.color_format = Regs::FramebufferFormat::RGB8;
|
|
framebuffer_sub.active_fb = 0;
|
|
|
|
NOTICE_LOG(GPU, "initialized OK");
|
|
}
|
|
|
|
/// Shutdown hardware
|
|
void Shutdown() {
|
|
NOTICE_LOG(GPU, "shutdown OK");
|
|
}
|
|
|
|
} // namespace
|