Massive refactoring of xenia::ui and GL swap behavior.

This seems to dramatically improve most games (especially with
--vsync=false), though it may cause swap issues with others.
New code should be easier to port, and enables elemental-forms to be
drawn for any emulator UI.
This commit is contained in:
Ben Vanik
2015-07-12 22:03:47 -07:00
parent e69c7b00a8
commit 15c17459be
61 changed files with 1994 additions and 2623 deletions

View File

@@ -71,8 +71,6 @@ CommandProcessor::CommandProcessor(GL4GraphicsSystem* graphics_system)
active_pixel_shader_(nullptr),
active_framebuffer_(nullptr),
last_framebuffer_texture_(0),
last_swap_width_(0),
last_swap_height_(0),
point_list_geometry_program_(0),
rect_list_geometry_program_(0),
quad_list_geometry_program_(0),
@@ -83,7 +81,7 @@ CommandProcessor::CommandProcessor(GL4GraphicsSystem* graphics_system)
CommandProcessor::~CommandProcessor() { CloseHandle(write_ptr_index_event_); }
bool CommandProcessor::Initialize(
std::unique_ptr<xe::ui::gl::GLContext> context) {
std::unique_ptr<xe::ui::GraphicsContext> context) {
context_ = std::move(context);
worker_running_ = true;
@@ -197,7 +195,7 @@ void CommandProcessor::WorkerThreadMain() {
// We've run out of commands to execute.
// We spin here waiting for new ones, as the overhead of waiting on our
// event is too high.
// PrepareForWait();
PrepareForWait();
do {
// TODO(benvanik): if we go longer than Nms, switch to waiting?
// It'll keep us from burning power.
@@ -209,7 +207,7 @@ void CommandProcessor::WorkerThreadMain() {
} while (worker_running_ && pending_fns_.empty() &&
(write_ptr_index == 0xBAADF00D ||
read_ptr_index_ == write_ptr_index));
// ReturnFromWait();
ReturnFromWait();
if (!worker_running_ || !pending_fns_.empty()) {
continue;
}
@@ -576,18 +574,50 @@ void CommandProcessor::ReturnFromWait() {
}
}
void CommandProcessor::IssueSwap() {
IssueSwap(last_swap_width_, last_swap_height_);
}
void CommandProcessor::IssueSwap(uint32_t frontbuffer_width,
uint32_t frontbuffer_height) {
if (!swap_handler_) {
SCOPE_profile_cpu_f("gpu");
if (!swap_request_handler_) {
return;
}
auto& regs = *register_file_;
SwapParameters swap_params;
// If there was a swap pending we drop it on the floor.
// This prevents the display from pulling the backbuffer out from under us.
// If we skip a lot then we may need to buffer more, but as the display
// thread should be fairly idle that shouldn't happen.
if (!FLAGS_vsync) {
std::lock_guard<xe::mutex> lock(swap_state_.mutex);
if (swap_state_.pending) {
swap_state_.pending = false;
// TODO(benvanik): frame skip counter.
XELOGW("Skipped frame!");
}
} else {
// Spin until no more pending swap.
while (true) {
{
std::lock_guard<xe::mutex> lock(swap_state_.mutex);
if (!swap_state_.pending) {
break;
}
}
xe::threading::MaybeYield();
}
}
// One-time initialization.
// TODO(benvanik): move someplace more sane?
if (!swap_state_.front_buffer_texture) {
std::lock_guard<xe::mutex> lock(swap_state_.mutex);
swap_state_.width = frontbuffer_width;
swap_state_.height = frontbuffer_height;
glCreateTextures(GL_TEXTURE_2D, 1, &swap_state_.front_buffer_texture);
glCreateTextures(GL_TEXTURE_2D, 1, &swap_state_.back_buffer_texture);
glTextureStorage2D(swap_state_.front_buffer_texture, 1, GL_RGBA8,
swap_state_.width, swap_state_.height);
glTextureStorage2D(swap_state_.back_buffer_texture, 1, GL_RGBA8,
swap_state_.width, swap_state_.height);
}
// Lookup the framebuffer in the recently-resolved list.
// TODO(benvanik): make this much more sophisticated.
@@ -595,20 +625,34 @@ void CommandProcessor::IssueSwap(uint32_t frontbuffer_width,
// TODO(benvanik): handle dirty cases (resolved to sysmem, touched).
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// HACK: just use whatever our current framebuffer is.
swap_params.framebuffer_texture = last_framebuffer_texture_;
/*swap_params.framebuffer_texture = active_framebuffer_
GLuint framebuffer_texture = last_framebuffer_texture_;
/*GLuint framebuffer_texture = active_framebuffer_
? active_framebuffer_->color_targets[0]
: last_framebuffer_texture_;*/
// Frontbuffer dimensions, if valid.
swap_params.x = 0;
swap_params.y = 0;
swap_params.width = frontbuffer_width ? frontbuffer_width : 1280;
swap_params.height = frontbuffer_height ? frontbuffer_height : 720;
// Copy the the given framebuffer to the current backbuffer.
Rect2D src_rect(0, 0, frontbuffer_width ? frontbuffer_width : 1280,
frontbuffer_height ? frontbuffer_height : 720);
Rect2D dest_rect(0, 0, swap_state_.width, swap_state_.height);
reinterpret_cast<xe::ui::gl::GLContext*>(context_.get())
->blitter()
->CopyColorTexture2D(framebuffer_texture, src_rect,
swap_state_.back_buffer_texture, dest_rect,
GL_LINEAR);
PrepareForWait();
swap_handler_(swap_params);
ReturnFromWait();
// Need to finish to be sure the other context sees the right data.
// TODO(benvanik): prevent this? fences?
glFinish();
{
// Set pending so that the display will swap the next time it can.
std::lock_guard<xe::mutex> lock(swap_state_.mutex);
swap_state_.pending = true;
}
// Notify the display a swap is pending so that our changes are picked up.
// It does the actual front/back buffer swap.
swap_request_handler_();
// Remove any dead textures, etc.
texture_cache_.Scavenge();
@@ -964,8 +1008,6 @@ bool CommandProcessor::ExecutePacketType3_XE_SWAP(RingbufferReader* reader,
uint32_t frontbuffer_width = reader->Read();
uint32_t frontbuffer_height = reader->Read();
reader->Advance(count - 4);
last_swap_width_ = frontbuffer_width;
last_swap_height_ = frontbuffer_height;
// Ensure we issue any pending draws.
draw_batcher_.Flush(DrawBatcher::FlushMode::kMakeCoherent);
@@ -2757,6 +2799,8 @@ bool CommandProcessor::IssueCopy() {
// TODO(benvanik): copy to staging texture then PBO back?
void* ptr = memory_->TranslatePhysical(copy_dest_base);
auto blitter = static_cast<xe::ui::gl::GLContext*>(context_.get())->blitter();
// Make active so glReadPixels reads from us.
switch (copy_command) {
case CopyCommand::kRaw: {
@@ -2766,8 +2810,8 @@ bool CommandProcessor::IssueCopy() {
// Source from a bound render target.
// TODO(benvanik): RAW copy.
last_framebuffer_texture_ = texture_cache_.CopyTexture(
context_->blitter(), copy_dest_base, dest_logical_width,
dest_logical_height, dest_block_width, dest_block_height,
blitter, copy_dest_base, dest_logical_width, dest_logical_height,
dest_block_width, dest_block_height,
ColorFormatToTextureFormat(copy_dest_format),
copy_dest_swap ? true : false, color_targets[copy_src_select],
src_rect, dest_rect);
@@ -2777,11 +2821,10 @@ bool CommandProcessor::IssueCopy() {
} else {
// Source from the bound depth/stencil target.
// TODO(benvanik): RAW copy.
texture_cache_.CopyTexture(context_->blitter(), copy_dest_base,
dest_logical_width, dest_logical_height,
dest_block_width, dest_block_height,
src_format, copy_dest_swap ? true : false,
depth_target, src_rect, dest_rect);
texture_cache_.CopyTexture(
blitter, copy_dest_base, dest_logical_width, dest_logical_height,
dest_block_width, dest_block_height, src_format,
copy_dest_swap ? true : false, depth_target, src_rect, dest_rect);
if (!FLAGS_disable_framebuffer_readback) {
// glReadPixels(x, y, w, h, GL_DEPTH_STENCIL, read_type, ptr);
}
@@ -2794,8 +2837,8 @@ bool CommandProcessor::IssueCopy() {
// Either copy the readbuffer into an existing texture or create a new
// one in the cache so we can service future upload requests.
last_framebuffer_texture_ = texture_cache_.ConvertTexture(
context_->blitter(), copy_dest_base, dest_logical_width,
dest_logical_height, dest_block_width, dest_block_height,
blitter, copy_dest_base, dest_logical_width, dest_logical_height,
dest_block_width, dest_block_height,
ColorFormatToTextureFormat(copy_dest_format),
copy_dest_swap ? true : false, color_targets[copy_src_select],
src_rect, dest_rect);
@@ -2804,11 +2847,10 @@ bool CommandProcessor::IssueCopy() {
}
} else {
// Source from the bound depth/stencil target.
texture_cache_.ConvertTexture(context_->blitter(), copy_dest_base,
dest_logical_width, dest_logical_height,
dest_block_width, dest_block_height,
src_format, copy_dest_swap ? true : false,
depth_target, src_rect, dest_rect);
texture_cache_.ConvertTexture(
blitter, copy_dest_base, dest_logical_width, dest_logical_height,
dest_block_width, dest_block_height, src_format,
copy_dest_swap ? true : false, depth_target, src_rect, dest_rect);
if (!FLAGS_disable_framebuffer_readback) {
// glReadPixels(x, y, w, h, GL_DEPTH_STENCIL, read_type, ptr);
}

View File

@@ -42,13 +42,18 @@ namespace gl4 {
class GL4GraphicsSystem;
struct SwapParameters {
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
GLuint framebuffer_texture;
struct SwapState {
// Lock must be held when changing data in this structure.
xe::mutex mutex;
// Dimensions of the framebuffer textures. Should match window size.
uint32_t width = 0;
uint32_t height = 0;
// Current front buffer, being drawn to the screen.
GLuint front_buffer_texture = 0;
// Current back buffer, being updated by the CP.
GLuint back_buffer_texture = 0;
// Whether the back buffer is dirty and a swap is pending.
bool pending = false;
};
enum class SwapMode {
@@ -61,22 +66,23 @@ class CommandProcessor {
CommandProcessor(GL4GraphicsSystem* graphics_system);
~CommandProcessor();
typedef std::function<void(const SwapParameters& params)> SwapHandler;
void set_swap_handler(SwapHandler fn) { swap_handler_ = fn; }
uint32_t counter() const { return counter_; }
void increment_counter() { counter_++; }
bool Initialize(std::unique_ptr<xe::ui::gl::GLContext> context);
bool Initialize(std::unique_ptr<xe::ui::GraphicsContext> context);
void Shutdown();
void CallInThread(std::function<void()> fn);
void ClearCaches();
SwapState& swap_state() { return swap_state_; }
void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; }
void IssueSwap();
void IssueSwap(uint32_t frontbuffer_width, uint32_t frontbuffer_height);
void set_swap_request_handler(std::function<void()> fn) {
swap_request_handler_ = fn;
}
void RequestFrameTrace(const std::wstring& root_path);
void BeginTracing(const std::wstring& root_path);
void EndTracing();
@@ -238,11 +244,11 @@ class CommandProcessor {
std::atomic<bool> worker_running_;
kernel::object_ref<kernel::XHostThread> worker_thread_;
std::unique_ptr<xe::ui::gl::GLContext> context_;
SwapHandler swap_handler_;
std::queue<std::function<void()>> pending_fns_;
std::unique_ptr<xe::ui::GraphicsContext> context_;
SwapMode swap_mode_;
SwapState swap_state_;
std::function<void()> swap_request_handler_;
std::queue<std::function<void()>> pending_fns_;
uint32_t counter_;
@@ -266,8 +272,6 @@ class CommandProcessor {
GL4Shader* active_pixel_shader_;
CachedFramebuffer* active_framebuffer_;
GLuint last_framebuffer_texture_;
uint32_t last_swap_width_;
uint32_t last_swap_height_;
std::vector<CachedFramebuffer> cached_framebuffers_;
std::vector<CachedColorRenderTarget> cached_color_render_targets_;

View File

@@ -19,7 +19,8 @@
#include "xenia/gpu/gl4/gl4_gpu_flags.h"
#include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/tracing.h"
#include "xenia/ui/gl/gl_profiler_display.h"
#include "xenia/profiling.h"
#include "xenia/ui/window.h"
namespace xe {
namespace gpu {
@@ -47,6 +48,14 @@ std::unique_ptr<GraphicsSystem> GL4GraphicsSystem::Create(Emulator* emulator) {
return std::make_unique<GL4GraphicsSystem>(emulator);
}
std::unique_ptr<ui::GraphicsContext> GL4GraphicsSystem::CreateContext(
ui::Window* target_window) {
// Setup the GL control that actually does the drawing.
// We run here in the loop and only touch it (and its context) on this
// thread. That means some sync-fu when we want to swap.
return xe::ui::gl::GLContext::Create(target_window);
}
GL4GraphicsSystem::GL4GraphicsSystem(Emulator* emulator)
: GraphicsSystem(emulator), worker_running_(false) {}
@@ -54,38 +63,36 @@ GL4GraphicsSystem::~GL4GraphicsSystem() = default;
X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor,
ui::Loop* target_loop,
ui::PlatformWindow* target_window) {
ui::Window* target_window) {
auto result = GraphicsSystem::Setup(processor, target_loop, target_window);
if (result) {
return result;
}
display_context_ =
reinterpret_cast<xe::ui::gl::GLContext*>(target_window->context());
// Watch for paint requests to do our swap.
target_window->on_painting.AddListener(
[this](xe::ui::UIEvent& e) { Swap(e); });
// Create rendering control.
// This must happen on the UI thread.
xe::threading::Fence control_ready_fence;
std::unique_ptr<xe::ui::gl::GLContext> processor_context;
target_loop_->Post([&]() {
// Setup the GL control that actually does the drawing.
// We run here in the loop and only touch it (and its context) on this
// thread. That means some sync-fu when we want to swap.
control_ = std::make_unique<xe::ui::gl::WGLControl>(target_loop_);
target_window_->AddChild(control_.get());
std::unique_ptr<xe::ui::GraphicsContext> processor_context;
target_loop_->PostSynchronous([&]() {
// Setup the GL context the command processor will do all its drawing in.
// It's shared with the control context so that we can resolve framebuffers
// It's shared with the display context so that we can resolve framebuffers
// from it.
processor_context = control_->context()->CreateShared();
{
xe::ui::gl::GLContextLock context_lock(control_->context());
auto profiler_display =
std::make_unique<xe::ui::gl::GLProfilerDisplay>(control_.get());
Profiler::set_display(std::move(profiler_display));
}
control_ready_fence.Signal();
processor_context = display_context_->CreateShared();
processor_context->ClearCurrent();
});
control_ready_fence.Wait();
if (!processor_context) {
XEFATAL(
"Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure "
"you have the latest drivers for your GPU and that it supports OpenGL "
"4.5. See http://xenia.jp/faq/ for more information.");
return X_STATUS_UNSUCCESSFUL;
}
// Create command processor. This will spin up a thread to process all
// incoming ringbuffer packets.
@@ -94,8 +101,8 @@ X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor,
XELOGE("Unable to initialize command processor");
return X_STATUS_UNSUCCESSFUL;
}
command_processor_->set_swap_handler(
[this](const SwapParameters& swap_params) { SwapHandler(swap_params); });
command_processor_->set_swap_request_handler(
[this]() { target_window_->Invalidate(); });
// Let the processor know we want register access callbacks.
memory_->AddVirtualMappedRange(
@@ -144,7 +151,6 @@ void GL4GraphicsSystem::Shutdown() {
// TODO(benvanik): remove mapped range.
command_processor_.reset();
control_.reset();
GraphicsSystem::Shutdown();
}
@@ -159,10 +165,6 @@ void GL4GraphicsSystem::EnableReadPointerWriteBack(uint32_t ptr,
command_processor_->EnableReadPointerWriteBack(ptr, block_size);
}
void GL4GraphicsSystem::RequestSwap() {
command_processor_->CallInThread([&]() { command_processor_->IssueSwap(); });
}
void GL4GraphicsSystem::RequestFrameTrace() {
command_processor_->RequestFrameTrace(xe::to_wstring(FLAGS_trace_gpu_prefix));
}
@@ -268,6 +270,7 @@ void GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data, size_t trace_size,
}
command_processor_->set_swap_mode(SwapMode::kNormal);
command_processor_->IssueSwap(1280, 720);
});
}
@@ -288,22 +291,29 @@ void GL4GraphicsSystem::MarkVblank() {
DispatchInterruptCallback(0, 2);
}
void GL4GraphicsSystem::SwapHandler(const SwapParameters& swap_params) {
SCOPE_profile_cpu_f("gpu");
// Swap requested. Synchronously post a request to the loop so that
// we do the swap in the right thread.
control_->SynchronousRepaint([this, swap_params]() {
if (!swap_params.framebuffer_texture) {
// no-op.
return;
void GL4GraphicsSystem::Swap(xe::ui::UIEvent& e) {
// Check for pending swap.
auto& swap_state = command_processor_->swap_state();
{
std::lock_guard<xe::mutex> lock(swap_state.mutex);
if (swap_state.pending) {
swap_state.pending = false;
std::swap(swap_state.front_buffer_texture,
swap_state.back_buffer_texture);
}
Rect2D src_rect(swap_params.x, swap_params.y, swap_params.width,
swap_params.height);
Rect2D dest_rect(0, 0, control_->width(), control_->height());
control_->context()->blitter()->BlitTexture2D(
swap_params.framebuffer_texture, src_rect, dest_rect, GL_LINEAR);
});
}
if (!swap_state.front_buffer_texture) {
// Not yet ready.
return;
}
// Blit the frontbuffer.
display_context_->blitter()->BlitTexture2D(
swap_state.front_buffer_texture,
Rect2D(0, 0, swap_state.width, swap_state.height),
Rect2D(0, 0, target_window_->width(), target_window_->height()),
GL_LINEAR);
}
uint64_t GL4GraphicsSystem::ReadRegister(uint32_t addr) {

View File

@@ -16,7 +16,7 @@
#include "xenia/gpu/graphics_system.h"
#include "xenia/gpu/register_file.h"
#include "xenia/kernel/objects/xthread.h"
#include "xenia/ui/gl/wgl_control.h"
#include "xenia/ui/gl/gl_context.h"
namespace xe {
namespace gpu {
@@ -28,9 +28,11 @@ class GL4GraphicsSystem : public GraphicsSystem {
~GL4GraphicsSystem() override;
static std::unique_ptr<GraphicsSystem> Create(Emulator* emulator);
std::unique_ptr<ui::GraphicsContext> CreateContext(
ui::Window* target_window) override;
X_STATUS Setup(cpu::Processor* processor, ui::Loop* target_loop,
ui::PlatformWindow* target_window) override;
ui::Window* target_window) override;
void Shutdown() override;
RegisterFile* register_file() { return &register_file_; }
@@ -41,8 +43,6 @@ class GL4GraphicsSystem : public GraphicsSystem {
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count) override;
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size) override;
void RequestSwap() override;
void RequestFrameTrace() override;
void BeginTracing() override;
void EndTracing() override;
@@ -52,7 +52,7 @@ class GL4GraphicsSystem : public GraphicsSystem {
private:
void MarkVblank();
void SwapHandler(const SwapParameters& swap_params);
void Swap(xe::ui::UIEvent& e);
uint64_t ReadRegister(uint32_t addr);
void WriteRegister(uint32_t addr, uint64_t value);
@@ -67,7 +67,8 @@ class GL4GraphicsSystem : public GraphicsSystem {
RegisterFile register_file_;
std::unique_ptr<CommandProcessor> command_processor_;
std::unique_ptr<xe::ui::gl::WGLControl> control_;
xe::ui::gl::GLContext* display_context_ = nullptr;
std::atomic<bool> worker_running_;
kernel::object_ref<kernel::XHostThread> worker_thread_;