Switching to my turbo badger fork.

This commit is contained in:
Ben Vanik
2015-07-06 18:12:17 -07:00
parent f2ce11d268
commit 253a685dde
21 changed files with 882 additions and 1576 deletions

View File

@@ -87,7 +87,7 @@ class Control {
protected:
explicit Control(uint32_t flags);
virtual bool Create() = 0;
virtual bool Create() { return true; }
virtual void Destroy() {}
virtual void OnCreate() {}

View File

@@ -0,0 +1,478 @@
/**
******************************************************************************
* 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/elemental_control.h"
#include "el/animation_manager.h"
#include "el/elemental_forms.h"
#include "el/text/font_manager.h"
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
namespace xe {
namespace ui {
constexpr bool kContinuousRepaint = false;
// Enables long press behaviors (context menu, etc).
constexpr bool kTouch = false;
constexpr uint64_t kDoubleClickDelayMillis = 600;
constexpr double kDoubleClickDistance = 5;
constexpr int32_t kMouseWheelDetent = 120;
class RootElement : public el::Element {
public:
RootElement(ElementalControl* owner) : owner_(owner) {}
void OnInvalid() override { owner_->Invalidate(); }
private:
ElementalControl* owner_ = nullptr;
};
bool ElementalControl::InitializeElemental(el::graphics::Renderer* renderer) {
static bool has_initialized = false;
if (has_initialized) {
return true;
}
has_initialized = true;
if (!el::Initialize(
renderer,
"third_party/turbobadger/resources/language/lng_en.tb.txt")) {
XELOGE("Failed to initialize turbobadger core");
return false;
}
// Load the default skin, and override skin that contains the graphics
// specific to the demo.
if (!el::Skin::get()->Load(
"third_party/elemental-forms/resources/default_skin/skin.tb.txt",
"third_party/elemental-forms/testbed/resources/skin/skin.tb.txt")) {
XELOGE("Failed to load turbobadger skin");
return false;
}
// Register font renderers.
#ifdef EL_FONT_RENDERER_TBBF
void register_tbbf_font_renderer();
register_tbbf_font_renderer();
#endif
#ifdef EL_FONT_RENDERER_STB
void register_stb_font_renderer();
register_stb_font_renderer();
#endif
#ifdef EL_FONT_RENDERER_FREETYPE
void register_freetype_font_renderer();
register_freetype_font_renderer();
#endif
auto font_manager = el::text::FontManager::get();
// Add fonts we can use to the font manager.
#if defined(EL_FONT_RENDERER_STB) || defined(EL_FONT_RENDERER_FREETYPE)
font_manager->AddFontInfo("third_party/elemental-forms/resources/vera.ttf",
"Default");
#endif
#ifdef EL_FONT_RENDERER_TBBF
font_manager->AddFontInfo(
"third_party/elemental-forms/resources/default_font/"
"segoe_white_with_shadow.tb.txt",
"Default");
#endif
// Set the default font description for elements to one of the fonts we just
// added.
el::FontDescription fd;
fd.set_id(TBIDC("Default"));
fd.set_size(el::Skin::get()->dimension_converter()->DpToPx(14));
font_manager->set_default_font_description(fd);
// Create the font now.
auto font =
font_manager->CreateFontFace(font_manager->default_font_description());
return true;
}
ElementalControl::ElementalControl(Loop* loop, uint32_t flags) : super(flags) {}
ElementalControl::~ElementalControl() = default;
bool ElementalControl::Create() {
if (!super::Create()) {
return false;
}
// Create subclass renderer (GL, etc).
renderer_ = CreateRenderer();
// Initialize elemental.
// TODO(benvanik): once? Do we care about multiple controls?
if (!InitializeElemental(renderer_.get())) {
XELOGE("Unable to initialize turbobadger");
return false;
}
// TODO(benvanik): setup elements.
root_element_ = std::make_unique<RootElement>(this);
root_element_->set_background_skin(TBIDC("background"));
root_element_->set_rect({0, 0, 1000, 1000});
// Block animations during init.
el::AnimationBlocker anim_blocker;
// TODO(benvanik): dummy UI.
auto message_window = new el::MessageWindow(root_element(), TBIDC(""));
message_window->Show("Title", "Hello!");
// el::ShowDebugInfoSettingsWindow(root_element());
return true;
}
void ElementalControl::Destroy() {
el::Shutdown();
super::Destroy();
}
void ElementalControl::OnLayout(UIEvent& e) {
super::OnLayout(e);
if (!root_element()) {
return;
}
// TODO(benvanik): subregion?
root_element()->set_rect({0, 0, width(), height()});
}
void ElementalControl::OnPaint(UIEvent& e) {
super::OnPaint(e);
if (!root_element()) {
return;
}
++frame_count_;
++fps_frame_count_;
uint64_t now_ns = xe::Clock::QueryHostSystemTime();
if (now_ns > fps_update_time_ + 1000 * 10000) {
fps_ = uint32_t(fps_frame_count_ /
(double(now_ns - fps_update_time_) / 10000000.0));
fps_update_time_ = now_ns;
fps_frame_count_ = 0;
}
// Update TB (run animations, handle deferred input, etc).
el::AnimationManager::Update();
root_element()->InvokeProcessStates();
root_element()->InvokeProcess();
renderer()->BeginPaint(width(), height());
// Render entire control hierarchy.
root_element()->InvokePaint(el::Element::PaintProps());
// Render debug overlay.
root_element()->computed_font()->DrawString(
5, 5, el::Color(255, 0, 0),
el::format_string("Frame %lld", frame_count_));
if (kContinuousRepaint) {
root_element()->computed_font()->DrawString(
5, 20, el::Color(255, 0, 0), el::format_string("FPS: %d", fps_));
}
renderer()->EndPaint();
// If animations are running, reinvalidate immediately.
if (el::AnimationManager::has_running_animations()) {
root_element()->Invalidate();
}
if (kContinuousRepaint) {
// Force an immediate repaint, always.
root_element()->Invalidate();
}
}
void ElementalControl::OnGotFocus(UIEvent& e) { super::OnGotFocus(e); }
void ElementalControl::OnLostFocus(UIEvent& e) {
super::OnLostFocus(e);
modifier_shift_pressed_ = false;
modifier_cntrl_pressed_ = false;
modifier_alt_pressed_ = false;
modifier_super_pressed_ = false;
last_click_time_ = 0;
}
el::ModifierKeys ElementalControl::GetModifierKeys() {
auto modifiers = el::ModifierKeys::kNone;
if (modifier_shift_pressed_) {
modifiers |= el::ModifierKeys::kShift;
}
if (modifier_cntrl_pressed_) {
modifiers |= el::ModifierKeys::kCtrl;
}
if (modifier_alt_pressed_) {
modifiers |= el::ModifierKeys::kAlt;
}
if (modifier_super_pressed_) {
modifiers |= el::ModifierKeys::kSuper;
}
return modifiers;
}
void ElementalControl::OnKeyPress(KeyEvent& e, bool is_down) {
if (!root_element()) {
return;
}
auto special_key = el::SpecialKey::kUndefined;
switch (e.key_code()) {
case 38:
special_key = el::SpecialKey::kUp;
break;
case 39:
special_key = el::SpecialKey::kRight;
break;
case 40:
special_key = el::SpecialKey::kDown;
break;
case 37:
special_key = el::SpecialKey::kLeft;
break;
case 112:
special_key = el::SpecialKey::kF1;
break;
case 113:
special_key = el::SpecialKey::kF2;
break;
case 114:
special_key = el::SpecialKey::kF3;
break;
case 115:
special_key = el::SpecialKey::kF4;
break;
case 116:
special_key = el::SpecialKey::kF5;
break;
case 117:
special_key = el::SpecialKey::kF6;
break;
case 118:
special_key = el::SpecialKey::kF7;
break;
case 119:
special_key = el::SpecialKey::kF8;
break;
case 120:
special_key = el::SpecialKey::kF9;
break;
case 121:
special_key = el::SpecialKey::kF10;
break;
case 122:
special_key = el::SpecialKey::kF11;
break;
case 123:
special_key = el::SpecialKey::kF12;
break;
case 33:
special_key = el::SpecialKey::kPageUp;
break;
case 34:
special_key = el::SpecialKey::kPageDown;
break;
case 36:
special_key = el::SpecialKey::kHome;
break;
case 35:
special_key = el::SpecialKey::kEnd;
break;
case 45:
special_key = el::SpecialKey::kInsert;
break;
case 9:
special_key = el::SpecialKey::kTab;
break;
case 46:
special_key = el::SpecialKey::kDelete;
break;
case 8:
special_key = el::SpecialKey::kBackspace;
break;
case 13:
special_key = el::SpecialKey::kEnter;
break;
case 27:
special_key = el::SpecialKey::kEsc;
break;
case 93:
if (!is_down && el::Element::focused_element) {
el::Event ev(el::EventType::kContextMenu);
ev.modifierkeys = GetModifierKeys();
el::Element::focused_element->InvokeEvent(ev);
e.set_handled(true);
return;
}
break;
case 16:
modifier_shift_pressed_ = is_down;
break;
case 17:
modifier_cntrl_pressed_ = is_down;
break;
// case xx:
// // alt ??
// modifier_alt_pressed_ = is_down;
// break;
case 91:
modifier_super_pressed_ = is_down;
break;
}
if (!CheckShortcutKey(e, special_key, is_down)) {
e.set_handled(root_element()->InvokeKey(
special_key != el::SpecialKey::kUndefined ? e.key_code() : 0,
special_key, GetModifierKeys(), is_down));
}
}
bool ElementalControl::CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key,
bool is_down) {
bool shortcut_key = modifier_cntrl_pressed_;
if (!el::Element::focused_element || !is_down || !shortcut_key) {
return false;
}
bool reverse_key = modifier_shift_pressed_;
int upper_key = e.key_code();
if (upper_key >= 'a' && upper_key <= 'z') {
upper_key += 'A' - 'a';
}
el::TBID id;
if (upper_key == 'X') {
id = TBIDC("cut");
} else if (upper_key == 'C' || special_key == el::SpecialKey::kInsert) {
id = TBIDC("copy");
} else if (upper_key == 'V' ||
(special_key == el::SpecialKey::kInsert && reverse_key)) {
id = TBIDC("paste");
} else if (upper_key == 'A') {
id = TBIDC("selectall");
} else if (upper_key == 'Z' || upper_key == 'Y') {
bool undo = upper_key == 'Z';
if (reverse_key) {
undo = !undo;
}
id = undo ? TBIDC("undo") : TBIDC("redo");
} else if (upper_key == 'N') {
id = TBIDC("new");
} else if (upper_key == 'O') {
id = TBIDC("open");
} else if (upper_key == 'S') {
id = TBIDC("save");
} else if (upper_key == 'W') {
id = TBIDC("close");
} else if (special_key == el::SpecialKey::kPageUp) {
id = TBIDC("prev_doc");
} else if (special_key == el::SpecialKey::kPageDown) {
id = TBIDC("next_doc");
} else {
return false;
}
el::Event ev(el::EventType::kShortcut);
ev.modifierkeys = GetModifierKeys();
ev.ref_id = id;
if (!el::Element::focused_element->InvokeEvent(ev)) {
return false;
}
e.set_handled(true);
return true;
}
void ElementalControl::OnKeyDown(KeyEvent& e) {
super::OnKeyDown(e);
OnKeyPress(e, true);
}
void ElementalControl::OnKeyUp(KeyEvent& e) {
super::OnKeyUp(e);
OnKeyPress(e, false);
}
void ElementalControl::OnMouseDown(MouseEvent& e) {
super::OnMouseDown(e);
if (!root_element()) {
return;
}
// TODO(benvanik): more button types.
if (e.button() == MouseEvent::Button::kLeft) {
// Simulated click count support.
// TODO(benvanik): move into Control?
uint64_t now = xe::Clock::QueryHostUptimeMillis();
if (now < last_click_time_ + kDoubleClickDelayMillis) {
double distance_moved = std::sqrt(std::pow(e.x() - last_click_x_, 2.0) +
std::pow(e.y() - last_click_y_, 2.0));
if (distance_moved < kDoubleClickDistance) {
++last_click_counter_;
} else {
last_click_counter_ = 1;
}
} else {
last_click_counter_ = 1;
}
last_click_x_ = e.x();
last_click_y_ = e.y();
last_click_time_ = now;
e.set_handled(root_element()->InvokePointerDown(
e.x(), e.y(), last_click_counter_, GetModifierKeys(), kTouch));
}
}
void ElementalControl::OnMouseMove(MouseEvent& e) {
super::OnMouseMove(e);
if (!root_element()) {
return;
}
root_element()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch);
e.set_handled(true);
}
void ElementalControl::OnMouseUp(MouseEvent& e) {
super::OnMouseUp(e);
if (!root_element()) {
return;
}
if (e.button() == MouseEvent::Button::kLeft) {
e.set_handled(root_element()->InvokePointerUp(e.x(), e.y(),
GetModifierKeys(), kTouch));
} else if (e.button() == MouseEvent::Button::kRight) {
root_element()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch);
if (el::Element::hovered_element) {
int x = e.x();
int y = e.y();
el::Element::hovered_element->ConvertFromRoot(x, y);
el::Event ev(el::EventType::kContextMenu, x, y, kTouch,
GetModifierKeys());
el::Element::hovered_element->InvokeEvent(ev);
}
e.set_handled(true);
}
}
void ElementalControl::OnMouseWheel(MouseEvent& e) {
super::OnMouseWheel(e);
if (!root_element()) {
return;
}
e.set_handled(root_element()->InvokeWheel(
e.x(), e.y(), e.dx(), -e.dy() / kMouseWheelDetent, GetModifierKeys()));
}
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,80 @@
/**
******************************************************************************
* 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_ELEMENTAL_CONTROL_H_
#define XENIA_UI_ELEMENTAL_CONTROL_H_
#include <memory>
#include "el/element.h"
#include "el/graphics/renderer.h"
#include "xenia/ui/control.h"
#include "xenia/ui/loop.h"
#include "xenia/ui/platform.h"
namespace xe {
namespace ui {
class ElementalControl : public PlatformControl {
public:
ElementalControl(Loop* loop, uint32_t flags);
~ElementalControl() override;
el::graphics::Renderer* renderer() const { return renderer_.get(); }
el::Element* root_element() const { return root_element_.get(); }
protected:
using super = PlatformControl;
bool InitializeElemental(el::graphics::Renderer* renderer);
virtual std::unique_ptr<el::graphics::Renderer> CreateRenderer() = 0;
bool Create() override;
void Destroy() override;
void OnLayout(UIEvent& e) override;
void OnPaint(UIEvent& e) override;
void OnGotFocus(UIEvent& e) override;
void OnLostFocus(UIEvent& e) override;
el::ModifierKeys GetModifierKeys();
void OnKeyPress(KeyEvent& e, bool is_down);
bool CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, bool is_down);
void OnKeyDown(KeyEvent& e) override;
void OnKeyUp(KeyEvent& e) override;
void OnMouseDown(MouseEvent& e) override;
void OnMouseMove(MouseEvent& e) override;
void OnMouseUp(MouseEvent& e) override;
void OnMouseWheel(MouseEvent& e) override;
std::unique_ptr<el::graphics::Renderer> renderer_;
std::unique_ptr<el::Element> root_element_;
uint32_t frame_count_ = 0;
uint32_t fps_ = 0;
uint64_t fps_update_time_ = 0;
uint64_t fps_frame_count_ = 0;
bool modifier_shift_pressed_ = false;
bool modifier_cntrl_pressed_ = false;
bool modifier_alt_pressed_ = false;
bool modifier_super_pressed_ = false;
uint64_t last_click_time_ = 0;
int last_click_x_ = 0;
int last_click_y_ = 0;
int last_click_counter_ = 0;
};
} // namespace ui
} // namespace xe
#endif // XENIA_UI_ELEMENTAL_CONTROL_H_

View File

@@ -0,0 +1,455 @@
/**
******************************************************************************
* 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/wgl_elemental_control.h"
#include <memory>
#include "el/graphics/batching_renderer.h"
#include "el/graphics/bitmap_fragment.h"
#include "el/util/math.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/profiling.h"
#include "xenia/ui/gl/circular_buffer.h"
#include "xenia/ui/gl/gl_context.h"
#include "xenia/ui/gl/gl.h"
namespace xe {
namespace ui {
namespace gl {
class GL4BatchingRenderer : public el::graphics::BatchingRenderer {
public:
GL4BatchingRenderer(GLContext* context);
~GL4BatchingRenderer() override;
static std::unique_ptr<GL4BatchingRenderer> Create(GLContext* context);
void BeginPaint(int render_target_w, int render_target_h) override;
void EndPaint() override;
std::unique_ptr<el::graphics::Bitmap> CreateBitmap(int width, int height,
uint32_t* data) override;
void RenderBatch(Batch* batch) override;
void set_clip_rect(const el::Rect& rect) override;
private:
class GL4Bitmap : public el::graphics::Bitmap {
public:
GL4Bitmap(GLContext* context, GL4BatchingRenderer* renderer);
~GL4Bitmap();
bool Init(int width, int height, uint32_t* data);
int width() override { return width_; }
int height() override { return height_; }
void set_data(uint32_t* data) override;
GLContext* context_ = nullptr;
GL4BatchingRenderer* renderer_ = nullptr;
int width_ = 0;
int height_ = 0;
GLuint handle_ = 0;
GLuint64 gpu_handle_ = 0;
};
bool Initialize();
void Flush();
GLContext* context_ = nullptr;
GLuint program_ = 0;
GLuint vao_ = 0;
CircularBuffer vertex_buffer_;
static const size_t kMaxCommands = 512;
struct {
GLenum prim_type;
size_t vertex_offset;
size_t vertex_count;
} draw_commands_[kMaxCommands] = {0};
uint32_t draw_command_count_ = 0;
GL4Bitmap* current_bitmap_ = nullptr;
};
WGLElementalControl::WGLElementalControl(Loop* loop)
: ElementalControl(loop, Flags::kFlagOwnPaint), loop_(loop) {}
WGLElementalControl::~WGLElementalControl() = default;
bool WGLElementalControl::Create() {
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wcex.lpfnWndProc = Win32Control::WndProcThunk;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = nullptr;
wcex.hIconSm = nullptr;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = L"XeniaWglElementalClass";
if (!RegisterClassEx(&wcex)) {
XELOGE("WGL RegisterClassEx failed");
return false;
}
// Create window.
DWORD window_style = WS_CHILD | WS_VISIBLE | SS_NOTIFY;
DWORD window_ex_style = 0;
hwnd_ =
CreateWindowEx(window_ex_style, L"XeniaWglElementalClass", L"Xenia",
window_style, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, parent_hwnd(), nullptr, hInstance, this);
if (!hwnd_) {
XELOGE("WGL CreateWindow failed");
return false;
}
if (!context_.Initialize(hwnd_)) {
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 false;
}
context_.AssertExtensionsPresent();
SetFocus(hwnd_);
return super::Create();
}
std::unique_ptr<el::graphics::Renderer> WGLElementalControl::CreateRenderer() {
return GL4BatchingRenderer::Create(&context_);
}
void WGLElementalControl::OnLayout(UIEvent& e) {
Control::ResizeToFill();
super::OnLayout(e);
}
LRESULT WGLElementalControl::WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam) {
switch (message) {
case WM_PAINT: {
invalidated_ = false;
ValidateRect(hWnd, nullptr);
SCOPE_profile_cpu_i("gpu", "xe::ui::gl::WGLElementalControl::WM_PAINT");
{
GLContextLock context_lock(&context_);
float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f};
glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color);
if (current_paint_callback_) {
current_paint_callback_();
current_paint_callback_ = nullptr;
}
UIEvent e(this);
OnPaint(e);
// TODO(benvanik): profiler present.
Profiler::Present();
}
{
SCOPE_profile_cpu_i("gpu",
"xe::ui::gl::WGLElementalControl::SwapBuffers");
SwapBuffers(context_.dc());
}
return 0;
} break;
}
return Win32Control::WndProc(hWnd, message, wParam, lParam);
}
void WGLElementalControl::SynchronousRepaint(
std::function<void()> paint_callback) {
SCOPE_profile_cpu_f("gpu");
// We may already have a pending paint from a previous request when we
// were minimized. We just overwrite it.
current_paint_callback_ = std::move(paint_callback);
// This will not return until the WM_PAINT has completed.
// Note, if we are minimized this won't do anything.
RedrawWindow(hwnd(), nullptr, nullptr,
RDW_INTERNALPAINT | RDW_UPDATENOW | RDW_ALLCHILDREN);
}
GL4BatchingRenderer::GL4Bitmap::GL4Bitmap(GLContext* context,
GL4BatchingRenderer* renderer)
: context_(context), renderer_(renderer) {}
GL4BatchingRenderer::GL4Bitmap::~GL4Bitmap() {
GLContextLock lock(context_);
// Must flush and unbind before we delete the texture.
renderer_->FlushBitmap(this);
glMakeTextureHandleNonResidentARB(gpu_handle_);
glDeleteTextures(1, &handle_);
}
bool GL4BatchingRenderer::GL4Bitmap::Init(int width, int height,
uint32_t* data) {
assert(width == el::util::GetNearestPowerOfTwo(width));
assert(height == el::util::GetNearestPowerOfTwo(height));
width_ = width;
height_ = height;
glCreateTextures(GL_TEXTURE_2D, 1, &handle_);
glTextureParameteri(handle_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(handle_, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTextureParameteri(handle_, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(handle_, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTextureStorage2D(handle_, 1, GL_RGBA8, width_, height_);
gpu_handle_ = glGetTextureHandleARB(handle_);
glMakeTextureHandleResidentARB(gpu_handle_);
set_data(data);
return true;
}
void GL4BatchingRenderer::GL4Bitmap::set_data(uint32_t* data) {
renderer_->FlushBitmap(this);
glTextureSubImage2D(handle_, 0, 0, 0, width_, height_, GL_RGBA,
GL_UNSIGNED_BYTE, data);
}
GL4BatchingRenderer::GL4BatchingRenderer(GLContext* context)
: context_(context), vertex_buffer_(kVertexBatchSize * sizeof(Vertex)) {}
GL4BatchingRenderer::~GL4BatchingRenderer() {
GLContextLock lock(context_);
vertex_buffer_.Shutdown();
glDeleteVertexArrays(1, &vao_);
glDeleteProgram(program_);
}
std::unique_ptr<GL4BatchingRenderer> GL4BatchingRenderer::Create(
GLContext* context) {
auto renderer = std::make_unique<GL4BatchingRenderer>(context);
if (!renderer->Initialize()) {
XELOGE("Failed to initialize TurboBadger GL4 renderer");
return nullptr;
}
return renderer;
}
bool GL4BatchingRenderer::Initialize() {
if (!vertex_buffer_.Initialize()) {
XELOGE("Failed to initialize circular buffer");
return false;
}
const std::string header =
"\n\
#version 450 \n\
#extension GL_ARB_bindless_texture : require \n\
#extension GL_ARB_explicit_uniform_location : require \n\
#extension GL_ARB_shading_language_420pack : require \n\
precision highp float; \n\
precision highp int; \n\
layout(std140, column_major) uniform; \n\
layout(std430, column_major) buffer; \n\
";
const std::string vertex_shader_source = header +
"\n\
layout(location = 0) uniform mat4 projection_matrix; \n\
layout(location = 0) in vec2 in_pos; \n\
layout(location = 1) in vec4 in_color; \n\
layout(location = 2) in vec2 in_uv; \n\
layout(location = 0) out vec4 vtx_color; \n\
layout(location = 1) out vec2 vtx_uv; \n\
void main() { \n\
gl_Position = projection_matrix * vec4(in_pos.xy, 0.0, 1.0); \n\
vtx_color = in_color; \n\
vtx_uv = in_uv; \n\
} \n\
";
const std::string fragment_shader_source = header +
"\n\
layout(location = 1, bindless_sampler) uniform sampler2D texture_sampler; \n\
layout(location = 2) uniform float texture_mix; \n\
layout(location = 0) in vec4 vtx_color; \n\
layout(location = 1) in vec2 vtx_uv; \n\
layout(location = 0) out vec4 oC; \n\
void main() { \n\
oC = vtx_color; \n\
if (texture_mix > 0.0) { \n\
vec4 color = texture(texture_sampler, vtx_uv); \n\
oC *= color.rgba; \n\
} \n\
} \n\
";
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);
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, col));
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;
}
std::unique_ptr<el::graphics::Bitmap> GL4BatchingRenderer::CreateBitmap(
int width, int height, uint32_t* data) {
auto bitmap = std::make_unique<GL4Bitmap>(context_, this);
if (!bitmap->Init(width, height, data)) {
return nullptr;
}
return std::unique_ptr<el::graphics::Bitmap>(bitmap.release());
}
void GL4BatchingRenderer::set_clip_rect(const el::Rect& rect) {
Flush();
glScissor(clip_rect_.x, screen_rect_.h - (clip_rect_.y + clip_rect_.h),
clip_rect_.w, clip_rect_.h);
}
void GL4BatchingRenderer::BeginPaint(int render_target_w, int render_target_h) {
BatchingRenderer::BeginPaint(render_target_w, render_target_h);
glEnablei(GL_BLEND, 0);
glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glEnable(GL_SCISSOR_TEST);
glViewport(0, 0, render_target_w, render_target_h);
glScissor(0, 0, render_target_w, render_target_h);
float left = 0.0f;
float right = float(render_target_w);
float bottom = float(render_target_h);
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);
current_bitmap_ = nullptr;
glUseProgram(program_);
glBindVertexArray(vao_);
}
void GL4BatchingRenderer::EndPaint() {
BatchingRenderer::EndPaint();
Flush();
glUseProgram(0);
glBindVertexArray(0);
}
void GL4BatchingRenderer::Flush() {
if (!draw_command_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();
}
void GL4BatchingRenderer::RenderBatch(Batch* batch) {
auto bitmap = static_cast<GL4Bitmap*>(batch->bitmap);
if (bitmap != current_bitmap_) {
current_bitmap_ = bitmap;
Flush();
glProgramUniformHandleui64ARB(program_, 1,
bitmap ? bitmap->gpu_handle_ : 0);
glProgramUniform1f(program_, 2, bitmap ? 1.0f : 0.0f);
}
if (draw_command_count_ + 1 > kMaxCommands) {
Flush();
}
size_t total_length = sizeof(Vertex) * batch->vertex_count;
if (!vertex_buffer_.CanAcquire(total_length)) {
Flush();
}
auto allocation = vertex_buffer_.Acquire(total_length);
// TODO(benvanik): custom batcher that lets us use the ringbuffer memory
// without a copy.
std::memcpy(allocation.host_ptr, batch->vertex, total_length);
if (draw_command_count_ &&
draw_commands_[draw_command_count_ - 1].prim_type == GL_TRIANGLES) {
// Coalesce.
assert_always("haven't seen this yet");
auto& prev_command = draw_commands_[draw_command_count_ - 1];
prev_command.vertex_count += batch->vertex_count;
} else {
auto& command = draw_commands_[draw_command_count_++];
command.prim_type = GL_TRIANGLES;
command.vertex_offset = allocation.offset / sizeof(Vertex);
command.vertex_count = batch->vertex_count;
}
vertex_buffer_.Commit(std::move(allocation));
}
} // namespace gl
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,55 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_
#define XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_
#include <functional>
#include "xenia/base/threading.h"
#include "xenia/ui/elemental_control.h"
#include "xenia/ui/gl/gl_context.h"
#include "xenia/ui/loop.h"
namespace xe {
namespace ui {
namespace gl {
class WGLElementalControl : public ElementalControl {
public:
WGLElementalControl(Loop* loop);
~WGLElementalControl() override;
GLContext* context() { return &context_; }
void SynchronousRepaint(std::function<void()> paint_callback);
protected:
using super = ElementalControl;
std::unique_ptr<el::graphics::Renderer> CreateRenderer() override;
bool Create() override;
void OnLayout(UIEvent& e) override;
LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam) override;
private:
Loop* loop_;
GLContext context_;
std::function<void()> current_paint_callback_;
};
} // namespace gl
} // namespace ui
} // namespace xe
#endif // XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_

View File

@@ -12,6 +12,7 @@
#define XENIA_UI_PLATFORM_H_
// TODO(benvanik): only on windows.
#include "xenia/ui/win32/win32_control.h"
#include "xenia/ui/win32/win32_file_picker.h"
#include "xenia/ui/win32/win32_loop.h"
#include "xenia/ui/win32/win32_menu_item.h"
@@ -20,6 +21,7 @@
namespace xe {
namespace ui {
using PlatformControl = xe::ui::win32::Win32Control;
using PlatformFilePicker = xe::ui::win32::Win32FilePicker;
using PlatformLoop = xe::ui::win32::Win32Loop;
using PlatformMenu = xe::ui::win32::Win32MenuItem;