Initial Commit

This commit is contained in:
2025-11-19 22:20:27 +01:00
parent 8dfe2dce9e
commit 2ad6d49511
29 changed files with 8930 additions and 3 deletions

35
source/amethyst.cpp Executable file
View File

@@ -0,0 +1,35 @@
#include <amethyst.hpp>
#include <exception>
#include <iostream>
#ifdef AMY_3DS
#include <3ds.h>
void NpiD7CxxExceptionHandler() {
gfxInitDefault();
consoleInit(GFX_TOP, nullptr);
std::cout << "[C++ Error Handler]" << std::endl;
try {
std::rethrow_exception(std::current_exception());
} catch (const std::exception& e) {
std::cout << "\n\n" << e.what() << std::endl;
}
aptSetHomeAllowed(false);
aptSetSleepAllowed(false);
while (aptMainLoop()) {
hidScanInput();
if (hidKeysDown() & KEY_START) {
break;
}
}
std::abort();
}
#endif
namespace amy {
void registerCxxExceptionHandler() {
#ifdef AMY_3DS
std::set_terminate(NpiD7CxxExceptionHandler);
#endif
}
} // namespace amy

74
source/c3d.cpp Executable file
View File

@@ -0,0 +1,74 @@
#include <citro3d.h>
#include <amethyst/c3d.hpp>
#include <amethyst/utils.hpp>
namespace amy {
const auto DISPLAY_TRANSFER_FLAGS =
GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) |
GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) |
GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) |
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO);
C3D_TexEnv* c3d::frag::m_env = nullptr;
void c3d::init() { C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); }
void c3d::deinit() { C3D_Fini(); }
void c3d::startFrame(bool sync) {
C3D_FrameBegin(sync ? C3D_FRAME_SYNCDRAW : C3D_FRAME_NONBLOCK);
}
void c3d::endFrame() { C3D_FrameEnd(0); }
c3d::screen* c3d::createScreen(gfxScreen_t screen, gfx3dSide_t side) {
auto t = C3D_RenderTargetCreate(240, screen == GFX_TOP ? 400 : 320,
GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
C3D_RenderTargetSetOutput(t, screen, side, DISPLAY_TRANSFER_FLAGS);
return new c3d::screen(t);
}
void c3d::deleteScreen(c3d::screen* screen) { delete screen; }
c3d::shader::shader(const std::string& path) { load(path); }
c3d::shader::~shader() {}
void c3d::shader::load(const std::string& path) {
auto code = utils::loadFile2Mem(path);
if (!code.size()) {
throw std::runtime_error("[amy] shader: unable to load " + path);
}
m_code = DVLB_ParseFile((u32*)&code[0], code.size());
shaderProgramInit(&m_program);
shaderProgramSetVsh(&m_program, &m_code->DVLE[0]);
C3D_BindProgram(&m_program);
AttrInfo_Init(&m_info);
}
void c3d::shader::use() {
shaderProgramUse(&m_program);
C3D_SetAttrInfo(&m_info);
}
void c3d::shader::setMat4(int loc, C3D_Mtx* m) {
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, loc, m);
}
void c3d::shader::input(int reg, GPU_FORMATS f, int num) {
AttrInfo_AddLoader(&m_info, reg, f, num);
}
void c3d::frag::src(C3D_TexEnvMode mode, GPU_TEVSRC s1, GPU_TEVSRC s2,
GPU_TEVSRC s3) {
C3D_TexEnvSrc(m_env, mode, s1, s2, s3);
}
void c3d::frag::func(C3D_TexEnvMode mode, GPU_COMBINEFUNC func) {
C3D_TexEnvFunc(m_env, mode, func);
}
void c3d::frag::edit(int id) {
m_env = C3D_GetTexEnv(id);
C3D_TexEnvInit(m_env);
}
} // namespace amy

16
source/ctru.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include <3ds.h>
#include <amethyst/ctru.hpp>
namespace amy {
namespace ctru {
void init(unsigned int srvs) {
if (srvs & romfs) {
romfsInit();
}
if (srvs & gfx_def) {
gfxInitDefault();
}
}
} // namespace ctru
} // namespace amy

149
source/image.cpp Executable file
View File

@@ -0,0 +1,149 @@
#include <amethyst/image.hpp>
#include <amethyst/utils.hpp>
#include <stdexcept>
// #define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
namespace amy {
void image::load(cstr& path) {
int c = 0;
uc* buf = stbi_load(path.c_str(), &m_w, &m_h, &c, 4);
if (c == 3) {
// Releoading the Image with tree channels requestet
// Still need to find a better way for this :(
stbi_image_free(buf);
buf = stbi_load(path.c_str(), &m_w, &m_h, &c, 3);
m_buffer.assign(buf, buf + (m_w * m_h * 3));
m_fmt = RGB;
stbi_image_free(buf);
} else if (c == 4) {
m_buffer.assign(buf, buf + (m_w * m_h * 4));
m_fmt = RGBA;
stbi_image_free(buf);
} else {
throw std::runtime_error(path + " is using an unsupported image format!");
}
}
void image::load(const std::vector<uc>& data) {
int c = 0;
uc* buf =
stbi_load_from_memory(data.data(), data.size(), &m_w, &m_h, &c, 4);
if (c == 3) {
// Releoading the Image with tree channels requestet
// Still need to find a better way for this :(
stbi_image_free(buf);
buf = stbi_load_from_memory(data.data(), data.size(), &m_w, &m_h, &c, 3);
m_buffer.assign(buf, buf + (m_w * m_h * 3));
m_fmt = RGB;
stbi_image_free(buf);
} else if (m_fmt == 4) {
m_buffer.assign(buf, buf + (m_w * m_h * 4));
m_fmt = RGBA;
stbi_image_free(buf);
} else {
throw std::runtime_error("image mem using an unsupported image format!");
}
}
void image::copy(const std::vector<uc>& pixels, int w, int h,
const format& fmt) {
int f = getBppOfFmt(fmt);
if (pixels.size() != static_cast<size_t>(w * h * f)) {
throw std::runtime_error("Connot copy image due to size error!");
}
m_fmt = fmt;
m_w = w;
m_h = h;
m_buffer.clear();
m_buffer.resize(w * h * f);
for (size_t i = 0; i < m_buffer.size(); i++) {
m_buffer[i] = pixels[i];
}
}
int image::getBppOfFmt(const image::format& fmt) {
switch (fmt) {
case RGBA:
case ABGR:
case BGRA:
return 4;
break;
case RGB:
case BGR:
return 3;
break;
case RGB565:
return 2;
break;
default:
return 0; // Unknown
break;
}
}
void image::convert(image& img, const format& dst) {
if (img.m_fmt == dst) {
return;
} else if (img.m_fmt == RGB && dst == BGR) {
utils::image::reverseBuf(img.m_buffer, img.m_w, img.m_h, 3);
img.m_fmt = BGR;
} else if (img.m_fmt == RGB && dst == RGBA) {
utils::image::addAlphaChannel(img.m_buffer, img.m_w, img.m_h);
img.m_fmt = RGBA;
} else if (img.m_fmt == RGBA && dst == RGB) {
utils::image::removeAlphaChannel(img.m_buffer, img.m_w, img.m_h);
img.m_fmt = RGB;
} else if (img.m_fmt == RGBA && dst == ABGR) {
utils::image::reverseBuf(img.m_buffer, img.m_w, img.m_h, 4);
img.m_fmt = ABGR;
} else if (img.m_fmt == RGB && dst == ABGR) {
convert(img, RGBA);
convert(img, ABGR);
} else if (img.m_fmt == RGBA && dst == RGB565) {
convert(img, RGB);
convert(img, RGB565);
} else if (img.m_fmt == RGB && dst == RGB565) {
// Inlined make pixel 565 func
auto f = [](uc r, uc g,
uc b) -> unsigned short {
unsigned short _r = (r >> 3);
unsigned short _g = (g >> 2);
unsigned short _b = (b >> 3);
return (_r << 11) | (_g << 5) | _b;
};
std::vector<uc> cpy = img.m_buffer;
img.m_buffer.resize(img.m_w * img.m_h * 2);
for (int y = 0; y < img.m_w; y++) {
for (int x = 0; x < img.m_h; x++) {
int src = (y * img.m_w + x) * 3;
int dst = (y * img.m_w + x) * 2;
unsigned short new_px = f(cpy[src + 0], cpy[src + 1], cpy[src + 2]);
img.m_buffer[dst + 0] = new_px >> 8;
img.m_buffer[dst + 1] = new_px & 0xff;
}
}
img.m_fmt = RGB565;
}
}
void image::retile(image& img,
std::function<ui(int x, int y, int w)> src,
std::function<ui(int x, int y, int w)> dst) {
std::vector<uc> cpy = img.m_buffer;
/** could use fmt here but for 565 that woulnt work as it is not supported by
* file loading where fmt is used */
int bpp = img.bpp();
for (int y = 0; y < img.m_h; y++) {
for (int x = 0; x < img.m_w; x++) {
int src_idx = src(x, y, img.m_w);
int dst_idx = dst(x, y, img.m_w);
for (int i = 0; i < bpp; i++) {
img.m_buffer[dst_idx + i] = cpy[src_idx + i];
}
}
}
}
} // namespace amy

7
source/renderer.cpp Executable file
View File

@@ -0,0 +1,7 @@
#include <amethyst/renderer.hpp>
namespace amy {
Renderer::Renderer() {}
Renderer::~Renderer() {}
} // namespace amy

84
source/texture.cpp Executable file
View File

@@ -0,0 +1,84 @@
#include <3ds.h>
#include <algorithm>
#include <amethyst/image.hpp>
#include <amethyst/texture.hpp>
#include <amethyst/utils.hpp>
#include <stdexcept>
namespace amy {
ui tile3dsTex(int x, int y, int w) {
return ((((y >> 3) * ((int)w >> 3) + (x >> 3)) << 6) +
((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) |
((x & 4) << 2) | ((y & 4) << 3)));
}
GPU_TEXCOLOR image2TexFmt(const image::format& fmt) {
switch (fmt) {
case image::RGB:
return GPU_RGB8;
break;
case image::RGBA:
return GPU_RGBA8;
break;
case image::RGB565:
return GPU_RGB565;
break;
default:
// Dummy
return GPU_A4;
throw std::runtime_error(
"[amy] texture: Unsupported texture format used!");
break;
}
}
texture::texture(cstr& path) { load(path); }
texture::~texture() { unload(); }
void texture::unload() {
if (m_loaded) {
C3D_TexDelete(&m_tex);
m_loaded = false;
}
}
void texture::load(cstr& path) {
unload();
image img(path);
if (img.width() > 1024 || img.height() > 1024) {
throw std::runtime_error("Max Texture Size is 1024x1024!");
}
int bpp = img.bpp();
m_w = img.width();
if (utils::isSingleBitNum(m_w)) {
m_w = utils::nextPow2(m_w);
}
m_h = img.width();
if (utils::isSingleBitNum(m_h)) {
m_h = utils::nextPow2(m_h);
}
auto filter = GPU_NEAREST;
auto format = image2TexFmt(img.fmt());
C3D_TexInit(&m_tex, (u16)m_w, (u16)m_h, format);
C3D_TexSetFilter(&m_tex, filter, filter);
// Using std::fill_n instead cause i hate this error lines
// under the memset func in my editor
std::fill_n((unsigned char*)m_tex.data, m_tex.size, 0);
for (int x = 0; x < img.width(); x++) {
for (int y = 0; y < img.height(); y++) {
int dst_pos = tile3dsTex(x, y, m_w) * bpp;
int src_pos = (y * img.width() + x) * bpp;
/// Best idea i had
for (int i = 0; i < bpp; i++) {
((u8*)m_tex.data)[dst_pos + bpp - 1 - i] = img[src_pos + i];
}
}
}
C3D_TexFlush(&m_tex);
m_tex.border = 0x00000000;
C3D_TexSetWrap(&m_tex, GPU_REPEAT, GPU_REPEAT);
m_loaded = true;
}
} // namespace amy

92
source/utils.cpp Executable file
View File

@@ -0,0 +1,92 @@
#include <amethyst/utils.hpp>
#include <fstream>
namespace amy {
namespace utils {
ui fastHash(cstr& s) {
ui hash = 5381;
for (auto& it : s) {
hash = (hash * 33) + static_cast<uc>(it);
}
return hash;
}
vec<uc> loadFile2Mem(cstr& path) {
std::ifstream iff(path, std::ios::binary);
if (!iff) {
return vec<uc>();
}
iff.seekg(0, std::ios::end);
size_t szs = iff.tellg();
iff.seekg(0, std::ios::beg);
vec<uc> res(szs, 0);
iff.read(reinterpret_cast<char*>(res.data()), res.size());
iff.close();
return res;
}
ui hashMemory(cvec<uc>& data) {
ui hash = 4477;
for (auto& it : data) {
hash = (hash * 33) + it;
}
return hash;
}
ui nextPow2(ui v) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return (v >= 64 ? v : 64);
}
bool isSingleBitNum(ui v) { return v && !(v & (v - 1)); }
namespace image {
void reverseBuf(vec<uc>& buf, int w, int h, int c) {
vec<uc> cpy = buf;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
int pos = (y * w + x) * c;
for (size_t i = 0; i < c; i++) {
buf[pos + c - 1 - i] = cpy[pos + i];
}
}
}
}
void removeAlphaChannel(vec<uc>& buf, int w, int h) {
vec<uc> cpy = buf;
buf.resize(w * h * 3);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int src = (y * w + x) * 4;
int dst = (y * w + x) * 3;
buf[dst + 0] = cpy[src + 0];
buf[dst + 1] = cpy[src + 1];
buf[dst + 2] = cpy[src + 2];
}
}
}
void addAlphaChannel(vec<uc>& buf, int w, int h) {
vec<uc> cpy = buf;
buf.resize(w * h * 4);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int src = (y * w + x) * 3;
int dst = (y * w + x) * 4;
buf[dst + 0] = cpy[src + 0];
buf[dst + 1] = cpy[src + 1];
buf[dst + 2] = cpy[src + 2];
buf[dst + 3] = 255;
}
}
}
} // namespace image
} // namespace utils
} // namespace amy