From 1ff768935ed25c4a726b2dee2455d97f77c64b66 Mon Sep 17 00:00:00 2001 From: Joshua Peisach Date: Mon, 30 Jun 2025 22:43:10 -0400 Subject: [PATCH] 3DS Port (#450) * [WIP] 3ds port Recommit of everything after the 2d renderer merge * VERY AWESOME FEATURE FRFR * Stopped CPU suicide and app crashing for now * put in Texture3DS function thing * Fix clear color * Implement 2D rendering via Citro3D * Set 3dsx smdh metadata * Render world content, sort of * Push mesh dynamically * Remove Citro3D init hacks * Clean up Citro3D implementation * Try to upload meshes and convert matricies * Fix 3D rendering * Apply optimizations * Implement lighting * Set 3dsx smdh metadata * Revert "Apply optimizations" This reverts commit 6660082fefb2cebbeeec8428abd8508099f93ad4. * Apply optimizations * Added a cleaner icon (#4) * Fix pure buffer clear frames (#9) * Disable OpenGL on 3DS (#10) * Fix tiled textures and improve UI image quality (#11) * Create 3DS default config overrides * 3ds: implement apt hooks * remove unused import * Apply suggestions from code review Co-authored-by: Christian Semmler Co-authored-by: Anonymous Maarten * Update miniwin/src/d3drm/backends/citro3d/renderer.cpp Co-authored-by: Anonymous Maarten * Separate 3DS apt hook code + move cmake 3ds into ISLE_BUILD_APP * miniwin: use citro3dd if debugging * Optimize texture encoding (#12) * Cleanup * Set correct mipmap level for UI textures (#13) * cpack: include the .3dsx * Add 3DS CI * Fix CI Co-authored-by: Anonymous Maarten * syntax * Refactor c3d renderer (#14) * Refactor c3d renderer * format * Apply suggestions from code review Co-authored-by: Anders Jenbo --------- Co-authored-by: Anders Jenbo * n3ds: just distribute the .3dsx * upload 3dsx * Skip uploading 3DS artifacts * Update ci.yml * Update ci.yml * Remove extraneous ifdef --------- Co-authored-by: MaxBrick Co-authored-by: Anders Jenbo Co-authored-by: Steven <139715581+StevenSYS@users.noreply.github.com> Co-authored-by: Christian Semmler Co-authored-by: Anonymous Maarten --- .github/workflows/ci.yml | 12 + CMakeLists.txt | 22 +- ISLE/3ds/apthooks.cpp | 30 + ISLE/3ds/apthooks.h | 9 + ISLE/3ds/config.cpp | 22 + ISLE/3ds/config.h | 8 + ISLE/isleapp.cpp | 13 +- ISLE/isleapp.h | 1 + ISLE/isledebug.cpp | 2 +- ISLE/res/3ds/isle.png | Bin 0 -> 438 bytes miniwin/CMakeLists.txt | 17 + .../src/d3drm/backends/citro3d/renderer.cpp | 606 ++++++++++++++++++ .../src/d3drm/backends/citro3d/vshader.v.pica | 130 ++++ .../src/d3drm/backends/software/renderer.cpp | 2 +- miniwin/src/d3drm/d3drm.cpp | 8 + miniwin/src/d3drm/d3drmdevice.cpp | 4 + miniwin/src/ddraw/ddraw.cpp | 11 + miniwin/src/internal/d3drmrenderer_citro3d.h | 82 +++ 18 files changed, 975 insertions(+), 4 deletions(-) create mode 100644 ISLE/3ds/apthooks.cpp create mode 100644 ISLE/3ds/apthooks.h create mode 100644 ISLE/3ds/config.cpp create mode 100644 ISLE/3ds/config.h create mode 100644 ISLE/res/3ds/isle.png create mode 100644 miniwin/src/d3drm/backends/citro3d/renderer.cpp create mode 100644 miniwin/src/d3drm/backends/citro3d/vshader.v.pica create mode 100644 miniwin/src/internal/d3drmrenderer_citro3d.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6124f37..57d6e539 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,7 @@ jobs: build: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} + container: ${{ matrix.container || '' }} defaults: run: shell: ${{ matrix.shell || 'sh' }} @@ -41,6 +42,7 @@ jobs: - { name: 'msys2 mingw64', os: 'windows-latest', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } - { name: 'macOS', os: 'macos-latest', dx5: false, config: true, brew: true, werror: true, clang-tidy: false } - { name: 'Emscripten', os: 'ubuntu-latest', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' } + - { name: 'Nintendo 3DS', os: 'ubuntu-latest', dx5: false, config: false, n3ds: true, werror: true, clang-tidy: false, container: 'devkitpro/devkitarm:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake' } steps: - name: Setup vcvars if: ${{ !!matrix.msvc }} @@ -89,6 +91,7 @@ jobs: - name: Configure (CMake) run: | ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \ + ${{ matrix.cmake-args || '' }} \ -DCMAKE_BUILD_TYPE=Release \ -DISLE_USE_DX5=${{ !!matrix.dx5 }} \ -DISLE_BUILD_CONFIG=${{ !!matrix.config }} \ @@ -101,6 +104,7 @@ jobs: run: cmake --build build --verbose - name: Package (CPack) + if: ${{ !matrix.n3ds }} run: | cd build cpack . @@ -128,6 +132,13 @@ jobs: --output appimage && \ mv *.AppImage dist/ + - name: Package (3DS) + if: ${{ matrix.n3ds }} + run: | + cd build + mkdir dist + mv *.3dsx dist/ + - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: @@ -135,6 +146,7 @@ jobs: path: | build/dist/isle-* build/dist/*.AppImage + build/dist/*.3dsx flatpak: name: "Flatpak (${{ matrix.arch }})" diff --git a/CMakeLists.txt b/CMakeLists.txt index ac3663fc..e9812aea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ option(ISLE_WERROR "Treat warnings as errors" OFF) option(ISLE_DEBUG "Enable imgui debug" ON) cmake_dependent_option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" "${NOT_MINGW}" "WIN32;CMAKE_SIZEOF_VOID_P EQUAL 4" OFF) cmake_dependent_option(ISLE_MINIWIN "Use miniwin" ON "NOT ISLE_USE_DX5" OFF) -cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN" OFF) +cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS" OFF) cmake_dependent_option(ISLE_COMPILE_SHADERS "Compile shaders" ON "SDL_SHADERCROSS_BIN;TARGET Python3::Interpreter" OFF) option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON) option(ENABLE_CLANG_TIDY "Enable clang-tidy") @@ -523,6 +523,12 @@ if (ISLE_BUILD_APP) target_compile_definitions(isle PRIVATE "ISLE_EMSCRIPTEN_HOST=\"${ISLE_EMSCRIPTEN_HOST}\"") set_property(TARGET isle PROPERTY SUFFIX ".html") endif() + if(NINTENDO_3DS) + target_sources(isle PRIVATE + ISLE/3ds/apthooks.cpp + ISLE/3ds/config.cpp + ) + endif() endif() if (ISLE_BUILD_CONFIG) @@ -652,9 +658,23 @@ add_subdirectory(packaging) set(CPACK_PACKAGE_DIRECTORY "dist") set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}") +if(NINTENDO_3DS) + ctr_generate_smdh(isle.smdh + NAME "LEGO Island" + TITLE "LEGO Island" + DESCRIPTION "LEGO Island for the Nintendo 3DS" + AUTHOR "isledecomp/isle-portable" + VERSION "${PROJECT_VERSION}" + ICON "ISLE/res/3ds/isle.png" + ) + + ctr_create_3dsx(isle SMDH isle.smdh) + install(FILES "$/isle.3dsx" DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() if(MSVC) set(CPACK_GENERATOR ZIP) else() set(CPACK_GENERATOR TGZ) endif() + include(CPack) diff --git a/ISLE/3ds/apthooks.cpp b/ISLE/3ds/apthooks.cpp new file mode 100644 index 00000000..3cd9c3e1 --- /dev/null +++ b/ISLE/3ds/apthooks.cpp @@ -0,0 +1,30 @@ +#include "apthooks.h" + +#include "legomain.h" +#include "misc.h" + +aptHookCookie g_aptCookie; + +void N3DS_AptHookCallback(APT_HookType hookType, void* param) +{ + switch (hookType) { + case APTHOOK_ONSLEEP: + case APTHOOK_ONSUSPEND: + Lego()->Pause(); + break; + case APTHOOK_ONWAKEUP: + case APTHOOK_ONRESTORE: + Lego()->Resume(); + break; + case APTHOOK_ONEXIT: + Lego()->CloseMainWindow(); + break; + default: + break; + } +} + +void N3DS_SetupAptHooks() +{ + aptHook(&g_aptCookie, N3DS_AptHookCallback, NULL); +} diff --git a/ISLE/3ds/apthooks.h b/ISLE/3ds/apthooks.h new file mode 100644 index 00000000..7bfa30f7 --- /dev/null +++ b/ISLE/3ds/apthooks.h @@ -0,0 +1,9 @@ +#ifndef N3DS_APTHOOKS_H +#define N3DS_APTHOOKS_H + +#include <3ds.h> + +void N3DS_AptHookCallback(APT_HookType hookType, void* param); +void N3DS_SetupAptHooks(); + +#endif // N3DS_APTHOOKS_H diff --git a/ISLE/3ds/config.cpp b/ISLE/3ds/config.cpp new file mode 100644 index 00000000..dc5dd8dc --- /dev/null +++ b/ISLE/3ds/config.cpp @@ -0,0 +1,22 @@ +#include "config.h" + +#include +#include + +void N3DS_SetupDefaultConfigOverrides(dictionary* p_dictionary) +{ + SDL_Log("Overriding default config for 3DS"); + + // We are currently not bundling the assets into romfs. + // User must place assets in sdmc:/3ds/isle where + // sdmc:/3ds/isle/LEGO/SCRIPTS/CREDITS.si exists, for example. + iniparser_set(p_dictionary, "isle:diskpath", "sdmc:/3ds/isle/LEGO/disk"); + iniparser_set(p_dictionary, "isle:cdpath", "sdmc:/3ds/isle"); + + // TODO: Save path: can we use libctru FS save data functions? Would be neat, especially for CIA install + // Extra / at the end causes some issues + iniparser_set(p_dictionary, "isle:savepath", "sdmc:/3ds/isle"); + + // Use e_noAnimation/cut transition + iniparser_set(p_dictionary, "isle:Transition Type", "1"); +} diff --git a/ISLE/3ds/config.h b/ISLE/3ds/config.h new file mode 100644 index 00000000..64d3a1b0 --- /dev/null +++ b/ISLE/3ds/config.h @@ -0,0 +1,8 @@ +#ifndef N3DS_CONFIG_H +#define N3DS_CONFIG_H + +#include "dictionary.h" + +void N3DS_SetupDefaultConfigOverrides(dictionary* p_dictionary); + +#endif // N3DS_CONFIG_H diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index e700d5b2..e12f9183 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -50,6 +50,11 @@ #include "emscripten/messagebox.h" #endif +#ifdef __3DS__ +#include "3ds/apthooks.h" +#include "3ds/config.h" +#endif + DECOMP_SIZE_ASSERT(IsleApp, 0x8c) // GLOBAL: ISLE 0x410030 @@ -313,6 +318,9 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) }, NULL ); +#endif +#ifdef __3DS__ + N3DS_SetupAptHooks(); #endif return SDL_APP_CONTINUE; } @@ -654,7 +662,7 @@ MxResult IsleApp::SetupWindow() SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight); SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, WINDOW_TITLE); -#ifdef MINIWIN +#if defined(MINIWIN) && !defined(__3DS__) SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); @@ -825,6 +833,9 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10)); iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); +#ifdef __3DS__ + N3DS_SetupDefaultConfigOverrides(dict); +#endif iniparser_dump_ini(dict, iniFP); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig); fclose(iniFP); diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index dd7bfdb1..d0a7f523 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -96,6 +96,7 @@ class IsleApp { }; extern IsleApp* g_isle; +extern MxS32 g_closed; extern IDirect3DRMMiniwinDevice* GetD3DRMMiniwinDevice(); diff --git a/ISLE/isledebug.cpp b/ISLE/isledebug.cpp index a8950fa3..ec27004b 100644 --- a/ISLE/isledebug.cpp +++ b/ISLE/isledebug.cpp @@ -309,7 +309,7 @@ void IsleDebug_Render() if (ImGui::TreeNode("Sound Manager")) { LegoSoundManager* soundManager = lego->GetSoundManager(); Sint32 oldVolume = soundManager->GetVolume(); - Sint32 volume = oldVolume; + int volume = oldVolume; ImGui::SliderInt("volume", &volume, 0, 100); if (volume != oldVolume) { soundManager->SetVolume(volume); diff --git a/ISLE/res/3ds/isle.png b/ISLE/res/3ds/isle.png new file mode 100644 index 0000000000000000000000000000000000000000..8fd9f2c4ea61bad4ac49ef913f4f0a5eba8a507d GIT binary patch literal 438 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-st^qzFu0WcB;lD8hLnyuVVNQ6as5u0Lg)PKpf|zsSgwsED7=phDalk zhh@*cV_;z9_jGX#@i_i=awp#*1p$}s+rC9ar-uIc?=KpVF@5osD>G(TWHwx=Ol)9n z35ZHkjF|q_IO(6ero KMWF%kqVNX_gEV;R43gF*y7-$W0otxA=crn+jmt*e4du; z@3NDj&!m(NMP1bncyujj;v4pr4l7q1BA3ysTzx`at{3CV2`)-H%tUHVt4y;xA!EC>l{bX2DLD};SUZ9ZjboFyt=akR{ E03U~_mH+?% literal 0 HcmV?d00001 diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index 508e685d..851403cc 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -54,6 +54,23 @@ else() message(STATUS "🧩 OpenGL ES 2.x support not enabled") endif() +if(NINTENDO_3DS) + if(ISLE_DEBUG) + find_library(CITRO3D_LIBRARY NAMES citro3dd) + else() + find_library(CITRO3D_LIBRARY NAMES citro3d) + endif() + if(CITRO3D_LIBRARY) + message(STATUS "Found citro3d: enabling Citro3D renderer") + target_sources(miniwin PRIVATE src/d3drm/backends/citro3d/renderer.cpp) + ctr_add_shader_library(vshader src/d3drm/backends/citro3d/vshader.v.pica) + dkp_add_embedded_binary_library(3ds_shaders vshader) + target_link_libraries(miniwin PRIVATE ${CITRO3D_LIBRARY} 3ds_shaders) + else() + message(STATUS "🧩 Citro3D support not enabled") + endif() +endif() + if(WIN32) target_sources(miniwin PRIVATE src/d3drm/backends/directx9/actual.cpp diff --git a/miniwin/src/d3drm/backends/citro3d/renderer.cpp b/miniwin/src/d3drm/backends/citro3d/renderer.cpp new file mode 100644 index 00000000..f9167401 --- /dev/null +++ b/miniwin/src/d3drm/backends/citro3d/renderer.cpp @@ -0,0 +1,606 @@ +#include "d3drmrenderer.h" +#include "d3drmrenderer_citro3d.h" +#include "d3drmtexture_impl.h" +#include "ddraw_impl.h" +#include "meshutils.h" +#include "miniwin.h" +#include "vshader_shbin.h" + +#include + +static bool g_rendering = false; + +static DVLB_s* vshader_dvlb; +static shaderProgram_s program; +static int uLoc_projection; +static int uLoc_modelView; +static int uLoc_meshColor; +static int uLoc_lightVec; +static int uLoc_lightClr; +static int uLoc_shininess; + +Citro3DRenderer::Citro3DRenderer(DWORD width, DWORD height) +{ + m_width = 320; + m_height = 240; + m_virtualWidth = width; + m_virtualHeight = height; + + gfxInitDefault(); + consoleInit(GFX_TOP, nullptr); + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); + + m_renderTarget = C3D_RenderTargetCreate(m_height, m_width, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetOutput( + m_renderTarget, + GFX_BOTTOM, + GFX_LEFT, + 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) + ); + + vshader_dvlb = DVLB_ParseFile((u32*) vshader_shbin, vshader_shbin_size); + shaderProgramInit(&program); + shaderProgramSetVsh(&program, &vshader_dvlb->DVLE[0]); + C3D_BindProgram(&program); + + C3D_CullFace(GPU_CULL_FRONT_CCW); + + uLoc_projection = shaderInstanceGetUniformLocation(program.vertexShader, "projection"); + uLoc_modelView = shaderInstanceGetUniformLocation(program.vertexShader, "modelView"); + uLoc_meshColor = shaderInstanceGetUniformLocation(program.vertexShader, "meshColor"); + uLoc_lightVec = shaderInstanceGetUniformLocation(program.vertexShader, "lightVec"); + uLoc_lightClr = shaderInstanceGetUniformLocation(program.vertexShader, "lightClr"); + uLoc_shininess = shaderInstanceGetUniformLocation(program.vertexShader, "shininess"); + + C3D_AttrInfo* attrInfo = C3D_GetAttrInfo(); + AttrInfo_Init(attrInfo); + AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position + AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 3); // v2=normal + AttrInfo_AddLoader(attrInfo, 2, GPU_FLOAT, 2); // v1=texcoord +} + +Citro3DRenderer::~Citro3DRenderer() +{ + shaderProgramFree(&program); + DVLB_Free(vshader_dvlb); + C3D_Fini(); + gfxExit(); +} + +void Citro3DRenderer::PushLights(const SceneLight* lights, size_t count) +{ + m_lights.assign(lights, lights + count); +} + +void Citro3DRenderer::SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) +{ + memcpy(&m_projection, projection, sizeof(D3DRMMATRIX4D)); +} + +void Citro3DRenderer::SetFrustumPlanes(const Plane* frustumPlanes) +{ +} + +struct Citro3DCacheDestroyContext { + Citro3DRenderer* renderer; + Uint32 id; +}; + +void Citro3DRenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture) +{ + auto* ctx = new Citro3DCacheDestroyContext{this, id}; + texture->AddDestroyCallback( + [](IDirect3DRMObject* obj, void* arg) { + auto* ctx = static_cast(arg); + auto& entry = ctx->renderer->m_textures[ctx->id]; + if (entry.texture) { + C3D_TexDelete(&entry.c3dTex); + entry.texture = nullptr; + } + delete ctx; + }, + ctx + ); +} + +static int NearestPowerOfTwoClamp(int val) +{ + static const int sizes[] = {8, 16, 32, 64, 128, 256, 512}; + for (int size : sizes) { + if (val <= size) { + return size; + } + } + return 512; +} + +static SDL_Surface* ConvertAndResizeSurface(SDL_Surface* original, bool isUI, float scale) +{ + SDL_Surface* converted = SDL_ConvertSurface(original, SDL_PIXELFORMAT_RGBA8888); + if (!converted) { + return nullptr; + } + if (!isUI) { + return converted; + } + + int scaledW = static_cast(converted->w * scale); + int scaledH = static_cast(converted->h * scale); + + int paddedW = NearestPowerOfTwoClamp(scaledW); + int paddedH = NearestPowerOfTwoClamp(scaledH); + + SDL_Surface* padded = SDL_CreateSurface(paddedW, paddedH, SDL_PIXELFORMAT_RGBA8888); + if (!padded) { + SDL_DestroySurface(converted); + return nullptr; + } + + SDL_Rect dstRect = {0, 0, scaledW, scaledH}; + SDL_BlitSurfaceScaled(converted, nullptr, padded, &dstRect, SDL_SCALEMODE_LINEAR); + SDL_DestroySurface(converted); + + return padded; +} + +static void EncodeTextureLayout(const u8* src, u8* dst, int width, int height) +{ + const int tileSize = 8; + const int bytesPerPixel = 4; + + int tilesPerRow = (width + tileSize - 1) / tileSize; + + static const uint8_t mortonLUT[64] = {0, 1, 4, 5, 16, 17, 20, 21, 2, 3, 6, 7, 18, 19, 22, 23, + 8, 9, 12, 13, 24, 25, 28, 29, 10, 11, 14, 15, 26, 27, 30, 31, + 32, 33, 36, 37, 48, 49, 52, 53, 34, 35, 38, 39, 50, 51, 54, 55, + 40, 41, 44, 45, 56, 57, 60, 61, 42, 43, 46, 47, 58, 59, 62, 63}; + + for (int tileY = 0; tileY < height; tileY += tileSize) { + for (int tileX = 0; tileX < width; tileX += tileSize) { + int tileIndex = (tileY / tileSize) * tilesPerRow + (tileX / tileSize); + tileIndex *= tileSize * tileSize; + + for (int y = 0; y < tileSize; ++y) { + for (int x = 0; x < tileSize; ++x) { + int srcX = tileX + x; + int srcY = tileY + y; + + if (srcX >= width || srcY >= height) { + continue; + } + + int morton = mortonLUT[y * tileSize + x]; + int dstIndex = (tileIndex + morton) * bytesPerPixel; + int srcIndex = ((height - 1 - srcY) * width + srcX); + + *(u32*) &dst[dstIndex] = ((u32*) src)[srcIndex]; + } + } + } + } +} + +static bool ConvertAndUploadTexture(C3D_Tex* tex, SDL_Surface* originalSurface, bool isUI, float scale) +{ + SDL_Surface* resized = ConvertAndResizeSurface(originalSurface, isUI, scale); + if (!resized) { + return false; + } + + int width = resized->w; + int height = resized->h; + + C3D_TexInitParams params = {}; + params.width = width; + params.height = height; + params.format = GPU_RGBA8; + params.maxLevel = isUI ? 0 : 4; + params.type = GPU_TEX_2D; + if (!C3D_TexInitWithParams(tex, nullptr, params)) { + SDL_DestroySurface(resized); + return false; + } + + uint8_t* tiledData = (uint8_t*) malloc(width * height * 4); + if (!tiledData) { + SDL_DestroySurface(resized); + return false; + } + + EncodeTextureLayout((const u8*) resized->pixels, tiledData, width, height); + SDL_DestroySurface(resized); + + C3D_TexUpload(tex, tiledData); + free(tiledData); + + if (isUI) { + C3D_TexSetFilter(tex, GPU_NEAREST, GPU_NEAREST); + C3D_TexSetWrap(tex, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE); + } + else { + C3D_TexSetFilter(tex, GPU_LINEAR, GPU_LINEAR); + C3D_TexSetWrap(tex, GPU_REPEAT, GPU_REPEAT); + C3D_TexSetFilterMipmap(tex, GPU_LINEAR); + C3D_TexGenerateMipmap(tex, GPU_TEXFACE_2D); + } + + return true; +} + +Uint32 Citro3DRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) +{ + auto texture = static_cast(iTexture); + auto surface = static_cast(texture->m_surface); + SDL_Surface* originalSurface = surface->m_surface; + + int originalW = originalSurface->w; + int originalH = originalSurface->h; + + for (Uint32 i = 0; i < m_textures.size(); ++i) { + auto& tex = m_textures[i]; + if (tex.texture == texture) { + if (tex.version != texture->m_version) { + C3D_TexDelete(&tex.c3dTex); + if (!ConvertAndUploadTexture(&tex.c3dTex, originalSurface, isUi, m_viewportTransform.scale)) { + return NO_TEXTURE_ID; + } + + tex.version = texture->m_version; + tex.width = NearestPowerOfTwoClamp(originalW * m_viewportTransform.scale); + tex.height = NearestPowerOfTwoClamp(originalH * m_viewportTransform.scale); + } + return i; + } + } + + C3DTextureCacheEntry entry; + entry.texture = texture; + entry.version = texture->m_version; + entry.width = NearestPowerOfTwoClamp(originalW * m_viewportTransform.scale); + entry.height = NearestPowerOfTwoClamp(originalH * m_viewportTransform.scale); + + if (!ConvertAndUploadTexture(&entry.c3dTex, originalSurface, isUi, m_viewportTransform.scale)) { + return NO_TEXTURE_ID; + } + + for (Uint32 i = 0; i < m_textures.size(); ++i) { + if (!m_textures[i].texture) { + m_textures[i] = std::move(entry); + AddTextureDestroyCallback(i, texture); + return i; + } + } + + m_textures.push_back(std::move(entry)); + AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture); + return (Uint32) (m_textures.size() - 1); +} + +C3DMeshCacheEntry C3DUploadMesh(const MeshGroup& meshGroup) +{ + C3DMeshCacheEntry cache{&meshGroup, meshGroup.version}; + + std::vector vertexBuffer; + std::vector indexBuffer; + + if (meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT) { + FlattenSurfaces( + meshGroup.vertices.data(), + meshGroup.vertices.size(), + meshGroup.indices.data(), + meshGroup.indices.size(), + meshGroup.texture != nullptr, + vertexBuffer, + indexBuffer + ); + } + else { + vertexBuffer.assign(meshGroup.vertices.begin(), meshGroup.vertices.end()); + indexBuffer.assign(meshGroup.indices.begin(), meshGroup.indices.end()); + } + + // Flatten vertices as IBO is buggy on 3DS hardware + std::vector vertexUploadBuffer; + vertexUploadBuffer.reserve(indexBuffer.size()); + + for (size_t i = 0; i < indexBuffer.size(); ++i) { + vertexUploadBuffer.emplace_back(vertexBuffer[indexBuffer[i]]); + } + + size_t vertexBufferSize = vertexUploadBuffer.size() * sizeof(D3DRMVERTEX); + cache.vbo = linearAlloc(vertexBufferSize); + memcpy(cache.vbo, vertexUploadBuffer.data(), vertexBufferSize); + cache.vertexCount = vertexUploadBuffer.size(); + + return cache; +} + +void Citro3DRenderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh) +{ + auto* ctx = new Citro3DCacheDestroyContext{this, id}; + mesh->AddDestroyCallback( + [](IDirect3DRMObject* obj, void* arg) { + auto* ctx = static_cast(arg); + auto& cacheEntry = ctx->renderer->m_meshs[ctx->id]; + if (cacheEntry.meshGroup) { + cacheEntry.meshGroup = nullptr; + linearFree(cacheEntry.vbo); + cacheEntry.vertexCount = 0; + } + delete ctx; + }, + ctx + ); +} + +Uint32 Citro3DRenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) +{ + for (Uint32 i = 0; i < m_meshs.size(); ++i) { + auto& cache = m_meshs[i]; + if (cache.meshGroup == meshGroup) { + if (cache.version != meshGroup->version) { + cache = std::move(C3DUploadMesh(*meshGroup)); + } + return i; + } + } + + auto newCache = C3DUploadMesh(*meshGroup); + + for (Uint32 i = 0; i < m_meshs.size(); ++i) { + auto& cache = m_meshs[i]; + if (!cache.meshGroup) { + cache = std::move(newCache); + AddMeshDestroyCallback(i, mesh); + return i; + } + } + + m_meshs.push_back(std::move(newCache)); + AddMeshDestroyCallback((Uint32) (m_meshs.size() - 1), mesh); + return (Uint32) (m_meshs.size() - 1); +} + +void Citro3DRenderer::StartFrame() +{ + if (g_rendering) { + return; + } + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + C3D_FrameDrawOn(m_renderTarget); + g_rendering = true; +} + +void ConvertPerspective(const D3DRMMATRIX4D in, C3D_Mtx* out) +{ + float f_h = in[0][0]; + float f_v = in[1][1]; + + float aspect = f_v / f_h; + float fovY = 2.0f * atanf(1.0f / f_v); + + float nearZ = -in[3][2] / in[2][2]; + float farZ = nearZ * in[2][2] / (in[2][2] - 1.0f); + + Mtx_PerspTilt(out, fovY, aspect, nearZ, farZ, true); +} + +HRESULT Citro3DRenderer::BeginFrame() +{ + StartFrame(); + C3D_DepthTest(true, GPU_GREATER, GPU_WRITE_ALL); + + C3D_Mtx projection; + ConvertPerspective(m_projection, &projection); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection); + + for (const auto& light : m_lights) { + FColor lightColor = light.color; + if (light.positional == 0.0f && light.directional == 0.0f) { + // Ambient light + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 2, lightColor.r, lightColor.g, lightColor.b, 1.0f); + } + else if (light.directional == 1.0f) { + C3D_FVUnifSet( + GPU_VERTEX_SHADER, + uLoc_lightVec + 1, + -light.direction.x, + -light.direction.y, + -light.direction.z, + 0.0f + ); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 1, lightColor.r, lightColor.g, lightColor.b, 0.0f); + } + else if (light.positional == 1.0f) { + C3D_FVUnifSet( + GPU_VERTEX_SHADER, + uLoc_lightVec + 0, + light.position.x, + light.position.y, + light.position.z, + 0.0f + ); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 0, lightColor.r, lightColor.g, lightColor.b, 0.0f); + } + } + + return S_OK; +} + +void Citro3DRenderer::EnableTransparency() +{ + C3D_DepthTest(true, GPU_GREATER, GPU_WRITE_COLOR); +} + +void ConvertMatrix(const D3DRMMATRIX4D in, C3D_Mtx* out) +{ + for (int i = 0; i < 4; i++) { + out->r[i].x = in[0][i]; + out->r[i].y = in[1][i]; + out->r[i].z = in[2][i]; + out->r[i].w = in[3][i]; + } +} + +void Citro3DRenderer::SubmitDraw( + DWORD meshId, + const D3DRMMATRIX4D& modelViewMatrix, + const D3DRMMATRIX4D& worldMatrix, + const D3DRMMATRIX4D& viewMatrix, + const Matrix3x3& normalMatrix, + const Appearance& appearance +) +{ + C3D_Mtx modelView; + ConvertMatrix(modelViewMatrix, &modelView); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_modelView, &modelView); + + auto& mesh = m_meshs[meshId]; + + C3D_BufInfo* bufInfo = C3D_GetBufInfo(); + BufInfo_Init(bufInfo); + BufInfo_Add(bufInfo, mesh.vbo, sizeof(D3DRMVERTEX), 3, 0x210); + + C3D_FVUnifSet( + GPU_VERTEX_SHADER, + uLoc_meshColor, + appearance.color.r / 255.0f, + appearance.color.g / 255.0f, + appearance.color.b / 255.0f, + appearance.color.a / 255.0f + ); + + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, appearance.shininess / 255.0f, 0.0f, 0.0f, 0.0f); + + if (appearance.textureId != NO_TEXTURE_ID) { + C3D_TexBind(0, &m_textures[appearance.textureId].c3dTex); + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + } + else { + C3D_TexBind(0, nullptr); + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + } + + C3D_DrawArrays(GPU_TRIANGLES, 0, mesh.vertexCount); +} + +HRESULT Citro3DRenderer::FinalizeFrame() +{ + return S_OK; +} + +void Citro3DRenderer::Resize(int width, int height, const ViewportTransform& viewportTransform) +{ + m_width = width; + m_height = height; + m_viewportTransform = viewportTransform; +} + +void Citro3DRenderer::Clear(float r, float g, float b) +{ + StartFrame(); + u32 color = + (static_cast(r * 255) << 24) | (static_cast(g * 255) << 16) | (static_cast(b * 255) << 8) | 255; + C3D_RenderTargetClear(m_renderTarget, C3D_CLEAR_ALL, color, 0); +} + +void Citro3DRenderer::Flip() +{ + C3D_FrameEnd(0); + gfxFlushBuffers(); + gspWaitForVBlank(); + g_rendering = false; +} + +void Citro3DRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +{ + C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA); + StartFrame(); + C3D_DepthTest(false, GPU_GREATER, GPU_WRITE_COLOR); + + float left = -m_viewportTransform.offsetX / m_viewportTransform.scale; + float right = (m_width - m_viewportTransform.offsetX) / m_viewportTransform.scale; + float top = -m_viewportTransform.offsetY / m_viewportTransform.scale; + float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale; + + C3D_Mtx projection, modelView; + Mtx_OrthoTilt(&projection, left, right, bottom, top, 0.0f, 1.0f, true); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection); + Mtx_Identity(&modelView); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_modelView, &modelView); + + // Set light directions + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightVec + 0, 0.0f, 0.0f, 0.0f, 0.0f); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightVec + 1, 0.0f, 0.0f, 0.0f, 0.0f); + + // Set light colors + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 0, 0.0f, 0.0f, 0.0f, 0.0f); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 1, 0.0f, 0.0f, 0.0f, 0.0f); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 2, 1.0f, 1.0f, 1.0f, 1.0f); // Ambient + + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, 0.0f, 0.0f, 0.0f, 0.0f); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_meshColor, 1.0f, 1.0f, 1.0f, 1.0f); + + C3DTextureCacheEntry& texture = m_textures[textureId]; + + C3D_TexBind(0, &texture.c3dTex); + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + + float scale = m_viewportTransform.scale; + + float x1 = static_cast(dstRect.x); + float y1 = static_cast(dstRect.y); + float x2 = x1 + static_cast(dstRect.w); + float y2 = y1 + static_cast(dstRect.h); + + float u0 = (srcRect.x * scale) / texture.width; + float u1 = ((srcRect.x + srcRect.w) * scale) / texture.width; + float v0 = (srcRect.y * scale) / texture.height; + float v1 = ((srcRect.y + srcRect.h) * scale) / texture.height; + + C3D_ImmDrawBegin(GPU_TRIANGLES); + + // Triangle 1 + C3D_ImmSendAttrib(x1, y1, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u0, v0, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x2, y1, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u1, v0, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x2, y2, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u1, v1, 0.0f, 0.0f); + + // Triangle 2 + C3D_ImmSendAttrib(x2, y2, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u1, v1, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x1, y2, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u0, v1, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x1, y1, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u0, v0, 0.0f, 0.0f); + + C3D_ImmDrawEnd(); +} + +void Citro3DRenderer::Download(SDL_Surface* target) +{ + MINIWIN_NOT_IMPLEMENTED(); +} diff --git a/miniwin/src/d3drm/backends/citro3d/vshader.v.pica b/miniwin/src/d3drm/backends/citro3d/vshader.v.pica new file mode 100644 index 00000000..cd7f91c4 --- /dev/null +++ b/miniwin/src/d3drm/backends/citro3d/vshader.v.pica @@ -0,0 +1,130 @@ +; Uniforms +.fvec projection[4], modelView[4], meshColor +.fvec lightVec[2], lightClr[3], shininess + +; Constants +.constf myconst(0.0, 1.0, -1.0, -0.5) + +; Outputs +.out outpos position +.out outtc0 texcoord0 +.out outclr color + +; Inputs +.alias inpos v0 +.alias innrm v1 +.alias intex v2 + +.proc main + ; Prepare constants in usable temp regs + mov r15.x, myconst.x ; 0.0 + mov r15.y, myconst.y ; 1.0 + mov r15.z, myconst.z ; -1.0 + + ; Force the w component of inpos to be 1.0 + mov r0.xyz, inpos + mov r0.w, r15.y + + ; r1 = modelView * inpos + dp4 r1.x, modelView[0], r0 + dp4 r1.y, modelView[1], r0 + dp4 r1.z, modelView[2], r0 + dp4 r1.w, modelView[3], r0 + + ; outpos = projection * r1 + dp4 outpos.x, projection[0], r1 + dp4 outpos.y, projection[1], r1 + dp4 outpos.z, projection[2], r1 + dp4 outpos.w, projection[3], r1 + + ; outtex = intex + mov outtc0, intex + mov outtc0.zw, myconst.xy + + ; Transform normal + mov r2.xyz, innrm + mov r2.w, r15.x + dp4 r3.x, modelView[0], r2 + dp4 r3.y, modelView[1], r2 + dp4 r3.z, modelView[2], r2 + mov r3.w, r15.x + dp3 r4.x, r3, r3 + rsq r4.x, r4.x + mul r3, r4.xxxx, r3 ; r3 = normalized normal + + ; Normalize lightVec[0] + mov r5, lightVec[0] + dp3 r6.x, r5, r5 + rsq r6.x, r6.x + mul r5, r6.xxxx, r5 + + ; dot(normal, lightVec[0]) + dp3 r6.x, r3, r5 + max r6.x, r6.x, r15.xxxx + + ; Normalize lightVec[1] + mov r7, lightVec[1] + dp3 r8.x, r7, r7 + rsq r8.x, r8.x + mul r7, r8.xxxx, r7 + + ; dot(normal, lightVec[1]) + dp3 r6.y, r3, r7 + max r6.y, r6.y, r15.xxxx + + ; Load lightClr + mov r8, lightClr[2] ; ambient + mov r9, lightClr[0] ; point + mov r10, lightClr[1] ; directional + + ; diffuse = ambient + (lightClr[0] * dot0) + (lightClr[1] * dot1) + mul r11, r9, r6.xxxx + add r8, r8, r11 + mul r11, r10, r6.yyyy + add r8, r8, r11 ; r8 = diffuse + + ; Check if shininess > 0 + mov r12, shininess + slt r13.x, r15.x, r12.x + + ; viewVec = normalize(-position.xyz) + mov r14.xyz, r1.xyz + mul r14.xyz, r14.xyz, r15.zzz + dp3 r4.x, r14, r14 + rsq r4.x, r4.x + mul r14, r4.xxxx, r14 + + ; H = normalize(view + lightVec[1]) + add r11, r14, r7 + dp3 r4.x, r11, r11 + rsq r4.x, r4.x + mul r11, r4.xxxx, r11 + + ; dot(normal, H) + dp3 r4.x, r3, r11 + max r4.x, r4.x, r15.x + + ; Approximate pow(dotNH, 10) by repeated multiplication + mul r5.x, r4.x, r4.x ; dotNH^2 + mul r5.x, r5.x, r5.x ; dotNH^4 + mul r5.x, r5.x, r5.x ; dotNH^8 + mul r4.x, r5.x, r4.x ; dotNH^9 + mul r4.x, r4.x, r4.x ; dotNH^10 + + ; Multiply by shininess > 0 flag + mul r4.x, r4.x, r13.x + + ; specular = lightClr[1] * spec + mul r5, r10, r4.xxxx + + ; final = diffuse * meshColor + specular * lightClr[1] + mov r9, meshColor + mul r6, r8, r9 ; diffuse * meshColor + add r7.xyz, r6.xyz, r5.xyz ; add specular (already multiplied by lightClr) + min r7.xyz, r7.xyz, r15.yyyy + + mov outclr.xyz, r7.xyz + mov outclr.w, meshColor.w + + end +.end diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index 6b969769..02c5bfc0 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -80,7 +80,7 @@ void Direct3DRMSoftwareRenderer::ClearZBuffer() _mm_empty(); } #endif -#elif defined(__arm__) || defined(__aarch64__) +#elif (defined(__arm__) || defined(__aarch64__)) && !defined(__3DS__) if (SDL_HasNEON()) { float32x4_t inf4 = vdupq_n_f32(inf); for (; i + 4 <= size; i += 4) { diff --git a/miniwin/src/d3drm/d3drm.cpp b/miniwin/src/d3drm/d3drm.cpp index e8df37b1..45a4e3ad 100644 --- a/miniwin/src/d3drm/d3drm.cpp +++ b/miniwin/src/d3drm/d3drm.cpp @@ -13,6 +13,9 @@ #ifdef USE_OPENGLES2 #include "d3drmrenderer_opengles2.h" #endif +#ifdef __3DS__ +#include "d3drmrenderer_citro3d.h" +#endif #ifdef _WIN32 #include "d3drmrenderer_directx9.h" #endif @@ -159,6 +162,11 @@ HRESULT Direct3DRMImpl::CreateDeviceFromSurface( DDRenderer = OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); } #endif +#ifdef __3DS__ + else if (SDL_memcmp(&guid, &Citro3D_GUID, sizeof(GUID)) == 0) { + DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif #ifdef _WIN32 else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) { DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index f6b2f9f7..b738c809 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -155,6 +155,10 @@ void Direct3DRMDevice2Impl::Resize() { int width, height; SDL_GetWindowSizeInPixels(DDWindow, &width, &height); +#ifdef __3DS__ + width = 320; // We are on the lower screen + height = 240; +#endif m_viewportTransform = CalculateViewportTransform(m_virtualWidth, m_virtualHeight, width, height); m_renderer->Resize(width, height, m_viewportTransform); for (int i = 0; i < m_viewports->GetSize(); i++) { diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index 018fffd1..a3bc5d4d 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -4,6 +4,9 @@ #ifdef USE_OPENGLES2 #include "d3drmrenderer_opengles2.h" #endif +#ifdef __3DS__ +#include "d3drmrenderer_citro3d.h" +#endif #ifdef _WIN32 #include "d3drmrenderer_directx9.h" #endif @@ -232,6 +235,9 @@ HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) #ifdef USE_OPENGL1 OpenGL1Renderer_EnumDevice(cb, ctx); #endif +#ifdef __3DS__ + Citro3DRenderer_EnumDevice(cb, ctx); +#endif #ifdef _WIN32 DirectX9Renderer_EnumDevice(cb, ctx); #endif @@ -346,6 +352,11 @@ HRESULT DirectDrawImpl::CreateDevice( DDRenderer = OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); } #endif +#ifdef __3DS__ + else if (SDL_memcmp(&guid, &Citro3D_GUID, sizeof(GUID)) == 0) { + DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif #ifdef _WIN32 else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) { DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); diff --git a/miniwin/src/internal/d3drmrenderer_citro3d.h b/miniwin/src/internal/d3drmrenderer_citro3d.h new file mode 100644 index 00000000..426f75c8 --- /dev/null +++ b/miniwin/src/internal/d3drmrenderer_citro3d.h @@ -0,0 +1,82 @@ +#pragma once + +#include "d3drmrenderer.h" +#include "ddraw_impl.h" + +#include +#include +#include + +DEFINE_GUID(Citro3D_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D, 0x53); + +struct C3DTextureCacheEntry { + IDirect3DRMTexture* texture; + Uint32 version; + C3D_Tex c3dTex; + uint16_t width; + uint16_t height; +}; + +struct C3DMeshCacheEntry { + const MeshGroup* meshGroup = nullptr; + int version = 0; + void* vbo = nullptr; + int vertexCount = 0; +}; + +class Citro3DRenderer : public Direct3DRMRenderer { +public: + Citro3DRenderer(DWORD width, DWORD height); + ~Citro3DRenderer() override; + + void PushLights(const SceneLight* lightsArray, size_t count) override; + void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; + void SetFrustumPlanes(const Plane* frustumPlanes) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; + Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; + HRESULT BeginFrame() override; + void EnableTransparency() override; + void SubmitDraw( + DWORD meshId, + const D3DRMMATRIX4D& modelViewMatrix, + const D3DRMMATRIX4D& worldMatrix, + const D3DRMMATRIX4D& viewMatrix, + const Matrix3x3& normalMatrix, + const Appearance& appearance + ) override; + HRESULT FinalizeFrame() override; + void Resize(int width, int height, const ViewportTransform& viewportTransform) override; + void Clear(float r, float g, float b) override; + void Flip() override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; + void Download(SDL_Surface* target) override; + +private: + void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); + void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh); + void StartFrame(); + + D3DRMMATRIX4D m_projection; + SDL_Surface* m_renderedImage; + C3D_RenderTarget* m_renderTarget; + std::vector m_textures; + std::vector m_meshs; + ViewportTransform m_viewportTransform; + std::vector m_lights; +}; + +inline static void Citro3DRenderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) +{ + D3DDEVICEDESC halDesc = {}; + halDesc.dcmColorModel = D3DCOLOR_RGB; + halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + halDesc.dwDeviceZBufferBitDepth = DDBD_24; + halDesc.dwDeviceRenderBitDepth = DDBD_32; + halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; + + D3DDEVICEDESC helDesc = {}; + + EnumDevice(cb, ctx, "Citro3D", &halDesc, &helDesc, Citro3D_GUID); +}