video_core, core: Move pixel download to its own thread
This uses the mailbox model to move pixel downloading to its own thread, eliminating Nvidia's warnings and (possibly) making use of GPU copy engine. To achieve this, we created a new mailbox type that is different from the presentation mailbox in that it never discards a rendered frame. Also, I tweaked the projection matrix thing so that it can just draw the frame upside down instead of having the CPU flip it.
This commit is contained in:
parent
5b54a99f96
commit
06a0d86e9c
7 changed files with 324 additions and 169 deletions
|
@ -308,6 +308,12 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
|
||||||
Service::Init(*this);
|
Service::Init(*this);
|
||||||
GDBStub::Init();
|
GDBStub::Init();
|
||||||
|
|
||||||
|
#ifdef ENABLE_FFMPEG_VIDEO_DUMPER
|
||||||
|
video_dumper = std::make_unique<VideoDumper::FFmpegBackend>();
|
||||||
|
#else
|
||||||
|
video_dumper = std::make_unique<VideoDumper::NullBackend>();
|
||||||
|
#endif
|
||||||
|
|
||||||
VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory);
|
VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory);
|
||||||
if (result != VideoCore::ResultStatus::Success) {
|
if (result != VideoCore::ResultStatus::Success) {
|
||||||
switch (result) {
|
switch (result) {
|
||||||
|
@ -320,12 +326,6 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_FFMPEG_VIDEO_DUMPER
|
|
||||||
video_dumper = std::make_unique<VideoDumper::FFmpegBackend>();
|
|
||||||
#else
|
|
||||||
video_dumper = std::make_unique<VideoDumper::NullBackend>();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
LOG_DEBUG(Core, "Initialized OK");
|
LOG_DEBUG(Core, "Initialized OK");
|
||||||
|
|
||||||
initalized = true;
|
initalized = true;
|
||||||
|
|
|
@ -8,17 +8,7 @@
|
||||||
namespace VideoDumper {
|
namespace VideoDumper {
|
||||||
|
|
||||||
VideoFrame::VideoFrame(std::size_t width_, std::size_t height_, u8* data_)
|
VideoFrame::VideoFrame(std::size_t width_, std::size_t height_, u8* data_)
|
||||||
: width(width_), height(height_), stride(width * 4), data(width * height * 4) {
|
: width(width_), height(height_), stride(width * 4), data(data_, data_ + width * height * 4) {}
|
||||||
// While copying, rotate the image to put the pixels in correct order
|
|
||||||
// (As OpenGL returns pixel data starting from the lowest position)
|
|
||||||
for (std::size_t i = 0; i < height; i++) {
|
|
||||||
for (std::size_t j = 0; j < width; j++) {
|
|
||||||
for (std::size_t k = 0; k < 4; k++) {
|
|
||||||
data[i * stride + j * 4 + k] = data_[(height - i - 1) * stride + j * 4 + k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Backend::~Backend() = default;
|
Backend::~Backend() = default;
|
||||||
NullBackend::~NullBackend() = default;
|
NullBackend::~NullBackend() = default;
|
||||||
|
|
|
@ -23,6 +23,8 @@ add_library(video_core STATIC
|
||||||
regs_texturing.h
|
regs_texturing.h
|
||||||
renderer_base.cpp
|
renderer_base.cpp
|
||||||
renderer_base.h
|
renderer_base.h
|
||||||
|
renderer_opengl/frame_dumper_opengl.cpp
|
||||||
|
renderer_opengl/frame_dumper_opengl.h
|
||||||
renderer_opengl/gl_rasterizer.cpp
|
renderer_opengl/gl_rasterizer.cpp
|
||||||
renderer_opengl/gl_rasterizer.h
|
renderer_opengl/gl_rasterizer.h
|
||||||
renderer_opengl/gl_rasterizer_cache.cpp
|
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(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/renderer_opengl/renderer_opengl.h"
|
||||||
#include "video_core/video_core.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 {
|
namespace OpenGL {
|
||||||
|
|
||||||
// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
|
// 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);
|
std::queue<Frontend::Frame*>().swap(free_queue);
|
||||||
present_queue.clear();
|
present_queue.clear();
|
||||||
present_cv.notify_all();
|
present_cv.notify_all();
|
||||||
|
free_cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override {
|
void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override {
|
||||||
|
@ -88,7 +75,7 @@ public:
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
|
glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||||
frame->color.handle);
|
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!");
|
LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
|
||||||
}
|
}
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
|
||||||
|
@ -114,7 +101,7 @@ public:
|
||||||
state.Apply();
|
state.Apply();
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||||
frame->color.handle);
|
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!");
|
LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
|
||||||
}
|
}
|
||||||
prev_state.Apply();
|
prev_state.Apply();
|
||||||
|
@ -144,19 +131,12 @@ public:
|
||||||
present_cv.notify_one();
|
present_cv.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
|
// This is virtual as it is to be overriden in OGLVideoDumpingMailbox below.
|
||||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
virtual void LoadPresentFrame() {
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// free the previous frame and add it back to the free queue
|
// free the previous frame and add it back to the free queue
|
||||||
if (previous_frame) {
|
if (previous_frame) {
|
||||||
free_queue.push(previous_frame);
|
free_queue.push(previous_frame);
|
||||||
|
free_cv.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
// the newest entries are pushed to the front of the queue
|
// the newest entries are pushed to the front of the queue
|
||||||
|
@ -168,8 +148,72 @@ public:
|
||||||
}
|
}
|
||||||
present_queue.clear();
|
present_queue.clear();
|
||||||
previous_frame = frame;
|
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;
|
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"(
|
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
|
* The projection part of the matrix is trivial, hence these operations are represented
|
||||||
* by a 3x2 matrix.
|
* 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
|
std::array<GLfloat, 3 * 2> matrix; // Laid out in column-major order
|
||||||
|
|
||||||
|
// Last matrix row is implicitly assumed to be [0, 0, 1].
|
||||||
|
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
|
// clang-format off
|
||||||
matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
|
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;
|
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
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
return matrix;
|
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>();
|
window.mailbox = std::make_unique<OGLTextureMailbox>();
|
||||||
|
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
|
||||||
}
|
}
|
||||||
|
|
||||||
RendererOpenGL::~RendererOpenGL() = default;
|
RendererOpenGL::~RendererOpenGL() = default;
|
||||||
|
@ -310,56 +368,14 @@ void RendererOpenGL::SwapBuffers() {
|
||||||
|
|
||||||
RenderScreenshot();
|
RenderScreenshot();
|
||||||
|
|
||||||
RenderVideoDumping();
|
|
||||||
|
|
||||||
const auto& layout = render_window.GetFramebufferLayout();
|
const auto& layout = render_window.GetFramebufferLayout();
|
||||||
|
RenderToMailbox(layout, render_window.mailbox, false);
|
||||||
|
|
||||||
Frontend::Frame* frame;
|
if (frame_dumper.IsDumping()) {
|
||||||
{
|
RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
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();
|
Core::System::GetInstance().perf_stats->EndSystemFrame();
|
||||||
|
|
||||||
|
@ -395,7 +411,7 @@ void RendererOpenGL::RenderScreenshot() {
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||||
renderbuffer);
|
renderbuffer);
|
||||||
|
|
||||||
DrawScreens(layout);
|
DrawScreens(layout, false);
|
||||||
|
|
||||||
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||||
VideoCore::g_screenshot_bits);
|
VideoCore::g_screenshot_bits);
|
||||||
|
@ -448,33 +464,54 @@ void RendererOpenGL::PrepareRendertarget() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererOpenGL::RenderVideoDumping() {
|
void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
|
||||||
if (cleanup_video_dumping.exchange(false)) {
|
std::unique_ptr<Frontend::TextureMailbox>& mailbox,
|
||||||
ReleaseVideoDumpingGLObjects();
|
bool flipped) {
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Core::System::GetInstance().VideoDumper().IsDumping()) {
|
// delete the draw fence if the frame wasn't presented
|
||||||
if (prepare_video_dumping.exchange(false)) {
|
if (frame->render_fence) {
|
||||||
InitVideoDumpingGLObjects();
|
glDeleteSync(frame->render_fence);
|
||||||
|
frame->render_fence = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& layout = Core::System::GetInstance().VideoDumper().GetLayout();
|
// wait for the presentation to be done
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, frame_dumping_framebuffer.handle);
|
if (frame->present_fence) {
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_dumping_framebuffer.handle);
|
glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
|
||||||
DrawScreens(layout);
|
glDeleteSync(frame->present_fence);
|
||||||
|
frame->present_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);
|
MICROPROFILE_SCOPE(OpenGL_RenderFrame);
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, frame_dumping_pbos[next_pbo].handle);
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
GLubyte* pixels = static_cast<GLubyte*>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
|
GLuint render_texture = frame->color.handle;
|
||||||
VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels};
|
state.draw.draw_framebuffer = frame->render.handle;
|
||||||
Core::System::GetInstance().VideoDumper().AddVideoFrame(frame_data);
|
state.Apply();
|
||||||
|
DrawScreens(layout, flipped);
|
||||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
// Create a fence for the frontend to wait on and swap this frame to OffTex
|
||||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
current_pbo = (current_pbo + 1) % 2;
|
glFlush();
|
||||||
next_pbo = (current_pbo + 1) % 2;
|
mailbox->ReleaseRenderFrame(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -885,7 +922,7 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
|
||||||
/**
|
/**
|
||||||
* Draws the emulated screens to the emulator window.
|
* 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)) {
|
if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
|
||||||
// Update background color before drawing
|
// Update background color before drawing
|
||||||
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
|
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
|
// Set projection matrix
|
||||||
std::array<GLfloat, 3 * 2> ortho_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());
|
glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data());
|
||||||
|
|
||||||
// Bind texture in Texture Unit 0
|
// Bind texture in Texture Unit 0
|
||||||
|
@ -1051,41 +1088,11 @@ void RendererOpenGL::TryPresent(int timeout_ms) {
|
||||||
void RendererOpenGL::UpdateFramerate() {}
|
void RendererOpenGL::UpdateFramerate() {}
|
||||||
|
|
||||||
void RendererOpenGL::PrepareVideoDumping() {
|
void RendererOpenGL::PrepareVideoDumping() {
|
||||||
prepare_video_dumping = true;
|
frame_dumper.StartDumping();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererOpenGL::CleanupVideoDumping() {
|
void RendererOpenGL::CleanupVideoDumping() {
|
||||||
cleanup_video_dumping = true;
|
frame_dumper.StopDumping();
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* GetSource(GLenum source) {
|
static const char* GetSource(GLenum source) {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
#include "core/hw/gpu.h"
|
#include "core/hw/gpu.h"
|
||||||
#include "video_core/renderer_base.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_resource_manager.h"
|
||||||
#include "video_core/renderer_opengl/gl_state.h"
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
|
|
||||||
|
@ -17,6 +18,20 @@ namespace Layout {
|
||||||
struct FramebufferLayout;
|
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 {
|
namespace OpenGL {
|
||||||
|
|
||||||
/// Structure used for storing information about the textures for each 3DS screen
|
/// Structure used for storing information about the textures for each 3DS screen
|
||||||
|
@ -72,10 +87,11 @@ private:
|
||||||
void ReloadShader();
|
void ReloadShader();
|
||||||
void PrepareRendertarget();
|
void PrepareRendertarget();
|
||||||
void RenderScreenshot();
|
void RenderScreenshot();
|
||||||
void RenderVideoDumping();
|
void RenderToMailbox(const Layout::FramebufferLayout& layout,
|
||||||
|
std::unique_ptr<Frontend::TextureMailbox>& mailbox, bool flipped);
|
||||||
void ConfigureFramebufferTexture(TextureInfo& texture,
|
void ConfigureFramebufferTexture(TextureInfo& texture,
|
||||||
const GPU::Regs::FramebufferConfig& framebuffer);
|
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 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 DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||||
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
|
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
|
||||||
|
@ -91,9 +107,6 @@ private:
|
||||||
// Fills active OpenGL texture with the given RGB color.
|
// Fills active OpenGL texture with the given RGB color.
|
||||||
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture);
|
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture);
|
||||||
|
|
||||||
void InitVideoDumpingGLObjects();
|
|
||||||
void ReleaseVideoDumpingGLObjects();
|
|
||||||
|
|
||||||
OpenGLState state;
|
OpenGLState state;
|
||||||
|
|
||||||
// OpenGL object IDs
|
// OpenGL object IDs
|
||||||
|
@ -120,19 +133,7 @@ private:
|
||||||
GLuint attrib_position;
|
GLuint attrib_position;
|
||||||
GLuint attrib_tex_coord;
|
GLuint attrib_tex_coord;
|
||||||
|
|
||||||
// Frame dumping
|
FrameDumperOpenGL frame_dumper;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace OpenGL
|
} // namespace OpenGL
|
||||||
|
|
Loading…
Reference in a new issue