Initial Commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
*.shbin
|
||||
25
CMakeLists.txt
Executable file
25
CMakeLists.txt
Executable 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
6
README.md
Normal file → Executable 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
42
example/CMakeLists.txt
Executable 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
BIN
example/romfs/icon.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 844 B |
0
example/romfs/shaders/.gitkeep
Executable file
0
example/romfs/shaders/.gitkeep
Executable file
34
example/shaders/lithium.v.pica
Executable file
34
example/shaders/lithium.v.pica
Executable 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
38
example/shaders/shader2d.v.pica
Executable 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
36
example/source/main.cpp
Executable 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
12
include/amethyst.hpp
Executable 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
15
include/amethyst/app.hpp
Normal 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
9
include/amethyst/asset.hpp
Executable 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
34
include/amethyst/assets.hpp
Executable 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
76
include/amethyst/c3d.hpp
Executable 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
14
include/amethyst/ctru.hpp
Normal 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
|
||||
14
include/amethyst/godtrace.hpp
Normal file
14
include/amethyst/godtrace.hpp
Normal 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
55
include/amethyst/image.hpp
Executable 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
9
include/amethyst/renderer.hpp
Executable file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace amy {
|
||||
class Renderer {
|
||||
public:
|
||||
Renderer();
|
||||
~Renderer();
|
||||
};
|
||||
} // namespace amys
|
||||
30
include/amethyst/texture.hpp
Executable file
30
include/amethyst/texture.hpp
Executable 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
|
||||
19
include/amethyst/types.hpp
Normal file
19
include/amethyst/types.hpp
Normal 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
18
include/amethyst/utils.hpp
Executable 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
7988
include/stb_image.h
Executable file
File diff suppressed because it is too large
Load Diff
35
source/amethyst.cpp
Executable file
35
source/amethyst.cpp
Executable 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
74
source/c3d.cpp
Executable 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
16
source/ctru.cpp
Normal 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
149
source/image.cpp
Executable 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
7
source/renderer.cpp
Executable file
@@ -0,0 +1,7 @@
|
||||
#include <amethyst/renderer.hpp>
|
||||
|
||||
namespace amy {
|
||||
Renderer::Renderer() {}
|
||||
|
||||
Renderer::~Renderer() {}
|
||||
} // namespace amy
|
||||
84
source/texture.cpp
Executable file
84
source/texture.cpp
Executable 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
92
source/utils.cpp
Executable 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
|
||||
Reference in New Issue
Block a user