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

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
build/
*.shbin

25
CMakeLists.txt Executable file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.22)
project(amethyst)
option(AMY_BUILD_3DS "Build for 3ds" ON)
option(AMY_GOD_DEV "Turn this on if you think you are god" OFF)
add_library(${PROJECT_NAME} STATIC
source/amethyst.cpp
source/image.cpp
source/renderer.cpp
source/texture.cpp
source/utils.cpp
source/c3d.cpp
source/ctru.cpp
)
target_include_directories(${PROJECT_NAME} PUBLIC include)
if(${AMY_BUILD_3DS})
target_link_libraries(${PROJECT_NAME} PUBLIC m z ctru citro3d)
target_compile_definitions(${PROJECT_NAME} PUBLIC AMY_3DS)
endif()
add_subdirectory(example)
install(TARGETS ${PROJECT_NAME})

6
README.md Normal file → Executable file
View File

@@ -1,3 +1,3 @@
# amethyst
3ds high performance rendering lib
# amethyst
2d Renderer Library made for ReCraft3DS based on palladium.

42
example/CMakeLists.txt Executable file
View File

@@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.22)
find_program(PICASSO NAMES picasso REQUIRED)
# Function origanally Created in Re-Craft-3DS
function(__amy_make_shader arg1 arg2)
# These only exist cause i was stupid
set(__FILE ${arg1})
set(__NAME ${arg2})
# Need to build shaders during config stage :(
execute_process(
COMMAND ${PICASSO} -o "${CMAKE_CURRENT_SOURCE_DIR}/romfs/shaders/${__NAME}.shbin" "${__FILE}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
endfunction()
project(amethyst-example)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED true)
__amy_make_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/shader2d.v.pica shader2d)
__amy_make_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/lithium.v.pica lithium)
add_executable(${PROJECT_NAME} source/main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE amethyst)
target_compile_options(${PROJECT_NAME} PRIVATE -O2)
ctr_generate_smdh(
${CMAKE_BINARY_DIR}/${PROJECT_NAME}.smdh
NAME "Amethyst Example"
DESCRIPTION "Example of Amethyst lib"
AUTHOR "tobid7"
ICON "${CMAKE_CURRENT_SOURCE_DIR}/romfs/icon.png"
)
ctr_create_3dsx(
${PROJECT_NAME}
OUTPUT "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.3dsx"
SMDH "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.smdh"
ROMFS "${CMAKE_CURRENT_SOURCE_DIR}/romfs"
)

BIN
example/romfs/icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

0
example/romfs/shaders/.gitkeep Executable file
View File

34
example/shaders/lithium.v.pica Executable file
View File

@@ -0,0 +1,34 @@
; LI7 Shader
; Constants
.constf myconst(0.0, 1.0, 0.00392156862745, 0.0)
.alias ones myconst.yyyy ; Vector full of ones
; Uniforms
.fvec projection[4]
; Outputs
.out out_position position
.out out_color color
.out out_uv texcoord0
; Inputs
.alias in_xy v0
.alias in_uvc v1
.alias in_col v2
.entry vmain
.proc vmain
mov r0.xy, in_xy.xy
mov r0.w, ones
dp4 out_position.x, projection[0], r0
dp4 out_position.y, projection[1], r0
dp4 out_position.z, projection[2], r0
dp4 out_position.w, projection[3], r0
mov out_uv, in_uvc.xy
mul r1, myconst.zzzz, in_col
mov out_color, r1
end
.end

38
example/shaders/shader2d.v.pica Executable file
View File

@@ -0,0 +1,38 @@
; Example PICA200 vertex shader
; Uniforms
.fvec projection[4]
; Constants
.constf myconst(0.0, 1.0, -1.0, 0.1)
.constf myconst2(0.3, 0.0, 0.0, 0.0)
.alias zeros myconst.xxxx ; Vector full of zeros
.alias ones myconst.yyyy ; Vector full of ones
; Outputs
.out outpos position
.out outclr color
; Inputs (defined as aliases for convenience)
.alias inpos v0
.alias inclr v1
.bool test
.proc main
; Force the w component of inpos to be 1.0
mov r0.xyz, inpos
mov r0.w, ones
; outpos = projectionMatrix * inpos
dp4 outpos.x, projection[0], r0
dp4 outpos.y, projection[1], r0
dp4 outpos.z, projection[2], r0
dp4 outpos.w, projection[3], r0
; outclr = inclr
mov outclr, inclr
; We're finished
end
.end

36
example/source/main.cpp Executable file
View File

@@ -0,0 +1,36 @@
#include <amethyst.hpp>
static C3D_Mtx projection;
int main() {
amy::registerCxxExceptionHandler();
amy::ctru::init();
amy::c3d::init();
auto top = amy::c3d::createScreen(GFX_TOP, GFX_LEFT);
auto shader = new amy::c3d::shader("romfs:/shaders/shader2d.shbin");
shader->input(GPU_FLOAT, 3);
shader->input(GPU_FLOAT, 3);
amy::c3d::frag::edit();
amy::c3d::frag::src(C3D_Both);
amy::c3d::frag::func(C3D_Both, GPU_REPLACE);
Mtx_Identity(&projection);
Mtx_OrthoTilt(&projection, 0.0, 400.0, 0.0, 240.0, 0.0, 1.0, true);
while (aptMainLoop()) {
amy::c3d::startFrame();
top->startDraw();
top->clear();
shader->use();
shader->setMat4(0, &projection);
C3D_ImmDrawBegin(GPU_TRIANGLES);
C3D_ImmSendAttrib(200, 200, 0.5, 0);
C3D_ImmSendAttrib(1, 0, 0, 1);
C3D_ImmSendAttrib(100, 40, 0.5, 0);
C3D_ImmSendAttrib(0, 1, 0, 1);
C3D_ImmSendAttrib(300, 40, 0.5, 0);
C3D_ImmSendAttrib(0, 0, 1, 1);
C3D_ImmDrawEnd();
amy::c3d::endFrame();
}
amy::c3d::deleteScreen(top);
return 0;
}

12
include/amethyst.hpp Executable file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <amethyst/assets.hpp>
#include <amethyst/c3d.hpp>
#include <amethyst/ctru.hpp>
#include <amethyst/image.hpp>
#include <amethyst/renderer.hpp>
#include <amethyst/texture.hpp>
namespace amy {
void registerCxxExceptionHandler();
}

15
include/amethyst/app.hpp Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <amethyst/asset.hpp>
namespace amy {
class app {
public:
app() {}
~app() {}
void run();
private:
};
} // namespace amy

9
include/amethyst/asset.hpp Executable file
View File

@@ -0,0 +1,9 @@
#pragma once
namespace amy {
class asset {
public:
asset() = default;
virtual ~asset() = default;
};
} // namespace amy

34
include/amethyst/assets.hpp Executable file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <amethyst/texture.hpp>
#include <amethyst/types.hpp>
namespace amy {
class assets {
public:
assets() = default;
~assets() = default;
void add(cstr& id, asset* v) { m_assets[id] = v; }
void remove(cstr& id) {
if (m_assets.count(id)) {
m_assets.erase(id);
}
}
template <typename T>
T* get(cstr& id) {
auto r = m_assets.find(id);
if (r == m_assets.end()) {
throw std::runtime_error("[amy] assets: unable to find " + id);
}
if (auto v = dynamic_cast<T*>(r->second)) {
return v;
} else {
throw std::runtime_error(id + " is not of type " + typeid(T).name());
}
}
private:
std::map<str, asset*> m_assets;
};
} // namespace amy

76
include/amethyst/c3d.hpp Executable file
View File

@@ -0,0 +1,76 @@
#pragma once
#include <3ds.h>
#include <citro3d.h>
#include <amethyst/asset.hpp>
#include <string>
namespace amy {
class c3d {
public:
c3d() = default;
~c3d() = default;
class screen {
public:
screen(C3D_RenderTarget* t) {
m_target = t;
// Width and height are swapped on 3ds due to screen layout
m_width = m_target->frameBuf.height;
m_height = m_target->frameBuf.width;
}
~screen() { C3D_RenderTargetDelete(m_target); }
int width() const { return m_width; }
int height() const { return m_height; }
void clear() { C3D_RenderTargetClear(m_target, C3D_CLEAR_ALL, 0, 0); }
void startDraw() { C3D_FrameDrawOn(m_target); }
private:
C3D_RenderTarget* m_target = nullptr;
int m_width = 0;
int m_height = 0;
};
class shader : public asset {
public:
shader(const std::string& path);
~shader();
void load(const std::string& path);
void use();
void input(int reg, GPU_FORMATS f, int num);
void input(GPU_FORMATS f, int num) { input(m_reg++, f, num); }
void setMat4(int loc, C3D_Mtx* m);
private:
C3D_AttrInfo m_info;
shaderProgram_s m_program;
DVLB_s* m_code;
int m_reg = 0;
};
class frag {
public:
frag() = default;
~frag() = default;
static void edit(int id = 0);
static void src(C3D_TexEnvMode mode, GPU_TEVSRC s1 = GPU_PRIMARY_COLOR,
GPU_TEVSRC s2 = GPU_PRIMARY_COLOR,
GPU_TEVSRC s3 = GPU_PRIMARY_COLOR);
static void func(C3D_TexEnvMode mode, GPU_COMBINEFUNC func);
private:
static C3D_TexEnv* m_env;
};
static void init();
static void deinit();
static void startFrame(bool sync = true);
static void endFrame();
static screen* createScreen(gfxScreen_t screen, gfx3dSide_t side = GFX_LEFT);
static void deleteScreen(screen* screen);
};
} // namespace amy

14
include/amethyst/ctru.hpp Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
namespace amy {
namespace ctru {
enum services {
romfs = 1 << 0,
cfgu = 1 << 1,
gfx = 1 << 2,
gfx_def = 1 << 3,
def = romfs | gfx_def
};
void init(unsigned int srv = def);
} // namespace ctru
} // namespace amy

View File

@@ -0,0 +1,14 @@
#pragma once
#include <string>
namespace amy {
class gtrace {
public:
gtrace() = default;
~gtrace() = default;
private:
};
}

55
include/amethyst/image.hpp Executable file
View File

@@ -0,0 +1,55 @@
#pragma once
#include <amethyst/types.hpp>
namespace amy {
class image {
public:
enum format {
RGBA, // bpp == 4
RGB, // bpp == 3
RGB565, // bpp == 2 (not supported in laoding)
BGR, // bpp == 3
ABGR, // bpp == 4
BGRA, // bpp == 4
};
image() = default;
image(cstr& path) { this->load(path); }
image(const std::vector<uc>& buf) { this->load(buf); }
image(const std::vector<uc>& buf, int w, int h, const format& fmt) {
this->copy(buf, w, h, fmt);
}
~image() = default;
void load(cstr& path);
void load(const std::vector<uc>& buf);
void copy(const std::vector<uc>& buf, int w, int h, const format& fmt);
std::vector<uc>& getBuffer() { return m_buffer; }
std::vector<uc> getBuffer() const { return m_buffer; }
int width() const { return m_w; }
int height() const { return m_h; }
int bpp() const { return getBppOfFmt(m_fmt); }
format fmt() const { return m_fmt; }
void convert(const format& dst) { convert(*this, dst); }
void retile(std::function<ui(int x, int y, int w)> src,
std::function<ui(int x, int y, int w)> dst) {
retile(*this, src, dst);
}
uc& operator[](int idx) { return m_buffer[idx]; }
uc operator[](int idx) const { return m_buffer[idx]; }
static void convert(image& img, const format& dst);
static void retile(image& img, std::function<ui(int x, int y, int w)> src,
std::function<ui(int x, int y, int w)> dst);
static int getBppOfFmt(const format& fmt);
private:
std::vector<uc> m_buffer;
int m_w = 0;
int m_h = 0;
format m_fmt = RGBA;
};
} // namespace amy

9
include/amethyst/renderer.hpp Executable file
View File

@@ -0,0 +1,9 @@
#pragma once
namespace amy {
class Renderer {
public:
Renderer();
~Renderer();
};
} // namespace amys

30
include/amethyst/texture.hpp Executable file
View File

@@ -0,0 +1,30 @@
#pragma once
#include <citro3d.h>
#include <amethyst/asset.hpp>
#include <amethyst/image.hpp>
namespace amy {
class texture : public asset {
public:
texture() = default;
texture(cstr& path);
~texture();
void load(cstr& path);
void unload();
int w() const { return m_w; }
int& w() { return m_w; }
int h() const { return m_h; }
int& h() { return m_h; }
C3D_Tex* getTex() { return m_loaded ? &m_tex : nullptr; }
private:
C3D_Tex m_tex;
int m_w = 0;
int m_h = 0;
bool m_loaded = false;
};
} // namespace amy

View File

@@ -0,0 +1,19 @@
#pragma once
#include <functional>
#include <map>
#include <stdexcept>
#include <string>
#include <vector>
namespace amy {
using uc = unsigned char;
using us = unsigned short;
using ui = unsigned int;
using ull = unsigned long long;
using str = std::string;
using cstr = const std::string;
template <typename T> using vec = std::vector<T>;
template <typename T>
using cvec = const std::vector<T>;
} // namespace amy

18
include/amethyst/utils.hpp Executable file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <amethyst/types.hpp>
namespace amy {
namespace utils {
ui fastHash(cstr& s);
vec<uc> loadFile2Mem(cstr& path);
ui hashMemory(cvec<uc>& data);
ui nextPow2(ui v);
bool isSingleBitNum(ui v);
namespace image {
void reverseBuf(vec<uc>& buf, int w, int h, int c);
void removeAlphaChannel(vec<uc>& buf, int w, int h);
void addAlphaChannel(vec<uc>& buf, int w, int h);
} // namespace image
} // namespace utils
} // namespace amy

7988
include/stb_image.h Executable file

File diff suppressed because it is too large Load Diff

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