Compare commits
22 Commits
03a1a8abca
...
37f6c388fc
| Author | SHA1 | Date | |
|---|---|---|---|
| 37f6c388fc | |||
| 25954d5503 | |||
| 68b6942606 | |||
| ce2ac2e29e | |||
| 536f0ee8b7 | |||
| e1a1938762 | |||
| 1741822ba0 | |||
| be7b32906f | |||
| db3f29d52f | |||
| 022f148701 | |||
| b484d601e5 | |||
| 60aad6335c | |||
| 8cd2ef2d8b | |||
| 5c8242490e | |||
| 4364911229 | |||
| 1d35f6ce70 | |||
| 8c43baedea | |||
| 07284ac595 | |||
| 437b33cdb5 | |||
| d598efb5ea | |||
| fa4e3c6329 | |||
| 90ef5866dd |
@@ -47,7 +47,7 @@ include_directories(
|
||||
)
|
||||
|
||||
if(BUILDCORE_TARGET STREQUAL "gba")
|
||||
add_subdirectory(deps/gbastartup)
|
||||
add_subdirectory(deps/teagba)
|
||||
else()
|
||||
include_directories(
|
||||
SYSTEM
|
||||
|
||||
12
deps/gbastartup/CMakeLists.txt
vendored
12
deps/gbastartup/CMakeLists.txt
vendored
@@ -1,12 +0,0 @@
|
||||
enable_language(CXX ASM)
|
||||
|
||||
add_library(
|
||||
GbaStartup OBJECT
|
||||
gba_crt0.s
|
||||
cstartup.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
GbaStartup PUBLIC
|
||||
OxStd
|
||||
)
|
||||
4
deps/ox/src/ox/std/assert.cpp
vendored
4
deps/ox/src/ox/std/assert.cpp
vendored
@@ -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();
|
||||
|
||||
17
deps/ox/src/ox/std/bounds.hpp
vendored
17
deps/ox/src/ox/std/bounds.hpp
vendored
@@ -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 {};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
11
deps/ox/src/ox/std/def.hpp
vendored
11
deps/ox/src/ox/std/def.hpp
vendored
@@ -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
|
||||
|
||||
16
deps/ox/src/ox/std/new.hpp
vendored
16
deps/ox/src/ox/std/new.hpp
vendored
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
11
deps/ox/src/ox/std/point.hpp
vendored
11
deps/ox/src/ox/std/point.hpp
vendored
@@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
12
deps/ox/src/ox/std/size.hpp
vendored
12
deps/ox/src/ox/std/size.hpp
vendored
@@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
17
deps/ox/src/ox/std/stacktrace.cpp
vendored
17
deps/ox/src/ox/std/stacktrace.cpp
vendored
@@ -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);
|
||||
|
||||
13
deps/ox/src/ox/std/uuid.hpp
vendored
13
deps/ox/src/ox/std/uuid.hpp
vendored
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
11
deps/ox/src/ox/std/vec.hpp
vendored
11
deps/ox/src/ox/std/vec.hpp
vendored
@@ -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
1
deps/teagba/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
add_subdirectory(src)
|
||||
@@ -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
22
deps/teagba/include/teagba/bios.hpp
vendored
Normal 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
46
deps/teagba/include/teagba/gfx.hpp
vendored
Normal 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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
29
deps/teagba/src/CMakeLists.txt
vendored
Normal 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
37
deps/teagba/src/bios.s
vendored
Normal 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
|
||||
@@ -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
40
deps/teagba/src/gfx.cpp
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
29
src/glutils/CMakeLists.txt
Normal file
29
src/glutils/CMakeLists.txt
Normal 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
|
||||
)
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
@@ -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:
|
||||
@@ -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)
|
||||
|
||||
@@ -9,8 +9,8 @@ endif()
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaAppModules PUBLIC
|
||||
NostalgiaCore
|
||||
Keel
|
||||
NostalgiaCore
|
||||
NostalgiaScene
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
16
src/nostalgia/core/context.cpp
Normal file
16
src/nostalgia/core/context.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ¶ms) 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
}
|
||||
@@ -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 = ®_BG0CTL; bgCtl <= ®_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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
13
src/nostalgia/core/initparams.hpp
Normal file
13
src/nostalgia/core/initparams.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
target_sources(
|
||||
NostalgiaCore PRIVATE
|
||||
core.cpp
|
||||
gfx.cpp
|
||||
gfx_opengl.cpp
|
||||
)
|
||||
target_link_libraries(
|
||||
NostalgiaCore PUBLIC
|
||||
glad
|
||||
imgui
|
||||
NostalgiaGlUtils
|
||||
GlUtils
|
||||
)
|
||||
|
||||
26
src/nostalgia/core/opengl/context.hpp
Normal file
26
src/nostalgia/core/opengl/context.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
21
src/nostalgia/core/opengl/core.cpp
Normal file
21
src/nostalgia/core/opengl/core.cpp
Normal 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 ¶ms) noexcept {
|
||||
ox::UPtr<Context> ctx = ox::make_unique<GlContext>();
|
||||
ctx->turbineCtx = tctx;
|
||||
oxReturnError(initGfx(ctx.get(), params));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,6 @@ target_link_libraries(
|
||||
NostalgiaCore-Studio PUBLIC
|
||||
NostalgiaStudio
|
||||
NostalgiaCore
|
||||
NostalgiaGlUtils
|
||||
lodepng
|
||||
)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() : "";
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ endif()
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaScene-Studio PUBLIC
|
||||
NostalgiaGlUtils
|
||||
NostalgiaStudio
|
||||
NostalgiaScene
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user