Compare commits

...

22 Commits

Author SHA1 Message Date
37f6c388fc [nostaliga/studio] Remove old studio profiles 2023-06-02 20:30:43 -05:00
25954d5503 [ox/std] Make stacktrace code more readable 2023-06-02 20:29:58 -05:00
68b6942606 [nostalgia/scene/studio] Replace glutils::bind with glutils::FrameBufferBind 2023-06-02 20:29:17 -05:00
ce2ac2e29e [nostalgia/appmodules] Cleanup 2023-06-02 20:28:45 -05:00
536f0ee8b7 [glutils] Fix FrameBufferBind unbind to no frame buffer 2023-06-02 20:28:26 -05:00
e1a1938762 [nostalgia/studio] Update year in Info.plist 2023-06-02 20:16:04 -05:00
1741822ba0 [nostalgia/studio] Replace NostalgiaCore with Turbine as NostalgiaStudio dependency 2023-06-02 20:15:49 -05:00
be7b32906f [nostalgia/studio] Move BuiltinModules to a cpp file 2023-06-02 20:14:51 -05:00
db3f29d52f [nostalgia] Make drawMainView render size seting come from function param 2023-06-02 00:55:57 -05:00
022f148701 [teagba] Make install library 2023-06-02 00:21:41 -05:00
b484d601e5 [glutils] Make static lib instead of object lib 2023-06-02 00:06:37 -05:00
60aad6335c [keel] Enable sign-conversion warning 2023-06-02 00:00:39 -05:00
8cd2ef2d8b [nostalgia] Move GlUtils out of Nostalgia 2023-06-01 23:58:39 -05:00
5c8242490e [ox] Make panic always print message 2023-06-01 23:48:39 -05:00
4364911229 [nostalgia,keel] Move core::TypeStore to Keel 2023-06-01 23:43:19 -05:00
1d35f6ce70 [nostalgia/core/studio] Remove old unused files 2023-06-01 23:27:07 -05:00
8c43baedea [nostalgia] Break part of core out into Turbine and TeaGBA libraries 2023-06-01 23:22:31 -05:00
07284ac595 [ox/std] Cleanup geo types to not depend on ox/model 2023-06-01 23:19:51 -05:00
437b33cdb5 [nostalgia] Remove anti-references policy from dev handbook 2023-05-30 20:55:20 -05:00
d598efb5ea [ox] Put oxReturnError and oxThrowError in brackets 2023-05-30 20:51:54 -05:00
fa4e3c6329 [ox] Cleanup 2023-05-30 20:50:47 -05:00
90ef5866dd [ox] Add makeCatch function 2023-05-30 20:49:57 -05:00
159 changed files with 2077 additions and 2380 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
)

View File

@@ -13,8 +13,7 @@
namespace ox {
void panic([[maybe_unused]]const char *file, [[maybe_unused]]int line, [[maybe_unused]]const char *panicMsg, [[maybe_unused]]const Error &err) noexcept {
#ifdef OX_USE_STDLIB
void panic(const char *file, int line, const char *panicMsg, const Error &err) noexcept {
oxErrf("\033[31;1;1mPANIC:\033[0m [{}:{}]: {}\n", file, line, panicMsg);
if (err.msg) {
oxErrf("\tError Message:\t{}\n", err.msg);
@@ -23,6 +22,7 @@ void panic([[maybe_unused]]const char *file, [[maybe_unused]]int line, [[maybe_u
if (err.file != nullptr) {
oxErrf("\tError Location:\t{}:{}\n", err.file, err.line);
}
#ifdef OX_USE_STDLIB
printStackTrace(2);
oxTrace("panic").del("") << "Panic: " << panicMsg << " (" << file << ":" << line << ")";
std::abort();

View File

@@ -8,7 +8,6 @@
#pragma once
#include <ox/model/def.hpp>
#include <ox/std/error.hpp>
#include <ox/std/new.hpp>
@@ -125,11 +124,15 @@ constexpr void Bounds::set(const Point &pt1, const Point &pt2) noexcept {
this->height = y2 - y1;
}
oxModelBegin(Bounds)
oxModelField(x)
oxModelField(y)
oxModelField(width)
oxModelField(height)
oxModelEnd()
template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<Bounds> auto *obj) noexcept {
io->template setTypeInfo<Bounds>();
oxReturnError(io->field("x", &obj->x));
oxReturnError(io->field("y", &obj->y));
oxReturnError(io->field("width", &obj->width));
oxReturnError(io->field("height", &obj->height));
return {};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 - 2022 gary@drinkingtea.net
* Copyright 2015 - 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
@@ -49,13 +49,8 @@
// Error handling
#if __cplusplus >= 202002L
#define oxReturnError(x) if (const auto _ox_error = ox::detail::toError(x)) [[unlikely]] return _ox_error
#define oxThrowError(x) if (const auto _ox_error = ox::detail::toError(x)) [[unlikely]] throw ox::Exception(_ox_error)
#else
#define oxReturnError(err) if (const auto _ox_error = ox::detail::toError(err)) return _ox_error
#define oxThrowError(err) if (const auto _ox_error = ox::detail::toError(err)) throw ox::Exception(_ox_error)
#endif
#define oxReturnError(x) { if (const auto _ox_error = ox::detail::toError(x)) [[unlikely]] return _ox_error; } (void) 0
#define oxThrowError(x) { if (const auto _ox_error = ox::detail::toError(x)) [[unlikely]] throw ox::Exception(_ox_error); } (void) 0
#define oxConcatImpl(a, b) a##b
#define oxConcat(a, b) oxConcatImpl(a, b)
// oxRequire Mutable

View File

@@ -50,6 +50,22 @@ constexpr T *make(Args &&...args) noexcept {
#endif
}
template<typename T, typename ...Args>
[[nodiscard]]
constexpr Result<T*> makeCatch(Args &&...args) noexcept {
#ifdef __cpp_exceptions
try {
return new T(ox::forward<Args>(args)...);
} catch (const ox::Exception &ex) {
return ex.toError();
} catch (...) {
return OxError(1, "Allocation or constructor failed");
}
#else
return new T(ox::forward<Args>(args)...);
#endif
}
constexpr auto MallocaStackLimit = defines::UseStdLib ? 1024 : 0;
/**

View File

@@ -184,9 +184,12 @@ constexpr bool Point::operator!=(const Point &p) const noexcept {
return x != p.x || y != p.y;
}
oxModelBegin(Point)
oxModelField(x)
oxModelField(y)
oxModelEnd()
template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<Point> auto *obj) noexcept {
io->template setTypeInfo<Point>();
oxReturnError(io->field("x", &obj->x));
oxReturnError(io->field("y", &obj->y));
return {};
}
}

View File

@@ -9,6 +9,7 @@
#pragma once
#include <ox/std/error.hpp>
#include <ox/std/concepts.hpp>
namespace ox {
@@ -184,9 +185,12 @@ constexpr bool Size::operator!=(const Size &p) const noexcept {
}
oxModelBegin(Size)
oxModelField(width)
oxModelField(height)
oxModelEnd()
template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<Size> auto *obj) noexcept {
io->template setTypeInfo<Size>();
oxReturnError(io->field("width", &obj->width));
oxReturnError(io->field("height", &obj->height));
return {};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 - 2022 gary@drinkingtea.net
* Copyright 2015 - 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
@@ -7,7 +7,6 @@
*/
#if defined(OX_USE_STDLIB) && __has_include(<unistd.h>)
#include <iostream>
#if __has_include(<execinfo.h>)
#include <execinfo.h>
@@ -27,13 +26,14 @@ namespace ox {
#if defined(OX_USE_STDLIB) && __has_include(<unistd.h>)
[[nodiscard]]
static auto symbolicate([[maybe_unused]]char **frames,
[[maybe_unused]]std::size_t symbolsLen,
static auto symbolicate([[maybe_unused]]void **frames,
[[maybe_unused]]std::size_t frameCnt,
[[maybe_unused]]const char *linePrefix) {
using StrT = BasicString<100>;
Vector<StrT, 30> out;
#if __has_include(<cxxabi.h>) && __has_include(<execinfo.h>)
for (auto i = 0u; i < symbolsLen; ++i) {
const auto mangledSymbols = backtrace_symbols(frames, static_cast<int>(frameCnt));
for (auto i = 0u; i < frameCnt; ++i) {
Dl_info info;
if (dladdr(frames[i], &info) && info.dli_sname) {
int status = -1;
@@ -43,8 +43,9 @@ static auto symbolicate([[maybe_unused]]char **frames,
continue;
}
}
out.emplace_back(sfmt<StrT>("{}", frames[i]));
out.emplace_back(sfmt<StrT>("{}: {}", i, mangledSymbols[i]));
}
free(mangledSymbols);
#endif // __has_include(<cxxabi.h>)
return out;
}
@@ -56,9 +57,7 @@ void printStackTrace([[maybe_unused]]unsigned shave) noexcept {
Vector<void*, FrameCnt> frames(FrameCnt);
frames.resize(static_cast<std::size_t>(backtrace(frames.data(), static_cast<int>(frames.size()))));
if (frames.size() - shave > 2) {
const auto symbols = backtrace_symbols(frames.data() + shave, static_cast<int>(frames.size() - shave));
const auto symbolicatedStacktrace = symbolicate(reinterpret_cast<char**>(frames.data() + shave), frames.size() - shave, "\t");
free(symbols);
const auto symbolicatedStacktrace = symbolicate(frames.data() + shave, frames.size() - shave, "\t");
oxErr("Stacktrace:\n");
for (const auto &s : symbolicatedStacktrace) {
oxErrf("\t{}\n", s);

View File

@@ -153,24 +153,23 @@ class UUID {
UUIDStr *out,
const Array<uint8_t, 16> &value,
std::size_t cnt,
unsigned valueI) {
unsigned &valueI) {
for (auto i = 0u; i < cnt; ++i) {
const auto v = value[valueI];
const auto h = detail::toHex(v);
oxIgnoreError(out->append(h.c_str(), h.len()));
++valueI;
}
return valueI;
};
valueI = printChars(&out, m_value, 4, valueI);
printChars(&out, m_value, 4, valueI);
out += "-";
valueI = printChars(&out, m_value, 2, valueI);
printChars(&out, m_value, 2, valueI);
out += "-";
valueI = printChars(&out, m_value, 2, valueI);
printChars(&out, m_value, 2, valueI);
out += "-";
valueI = printChars(&out, m_value, 2, valueI);
printChars(&out, m_value, 2, valueI);
out += "-";
valueI = printChars(&out, m_value, 6, valueI);
printChars(&out, m_value, 6, valueI);
return out;
}
};

View File

@@ -260,9 +260,12 @@ struct Vec {
using Vec2 = Vec<float>;
oxModelBegin(Vec2)
oxModelField(x)
oxModelField(y)
oxModelEnd()
template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<Vec2> auto *obj) noexcept {
io->template setTypeInfo<Vec2>();
oxReturnError(io->field("x", &obj->x));
oxReturnError(io->field("y", &obj->y));
return {};
}
}

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

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

@@ -0,0 +1,29 @@
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
)
install(
TARGETS
TeaGBA
DESTINATION
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)

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

@@ -128,18 +128,6 @@ the entire life of the process, other classes likely will not get away with it
due to more substantial constructors and more frequent instantiations of the
classes in question.
### Pointers vs References
Pointers are generally preferred to references. References should be used for
optimizing the passing in of parameters and for returning from accessor
operators (e.g. ```T &Vector::operator[](size_t)```).
As parameters, references should always be const.
A non-const reference is generally used because the parameter value is changed
in the function, but it will look like it was passed in by value where it is
called and thus not subject to change.
The reference operator makes it clear to the caller that the value can and
likely will change.
## Project Systems
### Error Handling

View File

@@ -1,4 +1,8 @@
include_directories(".")
if(NOSTALGIA_BUILD_TYPE STREQUAL "Native")
add_subdirectory(glutils)
endif()
add_subdirectory(keel)
add_subdirectory(nostalgia)
add_subdirectory(nostalgia)
add_subdirectory(turbine)

View File

@@ -0,0 +1,29 @@
add_library(
GlUtils
glutils.cpp
)
if(NOT MSVC)
target_compile_options(GlUtils PRIVATE -Wsign-conversion)
endif()
target_link_libraries(
GlUtils PUBLIC
OxStd
glad
)
install(
FILES
glutils.hpp
DESTINATION
include/glutils
)
install(
TARGETS
GlUtils
DESTINATION
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)

View File

@@ -8,7 +8,7 @@
#include "glutils.hpp"
namespace nostalgia::glutils {
namespace glutils {
void deleteBuffer(GLuint b) noexcept {
glDeleteBuffers(1, &b);
@@ -46,6 +46,23 @@ 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);
}
}
void bind(const FrameBuffer &fb) noexcept {
glBindFramebuffer(GL_FRAMEBUFFER, fb);

View File

@@ -8,11 +8,12 @@
#include <glad/glad.h>
#include <ox/std/bounds.hpp>
#include <ox/std/error.hpp>
#include <ox/std/string.hpp>
#include <ox/std/vector.hpp>
namespace nostalgia::glutils {
namespace glutils {
constexpr auto GlslVersion = "#version 330";
@@ -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

@@ -6,8 +6,13 @@ add_library(
module.cpp
pack.cpp
typeconv.cpp
typestore.cpp
)
if(NOT MSVC)
target_compile_options(Keel PRIVATE -Wsign-conversion)
endif()
target_link_libraries(
Keel PUBLIC
OxClaw
@@ -28,6 +33,7 @@ install(
module.hpp
pack.hpp
typeconv.hpp
typestore.hpp
DESTINATION
include/keel
)

View File

@@ -32,7 +32,7 @@ ox::Result<ox::ModelObject> readAsset(ox::TypeStore *ts, const ox::Buffer &buff)
ox::Result<AssetHdr> readAssetHeader(const char *buff, std::size_t buffLen) noexcept {
AssetHdr out;
const auto err = readUuidHeader(buff, buffLen).moveTo(&out.uuid);
const auto offset = err ? 0 : K1HdrSz;
const auto offset = err ? 0u : K1HdrSz;
buff = buff + offset;
buffLen = buffLen - offset;
oxReturnError(ox::readClawHeader(buff, buffLen).moveTo(&out.clawHdr));

View File

@@ -10,6 +10,7 @@
#include "media.hpp"
#include "module.hpp"
#include "pack.hpp"
#include "typestore.hpp"
namespace keel {
@@ -18,6 +19,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 +31,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

@@ -4,11 +4,10 @@
#include "typestore.hpp"
namespace nostalgia::core {
namespace keel {
ox::Result<ox::UniquePtr<ox::DescriptorType>> TypeStore::loadDescriptor(ox::CRStringView typeId) noexcept {
constexpr auto descPath = "/.nostalgia/type_descriptors";
auto path = ox::sfmt("{}/{}", descPath, typeId);
auto path = ox::sfmt("{}/{}", m_descPath, typeId);
oxRequire(buff, m_fs->read(path));
auto dt = ox::make_unique<ox::DescriptorType>();
oxReturnError(ox::readClaw<ox::DescriptorType>(buff, dt.get()));

View File

@@ -8,14 +8,17 @@
#include <ox/fs/fs.hpp>
#include <ox/model/typestore.hpp>
namespace nostalgia::core {
namespace keel {
class TypeStore: public ox::TypeStore {
private:
ox::FileSystem *m_fs = nullptr;
ox::String m_descPath;
public:
constexpr explicit TypeStore(ox::FileSystem *fs) noexcept: m_fs(fs) {
constexpr explicit TypeStore(ox::FileSystem *fs, ox::String descPath = "/.keel/type_descriptors") noexcept:
m_fs(fs),
m_descPath(std::move(descPath)) {
}
protected:

View File

@@ -10,7 +10,6 @@ if(NOSTALGIA_BUILD_PLAYER)
endif()
if(NOSTALGIA_BUILD_TYPE STREQUAL "Native")
add_subdirectory(glutils)
add_subdirectory(tools)
if(NOSTALGIA_BUILD_STUDIO)
add_subdirectory(studio)

View File

@@ -9,8 +9,8 @@ endif()
target_link_libraries(
NostalgiaAppModules PUBLIC
NostalgiaCore
Keel
NostalgiaCore
NostalgiaScene
)

View File

@@ -1,16 +1,15 @@
add_library(
NostalgiaCore
context.cpp
gfx.cpp
module.cpp
tilesheet.cpp
typeconv.cpp
typestore.cpp
)
if(NOSTALGIA_BUILD_TYPE STREQUAL "GBA")
add_subdirectory(gba)
else()
add_subdirectory(glfw)
add_subdirectory(opengl)
endif()
@@ -20,7 +19,7 @@ endif()
target_link_libraries(
NostalgiaCore PUBLIC
Keel
Turbine
)
if(NOSTALGIA_BUILD_STUDIO)
@@ -29,19 +28,16 @@ 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
DESTINATION
include/nostalgia/core
)

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,26 @@
#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*, ox::Size const&) 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 +39,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 +55,11 @@ 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*, ox::Size const&) 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 +68,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,11 +1,11 @@
target_sources(
NostalgiaCore PRIVATE
core.cpp
gfx.cpp
gfx_opengl.cpp
)
target_link_libraries(
NostalgiaCore PUBLIC
glad
imgui
NostalgiaGlUtils
GlUtils
)

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 <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 <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, ox::Size const&renderSz) 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] = renderSz;
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, ox::Size const&renderSz) 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] = renderSz;
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,155 @@ 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, ox::Size const&renderSz) noexcept {
// clear screen
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
auto &gctx = static_cast<GlContext&>(*ctx);
renderer::drawBackgrounds(&gctx, renderSz);
if (gctx.spriteBlocks.tex) {
renderer::drawSprites(&gctx, renderSz);
}
}
void drawMainView(core::Context *ctx) noexcept {
drawMainView(ctx, 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 <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

@@ -21,7 +21,6 @@ target_link_libraries(
NostalgiaCore-Studio PUBLIC
NostalgiaStudio
NostalgiaCore
NostalgiaGlUtils
lodepng
)

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <QColor>
#include <QImage>
#include <QMap>
#include <nostalgia/core/core.hpp>
#include "imgconv.hpp"
namespace nostalgia::core {
[[nodiscard]]
static uint16_t toGbaColor(QColor c) {
const auto r = static_cast<uint32_t>(c.red()) >> 3;
const auto g = static_cast<uint32_t>(c.green()) >> 3;
const auto b = static_cast<uint32_t>(c.blue()) >> 3;
const auto a = static_cast<uint32_t>(c.alpha() > 128 ? 1 : 0);
return (a << 15) | (r << 10) | (g << 5) | (b << 0);
}
[[nodiscard]]
static int countColors(const QImage &img, int argTiles) {
QMap<QRgb, bool> colors;
// copy pixels as color ids
for (int x = 0; x < img.width(); x++) {
for (int y = 0; y < img.height(); y++) {
auto destI = pointToIdx(img.width(), x, y);
if (destI <= argTiles * PixelsPerTile) {
auto c = img.pixel(x, y);
// assign color a color id for the palette
if (!colors.contains(c)) {
colors[c] = true;
}
}
}
}
return colors.size();
}
[[nodiscard]]
ox::UniquePtr<core::NostalgiaGraphic> imgToNg(QString argSrc, int argBpp) {
QImage src(argSrc);
if (src.isNull()) {
return {};
}
const auto Pixels = src.width() * src.height();
const auto Tiles = Pixels / PixelsPerTile;
const auto Colors = countColors(src, Tiles);
if (argBpp != 4 && argBpp != 8) {
argBpp = Colors > 16 ? 8 : 4;
}
QMap<QRgb, int> colors;
auto ng = std::make_unique<core::NostalgiaGraphic>();
ng->pal.colors.resize(static_cast<std::size_t>(countColors(src, Tiles)));
if (argBpp == 4) {
ng->pixels.resize(static_cast<std::size_t>(Pixels / 2));
} else {
ng->pixels.resize(static_cast<std::size_t>(Pixels));
}
ng->bpp = argBpp;
ng->columns = src.width() / TileWidth;
ng->rows = src.height() / TileHeight;
int colorIdx = 0;
// copy pixels as color ids
for (int x = 0; x < src.width(); x++) {
for (int y = 0; y < src.height(); y++) {
auto destI = pointToIdx(src.width(), x, y);
if (destI < Tiles * PixelsPerTile) {
const auto c = src.pixel(x, y);
// assign color a color id for the palette
if (!colors.contains(c)) {
colors[c] = colorIdx;
colorIdx++;
}
// set pixel color
if (argBpp == 4) {
if (destI % 2) { // is odd number pixel
ng->pixels[static_cast<std::size_t>(destI / 2)] |= colors[c] << 4;
} else {
ng->pixels[static_cast<std::size_t>(destI / 2)] |= colors[c];
}
} else {
ng->pixels[static_cast<std::size_t>(destI)] = static_cast<std::size_t>(colors[c]);
}
}
}
}
// store colors in palette with the corresponding color id
for (auto key : colors.keys()) {
auto colorId = static_cast<std::size_t>(colors[key]);
ng->pal.colors[colorId] = toGbaColor(key);
}
return ng;
}
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/error.hpp>
#include <ox/std/types.hpp>
#include <ox/mc/mc.hpp>
#include <nostalgia/core/gfx.hpp>
namespace nostalgia::core {
[[nodiscard]]
ox::UniquePtr<core::NostalgiaGraphic> imgToNg(ox::String argInPath, int argBpp = -1);
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <QBuffer>
#include <QDebug>
#include <QFile>
#include <nostalgia/core/consts.hpp>
#include "imgconv.hpp"
#include "import_tilesheet_wizard.hpp"
namespace nostalgia::core {
ImportTilesheetWizardMainPage::ImportTilesheetWizardMainPage(const studio::Context *ctx) {
m_ctx = ctx;
addLineEdit(tr("&Tile Sheet Name:"), QString(TileSheetName) + "*", "", [this](QString) {
auto importPath = field(ImportPath).toString();
if (QFile(importPath).exists()) {
return 0;
} else {
this->showValidationError(tr("Invalid image file: %1").arg(importPath));
return 1;
}
}
);
auto fileTypes = "(*.png);;(*.bmp);;(*.jpg);;(*.jpeg)";
addPathBrowse(tr("Tile Sheet &Path:"), QString(ImportPath) + "*", "",
QFileDialog::ExistingFile, fileTypes);
}
ImportTilesheetWizardPalettePage::ImportTilesheetWizardPalettePage(const studio::Context *ctx) {
m_ctx = ctx;
addLineEdit(tr("Palette &Name:"), PaletteName);
}
int ImportTilesheetWizardPalettePage::accept() {
const auto tilesheetName = field(TileSheetName).toString();
const auto importPath = field(ImportPath).toString();
const auto paletteName = field(PaletteName).toString();
const auto outPath = TileSheetDir + tilesheetName + FileExt_ng;
if (!QFile(importPath).exists()) {
return OxError(1);
}
auto ng = imgToNg(importPath, 0);
if (!ng) {
return OxError(1);
}
const auto paletteOutPath = PaletteDir + paletteName + FileExt_npal;
core::NostalgiaPalette pal;
pal = std::move(ng->pal);
m_ctx->project->writeObj(paletteOutPath, &pal);
auto defaultPalette = paletteOutPath.toUtf8();
ng->defaultPalette = defaultPalette.data();
m_ctx->project->writeObj(outPath, ng.get());
return 0;
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <nostalgia/studio/studio.hpp>
#include "consts.hpp"
namespace nostalgia::core {
constexpr auto TileSheetName = "tilesheetName";
constexpr auto ImportPath = "importPath";
constexpr auto Palette = "palette";
constexpr auto PaletteName = "paletteName";
class ImportTilesheetWizardMainPage: public studio::WizardFormPage {
private:
//static constexpr auto BPP = "bpp";
const studio::Context *m_ctx = nullptr;
public:
ImportTilesheetWizardMainPage(const studio::Context *args);
};
class ImportTilesheetWizardPalettePage: public studio::WizardFormPage {
private:
//static constexpr auto BPP = "bpp";
const studio::Context *m_ctx = nullptr;
public:
ImportTilesheetWizardPalettePage(const studio::Context *args);
int accept();
};
}

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

@@ -1,46 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <QBuffer>
#include <QFile>
#include <nostalgia/core/consts.hpp>
#include <nostalgia/core/gfx.hpp>
#include "consts.hpp"
#include "new_tilesheet_wizard.hpp"
namespace nostalgia::core {
NewTilesheetWizardPage::NewTilesheetWizardPage(const studio::Context *ctx) {
m_ctx = ctx;
addLineEdit(tr("&Tile Sheet Name:"), QString(TileSheetName) + "*", "", [](QString) {
return 0;
});
m_palettePicker = addComboBox(tr("&Palette:"), QString(Palette) + "*", {""});
m_ctx->project->subscribe(studio::ProjectEvent::FileRecognized, m_palettePicker, [this](QString path) {
if (path.startsWith(PaletteDir) && path.endsWith(FileExt_npal)) {
m_palettePicker->addItem(studio::filePathToName(path, PaletteDir, FileExt_npal), path);
}
});
}
int NewTilesheetWizardPage::accept() {
const auto tilesheetName = field(TileSheetName).toString();
const auto palette = m_palettePicker->itemData(field(Palette).toInt()).toString();
const auto outPath = QString(TileSheetDir) + tilesheetName + FileExt_ng;
auto err = m_ctx->project->exists(outPath);
if (err) {
showValidationError(tr("A tile sheet with this name already exists."));
return err;
}
NostalgiaGraphic ng;
ng.columns = 1;
ng.rows = 1;
ng.defaultPalette = palette.toUtf8().data();
m_ctx->project->writeObj(outPath, &ng);
return 0;
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <nostalgia/studio/studio.hpp>
namespace nostalgia::core {
class NewTilesheetWizardPage: public studio::WizardFormPage {
private:
static constexpr auto TileSheetName = "projectName";
static constexpr auto Palette = "palette";
class QComboBox *m_palettePicker = nullptr;
const studio::Context *m_ctx = nullptr;
public:
NewTilesheetWizardPage(const studio::Context *args);
int accept();
private:
};
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <QBuffer>
#include <QDebug>
#include <nostalgia/core/consts.hpp>
#include <nostalgia/core/gfx.hpp>
#include "consts.hpp"
#include "newpalettewizard.hpp"
namespace nostalgia::core {
NewPaletteWizardPage::NewPaletteWizardPage(const studio::Context *ctx) {
m_ctx = ctx;
addLineEdit(tr("&Palette Name:"), QString(PaletteName) + "*", "", [](QString) {
return 0;
});
}
int NewPaletteWizardPage::accept() {
const auto paletteName = field(PaletteName).toString();
const auto path = PaletteDir + paletteName + FileExt_npal;
if (m_ctx->project->exists(path)) {
showValidationError(tr("A palette with this name already exists."));
return 1;
}
NostalgiaPalette pal;
m_ctx->project->writeObj(path, &pal);
return 0;
}
}

View File

@@ -1,24 +0,0 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <nostalgia/studio/studio.hpp>
namespace nostalgia::core {
class NewPaletteWizardPage: public studio::WizardFormPage {
private:
static constexpr auto PaletteName = "paletteName";
const studio::Context *m_ctx = nullptr;
public:
NewPaletteWizardPage(const studio::Context *args);
int accept();
private:
};
}

View File

@@ -4,17 +4,24 @@
#include <imgui.h>
#include <nostalgia/core/gfx.hpp>
#include <keel/media.hpp>
#include <ox/std/memory.hpp>
#include <keel/media.hpp>
#include <nostalgia/core/gfx.hpp>
#include "paletteeditor.hpp"
#include "paletteeditor-imgui.hpp"
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 +39,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 +144,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

@@ -7,7 +7,8 @@
#include <ox/model/def.hpp>
#include <ox/std/vec.hpp>
#include <nostalgia/glutils/glutils.hpp>
#include <glutils/glutils.hpp>
#include <nostalgia/studio/studio.hpp>
#include "tilesheetpixelgrid.hpp"
@@ -43,7 +44,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 +56,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 +72,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

@@ -7,8 +7,9 @@
#include <ox/std/vec.hpp>
#include <ox/model/def.hpp>
#include <glutils/glutils.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/glutils/glutils.hpp>
#include <nostalgia/studio/studio.hpp>
#include "tilesheeteditormodel.hpp"
@@ -49,7 +50,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

@@ -4,8 +4,9 @@
#pragma once
#include <glutils/glutils.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/glutils/glutils.hpp>
#include <nostalgia/studio/studio.hpp>
namespace nostalgia::core {

View File

@@ -6,8 +6,9 @@
#include <ox/std/vec.hpp>
#include <glutils/glutils.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/glutils/glutils.hpp>
#include <nostalgia/studio/studio.hpp>
namespace nostalgia::core {

View File

@@ -1,21 +0,0 @@
add_library(
NostalgiaGlUtils OBJECT
glutils.cpp
)
if(NOT MSVC)
target_compile_options(NostalgiaGlUtils PRIVATE -Wsign-conversion)
endif()
target_link_libraries(
NostalgiaGlUtils PUBLIC
OxStd
glad
)
install(
FILES
glutils.hpp
DESTINATION
include/nostalgia/glutils
)

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

@@ -12,7 +12,6 @@ endif()
target_link_libraries(
NostalgiaScene-Studio PUBLIC
NostalgiaGlUtils
NostalgiaStudio
NostalgiaScene
)

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,30 +2,28 @@
* 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 {
if (width != m_frameBuffer.width || height != m_frameBuffer.height) {
glutils::resizeInitFrameBuffer(&m_frameBuffer, width, height);
}
glutils::bind(m_frameBuffer);
core::gl::setRenderSize(m_ctx, width, height);
core::gl::drawMainView(m_ctx);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glutils::FrameBufferBind frameBufferBind(m_frameBuffer);
core::gl::drawMainView(m_cctx.get(), {width, height});
}
const glutils::FrameBuffer &SceneEditorView::framebuffer() const noexcept {

View File

@@ -4,8 +4,9 @@
#pragma once
#include <glutils/glutils.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/glutils/glutils.hpp>
#include <nostalgia/scene/scene.hpp>
namespace nostalgia::scene {
@@ -13,13 +14,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

@@ -3,6 +3,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_executable(
nostalgia-studio MACOSX_BUNDLE
aboutpopup.cpp
builtinmodules.cpp
clawviewer.cpp
filedialogmanager.cpp
main.cpp
@@ -31,13 +32,6 @@ if(APPLE)
set_target_properties(nostalgia-studio PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
endif()
install(
FILES
nostalgia-studio.json
DESTINATION
${NOSTALGIA_DIST_RESOURCES}
)
install(
FILES
ns_logo128.png

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