Adding generic immediate mode drawing API and migrating microprofile.

Future changes will move elemental-forms and imgui to the common API as
well.
This commit is contained in:
Ben Vanik 2015-11-05 21:36:05 -08:00
parent dc04baeb3f
commit c631b965d7
13 changed files with 674 additions and 427 deletions

View File

@ -26,6 +26,7 @@
#include "xenia/kernel/xam/xam_module.h"
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
#include "xenia/memory.h"
#include "xenia/profiling.h"
#include "xenia/vfs/devices/disc_image_device.h"
#include "xenia/vfs/devices/host_path_device.h"
#include "xenia/vfs/devices/stfs_container_device.h"
@ -164,8 +165,7 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
// Finish initializing the display.
display_window_->loop()->PostSynchronous([this]() {
xe::ui::GraphicsContextLock context_lock(display_window_->context());
auto profiler_display = display_window_->context()->CreateProfilerDisplay();
Profiler::set_display(std::move(profiler_display));
Profiler::set_window(display_window_);
});
return result;

View File

@ -2236,7 +2236,7 @@ int trace_viewer_main(const std::vector<std::wstring>& args) {
window->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name);
auto graphics_system = emulator->graphics_system();
Profiler::set_display(nullptr);
Profiler::set_window(nullptr);
TracePlayer player(loop.get(), emulator->graphics_system());
if (!player.Open(abs_path)) {

View File

@ -29,17 +29,25 @@
#define MICROPROFILE_MAX_THREADS 128
#include "third_party/microprofile/microprofile.h"
#include "xenia/base/assert.h"
#include "xenia/profiling.h"
#include "xenia/ui/window.h"
#if XE_OPTION_PROFILING
#include "third_party/microprofile/microprofileui.h"
#endif // XE_OPTION_PROFILING
#if XE_OPTION_PROFILING_UI
#undef DrawText
#include "xenia/ui/microprofile_drawer.h"
#endif // XE_OPTION_PROFILING_UI
DEFINE_bool(show_profiler, false, "Show profiling UI by default.");
namespace xe {
std::unique_ptr<ProfilerDisplay> Profiler::display_ = nullptr;
ui::Window* Profiler::window_ = nullptr;
std::unique_ptr<ui::MicroprofileDrawer> Profiler::drawer_ = nullptr;
#if XE_OPTION_PROFILING
@ -86,7 +94,8 @@ void Profiler::Dump() {
}
void Profiler::Shutdown() {
display_.reset();
drawer_.reset();
window_ = nullptr;
MicroProfileShutdown();
}
@ -165,20 +174,74 @@ void Profiler::TogglePause() {}
#endif // XE_OPTION_PROFILING_UI
void Profiler::set_display(std::unique_ptr<ProfilerDisplay> display) {
display_ = std::move(display);
void Profiler::set_window(ui::Window* window) {
assert_null(window_);
if (!window) {
return;
}
window_ = window;
drawer_ = std::make_unique<ui::MicroprofileDrawer>(window);
window_->on_painted.AddListener([](ui::UIEvent* e) { Profiler::Present(); });
// Pass through mouse events.
window_->on_mouse_down.AddListener([](ui::MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseDown(e->button() == ui::MouseEvent::Button::kLeft,
e->button() == ui::MouseEvent::Button::kRight);
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_mouse_up.AddListener([](ui::MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseUp();
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_mouse_move.AddListener([](ui::MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseMove(e->x(), e->y());
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_mouse_wheel.AddListener([](ui::MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseWheel(e->x(), e->y(), -e->dy());
e->set_handled(true);
window_->Invalidate();
}
});
// Watch for toggle/mode keys and such.
window_->on_key_down.AddListener([](ui::KeyEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnKeyDown(e->key_code());
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_key_up.AddListener([](ui::KeyEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnKeyUp(e->key_code());
e->set_handled(true);
window_->Invalidate();
}
});
}
void Profiler::Present() {
SCOPE_profile_cpu_f("internal");
MicroProfileFlip();
#if XE_OPTION_PROFILING_UI
if (!display_) {
if (!window_ || !drawer_) {
return;
}
display_->Begin();
MicroProfileDraw(display_->width(), display_->height());
display_->End();
drawer_->Begin();
MicroProfileDraw(window_->width(), window_->height());
drawer_->End();
#endif // XE_OPTION_PROFILING_UI
}
@ -198,7 +261,7 @@ void Profiler::OnMouseDown(bool left_button, bool right_button) {}
void Profiler::OnMouseUp() {}
void Profiler::OnMouseMove(int x, int y) {}
void Profiler::OnMouseWheel(int x, int y, int dy) {}
void Profiler::set_display(std::unique_ptr<ProfilerDisplay> display) {}
void Profiler::set_display(std::unique_ptr<ui::MicroprofilerDrawer> display) {}
void Profiler::Present() {}
#endif // XE_OPTION_PROFILING
@ -219,30 +282,30 @@ const char* MicroProfileGetThreadName() { return "TODO: get thread name!"; }
void MicroProfileDrawBox(int nX, int nY, int nX1, int nY1, uint32_t nColor,
MicroProfileBoxType type) {
auto display = xe::Profiler::display();
if (!display) {
auto drawer = xe::Profiler::drawer();
if (!drawer) {
return;
}
display->DrawBox(nX, nY, nX1, nY1, nColor,
static_cast<xe::ProfilerDisplay::BoxType>(type));
drawer->DrawBox(nX, nY, nX1, nY1, nColor,
static_cast<xe::ui::MicroprofileDrawer::BoxType>(type));
}
void MicroProfileDrawLine2D(uint32_t nVertices, float* pVertices,
uint32_t nColor) {
auto display = xe::Profiler::display();
if (!display) {
auto drawer = xe::Profiler::drawer();
if (!drawer) {
return;
}
display->DrawLine2D(nVertices, pVertices, nColor);
drawer->DrawLine2D(nVertices, pVertices, nColor);
}
void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText,
uint32_t nLen) {
auto display = xe::Profiler::display();
if (!display) {
auto drawer = xe::Profiler::drawer();
if (!drawer) {
return;
}
display->DrawText(nX, nY, nColor, pText, nLen);
drawer->DrawText(nX, nY, nColor, pText, nLen);
}
#endif // XE_OPTION_PROFILING_UI

View File

@ -27,6 +27,13 @@
#include <microprofile/microprofile.h>
#endif // XE_OPTION_PROFILING
namespace xe {
namespace ui {
class MicroprofileDrawer;
class Window;
} // namespace ui
} // namespace xe
namespace xe {
#if XE_OPTION_PROFILING
@ -125,32 +132,6 @@ namespace xe {
#endif // XE_OPTION_PROFILING
class ProfilerDisplay {
public:
enum class BoxType {
#if XE_OPTION_PROFILING
kBar = MicroProfileBoxTypeBar,
kFlat = MicroProfileBoxTypeFlat,
#else
kBar,
kFlat,
#endif // XE_OPTION_PROFILING
};
virtual uint32_t width() const = 0;
virtual uint32_t height() const = 0;
// TODO(benvanik): GPU timestamping.
virtual void Begin() = 0;
virtual void End() = 0;
virtual void DrawBox(int x0, int y0, int x1, int y1, uint32_t color,
BoxType type) = 0;
virtual void DrawLine2D(uint32_t count, float* vertices, uint32_t color) = 0;
virtual void DrawText(int x, int y, uint32_t color, const char* text,
size_t text_length) = 0;
};
class Profiler {
public:
static bool is_enabled();
@ -181,15 +162,16 @@ class Profiler {
static void ToggleDisplay();
static void TogglePause();
// Initializes input and drawing with the given display.
static void set_window(ui::Window* window);
// Gets the current display, if any.
static ProfilerDisplay* display() { return display_.get(); }
// Initializes drawing with the given display.
static void set_display(std::unique_ptr<ProfilerDisplay> display);
static ui::MicroprofileDrawer* drawer() { return drawer_.get(); }
// Presents the profiler to the bound display, if any.
static void Present();
private:
static std::unique_ptr<ProfilerDisplay> display_;
static ui::Window* window_;
static std::unique_ptr<ui::MicroprofileDrawer> drawer_;
};
} // namespace xe

View File

@ -19,7 +19,7 @@
#include "xenia/base/math.h"
#include "xenia/profiling.h"
#include "xenia/ui/gl/gl4_elemental_renderer.h"
#include "xenia/ui/gl/gl_profiler_display.h"
#include "xenia/ui/gl/gl_immediate_drawer.h"
#include "xenia/ui/window.h"
// TODO(benvanik): move win32 code to _win?
@ -84,6 +84,7 @@ GLContext::GLContext(Window* target_window, HGLRC glrc)
GLContext::~GLContext() {
MakeCurrent();
blitter_.Shutdown();
immediate_drawer_.reset();
ClearCurrent();
if (glrc_) {
wglDeleteContext(glrc_);
@ -186,6 +187,8 @@ bool GLContext::Initialize(Window* target_window, GLContext* share_context) {
return false;
}
immediate_drawer_ = std::make_unique<GLImmediateDrawer>(this);
ClearCurrent();
return true;
@ -249,6 +252,8 @@ std::unique_ptr<GraphicsContext> GLContext::CreateShared() {
return nullptr;
}
new_context->immediate_drawer_ = std::make_unique<GLImmediateDrawer>(this);
new_context->ClearCurrent();
return std::unique_ptr<GraphicsContext>(new_context.release());
@ -408,14 +413,14 @@ void GLContext::SetupDebugging() {
this);
}
std::unique_ptr<ProfilerDisplay> GLContext::CreateProfilerDisplay() {
return std::make_unique<GLProfilerDisplay>(target_window_);
}
std::unique_ptr<el::graphics::Renderer> GLContext::CreateElementalRenderer() {
return GL4ElementalRenderer::Create(this);
}
ImmediateDrawer* GLContext::immediate_drawer() {
return immediate_drawer_.get();
}
bool GLContext::is_current() {
return tls_glew_context_ == glew_context_.get();
}

View File

@ -28,6 +28,8 @@ namespace xe {
namespace ui {
namespace gl {
class GLImmediateDrawer;
class GLContext : public GraphicsContext {
public:
static std::unique_ptr<GLContext> Create(Window* target_window,
@ -38,9 +40,10 @@ class GLContext : public GraphicsContext {
HDC dc() const { return dc_; }
std::unique_ptr<GraphicsContext> CreateShared() override;
std::unique_ptr<ProfilerDisplay> CreateProfilerDisplay() override;
std::unique_ptr<el::graphics::Renderer> CreateElementalRenderer() override;
ImmediateDrawer* immediate_drawer() override;
bool is_current() override;
bool MakeCurrent() override;
void ClearCurrent() override;
@ -73,6 +76,7 @@ class GLContext : public GraphicsContext {
std::unique_ptr<WGLEWContext> wglew_context_;
Blitter blitter_;
std::unique_ptr<GLImmediateDrawer> immediate_drawer_;
};
} // namespace gl

View File

@ -0,0 +1,262 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/gl/gl_immediate_drawer.h"
#include <string>
#include "xenia/ui/graphics_context.h"
namespace xe {
namespace ui {
namespace gl {
constexpr uint32_t kMaxDrawVertices = 64 * 1024;
constexpr uint32_t kMaxDrawIndices = 64 * 1024;
class GLImmediateTexture : public ImmediateTexture {
public:
GLImmediateTexture(uint32_t width, uint32_t height,
ImmediateTextureFilter filter, bool repeat)
: ImmediateTexture(width, height) {
GLuint handle;
glCreateTextures(GL_TEXTURE_2D, 1, &handle);
GLenum gl_filter = GL_NEAREST;
switch (filter) {
case ImmediateTextureFilter::kNearest:
gl_filter = GL_NEAREST;
break;
case ImmediateTextureFilter::kLinear:
gl_filter = GL_LINEAR;
break;
}
glTextureParameteri(handle, GL_TEXTURE_MIN_FILTER, gl_filter);
glTextureParameteri(handle, GL_TEXTURE_MAG_FILTER, gl_filter);
glTextureParameteri(handle, GL_TEXTURE_WRAP_S,
repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTextureParameteri(handle, GL_TEXTURE_WRAP_T,
repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTextureStorage2D(handle, 1, GL_RGBA8, width, height);
this->handle = static_cast<uintptr_t>(handle);
}
~GLImmediateTexture() override {
//
}
};
GLImmediateDrawer::GLImmediateDrawer(GraphicsContext* graphics_context)
: ImmediateDrawer(graphics_context) {
glCreateBuffers(1, &vertex_buffer_);
glNamedBufferStorage(vertex_buffer_,
kMaxDrawVertices * sizeof(ImmediateVertex), nullptr,
GL_DYNAMIC_STORAGE_BIT);
glCreateBuffers(1, &index_buffer_);
glNamedBufferStorage(index_buffer_, kMaxDrawIndices * sizeof(uint16_t),
nullptr, GL_DYNAMIC_STORAGE_BIT);
glCreateVertexArrays(1, &vao_);
glEnableVertexArrayAttrib(vao_, 0);
glVertexArrayAttribBinding(vao_, 0, 0);
glVertexArrayAttribFormat(vao_, 0, 2, GL_FLOAT, GL_FALSE,
offsetof(ImmediateVertex, x));
glEnableVertexArrayAttrib(vao_, 1);
glVertexArrayAttribBinding(vao_, 1, 0);
glVertexArrayAttribFormat(vao_, 1, 2, GL_FLOAT, GL_FALSE,
offsetof(ImmediateVertex, u));
glEnableVertexArrayAttrib(vao_, 2);
glVertexArrayAttribBinding(vao_, 2, 0);
glVertexArrayAttribFormat(vao_, 2, 4, GL_UNSIGNED_BYTE, GL_TRUE,
offsetof(ImmediateVertex, color));
glVertexArrayVertexBuffer(vao_, 0, vertex_buffer_, 0,
sizeof(ImmediateVertex));
InitializeShaders();
}
GLImmediateDrawer::~GLImmediateDrawer() {
GraphicsContextLock lock(graphics_context_);
glDeleteBuffers(1, &vertex_buffer_);
glDeleteBuffers(1, &index_buffer_);
glDeleteVertexArrays(1, &vao_);
glDeleteProgram(program_);
}
void GLImmediateDrawer::InitializeShaders() {
const std::string header =
R"(
#version 450
#extension GL_ARB_explicit_uniform_location : require
#extension GL_ARB_shading_language_420pack : require
precision highp float;
layout(std140, column_major) uniform;
layout(std430, column_major) buffer;
)";
const std::string vertex_shader_source = header +
R"(
layout(location = 0) uniform mat4 projection_matrix;
layout(location = 0) in vec2 in_pos;
layout(location = 1) in vec2 in_uv;
layout(location = 2) in vec4 in_color;
layout(location = 0) out vec2 vtx_uv;
layout(location = 1) out vec4 vtx_color;
void main() {
gl_Position = projection_matrix * vec4(in_pos.xy, 0.0, 1.0);
vtx_uv = in_uv;
vtx_color = in_color;
})";
const std::string fragment_shader_source = header +
R"(
layout(location = 1) uniform sampler2D texture_sampler;
layout(location = 0) in vec2 vtx_uv;
layout(location = 1) in vec4 vtx_color;
layout(location = 0) out vec4 out_color;
void main() {
out_color = vtx_color;
if (vtx_uv.x <= 1.0) {
vec4 tex_color = texture(texture_sampler, vtx_uv);
out_color *= tex_color;
// TODO(benvanik): microprofiler shadows.
}
})";
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
const char* vertex_shader_source_ptr = vertex_shader_source.c_str();
GLint vertex_shader_source_length = GLint(vertex_shader_source.size());
glShaderSource(vertex_shader, 1, &vertex_shader_source_ptr,
&vertex_shader_source_length);
glCompileShader(vertex_shader);
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
const char* fragment_shader_source_ptr = fragment_shader_source.c_str();
GLint fragment_shader_source_length = GLint(fragment_shader_source.size());
glShaderSource(fragment_shader, 1, &fragment_shader_source_ptr,
&fragment_shader_source_length);
glCompileShader(fragment_shader);
program_ = glCreateProgram();
glAttachShader(program_, vertex_shader);
glAttachShader(program_, fragment_shader);
glLinkProgram(program_);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
}
std::unique_ptr<ImmediateTexture> GLImmediateDrawer::CreateTexture(
uint32_t width, uint32_t height, ImmediateTextureFilter filter, bool repeat,
const uint8_t* data) {
GraphicsContextLock lock(graphics_context_);
auto texture =
std::make_unique<GLImmediateTexture>(width, height, filter, repeat);
if (data) {
glTextureSubImage2D(static_cast<GLuint>(texture->handle), 0, 0, 0, width,
height, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
return std::unique_ptr<ImmediateTexture>(texture.release());
}
void GLImmediateDrawer::UpdateTexture(ImmediateTexture* texture,
const uint8_t* data) {
GraphicsContextLock lock(graphics_context_);
glTextureSubImage2D(static_cast<GLuint>(texture->handle), 0, 0, 0,
texture->width, texture->height, GL_RGBA,
GL_UNSIGNED_BYTE, data);
}
void GLImmediateDrawer::Begin(int render_target_width,
int render_target_height) {
was_current_ = graphics_context_->is_current();
if (!was_current_) {
graphics_context_->MakeCurrent();
}
// Setup render state.
glEnablei(GL_BLEND, 0);
glBlendEquationi(0, GL_FUNC_ADD);
glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_SCISSOR_TEST);
// Prepare drawing resources.
glUseProgram(program_);
glBindVertexArray(vao_);
// Setup orthographic projection matrix and viewport.
const float ortho_projection[4][4] = {
{2.0f / render_target_width, 0.0f, 0.0f, 0.0f},
{0.0f, 2.0f / -render_target_height, 0.0f, 0.0f},
{0.0f, 0.0f, -1.0f, 0.0f},
{-1.0f, 1.0f, 0.0f, 1.0f},
};
glProgramUniformMatrix4fv(program_, 0, 1, GL_FALSE, &ortho_projection[0][0]);
glViewport(0, 0, render_target_width, render_target_height);
}
void GLImmediateDrawer::Draw(const ImmediateDrawBatch& batch) {
glNamedBufferSubData(vertex_buffer_, 0,
batch.vertex_count * sizeof(ImmediateVertex),
batch.vertices);
if (batch.indices) {
glNamedBufferSubData(index_buffer_, 0, batch.index_count * sizeof(uint16_t),
batch.indices);
}
if (batch.scissor) {
glEnable(GL_SCISSOR_TEST);
glScissor(batch.scissor_rect[0], batch.scissor_rect[1],
batch.scissor_rect[2], batch.scissor_rect[3]);
} else {
glDisable(GL_SCISSOR_TEST);
}
if (batch.texture_handle) {
glBindTextureUnit(0, static_cast<GLuint>(batch.texture_handle));
} else {
glBindTextureUnit(0, 0);
}
GLenum mode = GL_TRIANGLES;
switch (batch.primitive_type) {
case ImmediatePrimitiveType::kLines:
mode = GL_LINES;
break;
case ImmediatePrimitiveType::kTriangles:
mode = GL_TRIANGLES;
break;
}
if (batch.indices) {
glDrawElements(mode, batch.index_count, GL_UNSIGNED_SHORT, nullptr);
} else {
glDrawArrays(mode, 0, batch.vertex_count);
}
glFlush();
}
void GLImmediateDrawer::End() {
// Restore modified state.
glDisable(GL_SCISSOR_TEST);
glBindTextureUnit(0, 0);
glUseProgram(0);
glBindVertexArray(0);
if (!was_current_) {
graphics_context_->ClearCurrent();
}
}
} // namespace gl
} // namespace ui
} // namespace xe

View File

@ -0,0 +1,53 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_GL_GL_IMMEDIATE_DRAWER_H_
#define XENIA_UI_GL_GL_IMMEDIATE_DRAWER_H_
#include <memory>
#include "xenia/ui/gl/gl.h"
#include "xenia/ui/immediate_drawer.h"
namespace xe {
namespace ui {
namespace gl {
class GLImmediateDrawer : public ImmediateDrawer {
public:
GLImmediateDrawer(GraphicsContext* graphics_context);
~GLImmediateDrawer() override;
std::unique_ptr<ImmediateTexture> CreateTexture(uint32_t width,
uint32_t height,
ImmediateTextureFilter filter,
bool repeat,
const uint8_t* data) override;
void UpdateTexture(ImmediateTexture* texture, const uint8_t* data) override;
void Begin(int render_target_width, int render_target_height) override;
void Draw(const ImmediateDrawBatch& batch) override;
void End() override;
private:
void InitializeShaders();
GLuint program_ = 0;
GLuint vao_ = 0;
GLuint vertex_buffer_ = 0;
GLuint index_buffer_ = 0;
bool was_current_ = false;
};
} // namespace gl
} // namespace ui
} // namespace xe
#endif // XENIA_UI_GL_GL_IMMEDIATE_DRAWER_H_

View File

@ -1,82 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_GL_GL_PROFILER_DISPLAY_H_
#define XENIA_UI_GL_GL_PROFILER_DISPLAY_H_
#include "xenia/profiling.h"
#include "xenia/ui/gl/circular_buffer.h"
#include "xenia/ui/window.h"
namespace xe {
namespace ui {
namespace gl {
class GLProfilerDisplay : public ProfilerDisplay {
public:
explicit GLProfilerDisplay(xe::ui::Window* window);
virtual ~GLProfilerDisplay();
uint32_t width() const override;
uint32_t height() const override;
// TODO(benvanik): GPU timestamping.
void Begin() override;
void End() override;
void DrawBox(int x0, int y0, int x1, int y1, uint32_t color,
BoxType type) override;
void DrawLine2D(uint32_t count, float* vertices, uint32_t color) override;
void DrawText(int x, int y, uint32_t color, const char* text,
size_t text_length) override;
private:
struct Vertex {
float x;
float y;
uint32_t color;
float u;
float v;
};
bool SetupFont();
bool SetupState();
bool SetupShaders();
Vertex* BeginVertices(size_t count);
void EndVertices(GLenum prim_type);
void Flush();
xe::ui::Window* window_ = nullptr;
GLuint program_ = 0;
GLuint vao_ = 0;
GLuint font_texture_ = 0;
GLuint64 font_handle_ = 0;
CircularBuffer vertex_buffer_;
static const size_t kMaxCommands = 32;
struct {
GLenum prim_type;
size_t vertex_offset;
size_t vertex_count;
} draw_commands_[kMaxCommands];
uint32_t draw_command_count_ = 0;
CircularBuffer::Allocation current_allocation_;
struct {
uint16_t char_offsets[256];
} font_description_ = {{0}};
};
} // namespace gl
} // namespace ui
} // namespace xe
#endif // XENIA_UI_GL_GL_PROFILER_DISPLAY_H_

View File

@ -13,11 +13,11 @@
#include <memory>
#include "el/graphics/renderer.h"
#include "xenia/profiling.h"
namespace xe {
namespace ui {
class ImmediateDrawer;
class Window;
class GraphicsContext {
@ -27,9 +27,10 @@ class GraphicsContext {
Window* target_window() const { return target_window_; }
virtual std::unique_ptr<GraphicsContext> CreateShared() = 0;
virtual std::unique_ptr<ProfilerDisplay> CreateProfilerDisplay() = 0;
virtual std::unique_ptr<el::graphics::Renderer> CreateElementalRenderer() = 0;
virtual ImmediateDrawer* immediate_drawer() = 0;
virtual bool is_current() = 0;
virtual bool MakeCurrent() = 0;
virtual void ClearCurrent() = 0;

View File

@ -0,0 +1,109 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_IMMEDIATE_DRAWER_H_
#define XENIA_UI_IMMEDIATE_DRAWER_H_
#include <memory>
namespace xe {
namespace ui {
class GraphicsContext;
// Describes the filter applied when sampling textures.
enum class ImmediateTextureFilter {
kNearest,
kLinear,
};
// Simple texture compatible with the immediate renderer.
class ImmediateTexture {
public:
virtual ~ImmediateTexture() = default;
// Texture width, in pixels.
uint32_t width;
// Texture height, in pixels.
uint32_t height;
// Internal handle. Can be passed with the ImmediateDrawBatch.
uintptr_t handle;
protected:
ImmediateTexture(uint32_t width, uint32_t height)
: width(width), height(height) {}
};
// Describes the primitive type used by a draw call.
enum class ImmediatePrimitiveType {
kLines,
kTriangles,
};
// Simple vertex used by the immediate mode drawer.
struct ImmediateVertex {
float x, y;
float u, v;
uint32_t color;
};
// All parameters required to draw an immediate-mode batch of vertices.
struct ImmediateDrawBatch {
// Primitive type the vertices/indices represent.
ImmediatePrimitiveType primitive_type = ImmediatePrimitiveType::kTriangles;
// Vertices to draw.
const ImmediateVertex* vertices = nullptr;
int vertex_count = 0;
// Optional index buffer indices.
const uint16_t* indices = nullptr;
int index_count = 0;
// Texture used when drawing, or nullptr if color only.
// This is most commonly the handle of an ImmediateTexture.
uintptr_t texture_handle = 0;
// True to enable scissoring using the region defined by scissor_rect.
bool scissor = false;
// Scissoring region in framebuffer pixels as (x, y, w, h).
int scissor_rect[4] = {0};
};
class ImmediateDrawer {
public:
virtual ~ImmediateDrawer() = default;
// Creates a new texture with the given attributes and optionally updates
// initial data.
virtual std::unique_ptr<ImmediateTexture> CreateTexture(
uint32_t width, uint32_t height, ImmediateTextureFilter filter,
bool repeat, const uint8_t* data = nullptr) = 0;
// Uploads data to the given texture, replacing the current contents.
virtual void UpdateTexture(ImmediateTexture* texture,
const uint8_t* data) = 0;
// Begins drawing in immediate mode using the given projection matrix.
virtual void Begin(int render_target_width, int render_target_height) = 0;
// Issues an immediate mode draw batch.
virtual void Draw(const ImmediateDrawBatch& batch) = 0;
// Ends drawing in immediate mode and flushes contents.
virtual void End() = 0;
protected:
ImmediateDrawer(GraphicsContext* graphics_context)
: graphics_context_(graphics_context) {}
GraphicsContext* graphics_context_ = nullptr;
};
} // namespace ui
} // namespace xe
#endif // XENIA_UI_IMMEDIATE_DRAWER_H_

View File

@ -7,37 +7,24 @@
******************************************************************************
*/
#include "xenia/ui/gl/gl_profiler_display.h"
#include "xenia/ui/microprofile_drawer.h"
#include <algorithm>
#include <string>
#include "third_party/microprofile/microprofile.h"
#include "third_party/microprofile/microprofileui.h"
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/ui/window.h"
namespace xe {
namespace ui {
namespace gl {
#define MICROPROFILE_MAX_VERTICES (16 << 10)
#define MICROPROFILE_NUM_QUERIES (8 << 10)
#define MAX_FONT_CHARS 256
#define Q0(d, member, v) d[0].member = v
#define Q1(d, member, v) \
d[1].member = v; \
d[3].member = v
#define Q2(d, member, v) d[4].member = v
#define Q3(d, member, v) \
d[2].member = v; \
d[5].member = v
const int kMaxVertices = 16 << 10;
const int FONT_TEX_X = 1024;
const int FONT_TEX_Y = 9;
const int kFontTextureWidth = 1024;
const int kFontTextureHeight = 9;
const int kFontCharWidth = 5;
const int kFontCharHeight = 8;
const uint8_t profiler_font[] = {
const uint8_t kFontData[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -136,66 +123,14 @@ const uint8_t profiler_font[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
GLProfilerDisplay::GLProfilerDisplay(xe::ui::Window* window)
MicroprofileDrawer::MicroprofileDrawer(xe::ui::Window* window)
: window_(window),
vertex_buffer_(MICROPROFILE_MAX_VERTICES * sizeof(Vertex) * 10,
sizeof(Vertex)) {
if (!SetupFont() || !SetupState() || !SetupShaders()) {
// Hrm.
assert_always();
}
window_->on_painted.AddListener([this](UIEvent* e) { Profiler::Present(); });
// Pass through mouse events.
window_->on_mouse_down.AddListener([this](MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseDown(e->button() == MouseEvent::Button::kLeft,
e->button() == MouseEvent::Button::kRight);
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_mouse_up.AddListener([this](MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseUp();
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_mouse_move.AddListener([this](MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseMove(e->x(), e->y());
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_mouse_wheel.AddListener([this](MouseEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnMouseWheel(e->x(), e->y(), -e->dy());
e->set_handled(true);
window_->Invalidate();
}
});
// Watch for toggle/mode keys and such.
window_->on_key_down.AddListener([this](KeyEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnKeyDown(e->key_code());
e->set_handled(true);
window_->Invalidate();
}
});
window_->on_key_up.AddListener([this](KeyEvent* e) {
if (Profiler::is_visible()) {
Profiler::OnKeyUp(e->key_code());
e->set_handled(true);
window_->Invalidate();
}
});
graphics_context_(window->context()),
vertices_(kMaxVertices) {
SetupFont();
}
bool GLProfilerDisplay::SetupFont() {
void MicroprofileDrawer::SetupFont() {
// Setup font lookup table.
for (uint32_t i = 0; i < xe::countof(font_description_.char_offsets); ++i) {
font_description_.char_offsets[i] = 206;
@ -223,223 +158,74 @@ bool GLProfilerDisplay::SetupFont() {
}
// Unpack font bitmap into an RGBA texture.
const int UNPACKED_SIZE = FONT_TEX_X * FONT_TEX_Y * 4;
const int UNPACKED_SIZE = kFontTextureWidth * kFontTextureHeight * 4;
uint32_t unpacked[UNPACKED_SIZE];
int idx = 0;
int end = FONT_TEX_X * FONT_TEX_Y / 8;
int end = kFontTextureWidth * kFontTextureHeight / 8;
for (int i = 0; i < end; i++) {
uint8_t b = profiler_font[i];
uint8_t b = kFontData[i];
for (int j = 0; j < 8; ++j) {
unpacked[idx++] = b & 0x80 ? 0xFFFFFFFFu : 0;
b <<= 1;
}
}
glCreateTextures(GL_TEXTURE_2D, 1, &font_texture_);
glTextureParameteri(font_texture_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(font_texture_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(font_texture_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(font_texture_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTextureStorage2D(font_texture_, 1, GL_RGBA8, FONT_TEX_X, FONT_TEX_Y);
glTextureSubImage2D(font_texture_, 0, 0, 0, FONT_TEX_X, FONT_TEX_Y, GL_RGBA,
GL_UNSIGNED_BYTE, unpacked);
font_handle_ = glGetTextureHandleARB(font_texture_);
glMakeTextureHandleResidentARB(font_handle_);
return true;
font_texture_ = graphics_context_->immediate_drawer()->CreateTexture(
kFontTextureWidth, kFontTextureHeight, ImmediateTextureFilter::kNearest,
false, reinterpret_cast<uint8_t*>(unpacked));
}
bool GLProfilerDisplay::SetupState() {
if (!vertex_buffer_.Initialize()) {
return false;
}
return true;
MicroprofileDrawer::~MicroprofileDrawer() { font_texture_.reset(); }
void MicroprofileDrawer::Begin() {
graphics_context_->immediate_drawer()->Begin(window_->width(),
window_->height());
}
bool GLProfilerDisplay::SetupShaders() {
const std::string header =
R"(
#version 450
#extension GL_ARB_bindless_texture : require
#extension GL_ARB_explicit_uniform_location : require
#extension GL_ARB_shading_language_420pack : require
precision highp float;
precision highp int;
layout(std140, column_major) uniform;
layout(std430, column_major) buffer;
)";
const std::string vertex_shader_source = header +
R"(
layout(location = 0) uniform mat4 projection_matrix;
layout(location = 0) in vec2 in_pos;
layout(location = 1) in vec4 in_color;
layout(location = 2) in vec2 in_uv;
layout(location = 0) out vec4 vtx_color;
layout(location = 1) out vec2 vtx_uv;
void main() {
gl_Position = projection_matrix * vec4(in_pos.xy, 0.0, 1.0);
vtx_color = in_color;
vtx_uv = in_uv;
})";
const std::string fragment_shader_source = header +
R"(
layout(location = 1, bindless_sampler) uniform sampler2D font_texture;
layout(location = 2) uniform float font_height;
layout(location = 0) in vec4 vtx_color;
layout(location = 1) in vec2 vtx_uv;
layout(location = 0) out vec4 oC;
void main() {
if (vtx_uv.x > 1.0) {
oC = vtx_color;
} else {
vec4 color = texture(font_texture, vtx_uv);
oC = color.rgba * vtx_color;
if (color.a < 0.5) {
vec4 c1 = texture(font_texture, vtx_uv + vec2(0.0, font_height));
oC = vec4(0, 0, 0, c1.a);
}
}
})";
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
const char* vertex_shader_source_ptr = vertex_shader_source.c_str();
GLint vertex_shader_source_length = GLint(vertex_shader_source.size());
glShaderSource(vertex_shader, 1, &vertex_shader_source_ptr,
&vertex_shader_source_length);
glCompileShader(vertex_shader);
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
const char* fragment_shader_source_ptr = fragment_shader_source.c_str();
GLint fragment_shader_source_length = GLint(fragment_shader_source.size());
glShaderSource(fragment_shader, 1, &fragment_shader_source_ptr,
&fragment_shader_source_length);
glCompileShader(fragment_shader);
program_ = glCreateProgram();
glAttachShader(program_, vertex_shader);
glAttachShader(program_, fragment_shader);
glLinkProgram(program_);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
glProgramUniformHandleui64ARB(program_, 1, font_handle_);
glProgramUniform1f(program_, 2, 1.0f / FONT_TEX_Y);
glCreateVertexArrays(1, &vao_);
glEnableVertexArrayAttrib(vao_, 0);
glVertexArrayAttribBinding(vao_, 0, 0);
glVertexArrayAttribFormat(vao_, 0, 2, GL_FLOAT, GL_FALSE,
offsetof(Vertex, x));
glEnableVertexArrayAttrib(vao_, 1);
glVertexArrayAttribBinding(vao_, 1, 0);
glVertexArrayAttribFormat(vao_, 1, 4, GL_UNSIGNED_BYTE, GL_TRUE,
offsetof(Vertex, color));
glEnableVertexArrayAttrib(vao_, 2);
glVertexArrayAttribBinding(vao_, 2, 0);
glVertexArrayAttribFormat(vao_, 2, 2, GL_FLOAT, GL_FALSE,
offsetof(Vertex, u));
glVertexArrayVertexBuffer(vao_, 0, vertex_buffer_.handle(), 0,
sizeof(Vertex));
return true;
}
GLProfilerDisplay::~GLProfilerDisplay() {
vertex_buffer_.Shutdown();
glMakeTextureHandleNonResidentARB(font_handle_);
glDeleteTextures(1, &font_texture_);
glDeleteVertexArrays(1, &vao_);
glDeleteProgram(program_);
}
uint32_t GLProfilerDisplay::width() const { return window_->width(); }
uint32_t GLProfilerDisplay::height() const { return window_->height(); }
void GLProfilerDisplay::Begin() {
glEnablei(GL_BLEND, 0);
glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_SCISSOR_TEST);
glViewport(0, 0, width(), height());
float left = 0.0f;
float right = static_cast<float>(width());
float bottom = static_cast<float>(height());
float top = 0.0f;
float z_near = -1.0f;
float z_far = 1.0f;
float projection[16] = {0};
projection[0] = 2.0f / (right - left);
projection[5] = 2.0f / (top - bottom);
projection[10] = -2.0f / (z_far - z_near);
projection[12] = -(right + left) / (right - left);
projection[13] = -(top + bottom) / (top - bottom);
projection[14] = -(z_far + z_near) / (z_far - z_near);
projection[15] = 1.0f;
glProgramUniformMatrix4fv(program_, 0, 1, GL_FALSE, projection);
glUseProgram(program_);
glBindVertexArray(vao_);
}
void GLProfilerDisplay::End() {
void MicroprofileDrawer::End() {
Flush();
glUseProgram(0);
glBindVertexArray(0);
graphics_context_->immediate_drawer()->End();
}
GLProfilerDisplay::Vertex* GLProfilerDisplay::BeginVertices(size_t count) {
if (draw_command_count_ + 1 > kMaxCommands) {
ImmediateVertex* MicroprofileDrawer::BeginVertices(
ImmediatePrimitiveType primitive_type, int count) {
if (vertex_count_ + count > vertices_.size() ||
primitive_type != current_primitive_type_) {
Flush();
}
size_t total_length = sizeof(Vertex) * count;
if (!vertex_buffer_.CanAcquire(total_length)) {
Flush();
}
current_allocation_ = vertex_buffer_.Acquire(total_length);
return reinterpret_cast<Vertex*>(current_allocation_.host_ptr);
current_primitive_type_ = primitive_type;
auto ptr = vertices_.data() + vertex_count_;
vertex_count_ += count;
return ptr;
}
void GLProfilerDisplay::EndVertices(GLenum prim_type) {
size_t vertex_count = current_allocation_.length / sizeof(Vertex);
void MicroprofileDrawer::EndVertices() {}
if (draw_command_count_ &&
draw_commands_[draw_command_count_ - 1].prim_type == prim_type) {
// Coalesce.
auto& prev_command = draw_commands_[draw_command_count_ - 1];
prev_command.vertex_count += vertex_count;
} else {
auto& command = draw_commands_[draw_command_count_++];
command.prim_type = prim_type;
command.vertex_offset = current_allocation_.offset / sizeof(Vertex);
command.vertex_count = vertex_count;
}
vertex_buffer_.Commit(std::move(current_allocation_));
}
void GLProfilerDisplay::Flush() {
if (!draw_command_count_) {
void MicroprofileDrawer::Flush() {
if (!vertex_count_) {
return;
}
vertex_buffer_.Flush();
for (size_t i = 0; i < draw_command_count_; ++i) {
glDrawArrays(draw_commands_[i].prim_type,
GLint(draw_commands_[i].vertex_offset),
GLsizei(draw_commands_[i].vertex_count));
}
draw_command_count_ = 0;
// TODO(benvanik): don't finish here.
vertex_buffer_.WaitUntilClean();
ImmediateDrawBatch batch;
batch.primitive_type = current_primitive_type_;
batch.vertices = vertices_.data();
batch.vertex_count = vertex_count_;
batch.texture_handle = font_texture_->handle;
graphics_context_->immediate_drawer()->Draw(batch);
vertex_count_ = 0;
}
void GLProfilerDisplay::DrawBox(int x0, int y0, int x1, int y1, uint32_t color,
BoxType type) {
auto v = BeginVertices(6);
#define Q0(d, member, v) d[0].member = v
#define Q1(d, member, v) \
d[1].member = v; \
d[3].member = v
#define Q2(d, member, v) d[4].member = v
#define Q3(d, member, v) \
d[2].member = v; \
d[5].member = v
void MicroprofileDrawer::DrawBox(int x0, int y0, int x1, int y1, uint32_t color,
BoxType type) {
auto v = BeginVertices(ImmediatePrimitiveType::kTriangles, 6);
if (type == BoxType::kFlat) {
color =
((color & 0xff) << 16) | ((color >> 16) & 0xff) | (0xff00ff00 & color);
@ -500,15 +286,15 @@ void GLProfilerDisplay::DrawBox(int x0, int y0, int x1, int y1, uint32_t color,
Q3(v, u, 2.0f);
Q3(v, v, 3.0f);
}
EndVertices(GL_TRIANGLES);
EndVertices();
}
void GLProfilerDisplay::DrawLine2D(uint32_t count, float* vertices,
uint32_t color) {
void MicroprofileDrawer::DrawLine2D(uint32_t count, float* vertices,
uint32_t color) {
if (!count || !vertices) {
return;
}
auto v = BeginVertices(2 * (count - 1));
auto v = BeginVertices(ImmediatePrimitiveType::kLines, 2 * (count - 1));
color = 0xff000000 | ((color & 0xff) << 16) | (color & 0xff00ff00) |
((color >> 16) & 0xff);
for (uint32_t i = 0; i < count - 1; ++i) {
@ -524,21 +310,21 @@ void GLProfilerDisplay::DrawLine2D(uint32_t count, float* vertices,
v[1].v = 2.0f;
v += 2;
}
EndVertices(GL_LINES);
EndVertices();
}
void GLProfilerDisplay::DrawText(int x, int y, uint32_t color, const char* text,
size_t text_length) {
void MicroprofileDrawer::DrawText(int x, int y, uint32_t color,
const char* text, int text_length) {
if (!text_length) {
return;
}
const float fOffsetU = 5.0f / 1024.0f;
const float fOffsetU = kFontCharWidth / static_cast<float>(kFontTextureWidth);
float fX = static_cast<float>(x);
float fY = static_cast<float>(y);
float fY2 = fY + (MICROPROFILE_TEXT_HEIGHT + 1);
float fY2 = fY + (kFontCharHeight + 1);
auto v = BeginVertices(6 * text_length);
auto v = BeginVertices(ImmediatePrimitiveType::kTriangles, 6 * text_length);
const char* pStr = text;
color = 0xff000000 | ((color & 0xff) << 16) | (color & 0xff00) |
((color >> 16) & 0xff);
@ -552,13 +338,13 @@ void GLProfilerDisplay::DrawText(int x, int y, uint32_t color, const char* text,
Q0(v, u, fOffset);
Q0(v, v, 0.0f);
Q1(v, x, fX + MICROPROFILE_TEXT_WIDTH);
Q1(v, x, fX + kFontCharWidth);
Q1(v, y, fY);
Q1(v, color, color);
Q1(v, u, fOffset + fOffsetU);
Q1(v, v, 0.0f);
Q2(v, x, fX + MICROPROFILE_TEXT_WIDTH);
Q2(v, x, fX + kFontCharWidth);
Q2(v, y, fY2);
Q2(v, color, color);
Q2(v, u, fOffset + fOffsetU);
@ -570,13 +356,12 @@ void GLProfilerDisplay::DrawText(int x, int y, uint32_t color, const char* text,
Q3(v, u, fOffset);
Q3(v, v, 1.0f);
fX += MICROPROFILE_TEXT_WIDTH + 1;
fX += kFontCharWidth + 1;
v += 6;
}
EndVertices(GL_TRIANGLES);
EndVertices();
}
} // namespace gl
} // namespace ui
} // namespace xe

View File

@ -0,0 +1,65 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_MICROPROFILE_DRAWER_H_
#define XENIA_UI_MICROPROFILE_DRAWER_H_
#include <memory>
#include <vector>
#include "xenia/ui/immediate_drawer.h"
namespace xe {
namespace ui {
class GraphicsContext;
class Window;
class MicroprofileDrawer {
public:
enum class BoxType {
kBar = 0, // MicroProfileBoxTypeBar
kFlat = 1, // MicroProfileBoxTypeFlat
};
MicroprofileDrawer(Window* window);
~MicroprofileDrawer();
void Begin();
void End();
void DrawBox(int x0, int y0, int x1, int y1, uint32_t color, BoxType type);
void DrawLine2D(uint32_t count, float* vertices, uint32_t color);
void DrawText(int x, int y, uint32_t color, const char* text,
int text_length);
protected:
void SetupFont();
ImmediateVertex* BeginVertices(ImmediatePrimitiveType primitive_type,
int count);
void EndVertices();
void Flush();
Window* window_ = nullptr;
GraphicsContext* graphics_context_ = nullptr;
std::vector<ImmediateVertex> vertices_;
int vertex_count_ = 0;
ImmediatePrimitiveType current_primitive_type_;
std::unique_ptr<ImmediateTexture> font_texture_;
struct {
uint16_t char_offsets[256];
} font_description_ = {{0}};
};
} // namespace ui
} // namespace xe
#endif // XENIA_UI_MICROPROFILE_DRAWER_H_