From 47736862a71d107d582e24ce2c3d90b8779c8745 Mon Sep 17 00:00:00 2001
From: olebeck <31539311+olebeck@users.noreply.github.com>
Date: Sat, 1 Nov 2025 00:23:12 +0100
Subject: [PATCH] Add PSVita port (#541)
* add psvita to cmake
* no PIE for vita
* add modules to vpk
* use custom pvr apphint
* select correct renderer for sdl renderer
* patch sdl3 shaders, got something on screen!
* use proper cmake patch for sdl
* add missing module
* remove test window which causes a bug in the vita sdl port to show up
* add gxm renderer (not working with display yet)
* avoid sdl renderer for vita, seems broken
* make gxm renderer work with new d3drm
* fix rendering somewhat, some geometry shows up
* support paletted textures directly to avoid copying the texture twice
* fix Draw2DImage
* make 3d work, broken lights
* clean up a bit
* fix normals matrix
* remove some unneeded changes
* forgot env var
* wrong env dest
* run clang format
* correct texture address mode, use tlsf instead of sceClibMspace
* double buffered uniforms seem to work now
* missed a line
* update GXMRenderer_EnumDevice
* hopefully actually fix uniform buffers
* run clang-format
* remove a change thats not needed
* improve fragment shader performance
* add vita to dist folder
* add base for vita config app
* add config self to vpk
* transform touch events to virtual size
* add livearea graphics
* Update cmake file to include livearea assets
* put manual in the right place
* add sample rco
* add messagebox on vita
* triple buffer textures because fences arent a thing on vita and making draw&cpu sync would be too slow
* make config app not crash on launch
* change defaults
* update gxm renderer with interface changes
* split 2d and 3d shaders completely
* update gxm renderer
* fix transition on gxm
* clang format
* move config cmake
* move CONFIG_vita
* always clear before drawing 2d image
* hopefully fix windows build
* clang-format fix broken includes
* order again
* undo moving qt cmake to its own list
* move uic search path
* use ifdefs for all d3drm backends, cpack to generate vpk
* cmake wrong escape
* small cleanups in gxm renderer
* defer texture delete to avoid overwriting the texture during a frame
* clang-format
* more of the layout for config
* remove top buttons
* use SceAppSettings instead of custom ui
* use select for back to info center on vita, to make screenshots possible again
* remove accidentally left in add_subdirectory
* adjust diskpath to be like other ports
* use vita_create_vpk and not cpack
* gxm: msaa support, fix wrong file path
* gxm: add mipmaps (disabled)
* clang-format
* fix open isle.ini with fopen
* add missing strings
* use iniparser_set not dictionary_set
* add default save path to config
* load config app after initializing ini on vita
* fix config build
* change the default disk & cd path, update the paf library
* update paf library headers
* include orders for clang-format
* clean up
* make shader compiler not required
* move asm language
* warn instead of error when shader source is changed when no compiler is found
---------
Co-authored-by: Li
Co-authored-by: Christian Semmler
---
.github/workflows/ci.yml | 23 +-
CMakeLists.txt | 61 +-
CONFIG/{ => qt}/AboutDlg.cpp | 0
CONFIG/{ => qt}/AboutDlg.h | 0
CONFIG/{ => qt}/MainDlg.cpp | 0
CONFIG/{ => qt}/MainDlg.h | 0
CONFIG/{ => qt}/config.cpp | 0
CONFIG/{ => qt}/config.h | 0
CONFIG/{ => qt}/detectdx5.cpp | 0
CONFIG/{ => qt}/detectdx5.h | 0
CONFIG/{ => qt}/res/about.ui | 0
CONFIG/{ => qt}/res/add.svg | 0
CONFIG/{ => qt}/res/config.qrc | 0
CONFIG/{ => qt}/res/config.rc | 0
CONFIG/{ => qt}/res/lego.ico | Bin
CONFIG/{ => qt}/res/lego1.png | Bin
CONFIG/{ => qt}/res/lego2.png | Bin
CONFIG/{ => qt}/res/maindialog.ui | 0
CONFIG/{ => qt}/res/remove.svg | 0
CONFIG/{ => qt}/res/shark.png | Bin
CONFIG/vita/CMakeLists.txt | 73 +
CONFIG/vita/cxml/config_plugin.xml | 34 +
CONFIG/vita/cxml/locale/en.xml | 57 +
CONFIG/vita/cxml/settings.xml | 51 +
CONFIG/vita/exports.yml | 8 +
CONFIG/vita/iniparser_paf.patch | 53 +
CONFIG/vita/src/app.cpp | 430 ++++++
CONFIG/vita/src/paf_runtime.cpp | 169 +++
ISLE/isleapp.cpp | 47 +-
ISLE/vita/config.cpp | 13 +
ISLE/vita/config.h | 8 +
ISLE/vita/messagebox.cpp | 52 +
ISLE/vita/messagebox.h | 8 +
README.md | 1 +
miniwin/CMakeLists.txt | 21 +-
.../src/d3drm/backends/gxm/gxm_context.cpp | 680 +++++++++
miniwin/src/d3drm/backends/gxm/gxm_context.h | 105 ++
miniwin/src/d3drm/backends/gxm/gxm_memory.cpp | 119 ++
miniwin/src/d3drm/backends/gxm/gxm_memory.h | 15 +
.../src/d3drm/backends/gxm/gxm_renderer.cpp | 1101 ++++++++++++++
miniwin/src/d3drm/backends/gxm/razor.h | 13 +
.../src/d3drm/backends/gxm/shaders/.gitignore | 2 +
.../d3drm/backends/gxm/shaders/CMakeLists.txt | 106 ++
.../d3drm/backends/gxm/shaders/color.frag.cg | 6 +
.../d3drm/backends/gxm/shaders/color.frag.gxp | Bin 0 -> 228 bytes
.../backends/gxm/shaders/color.frag.perf.txt | 26 +
.../d3drm/backends/gxm/shaders/gxm_shaders.h | 32 +
.../d3drm/backends/gxm/shaders/gxm_shaders.s | 15 +
.../d3drm/backends/gxm/shaders/image.frag.cg | 10 +
.../d3drm/backends/gxm/shaders/image.frag.gxp | Bin 0 -> 264 bytes
.../backends/gxm/shaders/image.frag.perf.txt | 29 +
.../backends/gxm/shaders/main.color.frag.gxp | Bin 0 -> 1248 bytes
.../gxm/shaders/main.color.frag.perf.txt | 134 ++
.../d3drm/backends/gxm/shaders/main.frag.cg | 54 +
.../gxm/shaders/main.texture.frag.gxp | Bin 0 -> 1312 bytes
.../gxm/shaders/main.texture.frag.perf.txt | 139 ++
.../d3drm/backends/gxm/shaders/main.vert.cg | 20 +
.../d3drm/backends/gxm/shaders/main.vert.gxp | Bin 0 -> 784 bytes
.../backends/gxm/shaders/main.vert.perf.txt | 87 ++
.../d3drm/backends/gxm/shaders/plane.vert.cg | 11 +
.../d3drm/backends/gxm/shaders/plane.vert.gxp | Bin 0 -> 284 bytes
.../backends/gxm/shaders/plane.vert.perf.txt | 30 +
miniwin/src/d3drm/backends/gxm/tlsf.c | 1264 +++++++++++++++++
miniwin/src/d3drm/backends/gxm/tlsf.h | 91 ++
miniwin/src/d3drm/backends/gxm/utils.h | 48 +
miniwin/src/d3drm/d3drmrenderer.cpp | 11 +
miniwin/src/internal/d3drmrenderer_gxm.h | 181 +++
packaging/vita/sce_sys/icon0.png | Bin 0 -> 1671 bytes
.../vita/sce_sys/livearea/contents/bg.png | Bin 0 -> 47338 bytes
.../sce_sys/livearea/contents/configure.png | Bin 0 -> 2715 bytes
.../vita/sce_sys/livearea/contents/gate.png | Bin 0 -> 8468 bytes
.../vita/sce_sys/livearea/contents/logo.png | Bin 0 -> 33724 bytes
.../sce_sys/livearea/contents/template.xml | 23 +
packaging/vita/sce_sys/manual/001.png | Bin 0 -> 242162 bytes
packaging/vita/sce_sys/manual/002.png | Bin 0 -> 98897 bytes
packaging/vita/sce_sys/manual/003.png | Bin 0 -> 196455 bytes
packaging/vita/sce_sys/manual/004.png | Bin 0 -> 170140 bytes
packaging/vita/sce_sys/manual/005.png | Bin 0 -> 149940 bytes
packaging/vita/sce_sys/manual/006.png | Bin 0 -> 151558 bytes
packaging/vita/sce_sys/manual/007.png | Bin 0 -> 160086 bytes
packaging/vita/sce_sys/manual/008.png | Bin 0 -> 157081 bytes
packaging/vita/sce_sys/manual/009.png | Bin 0 -> 163583 bytes
packaging/vita/sce_sys/manual/010.png | Bin 0 -> 155524 bytes
packaging/vita/sce_sys/manual/011.png | Bin 0 -> 175534 bytes
packaging/vita/sce_sys/manual/012.png | Bin 0 -> 128494 bytes
packaging/vita/sce_sys/manual/013.png | Bin 0 -> 150027 bytes
packaging/vita/sce_sys/manual/014.png | Bin 0 -> 147269 bytes
packaging/vita/sce_sys/manual/015.png | Bin 0 -> 135605 bytes
packaging/vita/sce_sys/manual/016.png | Bin 0 -> 144464 bytes
packaging/vita/sce_sys/manual/017.png | Bin 0 -> 141728 bytes
packaging/vita/sce_sys/manual/018.png | Bin 0 -> 144819 bytes
packaging/vita/sce_sys/manual/019.png | Bin 0 -> 190266 bytes
packaging/vita/sce_sys/manual/020.png | Bin 0 -> 155975 bytes
packaging/vita/sce_sys/manual/021.png | Bin 0 -> 125672 bytes
packaging/vita/sce_sys/manual/022.png | Bin 0 -> 143847 bytes
packaging/vita/sce_sys/manual/023.png | Bin 0 -> 157272 bytes
packaging/vita/sce_sys/manual/024.png | Bin 0 -> 148858 bytes
packaging/vita/sce_sys/manual/025.png | Bin 0 -> 82507 bytes
packaging/vita/sce_sys/manual/026.png | Bin 0 -> 206515 bytes
packaging/vita/sce_sys/manual/027.png | Bin 0 -> 141617 bytes
packaging/vita/sce_sys/manual/028.png | Bin 0 -> 227167 bytes
packaging/vita/sce_sys/pic0.png | Bin 0 -> 251847 bytes
util/compat.h | 2 +
103 files changed, 5448 insertions(+), 15 deletions(-)
rename CONFIG/{ => qt}/AboutDlg.cpp (100%)
rename CONFIG/{ => qt}/AboutDlg.h (100%)
rename CONFIG/{ => qt}/MainDlg.cpp (100%)
rename CONFIG/{ => qt}/MainDlg.h (100%)
rename CONFIG/{ => qt}/config.cpp (100%)
rename CONFIG/{ => qt}/config.h (100%)
rename CONFIG/{ => qt}/detectdx5.cpp (100%)
rename CONFIG/{ => qt}/detectdx5.h (100%)
rename CONFIG/{ => qt}/res/about.ui (100%)
rename CONFIG/{ => qt}/res/add.svg (100%)
rename CONFIG/{ => qt}/res/config.qrc (100%)
rename CONFIG/{ => qt}/res/config.rc (100%)
rename CONFIG/{ => qt}/res/lego.ico (100%)
rename CONFIG/{ => qt}/res/lego1.png (100%)
rename CONFIG/{ => qt}/res/lego2.png (100%)
rename CONFIG/{ => qt}/res/maindialog.ui (100%)
rename CONFIG/{ => qt}/res/remove.svg (100%)
rename CONFIG/{ => qt}/res/shark.png (100%)
create mode 100644 CONFIG/vita/CMakeLists.txt
create mode 100644 CONFIG/vita/cxml/config_plugin.xml
create mode 100644 CONFIG/vita/cxml/locale/en.xml
create mode 100644 CONFIG/vita/cxml/settings.xml
create mode 100644 CONFIG/vita/exports.yml
create mode 100644 CONFIG/vita/iniparser_paf.patch
create mode 100644 CONFIG/vita/src/app.cpp
create mode 100644 CONFIG/vita/src/paf_runtime.cpp
create mode 100644 ISLE/vita/config.cpp
create mode 100644 ISLE/vita/config.h
create mode 100644 ISLE/vita/messagebox.cpp
create mode 100644 ISLE/vita/messagebox.h
create mode 100644 miniwin/src/d3drm/backends/gxm/gxm_context.cpp
create mode 100644 miniwin/src/d3drm/backends/gxm/gxm_context.h
create mode 100644 miniwin/src/d3drm/backends/gxm/gxm_memory.cpp
create mode 100644 miniwin/src/d3drm/backends/gxm/gxm_memory.h
create mode 100644 miniwin/src/d3drm/backends/gxm/gxm_renderer.cpp
create mode 100644 miniwin/src/d3drm/backends/gxm/razor.h
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/.gitignore
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/CMakeLists.txt
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/color.frag.cg
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/color.frag.gxp
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/color.frag.perf.txt
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/gxm_shaders.h
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/gxm_shaders.s
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/image.frag.cg
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/image.frag.gxp
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/image.frag.perf.txt
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/main.color.frag.gxp
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/main.color.frag.perf.txt
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/main.frag.cg
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/main.texture.frag.gxp
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/main.texture.frag.perf.txt
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/main.vert.cg
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/main.vert.gxp
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/main.vert.perf.txt
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/plane.vert.cg
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/plane.vert.gxp
create mode 100644 miniwin/src/d3drm/backends/gxm/shaders/plane.vert.perf.txt
create mode 100644 miniwin/src/d3drm/backends/gxm/tlsf.c
create mode 100644 miniwin/src/d3drm/backends/gxm/tlsf.h
create mode 100644 miniwin/src/d3drm/backends/gxm/utils.h
create mode 100644 miniwin/src/internal/d3drmrenderer_gxm.h
create mode 100644 packaging/vita/sce_sys/icon0.png
create mode 100644 packaging/vita/sce_sys/livearea/contents/bg.png
create mode 100644 packaging/vita/sce_sys/livearea/contents/configure.png
create mode 100644 packaging/vita/sce_sys/livearea/contents/gate.png
create mode 100644 packaging/vita/sce_sys/livearea/contents/logo.png
create mode 100644 packaging/vita/sce_sys/livearea/contents/template.xml
create mode 100644 packaging/vita/sce_sys/manual/001.png
create mode 100644 packaging/vita/sce_sys/manual/002.png
create mode 100644 packaging/vita/sce_sys/manual/003.png
create mode 100644 packaging/vita/sce_sys/manual/004.png
create mode 100644 packaging/vita/sce_sys/manual/005.png
create mode 100644 packaging/vita/sce_sys/manual/006.png
create mode 100644 packaging/vita/sce_sys/manual/007.png
create mode 100644 packaging/vita/sce_sys/manual/008.png
create mode 100644 packaging/vita/sce_sys/manual/009.png
create mode 100644 packaging/vita/sce_sys/manual/010.png
create mode 100644 packaging/vita/sce_sys/manual/011.png
create mode 100644 packaging/vita/sce_sys/manual/012.png
create mode 100644 packaging/vita/sce_sys/manual/013.png
create mode 100644 packaging/vita/sce_sys/manual/014.png
create mode 100644 packaging/vita/sce_sys/manual/015.png
create mode 100644 packaging/vita/sce_sys/manual/016.png
create mode 100644 packaging/vita/sce_sys/manual/017.png
create mode 100644 packaging/vita/sce_sys/manual/018.png
create mode 100644 packaging/vita/sce_sys/manual/019.png
create mode 100644 packaging/vita/sce_sys/manual/020.png
create mode 100644 packaging/vita/sce_sys/manual/021.png
create mode 100644 packaging/vita/sce_sys/manual/022.png
create mode 100644 packaging/vita/sce_sys/manual/023.png
create mode 100644 packaging/vita/sce_sys/manual/024.png
create mode 100644 packaging/vita/sce_sys/manual/025.png
create mode 100644 packaging/vita/sce_sys/manual/026.png
create mode 100644 packaging/vita/sce_sys/manual/027.png
create mode 100644 packaging/vita/sce_sys/manual/028.png
create mode 100644 packaging/vita/sce_sys/pic0.png
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e4306017..8fd81c57 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -48,6 +48,7 @@ jobs:
- { name: 'Nintendo 3DS', os: 'ubuntu-latest', generator: 'Ninja', 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' }
- { name: 'Xbox One', os: 'windows-latest', generator: 'Visual Studio 17 2022', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64', cmake-args: '-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.26100.0', xbox-one: true}
- { name: 'Android', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, android: true, werror: true, clang-tidy: false,}
+ - { name: 'Vita', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, vita: true, werror: true, clang-tidy: false, cmake-args: '--toolchain /usr/local/vitasdk/share/vita.toolchain.cmake'}
steps:
- name: Setup vcvars
if: ${{ !!matrix.msvc }}
@@ -117,6 +118,18 @@ jobs:
- name: Setup ninja
if: ${{ matrix.msvc }}
uses: ashutoshvarma/setup-ninja@master
+
+ - name: Setup vitasdk
+ if: ${{ matrix.vita }}
+ run: |
+ git clone https://github.com/vitasdk/vdpm
+ cd vdpm
+ ./bootstrap-vitasdk.sh
+ export VITASDK=/usr/local/vitasdk
+ export PATH=$VITASDK/bin:$PATH
+ echo "VITASDK=/usr/local/vitasdk" >> $GITHUB_ENV
+ echo "$VITASDK/bin" >> $GITHUB_PATH
+ ./install-all.sh
- uses: actions/checkout@v4
@@ -177,7 +190,7 @@ jobs:
run: cmake --build build --verbose --config Release
- name: Package (CPack)
- if: ${{ !matrix.n3ds && !matrix.android }}
+ if: ${{ !matrix.n3ds && !matrix.android && !matrix.vita }}
run: |
cd build
success=0
@@ -225,6 +238,13 @@ jobs:
mkdir dist
mv *.3dsx dist/
mv *.cia dist/
+
+ - name: Package (Vita)
+ if: ${{ matrix.vita }}
+ run: |
+ cd build
+ mkdir dist
+ mv *.vpk dist/
- name: Package (Android)
if: ${{ matrix.android }}
@@ -246,6 +266,7 @@ jobs:
build/dist/*.3dsx
build/dist/*.cia
build/dist/*.apk
+ build/dist/*.vpk
flatpak:
name: "Flatpak (${{ matrix.arch }})"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8a4ec707..60e742d0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,18 +46,18 @@ option(ISLE_BUILD_ASSETS "Build assets from the /assets directory" OFF)
option(ISLE_ASAN "Enable Address Sanitizer" OFF)
option(ISLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF)
option(ISLE_WERROR "Treat warnings as errors" OFF)
-option(ISLE_DEBUG "Enable imgui debug" ON)
+cmake_dependent_option(ISLE_DEBUG "Enable imgui debug" ON "NOT VITA" OFF)
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_EXTENSIONS "Use extensions" ON "NOT ISLE_USE_DX5;NOT WINDOWS_STORE" OFF)
-cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS;NOT WINDOWS_STORE" OFF)
+cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS;NOT WINDOWS_STORE;NOT VITA" 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)
+cmake_dependent_option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON "NOT VITA" OFF)
option(ENABLE_CLANG_TIDY "Enable clang-tidy")
option(DOWNLOAD_DEPENDENCIES "Download dependencies" ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" CACHE PATH "Directory where to put executables and dll")
set(ISLE_EMSCRIPTEN_HOST "" CACHE STRING "Host URL for Emscripten streaming (e.g., https://test.com)")
-cmake_dependent_option(BUILD_SHARED_LIBS "Build lego1 as a shared library" ON "NOT EMSCRIPTEN" OFF)
+cmake_dependent_option(BUILD_SHARED_LIBS "Build lego1 as a shared library" ON "NOT EMSCRIPTEN;NOT VITA" OFF)
message(STATUS "Isle app: ${ISLE_BUILD_APP}")
message(STATUS "Config app: ${ISLE_BUILD_CONFIG}")
@@ -90,6 +90,7 @@ if (DOWNLOAD_DEPENDENCIES)
SDL3
GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git"
GIT_TAG "main"
+ UPDATE_DISCONNECTED TRUE
EXCLUDE_FROM_ALL
)
endif()
@@ -100,6 +101,7 @@ if (DOWNLOAD_DEPENDENCIES)
iniparser
GIT_REPOSITORY "https://gitlab.com/iniparser/iniparser.git"
GIT_TAG "main"
+ UPDATE_DISCONNECTED TRUE
EXCLUDE_FROM_ALL
)
block()
@@ -598,6 +600,12 @@ if (ISLE_BUILD_APP)
ISLE/android/config.cpp
)
endif()
+ if(VITA)
+ target_sources(isle PRIVATE
+ ISLE/vita/config.cpp
+ ISLE/vita/messagebox.cpp
+ )
+ endif()
if(Python3_FOUND)
if(NOT DEFINED PYTHON_PIL_AVAILABLE)
execute_process(
@@ -639,18 +647,18 @@ if (ISLE_BUILD_CONFIG)
qt_add_executable(isle-config WIN32
LEGO1/mxdirectx/mxdirectxinfo.cpp
LEGO1/mxdirectx/legodxinfo.cpp
- CONFIG/config.cpp
- CONFIG/AboutDlg.cpp
- CONFIG/MainDlg.cpp
- CONFIG/detectdx5.cpp
- CONFIG/res/config.rc
- CONFIG/res/config.qrc
+ CONFIG/qt/config.cpp
+ CONFIG/qt/AboutDlg.cpp
+ CONFIG/qt/MainDlg.cpp
+ CONFIG/qt/detectdx5.cpp
+ CONFIG/qt/res/config.rc
+ CONFIG/qt/res/config.qrc
)
target_link_libraries(isle-config PRIVATE Qt6::Core Qt6::Widgets)
set_property(TARGET isle-config PROPERTY AUTOMOC ON)
set_property(TARGET isle-config PROPERTY AUTORCC ON)
set_property(TARGET isle-config PROPERTY AUTOUIC ON)
- set_property(TARGET isle-config PROPERTY AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/CONFIG/res")
+ set_property(TARGET isle-config PROPERTY AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/CONFIG/qt/res")
list(APPEND isle_targets isle-config)
target_compile_definitions(isle-config PRIVATE _AFXDLL MXDIRECTX_FOR_CONFIG)
target_include_directories(isle-config PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/LEGO1")
@@ -782,7 +790,7 @@ endif()
if(BUILD_SHARED_LIBS)
list(APPEND install_extra_targets lego1)
endif()
-if (NOT (NINTENDO_3DS OR WINDOWS_STORE))
+if (NOT (NINTENDO_3DS OR WINDOWS_STORE OR VITA))
install(TARGETS isle ${install_extra_targets}
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
@@ -881,6 +889,35 @@ if(WINDOWS_STORE)
PATTERN "*/*.msix"
PATTERN "*/*.msixbundle")
endif()
+if(VITA)
+ include("${VITASDK}/share/vita.cmake" REQUIRED)
+
+ add_subdirectory(CONFIG/vita)
+
+ set(ISLE_PACKAGE_NAME "vita-isle")
+ set(VITA_APP_NAME "Lego Island")
+ set(VITA_TITLEID "LEGO00001")
+ set(VITA_VERSION "01.00")
+
+ vita_create_self(isle.self isle UNSAFE)
+
+ set(VPK_FILE_ARGS "")
+ file(GLOB_RECURSE SCE_SYS_FILES packaging/vita/sce_sys/*)
+ foreach(FILE ${SCE_SYS_FILES})
+ file(RELATIVE_PATH REL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/packaging/vita/ ${FILE})
+ list(APPEND VPK_FILE_ARGS FILE ${FILE} ${REL_FILE})
+ endforeach()
+
+ vita_create_vpk(isle.vpk ${VITA_TITLEID} isle.self
+ FILE "${CMAKE_CURRENT_BINARY_DIR}/CONFIG/vita/isle-config.self" isle-config.self
+ FILE "${CMAKE_CURRENT_BINARY_DIR}/CONFIG/vita/config_plugin.rco" config_plugin.rco
+ VERSION ${VITA_VERSION}
+ NAME ${VITA_APP_NAME}
+ ${VPK_FILE_ARGS}
+ )
+ add_dependencies(isle.vpk-vpk config_plugin.rco_target)
+endif()
+
if(MSVC OR IOS)
set(CPACK_GENERATOR ZIP)
if(IOS)
diff --git a/CONFIG/AboutDlg.cpp b/CONFIG/qt/AboutDlg.cpp
similarity index 100%
rename from CONFIG/AboutDlg.cpp
rename to CONFIG/qt/AboutDlg.cpp
diff --git a/CONFIG/AboutDlg.h b/CONFIG/qt/AboutDlg.h
similarity index 100%
rename from CONFIG/AboutDlg.h
rename to CONFIG/qt/AboutDlg.h
diff --git a/CONFIG/MainDlg.cpp b/CONFIG/qt/MainDlg.cpp
similarity index 100%
rename from CONFIG/MainDlg.cpp
rename to CONFIG/qt/MainDlg.cpp
diff --git a/CONFIG/MainDlg.h b/CONFIG/qt/MainDlg.h
similarity index 100%
rename from CONFIG/MainDlg.h
rename to CONFIG/qt/MainDlg.h
diff --git a/CONFIG/config.cpp b/CONFIG/qt/config.cpp
similarity index 100%
rename from CONFIG/config.cpp
rename to CONFIG/qt/config.cpp
diff --git a/CONFIG/config.h b/CONFIG/qt/config.h
similarity index 100%
rename from CONFIG/config.h
rename to CONFIG/qt/config.h
diff --git a/CONFIG/detectdx5.cpp b/CONFIG/qt/detectdx5.cpp
similarity index 100%
rename from CONFIG/detectdx5.cpp
rename to CONFIG/qt/detectdx5.cpp
diff --git a/CONFIG/detectdx5.h b/CONFIG/qt/detectdx5.h
similarity index 100%
rename from CONFIG/detectdx5.h
rename to CONFIG/qt/detectdx5.h
diff --git a/CONFIG/res/about.ui b/CONFIG/qt/res/about.ui
similarity index 100%
rename from CONFIG/res/about.ui
rename to CONFIG/qt/res/about.ui
diff --git a/CONFIG/res/add.svg b/CONFIG/qt/res/add.svg
similarity index 100%
rename from CONFIG/res/add.svg
rename to CONFIG/qt/res/add.svg
diff --git a/CONFIG/res/config.qrc b/CONFIG/qt/res/config.qrc
similarity index 100%
rename from CONFIG/res/config.qrc
rename to CONFIG/qt/res/config.qrc
diff --git a/CONFIG/res/config.rc b/CONFIG/qt/res/config.rc
similarity index 100%
rename from CONFIG/res/config.rc
rename to CONFIG/qt/res/config.rc
diff --git a/CONFIG/res/lego.ico b/CONFIG/qt/res/lego.ico
similarity index 100%
rename from CONFIG/res/lego.ico
rename to CONFIG/qt/res/lego.ico
diff --git a/CONFIG/res/lego1.png b/CONFIG/qt/res/lego1.png
similarity index 100%
rename from CONFIG/res/lego1.png
rename to CONFIG/qt/res/lego1.png
diff --git a/CONFIG/res/lego2.png b/CONFIG/qt/res/lego2.png
similarity index 100%
rename from CONFIG/res/lego2.png
rename to CONFIG/qt/res/lego2.png
diff --git a/CONFIG/res/maindialog.ui b/CONFIG/qt/res/maindialog.ui
similarity index 100%
rename from CONFIG/res/maindialog.ui
rename to CONFIG/qt/res/maindialog.ui
diff --git a/CONFIG/res/remove.svg b/CONFIG/qt/res/remove.svg
similarity index 100%
rename from CONFIG/res/remove.svg
rename to CONFIG/qt/res/remove.svg
diff --git a/CONFIG/res/shark.png b/CONFIG/qt/res/shark.png
similarity index 100%
rename from CONFIG/res/shark.png
rename to CONFIG/qt/res/shark.png
diff --git a/CONFIG/vita/CMakeLists.txt b/CONFIG/vita/CMakeLists.txt
new file mode 100644
index 00000000..6d44f518
--- /dev/null
+++ b/CONFIG/vita/CMakeLists.txt
@@ -0,0 +1,73 @@
+cmake_minimum_required(VERSION 3.25...4.0 FATAL_ERROR)
+
+project(isle-config LANGUAGES CXX C VERSION 0.1)
+
+include("${VITASDK}/share/vita.cmake" REQUIRED)
+
+include(FetchContent)
+FetchContent_Declare(
+ ScePaf_External
+ URL https://github.com/olebeck/ScePaf/releases/download/v21/ScePaf-1.0.0.zip
+ URL_HASH SHA256=357b914a5c99ea17afe0edc8787a05cbf2ecce2f1d73bb9be69f371a294b8943
+ UPDATE_DISCONNECTED TRUE
+)
+FetchContent_MakeAvailable(ScePaf_External)
+
+
+FetchContent_Declare(
+ iniparser_paf
+ GIT_REPOSITORY "https://gitlab.com/iniparser/iniparser.git"
+ GIT_TAG "v4.2.6"
+ UPDATE_DISCONNECTED TRUE
+ EXCLUDE_FROM_ALL
+ PATCH_COMMAND patch -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/iniparser_paf.patch
+)
+block()
+ set(BUILD_DOCS off)
+ set(BUILD_SHARED_LIBS off)
+ FetchContent_MakeAvailable(iniparser_paf)
+endblock()
+
+add_executable(isle-config
+ src/paf_runtime.cpp
+ src/app.cpp
+)
+
+set_target_properties(isle-config PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+target_compile_options(isle-config PRIVATE
+ -fno-rtti -fno-exceptions -Wl,-q -Wall -fno-builtin -fshort-wchar -Wno-unused-function -Wno-sign-compare -fno-use-cxa-atexit
+)
+
+target_link_options(isle-config PRIVATE
+ -nostartfiles -nostdlib
+)
+
+target_link_libraries(isle-config PRIVATE
+ SceAppMgr_stub
+ SceLibKernel_stub
+ SceSysmodule_stub
+
+ ScePafToplevel_stub
+ ScePafResource_stub
+ ScePafWidget_stub
+ ScePafCommon_stub
+ ScePafStdc_stub
+ SceAppSettings_stub
+ SceFios2_stub
+ SceLibc_stub
+
+ iniparser_paf-static
+)
+
+vita_create_self(isle-config.self isle-config
+ CONFIG exports.yml
+ UNSAFE
+ STRIPPED
+ REL_OPTIMIZE
+)
+
+include(${scepaf_external_SOURCE_DIR}/rco.cmake)
+make_rco(cxml/config_plugin.xml config_plugin.rco)
diff --git a/CONFIG/vita/cxml/config_plugin.xml b/CONFIG/vita/cxml/config_plugin.xml
new file mode 100644
index 00000000..2f1df305
--- /dev/null
+++ b/CONFIG/vita/cxml/config_plugin.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CONFIG/vita/cxml/locale/en.xml b/CONFIG/vita/cxml/locale/en.xml
new file mode 100644
index 00000000..22435a39
--- /dev/null
+++ b/CONFIG/vita/cxml/locale/en.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CONFIG/vita/cxml/settings.xml b/CONFIG/vita/cxml/settings.xml
new file mode 100644
index 00000000..e9dad3fb
--- /dev/null
+++ b/CONFIG/vita/cxml/settings.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CONFIG/vita/exports.yml b/CONFIG/vita/exports.yml
new file mode 100644
index 00000000..d20ef9e2
--- /dev/null
+++ b/CONFIG/vita/exports.yml
@@ -0,0 +1,8 @@
+isle-config:
+ attributes: 0
+ process_image: true
+ version:
+ major: 1
+ minor: 1
+ main:
+ start: module_start
\ No newline at end of file
diff --git a/CONFIG/vita/iniparser_paf.patch b/CONFIG/vita/iniparser_paf.patch
new file mode 100644
index 00000000..e2cdacc8
--- /dev/null
+++ b/CONFIG/vita/iniparser_paf.patch
@@ -0,0 +1,53 @@
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 4f7fdba..d388fc8 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -1,7 +1,7 @@
+ cmake_minimum_required(VERSION 3.18)
+
+ project(
+- iniparser
++ iniparser_paf
+ DESCRIPTION "C library for parsing INI-style files"
+ HOMEPAGE_URL https://gitlab.com/iniparser/iniparser/
+ LANGUAGES C
+@@ -108,6 +108,19 @@ foreach(TARGET_TYPE ${TARGET_TYPES})
+ -pedantic)
+ endif(UNIX)
+
++ if(VITA)
++ target_compile_options(
++ ${TARGET_NAME}
++ PRIVATE -Wl,-q -Wall -fshort-wchar)
++ target_link_libraries(
++ ${TARGET_NAME}
++ PRIVATE SceLibc_stub
++ )
++ target_link_options(
++ ${TARGET_NAME}
++ PUBLIC -nostartfiles -nostdlib)
++ endif()
++
+ # install targets
+ install(
+ TARGETS ${TARGET_NAME}
+diff --git a/src/iniparser.c b/src/iniparser.c
+index 1086b46..8e0e9c2 100644
+--- a/src/iniparser.c
++++ b/src/iniparser.c
+@@ -14,6 +14,15 @@
+ #include
+ #include "iniparser.h"
+
++#ifdef __vita__
++extern FILE* _Stderr;
++#undef stderr
++#define stderr _Stderr
++extern const char _Ctype[];
++#undef __locale_ctype_ptr
++#define __locale_ctype_ptr() _Ctype
++#endif
++
+ /*---------------------------- Defines -------------------------------------*/
+ #define ASCIILINESZ (1024)
+ #define INI_INVALID_KEY ((char*)-1)
diff --git a/CONFIG/vita/src/app.cpp b/CONFIG/vita/src/app.cpp
new file mode 100644
index 00000000..a97841db
--- /dev/null
+++ b/CONFIG/vita/src/app.cpp
@@ -0,0 +1,430 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+int sceLibcHeapSize = 10 * 1024 * 1024;
+
+const char* g_iniPath = "ux0:data/isledecomp/isle/isle.ini";
+
+paf::Framework* g_fw;
+paf::Plugin* g_configPlugin;
+sce::AppSettings* g_appSettings;
+sce::AppSettings::Interface* g_appSetIf;
+
+void merge_dicts(dictionary* dst, dictionary* src)
+{
+ for (int i = 0; i < src->n; i++) {
+ dictionary_set(dst, src->key[i], src->val[i]);
+ }
+}
+
+struct setting_map {
+ const char* key_ini;
+ const char* key_app;
+ const char type;
+};
+
+// mapping from ini key to settings.xml key
+const setting_map key_map[] = {
+ // Game
+ {"isle:diskpath", "disk_path", 's'},
+ {"isle:cdpath", "cd_path", 's'},
+ {"isle:savepath", "save_path", 's'},
+ {"isle:Transition Type", "transition_type", 'i'},
+ {"isle:Music", "music", 'b'},
+ {"isle:3DSound", "3d_sound", 'b'},
+
+ // Graphics
+ {"isle:Island Texture", "island_texture_quality", 'i'},
+ {"isle:Island Quality", "island_model_quality", 'i'},
+ //{"isle:Max LOD", "max_lod", 'f'},
+ //{"isle:Max Allowed Extras", "max_extras", 'i' },
+ {"isle:MSAA", "msaa", 'i'},
+
+ // Controls
+ {"isle:Touch Scheme", "touch_control_scheme", 'i'},
+ {"isle:Haptic", "rumble", 'b'},
+
+ // Extensions
+ {"extensions:texture loader", "texture_loader_extension", 'b'},
+ {"texture loader:texture path", "texture_loader_path", 's'}
+};
+
+struct Config {
+ sce::AppSettings* settings;
+ dictionary* dict;
+ char buffer[128];
+
+#define GetDictInt(x, name) x = iniparser_getint(this->dict, name, x)
+#define GetDictFloat(x, name) x = iniparser_getdouble(this->dict, name, x)
+#define GetDictString(x, name) \
+ { \
+ const char* val = iniparser_getstring(this->dict, name, nullptr); \
+ if (val != nullptr) { \
+ x = val; \
+ } \
+ }
+#define GetDictBool(x, name) x = iniparser_getboolean(this->dict, name, x)
+
+#define SetDictBool(NAME, VALUE) \
+ { \
+ const char* v = VALUE ? "true" : "false"; \
+ sceClibPrintf("SetIniBool(%s, %s)\n", NAME, v); \
+ iniparser_set(this->dict, NAME, v); \
+ }
+#define SetDictInt(NAME, VALUE) \
+ { \
+ sceClibSnprintf(buffer, sizeof(buffer), "%d", VALUE); \
+ sceClibPrintf("SetIniInt(%s, %d)\n", NAME, VALUE); \
+ iniparser_set(this->dict, NAME, buffer); \
+ }
+#define SetDictFloat(NAME, VALUE) \
+ { \
+ sceClibSnprintf(buffer, sizeof(buffer), "%f", VALUE); \
+ sceClibPrintf("SetIniFloat(%s, %f)\n", NAME, VALUE); \
+ iniparser_set(this->dict, NAME, buffer); \
+ }
+#define SetDictString(NAME, VALUE) \
+ { \
+ sceClibPrintf("SetString(%s, %s)\n", NAME, VALUE); \
+ iniparser_set(this->dict, NAME, VALUE); \
+ }
+
+ void Init(sce::AppSettings* settings)
+ {
+ this->settings = settings;
+ dict = dictionary_new(0);
+
+ // set defaults
+ iniparser_set(this->dict, "isle", nullptr);
+ iniparser_set(dict, "extensions", NULL);
+ iniparser_set(this->dict, "texture loader", nullptr);
+
+ SetDictString("isle:diskpath", "ux0:data/isledecomp/isle/disk");
+ SetDictString("isle:cdpath", "ux0:data/isledecomp/isle/cd");
+ SetDictString("isle:savepath", "ux0:data/isledecomp/isle");
+ SetDictInt("isle:MSAA", 4);
+
+ SetDictInt("isle:Display Bit Depth", 32);
+ SetDictBool("isle:Flip Surfaces", false);
+ SetDictBool("isle:Full Screen", true);
+ SetDictBool("isle:Exclusive Full Screen", true);
+ SetDictBool("isle:Wide View Angle", true);
+
+ SetDictInt("isle:Transition Type", 3); // 3: Mosaic
+ SetDictInt("isle:Touch Scheme", 2);
+
+ SetDictBool("isle:3DSound", true);
+ SetDictBool("isle:Music", true);
+ SetDictBool("isle:Haptic", true);
+
+ SetDictBool("isle:UseJoystick", true);
+ SetDictInt("isle:JoystickIndex", 0);
+ SetDictBool("isle:Draw Cursor", true);
+
+ SetDictBool("extensions:texture loader", false);
+ SetDictString("texture loader:texture path", "textures/");
+
+ SetDictBool("isle:Back Buffers in Video RAM", true);
+
+ SetDictInt("isle:Island Quality", 2);
+ SetDictInt("isle:Island Texture", 1);
+ SetDictInt("isle:MSAA", 4);
+
+ SetDictFloat("isle:Max LOD", 3.5);
+ SetDictInt("isle:Max Allowed Extras", 20);
+
+ SetDictInt("isle:Aspect Ratio", 0);
+ SetDictInt("isle:Horizontal Resolution", 640);
+ SetDictInt("isle:Vertical Resolution", 480);
+ SetDictFloat("isle:Frame Delta", 10.0f);
+ }
+
+ void LoadIni()
+ {
+ dictionary* ini = iniparser_load(g_iniPath);
+ if (ini) {
+ merge_dicts(this->dict, ini);
+ iniparser_freedict(ini);
+ }
+ }
+
+ bool SaveIni()
+ {
+ FILE* fd = fopen(g_iniPath, "w");
+ if (fd) {
+ iniparser_dump_ini(this->dict, fd);
+ }
+ else {
+ sceClibPrintf("failed to write isle.ini\n");
+ }
+ return true;
+ }
+
+ void ToSettings()
+ {
+ const int len = sizeof(key_map) / sizeof(key_map[0]);
+ for (int i = 0; i < len; i++) {
+ const setting_map m = key_map[i];
+ switch (m.type) {
+ case 'f': // float, AppSettings doesnt have float so just use string
+ case 's': {
+ const char* value = iniparser_getstring(this->dict, m.key_ini, "");
+ this->settings->SetString(m.key_app, value);
+ sceClibPrintf("ini->settings %s = %s\n", m.key_app, value);
+ break;
+ }
+ case 'i': {
+ int32_t value = iniparser_getint(this->dict, m.key_ini, 0);
+ this->settings->SetInt(m.key_app, value);
+ sceClibPrintf("ini->settings %s = %d\n", m.key_app, value);
+ break;
+ }
+ case 'b': {
+ bool value = iniparser_getboolean(this->dict, m.key_ini, 0) == 1;
+ this->settings->SetBool(m.key_app, value);
+ sceClibPrintf("ini->settings %s = %s\n", m.key_app, value ? "true" : "false");
+ break;
+ }
+ default: {
+ sceClibPrintf("invalid setting map entry %s %s %c\n", m.key_app, m.key_ini, m.type);
+ }
+ }
+ }
+ }
+
+ void FromSettings()
+ {
+ const int len = sizeof(key_map) / sizeof(key_map[0]);
+ for (int i = 0; i < len; i++) {
+ const setting_map m = key_map[i];
+ switch (m.type) {
+ case 'f': // float, AppSettings doesnt have float so just use string
+ case 's': {
+ const char* def = iniparser_getstring(this->dict, m.key_ini, "");
+ this->settings->GetString(m.key_app, this->buffer, sizeof(this->buffer), def);
+ iniparser_set(this->dict, m.key_ini, buffer);
+ sceClibPrintf("settings->ini %s = %s\n", m.key_ini, buffer);
+ break;
+ }
+ case 'i': {
+ int32_t value = iniparser_getint(this->dict, m.key_ini, 0);
+ this->settings->GetInt(m.key_app, &value, value);
+ SetDictInt(m.key_ini, value);
+ sceClibPrintf("settings->ini %s = %d\n", m.key_ini, value);
+ break;
+ }
+ case 'b': {
+ bool value = iniparser_getboolean(this->dict, m.key_ini, 0) == 1;
+ this->settings->GetBool(m.key_app, &value, value);
+ SetDictBool(m.key_ini, value);
+ sceClibPrintf("settings->ini %s = %s\n", m.key_ini, value ? "true" : "false");
+ break;
+ }
+ default: {
+ sceClibPrintf("invalid setting map entry %s %s %c\n", m.key_app, m.key_ini, m.type);
+ }
+ }
+ }
+ }
+};
+
+Config g_config;
+
+paf::Plugin* load_config_plugin(paf::Framework* paf_fw)
+{
+ paf::Plugin::InitParam pluginParam;
+ pluginParam.name = "config_plugin";
+ pluginParam.caller_name = "__main__";
+ pluginParam.resource_file = "app0:/config_plugin.rco";
+ pluginParam.init_func = NULL;
+ pluginParam.start_func = NULL;
+ pluginParam.stop_func = NULL;
+ pluginParam.exit_func = NULL;
+ paf::Plugin::LoadSync(pluginParam);
+ return paf_fw->FindPlugin("config_plugin");
+}
+
+int load_app_settings_plugin()
+{
+ paf::Plugin::InitParam pluginParam;
+ sceSysmoduleLoadModuleInternal(SCE_SYSMODULE_INTERNAL_BXCE);
+ sceSysmoduleLoadModuleInternal(SCE_SYSMODULE_INTERNAL_INI_FILE_PROCESSOR);
+ sceSysmoduleLoadModuleInternal(SCE_SYSMODULE_INTERNAL_COMMON_GUI_DIALOG);
+
+ pluginParam.name = "app_settings_plugin";
+ pluginParam.resource_file = "vs0:vsh/common/app_settings_plugin.rco";
+ pluginParam.caller_name = "__main__";
+ pluginParam.set_param_func = sce::AppSettings::PluginSetParamCB;
+ pluginParam.init_func = sce::AppSettings::PluginInitCB;
+ pluginParam.start_func = sce::AppSettings::PluginStartCB;
+ pluginParam.stop_func = sce::AppSettings::PluginStopCB;
+ pluginParam.exit_func = sce::AppSettings::PluginExitCB;
+ pluginParam.module_file = "vs0:vsh/common/app_settings.suprx";
+ pluginParam.draw_priority = 0x96;
+ paf::Plugin::LoadSync(pluginParam);
+ return 0;
+}
+
+int exit_type = 0;
+
+void save_and_exit()
+{
+ g_config.FromSettings();
+ g_config.SaveIni();
+ g_fw->RequestShutdown();
+ exit_type = 1;
+}
+
+void save_and_launch()
+{
+ g_config.FromSettings();
+ g_config.SaveIni();
+ g_fw->RequestShutdown();
+ exit_type = 2;
+}
+
+void CBOnStartPageTransition(const char* elementId, int32_t type)
+{
+}
+
+void CBOnPageActivate(const char* elementId, int32_t type)
+{
+}
+
+void CBOnPageDeactivate(const char* elementId, int32_t type)
+{
+}
+
+int32_t CBOnCheckVisible(const char* elementId, bool* pIsVisible)
+{
+ *pIsVisible = true;
+ return SCE_OK;
+}
+
+int32_t CBOnPreCreate(const char* elementId, sce::AppSettings::Element* element)
+{
+ return SCE_OK;
+}
+
+int32_t CBOnPostCreate(const char* elementId, paf::ui::Widget* widget)
+{
+ return SCE_OK;
+}
+
+int32_t CBOnPress(const char* elementId, const char* newValue)
+{
+ if (sce_paf_strcmp(elementId, "save_exit_button") == 0) {
+ save_and_exit();
+ return SCE_OK;
+ }
+
+ if (sce_paf_strcmp(elementId, "save_launch_button") == 0) {
+ save_and_launch();
+ return SCE_OK;
+ }
+
+ sceClibPrintf("OnPress %s %s\n", elementId, newValue);
+ return SCE_OK;
+}
+
+int32_t CBOnPress2(const char* elementId, const char* newValue)
+{
+ return SCE_OK;
+}
+
+void CBOnTerm(int32_t result)
+{
+ if (exit_type == 0) {
+ sceKernelExitProcess(0);
+ }
+}
+
+const wchar_t* CBOnGetString(const char* elementId)
+{
+ wchar_t* res = g_configPlugin->GetString(elementId);
+ if (res[0] != 0) {
+ return res;
+ }
+ return L"unknown string";
+}
+
+int32_t CBOnGetSurface(paf::graph::Surface** surf, const char* elementId)
+{
+ return SCE_OK;
+}
+
+void open_settings()
+{
+ g_config.Init(g_appSettings);
+ g_config.LoadIni();
+ g_config.ToSettings();
+
+ sce::AppSettings::InterfaceCallbacks ifCb;
+ ifCb.onStartPageTransitionCb = CBOnStartPageTransition;
+ ifCb.onPageActivateCb = CBOnPageActivate;
+ ifCb.onPageDeactivateCb = CBOnPageDeactivate;
+ ifCb.onCheckVisible = CBOnCheckVisible;
+ ifCb.onPreCreateCb = CBOnPreCreate;
+ ifCb.onPostCreateCb = CBOnPostCreate;
+ ifCb.onPressCb = CBOnPress;
+ ifCb.onPressCb2 = CBOnPress2;
+ ifCb.onTermCb = CBOnTerm;
+ ifCb.onGetStringCb = (sce::AppSettings::InterfaceCallbacks::GetStringCallback) CBOnGetString;
+ ifCb.onGetSurfaceCb = CBOnGetSurface;
+
+ paf::wstring msg_save_exit(g_configPlugin->GetString("msg_save_exit"));
+ paf::wstring msg_save_launch(g_configPlugin->GetString("msg_save_launch"));
+ paf::wstring msg_exit(g_configPlugin->GetString("msg_exit"));
+
+ paf::Plugin* appSetPlug = paf::Plugin::Find("app_settings_plugin");
+ g_appSetIf = (sce::AppSettings::Interface*) appSetPlug->GetInterface(1);
+ g_appSetIf->Show(&ifCb);
+ g_appSetIf->AddFooterButton("save_exit_button", &msg_save_exit, 1);
+ g_appSetIf->AddFooterButton("save_launch_button", &msg_save_launch, 2);
+ g_appSetIf->ShowFooter();
+}
+
+int main()
+{
+ paf::Framework::InitParam fwParam;
+ fwParam.mode = paf::Framework::Mode_Normal;
+
+ paf::Framework* paf_fw = new paf::Framework(fwParam);
+ g_fw = paf_fw;
+
+ paf_fw->LoadCommonResourceSync();
+ load_app_settings_plugin();
+ paf::Plugin* configPlugin = load_config_plugin(paf_fw);
+ g_configPlugin = configPlugin;
+ configPlugin->SetLocale(Locale_EN);
+
+ size_t fileSize = 0;
+ const char* mimeType = nullptr;
+ auto settingsXmlFile = configPlugin->GetResource()->GetFile("settings.xml", &fileSize, &mimeType);
+
+ sce::AppSettings::InitParam settingsParam;
+ settingsParam.xml_file = settingsXmlFile;
+ settingsParam.alloc_cb = sce_paf_malloc;
+ settingsParam.free_cb = sce_paf_free;
+ settingsParam.realloc_cb = sce_paf_realloc;
+ settingsParam.safemem_offset = 0;
+ settingsParam.safemem_size = 0x400;
+
+ sce::AppSettings::GetInstance(settingsParam, &g_appSettings);
+ g_appSettings->Initialize();
+
+ open_settings();
+ paf_fw->Run();
+
+ if (exit_type == 2) {
+ int ret = sceAppMgrLoadExec("app0:/eboot.bin", NULL, NULL);
+ printf("sceAppMgrLoadExec: %08x\n", ret);
+ }
+ return 0;
+}
diff --git a/CONFIG/vita/src/paf_runtime.cpp b/CONFIG/vita/src/paf_runtime.cpp
new file mode 100644
index 00000000..31266a1d
--- /dev/null
+++ b/CONFIG/vita/src/paf_runtime.cpp
@@ -0,0 +1,169 @@
+#include
+#include
+#include
+#include
+#include
+
+char sceUserMainThreadName[] = "isle_config";
+int sceUserMainThreadPriority = 0x10000100;
+int sceUserMainThreadCpuAffinityMask = 0x70000;
+SceSize sceUserMainThreadStackSize = 0x4000;
+
+void operator delete(void* ptr, unsigned int n)
+{
+ return sce_paf_free(ptr);
+}
+
+extern "C"
+{
+ void user_malloc_init()
+ {
+ }
+
+ void user_malloc_finalize(void)
+ {
+ }
+
+ void* user_malloc(size_t size)
+ {
+ return sce_paf_malloc(size);
+ }
+
+ void user_free(void* ptr)
+ {
+ sce_paf_free(ptr);
+ }
+
+ void* user_calloc(size_t nelem, size_t size)
+ {
+ return sce_paf_calloc(nelem, size);
+ }
+
+ void* user_realloc(void* ptr, size_t new_size)
+ {
+ return sce_paf_realloc(ptr, new_size);
+ }
+
+ void* user_memalign(size_t boundary, size_t size)
+ {
+ return sce_paf_memalign(boundary, size);
+ }
+
+ void* user_reallocalign(void* ptr, size_t size, size_t boundary)
+ {
+ sceClibPrintf("[PAF2LIBC] reallocalign is not supported\n");
+ abort();
+ return NULL;
+ }
+
+ int user_malloc_stats(struct malloc_managed_size* mmsize)
+ {
+ sceClibPrintf("malloc_stats\n");
+ abort();
+ return 0;
+ }
+
+ int user_malloc_stats_fast(struct malloc_managed_size* mmsize)
+ {
+ sceClibPrintf("user_malloc_stats_fast\n");
+ abort();
+ return 0;
+ }
+
+ size_t user_malloc_usable_size(void* ptr)
+ {
+ return sce_paf_musable_size(ptr);
+ }
+}
+
+void* user_new(std::size_t size)
+{
+ return sce_paf_malloc(size);
+}
+
+void* user_new(std::size_t size, const std::nothrow_t& x)
+{
+ return sce_paf_malloc(size);
+}
+
+void* user_new_array(std::size_t size)
+{
+ return sce_paf_malloc(size);
+}
+
+void* user_new_array(std::size_t size, const std::nothrow_t& x)
+{
+ return sce_paf_malloc(size);
+}
+
+void user_delete(void* ptr)
+{
+ sce_paf_free(ptr);
+}
+
+void user_delete(void* ptr, const std::nothrow_t& x)
+{
+ sce_paf_free(ptr);
+}
+
+void user_delete_array(void* ptr)
+{
+ sce_paf_free(ptr);
+}
+
+void user_delete_array(void* ptr, const std::nothrow_t& x)
+{
+ sce_paf_free(ptr);
+}
+
+int paf_init()
+{
+ int load_res;
+ ScePafInit init_param;
+ SceSysmoduleOpt sysmodule_opt;
+
+ init_param.global_heap_size = 0x1000000;
+ init_param.cdlg_mode = 0;
+ init_param.global_heap_alignment = 0;
+ init_param.global_heap_disable_assert_on_alloc_failure = 0;
+
+ load_res = 0xDEADBEEF;
+ sysmodule_opt.flags = 0;
+ sysmodule_opt.result = &load_res;
+
+ int res = sceSysmoduleLoadModuleInternalWithArg(
+ SCE_SYSMODULE_INTERNAL_PAF,
+ sizeof(init_param),
+ &init_param,
+ &sysmodule_opt
+ );
+ if ((res | load_res) != 0) {
+ sceClibPrintf(
+ "[PAF PRX Loader] Failed to load the PAF prx. (return value 0x%x, result code 0x%x )\n",
+ res,
+ load_res
+ );
+ return -1;
+ }
+ return 0;
+}
+
+int main();
+
+extern "C" int module_start(SceSize args, void* argp)
+{
+ int res = paf_init();
+ if (res < 0) {
+ sceKernelExitProcess(res);
+ return SCE_KERNEL_START_FAILED;
+ }
+
+ res = main();
+ sceKernelExitProcess(res);
+ return SCE_KERNEL_START_SUCCESS;
+}
+
+extern "C" void _start()
+{
+ module_start(0, nullptr);
+}
diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp
index c7444362..7b0fa23d 100644
--- a/ISLE/isleapp.cpp
+++ b/ISLE/isleapp.cpp
@@ -77,6 +77,14 @@
#include "android/config.h"
#endif
+#ifdef __vita__
+#include "vita/config.h"
+#include "vita/messagebox.h"
+
+#include
+#include
+#endif
+
DECOMP_SIZE_ASSERT(IsleApp, 0x8c)
// GLOBAL: ISLE 0x410030
@@ -316,6 +324,23 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
// Create global app instance
g_isle = new IsleApp();
+#ifdef __vita__
+ SceAppUtilInitParam appUtilInitParam = {0};
+ SceAppUtilBootParam appUtilBootParam = {0};
+ sceAppUtilInit(&appUtilInitParam, &appUtilBootParam);
+ SceAppUtilAppEventParam eventParam = {0};
+ sceAppUtilReceiveAppEvent(&eventParam);
+ if (eventParam.type == 0x05) {
+ g_isle->LoadConfig();
+ char buffer[2048];
+ sceAppUtilAppEventParseLiveArea(&eventParam, buffer);
+ if (strstr(buffer, "-config")) {
+ sceClibPrintf("Loading Config App.\n");
+ sceAppMgrLoadExec("app0:/isle-config.self", NULL, NULL);
+ }
+ }
+#endif
+
switch (g_isle->ParseArguments(argc, argv)) {
case SDL_APP_FAILURE:
Any_ShowSimpleMessageBox(
@@ -452,6 +477,19 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
break;
}
+#ifdef __vita__
+ // reject back touch panel
+ switch (event->type) {
+ case SDL_EVENT_FINGER_MOTION:
+ case SDL_EVENT_FINGER_DOWN:
+ case SDL_EVENT_FINGER_UP:
+ case SDL_EVENT_FINGER_CANCELED:
+ if (event->tfinger.touchID == 2) {
+ return SDL_APP_CONTINUE;
+ }
+ }
+#endif
+
switch (event->type) {
case SDL_EVENT_WINDOW_FOCUS_GAINED:
if (!IsleDebug_Enabled()) {
@@ -556,7 +594,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
}
break;
+#ifdef __vita__ // conflicts with screenshot button combination
+ case SDL_GAMEPAD_BUTTON_BACK:
+#else
case SDL_GAMEPAD_BUTTON_START:
+#endif
if (InputManager()) {
InputManager()->QueueEvent(c_notificationKeyPress, SDLK_ESCAPE, 0, 0, SDLK_ESCAPE);
}
@@ -884,7 +926,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);
-#if defined(MINIWIN) && !defined(__3DS__) && !defined(WINDOWS_STORE)
+#if defined(MINIWIN) && !defined(__3DS__) && !defined(WINDOWS_STORE) && !defined(__vita__)
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);
@@ -1110,6 +1152,9 @@ bool IsleApp::LoadConfig()
Android_SetupDefaultConfigOverrides(dict);
#endif
+#ifdef __vita__
+ VITA_SetupDefaultConfigOverrides(dict);
+#endif
iniparser_dump_ini(dict, iniFP);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig.GetData());
fclose(iniFP);
diff --git a/ISLE/vita/config.cpp b/ISLE/vita/config.cpp
new file mode 100644
index 00000000..40979ba2
--- /dev/null
+++ b/ISLE/vita/config.cpp
@@ -0,0 +1,13 @@
+#include "config.h"
+
+#include
+#include
+
+void VITA_SetupDefaultConfigOverrides(dictionary* p_dictionary)
+{
+ SDL_Log("Overriding default config for VITA");
+
+ iniparser_set(p_dictionary, "isle:diskpath", "ux0:data/isledecomp/isle/disk");
+ iniparser_set(p_dictionary, "isle:cdpath", "ux0:data/isledecomp/isle/cd");
+ iniparser_set(p_dictionary, "isle:MSAA", "4");
+}
diff --git a/ISLE/vita/config.h b/ISLE/vita/config.h
new file mode 100644
index 00000000..9c725c6f
--- /dev/null
+++ b/ISLE/vita/config.h
@@ -0,0 +1,8 @@
+#ifndef VITA_CONFIG_H
+#define VITA_CONFIG_H
+
+#include "dictionary.h"
+
+void VITA_SetupDefaultConfigOverrides(dictionary* p_dictionary);
+
+#endif // VITA_CONFIG_H
diff --git a/ISLE/vita/messagebox.cpp b/ISLE/vita/messagebox.cpp
new file mode 100644
index 00000000..6b3a485e
--- /dev/null
+++ b/ISLE/vita/messagebox.cpp
@@ -0,0 +1,52 @@
+#include "messagebox.h"
+
+#include "../../miniwin/src/d3drm/backends/gxm/gxm_context.h"
+
+#include
+#include
+
+bool Vita_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char* title, const char* message, SDL_Window* window)
+{
+ int ret;
+ SceMsgDialogParam param;
+ SceMsgDialogUserMessageParam msgParam;
+ SceMsgDialogButtonsParam buttonParam;
+ SceMsgDialogResult dialog_result;
+ SceCommonDialogErrorCode init_result;
+ bool setup_minimal_gxm = false;
+
+ SDL_zero(param);
+ sceMsgDialogParamInit(¶m);
+ param.mode = SCE_MSG_DIALOG_MODE_USER_MSG;
+
+ SDL_zero(msgParam);
+ char message_data[0x1000];
+ SDL_snprintf(message_data, sizeof(message_data), "%s\r\n\r\n%s", title, message);
+
+ msgParam.msg = (const SceChar8*) message_data;
+ msgParam.buttonType = SCE_MSG_DIALOG_BUTTON_TYPE_OK;
+ param.userMsgParam = &msgParam;
+
+ if (!gxm) {
+ gxm = (GXMContext*) SDL_malloc(sizeof(GXMContext));
+ }
+ if (ret = gxm->init(SCE_GXM_MULTISAMPLE_NONE); ret < 0) {
+ return false;
+ }
+
+ init_result = (SceCommonDialogErrorCode) sceMsgDialogInit(¶m);
+ if (init_result >= 0) {
+ while (sceMsgDialogGetStatus() == SCE_COMMON_DIALOG_STATUS_RUNNING) {
+ gxm->clear(0, 0, 0, true);
+ gxm->swap_display();
+ }
+ SDL_zero(dialog_result);
+ sceMsgDialogGetResult(&dialog_result);
+ sceMsgDialogTerm();
+ return dialog_result.buttonId == SCE_MSG_DIALOG_BUTTON_ID_OK;
+ }
+ else {
+ return false;
+ }
+ return true;
+}
diff --git a/ISLE/vita/messagebox.h b/ISLE/vita/messagebox.h
new file mode 100644
index 00000000..7ad877ee
--- /dev/null
+++ b/ISLE/vita/messagebox.h
@@ -0,0 +1,8 @@
+#ifndef VITA_MESSAGE_BOX_H
+#define VITA_MESSAGE_BOX_H
+
+#include
+
+bool Vita_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char* title, const char* message, SDL_Window* window);
+
+#endif // VITA_MESSAGE_BOX_H
diff --git a/README.md b/README.md
index a6bd8eff..435e7628 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ Please note: this project is primarily dedicated to achieving platform independe
| Xbox One | [](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
| iOS | [](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
| Android | [](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
+| Playstation Vita | [](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
We are actively working to support more platforms. If you have experience with a particular platform, we encourage you to contribute to `isle-portable`. You can find a [list of ongoing efforts](https://github.com/isledecomp/isle-portable/wiki/Work%E2%80%90in%E2%80%90progress-ports) in our Wiki.
diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt
index 07f40b04..4293780e 100644
--- a/miniwin/CMakeLists.txt
+++ b/miniwin/CMakeLists.txt
@@ -30,7 +30,7 @@ target_compile_definitions(miniwin PRIVATE
list(APPEND GRAPHICS_BACKENDS USE_SOFTWARE_RENDER)
list(APPEND GRAPHICS_BACKENDS USE_SDL_GPU)
-if(NOT WINDOWS_STORE)
+if(NOT (VITA OR WINDOWS_STORE))
find_package(OpenGL)
if(OpenGL_FOUND)
message(STATUS "Found OpenGL: enabling OpenGL 1.x renderer")
@@ -69,6 +69,25 @@ if(NOT WINDOWS_STORE)
endif()
endif()
+if(VITA)
+ add_subdirectory(src/d3drm/backends/gxm/shaders)
+
+ target_sources(miniwin PRIVATE
+ src/d3drm/backends/gxm/gxm_context.cpp
+ src/d3drm/backends/gxm/gxm_renderer.cpp
+ src/d3drm/backends/gxm/gxm_memory.cpp
+ src/d3drm/backends/gxm/tlsf.c
+ )
+ target_link_libraries(miniwin PRIVATE
+ SceGxm_stub
+ SceRazorCapture_stub
+ SceRazorHud_stub
+ taihen_stub
+ gxm_shaders
+ )
+ list(APPEND GRAPHICS_BACKENDS USE_GXM)
+ list(REMOVE_ITEM GRAPHICS_BACKENDS USE_SOFTWARE_RENDER USE_SDL_GPU)
+endif()
if(NINTENDO_3DS)
if(ISLE_DEBUG)
diff --git a/miniwin/src/d3drm/backends/gxm/gxm_context.cpp b/miniwin/src/d3drm/backends/gxm/gxm_context.cpp
new file mode 100644
index 00000000..1e13d1d0
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/gxm_context.cpp
@@ -0,0 +1,680 @@
+#include "gxm_context.h"
+
+#include "gxm_memory.h"
+#include "shaders/gxm_shaders.h"
+#include "tlsf.h"
+#include "utils.h"
+
+#include
+#include
+#include
+#include
+#include
+
+static bool gxm_initialized = false;
+
+bool with_razor_capture;
+bool with_razor_hud;
+
+#define CDRAM_POOL_SIZE 64 * 1024 * 1024
+#define VITA_GXM_COLOR_FORMAT SCE_GXM_COLOR_FORMAT_A8B8G8R8
+
+typedef struct GXMDisplayData {
+ void* address;
+ int index;
+} GXMDisplayData;
+
+static void display_callback(const void* callback_data)
+{
+ const GXMDisplayData* display_data = (const GXMDisplayData*) callback_data;
+
+ SceDisplayFrameBuf framebuf;
+ SDL_memset(&framebuf, 0x00, sizeof(SceDisplayFrameBuf));
+ framebuf.size = sizeof(SceDisplayFrameBuf);
+ framebuf.base = display_data->address;
+ framebuf.pitch = VITA_GXM_SCREEN_STRIDE;
+ framebuf.pixelformat = VITA_GXM_PIXEL_FORMAT;
+ framebuf.width = VITA_GXM_SCREEN_WIDTH;
+ framebuf.height = VITA_GXM_SCREEN_HEIGHT;
+ sceDisplaySetFrameBuf(&framebuf, SCE_DISPLAY_SETBUF_NEXTFRAME);
+ sceDisplayWaitSetFrameBuf();
+}
+
+#ifdef GXM_WITH_RAZOR
+static int load_suprx(const char* name)
+{
+ sceClibPrintf("loading %s\n", name);
+ int modid = _sceKernelLoadModule(name, 0, nullptr);
+ if (modid < 0) {
+ sceClibPrintf("%s load: 0x%08x\n", name, modid);
+ return modid;
+ }
+ int status;
+ int ret = sceKernelStartModule(modid, 0, nullptr, 0, nullptr, &status);
+ if (ret < 0) {
+ sceClibPrintf("%s start: 0x%08x\n", name, ret);
+ }
+ return ret;
+}
+
+static void load_razor()
+{
+ with_razor_capture = false;
+ with_razor_hud = false;
+ if (load_suprx("app0:librazorcapture_es4.suprx") >= 0) {
+ with_razor_capture = true;
+ }
+
+ if (with_razor_capture) {
+ // sceRazorGpuCaptureEnableSalvage("ux0:data/gpu_crash.sgx");
+ }
+
+ if (with_razor_hud) {
+ sceRazorGpuTraceSetFilename("ux0:data/gpu_trace", 3);
+ }
+}
+#endif
+
+int gxm_library_init()
+{
+ if (gxm_initialized) {
+ return 0;
+ }
+
+#ifdef GXM_WITH_RAZOR
+ load_razor();
+#endif
+
+ SceGxmInitializeParams initializeParams;
+ SDL_memset(&initializeParams, 0, sizeof(SceGxmInitializeParams));
+ initializeParams.flags = 0;
+ initializeParams.displayQueueMaxPendingCount = VITA_GXM_PENDING_SWAPS;
+ initializeParams.displayQueueCallback = display_callback;
+ initializeParams.displayQueueCallbackDataSize = sizeof(GXMDisplayData);
+ initializeParams.parameterBufferSize = SCE_GXM_DEFAULT_PARAMETER_BUFFER_SIZE;
+
+ int err = sceGxmInitialize(&initializeParams);
+ if (err != 0) {
+ SDL_LogError(SDL_LOG_CATEGORY_RENDER, "gxm init failed: %d", err);
+ return err;
+ }
+ gxm_initialized = true;
+ return 0;
+}
+
+GXMContext* gxm;
+
+void GXMContext::init_cdram_allocator()
+{
+ // allocator
+ this->cdramMem = vita_mem_alloc(
+ SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW,
+ CDRAM_POOL_SIZE,
+ 16,
+ SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE,
+ &this->cdramUID,
+ "cdram_pool"
+ );
+ this->cdramPool = SDL_malloc(tlsf_size());
+ tlsf_create(this->cdramPool);
+ tlsf_add_pool(this->cdramPool, this->cdramMem, CDRAM_POOL_SIZE);
+}
+
+int GXMContext::init_context()
+{
+ int ret;
+
+ const unsigned int patcherBufferSize = 64 * 1024;
+ const unsigned int patcherVertexUsseSize = 64 * 1024;
+ const unsigned int patcherFragmentUsseSize = 64 * 1024;
+
+ // allocate buffers
+ this->vdmRingBuffer = vita_mem_alloc(
+ SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE,
+ SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE,
+ 4,
+ SCE_GXM_MEMORY_ATTRIB_READ,
+ &this->vdmRingBufferUid,
+ "vdmRingBuffer"
+ );
+
+ this->vertexRingBuffer = vita_mem_alloc(
+ SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE,
+ SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE,
+ 4,
+ SCE_GXM_MEMORY_ATTRIB_READ,
+ &this->vertexRingBufferUid,
+ "vertexRingBuffer"
+ );
+
+ this->fragmentRingBuffer = vita_mem_alloc(
+ SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE,
+ SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE,
+ 4,
+ SCE_GXM_MEMORY_ATTRIB_READ,
+ &this->fragmentRingBufferUid,
+ "fragmentRingBuffer"
+ );
+
+ this->fragmentUsseRingBuffer = vita_mem_fragment_usse_alloc(
+ SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE,
+ &this->fragmentUsseRingBufferUid,
+ &this->fragmentUsseRingBufferOffset
+ );
+
+ // create context
+ SceGxmContextParams contextParams;
+ memset(&contextParams, 0, sizeof(SceGxmContextParams));
+ contextParams.hostMem = SDL_malloc(SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE);
+ contextParams.hostMemSize = SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE;
+ contextParams.vdmRingBufferMem = this->vdmRingBuffer;
+ contextParams.vdmRingBufferMemSize = SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE;
+ contextParams.vertexRingBufferMem = this->vertexRingBuffer;
+ contextParams.vertexRingBufferMemSize = SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE;
+ contextParams.fragmentRingBufferMem = this->fragmentRingBuffer;
+ contextParams.fragmentRingBufferMemSize = SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE;
+ contextParams.fragmentUsseRingBufferMem = this->fragmentUsseRingBuffer;
+ contextParams.fragmentUsseRingBufferMemSize = SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE;
+ contextParams.fragmentUsseRingBufferOffset = this->fragmentUsseRingBufferOffset;
+
+ ret = SCE_ERR(sceGxmCreateContext, &contextParams, &this->context);
+ if (ret < 0) {
+ return ret;
+ }
+ this->contextHostMem = contextParams.hostMem;
+
+ // shader patcher
+ this->patcherBuffer = vita_mem_alloc(
+ SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE,
+ patcherBufferSize,
+ 4,
+ SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE,
+ &this->patcherBufferUid,
+ "patcherBuffer"
+ );
+
+ this->patcherVertexUsse =
+ vita_mem_vertex_usse_alloc(patcherVertexUsseSize, &this->patcherVertexUsseUid, &this->patcherVertexUsseOffset);
+
+ this->patcherFragmentUsse = vita_mem_fragment_usse_alloc(
+ patcherFragmentUsseSize,
+ &this->patcherFragmentUsseUid,
+ &this->patcherFragmentUsseOffset
+ );
+
+ SceGxmShaderPatcherParams patcherParams;
+ memset(&patcherParams, 0, sizeof(SceGxmShaderPatcherParams));
+ patcherParams.userData = NULL;
+ patcherParams.hostAllocCallback = &patcher_host_alloc;
+ patcherParams.hostFreeCallback = &patcher_host_free;
+ patcherParams.bufferAllocCallback = NULL;
+ patcherParams.bufferFreeCallback = NULL;
+ patcherParams.bufferMem = this->patcherBuffer;
+ patcherParams.bufferMemSize = patcherBufferSize;
+ patcherParams.vertexUsseAllocCallback = NULL;
+ patcherParams.vertexUsseFreeCallback = NULL;
+ patcherParams.vertexUsseMem = this->patcherVertexUsse;
+ patcherParams.vertexUsseMemSize = patcherVertexUsseSize;
+ patcherParams.vertexUsseOffset = this->patcherVertexUsseOffset;
+ patcherParams.fragmentUsseAllocCallback = NULL;
+ patcherParams.fragmentUsseFreeCallback = NULL;
+ patcherParams.fragmentUsseMem = this->patcherFragmentUsse;
+ patcherParams.fragmentUsseMemSize = patcherFragmentUsseSize;
+ patcherParams.fragmentUsseOffset = this->patcherFragmentUsseOffset;
+
+ ret = SCE_ERR(sceGxmShaderPatcherCreate, &patcherParams, &this->shaderPatcher);
+ if (ret < 0) {
+ return ret;
+ }
+ return 0;
+}
+
+int GXMContext::create_display_buffers(SceGxmMultisampleMode msaaMode)
+{
+ int ret;
+
+ const uint32_t alignedWidth = ALIGN(VITA_GXM_SCREEN_WIDTH, SCE_GXM_TILE_SIZEX);
+ const uint32_t alignedHeight = ALIGN(VITA_GXM_SCREEN_HEIGHT, SCE_GXM_TILE_SIZEY);
+ uint32_t sampleCount = alignedWidth * alignedHeight;
+ uint32_t depthStrideInSamples = alignedWidth;
+
+ if (msaaMode == SCE_GXM_MULTISAMPLE_4X) {
+ sampleCount *= 4;
+ depthStrideInSamples *= 2;
+ }
+ else if (msaaMode == SCE_GXM_MULTISAMPLE_2X) {
+ sampleCount *= 2;
+ }
+
+ // render target
+ SceGxmRenderTargetParams renderTargetParams;
+ memset(&renderTargetParams, 0, sizeof(SceGxmRenderTargetParams));
+ renderTargetParams.flags = 0;
+ renderTargetParams.width = VITA_GXM_SCREEN_WIDTH;
+ renderTargetParams.height = VITA_GXM_SCREEN_HEIGHT;
+ renderTargetParams.scenesPerFrame = 1;
+ renderTargetParams.multisampleMode = msaaMode;
+ renderTargetParams.multisampleLocations = 0;
+ renderTargetParams.driverMemBlock = -1; // Invalid UID
+ ret = SCE_ERR(sceGxmCreateRenderTarget, &renderTargetParams, &this->renderTarget);
+ if (ret < 0) {
+ return ret;
+ }
+ this->renderTargetInit = true;
+
+ for (int i = 0; i < GXM_DISPLAY_BUFFER_COUNT; i++) {
+ this->displayBuffers[i] = vita_mem_alloc(
+ SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW,
+ 4 * VITA_GXM_SCREEN_STRIDE * VITA_GXM_SCREEN_HEIGHT,
+ SCE_GXM_COLOR_SURFACE_ALIGNMENT,
+ SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE,
+ &this->displayBuffersUid[i],
+ "displayBuffers"
+ );
+
+ ret = SCE_ERR(
+ sceGxmColorSurfaceInit,
+ &this->displayBuffersSurface[i],
+ SCE_GXM_COLOR_FORMAT_A8B8G8R8,
+ SCE_GXM_COLOR_SURFACE_LINEAR,
+ (msaaMode == SCE_GXM_MULTISAMPLE_NONE) ? SCE_GXM_COLOR_SURFACE_SCALE_NONE
+ : SCE_GXM_COLOR_SURFACE_SCALE_MSAA_DOWNSCALE,
+ SCE_GXM_OUTPUT_REGISTER_SIZE_32BIT,
+ VITA_GXM_SCREEN_WIDTH,
+ VITA_GXM_SCREEN_HEIGHT,
+ VITA_GXM_SCREEN_STRIDE,
+ this->displayBuffers[i]
+ );
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = SCE_ERR(sceGxmSyncObjectCreate, &this->displayBuffersSync[i]);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ // depth & stencil
+ this->depthBufferData = vita_mem_alloc(
+ SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE,
+ 4 * sampleCount,
+ SCE_GXM_DEPTHSTENCIL_SURFACE_ALIGNMENT,
+ SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE,
+ &this->depthBufferUid,
+ "depthBufferData"
+ );
+
+ this->stencilBufferData = vita_mem_alloc(
+ SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE,
+ 1 * sampleCount,
+ SCE_GXM_DEPTHSTENCIL_SURFACE_ALIGNMENT,
+ SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE,
+ &this->stencilBufferUid,
+ "stencilBufferData"
+ );
+
+ ret = SCE_ERR(
+ sceGxmDepthStencilSurfaceInit,
+ &this->depthSurface,
+ SCE_GXM_DEPTH_STENCIL_FORMAT_DF32_S8,
+ SCE_GXM_DEPTH_STENCIL_SURFACE_TILED,
+ depthStrideInSamples,
+ this->depthBufferData,
+ this->stencilBufferData
+ );
+ if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+void GXMContext::destroy_display_buffers()
+{
+ if (this->renderTargetInit) {
+ sceGxmFinish(this->context);
+ sceGxmDestroyRenderTarget(this->renderTarget);
+ this->renderTargetInit = false;
+ }
+ for (int i = 0; i < GXM_DISPLAY_BUFFER_COUNT; i++) {
+ if (this->displayBuffers[i]) {
+ vita_mem_free(this->displayBuffersUid[i]);
+ this->displayBuffers[i] = nullptr;
+ this->displayBuffersUid[i] = -1;
+ sceGxmSyncObjectDestroy(this->displayBuffersSync[i]);
+ }
+ }
+
+ if (this->depthBufferData) {
+ vita_mem_free(this->depthBufferUid);
+ this->depthBufferData = nullptr;
+ this->depthBufferUid = -1;
+ }
+
+ if (this->stencilBufferData) {
+ vita_mem_free(this->stencilBufferUid);
+ this->stencilBufferData = nullptr;
+ this->stencilBufferUid = -1;
+ }
+}
+
+void GXMContext::init_clear_mesh()
+{
+ this->clearVertices = static_cast(this->alloc(sizeof(GXMVertex2D) * 4, 4));
+ this->clearVertices[0] = {.position = {-1.0, 1.0}, .texCoord = {0, 0}};
+ this->clearVertices[1] = {.position = {1.0, 1.0}, .texCoord = {0, 0}};
+ this->clearVertices[2] = {.position = {-1.0, -1.0}, .texCoord = {0, 0}};
+ this->clearVertices[3] = {.position = {1.0, -1.0}, .texCoord = {0, 0}};
+
+ this->clearIndices = static_cast(this->alloc(sizeof(uint16_t) * 4, 4));
+ this->clearIndices[0] = 0;
+ this->clearIndices[1] = 1;
+ this->clearIndices[2] = 2;
+ this->clearIndices[3] = 3;
+}
+
+int GXMContext::register_base_shaders()
+{
+ int ret;
+ // register plane, color, image shaders
+ ret = SCE_ERR(
+ sceGxmShaderPatcherRegisterProgram,
+ this->shaderPatcher,
+ planeVertexProgramGxp,
+ &this->planeVertexProgramId
+ );
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = SCE_ERR(
+ sceGxmShaderPatcherRegisterProgram,
+ this->shaderPatcher,
+ colorFragmentProgramGxp,
+ &this->colorFragmentProgramId
+ );
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = SCE_ERR(
+ sceGxmShaderPatcherRegisterProgram,
+ this->shaderPatcher,
+ imageFragmentProgramGxp,
+ &this->imageFragmentProgramId
+ );
+ if (ret < 0) {
+ return ret;
+ }
+ this->color_uColor = sceGxmProgramFindParameterByName(colorFragmentProgramGxp, "uColor"); // vec4
+ return 0;
+}
+
+int GXMContext::patch_base_shaders(SceGxmMultisampleMode msaaMode)
+{
+ int ret;
+ {
+ GET_SHADER_PARAM(positionAttribute, planeVertexProgramGxp, "aPosition", -1);
+ GET_SHADER_PARAM(texCoordAttribute, planeVertexProgramGxp, "aTexCoord", -1);
+
+ SceGxmVertexAttribute vertexAttributes[2];
+ SceGxmVertexStream vertexStreams[1];
+
+ // position
+ vertexAttributes[0].streamIndex = 0;
+ vertexAttributes[0].offset = 0;
+ vertexAttributes[0].format = SCE_GXM_ATTRIBUTE_FORMAT_F32;
+ vertexAttributes[0].componentCount = 2;
+ vertexAttributes[0].regIndex = sceGxmProgramParameterGetResourceIndex(positionAttribute);
+
+ // uv
+ vertexAttributes[1].streamIndex = 0;
+ vertexAttributes[1].offset = 8;
+ vertexAttributes[1].format = SCE_GXM_ATTRIBUTE_FORMAT_F32;
+ vertexAttributes[1].componentCount = 2;
+ vertexAttributes[1].regIndex = sceGxmProgramParameterGetResourceIndex(texCoordAttribute);
+
+ vertexStreams[0].stride = sizeof(GXMVertex2D);
+ vertexStreams[0].indexSource = SCE_GXM_INDEX_SOURCE_INDEX_16BIT;
+
+ ret = SCE_ERR(
+ sceGxmShaderPatcherCreateVertexProgram,
+ this->shaderPatcher,
+ this->planeVertexProgramId,
+ vertexAttributes,
+ 2,
+ vertexStreams,
+ 1,
+ &this->planeVertexProgram
+ );
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = SCE_ERR(
+ sceGxmShaderPatcherCreateFragmentProgram,
+ this->shaderPatcher,
+ this->colorFragmentProgramId,
+ SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4,
+ msaaMode,
+ NULL,
+ planeVertexProgramGxp,
+ &this->colorFragmentProgram
+ );
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = SCE_ERR(
+ sceGxmShaderPatcherCreateFragmentProgram,
+ this->shaderPatcher,
+ this->imageFragmentProgramId,
+ SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4,
+ msaaMode,
+ &blendInfoTransparent,
+ planeVertexProgramGxp,
+ &this->imageFragmentProgram
+ );
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void GXMContext::destroy_base_shaders()
+{
+ sceGxmShaderPatcherReleaseVertexProgram(this->shaderPatcher, this->planeVertexProgram);
+ sceGxmShaderPatcherReleaseFragmentProgram(this->shaderPatcher, this->colorFragmentProgram);
+ sceGxmShaderPatcherReleaseFragmentProgram(this->shaderPatcher, this->imageFragmentProgram);
+}
+
+int GXMContext::init(SceGxmMultisampleMode msaaMode)
+{
+ int ret = 0;
+ ret = gxm_library_init();
+ if (ret < 0) {
+ return ret;
+ }
+ if (this->cdramPool == nullptr) {
+ this->init_cdram_allocator();
+ }
+
+ if (this->context == nullptr) {
+ ret = this->init_context();
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ if (this->planeVertexProgramId == 0) {
+ ret = this->register_base_shaders();
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ if (this->clearVertices == nullptr) {
+ this->init_clear_mesh();
+ }
+
+ // recreate when msaa is different
+ if (msaaMode != this->displayMsaa && this->renderTargetInit) {
+ this->destroy_display_buffers();
+ this->destroy_base_shaders();
+ }
+
+ if (!this->renderTargetInit) {
+ ret = this->create_display_buffers(msaaMode);
+ if (ret < 0) {
+ return ret;
+ }
+ ret = this->patch_base_shaders(msaaMode);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int inuse_mem = 0;
+void* GXMContext::alloc(size_t size, size_t align)
+{
+ if (this->cdramPool == nullptr) {
+ this->init_cdram_allocator();
+ }
+ DEBUG_ONLY_PRINTF("cdram_alloc(%d, %d) inuse=%d ", size, align, inuse_mem);
+ void* ptr = tlsf_memalign(this->cdramPool, align, size);
+ DEBUG_ONLY_PRINTF("ptr=%p\n", ptr);
+ inuse_mem += tlsf_block_size(ptr);
+ return ptr;
+}
+
+void GXMContext::free(void* ptr)
+{
+ inuse_mem -= tlsf_block_size(ptr);
+ DEBUG_ONLY_PRINTF("cdram_free(%p)\n", ptr);
+ tlsf_free(this->cdramPool, ptr);
+}
+
+void GXMContext::clear(float r, float g, float b, bool new_scene)
+{
+ new_scene = new_scene && !this->sceneStarted;
+ if (new_scene) {
+ sceGxmBeginScene(
+ this->context,
+ 0,
+ this->renderTarget,
+ nullptr,
+ nullptr,
+ this->displayBuffersSync[this->backBufferIndex],
+ &this->displayBuffersSurface[this->backBufferIndex],
+ &this->depthSurface
+ );
+ this->sceneStarted = true;
+ }
+
+ float color[] = {r, g, b, 1};
+
+ sceGxmSetVertexProgram(this->context, this->planeVertexProgram);
+ sceGxmSetFragmentProgram(this->context, this->colorFragmentProgram);
+
+ void* vertUniforms;
+ void* fragUniforms;
+ sceGxmReserveVertexDefaultUniformBuffer(gxm->context, &vertUniforms);
+ sceGxmReserveFragmentDefaultUniformBuffer(gxm->context, &fragUniforms);
+
+ sceGxmSetVertexStream(gxm->context, 0, this->clearVertices);
+ sceGxmSetUniformDataF(fragUniforms, this->color_uColor, 0, 4, color);
+
+ sceGxmSetFrontDepthFunc(gxm->context, SCE_GXM_DEPTH_FUNC_ALWAYS);
+ sceGxmDraw(gxm->context, SCE_GXM_PRIMITIVE_TRIANGLE_STRIP, SCE_GXM_INDEX_FORMAT_U16, this->clearIndices, 4);
+ if (new_scene) {
+ sceGxmEndScene(this->context, nullptr, nullptr);
+ this->sceneStarted = false;
+ }
+}
+
+void GXMContext::copy_frontbuffer()
+{
+ SceGxmTexture texture;
+ sceGxmTextureInitLinearStrided(
+ &texture,
+ this->displayBuffers[this->frontBufferIndex],
+ SCE_GXM_TEXTURE_FORMAT_U8U8U8U8_ABGR,
+ VITA_GXM_SCREEN_WIDTH,
+ VITA_GXM_SCREEN_HEIGHT,
+ VITA_GXM_SCREEN_STRIDE * 4
+ );
+ sceGxmSetVertexProgram(this->context, this->planeVertexProgram);
+ sceGxmSetFragmentProgram(this->context, this->imageFragmentProgram);
+
+ void* vertUniforms;
+ void* fragUniforms;
+ sceGxmReserveVertexDefaultUniformBuffer(this->context, &vertUniforms);
+ sceGxmReserveFragmentDefaultUniformBuffer(this->context, &fragUniforms);
+
+ sceGxmSetVertexStream(this->context, 0, this->clearVertices);
+ sceGxmSetFragmentTexture(this->context, 0, &texture);
+
+ sceGxmSetFrontDepthFunc(this->context, SCE_GXM_DEPTH_FUNC_ALWAYS);
+ sceGxmDraw(this->context, SCE_GXM_PRIMITIVE_TRIANGLE_STRIP, SCE_GXM_INDEX_FORMAT_U16, this->clearIndices, 4);
+}
+
+void GXMContext::destroy()
+{
+ sceGxmDisplayQueueFinish();
+ if (gxm->context) {
+ sceGxmFinish(gxm->context);
+ }
+
+ this->destroy_display_buffers();
+ this->destroy_base_shaders();
+
+ sceGxmShaderPatcherDestroy(this->shaderPatcher);
+ sceGxmDestroyContext(this->context);
+ vita_mem_fragment_usse_free(this->fragmentUsseRingBufferUid);
+ vita_mem_free(this->fragmentRingBufferUid);
+ vita_mem_free(this->vertexRingBufferUid);
+ vita_mem_free(this->vdmRingBufferUid);
+ SDL_free(this->contextHostMem);
+}
+
+void GXMContext::swap_display()
+{
+ if (this->sceneStarted) {
+ sceGxmEndScene(gxm->context, nullptr, nullptr);
+ this->sceneStarted = false;
+ }
+
+ SceCommonDialogUpdateParam updateParam;
+ SDL_zero(updateParam);
+ updateParam.renderTarget.colorFormat = VITA_GXM_COLOR_FORMAT;
+ updateParam.renderTarget.surfaceType = SCE_GXM_COLOR_SURFACE_LINEAR;
+ updateParam.renderTarget.width = VITA_GXM_SCREEN_WIDTH;
+ updateParam.renderTarget.height = VITA_GXM_SCREEN_HEIGHT;
+ updateParam.renderTarget.strideInPixels = VITA_GXM_SCREEN_STRIDE;
+ updateParam.renderTarget.colorSurfaceData = this->displayBuffers[this->backBufferIndex];
+ updateParam.displaySyncObject = this->displayBuffersSync[this->backBufferIndex];
+ sceCommonDialogUpdate(&updateParam);
+
+ sceGxmPadHeartbeat(
+ &this->displayBuffersSurface[this->backBufferIndex],
+ this->displayBuffersSync[this->backBufferIndex]
+ );
+
+ // display
+ GXMDisplayData displayData;
+ displayData.address = this->displayBuffers[this->backBufferIndex];
+ displayData.index = this->backBufferIndex;
+ sceGxmDisplayQueueAddEntry(
+ this->displayBuffersSync[this->frontBufferIndex],
+ this->displayBuffersSync[this->backBufferIndex],
+ &displayData
+ );
+
+ this->frontBufferIndex = this->backBufferIndex;
+ this->backBufferIndex = (this->backBufferIndex + 1) % GXM_DISPLAY_BUFFER_COUNT;
+}
diff --git a/miniwin/src/d3drm/backends/gxm/gxm_context.h b/miniwin/src/d3drm/backends/gxm/gxm_context.h
new file mode 100644
index 00000000..3b867f8d
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/gxm_context.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#include
+
+#define VITA_GXM_SCREEN_WIDTH 960
+#define VITA_GXM_SCREEN_HEIGHT 544
+#define VITA_GXM_SCREEN_STRIDE 1024
+#define VITA_GXM_PENDING_SWAPS 2
+#define VITA_GXM_PIXEL_FORMAT SCE_DISPLAY_PIXELFORMAT_A8B8G8R8
+
+#define GXM_DISPLAY_BUFFER_COUNT 3
+
+typedef struct GXMVertex2D {
+ float position[2];
+ float texCoord[2];
+} GXMVertex2D;
+
+typedef struct GXMContext {
+ // context
+ SceUID vdmRingBufferUid;
+ SceUID vertexRingBufferUid;
+ SceUID fragmentRingBufferUid;
+ SceUID fragmentUsseRingBufferUid;
+ size_t fragmentUsseRingBufferOffset;
+
+ void* vdmRingBuffer;
+ void* vertexRingBuffer;
+ void* fragmentRingBuffer;
+ void* fragmentUsseRingBuffer;
+
+ void* contextHostMem;
+ SceGxmContext* context;
+
+ // shader patcher
+ SceUID patcherBufferUid;
+ void* patcherBuffer;
+
+ SceUID patcherVertexUsseUid;
+ size_t patcherVertexUsseOffset;
+ void* patcherVertexUsse;
+ SceUID patcherFragmentUsseUid;
+ size_t patcherFragmentUsseOffset;
+ void* patcherFragmentUsse;
+
+ SceGxmShaderPatcher* shaderPatcher;
+
+ // clear
+ SceGxmShaderPatcherId planeVertexProgramId;
+ SceGxmShaderPatcherId colorFragmentProgramId;
+ SceGxmShaderPatcherId imageFragmentProgramId;
+ SceGxmVertexProgram* planeVertexProgram;
+ SceGxmFragmentProgram* colorFragmentProgram;
+ SceGxmFragmentProgram* imageFragmentProgram;
+ const SceGxmProgramParameter* color_uColor;
+ GXMVertex2D* clearVertices;
+ uint16_t* clearIndices;
+
+ // display
+ SceGxmRenderTarget* renderTarget;
+ bool renderTargetInit = false;
+ void* displayBuffers[GXM_DISPLAY_BUFFER_COUNT];
+ SceUID displayBuffersUid[GXM_DISPLAY_BUFFER_COUNT];
+ SceGxmColorSurface displayBuffersSurface[GXM_DISPLAY_BUFFER_COUNT];
+ SceGxmSyncObject* displayBuffersSync[GXM_DISPLAY_BUFFER_COUNT];
+ int backBufferIndex = 0;
+ int frontBufferIndex = 1;
+ SceGxmMultisampleMode displayMsaa;
+
+ // depth buffer
+ SceUID depthBufferUid;
+ void* depthBufferData;
+ SceUID stencilBufferUid;
+ void* stencilBufferData;
+ SceGxmDepthStencilSurface depthSurface;
+
+ // allocator
+ SceUID cdramUID;
+ void* cdramMem;
+ void* cdramPool;
+
+ bool sceneStarted;
+
+ void swap_display();
+ void copy_frontbuffer();
+ int init(SceGxmMultisampleMode msaaMode);
+ void init_cdram_allocator();
+ int init_context();
+ int create_display_buffers(SceGxmMultisampleMode msaaMode);
+ void init_clear_mesh();
+ void destroy_display_buffers();
+ int register_base_shaders();
+ int patch_base_shaders(SceGxmMultisampleMode msaaMode);
+ void destroy_base_shaders();
+
+ void destroy();
+ void clear(float r, float g, float b, bool new_scene);
+ void* alloc(size_t size, size_t align);
+ void free(void* ptr);
+} GXMContext;
+
+// global so that common dialog can be rendererd without GXMRenderer
+extern GXMContext* gxm;
+
+extern bool with_razor_capture;
+extern bool with_razor_hud;
diff --git a/miniwin/src/d3drm/backends/gxm/gxm_memory.cpp b/miniwin/src/d3drm/backends/gxm/gxm_memory.cpp
new file mode 100644
index 00000000..01313d0a
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/gxm_memory.cpp
@@ -0,0 +1,119 @@
+#include "gxm_memory.h"
+
+#include "tlsf.h"
+#include "utils.h"
+
+#include
+#include
+#include
+#include
+
+void* patcher_host_alloc(void* user_data, unsigned int size)
+{
+ void* mem = SDL_malloc(size);
+ (void) user_data;
+ return mem;
+}
+
+void patcher_host_free(void* user_data, void* mem)
+{
+ (void) user_data;
+ SDL_free(mem);
+}
+
+void* vita_mem_alloc(unsigned int type, size_t size, size_t alignment, int attribs, SceUID* uid, const char* name)
+{
+ void* mem;
+
+ if (type == SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW) {
+ size = ALIGN(size, 256 * 1024);
+ }
+ else if (type == SCE_KERNEL_MEMBLOCK_TYPE_USER_MAIN_PHYCONT_NC_RW) {
+ size = ALIGN(size, 1024 * 1024);
+ }
+ else {
+ size = ALIGN(size, 4 * 1024);
+ }
+
+ *uid = sceKernelAllocMemBlock(name, type, size, NULL);
+
+ if (*uid < 0) {
+ SDL_Log("sceKernelAllocMemBlock: 0x%x", *uid);
+ return NULL;
+ }
+
+ if (sceKernelGetMemBlockBase(*uid, &mem) < 0) {
+ return NULL;
+ }
+
+ if (sceGxmMapMemory(mem, size, (SceGxmMemoryAttribFlags) attribs) < 0) {
+ SDL_Log("sceGxmMapMemory 0x%x 0x%x %d failed", mem, size, attribs);
+ return NULL;
+ }
+
+ return mem;
+}
+
+void vita_mem_free(SceUID uid)
+{
+ void* mem = NULL;
+ if (sceKernelGetMemBlockBase(uid, &mem) < 0) {
+ return;
+ }
+ sceGxmUnmapMemory(mem);
+ sceKernelFreeMemBlock(uid);
+}
+
+void* vita_mem_vertex_usse_alloc(unsigned int size, SceUID* uid, unsigned int* usse_offset)
+{
+ void* mem = NULL;
+
+ size = ALIGN(size, 4096);
+ *uid = sceKernelAllocMemBlock("vertex_usse", SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE, size, NULL);
+
+ if (sceKernelGetMemBlockBase(*uid, &mem) < 0) {
+ return NULL;
+ }
+ if (sceGxmMapVertexUsseMemory(mem, size, usse_offset) < 0) {
+ return NULL;
+ }
+
+ return mem;
+}
+
+void vita_mem_vertex_usse_free(SceUID uid)
+{
+ void* mem = NULL;
+ if (sceKernelGetMemBlockBase(uid, &mem) < 0) {
+ return;
+ }
+ sceGxmUnmapVertexUsseMemory(mem);
+ sceKernelFreeMemBlock(uid);
+}
+
+void* vita_mem_fragment_usse_alloc(unsigned int size, SceUID* uid, unsigned int* usse_offset)
+{
+ void* mem = NULL;
+
+ size = ALIGN(size, 4096);
+ *uid = sceKernelAllocMemBlock("fragment_usse", SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE, size, NULL);
+
+ if (sceKernelGetMemBlockBase(*uid, &mem) < 0) {
+ return NULL;
+ }
+ if (sceGxmMapFragmentUsseMemory(mem, size, usse_offset) < 0) {
+ return NULL;
+ }
+
+ return mem;
+}
+
+void vita_mem_fragment_usse_free(SceUID uid)
+{
+ void* mem = NULL;
+ if (sceKernelGetMemBlockBase(uid, &mem) < 0) {
+ return;
+ }
+ sceGxmUnmapFragmentUsseMemory(mem);
+ sceKernelFreeMemBlock(uid);
+}
diff --git a/miniwin/src/d3drm/backends/gxm/gxm_memory.h b/miniwin/src/d3drm/backends/gxm/gxm_memory.h
new file mode 100644
index 00000000..f169603d
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/gxm_memory.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+#include
+
+void* patcher_host_alloc(void* user_data, unsigned int size);
+void patcher_host_free(void* user_data, void* mem);
+
+void* vita_mem_alloc(unsigned int type, size_t size, size_t alignment, int attribs, SceUID* uid, const char* name);
+void vita_mem_free(SceUID uid);
+
+void* vita_mem_vertex_usse_alloc(unsigned int size, SceUID* uid, unsigned int* usse_offset);
+void vita_mem_vertex_usse_free(SceUID uid);
+void* vita_mem_fragment_usse_alloc(unsigned int size, SceUID* uid, unsigned int* usse_offset);
+void vita_mem_fragment_usse_free(SceUID uid);
diff --git a/miniwin/src/d3drm/backends/gxm/gxm_renderer.cpp b/miniwin/src/d3drm/backends/gxm/gxm_renderer.cpp
new file mode 100644
index 00000000..efb25bdd
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/gxm_renderer.cpp
@@ -0,0 +1,1101 @@
+#include "d3drmrenderer_gxm.h"
+#include "gxm_context.h"
+#include "gxm_memory.h"
+#include "meshutils.h"
+#include "razor.h"
+#include "shaders/gxm_shaders.h"
+#include "tlsf.h"
+#include "utils.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// from isleapp
+extern bool g_dpadUp;
+extern bool g_dpadDown;
+extern bool g_dpadLeft;
+extern bool g_dpadRight;
+
+typedef struct GXMVertex {
+ float position[3];
+ float normal[3];
+ float texCoord[2];
+} GXMVertex;
+
+Direct3DRMRenderer* GXMRenderer::Create(DWORD width, DWORD height, DWORD msaaSamples)
+{
+ int ret = gxm_library_init();
+ if (ret < 0) {
+ return nullptr;
+ }
+
+ SceGxmMultisampleMode msaaMode = SCE_GXM_MULTISAMPLE_NONE;
+ if (msaaSamples == 2) {
+ msaaMode = SCE_GXM_MULTISAMPLE_2X;
+ }
+ if (msaaSamples == 4) {
+ msaaMode = SCE_GXM_MULTISAMPLE_4X;
+ }
+
+ if (!gxm) {
+ gxm = (GXMContext*) SDL_malloc(sizeof(GXMContext));
+ memset(gxm, 0, sizeof(GXMContext));
+ }
+ ret = SCE_ERR(gxm->init, msaaMode);
+ if (ret < 0) {
+ return nullptr;
+ }
+
+ return new GXMRenderer(width, height, msaaMode);
+}
+
+GXMRenderer::GXMRenderer(DWORD width, DWORD height, SceGxmMultisampleMode msaaMode)
+{
+ m_width = VITA_GXM_SCREEN_WIDTH;
+ m_height = VITA_GXM_SCREEN_HEIGHT;
+ m_virtualWidth = width;
+ m_virtualHeight = height;
+
+ // register shader programs
+ int ret = SCE_ERR(
+ sceGxmShaderPatcherRegisterProgram,
+ gxm->shaderPatcher,
+ mainVertexProgramGxp,
+ &this->mainVertexProgramId
+ );
+ if (ret < 0) {
+ return;
+ }
+
+ ret = SCE_ERR(
+ sceGxmShaderPatcherRegisterProgram,
+ gxm->shaderPatcher,
+ mainColorFragmentProgramGxp,
+ &this->mainColorFragmentProgramId
+ );
+ if (ret < 0) {
+ return;
+ }
+
+ ret = SCE_ERR(
+ sceGxmShaderPatcherRegisterProgram,
+ gxm->shaderPatcher,
+ mainTextureFragmentProgramGxp,
+ &this->mainTextureFragmentProgramId
+ );
+ if (ret < 0) {
+ return;
+ }
+
+ // main shader
+ {
+ GET_SHADER_PARAM(positionAttribute, mainVertexProgramGxp, "aPosition", );
+ GET_SHADER_PARAM(normalAttribute, mainVertexProgramGxp, "aNormal", );
+ GET_SHADER_PARAM(texCoordAttribute, mainVertexProgramGxp, "aTexCoord", );
+
+ SceGxmVertexAttribute vertexAttributes[3];
+ SceGxmVertexStream vertexStreams[1];
+
+ // position
+ vertexAttributes[0].streamIndex = 0;
+ vertexAttributes[0].offset = 0;
+ vertexAttributes[0].format = SCE_GXM_ATTRIBUTE_FORMAT_F32;
+ vertexAttributes[0].componentCount = 3;
+ vertexAttributes[0].regIndex = sceGxmProgramParameterGetResourceIndex(positionAttribute);
+
+ // normal
+ vertexAttributes[1].streamIndex = 0;
+ vertexAttributes[1].offset = 12;
+ vertexAttributes[1].format = SCE_GXM_ATTRIBUTE_FORMAT_F32;
+ vertexAttributes[1].componentCount = 3;
+ vertexAttributes[1].regIndex = sceGxmProgramParameterGetResourceIndex(normalAttribute);
+
+ vertexAttributes[2].streamIndex = 0;
+ vertexAttributes[2].offset = 24;
+ vertexAttributes[2].format = SCE_GXM_ATTRIBUTE_FORMAT_F32;
+ vertexAttributes[2].componentCount = 2;
+ vertexAttributes[2].regIndex = sceGxmProgramParameterGetResourceIndex(texCoordAttribute);
+
+ vertexStreams[0].stride = sizeof(GXMVertex);
+ vertexStreams[0].indexSource = SCE_GXM_INDEX_SOURCE_INDEX_16BIT;
+
+ ret = SCE_ERR(
+ sceGxmShaderPatcherCreateVertexProgram,
+ gxm->shaderPatcher,
+ this->mainVertexProgramId,
+ vertexAttributes,
+ 3,
+ vertexStreams,
+ 1,
+ &this->mainVertexProgram
+ );
+ if (ret < 0) {
+ return;
+ }
+ }
+
+ // main color opaque
+ ret = SCE_ERR(
+ sceGxmShaderPatcherCreateFragmentProgram,
+ gxm->shaderPatcher,
+ this->mainColorFragmentProgramId,
+ SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4,
+ msaaMode,
+ &blendInfoOpaque,
+ mainVertexProgramGxp,
+ &this->opaqueColorFragmentProgram
+ );
+ if (ret < 0) {
+ return;
+ }
+
+ // main color blended
+ ret = SCE_ERR(
+ sceGxmShaderPatcherCreateFragmentProgram,
+ gxm->shaderPatcher,
+ this->mainColorFragmentProgramId,
+ SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4,
+ msaaMode,
+ &blendInfoTransparent,
+ mainVertexProgramGxp,
+ &this->blendedColorFragmentProgram
+ );
+ if (ret < 0) {
+ return;
+ }
+
+ // main texture opaque
+ ret = SCE_ERR(
+ sceGxmShaderPatcherCreateFragmentProgram,
+ gxm->shaderPatcher,
+ this->mainTextureFragmentProgramId,
+ SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4,
+ msaaMode,
+ &blendInfoOpaque,
+ mainVertexProgramGxp,
+ &this->opaqueTextureFragmentProgram
+ );
+ if (ret < 0) {
+ return;
+ }
+
+ // main texture transparent
+ ret = SCE_ERR(
+ sceGxmShaderPatcherCreateFragmentProgram,
+ gxm->shaderPatcher,
+ this->mainTextureFragmentProgramId,
+ SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4,
+ msaaMode,
+ &blendInfoTransparent,
+ mainVertexProgramGxp,
+ &this->blendedTextureFragmentProgram
+ );
+ if (ret < 0) {
+ return;
+ }
+
+ // vertex uniforms
+ this->uModelViewMatrix = sceGxmProgramFindParameterByName(mainVertexProgramGxp, "uModelViewMatrix");
+ this->uNormalMatrix = sceGxmProgramFindParameterByName(mainVertexProgramGxp, "uNormalMatrix");
+ this->uProjectionMatrix = sceGxmProgramFindParameterByName(mainVertexProgramGxp, "uProjectionMatrix");
+
+ // fragment uniforms
+ this->uLights = sceGxmProgramFindParameterByName(mainColorFragmentProgramGxp, "uLights"); // SceneLight[2]
+ this->uAmbientLight = sceGxmProgramFindParameterByName(mainColorFragmentProgramGxp, "uAmbientLight"); // vec3
+ this->uShininess = sceGxmProgramFindParameterByName(mainColorFragmentProgramGxp, "uShininess"); // float
+ this->uColor = sceGxmProgramFindParameterByName(mainColorFragmentProgramGxp, "uColor"); // vec4
+
+ for (int i = 0; i < GXM_FRAGMENT_BUFFER_COUNT; i++) {
+ this->lights[i] = static_cast(gxm->alloc(sizeof(GXMSceneLightUniform), 4));
+ }
+ for (int i = 0; i < GXM_VERTEX_BUFFER_COUNT; i++) {
+ this->quadVertices[i] = static_cast(gxm->alloc(sizeof(GXMVertex2D) * 4 * 50, 4));
+ }
+ this->quadIndices = static_cast(gxm->alloc(sizeof(uint16_t) * 4, 4));
+ this->quadIndices[0] = 0;
+ this->quadIndices[1] = 1;
+ this->quadIndices[2] = 2;
+ this->quadIndices[3] = 3;
+
+ volatile uint32_t* notificationMem = sceGxmGetNotificationRegion();
+ for (uint32_t i = 0; i < GXM_FRAGMENT_BUFFER_COUNT; i++) {
+ this->fragmentNotifications[i].address = notificationMem++;
+ this->fragmentNotifications[i].value = 0;
+ }
+ this->currentFragmentBufferIndex = 0;
+
+ for (uint32_t i = 0; i < GXM_VERTEX_BUFFER_COUNT; i++) {
+ this->vertexNotifications[i].address = notificationMem++;
+ this->vertexNotifications[i].value = 0;
+ }
+ this->currentVertexBufferIndex = 0;
+ m_initialized = true;
+}
+
+GXMRenderer::~GXMRenderer()
+{
+ for (int i = 0; i < GXM_FRAGMENT_BUFFER_COUNT; i++) {
+ if (this->lights[i]) {
+ gxm->free(this->lights[i]);
+ }
+ }
+ for (int i = 0; i < GXM_VERTEX_BUFFER_COUNT; i++) {
+ if (this->quadVertices[i]) {
+ gxm->free(this->quadVertices[i]);
+ }
+ }
+ if (this->quadIndices) {
+ gxm->free(this->quadIndices);
+ }
+}
+
+void GXMRenderer::PushLights(const SceneLight* lightsArray, size_t count)
+{
+ if (count > 3) {
+ SDL_Log("Unsupported number of lights (%d)", static_cast(count));
+ count = 3;
+ }
+
+ m_lights.assign(lightsArray, lightsArray + count);
+}
+
+void GXMRenderer::SetFrustumPlanes(const Plane* frustumPlanes)
+{
+}
+
+void GXMRenderer::SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back)
+{
+ memcpy(&m_projection, projection, sizeof(D3DRMMATRIX4D));
+}
+
+struct TextureDestroyContextGXM {
+ GXMRenderer* renderer;
+ Uint32 textureId;
+};
+
+void GXMRenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture)
+{
+ auto* ctx = new TextureDestroyContextGXM{this, id};
+ texture->AddDestroyCallback(
+ [](IDirect3DRMObject* obj, void* arg) {
+ auto* ctx = static_cast(arg);
+ auto& cache = ctx->renderer->m_textures[ctx->textureId];
+ ctx->renderer->m_textures_delete[gxm->backBufferIndex].push_back(cache.gxmTexture);
+ cache.texture = nullptr;
+ memset(&cache.gxmTexture, 0, sizeof(SceGxmTexture));
+ delete ctx;
+ },
+ ctx
+ );
+}
+
+void GXMRenderer::DeferredDelete(int index)
+{
+ for (auto& del : this->m_textures_delete[index]) {
+ void* textureData = sceGxmTextureGetData(&del);
+ gxm->free(textureData);
+ }
+ this->m_textures_delete[index].clear();
+
+ for (auto& del : this->m_buffers_delete[index]) {
+ gxm->free(del);
+ }
+ this->m_buffers_delete[index].clear();
+}
+
+static int calculateMipLevels(int width, int height)
+{
+ if (width <= 0 || height <= 0) {
+ return 1;
+ }
+ int maxDim = (width > height) ? width : height;
+ return floor(log2(maxDim)) + 1;
+}
+
+static int nextPowerOf2(int n)
+{
+ if (n <= 0) {
+ return 1;
+ }
+ n--;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ n++;
+ return n;
+}
+
+static void convertTextureMetadata(
+ SDL_Surface* surface,
+ bool isUi,
+ bool* supportedFormat,
+ SceGxmTextureFormat* gxmTextureFormat,
+ size_t* textureSize, // size in bytes
+ size_t* textureAlignment, // alignment in bytes
+ size_t* textureStride, // stride in bytes
+ size_t* paletteOffset, // offset from textureData in bytes
+ size_t* mipLevels
+)
+{
+ int bytesPerPixel;
+ size_t paletteSize = 0;
+ *mipLevels = 1;
+
+ switch (surface->format) {
+ case SDL_PIXELFORMAT_INDEX8: {
+ *supportedFormat = true;
+ *gxmTextureFormat = SCE_GXM_TEXTURE_FORMAT_P8_ABGR;
+ *textureAlignment = SCE_GXM_PALETTE_ALIGNMENT;
+ bytesPerPixel = 1;
+ if (!isUi) {
+ *mipLevels = calculateMipLevels(surface->w, surface->h);
+ }
+ paletteSize = 256 * 4; // palette
+ break;
+ }
+ case SDL_PIXELFORMAT_ABGR8888: {
+ *supportedFormat = true;
+ *gxmTextureFormat = SCE_GXM_TEXTURE_FORMAT_U8U8U8U8_ABGR;
+ *textureAlignment = SCE_GXM_TEXTURE_ALIGNMENT;
+ bytesPerPixel = 4;
+ if (!isUi) {
+ *mipLevels = calculateMipLevels(surface->w, surface->h);
+ }
+ break;
+ }
+ default: {
+ *supportedFormat = false;
+ *gxmTextureFormat = SCE_GXM_TEXTURE_FORMAT_U8U8U8U8_ABGR;
+ *textureAlignment = SCE_GXM_TEXTURE_ALIGNMENT;
+ bytesPerPixel = 4;
+ break;
+ }
+ }
+ *textureStride = ALIGN(surface->w, 8) * bytesPerPixel;
+
+ *mipLevels = 1; // look weird
+
+ size_t totalSize = 0;
+ int currentW = surface->w;
+ int currentH = surface->h;
+
+ // top mip
+ totalSize += (ALIGN(currentW, 8) * currentH) * bytesPerPixel;
+
+ for (size_t i = 1; i < *mipLevels; ++i) {
+ currentW = currentW > 1 ? currentW / 2 : 1;
+ currentH = currentH > 1 ? currentH / 2 : 1;
+ int po2W = nextPowerOf2(currentW * 2);
+ int po2H = nextPowerOf2(currentH * 2);
+ if (po2W < 8) {
+ po2W = 8;
+ }
+ totalSize += po2W * po2H * bytesPerPixel;
+ }
+
+ if (paletteSize != 0) {
+ int alignBytes = ALIGNMENT(totalSize, SCE_GXM_PALETTE_ALIGNMENT);
+ totalSize += alignBytes;
+ *paletteOffset = totalSize;
+ totalSize += paletteSize;
+ }
+
+ *textureSize = totalSize;
+}
+
+void copySurfaceToGxmARGB888(SDL_Surface* src, uint8_t* textureData, size_t dstStride, size_t mipLevels)
+{
+ uint8_t* currentLevelData = textureData;
+ uint32_t currentLevelWidth = src->w;
+ uint32_t currentLevelHeight = src->h;
+ size_t bytesPerPixel = 4;
+
+ // copy top level mip (cant use transfer because this isnt gpu mapped)
+ size_t topLevelStride = ALIGN(currentLevelWidth, 8) * bytesPerPixel;
+ for (int y = 0; y < currentLevelHeight; y++) {
+ uint8_t* srcRow = (uint8_t*) src->pixels + (y * src->pitch);
+ uint8_t* dstRow = textureData + (y * topLevelStride);
+ memcpy(dstRow, srcRow, currentLevelWidth * bytesPerPixel);
+ }
+
+ for (size_t i = 1; i < mipLevels; ++i) {
+ uint32_t currentLevelSrcStride = SDL_max(currentLevelWidth, 8) * bytesPerPixel;
+ uint32_t currentLevelDestStride = SDL_max((currentLevelWidth / 2), 8) * bytesPerPixel;
+ uint8_t* nextLevelData = currentLevelData + (currentLevelSrcStride * currentLevelHeight);
+
+ sceGxmTransferDownscale(
+ SCE_GXM_TRANSFER_FORMAT_U8U8U8U8_ABGR, // src format
+ currentLevelData, // src address
+ 0,
+ 0,
+ currentLevelWidth,
+ currentLevelHeight, // x,y,w,h
+ currentLevelSrcStride, // stride
+ SCE_GXM_TRANSFER_FORMAT_U8U8U8U8_ABGR, // dst format
+ nextLevelData, // dst address
+ 0,
+ 0, // x,y
+ currentLevelDestStride, // stride
+ NULL, // sync
+ SCE_GXM_TRANSFER_FRAGMENT_SYNC, // flag
+ NULL // notification
+ );
+
+ currentLevelData = nextLevelData;
+ currentLevelWidth = currentLevelWidth / 2;
+ currentLevelHeight = currentLevelHeight / 2;
+ }
+}
+
+void copySurfaceToGxmIndexed8(
+ DirectDrawSurfaceImpl* surface,
+ SDL_Surface* src,
+ uint8_t* textureData,
+ size_t dstStride,
+ uint8_t* paletteData,
+ size_t mipLevels
+)
+{
+ LPDIRECTDRAWPALETTE _palette;
+ surface->GetPalette(&_palette);
+ auto palette = static_cast(_palette);
+
+ // copy palette
+ memcpy(paletteData, palette->m_palette->colors, 256 * 4);
+
+ uint8_t* currentLevelData = textureData;
+ uint32_t currentLevelWidth = src->w;
+ uint32_t currentLevelHeight = src->h;
+ size_t bytesPerPixel = 1;
+
+ // copy top level mip (cant use transfer because this isnt gpu mapped)
+ size_t topLevelStride = ALIGN(currentLevelWidth, 8) * bytesPerPixel;
+ for (int y = 0; y < currentLevelHeight; y++) {
+ uint8_t* srcRow = (uint8_t*) src->pixels + (y * src->pitch);
+ uint8_t* dstRow = textureData + (y * topLevelStride);
+ memcpy(dstRow, srcRow, currentLevelWidth * bytesPerPixel);
+ }
+
+ for (size_t i = 1; i < mipLevels; ++i) {
+ uint32_t currentLevelSrcStride = SDL_max(currentLevelWidth, 8) * bytesPerPixel;
+ uint32_t currentLevelDestStride = SDL_max((currentLevelWidth / 2), 8) * bytesPerPixel;
+ uint8_t* nextLevelData = currentLevelData + (currentLevelSrcStride * currentLevelHeight);
+
+ sceGxmTransferDownscale(
+ SCE_GXM_TRANSFER_FORMAT_U8_R, // src format
+ currentLevelData, // src address
+ 0,
+ 0,
+ currentLevelWidth,
+ currentLevelHeight, // x,y,w,h
+ currentLevelSrcStride, // stride
+ SCE_GXM_TRANSFER_FORMAT_U8_R, // dst format
+ nextLevelData, // dst address
+ 0,
+ 0, // x,y
+ currentLevelDestStride, // stride
+ NULL, // sync
+ SCE_GXM_TRANSFER_FRAGMENT_SYNC, // flag
+ NULL // notification
+ );
+
+ currentLevelData = nextLevelData;
+ currentLevelWidth = currentLevelWidth / 2;
+ currentLevelHeight = currentLevelHeight / 2;
+ }
+
+ palette->Release();
+}
+
+void copySurfaceToGxm(
+ DirectDrawSurfaceImpl* surface,
+ uint8_t* textureData,
+ size_t dstStride,
+ size_t paletteOffset,
+ size_t mipLevels
+)
+{
+ SDL_Surface* src = surface->m_surface;
+
+ switch (src->format) {
+ case SDL_PIXELFORMAT_ABGR8888: {
+ copySurfaceToGxmARGB888(src, textureData, dstStride, mipLevels);
+ break;
+ }
+ case SDL_PIXELFORMAT_INDEX8: {
+ copySurfaceToGxmIndexed8(surface, src, textureData, dstStride, textureData + paletteOffset, mipLevels);
+ break;
+ }
+ default: {
+ DEBUG_ONLY_PRINTF("unsupported format %d\n", SDL_GetPixelFormatName(src->format));
+ SDL_Surface* dst = SDL_CreateSurfaceFrom(src->w, src->h, SDL_PIXELFORMAT_ABGR8888, textureData, src->w * 4);
+ SDL_BlitSurface(src, nullptr, dst, nullptr);
+ SDL_DestroySurface(dst);
+ break;
+ }
+ }
+}
+
+Uint32 GXMRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi, float scaleX, float scaleY)
+{
+ auto texture = static_cast(iTexture);
+ auto surface = static_cast(texture->m_surface);
+
+ bool supportedFormat;
+ SceGxmTextureFormat gxmTextureFormat;
+ size_t textureSize;
+ size_t textureAlignment;
+ size_t textureStride;
+ size_t paletteOffset;
+ size_t mipLevels;
+
+ int textureWidth = surface->m_surface->w;
+ int textureHeight = surface->m_surface->h;
+
+ convertTextureMetadata(
+ surface->m_surface,
+ isUi,
+ &supportedFormat,
+ &gxmTextureFormat,
+ &textureSize,
+ &textureAlignment,
+ &textureStride,
+ &paletteOffset,
+ &mipLevels
+ );
+
+ if (!supportedFormat) {
+ return NO_TEXTURE_ID;
+ }
+
+ for (Uint32 i = 0; i < m_textures.size(); ++i) {
+ auto& tex = m_textures[i];
+ if (tex.texture == texture) {
+ if (tex.version != texture->m_version) {
+ sceGxmNotificationWait(tex.notification);
+ tex.notification = &this->fragmentNotifications[this->currentFragmentBufferIndex];
+ uint8_t* textureData = (uint8_t*) sceGxmTextureGetData(&tex.gxmTexture);
+ copySurfaceToGxm(surface, textureData, textureStride, paletteOffset, mipLevels);
+ tex.version = texture->m_version;
+ }
+ return i;
+ }
+ }
+
+ DEBUG_ONLY_PRINTF(
+ "Create Texture %s w=%d h=%d s=%d size=%d align=%d mips=%d\n",
+ SDL_GetPixelFormatName(surface->m_surface->format),
+ textureWidth,
+ textureHeight,
+ textureStride,
+ textureSize,
+ textureAlignment,
+ mipLevels
+ );
+
+ // allocate gpu memory
+ uint8_t* textureData = (uint8_t*) gxm->alloc(textureSize, textureAlignment);
+ copySurfaceToGxm(surface, textureData, textureStride, paletteOffset, mipLevels);
+
+ SceGxmTexture gxmTexture;
+ SCE_ERR(
+ sceGxmTextureInitLinear,
+ &gxmTexture,
+ textureData,
+ gxmTextureFormat,
+ textureWidth,
+ textureHeight,
+ mipLevels
+ );
+ if (isUi) {
+ sceGxmTextureSetMinFilter(&gxmTexture, SCE_GXM_TEXTURE_FILTER_POINT);
+ sceGxmTextureSetMagFilter(&gxmTexture, SCE_GXM_TEXTURE_FILTER_POINT);
+ sceGxmTextureSetUAddrMode(&gxmTexture, SCE_GXM_TEXTURE_ADDR_CLAMP);
+ sceGxmTextureSetVAddrMode(&gxmTexture, SCE_GXM_TEXTURE_ADDR_CLAMP);
+ }
+ else {
+ sceGxmTextureSetMinFilter(&gxmTexture, SCE_GXM_TEXTURE_FILTER_LINEAR);
+ sceGxmTextureSetMagFilter(&gxmTexture, SCE_GXM_TEXTURE_FILTER_LINEAR);
+ sceGxmTextureSetUAddrMode(&gxmTexture, SCE_GXM_TEXTURE_ADDR_REPEAT);
+ sceGxmTextureSetVAddrMode(&gxmTexture, SCE_GXM_TEXTURE_ADDR_REPEAT);
+ }
+ if (gxmTextureFormat == SCE_GXM_TEXTURE_FORMAT_P8_ABGR) {
+ sceGxmTextureSetPalette(&gxmTexture, textureData + paletteOffset);
+ }
+
+ for (Uint32 i = 0; i < m_textures.size(); ++i) {
+ auto& tex = m_textures[i];
+ if (!tex.texture) {
+ memset(&tex, 0, sizeof(tex));
+ tex.texture = texture;
+ tex.version = texture->m_version;
+ tex.gxmTexture = gxmTexture;
+ tex.notification = &this->fragmentNotifications[this->currentFragmentBufferIndex];
+ AddTextureDestroyCallback(i, texture);
+ return i;
+ }
+ }
+
+ GXMTextureCacheEntry tex;
+ memset(&tex, 0, sizeof(tex));
+ tex.texture = texture;
+ tex.version = texture->m_version;
+ tex.gxmTexture = gxmTexture;
+ tex.notification = &this->fragmentNotifications[this->currentFragmentBufferIndex];
+ m_textures.push_back(tex);
+ Uint32 textureId = (Uint32) (m_textures.size() - 1);
+ AddTextureDestroyCallback(textureId, texture);
+ return textureId;
+}
+
+const SceGxmTexture* GXMRenderer::UseTexture(GXMTextureCacheEntry& texture)
+{
+ texture.notification = &this->fragmentNotifications[this->currentFragmentBufferIndex];
+ sceGxmSetFragmentTexture(gxm->context, 0, &texture.gxmTexture);
+ return &texture.gxmTexture;
+}
+
+GXMMeshCacheEntry GXMRenderer::GXMUploadMesh(const MeshGroup& meshGroup)
+{
+ GXMMeshCacheEntry cache{&meshGroup, meshGroup.version};
+
+ cache.flat = meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT;
+
+ std::vector vertices;
+ std::vector indices;
+ if (cache.flat) {
+ FlattenSurfaces(
+ meshGroup.vertices.data(),
+ meshGroup.vertices.size(),
+ meshGroup.indices.data(),
+ meshGroup.indices.size(),
+ meshGroup.texture != nullptr,
+ vertices,
+ indices
+ );
+ }
+ else {
+ vertices = meshGroup.vertices;
+ indices.resize(meshGroup.indices.size());
+ std::transform(meshGroup.indices.begin(), meshGroup.indices.end(), indices.begin(), [](DWORD index) {
+ return static_cast(index);
+ });
+ }
+
+ size_t vertexBufferSize = sizeof(GXMVertex) * vertices.size();
+ size_t indexBufferSize = sizeof(uint16_t) * indices.size();
+ void* meshData = gxm->alloc(vertexBufferSize + indexBufferSize, 4);
+
+ GXMVertex* vertexBuffer = (GXMVertex*) meshData;
+ uint16_t* indexBuffer = (uint16_t*) ((uint8_t*) meshData + vertexBufferSize);
+
+ for (int i = 0; i < vertices.size(); i++) {
+ D3DRMVERTEX vertex = vertices.data()[i];
+ vertexBuffer[i] = GXMVertex{
+ .position =
+ {
+ vertex.position.x,
+ vertex.position.y,
+ vertex.position.z,
+ },
+ .normal =
+ {
+ vertex.normal.x,
+ vertex.normal.y,
+ vertex.normal.z,
+ },
+ .texCoord =
+ {
+ vertex.tu,
+ vertex.tv,
+ }
+ };
+ }
+ memcpy(indexBuffer, indices.data(), indices.size() * sizeof(uint16_t));
+
+ cache.meshData = meshData;
+ cache.vertexBuffer = vertexBuffer;
+ cache.indexBuffer = indexBuffer;
+ cache.indexCount = indices.size();
+ return cache;
+}
+
+struct GXMMeshDestroyContext {
+ GXMRenderer* renderer;
+ Uint32 id;
+};
+
+void GXMRenderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh)
+{
+ auto* ctx = new GXMMeshDestroyContext{this, id};
+ mesh->AddDestroyCallback(
+ [](IDirect3DRMObject*, void* arg) {
+ auto* ctx = static_cast(arg);
+ auto& cache = ctx->renderer->m_meshes[ctx->id];
+ cache.meshGroup = nullptr;
+ ctx->renderer->m_buffers_delete[gxm->backBufferIndex].push_back(cache.meshData);
+ cache.meshData = nullptr;
+ cache.indexBuffer = nullptr;
+ cache.vertexBuffer = nullptr;
+ cache.indexCount = 0;
+ delete ctx;
+ },
+ ctx
+ );
+}
+
+Uint32 GXMRenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup)
+{
+ for (Uint32 i = 0; i < m_meshes.size(); ++i) {
+ auto& cache = m_meshes[i];
+ if (cache.meshGroup == meshGroup) {
+ if (cache.version != meshGroup->version) {
+ cache = std::move(this->GXMUploadMesh(*meshGroup));
+ }
+ return i;
+ }
+ }
+
+ auto newCache = this->GXMUploadMesh(*meshGroup);
+
+ for (Uint32 i = 0; i < m_meshes.size(); ++i) {
+ auto& cache = m_meshes[i];
+ if (!cache.meshGroup) {
+ cache = std::move(newCache);
+ AddMeshDestroyCallback(i, mesh);
+ return i;
+ }
+ }
+
+ m_meshes.push_back(std::move(newCache));
+ AddMeshDestroyCallback((Uint32) (m_meshes.size() - 1), mesh);
+ return (Uint32) (m_meshes.size() - 1);
+}
+
+bool razor_live_started = false;
+bool razor_display_enabled = false;
+
+void GXMRenderer::StartScene()
+{
+ if (gxm->sceneStarted) {
+ return;
+ }
+
+ this->DeferredDelete(gxm->frontBufferIndex);
+
+#ifdef GXM_WITH_RAZOR
+ bool dpad_up_clicked = !this->last_dpad_up && g_dpadUp;
+ bool dpad_down_clicked = !this->last_dpad_down && g_dpadDown;
+ bool dpad_left_clicked = !this->last_dpad_left && g_dpadLeft;
+ bool dpad_right_clicked = !this->last_dpad_right && g_dpadRight;
+ this->last_dpad_up = g_dpadUp;
+ this->last_dpad_down = g_dpadDown;
+ this->last_dpad_left = g_dpadLeft;
+ this->last_dpad_right = g_dpadRight;
+
+ if (with_razor_hud) {
+ if (dpad_up_clicked) {
+ razor_display_enabled = !razor_display_enabled;
+ sceRazorHudSetDisplayEnabled(razor_display_enabled);
+ }
+ if (dpad_left_clicked) {
+ if (razor_live_started) {
+ sceRazorGpuLiveStop();
+ }
+ else {
+ sceRazorGpuLiveStart();
+ }
+ razor_live_started = !razor_live_started;
+ }
+ if (dpad_right_clicked) {
+ sceRazorGpuTraceTrigger();
+ }
+ }
+ if (with_razor_capture) {
+ if (dpad_down_clicked) {
+ sceRazorGpuCaptureSetTriggerNextFrame("ux0:/data/capture.sgx");
+ }
+ }
+#endif
+
+ sceGxmBeginScene(
+ gxm->context,
+ SCE_GXM_SCENE_FRAGMENT_TRANSFER_SYNC,
+ gxm->renderTarget,
+ nullptr,
+ nullptr,
+ gxm->displayBuffersSync[gxm->backBufferIndex],
+ &gxm->displayBuffersSurface[gxm->backBufferIndex],
+ &gxm->depthSurface
+ );
+ sceGxmSetCullMode(gxm->context, SCE_GXM_CULL_CCW);
+ gxm->sceneStarted = true;
+ this->quadsUsed = 0;
+ this->cleared = false;
+
+ sceGxmNotificationWait(&this->vertexNotifications[this->currentVertexBufferIndex]);
+ sceGxmNotificationWait(&this->fragmentNotifications[this->currentFragmentBufferIndex]);
+}
+
+HRESULT GXMRenderer::BeginFrame()
+{
+ this->transparencyEnabled = false;
+ this->StartScene();
+
+ auto lightData = this->LightsBuffer();
+ int i = 0;
+ for (const auto& light : m_lights) {
+ if (!light.directional && !light.positional) {
+ lightData->ambientLight[0] = light.color.r;
+ lightData->ambientLight[1] = light.color.g;
+ lightData->ambientLight[2] = light.color.b;
+ continue;
+ }
+ if (i == 2) {
+ sceClibPrintf("light overflow\n");
+ continue;
+ }
+
+ lightData->lights[i].color[0] = light.color.r;
+ lightData->lights[i].color[1] = light.color.g;
+ lightData->lights[i].color[2] = light.color.b;
+ lightData->lights[i].color[3] = light.color.a;
+
+ bool isDirectional = light.directional == 1.0;
+ if (isDirectional) {
+ lightData->lights[i].vec[0] = light.direction.x;
+ lightData->lights[i].vec[1] = light.direction.y;
+ lightData->lights[i].vec[2] = light.direction.z;
+ }
+ else {
+ lightData->lights[i].vec[0] = light.position.x;
+ lightData->lights[i].vec[1] = light.position.y;
+ lightData->lights[i].vec[2] = light.position.z;
+ }
+ lightData->lights[i].isDirectional = isDirectional;
+ i++;
+ }
+ sceGxmSetFragmentUniformBuffer(gxm->context, 0, lightData);
+
+ return DD_OK;
+}
+
+void GXMRenderer::EnableTransparency()
+{
+ this->transparencyEnabled = true;
+}
+
+void GXMRenderer::SubmitDraw(
+ DWORD meshId,
+ const D3DRMMATRIX4D& modelViewMatrix,
+ const D3DRMMATRIX4D& worldMatrix,
+ const D3DRMMATRIX4D& viewMatrix,
+ const Matrix3x3& normalMatrix,
+ const Appearance& appearance
+)
+{
+ auto& mesh = m_meshes[meshId];
+
+#ifdef DEBUG
+ char marker[256];
+ snprintf(marker, sizeof(marker), "SubmitDraw: %d", meshId);
+ sceGxmPushUserMarker(gxm->context, marker);
+#endif
+
+ bool textured = appearance.textureId != NO_TEXTURE_ID;
+ const SceGxmFragmentProgram* fragmentProgram;
+ if (this->transparencyEnabled) {
+ fragmentProgram = textured ? this->blendedTextureFragmentProgram : this->blendedColorFragmentProgram;
+ }
+ else {
+ fragmentProgram = textured ? this->opaqueTextureFragmentProgram : this->opaqueColorFragmentProgram;
+ }
+ sceGxmSetVertexProgram(gxm->context, this->mainVertexProgram);
+ sceGxmSetFragmentProgram(gxm->context, fragmentProgram);
+
+ void* vertUniforms;
+ void* fragUniforms;
+ sceGxmReserveVertexDefaultUniformBuffer(gxm->context, &vertUniforms);
+ sceGxmReserveFragmentDefaultUniformBuffer(gxm->context, &fragUniforms);
+
+ // vertex uniforms
+ sceGxmSetUniformDataF(vertUniforms, this->uModelViewMatrix, 0, 4 * 4, &modelViewMatrix[0][0]);
+ sceGxmSetUniformDataF(vertUniforms, this->uNormalMatrix, 0, 3 * 3, &normalMatrix[0][0]);
+ sceGxmSetUniformDataF(vertUniforms, this->uProjectionMatrix, 0, 4 * 4, &this->m_projection[0][0]);
+
+ // fragment uniforms
+ float color[4] = {
+ appearance.color.r / 255.0f,
+ appearance.color.g / 255.0f,
+ appearance.color.b / 255.0f,
+ appearance.color.a / 255.0f
+ };
+ sceGxmSetUniformDataF(fragUniforms, this->uColor, 0, 4, color);
+ sceGxmSetUniformDataF(fragUniforms, this->uShininess, 0, 1, &appearance.shininess);
+
+ if (textured) {
+ auto& texture = m_textures[appearance.textureId];
+ this->UseTexture(texture);
+ }
+ sceGxmSetVertexStream(gxm->context, 0, mesh.vertexBuffer);
+ sceGxmSetFrontDepthFunc(gxm->context, SCE_GXM_DEPTH_FUNC_LESS_EQUAL);
+ sceGxmDraw(gxm->context, SCE_GXM_PRIMITIVE_TRIANGLES, SCE_GXM_INDEX_FORMAT_U16, mesh.indexBuffer, mesh.indexCount);
+
+#ifdef DEBUG
+ sceGxmPopUserMarker(gxm->context);
+#endif
+}
+
+HRESULT GXMRenderer::FinalizeFrame()
+{
+ return DD_OK;
+}
+
+void GXMRenderer::Resize(int width, int height, const ViewportTransform& viewportTransform)
+{
+ m_width = width;
+ m_height = height;
+ m_viewportTransform = viewportTransform;
+}
+
+void GXMRenderer::Clear(float r, float g, float b)
+{
+ this->StartScene();
+ gxm->clear(r, g, b, false);
+ this->cleared = true;
+}
+
+void GXMRenderer::Flip()
+{
+ if (!gxm->sceneStarted) {
+ return;
+ }
+
+ ++this->vertexNotifications[this->currentVertexBufferIndex].value;
+ ++this->fragmentNotifications[this->currentFragmentBufferIndex].value;
+ sceGxmEndScene(
+ gxm->context,
+ &this->vertexNotifications[this->currentVertexBufferIndex],
+ &this->fragmentNotifications[this->currentFragmentBufferIndex]
+ );
+ gxm->sceneStarted = false;
+
+ this->currentVertexBufferIndex = (this->currentVertexBufferIndex + 1) % GXM_VERTEX_BUFFER_COUNT;
+ this->currentFragmentBufferIndex = (this->currentFragmentBufferIndex + 1) % GXM_FRAGMENT_BUFFER_COUNT;
+ gxm->swap_display();
+}
+
+void GXMRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color)
+{
+ this->StartScene();
+ if (!this->cleared) {
+ gxm->clear(0, 0, 0, false);
+ this->cleared = true;
+ }
+
+#ifdef DEBUG
+ char marker[256];
+ snprintf(marker, sizeof(marker), "Draw2DImage: %d", textureId);
+ sceGxmPushUserMarker(gxm->context, marker);
+#endif
+
+ sceGxmSetVertexProgram(gxm->context, gxm->planeVertexProgram);
+ if (textureId != NO_TEXTURE_ID) {
+ sceGxmSetFragmentProgram(gxm->context, gxm->imageFragmentProgram);
+ }
+ else {
+ sceGxmSetFragmentProgram(gxm->context, gxm->colorFragmentProgram);
+ }
+
+ void* vertUniforms;
+ void* fragUniforms;
+ sceGxmReserveVertexDefaultUniformBuffer(gxm->context, &vertUniforms);
+ sceGxmReserveFragmentDefaultUniformBuffer(gxm->context, &fragUniforms);
+
+ float left = -this->m_viewportTransform.offsetX / this->m_viewportTransform.scale;
+ float right = (this->m_width - this->m_viewportTransform.offsetX) / this->m_viewportTransform.scale;
+ float top = -this->m_viewportTransform.offsetY / this->m_viewportTransform.scale;
+ float bottom = (this->m_height - this->m_viewportTransform.offsetY) / this->m_viewportTransform.scale;
+
+#define virtualToNDCX(x) (((x - left) / (right - left)) * 2 - 1);
+#define virtualToNDCY(y) -(((y - top) / (bottom - top)) * 2 - 1);
+
+ float x1_virtual = static_cast(dstRect.x);
+ float y1_virtual = static_cast(dstRect.y);
+ float x2_virtual = x1_virtual + dstRect.w;
+ float y2_virtual = y1_virtual + dstRect.h;
+
+ float x1 = virtualToNDCX(x1_virtual);
+ float y1 = virtualToNDCY(y1_virtual);
+ float x2 = virtualToNDCX(x2_virtual);
+ float y2 = virtualToNDCY(y2_virtual);
+
+ float u1 = 0.0;
+ float v1 = 0.0;
+ float u2 = 0.0;
+ float v2 = 0.0;
+
+ if (textureId != NO_TEXTURE_ID) {
+ GXMTextureCacheEntry& texture = m_textures[textureId];
+ const SceGxmTexture* gxmTexture = this->UseTexture(texture);
+ float texW = sceGxmTextureGetWidth(gxmTexture);
+ float texH = sceGxmTextureGetHeight(gxmTexture);
+
+ u1 = static_cast(srcRect.x) / texW;
+ v1 = static_cast(srcRect.y) / texH;
+ u2 = static_cast(srcRect.x + srcRect.w) / texW;
+ v2 = static_cast(srcRect.y + srcRect.h) / texH;
+ }
+ else {
+ SET_UNIFORM(fragUniforms, gxm->color_uColor, color);
+ }
+
+ GXMVertex2D* quadVertices = this->QuadVerticesBuffer();
+ quadVertices[0] = GXMVertex2D{.position = {x1, y1}, .texCoord = {u1, v1}};
+ quadVertices[1] = GXMVertex2D{.position = {x2, y1}, .texCoord = {u2, v1}};
+ quadVertices[2] = GXMVertex2D{.position = {x1, y2}, .texCoord = {u1, v2}};
+ quadVertices[3] = GXMVertex2D{.position = {x2, y2}, .texCoord = {u2, v2}};
+
+ sceGxmSetVertexStream(gxm->context, 0, quadVertices);
+
+ sceGxmSetFrontDepthWriteEnable(gxm->context, SCE_GXM_DEPTH_WRITE_DISABLED);
+ sceGxmSetFrontDepthFunc(gxm->context, SCE_GXM_DEPTH_FUNC_ALWAYS);
+ sceGxmDraw(gxm->context, SCE_GXM_PRIMITIVE_TRIANGLE_STRIP, SCE_GXM_INDEX_FORMAT_U16, this->quadIndices, 4);
+ sceGxmSetFrontDepthWriteEnable(gxm->context, SCE_GXM_DEPTH_WRITE_ENABLED);
+
+#ifdef DEBUG
+ sceGxmPopUserMarker(gxm->context);
+#endif
+}
+
+void GXMRenderer::SetDither(bool dither)
+{
+}
+
+void GXMRenderer::Download(SDL_Surface* target)
+{
+ SDL_Rect srcRect = {
+ static_cast(m_viewportTransform.offsetX),
+ static_cast(m_viewportTransform.offsetY),
+ static_cast(target->w * m_viewportTransform.scale),
+ static_cast(target->h * m_viewportTransform.scale),
+ };
+ SDL_Surface* src = SDL_CreateSurfaceFrom(
+ VITA_GXM_SCREEN_WIDTH,
+ VITA_GXM_SCREEN_HEIGHT,
+ SDL_PIXELFORMAT_ABGR8888,
+ gxm->displayBuffers[gxm->frontBufferIndex],
+ VITA_GXM_SCREEN_STRIDE * 4
+ );
+ SDL_BlitSurfaceScaled(src, &srcRect, target, nullptr, SDL_SCALEMODE_NEAREST);
+ SDL_DestroySurface(src);
+}
diff --git a/miniwin/src/d3drm/backends/gxm/razor.h b/miniwin/src/d3drm/backends/gxm/razor.h
new file mode 100644
index 00000000..d09260e7
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/razor.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include
+#include
+
+extern "C"
+{
+ extern int sceRazorGpuCaptureSetTrigger(int frames, const char* path);
+
+ extern int sceRazorGpuTraceTrigger();
+ extern int sceRazorGpuTraceSetFilename(const char* filename, int counter);
+ extern int sceRazorHudSetDisplayEnabled(bool enable);
+}
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/.gitignore b/miniwin/src/d3drm/backends/gxm/shaders/.gitignore
new file mode 100644
index 00000000..21ce5a1f
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/.gitignore
@@ -0,0 +1,2 @@
+*.exe
+cache/
\ No newline at end of file
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/CMakeLists.txt b/miniwin/src/d3drm/backends/gxm/shaders/CMakeLists.txt
new file mode 100644
index 00000000..8d6da2ee
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/CMakeLists.txt
@@ -0,0 +1,106 @@
+cmake_minimum_required(VERSION 3.25...4.0 FATAL_ERROR)
+
+enable_language(ASM)
+
+find_program(PSP2CGC NAMES ${CMAKE_CURRENT_SOURCE_DIR}/psp2cgc.exe psp2cgc.exe psp2cgc)
+find_program(PSP2SHADERPERF NAMES ${CMAKE_CURRENT_SOURCE_DIR}/psp2shaderperf.exe psp2shaderperf.exe psp2shaderperf)
+
+list(APPEND CGC_COMMON_FLAGS "-Wperf" "-cache" "-cachedir" "${CMAKE_CURRENT_BINARY_DIR}/cache" "-W4" "-Wsuppress=5206,5203")
+
+make_directory("${CMAKE_CURRENT_BINARY_DIR}/cache")
+
+# compile .cg to .gxp if psp2cgc is found
+macro(COMPILE_SHADER INPUT_CG OUTPUT_GXP PROFILE)
+ set(EXTRA_FLAGS "${ARGN}")
+
+ set(INPUT_CG_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${INPUT_CG}")
+ set(OUTPUT_GXP_PATH "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_GXP}")
+ set(TRACKED_GXP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${OUTPUT_GXP}")
+
+ if(PSP2CGC)
+ if(EXISTS ${TRACKED_GXP_PATH})
+ file(COPY ${TRACKED_GXP_PATH} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+ endif()
+ add_custom_command(
+ OUTPUT "${OUTPUT_GXP_PATH}"
+ COMMAND "${PSP2CGC}" ${CGC_COMMON_FLAGS} -profile "${PROFILE}" ${EXTRA_FLAGS} "${INPUT_CG_PATH}" -o "${OUTPUT_GXP_PATH}"
+ COMMAND ${CMAKE_COMMAND} -E copy "${OUTPUT_GXP_PATH}" "${TRACKED_GXP_PATH}"
+ DEPENDS "${INPUT_CG_PATH}"
+ COMMENT "Compiling ${INPUT_CG} -> ${OUTPUT_GXP} ${PROFILE}"
+ )
+ else()
+ if(NOT EXISTS "${TRACKED_GXP_PATH}")
+ message(FATAL_ERROR "missing shader ${TRACKED_GXP_PATH} ${INPUT_CG}, but psp2cgc.exe not found")
+ endif()
+ add_custom_command(
+ OUTPUT "${OUTPUT_GXP_PATH}"
+ COMMAND /usr/bin/env bash
+ ARGS "-c"
+ "if [ \"${INPUT_CG_PATH}\" -nt \"${TRACKED_GXP_PATH}\" ]; then
+ echo 'warning: ${INPUT_CG} changed but dont have psp2cgc, cant recompile' >&2;
+ fi"
+ COMMAND ${CMAKE_COMMAND} -E copy "${TRACKED_GXP_PATH}" "${OUTPUT_GXP_PATH}"
+ DEPENDS "${INPUT_CG_PATH}" "${TRACKED_GXP_PATH}"
+ VERBATIM
+ COMMENT "Copy ${TRACKED_GXP_PATH} -> ${OUTPUT_GXP} ${PROFILE}"
+ )
+ endif()
+ set(TARGET_NAME "compile_${OUTPUT_GXP}")
+ string(REPLACE "." "_" TARGET_NAME "${TARGET_NAME}")
+ add_custom_target("${TARGET_NAME}" DEPENDS "${OUTPUT_GXP_PATH}")
+ add_dependencies(gxm_shaders_compile "${TARGET_NAME}")
+endmacro()
+
+
+# analyze gxp to create a .perf.txt
+macro(GENERATE_PERF GXP_FILE)
+ set(GXP_PATH "${CMAKE_CURRENT_BINARY_DIR}/${GXP_FILE}")
+ string(REPLACE ".gxp" ".perf.txt" PERF_FILE_NAME "${GXP_FILE}")
+ set(PERF_FILE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PERF_FILE_NAME}")
+
+ add_custom_command(
+ OUTPUT "${PERF_FILE_PATH}"
+ COMMAND "${PSP2SHADERPERF}" -stats -symbols -disasm "${GXP_PATH}" > "${PERF_FILE_PATH}"
+ DEPENDS "${GXP_PATH}"
+ COMMENT "Generating performance analysis for ${GXP_FILE}"
+ )
+ set(TARGET_NAME "perf_${PERF_FILE_NAME}")
+ string(REPLACE "." "_" TARGET_NAME "${TARGET_NAME}")
+ add_custom_target("${TARGET_NAME}" DEPENDS "${PERF_FILE_PATH}")
+ add_dependencies(gxm_perfs "${TARGET_NAME}" )
+endmacro()
+
+
+
+add_library(gxm_shaders STATIC gxm_shaders.s)
+
+target_include_directories(gxm_shaders PRIVATE
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+add_custom_target(gxm_shaders_compile)
+add_dependencies(gxm_shaders gxm_shaders_compile)
+
+COMPILE_SHADER(plane.vert.cg plane.vert.gxp sce_vp_psp2)
+COMPILE_SHADER(color.frag.cg color.frag.gxp sce_fp_psp2)
+COMPILE_SHADER(image.frag.cg image.frag.gxp sce_fp_psp2)
+COMPILE_SHADER(main.vert.cg main.vert.gxp sce_vp_psp2)
+COMPILE_SHADER(main.frag.cg main.color.frag.gxp sce_fp_psp2)
+COMPILE_SHADER(main.frag.cg main.texture.frag.gxp sce_fp_psp2 -DTEXTURED=1)
+
+
+# .perf.txt
+if(PSP2SHADERPERF)
+ add_custom_target(gxm_perfs)
+ add_dependencies(gxm_shaders gxm_perfs)
+ GENERATE_PERF(plane.vert.gxp)
+ GENERATE_PERF(color.frag.gxp)
+ GENERATE_PERF(image.frag.gxp)
+ GENERATE_PERF(main.vert.gxp)
+ GENERATE_PERF(main.color.frag.gxp)
+ GENERATE_PERF(main.texture.frag.gxp)
+else()
+ message(STATUS "psp2shaderperf not found")
+endif()
+
+
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/color.frag.cg b/miniwin/src/d3drm/backends/gxm/shaders/color.frag.cg
new file mode 100644
index 00000000..67533b51
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/color.frag.cg
@@ -0,0 +1,6 @@
+void main(
+ uniform float4 uColor : COLOR,
+ out float4 outColor : COLOR
+) {
+ outColor = uColor;
+}
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/color.frag.gxp b/miniwin/src/d3drm/backends/gxm/shaders/color.frag.gxp
new file mode 100644
index 0000000000000000000000000000000000000000..dea3d7eb0cb0c10951912f13220266c553f79b77
GIT binary patch
literal 228
zcmZ>d2w-4j4PbuEz`(#$#~u1@JJUW!1_>aKK>$R603#4D0pbE6W@2Cgk|4kcWHT_7
zfY}TT89+J(%0{O^I%0s>!HyX$6#!KLqaC1p3m^@$%K*wpP>f(pVK#%s*
+
+#define GXP(sym) \
+ extern uint8_t _inc_##sym[]; \
+ static const SceGxmProgram* sym = (const SceGxmProgram*) _inc_##sym;
+
+GXP(mainVertexProgramGxp);
+GXP(mainColorFragmentProgramGxp);
+GXP(mainTextureFragmentProgramGxp);
+GXP(planeVertexProgramGxp);
+GXP(imageFragmentProgramGxp);
+GXP(colorFragmentProgramGxp);
+
+static const SceGxmBlendInfo blendInfoOpaque = {
+ .colorMask = SCE_GXM_COLOR_MASK_ALL,
+ .colorFunc = SCE_GXM_BLEND_FUNC_NONE,
+ .alphaFunc = SCE_GXM_BLEND_FUNC_NONE,
+ .colorSrc = SCE_GXM_BLEND_FACTOR_ZERO,
+ .colorDst = SCE_GXM_BLEND_FACTOR_ZERO,
+ .alphaSrc = SCE_GXM_BLEND_FACTOR_ZERO,
+ .alphaDst = SCE_GXM_BLEND_FACTOR_ZERO,
+};
+
+static const SceGxmBlendInfo blendInfoTransparent = {
+ .colorMask = SCE_GXM_COLOR_MASK_ALL,
+ .colorFunc = SCE_GXM_BLEND_FUNC_ADD,
+ .alphaFunc = SCE_GXM_BLEND_FUNC_ADD,
+ .colorSrc = SCE_GXM_BLEND_FACTOR_SRC_ALPHA,
+ .colorDst = SCE_GXM_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+ .alphaSrc = SCE_GXM_BLEND_FACTOR_ONE,
+ .alphaDst = SCE_GXM_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+};
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/gxm_shaders.s b/miniwin/src/d3drm/backends/gxm/shaders/gxm_shaders.s
new file mode 100644
index 00000000..2421947d
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/gxm_shaders.s
@@ -0,0 +1,15 @@
+.section .rodata, "a", %progbits
+
+.macro embed file, symbol
+.global _inc_\symbol
+.align 16
+_inc_\symbol:
+ .incbin "\file"
+.endm
+
+embed "main.vert.gxp", mainVertexProgramGxp
+embed "main.color.frag.gxp", mainColorFragmentProgramGxp
+embed "main.texture.frag.gxp", mainTextureFragmentProgramGxp
+embed "plane.vert.gxp", planeVertexProgramGxp
+embed "image.frag.gxp", imageFragmentProgramGxp
+embed "color.frag.gxp", colorFragmentProgramGxp
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/image.frag.cg b/miniwin/src/d3drm/backends/gxm/shaders/image.frag.cg
new file mode 100644
index 00000000..b3bc6b02
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/image.frag.cg
@@ -0,0 +1,10 @@
+void main(
+ float4 vPosition : POSITION,
+ float2 vTexCoord : TEXCOORD0,
+
+ uniform sampler2D uTexture,
+
+ out float4 outColor : COLOR
+) {
+ outColor = tex2D(uTexture, vTexCoord);
+}
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/image.frag.gxp b/miniwin/src/d3drm/backends/gxm/shaders/image.frag.gxp
new file mode 100644
index 0000000000000000000000000000000000000000..fefde5f0ee5d234b85ac9b626ce2afa4af56d59f
GIT binary patch
literal 264
zcmZ>d2w-4j4Pa(vWMJrwdbjp}nejG84haSj01ANth+YE31whOM76B2AVEGm(zXC{?
zK-t)+6riAk9Wz)e0;(VYN_#+Q2Pka;q+OtF2*t<(vW*4A`3Vtm08$DNCWK^n`DFlP
cF|ja#-31b4f>;NU$3m8dq*j!a7Nvru0FHwZv;Y7A
literal 0
HcmV?d00001
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/image.frag.perf.txt b/miniwin/src/d3drm/backends/gxm/shaders/image.frag.perf.txt
new file mode 100644
index 00000000..4b69dc4b
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/image.frag.perf.txt
@@ -0,0 +1,29 @@
+Total estimated cost: 0 cycles, parallel mode
+Register count: 2 PAs, 0 temps, 0 SAs *
+Texture reads: 1 non-dependent, dependent: 0 unconditional, 0 conditional
+
+High level analysis:
+No warnings.
+
+* Please refer to the Razor documentation for details regarding the meaning of these numbers. Decreasing the number of registers used will not necessarily increase performance
+
+Instruction statistics:
+Number of alu ops: 0
+Number of mem ops: 0
+Number of tex ops: 0
+Number of floating point ops: 0
+Number of integer ops: 0
+Number of pack ops: 0
+Number of mov ops: 0
+Number of nop ops: 0
+
+
+Samplers:
+TEXUNIT0 = uTexture
+
+Iterators:
+
+
+Primary program:
+pa0 = tex2D(uTexture, TEXCOORD0.xy)
+
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/main.color.frag.gxp b/miniwin/src/d3drm/backends/gxm/shaders/main.color.frag.gxp
new file mode 100644
index 0000000000000000000000000000000000000000..713e10c9ed88b5b800a9f484fd88afc7747a6ecc
GIT binary patch
literal 1248
zcmZ`&O-NKx6h8OO(|L*Z-Wv-hG<=4Hi>L`&%tEI(IHrpNrA<%~WkU|wjON&)Mck`*
z%ra4{O%ycq93$F9>mtTz69noeXj2TduvN9wuWuQZ_p@1FaebH8`acYnH0^#H5t
z34GK6R%H3fi@>Sp%nE}BV1aKn=w$$45WNBW!BlYuo#-1GfQ^0vzgnDcHKZu3#_<3G
zHx{7(f`xNh+UhSLOlF{`JouQV6=OQZu|+VgQZ2
zY=v?F&Kii;;$ypHxT3rG)G8UkMIjfVe0PUdH5vtIi<9R!%Y_{AZ!j2RLoo3JcwkA~`qHg&@$6w;$(4crrBFQAvTtT#
zgRtw&ekx5q505omn+e6a)9%eS@d*(Yfea5_6fL4Yo41&7P&~8hc=VTX1fW?u+%yyx
z!8F&B!qGf?nsX6`%o|(6yg~cr0<5)!I=Cc(1cL>fbE@rGg$~XP8AgHvU(Sl-r#%Fic6=>>ot{L`dkV#nM*hkE>57ByV>ddO*Q7wk|M&l7**H7Jop>fh>e_Pfox~54N
z{72W#;4ajabFk~L+}PCBx!A$D$iuP4Nzz5Kl&;Ak*x>g$yhD%u!tS@8(#61jM19xh
z3MQSaELJ&lODEJB0Pk{M{`W!<9T1qk42mitn$dH~tX6KYZ#z<^A;`#6`;E
z+ax}ojIw(;p7G}+C;Lo>XwEf4?z
literal 0
HcmV?d00001
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/main.color.frag.perf.txt b/miniwin/src/d3drm/backends/gxm/shaders/main.color.frag.perf.txt
new file mode 100644
index 00000000..5292cb7a
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/main.color.frag.perf.txt
@@ -0,0 +1,134 @@
+Total estimated cost: 63.5 cycles, parallel mode
+Register count: 8 PAs, 16 temps, 38 SAs *
+Texture reads: 0 non-dependent, dependent: 0 unconditional, 0 conditional
+
+High level analysis:
+No warnings.
+
+* Please refer to the Razor documentation for details regarding the meaning of these numbers. Decreasing the number of registers used will not necessarily increase performance
+
+Instruction statistics:
+Number of alu ops: 63
+Number of mem ops: 0
+Number of tex ops: 0
+Number of floating point ops: 43
+Number of integer ops: 1
+Number of pack ops: 1
+Number of mov ops: 15
+Number of nop ops: 2
+
+Constants:
+[DEFAULT + 0 ] sa0 = (float1) uShininess
+[DEFAULT + 2 ] sa2 = (float4) uColor
+[BUFFER0 + 0 ] = (float4) uLights[0].color
+[BUFFER0 + 4 ] = (float4) uLights[0].vec
+[BUFFER0 + 8 ] = (float1) uLights[0].isDirectional
+[BUFFER0 + 10 ] = (float4) uLights[1].color
+[BUFFER0 + 14 ] = (float4) uLights[1].vec
+[BUFFER0 + 18 ] = (float1) uLights[1].isDirectional
+[BUFFER0 + 20 ] = (float3) uAmbientLight
+[LITERAL + 1 ] sa7 = 0xffffffff (-1.#QNAN0f) (-1.#QNANh, -1.#QNANh)
+[LITERAL + 2 ] sa8 = 0x3f800000 (1.000000f) (0.00000h, 1.87500h)
+[BUFFER0 ] sa6 = buffer base address
+
+Iterators:
+pa0 = (float4) TEXCOORD1
+pa4 = (float4) TEXCOORD2
+
+
+Secondary program:
+ 0 : lda32 sa1, [sa6, 0x9]
+ 1 : lda32.fetch3 sa9, [sa6, 0x4]
+ 2 : mul.f32 sa32.xy, -sa8.yz, sa0.yy
+ 3 : mul.f32 sa34.x, -sa10.y, sa0.y
+ 4 : lda32.fetch4 sa12, [sa6, 0x0]
+ 5 : mov.f32 sa36.xy, {0, 0}
+ 6 : mov.f32 sa30.x, {0}
+ 7 : lda32 sa15, [sa6, 0x13]
+ 8 : lda32.fetch3 sa16, [sa6, 0xe]
+ 9 : mul.f32 sa26.xy, -sa16.xy, sa14.yy
+ 10: mul.f32 sa28.x, -sa18.x, sa14.y
+ 11: lda32.fetch4 sa19, [sa6, 0xa]
+ 12: lda32.fetch3 sa22, [sa6, 0x14]
+ 13: nop
+
+Primary program:
+ 0 : cmp.gt.f32 p0, sa0.x, {0}
+ 1 : mov.f32 i0.xyz, pa0.xyz
+ 2 : add.f32 i1.xyz, sa8.yzw, -i0.xyz
+ 2 : +dot.f32 pa3.x, i0.xyz, i0.xyz
+ 3 : mov.f32 i2.xyz, sa32.xyz
+ 4 : mad.f32 i2.xyz, -sa0.yyy, i1.xyz, i2.xyz
+ 5 : add.f32 i1.xyz, i2.xyz, i1.xyz
+ 5 : +rsq.f32 pa3.x, pa3.x
+ 6 : dot.f32 i2.x, i1.xyz, i1.xyz
+ 7 : rsq.f32 pa3.x, i2.x
+ 7 : +mul.f32 i0.xyz, -i0.xyz, pa2.yyy
+ 8 : mad.f32 i2.xyz, pa2.yyy, i1.xyz, i0.xyz
+ 8 : +mov.f32 r6.xy, i0.xy
+ 9 : mov.f32 r8.x, i0.z
+ 10: dot.f32 i0.x, i2.xyz, i2.xyz
+ 10: +mov.f32 r2.xy, i2.xy
+ 11: mov.f32 r4.x, i2.z
+ 12: rsq.f32 pa3.x, i0.x
+ 12: +mul.f32 i0.xyz, i1.xyz, pa2.yyy
+ 13: dot.f32 i0.x, pa4.xyz, i0.xyz
+ 14: max.f32 r14.x, i0.x, {0}
+ 15: p0 br #19
+ 16: mov.f32 r10.xy, sa36.xy
+ 17: or.u32 r12.x, sa30.x, 0x0
+ 18: br #30
+#19: cmov.lezero.f32 r0.x, r14.x, {0}, sa8.x
+ 20: cmov.ltzero.f32 i0.x, r14.x, sa8.x, {0}
+ 21: mad.f32 i0.x, r0.x, {1}, -i0.x
+ 22: mul.f32 i1.xyz, r2.xyz, pa2.yyy
+ 23: dot.f32 i1.x, pa4.xyz, i1.xyz
+ 24: max.f32 i1.x, i1.x, {0}
+ 25: mul.f32 i0.xyz, sa12.xyz, i0.xxx
+ 25: +log.f32 pa3.x, i1.x
+ 26: mul.f32 i1.x, sa0.x, pa2.y
+ 27: exp.f32 i1.x, i1.x
+ 28: mad.f32 r10.xy, i0.xy, i1.xx, {0, 0}
+ 29: mul.f32 r12.x, i0.z, i1.x
+#30: nop
+ 31: mov.f32 i0.xyz, sa16.xyz
+ 32: mad.f32 i0.xyz, -pa0.xyz, {1, 1, 1}, i0.xyz
+ 33: mov.f32 i1.xyz, sa26.xyz
+ 34: mad.f32 i1.xyz, -sa14.yyy, i0.xyz, i1.xyz
+ 35: add.f32 i0.xyz, i1.xyz, i0.xyz
+ 35: +mov.f32 i2.xyz, r6.xyz
+ 36: dot.f32 i1.x, i0.xyz, i0.xyz
+ 37: rsq.f32 i1.x, i1.x
+ 38: mad.f32 i2.xyz, i1.xxx, i0.xyz, i2.xyz
+ 39: mul.f32 i0.xyz, i0.xyz, i1.xxx
+ 39: +mov.f32 r0.xy, i2.xy
+ 40: dot.f32 i0.x, pa4.xyz, i0.xyz
+ 41: max.f32 pa2.x, i0.x, {0}
+ 42: dot.f32 i0.x, i2.xyz, i2.xyz
+ 43: mov.f32 r2.x, i2.z
+ 44: rsq.f32 pa0.-y, i0.x
+ 45: !p0 br #57
+ 46: cmov.lezero.f32 pa0.x, pa2.x, {0}, sa8.x
+ 47: cmov.ltzero.f32 i0.x, pa2.x, sa8.x, {0}
+ 48: mad.f32 i0.x, pa0.x, {1}, -i0.x
+ 49: mul.f32 i1.xyz, r0.xyz, pa0.yyy
+ 50: dot.f32 i1.x, pa4.xyz, i1.xyz
+ 51: max.f32 i1.x, i1.x, {0}
+ 52: mul.f32 i0.xyz, sa18.yzw, i0.xxx
+ 52: +log.f32 pa0.x, i1.x
+ 53: mul.f32 i1.x, sa0.x, pa0.x
+ 54: exp.f32 i1.x, i1.x
+ 55: mad.f32 r10.xy, i0.xy, i1.xx, r10.xy
+ 56: mad.f32 r12.x, i0.z, i1.x, r12.x
+#57: nop
+ 58: mad.f32 i0.xy, r14.xx, sa12.xy, sa22.xy
+ 59: mad.f32 i0.--z, r14.--x, sa14.--x, sa24.--x
+ 60: mov.f32 i1.xyz, sa18.yzw
+ 61: mad.f32 i0.xyz, pa2.xxx, i1.xyz, i0.xyz
+ 62: mov.f32 i1.xyz, r10.xyz
+ 63: mov.f32 i2.xyzw, sa2.xyzw
+ 64: mad.f32 i0.xyz, i2.xyz, i0.xyz, i1.xyz
+ 65: min.f32 i0.xyz, i0.xyz, {1, 1, 1}
+ 66: max.f32 i2.xyz, i0.xyz, {0, 0, 0}
+ 67: pack.f16.f32 pa0.xyzw, i2.xyzw
+
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/main.frag.cg b/miniwin/src/d3drm/backends/gxm/shaders/main.frag.cg
new file mode 100644
index 00000000..6960a5f1
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/main.frag.cg
@@ -0,0 +1,54 @@
+struct SceneLight {
+ float4 color;
+ float4 vec;
+ float isDirectional;
+};
+
+void main(
+ float4 vPosition : POSITION,
+ float2 vTexCoord : TEXCOORD0,
+ float3 vViewPos : TEXCOORD1,
+ float3 vNormal : TEXCOORD2,
+
+ uniform __nostrip SceneLight uLights[2] : BUFFER[0],
+ uniform __nostrip float3 uAmbientLight : BUFFER[0],
+ uniform __nostrip float uShininess,
+ uniform __nostrip float4 uColor,
+ uniform __nostrip sampler2D uTexture,
+
+ out float4 outColor : COLOR
+) {
+ float3 diffuse = float3(0.0, 0.0, 0.0);
+ float3 specular = float3(0.0, 0.0, 0.0);
+
+ float3 viewVec = normalize(-vViewPos); // Assuming camera at origin
+
+ diffuse += uAmbientLight;
+
+ for (int i = 0; i < 2; ++i) {
+ float3 lightColor = uLights[i].color.rgb;
+ float isDirectional = uLights[i].isDirectional;
+
+ float3 lightVec = normalize(lerp(uLights[i].vec.xyz - vViewPos, -uLights[i].vec.xyz, isDirectional));
+
+ float3 halfVec = normalize(viewVec + lightVec);
+ float dotNL = max(dot(vNormal, lightVec), 0.0);
+ float dotNH = max(dot(vNormal, halfVec), 0.0);
+
+ // Diffuse contribution
+ diffuse += dotNL * lightColor;
+
+ // Specular
+ float spec = pow(dotNH, uShininess);
+ if(uShininess > 0.0) {
+ specular += spec * lightColor * sign(dotNL);
+ }
+ }
+
+ outColor.rgb = clamp(diffuse * uColor.rgb + specular, 0.0, 1.0);
+ outColor.a = uColor.a;
+#if TEXTURED
+ float4 texel = tex2D(uTexture, vTexCoord);
+ outColor.rgb *= texel.rgb;
+#endif
+}
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/main.texture.frag.gxp b/miniwin/src/d3drm/backends/gxm/shaders/main.texture.frag.gxp
new file mode 100644
index 0000000000000000000000000000000000000000..501bfea422685f9e7d2e7935e9c4e5d4a7212680
GIT binary patch
literal 1312
zcmaJ=O-NKx6h8OO(|MKl-Wv;^X!s0?HsuH|W}(v?oYF;sQj6$^C>wIXX8f6nXc713
z8#8|}p-m9vJbxB#qJ79{ZX!d42wKHJi&|wn4a?5CGo=Z2;JtIdbIy0q`R;qS^?W<9
zl6GHB3BX(U^kiV}!ZBu9pa2*Q3bqg3s1KkAeHk19U4|K)LSKzVCi)@D{U*pf08*rt
z;=2)n6ZX-*VBwldgT(^EWCnW5Yv`AXF_{3SD#%a?ROZUat>F{IN4InP3DcU4yS0-p
zOnruXwOQ95_*{tE8z7EO&zd>S5pC1YjGP8sWHUj^x3wrG_d)RZy7Mp);dZk(QNxFXC45O9xGw5MMQYMu!Uo-7MmZ1t
z)EWV5U7PEg#KNE8N=(?QXO43&EJ!YzF3f0jUJk%UU7+RLXgPO4=Xni8I~^d#*VP{A
z4On*0|b?Ht0{~l#pvPaK3VjYa%?U
z!+suY5)qS&yw$9aF%DeA?ZMdt>wzI5^#MCHzBrP#s>iYtw;CPGQm_BLZbV$W$?MRi
zUfORj4>sEqI>+nbx!ct{E3f1~hVQP&UEfZjpU
zWv;EN1zqE$3;v<&f&T#N%GfydSFUaAI=S4!xR``f%Oj+VWJz5ky|Bd}b67%;^TNT`
zp47#_d`fedWwJVbS57%t?o8TF%&CHckr
zy@;3a`OT*pWWHD*K>UvKct44cUr5>`d|xWAslTE!nWqrd{?F29yW)v>BG%sz>6T=7
zvJcXy<2Sle{TG9mk9Y14+=_MX%*Xqq@xEARDxOSS?f#Q!*iFR1P9nlx#K_HS@mL}y
P8$~<>M7X$bOq!Kp2
literal 0
HcmV?d00001
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/main.texture.frag.perf.txt b/miniwin/src/d3drm/backends/gxm/shaders/main.texture.frag.perf.txt
new file mode 100644
index 00000000..ef379b18
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/main.texture.frag.perf.txt
@@ -0,0 +1,139 @@
+Total estimated cost: 64.5 cycles, parallel mode
+Register count: 12 PAs, 16 temps, 38 SAs *
+Texture reads: 1 non-dependent, dependent: 0 unconditional, 0 conditional
+
+High level analysis:
+No warnings.
+
+* Please refer to the Razor documentation for details regarding the meaning of these numbers. Decreasing the number of registers used will not necessarily increase performance
+
+Instruction statistics:
+Number of alu ops: 64
+Number of mem ops: 0
+Number of tex ops: 0
+Number of floating point ops: 44
+Number of integer ops: 1
+Number of pack ops: 1
+Number of mov ops: 15
+Number of nop ops: 2
+
+Constants:
+[DEFAULT + 0 ] sa0 = (float1) uShininess
+[DEFAULT + 2 ] sa2 = (float4) uColor
+[BUFFER0 + 0 ] = (float4) uLights[0].color
+[BUFFER0 + 4 ] = (float4) uLights[0].vec
+[BUFFER0 + 8 ] = (float1) uLights[0].isDirectional
+[BUFFER0 + 10 ] = (float4) uLights[1].color
+[BUFFER0 + 14 ] = (float4) uLights[1].vec
+[BUFFER0 + 18 ] = (float1) uLights[1].isDirectional
+[BUFFER0 + 20 ] = (float3) uAmbientLight
+[LITERAL + 1 ] sa7 = 0xffffffff (-1.#QNAN0f) (-1.#QNANh, -1.#QNANh)
+[LITERAL + 2 ] sa8 = 0x3f800000 (1.000000f) (0.00000h, 1.87500h)
+[BUFFER0 ] sa6 = buffer base address
+
+Samplers:
+TEXUNIT0 = uTexture
+
+Iterators:
+pa0 = (float4) TEXCOORD1
+pa8 = (float4) TEXCOORD2
+
+
+Secondary program:
+ 0 : lda32 sa1, [sa6, 0x9]
+ 1 : lda32.fetch3 sa9, [sa6, 0x4]
+ 2 : mul.f32 sa32.xy, -sa8.yz, sa0.yy
+ 3 : mul.f32 sa34.x, -sa10.y, sa0.y
+ 4 : lda32.fetch4 sa12, [sa6, 0x0]
+ 5 : mov.f32 sa36.xy, {0, 0}
+ 6 : mov.f32 sa30.x, {0}
+ 7 : lda32 sa15, [sa6, 0x13]
+ 8 : lda32.fetch3 sa16, [sa6, 0xe]
+ 9 : mul.f32 sa26.xy, -sa16.xy, sa14.yy
+ 10: mul.f32 sa28.x, -sa18.x, sa14.y
+ 11: lda32.fetch4 sa19, [sa6, 0xa]
+ 12: lda32.fetch3 sa22, [sa6, 0x14]
+ 13: nop
+
+Primary program:
+pa4 = tex2D(uTexture, TEXCOORD0.xy)
+ 0 : cmp.gt.f32 p0, sa0.x, {0}
+ 1 : mov.f32 i0.xyz, pa0.xyz
+ 2 : add.f32 i1.xyz, sa8.yzw, -i0.xyz
+ 2 : +dot.f32 pa3.x, i0.xyz, i0.xyz
+ 3 : mov.f32 i2.xyz, sa32.xyz
+ 4 : mad.f32 i2.xyz, -sa0.yyy, i1.xyz, i2.xyz
+ 5 : add.f32 i1.xyz, i2.xyz, i1.xyz
+ 5 : +rsq.f32 pa3.x, pa3.x
+ 6 : dot.f32 i2.x, i1.xyz, i1.xyz
+ 7 : rsq.f32 pa3.x, i2.x
+ 7 : +mul.f32 i0.xyz, -i0.xyz, pa2.yyy
+ 8 : mad.f32 i2.xyz, pa2.yyy, i1.xyz, i0.xyz
+ 8 : +mov.f32 r6.xy, i0.xy
+ 9 : mov.f32 r8.x, i0.z
+ 10: dot.f32 i0.x, i2.xyz, i2.xyz
+ 10: +mov.f32 r2.xy, i2.xy
+ 11: mov.f32 r4.x, i2.z
+ 12: rsq.f32 pa3.x, i0.x
+ 12: +mul.f32 i0.xyz, i1.xyz, pa2.yyy
+ 13: dot.f32 i0.x, pa8.xyz, i0.xyz
+ 14: max.f32 r14.x, i0.x, {0}
+ 15: p0 br #19
+ 16: mov.f32 r10.xy, sa36.xy
+ 17: or.u32 r12.x, sa30.x, 0x0
+ 18: br #30
+#19: cmov.lezero.f32 r0.x, r14.x, {0}, sa8.x
+ 20: cmov.ltzero.f32 i0.x, r14.x, sa8.x, {0}
+ 21: mad.f32 i0.x, r0.x, {1}, -i0.x
+ 22: mul.f32 i1.xyz, r2.xyz, pa2.yyy
+ 23: dot.f32 i1.x, pa8.xyz, i1.xyz
+ 24: max.f32 i1.x, i1.x, {0}
+ 25: mul.f32 i0.xyz, sa12.xyz, i0.xxx
+ 25: +log.f32 pa3.x, i1.x
+ 26: mul.f32 i1.x, sa0.x, pa2.y
+ 27: exp.f32 i1.x, i1.x
+ 28: mad.f32 r10.xy, i0.xy, i1.xx, {0, 0}
+ 29: mul.f32 r12.x, i0.z, i1.x
+#30: nop
+ 31: mov.f32 i0.xyz, sa16.xyz
+ 32: mad.f32 i0.xyz, -pa0.xyz, {1, 1, 1}, i0.xyz
+ 33: mov.f32 i1.xyz, sa26.xyz
+ 34: mad.f32 i1.xyz, -sa14.yyy, i0.xyz, i1.xyz
+ 35: add.f32 i0.xyz, i1.xyz, i0.xyz
+ 35: +mov.f32 i2.xyz, r6.xyz
+ 36: dot.f32 i1.x, i0.xyz, i0.xyz
+ 37: rsq.f32 i1.x, i1.x
+ 38: mad.f32 i2.xyz, i1.xxx, i0.xyz, i2.xyz
+ 39: mul.f32 i0.xyz, i0.xyz, i1.xxx
+ 39: +mov.f32 r0.xy, i2.xy
+ 40: dot.f32 i0.x, pa8.xyz, i0.xyz
+ 41: max.f32 pa2.x, i0.x, {0}
+ 42: dot.f32 i0.x, i2.xyz, i2.xyz
+ 43: mov.f32 r2.x, i2.z
+ 44: rsq.f32 pa0.-y, i0.x
+ 45: !p0 br #57
+ 46: cmov.lezero.f32 pa0.x, pa2.x, {0}, sa8.x
+ 47: cmov.ltzero.f32 i0.x, pa2.x, sa8.x, {0}
+ 48: mad.f32 i0.x, pa0.x, {1}, -i0.x
+ 49: mul.f32 i1.xyz, r0.xyz, pa0.yyy
+ 50: dot.f32 i1.x, pa8.xyz, i1.xyz
+ 51: max.f32 i1.x, i1.x, {0}
+ 52: mul.f32 i0.xyz, sa18.yzw, i0.xxx
+ 52: +log.f32 pa0.x, i1.x
+ 53: mul.f32 i1.x, sa0.x, pa0.x
+ 54: exp.f32 i1.x, i1.x
+ 55: mad.f32 r10.xy, i0.xy, i1.xx, r10.xy
+ 56: mad.f32 r12.x, i0.z, i1.x, r12.x
+#57: nop
+ 58: mad.f32 i0.xy, r14.xx, sa12.xy, sa22.xy
+ 59: mad.f32 i0.--z, r14.--x, sa14.--x, sa24.--x
+ 60: mov.f32 i1.xyz, sa18.yzw
+ 61: mad.f32 i0.xyz, pa2.xxx, i1.xyz, i0.xyz
+ 62: mov.f32 i1.xyz, r10.xyz
+ 63: mov.f32 i2.xyzw, sa2.xyzw
+ 64: mad.f32 i0.xyz, i2.xyz, i0.xyz, i1.xyz
+ 65: min.f32 i0.xyz, i0.xyz, {1, 1, 1}
+ 66: max.f32 i0.xyz, i0.xyz, {0, 0, 0}
+ 67: mad.f32 i2.xyz, pa4.xyz, i0.xyz, {0, 0, 0}
+ 68: pack.f16.f32 pa0.xyzw, i2.xyzw
+
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/main.vert.cg b/miniwin/src/d3drm/backends/gxm/shaders/main.vert.cg
new file mode 100644
index 00000000..2f74fee6
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/main.vert.cg
@@ -0,0 +1,20 @@
+void main(
+ float3 aPosition : POSITION,
+ float3 aNormal : NORMAL,
+ float2 aTexCoord : TEXCOORD0,
+
+ column_major uniform float4x4 uModelViewMatrix,
+ column_major uniform float3x3 uNormalMatrix,
+ column_major uniform float4x4 uProjectionMatrix,
+
+ out float4 vPosition : POSITION,
+ out float2 vTexCoord : TEXCOORD0,
+ out float3 vViewPos : TEXCOORD1,
+ out float3 vNormal : TEXCOORD2
+) {
+ float4 viewPos = mul(uModelViewMatrix, float4(aPosition, 1.0));
+ vPosition = mul(uProjectionMatrix, viewPos);
+ vViewPos = viewPos.xyz;
+ vNormal = normalize(mul(uNormalMatrix, aNormal));
+ vTexCoord = aTexCoord;
+}
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/main.vert.gxp b/miniwin/src/d3drm/backends/gxm/shaders/main.vert.gxp
new file mode 100644
index 0000000000000000000000000000000000000000..b4d3427db1fb1ad7850fa0e5775fd878df32f95f
GIT binary patch
literal 784
zcmZuv&ubGw6n?Y$;qJQGA3<~#gzm{6dI;hnhY-`WMMNX36cJBGn?t0!5>pz`!|b{Z
z2}L6wih^J)^&D~(6cl@uTERN15A^gN%sfZuJ*o$=&
z{1)@ih@#w%`RZ6sGqjkXjSd)9B?W$|;fuk$%!g7zn(u|imaJL}J>DDInsuH~w;s0x
ze`p(DciKUBXzT5_q<^0Xd*RtFS#!FtgkE*l2xW(D2)z=OH-tVNj7VQR@E^qfgy{c9
z=+K`~;$#2Lu|;0aZBMH#MZOwS2z6F@M*5FBAva`6&G39C`KkW|&rvdfNyxbY5+O4K
z=_Jp~$+@_1vTRE9O(~lSCl>;6X#O`a`H_RO>A)YnL@(2y{leEHJ8LZ<4(%hBLMZXq
z)Ia@vv@ibM38(vAoav~uMyNP!*T*x-QJD}n|L*$JPwm3yWrKHo+b{MFPLssnr@9Q)#ae65^BPOAQt_5*%UA2Q)k?M5sJCEcESWI3
O;oYv?62tKh`uzhx*TC@r
literal 0
HcmV?d00001
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/main.vert.perf.txt b/miniwin/src/d3drm/backends/gxm/shaders/main.vert.perf.txt
new file mode 100644
index 00000000..ab3c679e
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/main.vert.perf.txt
@@ -0,0 +1,87 @@
+Total estimated cost: 18 cycles, parallel mode
+Register count: 12 PAs, 0 temps, 56 SAs *
+Texture reads: 0 non-dependent, dependent: 0 unconditional, 0 conditional
+
+High level analysis:
+- One or multiple vertex outputs are misaligned due to a three coefficient TEXCOORD vertex output. This will lead to additional data movement instructions being generated. If only one three coefficient TEXCOORD output is present, assigning it the coordinate with the greatest index will allow the previous coordinates to be aligned.
+
+* Please refer to the Razor documentation for details regarding the meaning of these numbers. Decreasing the number of registers used will not necessarily increase performance
+
+Instruction statistics:
+Number of alu ops: 19
+Number of mem ops: 0
+Number of tex ops: 0
+Number of floating point ops: 14
+Number of integer ops: 0
+Number of pack ops: 0
+Number of mov ops: 4
+Number of nop ops: 0
+
+Constants:
+[DEFAULT + 0 ] sa0 = (float4) uModelViewMatrix[4]
+[DEFAULT + 32 ] sa32 = (float3) uNormalMatrix[3]
+[DEFAULT + 16 ] sa16 = (float4) uProjectionMatrix[4]
+
+Vertex attributes:
+pa0 = (float4) aPosition
+pa4 = (float4) aNormal
+pa8 = (float4) aTexCoord
+
+
+Secondary program:
+ 0 : nop
+ 1 : mov.f32 i0.xyzw, sa8.xyzw
+ 2 : mul.f32 i1.xyzw, sa16.xyzw, i0.xxxx
+ 3 : mad.f32 i1.xyzw, sa20.xyzw, i0.yyyy, i1.xyzw
+ 4 : mad.f32 i1.xyzw, sa24.xyzw, i0.zzzz, i1.xyzw
+ 5 : mad.f32 sa52.xy, sa28.xy, i0.ww, i1.xy
+ 6 : mad.f32 sa54.xy, sa30.xy, i0.ww, i1.zw
+ 7 : mov.f32 i1.xyzw, sa12.xyzw
+ 8 : mul.f32 i2.xyzw, sa28.xyzw, i1.wwww
+ 9 : mad.f32 i2.xyzw, sa24.xyzw, i1.zzzz, i2.xyzw
+ 10: mad.f32 i2.xyzw, sa20.xyzw, i1.yyyy, i2.xyzw
+ 11: mad.f32 sa48.xy, sa16.xy, i1.xx, i2.xy
+ 12: mad.f32 sa50.xy, sa18.xy, i1.xx, i2.zw
+ 13: mov.f32 i2.xyzw, sa4.xyzw
+ 14: mul.f32 i1.xyzw, sa16.xyzw, i2.xxxx
+ 15: mad.f32 i1.xyzw, sa20.xyzw, i2.yyyy, i1.xyzw
+ 16: mad.f32 i1.xyzw, sa24.xyzw, i2.zzzz, i1.xyzw
+ 17: mad.f32 sa44.xy, sa28.xy, i2.ww, i1.xy
+ 18: mad.f32 sa46.xy, sa30.xy, i2.ww, i1.zw
+ 19: mov.f32 i1.xyzw, sa0.xyzw
+ 20: mul.f32 i0.xyzw, sa16.xyzw, i1.xxxx
+ 20: +mov.f32 sa4.xy, i1.yy
+ 21: mad.f32 i0.xyzw, sa20.xyzw, i1.yyyy, i0.xyzw
+ 22: mad.f32 i0.xyzw, sa24.xyzw, i1.zzzz, i0.xyzw
+ 23: mad.f32 sa18.xy, sa28.xy, i1.ww, i0.xy
+ 24: mad.f32 sa16.xy, sa30.xy, i1.ww, i0.zw
+ 25: mov.f32 sa4.-y, i2.-y
+ 26: mov.f32 sa6.xy, (sa8.y, sa12.y)
+ 27: mov.f32 sa0.x, i1.x
+ 28: mov.f32 sa0.-y, i2.-x
+ 29: mov.f32 sa8.-y, sa12.-x
+ 30: mov.f32 sa10.-y, sa14.-x
+ 31: mov.f32 sa2.xy, sa8.xy
+ 32: mov.f32 sa8.x, i1.z
+ 33: mov.f32 sa8.-y, i2.-z
+
+Primary program:
+ 0 : mov.f32 o4.xy, pa8.xy
+ 1 : mov.f32 i0.xyz, pa4.xyz
+ 2 : mad.f32 i1.xyz, sa32.xyz, i0.xxx, {0, 0, 0}
+ 3 : mad.f32 i1.xyz, sa36.xyz, i0.yyy, i1.xyz
+ 4 : mad.f32 i0.xyz, sa40.xyz, i0.zzz, i1.xyz
+ 5 : mov.f32 i1.xyz, pa0.xyz
+ 6 : mov.f32 i2.xyzw, sa48.xyzw
+ 7 : mad.f32 i2.xyzw, sa52.xyzw, i1.zzzz, i2.xyzw
+ 8 : mad.f32 i2.xyzw, sa44.xyzw, i1.yyyy, i2.xyzw
+ 9 : mad.f32 o0.xy, sa18.xy, i1.xx, i2.xy
+ 10: mad.f32 o2.xy, sa16.xy, i1.xx, i2.zw
+ 11: dot.f32 o6.x, sa0.xyzw, i1.xyz1
+ 12: dot.f32 o6.-y, sa4.xyzw, i1.xyz1
+ 13: dot.f32 o6.--z, sa8.xyzw, i1.xyz1
+ 14: dot.f32 i1.x, i0.xyz, i0.xyz
+ 15: rsq.f32 i1.x, i1.x
+ 16: mad.f32 o10.xy, i0.yz, i1.xx, {0, 0}
+ 17: mad.f32 o8.-y, i0.-x, i1.-x, {0, 0}
+
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/plane.vert.cg b/miniwin/src/d3drm/backends/gxm/shaders/plane.vert.cg
new file mode 100644
index 00000000..532431d6
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/plane.vert.cg
@@ -0,0 +1,11 @@
+
+void main(
+ float2 aPosition : POSITION,
+ float2 aTexCoord : TEXCOORD0,
+
+ out float4 vPosition : POSITION,
+ out float2 vTexCoord : TEXCOORD0
+) : POSITION {
+ vPosition = float4(aPosition, 1.f, 1.f);
+ vTexCoord = aTexCoord;
+}
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/plane.vert.gxp b/miniwin/src/d3drm/backends/gxm/shaders/plane.vert.gxp
new file mode 100644
index 0000000000000000000000000000000000000000..b9a45ae858e364e9a219726f8ad631114492277e
GIT binary patch
literal 284
zcmZ>d2w-4j4PcgGWMHtFTcWvD(*HgSgCqkoU;=Uu0C52ja{w^}Ffy3r1EoWtv@w*1QvwWZKxbkCc9&lb3>?jhtPK{73@j5`
z1($L#HiS)R6l?}MS78A|fckF*kS-Q(2AF;+FrN?1X92Pk1M-VAOEUBG7!pHLE1dK5
Hi&7W>phO%r
literal 0
HcmV?d00001
diff --git a/miniwin/src/d3drm/backends/gxm/shaders/plane.vert.perf.txt b/miniwin/src/d3drm/backends/gxm/shaders/plane.vert.perf.txt
new file mode 100644
index 00000000..60080fde
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/shaders/plane.vert.perf.txt
@@ -0,0 +1,30 @@
+Total estimated cost: 3 cycles, parallel mode
+Register count: 8 PAs, 0 temps, 0 SAs *
+Texture reads: 0 non-dependent, dependent: 0 unconditional, 0 conditional
+
+High level analysis:
+No warnings.
+
+* Please refer to the Razor documentation for details regarding the meaning of these numbers. Decreasing the number of registers used will not necessarily increase performance
+
+Instruction statistics:
+Number of alu ops: 4
+Number of mem ops: 0
+Number of tex ops: 0
+Number of floating point ops: 2
+Number of integer ops: 0
+Number of pack ops: 0
+Number of mov ops: 1
+Number of nop ops: 0
+
+
+Vertex attributes:
+pa0 = (float4) aPosition
+pa4 = (float4) aTexCoord
+
+
+Primary program:
+ 0: mov.f32 o4.xy, pa4.xy
+ 1: mul.f32 o0.xy, pa0.xy, {1, 1}
+ 2: mul.f32 o2.xy, {1, 1}, {1, 1}
+
diff --git a/miniwin/src/d3drm/backends/gxm/tlsf.c b/miniwin/src/d3drm/backends/gxm/tlsf.c
new file mode 100644
index 00000000..31d25bdc
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/tlsf.c
@@ -0,0 +1,1264 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "tlsf.h"
+
+#if defined(__cplusplus)
+#define tlsf_decl inline
+#else
+#define tlsf_decl static
+#endif
+
+/*
+** Architecture-specific bit manipulation routines.
+**
+** TLSF achieves O(1) cost for malloc and free operations by limiting
+** the search for a free block to a free list of guaranteed size
+** adequate to fulfill the request, combined with efficient free list
+** queries using bitmasks and architecture-specific bit-manipulation
+** routines.
+**
+** Most modern processors provide instructions to count leading zeroes
+** in a word, find the lowest and highest set bit, etc. These
+** specific implementations will be used when available, falling back
+** to a reasonably efficient generic implementation.
+**
+** NOTE: TLSF spec relies on ffs/fls returning value 0..31.
+** ffs/fls return 1-32 by default, returning 0 for error.
+*/
+
+/*
+** Detect whether or not we are building for a 32- or 64-bit (LP/LLP)
+** architecture. There is no reliable portable method at compile-time.
+*/
+#if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) \
+ || defined (_WIN64) || defined (__LP64__) || defined (__LLP64__)
+#define TLSF_64BIT
+#endif
+
+/*
+** gcc 3.4 and above have builtin support, specialized for architecture.
+** Some compilers masquerade as gcc; patchlevel test filters them out.
+*/
+#if defined (__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) \
+ && defined (__GNUC_PATCHLEVEL__)
+
+#if defined (__SNC__)
+/* SNC for Playstation 3. */
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ const unsigned int reverse = word & (~word + 1);
+ const int bit = 32 - __builtin_clz(reverse);
+ return bit - 1;
+}
+
+#else
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ return __builtin_ffs(word) - 1;
+}
+
+#endif
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ const int bit = word ? 32 - __builtin_clz(word) : 0;
+ return bit - 1;
+}
+
+#elif defined (_MSC_VER) && (_MSC_VER >= 1400) && (defined (_M_IX86) || defined (_M_X64))
+/* Microsoft Visual C++ support on x86/X64 architectures. */
+
+#include
+
+#pragma intrinsic(_BitScanReverse)
+#pragma intrinsic(_BitScanForward)
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ unsigned long index;
+ return _BitScanReverse(&index, word) ? index : -1;
+}
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ unsigned long index;
+ return _BitScanForward(&index, word) ? index : -1;
+}
+
+#elif defined (_MSC_VER) && defined (_M_PPC)
+/* Microsoft Visual C++ support on PowerPC architectures. */
+
+#include
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ const int bit = 32 - _CountLeadingZeros(word);
+ return bit - 1;
+}
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ const unsigned int reverse = word & (~word + 1);
+ const int bit = 32 - _CountLeadingZeros(reverse);
+ return bit - 1;
+}
+
+#elif defined (__ARMCC_VERSION)
+/* RealView Compilation Tools for ARM */
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ const unsigned int reverse = word & (~word + 1);
+ const int bit = 32 - __clz(reverse);
+ return bit - 1;
+}
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ const int bit = word ? 32 - __clz(word) : 0;
+ return bit - 1;
+}
+
+#elif defined (__ghs__)
+/* Green Hills support for PowerPC */
+
+#include
+
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ const unsigned int reverse = word & (~word + 1);
+ const int bit = 32 - __CLZ32(reverse);
+ return bit - 1;
+}
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ const int bit = word ? 32 - __CLZ32(word) : 0;
+ return bit - 1;
+}
+
+#else
+/* Fall back to generic implementation. */
+
+tlsf_decl int tlsf_fls_generic(unsigned int word)
+{
+ int bit = 32;
+
+ if (!word) bit -= 1;
+ if (!(word & 0xffff0000)) { word <<= 16; bit -= 16; }
+ if (!(word & 0xff000000)) { word <<= 8; bit -= 8; }
+ if (!(word & 0xf0000000)) { word <<= 4; bit -= 4; }
+ if (!(word & 0xc0000000)) { word <<= 2; bit -= 2; }
+ if (!(word & 0x80000000)) { word <<= 1; bit -= 1; }
+
+ return bit;
+}
+
+/* Implement ffs in terms of fls. */
+tlsf_decl int tlsf_ffs(unsigned int word)
+{
+ return tlsf_fls_generic(word & (~word + 1)) - 1;
+}
+
+tlsf_decl int tlsf_fls(unsigned int word)
+{
+ return tlsf_fls_generic(word) - 1;
+}
+
+#endif
+
+/* Possibly 64-bit version of tlsf_fls. */
+#if defined (TLSF_64BIT)
+tlsf_decl int tlsf_fls_sizet(size_t size)
+{
+ int high = (int)(size >> 32);
+ int bits = 0;
+ if (high)
+ {
+ bits = 32 + tlsf_fls(high);
+ }
+ else
+ {
+ bits = tlsf_fls((int)size & 0xffffffff);
+
+ }
+ return bits;
+}
+#else
+#define tlsf_fls_sizet tlsf_fls
+#endif
+
+#undef tlsf_decl
+
+/*
+** Constants.
+*/
+
+/* Public constants: may be modified. */
+enum tlsf_public
+{
+ /* log2 of number of linear subdivisions of block sizes. Larger
+ ** values require more memory in the control structure. Values of
+ ** 4 or 5 are typical.
+ */
+ SL_INDEX_COUNT_LOG2 = 5,
+};
+
+/* Private constants: do not modify. */
+enum tlsf_private
+{
+#if defined (TLSF_64BIT)
+ /* All allocation sizes and addresses are aligned to 8 bytes. */
+ ALIGN_SIZE_LOG2 = 3,
+#else
+ /* All allocation sizes and addresses are aligned to 4 bytes. */
+ ALIGN_SIZE_LOG2 = 2,
+#endif
+ ALIGN_SIZE = (1 << ALIGN_SIZE_LOG2),
+
+ /*
+ ** We support allocations of sizes up to (1 << FL_INDEX_MAX) bits.
+ ** However, because we linearly subdivide the second-level lists, and
+ ** our minimum size granularity is 4 bytes, it doesn't make sense to
+ ** create first-level lists for sizes smaller than SL_INDEX_COUNT * 4,
+ ** or (1 << (SL_INDEX_COUNT_LOG2 + 2)) bytes, as there we will be
+ ** trying to split size ranges into more slots than we have available.
+ ** Instead, we calculate the minimum threshold size, and place all
+ ** blocks below that size into the 0th first-level list.
+ */
+
+#if defined (TLSF_64BIT)
+ /*
+ ** TODO: We can increase this to support larger sizes, at the expense
+ ** of more overhead in the TLSF structure.
+ */
+ FL_INDEX_MAX = 32,
+#else
+ FL_INDEX_MAX = 30,
+#endif
+ SL_INDEX_COUNT = (1 << SL_INDEX_COUNT_LOG2),
+ FL_INDEX_SHIFT = (SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2),
+ FL_INDEX_COUNT = (FL_INDEX_MAX - FL_INDEX_SHIFT + 1),
+
+ SMALL_BLOCK_SIZE = (1 << FL_INDEX_SHIFT),
+};
+
+/*
+** Cast and min/max macros.
+*/
+
+#define tlsf_cast(t, exp) ((t) (exp))
+#define tlsf_min(a, b) ((a) < (b) ? (a) : (b))
+#define tlsf_max(a, b) ((a) > (b) ? (a) : (b))
+
+/*
+** Set assert macro, if it has not been provided by the user.
+*/
+#if !defined (tlsf_assert)
+#define tlsf_assert assert
+#endif
+
+/*
+** Static assertion mechanism.
+*/
+
+#define _tlsf_glue2(x, y) x ## y
+#define _tlsf_glue(x, y) _tlsf_glue2(x, y)
+#define tlsf_static_assert(exp) \
+ typedef char _tlsf_glue(static_assert, __LINE__) [(exp) ? 1 : -1]
+
+/* This code has been tested on 32- and 64-bit (LP/LLP) architectures. */
+tlsf_static_assert(sizeof(int) * CHAR_BIT == 32);
+tlsf_static_assert(sizeof(size_t) * CHAR_BIT >= 32);
+tlsf_static_assert(sizeof(size_t) * CHAR_BIT <= 64);
+
+/* SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type. */
+tlsf_static_assert(sizeof(unsigned int) * CHAR_BIT >= SL_INDEX_COUNT);
+
+/* Ensure we've properly tuned our sizes. */
+tlsf_static_assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
+
+/*
+** Data structures and associated constants.
+*/
+
+/*
+** Block header structure.
+**
+** There are several implementation subtleties involved:
+** - The prev_phys_block field is only valid if the previous block is free.
+** - The prev_phys_block field is actually stored at the end of the
+** previous block. It appears at the beginning of this structure only to
+** simplify the implementation.
+** - The next_free / prev_free fields are only valid if the block is free.
+*/
+typedef struct block_header_t
+{
+ /* Points to the previous physical block. */
+ struct block_header_t* prev_phys_block;
+
+ /* The size of this block, excluding the block header. */
+ size_t size;
+
+ /* Next and previous free blocks. */
+ struct block_header_t* next_free;
+ struct block_header_t* prev_free;
+} block_header_t;
+
+/*
+** Since block sizes are always at least a multiple of 4, the two least
+** significant bits of the size field are used to store the block status:
+** - bit 0: whether block is busy or free
+** - bit 1: whether previous block is busy or free
+*/
+static const size_t block_header_free_bit = 1 << 0;
+static const size_t block_header_prev_free_bit = 1 << 1;
+
+/*
+** The size of the block header exposed to used blocks is the size field.
+** The prev_phys_block field is stored *inside* the previous free block.
+*/
+static const size_t block_header_overhead = sizeof(size_t);
+
+/* User data starts directly after the size field in a used block. */
+static const size_t block_start_offset =
+ offsetof(block_header_t, size) + sizeof(size_t);
+
+/*
+** A free block must be large enough to store its header minus the size of
+** the prev_phys_block field, and no larger than the number of addressable
+** bits for FL_INDEX.
+*/
+static const size_t block_size_min =
+ sizeof(block_header_t) - sizeof(block_header_t*);
+static const size_t block_size_max = tlsf_cast(size_t, 1) << FL_INDEX_MAX;
+
+
+/* The TLSF control structure. */
+typedef struct control_t
+{
+ /* Empty lists point at this block to indicate they are free. */
+ block_header_t block_null;
+
+ /* Bitmaps for free lists. */
+ unsigned int fl_bitmap;
+ unsigned int sl_bitmap[FL_INDEX_COUNT];
+
+ /* Head of free lists. */
+ block_header_t* blocks[FL_INDEX_COUNT][SL_INDEX_COUNT];
+} control_t;
+
+/* A type used for casting when doing pointer arithmetic. */
+typedef ptrdiff_t tlsfptr_t;
+
+/*
+** block_header_t member functions.
+*/
+
+static size_t block_size(const block_header_t* block)
+{
+ return block->size & ~(block_header_free_bit | block_header_prev_free_bit);
+}
+
+static void block_set_size(block_header_t* block, size_t size)
+{
+ const size_t oldsize = block->size;
+ block->size = size | (oldsize & (block_header_free_bit | block_header_prev_free_bit));
+}
+
+static int block_is_last(const block_header_t* block)
+{
+ return block_size(block) == 0;
+}
+
+static int block_is_free(const block_header_t* block)
+{
+ return tlsf_cast(int, block->size & block_header_free_bit);
+}
+
+static void block_set_free(block_header_t* block)
+{
+ block->size |= block_header_free_bit;
+}
+
+static void block_set_used(block_header_t* block)
+{
+ block->size &= ~block_header_free_bit;
+}
+
+static int block_is_prev_free(const block_header_t* block)
+{
+ return tlsf_cast(int, block->size & block_header_prev_free_bit);
+}
+
+static void block_set_prev_free(block_header_t* block)
+{
+ block->size |= block_header_prev_free_bit;
+}
+
+static void block_set_prev_used(block_header_t* block)
+{
+ block->size &= ~block_header_prev_free_bit;
+}
+
+static block_header_t* block_from_ptr(const void* ptr)
+{
+ return tlsf_cast(block_header_t*,
+ tlsf_cast(unsigned char*, ptr) - block_start_offset);
+}
+
+static void* block_to_ptr(const block_header_t* block)
+{
+ return tlsf_cast(void*,
+ tlsf_cast(unsigned char*, block) + block_start_offset);
+}
+
+/* Return location of next block after block of given size. */
+static block_header_t* offset_to_block(const void* ptr, size_t size)
+{
+ return tlsf_cast(block_header_t*, tlsf_cast(tlsfptr_t, ptr) + size);
+}
+
+/* Return location of previous block. */
+static block_header_t* block_prev(const block_header_t* block)
+{
+ tlsf_assert(block_is_prev_free(block) && "previous block must be free");
+ return block->prev_phys_block;
+}
+
+/* Return location of next existing block. */
+static block_header_t* block_next(const block_header_t* block)
+{
+ block_header_t* next = offset_to_block(block_to_ptr(block),
+ block_size(block) - block_header_overhead);
+ tlsf_assert(!block_is_last(block));
+ return next;
+}
+
+/* Link a new block with its physical neighbor, return the neighbor. */
+static block_header_t* block_link_next(block_header_t* block)
+{
+ block_header_t* next = block_next(block);
+ next->prev_phys_block = block;
+ return next;
+}
+
+static void block_mark_as_free(block_header_t* block)
+{
+ /* Link the block to the next block, first. */
+ block_header_t* next = block_link_next(block);
+ block_set_prev_free(next);
+ block_set_free(block);
+}
+
+static void block_mark_as_used(block_header_t* block)
+{
+ block_header_t* next = block_next(block);
+ block_set_prev_used(next);
+ block_set_used(block);
+}
+
+static size_t align_up(size_t x, size_t align)
+{
+ tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
+ return (x + (align - 1)) & ~(align - 1);
+}
+
+static size_t align_down(size_t x, size_t align)
+{
+ tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
+ return x - (x & (align - 1));
+}
+
+static void* align_ptr(const void* ptr, size_t align)
+{
+ const tlsfptr_t aligned =
+ (tlsf_cast(tlsfptr_t, ptr) + (align - 1)) & ~(align - 1);
+ tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two");
+ return tlsf_cast(void*, aligned);
+}
+
+/*
+** Adjust an allocation size to be aligned to word size, and no smaller
+** than internal minimum.
+*/
+static size_t adjust_request_size(size_t size, size_t align)
+{
+ size_t adjust = 0;
+ if (size)
+ {
+ const size_t aligned = align_up(size, align);
+
+ /* aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap */
+ if (aligned < block_size_max)
+ {
+ adjust = tlsf_max(aligned, block_size_min);
+ }
+ }
+ return adjust;
+}
+
+/*
+** TLSF utility functions. In most cases, these are direct translations of
+** the documentation found in the white paper.
+*/
+
+static void mapping_insert(size_t size, int* fli, int* sli)
+{
+ int fl, sl;
+ if (size < SMALL_BLOCK_SIZE)
+ {
+ /* Store small blocks in first list. */
+ fl = 0;
+ sl = tlsf_cast(int, size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
+ }
+ else
+ {
+ fl = tlsf_fls_sizet(size);
+ sl = tlsf_cast(int, size >> (fl - SL_INDEX_COUNT_LOG2)) ^ (1 << SL_INDEX_COUNT_LOG2);
+ fl -= (FL_INDEX_SHIFT - 1);
+ }
+ *fli = fl;
+ *sli = sl;
+}
+
+/* This version rounds up to the next block size (for allocations) */
+static void mapping_search(size_t size, int* fli, int* sli)
+{
+ if (size >= SMALL_BLOCK_SIZE)
+ {
+ const size_t round = (1 << (tlsf_fls_sizet(size) - SL_INDEX_COUNT_LOG2)) - 1;
+ size += round;
+ }
+ mapping_insert(size, fli, sli);
+}
+
+static block_header_t* search_suitable_block(control_t* control, int* fli, int* sli)
+{
+ int fl = *fli;
+ int sl = *sli;
+
+ /*
+ ** First, search for a block in the list associated with the given
+ ** fl/sl index.
+ */
+ unsigned int sl_map = control->sl_bitmap[fl] & (~0U << sl);
+ if (!sl_map)
+ {
+ /* No block exists. Search in the next largest first-level list. */
+ const unsigned int fl_map = control->fl_bitmap & (~0U << (fl + 1));
+ if (!fl_map)
+ {
+ /* No free blocks available, memory has been exhausted. */
+ return 0;
+ }
+
+ fl = tlsf_ffs(fl_map);
+ *fli = fl;
+ sl_map = control->sl_bitmap[fl];
+ }
+ tlsf_assert(sl_map && "internal error - second level bitmap is null");
+ sl = tlsf_ffs(sl_map);
+ *sli = sl;
+
+ /* Return the first block in the free list. */
+ return control->blocks[fl][sl];
+}
+
+/* Remove a free block from the free list.*/
+static void remove_free_block(control_t* control, block_header_t* block, int fl, int sl)
+{
+ block_header_t* prev = block->prev_free;
+ block_header_t* next = block->next_free;
+ tlsf_assert(prev && "prev_free field can not be null");
+ tlsf_assert(next && "next_free field can not be null");
+ next->prev_free = prev;
+ prev->next_free = next;
+
+ /* If this block is the head of the free list, set new head. */
+ if (control->blocks[fl][sl] == block)
+ {
+ control->blocks[fl][sl] = next;
+
+ /* If the new head is null, clear the bitmap. */
+ if (next == &control->block_null)
+ {
+ control->sl_bitmap[fl] &= ~(1U << sl);
+
+ /* If the second bitmap is now empty, clear the fl bitmap. */
+ if (!control->sl_bitmap[fl])
+ {
+ control->fl_bitmap &= ~(1U << fl);
+ }
+ }
+ }
+}
+
+/* Insert a free block into the free block list. */
+static void insert_free_block(control_t* control, block_header_t* block, int fl, int sl)
+{
+ block_header_t* current = control->blocks[fl][sl];
+ tlsf_assert(current && "free list cannot have a null entry");
+ tlsf_assert(block && "cannot insert a null entry into the free list");
+ block->next_free = current;
+ block->prev_free = &control->block_null;
+ current->prev_free = block;
+
+ tlsf_assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE)
+ && "block not aligned properly");
+ /*
+ ** Insert the new block at the head of the list, and mark the first-
+ ** and second-level bitmaps appropriately.
+ */
+ control->blocks[fl][sl] = block;
+ control->fl_bitmap |= (1U << fl);
+ control->sl_bitmap[fl] |= (1U << sl);
+}
+
+/* Remove a given block from the free list. */
+static void block_remove(control_t* control, block_header_t* block)
+{
+ int fl, sl;
+ mapping_insert(block_size(block), &fl, &sl);
+ remove_free_block(control, block, fl, sl);
+}
+
+/* Insert a given block into the free list. */
+static void block_insert(control_t* control, block_header_t* block)
+{
+ int fl, sl;
+ mapping_insert(block_size(block), &fl, &sl);
+ insert_free_block(control, block, fl, sl);
+}
+
+static int block_can_split(block_header_t* block, size_t size)
+{
+ return block_size(block) >= sizeof(block_header_t) + size;
+}
+
+/* Split a block into two, the second of which is free. */
+static block_header_t* block_split(block_header_t* block, size_t size)
+{
+ /* Calculate the amount of space left in the remaining block. */
+ block_header_t* remaining =
+ offset_to_block(block_to_ptr(block), size - block_header_overhead);
+
+ const size_t remain_size = block_size(block) - (size + block_header_overhead);
+
+ tlsf_assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE)
+ && "remaining block not aligned properly");
+
+ tlsf_assert(block_size(block) == remain_size + size + block_header_overhead);
+ block_set_size(remaining, remain_size);
+ tlsf_assert(block_size(remaining) >= block_size_min && "block split with invalid size");
+
+ block_set_size(block, size);
+ block_mark_as_free(remaining);
+
+ return remaining;
+}
+
+/* Absorb a free block's storage into an adjacent previous free block. */
+static block_header_t* block_absorb(block_header_t* prev, block_header_t* block)
+{
+ tlsf_assert(!block_is_last(prev) && "previous block can't be last");
+ /* Note: Leaves flags untouched. */
+ prev->size += block_size(block) + block_header_overhead;
+ block_link_next(prev);
+ return prev;
+}
+
+/* Merge a just-freed block with an adjacent previous free block. */
+static block_header_t* block_merge_prev(control_t* control, block_header_t* block)
+{
+ if (block_is_prev_free(block))
+ {
+ block_header_t* prev = block_prev(block);
+ tlsf_assert(prev && "prev physical block can't be null");
+ tlsf_assert(block_is_free(prev) && "prev block is not free though marked as such");
+ block_remove(control, prev);
+ block = block_absorb(prev, block);
+ }
+
+ return block;
+}
+
+/* Merge a just-freed block with an adjacent free block. */
+static block_header_t* block_merge_next(control_t* control, block_header_t* block)
+{
+ block_header_t* next = block_next(block);
+ tlsf_assert(next && "next physical block can't be null");
+
+ if (block_is_free(next))
+ {
+ tlsf_assert(!block_is_last(block) && "previous block can't be last");
+ block_remove(control, next);
+ block = block_absorb(block, next);
+ }
+
+ return block;
+}
+
+/* Trim any trailing block space off the end of a block, return to pool. */
+static void block_trim_free(control_t* control, block_header_t* block, size_t size)
+{
+ tlsf_assert(block_is_free(block) && "block must be free");
+ if (block_can_split(block, size))
+ {
+ block_header_t* remaining_block = block_split(block, size);
+ block_link_next(block);
+ block_set_prev_free(remaining_block);
+ block_insert(control, remaining_block);
+ }
+}
+
+/* Trim any trailing block space off the end of a used block, return to pool. */
+static void block_trim_used(control_t* control, block_header_t* block, size_t size)
+{
+ tlsf_assert(!block_is_free(block) && "block must be used");
+ if (block_can_split(block, size))
+ {
+ /* If the next block is free, we must coalesce. */
+ block_header_t* remaining_block = block_split(block, size);
+ block_set_prev_used(remaining_block);
+
+ remaining_block = block_merge_next(control, remaining_block);
+ block_insert(control, remaining_block);
+ }
+}
+
+static block_header_t* block_trim_free_leading(control_t* control, block_header_t* block, size_t size)
+{
+ block_header_t* remaining_block = block;
+ if (block_can_split(block, size))
+ {
+ /* We want the 2nd block. */
+ remaining_block = block_split(block, size - block_header_overhead);
+ block_set_prev_free(remaining_block);
+
+ block_link_next(block);
+ block_insert(control, block);
+ }
+
+ return remaining_block;
+}
+
+static block_header_t* block_locate_free(control_t* control, size_t size)
+{
+ int fl = 0, sl = 0;
+ block_header_t* block = 0;
+
+ if (size)
+ {
+ mapping_search(size, &fl, &sl);
+
+ /*
+ ** mapping_search can futz with the size, so for excessively large sizes it can sometimes wind up
+ ** with indices that are off the end of the block array.
+ ** So, we protect against that here, since this is the only callsite of mapping_search.
+ ** Note that we don't need to check sl, since it comes from a modulo operation that guarantees it's always in range.
+ */
+ if (fl < FL_INDEX_COUNT)
+ {
+ block = search_suitable_block(control, &fl, &sl);
+ }
+ }
+
+ if (block)
+ {
+ tlsf_assert(block_size(block) >= size);
+ remove_free_block(control, block, fl, sl);
+ }
+
+ return block;
+}
+
+static void* block_prepare_used(control_t* control, block_header_t* block, size_t size)
+{
+ void* p = 0;
+ if (block)
+ {
+ tlsf_assert(size && "size must be non-zero");
+ block_trim_free(control, block, size);
+ block_mark_as_used(block);
+ p = block_to_ptr(block);
+ }
+ return p;
+}
+
+/* Clear structure and point all empty lists at the null block. */
+static void control_construct(control_t* control)
+{
+ int i, j;
+
+ control->block_null.next_free = &control->block_null;
+ control->block_null.prev_free = &control->block_null;
+
+ control->fl_bitmap = 0;
+ for (i = 0; i < FL_INDEX_COUNT; ++i)
+ {
+ control->sl_bitmap[i] = 0;
+ for (j = 0; j < SL_INDEX_COUNT; ++j)
+ {
+ control->blocks[i][j] = &control->block_null;
+ }
+ }
+}
+
+/*
+** Debugging utilities.
+*/
+
+typedef struct integrity_t
+{
+ int prev_status;
+ int status;
+} integrity_t;
+
+#define tlsf_insist(x) { tlsf_assert(x); if (!(x)) { status--; } }
+
+static void integrity_walker(void* ptr, size_t size, int used, void* user)
+{
+ block_header_t* block = block_from_ptr(ptr);
+ integrity_t* integ = tlsf_cast(integrity_t*, user);
+ const int this_prev_status = block_is_prev_free(block) ? 1 : 0;
+ const int this_status = block_is_free(block) ? 1 : 0;
+ const size_t this_block_size = block_size(block);
+
+ int status = 0;
+ (void)used;
+ tlsf_insist(integ->prev_status == this_prev_status && "prev status incorrect");
+ tlsf_insist(size == this_block_size && "block size incorrect");
+
+ integ->prev_status = this_status;
+ integ->status += status;
+}
+
+int tlsf_check(tlsf_t tlsf)
+{
+ int i, j;
+
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ int status = 0;
+
+ /* Check that the free lists and bitmaps are accurate. */
+ for (i = 0; i < FL_INDEX_COUNT; ++i)
+ {
+ for (j = 0; j < SL_INDEX_COUNT; ++j)
+ {
+ const int fl_map = control->fl_bitmap & (1U << i);
+ const int sl_list = control->sl_bitmap[i];
+ const int sl_map = sl_list & (1U << j);
+ const block_header_t* block = control->blocks[i][j];
+
+ /* Check that first- and second-level lists agree. */
+ if (!fl_map)
+ {
+ tlsf_insist(!sl_map && "second-level map must be null");
+ }
+
+ if (!sl_map)
+ {
+ tlsf_insist(block == &control->block_null && "block list must be null");
+ continue;
+ }
+
+ /* Check that there is at least one free block. */
+ tlsf_insist(sl_list && "no free blocks in second-level map");
+ tlsf_insist(block != &control->block_null && "block should not be null");
+
+ while (block != &control->block_null)
+ {
+ int fli, sli;
+ tlsf_insist(block_is_free(block) && "block should be free");
+ tlsf_insist(!block_is_prev_free(block) && "blocks should have coalesced");
+ tlsf_insist(!block_is_free(block_next(block)) && "blocks should have coalesced");
+ tlsf_insist(block_is_prev_free(block_next(block)) && "block should be free");
+ tlsf_insist(block_size(block) >= block_size_min && "block not minimum size");
+
+ mapping_insert(block_size(block), &fli, &sli);
+ tlsf_insist(fli == i && sli == j && "block size indexed in wrong list");
+ block = block->next_free;
+ }
+ }
+ }
+
+ return status;
+}
+
+#undef tlsf_insist
+
+static void default_walker(void* ptr, size_t size, int used, void* user)
+{
+ (void)user;
+ printf("\t%p %s size: %x (%p)\n", ptr, used ? "used" : "free", (unsigned int)size, block_from_ptr(ptr));
+}
+
+void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user)
+{
+ tlsf_walker pool_walker = walker ? walker : default_walker;
+ block_header_t* block =
+ offset_to_block(pool, -(int)block_header_overhead);
+
+ while (block && !block_is_last(block))
+ {
+ pool_walker(
+ block_to_ptr(block),
+ block_size(block),
+ !block_is_free(block),
+ user);
+ block = block_next(block);
+ }
+}
+
+size_t tlsf_block_size(void* ptr)
+{
+ size_t size = 0;
+ if (ptr)
+ {
+ const block_header_t* block = block_from_ptr(ptr);
+ size = block_size(block);
+ }
+ return size;
+}
+
+int tlsf_check_pool(pool_t pool)
+{
+ /* Check that the blocks are physically correct. */
+ integrity_t integ = { 0, 0 };
+ tlsf_walk_pool(pool, integrity_walker, &integ);
+
+ return integ.status;
+}
+
+/*
+** Size of the TLSF structures in a given memory block passed to
+** tlsf_create, equal to the size of a control_t
+*/
+size_t tlsf_size(void)
+{
+ return sizeof(control_t);
+}
+
+size_t tlsf_align_size(void)
+{
+ return ALIGN_SIZE;
+}
+
+size_t tlsf_block_size_min(void)
+{
+ return block_size_min;
+}
+
+size_t tlsf_block_size_max(void)
+{
+ return block_size_max;
+}
+
+/*
+** Overhead of the TLSF structures in a given memory block passed to
+** tlsf_add_pool, equal to the overhead of a free block and the
+** sentinel block.
+*/
+size_t tlsf_pool_overhead(void)
+{
+ return 2 * block_header_overhead;
+}
+
+size_t tlsf_alloc_overhead(void)
+{
+ return block_header_overhead;
+}
+
+pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes)
+{
+ block_header_t* block;
+ block_header_t* next;
+
+ const size_t pool_overhead = tlsf_pool_overhead();
+ const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
+
+ if (((ptrdiff_t)mem % ALIGN_SIZE) != 0)
+ {
+ printf("tlsf_add_pool: Memory must be aligned by %u bytes.\n",
+ (unsigned int)ALIGN_SIZE);
+ return 0;
+ }
+
+ if (pool_bytes < block_size_min || pool_bytes > block_size_max)
+ {
+#if defined (TLSF_64BIT)
+ printf("tlsf_add_pool: Memory size must be between 0x%x and 0x%x00 bytes.\n",
+ (unsigned int)(pool_overhead + block_size_min),
+ (unsigned int)((pool_overhead + block_size_max) / 256));
+#else
+ printf("tlsf_add_pool: Memory size must be between %u and %u bytes.\n",
+ (unsigned int)(pool_overhead + block_size_min),
+ (unsigned int)(pool_overhead + block_size_max));
+#endif
+ return 0;
+ }
+
+ /*
+ ** Create the main free block. Offset the start of the block slightly
+ ** so that the prev_phys_block field falls outside of the pool -
+ ** it will never be used.
+ */
+ block = offset_to_block(mem, -(tlsfptr_t)block_header_overhead);
+ block_set_size(block, pool_bytes);
+ block_set_free(block);
+ block_set_prev_used(block);
+ block_insert(tlsf_cast(control_t*, tlsf), block);
+
+ /* Split the block to create a zero-size sentinel block. */
+ next = block_link_next(block);
+ block_set_size(next, 0);
+ block_set_used(next);
+ block_set_prev_free(next);
+
+ return mem;
+}
+
+void tlsf_remove_pool(tlsf_t tlsf, pool_t pool)
+{
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ block_header_t* block = offset_to_block(pool, -(int)block_header_overhead);
+
+ int fl = 0, sl = 0;
+
+ tlsf_assert(block_is_free(block) && "block should be free");
+ tlsf_assert(!block_is_free(block_next(block)) && "next block should not be free");
+ tlsf_assert(block_size(block_next(block)) == 0 && "next block size should be zero");
+
+ mapping_insert(block_size(block), &fl, &sl);
+ remove_free_block(control, block, fl, sl);
+}
+
+/*
+** TLSF main interface.
+*/
+
+#if _DEBUG
+int test_ffs_fls()
+{
+ /* Verify ffs/fls work properly. */
+ int rv = 0;
+ rv += (tlsf_ffs(0) == -1) ? 0 : 0x1;
+ rv += (tlsf_fls(0) == -1) ? 0 : 0x2;
+ rv += (tlsf_ffs(1) == 0) ? 0 : 0x4;
+ rv += (tlsf_fls(1) == 0) ? 0 : 0x8;
+ rv += (tlsf_ffs(0x80000000) == 31) ? 0 : 0x10;
+ rv += (tlsf_ffs(0x80008000) == 15) ? 0 : 0x20;
+ rv += (tlsf_fls(0x80000008) == 31) ? 0 : 0x40;
+ rv += (tlsf_fls(0x7FFFFFFF) == 30) ? 0 : 0x80;
+
+#if defined (TLSF_64BIT)
+ rv += (tlsf_fls_sizet(0x80000000) == 31) ? 0 : 0x100;
+ rv += (tlsf_fls_sizet(0x100000000) == 32) ? 0 : 0x200;
+ rv += (tlsf_fls_sizet(0xffffffffffffffff) == 63) ? 0 : 0x400;
+#endif
+
+ if (rv)
+ {
+ printf("test_ffs_fls: %x ffs/fls tests failed.\n", rv);
+ }
+ return rv;
+}
+#endif
+
+tlsf_t tlsf_create(void* mem)
+{
+#if _DEBUG
+ if (test_ffs_fls())
+ {
+ return 0;
+ }
+#endif
+
+ if (((tlsfptr_t)mem % ALIGN_SIZE) != 0)
+ {
+ printf("tlsf_create: Memory must be aligned to %u bytes.\n",
+ (unsigned int)ALIGN_SIZE);
+ return 0;
+ }
+
+ control_construct(tlsf_cast(control_t*, mem));
+
+ return tlsf_cast(tlsf_t, mem);
+}
+
+tlsf_t tlsf_create_with_pool(void* mem, size_t bytes)
+{
+ tlsf_t tlsf = tlsf_create(mem);
+ tlsf_add_pool(tlsf, (char*)mem + tlsf_size(), bytes - tlsf_size());
+ return tlsf;
+}
+
+void tlsf_destroy(tlsf_t tlsf)
+{
+ /* Nothing to do. */
+ (void)tlsf;
+}
+
+pool_t tlsf_get_pool(tlsf_t tlsf)
+{
+ return tlsf_cast(pool_t, (char*)tlsf + tlsf_size());
+}
+
+void* tlsf_malloc(tlsf_t tlsf, size_t size)
+{
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ const size_t adjust = adjust_request_size(size, ALIGN_SIZE);
+ block_header_t* block = block_locate_free(control, adjust);
+ return block_prepare_used(control, block, adjust);
+}
+
+void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size)
+{
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ const size_t adjust = adjust_request_size(size, ALIGN_SIZE);
+
+ /*
+ ** We must allocate an additional minimum block size bytes so that if
+ ** our free block will leave an alignment gap which is smaller, we can
+ ** trim a leading free block and release it back to the pool. We must
+ ** do this because the previous physical block is in use, therefore
+ ** the prev_phys_block field is not valid, and we can't simply adjust
+ ** the size of that block.
+ */
+ const size_t gap_minimum = sizeof(block_header_t);
+ const size_t size_with_gap = adjust_request_size(adjust + align + gap_minimum, align);
+
+ /*
+ ** If alignment is less than or equals base alignment, we're done.
+ ** If we requested 0 bytes, return null, as tlsf_malloc(0) does.
+ */
+ const size_t aligned_size = (adjust && align > ALIGN_SIZE) ? size_with_gap : adjust;
+
+ block_header_t* block = block_locate_free(control, aligned_size);
+
+ /* This can't be a static assert. */
+ tlsf_assert(sizeof(block_header_t) == block_size_min + block_header_overhead);
+
+ if (block)
+ {
+ void* ptr = block_to_ptr(block);
+ void* aligned = align_ptr(ptr, align);
+ size_t gap = tlsf_cast(size_t,
+ tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr));
+
+ /* If gap size is too small, offset to next aligned boundary. */
+ if (gap && gap < gap_minimum)
+ {
+ const size_t gap_remain = gap_minimum - gap;
+ const size_t offset = tlsf_max(gap_remain, align);
+ const void* next_aligned = tlsf_cast(void*,
+ tlsf_cast(tlsfptr_t, aligned) + offset);
+
+ aligned = align_ptr(next_aligned, align);
+ gap = tlsf_cast(size_t,
+ tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr));
+ }
+
+ if (gap)
+ {
+ tlsf_assert(gap >= gap_minimum && "gap size too small");
+ block = block_trim_free_leading(control, block, gap);
+ }
+ }
+
+ return block_prepare_used(control, block, adjust);
+}
+
+void tlsf_free(tlsf_t tlsf, void* ptr)
+{
+ /* Don't attempt to free a NULL pointer. */
+ if (ptr)
+ {
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ block_header_t* block = block_from_ptr(ptr);
+ tlsf_assert(!block_is_free(block) && "block already marked as free");
+ block_mark_as_free(block);
+ block = block_merge_prev(control, block);
+ block = block_merge_next(control, block);
+ block_insert(control, block);
+ }
+}
+
+/*
+** The TLSF block information provides us with enough information to
+** provide a reasonably intelligent implementation of realloc, growing or
+** shrinking the currently allocated block as required.
+**
+** This routine handles the somewhat esoteric edge cases of realloc:
+** - a non-zero size with a null pointer will behave like malloc
+** - a zero size with a non-null pointer will behave like free
+** - a request that cannot be satisfied will leave the original buffer
+** untouched
+** - an extended buffer size will leave the newly-allocated area with
+** contents undefined
+*/
+void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size)
+{
+ control_t* control = tlsf_cast(control_t*, tlsf);
+ void* p = 0;
+
+ /* Zero-size requests are treated as free. */
+ if (ptr && size == 0)
+ {
+ tlsf_free(tlsf, ptr);
+ }
+ /* Requests with NULL pointers are treated as malloc. */
+ else if (!ptr)
+ {
+ p = tlsf_malloc(tlsf, size);
+ }
+ else
+ {
+ block_header_t* block = block_from_ptr(ptr);
+ block_header_t* next = block_next(block);
+
+ const size_t cursize = block_size(block);
+ const size_t combined = cursize + block_size(next) + block_header_overhead;
+ const size_t adjust = adjust_request_size(size, ALIGN_SIZE);
+
+ tlsf_assert(!block_is_free(block) && "block already marked as free");
+
+ /*
+ ** If the next block is used, or when combined with the current
+ ** block, does not offer enough space, we must reallocate and copy.
+ */
+ if (adjust > cursize && (!block_is_free(next) || adjust > combined))
+ {
+ p = tlsf_malloc(tlsf, size);
+ if (p)
+ {
+ const size_t minsize = tlsf_min(cursize, size);
+ memcpy(p, ptr, minsize);
+ tlsf_free(tlsf, ptr);
+ }
+ }
+ else
+ {
+ /* Do we need to expand to the next block? */
+ if (adjust > cursize)
+ {
+ block_merge_next(control, block);
+ block_mark_as_used(block);
+ }
+
+ /* Trim the resulting block and return the original pointer. */
+ block_trim_used(control, block, adjust);
+ p = ptr;
+ }
+ }
+
+ return p;
+}
\ No newline at end of file
diff --git a/miniwin/src/d3drm/backends/gxm/tlsf.h b/miniwin/src/d3drm/backends/gxm/tlsf.h
new file mode 100644
index 00000000..9f890175
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/tlsf.h
@@ -0,0 +1,91 @@
+#ifndef INCLUDED_tlsf
+#define INCLUDED_tlsf
+
+/*
+** Two Level Segregated Fit memory allocator, version 3.1.
+** Written by Matthew Conte
+** http://tlsf.baisoku.org
+**
+** Based on the original documentation by Miguel Masmano:
+** http://www.gii.upv.es/tlsf/main/docs
+**
+** This implementation was written to the specification
+** of the document, therefore no GPL restrictions apply.
+**
+** Copyright (c) 2006-2016, Matthew Conte
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of the copyright holder nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
+** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+ /* tlsf_t: a TLSF structure. Can contain 1 to N pools. */
+ /* pool_t: a block of memory that TLSF can manage. */
+ typedef void* tlsf_t;
+ typedef void* pool_t;
+
+ /* Create/destroy a memory pool. */
+ tlsf_t tlsf_create(void* mem);
+ tlsf_t tlsf_create_with_pool(void* mem, size_t bytes);
+ void tlsf_destroy(tlsf_t tlsf);
+ pool_t tlsf_get_pool(tlsf_t tlsf);
+
+ /* Add/remove memory pools. */
+ pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes);
+ void tlsf_remove_pool(tlsf_t tlsf, pool_t pool);
+
+ /* malloc/memalign/realloc/free replacements. */
+ void* tlsf_malloc(tlsf_t tlsf, size_t bytes);
+ void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t bytes);
+ void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size);
+ void tlsf_free(tlsf_t tlsf, void* ptr);
+
+ /* Returns internal block size, not original request size */
+ size_t tlsf_block_size(void* ptr);
+
+ /* Overheads/limits of internal structures. */
+ size_t tlsf_size(void);
+ size_t tlsf_align_size(void);
+ size_t tlsf_block_size_min(void);
+ size_t tlsf_block_size_max(void);
+ size_t tlsf_pool_overhead(void);
+ size_t tlsf_alloc_overhead(void);
+
+ /* Debugging. */
+ typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
+ void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user);
+ /* Returns nonzero if any internal consistency check fails. */
+ int tlsf_check(tlsf_t tlsf);
+ int tlsf_check_pool(pool_t pool);
+
+#if defined(__cplusplus)
+};
+#endif
+
+#endif
diff --git a/miniwin/src/d3drm/backends/gxm/utils.h b/miniwin/src/d3drm/backends/gxm/utils.h
new file mode 100644
index 00000000..18fe5dc1
--- /dev/null
+++ b/miniwin/src/d3drm/backends/gxm/utils.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include
+#include
+#include
+
+#ifdef DEBUG
+#define DEBUG_ONLY_PRINTF(...) sceClibPrintf(__VA_ARGS__)
+#else
+#define DEBUG_ONLY_PRINTF(...)
+#endif
+
+#define SCE_ERR(func, ...) \
+ ({ \
+ DEBUG_ONLY_PRINTF("%s\n", #func); \
+ int __sce_err_ret_val = func(__VA_ARGS__); \
+ if (__sce_err_ret_val < 0) { \
+ sceClibPrintf(#func " error: 0x%x\n", __sce_err_ret_val); \
+ } \
+ __sce_err_ret_val; \
+ })
+
+#define ALIGN(x, a) (((x) + ((a) -1)) & ~((a) -1))
+
+#define ALIGNMENT(n, a) (((a) - ((n) % (a))) % (a))
+
+#define SET_UNIFORM(buffer, param, value) \
+ do { \
+ size_t __offset = sceGxmProgramParameterGetResourceIndex(param); \
+ void* __dst = (uint8_t*) (buffer) + (__offset * sizeof(uint32_t)); \
+ memcpy(__dst, reinterpret_cast(&(value)), sizeof(value)); \
+ } while (0)
+
+#define GET_SHADER_PARAM(var, gxp, name, ret) \
+ const SceGxmProgramParameter* var = sceGxmProgramFindParameterByName(gxp, name); \
+ if (!var) { \
+ SDL_Log("Failed to find param %s", name); \
+ return ret; \
+ }
+
+static void printMatrix4x4(const float mat[4][4])
+{
+ sceClibPrintf("mat4{\n");
+ for (int i = 0; i < 4; i++) {
+ sceClibPrintf("%f %f %f %f\n", mat[i][0], mat[i][1], mat[i][2], mat[i][3]);
+ }
+ sceClibPrintf("}\n");
+}
diff --git a/miniwin/src/d3drm/d3drmrenderer.cpp b/miniwin/src/d3drm/d3drmrenderer.cpp
index f1e1fb8b..b30c5b23 100644
--- a/miniwin/src/d3drm/d3drmrenderer.cpp
+++ b/miniwin/src/d3drm/d3drmrenderer.cpp
@@ -20,6 +20,9 @@
#ifdef USE_SOFTWARE_RENDER
#include "d3drmrenderer_software.h"
#endif
+#ifdef USE_GXM
+#include "d3drmrenderer_gxm.h"
+#endif
Direct3DRMRenderer* CreateDirect3DRMRenderer(
const IDirect3DMiniwin* d3d,
@@ -66,6 +69,11 @@ Direct3DRMRenderer* CreateDirect3DRMRenderer(
if (SDL_memcmp(guid, &DirectX9_GUID, sizeof(GUID)) == 0) {
return DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight);
}
+#endif
+#ifdef USE_GXM
+ if (SDL_memcmp(guid, &GXM_GUID, sizeof(GUID)) == 0) {
+ return GXMRenderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight, d3d->GetMSAASamples());
+ }
#endif
return nullptr;
}
@@ -93,4 +101,7 @@ void Direct3DRMRenderer_EnumDevices(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICE
#ifdef USE_SOFTWARE_RENDER
Direct3DRMSoftware_EnumDevice(cb, ctx);
#endif
+#ifdef USE_GXM
+ GXMRenderer_EnumDevice(cb, ctx);
+#endif
}
diff --git a/miniwin/src/internal/d3drmrenderer_gxm.h b/miniwin/src/internal/d3drmrenderer_gxm.h
new file mode 100644
index 00000000..1a8fe16c
--- /dev/null
+++ b/miniwin/src/internal/d3drmrenderer_gxm.h
@@ -0,0 +1,181 @@
+#pragma once
+
+#include "../d3drm/backends/gxm/gxm_context.h"
+#include "d3drmrenderer.h"
+#include "d3drmtexture_impl.h"
+#include "ddpalette_impl.h"
+#include "ddraw_impl.h"
+
+#include
+#include
+#include
+#include
+#include
+
+DEFINE_GUID(GXM_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x58, 0x4D);
+
+#define GXM_VERTEX_BUFFER_COUNT 2
+#define GXM_FRAGMENT_BUFFER_COUNT 3
+
+#define GXM_WITH_RAZOR DEBUG
+
+struct GXMTextureCacheEntry {
+ IDirect3DRMTexture* texture;
+ Uint32 version;
+ SceGxmTexture gxmTexture;
+ SceGxmNotification* notification; // latest frame it was used in
+};
+
+struct GXMMeshCacheEntry {
+ const MeshGroup* meshGroup;
+ int version;
+ bool flat;
+
+ void* meshData;
+ void* vertexBuffer;
+ void* indexBuffer;
+ uint16_t indexCount;
+};
+
+struct SceneLightGXM {
+ float color[4];
+ float vec[4];
+ float isDirectional;
+ float _align;
+};
+
+struct GXMSceneLightUniform {
+ SceneLightGXM lights[2];
+ float ambientLight[3];
+};
+
+class GXMRenderer : public Direct3DRMRenderer {
+public:
+ static Direct3DRMRenderer* Create(DWORD width, DWORD height, DWORD msaaSamples);
+ GXMRenderer(DWORD width, DWORD height, SceGxmMultisampleMode msaaMode);
+ ~GXMRenderer() 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, float scaleX, float scaleY) 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, FColor color) override;
+ void Download(SDL_Surface* target) override;
+ void SetDither(bool dither) override;
+
+ void DeferredDelete(int index);
+
+private:
+ void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture);
+ void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh);
+
+ GXMMeshCacheEntry GXMUploadMesh(const MeshGroup& meshGroup);
+
+ void StartScene();
+ const SceGxmTexture* UseTexture(GXMTextureCacheEntry& texture);
+
+ inline GXMVertex2D* QuadVerticesBuffer()
+ {
+ GXMVertex2D* verts = &this->quadVertices[this->currentVertexBufferIndex][this->quadsUsed * 4];
+ this->quadsUsed += 1;
+ if (this->quadsUsed >= 50) {
+ SDL_Log("QuadVerticesBuffer overflow");
+ this->quadsUsed = 0; // declare bankruptcy
+ }
+ return verts;
+ }
+
+ inline GXMSceneLightUniform* LightsBuffer() { return this->lights[this->currentFragmentBufferIndex]; }
+
+ std::vector m_textures;
+ std::vector m_meshes;
+ D3DRMMATRIX4D m_projection;
+ std::vector m_lights;
+ std::vector m_textures_delete[GXM_FRAGMENT_BUFFER_COUNT];
+ std::vector m_buffers_delete[GXM_FRAGMENT_BUFFER_COUNT];
+
+ bool transparencyEnabled = false;
+
+ // shader
+ SceGxmShaderPatcherId mainVertexProgramId;
+ SceGxmShaderPatcherId mainColorFragmentProgramId;
+ SceGxmShaderPatcherId mainTextureFragmentProgramId;
+
+ SceGxmVertexProgram* mainVertexProgram;
+ SceGxmFragmentProgram* opaqueColorFragmentProgram;
+ SceGxmFragmentProgram* blendedColorFragmentProgram;
+ SceGxmFragmentProgram* opaqueTextureFragmentProgram;
+ SceGxmFragmentProgram* blendedTextureFragmentProgram;
+
+ // main shader vertex uniforms
+ const SceGxmProgramParameter* uModelViewMatrix;
+ const SceGxmProgramParameter* uNormalMatrix;
+ const SceGxmProgramParameter* uProjectionMatrix;
+
+ // main shader fragment uniforms
+ const SceGxmProgramParameter* uShininess;
+ const SceGxmProgramParameter* uColor;
+ const SceGxmProgramParameter* uLights;
+ const SceGxmProgramParameter* uAmbientLight;
+
+ // uniforms / quad meshes
+ GXMSceneLightUniform* lights[GXM_FRAGMENT_BUFFER_COUNT];
+ GXMVertex2D* quadVertices[GXM_VERTEX_BUFFER_COUNT];
+ uint16_t* quadIndices;
+ int quadsUsed = 0;
+ bool cleared = false;
+
+ SceGxmNotification vertexNotifications[GXM_VERTEX_BUFFER_COUNT];
+ SceGxmNotification fragmentNotifications[GXM_FRAGMENT_BUFFER_COUNT];
+ int currentFragmentBufferIndex = 0;
+ int currentVertexBufferIndex = 0;
+
+#ifdef GXM_WITH_RAZOR
+ bool last_dpad_up;
+ bool last_dpad_down;
+ bool last_dpad_left;
+ bool last_dpad_right;
+#endif
+
+ bool m_initialized = false;
+};
+
+int gxm_library_init();
+
+inline static void GXMRenderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
+{
+ D3DDEVICEDESC halDesc = {};
+ halDesc.dcmColorModel = D3DCOLORMODEL::RGB;
+ halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
+ halDesc.dwDeviceZBufferBitDepth = DDBD_16;
+ halDesc.dwDeviceZBufferBitDepth |= DDBD_32;
+ halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
+ halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
+ halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
+
+ D3DDEVICEDESC helDesc = {};
+ helDesc.dwDeviceRenderBitDepth = DDBD_32;
+
+ int ret = gxm_library_init();
+ if (ret < 0) {
+ SDL_Log("gxm_library_init failed: %08x", ret);
+ return;
+ }
+
+ EnumDevice(cb, ctx, "GXM HAL", &halDesc, &helDesc, GXM_GUID);
+}
diff --git a/packaging/vita/sce_sys/icon0.png b/packaging/vita/sce_sys/icon0.png
new file mode 100644
index 0000000000000000000000000000000000000000..fe68bd7c0d44174f18546d91d0355ba2da83765c
GIT binary patch
literal 1671
zcmV;226*|2P)C00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj5Gf+%aMgO?~%Mu{+jtHbHB>MXL|L2_PnxL?rGC)8;
z0C+8NLll$7rh<{E
z-AP12RCr$Pn(2D-It&I|vOrnV-v8Bp8NU*;odBf^=A5VVqaIyC}%%p-0mZ}B#E*5cx2M7?xBCg;9T{n)YU=Pzg0mLMbo|hr`=jH)91xL`?~9(Oe+)nF)nqI36W%
zI%)9%%QFO+rqhXb`#qDUQXr=PDzv|enl=;grr-(;DVO1XKaS;X*9BOX{Nz&Q#uN&Q
z$+RePN^*_n0;$&-S8!rzCE@~?AR_&DyS_gjDWS(zQol0~7l>qjDdh?*J3e>2Y3h59
zCD3*IeSYa}x6<>7Y-6X>G^NVxlh9Zo(zEgSttVF?&2Gm9c|5v+wrOvz)YN&zt{aAF
zIvztYC0nDhK%{?tB_x}w&T<6_9A`MD=KWo*IoIU~5fPaZdadwO0vcupPv^OUmXyL$
zt|^!%HXmQ6b-bFCu%@v<*_@4+^bDxB)m%X^L6rJ&u#8tjzl!DpR#PyJ+pQhWlnPyk
zoC|=rC7Rm#gshR0=2&^b0|l-Rn1?A9n3@pq8+9H{l3beQ6hTTGJh=diQQ>n19y~bb
zQ`G7CnLnK5q_jbEf#o9PA{>Sox_TOvkTV_C>DkJb1MkqU!fMVm7f=eq@-WIU`=CmJ
za~<;uM2X>NdtScG1}SNdm1jIuz&n<(wdl6Q?T2#~BM;c#BLvDQo6X@cP5A_JL6)S%
zPICbj(JUV-Ubd8;7cNRPDN0N_7a#`HqFNGj9P!o!=zB&*%>}e&!x6PCJ=XxSkS==pXeCUv$}Fg>{DJ0))69
z>pEivcVeA|mEDDPow0&DvCe;ht%~_#d4JC@nB~P2%Pk*5FS)eb)+naH%32HPCK{WL
z9>W42QX++QO}LhCQ=h@Q3KcaJPypX{x$&@}DB0;G5x=#lRJ|BR4W=tdfXyqN&V(}h
zJgP^8GfyI*TaUVO`{ev|;-4K@5EQY>#siJj&GO9dWC^DpayytNB(%XI}20$RM!FW^I2qA%le
zXvvn#!W;${(2(Z}DEU{o)cPWHXj@@51x*F?B~?CXG5l;POp*i$uli76;Q14lKhW2t
zlqFVkrn!KIm9O4O1+?038CKs;T_9dNLgDpwnh;nnV@N4WtmaH}fg0Luw`E8Pe0&@Z
zWtkKtkpHRAq0IuTy9jL>3eaM+@f)^>lt8{^gDWtJ$7A376b30_jn!R%77YccCnPWI
z(-ea$0UqTJhkT%2K^sykDo6=ytZo(Re_cQwa(siyYy1A=Lq{M1sMCbP66#iHE|5
z=|dpu_0Jy5+jCAzwT`uDCr~!dOBiG&(el(3Ng$Ww9~C5^(xn0=7ilgKlTfAypO*X;
z-36j#9R1eJYZ{-WLP~NCYd6DL0|9TEA3OXr&YA$peA}XxJ}K3wEAV-f<^nYJPjz>e
ze}>5g`hNN7MX5F@DJbqunhQ9!fIp7eX~G3mxAqE>Ps5!w7eFDG&=(f5#Mp{Tfv8*Z
z-xL7rzXF5n0_gop`X>-rE=Hln6#|#}R&r=AP%lQIe`&ZaU?ILoa{*xy4Lzn`6ZnCl
zFR{2r1A(=PSQoGkKTUH1;Trz47WnajuW2q2pg=mQaQwjJ&|Kh;{{=5E{{X7fTy@X)
RAIbm#002ovPDHLkV1m$Y>OlYi
literal 0
HcmV?d00001
diff --git a/packaging/vita/sce_sys/livearea/contents/bg.png b/packaging/vita/sce_sys/livearea/contents/bg.png
new file mode 100644
index 0000000000000000000000000000000000000000..5e742ac37ae645df3ce2865b2df8f8460002b4f7
GIT binary patch
literal 47338
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