[nostalgia] Break part of core out into Turbine and TeaGBA libraries

This commit is contained in:
2023-06-01 23:22:31 -05:00
parent 07284ac595
commit 8c43baedea
119 changed files with 1918 additions and 1873 deletions

View File

@ -0,0 +1,40 @@
add_library(
Turbine
event.cpp
)
if(NOSTALGIA_BUILD_TYPE STREQUAL "GBA")
add_subdirectory(gba)
else()
add_subdirectory(glfw)
endif()
if(NOT MSVC)
target_compile_options(Turbine PRIVATE -Wsign-conversion)
endif()
target_link_libraries(
Turbine PUBLIC
Keel
)
install(
FILES
clipboard.hpp
config.hpp
context.hpp
event.hpp
gfx.hpp
input.hpp
turbine.hpp
DESTINATION
include/turbine
)
install(
TARGETS
Turbine
DESTINATION
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)

32
src/turbine/clipboard.hpp Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/claw/claw.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include "context.hpp"
namespace turbine {
ox::String getClipboardText(class Context &ctx) noexcept;
void setClipboardText(class Context &ctx, ox::CRStringView text) noexcept;
template<typename T>
void setClipboardObject([[maybe_unused]] class Context &ctx, [[maybe_unused]] ox::UniquePtr<T> obj) noexcept {
ctx.clipboard = std::move(obj);
}
template<typename T>
ox::Result<T*> getClipboardObject([[maybe_unused]] class Context &ctx) noexcept {
if (ctx.clipboard && ctx.clipboard->typeMatch(T::TypeName, T::TypeVersion)) {
return static_cast<T*>(ctx.clipboard.get());
}
return OxError(1);
}
}

25
src/turbine/config.hpp Normal file
View File

@ -0,0 +1,25 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#if __has_include(<imgui.h>)
#endif
#include <ox/std/types.hpp>
namespace turbine::config {
constexpr auto ImGuiEnabled =
#if __has_include(<imgui.h>)
true;
#else
false;
#endif
constexpr auto GbaEventLoopTimerBased = false;
constexpr auto GbaTimerBits = 32;
constexpr auto GlFpsPrint = false;
}

93
src/turbine/context.hpp Normal file
View File

@ -0,0 +1,93 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <ox/model/desctypes.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/size.hpp>
#include <keel/context.hpp>
#include "event.hpp"
#include "input.hpp"
namespace turbine {
class BaseClipboardObject {
public:
virtual ~BaseClipboardObject() noexcept = default;
[[nodiscard]]
virtual ox::String typeId() const noexcept = 0;
[[nodiscard]]
constexpr auto typeMatch(auto name, auto version) const noexcept {
return typeId() == ox::buildTypeId(name, version);
}
};
template<typename T>
class ClipboardObject: public BaseClipboardObject {
[[nodiscard]]
ox::String typeId() const noexcept final {
return ox::buildTypeId(T::TypeName, T::TypeVersion);
}
};
void shutdown(Context &ctx) noexcept;
// User Input Output
class Context: public keel::Context {
friend constexpr void setApplicationData(Context &ctx, void *applicationData) noexcept;
template<typename T>
friend constexpr T *applicationData(Context &ctx) noexcept;
friend void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept;
friend constexpr void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept;
friend constexpr KeyEventHandler keyEventHandler(Context &ctx) noexcept;
public:
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
ox::UPtr<BaseClipboardObject> clipboard;
protected:
KeyEventHandler m_keyEventHandler = nullptr;
void *m_applicationData = nullptr;
public:
Context() noexcept = default;
Context(Context &other) noexcept = delete;
Context(const Context &other) noexcept = delete;
Context(const Context &&other) noexcept = delete;
inline ~Context() noexcept {
shutdown(*this);
}
};
constexpr void setApplicationData(Context &ctx, void *applicationData) noexcept {
ctx.m_applicationData = applicationData;
}
template<typename T>
[[nodiscard]]
constexpr T *applicationData(Context &ctx) noexcept {
return static_cast<T*>(ctx.m_applicationData);
}
constexpr void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
ctx.m_keyEventHandler = h;
}
constexpr KeyEventHandler keyEventHandler(Context &ctx) noexcept {
return ctx.m_keyEventHandler;
}
void setConstantRefresh(Context &ctx, bool r) noexcept;
}

14
src/turbine/event.cpp Normal file
View File

@ -0,0 +1,14 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "context.hpp"
#include "event.hpp"
namespace turbine {
void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept {
ctx.updateHandler = h;
}
}

17
src/turbine/event.hpp Normal file
View File

@ -0,0 +1,17 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
namespace turbine {
class Context;
using UpdateHandler = int(*)(Context&);
// Sets event handler that sleeps for the time given in the return value. The
// sleep time is a minimum of ~16 milliseconds.
void setUpdateHandler(Context &ctx, UpdateHandler) noexcept;
}

View File

@ -0,0 +1,16 @@
enable_language(CXX ASM)
set_source_files_properties(turbine.arm.cpp irq.arm.cpp PROPERTIES COMPILE_FLAGS -marm)
target_sources(
Turbine PRIVATE
clipboard.cpp
gfx.cpp
irq.arm.cpp
irq.s
turbine.arm.cpp
turbine.cpp
)
target_link_libraries(
Turbine PUBLIC
TeaGBA
)

View File

@ -0,0 +1,19 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/std/string.hpp>
#include <turbine/context.hpp>
namespace turbine {
ox::String getClipboardText(Context&) noexcept {
return {};
}
void setClipboardText(Context&, ox::CRStringView) noexcept {
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <ox/model/desctypes.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/size.hpp>
#include <keel/context.hpp>
#include "../context.hpp"
namespace turbine::gba {
class Context: public turbine::Context {
public:
bool running = true;
Context() noexcept = default;
Context(Context &other) noexcept = delete;
Context(const Context &other) noexcept = delete;
Context(const Context &&other) noexcept = delete;
};
}

42
src/turbine/gba/gfx.cpp Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/std/size.hpp>
#include <ox/std/stringview.hpp>
#include <teagba/addresses.hpp>
#include <teagba/gfx.hpp>
#include <teagba/irq.hpp>
#include <turbine/context.hpp>
namespace turbine {
ox::Error initGfx(Context&) noexcept {
REG_DISPCTL = teagba::DispCtl_Mode0
| teagba::DispCtl_SpriteMap1D
| teagba::DispCtl_Obj;
// tell display to trigger vblank interrupts
REG_DISPSTAT = REG_DISPSTAT | teagba::DispStat_irq_vblank;
// enable vblank interrupt
REG_IE = REG_IE | teagba::Int_vblank;
return {};
}
void setWindowTitle(Context&, ox::CRStringView) noexcept {
}
int getScreenWidth(Context&) noexcept {
return 240;
}
int getScreenHeight(Context&) noexcept {
return 160;
}
ox::Size getScreenSize(Context&) noexcept {
return {240, 160};
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
// NOTE: this file is compiled as ARM and not THUMB, so don't but too much in
// here
#include <teagba/addresses.hpp>
#include <teagba/gfx.hpp>
#include <teagba/irq.hpp>
#include "turbine.hpp"
namespace turbine {
volatile gba_timer_t g_timerMs = 0;
}
using namespace turbine;
extern "C" {
void turbine_isr_vblank() {
teagba::applySpriteUpdates();
if constexpr(config::GbaEventLoopTimerBased) {
// disable vblank interrupt until it is needed again
REG_IE = REG_IE & ~teagba::Int_vblank;
}
}
void turbine_isr_timer0() {
g_timerMs = g_timerMs + 1;
}
}

110
src/turbine/gba/irq.s Normal file
View File

@ -0,0 +1,110 @@
//
// Copyright 2016 - 2023 gary@drinkingtea.net
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
.section .iwram, "ax", %progbits
.arm
.align
.extern turbine_isr_vblank
.extern turbine_isr_timer0
.equ REG_IFBIOS, 0x03007ff8
.equ REG_IE, 0x04000200
.equ REG_IF, 0x04000202
.equ REG_IME, 0x04000208
.equ Int_vblank, 1
.equ Int_hblank, 2
.equ Int_vcount, 4
.equ Int_timer0, 8
.equ Int_timer1, 16
.equ Int_timer2, 32
.equ Int_timer3, 64
.equ Int_serial, 128 // link cable
.equ Int_dma0, 256
.equ Int_dma1, 512
.equ Int_dma2, 1024
.equ Int_dma3, 2048
.equ Int_dma4, 4096
.equ Int_dma5, 8192
.equ Int_input, 16384 // gamepad
.equ Int_cart, 32768 // cartridge removed
.global isr
.type isr, %function
isr:
// read IE
ldr r0, =#REG_IE
ldrh r1, [r0] // r1 becomes IE value
ldrh r2, [r0, #2] // r2 becomes IF value
and r1, r1, r2 // r1 becomes IE & IF
// done with r2 as IF value
// Acknowledge IRQ in REG_IF
strh r1, [r0, #2]
ldr r0, =#REG_IFBIOS
// Acknowledge IRQ in REG_IFBIOS
ldr r2, [r0] // r2 becomes REG_IFBIOS value
orr r2, r2, r1
str r2, [r0]
// done with r2 as IFBIOS value
// done with r0 as REG_IFBIOS
////////////////////////////////////////////////////
// Interrupt Table Begin //
////////////////////////////////////////////////////
cmp r1, #Int_timer0
ldreq r0, =turbine_isr_timer0
beq isr_call_handler
cmp r1, #Int_vblank
ldreq r0, =turbine_isr_vblank
beq isr_call_handler
////////////////////////////////////////////////////
// Interrupt Table End //
////////////////////////////////////////////////////
bx lr
isr_call_handler:
// clear IME to disable interrupts
ldr r2, =#REG_IME
mov r1, #0
str r1, [r2]
// enter system mode
mrs r1, cpsr
bic r1, r1, #0xDF
orr r1, r1, #0x1F
msr cpsr, r1
push {lr}
ldr lr, =isr_restore
bx r0
isr_restore:
pop {lr}
// re-enter irq mode
mrs r0, cpsr
bic r0, r0, #0xDF
orr r0, r0, #0x92
msr cpsr, r0
// set IME to re-enable interrupts
ldr r2, =#REG_IME
mov r0, #1
str r0, [r2]
bx lr
// vim: ft=armv4

View File

@ -0,0 +1,41 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <teagba/addresses.hpp>
#include <teagba/bios.hpp>
#include <teagba/irq.hpp>
#include "context.hpp"
#include "turbine.hpp"
namespace turbine {
extern volatile gba_timer_t g_timerMs;
gba_timer_t g_wakeupTime;
ox::Error run(Context &ctx) noexcept {
g_wakeupTime = 0;
const auto gbaCtx = static_cast<gba::Context*>(&ctx);
while (gbaCtx->running) {
if (g_wakeupTime <= g_timerMs && ctx.updateHandler) {
auto sleepTime = ctx.updateHandler(ctx);
if (sleepTime >= 0) {
g_wakeupTime = g_timerMs + static_cast<unsigned>(sleepTime);
} else {
g_wakeupTime = ~gba_timer_t(0);
}
}
if constexpr(config::GbaEventLoopTimerBased) {
// wait for timer interrupt
teagba_intrwait(
0,
teagba::Int_timer0 | teagba::Int_timer1 | teagba::Int_timer2 | teagba::Int_timer3);
} else {
teagba_vblankintrwait();
}
}
return {};
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <teagba/addresses.hpp>
#include <teagba/irq.hpp>
#include <keel/keel.hpp>
#include <turbine/gfx.hpp>
#include "context.hpp"
#include "turbine.hpp"
extern "C" void isr();
namespace turbine {
// Timer Consts
constexpr int NanoSecond = 1000000000;
constexpr int MilliSecond = 1000;
constexpr int TicksMs59ns = 65535 - (NanoSecond / MilliSecond) / 59.59;
extern volatile gba_timer_t g_timerMs;
static void initIrq() noexcept {
REG_ISR = isr;
REG_IME = 1; // enable interrupts
}
static void initTimer() noexcept {
// make timer0 a ~1 millisecond timer
REG_TIMER0 = TicksMs59ns;
REG_TIMER0CTL = 0b11000000;
// enable interrupt for timer0
REG_IE = REG_IE | teagba::Int_timer0;
}
static ox::Result<std::size_t> findPreloadSection() noexcept {
// put the header in the wrong order to prevent mistaking this code for the
// media section
constexpr auto headerP2 = "DER_____________";
constexpr auto headerP1 = "KEEL_PRELOAD_HEA";
constexpr auto headerP1Len = ox_strlen(headerP2);
constexpr auto headerP2Len = ox_strlen(headerP1);
constexpr auto headerLen = headerP1Len + headerP2Len;
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
if (ox_memcmp(current, headerP1, headerP1Len) == 0 &&
ox_memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
return reinterpret_cast<std::size_t>(current + headerLen);
}
}
return OxError(1);
}
ox::Result<ox::UniquePtr<turbine::Context>> init(ox::UPtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept {
oxRequireM(ctx, keel::init<gba::Context>(std::move(fs), appName));
oxReturnError(findPreloadSection().moveTo(&ctx->preloadSectionOffset));
oxReturnError(initGfx(*ctx));
initTimer();
initIrq();
return ox::UPtr<turbine::Context>(std::move(ctx));
}
void shutdown(Context&) noexcept {
}
uint64_t ticksMs(Context&) noexcept {
return g_timerMs;
}
bool buttonDown(Context&, Key k) noexcept {
return k <= Key::GamePad_L && !(REG_GAMEPAD & (1 << static_cast<int>(k)));
}
void requestShutdown(Context &ctx) noexcept {
const auto gbaCtx = static_cast<gba::Context*>(&ctx);
gbaCtx->running = false;
}
}

View File

@ -0,0 +1,15 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/types.hpp>
#include "../config.hpp"
namespace turbine {
using gba_timer_t = ox::Uint<config::GbaTimerBits>;
}

42
src/turbine/gfx.hpp Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/point.hpp>
#include <ox/std/size.hpp>
#include <ox/std/types.hpp>
#include <ox/model/def.hpp>
#include "context.hpp"
namespace turbine {
namespace gl {
class Drawer {
public:
virtual ~Drawer() = default;
virtual void draw(Context&) noexcept = 0;
};
void addDrawer(Context &ctx, Drawer *cd) noexcept;
void removeDrawer(Context &ctx, Drawer *cd) noexcept;
}
ox::Error initGfx(Context &ctx) noexcept;
void setWindowTitle(Context &ctx, ox::CRStringView title) noexcept;
void focusWindow(Context &ctx) noexcept;
[[nodiscard]]
int getScreenWidth(Context &ctx) noexcept;
[[nodiscard]]
int getScreenHeight(Context &ctx) noexcept;
[[nodiscard]]
ox::Size getScreenSize(Context &ctx) noexcept;
}

View File

@ -0,0 +1,12 @@
target_sources(
Turbine PRIVATE
clipboard.cpp
gfx.cpp
turbine.cpp
)
target_link_libraries(
Turbine PUBLIC
glad
glfw
imgui
)

View File

@ -0,0 +1,27 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <GLFW/glfw3.h>
#include <ox/std/string.hpp>
#include <turbine/turbine.hpp>
#include "context.hpp"
namespace turbine {
ox::String getClipboardText(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
return glfwGetClipboardString(gctx.window);
}
void setClipboardText(Context &ctx, ox::CRStringView text) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
auto cstr = ox_malloca(text.bytes() + 1, char);
ox_strncpy(cstr.get(), text.data(), text.bytes());
glfwSetClipboardString(gctx.window, cstr);
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <turbine/turbine.hpp>
namespace turbine {
struct GlfwContext: public turbine::Context {
int uninterruptedRefreshes = 3;
ox::UPtr<BaseClipboardObject> clipboard;
struct GLFWwindow *window = nullptr;
// sets screen refresh to constant instead of only on event
bool constantRefresh = true;
ox::Vector<gl::Drawer*, 5> drawers;
int64_t startTime = 0;
uint64_t wakeupTime = 0;
uint64_t keysDown = 0;
uint64_t prevFpsCheckTime = 0;
uint64_t draws = 0;
};
}

274
src/turbine/glfw/gfx.cpp Normal file
View File

@ -0,0 +1,274 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <imgui_impl_glfw.h>
#include <ox/std/defines.hpp>
#include <turbine/config.hpp>
#include "context.hpp"
namespace turbine {
namespace gl {
void addDrawer(Context &ctx, Drawer *cd) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
gctx.drawers.emplace_back(cd);
}
void removeDrawer(Context &ctx, Drawer *cd) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
for (auto i = 0u; i < gctx.drawers.size(); ++i) {
if (gctx.drawers[i] == cd) {
oxIgnoreError(gctx.drawers.erase(i));
break;
}
}
}
}
constexpr auto Scale = 5;
static void handleGlfwError(int err, const char *desc) noexcept {
oxErrf("GLFW error ({}): {}\n", err, desc);
}
static auto setKeyDownStatus(GlfwContext *gctx, Key key, bool down) noexcept {
if (down) {
gctx->keysDown |= 1llu << static_cast<int>(key);
} else {
gctx->keysDown &= ~(1llu << static_cast<int>(key));
}
}
static void handleKeyPress(Context *ctx, int key, bool down) noexcept {
static constexpr auto keyMap = [] {
ox::Array<Key, GLFW_KEY_LAST> map = {};
for (auto i = 0u; i < 26; ++i) {
map[GLFW_KEY_A + i] = static_cast<Key>(static_cast<unsigned>(Key::Alpha_A) + i);
}
for (auto i = 0u; i < 10; ++i) {
map[GLFW_KEY_0 + i] = static_cast<Key>(static_cast<unsigned>(Key::Num_0) + i);
}
map[GLFW_KEY_LEFT_ALT] = Key::Mod_Alt;
map[GLFW_KEY_RIGHT_ALT] = Key::Mod_Alt;
map[GLFW_KEY_LEFT_CONTROL] = Key::Mod_Ctrl;
map[GLFW_KEY_RIGHT_CONTROL] = Key::Mod_Ctrl;
map[GLFW_KEY_LEFT_SUPER] = Key::Mod_Super;
map[GLFW_KEY_RIGHT_SUPER] = Key::Mod_Super;
map[GLFW_KEY_ESCAPE] = Key::Escape;
return map;
}();
const auto eventHandler = keyEventHandler(*ctx);
auto &gctx = static_cast<GlfwContext&>(*ctx);
const auto k = keyMap[static_cast<std::size_t>(key)];
setKeyDownStatus(&gctx, k, down);
if (eventHandler) {
eventHandler(*ctx, k, down);
}
//if constexpr(ox::defines::Debug) {
// switch (key) {
// case GLFW_KEY_ESCAPE:
// case GLFW_KEY_Q:
// oxIgnoreError(requestShutdown(ctx));
// break;
// default:
// break;
// }
//}
}
static void handleGlfwCursorPosEvent(GLFWwindow*, double, double) noexcept {
}
static void handleGlfwMouseButtonEvent(GLFWwindow *window, int, int, int) noexcept {
const auto ctx = static_cast<GlfwContext*>(glfwGetWindowUserPointer(window));
ctx->uninterruptedRefreshes = 25;
}
static void handleGlfwWindowResize(GLFWwindow*, int, int) noexcept {
}
static void handleGlfwKeyEvent(GLFWwindow *window, int key, int, int action, int) noexcept {
const auto ctx = static_cast<GlfwContext*>(glfwGetWindowUserPointer(window));
ctx->uninterruptedRefreshes = 25;
if (action == GLFW_PRESS) {
handleKeyPress(ctx, key, true);
} else if (action == GLFW_RELEASE) {
handleKeyPress(ctx, key, false);
}
}
static void themeImgui() noexcept {
// Dark Ruda style by Raikiri from ImThemes
auto &style = ImGui::GetStyle();
style.Alpha = 1.0;
style.DisabledAlpha = 0.6000000238418579;
style.WindowPadding = ImVec2(8.0, 8.0);
style.WindowRounding = 0.0;
style.WindowBorderSize = 1.0;
style.WindowMinSize = ImVec2(32.0, 32.0);
style.WindowTitleAlign = ImVec2(0.0, 0.5);
style.WindowMenuButtonPosition = ImGuiDir_Left;
style.ChildRounding = 0.0;
style.ChildBorderSize = 1.0;
style.PopupRounding = 0.0;
style.PopupBorderSize = 1.0;
style.FramePadding = ImVec2(4.0, 3.0);
// custom value
style.FrameRounding = 3.0;
style.FrameBorderSize = 0.0;
style.ItemSpacing = ImVec2(8.0, 4.0);
style.ItemInnerSpacing = ImVec2(4.0, 4.0);
style.CellPadding = ImVec2(4.0, 2.0);
style.IndentSpacing = 21.0;
style.ColumnsMinSpacing = 6.0;
style.ScrollbarSize = 14.0;
style.ScrollbarRounding = 9.0;
style.GrabMinSize = 10.0;
style.GrabRounding = 4.0;
style.TabRounding = 4.0;
style.TabBorderSize = 0.0;
style.TabMinWidthForCloseButton = 0.0;
style.ColorButtonPosition = ImGuiDir_Right;
style.ButtonTextAlign = ImVec2(0.5, 0.5);
style.SelectableTextAlign = ImVec2(0.0, 0.0);
// colors
style.Colors[ImGuiCol_Text] = ImVec4(0.9490196108818054, 0.95686274766922, 0.9764705896377563, 1.0);
style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.3568627536296844, 0.4196078479290009, 0.4666666686534882, 1.0);
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
style.Colors[ImGuiCol_ChildBg] = ImVec4(0.1490196138620377, 0.1764705926179886, 0.2196078449487686, 1.0);
style.Colors[ImGuiCol_PopupBg] = ImVec4(0.0784313753247261, 0.0784313753247261, 0.0784313753247261, 0.9399999976158142);
style.Colors[ImGuiCol_Border] = ImVec4(0.0784313753247261, 0.09803921729326248, 0.1176470592617989, 1.0);
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.0, 0.0, 0.0, 0.0);
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.1176470592617989, 0.2000000029802322, 0.2784313857555389, 1.0);
style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.08627451211214066, 0.1176470592617989, 0.1372549086809158, 1.0);
style.Colors[ImGuiCol_TitleBg] = ImVec4(0.08627451211214066, 0.1176470592617989, 0.1372549086809158, 0.6499999761581421);
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.0784313753247261, 0.09803921729326248, 0.1176470592617989, 1.0);
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.0, 0.0, 0.0, 0.5099999904632568);
style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.1490196138620377, 0.1764705926179886, 0.2196078449487686, 1.0);
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.01960784383118153, 0.01960784383118153, 0.01960784383118153, 0.3899999856948853);
style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.1764705926179886, 0.2196078449487686, 0.2470588237047195, 1.0);
style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.08627451211214066, 0.2078431397676468, 0.3098039329051971, 1.0);
style.Colors[ImGuiCol_CheckMark] = ImVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.3686274588108063, 0.6078431606292725, 1.0, 1.0);
style.Colors[ImGuiCol_Button] = ImVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.05882352963089943, 0.529411792755127, 0.9764705896377563, 1.0);
// custom value
style.Colors[ImGuiCol_Header] = ImVec4(0.4000000029802322, 0.4470588237047195, 0.4862745225429535, 0.550000011920929);
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.800000011920929);
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 1.0);
style.Colors[ImGuiCol_Separator] = ImVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.09803921729326248, 0.4000000059604645, 0.7490196228027344, 0.7799999713897705);
style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.09803921729326248, 0.4000000059604645, 0.7490196228027344, 1.0);
style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.25);
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.6700000166893005);
style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.949999988079071);
style.Colors[ImGuiCol_Tab] = ImVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
style.Colors[ImGuiCol_TabHovered] = ImVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.800000011920929);
style.Colors[ImGuiCol_TabActive] = ImVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
style.Colors[ImGuiCol_PlotLines] = ImVec4(0.6078431606292725, 0.6078431606292725, 0.6078431606292725, 1.0);
style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.0, 0.4274509847164154, 0.3490196168422699, 1.0);
style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.8980392217636108, 0.6980392336845398, 0.0, 1.0);
style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.0, 0.6000000238418579, 0.0, 1.0);
style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.1882352977991104, 0.1882352977991104, 0.2000000029802322, 1.0);
style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.3098039329051971, 0.3098039329051971, 0.3490196168422699, 1.0);
style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.2274509817361832, 0.2274509817361832, 0.2470588237047195, 1.0);
style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.0, 0.0, 0.0, 0.0);
style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.0, 1.0, 1.0, 0.05999999865889549);
style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.3499999940395355);
style.Colors[ImGuiCol_DragDropTarget] = ImVec4(1.0, 1.0, 0.0, 0.8999999761581421);
style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 1.0);
style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.0, 1.0, 1.0, 0.699999988079071);
style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 0.2000000029802322);
style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 0.3499999940395355);
}
ox::Error initGfx(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
glfwSetErrorCallback(handleGlfwError);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
if constexpr(ox::defines::OS == ox::OS::Darwin) {
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
}
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
auto cstr = ox_malloca(ctx.appName.bytes() + 1, char);
ox_strncpy(cstr.get(), ctx.appName.data(), ctx.appName.bytes());
gctx.window = glfwCreateWindow(240 * Scale, 160 * Scale, cstr, nullptr, nullptr);
if (gctx.window == nullptr) {
return OxError(1, "Could not open GLFW window");
}
glfwSetCursorPosCallback(gctx.window, handleGlfwCursorPosEvent);
glfwSetMouseButtonCallback(gctx.window, handleGlfwMouseButtonEvent);
glfwSetWindowSizeCallback(gctx.window, handleGlfwWindowResize);
glfwSetKeyCallback(gctx.window, handleGlfwKeyEvent);
glfwSetWindowUserPointer(gctx.window, &ctx);
glfwMakeContextCurrent(gctx.window);
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress))) {
return OxError(2, "Could not init Glad");
}
if constexpr(config::ImGuiEnabled) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
auto &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
//io.MouseDrawCursor = true;
ImGui_ImplGlfw_InitForOpenGL(gctx.window, true);
themeImgui();
}
return {};
}
void setWindowTitle(Context &ctx, ox::CRStringView title) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
auto cstr = ox_malloca(title.bytes() + 1, char);
ox_strncpy(cstr.get(), title.data(), title.bytes());
glfwSetWindowTitle(gctx.window, cstr);
}
void focusWindow(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
glfwFocusWindow(gctx.window);
}
int getScreenWidth(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
int w = 0, h = 0;
glfwGetFramebufferSize(gctx.window, &w, &h);
return w;
}
int getScreenHeight(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
int w = 0, h = 0;
glfwGetFramebufferSize(gctx.window, &w, &h);
return h;
}
ox::Size getScreenSize(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
int w = 0, h = 0;
glfwGetFramebufferSize(gctx.window, &w, &h);
return {w, h};
}
void setConstantRefresh(Context &ctx, bool r) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
gctx.constantRefresh = r;
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <GLFW/glfw3.h>
#include <imgui_impl_opengl3.h>
#include <imgui_impl_glfw.h>
#include <keel/keel.hpp>
#include <turbine/gfx.hpp>
#include "../config.hpp"
#include "context.hpp"
namespace turbine {
ox::Result<ox::UPtr<Context>> init(ox::UPtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept {
oxRequireM(ctx, keel::init<GlfwContext>(std::move(fs), appName));
using namespace std::chrono;
ctx->startTime = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
glfwInit();
oxReturnError(initGfx(*ctx));
return ox::UPtr<Context>(ctx.release());
}
static void tickFps(GlfwContext &gctx, uint64_t nowMs) noexcept {
++gctx.draws;
if (gctx.draws >= 500) {
const auto duration = static_cast<double>(nowMs - gctx.prevFpsCheckTime) / 1000.0;
const auto fps = static_cast<int>(static_cast<double>(gctx.draws) / duration);
if constexpr(config::GlFpsPrint) {
oxOutf("FPS: {}\n", fps);
}
oxTracef("turbine::fps", "FPS: {}", fps);
gctx.prevFpsCheckTime = nowMs;
gctx.draws = 0;
}
}
ox::Error run(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
int sleepTime = 0;
while (!glfwWindowShouldClose(gctx.window)) {
glfwPollEvents();
const auto ticks = ticksMs(gctx);
if (gctx.wakeupTime <= ticks) {
sleepTime = gctx.updateHandler(gctx);
if (sleepTime >= 0) {
gctx.wakeupTime = ticks + static_cast<unsigned>(sleepTime);
} else {
gctx.wakeupTime = ~uint64_t(0);
}
} else {
sleepTime = 10;
}
tickFps(gctx, ticks);
// draw start
if constexpr(turbine::config::ImGuiEnabled) {
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}
for (auto d : gctx.drawers) {
d->draw(gctx);
}
if constexpr(turbine::config::ImGuiEnabled) {
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
// draw end
glfwSwapBuffers(gctx.window);
if (!gctx.constantRefresh) {
if (gctx.uninterruptedRefreshes) {
--gctx.uninterruptedRefreshes;
} else {
glfwWaitEventsTimeout(sleepTime);
}
}
}
shutdown(gctx);
return {};
}
void shutdown(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
if (gctx.window) {
glfwDestroyWindow(gctx.window);
gctx.window = nullptr;
}
}
uint64_t ticksMs(const Context &ctx) noexcept {
using namespace std::chrono;
auto &gctx = static_cast<const GlfwContext&>(ctx);
const auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
return static_cast<uint64_t>(now - gctx.startTime);
}
bool buttonDown(const Context &ctx, Key key) noexcept {
auto &gctx = static_cast<const GlfwContext&>(ctx);
return (gctx.keysDown >> static_cast<int>(key)) & 1;
}
void requestShutdown(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
glfwSetWindowShouldClose(gctx.window, true);
}
}

79
src/turbine/input.hpp Normal file
View File

@ -0,0 +1,79 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/defines.hpp>
namespace turbine {
enum Key {
// GBA implementation currently relies on GamePad entry order
GamePad_A = 0,
GamePad_B,
GamePad_Select,
GamePad_Start,
GamePad_Right,
GamePad_Left,
GamePad_Up,
GamePad_Down,
GamePad_R,
GamePad_L,
Num_0,
Num_1,
Num_2,
Num_3,
Num_4,
Num_5,
Num_6,
Num_7,
Num_8,
Num_9,
Alpha_A,
Alpha_B,
Alpha_C,
Alpha_D,
Alpha_E,
Alpha_F,
Alpha_G,
Alpha_H,
Alpha_I,
Alpha_J,
Alpha_K,
Alpha_L,
Alpha_M,
Alpha_N,
Alpha_O,
Alpha_P,
Alpha_Q,
Alpha_R,
Alpha_S,
Alpha_T,
Alpha_U,
Alpha_V,
Alpha_W,
Alpha_X,
Alpha_Y,
Alpha_Z,
Mod_Alt,
Mod_Ctrl,
Mod_Super,
Mod_Shift,
Escape,
End
};
class Context;
[[nodiscard]]
bool buttonDown(const Context &ctx, Key) noexcept;
using KeyEventHandler = void(*)(Context&, Key, bool);
}

29
src/turbine/turbine.hpp Normal file
View File

@ -0,0 +1,29 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include "clipboard.hpp"
#include "event.hpp"
#include "gfx.hpp"
#include "input.hpp"
namespace turbine {
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, ox::CRStringView appName = "Nostalgia") noexcept;
void shutdown(Context &ctx) noexcept;
ox::Error run(Context &ctx) noexcept;
// Returns the number of milliseconds that have passed since the start of the
// program.
[[nodiscard]]
uint64_t ticksMs(const Context &ctx) noexcept;
void requestShutdown(Context &ctx) noexcept;
}