xe::ui control for hosting a turbobadger UI.

This commit is contained in:
Ben Vanik 2015-07-01 15:58:04 -07:00
parent dec0d12cc9
commit 4ec0655751
19 changed files with 1069 additions and 13 deletions

View File

@ -58,6 +58,8 @@
#define strdup _strdup
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#undef DeleteBitmap
#undef GetFirstChild
#endif // XE_PLATFORM_WIN32
#if XE_COMPILER_MSVC

View File

@ -9,19 +9,33 @@
#include "xenia/debug/ui/application.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
#include "xenia/debug/ui/main_window.h"
#include "xenia/profiling.h"
#include "third_party/turbobadger/src/tb/tb_msg.h"
#include "third_party/turbobadger/src/tb/tb_system.h"
namespace xe {
namespace debug {
namespace ui {
Application::Application() {}
Application* current_application_ = nullptr;
Application::~Application() = default;
Application* Application::current() {
assert_not_null(current_application_);
return current_application_;
}
Application::Application() { current_application_ = this; }
Application::~Application() {
assert_true(current_application_ == this);
current_application_ = nullptr;
}
std::unique_ptr<Application> Application::Create() {
std::unique_ptr<Application> app(new Application());
@ -64,3 +78,33 @@ void Application::Quit() {
} // namespace ui
} // namespace debug
} // namespace xe
// This doesn't really belong here (it belongs in tb_system_[linux/windows].cpp.
// This is here since the proper implementations has not yet been done.
void tb::TBSystem::RescheduleTimer(uint64_t fire_time) {
if (fire_time == tb::TB_NOT_SOON) {
return;
}
uint64_t now = tb::TBSystem::GetTimeMS();
uint64_t delay_millis = fire_time >= now ? fire_time - now : 0;
xe::debug::ui::Application::current()->loop()->PostDelayed([]() {
uint64_t next_fire_time = tb::TBMessageHandler::GetNextMessageFireTime();
uint64_t now = tb::TBSystem::GetTimeMS();
if (now < next_fire_time) {
// We timed out *before* we were supposed to (the OS is not playing nice).
// Calling ProcessMessages now won't achieve a thing so force a reschedule
// of the platform timer again with the same time.
// ReschedulePlatformTimer(next_fire_time, true);
return;
}
tb::TBMessageHandler::ProcessMessages();
// If we still have things to do (because we didn't process all messages,
// or because there are new messages), we need to rescedule, so call
// RescheduleTimer.
tb::TBSystem::RescheduleTimer(
tb::TBMessageHandler::GetNextMessageFireTime());
}, delay_millis);
}

View File

@ -25,6 +25,7 @@ class Application {
~Application();
static std::unique_ptr<Application> Create();
static Application* current();
xe::ui::Loop* loop() { return &loop_; }
MainWindow* main_window() const { return main_window_.get(); }

View File

@ -13,6 +13,7 @@
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
#include "xenia/debug/ui/turbo_badger_control.h"
namespace xe {
namespace debug {
@ -88,7 +89,7 @@ bool MainWindow::Initialize() {
// 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>(loop());
control_ = std::make_unique<TurboBadgerControl>(loop());
AddChild(control_.get());
Resize(1440, 1200);

View File

@ -13,7 +13,6 @@
#include <memory>
#include "xenia/debug/ui/application.h"
#include "xenia/ui/gl/wgl_control.h"
#include "xenia/ui/platform.h"
#include "xenia/ui/window.h"
@ -21,6 +20,8 @@ namespace xe {
namespace debug {
namespace ui {
class TurboBadgerControl;
class MainWindow : public xe::ui::PlatformWindow {
public:
MainWindow(Application* app);
@ -36,7 +37,7 @@ class MainWindow : public xe::ui::PlatformWindow {
Application* app_ = nullptr;
xe::ui::PlatformMenu main_menu_;
std::unique_ptr<xe::ui::gl::WGLControl> control_;
std::unique_ptr<TurboBadgerControl> control_;
};
} // namespace ui

View File

@ -0,0 +1,471 @@
/**
******************************************************************************
* 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/debug/ui/turbo_badger_control.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/debug/ui/turbo_badger_renderer.h"
// TODO(benvanik): remove this.
#include "xenia/debug/ui/application.h"
#include "third_party/turbobadger/src/tb/tb_core.h"
#include "third_party/turbobadger/src/tb/tb_font_renderer.h"
#include "third_party/turbobadger/src/tb/tb_message_window.h"
#include "third_party/turbobadger/src/tb/tb_widget_animation.h"
namespace xe {
namespace debug {
namespace ui {
constexpr bool kContinuousRepaint = true;
// 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 RootWidget : public tb::TBWidget {
public:
RootWidget(TurboBadgerControl* owner) : owner_(owner) {}
void OnInvalid() override { owner_->Invalidate(); }
private:
TurboBadgerControl* owner_ = nullptr;
};
bool TurboBadgerControl::InitializeTurboBadger(tb::TBRenderer* renderer) {
static bool has_initialized = false;
if (has_initialized) {
return true;
}
has_initialized = true;
if (!tb::tb_core_init(
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 (!tb::g_tb_skin->Load(
"third_party/turbobadger/resources/default_skin/skin.tb.txt",
"third_party/turbobadger/Demo/demo01/skin/skin.tb.txt")) {
XELOGE("Failed to load turbobadger skin");
return false;
}
// Register font renderers.
#ifdef TB_FONT_RENDERER_TBBF
void register_tbbf_font_renderer();
register_tbbf_font_renderer();
#endif
#ifdef TB_FONT_RENDERER_STB
void register_stb_font_renderer();
register_stb_font_renderer();
#endif
#ifdef TB_FONT_RENDERER_FREETYPE
void register_freetype_font_renderer();
register_freetype_font_renderer();
#endif
// Add fonts we can use to the font manager.
#if defined(TB_FONT_RENDERER_STB) || defined(TB_FONT_RENDERER_FREETYPE)
tb::g_font_manager->AddFontInfo("third_party/turbobadger/resources/vera.ttf",
"Default");
#endif
#ifdef TB_FONT_RENDERER_TBBF
tb::g_font_manager->AddFontInfo(
"third_party/turbobadger/resources/default_font/"
"segoe_white_with_shadow.tb.txt",
"Default");
#endif
// Set the default font description for widgets to one of the fonts we just
// added.
tb::TBFontDescription fd;
fd.SetID(TBIDC("Default"));
fd.SetSize(tb::g_tb_skin->GetDimensionConverter()->DpToPx(14));
tb::g_font_manager->SetDefaultFontDescription(fd);
// Create the font now.
auto font = tb::g_font_manager->CreateFontFace(
tb::g_font_manager->GetDefaultFontDescription());
return true;
}
void TurboBadgerControl::ShutdownTurboBadger() { tb::tb_core_shutdown(); }
TurboBadgerControl::TurboBadgerControl(xe::ui::Loop* loop) : super(loop) {}
TurboBadgerControl::~TurboBadgerControl() = default;
bool TurboBadgerControl::Create() {
if (!super::Create()) {
return false;
}
// TODO(benvanik): setup renderer?
renderer_ = TBRendererGL4::Create();
if (!InitializeTurboBadger(renderer_.get())) {
XELOGE("Unable to initialize turbobadger");
return false;
}
tb::TBWidgetsAnimationManager::Init();
// TODO(benvanik): setup widgets.
root_widget_ = std::make_unique<RootWidget>(this);
root_widget_->SetSkinBg(TBIDC("background"));
root_widget_->SetRect(tb::TBRect(0, 0, 1000, 1000));
// Block animations during init.
tb::TBAnimationBlocker anim_blocker;
// TODO(benvanik): dummy UI.
auto message_window = new tb::TBMessageWindow(root_widget(), TBIDC(""));
message_window->Show("Title", "Hello!");
// tb::ShowDebugInfoSettingsWindow(root_widget());
return true;
}
void TurboBadgerControl::Destroy() {
tb::TBWidgetsAnimationManager::Shutdown();
super::Destroy();
}
void TurboBadgerControl::OnLayout(xe::ui::UIEvent& e) {
super::OnLayout(e);
if (!root_widget()) {
return;
}
// TODO(benvanik): subregion?
root_widget()->SetRect(tb::TBRect(0, 0, width(), height()));
}
void TurboBadgerControl::OnPaint(xe::ui::UIEvent& e) {
super::OnPaint(e);
++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).
tb::TBAnimationManager::Update();
root_widget()->InvokeProcessStates();
root_widget()->InvokeProcess();
renderer()->BeginPaint(width(), height());
// Render entire control hierarchy.
root_widget()->InvokePaint(tb::TBWidget::PaintProps());
// Render debug overlay.
root_widget()->GetFont()->DrawString(
5, 5, tb::TBColor(255, 0, 0),
tb::format_string("Frame %lld", frame_count_));
if (kContinuousRepaint) {
root_widget()->GetFont()->DrawString(5, 20, tb::TBColor(255, 0, 0),
tb::format_string("FPS: %d", fps_));
}
renderer()->EndPaint();
// If animations are running, reinvalidate immediately.
if (tb::TBAnimationManager::HasAnimationsRunning()) {
root_widget()->Invalidate();
}
if (kContinuousRepaint) {
// Force an immediate repaint, always.
root_widget()->Invalidate();
}
}
void TurboBadgerControl::OnGotFocus(xe::ui::UIEvent& e) {
super::OnGotFocus(e);
}
void TurboBadgerControl::OnLostFocus(xe::ui::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;
}
tb::MODIFIER_KEYS TurboBadgerControl::GetModifierKeys() {
auto modifiers = tb::TB_MODIFIER_NONE;
if (modifier_shift_pressed_) {
modifiers |= tb::MODIFIER_KEYS::TB_SHIFT;
}
if (modifier_cntrl_pressed_) {
modifiers |= tb::MODIFIER_KEYS::TB_CTRL;
}
if (modifier_alt_pressed_) {
modifiers |= tb::MODIFIER_KEYS::TB_ALT;
}
if (modifier_super_pressed_) {
modifiers |= tb::MODIFIER_KEYS::TB_SUPER;
}
return modifiers;
}
void TurboBadgerControl::OnKeyPress(xe::ui::KeyEvent& e, bool is_down) {
tb::SPECIAL_KEY special_key = tb::SPECIAL_KEY::TB_KEY_UNDEFINED;
switch (e.key_code()) {
case 38:
special_key = tb::SPECIAL_KEY::TB_KEY_UP;
break;
case 39:
special_key = tb::SPECIAL_KEY::TB_KEY_RIGHT;
break;
case 40:
special_key = tb::SPECIAL_KEY::TB_KEY_DOWN;
break;
case 37:
special_key = tb::SPECIAL_KEY::TB_KEY_LEFT;
break;
case 112:
special_key = tb::SPECIAL_KEY::TB_KEY_F1;
break;
case 113:
special_key = tb::SPECIAL_KEY::TB_KEY_F2;
break;
case 114:
special_key = tb::SPECIAL_KEY::TB_KEY_F3;
break;
case 115:
special_key = tb::SPECIAL_KEY::TB_KEY_F4;
break;
case 116:
special_key = tb::SPECIAL_KEY::TB_KEY_F5;
break;
case 117:
special_key = tb::SPECIAL_KEY::TB_KEY_F6;
break;
case 118:
special_key = tb::SPECIAL_KEY::TB_KEY_F7;
break;
case 119:
special_key = tb::SPECIAL_KEY::TB_KEY_F8;
break;
case 120:
special_key = tb::SPECIAL_KEY::TB_KEY_F9;
break;
case 121:
special_key = tb::SPECIAL_KEY::TB_KEY_F10;
break;
case 122:
special_key = tb::SPECIAL_KEY::TB_KEY_F11;
break;
case 123:
special_key = tb::SPECIAL_KEY::TB_KEY_F12;
break;
case 33:
special_key = tb::SPECIAL_KEY::TB_KEY_PAGE_UP;
break;
case 34:
special_key = tb::SPECIAL_KEY::TB_KEY_PAGE_DOWN;
break;
case 36:
special_key = tb::SPECIAL_KEY::TB_KEY_HOME;
break;
case 35:
special_key = tb::SPECIAL_KEY::TB_KEY_END;
break;
case 45:
special_key = tb::SPECIAL_KEY::TB_KEY_INSERT;
break;
case 9:
special_key = tb::SPECIAL_KEY::TB_KEY_TAB;
break;
case 46:
special_key = tb::SPECIAL_KEY::TB_KEY_DELETE;
break;
case 8:
special_key = tb::SPECIAL_KEY::TB_KEY_BACKSPACE;
break;
case 13:
special_key = tb::SPECIAL_KEY::TB_KEY_ENTER;
break;
case 27:
special_key = tb::SPECIAL_KEY::TB_KEY_ESC;
break;
case 93:
if (!is_down && tb::TBWidget::focused_widget) {
tb::TBWidgetEvent ev(tb::EVENT_TYPE_CONTEXT_MENU);
ev.modifierkeys = GetModifierKeys();
tb::TBWidget::focused_widget->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_widget()->InvokeKey(
special_key != tb::SPECIAL_KEY::TB_KEY_UNDEFINED ? e.key_code() : 0,
special_key, GetModifierKeys(), is_down));
}
}
bool TurboBadgerControl::CheckShortcutKey(xe::ui::KeyEvent& e,
tb::SPECIAL_KEY special_key,
bool is_down) {
bool shortcut_key = modifier_cntrl_pressed_;
if (!tb::TBWidget::focused_widget || !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';
}
tb::TBID id;
if (upper_key == 'X') {
id = TBIDC("cut");
} else if (upper_key == 'C' || special_key == tb::TB_KEY_INSERT) {
id = TBIDC("copy");
} else if (upper_key == 'V' ||
(special_key == tb::TB_KEY_INSERT && 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 == tb::TB_KEY_PAGE_UP) {
id = TBIDC("prev_doc");
} else if (special_key == tb::TB_KEY_PAGE_DOWN) {
id = TBIDC("next_doc");
} else {
return false;
}
tb::TBWidgetEvent ev(tb::EVENT_TYPE_SHORTCUT);
ev.modifierkeys = GetModifierKeys();
ev.ref_id = id;
if (!tb::TBWidget::focused_widget->InvokeEvent(ev)) {
return false;
}
e.set_handled(true);
return true;
}
void TurboBadgerControl::OnKeyDown(xe::ui::KeyEvent& e) {
super::OnKeyDown(e);
OnKeyPress(e, true);
}
void TurboBadgerControl::OnKeyUp(xe::ui::KeyEvent& e) {
super::OnKeyUp(e);
OnKeyPress(e, false);
}
void TurboBadgerControl::OnMouseDown(xe::ui::MouseEvent& e) {
super::OnMouseDown(e);
// TODO(benvanik): more button types.
if (e.button() == xe::ui::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_widget()->InvokePointerDown(
e.x(), e.y(), last_click_counter_, GetModifierKeys(), kTouch));
}
}
void TurboBadgerControl::OnMouseMove(xe::ui::MouseEvent& e) {
super::OnMouseMove(e);
root_widget()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch);
e.set_handled(true);
}
void TurboBadgerControl::OnMouseUp(xe::ui::MouseEvent& e) {
super::OnMouseUp(e);
if (e.button() == xe::ui::MouseEvent::Button::kLeft) {
e.set_handled(root_widget()->InvokePointerUp(e.x(), e.y(),
GetModifierKeys(), kTouch));
} else if (e.button() == xe::ui::MouseEvent::Button::kRight) {
root_widget()->InvokePointerMove(e.x(), e.y(), GetModifierKeys(), kTouch);
if (tb::TBWidget::hovered_widget) {
int x = e.x();
int y = e.y();
tb::TBWidget::hovered_widget->ConvertFromRoot(x, y);
tb::TBWidgetEvent ev(tb::EVENT_TYPE_CONTEXT_MENU, x, y, kTouch,
GetModifierKeys());
tb::TBWidget::hovered_widget->InvokeEvent(ev);
}
e.set_handled(true);
}
}
void TurboBadgerControl::OnMouseWheel(xe::ui::MouseEvent& e) {
super::OnMouseWheel(e);
e.set_handled(root_widget()->InvokeWheel(
e.x(), e.y(), e.dx(), -e.dy() / kMouseWheelDetent, GetModifierKeys()));
}
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -0,0 +1,83 @@
/**
******************************************************************************
* 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_DEBUG_UI_TURBO_BADGER_CONTROL_H_
#define XENIA_DEBUG_UI_TURBO_BADGER_CONTROL_H_
#include <memory>
#include "xenia/ui/gl/wgl_control.h"
#include "xenia/ui/loop.h"
#include "third_party/turbobadger/src/tb/tb_core.h"
#include "third_party/turbobadger/src/tb/tb_widgets.h"
namespace xe {
namespace debug {
namespace ui {
class TurboBadgerControl : public xe::ui::gl::WGLControl {
public:
TurboBadgerControl(xe::ui::Loop* loop);
~TurboBadgerControl() override;
static bool InitializeTurboBadger(tb::TBRenderer* renderer);
static void ShutdownTurboBadger();
tb::TBRenderer* renderer() const { return renderer_.get(); }
tb::TBWidget* root_widget() const { return root_widget_.get(); }
protected:
using super = xe::ui::gl::WGLControl;
bool Create() override;
void Destroy() override;
void OnLayout(xe::ui::UIEvent& e) override;
void OnPaint(xe::ui::UIEvent& e) override;
void OnGotFocus(xe::ui::UIEvent& e) override;
void OnLostFocus(xe::ui::UIEvent& e) override;
tb::MODIFIER_KEYS GetModifierKeys();
void OnKeyPress(xe::ui::KeyEvent& e, bool is_down);
bool CheckShortcutKey(xe::ui::KeyEvent& e, tb::SPECIAL_KEY special_key,
bool is_down);
void OnKeyDown(xe::ui::KeyEvent& e) override;
void OnKeyUp(xe::ui::KeyEvent& e) override;
void OnMouseDown(xe::ui::MouseEvent& e) override;
void OnMouseMove(xe::ui::MouseEvent& e) override;
void OnMouseUp(xe::ui::MouseEvent& e) override;
void OnMouseWheel(xe::ui::MouseEvent& e) override;
std::unique_ptr<tb::TBRenderer> renderer_;
std::unique_ptr<tb::TBWidget> root_widget_;
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 debug
} // namespace xe
#endif // XENIA_DEBUG_UI_TURBO_BADGER_CONTROL_H_

View File

@ -0,0 +1,297 @@
/**
******************************************************************************
* 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/debug/ui/turbo_badger_renderer.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "third_party/turbobadger/src/tb/tb_types.h"
#include "third_party/turbobadger/src/tb/tb_bitmap_fragment.h"
#include "third_party/turbobadger/src/tb/tb_system.h"
namespace xe {
namespace debug {
namespace ui {
using namespace tb;
class TBRendererGL4::TBBitmapGL4 : public tb::TBBitmap {
public:
TBBitmapGL4(TBRendererGL4* renderer);
~TBBitmapGL4();
bool Init(int width, int height, uint32_t* data);
int Width() override { return width_; }
int Height() override { return height_; }
void SetData(uint32_t* data) override;
TBRendererGL4* renderer_ = nullptr;
int width_ = 0;
int height_ = 0;
GLuint handle_ = 0;
GLuint64 gpu_handle_ = 0;
};
TBRendererGL4::TBBitmapGL4::TBBitmapGL4(TBRendererGL4* renderer)
: renderer_(renderer) {}
TBRendererGL4::TBBitmapGL4::~TBBitmapGL4() {
// Must flush and unbind before we delete the texture
renderer_->FlushBitmap(this);
glMakeTextureHandleNonResidentARB(gpu_handle_);
glDeleteTextures(1, &handle_);
}
bool TBRendererGL4::TBBitmapGL4::Init(int width, int height, uint32_t* data) {
assert(width == TBGetNearestPowerOfTwo(width));
assert(height == TBGetNearestPowerOfTwo(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_);
SetData(data);
return true;
}
void TBRendererGL4::TBBitmapGL4::SetData(uint32_t* data) {
renderer_->FlushBitmap(this);
glTextureSubImage2D(handle_, 0, 0, 0, width_, height_, GL_RGBA,
GL_UNSIGNED_BYTE, data);
}
TBRendererGL4::TBRendererGL4()
: vertex_buffer_(VERTEX_BATCH_SIZE * sizeof(Vertex)) {}
TBRendererGL4::~TBRendererGL4() {
vertex_buffer_.Shutdown();
glDeleteVertexArrays(1, &vao_);
glDeleteProgram(program_);
}
std::unique_ptr<TBRendererGL4> TBRendererGL4::Create() {
auto renderer = std::make_unique<TBRendererGL4>();
if (!renderer->Initialize()) {
XELOGE("Failed to initialize TurboBadger GL4 renderer");
return nullptr;
}
return renderer;
}
bool TBRendererGL4::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;
}
TBBitmap* TBRendererGL4::CreateBitmap(int width, int height, uint32_t* data) {
auto bitmap = std::make_unique<TBBitmapGL4>(this);
if (!bitmap->Init(width, height, data)) {
return nullptr;
}
return bitmap.release();
}
void TBRendererGL4::SetClipRect(const TBRect& rect) {
Flush();
glScissor(m_clip_rect.x, m_screen_rect.h - (m_clip_rect.y + m_clip_rect.h),
m_clip_rect.w, m_clip_rect.h);
}
void TBRendererGL4::BeginPaint(int render_target_w, int render_target_h) {
TBRendererBatcher::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 TBRendererGL4::EndPaint() {
TBRendererBatcher::EndPaint();
Flush();
glUseProgram(0);
glBindVertexArray(0);
}
void TBRendererGL4::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 TBRendererGL4::RenderBatch(Batch* batch) {
auto bitmap = static_cast<TBBitmapGL4*>(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 ui
} // namespace debug
} // namespace xe

View File

@ -0,0 +1,64 @@
/**
******************************************************************************
* 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_DEBUG_UI_TURBO_BADGER_RENDERER_H_
#define XENIA_DEBUG_UI_TURBO_BADGER_RENDERER_H_
#include <memory>
#include "xenia/ui/gl/circular_buffer.h"
#include "xenia/ui/gl/gl.h"
#include "third_party/turbobadger/src/tb/tb_renderer.h"
#include "third_party/turbobadger/src/tb/tb_renderer_batcher.h"
namespace xe {
namespace debug {
namespace ui {
class TBRendererGL4 : public tb::TBRendererBatcher {
public:
TBRendererGL4();
~TBRendererGL4() override;
static std::unique_ptr<TBRendererGL4> Create();
void BeginPaint(int render_target_w, int render_target_h) override;
void EndPaint() override;
tb::TBBitmap* CreateBitmap(int width, int height, uint32_t* data) override;
void RenderBatch(Batch* batch) override;
void SetClipRect(const tb::TBRect& rect) override;
private:
class TBBitmapGL4;
bool Initialize();
void Flush();
GLuint program_ = 0;
GLuint vao_ = 0;
xe::ui::gl::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;
TBBitmapGL4* current_bitmap_ = nullptr;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_TURBO_BADGER_RENDERER_H_

View File

@ -77,11 +77,15 @@
<ClInclude Include="..\..\base\main.h" />
<ClInclude Include="application.h" />
<ClInclude Include="main_window.h" />
<ClInclude Include="turbo_badger_control.h" />
<ClInclude Include="turbo_badger_renderer.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\base\main_win.cc" />
<ClCompile Include="application.cc" />
<ClCompile Include="main_window.cc" />
<ClCompile Include="turbo_badger_control.cc" />
<ClCompile Include="turbo_badger_renderer.cc" />
<ClCompile Include="xe-debug-ui.cc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@ -27,6 +27,12 @@
<ClInclude Include="application.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
<ClInclude Include="turbo_badger_control.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
<ClInclude Include="turbo_badger_renderer.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\base\main_win.cc">
@ -41,5 +47,11 @@
<ClCompile Include="application.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
<ClCompile Include="turbo_badger_control.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
<ClCompile Include="turbo_badger_renderer.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -77,10 +77,11 @@ LRESULT WGLControl::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::gpu::gl4::WGLControl::WM_PAINT");
{
GLContextLock context_lock(&context_);
wglSwapIntervalEXT(0);
float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f};
glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color);
@ -100,6 +101,7 @@ LRESULT WGLControl::WndProc(HWND hWnd, UINT message, WPARAM wParam,
SCOPE_profile_cpu_i("gpu", "xe::gpu::gl4::WGLControl::SwapBuffers");
SwapBuffers(context_.dc());
}
return 0;
} break;
}
return Win32Control::WndProc(hWnd, message, wParam, lParam);

View File

@ -21,6 +21,7 @@ class Loop {
virtual ~Loop() = default;
virtual void Post(std::function<void()> fn) = 0;
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
virtual void Quit() = 0;
virtual void AwaitQuit() = 0;

View File

@ -121,10 +121,13 @@ void Win32Control::OnResize(UIEvent& e) {
}
void Win32Control::Invalidate() {
if (invalidated_) {
return;
}
invalidated_ = true;
InvalidateRect(hwnd_, nullptr, FALSE);
for (auto& child_control : children_) {
auto win32_control = static_cast<Win32Control*>(child_control.get());
win32_control->Invalidate();
child_control->Invalidate();
}
}
@ -234,8 +237,15 @@ LRESULT Win32Control::WndProc(HWND hWnd, UINT message, WPARAM wParam,
break;
}
case WM_PAINT:
case WM_PAINT: {
if (flags_ & kFlagOwnPaint) {
return 0; // ignore
}
invalidated_ = false;
auto e = UIEvent(this);
OnPaint(e);
break;
}
case WM_ERASEBKGND:
if (flags_ & kFlagOwnPaint) {
return 0; // ignore

View File

@ -54,7 +54,8 @@ class Win32Control : public Control {
virtual LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam);
HWND hwnd_;
HWND hwnd_ = nullptr;
bool invalidated_ = true;
private:
bool HandleMouse(UINT message, WPARAM wParam, LPARAM lParam);

View File

@ -28,6 +28,8 @@ class PostedFn {
};
Win32Loop::Win32Loop() : thread_id_(0) {
timer_queue_ = CreateTimerQueue();
xe::threading::Fence init_fence;
thread_ = std::thread([&init_fence, this]() {
xe::threading::set_name("Win32 Loop");
@ -49,6 +51,14 @@ Win32Loop::Win32Loop() : thread_id_(0) {
Win32Loop::~Win32Loop() {
Quit();
thread_.join();
DeleteTimerQueueEx(timer_queue_, INVALID_HANDLE_VALUE);
std::lock_guard<xe::mutex> lock(pending_timers_mutex_);
while (!pending_timers_.empty()) {
auto timer = pending_timers_.back();
pending_timers_.pop_back();
delete timer;
}
}
void Win32Loop::ThreadMain() {
@ -82,6 +92,38 @@ void Win32Loop::Post(std::function<void()> fn) {
}
}
void Win32Loop::TimerQueueCallback(void* context, uint8_t) {
auto timer = reinterpret_cast<PendingTimer*>(context);
auto loop = timer->loop;
auto fn = std::move(timer->fn);
DeleteTimerQueueTimer(timer->timer_queue, timer->timer_handle, NULL);
{
std::lock_guard<xe::mutex> lock(loop->pending_timers_mutex_);
loop->pending_timers_.remove(timer);
}
delete timer;
loop->Post(std::move(fn));
}
void Win32Loop::PostDelayed(std::function<void()> fn, uint64_t delay_millis) {
if (!delay_millis) {
Post(std::move(fn));
return;
}
auto timer = new PendingTimer();
timer->loop = this;
timer->timer_queue = timer_queue_;
timer->fn = std::move(fn);
{
std::lock_guard<xe::mutex> lock(pending_timers_mutex_);
pending_timers_.push_back(timer);
}
CreateTimerQueueTimer(&timer->timer_handle, timer_queue_,
WAITORTIMERCALLBACK(TimerQueueCallback), timer,
DWORD(delay_millis), 0,
WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE);
}
void Win32Loop::Quit() {
assert_true(thread_id_ != 0);
PostThreadMessage(thread_id_, kWmWin32LoopQuit,

View File

@ -10,11 +10,11 @@
#ifndef XENIA_UI_WIN32_WIN32_LOOP_H_
#define XENIA_UI_WIN32_WIN32_LOOP_H_
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <list>
#include <thread>
#include "xenia/base/mutex.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
#include "xenia/ui/loop.h"
@ -29,16 +29,30 @@ class Win32Loop : public Loop {
~Win32Loop() override;
void Post(std::function<void()> fn) override;
void PostDelayed(std::function<void()> fn, uint64_t delay_millis) override;
void Quit() override;
void AwaitQuit() override;
private:
struct PendingTimer {
Win32Loop* loop;
HANDLE timer_queue;
HANDLE timer_handle;
std::function<void()> fn;
};
void ThreadMain();
static void TimerQueueCallback(void* context, uint8_t);
std::thread thread_;
DWORD thread_id_;
xe::threading::Fence quit_fence_;
HANDLE timer_queue_;
xe::mutex pending_timers_mutex_;
std::list<PendingTimer*> pending_timers_;
};
} // namespace win32

@ -1 +1 @@
Subproject commit 654b731db496c6014daf48384e74f517fe6905ac
Subproject commit b5f4e50b9d8626a27ec111cc00fef1438062fbb8

View File

@ -126,11 +126,17 @@ Global
{21DDCB81-68A3-4AB2-8CB0-C2B051B9FDDC}.Release|x64.ActiveCfg = Release|x64
{21DDCB81-68A3-4AB2-8CB0-C2B051B9FDDC}.Release|x64.Build.0 = Release|x64
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Checked|x64.ActiveCfg = Debug|x64
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Checked|x64.Build.0 = Debug|x64
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Debug|x64.ActiveCfg = Debug|x64
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Debug|x64.Build.0 = Debug|x64
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Release|x64.ActiveCfg = Release|x64
{156102D7-F2DD-4618-B2EB-2DFE607EE6DD}.Release|x64.Build.0 = Release|x64
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Checked|x64.ActiveCfg = Release|x64
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Checked|x64.Build.0 = Release|x64
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Debug|x64.ActiveCfg = Debug|x64
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Debug|x64.Build.0 = Debug|x64
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Release|x64.ActiveCfg = Release|x64
{C5BA52F0-C86B-4817-921C-CCA257FC04BE}.Release|x64.Build.0 = Release|x64
{93533067-6449-4691-88A8-026EBCFDCA97}.Checked|x64.ActiveCfg = Checked|x64
{93533067-6449-4691-88A8-026EBCFDCA97}.Checked|x64.Build.0 = Checked|x64
{93533067-6449-4691-88A8-026EBCFDCA97}.Debug|x64.ActiveCfg = Debug|x64