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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ®ister_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_;
|
||||
|
||||
@@ -43,7 +43,7 @@ GraphicsSystem::GraphicsSystem(Emulator* emulator) : emulator_(emulator) {}
|
||||
GraphicsSystem::~GraphicsSystem() = default;
|
||||
|
||||
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, ui::Loop* target_loop,
|
||||
ui::PlatformWindow* target_window) {
|
||||
ui::Window* target_window) {
|
||||
processor_ = processor;
|
||||
memory_ = processor->memory();
|
||||
target_loop_ = target_loop;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/platform.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
@@ -32,13 +32,15 @@ class GraphicsSystem {
|
||||
virtual ~GraphicsSystem();
|
||||
|
||||
static std::unique_ptr<GraphicsSystem> Create(Emulator* emulator);
|
||||
virtual std::unique_ptr<ui::GraphicsContext> CreateContext(
|
||||
ui::Window* target_window) = 0;
|
||||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
Memory* memory() const { return memory_; }
|
||||
cpu::Processor* processor() const { return processor_; }
|
||||
|
||||
virtual X_STATUS Setup(cpu::Processor* processor, ui::Loop* target_loop,
|
||||
ui::PlatformWindow* target_window);
|
||||
ui::Window* target_window);
|
||||
virtual void Shutdown();
|
||||
|
||||
void SetInterruptCallback(uint32_t callback, uint32_t user_data);
|
||||
@@ -46,8 +48,6 @@ class GraphicsSystem {
|
||||
virtual void EnableReadPointerWriteBack(uint32_t ptr,
|
||||
uint32_t block_size) = 0;
|
||||
|
||||
virtual void RequestSwap() = 0;
|
||||
|
||||
void DispatchInterruptCallback(uint32_t source, uint32_t cpu);
|
||||
|
||||
virtual void RequestFrameTrace() {}
|
||||
@@ -68,7 +68,7 @@ class GraphicsSystem {
|
||||
Memory* memory_ = nullptr;
|
||||
cpu::Processor* processor_ = nullptr;
|
||||
ui::Loop* target_loop_ = nullptr;
|
||||
ui::PlatformWindow* target_window_ = nullptr;
|
||||
ui::Window* target_window_ = nullptr;
|
||||
|
||||
uint32_t interrupt_callback_ = 0;
|
||||
uint32_t interrupt_callback_data_ = 0;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/profiling.h"
|
||||
#include "xenia/ui/gl/gl_context.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
// HACK: until we have another impl, we just use gl4 directly.
|
||||
#include "xenia/gpu/gl4/command_processor.h"
|
||||
@@ -842,7 +843,7 @@ class TracePlayer : public TraceReader {
|
||||
int current_command_index_;
|
||||
};
|
||||
|
||||
void DrawControllerUI(xe::ui::PlatformWindow* window, TracePlayer& player,
|
||||
void DrawControllerUI(xe::ui::Window* window, TracePlayer& player,
|
||||
Memory* memory) {
|
||||
ImGui::SetNextWindowPos(ImVec2(5, 5), ImGuiSetCond_FirstUseEver);
|
||||
if (!ImGui::Begin("Controller", nullptr, ImVec2(340, 60))) {
|
||||
@@ -883,7 +884,7 @@ void DrawControllerUI(xe::ui::PlatformWindow* window, TracePlayer& player,
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void DrawCommandListUI(xe::ui::PlatformWindow* window, TracePlayer& player,
|
||||
void DrawCommandListUI(xe::ui::Window* window, TracePlayer& player,
|
||||
Memory* memory) {
|
||||
ImGui::SetNextWindowPos(ImVec2(5, 70), ImGuiSetCond_FirstUseEver);
|
||||
if (!ImGui::Begin("Command List", nullptr, ImVec2(200, 640))) {
|
||||
@@ -1027,9 +1028,8 @@ ShaderDisplayType DrawShaderTypeUI() {
|
||||
return shader_display_type;
|
||||
}
|
||||
|
||||
void DrawShaderUI(xe::ui::PlatformWindow* window, TracePlayer& player,
|
||||
Memory* memory, gl4::GL4Shader* shader,
|
||||
ShaderDisplayType display_type) {
|
||||
void DrawShaderUI(xe::ui::Window* window, TracePlayer& player, Memory* memory,
|
||||
gl4::GL4Shader* shader, ShaderDisplayType display_type) {
|
||||
// Must be prepared for advanced display modes.
|
||||
if (display_type != ShaderDisplayType::kUcode) {
|
||||
if (!shader->has_prepared()) {
|
||||
@@ -1393,8 +1393,7 @@ static const char* kEndiannessNames[] = {
|
||||
"unspecified endianness", "8-in-16", "8-in-32", "16-in-32",
|
||||
};
|
||||
|
||||
void DrawStateUI(xe::ui::PlatformWindow* window, TracePlayer& player,
|
||||
Memory* memory) {
|
||||
void DrawStateUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) {
|
||||
auto gs = static_cast<gl4::GL4GraphicsSystem*>(player.graphics_system());
|
||||
auto cp = gs->command_processor();
|
||||
auto& regs = *gs->register_file();
|
||||
@@ -2033,8 +2032,8 @@ void DrawStateUI(xe::ui::PlatformWindow* window, TracePlayer& player,
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void DrawPacketDisassemblerUI(xe::ui::PlatformWindow* window,
|
||||
TracePlayer& player, Memory* memory) {
|
||||
void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player,
|
||||
Memory* memory) {
|
||||
ImGui::SetNextWindowCollapsed(true, ImGuiSetCond_FirstUseEver);
|
||||
ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 5),
|
||||
ImGuiSetCond_FirstUseEver);
|
||||
@@ -2175,8 +2174,7 @@ void DrawPacketDisassemblerUI(xe::ui::PlatformWindow* window,
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void DrawUI(xe::ui::PlatformWindow* window, TracePlayer& player,
|
||||
Memory* memory) {
|
||||
void DrawUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) {
|
||||
// ImGui::ShowTestWindow();
|
||||
|
||||
DrawControllerUI(window, player, memory);
|
||||
@@ -2189,129 +2187,151 @@ void ImImpl_Setup();
|
||||
void ImImpl_Shutdown();
|
||||
|
||||
int trace_viewer_main(std::vector<std::wstring>& args) {
|
||||
// Create the emulator.
|
||||
// Create the emulator but don't initialize so we can setup the window.
|
||||
auto emulator = std::make_unique<Emulator>(L"");
|
||||
X_STATUS result = emulator->Setup();
|
||||
|
||||
// Main emulator display window.
|
||||
auto loop = ui::Loop::Create();
|
||||
auto window = xe::ui::Window::Create(loop.get(), L"xe-gpu-trace-viewer");
|
||||
loop->PostSynchronous([&window]() {
|
||||
xe::threading::set_name("Win32 Loop");
|
||||
if (!window->Initialize()) {
|
||||
XEFATAL("Failed to initialize main window");
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
window->on_closed.AddListener([&loop](xe::ui::UIEvent& e) {
|
||||
loop->Quit();
|
||||
XELOGI("User-initiated death!");
|
||||
exit(1);
|
||||
});
|
||||
loop->on_quit.AddListener([&window](xe::ui::UIEvent& e) { window.reset(); });
|
||||
window->Resize(1920, 1200);
|
||||
|
||||
X_STATUS result = emulator->Setup(window.get());
|
||||
if (XFAILED(result)) {
|
||||
XELOGE("Failed to setup emulator: %.8X", result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Grab path from the flag or unnamed argument.
|
||||
if (!FLAGS_target_trace_file.empty() || args.size() >= 2) {
|
||||
std::wstring path;
|
||||
if (!FLAGS_target_trace_file.empty()) {
|
||||
// Passed as a named argument.
|
||||
// TODO(benvanik): find something better than gflags that supports
|
||||
// unicode.
|
||||
path = xe::to_wstring(FLAGS_target_trace_file);
|
||||
} else {
|
||||
// Passed as an unnamed argument.
|
||||
path = args[1];
|
||||
}
|
||||
// Normalize the path and make absolute.
|
||||
auto abs_path = xe::to_absolute_path(path);
|
||||
|
||||
auto window = emulator->display_window();
|
||||
auto loop = window->loop();
|
||||
auto file_name = xe::find_name_from_path(path);
|
||||
window->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name);
|
||||
|
||||
auto graphics_system = emulator->graphics_system();
|
||||
Profiler::set_display(nullptr);
|
||||
|
||||
TracePlayer player(loop, emulator->graphics_system());
|
||||
if (!player.Open(abs_path)) {
|
||||
XELOGE("Could not load trace file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto control = window->child(0);
|
||||
control->on_key_char.AddListener([graphics_system](xe::ui::KeyEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
if (e.key_code() > 0 && e.key_code() < 0x10000) {
|
||||
if (e.key_code() == 0x74 /* VK_F5 */) {
|
||||
graphics_system->ClearCaches();
|
||||
} else {
|
||||
io.AddInputCharacter(e.key_code());
|
||||
}
|
||||
}
|
||||
e.set_handled(true);
|
||||
});
|
||||
control->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
switch (e.button()) {
|
||||
case xe::ui::MouseEvent::Button::kLeft:
|
||||
io.MouseDown[0] = true;
|
||||
break;
|
||||
case xe::ui::MouseEvent::Button::kRight:
|
||||
io.MouseDown[1] = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
control->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
});
|
||||
control->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
switch (e.button()) {
|
||||
case xe::ui::MouseEvent::Button::kLeft:
|
||||
io.MouseDown[0] = false;
|
||||
break;
|
||||
case xe::ui::MouseEvent::Button::kRight:
|
||||
io.MouseDown[1] = false;
|
||||
break;
|
||||
}
|
||||
});
|
||||
control->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
io.MouseWheel += float(e.dy() / 120.0f);
|
||||
});
|
||||
|
||||
control->on_paint.AddListener([&](xe::ui::UIEvent& e) {
|
||||
static bool imgui_setup = false;
|
||||
if (!imgui_setup) {
|
||||
ImImpl_Setup();
|
||||
imgui_setup = true;
|
||||
}
|
||||
auto& io = ImGui::GetIO();
|
||||
auto current_ticks = Clock::QueryHostTickCount();
|
||||
static uint64_t last_ticks = 0;
|
||||
io.DeltaTime =
|
||||
(current_ticks - last_ticks) / float(Clock::host_tick_frequency());
|
||||
last_ticks = current_ticks;
|
||||
|
||||
io.DisplaySize =
|
||||
ImVec2(float(e.control()->width()), float(e.control()->height()));
|
||||
|
||||
BYTE keystate[256];
|
||||
GetKeyboardState(keystate);
|
||||
for (int i = 0; i < 256; i++) io.KeysDown[i] = (keystate[i] & 0x80) != 0;
|
||||
io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0;
|
||||
io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0;
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
DrawUI(window, player, emulator->memory());
|
||||
|
||||
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
|
||||
ImGui::Render();
|
||||
|
||||
graphics_system->RequestSwap();
|
||||
});
|
||||
graphics_system->RequestSwap();
|
||||
|
||||
// Wait until we are exited.
|
||||
emulator->display_window()->loop()->AwaitQuit();
|
||||
|
||||
ImImpl_Shutdown();
|
||||
if (FLAGS_target_trace_file.empty() && args.size() < 2) {
|
||||
XELOGE("No trace file specified");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::wstring path;
|
||||
if (!FLAGS_target_trace_file.empty()) {
|
||||
// Passed as a named argument.
|
||||
// TODO(benvanik): find something better than gflags that supports
|
||||
// unicode.
|
||||
path = xe::to_wstring(FLAGS_target_trace_file);
|
||||
} else {
|
||||
// Passed as an unnamed argument.
|
||||
path = args[1];
|
||||
}
|
||||
// Normalize the path and make absolute.
|
||||
auto abs_path = xe::to_absolute_path(path);
|
||||
|
||||
auto file_name = xe::find_name_from_path(path);
|
||||
window->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name);
|
||||
|
||||
auto graphics_system = emulator->graphics_system();
|
||||
Profiler::set_display(nullptr);
|
||||
|
||||
TracePlayer player(loop.get(), emulator->graphics_system());
|
||||
if (!player.Open(abs_path)) {
|
||||
XELOGE("Could not load trace file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
window->on_key_char.AddListener([graphics_system](xe::ui::KeyEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
if (e.key_code() > 0 && e.key_code() < 0x10000) {
|
||||
if (e.key_code() == 0x74 /* VK_F5 */) {
|
||||
graphics_system->ClearCaches();
|
||||
} else {
|
||||
io.AddInputCharacter(e.key_code());
|
||||
}
|
||||
}
|
||||
e.set_handled(true);
|
||||
});
|
||||
window->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
switch (e.button()) {
|
||||
case xe::ui::MouseEvent::Button::kLeft:
|
||||
io.MouseDown[0] = true;
|
||||
break;
|
||||
case xe::ui::MouseEvent::Button::kRight:
|
||||
io.MouseDown[1] = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
window->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
});
|
||||
window->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
switch (e.button()) {
|
||||
case xe::ui::MouseEvent::Button::kLeft:
|
||||
io.MouseDown[0] = false;
|
||||
break;
|
||||
case xe::ui::MouseEvent::Button::kRight:
|
||||
io.MouseDown[1] = false;
|
||||
break;
|
||||
}
|
||||
});
|
||||
window->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
io.MouseWheel += float(e.dy() / 120.0f);
|
||||
});
|
||||
|
||||
window->on_painting.AddListener([&](xe::ui::UIEvent& e) {
|
||||
static bool imgui_setup = false;
|
||||
if (!imgui_setup) {
|
||||
ImImpl_Setup();
|
||||
imgui_setup = true;
|
||||
}
|
||||
auto& io = ImGui::GetIO();
|
||||
auto current_ticks = Clock::QueryHostTickCount();
|
||||
static uint64_t last_ticks = 0;
|
||||
io.DeltaTime =
|
||||
(current_ticks - last_ticks) / float(Clock::host_tick_frequency());
|
||||
last_ticks = current_ticks;
|
||||
|
||||
io.DisplaySize =
|
||||
ImVec2(float(e.target()->width()), float(e.target()->height()));
|
||||
|
||||
BYTE keystate[256];
|
||||
GetKeyboardState(keystate);
|
||||
for (int i = 0; i < 256; i++) io.KeysDown[i] = (keystate[i] & 0x80) != 0;
|
||||
io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0;
|
||||
io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0;
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
DrawUI(window.get(), player, emulator->memory());
|
||||
|
||||
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
|
||||
ImGui::Render();
|
||||
|
||||
// Continuous paint.
|
||||
window->Invalidate();
|
||||
});
|
||||
window->Invalidate();
|
||||
|
||||
// Wait until we are exited.
|
||||
loop->AwaitQuit();
|
||||
|
||||
ImImpl_Shutdown();
|
||||
|
||||
emulator.reset();
|
||||
window.reset();
|
||||
loop.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user