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

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

View File

@ -47,7 +47,7 @@ include_directories(
)
if(BUILDCORE_TARGET STREQUAL "gba")
add_subdirectory(deps/gbastartup)
add_subdirectory(deps/teagba)
else()
include_directories(
SYSTEM

View File

@ -1,12 +0,0 @@
enable_language(CXX ASM)
add_library(
GbaStartup OBJECT
gba_crt0.s
cstartup.cpp
)
target_link_libraries(
GbaStartup PUBLIC
OxStd
)

1
deps/teagba/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_subdirectory(src)

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once

22
deps/teagba/include/teagba/bios.hpp vendored Normal file
View File

@ -0,0 +1,22 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
// Functions for accessing BIOS calls
extern "C" {
// waits for any interrupt
void teagba_halt();
void teagba_stop();
// waits for interrupts specified in interSubs
void teagba_intrwait(unsigned discardExistingIntrs, unsigned intrSubs);
// waits for vblank interrupt
void teagba_vblankintrwait();
}

46
deps/teagba/include/teagba/gfx.hpp vendored Normal file
View File

@ -0,0 +1,46 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/stddef.hpp>
#include <ox/std/types.hpp>
namespace teagba {
enum DispCtl {
DispCtl_Mode0 = 0,
DispCtl_Mode1 = 1,
DispCtl_Mode2 = 2,
DispCtl_Mode3 = 3,
DispCtl_Mode4 = 4,
DispCtl_Mode5 = 5,
DispCtl_SpriteMap1D = 1 << 6,
DispCtl_Bg0 = 1 << 8,
DispCtl_Bg1 = 1 << 9,
DispCtl_Bg2 = 1 << 10,
DispCtl_Bg3 = 1 << 11,
DispCtl_Obj = 1 << 12,
};
struct OX_ALIGN8 GbaSpriteAttrUpdate {
uint16_t attr0 = 0;
uint16_t attr1 = 0;
uint16_t attr2 = 0;
uint16_t idx = 0;
};
extern volatile uint16_t g_spriteUpdates;
extern ox::Array<GbaSpriteAttrUpdate, 128> g_spriteBuffer;
void addSpriteUpdate(const GbaSpriteAttrUpdate &upd) noexcept;
void applySpriteUpdates() noexcept;
}

View File

@ -1,10 +1,14 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/std/types.hpp>
namespace nostalgia::core {
namespace teagba {
constexpr uint16_t DispStat_irq_vblank = 1 << 3;
constexpr uint16_t DispStat_irq_hblank = 1 << 4;
constexpr uint16_t DispStat_irq_vcount = 1 << 5;
constexpr uint16_t Int_vblank = 1 << 0;
constexpr uint16_t Int_hblank = 1 << 1;

View File

@ -1,12 +1,12 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include "addresses.hpp"
namespace nostalgia::core {
namespace teagba {
inline auto bgSetSbb(volatile BgCtl *bgCtl, unsigned sbb) noexcept {
*bgCtl = (*bgCtl & ~0b11111'0000'0000u) | (sbb << 8);

21
deps/teagba/src/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,21 @@
enable_language(CXX ASM)
set_source_files_properties(gfx.cpp PROPERTIES COMPILE_FLAGS -marm)
add_library(
TeaGBA
bios.s
gba_crt0.s
cstartup.cpp
gfx.cpp
)
target_include_directories(
TeaGBA PUBLIC
../include
)
target_link_libraries(
TeaGBA PUBLIC
OxStd
)

37
deps/teagba/src/bios.s vendored Normal file
View File

@ -0,0 +1,37 @@
//
// 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
.thumb
.align
.global teagba_halt
.type teagba_halt, %function
teagba_halt:
swi 2
bx lr
.global teagba_stop
.type teagba_stop, %function
teagba_stop:
swi 3
bx lr
.global teagba_intrwait
.type teagba_intrwait, %function
teagba_intrwait:
swi 4
bx lr
.global teagba_vblankintrwait
.type teagba_vblankintrwait, %function
teagba_vblankintrwait:
swi 5
bx lr
// vim: ft=armv4

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2021 gary@drinkingtea.net
* 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

40
deps/teagba/src/gfx.cpp vendored Normal file
View File

@ -0,0 +1,40 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <teagba/addresses.hpp>
#include <teagba/bios.hpp>
#include <teagba/irq.hpp>
#include <teagba/gfx.hpp>
namespace teagba {
volatile uint16_t g_spriteUpdates = 0;
ox::Array<GbaSpriteAttrUpdate, 128> g_spriteBuffer;
void addSpriteUpdate(const GbaSpriteAttrUpdate &upd) noexcept {
// block until g_spriteUpdates is less than buffer len
if (g_spriteUpdates >= g_spriteBuffer.size()) [[unlikely]] {
teagba_vblankintrwait();
}
const auto ie = REG_IE; // disable vblank interrupt handler
REG_IE = REG_IE & static_cast<uint16_t>(~teagba::Int_vblank); // disable vblank interrupt handler
const auto updateCnt = g_spriteUpdates;
g_spriteBuffer[updateCnt] = upd;
g_spriteUpdates = updateCnt + 1;
REG_IE = ie; // enable vblank interrupt handler
}
void applySpriteUpdates() noexcept {
// copy g_spriteUpdates to allow it to use a register instead of reading
// from memory every iteration of the loop, needed because g_spriteUpdates
// is volatile
const unsigned updates = g_spriteUpdates;
for (unsigned i = 0; i < updates; ++i) {
const auto &oa = g_spriteBuffer[i];
MEM_OAM[oa.idx] = *reinterpret_cast<const uint64_t*>(&oa);
}
}
}

View File

@ -1,4 +1,5 @@
include_directories(".")
add_subdirectory(keel)
add_subdirectory(nostalgia)
add_subdirectory(nostalgia)
add_subdirectory(turbine)

View File

@ -18,6 +18,7 @@ ox::Result<ox::UPtr<Ctx>> init(ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView a
auto ctx = ox::make_unique<Ctx>();
ctx->appName = appName;
oxIgnoreError(setRomFs(ctx.get(), std::move(fs)));
#ifndef OX_BARE_METAL
const auto &mods = modules();
for (auto &mod : mods) {
// register type converters
@ -29,6 +30,7 @@ ox::Result<ox::UPtr<Ctx>> init(ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView a
ctx->packTransforms.emplace_back(c);
}
}
#endif
return ctx;
}

View File

@ -1,5 +1,6 @@
add_library(
NostalgiaCore
context.cpp
gfx.cpp
module.cpp
tilesheet.cpp
@ -10,7 +11,6 @@ add_library(
if(NOSTALGIA_BUILD_TYPE STREQUAL "GBA")
add_subdirectory(gba)
else()
add_subdirectory(glfw)
add_subdirectory(opengl)
endif()
@ -21,6 +21,7 @@ endif()
target_link_libraries(
NostalgiaCore PUBLIC
Keel
Turbine
)
if(NOSTALGIA_BUILD_STUDIO)
@ -29,16 +30,14 @@ endif()
install(
FILES
clipboard.hpp
color.hpp
config.hpp
consts.hpp
context.hpp
core.hpp
event.hpp
gfx.hpp
initparams.hpp
ptidxconv.hpp
input.hpp
tilesheet.hpp
typeconv.hpp
typestore.hpp

View File

@ -1,36 +0,0 @@
/*
* Copyright 2016 - 2022 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 nostalgia::core {
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 {
#ifndef OX_BARE_METAL
ctx->clipboard = std::move(obj);
#endif
}
template<typename T>
ox::Result<T*> getClipboardObject([[maybe_unused]] class Context *ctx) noexcept {
#ifndef OX_BARE_METAL
if (ctx->clipboard && ctx->clipboard->typeMatch(T::TypeName, T::TypeVersion)) {
return static_cast<T*>(ctx->clipboard.get());
}
#endif
return OxError(1);
}
}

View File

@ -1,33 +1,11 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* 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 nostalgia::core::config {
constexpr auto ImGuiEnabled =
#if __has_include(<imgui.h>)
true;
#else
false;
#endif
enum class SdlVsync {
Adaptive = -1,
Off = 0,
On = 1,
};
constexpr auto GbaSpriteBufferLen = 128;
constexpr auto GbaEventLoopTimerBased = false;
constexpr auto GbaTimerBits = 32;
constexpr auto UserlandFpsPrint = false;
constexpr auto SdlVsyncOption = static_cast<int>(SdlVsync::Adaptive);
}

View File

@ -0,0 +1,16 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "context.hpp"
#include "gfx.hpp"
#include "core.hpp"
namespace nostalgia::core {
Context::~Context() noexcept {
shutdownGfx(this);
}
}

View File

@ -10,68 +10,28 @@
#include <ox/std/size.hpp>
#include <keel/context.hpp>
#include <turbine/context.hpp>
#include "event.hpp"
#include "input.hpp"
#include "initparams.hpp"
namespace nostalgia::core {
class Context;
}
namespace nostalgia::core::gl {
void setMainViewEnabled(core::Context *ctx, bool enabled) noexcept;
void drawMainView(core::Context*) noexcept;
void setRenderSize(core::Context*, int width, int height) noexcept;
ox::Size getRenderSize(core::Context*) noexcept;
void clearRenderSize(core::Context *ctx) noexcept;
}
namespace nostalgia::core::renderer {
ox::Error init(Context *ctx) noexcept;
}
namespace ox {
class Size;
}
namespace nostalgia::core {
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);
}
};
class Drawer;
struct BgCbbData {
unsigned bpp = 4;
};
// 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 constexpr void setConstantRefresh(Context *ctx, bool) noexcept;
class Context {
friend bool bgStatus(Context *ctx, unsigned bg) noexcept;
friend bool buttonDown(Context *ctx, Key) noexcept;
friend ox::Size getScreenSize(Context *ctx) noexcept;
friend int getScreenHeight(Context *ctx) noexcept;
friend int getScreenWidth(Context *ctx) noexcept;
friend ox::Error initGfx(Context *ctx) noexcept;
friend ox::Error renderer::init(Context *ctx) noexcept;
friend ox::Error initGfx(Context *ctx, const InitParams&) noexcept;
friend void shutdownGfx(Context *ctx) noexcept;
friend ox::Error loadBgTileSheet(Context *ctx,
unsigned cbb,
const ox::FileAddress &tilesheetPath,
@ -81,21 +41,12 @@ class Context: public keel::Context {
const ox::FileAddress &paletteAddr) noexcept;
friend ox::Result<struct TileSheetData> loadTileSheet(Context *ctx,
const struct CompactTileSheet &tilesheetAddr) noexcept;
friend ox::Error run(Context *ctx) noexcept;
friend void shutdown(Context *ctx) noexcept;
friend ox::Result<ox::UPtr<Context>> init(ox::UPtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept;
friend ox::String getClipboardText(Context *ctx) noexcept;
friend uint64_t ticksMs(Context *ctx) noexcept;
friend uint8_t bgStatus(Context *ctx) noexcept;
friend void clearTileLayer(Context *ctx, unsigned bgIdx) noexcept;
friend void draw(Context *ctx) noexcept;
friend void focusWindow(Context *ctx) noexcept;
friend void setBgCbb(Context *ctx, unsigned bgIdx, unsigned cbb) noexcept;
friend void setBgStatus(Context *ctx, uint32_t status) noexcept;
friend void setBgStatus(Context *ctx, unsigned bg, bool status) 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;
friend void setTile(Context *ctx, unsigned bgIdx, int column, int row, uint8_t tile) noexcept;
friend void setSprite(Context *ctx,
unsigned idx,
@ -106,30 +57,13 @@ class Context: public keel::Context {
unsigned spriteSize,
unsigned flipX) noexcept;
friend void hideSprite(Context *ctx, unsigned idx) noexcept;
friend void gl::setMainViewEnabled(core::Context *ctx, bool enabled) noexcept;
friend void gl::drawMainView(core::Context*) noexcept;
friend void gl::setRenderSize(core::Context*, int width, int height) noexcept;
friend ox::Size gl::getRenderSize(core::Context*) noexcept;
friend void gl::clearRenderSize(core::Context *ctx) noexcept;
public:
ox::Vector<Drawer*, 5> drawers;
#ifndef OX_BARE_METAL
int uninterruptedRefreshes = 3;
ox::UPtr<BaseClipboardObject> clipboard;
#endif
protected:
#ifndef OX_BARE_METAL
// sets screen refresh to constant instead of only on event
bool constantRefresh = true;
#endif
KeyEventHandler m_keyEventHandler = nullptr;
void *m_customData = nullptr;
private:
void *m_windowerData = nullptr;
void *m_rendererData = nullptr;
turbine::Context *turbineCtx = nullptr;
public:
Context() noexcept = default;
@ -138,52 +72,13 @@ class Context: public keel::Context {
Context(const Context &other) noexcept = delete;
Context(const Context &&other) noexcept = delete;
template<typename T>
[[nodiscard]]
constexpr T *windowerData() noexcept {
return static_cast<T*>(m_windowerData);
}
~Context() noexcept;
protected:
constexpr void setWindowerData(void *windowerData) noexcept {
m_windowerData = windowerData;
}
constexpr void setRendererData(void *rendererData) noexcept {
m_rendererData = rendererData;
}
template<typename T>
[[nodiscard]]
constexpr T *rendererData() noexcept {
return static_cast<T*>(m_rendererData);
auto &rom() noexcept {
return *turbineCtx->rom;
}
};
constexpr void setApplicationData(Context *ctx, void *applicationData) noexcept {
ctx->m_customData = applicationData;
}
template<typename T>
[[nodiscard]]
constexpr T *applicationData(Context *ctx) noexcept {
return static_cast<T*>(ctx->m_customData);
}
constexpr void setKeyEventHandler(Context *ctx, KeyEventHandler h) noexcept {
ctx->m_keyEventHandler = h;
}
constexpr KeyEventHandler keyEventHandler(Context *ctx) noexcept {
return ctx->m_keyEventHandler;
}
constexpr void setConstantRefresh([[maybe_unused]] Context *ctx, [[maybe_unused]] bool r) noexcept {
#ifndef OX_BARE_METAL
ctx->constantRefresh = r;
#endif
}
}

View File

@ -4,27 +4,10 @@
#pragma once
#include <ox/fs/fs.hpp>
#include "clipboard.hpp"
#include "consts.hpp"
#include "event.hpp"
#include "gfx.hpp"
#include "input.hpp"
#include "module.hpp"
#include "typeconv.hpp"
#include "context.hpp"
namespace nostalgia::core {
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, ox::CRStringView appName = "Nostalgia") 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(Context *ctx) noexcept;
void shutdown(Context *ctx) noexcept;
ox::Result<ox::UniquePtr<Context>> init(turbine::Context *tctx, const InitParams& = {}) noexcept;
}

View File

@ -1,17 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
namespace nostalgia::core {
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

@ -1,16 +1,12 @@
enable_language(CXX ASM)
set_source_files_properties(core.arm.cpp irq.arm.cpp PROPERTIES COMPILE_FLAGS -marm)
set_source_files_properties(gfx.cpp PROPERTIES COMPILE_FLAGS -marm)
target_sources(
NostalgiaCore PRIVATE
bios.s
core.arm.cpp
core.cpp
gfx.cpp
irq.arm.cpp
irq.s
panic.cpp
)
target_link_libraries(
NostalgiaCore PUBLIC
GbaStartup
)
TeaGBA
)

View File

@ -1,22 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
// Functions for accessing BIOS calls
extern "C" {
// waits for any interrupt
void nostalgia_core_halt();
void nostalgia_core_stop();
// waits for interrupts specified in interSubs
void nostalgia_core_intrwait(unsigned discardExistingIntrs, unsigned intrSubs);
// waits for vblank interrupt
void nostalgia_core_vblankintrwait();
}

View File

@ -1,37 +0,0 @@
//
// Copyright 2016 - 2020 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
.thumb
.align
.global nostalgia_core_halt
.type nostalgia_core_halt, %function
nostalgia_core_halt:
swi 2
bx lr
.global nostalgia_core_stop
.type nostalgia_core_stop, %function
nostalgia_core_stop:
swi 3
bx lr
.global nostalgia_core_intrwait
.type nostalgia_core_intrwait, %function
nostalgia_core_intrwait:
swi 4
bx lr
.global nostalgia_core_vblankintrwait
.type nostalgia_core_vblankintrwait, %function
nostalgia_core_vblankintrwait:
swi 5
bx lr
// vim: ft=armv4

View File

@ -1,42 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <nostalgia/core/config.hpp>
#include <nostalgia/core/core.hpp>
#include "addresses.hpp"
#include "context.hpp"
#include "bios.hpp"
#include "irq.hpp"
#include "core.hpp"
namespace nostalgia::core {
extern volatile gba_timer_t g_timerMs;
gba_timer_t g_wakeupTime;
UpdateHandler g_updateHandler = nullptr;
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 && g_updateHandler) {
auto sleepTime = g_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
nostalgia_core_intrwait(0, Int_timer0 | Int_timer1 | Int_timer2 | Int_timer3);
} else {
nostalgia_core_vblankintrwait();
}
}
return OxError(0);
}
}

View File

@ -1,86 +1,21 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <nostalgia/core/config.hpp>
#include <nostalgia/core/core.hpp>
#include <nostalgia/core/input.hpp>
#include "../context.hpp"
#include "../gfx.hpp"
#include "addresses.hpp"
#include "bios.hpp"
#include "irq.hpp"
#include "context.hpp"
#include "core.hpp"
extern "C" void isr();
#include "../core.hpp"
namespace nostalgia::core {
// Timer Consts
constexpr int NanoSecond = 1000000000;
constexpr int MilliSecond = 1000;
constexpr int TicksMs59ns = 65535 - (NanoSecond / MilliSecond) / 59.59;
ox::Error initGfx(Context *ctx, const InitParams&) noexcept;
extern UpdateHandler g_updateHandler;
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 | 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<Context>> init(ox::UniquePtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept {
ox::UPtr<Context> ctx(ox::make<gba::Context>());
ctx->rom = std::move(fs);
ctx->appName = appName;
oxReturnError(initGfx(ctx.get()));
initTimer();
initIrq();
oxReturnError(findPreloadSection().moveTo(&ctx->preloadSectionOffset));
ox::Result<ox::UniquePtr<Context>> init(turbine::Context *tctx, const InitParams &params) noexcept {
ox::UPtr<Context> ctx = ox::make_unique<Context>();
ctx->turbineCtx = tctx;
oxReturnError(initGfx(ctx.get(), params));
return ctx;
}
void setUpdateHandler(Context*, UpdateHandler h) noexcept {
g_updateHandler = h;
}
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 shutdown(Context *ctx) noexcept {
const auto gbaCtx = static_cast<gba::Context*>(ctx);
gbaCtx->running = false;
}
}
}

View File

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

View File

@ -6,27 +6,26 @@
#include <ox/mc/mc.hpp>
#include <ox/std/array.hpp>
#include <teagba/addresses.hpp>
#include <teagba/gfx.hpp>
#include <teagba/registers.hpp>
#include <keel/media.hpp>
#include <turbine/turbine.hpp>
#include <nostalgia/core/context.hpp>
#include <nostalgia/core/gfx.hpp>
#include "addresses.hpp"
#include "bios.hpp"
#include "gfx.hpp"
#include "irq.hpp"
#include "registers.hpp"
namespace nostalgia::core {
struct BgCbbData {
unsigned bpp = 4;
};
static ox::Array<BgCbbData, 4> g_cbbData;
constexpr auto GbaTileColumns = 32;
constexpr auto GbaTileRows = 32;
constexpr uint16_t DispStat_irq_vblank = 1 << 3;
constexpr uint16_t DispStat_irq_hblank = 1 << 4;
constexpr uint16_t DispStat_irq_vcount = 1 << 5;
struct GbaPaletteTarget {
static constexpr auto TypeName = Palette::TypeName;
static constexpr auto TypeVersion = Palette::TypeVersion;
@ -68,40 +67,14 @@ constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaTileMapTarget> auto *t)
return io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
}
ox::Error initGfx(Context*) noexcept {
REG_DISPCTL = DispCtl_Mode0
| DispCtl_SpriteMap1D
| DispCtl_Obj;
// tell display to trigger vblank interrupts
REG_DISPSTAT = REG_DISPSTAT | DispStat_irq_vblank;
// enable vblank interrupt
REG_IE = REG_IE | Int_vblank;
ox::Error initGfx(Context*, const InitParams&) noexcept {
for (auto bgCtl = &REG_BG0CTL; bgCtl <= &REG_BG3CTL; bgCtl += 2) {
bgSetSbb(bgCtl, 28);
teagba::bgSetSbb(bgCtl, 28);
}
return {};
}
ox::Error shutdownGfx(Context*) noexcept {
return {};
}
void setWindowTitle(Context*, ox::CRStringView) noexcept {
}
void focusWindow(Context*) noexcept {
}
int getScreenWidth(Context*) noexcept {
return 240;
}
int getScreenHeight(Context*) noexcept {
return 160;
}
ox::Size getScreenSize(Context*) noexcept {
return {240, 160};
void shutdownGfx(Context*) noexcept {
}
uint8_t bgStatus(Context*) noexcept {
@ -129,30 +102,35 @@ ox::Error initConsole(Context *ctx) noexcept {
constexpr ox::FileAddress PaletteAddr("/Palettes/Charset.npal");
setBgStatus(ctx, 0b0001);
if (!ctx) {
ctx = new (ox_alloca(sizeof(Context))) Context();
ctx = new(ox_alloca(sizeof(Context))) Context;
oxRequire(rom, keel::loadRom());
ox::FileStore32 fs(rom, 32 * ox::units::MB);
auto romFs = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
new (&ctx->rom) ox::UniquePtr<ox::FileSystem>(romFs);
auto romFs = new(ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
oxRequireM(tctx, turbine::init(ox::UPtr<ox::FileSystem>(romFs)));
ctx->turbineCtx = tctx.release();
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr));
setBgCbb(ctx, 0, 0);
return {};
} else {
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr));
setBgCbb(ctx, 0, 0);
return {};
}
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr));
setBgCbb(ctx, 0, 0);
return {};
}
void setBgCbb(Context*, unsigned bgIdx, unsigned cbb) noexcept {
auto &bgCtl = regBgCtl(bgIdx);
const auto &cbbData = g_cbbData[cbb];
bgSetBpp(&bgCtl, cbbData.bpp);
bgSetCbb(&bgCtl, cbb);
teagba::bgSetBpp(&bgCtl, cbbData.bpp);
teagba::bgSetCbb(&bgCtl, cbb);
}
ox::Error loadBgTileSheet(Context *ctx,
unsigned cbb,
const ox::FileAddress &tilesheetAddr,
const ox::FileAddress &paletteAddr) noexcept {
oxRequire(tsStat, ctx->rom->stat(tilesheetAddr));
oxRequire(ts, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(tilesheetAddr));
oxRequire(tsStat, ctx->rom().stat(tilesheetAddr));
oxRequire(ts, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(tilesheetAddr));
GbaTileMapTarget target;
target.pal.palette = MEM_BG_PALETTE;
target.cbbData = &g_cbbData[cbb];
@ -160,15 +138,15 @@ ox::Error loadBgTileSheet(Context *ctx,
oxReturnError(ox::readMC(ts, tsStat.size, &target));
// load external palette if available
if (paletteAddr) {
oxRequire(palStat, ctx->rom->stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(paletteAddr));
oxRequire(palStat, ctx->rom().stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
}
// update bpp of all bgs with the updated cbb
const auto bpp = g_cbbData[cbb].bpp;
iterateBgCtl([bpp, cbb](auto bgCtl) {
if (bgCbb(bgCtl) == cbb) {
bgSetBpp(bgCtl, bpp);
teagba::iterateBgCtl([bpp, cbb](auto bgCtl) {
if (teagba::bgCbb(bgCtl) == cbb) {
teagba::bgSetBpp(bgCtl, bpp);
}
});
return {};
@ -177,16 +155,16 @@ ox::Error loadBgTileSheet(Context *ctx,
ox::Error loadSpriteTileSheet(Context *ctx,
const ox::FileAddress &tilesheetAddr,
const ox::FileAddress &paletteAddr) noexcept {
oxRequire(tsStat, ctx->rom->stat(tilesheetAddr));
oxRequire(ts, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(tilesheetAddr));
oxRequire(tsStat, ctx->rom().stat(tilesheetAddr));
oxRequire(ts, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(tilesheetAddr));
GbaTileMapTarget target;
target.pal.palette = MEM_SPRITE_PALETTE;
target.tileMap = MEM_SPRITE_TILES;
oxReturnError(ox::readMC(ts, tsStat.size, &target));
// load external palette if available
if (paletteAddr) {
oxRequire(palStat, ctx->rom->stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(paletteAddr));
oxRequire(palStat, ctx->rom().stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
}
return {};
@ -195,8 +173,8 @@ ox::Error loadSpriteTileSheet(Context *ctx,
ox::Error loadBgPalette(Context *ctx, unsigned, const ox::FileAddress &paletteAddr) noexcept {
GbaPaletteTarget target;
target.palette = MEM_BG_PALETTE;
oxRequire(palStat, ctx->rom->stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(paletteAddr));
oxRequire(palStat, ctx->rom().stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
oxReturnError(ox::readMC(pal, palStat.size, &target));
return {};
}
@ -204,13 +182,12 @@ ox::Error loadBgPalette(Context *ctx, unsigned, const ox::FileAddress &paletteAd
ox::Error loadSpritePalette(Context *ctx, unsigned cbb, const ox::FileAddress &paletteAddr) noexcept {
GbaPaletteTarget target;
target.palette = &MEM_SPRITE_PALETTE[cbb];
oxRequire(palStat, ctx->rom->stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(paletteAddr));
oxRequire(palStat, ctx->rom().stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
oxReturnError(ox::readMC(pal, palStat.size, &target));
return {};
}
// Do NOT use Context in the GBA version of this function.
void puts(Context *ctx, int column, int row, ox::CRStringView str) noexcept {
const auto col = static_cast<unsigned>(column);
for (auto i = 0u; i < str.bytes(); i++) {
@ -231,25 +208,11 @@ void clearTileLayer(Context*, unsigned bgIdx) noexcept {
[[maybe_unused]]
void hideSprite(Context*, unsigned idx) noexcept {
oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
GbaSpriteAttrUpdate oa;
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
teagba::GbaSpriteAttrUpdate oa;
oa.attr0 = 2 << 8;
oa.idx = idx;
// block until g_spriteUpdates is less than buffer len
if (g_spriteUpdates >= config::GbaSpriteBufferLen) [[unlikely]] {
nostalgia_core_vblankintrwait();
}
if constexpr(config::GbaEventLoopTimerBased) {
REG_IE = REG_IE & ~Int_vblank; // disable vblank interrupt handler
g_spriteBuffer[g_spriteUpdates] = oa;
REG_IE = REG_IE | Int_vblank; // enable vblank interrupt handler
} else {
const auto ie = REG_IE; // disable vblank interrupt handler
REG_IE = REG_IE & static_cast<uint16_t>(~Int_vblank); // disable vblank interrupt handler
g_spriteBuffer[g_spriteUpdates] = oa;
REG_IE = ie; // enable vblank interrupt handler
}
g_spriteUpdates = g_spriteUpdates + 1;
teagba::addSpriteUpdate(oa);
}
void setSprite(Context*,
@ -260,8 +223,8 @@ void setSprite(Context*,
unsigned spriteShape,
unsigned spriteSize,
unsigned flipX) noexcept {
oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
GbaSpriteAttrUpdate oa;
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
teagba::GbaSpriteAttrUpdate oa;
oa.attr0 = static_cast<uint16_t>(y & ox::onMask<uint8_t>(0b111'1111))
| (static_cast<uint16_t>(1) << 10) // enable alpha
| (static_cast<uint16_t>(spriteShape) << 14);
@ -270,21 +233,7 @@ void setSprite(Context*,
| (static_cast<uint16_t>(spriteSize) << 14);
oa.attr2 = static_cast<uint16_t>(tileIdx & ox::onMask<uint16_t>(8));
oa.idx = idx;
// block until g_spriteUpdates is less than buffer len
if (g_spriteUpdates >= config::GbaSpriteBufferLen) [[unlikely]] {
nostalgia_core_vblankintrwait();
}
if constexpr(config::GbaEventLoopTimerBased) {
REG_IE = REG_IE & ~Int_vblank; // disable vblank interrupt handler
g_spriteBuffer[g_spriteUpdates] = oa;
REG_IE = REG_IE | Int_vblank; // enable vblank interrupt handler
} else {
const auto ie = REG_IE; // disable vblank interrupt handler
REG_IE = REG_IE & static_cast<uint16_t>(~Int_vblank); // disable vblank interrupt handler
g_spriteBuffer[g_spriteUpdates] = oa;
REG_IE = ie; // enable vblank interrupt handler
}
g_spriteUpdates = g_spriteUpdates + 1;
teagba::addSpriteUpdate(oa);
}
}

View File

@ -1,43 +1,11 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/stddef.hpp>
#include <ox/std/types.hpp>
#include <nostalgia/core/config.hpp>
#include "../context.hpp"
namespace nostalgia::core {
enum DispCtl {
DispCtl_Mode0 = 0,
DispCtl_Mode1 = 1,
DispCtl_Mode2 = 2,
DispCtl_Mode3 = 3,
DispCtl_Mode4 = 4,
DispCtl_Mode5 = 5,
DispCtl_SpriteMap1D = 1 << 6,
DispCtl_Bg0 = 1 << 8,
DispCtl_Bg1 = 1 << 9,
DispCtl_Bg2 = 1 << 10,
DispCtl_Bg3 = 1 << 11,
DispCtl_Obj = 1 << 12,
};
struct OX_ALIGN8 GbaSpriteAttrUpdate {
uint16_t attr0 = 0;
uint16_t attr1 = 0;
uint16_t attr2 = 0;
uint16_t idx = 0;
};
extern volatile uint16_t g_spriteUpdates;
extern ox::Array<GbaSpriteAttrUpdate, config::GbaSpriteBufferLen> g_spriteBuffer;
ox::Error initGfx(Context *ctx, const InitParams &) noexcept;
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2016 - 2022 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 "addresses.hpp"
#include "core.hpp"
#include "gfx.hpp"
#include "irq.hpp"
namespace nostalgia::core {
volatile uint16_t g_spriteUpdates = 0;
ox::Array<GbaSpriteAttrUpdate, config::GbaSpriteBufferLen> g_spriteBuffer;
volatile gba_timer_t g_timerMs = 0;
}
using namespace nostalgia::core;
extern "C" {
void nostalgia_core_isr_vblank() {
// copy g_spriteUpdates to allow it to use a register instead of reading
// from memory every iteration of the loop, needed because g_spriteUpdates
// is volatile
const unsigned updates = g_spriteUpdates;
for (unsigned i = 0; i < updates; ++i) {
const auto &oa = g_spriteBuffer[i];
MEM_OAM[oa.idx] = *reinterpret_cast<const uint64_t*>(&oa);
}
g_spriteUpdates = 0;
if constexpr(config::GbaEventLoopTimerBased) {
// disable vblank interrupt until it is needed again
REG_IE = REG_IE & ~Int_vblank;
}
}
void nostalgia_core_isr_timer0() {
g_timerMs = g_timerMs + 1;
}
}

View File

@ -7,32 +7,43 @@
#include <nostalgia/core/core.hpp>
#include <nostalgia/core/gfx.hpp>
#include "addresses.hpp"
#include "bios.hpp"
#include <teagba/addresses.hpp>
#include <teagba/bios.hpp>
#include <teagba/gfx.hpp>
#include "gfx.hpp"
namespace ox {
using namespace nostalgia::core;
void panic(const char*, int, const char *msg, const ox::Error &err) noexcept {
oxIgnoreError(initGfx(nullptr));
void panic(const char *file, int line, const char *panicMsg, const ox::Error &err) noexcept {
oxIgnoreError(initGfx(nullptr, {}));
oxIgnoreError(initConsole(nullptr));
// enable only BG 0
REG_DISPCTL = DispCtl_Bg0;
REG_DISPCTL = teagba::DispCtl_Bg0;
clearTileLayer(nullptr, 0);
ox::BString<23> serr = "Error code: ";
serr += static_cast<int64_t>(err);
puts(nullptr, 32 + 1, 1, "SADNESS...");
puts(nullptr, 32 + 1, 4, "UNEXPECTED STATE:");
puts(nullptr, 32 + 2, 6, msg);
puts(nullptr, 32 + 2, 6, panicMsg);
if (err) {
puts(nullptr, 32 + 2, 8, serr);
}
puts(nullptr, 32 + 1, 15, "PLEASE RESTART THE SYSTEM");
// print to terminal if in mGBA
oxErrf("\033[31;1;1mPANIC:\033[0m [{}:{}]: {}\n", file, line, panicMsg);
if (err.msg) {
oxErrf("\tError Message:\t{}\n", err.msg);
}
oxErrf("\tError Code:\t{}\n", static_cast<ErrorCode>(err));
if (err.file != nullptr) {
oxErrf("\tError Location:\t{}:{}\n", err.file, err.line);
}
// disable all interrupt handling and IntrWait on no interrupts
REG_IE = 0;
nostalgia_core_intrwait(0, 0);
teagba_intrwait(0, 0);
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <map>
#include <string>
#include <ox/std/std.hpp>
namespace ox::heapmgr {
[[nodiscard]]
void *malloc(std::size_t allocSize) noexcept;
void free(void *ptr) noexcept;
void initHeap(char *heapBegin, char *heapEnd) noexcept;
}
std::map<std::string, int(*)(std::string)> tests = {
};
int main(int argc, const char **args) {
int retval = -1;
if (argc > 1) {
auto testName = args[1];
std::string testArg = "";
if (args[2]) {
testArg = args[2];
}
if (tests.find(testName) != tests.end()) {
retval = tests[testName](testArg);
}
}
return retval;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "gfx.hpp"
@ -137,19 +137,6 @@ ox::Array<char, 128> charMap = {
0, // ~
};
void addCustomDrawer(Context *ctx, Drawer *cd) noexcept {
ctx->drawers.emplace_back(cd);
}
void removeCustomDrawer(Context *ctx, Drawer *cd) noexcept {
for (auto i = 0u; i < ctx->drawers.size(); ++i) {
if (ctx->drawers[i] == cd) {
oxIgnoreError(ctx->drawers.erase(i));
break;
}
}
}
void setSprite(Context *c, const Sprite &s) noexcept {
setSprite(c, s.idx, s.x, s.y, s.tileIdx, s.spriteShape, s.spriteSize, s.flipX);
}

View File

@ -19,13 +19,6 @@ namespace nostalgia::core {
extern ox::Array<char, 128> charMap;
class Drawer {
public:
virtual ~Drawer() = default;
virtual void draw(Context*) noexcept = 0;
};
enum class TileSheetSpace {
Background,
Sprite
@ -51,25 +44,6 @@ oxModelBegin(Sprite)
oxModelField(flipX)
oxModelEnd()
ox::Error initGfx(Context *ctx) noexcept;
void addCustomDrawer(Context *ctx, Drawer *cd) noexcept;
void removeCustomDrawer(Context *ctx, Drawer *cd) 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;
[[nodiscard]]
uint8_t bgStatus(Context *ctx) noexcept;

View File

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

View File

@ -1,82 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <GLFW/glfw3.h>
#include <keel/keel.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/core/opengl/gfx.hpp>
#include "core.hpp"
namespace nostalgia::core {
ox::Result<ox::UPtr<Context>> init(ox::UPtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept {
oxRequireM(ctx, keel::init<Context>(std::move(fs), appName));
const auto id = ox::make<GlfwImplData>();
ctx->setWindowerData(id);
using namespace std::chrono;
id->startTime = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
glfwInit();
oxReturnError(initGfx(ctx.get()));
return std::move(ctx);
}
ox::Error run(Context *ctx) noexcept {
const auto id = ctx->windowerData<GlfwImplData>();
int sleepTime = 0;
while (!glfwWindowShouldClose(id->window)) {
glfwPollEvents();
const auto ticks = ticksMs(ctx);
if (id->wakeupTime <= ticks) {
sleepTime = id->eventHandler(ctx);
if (sleepTime >= 0) {
id->wakeupTime = ticks + static_cast<unsigned>(sleepTime);
} else {
id->wakeupTime = ~uint64_t(0);
}
} else {
sleepTime = 10;
}
draw(ctx);
glfwSwapBuffers(id->window);
if (!ctx->constantRefresh) {
if (ctx->uninterruptedRefreshes) {
--ctx->uninterruptedRefreshes;
} else {
glfwWaitEventsTimeout(sleepTime);
}
}
}
// destroy GLFW window
renderer::shutdown(ctx, ctx->rendererData<void>());
glfwDestroyWindow(id->window);
ctx->setWindowerData(nullptr);
ox::safeDelete(id);
return OxError(0);
}
void setUpdateHandler(Context *ctx, UpdateHandler h) noexcept {
const auto id = ctx->windowerData<GlfwImplData>();
id->eventHandler = h;
}
uint64_t ticksMs(Context *ctx) noexcept {
using namespace std::chrono;
const auto id = ctx->windowerData<GlfwImplData>();
const auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
return static_cast<uint64_t>(now - id->startTime);
}
bool buttonDown(Context *ctx, Key key) noexcept {
const auto id = ctx->windowerData<GlfwImplData>();
return (id->keysDown >> static_cast<int>(key)) & 1;
}
void shutdown(Context *ctx) noexcept {
const auto id = ctx->windowerData<GlfwImplData>();
glfwSetWindowShouldClose(id->window, true);
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <nostalgia/core/core.hpp>
namespace nostalgia::core {
struct GlfwImplData {
struct GLFWwindow *window = nullptr;
int64_t startTime = 0;
UpdateHandler eventHandler = [](Context*) -> int {return 0;};
KeyEventHandler keyEventHandler = nullptr;
uint64_t wakeupTime = 0;
uint64_t keysDown = 0;
};
}

View File

@ -0,0 +1,13 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
namespace nostalgia::core {
struct InitParams {
bool glInstallDrawer = true;
};
}

View File

@ -1,7 +1,7 @@
target_sources(
NostalgiaCore PRIVATE
core.cpp
gfx.cpp
gfx_opengl.cpp
)
target_link_libraries(
NostalgiaCore PUBLIC

View File

@ -0,0 +1,26 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/types.hpp>
#include <nostalgia/glutils/glutils.hpp>
#include "gfx.hpp"
namespace nostalgia::core {
struct GlContext: public core::Context {
glutils::GLProgram bgShader;
glutils::GLProgram spriteShader;
ox::Array<renderer::CBB, 4> cbbs;
renderer::SpriteBlockset spriteBlocks;
ox::Array<Sprite, 128> spriteStates;
ox::Array<renderer::Background, 4> backgrounds;
ox::Optional<ox::Size> renderSize;
renderer::Drawer drawer;
};
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <nostalgia/core/context.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/core/core.hpp>
#include "context.hpp"
namespace nostalgia::core {
ox::Result<ox::UniquePtr<Context>> init(turbine::Context *tctx, const InitParams &params) noexcept {
ox::UPtr<Context> ctx = ox::make_unique<GlContext>();
ctx->turbineCtx = tctx;
oxReturnError(initGfx(ctx.get(), params));
return ctx;
}
}

View File

@ -2,13 +2,337 @@
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <nostalgia/core/gfx.hpp>
#include <imgui_impl_opengl3.h>
#include <ox/std/array.hpp>
#include <ox/std/fmt.hpp>
#include <ox/std/vec.hpp>
#include <keel/media.hpp>
#include "gfx.hpp"
#include <nostalgia/glutils/glutils.hpp>
#include <nostalgia/core/context.hpp>
#include <nostalgia/core/gfx.hpp>
#include "context.hpp"
namespace nostalgia::core {
void draw(Context *ctx) noexcept;
namespace renderer {
void Drawer::draw(turbine::Context&) noexcept {
core::draw(m_ctx);
}
constexpr ox::StringView bgvshadTmpl = R"(
{}
in vec2 vTexCoord;
in vec2 vPosition;
out vec2 fTexCoord;
uniform float vXScale;
uniform float vTileHeight;
void main() {
float xScaleInvert = 1.0 - vXScale;
gl_Position = vec4(
vPosition.x * vXScale - xScaleInvert, vPosition.y,
0.0, 1.0);
fTexCoord = vTexCoord * vec2(1, vTileHeight);
})";
constexpr ox::StringView bgfshadTmpl = R"(
{}
out vec4 outColor;
in vec2 fTexCoord;
uniform sampler2D image;
uniform vec4 fPalette[256];
void main() {
int idx = int(texture(image, fTexCoord).rgb.r * 256);
outColor = fPalette[idx];
//outColor = vec4(0.0, 0.7, 1.0, 1.0);
})";
constexpr ox::StringView spritevshadTmpl = R"(
{}
in float vEnabled;
in vec2 vTexCoord;
in vec2 vPosition;
out vec2 fTexCoord;
uniform float vXScale;
uniform float vTileHeight;
void main() {
float xScaleInvert = 1.0 - vXScale;
gl_Position = vec4(
vPosition.x * vXScale - xScaleInvert, vPosition.y,
0.0, 1.0);
fTexCoord = vTexCoord * vec2(1, vTileHeight) * vec2(vEnabled, vEnabled);
})";
constexpr ox::StringView spritefshadTmpl = bgfshadTmpl;
[[nodiscard]]
static constexpr auto bgVertexRow(unsigned x, unsigned y) noexcept {
return y * TileRows + x;
}
static void setSpriteBufferObject(Context*,
unsigned vi,
float enabled,
float x, float y,
unsigned textureRow,
unsigned flipX,
float *vbo,
GLuint *ebo) noexcept {
// don't worry, this memcpy gets optimized to something much more ideal
constexpr float xmod = 0.1f;
constexpr float ymod = 0.1f;
x *= xmod;
y *= -ymod;
x -= 1.f;
y += 1.f - ymod;
const auto textureRowf = static_cast<float>(textureRow);
const float L = flipX ? 1 : 0;
const float R = flipX ? 0 : 1;
const ox::Array<float, SpriteVertexVboLength> vertices {
enabled, x, y, L, textureRowf + 1, // bottom left
enabled, x + xmod, y, R, textureRowf + 1, // bottom right
enabled, x + xmod, y + ymod, R, textureRowf + 0, // top right
enabled, x, y + ymod, L, textureRowf + 0, // top left
};
memcpy(vbo, vertices.data(), sizeof(vertices));
const ox::Array<GLuint, SpriteVertexEboLength> elms {
vi + 0, vi + 1, vi + 2,
vi + 2, vi + 3, vi + 0,
};
memcpy(ebo, elms.data(), sizeof(elms));
}
static void setTileBufferObject(Context*,
unsigned vi,
float x,
float y,
unsigned textureRow,
float *vbo,
GLuint *ebo) noexcept {
// don't worry, this memcpy gets optimized to something much more ideal
constexpr float ymod = 0.1f;
constexpr float xmod = 0.1f;
x *= xmod;
y *= -ymod;
x -= 1.0f;
y += 1.0f - ymod;
const auto textureRowf = static_cast<float>(textureRow);
const ox::Array<float, BgVertexVboLength> vertices {
x, y, 0, textureRowf + 1, // bottom left
x + xmod, y, 1, textureRowf + 1, // bottom right
x + xmod, y + ymod, 1, textureRowf + 0, // top right
x, y + ymod, 0, textureRowf + 0, // top left
};
memcpy(vbo, vertices.data(), sizeof(vertices));
const ox::Array<GLuint, BgVertexEboLength> elms {
vi + 0, vi + 1, vi + 2,
vi + 2, vi + 3, vi + 0,
};
memcpy(ebo, elms.data(), sizeof(elms));
}
static void initSpriteBufferObjects(Context *ctx, glutils::BufferSet *bs) noexcept {
for (auto i = 0u; i < SpriteCount; ++i) {
auto vbo = &bs->vertices[i * static_cast<std::size_t>(SpriteVertexVboLength)];
auto ebo = &bs->elements[i * static_cast<std::size_t>(SpriteVertexEboLength)];
setSpriteBufferObject(ctx, i * SpriteVertexVboRows, 0, 0, 0, 0, false, vbo, ebo);
}
}
static void initBackgroundBufferObjects(Context *ctx, glutils::BufferSet *bg) noexcept {
for (auto x = 0u; x < TileColumns; ++x) {
for (auto y = 0u; y < TileRows; ++y) {
const auto i = bgVertexRow(x, y);
auto vbo = &bg->vertices[i * static_cast<std::size_t>(BgVertexVboLength)];
auto ebo = &bg->elements[i * static_cast<std::size_t>(BgVertexEboLength)];
setTileBufferObject(ctx, i * BgVertexVboRows, static_cast<float>(x), static_cast<float>(y), 0, vbo, ebo);
}
}
}
static void initSpritesBufferset(Context *ctx, GLuint shader, glutils::BufferSet *bs) noexcept {
// vao
bs->vao = glutils::generateVertexArrayObject();
glBindVertexArray(bs->vao);
// vbo & ebo
bs->vbo = glutils::generateBuffer();
bs->ebo = glutils::generateBuffer();
initSpriteBufferObjects(ctx, bs);
glutils::sendVbo(*bs);
glutils::sendEbo(*bs);
// vbo layout
auto enabledAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vEnabled"));
glEnableVertexAttribArray(enabledAttr);
glVertexAttribPointer(enabledAttr, 1, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), nullptr);
auto posAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vPosition"));
glEnableVertexAttribArray(posAttr);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
reinterpret_cast<void*>(1 * sizeof(float)));
auto texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
glEnableVertexAttribArray(texCoordAttr);
glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
reinterpret_cast<void*>(3 * sizeof(float)));
}
static void initBackgroundBufferset(Context *ctx, GLuint shader, glutils::BufferSet *bg) noexcept {
// vao
bg->vao = glutils::generateVertexArrayObject();
glBindVertexArray(bg->vao);
// vbo & ebo
bg->vbo = glutils::generateBuffer();
bg->ebo = glutils::generateBuffer();
initBackgroundBufferObjects(ctx, bg);
glutils::sendVbo(*bg);
glutils::sendEbo(*bg);
// vbo layout
auto posAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vPosition"));
glEnableVertexAttribArray(posAttr);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float), nullptr);
auto texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
glEnableVertexAttribArray(texCoordAttr);
glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float),
reinterpret_cast<void*>(2 * sizeof(float)));
}
static glutils::GLTexture loadTexture(GLsizei w, GLsizei h, const void *pixels) noexcept {
GLuint texId = 0;
glGenTextures(1, &texId);
glutils::GLTexture tex(texId);
tex.width = w;
tex.height = h;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex.id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return tex;
}
static void drawBackground(CBB *cbb) noexcept {
glBindVertexArray(cbb->vao);
if (cbb->updated) {
cbb->updated = false;
glutils::sendVbo(*cbb);
}
glBindTexture(GL_TEXTURE_2D, cbb->tex);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(cbb->elements.size()), GL_UNSIGNED_INT, nullptr);
}
static void drawBackgrounds(GlContext *gctx) noexcept {
// load background shader and its uniforms
glUseProgram(gctx->bgShader);
const auto uniformXScale = static_cast<GLint>(glGetUniformLocation(gctx->bgShader, "vXScale"));
const auto uniformTileHeight = static_cast<GLint>(glGetUniformLocation(gctx->bgShader, "vTileHeight"));
const auto [wi, hi] = gl::getRenderSize(gctx);
const auto wf = static_cast<float>(wi);
const auto hf = static_cast<float>(hi);
glUniform1f(uniformXScale, hf / wf);
for (const auto &bg : gctx->backgrounds) {
if (bg.enabled) {
auto &cbb = gctx->cbbs[bg.cbbIdx];
const auto tileRows = cbb.tex.height / TileHeight;
glUniform1f(uniformTileHeight, 1.0f / static_cast<float>(tileRows));
drawBackground(&cbb);
}
}
}
static void drawSprites(GlContext *gctx) noexcept {
glUseProgram(gctx->spriteShader);
auto &sb = gctx->spriteBlocks;
const auto uniformXScale = static_cast<GLint>(glGetUniformLocation(gctx->bgShader, "vXScale"));
const auto uniformTileHeight = static_cast<GLint>(glGetUniformLocation(gctx->spriteShader, "vTileHeight"));
const auto [wi, hi] = gl::getRenderSize(gctx);
const auto wf = static_cast<float>(wi);
const auto hf = static_cast<float>(hi);
glUniform1f(uniformXScale, hf / wf);
// update vbo
glBindVertexArray(sb.vao);
if (sb.updated) {
sb.updated = false;
glutils::sendVbo(sb);
}
// set vTileHeight uniform
const auto tileRows = sb.tex.height / TileHeight;
glUniform1f(uniformTileHeight, 1.0f / static_cast<float>(tileRows));
// draw
glBindTexture(GL_TEXTURE_2D, sb.tex);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(sb.elements.size()), GL_UNSIGNED_INT, nullptr);
}
static void loadPalette(GLuint shaderPgrm, const Palette &pal, bool firstIsTransparent = false) noexcept {
static constexpr std::size_t ColorCnt = 256;
ox::Array<GLfloat, ColorCnt * 4> palette{};
for (auto i = 0u; const auto c : pal.colors) {
palette[i++] = redf(c);
palette[i++] = greenf(c);
palette[i++] = bluef(c);
palette[i++] = 255;
}
if (firstIsTransparent) {
palette[3] = 0;
}
glUseProgram(shaderPgrm);
const auto uniformPalette = static_cast<GLint>(glGetUniformLocation(shaderPgrm, "fPalette"));
glUniform4fv(uniformPalette, ColorCnt, palette.data());
}
static void loadBgPalette(GlContext *gctx, const Palette &pal) noexcept {
loadPalette(gctx->bgShader, pal);
}
static void loadSpritePalette(GlContext *gctx, const Palette &pal) noexcept {
loadPalette(gctx->spriteShader, pal, true);
}
static void loadBgTexture(GlContext *gctx, unsigned cbbIdx, const void *pixels, int w, int h) noexcept {
oxTracef("nostalgia::core::gfx::gl", "loadBgTexture: { cbbIdx: {}, w: {}, h: {} }", cbbIdx, w, h);
gctx->cbbs[cbbIdx].tex = loadTexture(w, h, pixels);
}
static void loadSpriteTexture(GlContext *gctx, const void *pixels, int w, int h) noexcept {
oxTracef("nostalgia::core::gfx::gl", "loadSpriteTexture: { w: {}, h: {} }", w, h);
gctx->spriteBlocks.tex = loadTexture(w, h, pixels);
}
}
ox::Error initGfx(Context *ctx, const InitParams &initParams) noexcept {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const auto bgVshad = ox::sfmt(renderer::bgvshadTmpl, glutils::GlslVersion);
const auto bgFshad = ox::sfmt(renderer::bgfshadTmpl, glutils::GlslVersion);
const auto spriteVshad = ox::sfmt(renderer::spritevshadTmpl, glutils::GlslVersion);
const auto spriteFshad = ox::sfmt(renderer::spritefshadTmpl, glutils::GlslVersion);
const auto gctx = static_cast<GlContext*>(ctx);
oxReturnError(glutils::buildShaderProgram(bgVshad.c_str(), bgFshad.c_str()).moveTo(&gctx->bgShader));
oxReturnError(glutils::buildShaderProgram(spriteVshad.c_str(), spriteFshad.c_str()).moveTo(&gctx->spriteShader));
for (auto &bg : gctx->cbbs) {
initBackgroundBufferset(ctx, gctx->bgShader, &bg);
}
gctx->drawer.m_ctx = ctx;
if (initParams.glInstallDrawer) {
turbine::gl::addDrawer(*ctx->turbineCtx, &gctx->drawer);
initSpritesBufferset(ctx, gctx->spriteShader, &gctx->spriteBlocks);
}
ImGui_ImplOpenGL3_Init(glutils::GlslVersion);
return {};
}
void shutdownGfx(Context *ctx) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
turbine::gl::removeDrawer(*ctx->turbineCtx, &gctx->drawer);
}
ox::Error initConsole(Context *ctx) noexcept {
constexpr ox::FileAddress TilesheetAddr("/TileSheets/Charset.ng");
constexpr ox::FileAddress PaletteAddr("/Palettes/Charset.npal");
@ -24,6 +348,7 @@ struct TileSheetData {
};
ox::Result<TileSheetData> loadTileSheet(Context *ctx, const CompactTileSheet &tilesheet) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
const unsigned bytesPerTile = tilesheet.bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
const auto tiles = tilesheet.pixels.size() / bytesPerTile;
constexpr int width = 8;
@ -41,8 +366,7 @@ ox::Result<TileSheetData> loadTileSheet(Context *ctx, const CompactTileSheet &ti
pixels[i * 2 + 1] = tilesheet.pixels[i] >> 4;
}
}
const auto rd = ctx->rendererData<void>();
renderer::loadSpriteTexture(rd, pixels.data(), width, height);
renderer::loadSpriteTexture(gctx, pixels.data(), width, height);
return TileSheetData{std::move(pixels), width, height};
}
@ -50,24 +374,26 @@ ox::Error loadBgTileSheet(Context *ctx,
unsigned cbb,
const ox::FileAddress &tilesheetAddr,
const ox::FileAddress &paletteAddr) noexcept {
oxRequire(tilesheet, readObj<CompactTileSheet>(ctx, tilesheetAddr));
oxRequire(palette, readObj<Palette>(ctx, paletteAddr ? paletteAddr : tilesheet->defaultPalette));
auto &kctx = *ctx->turbineCtx;
const auto gctx = static_cast<GlContext*>(ctx);
oxRequire(tilesheet, readObj<CompactTileSheet>(&kctx, tilesheetAddr));
oxRequire(palette, readObj<Palette>(&kctx, paletteAddr ? paletteAddr : tilesheet->defaultPalette));
oxRequire(tsd, loadTileSheet(ctx, *tilesheet));
const auto rd = ctx->rendererData<void>();
renderer::loadBgTexture(rd, cbb, tsd.pixels.data(), tsd.width, tsd.height);
renderer::loadBgPalette(rd, *palette);
renderer::loadBgTexture(gctx, cbb, tsd.pixels.data(), tsd.width, tsd.height);
renderer::loadBgPalette(gctx, *palette);
return {};
}
ox::Error loadSpriteTileSheet(Context *ctx,
const ox::FileAddress &tilesheetAddr,
const ox::FileAddress &paletteAddr) noexcept {
oxRequire(tilesheet, readObj<CompactTileSheet>(ctx, tilesheetAddr));
oxRequire(palette, readObj<Palette>(ctx, paletteAddr ? paletteAddr : tilesheet->defaultPalette));
auto &kctx = *ctx->turbineCtx;
const auto gctx = static_cast<GlContext*>(ctx);
oxRequire(tilesheet, readObj<CompactTileSheet>(&kctx, tilesheetAddr));
oxRequire(palette, readObj<Palette>(&kctx, paletteAddr ? paletteAddr : tilesheet->defaultPalette));
oxRequire(tsd, loadTileSheet(ctx, *tilesheet));
const auto rd = ctx->rendererData<void>();
renderer::loadSpriteTexture(rd, tsd.pixels.data(), tsd.width, tsd.height);
renderer::loadSpritePalette(rd, *palette);
renderer::loadSpriteTexture(gctx, tsd.pixels.data(), tsd.width, tsd.height);
renderer::loadSpritePalette(gctx, *palette);
return {};
}
@ -77,5 +403,170 @@ void puts(Context *ctx, int column, int row, ox::CRStringView str) noexcept {
setTile(ctx, 0, static_cast<int>(col + i), row, static_cast<uint8_t>(charMap[static_cast<uint8_t>(str[i])]));
}
}
void setBgCbb(Context *ctx, unsigned bgIdx, unsigned cbbIdx) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
auto &bg = gctx->backgrounds[bgIdx];
bg.cbbIdx = cbbIdx;
}
uint8_t bgStatus(Context *ctx) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
uint8_t out = 0;
for (unsigned i = 0; i < gctx->cbbs.size(); ++i) {
out |= gctx->backgrounds[i].enabled << i;
}
return out;
}
void setBgStatus(Context *ctx, uint32_t status) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
for (unsigned i = 0; i < gctx->cbbs.size(); ++i) {
gctx->backgrounds[i].enabled = (status >> i) & 1;
}
}
bool bgStatus(Context *ctx, unsigned bg) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
return gctx->backgrounds[bg].enabled;
}
void setBgStatus(Context *ctx, unsigned bg, bool status) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
gctx->backgrounds[bg].enabled = status;
}
void draw(Context *ctx) noexcept {
gl::drawMainView(ctx);
}
void clearTileLayer(Context *ctx, unsigned bgIdx) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
auto &bg = gctx->cbbs[static_cast<std::size_t>(bgIdx)];
initBackgroundBufferObjects(ctx, &bg);
bg.updated = true;
}
void hideSprite(Context *ctx, unsigned idx) noexcept {
auto &gctx = *static_cast<GlContext*>(ctx);
auto vbo = &gctx.spriteBlocks.vertices[idx * renderer::SpriteVertexVboLength];
auto ebo = &gctx.spriteBlocks.elements[idx * renderer::SpriteVertexEboLength];
renderer::setSpriteBufferObject(ctx, idx * renderer::SpriteVertexVboRows, 0,
0, 0, 0, false, vbo, ebo);
gctx.spriteBlocks.updated = true;
}
void setSprite(Context *ctx,
unsigned idx,
int x,
int y,
unsigned tileIdx,
[[maybe_unused]] unsigned spriteShape,
[[maybe_unused]] unsigned spriteSize,
unsigned flipX) noexcept {
//oxTracef("nostalgia::core::gfx::gl", "setSprite(ctx, {}, {}, {}, {}, {}, {}, {})",
// idx, x, y, tileIdx, spriteShape, spriteSize, flipX);
// Tonc Table 8.4
static constexpr ox::Array<ox::Vec<unsigned>, 12> dimensions{
// col 0
{1, 1}, // 0, 0
{2, 2}, // 0, 1
{4, 4}, // 0, 2
{8, 8}, // 0, 3
// col 1
{2, 1}, // 1, 0
{4, 1}, // 1, 1
{4, 2}, // 1, 2
{8, 4}, // 1, 3
// col 2
{1, 1}, // 2, 0
{1, 4}, // 2, 1
{2, 4}, // 2, 2
{4, 8}, // 2, 3
};
const auto dim = dimensions[(spriteShape << 2) | spriteSize];
const auto uX = static_cast<int>(x) % 255;
const auto uY = static_cast<int>(y + 8) % 255 - 8;
auto &gctx = static_cast<GlContext&>(*ctx);
auto i = 0u;
const auto set = [&](int xIt, int yIt) {
const auto fX = static_cast<float>(uX + xIt * 8) / 8;
const auto fY = static_cast<float>(uY + yIt * 8) / 8;
const auto cidx = idx + i;
auto vbo = &gctx.spriteBlocks.vertices[cidx * renderer::SpriteVertexVboLength];
auto ebo = &gctx.spriteBlocks.elements[cidx * renderer::SpriteVertexEboLength];
renderer::setSpriteBufferObject(ctx, cidx * renderer::SpriteVertexVboRows, 1,
fX, fY, tileIdx + i, flipX, vbo, ebo);
++i;
};
if (!flipX) {
for (auto yIt = 0; yIt < static_cast<int>(dim.y); ++yIt) {
for (auto xIt = 0u; xIt < dim.x; ++xIt) {
set(static_cast<int>(xIt), static_cast<int>(yIt));
}
}
} else {
for (auto yIt = 0u; yIt < dim.y; ++yIt) {
for (auto xIt = dim.x - 1; xIt < ~0u; --xIt) {
set(static_cast<int>(xIt), static_cast<int>(yIt));
}
}
}
gctx.spriteBlocks.updated = true;
}
void setTile(Context *ctx, unsigned bgIdx, int column, int row, uint8_t tile) noexcept {
oxTracef(
"nostalgia::core::gfx::setTile",
"bgIdx: {}, column: {}, row: {}, tile: {}",
bgIdx, column, row, tile);
const auto gctx = static_cast<GlContext*>(ctx);
const auto z = static_cast<unsigned>(bgIdx);
const auto y = static_cast<unsigned>(row);
const auto x = static_cast<unsigned>(column);
const auto i = renderer::bgVertexRow(x, y);
auto &bg = gctx->cbbs[z];
auto vbo = &bg.vertices[i * renderer::BgVertexVboLength];
auto ebo = &bg.elements[i * renderer::BgVertexEboLength];
renderer::setTileBufferObject(
ctx, i * renderer::BgVertexVboRows,
static_cast<float>(x), static_cast<float>(y),
tile, vbo, ebo);
bg.updated = true;
}
namespace gl {
void drawMainView(core::Context *ctx) noexcept {
// clear screen
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
const auto gctx = static_cast<GlContext*>(ctx);
renderer::drawBackgrounds(gctx);
if (gctx->spriteBlocks.tex) {
renderer::drawSprites(gctx);
}
}
void setRenderSize(core::Context *ctx, int width, int height) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
gctx->renderSize.emplace(width, height);
}
void clearRenderSize(core::Context *ctx) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
gctx->renderSize.reset();
}
ox::Size getRenderSize(core::Context *ctx) noexcept {
const auto gctx = static_cast<GlContext*>(ctx);
if (gctx->renderSize.has_value()) {
return gctx->renderSize.value();
} else {
return turbine::getScreenSize(*ctx->turbineCtx);
}
}
}
}

View File

@ -1,29 +1,63 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/types.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/gfx.hpp>
namespace nostalgia::core {
struct Palette;
}
#include <nostalgia/glutils/glutils.hpp>
namespace nostalgia::core::renderer {
ox::Error init(Context *ctx, void **rendererData) noexcept;
constexpr uint64_t TileRows = 128;
constexpr uint64_t TileColumns = 128;
constexpr uint64_t TileCount = TileRows * TileColumns;
constexpr uint64_t SpriteCount = 128;
constexpr uint64_t BgVertexVboRows = 4;
constexpr uint64_t BgVertexVboRowLength = 4;
constexpr uint64_t BgVertexVboLength = BgVertexVboRows * BgVertexVboRowLength;
constexpr uint64_t BgVertexEboLength = 6;
constexpr uint64_t SpriteVertexVboRows = 256;
constexpr uint64_t SpriteVertexVboRowLength = 5;
constexpr uint64_t SpriteVertexVboLength = SpriteVertexVboRows * SpriteVertexVboRowLength;
constexpr uint64_t SpriteVertexEboLength = 6;
void shutdown(Context *ctx, void *rendererData) noexcept;
struct CBB: public glutils::BufferSet {
bool updated = false;
constexpr CBB() noexcept {
vertices.resize(TileCount * BgVertexVboLength);
elements.resize(TileCount * BgVertexEboLength);
}
};
void loadBgPalette(void *rendererData, const Palette &pal) noexcept;
struct SpriteBlockset: public glutils::BufferSet {
bool updated = false;
constexpr SpriteBlockset() noexcept {
vertices.resize(SpriteCount * SpriteVertexVboLength);
elements.resize(SpriteCount * SpriteVertexEboLength);
}
};
void loadBgTexture(void *rendererData, unsigned cbb, const void *pixels, int w, int h) noexcept;
struct Background {
bool enabled = false;
unsigned cbbIdx = 0;
};
void loadSpritePalette(void *rendererData, const Palette &pal) noexcept;
struct Sprite {
bool enabled = false;
};
void loadSpriteTexture(void *rendererData, const void *pixels, int w, int h) noexcept;
class Drawer: public turbine::gl::Drawer {
public:
Context *m_ctx = nullptr;
void draw(turbine::Context&) noexcept final;
};
}
namespace nostalgia::core {
ox::Error initGfx(Context *ctx, const InitParams &) noexcept;
}

View File

@ -1,585 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <imgui_impl_opengl3.h>
#include <ox/std/array.hpp>
#include <ox/std/fmt.hpp>
#include <ox/std/vec.hpp>
#include <nostalgia/glutils/glutils.hpp>
#include <nostalgia/core/context.hpp>
#include <nostalgia/core/config.hpp>
#include <nostalgia/core/gfx.hpp>
namespace nostalgia::core {
void ImGui_Impl_NewFrame() noexcept;
namespace renderer {
constexpr uint64_t TileRows = 128;
constexpr uint64_t TileColumns = 128;
constexpr uint64_t TileCount = TileRows * TileColumns;
constexpr uint64_t SpriteCount = 128;
constexpr uint64_t BgVertexVboRows = 4;
constexpr uint64_t BgVertexVboRowLength = 4;
constexpr uint64_t BgVertexVboLength = BgVertexVboRows * BgVertexVboRowLength;
constexpr uint64_t BgVertexEboLength = 6;
constexpr uint64_t SpriteVertexVboRows = 256;
constexpr uint64_t SpriteVertexVboRowLength = 5;
constexpr uint64_t SpriteVertexVboLength = SpriteVertexVboRows * SpriteVertexVboRowLength;
constexpr uint64_t SpriteVertexEboLength = 6;
struct CBB: public glutils::BufferSet {
bool updated = false;
constexpr CBB() noexcept {
vertices.resize(TileCount * BgVertexVboLength);
elements.resize(TileCount * BgVertexEboLength);
}
};
struct SpriteBlockset: public glutils::BufferSet {
bool updated = false;
constexpr SpriteBlockset() noexcept {
vertices.resize(SpriteCount * SpriteVertexVboLength);
elements.resize(SpriteCount * SpriteVertexEboLength);
}
};
struct Background {
bool enabled = false;
unsigned cbbIdx = 0;
};
struct Sprite {
bool enabled = false;
};
struct GlImplData {
glutils::GLProgram bgShader;
glutils::GLProgram spriteShader;
int64_t prevFpsCheckTime = 0;
uint64_t draws = 0;
bool mainViewEnabled = true;
ox::Array<CBB, 4> cbbs;
SpriteBlockset spriteBlocks;
ox::Array<Sprite, 128> spriteStates;
ox::Array<Background, 4> backgrounds;
ox::Optional<ox::Size> renderSize;
};
constexpr ox::StringView bgvshadTmpl = R"(
{}
in vec2 vTexCoord;
in vec2 vPosition;
out vec2 fTexCoord;
uniform float vXScale;
uniform float vTileHeight;
void main() {
float xScaleInvert = 1.0 - vXScale;
gl_Position = vec4(
vPosition.x * vXScale - xScaleInvert, vPosition.y,
0.0, 1.0);
fTexCoord = vTexCoord * vec2(1, vTileHeight);
})";
constexpr ox::StringView bgfshadTmpl = R"(
{}
out vec4 outColor;
in vec2 fTexCoord;
uniform sampler2D image;
uniform vec4 fPalette[256];
void main() {
int idx = int(texture(image, fTexCoord).rgb.r * 256);
outColor = fPalette[idx];
//outColor = vec4(0.0, 0.7, 1.0, 1.0);
})";
constexpr ox::StringView spritevshadTmpl = R"(
{}
in float vEnabled;
in vec2 vTexCoord;
in vec2 vPosition;
out vec2 fTexCoord;
uniform float vXScale;
uniform float vTileHeight;
void main() {
float xScaleInvert = 1.0 - vXScale;
gl_Position = vec4(
vPosition.x * vXScale - xScaleInvert, vPosition.y,
0.0, 1.0);
fTexCoord = vTexCoord * vec2(1, vTileHeight) * vec2(vEnabled, vEnabled);
})";
constexpr ox::StringView spritefshadTmpl = bgfshadTmpl;
[[nodiscard]]
static constexpr auto bgVertexRow(unsigned x, unsigned y) noexcept {
return y * TileRows + x;
}
static void setSpriteBufferObject(Context*,
unsigned vi,
float enabled,
float x, float y,
unsigned textureRow,
unsigned flipX,
float *vbo,
GLuint *ebo) noexcept {
// don't worry, this memcpy gets optimized to something much more ideal
constexpr float xmod = 0.1f;
constexpr float ymod = 0.1f;
x *= xmod;
y *= -ymod;
x -= 1.f;
y += 1.f - ymod;
const auto textureRowf = static_cast<float>(textureRow);
const float L = flipX ? 1 : 0;
const float R = flipX ? 0 : 1;
const ox::Array<float, SpriteVertexVboLength> vertices {
enabled, x, y, L, textureRowf + 1, // bottom left
enabled, x + xmod, y, R, textureRowf + 1, // bottom right
enabled, x + xmod, y + ymod, R, textureRowf + 0, // top right
enabled, x, y + ymod, L, textureRowf + 0, // top left
};
memcpy(vbo, vertices.data(), sizeof(vertices));
const ox::Array<GLuint, SpriteVertexEboLength> elms {
vi + 0, vi + 1, vi + 2,
vi + 2, vi + 3, vi + 0,
};
memcpy(ebo, elms.data(), sizeof(elms));
}
static void setTileBufferObject(Context*,
unsigned vi,
float x,
float y,
unsigned textureRow,
float *vbo,
GLuint *ebo) noexcept {
// don't worry, this memcpy gets optimized to something much more ideal
constexpr float ymod = 0.1f;
constexpr float xmod = 0.1f;
x *= xmod;
y *= -ymod;
x -= 1.0f;
y += 1.0f - ymod;
const auto textureRowf = static_cast<float>(textureRow);
const ox::Array<float, BgVertexVboLength> vertices {
x, y, 0, textureRowf + 1, // bottom left
x + xmod, y, 1, textureRowf + 1, // bottom right
x + xmod, y + ymod, 1, textureRowf + 0, // top right
x, y + ymod, 0, textureRowf + 0, // top left
};
memcpy(vbo, vertices.data(), sizeof(vertices));
const ox::Array<GLuint, BgVertexEboLength> elms {
vi + 0, vi + 1, vi + 2,
vi + 2, vi + 3, vi + 0,
};
memcpy(ebo, elms.data(), sizeof(elms));
}
static void initSpriteBufferObjects(Context *ctx, glutils::BufferSet *bs) noexcept {
for (auto i = 0u; i < SpriteCount; ++i) {
auto vbo = &bs->vertices[i * static_cast<std::size_t>(SpriteVertexVboLength)];
auto ebo = &bs->elements[i * static_cast<std::size_t>(SpriteVertexEboLength)];
setSpriteBufferObject(ctx, i * SpriteVertexVboRows, 0, 0, 0, 0, false, vbo, ebo);
}
}
static void initBackgroundBufferObjects(Context *ctx, glutils::BufferSet *bg) noexcept {
for (auto x = 0u; x < TileColumns; ++x) {
for (auto y = 0u; y < TileRows; ++y) {
const auto i = bgVertexRow(x, y);
auto vbo = &bg->vertices[i * static_cast<std::size_t>(BgVertexVboLength)];
auto ebo = &bg->elements[i * static_cast<std::size_t>(BgVertexEboLength)];
setTileBufferObject(ctx, i * BgVertexVboRows, static_cast<float>(x), static_cast<float>(y), 0, vbo, ebo);
}
}
}
static void initSpritesBufferset(Context *ctx, GLuint shader, glutils::BufferSet *bs) noexcept {
// vao
bs->vao = glutils::generateVertexArrayObject();
glBindVertexArray(bs->vao);
// vbo & ebo
bs->vbo = glutils::generateBuffer();
bs->ebo = glutils::generateBuffer();
initSpriteBufferObjects(ctx, bs);
glutils::sendVbo(*bs);
glutils::sendEbo(*bs);
// vbo layout
auto enabledAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vEnabled"));
glEnableVertexAttribArray(enabledAttr);
glVertexAttribPointer(enabledAttr, 1, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), nullptr);
auto posAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vPosition"));
glEnableVertexAttribArray(posAttr);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
reinterpret_cast<void*>(1 * sizeof(float)));
auto texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
glEnableVertexAttribArray(texCoordAttr);
glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
reinterpret_cast<void*>(3 * sizeof(float)));
}
static void initBackgroundBufferset(Context *ctx, GLuint shader, glutils::BufferSet *bg) noexcept {
// vao
bg->vao = glutils::generateVertexArrayObject();
glBindVertexArray(bg->vao);
// vbo & ebo
bg->vbo = glutils::generateBuffer();
bg->ebo = glutils::generateBuffer();
initBackgroundBufferObjects(ctx, bg);
glutils::sendVbo(*bg);
glutils::sendEbo(*bg);
// vbo layout
auto posAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vPosition"));
glEnableVertexAttribArray(posAttr);
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float), nullptr);
auto texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
glEnableVertexAttribArray(texCoordAttr);
glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float),
reinterpret_cast<void*>(2 * sizeof(float)));
}
static glutils::GLTexture loadTexture(GLsizei w, GLsizei h, const void *pixels) noexcept {
GLuint texId = 0;
glGenTextures(1, &texId);
glutils::GLTexture tex(texId);
tex.width = w;
tex.height = h;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex.id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return tex;
}
static void tickFps(GlImplData *id) noexcept {
++id->draws;
if (id->draws >= 500) {
using namespace std::chrono;
const auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
const auto duration = static_cast<double>(now - id->prevFpsCheckTime) / 1000.0;
const auto fps = static_cast<int>(static_cast<double>(id->draws) / duration);
if constexpr(config::UserlandFpsPrint) {
oxOutf("FPS: {}\n", fps);
}
oxTracef("nostalgia::core::gfx::fps", "FPS: {}", fps);
id->prevFpsCheckTime = now;
id->draws = 0;
}
}
static void drawBackground(CBB *cbb) noexcept {
glBindVertexArray(cbb->vao);
if (cbb->updated) {
cbb->updated = false;
glutils::sendVbo(*cbb);
}
glBindTexture(GL_TEXTURE_2D, cbb->tex);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(cbb->elements.size()), GL_UNSIGNED_INT, nullptr);
}
static void drawBackgrounds(core::Context *ctx, GlImplData *id) noexcept {
// load background shader and its uniforms
glUseProgram(id->bgShader);
const auto uniformXScale = static_cast<GLint>(glGetUniformLocation(id->bgShader, "vXScale"));
const auto uniformTileHeight = static_cast<GLint>(glGetUniformLocation(id->bgShader, "vTileHeight"));
const auto [wi, hi] = gl::getRenderSize(ctx);
const auto wf = static_cast<float>(wi);
const auto hf = static_cast<float>(hi);
glUniform1f(uniformXScale, hf / wf);
for (const auto &bg : id->backgrounds) {
if (bg.enabled) {
auto &cbb = id->cbbs[bg.cbbIdx];
const auto tileRows = cbb.tex.height / TileHeight;
glUniform1f(uniformTileHeight, 1.0f / static_cast<float>(tileRows));
drawBackground(&cbb);
}
}
}
static void drawSprites(core::Context *ctx, GlImplData *id) noexcept {
glUseProgram(id->spriteShader);
auto &sb = id->spriteBlocks;
const auto uniformXScale = static_cast<GLint>(glGetUniformLocation(id->bgShader, "vXScale"));
const auto uniformTileHeight = static_cast<GLint>(glGetUniformLocation(id->spriteShader, "vTileHeight"));
const auto [wi, hi] = gl::getRenderSize(ctx);
const auto wf = static_cast<float>(wi);
const auto hf = static_cast<float>(hi);
glUniform1f(uniformXScale, hf / wf);
// update vbo
glBindVertexArray(sb.vao);
if (sb.updated) {
sb.updated = false;
glutils::sendVbo(sb);
}
// set vTileHeight uniform
const auto tileRows = sb.tex.height / TileHeight;
glUniform1f(uniformTileHeight, 1.0f / static_cast<float>(tileRows));
// draw
glBindTexture(GL_TEXTURE_2D, sb.tex);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(sb.elements.size()), GL_UNSIGNED_INT, nullptr);
}
ox::Error init(Context *ctx) noexcept {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const auto bgVshad = ox::sfmt(bgvshadTmpl, glutils::GlslVersion);
const auto bgFshad = ox::sfmt(bgfshadTmpl, glutils::GlslVersion);
const auto spriteVshad = ox::sfmt(spritevshadTmpl, glutils::GlslVersion);
const auto spriteFshad = ox::sfmt(spritefshadTmpl, glutils::GlslVersion);
const auto id = ox::make<GlImplData>();
ctx->setRendererData(id);
oxReturnError(glutils::buildShaderProgram(bgVshad.c_str(), bgFshad.c_str()).moveTo(&id->bgShader));
oxReturnError(glutils::buildShaderProgram(spriteVshad.c_str(), spriteFshad.c_str()).moveTo(&id->spriteShader));
for (auto &bg : id->cbbs) {
initBackgroundBufferset(ctx, id->bgShader, &bg);
}
initSpritesBufferset(ctx, id->spriteShader, &id->spriteBlocks);
ImGui_ImplOpenGL3_Init(glutils::GlslVersion);
return {};
}
void shutdown(Context*, void *rendererData) noexcept {
const auto id = reinterpret_cast<GlImplData*>(rendererData);
ox::safeDelete(id);
}
static void loadPalette(GLuint shaderPgrm, const Palette &pal, bool firstIsTransparent = false) noexcept {
static constexpr std::size_t ColorCnt = 256;
ox::Array<GLfloat, ColorCnt * 4> palette{};
for (auto i = 0u; const auto c : pal.colors) {
palette[i++] = redf(c);
palette[i++] = greenf(c);
palette[i++] = bluef(c);
palette[i++] = 255;
}
if (firstIsTransparent) {
palette[3] = 0;
}
glUseProgram(shaderPgrm);
const auto uniformPalette = static_cast<GLint>(glGetUniformLocation(shaderPgrm, "fPalette"));
glUniform4fv(uniformPalette, ColorCnt, palette.data());
}
void loadBgPalette(void *rendererData, const Palette &pal) noexcept {
const auto id = static_cast<GlImplData*>(rendererData);
loadPalette(id->bgShader, pal);
}
void loadSpritePalette(void *rendererData, const Palette &pal) noexcept {
const auto id = static_cast<GlImplData*>(rendererData);
loadPalette(id->spriteShader, pal, true);
}
void loadBgTexture(void *rendererData, unsigned cbbIdx, const void *pixels, int w, int h) noexcept {
oxTracef("nostalgia::core::gfx::gl", "loadBgTexture: { cbbIdx: {}, w: {}, h: {} }", cbbIdx, w, h);
const auto id = static_cast<GlImplData*>(rendererData);
id->cbbs[cbbIdx].tex = loadTexture(w, h, pixels);
}
void loadSpriteTexture(void *rendererData, const void *pixels, int w, int h) noexcept {
oxTracef("nostalgia::core::gfx::gl", "loadSpriteTexture: { w: {}, h: {} }", w, h);
const auto id = static_cast<GlImplData*>(rendererData);
id->spriteBlocks.tex = loadTexture(w, h, pixels);
}
}
void setBgCbb(Context *ctx, unsigned bgIdx, unsigned cbbIdx) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
auto &bg = id->backgrounds[bgIdx];
bg.cbbIdx = cbbIdx;
}
uint8_t bgStatus(Context *ctx) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
uint8_t out = 0;
for (unsigned i = 0; i < id->cbbs.size(); ++i) {
out |= id->backgrounds[i].enabled << i;
}
return out;
}
void setBgStatus(Context *ctx, uint32_t status) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
for (unsigned i = 0; i < id->cbbs.size(); ++i) {
id->backgrounds[i].enabled = (status >> i) & 1;
}
}
bool bgStatus(Context *ctx, unsigned bg) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
return id->backgrounds[bg].enabled;
}
void setBgStatus(Context *ctx, unsigned bg, bool status) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
id->backgrounds[bg].enabled = status;
}
void draw(Context *ctx) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
renderer::tickFps(id);
ImGui_ImplOpenGL3_NewFrame();
ImGui_Impl_NewFrame();
if constexpr(config::ImGuiEnabled) {
ImGui::NewFrame();
}
// clear screen
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// render
if (id->mainViewEnabled) {
gl::drawMainView(ctx);
}
for (const auto cd : ctx->drawers) {
cd->draw(ctx);
}
if constexpr(config::ImGuiEnabled) {
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
}
void clearTileLayer(Context *ctx, unsigned bgIdx) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
auto &bg = id->cbbs[static_cast<std::size_t>(bgIdx)];
initBackgroundBufferObjects(ctx, &bg);
bg.updated = true;
}
void hideSprite(Context *ctx, unsigned idx) noexcept {
auto &id = *ctx->rendererData<renderer::GlImplData>();
auto vbo = &id.spriteBlocks.vertices[idx * renderer::SpriteVertexVboLength];
auto ebo = &id.spriteBlocks.elements[idx * renderer::SpriteVertexEboLength];
renderer::setSpriteBufferObject(ctx, idx * renderer::SpriteVertexVboRows, 0,
0, 0, 0, false, vbo, ebo);
id.spriteBlocks.updated = true;
}
void setSprite(Context *ctx,
unsigned idx,
int x,
int y,
unsigned tileIdx,
[[maybe_unused]] unsigned spriteShape,
[[maybe_unused]] unsigned spriteSize,
unsigned flipX) noexcept {
//oxTracef("nostalgia::core::gfx::gl", "setSprite(ctx, {}, {}, {}, {}, {}, {}, {})",
// idx, x, y, tileIdx, spriteShape, spriteSize, flipX);
// Tonc Table 8.4
static constexpr ox::Array<ox::Vec<unsigned>, 12> dimensions{
// col 0
{1, 1}, // 0, 0
{2, 2}, // 0, 1
{4, 4}, // 0, 2
{8, 8}, // 0, 3
// col 1
{2, 1}, // 1, 0
{4, 1}, // 1, 1
{4, 2}, // 1, 2
{8, 4}, // 1, 3
// col 2
{1, 1}, // 2, 0
{1, 4}, // 2, 1
{2, 4}, // 2, 2
{4, 8}, // 2, 3
};
const auto dim = dimensions[(spriteShape << 2) | spriteSize];
const auto uX = static_cast<int>(x) % 255;
const auto uY = static_cast<int>(y + 8) % 255 - 8;
auto &id = *ctx->rendererData<renderer::GlImplData>();
auto i = 0u;
const auto set = [&](int xIt, int yIt) {
const auto fX = static_cast<float>(uX + xIt * 8) / 8;
const auto fY = static_cast<float>(uY + yIt * 8) / 8;
const auto cidx = idx + i;
auto vbo = &id.spriteBlocks.vertices[cidx * renderer::SpriteVertexVboLength];
auto ebo = &id.spriteBlocks.elements[cidx * renderer::SpriteVertexEboLength];
renderer::setSpriteBufferObject(ctx, cidx * renderer::SpriteVertexVboRows, 1,
fX, fY, tileIdx + i, flipX, vbo, ebo);
++i;
};
if (!flipX) {
for (auto yIt = 0; yIt < static_cast<int>(dim.y); ++yIt) {
for (auto xIt = 0u; xIt < dim.x; ++xIt) {
set(static_cast<int>(xIt), static_cast<int>(yIt));
}
}
} else {
for (auto yIt = 0u; yIt < dim.y; ++yIt) {
for (auto xIt = dim.x - 1; xIt < ~0u; --xIt) {
set(static_cast<int>(xIt), static_cast<int>(yIt));
}
}
}
id.spriteBlocks.updated = true;
}
void setTile(Context *ctx, unsigned bgIdx, int column, int row, uint8_t tile) noexcept {
oxTracef(
"nostalgia::core::gfx::setTile",
"bgIdx: {}, column: {}, row: {}, tile: {}",
bgIdx, column, row, tile);
const auto id = ctx->rendererData<renderer::GlImplData>();
const auto z = static_cast<unsigned>(bgIdx);
const auto y = static_cast<unsigned>(row);
const auto x = static_cast<unsigned>(column);
const auto i = renderer::bgVertexRow(x, y);
auto &bg = id->cbbs[z];
auto vbo = &bg.vertices[i * renderer::BgVertexVboLength];
auto ebo = &bg.elements[i * renderer::BgVertexEboLength];
renderer::setTileBufferObject(
ctx, i * renderer::BgVertexVboRows,
static_cast<float>(x), static_cast<float>(y),
tile, vbo, ebo);
bg.updated = true;
}
namespace gl {
void setMainViewEnabled(core::Context *ctx, bool enabled) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
id->mainViewEnabled = enabled;
}
void drawMainView(core::Context *ctx) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
renderer::drawBackgrounds(ctx, id);
if (id->spriteBlocks.tex) {
renderer::drawSprites(ctx, id);
}
}
void setRenderSize(core::Context *ctx, int width, int height) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
id->renderSize.emplace(width, height);
}
void clearRenderSize(core::Context *ctx) noexcept {
auto id = ctx->rendererData<renderer::GlImplData>();
id->renderSize.reset();
}
ox::Size getRenderSize(core::Context *ctx) noexcept {
const auto id = ctx->rendererData<renderer::GlImplData>();
if (id->renderSize.has_value()) {
return id->renderSize.value();
} else {
return core::getScreenSize(ctx);
}
}
}
}

View File

@ -1,88 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <SDL.h>
#include <nostalgia/core/config.hpp>
#include <nostalgia/core/core.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/core/input.hpp>
#include "core.hpp"
namespace nostalgia::core {
static event_handler g_eventHandler = nullptr;
static uint64_t g_wakeupTime;
void draw(Context *ctx);
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, const char *appName) noexcept {
auto ctx = ox::make_unique<Context>();
ctx->rom = std::move(fs);
ctx->appName = appName;
const auto id = new SdlImplData;
ctx->setWindowerData(id);
oxReturnError(initGfx(ctx.get()));
return OxError(0);
}
ox::Error run(Context *ctx) noexcept {
const auto id = ctx->windowerData<SdlImplData>();
// try adaptive vsync
if (SDL_GL_SetSwapInterval(config::SdlVsyncOption) < 0) {
oxTrace("nostalgia::core::sdl", "Could not enable adaptive vsync, falling back on vsync");
SDL_GL_SetSwapInterval(1); // fallback on normal vsync
}
id->running = true;
while (id->running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_q) {
id->running = false;
}
break;
case SDL_QUIT: {
id->running = false;
break;
}
}
}
const auto ticks = ticksMs(ctx);
if (g_wakeupTime <= ticks && g_eventHandler) {
auto sleepTime = g_eventHandler(ctx);
if (sleepTime >= 0) {
g_wakeupTime = ticks + static_cast<unsigned>(sleepTime);
} else {
g_wakeupTime = ~uint64_t(0);
}
}
draw(ctx);
SDL_GL_SwapWindow(id->window);
}
ctx->setWindowerData(nullptr);
delete id;
return OxError(0);
}
void setEventHandler(event_handler h) noexcept {
g_eventHandler = h;
}
uint64_t ticksMs() noexcept {
return SDL_GetTicks();
}
bool buttonDown(Key) noexcept {
return false;
}
void shutdown(Context *ctx) noexcept {
const auto id = ctx->windowerData<SdlImplData>();
id->running = false;
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <array>
#include <SDL.h>
namespace nostalgia::core {
struct SdlImplData {
SDL_Window *window = nullptr;
SDL_GLContext renderer = nullptr;
int64_t startTime = 0;
uint64_t wakeupTime = 0;
bool running = false;
};
}

View File

@ -1,71 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <array>
#include <SDL.h>
#include <ox/claw/read.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/core/userland/gfx.hpp>
#include "core.hpp"
namespace nostalgia::core {
constexpr auto Scale = 5;
ox::Error initGfx(Context *ctx) noexcept {
auto id = new SdlImplData;
ctx->setWindowerData(id);
id->window = SDL_CreateWindow("nostalgia", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
240 * Scale, 160 * Scale,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI);
if (id->window == nullptr) {
return OxError(1, SDL_GetError());
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
id->renderer = SDL_GL_CreateContext(id->window);
if (id->renderer == nullptr) {
return OxError(1, SDL_GetError());
}
oxReturnError(renderer::init(ctx));
return OxError(0);
}
ox::Error shutdownGfx(Context *ctx) noexcept {
oxReturnError(renderer::shutdown(ctx));
auto id = ctx->windowerData<SdlImplData>();
SDL_GL_DeleteContext(id->renderer);
SDL_DestroyWindow(id->window);
ctx->setWindowerData(nullptr);
delete id;
return OxError(0);
}
int getScreenWidth(Context *ctx) noexcept {
auto id = ctx->windowerData<SdlImplData>();
int x = 0, y = 0;
SDL_GetWindowSize(id->window, &x, &y);
return x;
}
int getScreenHeight(Context *ctx) noexcept {
auto id = ctx->windowerData<SdlImplData>();
int x = 0, y = 0;
SDL_GetWindowSize(id->window, &x, &y);
return y;
}
common::Size getScreenSize(Context *ctx) noexcept {
auto id = ctx->windowerData<SdlImplData>();
int x = 0, y = 0;
SDL_GetWindowSize(id->window, &x, &y);
return {x, y};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/std/memory.hpp>
@ -11,7 +11,7 @@
namespace nostalgia::core {
ox::Vector<studio::EditorMaker> StudioModule::editors(core::Context *ctx) noexcept {
ox::Vector<studio::EditorMaker> StudioModule::editors(turbine::Context *ctx) noexcept {
return {
{
{FileExt_ng},
@ -32,7 +32,7 @@ ox::Vector<studio::EditorMaker> StudioModule::editors(core::Context *ctx) noexce
};
}
ox::Vector<ox::UniquePtr<studio::ItemMaker>> StudioModule::itemMakers(core::Context*) noexcept {
ox::Vector<ox::UniquePtr<studio::ItemMaker>> StudioModule::itemMakers(turbine::Context*) noexcept {
ox::Vector<ox::UniquePtr<studio::ItemMaker>> out;
out.emplace_back(ox::make<studio::ItemMakerT<core::TileSheet>>("Tile Sheet", "TileSheets", "ng"));
out.emplace_back(ox::make<studio::ItemMakerT<core::Palette>>("Palette", "Palettes", "npal"));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -10,8 +10,8 @@ namespace nostalgia::core {
class StudioModule: public studio::Module {
public:
ox::Vector<studio::EditorMaker> editors(core::Context *ctx) noexcept override;
ox::Vector<ox::UniquePtr<studio::ItemMaker>> itemMakers(core::Context*) noexcept override;
ox::Vector<studio::EditorMaker> editors(turbine::Context *ctx) noexcept override;
ox::Vector<ox::UniquePtr<studio::ItemMaker>> itemMakers(turbine::Context*) noexcept override;
};
}

View File

@ -13,8 +13,13 @@
namespace nostalgia::core {
ox::Result<PaletteEditorImGui*> PaletteEditorImGui::make(Context *ctx, ox::CRStringView path) noexcept {
auto out = ox::UniquePtr<PaletteEditorImGui>(new PaletteEditorImGui);
ox::Result<PaletteEditorImGui*> PaletteEditorImGui::make(turbine::Context *ctx, ox::CRStringView path) noexcept {
ox::UniquePtr<PaletteEditorImGui> out;
try {
out = ox::UniquePtr<PaletteEditorImGui>(new PaletteEditorImGui);
} catch (...) {
return OxError(1);
}
out->m_ctx = ctx;
out->m_itemPath = path;
const auto lastSlash = std::find(out->m_itemPath.rbegin(), out->m_itemPath.rend(), '/').offset();
@ -32,7 +37,7 @@ const ox::String &PaletteEditorImGui::itemDisplayName() const noexcept {
return m_itemName;
}
void PaletteEditorImGui::draw(core::Context*) noexcept {
void PaletteEditorImGui::draw(turbine::Context*) noexcept {
static constexpr auto flags = ImGuiTableFlags_RowBg;
const auto paneSize = ImGui::GetContentRegionAvail();
ImGui::BeginChild("PaletteEditor");
@ -137,7 +142,7 @@ void PaletteEditorImGui::draw(core::Context*) noexcept {
}
ox::Error PaletteEditorImGui::saveItem() noexcept {
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
const auto sctx = applicationData<studio::StudioContext>(*m_ctx);
oxReturnError(sctx->project->writeObj(m_itemPath, &m_pal));
oxReturnError(m_ctx->assetManager.setAsset(m_itemPath, m_pal));
return OxError(0);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -12,7 +12,7 @@ namespace nostalgia::core {
class PaletteEditorImGui: public studio::Editor {
private:
Context *m_ctx = nullptr;
turbine::Context *m_ctx = nullptr;
ox::String m_itemName;
ox::String m_itemPath;
Palette m_pal;
@ -21,7 +21,7 @@ class PaletteEditorImGui: public studio::Editor {
PaletteEditorImGui() noexcept = default;
public:
static ox::Result<PaletteEditorImGui*> make(Context *ctx, ox::CRStringView path) noexcept;
static ox::Result<PaletteEditorImGui*> make(turbine::Context *ctx, ox::CRStringView path) noexcept;
/**
* Returns the name of item being edited.
@ -30,7 +30,7 @@ class PaletteEditorImGui: public studio::Editor {
const ox::String &itemDisplayName() const noexcept final;
void draw(core::Context*) noexcept final;
void draw(turbine::Context*) noexcept final;
protected:
ox::Error saveItem() noexcept final;

View File

@ -38,14 +38,14 @@ ox::Error toPngFile(const ox::String &path, const TileSheet::SubSheet &s, const
return OxError(lodepng_encode_file(path.c_str(), outData.data(), width, height, fmt, 8));
}
TileSheetEditorImGui::TileSheetEditorImGui(Context *ctx, ox::CRStringView path): m_tileSheetEditor(ctx, path) {
TileSheetEditorImGui::TileSheetEditorImGui(turbine::Context *ctx, ox::CRStringView path): m_tileSheetEditor(ctx, path) {
m_ctx = ctx;
m_itemPath = path;
const auto lastSlash = ox::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset();
m_itemName = m_itemPath.substr(lastSlash + 1);
// init palette idx
const auto &palPath = model()->palPath();
auto sctx = applicationData<studio::StudioContext>(m_ctx);
auto sctx = applicationData<studio::StudioContext>(*m_ctx);
const auto &palList = sctx->project->fileList(core::FileExt_npal);
for (std::size_t i = 0; const auto &pal : palList) {
if (palPath == pal) {
@ -83,35 +83,35 @@ void TileSheetEditorImGui::paste() {
model()->paste();
}
void TileSheetEditorImGui::keyStateChanged(core::Key key, bool down) {
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
if (!down) {
return;
}
if (key == core::Key::Escape) {
if (key == turbine::Key::Escape) {
m_subsheetEditor.close();
}
auto pal = model()->pal();
if (pal) {
const auto colorCnt = pal->colors.size();
if (key == core::Key::Alpha_D) {
if (key == turbine::Key::Alpha_D) {
m_tool = Tool::Draw;
model()->clearSelection();
} else if (key == core::Key::Alpha_S) {
} else if (key == turbine::Key::Alpha_S) {
m_tool = Tool::Select;
} else if (key == core::Key::Alpha_F) {
} else if (key == turbine::Key::Alpha_F) {
m_tool = Tool::Fill;
model()->clearSelection();
} else if (key >= core::Key::Num_1 && key <= core::Key::Num_9 && key <= core::Key::Num_0 + colorCnt) {
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - core::Key::Num_1), colorCnt - 1);
} else if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9 && key <= turbine::Key::Num_0 + colorCnt) {
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
m_tileSheetEditor.setPalIdx(idx);
} else if (key == core::Key::Num_0 && colorCnt >= 10) {
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - core::Key::Num_1 + 9), colorCnt - 1);
} else if (key == turbine::Key::Num_0 && colorCnt >= 10) {
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), colorCnt - 1);
m_tileSheetEditor.setPalIdx(idx);
}
}
}
void TileSheetEditorImGui::draw(core::Context*) noexcept {
void TileSheetEditorImGui::draw(turbine::Context*) noexcept {
const auto paneSize = ImGui::GetContentRegionAvail();
const auto tileSheetParentSize = ImVec2(paneSize.x - m_palViewWidth, paneSize.y);
const auto fbSize = ox::Vec2(tileSheetParentSize.x - 16, tileSheetParentSize.y - 16);
@ -302,7 +302,7 @@ void TileSheetEditorImGui::drawTileSheet(const ox::Vec2 &fbSize) noexcept {
const auto wheelh = io.MouseWheelH;
if (wheel != 0) {
const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl);
io.KeySuper : turbine::buttonDown(*m_ctx, turbine::Key::Mod_Ctrl);
m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod);
}
if (wheelh != 0) {
@ -342,7 +342,7 @@ void TileSheetEditorImGui::drawTileSheet(const ox::Vec2 &fbSize) noexcept {
}
void TileSheetEditorImGui::drawPaletteSelector() noexcept {
auto sctx = applicationData<studio::StudioContext>(m_ctx);
auto sctx = applicationData<studio::StudioContext>(*m_ctx);
const auto &files = sctx->project->fileList(core::FileExt_npal);
const auto first = m_selectedPaletteIdx < files.size() ?
files[m_selectedPaletteIdx].c_str() : "";

View File

@ -43,7 +43,7 @@ class TileSheetEditorImGui: public studio::BaseEditor {
void close() noexcept;
};
std::size_t m_selectedPaletteIdx = 0;
Context *m_ctx = nullptr;
turbine::Context *m_ctx = nullptr;
ox::Vector<ox::String> m_paletteList;
SubSheetEditor m_subsheetEditor;
ox::String m_itemPath;
@ -55,7 +55,7 @@ class TileSheetEditorImGui: public studio::BaseEditor {
Tool m_tool = Tool::Draw;
public:
TileSheetEditorImGui(Context *ctx, ox::CRStringView path);
TileSheetEditorImGui(turbine::Context *ctx, ox::CRStringView path);
~TileSheetEditorImGui() override = default;
@ -71,9 +71,9 @@ class TileSheetEditorImGui: public studio::BaseEditor {
void paste() override;
void keyStateChanged(core::Key key, bool down) override;
void keyStateChanged(turbine::Key key, bool down) override;
void draw(core::Context*) noexcept override;
void draw(turbine::Context*) noexcept override;
void drawSubsheetSelector(TileSheet::SubSheet*, TileSheet::SubSheetIdx *path);

View File

@ -8,7 +8,7 @@
#include <ox/std/buffer.hpp>
#include <ox/std/memory.hpp>
#include <nostalgia/core/clipboard.hpp>
#include <turbine/clipboard.hpp>
#include <keel/media.hpp>
#include "tilesheeteditormodel.hpp"
@ -19,7 +19,7 @@ const Palette TileSheetEditorModel::s_defaultPalette = {
.colors = ox::Vector<Color16>(128),
};
class TileSheetClipboard: public ClipboardObject<TileSheetClipboard> {
class TileSheetClipboard: public turbine::ClipboardObject<TileSheetClipboard> {
public:
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
static constexpr auto TypeVersion = 1;
@ -535,7 +535,7 @@ class PaletteChangeCommand: public TileSheetCommand {
};
TileSheetEditorModel::TileSheetEditorModel(Context *ctx, ox::String path) {
TileSheetEditorModel::TileSheetEditorModel(turbine::Context *ctx, ox::String path) {
m_ctx = ctx;
m_path = std::move(path);
oxRequireT(img, readObj<TileSheet>(ctx, m_path));
@ -564,7 +564,7 @@ void TileSheetEditorModel::cut() {
}
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
setClipboardObject(m_ctx, std::move(cb));
turbine::setClipboardObject(*m_ctx, std::move(cb));
pushCommand(ox::make<CutPasteCommand<CommandId::Cut>>(&m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
}
@ -581,11 +581,11 @@ void TileSheetEditorModel::copy() {
cb->addPixel(pt, c);
}
}
setClipboardObject(m_ctx, std::move(cb));
turbine::setClipboardObject(*m_ctx, std::move(cb));
}
void TileSheetEditorModel::paste() {
auto [cb, err] = getClipboardObject<TileSheetClipboard>(m_ctx);
auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(*m_ctx);
if (err) {
oxLogError(err);
oxErrf("Could not read clipboard: {}", toStr(err));
@ -744,7 +744,7 @@ void TileSheetEditorModel::ackUpdate() noexcept {
}
ox::Error TileSheetEditorModel::saveFile() noexcept {
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
const auto sctx = applicationData<studio::StudioContext>(*m_ctx);
oxReturnError(sctx->project->writeObj(m_path, &m_img));
return m_ctx->assetManager.setAsset(m_path, m_img).error;
}

View File

@ -27,14 +27,14 @@ class TileSheetEditorModel: public ox::SignalHandler {
studio::UndoStack m_undoStack;
class DrawCommand *m_ongoingDrawCommand = nullptr;
bool m_updated = false;
Context *m_ctx = nullptr;
turbine::Context *m_ctx = nullptr;
ox::String m_path;
bool m_selectionOngoing = false;
ox::Point m_selectionOrigin = {-1, -1};
ox::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}};
public:
TileSheetEditorModel(Context *ctx, ox::String path);
TileSheetEditorModel(turbine::Context *ctx, ox::String path);
~TileSheetEditorModel() override = default;

View File

@ -11,7 +11,7 @@
namespace nostalgia::core {
TileSheetEditorView::TileSheetEditorView(Context *ctx, ox::CRStringView path): m_model(ctx, path), m_pixelsDrawer(&m_model) {
TileSheetEditorView::TileSheetEditorView(turbine::Context *ctx, ox::CRStringView path): m_model(ctx, path), m_pixelsDrawer(&m_model) {
// build shaders
oxThrowError(m_pixelsDrawer.buildShader());
oxThrowError(m_pixelGridDrawer.buildShader());

View File

@ -49,7 +49,7 @@ class TileSheetEditorView: public ox::SignalHandler {
std::size_t m_palIdx = 0;
public:
TileSheetEditorView(Context *ctx, ox::CRStringView path);
TileSheetEditorView(turbine::Context *ctx, ox::CRStringView path);
~TileSheetEditorView() override = default;

View File

@ -46,6 +46,24 @@ template struct GLObject<deleteVertexArray>;
template struct GLObject<deleteProgram>;
template struct GLObject<deleteShader>;
const FrameBuffer *FrameBufferBind::s_activeFb = nullptr;
FrameBufferBind::FrameBufferBind(const FrameBuffer &fb) noexcept: m_restoreFb(s_activeFb) {
s_activeFb = &fb;
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glViewport(0, 0, fb.width, fb.height);
}
FrameBufferBind::~FrameBufferBind() noexcept {
s_activeFb = m_restoreFb;
if (s_activeFb) {
glBindFramebuffer(GL_FRAMEBUFFER, *s_activeFb);
glViewport(0, 0, s_activeFb->width, s_activeFb->height);
} else {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, s_activeFb->width, s_activeFb->height);
}
}
void bind(const FrameBuffer &fb) noexcept {
glBindFramebuffer(GL_FRAMEBUFFER, fb);

View File

@ -4,6 +4,7 @@
#pragma once
#include <ox/std/bounds.hpp>
#include <ox/std/defines.hpp>
#include <glad/glad.h>
@ -134,6 +135,15 @@ struct FrameBuffer {
}
};
class FrameBufferBind {
private:
static const FrameBuffer *s_activeFb;
const FrameBuffer *m_restoreFb = nullptr;
public:
explicit FrameBufferBind(const FrameBuffer &fb) noexcept;
~FrameBufferBind() noexcept;
};
void bind(const FrameBuffer &fb) noexcept;

View File

@ -3,6 +3,7 @@
*/
#include <keel/media.hpp>
#include <turbine/turbine.hpp>
#include <nostalgia/core/core.hpp>
#include <nostalgia/scene/scene.hpp>
@ -12,7 +13,7 @@ using namespace nostalgia;
static bool s_paused = false;
static ox::Optional<scene::Scene> s_scene;
static int updateHandler(core::Context*) noexcept {
static int updateHandler(turbine::Context&) noexcept {
constexpr auto sleepTime = 16;
if (s_paused) {
return sleepTime;
@ -21,11 +22,11 @@ static int updateHandler(core::Context*) noexcept {
return sleepTime;
}
static void keyEventHandler(core::Context *ctx, core::Key key, bool down) noexcept {
static void keyEventHandler(turbine::Context &tctx, turbine::Key key, bool down) noexcept {
if (down) {
if (key == core::Key::Alpha_Q) {
core::shutdown(ctx);
} else if (key == core::Key::Alpha_P) {
if (key == turbine::Key::Alpha_Q) {
turbine::requestShutdown(tctx);
} else if (key == turbine::Key::Alpha_P) {
s_paused = !s_paused;
}
}
@ -33,12 +34,13 @@ static void keyEventHandler(core::Context *ctx, core::Key key, bool down) noexce
ox::Error run(ox::UniquePtr<ox::FileSystem> fs) noexcept {
oxTraceInitHook();
oxRequireM(ctx, core::init(std::move(fs)));
oxRequireM(tctx, turbine::init(std::move(fs)));
oxRequireM(cctx, core::init(tctx.get()));
constexpr ox::FileAddress SceneAddr("/Scenes/Chester.nscn");
oxRequire(scn, keel::readObj<scene::SceneStatic>(ctx.get(), SceneAddr));
core::setUpdateHandler(ctx.get(), updateHandler);
core::setKeyEventHandler(ctx.get(), keyEventHandler);
oxRequire(scn, keel::readObj<scene::SceneStatic>(tctx.get(), SceneAddr));
turbine::setUpdateHandler(*tctx, updateHandler);
turbine::setKeyEventHandler(*tctx, keyEventHandler);
s_scene.emplace(*scn);
oxReturnError(s_scene->setupDisplay(ctx.get()));
return core::run(ctx.get());
oxReturnError(s_scene->setupDisplay(cctx.get()));
return turbine::run(*tctx);
}

View File

@ -2,14 +2,12 @@
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/std/memory.hpp>
#include "sceneeditor-imgui.hpp"
#include "module.hpp"
namespace nostalgia::scene {
ox::Vector<studio::EditorMaker> StudioModule::editors(core::Context *ctx) noexcept {
ox::Vector<studio::EditorMaker> StudioModule::editors(turbine::Context *ctx) noexcept {
return {
{
{"nscn"},
@ -20,7 +18,7 @@ ox::Vector<studio::EditorMaker> StudioModule::editors(core::Context *ctx) noexce
};
}
ox::Vector<ox::UPtr<studio::ItemMaker>> StudioModule::itemMakers(core::Context*) noexcept {
ox::Vector<ox::UPtr<studio::ItemMaker>> StudioModule::itemMakers(turbine::Context*) noexcept {
ox::Vector<ox::UPtr<studio::ItemMaker>> out;
return out;
}

View File

@ -4,14 +4,16 @@
#pragma once
#include <turbine/turbine.hpp>
#include <nostalgia/studio/studio.hpp>
namespace nostalgia::scene {
class StudioModule: public studio::Module {
public:
ox::Vector<studio::EditorMaker> editors(core::Context *ctx) noexcept override;
ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(core::Context*) noexcept override;
ox::Vector<studio::EditorMaker> editors(turbine::Context *ctx) noexcept override;
ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(turbine::Context*) noexcept override;
};
}

View File

@ -12,7 +12,7 @@
namespace nostalgia::scene {
SceneEditorImGui::SceneEditorImGui(core::Context *ctx, ox::CRStringView path):
SceneEditorImGui::SceneEditorImGui(turbine::Context *ctx, ox::CRStringView path):
m_editor(ctx, path),
m_view(ctx, m_editor.scene()) {
m_ctx = ctx;
@ -30,7 +30,7 @@ ox::CRString SceneEditorImGui::itemDisplayName() const noexcept {
return m_itemName;
}
void SceneEditorImGui::draw(core::Context*) noexcept {
void SceneEditorImGui::draw(turbine::Context*) noexcept {
const auto paneSize = ImGui::GetContentRegionAvail();
const ox::Size fbSize{
static_cast<int>(paneSize.x),
@ -50,7 +50,7 @@ void SceneEditorImGui::onActivated() noexcept {
}
ox::Error SceneEditorImGui::saveItem() noexcept {
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
const auto sctx = applicationData<studio::StudioContext>(*m_ctx);
oxReturnError(sctx->project->writeObj(m_itemPath, &m_editor.scene()));
oxReturnError(m_ctx->assetManager.setAsset(m_itemPath, m_editor.scene()));
return {};

View File

@ -15,14 +15,14 @@ namespace nostalgia::scene {
class SceneEditorImGui: public studio::Editor {
private:
core::Context *m_ctx = nullptr;
turbine::Context *m_ctx = nullptr;
ox::String m_itemName;
ox::String m_itemPath;
SceneEditor m_editor;
SceneEditorView m_view;
public:
SceneEditorImGui(core::Context *ctx, ox::CRStringView path);
SceneEditorImGui(turbine::Context *ctx, ox::CRStringView path);
/**
* Returns the name of item being edited.
@ -31,7 +31,7 @@ class SceneEditorImGui: public studio::Editor {
ox::CRString itemDisplayName() const noexcept final;
void draw(core::Context*) noexcept final;
void draw(turbine::Context*) noexcept final;
void onActivated() noexcept override;

View File

@ -6,7 +6,7 @@
namespace nostalgia::scene {
SceneEditor::SceneEditor(core::Context *ctx, ox::CRStringView path) {
SceneEditor::SceneEditor(turbine::Context *ctx, ox::CRStringView path) {
m_ctx = ctx;
oxRequireT(scn, keel::readObj<SceneStatic>(m_ctx, path));
m_scene = *scn;

View File

@ -13,13 +13,13 @@ namespace nostalgia::scene {
class SceneEditor {
private:
core::Context *m_ctx = nullptr;
turbine::Context *m_ctx = nullptr;
ox::String m_itemName;
ox::String m_itemPath;
SceneStatic m_scene;
public:
SceneEditor(core::Context *ctx, ox::CRStringView path);
SceneEditor(turbine::Context *ctx, ox::CRStringView path);
const SceneStatic &scene() noexcept {
return m_scene;

View File

@ -2,20 +2,20 @@
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/core/core.hpp>
#include "sceneeditorview.hpp"
namespace nostalgia::scene {
SceneEditorView::SceneEditorView(core::Context *ctx, const SceneStatic &sceneStatic) noexcept:
m_ctx(ctx),
SceneEditorView::SceneEditorView(turbine::Context *tctx, const SceneStatic &sceneStatic):
m_sceneStatic(sceneStatic),
m_scene(m_sceneStatic) {
oxThrowError(core::init(tctx, {.glInstallDrawer = false}).moveTo(&m_cctx));
}
ox::Error SceneEditorView::setupScene() noexcept {
return m_scene.setupDisplay(m_ctx);
return m_scene.setupDisplay(m_cctx.get());
}
void SceneEditorView::draw(int width, int height) noexcept {
@ -23,8 +23,8 @@ void SceneEditorView::draw(int width, int height) noexcept {
glutils::resizeInitFrameBuffer(&m_frameBuffer, width, height);
}
glutils::bind(m_frameBuffer);
core::gl::setRenderSize(m_ctx, width, height);
core::gl::drawMainView(m_ctx);
core::gl::setRenderSize(m_cctx.get(), width, height);
core::gl::drawMainView(m_cctx.get());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

View File

@ -13,13 +13,13 @@ namespace nostalgia::scene {
class SceneEditorView {
private:
core::Context *const m_ctx = nullptr;
ox::UPtr<core::Context> m_cctx;
const SceneStatic &m_sceneStatic;
Scene m_scene;
glutils::FrameBuffer m_frameBuffer;
public:
SceneEditorView(core::Context *ctx, const SceneStatic &sceneStatic) noexcept;
SceneEditorView(turbine::Context *ctx, const SceneStatic &sceneStatic);
ox::Error setupScene() noexcept;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <imgui.h>
@ -21,7 +21,7 @@ bool AboutPopup::isOpen() const noexcept {
return m_stage == Stage::Open;
}
void AboutPopup::draw(core::Context *ctx) noexcept {
void AboutPopup::draw(turbine::Context *ctx) noexcept {
switch (m_stage) {
case Stage::Closed:
break;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -7,13 +7,13 @@
#include <ox/event/signal.hpp>
#include <ox/std/string.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/context.hpp>
#include "lib/popup.hpp"
namespace nostalgia {
class AboutPopup: public studio::Popup {
class AboutPopup: public studio::Popup {
public:
enum class Stage {
Closed,
@ -32,7 +32,7 @@ namespace nostalgia {
[[nodiscard]]
bool isOpen() const noexcept override;
void draw(core::Context *ctx) noexcept override;
void draw(turbine::Context *ctx) noexcept override;
};

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <imgui.h>
@ -22,7 +22,7 @@ ox::CRString ClawEditor::itemDisplayName() const noexcept {
return m_itemDisplayName;
}
void ClawEditor::draw(core::Context*) noexcept {
void ClawEditor::draw(turbine::Context*) noexcept {
//const auto paneSize = ImGui::GetContentRegionAvail();
ImGui::BeginChild("PaletteEditor");
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;

View File

@ -1,11 +1,11 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/model/modelvalue.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/context.hpp>
#include "lib/editor.hpp"
@ -27,7 +27,7 @@ class ClawEditor: public studio::Editor {
ox::CRString itemDisplayName() const noexcept final;
void draw(core::Context*) noexcept final;
void draw(turbine::Context*) noexcept final;
private:
static void drawRow(const ox::ModelValue &value) noexcept;

View File

@ -1,14 +1,14 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <nostalgia/core/gfx.hpp>
#include <turbine/gfx.hpp>
#include "filedialogmanager.hpp"
namespace nostalgia {
studio::TaskState FileDialogManager::update(core::Context *ctx) noexcept {
studio::TaskState FileDialogManager::update(turbine::Context *ctx) noexcept {
switch (m_state) {
case UpdateProjectPathState::EnableSystemCursor: {
// switch to system cursor in this update and open file dialog in the next
@ -20,7 +20,7 @@ studio::TaskState FileDialogManager::update(core::Context *ctx) noexcept {
// switch to system cursor
auto [path, err] = studio::chooseDirectory();
// Mac file dialog doesn't restore focus to main window when closed...
core::focusWindow(ctx);
turbine::focusWindow(*ctx);
if (!err) {
err = pathChosen.emitCheckError(path);
oxAssert(err, "Path chosen response failed");

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -7,7 +7,7 @@
#include <ox/event/signal.hpp>
#include <ox/std/string.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/context.hpp>
#include "lib/filedialog.hpp"
#include "lib/task.hpp"
@ -33,7 +33,7 @@ class FileDialogManager : public nostalgia::studio::Task {
~FileDialogManager() noexcept override = default;
nostalgia::studio::TaskState update(nostalgia::core::Context *ctx) noexcept final;
nostalgia::studio::TaskState update(turbine::Context *ctx) noexcept final;
// signals
ox::Signal<ox::Error(const ox::String &)> pathChosen;

View File

@ -13,7 +13,7 @@
#include <ox/std/trace.hpp>
#include <ox/std/string.hpp>
#include <nostalgia/core/context.hpp>
#include <keel/context.hpp>
namespace nostalgia::studio {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <algorithm>
@ -26,7 +26,7 @@ void BaseEditor::paste() {
void BaseEditor::exportFile() {
}
void BaseEditor::keyStateChanged(core::Key, bool) {
void BaseEditor::keyStateChanged(turbine::Key, bool) {
}
void BaseEditor::onActivated() noexcept {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -49,7 +49,7 @@ class NOSTALGIASTUDIO_EXPORT BaseEditor: public Widget {
virtual void exportFile();
virtual void keyStateChanged(core::Key key, bool down);
virtual void keyStateChanged(turbine::Key key, bool down);
virtual void onActivated() noexcept;

View File

@ -1,13 +1,13 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <imgui.h>
#include <nostalgia/core/core.hpp>
#include <turbine/context.hpp>
namespace nostalgia::studio::ig {
void centerNextWindow(core::Context *ctx) noexcept;
void centerNextWindow(turbine::Context *ctx) noexcept;
}

View File

@ -4,12 +4,12 @@
#include <imgui.h>
#include <nostalgia/core/core.hpp>
#include <turbine/gfx.hpp>
namespace nostalgia::studio::ig {
void centerNextWindow(core::Context *ctx) noexcept {
const auto sz = core::getScreenSize(ctx);
void centerNextWindow(turbine::Context *ctx) noexcept {
const auto sz = turbine::getScreenSize(*ctx);
const auto screenW = static_cast<float>(sz.width);
const auto screenH = static_cast<float>(sz.height);
const auto mod = ImGui::GetWindowDpiScale() * 2;

View File

@ -7,7 +7,7 @@
#include <ox/claw/claw.hpp>
#include <keel/media.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/context.hpp>
#include "context.hpp"
@ -24,7 +24,7 @@ class ItemMaker {
fileExt(std::move(pFileExt)) {
}
virtual ~ItemMaker() noexcept = default;
virtual ox::Error write(core::Context *ctx, ox::CRStringView pName) const noexcept = 0;
virtual ox::Error write(turbine::Context *ctx, ox::CRStringView pName) const noexcept = 0;
};
template<typename T>
@ -47,9 +47,9 @@ class ItemMakerT: public ItemMaker {
item(std::forward(pItem)),
fmt(pFmt) {
}
ox::Error write(core::Context *ctx, ox::CRStringView pName) const noexcept override {
ox::Error write(turbine::Context *ctx, ox::CRStringView pName) const noexcept override {
const auto path = ox::sfmt("/{}/{}.{}", parentDir, pName, fileExt);
auto sctx = core::applicationData<studio::StudioContext>(ctx);
auto sctx = turbine::applicationData<studio::StudioContext>(*ctx);
keel::createUuidMapping(ctx, path, ox::UUID::generate().unwrap());
return sctx->project->writeObj(path, &item, fmt);
}

View File

@ -6,11 +6,11 @@
namespace nostalgia::studio {
ox::Vector<EditorMaker> Module::editors(core::Context*) {
ox::Vector<EditorMaker> Module::editors(turbine::Context*) {
return {};
}
ox::Vector<ox::UniquePtr<ItemMaker>> Module::itemMakers(core::Context*) {
ox::Vector<ox::UniquePtr<ItemMaker>> Module::itemMakers(turbine::Context*) {
return {};
}

View File

@ -9,8 +9,9 @@
#include <ox/std/string.hpp>
#include <ox/std/vector.hpp>
#include <turbine/context.hpp>
#include <nostalgia/studio/studio.hpp>
#include <nostalgia/core/context.hpp>
namespace nostalgia::studio {
@ -26,9 +27,9 @@ class Module {
public:
virtual ~Module() noexcept = default;
virtual ox::Vector<EditorMaker> editors(core::Context *ctx);
virtual ox::Vector<EditorMaker> editors(turbine::Context *ctx);
virtual ox::Vector<ox::UniquePtr<ItemMaker>> itemMakers(core::Context*);
virtual ox::Vector<ox::UniquePtr<ItemMaker>> itemMakers(turbine::Context*);
};

View File

@ -8,7 +8,7 @@
#include <ox/std/string.hpp>
#include <ox/std/vec.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/context.hpp>
#include "imguiuitl.hpp"
@ -32,7 +32,7 @@ class Popup {
[[nodiscard]]
virtual bool isOpen() const noexcept = 0;
virtual void draw(core::Context *ctx) noexcept = 0;
virtual void draw(turbine::Context *ctx) noexcept = 0;
protected:
constexpr void setSize(ox::Size sz) noexcept {
@ -47,7 +47,7 @@ class Popup {
return m_title;
}
void drawWindow(core::Context *ctx, bool *open, auto drawContents) {
void drawWindow(turbine::Context *ctx, bool *open, auto drawContents) {
studio::ig::centerNextWindow(ctx);
ImGui::SetNextWindowSize(static_cast<ImVec2>(m_size));
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;

View File

@ -8,8 +8,8 @@
namespace nostalgia::studio {
void TaskRunner::update(core::Context *ctx) noexcept {
oxIgnoreError(m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(), [&](auto &t) {
void TaskRunner::update(turbine::Context *ctx) noexcept {
oxIgnoreError(m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(), [&](ox::UPtr<studio::Task> &t) {
const auto done = t->update(ctx) == TaskState::Done;
if (done) {
t->finished.emit();

View File

@ -6,7 +6,7 @@
#include <ox/event/signal.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/context.hpp>
namespace nostalgia::studio {
@ -19,14 +19,14 @@ class Task: public ox::SignalHandler {
public:
ox::Signal<ox::Error()> finished;
~Task() noexcept override = default;
virtual TaskState update(nostalgia::core::Context *ctx) noexcept = 0;
virtual TaskState update(turbine::Context *ctx) noexcept = 0;
};
class TaskRunner {
private:
ox::Vector<ox::UniquePtr<studio::Task>> m_tasks;
public:
void update(core::Context *ctx) noexcept;
void update(turbine::Context *ctx) noexcept;
void add(Task *task) noexcept;
};

View File

@ -6,14 +6,14 @@
#include <ox/event/signal.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/context.hpp>
namespace nostalgia::studio {
class Widget: public ox::SignalHandler {
public:
~Widget() noexcept override = default;
virtual void draw(core::Context*) noexcept = 0;
virtual void draw(turbine::Context*) noexcept = 0;
};
}

View File

@ -7,56 +7,55 @@
#include <ox/logconn/logconn.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/uuid.hpp>
#include <keel/media.hpp>
#include <turbine/turbine.hpp>
#include <nostalgia/appmodules/appmodules.hpp>
#include <nostalgia/core/core.hpp>
#include <keel/media.hpp>
#include "lib/context.hpp"
#include "studioapp.hpp"
namespace nostalgia {
class StudioUIDrawer: public core::Drawer {
class StudioUIDrawer: public turbine::gl::Drawer {
private:
StudioUI *m_ui = nullptr;
public:
explicit StudioUIDrawer(StudioUI *ui) noexcept: m_ui(ui) {
}
protected:
void draw(core::Context*) noexcept final {
void draw(turbine::Context&) noexcept final {
m_ui->draw();
}
};
static int updateHandler(core::Context *ctx) noexcept {
auto sctx = core::applicationData<studio::StudioContext>(ctx);
static int updateHandler(turbine::Context &ctx) noexcept {
auto sctx = turbine::applicationData<studio::StudioContext>(ctx);
auto ui = dynamic_cast<StudioUI*>(sctx->ui);
ui->update();
return 16;
}
static void keyEventHandler(core::Context *ctx, core::Key key, bool down) noexcept {
auto sctx = core::applicationData<studio::StudioContext>(ctx);
static void keyEventHandler(turbine::Context &ctx, turbine::Key key, bool down) noexcept {
auto sctx = turbine::applicationData<studio::StudioContext>(ctx);
auto ui = dynamic_cast<StudioUI*>(sctx->ui);
ui->handleKeyEvent(key, down);
}
static ox::Error run(ox::UniquePtr<ox::FileSystem> fs) noexcept {
oxRequireM(ctx, core::init(std::move(fs), "NostalgiaStudio"));
core::setWindowTitle(ctx.get(), "Nostalgia Studio");
core::setUpdateHandler(ctx.get(), updateHandler);
core::setKeyEventHandler(ctx.get(), keyEventHandler);
core::setConstantRefresh(ctx.get(), false);
core::gl::setMainViewEnabled(ctx.get(), false);
oxRequireM(ctx, turbine::init(std::move(fs), "NostalgiaStudio"));
turbine::setWindowTitle(*ctx, "Nostalgia Studio");
turbine::setUpdateHandler(*ctx, updateHandler);
turbine::setKeyEventHandler(*ctx, keyEventHandler);
turbine::setConstantRefresh(*ctx, false);
studio::StudioContext studioCtx;
core::setApplicationData(ctx.get(), &studioCtx);
turbine::setApplicationData(*ctx, &studioCtx);
StudioUI ui(ctx.get());
studioCtx.ui = &ui;
StudioUIDrawer drawer(&ui);
core::addCustomDrawer(ctx.get(), &drawer);
auto err = core::run(ctx.get());
core::removeCustomDrawer(ctx.get(), &drawer);
turbine::gl::addDrawer(*ctx, &drawer);
const auto err = turbine::run(*ctx);
turbine::gl::removeDrawer(*ctx, &drawer);
return err;
}

View File

@ -30,7 +30,7 @@ bool NewMenu::isOpen() const noexcept {
return m_open;
}
void NewMenu::draw(core::Context *ctx) noexcept {
void NewMenu::draw(turbine::Context *ctx) noexcept {
switch (m_stage) {
case Stage::Opening:
ImGui::OpenPopup(title().c_str());
@ -53,7 +53,7 @@ void NewMenu::addItemMaker(ox::UniquePtr<studio::ItemMaker> im) noexcept {
m_types.emplace_back(std::move(im));
}
void NewMenu::drawNewItemType(core::Context *ctx) noexcept {
void NewMenu::drawNewItemType(turbine::Context *ctx) noexcept {
drawWindow(ctx, &m_open, [this] {
auto items = ox_malloca(m_types.size() * sizeof(const char*), const char*, nullptr);
for (auto i = 0u; const auto &im : m_types) {
@ -66,7 +66,7 @@ void NewMenu::drawNewItemType(core::Context *ctx) noexcept {
});
}
void NewMenu::drawNewItemName(core::Context *ctx) noexcept {
void NewMenu::drawNewItemName(turbine::Context *ctx) noexcept {
drawWindow(ctx, &m_open, [this, ctx] {
const auto typeIdx = static_cast<std::size_t>(m_selectedType);
if (typeIdx < m_types.size()) {
@ -90,7 +90,7 @@ void NewMenu::drawFirstPageButtons() noexcept {
}
}
void NewMenu::drawLastPageButtons(core::Context *ctx) noexcept {
void NewMenu::drawLastPageButtons(turbine::Context *ctx) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 138);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20);
if (ImGui::Button("Back")) {
@ -107,7 +107,7 @@ void NewMenu::drawLastPageButtons(core::Context *ctx) noexcept {
}
}
void NewMenu::finish(core::Context *ctx) noexcept {
void NewMenu::finish(turbine::Context *ctx) noexcept {
const auto itemName = ox::String(m_itemName);
const auto err = m_types[static_cast<std::size_t>(m_selectedType)]->write(ctx, itemName);
if (err) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -8,8 +8,6 @@
#include <ox/event/signal.hpp>
#include <ox/std/string.hpp>
#include <nostalgia/core/context.hpp>
#include "lib/itemmaker.hpp"
#include "lib/popup.hpp"
@ -45,7 +43,7 @@ class NewMenu: public studio::Popup {
[[nodiscard]]
bool isOpen() const noexcept override;
void draw(core::Context *ctx) noexcept override;
void draw(turbine::Context *ctx) noexcept override;
template<typename T>
void addItemType(ox::String name, ox::String parentDir, ox::String fileExt, T itemTempl, ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept;
@ -56,15 +54,15 @@ class NewMenu: public studio::Popup {
void addItemMaker(ox::UniquePtr<studio::ItemMaker> im) noexcept;
private:
void drawNewItemType(core::Context *ctx) noexcept;
void drawNewItemType(turbine::Context *ctx) noexcept;
void drawNewItemName(core::Context *ctx) noexcept;
void drawNewItemName(turbine::Context *ctx) noexcept;
void drawFirstPageButtons() noexcept;
void drawLastPageButtons(core::Context *ctx) noexcept;
void drawLastPageButtons(turbine::Context *ctx) noexcept;
void finish(core::Context *ctx) noexcept;
void finish(turbine::Context *ctx) noexcept;
};

View File

@ -31,11 +31,11 @@ buildProjectTreeModel(ProjectExplorer *explorer, ox::CRStringView name, ox::CRSt
return out;
}
ProjectExplorer::ProjectExplorer(core::Context *ctx) noexcept {
ProjectExplorer::ProjectExplorer(turbine::Context *ctx) noexcept {
m_ctx = ctx;
}
void ProjectExplorer::draw(core::Context *ctx) noexcept {
void ProjectExplorer::draw(turbine::Context *ctx) noexcept {
const auto viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 20));
ImGui::SetNextWindowSize(ImVec2(313, viewport->Size.y - 20));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -15,11 +15,11 @@ namespace nostalgia {
class ProjectExplorer: public studio::Widget {
private:
ox::UniquePtr<ProjectTreeModel> m_treeModel;
core::Context *m_ctx = nullptr;
turbine::Context *m_ctx = nullptr;
public:
explicit ProjectExplorer(core::Context *ctx) noexcept;
explicit ProjectExplorer(turbine::Context *ctx) noexcept;
void draw(core::Context *ctx) noexcept override;
void draw(turbine::Context *ctx) noexcept override;
void setModel(ox::UniquePtr<ProjectTreeModel> model) noexcept;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <imgui.h>
@ -23,7 +23,7 @@ ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept:
m_children(std::move(other.m_children)) {
}
void ProjectTreeModel::draw(core::Context *ctx) const noexcept {
void ProjectTreeModel::draw(turbine::Context *ctx) const noexcept {
constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
if (!m_children.empty()) {
if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -7,7 +7,7 @@
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <nostalgia/core/context.hpp>
#include <turbine/context.hpp>
namespace nostalgia {
@ -23,7 +23,7 @@ class ProjectTreeModel {
ProjectTreeModel(ProjectTreeModel &&other) noexcept;
void draw(core::Context *ctx) const noexcept;
void draw(turbine::Context *ctx) const noexcept;
void setChildren(ox::Vector<ox::UniquePtr<ProjectTreeModel>> children) noexcept;

View File

@ -4,8 +4,8 @@
#include <imgui.h>
#include <nostalgia/core/core.hpp>
#include <keel/media.hpp>
#include <turbine/turbine.hpp>
#include "lib/configio.hpp"
#include "builtinmodules.hpp"
@ -31,7 +31,7 @@ oxModelBegin(StudioConfig)
oxModelFieldRename(show_project_explorer, showProjectExplorer)
oxModelEnd()
StudioUI::StudioUI(core::Context *ctx) noexcept {
StudioUI::StudioUI(turbine::Context *ctx) noexcept {
m_ctx = ctx;
m_projectExplorer = ox::make_unique<ProjectExplorer>(m_ctx);
m_projectExplorer->fileChosen.connect(this, &StudioUI::openFile);
@ -61,11 +61,11 @@ void StudioUI::update() noexcept {
m_taskRunner.update(m_ctx);
}
void StudioUI::handleKeyEvent(core::Key key, bool down) noexcept {
const auto ctrlDown = core::buttonDown(m_ctx, core::Key::Mod_Ctrl);
void StudioUI::handleKeyEvent(turbine::Key key, bool down) noexcept {
const auto ctrlDown = turbine::buttonDown(*m_ctx, turbine::Key::Mod_Ctrl);
for (auto p : m_popups) {
if (p->isOpen()) {
if (key == core::Key::Escape) {
if (key == turbine::Key::Escape) {
p->close();
}
return;
@ -73,34 +73,34 @@ void StudioUI::handleKeyEvent(core::Key key, bool down) noexcept {
}
if (down && ctrlDown) {
switch (key) {
case core::Key::Num_1:
case turbine::Key::Num_1:
toggleProjectExplorer();
break;
case core::Key::Alpha_C:
case turbine::Key::Alpha_C:
m_activeEditor->copy();
break;
case core::Key::Alpha_N:
case turbine::Key::Alpha_N:
m_newMenu.open();
break;
case core::Key::Alpha_O:
case turbine::Key::Alpha_O:
m_taskRunner.add(ox::make<FileDialogManager>(this, &StudioUI::openProject));
break;
case core::Key::Alpha_Q:
core::shutdown(m_ctx);
case turbine::Key::Alpha_Q:
turbine::requestShutdown(*m_ctx);
break;
case core::Key::Alpha_S:
case turbine::Key::Alpha_S:
save();
break;
case core::Key::Alpha_V:
case turbine::Key::Alpha_V:
m_activeEditor->paste();
break;
case core::Key::Alpha_X:
case turbine::Key::Alpha_X:
m_activeEditor->cut();
break;
case core::Key::Alpha_Y:
case turbine::Key::Alpha_Y:
redo();
break;
case core::Key::Alpha_Z:
case turbine::Key::Alpha_Z:
undo();
break;
default:
@ -141,7 +141,7 @@ void StudioUI::drawMenu() noexcept {
m_activeEditor->save();
}
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
core::shutdown(m_ctx);
turbine::requestShutdown(*m_ctx);
}
ImGui::EndMenu();
}
@ -219,7 +219,7 @@ void StudioUI::drawTabs() noexcept {
}
if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] {
m_activeEditor->onActivated();
core::setConstantRefresh(m_ctx, m_activeEditor->requiresConstantRefresh());
turbine::setConstantRefresh(*m_ctx, m_activeEditor->requiresConstantRefresh());
}
e->draw(m_ctx);
m_activeEditorOnLastDraw = e.get();
@ -292,9 +292,9 @@ void StudioUI::save() noexcept {
ox::Error StudioUI::openProject(ox::CRStringView path) noexcept {
oxRequireM(fs, keel::loadRomFs(path));
oxReturnError(keel::setRomFs(m_ctx, std::move(fs)));
core::setWindowTitle(m_ctx, ox::sfmt("Nostalgia Studio - {}", path));
turbine::setWindowTitle(*m_ctx, ox::sfmt("Nostalgia Studio - {}", path));
m_project = ox::make_unique<studio::Project>(m_ctx, path);
auto sctx = applicationData<studio::StudioContext>(m_ctx);
auto sctx = applicationData<studio::StudioContext>(*m_ctx);
sctx->project = m_project.get();
m_project->fileAdded.connect(m_projectExplorer.get(), &ProjectExplorer::refreshProjectTreeModel);
m_project->fileDeleted.connect(m_projectExplorer.get(), &ProjectExplorer::refreshProjectTreeModel);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
@ -25,7 +25,7 @@ class StudioUI: public ox::SignalHandler {
private:
ox::UniquePtr<studio::Project> m_project;
studio::TaskRunner m_taskRunner;
core::Context *m_ctx = nullptr;
turbine::Context *m_ctx = nullptr;
ox::Vector<ox::UniquePtr<studio::BaseEditor>> m_editors;
ox::Vector<ox::UniquePtr<studio::Widget>> m_widgets;
ox::HashMap<ox::String, studio::EditorMaker::Func> m_editorMakers;
@ -43,11 +43,11 @@ class StudioUI: public ox::SignalHandler {
bool m_showProjectExplorer = true;
public:
explicit StudioUI(core::Context *ctx) noexcept;
explicit StudioUI(turbine::Context *ctx) noexcept;
void update() noexcept;
void handleKeyEvent(core::Key, bool down) noexcept;
void handleKeyEvent(turbine::Key, bool down) noexcept;
[[nodiscard]]
constexpr auto project() noexcept {

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;
}

Some files were not shown because too many files have changed in this diff Show More