mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-14 23:40:05 +00:00
service/gsp: Implement saving of framebuffers in SaveVramSysArea. (#6821)
* service/gsp: Implement saving of framebuffers in SaveVramSysArea. * Address review comments. * service/apt: Separate capture info and capture buffer info. The former is used with the RequestForSysApplet message and GetCaptureInfo. The latter is used with SendCaptureBufferInfo and ReceiveCaptureBufferInfo.
This commit is contained in:
parent
bb364d9bc0
commit
964f9ee3cf
@ -232,7 +232,7 @@ void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) {
|
||||
parameter.sender_id);
|
||||
|
||||
if (parameter.buffer.size() >= sizeof(CaptureBufferInfo)) {
|
||||
SendCaptureBufferInfo(parameter.buffer);
|
||||
SetCaptureInfo(parameter.buffer);
|
||||
CaptureFrameBuffers();
|
||||
}
|
||||
|
||||
@ -511,6 +511,8 @@ ResultCode AppletManager::PrepareToStartLibraryApplet(AppletId applet_id) {
|
||||
last_library_launcher_slot = active_slot;
|
||||
last_prepared_library_applet = applet_id;
|
||||
|
||||
capture_buffer_info.reset();
|
||||
|
||||
auto cfg = Service::CFG::GetModule(system);
|
||||
auto process =
|
||||
NS::LaunchTitle(FS::MediaType::NAND, GetTitleIdForApplet(applet_id, cfg->GetRegionValue()));
|
||||
@ -849,6 +851,9 @@ ResultCode AppletManager::PrepareToJumpToHomeMenu() {
|
||||
}
|
||||
|
||||
last_jump_to_home_slot = active_slot;
|
||||
|
||||
capture_buffer_info.reset();
|
||||
|
||||
if (last_jump_to_home_slot == AppletSlot::Application) {
|
||||
EnsureHomeMenuLoaded();
|
||||
}
|
||||
@ -1250,6 +1255,8 @@ ResultCode AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType
|
||||
app_start_parameters->next_title_id = title_id;
|
||||
app_start_parameters->next_media_type = media_type;
|
||||
|
||||
capture_buffer_info.reset();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@ -1359,48 +1366,74 @@ void AppletManager::EnsureHomeMenuLoaded() {
|
||||
}
|
||||
}
|
||||
|
||||
static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr src, u32 height,
|
||||
u32 format) {
|
||||
static constexpr auto screen_capture_base_vaddr = static_cast<VAddr>(0x1F500000);
|
||||
static constexpr auto screen_width = 240;
|
||||
static constexpr auto screen_width_pow2 = 256;
|
||||
const auto bpp = format < 2 ? 3 : 2;
|
||||
static u32 GetDisplayBufferModePixelSize(DisplayBufferMode mode) {
|
||||
switch (mode) {
|
||||
// NOTE: APT does in fact use pixel size 3 for R8G8B8A8 captures.
|
||||
case DisplayBufferMode::R8G8B8A8:
|
||||
case DisplayBufferMode::R8G8B8:
|
||||
return 3;
|
||||
case DisplayBufferMode::R5G6B5:
|
||||
case DisplayBufferMode::R5G5B5A1:
|
||||
case DisplayBufferMode::R4G4B4A4:
|
||||
return 2;
|
||||
case DisplayBufferMode::Unimportable:
|
||||
return 0;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown display buffer mode {}", mode);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Memory::RasterizerFlushVirtualRegion(src, screen_width * height * bpp,
|
||||
static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr src, u32 height,
|
||||
DisplayBufferMode mode) {
|
||||
const auto bpp = GetDisplayBufferModePixelSize(mode);
|
||||
if (bpp == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Memory::RasterizerFlushVirtualRegion(src, GSP::FRAMEBUFFER_WIDTH * height * bpp,
|
||||
Memory::FlushMode::Flush);
|
||||
|
||||
auto dst_vaddr = screen_capture_base_vaddr + capture_offset;
|
||||
// Address in VRAM that APT copies framebuffer captures to.
|
||||
constexpr VAddr screen_capture_base_vaddr = Memory::VRAM_VADDR + 0x500000;
|
||||
const auto dst_vaddr = screen_capture_base_vaddr + capture_offset;
|
||||
auto dst_ptr = system.Memory().GetPointer(dst_vaddr);
|
||||
if (!dst_ptr) {
|
||||
LOG_ERROR(Service_APT,
|
||||
"Could not retrieve framebuffer capture destination buffer, skipping screen.");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto src_ptr = system.Memory().GetPointer(src);
|
||||
if (!src_ptr) {
|
||||
LOG_ERROR(Service_APT,
|
||||
"Could not retrieve framebuffer capture source buffer, skipping screen.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (u32 y = 0; y < height; y++) {
|
||||
for (u32 x = 0; x < screen_width; x++) {
|
||||
auto dst_offset =
|
||||
VideoCore::GetMortonOffset(x, y, bpp) + (y & ~7) * screen_width_pow2 * bpp;
|
||||
auto src_offset = bpp * (screen_width * y + x);
|
||||
for (u32 x = 0; x < GSP::FRAMEBUFFER_WIDTH; x++) {
|
||||
const auto dst_offset = VideoCore::GetMortonOffset(x, y, bpp) +
|
||||
(y & ~7) * GSP::FRAMEBUFFER_WIDTH_POW2 * bpp;
|
||||
const auto src_offset = bpp * (GSP::FRAMEBUFFER_WIDTH * y + x);
|
||||
std::memcpy(dst_ptr + dst_offset, src_ptr + src_offset, bpp);
|
||||
}
|
||||
}
|
||||
|
||||
Memory::RasterizerFlushVirtualRegion(dst_vaddr, screen_width_pow2 * height * bpp,
|
||||
Memory::RasterizerFlushVirtualRegion(dst_vaddr, GSP::FRAMEBUFFER_WIDTH_POW2 * height * bpp,
|
||||
Memory::FlushMode::Invalidate);
|
||||
}
|
||||
|
||||
void AppletManager::CaptureFrameBuffers() {
|
||||
auto gsp =
|
||||
Core::System::GetInstance().ServiceManager().GetService<Service::GSP::GSP_GPU>("gsp::Gpu");
|
||||
auto active_thread_id = gsp->GetActiveThreadId();
|
||||
auto top_screen = gsp->GetFrameBufferInfo(active_thread_id, 0);
|
||||
auto bottom_screen = gsp->GetFrameBufferInfo(active_thread_id, 1);
|
||||
|
||||
auto top_fb = top_screen->framebuffer_info[top_screen->index];
|
||||
auto bottom_fb = bottom_screen->framebuffer_info[bottom_screen->index];
|
||||
|
||||
CaptureFrameBuffer(system, capture_info->bottom_screen_left_offset, bottom_fb.address_left, 320,
|
||||
CaptureFrameBuffer(system, capture_info->bottom_screen_left_offset,
|
||||
GSP::FRAMEBUFFER_SAVE_AREA_BOTTOM, GSP::BOTTOM_FRAMEBUFFER_HEIGHT,
|
||||
capture_info->bottom_screen_format);
|
||||
CaptureFrameBuffer(system, capture_info->top_screen_left_offset, top_fb.address_left, 400,
|
||||
CaptureFrameBuffer(system, capture_info->top_screen_left_offset,
|
||||
GSP::FRAMEBUFFER_SAVE_AREA_TOP_LEFT, GSP::TOP_FRAMEBUFFER_HEIGHT,
|
||||
capture_info->top_screen_format);
|
||||
if (capture_info->is_3d) {
|
||||
CaptureFrameBuffer(system, capture_info->top_screen_right_offset, top_fb.address_right, 400,
|
||||
CaptureFrameBuffer(system, capture_info->top_screen_right_offset,
|
||||
GSP::FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, GSP::TOP_FRAMEBUFFER_HEIGHT,
|
||||
capture_info->top_screen_format);
|
||||
}
|
||||
}
|
||||
|
@ -210,6 +210,15 @@ private:
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
enum class DisplayBufferMode : u32_le {
|
||||
R8G8B8A8 = 0,
|
||||
R8G8B8 = 1,
|
||||
R5G6B5 = 2,
|
||||
R5G5B5A1 = 3,
|
||||
R4G4B4A4 = 4,
|
||||
Unimportable = 0xFFFFFFFF,
|
||||
};
|
||||
|
||||
/// Used by the application to pass information about the current framebuffer to applets.
|
||||
struct CaptureBufferInfo {
|
||||
u32_le size;
|
||||
@ -217,10 +226,10 @@ struct CaptureBufferInfo {
|
||||
INSERT_PADDING_BYTES(0x3); // Padding for alignment
|
||||
u32_le top_screen_left_offset;
|
||||
u32_le top_screen_right_offset;
|
||||
u32_le top_screen_format;
|
||||
DisplayBufferMode top_screen_format;
|
||||
u32_le bottom_screen_left_offset;
|
||||
u32_le bottom_screen_right_offset;
|
||||
u32_le bottom_screen_format;
|
||||
DisplayBufferMode bottom_screen_format;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
@ -333,16 +342,27 @@ public:
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
void SetCaptureInfo(std::vector<u8> buffer) {
|
||||
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
|
||||
|
||||
capture_info.emplace();
|
||||
std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
|
||||
}
|
||||
|
||||
std::vector<u8> ReceiveCaptureBufferInfo() {
|
||||
std::vector<u8> buffer = GetCaptureInfo();
|
||||
capture_info.reset();
|
||||
std::vector<u8> buffer;
|
||||
if (capture_buffer_info) {
|
||||
buffer.resize(sizeof(CaptureBufferInfo));
|
||||
std::memcpy(buffer.data(), &capture_buffer_info.get(), sizeof(CaptureBufferInfo));
|
||||
capture_buffer_info.reset();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
void SendCaptureBufferInfo(std::vector<u8> buffer) {
|
||||
ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small.");
|
||||
|
||||
capture_info.emplace();
|
||||
std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
|
||||
capture_buffer_info.emplace();
|
||||
std::memcpy(&capture_buffer_info.get(), buffer.data(), sizeof(CaptureBufferInfo));
|
||||
}
|
||||
|
||||
ResultCode PrepareToStartApplication(u64 title_id, FS::MediaType media_type);
|
||||
@ -395,6 +415,7 @@ private:
|
||||
boost::optional<DeliverArg> deliver_arg{};
|
||||
|
||||
boost::optional<CaptureBufferInfo> capture_info;
|
||||
boost::optional<CaptureBufferInfo> capture_buffer_info;
|
||||
|
||||
static constexpr std::size_t NumAppletSlot = 4;
|
||||
|
||||
@ -500,6 +521,8 @@ private:
|
||||
ar& delayed_parameter;
|
||||
ar& app_start_parameters;
|
||||
ar& deliver_arg;
|
||||
ar& capture_info;
|
||||
ar& capture_buffer_info;
|
||||
ar& active_slot;
|
||||
ar& last_library_launcher_slot;
|
||||
ar& last_prepared_library_applet;
|
||||
|
@ -704,18 +704,73 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_GSP, "called");
|
||||
}
|
||||
|
||||
static void CopyFrameBuffer(Core::System& system, VAddr dst, VAddr src, u32 stride, u32 lines) {
|
||||
auto dst_ptr = system.Memory().GetPointer(dst);
|
||||
const auto src_ptr = system.Memory().GetPointer(src);
|
||||
if (!dst_ptr || !src_ptr) {
|
||||
LOG_WARNING(Service_GSP,
|
||||
"Could not resolve pointers for framebuffer capture, skipping screen.");
|
||||
return;
|
||||
}
|
||||
|
||||
Memory::RasterizerFlushVirtualRegion(src, stride * lines, Memory::FlushMode::Flush);
|
||||
std::memcpy(dst_ptr, src_ptr, stride * lines);
|
||||
Memory::RasterizerFlushVirtualRegion(dst, stride * lines, Memory::FlushMode::Invalidate);
|
||||
}
|
||||
|
||||
void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
if (active_thread_id == std::numeric_limits<u32>::max()) {
|
||||
LOG_WARNING(Service_GSP, "Called without an active thread.");
|
||||
|
||||
// TODO: Find the right error code.
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Service_GSP, "called");
|
||||
|
||||
// TODO: This should also DMA framebuffers into VRAM and save LCD register state.
|
||||
// TODO: This should also save LCD register state.
|
||||
Memory::RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE,
|
||||
Memory::FlushMode::Flush);
|
||||
auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR);
|
||||
const auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR);
|
||||
saved_vram.emplace(std::vector<u8>(Memory::VRAM_SIZE));
|
||||
std::memcpy(saved_vram.get().data(), vram, Memory::VRAM_SIZE);
|
||||
|
||||
const auto top_screen = GetFrameBufferInfo(active_thread_id, 0);
|
||||
if (top_screen) {
|
||||
const auto top_fb = top_screen->framebuffer_info[top_screen->index];
|
||||
if (top_fb.address_left) {
|
||||
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left,
|
||||
top_fb.stride, TOP_FRAMEBUFFER_HEIGHT);
|
||||
} else {
|
||||
LOG_WARNING(Service_GSP, "No framebuffer bound to top left screen, skipping capture.");
|
||||
}
|
||||
if (top_fb.address_right) {
|
||||
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, top_fb.address_right,
|
||||
top_fb.stride, TOP_FRAMEBUFFER_HEIGHT);
|
||||
} else {
|
||||
LOG_WARNING(Service_GSP, "No framebuffer bound to top right screen, skipping capture.");
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Service_GSP, "No top screen bound, skipping capture.");
|
||||
}
|
||||
|
||||
const auto bottom_screen = GetFrameBufferInfo(active_thread_id, 1);
|
||||
if (bottom_screen) {
|
||||
const auto bottom_fb = bottom_screen->framebuffer_info[bottom_screen->index];
|
||||
if (bottom_fb.address_left) {
|
||||
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left,
|
||||
bottom_fb.stride, BOTTOM_FRAMEBUFFER_HEIGHT);
|
||||
} else {
|
||||
LOG_WARNING(Service_GSP, "No framebuffer bound to bottom screen, skipping capture.");
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Service_GSP, "No bottom screen bound, skipping capture.");
|
||||
}
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
@ -819,7 +874,7 @@ SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 2), system(system) {
|
||||
GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 4), system(system) {
|
||||
static const FunctionInfo functions[] = {
|
||||
// clang-format off
|
||||
{0x0001, &GSP_GPU::WriteHWRegs, "WriteHWRegs"},
|
||||
|
@ -186,6 +186,17 @@ struct CommandBuffer {
|
||||
};
|
||||
static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size");
|
||||
|
||||
constexpr u32 FRAMEBUFFER_WIDTH = 240;
|
||||
constexpr u32 FRAMEBUFFER_WIDTH_POW2 = 256;
|
||||
constexpr u32 TOP_FRAMEBUFFER_HEIGHT = 400;
|
||||
constexpr u32 BOTTOM_FRAMEBUFFER_HEIGHT = 320;
|
||||
constexpr u32 FRAMEBUFFER_HEIGHT_POW2 = 512;
|
||||
|
||||
// These are the VRAM addresses that GSP copies framebuffers to in SaveVramSysArea.
|
||||
constexpr VAddr FRAMEBUFFER_SAVE_AREA_TOP_LEFT = Memory::VRAM_VADDR + 0x273000;
|
||||
constexpr VAddr FRAMEBUFFER_SAVE_AREA_TOP_RIGHT = Memory::VRAM_VADDR + 0x2B9800;
|
||||
constexpr VAddr FRAMEBUFFER_SAVE_AREA_BOTTOM = Memory::VRAM_VADDR + 0x4C7800;
|
||||
|
||||
class GSP_GPU;
|
||||
|
||||
class SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
||||
|
Loading…
Reference in New Issue
Block a user