Merge pull request #5083 from zhaowenlan1779/video-dumping-update
video_core, citra_qt: Video dumping updates
This commit is contained in:
commit
9c7da35382
35 changed files with 2085 additions and 309 deletions
|
@ -23,6 +23,8 @@ add_library(video_core STATIC
|
|||
regs_texturing.h
|
||||
renderer_base.cpp
|
||||
renderer_base.h
|
||||
renderer_opengl/frame_dumper_opengl.cpp
|
||||
renderer_opengl/frame_dumper_opengl.h
|
||||
renderer_opengl/gl_rasterizer.cpp
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_rasterizer_cache.cpp
|
||||
|
|
98
src/video_core/renderer_opengl/frame_dumper_opengl.cpp
Normal file
98
src/video_core/renderer_opengl/frame_dumper_opengl.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
FrameDumperOpenGL::FrameDumperOpenGL(VideoDumper::Backend& video_dumper_,
|
||||
Frontend::EmuWindow& emu_window)
|
||||
: video_dumper(video_dumper_), context(emu_window.CreateSharedContext()) {}
|
||||
|
||||
FrameDumperOpenGL::~FrameDumperOpenGL() {
|
||||
if (present_thread.joinable())
|
||||
present_thread.join();
|
||||
}
|
||||
|
||||
bool FrameDumperOpenGL::IsDumping() const {
|
||||
return video_dumper.IsDumping();
|
||||
}
|
||||
|
||||
Layout::FramebufferLayout FrameDumperOpenGL::GetLayout() const {
|
||||
return video_dumper.GetLayout();
|
||||
}
|
||||
|
||||
void FrameDumperOpenGL::StartDumping() {
|
||||
if (present_thread.joinable())
|
||||
present_thread.join();
|
||||
|
||||
present_thread = std::thread(&FrameDumperOpenGL::PresentLoop, this);
|
||||
}
|
||||
|
||||
void FrameDumperOpenGL::StopDumping() {
|
||||
stop_requested.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FrameDumperOpenGL::PresentLoop() {
|
||||
Frontend::ScopeAcquireContext scope{*context};
|
||||
InitializeOpenGLObjects();
|
||||
|
||||
const auto& layout = GetLayout();
|
||||
while (!stop_requested.exchange(false)) {
|
||||
auto frame = mailbox->TryGetPresentFrame(200);
|
||||
if (!frame) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame->color_reloaded) {
|
||||
LOG_DEBUG(Render_OpenGL, "Reloading present frame");
|
||||
mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
|
||||
}
|
||||
glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[current_pbo].handle);
|
||||
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
|
||||
|
||||
// Insert fence for the main thread to block on
|
||||
frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
|
||||
// Bind the previous PBO and read the pixels
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[next_pbo].handle);
|
||||
GLubyte* pixels = static_cast<GLubyte*>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
|
||||
VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels};
|
||||
video_dumper.AddVideoFrame(std::move(frame_data));
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
current_pbo = (current_pbo + 1) % 2;
|
||||
next_pbo = (current_pbo + 1) % 2;
|
||||
}
|
||||
|
||||
CleanupOpenGLObjects();
|
||||
}
|
||||
|
||||
void FrameDumperOpenGL::InitializeOpenGLObjects() {
|
||||
const auto& layout = GetLayout();
|
||||
for (auto& buffer : pbos) {
|
||||
buffer.Create();
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer.handle);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, layout.width * layout.height * 4, nullptr,
|
||||
GL_STREAM_READ);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void FrameDumperOpenGL::CleanupOpenGLObjects() {
|
||||
for (auto& buffer : pbos) {
|
||||
buffer.Release();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
57
src/video_core/renderer_opengl/frame_dumper_opengl.h
Normal file
57
src/video_core/renderer_opengl/frame_dumper_opengl.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include "core/dumping/backend.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
|
||||
namespace Frontend {
|
||||
class EmuWindow;
|
||||
class GraphicsContext;
|
||||
class TextureMailbox;
|
||||
} // namespace Frontend
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class RendererOpenGL;
|
||||
|
||||
/**
|
||||
* This is the 'presentation' part in frame dumping.
|
||||
* Processes frames/textures sent to its mailbox, downloads the pixels and sends the data
|
||||
* to the video encoding backend.
|
||||
*/
|
||||
class FrameDumperOpenGL {
|
||||
public:
|
||||
explicit FrameDumperOpenGL(VideoDumper::Backend& video_dumper, Frontend::EmuWindow& emu_window);
|
||||
~FrameDumperOpenGL();
|
||||
|
||||
bool IsDumping() const;
|
||||
Layout::FramebufferLayout GetLayout() const;
|
||||
void StartDumping();
|
||||
void StopDumping();
|
||||
|
||||
std::unique_ptr<Frontend::TextureMailbox> mailbox;
|
||||
|
||||
private:
|
||||
void InitializeOpenGLObjects();
|
||||
void CleanupOpenGLObjects();
|
||||
void PresentLoop();
|
||||
|
||||
VideoDumper::Backend& video_dumper;
|
||||
std::unique_ptr<Frontend::GraphicsContext> context;
|
||||
std::thread present_thread;
|
||||
std::atomic_bool stop_requested{false};
|
||||
|
||||
// PBOs used to dump frames faster
|
||||
std::array<OGLBuffer, 2> pbos;
|
||||
GLuint current_pbo = 1;
|
||||
GLuint next_pbo = 0;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
|
@ -34,20 +34,6 @@
|
|||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
struct Frame {
|
||||
u32 width{}; /// Width of the frame (to detect resize)
|
||||
u32 height{}; /// Height of the frame
|
||||
bool color_reloaded = false; /// Texture attachment was recreated (ie: resized)
|
||||
OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
|
||||
OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
|
||||
OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
|
||||
GLsync render_fence{}; /// Fence created on the render thread
|
||||
GLsync present_fence{}; /// Fence created on the presentation thread
|
||||
};
|
||||
} // namespace Frontend
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
|
||||
|
@ -78,6 +64,7 @@ public:
|
|||
std::queue<Frontend::Frame*>().swap(free_queue);
|
||||
present_queue.clear();
|
||||
present_cv.notify_all();
|
||||
free_cv.notify_all();
|
||||
}
|
||||
|
||||
void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override {
|
||||
|
@ -88,7 +75,7 @@ public:
|
|||
glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||
frame->color.handle);
|
||||
if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
|
||||
}
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
|
||||
|
@ -114,7 +101,7 @@ public:
|
|||
state.Apply();
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||
frame->color.handle);
|
||||
if (!glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
|
||||
}
|
||||
prev_state.Apply();
|
||||
|
@ -144,19 +131,12 @@ public:
|
|||
present_cv.notify_one();
|
||||
}
|
||||
|
||||
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
|
||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
||||
// wait for new entries in the present_queue
|
||||
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
|
||||
[&] { return !present_queue.empty(); });
|
||||
if (present_queue.empty()) {
|
||||
// timed out waiting for a frame to draw so return the previous frame
|
||||
return previous_frame;
|
||||
}
|
||||
|
||||
// This is virtual as it is to be overriden in OGLVideoDumpingMailbox below.
|
||||
virtual void LoadPresentFrame() {
|
||||
// free the previous frame and add it back to the free queue
|
||||
if (previous_frame) {
|
||||
free_queue.push(previous_frame);
|
||||
free_cv.notify_one();
|
||||
}
|
||||
|
||||
// the newest entries are pushed to the front of the queue
|
||||
|
@ -168,8 +148,72 @@ public:
|
|||
}
|
||||
present_queue.clear();
|
||||
previous_frame = frame;
|
||||
}
|
||||
|
||||
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
|
||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
||||
// wait for new entries in the present_queue
|
||||
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
|
||||
[&] { return !present_queue.empty(); });
|
||||
if (present_queue.empty()) {
|
||||
// timed out waiting for a frame to draw so return the previous frame
|
||||
return previous_frame;
|
||||
}
|
||||
|
||||
LoadPresentFrame();
|
||||
return previous_frame;
|
||||
}
|
||||
};
|
||||
|
||||
/// This mailbox is different in that it will never discard rendered frames
|
||||
class OGLVideoDumpingMailbox : public OGLTextureMailbox {
|
||||
public:
|
||||
Frontend::Frame* GetRenderFrame() override {
|
||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
||||
|
||||
// If theres no free frames, we will wait until one shows up
|
||||
if (free_queue.empty()) {
|
||||
free_cv.wait(lock, [&] { return !free_queue.empty(); });
|
||||
}
|
||||
|
||||
if (free_queue.empty()) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Could not get free frame");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Frontend::Frame* frame = free_queue.front();
|
||||
free_queue.pop();
|
||||
return frame;
|
||||
}
|
||||
|
||||
void LoadPresentFrame() override {
|
||||
// free the previous frame and add it back to the free queue
|
||||
if (previous_frame) {
|
||||
free_queue.push(previous_frame);
|
||||
free_cv.notify_one();
|
||||
}
|
||||
|
||||
Frontend::Frame* frame = present_queue.back();
|
||||
present_queue.pop_back();
|
||||
previous_frame = frame;
|
||||
|
||||
// Do not remove entries from the present_queue, as video dumping would require
|
||||
// that we preserve all frames
|
||||
}
|
||||
|
||||
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
|
||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
||||
// wait for new entries in the present_queue
|
||||
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
|
||||
[&] { return !present_queue.empty(); });
|
||||
if (present_queue.empty()) {
|
||||
// timed out waiting for a frame
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LoadPresentFrame();
|
||||
return previous_frame;
|
||||
}
|
||||
};
|
||||
|
||||
static const char vertex_shader[] = R"(
|
||||
|
@ -278,21 +322,35 @@ struct ScreenRectVertex {
|
|||
*
|
||||
* The projection part of the matrix is trivial, hence these operations are represented
|
||||
* by a 3x2 matrix.
|
||||
*
|
||||
* @param flipped Whether the frame should be flipped upside down.
|
||||
*/
|
||||
static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, const float height) {
|
||||
static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, const float height,
|
||||
bool flipped) {
|
||||
|
||||
std::array<GLfloat, 3 * 2> matrix; // Laid out in column-major order
|
||||
|
||||
// clang-format off
|
||||
matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
|
||||
matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f;
|
||||
// Last matrix row is implicitly assumed to be [0, 0, 1].
|
||||
// clang-format on
|
||||
if (flipped) {
|
||||
// clang-format off
|
||||
matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
|
||||
matrix[1] = 0.f; matrix[3] = 2.f / height; matrix[5] = -1.f;
|
||||
// clang-format on
|
||||
} else {
|
||||
// clang-format off
|
||||
matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
|
||||
matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f;
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} {
|
||||
RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window)
|
||||
: RendererBase{window}, frame_dumper(Core::System::GetInstance().VideoDumper(), window) {
|
||||
|
||||
window.mailbox = std::make_unique<OGLTextureMailbox>();
|
||||
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
|
||||
}
|
||||
|
||||
RendererOpenGL::~RendererOpenGL() = default;
|
||||
|
@ -310,56 +368,14 @@ void RendererOpenGL::SwapBuffers() {
|
|||
|
||||
RenderScreenshot();
|
||||
|
||||
RenderVideoDumping();
|
||||
|
||||
const auto& layout = render_window.GetFramebufferLayout();
|
||||
RenderToMailbox(layout, render_window.mailbox, false);
|
||||
|
||||
Frontend::Frame* frame;
|
||||
{
|
||||
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
|
||||
|
||||
frame = render_window.mailbox->GetRenderFrame();
|
||||
|
||||
// Clean up sync objects before drawing
|
||||
|
||||
// INTEL driver workaround. We can't delete the previous render sync object until we are
|
||||
// sure that the presentation is done
|
||||
if (frame->present_fence) {
|
||||
glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
|
||||
}
|
||||
|
||||
// delete the draw fence if the frame wasn't presented
|
||||
if (frame->render_fence) {
|
||||
glDeleteSync(frame->render_fence);
|
||||
frame->render_fence = 0;
|
||||
}
|
||||
|
||||
// wait for the presentation to be done
|
||||
if (frame->present_fence) {
|
||||
glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(frame->present_fence);
|
||||
frame->present_fence = 0;
|
||||
}
|
||||
if (frame_dumper.IsDumping()) {
|
||||
RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true);
|
||||
}
|
||||
|
||||
{
|
||||
MICROPROFILE_SCOPE(OpenGL_RenderFrame);
|
||||
// Recreate the frame if the size of the window has changed
|
||||
if (layout.width != frame->width || layout.height != frame->height) {
|
||||
LOG_DEBUG(Render_OpenGL, "Reloading render frame");
|
||||
render_window.mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
|
||||
}
|
||||
|
||||
GLuint render_texture = frame->color.handle;
|
||||
state.draw.draw_framebuffer = frame->render.handle;
|
||||
state.Apply();
|
||||
DrawScreens(layout);
|
||||
// Create a fence for the frontend to wait on and swap this frame to OffTex
|
||||
frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
render_window.mailbox->ReleaseRenderFrame(frame);
|
||||
m_current_frame++;
|
||||
}
|
||||
m_current_frame++;
|
||||
|
||||
Core::System::GetInstance().perf_stats->EndSystemFrame();
|
||||
|
||||
|
@ -395,7 +411,7 @@ void RendererOpenGL::RenderScreenshot() {
|
|||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||
renderbuffer);
|
||||
|
||||
DrawScreens(layout);
|
||||
DrawScreens(layout, false);
|
||||
|
||||
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
VideoCore::g_screenshot_bits);
|
||||
|
@ -448,33 +464,54 @@ void RendererOpenGL::PrepareRendertarget() {
|
|||
}
|
||||
}
|
||||
|
||||
void RendererOpenGL::RenderVideoDumping() {
|
||||
if (cleanup_video_dumping.exchange(false)) {
|
||||
ReleaseVideoDumpingGLObjects();
|
||||
}
|
||||
void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
|
||||
std::unique_ptr<Frontend::TextureMailbox>& mailbox,
|
||||
bool flipped) {
|
||||
|
||||
if (Core::System::GetInstance().VideoDumper().IsDumping()) {
|
||||
if (prepare_video_dumping.exchange(false)) {
|
||||
InitVideoDumpingGLObjects();
|
||||
Frontend::Frame* frame;
|
||||
{
|
||||
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
|
||||
|
||||
frame = mailbox->GetRenderFrame();
|
||||
|
||||
// Clean up sync objects before drawing
|
||||
|
||||
// INTEL driver workaround. We can't delete the previous render sync object until we are
|
||||
// sure that the presentation is done
|
||||
if (frame->present_fence) {
|
||||
glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
|
||||
}
|
||||
|
||||
const auto& layout = Core::System::GetInstance().VideoDumper().GetLayout();
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, frame_dumping_framebuffer.handle);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_dumping_framebuffer.handle);
|
||||
DrawScreens(layout);
|
||||
// delete the draw fence if the frame wasn't presented
|
||||
if (frame->render_fence) {
|
||||
glDeleteSync(frame->render_fence);
|
||||
frame->render_fence = 0;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, frame_dumping_pbos[current_pbo].handle);
|
||||
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, frame_dumping_pbos[next_pbo].handle);
|
||||
// wait for the presentation to be done
|
||||
if (frame->present_fence) {
|
||||
glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(frame->present_fence);
|
||||
frame->present_fence = 0;
|
||||
}
|
||||
}
|
||||
|
||||
GLubyte* pixels = static_cast<GLubyte*>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
|
||||
VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels};
|
||||
Core::System::GetInstance().VideoDumper().AddVideoFrame(frame_data);
|
||||
{
|
||||
MICROPROFILE_SCOPE(OpenGL_RenderFrame);
|
||||
// Recreate the frame if the size of the window has changed
|
||||
if (layout.width != frame->width || layout.height != frame->height) {
|
||||
LOG_DEBUG(Render_OpenGL, "Reloading render frame");
|
||||
mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
|
||||
}
|
||||
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
current_pbo = (current_pbo + 1) % 2;
|
||||
next_pbo = (current_pbo + 1) % 2;
|
||||
GLuint render_texture = frame->color.handle;
|
||||
state.draw.draw_framebuffer = frame->render.handle;
|
||||
state.Apply();
|
||||
DrawScreens(layout, flipped);
|
||||
// Create a fence for the frontend to wait on and swap this frame to OffTex
|
||||
frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
mailbox->ReleaseRenderFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -885,7 +922,7 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
|
|||
/**
|
||||
* Draws the emulated screens to the emulator window.
|
||||
*/
|
||||
void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) {
|
||||
void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool flipped) {
|
||||
if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
|
||||
// Update background color before drawing
|
||||
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
|
||||
|
@ -912,7 +949,7 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) {
|
|||
|
||||
// Set projection matrix
|
||||
std::array<GLfloat, 3 * 2> ortho_matrix =
|
||||
MakeOrthographicMatrix((float)layout.width, (float)layout.height);
|
||||
MakeOrthographicMatrix((float)layout.width, (float)layout.height, flipped);
|
||||
glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data());
|
||||
|
||||
// Bind texture in Texture Unit 0
|
||||
|
@ -1051,41 +1088,11 @@ void RendererOpenGL::TryPresent(int timeout_ms) {
|
|||
void RendererOpenGL::UpdateFramerate() {}
|
||||
|
||||
void RendererOpenGL::PrepareVideoDumping() {
|
||||
prepare_video_dumping = true;
|
||||
frame_dumper.StartDumping();
|
||||
}
|
||||
|
||||
void RendererOpenGL::CleanupVideoDumping() {
|
||||
cleanup_video_dumping = true;
|
||||
}
|
||||
|
||||
void RendererOpenGL::InitVideoDumpingGLObjects() {
|
||||
const auto& layout = Core::System::GetInstance().VideoDumper().GetLayout();
|
||||
|
||||
frame_dumping_framebuffer.Create();
|
||||
glGenRenderbuffers(1, &frame_dumping_renderbuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, frame_dumping_renderbuffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_dumping_framebuffer.handle);
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||
frame_dumping_renderbuffer);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
for (auto& buffer : frame_dumping_pbos) {
|
||||
buffer.Create();
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer.handle);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, layout.width * layout.height * 4, nullptr,
|
||||
GL_STREAM_READ);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void RendererOpenGL::ReleaseVideoDumpingGLObjects() {
|
||||
frame_dumping_framebuffer.Release();
|
||||
glDeleteRenderbuffers(1, &frame_dumping_renderbuffer);
|
||||
|
||||
for (auto& buffer : frame_dumping_pbos) {
|
||||
buffer.Release();
|
||||
}
|
||||
frame_dumper.StopDumping();
|
||||
}
|
||||
|
||||
static const char* GetSource(GLenum source) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "common/math_util.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
||||
|
@ -17,6 +18,20 @@ namespace Layout {
|
|||
struct FramebufferLayout;
|
||||
}
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
struct Frame {
|
||||
u32 width{}; /// Width of the frame (to detect resize)
|
||||
u32 height{}; /// Height of the frame
|
||||
bool color_reloaded = false; /// Texture attachment was recreated (ie: resized)
|
||||
OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
|
||||
OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
|
||||
OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
|
||||
GLsync render_fence{}; /// Fence created on the render thread
|
||||
GLsync present_fence{}; /// Fence created on the presentation thread
|
||||
};
|
||||
} // namespace Frontend
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
/// Structure used for storing information about the textures for each 3DS screen
|
||||
|
@ -72,10 +87,11 @@ private:
|
|||
void ReloadShader();
|
||||
void PrepareRendertarget();
|
||||
void RenderScreenshot();
|
||||
void RenderVideoDumping();
|
||||
void RenderToMailbox(const Layout::FramebufferLayout& layout,
|
||||
std::unique_ptr<Frontend::TextureMailbox>& mailbox, bool flipped);
|
||||
void ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
const GPU::Regs::FramebufferConfig& framebuffer);
|
||||
void DrawScreens(const Layout::FramebufferLayout& layout);
|
||||
void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
|
||||
void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||
void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
|
||||
|
@ -91,9 +107,6 @@ private:
|
|||
// Fills active OpenGL texture with the given RGB color.
|
||||
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture);
|
||||
|
||||
void InitVideoDumpingGLObjects();
|
||||
void ReleaseVideoDumpingGLObjects();
|
||||
|
||||
OpenGLState state;
|
||||
|
||||
// OpenGL object IDs
|
||||
|
@ -120,19 +133,7 @@ private:
|
|||
GLuint attrib_position;
|
||||
GLuint attrib_tex_coord;
|
||||
|
||||
// Frame dumping
|
||||
OGLFramebuffer frame_dumping_framebuffer;
|
||||
GLuint frame_dumping_renderbuffer;
|
||||
|
||||
// Whether prepare/cleanup video dumping has been requested.
|
||||
// They will be executed on next frame.
|
||||
std::atomic_bool prepare_video_dumping = false;
|
||||
std::atomic_bool cleanup_video_dumping = false;
|
||||
|
||||
// PBOs used to dump frames faster
|
||||
std::array<OGLBuffer, 2> frame_dumping_pbos;
|
||||
GLuint current_pbo = 1;
|
||||
GLuint next_pbo = 0;
|
||||
FrameDumperOpenGL frame_dumper;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue