From 29550ea04571fc10ce50aba022150165a1d1cd33 Mon Sep 17 00:00:00 2001 From: The Gang Date: Thu, 26 Dec 2024 11:10:17 +0200 Subject: [PATCH] Dreamcast Changes --- .gitignore | 29 +- .gitlab-ci.yml | 151 + .gitmodules | 7 +- .vscode/c_cpp_properties.json | 108 +- .vscode/launch.json | 108 + .vscode/settings.json | 111 +- README.md | 203 +- dreamcast/GTA3SF8.b | Bin 0 -> 40342 bytes dreamcast/Makefile | 489 + dreamcast/analyze-profile.cpp | 569 + dreamcast/aud2adpcm.c | 585 + dreamcast/common.mk | 371 + dreamcast/extract-sfx.cpp | 140 + dreamcast/gen-mesh-variants.py | 114 + dreamcast/gta3files.mk | 178 + dreamcast/imgtool.cpp | 147 + dreamcast/ip.txt | 9 + dreamcast/modlist.mk | 3135 +++++ dreamcast/mp3list.mk | 71 + dreamcast/pack-sfx.cpp | 224 + dreamcast/pvrtex/.gitignore | 3 + dreamcast/pvrtex/Makefile | 42 + dreamcast/pvrtex/README | 495 + dreamcast/pvrtex/README.md | 1 + dreamcast/pvrtex/avstring.c | 463 + dreamcast/pvrtex/bprint.c | 332 + dreamcast/pvrtex/compat/va_copy.h | 34 + dreamcast/pvrtex/config.h | 0 dreamcast/pvrtex/crc.c | 415 + dreamcast/pvrtex/dither.cpp | 493 + dreamcast/pvrtex/elbg.c | 530 + dreamcast/pvrtex/elbg.h | 59 + dreamcast/pvrtex/file_common.c | 74 + dreamcast/pvrtex/file_common.h | 22 + dreamcast/pvrtex/file_dctex.c | 88 + dreamcast/pvrtex/file_dctex.h | 560 + dreamcast/pvrtex/file_pvr.c | 101 + dreamcast/pvrtex/file_pvr.h | 21 + dreamcast/pvrtex/file_tex.c | 76 + dreamcast/pvrtex/file_tex.h | 8 + dreamcast/pvrtex/lfg.c | 87 + dreamcast/pvrtex/libavcodec/elbg.h | 59 + dreamcast/pvrtex/libavutil/attributes.h | 173 + .../pvrtex/libavutil/attributes_internal.h | 34 + dreamcast/pvrtex/libavutil/avassert.h | 75 + dreamcast/pvrtex/libavutil/avconfig.h | 6 + dreamcast/pvrtex/libavutil/avstring.h | 429 + dreamcast/pvrtex/libavutil/avutil.h | 371 + dreamcast/pvrtex/libavutil/bprint.h | 251 + dreamcast/pvrtex/libavutil/bswap.h | 111 + dreamcast/pvrtex/libavutil/common.h | 578 + dreamcast/pvrtex/libavutil/crc.h | 102 + dreamcast/pvrtex/libavutil/dynarray.h | 70 + dreamcast/pvrtex/libavutil/error.h | 128 + dreamcast/pvrtex/libavutil/internal.h | 189 + dreamcast/pvrtex/libavutil/intfloat.h | 77 + dreamcast/pvrtex/libavutil/intreadwrite.h | 644 + dreamcast/pvrtex/libavutil/lfg.c | 87 + dreamcast/pvrtex/libavutil/lfg.h | 81 + dreamcast/pvrtex/libavutil/libm.h | 472 + dreamcast/pvrtex/libavutil/log.h | 387 + dreamcast/pvrtex/libavutil/macros.h | 80 + dreamcast/pvrtex/libavutil/mathematics.c | 319 + dreamcast/pvrtex/libavutil/mathematics.h | 300 + dreamcast/pvrtex/libavutil/md5.h | 89 + dreamcast/pvrtex/libavutil/mem.c | 568 + dreamcast/pvrtex/libavutil/mem.h | 609 + dreamcast/pvrtex/libavutil/pixfmt.h | 699 + dreamcast/pvrtex/libavutil/rational.c | 193 + dreamcast/pvrtex/libavutil/rational.h | 221 + dreamcast/pvrtex/libavutil/thread.h | 204 + dreamcast/pvrtex/libavutil/version.h | 128 + dreamcast/pvrtex/log.c | 492 + dreamcast/pvrtex/main.c | 446 + dreamcast/pvrtex/md5.c | 208 + dreamcast/pvrtex/mem.c | 568 + dreamcast/pvrtex/mycommon.c | 32 + dreamcast/pvrtex/mycommon.h | 40 + dreamcast/pvrtex/nvmath.h | 722 + dreamcast/pvrtex/optparse.h | 403 + dreamcast/pvrtex/optparse_impl.c | 2 + dreamcast/pvrtex/pixel.h | 492 + dreamcast/pvrtex/pvr_texture.c | 480 + dreamcast/pvrtex/pvr_texture.h | 115 + dreamcast/pvrtex/pvr_texture_encoder.c | 932 ++ dreamcast/pvrtex/pvr_texture_encoder.h | 226 + dreamcast/pvrtex/readme_unformatted.txt | 319 + dreamcast/pvrtex/stb_image.h | 7897 +++++++++++ dreamcast/pvrtex/stb_image_impl.c | 3 + dreamcast/pvrtex/stb_image_resize.h | 2634 ++++ dreamcast/pvrtex/stb_image_resize_impl.c | 3 + dreamcast/pvrtex/stb_image_write.h | 1724 +++ dreamcast/pvrtex/stb_image_write_impl.c | 3 + dreamcast/pvrtex/tddither.c | 231 + dreamcast/pvrtex/tddither.h | 13 + dreamcast/pvrtex/vqcompress.c | 164 + dreamcast/pvrtex/vqcompress.h | 38 + dreamcast/sfxlist.mk | 3033 ++++ dreamcast/sfxlooplist.mk | 23 + dreamcast/sim.mk | 67 + dreamcast/texconv.cpp | 321 + dreamcast/texlist.mk | 718 + dreamcast/wavlist.mk | 127 + public/index.html | 102 + res/images/regta3dc_logo_1024.png | Bin 0 -> 18513 bytes res/images/screenshot_01.jpg | Bin 0 -> 196869 bytes src/animation/AnimBlendAssocGroup.cpp | 2 - src/animation/AnimBlendAssociation.cpp | 8 +- src/animation/AnimBlendHierarchy.cpp | 32 +- src/animation/AnimBlendHierarchy.h | 4 - src/animation/AnimBlendNode.cpp | 39 +- src/animation/AnimBlendSequence.cpp | 137 +- src/animation/AnimBlendSequence.h | 140 +- src/animation/AnimManager.cpp | 75 +- src/animation/AnimManager.h | 2 - src/animation/CutsceneMgr.cpp | 2 +- src/animation/RpAnimBlend.cpp | 43 +- src/audio/AudioLogic.cpp | 4 +- src/audio/sampman.h | 219 +- src/audio/sampman_dc.cpp | 1342 ++ src/audio/sampman_null.cpp | 2 +- src/collision/ColModel.cpp | 4 +- src/collision/ColTriangle.cpp | 11 +- src/collision/ColTriangle.h | 3 +- src/collision/CompressedVector.h | 4 +- src/control/CarCtrl.cpp | 8 +- src/control/PathFind.cpp | 4 +- src/control/Pickups.h | 2 +- src/control/Script.cpp | 4 +- src/control/Script6.cpp | 2 +- src/core/Cam.cpp | 18 +- src/core/Camera.cpp | 7 +- src/core/CdStream.h | 3 + src/core/CdStreamDC.cpp | 737 + src/core/ControllerConfig.cpp | 1060 +- src/core/ControllerConfig.h | 3 + src/core/Directory.cpp | 2 +- src/core/FileLoader.cpp | 12 +- src/core/Frontend.cpp | 87 +- src/core/Frontend.h | 9 +- src/core/Game.cpp | 2 +- src/core/General.h | 12 +- src/core/IniFile.cpp | 7 +- src/core/MenuScreensCustom.cpp | 18 +- src/core/Pad.cpp | 527 +- src/core/Pad.h | 75 +- src/core/PlayerInfo.cpp | 2 + src/core/Radar.cpp | 29 +- src/core/Streaming.cpp | 74 +- src/core/common.h | 73 +- src/core/common_defines.h | 17 + src/core/config.h | 69 +- src/core/main.cpp | 13 +- src/core/re3.cpp | 72 +- src/core/timebars.cpp | 6 +- src/extras/debugmenu.h | 10 +- src/extras/re3_inttypes.h | 6 +- src/fakerw/fake.cpp | 18 +- src/fakerw/rpworld.h | 2 +- src/fakerw/rtbmp.h | 2 + src/fakerw/rtpng.h | 3 +- src/math/Matrix.cpp | 122 +- src/math/Matrix.h | 4 +- src/math/Quaternion.h | 10 +- src/math/Vector.cpp | 28 +- src/math/Vector.h | 159 +- src/math/VuVector.h | 127 +- src/math/float16.h | 66 + src/math/math.cpp | 112 - src/math/maths.h | 86 +- src/modelinfo/ClumpModelInfo.cpp | 3 + src/modelinfo/ModelInfo.cpp | 2 +- src/modelinfo/ModelInfo.h | 2 +- src/modelinfo/PedModelInfo.cpp | 8 +- src/objects/CutsceneHead.cpp | 2 + src/peds/Ped.cpp | 6 +- src/peds/PedAI.cpp | 30 +- src/peds/Population.cpp | 4 +- src/prof/profiler.cpp | 376 + src/prof/profiler.h | 27 + src/renderer/Clouds.cpp | 14 +- src/renderer/Font.cpp | 2 +- src/renderer/Glass.cpp | 2 +- src/renderer/MBlur.cpp | 57 +- src/renderer/MBlur.h | 4 +- src/renderer/Particle.cpp | 5 +- src/renderer/PlayerSkin.cpp | 4 +- src/renderer/Renderer.cpp | 12 +- src/renderer/Rubbish.h | 9 +- src/renderer/Shadows.cpp | 2 +- src/renderer/Shadows.h | 15 +- src/renderer/Sprite2d.cpp | 29 +- src/renderer/Sprite2d.h | 4 +- src/renderer/WaterLevel.cpp | 3 +- src/rw/RwHelper.h | 1 + src/rw/TexRead.cpp | 30 +- src/rw/TxdStore.cpp | 29 + src/rw/VisibilityPlugins.cpp | 52 +- src/rw/VisibilityPlugins.h | 18 +- src/save/GenericGameStorage.cpp | 47 +- src/save/PCSave.cpp | 96 +- src/save/PCSave.h | 1 + src/skel/crossplatform.cpp | 64 +- src/skel/crossplatform.h | 28 +- src/skel/dc/dc.cpp | 2823 ++++ src/skel/fcaseopen.h | 6 + src/text/Pager.cpp | 2 +- src/vehicles/Automobile.cpp | 4 + src/vehicles/Train.cpp | 2 +- src/vmu/vmu.cpp | 169 + src/vmu/vmu.h | 88 + vendor/TriStripper/README.md | 21 + .../include/detail/cache_simulator.h | 152 + .../include/detail/connectivity_graph.h | 34 + .../TriStripper/include/detail/graph_array.h | 461 + .../TriStripper/include/detail/heap_array.h | 295 + vendor/TriStripper/include/detail/policy.h | 64 + vendor/TriStripper/include/detail/types.h | 101 + vendor/TriStripper/include/public_types.h | 40 + vendor/TriStripper/include/tri_stripper.h | 188 + vendor/TriStripper/src/connectivity_graph.cpp | 130 + vendor/TriStripper/src/policy.cpp | 61 + vendor/TriStripper/src/tri_stripper.cpp | 585 + vendor/crypto/sha256.h | 233 + vendor/dca3-kos | 1 + vendor/emu/emu/emu.h | 10 + vendor/emu/emu/types.h | 19 + vendor/emu/emu/window.cpp | 241 + vendor/emu/license/bsd | 1 + vendor/emu/license/gpl | 1 + vendor/emu/lxdream/tacore.cpp | 1273 ++ vendor/emu/lxdream/tacore.h | 11 + vendor/emu/refsw/TexUtils.cpp | 78 + vendor/emu/refsw/TexUtils.h | 56 + vendor/emu/refsw/core_structs.h | 182 + vendor/emu/refsw/pvr_mem.cpp | 69 + vendor/emu/refsw/pvr_mem.h | 28 + vendor/emu/refsw/pvr_regs.cpp | 194 + vendor/emu/refsw/pvr_regs.h | 571 + vendor/emu/refsw/refsw_lists.cpp | 633 + vendor/emu/refsw/refsw_lists.h | 41 + vendor/emu/refsw/refsw_lists_regtypes.h | 90 + vendor/emu/refsw/refsw_tile.cpp | 1311 ++ vendor/emu/refsw/refsw_tile.h | 183 + vendor/koshle/arch/arch.h | 2 + vendor/koshle/arch/spinlock.h | 111 + vendor/koshle/dc/asic.h | 277 + vendor/koshle/dc/maple.h | 860 ++ vendor/koshle/dc/maple/controller.h | 481 + vendor/koshle/dc/matrix.h | 56 + vendor/koshle/dc/pvr.h | 2549 ++++ vendor/koshle/dc/sq.h | 224 + vendor/koshle/dc_hle_types.h | 65 + vendor/koshle/hlekos.cpp | 75 + vendor/koshle/hlematrix3d.cpp | 200 + vendor/koshle/hlepvr_buffers.cpp | 329 + vendor/koshle/hlepvr_fog.cpp | 360 + vendor/koshle/hlepvr_init_term.cpp | 278 + vendor/koshle/hlepvr_irq.cpp | 244 + vendor/koshle/hlepvr_mem.cpp | 229 + vendor/koshle/hlepvr_mem_core.h | 1402 ++ vendor/koshle/hlepvr_misc.cpp | 286 + vendor/koshle/hlepvr_prim.cpp | 807 ++ vendor/koshle/hlepvr_scene.cpp | 376 + vendor/koshle/kos/dbglog.h | 39 + vendor/koshle/kos/img.h | 183 + vendor/koshle/kos/sem.h | 181 + vendor/koshle/pvr_fog_tables.h | 192 + vendor/koshle/pvr_internal.h | 328 + vendor/librw | 1 - vendor/librw/.appveyor.yml | 32 + .../.github/workflows/build-cmake-conan.yml | 60 + vendor/librw/.github/workflows/build-ps2.yml | 29 + .../librw/.github/workflows/build-switch.yml | 26 + vendor/librw/.gitignore | 12 + vendor/librw/.travis.yml | 46 + vendor/librw/ARCHITECTURE.MD | 174 + vendor/librw/CMakeLists.txt | 160 + vendor/librw/Dockerfile | 60 + vendor/librw/LICENSE | 22 + vendor/librw/README.cmake | 9 + vendor/librw/README.md | 26 + vendor/librw/TODO | 24 + vendor/librw/args.h | 24 + vendor/librw/cmake/FindSDL2.cmake | 38 + vendor/librw/cmake/librw-config.cmake.in | 22 + vendor/librw/cmake/nx/NXFunctions.cmake | 37 + vendor/librw/cmake/ps2/PS2Functions.cmake | 18 + .../cmaketoolchain/CMakeDSMCompiler.cmake.in | 16 + .../cmaketoolchain/CMakeDSMInformation.cmake | 79 + .../CMakeDetermineDSMCompiler.cmake | 87 + .../cmaketoolchain/CMakeTestDSMCompiler.cmake | 7 + .../Platform/PlayStation2.cmake | 1 + .../cmake/ps2/cmaketoolchain/conanfile.py | 24 + .../ps2/cmaketoolchain/toolchain_ps2_ee.cmake | 92 + vendor/librw/conan/playstation2 | 12 + vendor/librw/conanfile.py | 136 + vendor/librw/docker_rebuild_ps2.sh | 22 + vendor/librw/premake-vs2019.cmd | 1 + vendor/librw/premake5.exe | Bin 0 -> 1362432 bytes vendor/librw/premake5.lua | 277 + vendor/librw/rw.h | 28 + vendor/librw/skeleton/CMakeLists.txt | 80 + vendor/librw/skeleton/glfw.cpp | 262 + vendor/librw/skeleton/imgui/ImGuizmo.cpp | 2724 ++++ vendor/librw/skeleton/imgui/ImGuizmo.h | 213 + vendor/librw/skeleton/imgui/LICENSE_imgui.txt | 21 + .../librw/skeleton/imgui/LICENSE_imguizmo.txt | 21 + vendor/librw/skeleton/imgui/imconfig.h | 121 + vendor/librw/skeleton/imgui/imgui.cpp | 11589 ++++++++++++++++ vendor/librw/skeleton/imgui/imgui.h | 2852 ++++ vendor/librw/skeleton/imgui/imgui_demo.cpp | 7725 ++++++++++ vendor/librw/skeleton/imgui/imgui_draw.cpp | 4152 ++++++ vendor/librw/skeleton/imgui/imgui_impl_rw.cpp | 240 + vendor/librw/skeleton/imgui/imgui_impl_rw.h | 5 + vendor/librw/skeleton/imgui/imgui_internal.h | 2688 ++++ vendor/librw/skeleton/imgui/imgui_tables.cpp | 4028 ++++++ vendor/librw/skeleton/imgui/imgui_widgets.cpp | 8056 +++++++++++ vendor/librw/skeleton/imgui/imstb_rectpack.h | 639 + vendor/librw/skeleton/imgui/imstb_textedit.h | 1447 ++ vendor/librw/skeleton/imgui/imstb_truetype.h | 4903 +++++++ vendor/librw/skeleton/sdl2.cpp | 326 + vendor/librw/skeleton/skeleton.cpp | 192 + vendor/librw/skeleton/skeleton.h | 120 + vendor/librw/skeleton/win.cpp | 315 + vendor/librw/src/CMakeLists.txt | 255 + vendor/librw/src/anim.cpp | 293 + vendor/librw/src/base.cpp | 1233 ++ vendor/librw/src/base.err | 24 + vendor/librw/src/bmp.cpp | 284 + vendor/librw/src/camera.cpp | 525 + vendor/librw/src/charset.cpp | 250 + vendor/librw/src/clump.cpp | 678 + vendor/librw/src/d3d-x/d3d.cpp | 1092 ++ vendor/librw/src/d3d-x/d3d8.cpp | 678 + vendor/librw/src/d3d-x/d3d8matfx.cpp | 56 + vendor/librw/src/d3d-x/d3d8render.cpp | 65 + vendor/librw/src/d3d-x/d3d8skin.cpp | 56 + vendor/librw/src/d3d-x/d3d9.cpp | 853 ++ vendor/librw/src/d3d-x/d3d9matfx.cpp | 313 + vendor/librw/src/d3d-x/d3d9render.cpp | 185 + vendor/librw/src/d3d-x/d3d9skin.cpp | 418 + vendor/librw/src/d3d-x/d3ddevice.cpp | 2023 +++ vendor/librw/src/d3d-x/d3dimmed.cpp | 386 + vendor/librw/src/d3d-x/d3drender.cpp | 413 + vendor/librw/src/d3d-x/rwd3d.h | 411 + vendor/librw/src/d3d-x/rwd3d8.h | 74 + vendor/librw/src/d3d-x/rwd3d9.h | 112 + vendor/librw/src/d3d-x/rwd3dimpl.h | 81 + vendor/librw/src/d3d-x/rwxbox.h | 195 + vendor/librw/src/d3d-x/rwxboximpl.h | 11 + vendor/librw/src/d3d-x/shaders/default_PS.h | 72 + .../librw/src/d3d-x/shaders/default_PS.hlsl | 19 + .../librw/src/d3d-x/shaders/default_VS.hlsl | 51 + .../librw/src/d3d-x/shaders/default_all_VS.h | 491 + .../librw/src/d3d-x/shaders/default_amb_VS.h | 156 + .../src/d3d-x/shaders/default_amb_dir_VS.h | 272 + .../librw/src/d3d-x/shaders/default_tex_PS.h | 89 + vendor/librw/src/d3d-x/shaders/im2d_PS.h | 72 + vendor/librw/src/d3d-x/shaders/im2d_PS.hlsl | 19 + vendor/librw/src/d3d-x/shaders/im2d_VS.h | 107 + vendor/librw/src/d3d-x/shaders/im2d_VS.hlsl | 30 + vendor/librw/src/d3d-x/shaders/im2d_tex_PS.h | 89 + vendor/librw/src/d3d-x/shaders/lighting.h | 44 + .../librw/src/d3d-x/shaders/make_default.cmd | 11 + vendor/librw/src/d3d-x/shaders/make_matfx.cmd | 7 + vendor/librw/src/d3d-x/shaders/make_skin.cmd | 4 + vendor/librw/src/d3d-x/shaders/matfx_env_PS.h | 124 + .../librw/src/d3d-x/shaders/matfx_env_PS.hlsl | 42 + .../librw/src/d3d-x/shaders/matfx_env_VS.hlsl | 59 + .../src/d3d-x/shaders/matfx_env_all_VS.h | 535 + .../src/d3d-x/shaders/matfx_env_amb_VS.h | 225 + .../src/d3d-x/shaders/matfx_env_amb_dir_VS.h | 316 + .../src/d3d-x/shaders/matfx_env_tex_PS.h | 141 + vendor/librw/src/d3d-x/shaders/skin_VS.hlsl | 63 + vendor/librw/src/d3d-x/shaders/skin_all_VS.h | 664 + vendor/librw/src/d3d-x/shaders/skin_amb_VS.h | 253 + .../librw/src/d3d-x/shaders/skin_amb_dir_VS.h | 503 + .../src/d3d-x/shaders/standardConstants.h | 28 + vendor/librw/src/d3d-x/xbox.cpp | 1005 ++ vendor/librw/src/d3d-x/xboxmatfx.cpp | 53 + vendor/librw/src/d3d-x/xboxskin.cpp | 252 + vendor/librw/src/d3d-x/xboxvfmt.cpp | 115 + vendor/librw/src/dc/alloc.cpp | 573 + vendor/librw/src/dc/alloc.h | 22 + vendor/librw/src/dc/rwdc.cpp | 5674 ++++++++ vendor/librw/src/dc/rwdc.h | 221 + vendor/librw/src/dc/tex-util.h | 163 + vendor/librw/src/dc/vq.cpp | 387 + vendor/librw/src/dc/vq.h | 24 + vendor/librw/src/engine.cpp | 580 + vendor/librw/src/error.cpp | 49 + vendor/librw/src/fontchange.cpp | 23 + vendor/librw/src/frame.cpp | 463 + vendor/librw/src/geometry.cpp | 1094 ++ vendor/librw/src/geoplg.cpp | 390 + vendor/librw/src/gl-x/gl3.cpp | 56 + vendor/librw/src/gl-x/gl3device.cpp | 2111 +++ vendor/librw/src/gl-x/gl3immed.cpp | 306 + vendor/librw/src/gl-x/gl3matfx.cpp | 258 + vendor/librw/src/gl-x/gl3pipe.cpp | 333 + vendor/librw/src/gl-x/gl3raster.cpp | 1045 ++ vendor/librw/src/gl-x/gl3render.cpp | 184 + vendor/librw/src/gl-x/gl3shader.cpp | 355 + vendor/librw/src/gl-x/gl3skin.cpp | 366 + vendor/librw/src/gl-x/glad/glad.c | 1590 +++ vendor/librw/src/gl-x/glad/glad.h | 2808 ++++ vendor/librw/src/gl-x/glad/khrplatform.h | 290 + vendor/librw/src/gl-x/rwgl3.h | 288 + vendor/librw/src/gl-x/rwgl3impl.h | 76 + vendor/librw/src/gl-x/rwgl3plg.h | 16 + vendor/librw/src/gl-x/rwgl3shader.h | 69 + vendor/librw/src/gl-x/rwwdgl.h | 88 + vendor/librw/src/gl-x/shaders/Makefile | 45 + vendor/librw/src/gl-x/shaders/default.vert | 23 + .../librw/src/gl-x/shaders/default_vs_gl.inc | 25 + vendor/librw/src/gl-x/shaders/header.frag | 30 + vendor/librw/src/gl-x/shaders/header.vert | 128 + vendor/librw/src/gl-x/shaders/header_fs.inc | 32 + vendor/librw/src/gl-x/shaders/header_vs.inc | 130 + vendor/librw/src/gl-x/shaders/im2d.vert | 18 + vendor/librw/src/gl-x/shaders/im2d_gl.inc | 20 + vendor/librw/src/gl-x/shaders/im3d.vert | 16 + vendor/librw/src/gl-x/shaders/im3d_gl.inc | 18 + vendor/librw/src/gl-x/shaders/matfx_env.frag | 34 + vendor/librw/src/gl-x/shaders/matfx_env.vert | 31 + vendor/librw/src/gl-x/shaders/matfx_gl.inc | 69 + vendor/librw/src/gl-x/shaders/simple.frag | 15 + .../librw/src/gl-x/shaders/simple_fs_gl.inc | 17 + vendor/librw/src/gl-x/shaders/skin.vert | 32 + vendor/librw/src/gl-x/shaders/skin_gl.inc | 34 + vendor/librw/src/gl-x/wdgl.cpp | 875 ++ vendor/librw/src/hanim.cpp | 418 + vendor/librw/src/image.cpp | 1326 ++ vendor/librw/src/light.cpp | 163 + vendor/librw/src/lodepng/lodepng.cpp | 6411 +++++++++ vendor/librw/src/lodepng/lodepng.h | 1945 +++ vendor/librw/src/matfx.cpp | 648 + vendor/librw/src/pipeline.cpp | 198 + vendor/librw/src/plg.cpp | 246 + vendor/librw/src/png.cpp | 151 + vendor/librw/src/prim.cpp | 70 + vendor/librw/src/ps2-x/pds.cpp | 203 + vendor/librw/src/ps2-x/ps2.cpp | 1573 +++ vendor/librw/src/ps2-x/ps2device.cpp | 49 + vendor/librw/src/ps2-x/ps2matfx.cpp | 68 + vendor/librw/src/ps2-x/ps2raster.cpp | 2238 +++ vendor/librw/src/ps2-x/ps2skin.cpp | 334 + vendor/librw/src/ps2-x/rwps2.h | 258 + vendor/librw/src/ps2-x/rwps2impl.h | 16 + vendor/librw/src/ps2-x/rwps2plg.h | 27 + vendor/librw/src/raster.cpp | 566 + vendor/librw/src/render.cpp | 96 + vendor/librw/src/rwanim.h | 197 + vendor/librw/src/rwbase.h | 736 + vendor/librw/src/rwcharset.h | 25 + vendor/librw/src/rwengine.h | 265 + vendor/librw/src/rwerror.h | 27 + vendor/librw/src/rwobjects.h | 912 ++ vendor/librw/src/rwpipeline.h | 67 + vendor/librw/src/rwplg.h | 87 + vendor/librw/src/rwplugins.h | 254 + vendor/librw/src/rwrender.h | 141 + vendor/librw/src/rwuserdata.h | 94 + vendor/librw/src/skin.cpp | 507 + vendor/librw/src/texture.cpp | 617 + vendor/librw/src/tga.cpp | 222 + vendor/librw/src/tristrip.cpp | 682 + vendor/librw/src/userdata.cpp | 364 + vendor/librw/src/uvanim.cpp | 502 + vendor/librw/src/vgafont.inc | 256 + vendor/librw/src/vgafont8.inc | 256 + vendor/librw/src/world.cpp | 203 + vendor/librw/tools/CMakeLists.txt | 20 + vendor/librw/tools/camera/CMakeLists.txt | 19 + vendor/librw/tools/camera/camexamp.cpp | 395 + vendor/librw/tools/camera/camexamp.h | 62 + vendor/librw/tools/camera/files/clump.dff | Bin 0 -> 57700 bytes .../tools/camera/files/clump/shinarm.png | Bin 0 -> 84781 bytes .../tools/camera/files/clump/shinbody.png | Bin 0 -> 101261 bytes .../tools/camera/files/clump/shinface.png | Bin 0 -> 81338 bytes .../tools/camera/files/clump/shinleg.png | Bin 0 -> 89939 bytes vendor/librw/tools/camera/main.cpp | 508 + vendor/librw/tools/camera/viewer.cpp | 51 + vendor/librw/tools/camera/viewer.h | 6 + vendor/librw/tools/dumprwtree/CMakeLists.txt | 16 + vendor/librw/tools/dumprwtree/dumprwtree.cpp | 145 + vendor/librw/tools/im2d/CMakeLists.txt | 23 + vendor/librw/tools/im2d/files/whiteash.png | Bin 0 -> 4313 bytes vendor/librw/tools/im2d/im2d.cpp | 134 + vendor/librw/tools/im2d/im2d.h | 62 + vendor/librw/tools/im2d/linelist.cpp | 159 + vendor/librw/tools/im2d/main.cpp | 254 + vendor/librw/tools/im2d/polyline.cpp | 132 + vendor/librw/tools/im2d/trifan.cpp | 119 + vendor/librw/tools/im2d/trilist.cpp | 166 + vendor/librw/tools/im2d/tristrip.cpp | 130 + vendor/librw/tools/im3d/CMakeLists.txt | 23 + vendor/librw/tools/im3d/files/whiteash.png | Bin 0 -> 4313 bytes vendor/librw/tools/im3d/im3d.cpp | 138 + vendor/librw/tools/im3d/im3d.h | 62 + vendor/librw/tools/im3d/linelist.cpp | 176 + vendor/librw/tools/im3d/main.cpp | 268 + vendor/librw/tools/im3d/polyline.cpp | 157 + vendor/librw/tools/im3d/trifan.cpp | 152 + vendor/librw/tools/im3d/trilist.cpp | 202 + vendor/librw/tools/im3d/tristrip.cpp | 169 + vendor/librw/tools/imguitest/CMakeLists.txt | 17 + vendor/librw/tools/imguitest/main.cpp | 175 + vendor/librw/tools/lights/CMakeLists.txt | 18 + vendor/librw/tools/lights/checker.dff | Bin 0 -> 14348 bytes vendor/librw/tools/lights/lights.cpp | 463 + vendor/librw/tools/lights/lights.h | 37 + vendor/librw/tools/lights/main.cpp | 396 + vendor/librw/tools/lights/main.h | 7 + vendor/librw/tools/playground/CMakeLists.txt | 22 + vendor/librw/tools/playground/camera.cpp | 134 + vendor/librw/tools/playground/camera.h | 26 + .../tools/playground/files/Bm437_IBM_BIOS.FON | Bin 0 -> 4064 bytes .../tools/playground/files/Bm437_IBM_BIOS.tga | Bin 0 -> 65554 bytes .../tools/playground/files/Bm437_IBM_VGA8.FON | Bin 0 -> 6112 bytes .../tools/playground/files/Bm437_IBM_VGA8.tga | Bin 0 -> 131090 bytes .../librw/tools/playground/files/foobar.tga | Bin 0 -> 65554 bytes vendor/librw/tools/playground/files/maze.tga | Bin 0 -> 49196 bytes .../librw/tools/playground/files/teapot.dff | Bin 0 -> 42796 bytes vendor/librw/tools/playground/font.cpp | 181 + vendor/librw/tools/playground/main.cpp | 641 + vendor/librw/tools/playground/ras_test.cpp | 902 ++ vendor/librw/tools/playground/splines.cpp | 520 + vendor/librw/tools/playground/tl_tests.cpp | 766 + vendor/librw/tools/ps2test/CMakeLists.txt | 26 + vendor/librw/tools/ps2test/gs.h | 437 + vendor/librw/tools/ps2test/main.cpp | 787 ++ vendor/librw/tools/ps2test/mem.h | 95 + vendor/librw/tools/ps2test/ps2.h | 23 + vendor/librw/tools/ps2test/vu/defaultpipe.dsm | 93 + vendor/librw/tools/ps2test/vu/light.vu | 94 + vendor/librw/tools/ps2test/vu/setup_persp.vu | 39 + vendor/librw/tools/ps2test/vu/skinpipe.dsm | 94 + vendor/librw/tools/ska2anm/CMakeLists.txt | 16 + vendor/librw/tools/ska2anm/ska2anm.cpp | 83 + vendor/librw/tools/subrast/CMakeLists.txt | 19 + vendor/librw/tools/subrast/files/clump.dff | Bin 0 -> 76384 bytes .../tools/subrast/files/textures/whiteash.png | Bin 0 -> 4313 bytes vendor/librw/tools/subrast/main.cpp | 354 + vendor/librw/tools/subrast/subrast.cpp | 141 + vendor/librw/tools/subrast/subrast.h | 10 + vendor/miniLZO/COPYING | 339 + vendor/miniLZO/README.LZO | 123 + vendor/miniLZO/lzoconf.h | 453 + vendor/miniLZO/lzodefs.h | 3134 +++++ vendor/miniLZO/minilzo.c | 6231 +++++++++ vendor/miniLZO/minilzo.h | 106 + vendor/minimp3/minimp3.h | 1865 +++ vendor/minimp3/minimp3_ex.h | 1397 ++ 555 files changed, 206934 insertions(+), 1507 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 dreamcast/GTA3SF8.b create mode 100644 dreamcast/Makefile create mode 100644 dreamcast/analyze-profile.cpp create mode 100644 dreamcast/aud2adpcm.c create mode 100644 dreamcast/common.mk create mode 100644 dreamcast/extract-sfx.cpp create mode 100644 dreamcast/gen-mesh-variants.py create mode 100644 dreamcast/gta3files.mk create mode 100644 dreamcast/imgtool.cpp create mode 100644 dreamcast/ip.txt create mode 100644 dreamcast/modlist.mk create mode 100644 dreamcast/mp3list.mk create mode 100644 dreamcast/pack-sfx.cpp create mode 100644 dreamcast/pvrtex/.gitignore create mode 100644 dreamcast/pvrtex/Makefile create mode 100644 dreamcast/pvrtex/README create mode 100644 dreamcast/pvrtex/README.md create mode 100644 dreamcast/pvrtex/avstring.c create mode 100644 dreamcast/pvrtex/bprint.c create mode 100644 dreamcast/pvrtex/compat/va_copy.h create mode 100644 dreamcast/pvrtex/config.h create mode 100644 dreamcast/pvrtex/crc.c create mode 100644 dreamcast/pvrtex/dither.cpp create mode 100644 dreamcast/pvrtex/elbg.c create mode 100644 dreamcast/pvrtex/elbg.h create mode 100644 dreamcast/pvrtex/file_common.c create mode 100644 dreamcast/pvrtex/file_common.h create mode 100644 dreamcast/pvrtex/file_dctex.c create mode 100644 dreamcast/pvrtex/file_dctex.h create mode 100644 dreamcast/pvrtex/file_pvr.c create mode 100644 dreamcast/pvrtex/file_pvr.h create mode 100644 dreamcast/pvrtex/file_tex.c create mode 100644 dreamcast/pvrtex/file_tex.h create mode 100644 dreamcast/pvrtex/lfg.c create mode 100644 dreamcast/pvrtex/libavcodec/elbg.h create mode 100644 dreamcast/pvrtex/libavutil/attributes.h create mode 100644 dreamcast/pvrtex/libavutil/attributes_internal.h create mode 100644 dreamcast/pvrtex/libavutil/avassert.h create mode 100644 dreamcast/pvrtex/libavutil/avconfig.h create mode 100644 dreamcast/pvrtex/libavutil/avstring.h create mode 100644 dreamcast/pvrtex/libavutil/avutil.h create mode 100644 dreamcast/pvrtex/libavutil/bprint.h create mode 100644 dreamcast/pvrtex/libavutil/bswap.h create mode 100644 dreamcast/pvrtex/libavutil/common.h create mode 100644 dreamcast/pvrtex/libavutil/crc.h create mode 100644 dreamcast/pvrtex/libavutil/dynarray.h create mode 100644 dreamcast/pvrtex/libavutil/error.h create mode 100644 dreamcast/pvrtex/libavutil/internal.h create mode 100644 dreamcast/pvrtex/libavutil/intfloat.h create mode 100644 dreamcast/pvrtex/libavutil/intreadwrite.h create mode 100644 dreamcast/pvrtex/libavutil/lfg.c create mode 100644 dreamcast/pvrtex/libavutil/lfg.h create mode 100644 dreamcast/pvrtex/libavutil/libm.h create mode 100644 dreamcast/pvrtex/libavutil/log.h create mode 100644 dreamcast/pvrtex/libavutil/macros.h create mode 100644 dreamcast/pvrtex/libavutil/mathematics.c create mode 100644 dreamcast/pvrtex/libavutil/mathematics.h create mode 100644 dreamcast/pvrtex/libavutil/md5.h create mode 100644 dreamcast/pvrtex/libavutil/mem.c create mode 100644 dreamcast/pvrtex/libavutil/mem.h create mode 100644 dreamcast/pvrtex/libavutil/pixfmt.h create mode 100644 dreamcast/pvrtex/libavutil/rational.c create mode 100644 dreamcast/pvrtex/libavutil/rational.h create mode 100644 dreamcast/pvrtex/libavutil/thread.h create mode 100644 dreamcast/pvrtex/libavutil/version.h create mode 100644 dreamcast/pvrtex/log.c create mode 100644 dreamcast/pvrtex/main.c create mode 100644 dreamcast/pvrtex/md5.c create mode 100644 dreamcast/pvrtex/mem.c create mode 100644 dreamcast/pvrtex/mycommon.c create mode 100644 dreamcast/pvrtex/mycommon.h create mode 100644 dreamcast/pvrtex/nvmath.h create mode 100644 dreamcast/pvrtex/optparse.h create mode 100644 dreamcast/pvrtex/optparse_impl.c create mode 100644 dreamcast/pvrtex/pixel.h create mode 100644 dreamcast/pvrtex/pvr_texture.c create mode 100644 dreamcast/pvrtex/pvr_texture.h create mode 100644 dreamcast/pvrtex/pvr_texture_encoder.c create mode 100644 dreamcast/pvrtex/pvr_texture_encoder.h create mode 100644 dreamcast/pvrtex/readme_unformatted.txt create mode 100644 dreamcast/pvrtex/stb_image.h create mode 100644 dreamcast/pvrtex/stb_image_impl.c create mode 100644 dreamcast/pvrtex/stb_image_resize.h create mode 100644 dreamcast/pvrtex/stb_image_resize_impl.c create mode 100644 dreamcast/pvrtex/stb_image_write.h create mode 100644 dreamcast/pvrtex/stb_image_write_impl.c create mode 100644 dreamcast/pvrtex/tddither.c create mode 100644 dreamcast/pvrtex/tddither.h create mode 100644 dreamcast/pvrtex/vqcompress.c create mode 100644 dreamcast/pvrtex/vqcompress.h create mode 100644 dreamcast/sfxlist.mk create mode 100644 dreamcast/sfxlooplist.mk create mode 100644 dreamcast/sim.mk create mode 100644 dreamcast/texconv.cpp create mode 100644 dreamcast/texlist.mk create mode 100644 dreamcast/wavlist.mk create mode 100644 public/index.html create mode 100644 res/images/regta3dc_logo_1024.png create mode 100644 res/images/screenshot_01.jpg create mode 100644 src/audio/sampman_dc.cpp create mode 100644 src/core/CdStreamDC.cpp create mode 100644 src/core/common_defines.h create mode 100644 src/math/float16.h create mode 100644 src/prof/profiler.cpp create mode 100644 src/prof/profiler.h create mode 100644 src/skel/dc/dc.cpp create mode 100644 src/skel/fcaseopen.h create mode 100644 src/vmu/vmu.cpp create mode 100644 src/vmu/vmu.h create mode 100644 vendor/TriStripper/README.md create mode 100644 vendor/TriStripper/include/detail/cache_simulator.h create mode 100644 vendor/TriStripper/include/detail/connectivity_graph.h create mode 100644 vendor/TriStripper/include/detail/graph_array.h create mode 100644 vendor/TriStripper/include/detail/heap_array.h create mode 100644 vendor/TriStripper/include/detail/policy.h create mode 100644 vendor/TriStripper/include/detail/types.h create mode 100644 vendor/TriStripper/include/public_types.h create mode 100644 vendor/TriStripper/include/tri_stripper.h create mode 100644 vendor/TriStripper/src/connectivity_graph.cpp create mode 100644 vendor/TriStripper/src/policy.cpp create mode 100644 vendor/TriStripper/src/tri_stripper.cpp create mode 100644 vendor/crypto/sha256.h create mode 160000 vendor/dca3-kos create mode 100644 vendor/emu/emu/emu.h create mode 100644 vendor/emu/emu/types.h create mode 100644 vendor/emu/emu/window.cpp create mode 100644 vendor/emu/license/bsd create mode 100644 vendor/emu/license/gpl create mode 100644 vendor/emu/lxdream/tacore.cpp create mode 100644 vendor/emu/lxdream/tacore.h create mode 100644 vendor/emu/refsw/TexUtils.cpp create mode 100644 vendor/emu/refsw/TexUtils.h create mode 100644 vendor/emu/refsw/core_structs.h create mode 100644 vendor/emu/refsw/pvr_mem.cpp create mode 100644 vendor/emu/refsw/pvr_mem.h create mode 100644 vendor/emu/refsw/pvr_regs.cpp create mode 100644 vendor/emu/refsw/pvr_regs.h create mode 100644 vendor/emu/refsw/refsw_lists.cpp create mode 100644 vendor/emu/refsw/refsw_lists.h create mode 100644 vendor/emu/refsw/refsw_lists_regtypes.h create mode 100644 vendor/emu/refsw/refsw_tile.cpp create mode 100644 vendor/emu/refsw/refsw_tile.h create mode 100644 vendor/koshle/arch/arch.h create mode 100644 vendor/koshle/arch/spinlock.h create mode 100644 vendor/koshle/dc/asic.h create mode 100644 vendor/koshle/dc/maple.h create mode 100644 vendor/koshle/dc/maple/controller.h create mode 100644 vendor/koshle/dc/matrix.h create mode 100644 vendor/koshle/dc/pvr.h create mode 100644 vendor/koshle/dc/sq.h create mode 100644 vendor/koshle/dc_hle_types.h create mode 100644 vendor/koshle/hlekos.cpp create mode 100644 vendor/koshle/hlematrix3d.cpp create mode 100644 vendor/koshle/hlepvr_buffers.cpp create mode 100644 vendor/koshle/hlepvr_fog.cpp create mode 100644 vendor/koshle/hlepvr_init_term.cpp create mode 100644 vendor/koshle/hlepvr_irq.cpp create mode 100644 vendor/koshle/hlepvr_mem.cpp create mode 100644 vendor/koshle/hlepvr_mem_core.h create mode 100644 vendor/koshle/hlepvr_misc.cpp create mode 100644 vendor/koshle/hlepvr_prim.cpp create mode 100644 vendor/koshle/hlepvr_scene.cpp create mode 100644 vendor/koshle/kos/dbglog.h create mode 100644 vendor/koshle/kos/img.h create mode 100644 vendor/koshle/kos/sem.h create mode 100644 vendor/koshle/pvr_fog_tables.h create mode 100644 vendor/koshle/pvr_internal.h delete mode 160000 vendor/librw create mode 100644 vendor/librw/.appveyor.yml create mode 100644 vendor/librw/.github/workflows/build-cmake-conan.yml create mode 100644 vendor/librw/.github/workflows/build-ps2.yml create mode 100644 vendor/librw/.github/workflows/build-switch.yml create mode 100644 vendor/librw/.gitignore create mode 100644 vendor/librw/.travis.yml create mode 100644 vendor/librw/ARCHITECTURE.MD create mode 100644 vendor/librw/CMakeLists.txt create mode 100644 vendor/librw/Dockerfile create mode 100644 vendor/librw/LICENSE create mode 100644 vendor/librw/README.cmake create mode 100644 vendor/librw/README.md create mode 100644 vendor/librw/TODO create mode 100644 vendor/librw/args.h create mode 100644 vendor/librw/cmake/FindSDL2.cmake create mode 100644 vendor/librw/cmake/librw-config.cmake.in create mode 100644 vendor/librw/cmake/nx/NXFunctions.cmake create mode 100644 vendor/librw/cmake/ps2/PS2Functions.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake create mode 100644 vendor/librw/conan/playstation2 create mode 100644 vendor/librw/conanfile.py create mode 100755 vendor/librw/docker_rebuild_ps2.sh create mode 100644 vendor/librw/premake-vs2019.cmd create mode 100644 vendor/librw/premake5.exe create mode 100755 vendor/librw/premake5.lua create mode 100644 vendor/librw/rw.h create mode 100644 vendor/librw/skeleton/CMakeLists.txt create mode 100644 vendor/librw/skeleton/glfw.cpp create mode 100644 vendor/librw/skeleton/imgui/ImGuizmo.cpp create mode 100644 vendor/librw/skeleton/imgui/ImGuizmo.h create mode 100644 vendor/librw/skeleton/imgui/LICENSE_imgui.txt create mode 100644 vendor/librw/skeleton/imgui/LICENSE_imguizmo.txt create mode 100644 vendor/librw/skeleton/imgui/imconfig.h create mode 100644 vendor/librw/skeleton/imgui/imgui.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui.h create mode 100644 vendor/librw/skeleton/imgui/imgui_demo.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui_draw.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui_impl_rw.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui_impl_rw.h create mode 100644 vendor/librw/skeleton/imgui/imgui_internal.h create mode 100644 vendor/librw/skeleton/imgui/imgui_tables.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui_widgets.cpp create mode 100644 vendor/librw/skeleton/imgui/imstb_rectpack.h create mode 100644 vendor/librw/skeleton/imgui/imstb_textedit.h create mode 100644 vendor/librw/skeleton/imgui/imstb_truetype.h create mode 100644 vendor/librw/skeleton/sdl2.cpp create mode 100644 vendor/librw/skeleton/skeleton.cpp create mode 100644 vendor/librw/skeleton/skeleton.h create mode 100644 vendor/librw/skeleton/win.cpp create mode 100644 vendor/librw/src/CMakeLists.txt create mode 100644 vendor/librw/src/anim.cpp create mode 100644 vendor/librw/src/base.cpp create mode 100644 vendor/librw/src/base.err create mode 100644 vendor/librw/src/bmp.cpp create mode 100644 vendor/librw/src/camera.cpp create mode 100644 vendor/librw/src/charset.cpp create mode 100644 vendor/librw/src/clump.cpp create mode 100644 vendor/librw/src/d3d-x/d3d.cpp create mode 100644 vendor/librw/src/d3d-x/d3d8.cpp create mode 100644 vendor/librw/src/d3d-x/d3d8matfx.cpp create mode 100644 vendor/librw/src/d3d-x/d3d8render.cpp create mode 100644 vendor/librw/src/d3d-x/d3d8skin.cpp create mode 100644 vendor/librw/src/d3d-x/d3d9.cpp create mode 100644 vendor/librw/src/d3d-x/d3d9matfx.cpp create mode 100644 vendor/librw/src/d3d-x/d3d9render.cpp create mode 100644 vendor/librw/src/d3d-x/d3d9skin.cpp create mode 100644 vendor/librw/src/d3d-x/d3ddevice.cpp create mode 100644 vendor/librw/src/d3d-x/d3dimmed.cpp create mode 100644 vendor/librw/src/d3d-x/d3drender.cpp create mode 100644 vendor/librw/src/d3d-x/rwd3d.h create mode 100644 vendor/librw/src/d3d-x/rwd3d8.h create mode 100644 vendor/librw/src/d3d-x/rwd3d9.h create mode 100644 vendor/librw/src/d3d-x/rwd3dimpl.h create mode 100644 vendor/librw/src/d3d-x/rwxbox.h create mode 100644 vendor/librw/src/d3d-x/rwxboximpl.h create mode 100644 vendor/librw/src/d3d-x/shaders/default_PS.h create mode 100644 vendor/librw/src/d3d-x/shaders/default_PS.hlsl create mode 100644 vendor/librw/src/d3d-x/shaders/default_VS.hlsl create mode 100644 vendor/librw/src/d3d-x/shaders/default_all_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/default_amb_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/default_amb_dir_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/default_tex_PS.h create mode 100644 vendor/librw/src/d3d-x/shaders/im2d_PS.h create mode 100644 vendor/librw/src/d3d-x/shaders/im2d_PS.hlsl create mode 100644 vendor/librw/src/d3d-x/shaders/im2d_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/im2d_VS.hlsl create mode 100644 vendor/librw/src/d3d-x/shaders/im2d_tex_PS.h create mode 100644 vendor/librw/src/d3d-x/shaders/lighting.h create mode 100644 vendor/librw/src/d3d-x/shaders/make_default.cmd create mode 100644 vendor/librw/src/d3d-x/shaders/make_matfx.cmd create mode 100644 vendor/librw/src/d3d-x/shaders/make_skin.cmd create mode 100644 vendor/librw/src/d3d-x/shaders/matfx_env_PS.h create mode 100644 vendor/librw/src/d3d-x/shaders/matfx_env_PS.hlsl create mode 100644 vendor/librw/src/d3d-x/shaders/matfx_env_VS.hlsl create mode 100644 vendor/librw/src/d3d-x/shaders/matfx_env_all_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/matfx_env_amb_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/matfx_env_amb_dir_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/matfx_env_tex_PS.h create mode 100644 vendor/librw/src/d3d-x/shaders/skin_VS.hlsl create mode 100644 vendor/librw/src/d3d-x/shaders/skin_all_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/skin_amb_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/skin_amb_dir_VS.h create mode 100644 vendor/librw/src/d3d-x/shaders/standardConstants.h create mode 100644 vendor/librw/src/d3d-x/xbox.cpp create mode 100644 vendor/librw/src/d3d-x/xboxmatfx.cpp create mode 100644 vendor/librw/src/d3d-x/xboxskin.cpp create mode 100644 vendor/librw/src/d3d-x/xboxvfmt.cpp create mode 100644 vendor/librw/src/dc/alloc.cpp create mode 100644 vendor/librw/src/dc/alloc.h create mode 100644 vendor/librw/src/dc/rwdc.cpp create mode 100644 vendor/librw/src/dc/rwdc.h create mode 100644 vendor/librw/src/dc/tex-util.h create mode 100644 vendor/librw/src/dc/vq.cpp create mode 100644 vendor/librw/src/dc/vq.h create mode 100644 vendor/librw/src/engine.cpp create mode 100644 vendor/librw/src/error.cpp create mode 100644 vendor/librw/src/fontchange.cpp create mode 100644 vendor/librw/src/frame.cpp create mode 100644 vendor/librw/src/geometry.cpp create mode 100644 vendor/librw/src/geoplg.cpp create mode 100644 vendor/librw/src/gl-x/gl3.cpp create mode 100644 vendor/librw/src/gl-x/gl3device.cpp create mode 100644 vendor/librw/src/gl-x/gl3immed.cpp create mode 100644 vendor/librw/src/gl-x/gl3matfx.cpp create mode 100644 vendor/librw/src/gl-x/gl3pipe.cpp create mode 100644 vendor/librw/src/gl-x/gl3raster.cpp create mode 100644 vendor/librw/src/gl-x/gl3render.cpp create mode 100644 vendor/librw/src/gl-x/gl3shader.cpp create mode 100644 vendor/librw/src/gl-x/gl3skin.cpp create mode 100644 vendor/librw/src/gl-x/glad/glad.c create mode 100644 vendor/librw/src/gl-x/glad/glad.h create mode 100644 vendor/librw/src/gl-x/glad/khrplatform.h create mode 100644 vendor/librw/src/gl-x/rwgl3.h create mode 100644 vendor/librw/src/gl-x/rwgl3impl.h create mode 100644 vendor/librw/src/gl-x/rwgl3plg.h create mode 100644 vendor/librw/src/gl-x/rwgl3shader.h create mode 100644 vendor/librw/src/gl-x/rwwdgl.h create mode 100644 vendor/librw/src/gl-x/shaders/Makefile create mode 100644 vendor/librw/src/gl-x/shaders/default.vert create mode 100644 vendor/librw/src/gl-x/shaders/default_vs_gl.inc create mode 100644 vendor/librw/src/gl-x/shaders/header.frag create mode 100644 vendor/librw/src/gl-x/shaders/header.vert create mode 100644 vendor/librw/src/gl-x/shaders/header_fs.inc create mode 100644 vendor/librw/src/gl-x/shaders/header_vs.inc create mode 100644 vendor/librw/src/gl-x/shaders/im2d.vert create mode 100644 vendor/librw/src/gl-x/shaders/im2d_gl.inc create mode 100644 vendor/librw/src/gl-x/shaders/im3d.vert create mode 100644 vendor/librw/src/gl-x/shaders/im3d_gl.inc create mode 100644 vendor/librw/src/gl-x/shaders/matfx_env.frag create mode 100644 vendor/librw/src/gl-x/shaders/matfx_env.vert create mode 100644 vendor/librw/src/gl-x/shaders/matfx_gl.inc create mode 100644 vendor/librw/src/gl-x/shaders/simple.frag create mode 100644 vendor/librw/src/gl-x/shaders/simple_fs_gl.inc create mode 100644 vendor/librw/src/gl-x/shaders/skin.vert create mode 100644 vendor/librw/src/gl-x/shaders/skin_gl.inc create mode 100644 vendor/librw/src/gl-x/wdgl.cpp create mode 100644 vendor/librw/src/hanim.cpp create mode 100644 vendor/librw/src/image.cpp create mode 100644 vendor/librw/src/light.cpp create mode 100644 vendor/librw/src/lodepng/lodepng.cpp create mode 100644 vendor/librw/src/lodepng/lodepng.h create mode 100644 vendor/librw/src/matfx.cpp create mode 100644 vendor/librw/src/pipeline.cpp create mode 100644 vendor/librw/src/plg.cpp create mode 100644 vendor/librw/src/png.cpp create mode 100644 vendor/librw/src/prim.cpp create mode 100644 vendor/librw/src/ps2-x/pds.cpp create mode 100644 vendor/librw/src/ps2-x/ps2.cpp create mode 100644 vendor/librw/src/ps2-x/ps2device.cpp create mode 100644 vendor/librw/src/ps2-x/ps2matfx.cpp create mode 100644 vendor/librw/src/ps2-x/ps2raster.cpp create mode 100644 vendor/librw/src/ps2-x/ps2skin.cpp create mode 100644 vendor/librw/src/ps2-x/rwps2.h create mode 100644 vendor/librw/src/ps2-x/rwps2impl.h create mode 100644 vendor/librw/src/ps2-x/rwps2plg.h create mode 100644 vendor/librw/src/raster.cpp create mode 100644 vendor/librw/src/render.cpp create mode 100644 vendor/librw/src/rwanim.h create mode 100644 vendor/librw/src/rwbase.h create mode 100644 vendor/librw/src/rwcharset.h create mode 100644 vendor/librw/src/rwengine.h create mode 100644 vendor/librw/src/rwerror.h create mode 100644 vendor/librw/src/rwobjects.h create mode 100644 vendor/librw/src/rwpipeline.h create mode 100644 vendor/librw/src/rwplg.h create mode 100644 vendor/librw/src/rwplugins.h create mode 100644 vendor/librw/src/rwrender.h create mode 100644 vendor/librw/src/rwuserdata.h create mode 100644 vendor/librw/src/skin.cpp create mode 100644 vendor/librw/src/texture.cpp create mode 100644 vendor/librw/src/tga.cpp create mode 100644 vendor/librw/src/tristrip.cpp create mode 100644 vendor/librw/src/userdata.cpp create mode 100644 vendor/librw/src/uvanim.cpp create mode 100644 vendor/librw/src/vgafont.inc create mode 100644 vendor/librw/src/vgafont8.inc create mode 100644 vendor/librw/src/world.cpp create mode 100644 vendor/librw/tools/CMakeLists.txt create mode 100644 vendor/librw/tools/camera/CMakeLists.txt create mode 100644 vendor/librw/tools/camera/camexamp.cpp create mode 100644 vendor/librw/tools/camera/camexamp.h create mode 100644 vendor/librw/tools/camera/files/clump.dff create mode 100644 vendor/librw/tools/camera/files/clump/shinarm.png create mode 100644 vendor/librw/tools/camera/files/clump/shinbody.png create mode 100644 vendor/librw/tools/camera/files/clump/shinface.png create mode 100644 vendor/librw/tools/camera/files/clump/shinleg.png create mode 100644 vendor/librw/tools/camera/main.cpp create mode 100644 vendor/librw/tools/camera/viewer.cpp create mode 100644 vendor/librw/tools/camera/viewer.h create mode 100644 vendor/librw/tools/dumprwtree/CMakeLists.txt create mode 100644 vendor/librw/tools/dumprwtree/dumprwtree.cpp create mode 100644 vendor/librw/tools/im2d/CMakeLists.txt create mode 100644 vendor/librw/tools/im2d/files/whiteash.png create mode 100644 vendor/librw/tools/im2d/im2d.cpp create mode 100644 vendor/librw/tools/im2d/im2d.h create mode 100644 vendor/librw/tools/im2d/linelist.cpp create mode 100644 vendor/librw/tools/im2d/main.cpp create mode 100644 vendor/librw/tools/im2d/polyline.cpp create mode 100644 vendor/librw/tools/im2d/trifan.cpp create mode 100644 vendor/librw/tools/im2d/trilist.cpp create mode 100644 vendor/librw/tools/im2d/tristrip.cpp create mode 100644 vendor/librw/tools/im3d/CMakeLists.txt create mode 100644 vendor/librw/tools/im3d/files/whiteash.png create mode 100644 vendor/librw/tools/im3d/im3d.cpp create mode 100644 vendor/librw/tools/im3d/im3d.h create mode 100644 vendor/librw/tools/im3d/linelist.cpp create mode 100644 vendor/librw/tools/im3d/main.cpp create mode 100644 vendor/librw/tools/im3d/polyline.cpp create mode 100644 vendor/librw/tools/im3d/trifan.cpp create mode 100644 vendor/librw/tools/im3d/trilist.cpp create mode 100644 vendor/librw/tools/im3d/tristrip.cpp create mode 100644 vendor/librw/tools/imguitest/CMakeLists.txt create mode 100644 vendor/librw/tools/imguitest/main.cpp create mode 100644 vendor/librw/tools/lights/CMakeLists.txt create mode 100644 vendor/librw/tools/lights/checker.dff create mode 100644 vendor/librw/tools/lights/lights.cpp create mode 100644 vendor/librw/tools/lights/lights.h create mode 100644 vendor/librw/tools/lights/main.cpp create mode 100644 vendor/librw/tools/lights/main.h create mode 100644 vendor/librw/tools/playground/CMakeLists.txt create mode 100644 vendor/librw/tools/playground/camera.cpp create mode 100644 vendor/librw/tools/playground/camera.h create mode 100644 vendor/librw/tools/playground/files/Bm437_IBM_BIOS.FON create mode 100644 vendor/librw/tools/playground/files/Bm437_IBM_BIOS.tga create mode 100644 vendor/librw/tools/playground/files/Bm437_IBM_VGA8.FON create mode 100644 vendor/librw/tools/playground/files/Bm437_IBM_VGA8.tga create mode 100644 vendor/librw/tools/playground/files/foobar.tga create mode 100644 vendor/librw/tools/playground/files/maze.tga create mode 100644 vendor/librw/tools/playground/files/teapot.dff create mode 100644 vendor/librw/tools/playground/font.cpp create mode 100644 vendor/librw/tools/playground/main.cpp create mode 100644 vendor/librw/tools/playground/ras_test.cpp create mode 100644 vendor/librw/tools/playground/splines.cpp create mode 100644 vendor/librw/tools/playground/tl_tests.cpp create mode 100644 vendor/librw/tools/ps2test/CMakeLists.txt create mode 100644 vendor/librw/tools/ps2test/gs.h create mode 100755 vendor/librw/tools/ps2test/main.cpp create mode 100644 vendor/librw/tools/ps2test/mem.h create mode 100644 vendor/librw/tools/ps2test/ps2.h create mode 100644 vendor/librw/tools/ps2test/vu/defaultpipe.dsm create mode 100644 vendor/librw/tools/ps2test/vu/light.vu create mode 100644 vendor/librw/tools/ps2test/vu/setup_persp.vu create mode 100644 vendor/librw/tools/ps2test/vu/skinpipe.dsm create mode 100644 vendor/librw/tools/ska2anm/CMakeLists.txt create mode 100644 vendor/librw/tools/ska2anm/ska2anm.cpp create mode 100644 vendor/librw/tools/subrast/CMakeLists.txt create mode 100644 vendor/librw/tools/subrast/files/clump.dff create mode 100644 vendor/librw/tools/subrast/files/textures/whiteash.png create mode 100644 vendor/librw/tools/subrast/main.cpp create mode 100644 vendor/librw/tools/subrast/subrast.cpp create mode 100644 vendor/librw/tools/subrast/subrast.h create mode 100644 vendor/miniLZO/COPYING create mode 100644 vendor/miniLZO/README.LZO create mode 100644 vendor/miniLZO/lzoconf.h create mode 100644 vendor/miniLZO/lzodefs.h create mode 100644 vendor/miniLZO/minilzo.c create mode 100644 vendor/miniLZO/minilzo.h create mode 100644 vendor/minimp3/minimp3.h create mode 100644 vendor/minimp3/minimp3_ex.h diff --git a/.gitignore b/.gitignore index 38ad5d73..3c6f9cc7 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,31 @@ codewarrior/re3_Data/ codewarrior/Release/ codewarrior/Debug/ -src/extras/GitSHA1.cpp \ No newline at end of file +src/extras/GitSHA1.cpp + +*.o +*.d +*.sim.o +*.sim.o3 +dreamcast/1ST_READ.BIN +dreamcast/IP.BIN +dreamcast/re3.cdi +dreamcast/re3.iso +dreamcast/re3.elf +dreamcast/re3.elf.bin +dreamcast/re3-sim.elf +dreamcast/dca3.cdi +dreamcast/dca3.iso +dreamcast/dca3.elf +dreamcast/dca3.elf.bin +dreamcast/dca3-sim.elf +dreamcast/texconv* +dreamcast/imgtool* +dreamcast/extract-sfx* +dreamcast/pack-sfx* +dreamcast/analyze-profile* +dreamcast/aud2adpcm* +dreamcast/repack-data +dreamcast/output.map +dreamcast/dca3.ds.iso +.DS_Store diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..deb86975 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,151 @@ +variables: + GIT_SUBMODULE_STRATEGY: recursive + +image: + name: ghcr.io/kos-builds/kos-dc:latest-14.1.0 + entrypoint: ["/bin/sh", "-c", 'echo gitlab command: "$@" && /bin/bash -c "$@"'] + +build-dreamcast: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - apt update + - apt install -y ccache + - update-ccache-symlinks + - rm -rf /opt/toolchains/dc/kos + - cp -R vendor/dca3-kos /opt/toolchains/dc/kos + - make -C /opt/toolchains/dc/kos/kernel/arch/dreamcast/sound/arm + - make -C /opt/toolchains/dc/kos -j $(nproc) + script: + - cd dreamcast + - make -j $(nproc) + artifacts: + paths: + - dreamcast/dca3.elf + +build-dreamcast-ide-32mb: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - apt update + - apt install -y ccache + - update-ccache-symlinks + - rm -rf /opt/toolchains/dc/kos + - cp -R vendor/dca3-kos /opt/toolchains/dc/kos + - make -C /opt/toolchains/dc/kos/kernel/arch/dreamcast/sound/arm + - make -C /opt/toolchains/dc/kos -j $(nproc) + script: + - cd dreamcast + - make -j $(nproc) WITH_IDE=1 WITH_32MB=1 TARGET=dca3-ide-32mb.elf + artifacts: + paths: + - dreamcast/dca3-ide-32mb.elf + +build-texconv: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - apt update + - apt install -y build-essential gcc g++ ccache + - update-ccache-symlinks + script: + - export PATH="/usr/lib/ccache:$PATH" + - cd dreamcast + - make texconv -j $(nproc) + artifacts: + paths: + - dreamcast/texconv + +build-texconv-clang: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - apt update + - apt install -y build-essential gcc g++ clang ccache + - update-ccache-symlinks + script: + - export PATH="/usr/lib/ccache:$PATH" + - cd dreamcast + - CC=clang CXX=clang++ make texconv -j $(nproc) + - mv texconv texconv-clang + artifacts: + paths: + - dreamcast/texconv-clang + +build-sim: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - dpkg --add-architecture i386 + - apt update + - apt install -y build-essential gcc g++ gcc-multilib g++-multilib libx11-dev:i386 ccache + - update-ccache-symlinks + script: + - export PATH="/usr/lib/ccache:$PATH" + - cd dreamcast + - make -f sim.mk -j $(nproc) + artifacts: + paths: + - dreamcast/dca3-sim.elf + +build-sim-clang: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - dpkg --add-architecture i386 + - apt update + - apt install -y build-essential gcc g++ gcc-multilib g++-multilib libx11-dev:i386 clang ccache + - update-ccache-symlinks + script: + - export PATH="/usr/lib/ccache:$PATH" + - cd dreamcast + - CC=clang CXX=clang++ make -f sim.mk -j $(nproc) TARGET=dca3-sim-clang.elf + artifacts: + paths: + - dreamcast/dca3-sim-clang.elf + +pages: + stage: deploy + script: + - echo "The site will be deployed to $CI_PAGES_URL" + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index c9a30c95..338efe87 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,7 +10,6 @@ path = vendor/opusfile url = https://github.com/xiph/opusfile.git branch = master -[submodule "vendor/librw"] - path = vendor/librw - url = https://github.com/aap/librw.git - branch = master +[submodule "vendor/dca3-kos"] + path = vendor/dca3-kos + url = https://gitlab.com/skmp/dca3-kos.git diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 2ce82727..55fb2efd 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,50 +1,60 @@ { - "configurations": [ - { - "name": "Mac", - "includePath": ["${default}"], - "defines": [], - "macFrameworkPath": [ - "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks" - ], - "compilerPath": "/opt/local/bin/clang", - "compilerArgs": ["-g"], - "cStandard": "gnu11", - "cppStandard": "gnu++14", - "browse": { - "path": [ - "/opt/local/include", - "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include" - ] - } - }, - { - "name": "Linux", - "includePath": ["${default}"], - "defines": ["XDG_ROOT"], - "compilerPath": "/usr/bin/gcc", - "compilerArgs": ["-ggdb"], - "cStandard": "gnu11", - "cppStandard": "gnu++14" - }, - { - "name": "devkitPro aarch64 (Nintendo Switch)", - "compilerPath": "${env:DEVKITPRO}/devkitA64/bin/aarch64-none-elf-g++", - "includePath": [ - "${default}", - "${env:DEVKITPRO}/portlibs/switch/include", - "${env:DEVKITPRO}/libnx/include" - ], - "intelliSenseMode": "gcc-arm64", - "cStandard": "gnu11", - "cppStandard": "gnu++11", - "defines": [ - "__SWITCH__", - "LIBRW", - "RW_GL3", - "AUDIO_OAL" - ] - } - ], - "version": 4 -} + "configurations": [ + { + "name": "Mac", + "includePath": [ + "${default}" + ], + "defines": ["LIBRW", "RW_DC"], + "macFrameworkPath": [ + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks" + ], + "compilerPath": "/opt/local/bin/clang", + "compilerArgs": [ + "-g" + ], + "cStandard": "gnu11", + "cppStandard": "gnu++14", + "browse": { + "path": [ + "/opt/local/include", + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include" + ] + } + }, + { + "name": "Linux", + "includePath": [ + "${default}" + ], + "defines": [ + "XDG_ROOT", + "LIBRW", + "RW_DC" + ], + "compilerPath": "/usr/bin/gcc", + "compilerArgs": [ + "-ggdb" + ], + "cStandard": "gnu11", + "cppStandard": "gnu++14" + }, + { + "name": "devkitPro aarch64 (Nintendo Switch)", + "compilerPath": "${env:DEVKITPRO}/devkitA64/bin/aarch64-none-elf-g++", + "includePath": [ + "${default}", + "${env:DEVKITPRO}/portlibs/switch/include", + "${env:DEVKITPRO}/libnx/include" + ], + "intelliSenseMode": "gcc-arm64", + "cStandard": "gnu11", + "cppStandard": "gnu++11", + "defines": [ + "LIBRW", + "RW_DC" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 82ce041f..c49cfdf5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,5 +1,113 @@ { "configurations": [ + { + "name": "dca3-sim", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/dreamcast/dca3-sim.elf", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, + { + "name": "dca3-sim (mac)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/dreamcast/dca3-sim.elf", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/dreamcast", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + }, + { + "name": "texconv (Windows)", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/dreamcast/texconv.exe", + "args": ["repack-data/img-orig/GTAElift.DFF", "repack-data/img-dc/GTAElift.DFF"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/dreamcast", + "environment": [], + "console": "externalTerminal" + }, + { + "name": "wav2adpcm (wav, mac)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/dreamcast/wav2adpcm", + "args": ["-t", "../../gta3/audio/CLASS.wav", "repack-data/gta3/stream/CLASS.wav"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/dreamcast", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + }, + { + "name": "wav2adpcm (mp3, mac)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/dreamcast/wav2adpcm", + "args": ["-t", "../../gta3/audio/JB.mp3", "repack-data/gta3/stream/JB.wav"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/dreamcast", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + }, + { + "name": "texconv (mac)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/dreamcast/texconv", + "args": ["repack-data/img-orig/IslandLODcomSUB.dff", "repack-data/img-dc/IslandLODcomSUB.dff"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/dreamcast", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + }, + { + "name": "texconv", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/dreamcast/texconv", + "args": ["../../gta3/models/fonts.txd", "repack/gta3/models/fonts.txd", "32", "32"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/dreamcast", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, + { "MIMode": "gdb", "args": [], diff --git a/.vscode/settings.json b/.vscode/settings.json index 98870ba1..82b6d2fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -32,5 +32,114 @@ "C_Cpp.vcFormat.space.pointerReferenceAlignment": "right", "cSpell.enabled": false, "files.trimFinalNewlines": false, - "files.trimTrailingWhitespace": false + "files.trimTrailingWhitespace": false, + "files.associations": { + "system_error": "cpp", + "xlocale": "cpp", + "xiosbase": "cpp", + "memory": "cpp", + "mutex": "cpp", + "xmemory": "cpp", + "deque": "cpp", + "initializer_list": "cpp", + "list": "cpp", + "queue": "cpp", + "type_traits": "cpp", + "vector": "cpp", + "xhash": "cpp", + "xstring": "cpp", + "xutility": "cpp", + "iosfwd": "cpp", + "algorithm": "cpp", + "atomic": "cpp", + "iterator": "cpp", + "xtree": "cpp", + "ios": "cpp", + "*.inc": "cpp", + "cstdlib": "cpp", + "ratio": "cpp", + "array": "cpp", + "functional": "cpp", + "tuple": "cpp", + "utility": "cpp", + "*.tcc": "cpp", + "unordered_map": "cpp", + "chrono": "cpp", + "bit": "cpp", + "cctype": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "exception": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "random": "cpp", + "string_view": "cpp", + "fstream": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "typeinfo": "cpp", + "codecvt": "cpp", + "iomanip": "cpp", + "format": "cpp", + "__locale": "cpp", + "optional": "cpp", + "span": "cpp", + "__bit_reference": "cpp", + "__config": "cpp", + "__hash_table": "cpp", + "__node_handle": "cpp", + "__split_buffer": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__verbose_abort": "cpp", + "bitset": "cpp", + "charconv": "cpp", + "complex": "cpp", + "execution": "cpp", + "locale": "cpp", + "variant": "cpp", + "text_encoding": "cpp", + "stack": "cpp", + "unordered_set": "cpp", + "shared_mutex": "cpp", + "forward_list": "cpp", + "cholesky": "cpp", + "core": "cpp", + "eigenvalues": "cpp", + "geometry": "cpp", + "householder": "cpp", + "jacobi": "cpp", + "lu": "cpp", + "qr": "cpp", + "svd": "cpp", + "print": "cpp", + "strstream": "cpp", + "regex": "cpp" + } } diff --git a/README.md b/README.md index 5e1641fd..db13288a 100644 --- a/README.md +++ b/README.md @@ -1,206 +1,23 @@ -re3 logo - -[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2FGTAmodding%2Fre3%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/GTAmodding/re3/goto?ref=master) - - ## Intro -In this repository you'll find the fully reversed source code for GTA III ([master](https://github.com/halpz/re3/tree/master/) branch) and GTA VC ([miami](https://github.com/halpz/re3/tree/miami/) branch). +dca3 is a port of GTA III for the Dreamcast made by The Gang, using [re3](https://github.com/halpz/re3/tree/master/) as a base. -It has been tested and works on Windows, Linux, MacOS and FreeBSD, on x86, amd64, arm and arm64.\ -Rendering is handled either by original RenderWare (D3D8) -or the reimplementation [librw](https://github.com/aap/librw) (D3D9, OpenGL 2.1 or above, OpenGL ES 2.0 or above).\ -Audio is done with MSS (using dlls from original GTA) or OpenAL. +re3 a fully reversed source code for GTA III. -The project has also been ported to the [Nintendo Switch](https://github.com/AGraber/re3-nx/), -[Playstation Vita](https://github.com/Rinnegatamante/re3) and -[Nintendo Wii U](https://github.com/GaryOderNichts/re3-wiiu/). +This project was started by [Stefanos Kornilios Mitsis Poiitidis](https://x.com/poiitidis) and uses [KallistiOS](https://kos-docs.dreamcast.wiki/). -We cannot build for PS2 or Xbox yet. If you're interested in doing so, get in touch with us. +## Building -## Installation +dca3 requires PC game assets to work, so you **must** have GTA III. We have tested the 2cd version, others might work. -- re3 requires PC game assets to work, so you **must** own [a copy of GTA III](https://store.steampowered.com/app/12100/Grand_Theft_Auto_III/). -- Build re3 or download the latest build: - - [Windows D3D9 MSS 32bit](https://nightly.link/GTAmodding/re3/workflows/re3_msvc_x86/master/re3_Release_win-x86-librw_d3d9-mss.zip) - - [Windows D3D9 64bit](https://nightly.link/GTAmodding/re3/workflows/re3_msvc_amd64/master/re3_Release_win-amd64-librw_d3d9-oal.zip) - - [Windows OpenGL 64bit](https://nightly.link/GTAmodding/re3/workflows/re3_msvc_amd64/master/re3_Release_win-amd64-librw_gl3_glfw-oal.zip) - - [Linux 64bit](https://nightly.link/GTAmodding/re3/workflows/build-cmake-conan/master/ubuntu-18.04-gl3.zip) - - [MacOS 64bit x86-64](https://nightly.link/GTAmodding/re3/workflows/build-cmake-conan/master/macos-latest-gl3.zip) -- Extract the downloaded zip over your GTA 3 directory and run re3. The zip includes the binary, updated and additional gamefiles and in case of OpenAL the required dlls. - -## Screenshots - -![re3 2021-02-11 22-57-03-23](https://user-images.githubusercontent.com/1521437/107704085-fbdabd00-6cbc-11eb-8406-8951a80ccb16.png) -![re3 2021-02-11 22-43-44-98](https://user-images.githubusercontent.com/1521437/107703339-cbdeea00-6cbb-11eb-8f0b-07daa105d470.png) -![re3 2021-02-11 22-46-33-76](https://user-images.githubusercontent.com/1521437/107703343-cd101700-6cbb-11eb-9ccd-012cb90524b7.png) -![re3 2021-02-11 22-50-29-54](https://user-images.githubusercontent.com/1521437/107703348-d00b0780-6cbb-11eb-8afd-054249c2b95e.png) - -## Improvements - -We have implemented a number of changes and improvements to the original game. -They can be configured in `core/config.h`. -Some of them can be toggled at runtime, some cannot. - -* Fixed a lot of smaller and bigger bugs -* User files (saves and settings) stored in GTA root directory -* Settings stored in re3.ini file instead of gta3.set -* Debug menu to do and change various things (Ctrl-M to open) -* Debug camera (Ctrl-B to toggle) -* Rotatable camera -* XInput controller support (Windows) -* No loading screens between islands ("map memory usage" in menu) -* Skinned ped support (models from Xbox or Mobile) -* Rendering - * Widescreen support (properly scaled HUD, Menu and FOV) - * PS2 MatFX (vehicle reflections) - * PS2 alpha test (better rendering of transparency) - * PS2 particles - * Xbox vehicle rendering - * Xbox world lightmap rendering (needs Xbox map) - * Xbox ped rim light - * Xbox screen rain droplets - * More customizable colourfilter -* Menu - * Map - * More options - * Controller configuration menu - * ... -* Can load DFFs and TXDs from other platforms, possibly with a performance penalty -* ... - -## To-Do - -The following things would be nice to have/do: - -* Fix physics for high FPS -* Improve performance on lower end devices, especially the OpenGL layer on the Raspberry Pi (if you have experience with this, please get in touch) -* Compare code with PS2 code (tedious, no good decompiler) -* [PS2 port](https://web.archive.org/web/20210217192931/https://github.com/GTAmodding/re3/wiki/PS2-port) -* Xbox port (not quite as important) -* reverse remaining unused/debug functions -* compare CodeWarrior build with original binary for more accurate code (very tedious) - -## Modding - -Asset modifications (models, texture, handling, script, ...) should work the same way as with original GTA for the most part. - -CLEO scripts work with [CLEO Redux](https://github.com/cleolibrary/CLEO-Redux). - -Mods that make changes to the code (dll/asi, limit adjusters) will *not* work. -Some things these mods do are already implemented in re3 (much of SkyGFX, GInput, SilentPatch, Widescreen fix), -others can easily be achieved (increasing limis, see `config.h`), -others will simply have to be rewritten and integrated into the code directly. -Sorry for the inconvenience. - -## Building from Source - -When using premake, you may want to point GTA_III_RE_DIR environment variable to GTA3 root folder if you want the executable to be moved there via post-build script. - -Clone the repository with `git clone --recursive https://github.com/halpz/re3.git`. Then `cd re3` into the cloned repository. - -
Linux Premake - -For Linux using premake, proceed: [Building on Linux](https://web.archive.org/web/20210217192751/https://github.com/GTAmodding/re3/wiki/Building-on-Linux) - -
- -
Linux Conan - -Install python and conan, and then run build. -``` -conan export vendor/librw librw/master@ -mkdir build -cd build -conan install .. re3/master@ -if build -o re3:audio=openal -o librw:platform=gl3 -o librw:gl3_gfxlib=glfw --build missing -s re3:build_type=RelWithDebInfo -s librw:build_type=RelWithDebInfo -conan build .. -if build -bf build -pf package -``` -
- -
MacOS Premake - -For MacOS using premake, proceed: [Building on MacOS](https://web.archive.org/web/20210717004757/https://github.com/GTAmodding/re3/wiki/Building-on-MacOS) - -
- -
FreeBSD - -For FreeBSD using premake, proceed: [Building on FreeBSD](https://web.archive.org/web/20210217192740/https://github.com/GTAmodding/re3/wiki/Building-on-FreeBSD) - -
- -
Windows - -Assuming you have Visual Studio 2015/2017/2019: -- Run one of the `premake-vsXXXX.cmd` variants on root folder. -- Open build/re3.sln with Visual Studio and compile the solution. - -Microsoft recently discontinued its downloads of the DX9 SDK. You can download an archived version here: https://archive.org/details/dxsdk_jun10 - -**If you choose OpenAL on Windows** You must read [Running OpenAL build on Windows](https://web.archive.org/web/20210217192855/https://github.com/GTAmodding/re3/wiki/Running-OpenAL-build-on-Windows). -
- -> :information_source: premake has an `--with-lto` option if you want the project to be compiled with Link Time Optimization. - -> :information_source: There are various settings in [config.h](https://github.com/halpz/re3/tree/master/src/core/config.h), you may want to take a look there. - -> :information_source: re3 uses completely homebrew RenderWare-replacement rendering engine; [librw](https://github.com/aap/librw/). librw comes as submodule of re3, but you also can use LIBRW enviorenment variable to specify path to your own librw. - -If you feel the need, you can also use CodeWarrior 7 to compile re3 using the supplied codewarrior/re3.mcp project - this requires the original RW33 libraries, and the DX8 SDK. The build is unstable compared to the MSVC builds though, and is mostly meant to serve as a reference. - -## Contributing -As long as it's not linux/cross-platform skeleton/compatibility layer, all of the code on the repo that's not behind a preprocessor condition(like FIX_BUGS) are **completely** reversed code from original binaries. - -We **don't** accept custom codes, as long as it's not wrapped via preprocessor conditions, or it's linux/cross-platform skeleton/compatibility layer. - -We accept only these kinds of PRs; - -- A new feature that exists in at least one of the GTAs (if it wasn't in III/VC then it doesn't have to be decompilation) -- Game, UI or UX bug fixes (if it's a fix to original code, it should be behind FIX_BUGS) -- Platform-specific and/or unused code that's not been reversed yet -- Makes reversed code more understandable/accurate, as in "which code would produce this assembly". -- A new cross-platform skeleton/compatibility layer, or improvements to them -- Translation fixes, for languages original game supported -- Code that increase maintainability - -We have a [Coding Style](https://github.com/halpz/re3/blob/master/CODING_STYLE.md) document that isn't followed or enforced very well. - -Do not use features from C++11 or later. - - -## History - -re3 was started sometime in the spring of 2018, -initially as a way to test reversed collision and physics code -inside the game. -This was done by replacing single functions of the game -with their reversed counterparts using a dll. - -After a bit of work the project lay dormant for about a year -and was picked up again and pushed to github in May 2019. -At the time I (aap) had reversed around 10k lines of code and estimated -the final game to have around 200-250k. -Others quickly joined the effort (Fire_Head, shfil, erorcun and Nick007J -in time order, and Serge a bit later) and we made very quick progress -throughout the summer of 2019 -after which the pace slowed down a bit. - -Due to everyone staying home during the start of the Corona pandemic -everybody had a lot of time to work on re3 again and -we finally got a standalone exe in April 2020 (around 180k lines by then). - -After the initial excitement and fixing and polishing the code further, -reVC was started in early May 2020 by starting from re3 code, -not by starting from scratch replacing functions with a dll. -After a few months of mostly steady progress we considered reVC -finished in December. - -Since then we have started reLCS, which is currently work in progress. +You need to have installed: +- GTA III PC +- [DreamSDK R3](https://github.com/dreamsdk/dreamsdk/releases/tag/r3-3.0.4.2311) +- more instructions to follow soon ## License -We don't feel like we're in a position to give this code a license.\ The code should only be used for educational, documentation and modding purposes.\ We do not encourage piracy or commercial use.\ -Please keep derivate work open source and give proper credit. +Please keep derivate work open source and give proper credit. \ No newline at end of file diff --git a/dreamcast/GTA3SF8.b b/dreamcast/GTA3SF8.b new file mode 100644 index 0000000000000000000000000000000000000000..8f09a034a2bfd256d210c277e509361aaed410af GIT binary patch literal 40342 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 literal 0 HcmV?d00001 diff --git a/dreamcast/Makefile b/dreamcast/Makefile new file mode 100644 index 00000000..07397f5b --- /dev/null +++ b/dreamcast/Makefile @@ -0,0 +1,489 @@ +# +# Basic KallistiOS skeleton / test program +# Copyright (C)2001-2004 Megan Potter +# + +# Put the filename of the output binary here +PROJECT_NAME = dca3 +TARGET ?= dca3.elf + +HAVE_CDI4DC := $(shell which cdi4dc > /dev/null 2>&1 && echo "yes" || echo "no") +IS_MAC := $(shell uname -s | grep -i "darwin" > /dev/null && echo "yes" || echo "no") + +MOD_NAME?= + +GTA_DIR?=../../gta3 +GTA_MOD_DIR?=../../gta3_mod$(MOD_NAME) +GTA_MOD_IMG_DIR?=$(GTA_MOD_DIR)/img +GTA_MOD_SFX_DIR?=$(GTA_MOD_DIR)/sfx +GTA_MOD_LOOSE_DIR?=$(GTA_MOD_DIR)/loose + +REPACK_DIR?=repack-data +REPACK_GTA_DIR?=$(REPACK_DIR)/gta3 +REPACK_IMG_ORIG_DIR?=$(REPACK_DIR)/img-orig +REPACK_IMG_DC_DIR?=$(REPACK_DIR)/img-dc +REPACK_SFX_ORIG_DIR?=$(REPACK_DIR)/sfx-orig +REPACK_SFX_DC_DIR?=$(REPACK_DIR)/sfx-dc +REPACK_STREAM_DECODED_DIR?=$(REPACK_DIR)/stream-decoded + +LIBS := +TEXCONV_FLAGS := + +ifeq ($(IS_MAC), yes) +TEXCONV_FLAGS += -DMACOS64 +endif + +AUDIO_STREAM_OPTION=-t +MKDCDISC_PAD_OPTION=-N +ifeq ($(FOR_DISC),1) +AUDIO_STREAM_OPTION=-q +MKDCDISC_PAD_OPTION= +endif + +include common.mk + +OBJS = $(RE3_OBJS) $(RW_OBJS) \ + ../src/audio/sampman_dc.o \ + ../src/prof/profiler.o + +OBJS_TEXCONV = $(RW_OBJS:.o=.texconv.o) +OBJS_TEXCONV += \ + ../vendor/koshle/hlekos.texconv.o \ + ../vendor/koshle/hlepvr_mem.texconv.o \ + ../vendor/koshle/hlepvr_prim.texconv.o \ + ../vendor/koshle/hlepvr_scene.texconv.o \ + ../vendor/koshle/hlepvr_misc.texconv.o \ + ../vendor/koshle/hlepvr_init_term.texconv.o \ + ../vendor/koshle/hlepvr_buffers.texconv.o \ + ../vendor/koshle/hlepvr_irq.texconv.o \ + ../vendor/koshle/hlematrix3d.texconv.o \ + ../vendor/librw/src/dc/vq.texconv.o \ + ../src/fakerw/fake.texconv.o \ + ../src/skel/crossplatform.texconv.o \ + ../src/rw/TxdStore.texconv.o \ + texconv.texconv.o \ + ../vendor/TriStripper/src/connectivity_graph.texconv.o \ + ../vendor/TriStripper/src/policy.texconv.o \ + ../vendor/TriStripper/src/tri_stripper.texconv.o \ + ../src/rw/VisibilityPlugins.texconv.o \ + ../src/rw/NodeName.texconv.o \ + ../src/animation/RpAnimBlend.texconv.o \ + ../src/animation/Bones.texconv.o \ + ../src/animation/AnimBlendAssociation.texconv.o \ + ../src/animation/AnimBlendNode.texconv.o \ + ../src/animation/AnimBlendClumpData.texconv.o \ + ../src/rw/MemoryMgr.texconv.o \ + ../src/math/Quaternion.texconv.o \ + ../vendor/librw/src/d3d-x/d3d.texconv.o \ + ../vendor/librw/src/d3d-x/d3d8.texconv.o \ + ../vendor/librw/src/d3d-x/d3d8render.texconv.o \ + +# Add compilation units to this list to explicity compile them with +# -O3 optimizations, while the rest get the default (-Os) treatment +# to conserve RAM. +OBJS_O3 = \ + ../vendor/librw/src/dc/rwdc.o \ + ../src/core/World.o \ + ../src/collision/Collision.o \ + ../src/math/math.o \ + ../src/math/Matrix.o \ + ../src/math/Quaternion.o \ + ../src/math/Rect.o \ + ../src/math/Vector.o \ + ../vendor/librw/src/base.o \ + ../src/renderer/Shadows.o + +OBJS_NO_FAST_MATH = \ + ../src/core/Cam.o \ + ../src/core/Camera.o + +KOS_CPPFLAGS += -fbuiltin -ffast-math -ffp-contract=fast \ + -mfsrra -mfsca + +# The rm-elf step is to remove the target before building, to force the +# re-creation of the rom disk. +all: $(TARGET) + +include $(KOS_BASE)/Makefile.rules + +DEPS = $(OBJS:.o=.d) $(OBJS_TEXCONV:.o:.d) + +CXXFLAGS += $(if $(WITH_32MB),-O3,-Os) \ + $(if $(WITH_IDE),-DWITH_IDE) \ + $(if $(WITH_PROF),-DWITH_PROF=\"$(WITH_PROF)\") \ + -MMD -MP -ffunction-sections -fdata-sections -ffast-math \ + -fmerge-all-constants -fomit-frame-pointer -ml -std=gnu++20 \ + -fno-exceptions -fno-rtti -flto=auto -fipa-pta -Wno-write-strings \ + -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion \ + -Wno-multichar -Wno-unused-value -Wno-char-subscripts -Wno-reorder \ + -Wno-unused-function -Wno-class-memaccess -fno-permissive \ + -fno-asynchronous-unwind-tables -fno-enforce-eh-specs -fno-non-call-exceptions \ + -fno-strict-aliasing -fwrapv + +clean-texconv: + -rm -f $(OBJS_TEXCONV) + -rm -f texconv + +clean-pvrtex: + $(MAKE) -C ./pvrtex clean + -rm -f ./pvrtex/pvrtex + +clean-objs: + -rm -f $(OBJS) + +clean: + -rm -f $(OBJS) + -rm -f $(OBJS_TEXCONV) + -rm -f $(TARGET) + -rm -f $(TARGET).bin + -rm -f 1ST_READ.BIN + -rm -f IP.BIN + -rm -f $(PROJECT_NAME).iso + -rm -f $(PROJECT_NAME).ds.iso + -rm -f $(PROJECT_NAME).cdi + -rm -f $(DEPS) + -rm -rf $(REPACK_DIR) + -rm -rf analyze-profile + +$(OBJS_O3): %.o: %.cpp + kos-c++ $(CXXFLAGS) $(CPPFLAGS) -O3 -c $< -o $@ + +$(OBJS_NO_FAST_MATH): %.o: %.cpp + kos-c++ $(CXXFLAGS) $(CPPFLAGS) -O3 -c $< -o $@ -fno-fast-math + +$(TARGET): $(OBJS) + kos-c++ -o $(TARGET) $(OBJS) -Wl,--gc-sections -Wl,--as-needed -Wl,-Map,output.map \ + -flto=auto $(if $(WITH_IDE),-lkosfat) $(if $(WITH_SD),-lkosfat) + @echo && echo && echo "*** Build Completed Successfully ***" && echo && echo + +run: $(TARGET) + $(KOS_LOADER) $(TARGET) + +$(REPACK_GTA_DIR)/GTA3SF8.b: GTA3SF8.b + mkdir -p $(@D) + cp $< $@ + +1ST_READ.BIN: $(TARGET) + rm -f $(TARGET).bin + rm -f 1ST_READ.BIN + kos-objcopy -R .stack -O binary $(TARGET) $(TARGET).bin + $(KOS_BASE)/utils/scramble/scramble $(TARGET).bin 1ST_READ.BIN + +.PHONY: pvrtex + +pvrtex: + $(MAKE) -C ./pvrtex + +IP.BIN: + rm -f IP.BIN + $(KOS_BASE)/utils/makeip/makeip ip.txt IP.BIN + +$(PROJECT_NAME).iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b + rm -f $(PROJECT_NAME).iso + rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN + cp 1ST_READ.BIN $(REPACK_GTA_DIR) + mkisofs -C 0,11702 -V $(PROJECT_NAME) -G IP.BIN -r -J -l -o $(PROJECT_NAME).iso $(REPACK_GTA_DIR) + +$(PROJECT_NAME)-no-repack.iso: IP.BIN 1ST_READ.BIN $(REPACK_GTA_DIR)/GTA3SF8.b + rm -f $(PROJECT_NAME)-no-repack.iso + rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN + cp 1ST_READ.BIN $(REPACK_GTA_DIR) + mkisofs -C 0,11702 -V $(PROJECT_NAME) -G IP.BIN -r -J -l -o $(PROJECT_NAME)-no-repack.iso $(REPACK_GTA_DIR) + + +$(PROJECT_NAME).ds.iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b + rm -f $(PROJECT_NAME).ds.iso + rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN + cp $(TARGET).bin $(REPACK_GTA_DIR)/1ST_READ.BIN + mkisofs -V $(PROJECT_NAME) -G IP.BIN -r -J -l -o $(PROJECT_NAME).ds.iso $(REPACK_GTA_DIR) + +1ST_READ_PREBUILT.BIN: + kos-objcopy -R .stack -O binary $(TARGET) $(TARGET)-prebuilt.bin + $(KOS_BASE)/utils/scramble/scramble $(TARGET)-prebuilt.bin 1ST_READ_PREBUILT.BIN + mkdir -p $(REPACK_GTA_DIR) + +$(PROJECT_NAME)-prebuilt.iso: IP.BIN 1ST_READ_PREBUILT.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b + rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN + cp 1ST_READ_PREBUILT.BIN $(REPACK_GTA_DIR)/1ST_READ.BIN + mkisofs -C 0,11702 -V $(PROJECT_NAME) -G IP.BIN -r -J -l -o $(PROJECT_NAME).iso $(REPACK_GTA_DIR) + +ifeq ($(HAVE_CDI4DC), yes) +$(PROJECT_NAME).cdi: $(PROJECT_NAME).iso + cdi4dc $(PROJECT_NAME).iso $(PROJECT_NAME).cdi > cdi.log + +$(PROJECT_NAME)-no-repack.cdi: $(PROJECT_NAME)-no-repack.iso + cdi4dc $(PROJECT_NAME)-no-repack.iso $(PROJECT_NAME)-no-repack.cdi > cdi.log + +$(PROJECT_NAME)-prebuilt.cdi: $(PROJECT_NAME)-prebuilt.iso + cdi4dc $(PROJECT_NAME).iso $(PROJECT_NAME).cdi > cdi.log + rm 1ST_READ_PREBUILT.BIN +else +$(PROJECT_NAME).cdi: $(TARGET) $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME).cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n DCA3 -a "the gang" + +$(PROJECT_NAME)-no-repack.cdi: $(TARGET) $(REPACK_GTA_DIR)/GTA3SF8.b + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME)-no-repack.cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n DCA3 -a "the gang" + +$(PROJECT_NAME)-prebuilt.cdi: $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME).cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n DCA3 -a "the gang" +endif + +cdi: $(PROJECT_NAME).cdi + +cdi-no-repack: $(PROJECT_NAME)-no-repack.cdi + +dsiso: $(PROJECT_NAME).ds.iso + +cdi-prebuilt: $(PROJECT_NAME)-prebuilt.cdi + +sim: $(REPACK_DIR)/repacked + $(MAKE) -f sim.mk + +run-flycast: + flycast ./$(PROJECT_NAME).cdi + +repack-dc: $(REPACK_DIR)/repacked + +gprof: + @sh-elf-gprof $(TARGET) $(REPACK_DIR)/kernel_gmon_1.out > gprof.out + @cat gprof.out | gprof2dot | dot -Tpng -o $(TARGET)-kernel.png + @-rm -rf gprof.out + @echo "\033[42m Profiling data saved to $(TARGET)-kernel.png \033[0m" + +# tools +imgtool: imgtool.cpp + $(CXX) -std=c++17 -o $@ -Og $< + +extract-sfx: extract-sfx.cpp + $(CXX) -std=c++17 -o $@ -Og $< + +pack-sfx: pack-sfx.cpp + $(CXX) -std=c++17 -o $@ -Og $< + +aud2adpcm: aud2adpcm.c + $(CC) -o $@ -O3 -g $< -I../vendor/minimp3 + +texconv: $(OBJS_TEXCONV) | pvrtex # You'll have to rebuild pvrtex manually if you change it + $(CXX) -o $@ $(OBJS_TEXCONV) + +%.texconv.o: %.cpp + $(CXX) -std=c++2a -c -O3 -g -MMD -MP -o $@ -I../vendor/koshle $(INCLUDE) -I../vendor/emu -I../vendor/crypto -I../vendor/TriStripper/include $(DEFINES) -DDC_TEXCONV -DDC_SIM -D_INC_WINDOWS $(TEXCONV_FLAGS) $< + +-include $(DEPS) + +#### Repacking #### + +TXD_OPTS_fonts = 256 256 +TXD_OPTS_hud = 128 128 +TXD_OPTS_menu = 512 512 +TXD_OPTS_LOADSC0 = 512 512 +TXD_OPTS_LOADSC1 = 512 512 +TXD_OPTS_LOADSC10 = 512 512 +TXD_OPTS_LOADSC11 = 512 512 +TXD_OPTS_LOADSC12 = 512 512 +TXD_OPTS_LOADSC13 = 512 512 +TXD_OPTS_LOADSC14 = 512 512 +TXD_OPTS_LOADSC15 = 512 512 +TXD_OPTS_LOADSC16 = 512 512 +TXD_OPTS_LOADSC17 = 512 512 +TXD_OPTS_LOADSC18 = 512 512 +TXD_OPTS_LOADSC19 = 512 512 +TXD_OPTS_LOADSC2 = 512 512 +TXD_OPTS_LOADSC20 = 512 512 +TXD_OPTS_LOADSC21 = 512 512 +TXD_OPTS_LOADSC22 = 512 512 +TXD_OPTS_LOADSC23 = 512 512 +TXD_OPTS_LOADSC24 = 512 512 +TXD_OPTS_LOADSC25 = 512 512 +TXD_OPTS_LOADSC3 = 512 512 +TXD_OPTS_LOADSC4 = 512 512 +TXD_OPTS_LOADSC5 = 512 512 +TXD_OPTS_LOADSC6 = 512 512 +TXD_OPTS_LOADSC7 = 512 512 +TXD_OPTS_LOADSC8 = 512 512 +TXD_OPTS_LOADSC9 = 512 512 +TXD_OPTS_mainsc1 = 512 512 +TXD_OPTS_mainsc2 = 512 512 +TXD_OPTS_NEWS = 512 512 +TXD_OPTS_SPLASH1 = 512 512 +TXD_OPTS_SPLASH2 = 512 512 +TXD_OPTS_SPLASH3 = 512 512 +DEFAULT_RES = 512 + +PVR_ENCODER ?= PVRTEX +TEXTURE_DOWNSAMPLE_TXD ?= NONE +TEXTURE_DOWNSAMPLE_IMG ?= HALF + +-include texlist.mk +-include modlist.mk +-include gta3files.mk +-include sfxlist.mk +-include sfxlooplist.mk +-include wavlist.mk +-include mp3list.mk + +IMG_TEXTURES_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_TEXTURES)) +IMG_MODELS_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_MODELS)) +LOOSE_FILES_DC = $(addprefix $(REPACK_GTA_DIR)/, $(MISC_FILES)) +SFX_DC_DIR = $(REPACK_GTA_DIR)/sfx +SFX_DC_RAW = $(SFX_DC_DIR)/sfx_all.raw +SFX_DC_DSC = $(SFX_DC_DIR)/sfx_all.dsc +STREAM_ADPCM_DC = $(addprefix $(REPACK_GTA_DIR)/stream/, $(STREAM_WAV:.wav=.APM)) \ + $(addprefix $(REPACK_GTA_DIR)/stream/, $(STREAM_MP3:.mp3=.APM)) + +IMG_TEXTURES_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_TEXTURES)) +IMG_MODELS_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_MODELS)) + +SFX_ORIG = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_WAV)) +SFX_ORIG_LOOP = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_LOOP_WAV)) +SFX_REPACK_DC_WAV = $(addprefix $(REPACK_SFX_DC_DIR)/, $(SFX_WAV) $(SFX_LOOP_WAV)) +SFX_REPACK_DC = $(SFX_REPACK_DC_WAV:.wav=.pcm) +STREAM_MP3_DECODED = $(addprefix $(REPACK_STREAM_DECODED_DIR)/, $(STREAM_MP3:.mp3=.wav)) +STREAM_WAV_DECODED = $(addprefix $(REPACK_STREAM_DECODED_DIR)/, $(STREAM_WAV)) + +.PRECIOUS: $(SFX_ORIG) $(SFX_REPACK_DC) $(STREAM_MP3_DECODED) $(STREAM_WAV_DECODED) + +$(REPACK_DIR)/repacked: $(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir $(LOOSE_FILES_DC) $(STREAM_ADPCM_DC) $(SFX_DC_RAW) $(SFX_DC_DSC) + mkdir -p $(@D) + touch $@ + @echo && echo && echo "*** Repack Completed Successfully ***" && echo && echo + +$(REPACK_DIR)/unpacked: imgtool $(GTA_DIR)/models/gta3.img $(GTA_DIR)/models/gta3.dir + mkdir -p $(@D) + ./imgtool unpack "$(GTA_DIR)/models/gta3" "$(REPACK_IMG_ORIG_DIR)" + @touch $@ + +$(IMG_TEXTURES_ORIG) $(IMG_MODELS_ORIG): $(REPACK_DIR)/unpacked + @touch $@ + +# First try the mods img directory +$(REPACK_IMG_DC_DIR)/%.dff: $(GTA_MOD_IMG_DIR)/%.dff texconv + @mkdir -p $(@D) + ./texconv $< $@ +$(REPACK_IMG_DC_DIR)/%.DFF: $(GTA_MOD_IMG_DIR)/%.DFF texconv + @mkdir -p $(@D) + ./texconv $< $@ + +# if not, the extracted img directory +$(REPACK_IMG_DC_DIR)/%.dff: $(REPACK_IMG_ORIG_DIR)/%.dff texconv + @mkdir -p $(@D) + ./texconv $< $@ +$(REPACK_IMG_DC_DIR)/%.DFF: $(REPACK_IMG_ORIG_DIR)/%.DFF texconv + @mkdir -p $(@D) + ./texconv $< $@ + +# first try the mods img directory. NB, the textures are not resized here, unlike normal .img textures +$(REPACK_IMG_DC_DIR)/%.txd: $(GTA_MOD_IMG_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE +$(REPACK_IMG_DC_DIR)/%.TXD: $(GTA_MOD_IMG_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE + +# if not, the extracted img directory +$(REPACK_IMG_DC_DIR)/%.txd: $(REPACK_IMG_ORIG_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ $(DEFAULT_RES) $(DEFAULT_RES) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_IMG) +$(REPACK_IMG_DC_DIR)/%.TXD: $(REPACK_IMG_ORIG_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ $(DEFAULT_RES) $(DEFAULT_RES) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_IMG) + +# first try the mods loose directory +$(REPACK_GTA_DIR)/%.dff: $(GTA_MOD_LOOSE_DIR)/%.dff texconv + @mkdir -p $(@D) + ./texconv $< $@ +$(REPACK_GTA_DIR)/%.DFF: $(GTA_MOD_LOOSE_DIR)/%.DFF texconv + @mkdir -p $(@D) + ./texconv $< $@ + +#if not, the original files +$(REPACK_GTA_DIR)/%.dff: $(GTA_DIR)/%.dff texconv + @mkdir -p $(@D) + ./texconv $< $@ +$(REPACK_GTA_DIR)/%.DFF: $(GTA_DIR)/%.DFF texconv + @mkdir -p $(@D) + ./texconv $< $@ + +# first try the mods loose directory +# Note the mods loose directory is not resized, unlike the normal .txd textures +$(REPACK_GTA_DIR)/%.txd: $(GTA_MOD_LOOSE_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE +$(REPACK_GTA_DIR)/%.TXD: $(GTA_MOD_LOOSE_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE + +# if not, the original files +$(REPACK_GTA_DIR)/%.txd: $(GTA_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) +$(REPACK_GTA_DIR)/%.TXD: $(GTA_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) + +$(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC) + mkdir -p $(@D) + mkdir -p "$(REPACK_GTA_DIR)/models/gta3" + ./imgtool pack "$(REPACK_GTA_DIR)/models/gta3" "$(REPACK_IMG_DC_DIR)" + touch $@ + +$(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir: $(REPACK_DIR)/packed + touch $@ + +# sfx processing +$(REPACK_DIR)/unpacked-sfx: extract-sfx $(GTA_DIR)/audio/sfx.SDT $(GTA_DIR)/audio/sfx.RAW + mkdir -p $(@D) + mkdir -p "$(REPACK_SFX_ORIG_DIR)" + ./extract-sfx "$(GTA_DIR)/audio/sfx.SDT" "$(GTA_DIR)/audio/sfx.RAW" "$(REPACK_SFX_ORIG_DIR)" + touch $@ + +$(SFX_ORIG): $(REPACK_DIR)/unpacked-sfx + touch $@ + +$(SFX_ORIG_LOOP): $(SFX_ORIG) + @touch $@ + +# try first mods sfx directory +$(REPACK_SFX_DC_DIR)/%.pcm: $(GTA_MOD_SFX_DIR)/%.wav aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm -raw $< $@ + +# then original (extracted) sfx directory +$(REPACK_SFX_DC_DIR)/%.pcm: $(REPACK_SFX_ORIG_DIR)/%.wav aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm -raw $< $@ + +# stream processing + +# first try the mods loose directory +$(REPACK_GTA_DIR)/stream/%.APM: $(GTA_MOD_LOOSE_DIR)/audio/%.wav aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +$(REPACK_GTA_DIR)/stream/%.APM: $(GTA_MOD_LOOSE_DIR)/audio/%.mp3 aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +# then original folder +$(REPACK_GTA_DIR)/stream/%.APM: $(GTA_DIR)/audio/%.wav aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +$(REPACK_GTA_DIR)/stream/%.APM: $(GTA_DIR)/audio/%.mp3 aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +# Note: This is last so it has least priority, files should be processed if possible +$(REPACK_GTA_DIR)/%: $(GTA_DIR)/% + @mkdir -p $(@D) + cp $< $@ + +$(SFX_DC_RAW): pack-sfx $(SFX_REPACK_DC) + mkdir -p $(@D) + ./pack-sfx "$(GTA_DIR)/audio/sfx.SDT" $(SFX_DC_RAW) $(SFX_DC_DSC) $(REPACK_SFX_DC_DIR) + + $(SFX_DC_DSC): $(SFX_DC_RAW) + ls -l $@ + +analyze-profile: analyze-profile.cpp + $(CXX) -std=c++17 -O3 analyze-profile.cpp -o analyze-profile diff --git a/dreamcast/analyze-profile.cpp b/dreamcast/analyze-profile.cpp new file mode 100644 index 00000000..51c99e90 --- /dev/null +++ b/dreamcast/analyze-profile.cpp @@ -0,0 +1,569 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // For demangling + +#define SAMPLE_PERCENTAGE_THRESHOLD 0.1 // Threshold for including functions + +struct GmonHeader { + char cookie[4]; // 'g','m','o','n' + int32_t version; // 1 + char spare[12]; // Padding +}; + +struct GmonArc { + uint32_t from_pc; // Address within caller's body + uint32_t self_pc; // Address within callee's body + uint32_t count; // Number of arc traversals +}; + +struct FunctionInfo { + std::string mangled_name; // Mangled function name + std::string demangled_name; // Demangled function name + uint32_t start_address; + uint32_t end_address; // Will be updated as we parse the source file + uint64_t sample_count; + double sample_percentage; // Percentage of total samples + uint64_t samples_with_children; // Total samples including callees + std::unordered_map callers; // Set of caller addresses +}; + +// Updated demangle_symbol function using abi::__cxa_demangle +std::string demangle_symbol(const std::string& mangled_name) { + static std::unordered_map demangle_cache; + + // Check if the name is already in the cache + auto cache_it = demangle_cache.find(mangled_name); + if (cache_it != demangle_cache.end()) { + return cache_it->second; + } + + int status = 0; + char* demangled = abi::__cxa_demangle(mangled_name.c_str(), nullptr, nullptr, &status); + std::string demangled_name; + if (status == 0 && demangled != nullptr) { + demangled_name = demangled; + free(demangled); + } else { + // If demangling fails, return the mangled name + demangled_name = mangled_name; + } + demangle_cache[mangled_name] = demangled_name; + return demangled_name; +} + +int main(int argc, char* argv[]) { + // Enable synchronization between C++ and C I/O + std::ios::sync_with_stdio(false); + + // Check for the required arguments + if (argc < 3) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + const char* gmon_filename = argv[1]; // Profiler output file + const char* source_filename = argv[2]; // Source dump file + + std::cout << "[*] Starting profiling data analysis..." << std::endl; + + // Open the profiler output file + std::cout << "[*] Reading profiler output file: " << gmon_filename << std::endl; + std::ifstream infile(gmon_filename, std::ios::binary); + if (!infile) { + std::cerr << "Error opening file " << gmon_filename << std::endl; + return 1; + } + + // Read GmonHeader + GmonHeader header; + infile.read(reinterpret_cast(&header), sizeof(GmonHeader)); + + // Verify the header + if (std::strncmp(header.cookie, "gmon", 4) != 0) { + std::cerr << "Invalid file format: missing 'gmon' cookie" << std::endl; + return 1; + } + + // Collect arc records + std::vector arcs; + while (infile.peek() != EOF) { + uint8_t tag; + infile.read(reinterpret_cast(&tag), sizeof(tag)); + + if (infile.eof()) { + break; + } + + if (tag == 1) { + // Arc record + GmonArc arc; + infile.read(reinterpret_cast(&arc), sizeof(GmonArc)); + arcs.push_back(arc); + } else if (tag == 0) { + // Histogram data: skip it + // Read GmonHistHeader to get hist_size + struct { + uint32_t low_pc; + uint32_t high_pc; + uint32_t hist_size; + uint32_t prof_rate; + char dimen[15]; + char dimen_abbrev; + } hist_header; + + infile.read(reinterpret_cast(&hist_header), sizeof(hist_header)); + + // Skip the histogram bins + infile.ignore(hist_header.hist_size * sizeof(uint16_t)); + } else { + std::cerr << "Unknown tag encountered: " << static_cast(tag) << std::endl; + break; + } + } + infile.close(); + std::cout << "[*] Profiler data reading completed." << std::endl; + + // Create a mapping from self_pc to count + std::unordered_map address_counts; + uint64_t total_samples = 0; + for (const auto& arc : arcs) { + // Sum counts for each address + address_counts[arc.self_pc] += arc.count; + total_samples += arc.count; + } + std::cout << "[*] Total samples collected: " << total_samples << std::endl; + + // Open the source dump file + std::cout << "[*] Processing source dump file: " << source_filename << std::endl; + std::ifstream source_file(source_filename); + if (!source_file) { + std::cerr << "Error opening source dump file " << source_filename << std::endl; + return 1; + } + + // Prepare the output annotated file + std::string output_filename = std::string(source_filename) + ".annotated"; + std::ofstream annotated_file(output_filename); + if (!annotated_file) { + std::cerr << "Error creating annotated file " << output_filename << std::endl; + return 1; + } + + // First pass: Identify functions and their ranges + std::cout << "[*] Identifying functions and their address ranges..." << std::endl; + std::string line; + FunctionInfo current_function; + current_function.start_address = 0; + current_function.end_address = 0; + current_function.sample_count = 0; + + std::vector functions; + uint32_t last_address = 0; + while (std::getline(source_file, line)) { + // Regular expression to match function headers + // Pattern: address : + std::regex function_header_regex(R"(^\s*([0-9a-fA-F]+)\s+<_(.*)>:)"); + + std::smatch match; + if (std::regex_search(line, match, function_header_regex)) { + uint32_t address = 0; + std::istringstream addr_ss(match[1]); + addr_ss >> std::hex >> address; + + std::string function_name = match[2]; + + // Use address as name if function_name is empty + if (function_name.empty()) { + function_name = "0x" + match[1].str(); + } + + // Finish the previous function + if (current_function.start_address != 0) { + current_function.end_address = last_address + 1; + functions.push_back(current_function); + } + + // Start a new function + current_function.mangled_name = function_name; + current_function.demangled_name = demangle_symbol(function_name); + current_function.start_address = address; + current_function.end_address = 0; + current_function.sample_count = 0; + + last_address = address; + continue; + } + + // Attempt to parse an address at the beginning of the line + std::istringstream iss(line); + std::string address_str; + iss >> address_str; + + // Remove any trailing colon from the address + if (!address_str.empty() && address_str.back() == ':') { + address_str.pop_back(); + } + + uint32_t address = 0; + std::istringstream addr_ss(address_str); + addr_ss >> std::hex >> address; + + if (!addr_ss.fail()) { + last_address = address; + } + } + + // Finish the last function + if (current_function.start_address != 0) { + current_function.end_address = UINT32_MAX; // Assume it goes to the end + functions.push_back(current_function); + } + std::cout << "[*] Function identification completed. Total functions found: " << functions.size() << std::endl; + + source_file.clear(); + source_file.seekg(0, std::ios::beg); // Reset the file stream + + // Map addresses to functions + // For efficiency, we can sort the functions and use binary search + std::sort(functions.begin(), functions.end(), [](const FunctionInfo& a, const FunctionInfo& b) { + return a.start_address < b.start_address; + }); + + // Second pass: Calculate sample counts and percentages per function + std::cout << "[*] Calculating sample counts per function..." << std::endl; + for (auto& func : functions) { + func.sample_count = 0; + func.samples_with_children = 0; // Initialize + } + + // Function to find a function by address + auto find_function_by_address = [&](uint32_t address) -> FunctionInfo* { + // Use binary search since functions are sorted by start_address + auto func_it = std::upper_bound(functions.begin(), functions.end(), address, [](uint32_t addr, const FunctionInfo& func) { + return addr < func.start_address; + }); + + if (func_it != functions.begin()) { + --func_it; + if (address >= func_it->start_address && address < func_it->end_address) { + return &(*func_it); + } + } + return nullptr; // Function not found + }; + + // Calculate sample counts per function + for (const auto& addr_count : address_counts) { + uint32_t address = addr_count.first; + uint64_t count = addr_count.second; + + // Find the function containing this address + FunctionInfo* func = find_function_by_address(address); + if (func) { + func->sample_count += count; + } + } + + // Update caller information for each function + for (const auto& arc : arcs) { + FunctionInfo* func = find_function_by_address(arc.self_pc); + if (func) { + func->callers[arc.from_pc] += arc.count; + } + } + + // Calculate sample percentages per function + for (auto& func : functions) { + func.sample_percentage = (total_samples > 0) ? (func.sample_count * 100.0) / total_samples : 0.0; + } + std::cout << "[*] Sample count calculation completed." << std::endl; + + // Filter out functions with sample_percentage <= 0.1% + functions.erase(std::remove_if(functions.begin(), functions.end(), [](const FunctionInfo& func) { + return func.sample_percentage <= SAMPLE_PERCENTAGE_THRESHOLD; + }), functions.end()); + + // Build the call graph including only functions with > 0.1% sample_percentage + std::cout << "[*] Building the call graph including only significant functions..." << std::endl; + + // Map from FunctionInfo* to callees with arc counts + std::unordered_map> call_graph; + + // Process arcs to build the call graph + for (const auto& arc : arcs) { + uint32_t from_address = arc.from_pc; + uint32_t self_address = arc.self_pc; + + FunctionInfo* caller_func = find_function_by_address(from_address); + FunctionInfo* callee_func = find_function_by_address(self_address); + + if (caller_func && callee_func) { + // Both functions must be significant (sample_percentage > 0.1%) + if (caller_func->sample_percentage > SAMPLE_PERCENTAGE_THRESHOLD && + callee_func->sample_percentage > SAMPLE_PERCENTAGE_THRESHOLD) { + // Add an edge from caller to callee + call_graph[caller_func][callee_func] += arc.count; + } + } + } + std::cout << "[*] Call graph construction completed." << std::endl; + + // Recompute samples_with_children for each function + std::cout << "[*] Computing samples with children for each function..." << std::endl; + std::unordered_map samples_with_children_map; + + std::function&)> compute_samples_with_children = + [&](FunctionInfo* func, std::unordered_set& visited) -> uint64_t { + if (samples_with_children_map.count(func)) { + return samples_with_children_map[func]; + } + + if (visited.count(func)) { + // Cycle detected, avoid infinite recursion + return 0; + } + visited.insert(func); + + uint64_t total_samples = func->sample_count; + + if (call_graph.count(func)) { + for (const auto& callee_entry : call_graph[func]) { + FunctionInfo* callee = callee_entry.first; + total_samples += compute_samples_with_children(callee, visited); + } + } + + visited.erase(func); + samples_with_children_map[func] = total_samples; + return total_samples; + }; + + for (auto& func : functions) { + std::unordered_set visited; + func.samples_with_children = compute_samples_with_children(&func, visited); + } + std::cout << "[*] Samples with children computation completed." << std::endl; + + // Generate the call graph in DOT format + std::cout << "[*] Generating the call graph DOT file..." << std::endl; + std::string dot_filename = std::string(source_filename) + ".graph.dot"; + std::ofstream dot_file(dot_filename); + if (!dot_file) { + std::cerr << "Error creating DOT file " << dot_filename << std::endl; + return 1; + } + + dot_file << "digraph CallGraph {\n"; + dot_file << " node [shape=box];\n"; + + // Write nodes with labels including samples_with_children%(samples_self%) + for (FunctionInfo* func_ptr = functions.data(); func_ptr != functions.data() + functions.size(); ++func_ptr) { + FunctionInfo& func = *func_ptr; + + // Prioritize demangled name; if empty, use mangled name; if empty, use address + std::string function_label; + if (!func.demangled_name.empty() && func.demangled_name != func.mangled_name) { + function_label = func.demangled_name; + } else if (!func.mangled_name.empty()) { + function_label = func.mangled_name; + } else { + std::ostringstream oss; + oss << "0x" << std::hex << func.start_address; + function_label = oss.str(); + } + + // Label format: samples_with_children%(samples_self%) + double samples_with_children_percentage = (total_samples > 0) ? (func.samples_with_children * 100.0) / total_samples : 0.0; + double samples_self_percentage = (total_samples > 0) ? (func.sample_count * 100.0) / total_samples : 0.0; + + std::ostringstream label; + label << function_label << "\\n"; + label << std::fixed << std::setprecision(2) << samples_with_children_percentage << "%"; + label << " (" << samples_self_percentage << "%)"; + + // Node identifier can be function's start_address + std::ostringstream node_id_stream; + node_id_stream << "0x" << std::hex << func.start_address; + std::string node_id = node_id_stream.str(); + + dot_file << " \"" << node_id << "\" [label=\"" << label.str() << "\"];\n"; + } + + // Write edges with arc counts + for (const auto& caller_entry : call_graph) { + FunctionInfo* caller_func = caller_entry.first; + std::ostringstream caller_id_stream; + caller_id_stream << "0x" << std::hex << caller_func->start_address; + std::string caller_id = caller_id_stream.str(); + + for (const auto& callee_entry : caller_entry.second) { + FunctionInfo* callee_func = callee_entry.first; + uint64_t arc_count = callee_entry.second; + + std::ostringstream callee_id_stream; + callee_id_stream << "0x" << std::hex << callee_func->start_address; + std::string callee_id = callee_id_stream.str(); + + // Edge from caller_func to callee_func with label arc_count + dot_file << " \"" << caller_id << "\" -> \"" << callee_id << "\" [label=\"" << arc_count << "\"];\n"; + } + } + + dot_file << "}\n"; + dot_file.close(); + std::cout << "[*] Call graph DOT file generated: " << dot_filename << std::endl; + + // Third pass: Annotate the source file + std::cout << "[*] Annotating the source file..." << std::endl; + source_file.clear(); + source_file.seekg(0, std::ios::beg); // Reset the file stream + FunctionInfo* current_function_ptr = nullptr; + + while (std::getline(source_file, line)) { + std::string original_line = line; // Keep the original line for output + + // Regular expression to match function headers + std::regex function_header_regex(R"(^\s*([0-9a-fA-F]+)\s+<(.*)>:)"); + + std::smatch match; + if (std::regex_search(line, match, function_header_regex)) { + // Function header line + uint32_t address = 0; + std::istringstream addr_ss(match[1]); + addr_ss >> std::hex >> address; + + // Find the function info + current_function_ptr = find_function_by_address(address); + + // Annotate the function header line if the function is significant + if (current_function_ptr && current_function_ptr->sample_percentage > SAMPLE_PERCENTAGE_THRESHOLD) { + uint32_t func_samples = current_function_ptr->sample_count; + double func_percentage = current_function_ptr->sample_percentage; + + // Prioritize demangled name; if empty, use mangled name; if empty, use address + std::string function_label; + if (!current_function_ptr->demangled_name.empty() && current_function_ptr->demangled_name != current_function_ptr->mangled_name) { + function_label = current_function_ptr->demangled_name; + } else if (!current_function_ptr->mangled_name.empty()) { + function_label = current_function_ptr->mangled_name; + } else { + std::ostringstream oss; + oss << "0x" << std::hex << current_function_ptr->start_address; + function_label = oss.str(); + } + + annotated_file << original_line << " !!!! " << func_samples << " " << std::fixed << std::setprecision(2) << func_percentage << "% @@@ " << function_label << std::endl; + } else { + // Function is not significant or not found + current_function_ptr = nullptr; + annotated_file << original_line << std::endl; + } + + continue; + } + + // Attempt to parse an address at the beginning of the line + std::istringstream iss(line); + std::string address_str; + iss >> address_str; + + // Remove any trailing colon from the address + if (!address_str.empty() && address_str.back() == ':') { + address_str.pop_back(); + } + + uint32_t address = 0; + std::istringstream addr_ss(address_str); + addr_ss >> std::hex >> address; + + if (addr_ss.fail()) { + // Not a valid address, write the line as is + annotated_file << original_line << std::endl; + continue; + } + + // For code lines within significant functions + if (current_function_ptr && current_function_ptr->sample_percentage > SAMPLE_PERCENTAGE_THRESHOLD) { + // Check if the address is in the address_counts map + auto count_it = address_counts.find(address); + if (count_it != address_counts.end()) { + uint32_t sample_count = count_it->second; + uint32_t func_samples = current_function_ptr->sample_count; + double line_percentage = (func_samples > 0) ? (sample_count * 100.0) / func_samples : 0.0; + + annotated_file << original_line << " !!!! " << sample_count << " " << std::fixed << std::setprecision(2) << line_percentage << "%" << std::endl; + } else { + // No samples for this line + annotated_file << original_line << std::endl; + } + } else { + // Not within a significant function + annotated_file << original_line << std::endl; + } + } + source_file.close(); + annotated_file.close(); + std::cout << "[*] Source file annotation completed. Annotated file: " << output_filename << std::endl; + + // Generate top functions report + std::cout << "[*] Generating top functions report..." << std::endl; + + // Sort functions by sample count in descending order + std::sort(functions.begin(), functions.end(), [](const FunctionInfo& a, const FunctionInfo& b) { + return a.sample_count > b.sample_count; + }); + + std::cout << "\nTop functions by sample count:" << std::endl; + std::cout << std::setw(6) << "Rank" + << std::setw(10) << "Samples" + << std::setw(10) << "Percent" + << std::setw(10) << "CumSum" + << " Function Name" << std::endl; + std::cout << "----------------------------------------------------------------" << std::endl; + + double cumulative_percentage = 0.0; + for (size_t i = 0; i < functions.size(); ++i) { + double percent = functions[i].sample_percentage; + cumulative_percentage += percent; + + // Prioritize demangled name; if empty, use mangled name; if empty, use address + std::string function_label; + if (!functions[i].demangled_name.empty() && functions[i].demangled_name != functions[i].mangled_name) { + function_label = functions[i].demangled_name; + } else if (!functions[i].mangled_name.empty()) { + function_label = functions[i].mangled_name; + } else { + std::ostringstream oss; + oss << "0x" << std::hex << functions[i].start_address; + function_label = oss.str(); + } + + std::cout << std::setw(6) << (i + 1) + << std::setw(10) << functions[i].sample_count + << std::setw(9) << std::fixed << std::setprecision(2) << percent << "%" + << std::setw(9) << std::fixed << std::setprecision(2) << cumulative_percentage << "%" + << " " << function_label << std::endl; + #if defined(VERBOSE_CALLERS) + for (auto &caller : functions[i].callers) { + auto func = find_function_by_address(caller.first); + std::cout << " Called by 0x" << std::hex << caller.first << " " << std::dec << caller.second << " times, " << (func ? func->demangled_name : "") << std::endl; + } + #endif + } + + std::cout << "[*] Top functions report generated." << std::endl; + std::cout << "[*] Profiling data analysis completed." << std::endl; + + return 0; +} diff --git a/dreamcast/aud2adpcm.c b/dreamcast/aud2adpcm.c new file mode 100644 index 00000000..696d6cf8 --- /dev/null +++ b/dreamcast/aud2adpcm.c @@ -0,0 +1,585 @@ +/* + aica adpcm <-> wave converter; + + (c) 2002 BERO + under GPL or notify me + + aica adpcm seems same as YMZ280B adpcm + adpcm->pcm algorithm can found MAME/src/sound/ymz280b.c by Aaron Giles + + this code is for little endian machine + + Modified by Megan Potter to read/write ADPCM WAV files, and to + handle stereo (though the stereo is very likely KOS specific + since we make no effort to interleave it). Please see README.GPL + in the KOS docs dir for more info on the GPL license. + + Modified by skmp for dca3 +*/ + +#include +#include +#include +#include +#include + +#define MINIMP3_IMPLEMENTATION +#define MINIMP3_ONLY_MP3 + +#include "minimp3_ex.h" + +static int ym_diff_lookup[16] = { + 1, 3, 5, 7, 9, 11, 13, 15, + -1, -3, -5, -7, -9, -11, -13, -15, +}; + +static int ym_index_scale[16] = { + 0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266, + 0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266 /* same value for speedup */ +}; + +static inline int limit(int val, int min, int max) { + if(val < min) return min; + else if(val > max) return max; + else return val; +} + +const int ima_step_size_table[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + +const int ima_index_table[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 +}; + +typedef struct { + int predictor; + int step_index; +} ImaAdpcmDecoder; + +void ima_initialize_decoder(ImaAdpcmDecoder *decoder, int initial_predictor, int initial_step_index) { + decoder->predictor = initial_predictor; + decoder->step_index = initial_step_index; +} + +int ima_decode_nibble(ImaAdpcmDecoder *decoder, uint8_t nibble) { + int step = ima_step_size_table[decoder->step_index]; + int diff = step >> 3; + + if (nibble & 1) diff += step >> 2; + if (nibble & 2) diff += step >> 1; + if (nibble & 4) diff += step; + + if (nibble & 8) + decoder->predictor -= diff; + else + decoder->predictor += diff; + + // Clamp the predictor to 16-bit signed values + if (decoder->predictor > 32767) + decoder->predictor = 32767; + else if (decoder->predictor < -32768) + decoder->predictor = -32768; + + // Update step_index + decoder->step_index += ima_index_table[nibble]; + if (decoder->step_index < 0) decoder->step_index = 0; + if (decoder->step_index > 88) decoder->step_index = 88; + + return decoder->predictor; +} +void ima_decode_adpcm_blocks(const uint8_t *input, int input_size, int16_t *output, size_t output_size, int channels, int block_size) { + ImaAdpcmDecoder decoders[2]; // Support for stereo + int out_index = 0; + + for (int i = 0; i + block_size <= input_size; i += block_size) { + // Read block header + int16_t predictor_left = (input[i + 1] << 8) | input[i]; // Predictor for left channel (first 2 bytes) + int step_index_left = input[i + 2]; // Step index for left channel (3rd byte) + + output[out_index++] = predictor_left; + + int16_t predictor_right = 0; + int step_index_right = 0; + + if (channels == 2) { + predictor_right = (input[i + 5] << 8) | input[i + 4]; // Predictor for right channel (4th and 5th bytes) + step_index_right = input[i + 6]; // Step index for right channel (6th byte) + + output[out_index++] = predictor_right; + } + + // Initialize decoders with block header values + ima_initialize_decoder(&decoders[0], predictor_left, step_index_left); + if (channels == 2) ima_initialize_decoder(&decoders[1], predictor_right, step_index_right); + + // Decode nibbles after the header + int start_offset = channels == 2 ? 8 : 4; // Stereo uses 8 bytes for the header, mono uses 4 + + if (channels == 2) { + for (int j = i + start_offset; j < i + block_size; j+= 8) { + for(int k=j; k> 4); + output[out_index++] = ima_decode_nibble(&decoders[1], input[k + 4] >> 4); + if (output_size*2 == out_index) return; + } + } + } else { + for (int j = i + start_offset; j < i + block_size; j++) { + uint8_t byte = input[j]; + // Mono: Decode both nibbles for the single channel + output[out_index++] = ima_decode_nibble(&decoders[0], byte & 0x0F); // Low nibble + output[out_index++] = ima_decode_nibble(&decoders[0], byte >> 4); // High nibble + if (output_size*2 == out_index) return; + } + } + } +} + +#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) + +static inline int16_t ymz_step(uint8_t step, int16_t *history, int16_t *step_size) { + static const int step_table[8] = { + 230, 230, 230, 230, 307, 409, 512, 614 + }; + + int sign = step & 8; + int delta = step & 7; + int diff = ((1 + (delta << 1)) * *step_size) >> 3; + int newval = *history; + int nstep = (step_table[delta] * *step_size) >> 8; + + /* Only found in the official AICA encoder + but it's possible all chips (including ADPCM-B) does this. */ + diff = CLAMP(diff, 0, 32767); + if(sign > 0) + newval -= diff; + else + newval += diff; + + *step_size = CLAMP(nstep, 127, 24576); + *history = newval = CLAMP(newval, -32768, 32767); + return newval; +} + +void pcm2adpcm(uint8_t *outbuffer, int16_t *buffer, size_t bytes) { + long i; + int16_t step_size = 127; + int16_t history = 0; + uint8_t buf_sample = 0, nibble = 0; + uint32_t adpcm_sample; + size_t num_samples = bytes / 2; /* Divide by 2 to get the number of 16-bit samples */ + + for(i = 0;i < num_samples;i++) { + /* We remove a few bits_per_sample of accuracy to reduce some noise. */ + int step = ((*buffer++) & -8) - history; + adpcm_sample = (abs(step) << 16) / (step_size << 14); + adpcm_sample = CLAMP(adpcm_sample, 0, 7); + if(step < 0) + adpcm_sample |= 8; + if(!nibble) + *outbuffer++ = buf_sample | (adpcm_sample<<4); + else + buf_sample = (adpcm_sample & 15); + nibble ^= 1; + ymz_step(adpcm_sample, &history, &step_size); + } +} + +size_t deinterleave(void *buffer, size_t size) { + short *buf, *buf1, *buf2; + int i; + + buf = (short *)buffer; + size_t channel_size = ((size/2) + 8191) & ~8191; + buf1 = calloc(1, channel_size); + buf2 = calloc(1, channel_size); + + for (i = 0; i < size / 4; i++) { + buf1[i] = buf[i * 2 + 0]; + buf2[i] = buf[i * 2 + 1]; + } + + memcpy(buf, buf1, channel_size); + memcpy(buf + channel_size / sizeof(*buf), buf2, channel_size); + + free(buf1); + free(buf2); + + return channel_size; +} + +// interleaves every 8KB (16K samples) +size_t interleave_adpcm(void *buffer, size_t bytes) { + + uint8_t *buf, *buf1, *buf2; + int i; + + buf = malloc(bytes + 64 * 1024); + buf1 = (uint8_t *)buffer; + buf2 = buf1 + bytes / 2; + + uint8_t *ptr = buf; + size_t remaining_bytes = bytes/2; + + size_t total_bytes = 0; + while (remaining_bytes) { + size_t bytes_to_copy = remaining_bytes > 8 * 1024 ? 8 * 1024 : remaining_bytes; + memset(ptr, 0, 8 * 1024); + memcpy(ptr, buf1, bytes_to_copy); + ptr += 8 * 1024; + buf1 += bytes_to_copy; + memset(ptr, 0, 8 * 1024); + memcpy(ptr, buf2, bytes_to_copy); + ptr += 8 * 1024; + buf2 += bytes_to_copy; + remaining_bytes -= bytes_to_copy; + + total_bytes += 16 * 1024; + } + + assert(total_bytes <= bytes + 64 * 1024); + memcpy(buffer, buf, total_bytes); + + free(buf); + + return total_bytes; +} + +typedef struct wavhdr_t { + char hdr1[4]; + int32_t totalsize; + + char hdr2[8]; + int32_t hdrsize; + short format; + short channels; + int32_t freq; + int32_t byte_per_sec; + short blocksize; + short bits; +} wavhdr_t; + +typedef struct wavhdr3_t { + char hdr3[4]; + int32_t datasize; +} wavhdr3_t; + +int validate_wav_header(wavhdr_t *wavhdr, wavhdr3_t *wavhdr3, int format, int bits, FILE *in) { + int result = 0; + + if (memcmp(wavhdr->hdr1, "RIFF", 4)) { + fprintf(stderr, "Invalid RIFF header.\n"); + result = -1; + } + + if (memcmp(wavhdr->hdr2, "WAVEfmt ", 8)) { + fprintf(stderr, "Invalid WAVEfmt header.\n"); + result = -1; + } + + if (wavhdr->hdrsize < 0x10) { + fprintf(stderr, "Invalid header size, %d bytes\n", wavhdr->hdrsize); + result = -1; + } else if (wavhdr->hdrsize > 0x10) { + fprintf(stderr, "Unusual header size, seeking %d bytes\n", wavhdr->hdrsize - 0x10); + fseek(in, wavhdr->hdrsize - 0x10, SEEK_CUR); + } + + if (wavhdr->format != format) { + fprintf(stderr, "Unsupported format.\n"); + result = -1; + } + + if (wavhdr->channels != 1 && wavhdr->channels != 2) { + fprintf(stderr, "Unsupported number of channels.\n"); + result = -1; + } + + if (wavhdr->bits != bits) { + fprintf(stderr, "Unsupported bit depth.\n"); + result = -1; + } + + for (;;) { + if (fread(wavhdr3->hdr3, 1, 4, in) != 4) { + fprintf(stderr, "Failed to read next chunk header!\n"); + result = -1; + break; + } + + if (fread(&wavhdr3->datasize, 1, 4, in) != 4) { + fprintf(stderr, "Failed to read chunk size!\n"); + result = -1; + break; + } + + if (memcmp(wavhdr3->hdr3, "data", 4)) { + fseek(in, wavhdr3->datasize, SEEK_CUR); + } else { + break; + } + } + + return result; +} + +int loadMp3(const char *infile, size_t *pcmsize, short **pcmbuf, int *channels, int *freq) { + static mp3dec_t mp3d; + mp3dec_file_info_t info; + if (mp3dec_load(&mp3d, infile, &info, NULL, NULL) || info.samples == 0) { + printf("Error: mp3dec_load() failed\n"); + return 0; + } else { + printf("MP3 channels: %d, hz: %d, samples: %ld\n", info.channels, info.hz, info.samples); + *pcmsize = info.samples * sizeof(short); + *pcmbuf = info.buffer; + *channels = info.channels; + *freq = info.hz; + return 1; + } +} + +int loadWav(const char *infile, size_t *pcmsize, short **pcmbuf, int *channels, int *freq) { + wavhdr_t wavhdr; + wavhdr3_t wavhdr3; + + FILE *in = fopen(infile, "rb"); + + if (!in) { + printf("can't open %s\n", infile); + return 0; + } + + if (fread(&wavhdr, sizeof(wavhdr), 1, in) != 1) { + fprintf(stderr, "Cannot read header.\n"); + fclose(in); + return 0; + } + + if (validate_wav_header(&wavhdr, &wavhdr3, 1, 16, in)) { + fclose(in); + return 0; + } + + *pcmsize = wavhdr3.datasize; + *pcmbuf = malloc(*pcmsize); + if (fread(*pcmbuf, *pcmsize, 1, in) != 1) { + fprintf(stderr, "Cannot read data.\n"); + fclose(in); + return 0; + } + + printf("PCM: channels: %d, hz: %d, samples: %d\n", wavhdr.channels, wavhdr.freq, wavhdr3.datasize); + + *channels = wavhdr.channels; + *freq = wavhdr.freq; + fclose(in); + return 1; +} + +int loadWavIMA(const char *infile, size_t *pcmsize, short **pcmbuf, int *channels, int *freq) { + wavhdr_t wavhdr; + wavhdr3_t wavhdr3; + + FILE *in = fopen(infile, "rb"); + + if (!in) { + printf("can't open %s\n", infile); + return 0; + } + + if (fread(&wavhdr, sizeof(wavhdr), 1, in) != 1) { + fprintf(stderr, "Cannot read header.\n"); + fclose(in); + return 0; + } + + if (validate_wav_header(&wavhdr, &wavhdr3, 0x11, 4, in)) { + fclose(in); + return 0; + } + + uint8_t *adpcmbuf = malloc(wavhdr3.datasize); + if (fread(adpcmbuf, wavhdr3.datasize, 1, in) != 1) { + fprintf(stderr, "Cannot read data.\n"); + free(adpcmbuf); + fclose(in); + return 0; + } + + printf("IMA ADPCM: channels: %d, hz: %d, block size: %d, data size: %d\n", + wavhdr.channels, wavhdr.freq, wavhdr.blocksize, wavhdr3.datasize); + + // Calculate the number of samples + int samples_per_block = (wavhdr.blocksize - 4 * wavhdr.channels) * 2 / wavhdr.channels + 1; + int total_blocks = wavhdr3.datasize / wavhdr.blocksize; + *pcmsize = total_blocks * samples_per_block * wavhdr.channels * sizeof(short); + + *pcmbuf = malloc(*pcmsize); + *channels = wavhdr.channels; + *freq = wavhdr.freq; + + ima_decode_adpcm_blocks(adpcmbuf, wavhdr3.datasize, *pcmbuf, *pcmsize, wavhdr.channels, wavhdr.blocksize); + + free(adpcmbuf); + fclose(in); + + return 1; +} + +int aud2adpcm(const char *infile, const char *outfile, int use_hdr, int to_mono, int lq) { + FILE *in, *out; + size_t pcmsize; + short *pcmbuf; + int channels, freq; + + if (!loadWav(infile, &pcmsize, &pcmbuf, &channels, &freq) && + !loadMp3(infile, &pcmsize, &pcmbuf, &channels, &freq) && + !loadWavIMA(infile, &pcmsize, &pcmbuf, &channels, &freq)) { + fprintf(stderr, "Cannot load input file as wav, mp3, or IMA ADPCM.\n"); + return -1; + } + + if (to_mono && channels == 2) { + pcmsize /= 2; + for (int i = 0; i < pcmsize / 2; i++) { + pcmbuf[i] = (pcmbuf[i * 2 + 0] + pcmbuf[i * 2 + 1]) / 2; + } + channels = 1; + } + + if (lq) { + // Downsample to quarter sample rate + freq /= 4; + short *newbuf = malloc(pcmsize / 4); + if (channels == 1) { + for (int i = 0; i < pcmsize / 8; i++) { + newbuf[i] = (pcmbuf[i * 4 + 0] + pcmbuf[i * 4 + 1] + pcmbuf[i * 4 + 2] + pcmbuf[i * 4 + 3]) / 4; + } + } else { + assert(channels == 2); + for (int i = 0; i < pcmsize / 16; i++) { + newbuf[i * 2 + 0 ] = (pcmbuf[i * 8 + 0] + pcmbuf[i * 8 + 2] + pcmbuf[i * 8 + 4] + pcmbuf[i * 8 + 6]) / 4; + newbuf[i * 2 + 1 ] = (pcmbuf[i * 8 + 1] + pcmbuf[i * 8 + 3] + pcmbuf[i * 8 + 5] + pcmbuf[i * 8 + 7]) / 4; + } + } + memcpy(pcmbuf, newbuf, pcmsize / 4); + free(newbuf); + pcmsize /= 4; + } + + // Round down to the nearest multiple of 4 + // adpcm data is always a multiple of 4 samples + pcmsize = pcmsize & ~3; + + size_t adpcmsize = pcmsize / 4; + size_t adpcmsize_data = adpcmsize; + unsigned char *adpcmbuf = calloc(1, adpcmsize + 64 * 1024);// extra alloc for interleave_adpcm + + if (channels == 1) { + pcmbuf = realloc(pcmbuf, pcmsize + 8192); + memset(pcmbuf + pcmsize / sizeof(*pcmbuf), 0, 8192); + pcm2adpcm(adpcmbuf, pcmbuf, pcmsize + 8192/sizeof(*pcmbuf)); + if (use_hdr) { + adpcmsize_data = (adpcmsize_data + 8191) & ~8191; + } + } else { + assert(use_hdr == 1); + pcmbuf = realloc(pcmbuf, pcmsize + 8192*2); + size_t channel_size = deinterleave(pcmbuf, pcmsize); + pcm2adpcm(adpcmbuf, pcmbuf, channel_size); + pcm2adpcm(adpcmbuf + channel_size / 4, pcmbuf + channel_size / sizeof(*pcmbuf), channel_size); + adpcmsize_data = interleave_adpcm(adpcmbuf, channel_size/2); + } + + out = fopen(outfile, "wb"); + if (!out) { + fprintf(stderr, "Cannot write ADPCM data.\n"); + fclose(out); + free(pcmbuf); + free(adpcmbuf); + return -1; + } + + if (use_hdr) { + wavhdr_t wavhdr; + wavhdr3_t wavhdr3; + + memset(&wavhdr, 0, sizeof(wavhdr)); + memcpy(wavhdr.hdr1, "RIFF", 4); + wavhdr.totalsize = adpcmsize + sizeof(wavhdr) + sizeof(wavhdr3) - 8; + memcpy(wavhdr.hdr2, "WAVEfmt ", 8); + wavhdr.hdrsize = 0x10; + wavhdr.format = 0x20; /* ITU G.723 ADPCM (Yamaha) */ + wavhdr.channels = channels; + wavhdr.freq = freq; + wavhdr.byte_per_sec = freq * channels / 2; + wavhdr.blocksize = 4; + wavhdr.bits = 4; + + memset(&wavhdr3, 0, sizeof(wavhdr3)); + memcpy(wavhdr3.hdr3, "data", 4); + wavhdr3.datasize = adpcmsize; + + fwrite(&wavhdr, sizeof(wavhdr), 1, out); + fwrite(&wavhdr3, sizeof(wavhdr3), 1, out); + + // data starts on the second sector + fseek(out, 2048, SEEK_SET); + + assert(0 == (adpcmsize_data & 8191)); + } + + if (fwrite(adpcmbuf, adpcmsize_data, 1, out) != 1) { + fprintf(stderr, "Cannot write ADPCM data.\n"); + fclose(out); + free(pcmbuf); + free(adpcmbuf); + return -1; + } + + fclose(out); + free(pcmbuf); + free(adpcmbuf); + + return 0; +} + +void usage() { + printf("based on wav2adpcm: 16bit mono wav to aica adpcm and vice-versa (c)2002 BERO\n" + " wav2adpcm -q (To adpcm long stream)\n" + " wav2adpcm -t (To adpcm long stream)\n" + " wav2adpcm -raw (To adpcm sfx)\n" + "\n" + "If you are having trouble with your input wav file you can run it" + "through ffmpeg first and then run wav2adpcm on output.wav:\n" + " ffmpeg -i input.wav -ac 1 -acodec pcm_s16le output.wav" + ); +} + +int main(int argc, char **argv) { + if (argc == 4) { + if (!strcmp(argv[1], "-t")) { + return aud2adpcm(argv[2], argv[3], 1, 0, 0); + } else if (!strcmp(argv[1], "-q")) { + return aud2adpcm(argv[2], argv[3], 1, 1, 1); + } else if (!strcmp(argv[1], "-raw")) { + return aud2adpcm(argv[2], argv[3], 0, 0, 0); + } else { + usage(); + return -1; + } + } else { + usage(); + return -1; + } +} diff --git a/dreamcast/common.mk b/dreamcast/common.mk new file mode 100644 index 00000000..2cacf927 --- /dev/null +++ b/dreamcast/common.mk @@ -0,0 +1,371 @@ + +# List all of your C files here, but change the extension to ".o" +# Include "romdisk.o" if you want a rom disk. +RE3_OBJS = \ + ../src/animation/AnimBlendAssocGroup.o \ + ../src/animation/AnimBlendAssociation.o \ + ../src/animation/AnimBlendClumpData.o \ + ../src/animation/AnimBlendHierarchy.o \ + ../src/animation/AnimBlendNode.o \ + ../src/animation/AnimBlendSequence.o \ + ../src/animation/AnimManager.o \ + ../src/animation/Bones.o \ + ../src/animation/CutsceneMgr.o \ + ../src/animation/FrameUpdate.o \ + ../src/animation/RpAnimBlend.o \ + \ + ../src/buildings/Building.o \ + ../src/buildings/Treadable.o \ + \ + ../src/collision/ColBox.o \ + ../src/collision/ColLine.o \ + ../src/collision/Collision.o \ + ../src/collision/ColModel.o \ + ../src/collision/ColPoint.o \ + ../src/collision/ColSphere.o \ + ../src/collision/ColTriangle.o \ + ../src/collision/TempColModels.o \ + ../src/collision/VuCollision.o \ + \ + ../src/control/AutoPilot.o \ + ../src/control/Bridge.o \ + ../src/control/CarAI.o \ + ../src/control/CarCtrl.o \ + ../src/control/Curves.o \ + ../src/control/Darkel.o \ + ../src/control/GameLogic.o \ + ../src/control/Garages.o \ + ../src/control/NameGrid.o \ + ../src/control/OnscreenTimer.o \ + ../src/control/PathFind.o \ + ../src/control/Phones.o \ + ../src/control/Pickups.o \ + ../src/control/PowerPoints.o \ + ../src/control/Record.o \ + ../src/control/Remote.o \ + ../src/control/Replay.o \ + ../src/control/Restart.o \ + ../src/control/RoadBlocks.o \ + ../src/control/SceneEdit.o \ + ../src/control/Script.o \ + ../src/control/Script2.o \ + ../src/control/Script3.o \ + ../src/control/Script4.o \ + ../src/control/Script5.o \ + ../src/control/Script6.o \ + ../src/control/ScriptDebug.o \ + ../src/control/TrafficLights.o \ + \ + ../src/core/Accident.o \ + ../src/core/Cam.o \ + ../src/core/Camera.o \ + ../src/core/CdStreamDC.o \ + ../src/core/Clock.o \ + ../src/core/ControllerConfig.o \ + ../src/core/Debug.o \ + ../src/core/Directory.o \ + ../src/core/EventList.o \ + ../src/core/FileLoader.o \ + ../src/core/FileMgr.o \ + ../src/core/Fire.o \ + ../src/core/Frontend.o \ + ../src/core/FrontEndControls.o \ + ../src/core/Frontend_PS2.o \ + ../src/core/Game.o \ + ../src/core/IniFile.o \ + ../src/core/Lists.o \ + ../src/core/main.o \ + ../src/core/MenuScreens.o \ + ../src/core/MenuScreensCustom.o \ + ../src/core/obrstr.o \ + ../src/core/Pad.o \ + ../src/core/Placeable.o \ + ../src/core/PlayerInfo.o \ + ../src/core/Pools.o \ + ../src/core/Profile.o \ + ../src/core/Radar.o \ + ../src/core/Range2D.o \ + ../src/core/Range3D.o \ + ../src/core/re3.o \ + ../src/core/References.o \ + ../src/core/Stats.o \ + ../src/core/Streaming.o \ + ../src/core/SurfaceTable.o \ + ../src/core/timebars.o \ + ../src/core/Timer.o \ + ../src/core/TimeStep.o \ + ../src/core/User.o \ + ../src/core/Wanted.o \ + ../src/core/World.o \ + ../src/core/ZoneCull.o \ + ../src/core/Zones.o \ + \ + ../src/entities/Dummy.o \ + ../src/entities/Entity.o \ + ../src/entities/Physical.o \ + \ + ../src/fakerw/fake.o \ + \ + ../src/math/math.o \ + ../src/math/Matrix.o \ + ../src/math/Quaternion.o \ + ../src/math/Rect.o \ + ../src/math/Vector.o \ + \ + ../src/modelinfo/BaseModelInfo.o \ + ../src/modelinfo/ClumpModelInfo.o \ + ../src/modelinfo/MloModelInfo.o \ + ../src/modelinfo/ModelIndices.o \ + ../src/modelinfo/ModelInfo.o \ + ../src/modelinfo/PedModelInfo.o \ + ../src/modelinfo/SimpleModelInfo.o \ + ../src/modelinfo/TimeModelInfo.o \ + ../src/modelinfo/VehicleModelInfo.o \ + \ + ../src/objects/CutsceneHead.o \ + ../src/objects/CutsceneObject.o \ + ../src/objects/DummyObject.o \ + ../src/objects/Object.o \ + ../src/objects/ObjectData.o \ + ../src/objects/ParticleObject.o \ + ../src/objects/Projectile.o \ + \ + ../src/peds/CivilianPed.o \ + ../src/peds/CopPed.o \ + ../src/peds/EmergencyPed.o \ + ../src/peds/Gangs.o \ + ../src/peds/Ped.o \ + ../src/peds/PedAI.o \ + ../src/peds/PedChat.o \ + ../src/peds/PedDebug.o \ + ../src/peds/PedFight.o \ + ../src/peds/PedIK.o \ + ../src/peds/PedPlacement.o \ + ../src/peds/PedRoutes.o \ + ../src/peds/PedType.o \ + ../src/peds/PlayerPed.o \ + ../src/peds/Population.o \ + \ + ../src/renderer/Antennas.o \ + ../src/renderer/Clouds.o \ + ../src/renderer/Console.o \ + ../src/renderer/Coronas.o \ + ../src/renderer/Credits.o \ + ../src/renderer/Draw.o \ + ../src/renderer/Fluff.o \ + ../src/renderer/Font.o \ + ../src/renderer/Glass.o \ + ../src/renderer/Hud.o \ + ../src/renderer/Instance.o \ + ../src/renderer/Lines.o \ + ../src/renderer/MBlur.o \ + ../src/renderer/Particle.o \ + ../src/renderer/ParticleMgr.o \ + ../src/renderer/PlayerSkin.o \ + ../src/renderer/PointLights.o \ + ../src/renderer/RenderBuffer.o \ + ../src/renderer/Renderer.o \ + ../src/renderer/Rubbish.o \ + ../src/renderer/Shadows.o \ + ../src/renderer/Skidmarks.o \ + ../src/renderer/SpecialFX.o \ + ../src/renderer/Sprite.o \ + ../src/renderer/Sprite2d.o \ + ../src/renderer/TexList.o \ + ../src/renderer/Timecycle.o \ + ../src/renderer/WaterCannon.o \ + ../src/renderer/WaterLevel.o \ + ../src/renderer/Weather.o \ + \ + ../src/rw/ClumpRead.o \ + ../src/rw/Lights.o \ + ../src/rw/MemoryHeap.o \ + ../src/rw/MemoryMgr.o \ + ../src/rw/NodeName.o \ + ../src/rw/RwHelper.o \ + ../src/rw/RwMatFX.o \ + ../src/rw/RwPS2AlphaTest.o \ + ../src/rw/TexRead.o \ + ../src/rw/TexturePools.o \ + ../src/rw/TxdStore.o \ + ../src/rw/VisibilityPlugins.o \ + \ + ../src/skel/crossplatform.o \ + ../src/skel/events.o \ + ../src/skel/skeleton.o \ + ../src/skel/dc/dc.o \ + \ + ../src/text/Messages.o \ + ../src/text/Pager.o \ + ../src/text/Text.o \ + \ + ../src/vehicles/Automobile.o \ + ../src/vehicles/Boat.o \ + ../src/vehicles/CarGen.o \ + ../src/vehicles/Cranes.o \ + ../src/vehicles/DamageManager.o \ + ../src/vehicles/Door.o \ + ../src/vehicles/Floater.o \ + ../src/vehicles/HandlingMgr.o \ + ../src/vehicles/Heli.o \ + ../src/vehicles/Plane.o \ + ../src/vehicles/Train.o \ + ../src/vehicles/Transmission.o \ + ../src/vehicles/Vehicle.o \ + \ + ../src/weapons/BulletInfo.o \ + ../src/weapons/Explosion.o \ + ../src/weapons/ProjectileInfo.o \ + ../src/weapons/ShotInfo.o \ + ../src/weapons/Weapon.o \ + ../src/weapons/WeaponEffects.o \ + ../src/weapons/WeaponInfo.o \ + \ + ../src/audio/AudioCollision.o \ + ../src/audio/AudioLogic.o \ + ../src/audio/AudioManager.o \ + ../src/audio/AudioScriptObject.o \ + ../src/audio/DMAudio.o \ + ../src/audio/MusicManager.o \ + ../src/audio/PolRadio.o \ + ../src/audio/sampman_miles.o \ + ../src/audio/sampman_oal.o \ + \ + ../src/save/Date.o \ + ../src/save/GenericGameStorage.o \ + ../src/save/MemoryCard.o \ + ../src/save/PCSave.o \ + \ + ../src/extras/debugmenu.o \ + ../src/extras/frontendoption.o \ + ../src/extras/postfx.o \ + ../src/extras/screendroplets.o \ + \ + ../src/vmu/vmu.o \ + ../vendor/miniLZO/minilzo.o \ + \ + +# Excluded \ + ../src/extras/custompipes.o \ + ../src/extras/custompipes_d3d9.o \ + ../src/extras/custompipes_gl.o \ + ../src/core/CdStream.o \ + ../src/core/CdStreamPosix.o \ + ../src/extras \ + ../src/extras/GitSHA1.cpp.in \ + ../src/core/AnimViewer.o \ + +RW_OBJS = \ + ../vendor/librw/src/anim.o \ + ../vendor/librw/src/base.o \ + ../vendor/librw/src/camera.o \ + ../vendor/librw/src/charset.o \ + ../vendor/librw/src/clump.o \ + ../vendor/librw/src/engine.o \ + ../vendor/librw/src/error.o \ + ../vendor/librw/src/frame.o \ + ../vendor/librw/src/geometry.o \ + ../vendor/librw/src/geoplg.o \ + ../vendor/librw/src/hanim.o \ + ../vendor/librw/src/image.o \ + ../vendor/librw/src/light.o \ + ../vendor/librw/src/matfx.o \ + ../vendor/librw/src/pipeline.o \ + ../vendor/librw/src/plg.o \ + ../vendor/librw/src/prim.o \ + ../vendor/librw/src/raster.o \ + ../vendor/librw/src/render.o \ + ../vendor/librw/src/skin.o \ + ../vendor/librw/src/texture.o \ + ../vendor/librw/src/tristrip.o \ + ../vendor/librw/src/userdata.o \ + ../vendor/librw/src/uvanim.o \ + ../vendor/librw/src/world.o \ + \ + ../vendor/librw/src/dc/rwdc.o \ + ../vendor/librw/src/dc/alloc.o + +# Excluded \ + ../vendor/librw/src/d3d-x/d3d.o \ + ../vendor/librw/src/d3d-x/d3d8.o \ + ../vendor/librw/src/d3d-x/d3d8render.o \ + ../vendor/librw/src/d3d/d3d8.o \ + ../vendor/librw/src/d3d/d3d8matfx.o \ + ../vendor/librw/src/d3d/d3d8render.o \ + ../vendor/librw/src/d3d/d3d8skin.o \ + ../vendor/librw/src/d3d/d3d9.o \ + ../vendor/librw/src/d3d/d3d9matfx.o \ + ../vendor/librw/src/d3d/d3d9render.o \ + ../vendor/librw/src/d3d/d3d9skin.o \ + ../vendor/librw/src/d3d/d3d.o \ + ../vendor/librw/src/d3d/d3ddevice.o \ + ../vendor/librw/src/d3d/d3dimmed.o \ + ../vendor/librw/src/d3d/d3drender.o \ + ../vendor/librw/src/d3d/xbox.o \ + ../vendor/librw/src/d3d/xboxmatfx.o \ + ../vendor/librw/src/d3d/xboxskin.o \ + ../vendor/librw/src/d3d/xboxvfmt.o \ + \ + ../vendor/librw/src/gl/gl3.o \ + ../vendor/librw/src/gl/gl3device.o \ + ../vendor/librw/src/gl/gl3immed.o \ + ../vendor/librw/src/gl/gl3matfx.o \ + ../vendor/librw/src/gl/gl3pipe.o \ + ../vendor/librw/src/gl/gl3raster.o \ + ../vendor/librw/src/gl/gl3render.o \ + ../vendor/librw/src/gl/gl3shader.o \ + ../vendor/librw/src/gl/gl3skin.o \ + ../vendor/librw/src/gl/wdgl.o \ + ../vendor/librw/src/gl/glad/glad.cXXX \ + \ + ../vendor/librw/src/ps2/pds.o \ + ../vendor/librw/src/ps2/ps2.o \ + ../vendor/librw/src/ps2/ps2device.o \ + ../vendor/librw/src/ps2/ps2matfx.o \ + ../vendor/librw/src/ps2/ps2raster.o \ + ../vendor/librw/src/ps2/ps2skin.o \ + +INCLUDE = \ +-I../src/animation \ +-I../src/audio \ +-I../src/buildings \ +-I../src/collision \ +-I../src/control \ +-I../src/core \ +-I../src/entities \ +-I../src/extras \ +-I../src/fakerw \ +-I../src/math \ +-I../src/modelinfo \ +-I../src/objects \ +-I../src/peds \ +-I../src/renderer \ +-I../src/rw \ +-I../src/save \ +-I../src/skel \ +-I../src/text \ +-I../src/vehicles \ +-I../src/weapons \ +-I../src/audio/eax \ +-I../src/audio/oal \ +-I../src/extras/shaders \ +-I../src/extras/shaders/obj \ +-I../src/skel/glfw \ +-I../src/skel/win \ +\ +-I../vendor/librw \ +\ +-I../vendor/miniLZO + +DEFINES = -DRW_DC -DLIBRW $(if $(WITH_LOGGING),-DWITH_LOGGING) $(if $(WITH_DCLOAD),-DDC_CHDIR=/pc) \ + $(if $(WITH_BEEPS),-DWITH_BEEPS) +FLAGS = -fpermissive -Wno-sign-compare -Wno-parentheses -Wno-maybe-uninitialized \ + -Wno-format -Wno-strict-aliasing -Wno-unused-variable \ + -Wno-unused-but-set-variable -Wno-write-strings \ + -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion \ + -Wno-multichar -Wno-unused-value -Wno-char-subscripts -Wno-reorder \ + -Wno-unused-function -Wno-class-memaccess -fno-permissive + +CPPFLAGS += $(INCLUDE) $(DEFINES) $(FLAGS) +CFLAGS += -std=gnu17 $(CPPFLAGS) +CXXFLAGS += -std=gnu++20 $(CPPFLAGS) \ No newline at end of file diff --git a/dreamcast/extract-sfx.cpp b/dreamcast/extract-sfx.cpp new file mode 100644 index 00000000..1f2ef9a3 --- /dev/null +++ b/dreamcast/extract-sfx.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include + +// Define the tSample structure +struct tSample { + uint32_t nOffset; + uint32_t nSize; + uint32_t nFrequency; + uint32_t nLoopStart; + int32_t nLoopEnd; +}; + +// WAV file header structure +struct WavHeader { + char riff[4]; // "RIFF" + uint32_t fileSize; // File size minus 8 bytes + char wave[4]; // "WAVE" + char fmt[4]; // "fmt " + uint32_t fmtSize; // Size of fmt chunk (16 for PCM) + uint16_t audioFormat; // Audio format (1 for PCM) + uint16_t numChannels; // Number of channels + uint32_t sampleRate; // Sample rate + uint32_t byteRate; // Byte rate + uint16_t blockAlign; // Block align + uint16_t bitsPerSample; // Bits per sample + char data[4]; // "data" + uint32_t dataSize; // Size of data chunk +}; + +void writeWavFile(const std::string &filename, const std::vector &data, uint32_t sampleRate) { + WavHeader header; + + // Fill in the WAV header fields + std::memcpy(header.riff, "RIFF", 4); + header.fileSize = 36 + data.size() * sizeof(int16_t); + std::memcpy(header.wave, "WAVE", 4); + std::memcpy(header.fmt, "fmt ", 4); + header.fmtSize = 16; + header.audioFormat = 1; // PCM + header.numChannels = 1; // Mono + header.sampleRate = sampleRate; + header.byteRate = sampleRate * sizeof(int16_t); + header.blockAlign = sizeof(int16_t); + header.bitsPerSample = 16; + std::memcpy(header.data, "data", 4); + header.dataSize = data.size() * sizeof(int16_t); + + // Open the output WAV file + std::ofstream outFile(filename, std::ios::binary); + if (!outFile) { + std::cerr << "Failed to open output file: " << filename << std::endl; + return; + } + + // Write the header + outFile.write(reinterpret_cast(&header), sizeof(WavHeader)); + + // Write the audio data + outFile.write(reinterpret_cast(data.data()), data.size() * sizeof(int16_t)); + + outFile.close(); +} + +int main(int argc, char *argv[]) { + // Check if enough arguments are provided + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + // Open the sample file in binary mode + std::ifstream sampleFile(argv[1], std::ios::binary); + if (!sampleFile) { + std::cerr << "Failed to open sample file: " << argv[1] << std::endl; + return 1; + } + + // Open the data file in binary mode + std::ifstream dataFile(argv[2], std::ios::binary); + if (!dataFile) { + std::cerr << "Failed to open data file: " << argv[2] << std::endl; + return 1; + } + + std::string folder = argv[3]; + + // Read and process all tSample structures from the sample file + tSample sample; + int recordNumber = 0; + + while (sampleFile.read(reinterpret_cast(&sample), sizeof(tSample))) { + // Seek to the correct offset in the data file + dataFile.seekg(sample.nOffset, std::ios::beg); + if (!dataFile) { + std::cerr << "Failed to seek in data file for record " << recordNumber << std::endl; + continue; + } + + assert(sample.nSize % sizeof(int16_t) == 0); + assert(sample.nLoopStart % sizeof(int16_t) == 0); + assert(sample.nLoopEnd == -1 || sample.nLoopEnd % sizeof(int16_t) == 0); + + // Read the sample data + std::vector audioData(sample.nSize / sizeof(int16_t)); + if (!dataFile.read(reinterpret_cast(audioData.data()), sample.nSize)) { + std::cerr << "Failed to read data for record " << recordNumber << std::endl; + continue; + } + + // Generate the WAV file name + std::string wavFilename = folder + "/sfx_" + std::to_string(recordNumber) + ".wav"; + std::string wavFilenameLoop = folder + "/sfx_" + std::to_string(recordNumber) + "_loop.wav"; + + // std::cout << "Sample size: " << sample.nSize << " bytes" << std::endl; + // std::cout << "Sample frequency: " << sample.nFrequency << " Hz" << std::endl; + // std::cout << "Sample loop start: " << sample.nLoopStart << std::endl; + // std::cout << "Sample loop end: " << sample.nLoopEnd << std::endl; + // std::cout << std::endl; + + // Write the data to a WAV file + if (sample.nLoopStart != 0) { + writeWavFile(wavFilename, audioData, sample.nFrequency); + std::vector loopData(audioData.begin() + sample.nLoopStart/2, audioData.begin() + sample.nLoopEnd/2); + writeWavFile(wavFilenameLoop, loopData, sample.nFrequency); + } else { + writeWavFile(wavFilename, audioData, sample.nFrequency); + } + recordNumber++; + } + + // Close the files + sampleFile.close(); + dataFile.close(); + + return 0; +} \ No newline at end of file diff --git a/dreamcast/gen-mesh-variants.py b/dreamcast/gen-mesh-variants.py new file mode 100644 index 00000000..9668b6a6 --- /dev/null +++ b/dreamcast/gen-mesh-variants.py @@ -0,0 +1,114 @@ + +def variant(small_xyz, pad_xyz, small_uv): + return f""" + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + 0, + 0, + 0, + 0, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + 0, + 0, + 0, + 0, + &submitMesh, + &submitMesh, + &submitMesh, + &submitMesh, + + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + 0, + 0, + 0, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + 0, + 0, + 0, + 0, + &submitMesh, + 0, + &submitMesh, + + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + 0, + 0, + 0, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + &submitMesh, + 0, + 0, + 0, + 0, + 0, + &submitMesh, + 0, + &submitMesh, +""" + +str = "" +str += variant(False, False, False) +str += variant(False, False, True) +str += variant(False, True, False) +str += variant(False, True, True) +str += variant(True, False, False) +str += variant(True, False, True) +str += variant(True, True, False) +str += variant(True, True, True) + +print(str) \ No newline at end of file diff --git a/dreamcast/gta3files.mk b/dreamcast/gta3files.mk new file mode 100644 index 00000000..f2aeaaf8 --- /dev/null +++ b/dreamcast/gta3files.mk @@ -0,0 +1,178 @@ +MISC_FILES = \ + anim/cuts.dir \ + anim/cuts.img \ + anim/gta3.ini \ + anim/ped.ifp \ + data/animviewer.dat \ + data/carcols.dat \ + data/CULLZONE.DAT \ + data/default.dat \ + data/default.ide \ + data/fistfite.dat \ + data/gta3.dat \ + data/gta3.zon \ + data/handling.cfg \ + data/main.scm \ + data/map.zon \ + data/maps/comnbtm/comNbtm.col \ + data/maps/comnbtm/comnbtm.ide \ + data/maps/comnbtm/comNbtm.ipl \ + data/maps/comNbtm.ipl \ + data/maps/comntop/comNtop.col \ + data/maps/comntop/comntop.ide \ + data/maps/comntop/comNtop.ipl \ + data/maps/comNtop.ipl \ + data/maps/comroad/comroad.col \ + data/maps/comroad/comroad.ide \ + data/maps/comroad/comroad.ipl \ + data/maps/comse/comSE.col \ + data/maps/comse/comse.ide \ + data/maps/comse/comSE.ipl \ + data/maps/comSE.ipl \ + data/maps/comsw/comSW.col \ + data/maps/comsw/comsw.ide \ + data/maps/comsw/comSW.ipl \ + data/maps/comSW.ipl \ + data/maps/cull.ipl \ + data/maps/generic.ide \ + data/maps/gta3.IDE \ + data/maps/indroads/indroads.col \ + data/maps/indroads/indroads.ide \ + data/maps/indroads/indroads.ipl \ + data/maps/industne/industNE.col \ + data/maps/industne/industne.ide \ + data/maps/industne/industNE.ipl \ + data/maps/industNE.ipl \ + data/maps/industnw/industNW.col \ + data/maps/industnw/industnw.ide \ + data/maps/industnw/industNW.ipl \ + data/maps/industNW.ipl \ + data/maps/industse/industSE.col \ + data/maps/industse/industse.ide \ + data/maps/industse/industSE.ipl \ + data/maps/industSE.ipl \ + data/maps/industsw/industSW.col \ + data/maps/industsw/industsw.ide \ + data/maps/industsw/industSW.ipl \ + data/maps/industSW.ipl \ + data/maps/landne/landne.col \ + data/maps/landne/landne.ide \ + data/maps/landne/landne.ipl \ + data/maps/landsw/landsw.col \ + data/maps/landsw/landsw.ide \ + data/maps/landsw/landsw.ipl \ + data/maps/making/making.col \ + data/maps/making/making.ide \ + data/maps/making/making.ipl \ + data/maps/overview.ipl \ + data/maps/props.IPL \ + data/maps/subroads/subroads.col \ + data/maps/subroads/subroads.ide \ + data/maps/subroads/subroads.ipl \ + data/maps/suburbne.ipl \ + data/maps/suburbsw.ipl \ + data/maps/temppart/temppart.col \ + data/maps/temppart/temppart.ide \ + data/maps/temppart/temppart.ipl \ + data/object.dat \ + data/particle.cfg \ + data/paths/CHASE0.DAT \ + data/paths/CHASE1.DAT \ + data/paths/CHASE10.DAT \ + data/paths/CHASE11.DAT \ + data/paths/CHASE14.DAT \ + data/paths/CHASE16.DAT \ + data/paths/CHASE18.DAT \ + data/paths/CHASE19.DAT \ + data/paths/CHASE2.DAT \ + data/paths/CHASE3.DAT \ + data/paths/CHASE4.DAT \ + data/paths/CHASE5.DAT \ + data/paths/CHASE6.DAT \ + data/paths/CHASE7.DAT \ + data/paths/flight.dat \ + data/paths/flight2.dat \ + data/paths/flight3.dat \ + data/paths/flight4.dat \ + data/paths/tracks.dat \ + data/paths/tracks2.dat \ + data/ped.dat \ + data/pedgrp.dat \ + data/pedstats.dat \ + data/surface.dat \ + data/timecyc.dat \ + data/train.dat \ + data/train2.dat \ + data/water.dat \ + data/waterpro.dat \ + data/weapon.dat \ + gta3.ini \ + models/Coll/commer.col \ + models/Coll/generic.col \ + models/Coll/indust.col \ + models/Coll/peds.col \ + models/Coll/suburb.col \ + models/Coll/vehicles.col \ + models/Coll/weapons.col \ + models/Generic/air_vlo.DFF \ + models/Generic/arrow.DFF \ + models/Generic/loplyguy.dff \ + models/Generic/peds.dff \ + models/Generic/qsphere.DFF \ + models/Generic/sphere.DFF \ + models/Generic/weapons.dff \ + models/Generic/wheels.DFF \ + models/Generic/zonecyla.DFF \ + models/Generic/zonecylb.DFF \ + models/Generic/zonesphr.DFF \ + movies/GTAtitles.mpg \ + movies/GTAtitlesGER.mpg \ + $(if $(wildcard $(GTA_DIR)/movies/LOGO.mpg), movies/LOGO.mpg, movies/Logo.mpg) \ + TEXT/american.gxt \ + TEXT/english.gxt \ + TEXT/french.gxt \ + TEXT/german.gxt \ + TEXT/italian.gxt \ + TEXT/spanish.gxt \ + \ + models/fonts.txd \ + models/frontend.txd \ + models/generic.txd \ + models/hud.txd \ + models/menu.txd \ + models/MISC.TXD \ + models/particle.txd \ + txd/LOADSC0.TXD \ + txd/LOADSC1.TXD \ + txd/LOADSC10.TXD \ + txd/LOADSC11.TXD \ + txd/LOADSC12.TXD \ + txd/LOADSC13.TXD \ + txd/LOADSC14.TXD \ + txd/LOADSC15.TXD \ + txd/LOADSC16.TXD \ + txd/LOADSC17.TXD \ + txd/LOADSC18.TXD \ + txd/LOADSC19.TXD \ + txd/LOADSC2.TXD \ + txd/LOADSC20.TXD \ + txd/LOADSC21.TXD \ + txd/LOADSC22.TXD \ + txd/LOADSC23.TXD \ + txd/LOADSC24.TXD \ + txd/LOADSC25.TXD \ + txd/LOADSC3.TXD \ + txd/LOADSC4.TXD \ + txd/LOADSC5.TXD \ + txd/LOADSC6.TXD \ + txd/LOADSC7.TXD \ + txd/LOADSC8.TXD \ + txd/LOADSC9.TXD \ + txd/mainsc1.txd \ + txd/mainsc2.txd \ + txd/NEWS.TXD \ + txd/SPLASH1.TXD \ + txd/SPLASH2.TXD \ + txd/SPLASH3.TXD \ + \ + audio/sfx.SDT diff --git a/dreamcast/imgtool.cpp b/dreamcast/imgtool.cpp new file mode 100644 index 00000000..416064e1 --- /dev/null +++ b/dreamcast/imgtool.cpp @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include + +// Sector size constant +const size_t SECTOR_SIZE = 2048; + +// Record structure for .dir file +struct DirRecord { + uint32_t offset; + uint32_t size; + char name[24]; +}; + +// Function to read .dir file +std::vector readDirFile(const std::string& dirFilePath) { + std::ifstream dirFile(dirFilePath, std::ios::binary); + if (!dirFile) { + throw std::runtime_error("Could not open .dir file."); + } + + std::vector records; + DirRecord record; + + while (dirFile.read(reinterpret_cast(&record), sizeof(DirRecord))) { + records.push_back(record); + } + + return records; +} + +// Function to unpack .img file +void unpackImg(const std::string& baseName, const std::string& dataDir) { + std::string dirFilePath = baseName + ".dir"; + std::string imgFilePath = baseName + ".img"; + + auto records = readDirFile(dirFilePath); + + std::ifstream imgFile(imgFilePath, std::ios::binary); + if (!imgFile) { + throw std::runtime_error("Could not open .img file."); + } + + std::filesystem::create_directories(dataDir); + + for (const auto& record : records) { + std::string outputFilePath = dataDir + "/" + std::string(record.name); + std::ofstream outputFile(outputFilePath, std::ios::binary); + if (!outputFile) { + throw std::runtime_error("Could not create output file: " + outputFilePath); + } + + imgFile.seekg(record.offset * SECTOR_SIZE); + std::vector buffer(record.size * SECTOR_SIZE); + imgFile.read(buffer.data(), buffer.size()); + + outputFile.write(buffer.data(), buffer.size()); + } +} + +// Function to create .dir file +void writeDirFile(const std::string& dirFilePath, const std::vector& records) { + std::ofstream dirFile(dirFilePath, std::ios::binary); + if (!dirFile) { + throw std::runtime_error("Could not create .dir file."); + } + + for (const auto& record : records) { + dirFile.write(reinterpret_cast(&record), sizeof(DirRecord)); + } +} + +// Function to pack .img file +void packImg(const std::string& dataDir, const std::string& baseName) { + std::string dirFilePath = baseName + ".dir"; + std::string imgFilePath = baseName + ".img"; + + std::ofstream imgFile(imgFilePath, std::ios::binary); + if (!imgFile) { + throw std::runtime_error("Could not create .img file."); + } + + std::vector records; + uint32_t offset = 0; + + for (const auto& entry : std::filesystem::directory_iterator(dataDir)) { + if (entry.is_regular_file()) { + DirRecord record; + std::memset(&record, 0, sizeof(DirRecord)); + std::strncpy(record.name, entry.path().filename().string().c_str(), sizeof(record.name) - 1); + + std::ifstream inputFile(entry.path(), std::ios::binary | std::ios::ate); + if (!inputFile) { + throw std::runtime_error("Could not open input file: " + entry.path().string()); + } + + size_t fileSize = inputFile.tellg(); + inputFile.seekg(0); + + size_t numSectors = (fileSize + SECTOR_SIZE - 1) / SECTOR_SIZE; + std::vector buffer(numSectors * SECTOR_SIZE, 0); + + inputFile.read(buffer.data(), fileSize); + + record.offset = offset; + record.size = numSectors; + + imgFile.write(buffer.data(), buffer.size()); + records.push_back(record); + + offset += numSectors; + } + } + + writeDirFile(dirFilePath, records); +} + +int main(int argc, char* argv[]) { + if (argc != 4) { + std::cerr << "Usage: imgtool " << std::endl; + return 1; + } + + std::string command = argv[1]; + std::string baseName = argv[2]; + std::string dataDir = argv[3]; + + try { + if (command == "unpack") { + unpackImg(baseName, dataDir); + } else if (command == "pack") { + packImg(dataDir, baseName); + } else { + std::cerr << "Unknown command: " << command << std::endl; + return 1; + } + } catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << std::endl; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/dreamcast/ip.txt b/dreamcast/ip.txt new file mode 100644 index 00000000..8cfb4b37 --- /dev/null +++ b/dreamcast/ip.txt @@ -0,0 +1,9 @@ +Device Info : CD-ROM1/1 +Area Symbols : JUE +Peripherals : E000F10 +Product No : T0000 +Version : V1.000 +Release Date : 20000627 +Boot Filename : 1ST_READ.BIN +SW Maker Name : the gang +Game Title : DCA3 diff --git a/dreamcast/modlist.mk b/dreamcast/modlist.mk new file mode 100644 index 00000000..bbbaf444 --- /dev/null +++ b/dreamcast/modlist.mk @@ -0,0 +1,3135 @@ +IMG_MODELS = \ + 3D8Ball.dff \ + 8ballsuburbandoor.dff \ + Airportdoor1.dff \ + Airportdoor2.dff \ + Airportroad01.dff \ + Airportroad02.dff \ + Airportroad03.dff \ + Airportroad04.dff \ + Airportroad05.dff \ + Airportroad06.dff \ + Airportroad07.dff \ + Airportroad08.dff \ + Airportroad09.dff \ + Airportroad10.dff \ + Airportroad11.dff \ + BRbomb.dff \ + Bank.dff \ + Broken_bridge.dff \ + Building1.dff \ + CS_TRUK.dff \ + Carparkbooth.dff \ + Chinabuildnew1.dff \ + Chinabuilds05.dff \ + Chinabuilds06.dff \ + Chinabuilds07.dff \ + Chinabuilds08.dff \ + Chinabuilds09.dff \ + Chinabuilds21.dff \ + Clnm_cthdrlfcde.dff \ + Clnm_stadium.dff \ + Columbiangate.dff \ + Com_docksa.dff \ + CrossRoadn1.dff \ + Cutscene_Watertanks.dff \ + DIRTPASS3.dff \ + Dam_pod1.dff \ + Dam_pod2.dff \ + Deadman1.dff \ + Deadmanoblood.dff \ + Dineradam.dff \ + Electricgate.dff \ + GTAElift.DFF \ + Garagedr_oddjob.dff \ + Gdyn_barrier17.dff \ + Gsel_canopy3.dff \ + Gsel_canopy4.dff \ + Gsel_canopy5.dff \ + Gsel_canopy9.dff \ + Gsel_cansm1.dff \ + Helipad.dff \ + Hospital_Sub.dff \ + Hospital_ind.dff \ + Hotel.dff \ + Hotel2.dff \ + Hotel3.dff \ + Hotel4.dff \ + Hotel5.dff \ + Hotel_Tenemant1.dff \ + IslandLODInd.dff \ + IslandLODcomIND.dff \ + IslandLODcomSUB.dff \ + IslandLODsubCOM.dff \ + IslandLODsubIND.dff \ + Jetty.dff \ + LODGICLUBout.dff \ + LODNcoast1.dff \ + LODNcoast2.dff \ + LODNcoast3.dff \ + LODNcoast5.dff \ + LODNcoast6.dff \ + LODRoad1C50kb.dff \ + LODRoadx.dff \ + LOD_GOD_DAM.dff \ + LOD_base.dff \ + LOD_bomb01.dff \ + LOD_bridge.dff \ + LOD_brway_01.dff \ + LOD_brway_02.dff \ + LOD_brway_03.dff \ + LOD_carprktrees.dff \ + LOD_carprktrees4.dff \ + LOD_cland127.dff \ + LOD_comp1.dff \ + LOD_comp3.dff \ + LOD_comp5.dff \ + LOD_comp6.dff \ + LOD_custm_rd010.dff \ + LOD_customroad051.dff \ + LOD_docksa.dff \ + LOD_docksaa.dff \ + LOD_docksb.dff \ + LOD_firetower.dff \ + LOD_gazground.dff \ + LOD_grnd01.dff \ + LOD_grnd02.dff \ + LOD_grnd03.dff \ + LOD_grnd04.dff \ + LOD_grnd05.dff \ + LOD_grnd06.dff \ + LOD_grnd08.dff \ + LOD_grnd09.dff \ + LOD_grnd10.dff \ + LOD_grnd11.dff \ + LOD_grnd12.dff \ + LOD_grnd13.dff \ + LOD_grnd14.dff \ + LOD_grnd7.dff \ + LOD_grndkb.dff \ + LOD_hospital.dff \ + LOD_ind03.dff \ + LOD_ind26.dff \ + LOD_indland01.dff \ + LOD_indland02.dff \ + LOD_indland06.dff \ + LOD_indland08.dff \ + LOD_inland667.dff \ + LOD_kb3_scrap6.dff \ + LOD_kbroads01.dff \ + LOD_kbroads02.dff \ + LOD_kbroads09.dff \ + LOD_kbroads12.dff \ + LOD_kbroads13.dff \ + LOD_kbroads14.dff \ + LOD_kbroads15.dff \ + LOD_kbroads16.dff \ + LOD_kbroads17.dff \ + LOD_kbroads18.dff \ + LOD_kbroads21.dff \ + LOD_kbroads22.dff \ + LOD_kbroads23.dff \ + LOD_kbroads24.dff \ + LOD_kbroads25.dff \ + LOD_kbroads26.dff \ + LOD_kbroads27.dff \ + LOD_kbroads28.dff \ + LOD_kbroads29.dff \ + LOD_kbroads30.dff \ + LOD_kbroads31.dff \ + LOD_kbroads32.dff \ + LOD_land013.dff \ + LOD_land014.dff \ + LOD_land015.dff \ + LOD_land019.dff \ + LOD_land020.dff \ + LOD_land023.dff \ + LOD_land025.dff \ + LOD_land026.dff \ + LOD_land028.dff \ + LOD_land029.dff \ + LOD_land030.dff \ + LOD_land031.dff \ + LOD_land033.dff \ + LOD_land034.dff \ + LOD_land037.dff \ + LOD_land038.dff \ + LOD_land039.dff \ + LOD_land040.dff \ + LOD_land041.dff \ + LOD_land045.dff \ + LOD_land047.dff \ + LOD_land048.dff \ + LOD_land048nite.dff \ + LOD_land049.dff \ + LOD_land050.dff \ + LOD_land052.dff \ + LOD_land053.dff \ + LOD_land054.dff \ + LOD_land055.dff \ + LOD_land056.dff \ + LOD_land057.dff \ + LOD_land058.dff \ + LOD_land063.dff \ + LOD_land063i.dff \ + LOD_land064.dff \ + LOD_land068.dff \ + LOD_land069.dff \ + LOD_land070.dff \ + LOD_land075a.dff \ + LOD_land075b.dff \ + LOD_land082.dff \ + LOD_land084.dff \ + LOD_land085.dff \ + LOD_land086.dff \ + LOD_land089a.dff \ + LOD_land089b.dff \ + LOD_land089c.dff \ + LOD_land090.dff \ + LOD_land091.dff \ + LOD_land097.dff \ + LOD_land098.dff \ + LOD_land099.dff \ + LOD_land100.dff \ + LOD_land101.dff \ + LOD_land102.dff \ + LOD_land103.dff \ + LOD_land104.dff \ + LOD_land105.dff \ + LOD_land107.dff \ + LOD_land108.dff \ + LOD_land109.dff \ + LOD_land110.dff \ + LOD_land111.dff \ + LOD_land112.dff \ + LOD_land113.dff \ + LOD_land116.dff \ + LOD_land118.dff \ + LOD_land119.dff \ + LOD_land119b.dff \ + LOD_land120.dff \ + LOD_land122.dff \ + LOD_land124.dff \ + LOD_land125ind.dff \ + LOD_land127.dff \ + LOD_land128.dff \ + LOD_land129.dff \ + LOD_land131.dff \ + LOD_land137.dff \ + LOD_land139.dff \ + LOD_land141.dff \ + LOD_land143.dff \ + LOD_land144.dff \ + LOD_land145.dff \ + LOD_land146.dff \ + LOD_land147.dff \ + LOD_land148.dff \ + LOD_land149.dff \ + LOD_land150.dff \ + LOD_land154.dff \ + LOD_land155.dff \ + LOD_land156.dff \ + LOD_land157.dff \ + LOD_land158.dff \ + LOD_land159.dff \ + LOD_land160.dff \ + LOD_land161.dff \ + LOD_land_128.dff \ + LOD_landnew1.dff \ + LOD_landnew2.dff \ + LOD_landnew21.dff \ + LOD_landnew221.dff \ + LOD_landnew221b.dff \ + LOD_landnew23.dff \ + LOD_landnew23a.dff \ + LOD_landnew23b.dff \ + LOD_landnew24.dff \ + LOD_landnew24b.dff \ + LOD_landnew25.dff \ + LOD_landnew3.dff \ + LOD_landnew3a.dff \ + LOD_maindrag1.dff \ + LOD_maindrag2.dff \ + LOD_mainten1.dff \ + LOD_mainten2.dff \ + LOD_mainten2b.dff \ + LOD_mainten3.dff \ + LOD_mainten5.dff \ + LOD_newbuilds01.dff \ + LOD_newbuilds02.dff \ + LOD_newbuilds03.dff \ + LOD_newbuilds06.dff \ + LOD_newbuilds07.dff \ + LOD_newland085.dff \ + LOD_newradio.dff \ + LOD_newrizzos.dff \ + LOD_newtenement01.dff \ + LOD_newtenement02.dff \ + LOD_newtenement03.dff \ + LOD_newtenement04.dff \ + LOD_newtenement05.dff \ + LOD_newtenement06.dff \ + LOD_newtenement07.dff \ + LOD_newtenement08.dff \ + LOD_newtenementx.dff \ + LOD_overpass19bkb.dff \ + LOD_overpass19kb.dff \ + LOD_overpass19kbc.dff \ + LOD_park1.dff \ + LOD_park1b.dff \ + LOD_park2.dff \ + LOD_park2b.dff \ + LOD_park3.dff \ + LOD_park3b.dff \ + LOD_pier3.dff \ + LOD_planeroad1.dff \ + LOD_planeroad2.dff \ + LOD_planeroad3.dff \ + LOD_planeroad4.dff \ + LOD_pod1.dff \ + LOD_pod2.dff \ + LOD_rave.dff \ + LOD_rave2.dff \ + LOD_roadgaz1.dff \ + LOD_roadgaz2.dff \ + LOD_roadkb22.dff \ + LOD_semtech.dff \ + LOD_shedbig1.dff \ + LOD_shedbig12.dff \ + LOD_shedbig13.dff \ + LOD_shedbig2.dff \ + LOD_shedbig3.dff \ + LOD_shedbig31.dff \ + LOD_shedbig4.dff \ + LOD_shedwee1.dff \ + LOD_shorecpark.dff \ + LOD_shorekb.dff \ + LOD_srway_01.dff \ + LOD_srway_02.dff \ + LOD_subroads02.dff \ + LOD_subroads03.dff \ + LOD_subroads04.dff \ + LOD_subroads05.dff \ + LOD_subroads06.dff \ + LOD_subroads07.dff \ + LOD_subroads08.dff \ + LOD_subroads09.dff \ + LOD_subroads10.dff \ + LOD_subroads11.dff \ + LOD_subroads12.dff \ + LOD_subroads13.dff \ + LOD_subroads14.dff \ + LOD_subroads15.dff \ + LOD_subroads16.dff \ + LOD_subroads17.dff \ + LOD_subroads18.dff \ + LOD_subroads19.dff \ + LOD_subroads20.dff \ + LOD_subroads21.dff \ + LOD_subroads22.dff \ + LOD_subroads23.dff \ + LOD_subroads24.dff \ + LOD_subroads25.dff \ + LOD_subroads26.dff \ + LOD_subroads27.dff \ + LOD_subroads28.dff \ + LOD_subroads29.dff \ + LOD_subroads30.dff \ + LOD_subroads31.dff \ + LOD_subroads32.dff \ + LOD_subroads33.dff \ + LOD_subroads34.dff \ + LOD_subroads35.dff \ + LOD_subroads36.dff \ + LOD_subroads37.dff \ + LOD_subroads38.dff \ + LOD_subroads39.dff \ + LOD_subroads40.dff \ + LOD_subroads41.dff \ + LOD_subroads42.dff \ + LOD_subroads43.dff \ + LOD_subroads44.dff \ + LOD_subroads45.dff \ + LOD_subroads46.dff \ + LOD_subroads47.dff \ + LOD_subroads48.dff \ + LOD_subroads49.dff \ + LOD_subroads50.dff \ + LOD_subroads51.dff \ + LOD_subroads52.dff \ + LOD_subroads53.dff \ + LOD_subroads54.dff \ + LOD_subroads55.dff \ + LOD_subroads56.dff \ + LOD_subroads57.dff \ + LOD_subroads58.dff \ + LOD_subroads59.dff \ + LOD_subroads60.dff \ + LOD_subroads61.dff \ + LOD_subroads62.dff \ + LOD_subroads63.dff \ + LOD_subroads64.dff \ + LOD_subroads65.dff \ + LOD_subroads66.dff \ + LOD_subroads67.dff \ + LOD_subroads68.dff \ + LOD_subroads69.dff \ + LOD_subroads70.dff \ + LOD_subroads71.dff \ + LOD_subroads72.dff \ + LOD_subroads73.dff \ + LOD_subroads74.dff \ + LOD_subroads75.dff \ + LOD_subroads76.dff \ + LOD_subroads77.dff \ + LOD_subroads78.dff \ + LOD_subroads79.dff \ + LOD_subroads80.dff \ + LOD_subroads81.dff \ + LOD_subroads82.dff \ + LOD_subroads83.dff \ + LOD_subroads84.dff \ + LOD_subroads85.dff \ + LOD_subroads86.dff \ + LOD_subroads87.dff \ + LOD_subroads96.dff \ + LOD_subroads97.dff \ + LOD_subroads98.dff \ + LOD_subroads99.dff \ + LOD_subway.dff \ + LOD_tar1.dff \ + LOD_tar2.dff \ + LOD_tar3.dff \ + LOD_tar4.dff \ + LOD_tar5.dff \ + LOD_tar6.dff \ + LOD_tar7.dff \ + LOD_tar8.dff \ + LOD_tar9.dff \ + LOD_tendragblk.dff \ + LOD_tendragblk2.dff \ + LODa5build2.dff \ + LODa5build5.dff \ + LODaSave.dff \ + LODadwaybuild.dff \ + LODadwaybuild2.dff \ + LODadwaybuild3.dff \ + LODage_oddjob.dff \ + LODament1ad.dff \ + LODament3ad.dff \ + LODaperkb3_nit.dff \ + LODarageext.dff \ + LODatree03.dff \ + LODbakland.dff \ + LODbigbuild.dff \ + LODbilbridge1.dff \ + LODblokb.dff \ + LODbntrnce_b.dff \ + LODbridgeroad.dff \ + LODbridgeroadb.dff \ + LODbuildind.dff \ + LODck3_flats2.dff \ + LODck3_flats3.dff \ + LODck3_flats4.dff \ + LODck3_scraper1.dff \ + LODck3_scraper2.dff \ + LODck3_scraper3.dff \ + LODck3scrtop.dff \ + LODck4_ground01.dff \ + LODck4_scraperl0.dff \ + LODck4_ten1.dff \ + LODck4_ten2.dff \ + LODck4_ten3.dff \ + LODck_maindrag.dff \ + LODcoasttrees1.dff \ + LODcoasttrees2.dff \ + LODcoasttrees3.dff \ + LODcom009.dff \ + LODcom010.dff \ + LODcom011.dff \ + LODcom012.dff \ + LODcom013.dff \ + LODcom017.dff \ + LODcom023.dff \ + LODcom024.dff \ + LODcom025.dff \ + LODcom026.dff \ + LODcom027.dff \ + LODcom028.dff \ + LODcom029.dff \ + LODcom030.dff \ + LODcom031.dff \ + LODcom032.dff \ + LODcom033.dff \ + LODcom034.dff \ + LODcom035.dff \ + LODcom039.dff \ + LODcom040.dff \ + LODcom041.dff \ + LODcom042.dff \ + LODcom043.dff \ + LODcom044.dff \ + LODcom045.dff \ + LODcom046.dff \ + LODcom048.dff \ + LODcom049.dff \ + LODcom050.dff \ + LODcom051.dff \ + LODcom052.dff \ + LODcom053.dff \ + LODcom057.dff \ + LODcom058.dff \ + LODcom059.dff \ + LODcom060.dff \ + LODcom061.dff \ + LODcom062.dff \ + LODcom063.dff \ + LODcom064.dff \ + LODcom065.dff \ + LODcom066.dff \ + LODcom068.dff \ + LODcom069.dff \ + LODcom070.dff \ + LODcom072.dff \ + LODcom073.dff \ + LODcom074.dff \ + LODcom075.dff \ + LODcom076.dff \ + LODcom077.dff \ + LODcom095.dff \ + LODcommyland.dff \ + LODcrdlrbuild01.dff \ + LODctm_rd4_ug.dff \ + LODctm_rd5_ug.dff \ + LODd_under.dff \ + LODdepot.dff \ + LODdepot_cover.dff \ + LODdepottar.dff \ + LODders01.dff \ + LODders02.dff \ + LODdge1.dff \ + LODdge2.dff \ + LODdge3.dff \ + LODdge4.dff \ + LODdge5.dff \ + LODdge6.dff \ + LODdge_liftsec.dff \ + LODdge_liftweight.dff \ + LODdgeroadsub01.dff \ + LODdgeroadsub02.dff \ + LODdgeroadsub03.dff \ + LODdgeroadsub04.dff \ + LODdockbuilding.dff \ + LODdockbuilding2.dff \ + LODdotree1.dff \ + LODdpart10.dff \ + LODdpart100.dff \ + LODdpart101.dff \ + LODdpart102.dff \ + LODdpart103.dff \ + LODdpart104.dff \ + LODdpart105.dff \ + LODdpart106.dff \ + LODdpart107.dff \ + LODdpart11.dff \ + LODdpart110.dff \ + LODdpart12.dff \ + LODdpart13.dff \ + LODdpart14.dff \ + LODdpart15.dff \ + LODdpart16.dff \ + LODdpart17.dff \ + LODdpart18.dff \ + LODdpart18a.dff \ + LODdpart19.dff \ + LODdpart21.dff \ + LODdpart23.dff \ + LODdpart25.dff \ + LODdpart26.dff \ + LODdpart30.dff \ + LODdpart7.dff \ + LODdpart85.dff \ + LODdpart86.dff \ + LODdpart87.dff \ + LODdpart88.dff \ + LODdpart89.dff \ + LODdpart90.dff \ + LODdpart91.dff \ + LODdpart92.dff \ + LODdpart93.dff \ + LODdpart94.dff \ + LODdpart95.dff \ + LODdpart96.dff \ + LODdpart97.dff \ + LODdpart98.dff \ + LODdpart99.dff \ + LODdpart_mount1.dff \ + LODdpart_mount2.dff \ + LODdpart_mount3.dff \ + LODdpart_mount33.dff \ + LODdpart_mount4.dff \ + LODdpart_mount44.dff \ + LODdtun_entrance.dff \ + LODe_platform.dff \ + LODe_start_box.dff \ + LODebarrier_gaz1.dff \ + LODebarrier_gaz2.dff \ + LODehouse1z.dff \ + LODehouse1z1.dff \ + LODehouse1z2.dff \ + LODehouse1z3.dff \ + LODehouse2z.dff \ + LODehouse3z.dff \ + LODel.dff \ + LODel2.dff \ + LODel3.dff \ + LODel4.dff \ + LODel5.dff \ + LODel_Tenemant1.dff \ + LODement2ad.dff \ + LODencom2.dff \ + LODent3.dff \ + LODent4.dff \ + LODentrance.dff \ + LODepatch.dff \ + LODepatch01_sub.dff \ + LODepatch02_sub.dff \ + LODepatch03.dff \ + LODepatch03_sub.dff \ + LODepatch04_sub.dff \ + LODepatch05_sub.dff \ + LODepatch06_sub.dff \ + LODepatch07_sub.dff \ + LODepatch08_sub.dff \ + LODepatch09_sub.dff \ + LODepatch10_sub.dff \ + LODepatch11_sub.dff \ + LODepatch12_sub.dff \ + LODepatch13_sub.dff \ + LODepatch14_sub.dff \ + LODepatch152_sub.dff \ + LODepatch153_sub.dff \ + LODepatch15_sub.dff \ + LODepatch16_sub.dff \ + LODepatch171_sub.dff \ + LODepatch172_sub.dff \ + LODepatch173_sub.dff \ + LODepatch17_sub.dff \ + LODepatch18_sub.dff \ + LODepatch19_sub.dff \ + LODepatch2.dff \ + LODepatch20_sub.dff \ + LODepatch212_sub.dff \ + LODepatch213_sub.dff \ + LODepatch214_sub.dff \ + LODepatch21_sub.dff \ + LODepatch22_sub.dff \ + LODepatch23_sub.dff \ + LODepatch24_sub.dff \ + LODepatch25_sub.dff \ + LODepatch26_sub.dff \ + LODepatch27_sub.dff \ + LODepatch28_sub.dff \ + LODepatch29_sub.dff \ + LODepatch2b.dff \ + LODepatch30_sub.dff \ + LODepatch31_sub.dff \ + LODepatch32_sub.dff \ + LODepatch34_sub.dff \ + LODepatch35_sub.dff \ + LODepatchcomtop1.dff \ + LODepatchindaa2.dff \ + LODepatchindnew.dff \ + LODepatchindnew2.dff \ + LODepatchkb4.dff \ + LODepatchkb5.dff \ + LODepatchkb6.dff \ + LODepatchkb7.dff \ + LODepatchnew_sub.dff \ + LODepatchttwrs.dff \ + LODeradam.dff \ + LODercenter.dff \ + LODerflat26.dff \ + LODerflat27.dff \ + LODerflat28.dff \ + LODerflat29.dff \ + LODergroSD1.dff \ + LODergroundS1.dff \ + LODergroundS2.dff \ + LODerground_over1.dff \ + LODerground_over12.dff \ + LODerground_over13.dff \ + LODerground_over14.dff \ + LODerground_over15.dff \ + LODerground_over2.dff \ + LODerground_over3.dff \ + LODerground_over4.dff \ + LODerground_over5.dff \ + LODerground_over6.dff \ + LODerground_over7.dff \ + LODerground_over9.dff \ + LODerind.dff \ + LODerind02.dff \ + LODerminalback.dff \ + LODerminalfront.dff \ + LODernew1.dff \ + LODersign.dff \ + LODersubridge1.dff \ + LODestatebuild1.dff \ + LODestation.dff \ + LODesuni1.dff \ + LODeum.dff \ + LODexpgrgesub.dff \ + LODf1.dff \ + LODf12.dff \ + LODf12_22.dff \ + LODf12_23.dff \ + LODf13.dff \ + LODf14.dff \ + LODf15.dff \ + LODf16.dff \ + LODf17.dff \ + LODf18.dff \ + LODf19.dff \ + LODf2.dff \ + LODf20.dff \ + LODf21.dff \ + LODf22.dff \ + LODf23.dff \ + LODf24.dff \ + LODf26.dff \ + LODf27.dff \ + LODf28.dff \ + LODf3.dff \ + LODf4.dff \ + LODf5.dff \ + LODf6.dff \ + LODgallerground.dff \ + LODgbbridgerda.dff \ + LODgbridgerdb.dff \ + LODgd_bit_side3.dff \ + LODglry_stn.dff \ + LODgstarbuild.dff \ + LODhedbuilding.dff \ + LODhibuild1.dff \ + LODhibuild10.dff \ + LODhibuild2.dff \ + LODhibuild3.dff \ + LODhibuild3B.dff \ + LODhibuild4.dff \ + LODhibuild5.dff \ + LODhibuild9.dff \ + LODhl_lndnite.dff \ + LODhpark2.dff \ + LODhwall.dff \ + LODice_Station_Sub.dff \ + LODice_Station_ind.dff \ + LODice_com.dff \ + LODiceally.dff \ + LODiceballhall.dff \ + LODicetenkb1.dff \ + LODicetenkb2.dff \ + LODicndo01.dff \ + LODicndo02.dff \ + LODigarage.dff \ + LODilsteps.dff \ + LODilstepswest0.dff \ + LODind_build01.dff \ + LODind_build02.dff \ + LODind_build03.dff \ + LODind_build06.dff \ + LODind_build07.dff \ + LODindseroad2.dff \ + LODino.dff \ + LODino_grnd.dff \ + LODinogrnd2.dff \ + LODinogrnd3.dff \ + LODinogrnd4.dff \ + LODipad.dff \ + LODir_terminal.dff \ + LODirestate.dff \ + LODistancoast01.dff \ + LODix_road1.dff \ + LODix_road2.dff \ + LODix_road3.dff \ + LODix_road4.dff \ + LODjectland1.dff \ + LODk3_flats02.dff \ + LODk3_flats03.dff \ + LODk3_flats04.dff \ + LODk3_flats1.dff \ + LODk3_ground.dff \ + LODkb_builds01.dff \ + LODkb_builds03.dff \ + LODkb_builds04.dff \ + LODkb_builds05.dff \ + LODkb_builds06.dff \ + LODkb_builds11.dff \ + LODkb_ground.dff \ + LODkb_raised.dff \ + LODkcranescale.dff \ + LODkdhse01.dff \ + LODkdhse020.dff \ + LODkpath1.dff \ + LODkwall.dff \ + LODkwall2z.dff \ + LODkwall3.dff \ + LODkwallbb_1.dff \ + LODkwallbb_2.dff \ + LODky_skyscrp6.dff \ + LODlA.dff \ + LODlB.dff \ + LODl_platformw.dff \ + LODl_tunnel1.dff \ + LODl_tunnel2.dff \ + LODlandnew4.dff \ + LODlandnew5.dff \ + LODlandnew6.dff \ + LODlandnew7.dff \ + LODlandnew8.dff \ + LODlandpart20.dff \ + LODlandpart22.dff \ + LODlandpart24.dff \ + LODlding_fucked.dff \ + LODldingground1.dff \ + LODldingground2.dff \ + LODldingground3.dff \ + LODldingground4.dff \ + LODldingground5.dff \ + LODldingground6.dff \ + LODlground02.dff \ + LODlground03.dff \ + LODlgroundb.dff \ + LODlightbuild03.dff \ + LODlightbuild05_dy.dff \ + LODlightbuild05_nt.dff \ + LODlightbuild06b.dff \ + LODlightbuild06e.dff \ + LODlightbuild06v.dff \ + LODlightbuild08a.dff \ + LODlightbuild09.dff \ + LODlightbuild10.dff \ + LODlightbuild11.dff \ + LODlightbuild12.dff \ + LODlightbuild13.dff \ + LODlightfront.dff \ + LODlnd1.dff \ + LODlsawmill.dff \ + LODm_cthdrlfcde.dff \ + LODm_stadium.dff \ + LODmill.dff \ + LODmnt4ad.dff \ + LODmnt6ad.dff \ + LODn3block.dff \ + LODn_alleygun.dff \ + LODn_baker01.dff \ + LODn_block01.dff \ + LODn_block06.dff \ + LODn_block07.dff \ + LODn_block09.dff \ + LODn_club01.dff \ + LODn_garage01.dff \ + LODn_hoteltop.dff \ + LODn_singbuild02.dff \ + LODnabuildne.dff \ + LODnabuildnew1.dff \ + LODnabuilds05.dff \ + LODnabuilds06.dff \ + LODnabuilds07.dff \ + LODnabuilds08.dff \ + LODnabuilds09.dff \ + LODnabuilds21.dff \ + LODnaquadhouse.dff \ + LODncoma02.dff \ + LODnd2.dff \ + LODnd_bit.dff \ + LODnderpas2.dff \ + LODnebasea.dff \ + LODnebaseb.dff \ + LODnel1.dff \ + LODnhall_nite.dff \ + LODnhallbig.dff \ + LODnk5land.dff \ + LODnksclbase.dff \ + LODnt5.dff \ + LODnt5ad.dff \ + LODnt6.dff \ + LODnt7.dff \ + LODnt8.dff \ + LODntrnce_b.dff \ + LODo_floor.dff \ + LODofis1.dff \ + LODofis2.dff \ + LODoheadqrts.dff \ + LODom_roadkb01.dff \ + LODoshp01.dff \ + LODoudet.dff \ + LODowerL.dff \ + LODp_grndfloor.dff \ + LODp_land2.dff \ + LODparknewtrees.dff \ + LODpatchindaa1.dff \ + LODpe_build_kb.dff \ + LODphouse.dff \ + LODpital_Sub.dff \ + LODpital_ind.dff \ + LODpitaland.dff \ + LODport_carpark.dff \ + LODportroad01.dff \ + LODportroad02.dff \ + LODportroad03.dff \ + LODportroad04.dff \ + LODportroad05.dff \ + LODportroad06.dff \ + LODportroad07.dff \ + LODportroad08.dff \ + LODportroad09.dff \ + LODportroad10.dff \ + LODportroad11.dff \ + LODpsmidbb.dff \ + LODra_house.dff \ + LODrd_bit_side4.dff \ + LODridgcabls01.dff \ + LODridgerda.dff \ + LODridgerdb.dff \ + LODridgfk2.dff \ + LODridgspprt01.dff \ + LODrjet.dff \ + LODrmas.dff \ + LODroad_se_01.dff \ + LODroad_se_02.dff \ + LODroad_se_04.dff \ + LODroad_se_05.dff \ + LODroad_se_06.dff \ + LODroad_se_07.dff \ + LODroad_se_08.dff \ + LODroad_se_09.dff \ + LODroad_se_10.dff \ + LODroad_se_11.dff \ + LODroad_se_12.dff \ + LODroad_se_13.dff \ + LODroad_se_14.dff \ + LODroad_se_15.dff \ + LODroad_se_16.dff \ + LODroad_se_17.dff \ + LODroad_se_18.dff \ + LODroad_se_19.dff \ + LODroad_se_20.dff \ + LODroadbit2.dff \ + LODroadbitindust.dff \ + LODrolstation.dff \ + LODroof_kb.dff \ + LODrpass_comse.dff \ + LODrpasscom04.dff \ + LODrpasscom05.dff \ + LODrpasscom06.dff \ + LODrpassind.dff \ + LODrsv_htl.dff \ + LODrthse_night.dff \ + LODs_bildkb_4.dff \ + LODs_buid02.dff \ + LODscrap_5.dff \ + LODscrap_6.dff \ + LODscrapenew.dff \ + LODscrpunbuilt2.dff \ + LODsion1.dff \ + LODsion2.dff \ + LODsion3.dff \ + LODsion4.dff \ + LODsion5.dff \ + LODsion6.dff \ + LODst_treepatch.dff \ + LODst_woodfence.dff \ + LODst_woodfence3.dff \ + LODswcentralbld1.dff \ + LODswcentralbld2.dff \ + LODswcentralbld3.dff \ + LODswcentralbld4.dff \ + LODswcentralbld5.dff \ + LODswcentralbld6.dff \ + LODswcentralbld7.dff \ + LODt_skyscrp1.dff \ + LODticalift_bridg2.dff \ + LODticalift_bridge.dff \ + LODtiron1.dff \ + LODtiron1b.dff \ + LODtiron1c.dff \ + LODtm_rd32.dff \ + LODtom_rd1_ug.dff \ + LODtom_rd2_ug.dff \ + LODtom_rd3_ug.dff \ + LODtopuni1.dff \ + LODtorye_roof1.dff \ + LODtoryf_walls1.dff \ + LODtorym_box.dff \ + LODtower1.dff \ + LODtower2.dff \ + LODtowerflat.dff \ + LODtreepatch06f.dff \ + LODtreepatch5.dff \ + LODtreepatch_sub.dff \ + LODtreepatchprk.dff \ + LODtrees1_sub.dff \ + LODtship_structure.dff \ + LODunderpass.dff \ + LODuni_gz.dff \ + LODunigrnd.dff \ + LODunitrepatch.dff \ + LODurbbridge1.dff \ + LODustpatch05.dff \ + LODway_footbridge.dff \ + LODwn_fmrkt.dff \ + LODy_skyscrp23.dff \ + LODy_skyscrp26b.dff \ + LUIGICLUBout.dff \ + Lod_indserdbit.dff \ + Mansion_feature.dff \ + Mistydoor.dff \ + Money.dff \ + New_Colmansn.dff \ + Otherside_subway.dff \ + PoliceBallSigns.dff \ + Police_Station_Sub.dff \ + Police_Station_ind.dff \ + Pumpfirescape.dff \ + Pumphouse.dff \ + REBEL.dff \ + Rear_Building.dff \ + SalvGarage.dff \ + Scffldng02top.dff \ + Security_Hut.dff \ + Streetlamp1.dff \ + Streetlamp2.dff \ + Sub_sprayshopdoor.dff \ + Subestatebuild1.dff \ + Subway_monument.dff \ + SupaSave.dff \ + Tunl_Junc_Road01.dff \ + Tunl_Junc_Road02.dff \ + Tunl_Junc_Road03.dff \ + adrenaline.dff \ + air_brway_01.dff \ + air_brway_02.dff \ + air_brway_03.dff \ + air_grnd01.dff \ + air_grnd02.dff \ + air_grnd03.dff \ + air_grnd0301.dff \ + air_grnd04.dff \ + air_grnd05.dff \ + air_grnd06.dff \ + air_grnd08.dff \ + air_grnd09.dff \ + air_grnd10.dff \ + air_grnd11.dff \ + air_grnd12.dff \ + air_grnd13.dff \ + air_grnd14.dff \ + air_grnd7.dff \ + air_planeroad1.dff \ + air_planeroad2.dff \ + air_planeroad3.dff \ + air_planeroad4.dff \ + air_smalldetail.dff \ + air_srway_01.dff \ + air_srway_02.dff \ + airport_carpark.dff \ + airportfence4.dff \ + airportfence5.dff \ + airportfence6.dff \ + airportgate.dff \ + airportjunc01.dff \ + airportsteps.dff \ + airpshadows01.dff \ + airpshadows02.dff \ + airpshadows03.dff \ + airpshadows04.dff \ + airpshadows05.dff \ + airpshadows06.dff \ + airtower1.dff \ + airtower2.dff \ + airtrain.dff \ + ambulan.dff \ + amco_floor.dff \ + amcogaragedoor.dff \ + amcoheadqrts.dff \ + amcoudet.dff \ + amcounder.dff \ + ammupoly.dff \ + apTerminalback.dff \ + apair_terminal.dff \ + apairporthanger.dff \ + apairporthangerA.dff \ + apairprtbits.dff \ + apterminalfront.dff \ + aptowerL.dff \ + arc_bridge.dff \ + archedbuilding.dff \ + area5build2.dff \ + area5build5.dff \ + army.DFF \ + art_glry_bboard.dff \ + artgallerground.dff \ + artglry_reflection.dff \ + artglry_stn.dff \ + asuka.dff \ + asukah.dff \ + b_man1.dff \ + b_man2.dff \ + b_man3.dff \ + b_wom1.dff \ + b_wom2.dff \ + b_wom3.dff \ + back_box.dff \ + backdoor.dff \ + bank_graffiti.dff \ + bankd.DFF \ + bankdoor.dff \ + bankjobdoor.dff \ + banshee.dff \ + bar_barrier10.dff \ + bar_barrier10b.dff \ + bar_barrier12.dff \ + bar_barrier16.dff \ + bar_barriergate1.dff \ + bar_gatebar01.dff \ + bar_gatebox01.dff \ + barracks.dff \ + barrel1.dff \ + barrel2.dff \ + barrel3.dff \ + barrel4.dff \ + barrelexpos.dff \ + barrierturn.dff \ + basketballcourt.dff \ + basketballnet.dff \ + bellyup.dff \ + bfinject.dff \ + big_rock.dff \ + billboard01.dff \ + billboard02.dff \ + billboard03.dff \ + billbrd_opra.dff \ + bin1.dff \ + blista.dff \ + block3_fireesc_1.dff \ + block3_fireesc_2.dff \ + block3_flats2.dff \ + block3_flats3.dff \ + block3_flats4.dff \ + block3_scraper1.dff \ + block3_scraper2.dff \ + block3_scraper3.dff \ + block3_shops2.dff \ + block3_shops3.dff \ + block3_shops4.dff \ + block3_shops5.dff \ + block3scrtop.dff \ + block4_alley1.dff \ + block4_alley2.dff \ + block4_curstair.dff \ + block4_ground01.dff \ + block4_groundbits1.dff \ + block4_scplights.dff \ + block4_scraperl0.dff \ + block4_ten1.dff \ + block4_ten2.dff \ + block4_ten3.dff \ + block_maindrag.dff \ + blok3_flats02.dff \ + blok3_flats03.dff \ + blok3_flats04.dff \ + blok3_flats1.dff \ + blok3_ground.dff \ + blok3_wall3.dff \ + boatcranelg.dff \ + boatcranesm.dff \ + boatramp1.dff \ + bobcat.dff \ + bodyarmour.dff \ + bodycast.dff \ + bogdoor.dff \ + bollard.dff \ + bollardlight.dff \ + bombdoor.dff \ + bomber.dff \ + bombshop_kb.dff \ + bonus.dff \ + borgnine.dff \ + bouy.dff \ + bribe.dff \ + bridge_endbit01.dff \ + bridge_liftsec.dff \ + bridge_liftweight.dff \ + bridge_path_parent.dff \ + bridge_support.dff \ + bridgefuka.dff \ + bridgefukb.dff \ + bridgeroadsub01.dff \ + bridgeroadsub02.dff \ + bridgeroadsub03.dff \ + bridgeroadsub04.dff \ + bridgesupport1.dff \ + bridgesupport2.dff \ + briefcase.dff \ + broadwaybuild.dff \ + broadwaybuild2.dff \ + broadwaybuild3.dff \ + broken_inside.dff \ + broken_outside.dff \ + bsemnt_cprk.dff \ + building3.dff \ + building4.dff \ + building5.dff \ + building_fucked.dff \ + buildingalarm.dff \ + buildingground1.dff \ + buildingground2.dff \ + buildingground3.dff \ + buildingground4.dff \ + buildingground5.dff \ + buildingground6.dff \ + bullion.dff \ + bus.dff \ + busdepot.dff \ + busdepot_cover.dff \ + busdepottar.dff \ + bussign1.dff \ + butler.dff \ + bvbridgspprt01.dff \ + cabbie.dff \ + calahan_bboard.dff \ + camerapickup.dff \ + camo_netting.dff \ + camo_sups.dff \ + canopy_grn_kb.dff \ + canopy_red_kb.dff \ + canopy_wht_kb.dff \ + canopy_whtlrg_kb.dff \ + caparwall3.dff \ + car_fucko.dff \ + cardboardbox.dff \ + cardboardbox2.dff \ + cardboardbox4.dff \ + carlift01.dff \ + carlift02.dff \ + carpark45.dff \ + carpark55.dff \ + carparkfence.dff \ + carwalls.dff \ + cas_man.dff \ + cas_wom.dff \ + casino.dff \ + casino_garden.dff \ + casino_grnd.dff \ + casinogrnd2.dff \ + casinogrnd3.dff \ + casinogrnd4.dff \ + cat.dff \ + cath.dff \ + ch_btmflr_3_kb.dff \ + ch_pil_kb.dff \ + ch_roof_kb.dff \ + chassis_heliradio.dff \ + cheetah.dff \ + chimney01.dff \ + chinabanner1.dff \ + chinabanner2.dff \ + chinabanner3.dff \ + chinabanner4.dff \ + chinabuildorn.dff \ + chinalanterns.dff \ + chinaledge2.dff \ + chinaquadhouse.dff \ + chnabankdoor.dff \ + chopper.dff \ + chtwn_fmrkt.dff \ + chunk5land.dff \ + chunky.DFF \ + clif1.dff \ + clif12.dff \ + clif12_2.dff \ + clif12_22.dff \ + clif12_23.dff \ + clif14.dff \ + clif15.dff \ + clif16.dff \ + clif17.dff \ + clif18.dff \ + clif19.dff \ + clif2.dff \ + clif20.dff \ + clif21.dff \ + clif22.dff \ + clif23.dff \ + clif24.dff \ + clif26.dff \ + clif27.dff \ + clif28.dff \ + clif3.dff \ + clif4.dff \ + clif5.dff \ + clif6.dff \ + cliffwall01.dff \ + cliffwall02.dff \ + cliffwall03.dff \ + cliffwall04.dff \ + cliffwall05.dff \ + cliffwall06.dff \ + cliffwall07.dff \ + cliffwall08.dff \ + cliffwall09.dff \ + cliffwall10.dff \ + cliffwall11.dff \ + cm1waycrosscom.dff \ + cmlnd2.dff \ + coach.dff \ + coast_treepatch.dff \ + coffee.dff \ + col1.dff \ + col1h.dff \ + col2.dff \ + col3.dff \ + col_comp1.dff \ + col_comp3.dff \ + col_comp5.dff \ + col_comp6.dff \ + colcomp4bits01.dff \ + colrob.dff \ + colt1.dff \ + colt2.dff \ + columansion_wall.dff \ + columb.dff \ + com1way1A50s.dff \ + com1way1bA50.dff \ + com1way1bA50s.dff \ + com1wayb1A50s.dff \ + com_1way10.dff \ + com_1way125.dff \ + com_1way20.dff \ + com_1way25.dff \ + com_1way50.dff \ + com_1wayB50.dff \ + com_1waybt3.dff \ + com_1waycrnr.dff \ + com_1wayrB50.dff \ + com_1waysl250.dff \ + com_1wayt3.dff \ + com_21way10.dff \ + com_21way20.dff \ + com_21way5.dff \ + com_21way50.dff \ + com_2lane_tunnl.dff \ + com_bridgesign.dff \ + com_cland127.dff \ + com_cusagroad1.dff \ + com_cusagroad2.dff \ + com_cust_roads14.dff \ + com_cust_roads18.dff \ + com_cust_roads19.dff \ + com_cust_roads20.dff \ + com_cust_roads21.dff \ + com_cust_roads22.dff \ + com_cust_roads23.dff \ + com_cust_roads24.dff \ + com_cust_roads25.dff \ + com_cust_roads26.dff \ + com_cust_roads27.dff \ + com_cust_roads28.dff \ + com_cust_roads29.dff \ + com_cust_roads30.dff \ + com_cust_roads31.dff \ + com_cust_roads310.dff \ + com_cust_roads32.dff \ + com_cust_roads33.dff \ + com_cust_roads34.dff \ + com_cust_roads35.dff \ + com_cust_roads36.dff \ + com_cust_roads38.dff \ + com_cust_roads39.dff \ + com_cust_roads40.dff \ + com_cust_roads42.dff \ + com_cust_roads42b.dff \ + com_cust_roads44.dff \ + com_cust_roads45.dff \ + com_cust_roads46.dff \ + com_cust_roads47.dff \ + com_cust_roads48.dff \ + com_cust_roads49.dff \ + com_cust_roads51.dff \ + com_cust_roads52.dff \ + com_cust_roads53.dff \ + com_cust_roads54.dff \ + com_cust_roads55.dff \ + com_cust_roads57.dff \ + com_custm_rd01.dff \ + com_custm_rd02.dff \ + com_custm_rd03.dff \ + com_custm_rd04.dff \ + com_custm_rd05.dff \ + com_custm_rd_gaz1.dff \ + com_custm_rd_gaz2.dff \ + com_docksaa.dff \ + com_docksb.dff \ + com_gaz2way.dff \ + com_gazground.dff \ + com_hospital.dff \ + com_land013.dff \ + com_land023.dff \ + com_land045.dff \ + com_land048nite.dff \ + com_land050.dff \ + com_land064.dff \ + com_land075a.dff \ + com_land075b.dff \ + com_land095.dff \ + com_land119.dff \ + com_land119b.dff \ + com_land120.dff \ + com_land_128.dff \ + com_landnew1.dff \ + com_landnew2.dff \ + com_landnew21.dff \ + com_landnew221.dff \ + com_landnew221b.dff \ + com_landnew23.dff \ + com_landnew23a.dff \ + com_landnew23b.dff \ + com_landnew24.dff \ + com_landnew24b.dff \ + com_landnew25.dff \ + com_landnew3.dff \ + com_landnew3a.dff \ + com_munation.dff \ + com_newland085.dff \ + com_park1.dff \ + com_park1b.dff \ + com_park2.dff \ + com_park2b.dff \ + com_park3.dff \ + com_park3b.dff \ + com_pier3.dff \ + com_road4116.dff \ + com_road41b.dff \ + com_roadkb02.dff \ + com_roadkb03.dff \ + com_roadkb05.dff \ + com_roadkb06.dff \ + com_roadkb07.dff \ + com_roadkb08.dff \ + com_roadkb11.dff \ + com_roadkb12.dff \ + com_roadkb13.dff \ + com_roadkb14.dff \ + com_roadkb17.dff \ + com_roadkb18.dff \ + com_roadkb19.dff \ + com_roadkb20.dff \ + com_roadkb21.dff \ + com_roadkb22.dff \ + com_roadkb23.dff \ + com_roadsrv.dff \ + com_rvroads52.dff \ + com_shorecpark.dff \ + com_shorekb.dff \ + com_spotbeams.dff \ + com_spotlites.dff \ + combaskoverlay.dff \ + combillboard03.dff \ + combillboard04.dff \ + combtm_treeshad01.dff \ + combtm_treeshad02.dff \ + combtm_treeshad03.dff \ + combtm_treeshad04.dff \ + combtm_treeshad05.dff \ + combtm_treeshad06.dff \ + comerside_subway.dff \ + comlandnew4.dff \ + comlandnew5.dff \ + comlandnew6.dff \ + comlandnew7.dff \ + comlandnew8.dff \ + comlights01.dff \ + comlights02.dff \ + comlights03.dff \ + comlights04.dff \ + comlights05.dff \ + comlights06.dff \ + comlights07.dff \ + comlights08.dff \ + comlights09.dff \ + comlights_top01.dff \ + comlights_top02.dff \ + comlights_top03.dff \ + comlights_top04.dff \ + comlights_top05.dff \ + comlnd1.dff \ + comparkbits.dff \ + comparknewtrees.dff \ + comrdnewfkr.dff \ + comroadnewbas.dff \ + comsbilboard05.dff \ + comsenullnodea01.dff \ + comsenullnodea02.dff \ + comsenullnodea03.dff \ + comsenullnodea04.dff \ + comsenullnodea05.dff \ + comsenullnodea06.dff \ + comsenullnodea07.dff \ + comsenullnodea08.dff \ + comsenullnodea09.dff \ + comsenullnodea10.dff \ + comsenullnodea11.dff \ + comswcentralbld1.dff \ + comswcentralbld2.dff \ + comswcentralbld3.dff \ + comswcentralbld4.dff \ + comswcentralbld5.dff \ + comswcentralbld6.dff \ + comswcentralbld7.dff \ + comtop_jety.dff \ + comtop_tshad.dff \ + comtop_tshad2.dff \ + comtop_tshad3.dff \ + comtop_tshad4.dff \ + comtop_tshad5.dff \ + comtop_tshad6.dff \ + comtopuni1.dff \ + comtreepatchprk.dff \ + condo_ivy.dff \ + condotree01.dff \ + condotree1.dff \ + cons_buid02.dff \ + cons_build2base.dff \ + const1.dff \ + const2.dff \ + const_woodfence.dff \ + const_woodfence3.dff \ + convstore01.dff \ + convstore01_door.dff \ + convstre_dmge01.dff \ + cooker1.dff \ + cop.DFF \ + cop2.dff \ + corpse.dff \ + courthse_night.dff \ + cranebasea.dff \ + cranebaseb.dff \ + cranesmalltop.dff \ + cranetopa.dff \ + cranetopb.dff \ + crbaskt02.dff \ + crgoshp01.dff \ + criminal01.DFF \ + criminal02.DFF \ + crushercrush.dff \ + crushertop.dff \ + cs8_door.dff \ + cs_ban.dff \ + cs_bomb.DFF \ + cs_loot.dff \ + csitecutscene.dff \ + csiteinttran.dff \ + csky_skyscrp23.dff \ + csky_skyscrp26b.dff \ + ct_man1.dff \ + ct_man2.dff \ + ct_wom1.dff \ + ct_wom2.dff \ + curly.dff \ + curlyh.dff \ + cussRoads5.dff \ + custm_rd12.dff \ + custm_rd19.dff \ + custm_rd20.dff \ + custm_rd21.dff \ + custm_rd32.dff \ + custm_rd33.dff \ + custm_rd34.dff \ + custm_rd35.dff \ + custm_rd36.dff \ + custm_rd38.dff \ + custm_rd39.dff \ + custm_rd40.dff \ + custm_rd41.dff \ + custm_rd42.dff \ + custm_rd43.dff \ + custm_rd44.dff \ + custm_rd51.dff \ + custm_rd53.dff \ + custm_rd54.dff \ + custm_rd56.dff \ + custm_rd57.dff \ + custm_rd58.dff \ + custm_rd59.dff \ + custm_rd60.dff \ + custm_rd61.dff \ + custm_rd62.dff \ + custm_rd71.dff \ + custm_rd72.dff \ + custm_rd75.dff \ + custm_rd76.dff \ + custm_rd79.dff \ + custm_rd792.dff \ + custm_rd82.dff \ + custm_rd83.dff \ + custm_rd84.dff \ + custm_rd85.dff \ + custm_rd86.dff \ + custm_rd87.dff \ + custm_rd91.dff \ + custm_rd_splay1.dff \ + custm_rd_splay1b.dff \ + custm_rd_splay2.dff \ + custm_rd_splay3.dff \ + custm_rd_splay4.dff \ + custm_rd_splay5.dff \ + custm_rd_splay6.dff \ + custm_rd_xroad.dff \ + custom_junction1.dff \ + custom_junction2.dff \ + custom_meshfence.dff \ + custom_rd1_ug.dff \ + custom_rd2_ug.dff \ + custom_rd3_ug.dff \ + custom_rd4_ug.dff \ + custom_rd5_ug.dff \ + custom_turn4.dff \ + customroadtr.dff \ + cutscene_cameras.dff \ + cutsceneroad1.dff \ + cutsceneroad2.dff \ + cutsceneroad3.dff \ + cutsceneroad4.dff \ + cutscenevan1.dff \ + d4props.dff \ + dam_base.dff \ + dam_road1.dff \ + damfence01.dff \ + damfence02.dff \ + damfence03.dff \ + damfence04.dff \ + damfence05.dff \ + damfence06.dff \ + damfence07.dff \ + damfence08.dff \ + damfencing.dff \ + damgbbridgerda.dff \ + damgbridgerdb.dff \ + damissionfence.dff \ + darkel.dff \ + deaddodo.dff \ + dealer.dff \ + decking1.dff \ + decking3.dff \ + diablos.dff \ + dinerind.dff \ + dinerind02.dff \ + dinersign.dff \ + diortpass.dff \ + dirtpass2.dff \ + doc_crane_cab.dff \ + doc_crane_leggs.dff \ + doc_floodlite.dff \ + doc_rave.dff \ + doc_rave2.dff \ + doc_shedbig1.dff \ + doc_shedbig12.dff \ + doc_shedbig13.dff \ + doc_shedbig2.dff \ + doc_shedbig3.dff \ + doc_shedbig31.dff \ + doc_shedbig4.dff \ + doc_shedwee1.dff \ + doc_shedwkway.dff \ + doc_tar1.dff \ + doc_tar2.dff \ + doc_tar3.dff \ + doc_tar4.dff \ + doc_tar5.dff \ + doc_tar6.dff \ + doc_tar7.dff \ + doc_tar8.dff \ + doc_tar9.dff \ + dockcranescale.dff \ + docker1.dff \ + docker2.dff \ + dockshadow01.dff \ + dockshadow02.dff \ + dockshadow03.dff \ + dockwall.dff \ + dockwall2z.dff \ + dockwall3.dff \ + dockwallbb_1.dff \ + dockwallbb_2.dff \ + dodo.dff \ + dogfoodoor01.dff \ + donkeymag.dff \ + donky.dff \ + door1_garage.dff \ + door2_garage.dff \ + door3_garage.dff \ + door4_garage.dff \ + door_bombshop.dff \ + door_col_compnd_01.dff \ + door_col_compnd_02.dff \ + door_col_compnd_03.dff \ + door_col_compnd_04.dff \ + door_col_compnd_05.dff \ + door_fucked.dff \ + door_jmsgrage.dff \ + door_sfehousegrge.dff \ + double_garage_dr.dff \ + doublestreetlght1.dff \ + dump1.dff \ + dumpster01.dff \ + eight.DFF \ + eight2.DFF \ + eighth.dff \ + eitballsdoor.dff \ + eitdoor.DFF \ + empirestate.dff \ + empstat_basement.dff \ + enforcer.dff \ + escape.dff \ + esperant.dff \ + f_escape_side1.dff \ + factorye_roof1.dff \ + factoryf_walls1.dff \ + factorym_box.dff \ + faketarget.dff \ + fan_man1.dff \ + fan_man2.dff \ + fan_wom.dff \ + fatfemale01.DFF \ + fatfemale02.DFF \ + fatmale01.DFF \ + fatmale02.DFF \ + fbi.DFF \ + fbicar.dff \ + female01.DFF \ + female02.DFF \ + female03.DFF \ + fencesmallb.dff \ + files.dff \ + fire_hydrant.dff \ + fire_platform.dff \ + firedoor.dff \ + firegrass.dff \ + fireman.DFF \ + firescapa1.dff \ + firescapb1.dff \ + firestation.dff \ + firetruk.dff \ + fish01.dff \ + fishfctory.dff \ + fishoildrum.dff \ + fishstall03.dff \ + fishstall04.dff \ + fishwall.dff \ + fixed_inside.dff \ + fixed_outside.dff \ + flagsitaly.dff \ + flatbed.dff \ + flatiron1.dff \ + flatiron1b.dff \ + flatiron1c.dff \ + flatironlt.dff \ + flatironlt01.dff \ + flatironlt02.dff \ + floatpackge1.dff \ + forsalesign3.dff \ + foundation.dff \ + fraightback01.dff \ + fraightback02.dff \ + fraightback03.dff \ + fraightback04.dff \ + frankh.dff \ + frankie.DFF \ + franksclb02.dff \ + franksclbase.dff \ + fratehuge.dff \ + fshcrte01.dff \ + fshfctry_dstryd.dff \ + fshgtsbx_dstryd.dff \ + fshgutsbox.dff \ + fuckedup_skewlbus.dff \ + fuel3.dff \ + fuel4.dff \ + fuel8.dff \ + fuel9.dff \ + fulcase.dff \ + fuzballdoor.dff \ + gRD_overpass12.dff \ + gRD_overpass14.dff \ + gRD_overpass16.dff \ + gRD_overpass17.dff \ + gRD_overpass18kb.dff \ + gRD_overpass19bkb.dff \ + gRD_overpass19kb.dff \ + gRD_overpass19kbc.dff \ + gang01.DFF \ + gang02.DFF \ + gang03.DFF \ + gang04.DFF \ + gang05.DFF \ + gang06.DFF \ + gang07.DFF \ + gang08.DFF \ + gang09.DFF \ + gang10.DFF \ + gang11.DFF \ + gang12.DFF \ + gang13.DFF \ + gang14.DFF \ + gangp.DFF \ + garage7.dff \ + garage_kb.dff \ + garage_oddjob.dff \ + gardenbencha.dff \ + gate.dff \ + gate2.dff \ + gate_small3.dff \ + ghost.dff \ + girders01.dff \ + girders02.dff \ + glassfx1.dff \ + glassfx2.dff \ + glassfx3.dff \ + glassfx4.dff \ + glassfx55.dff \ + glassfx_composh.dff \ + glassfxsub1.dff \ + glassfxsub2.dff \ + golf13.dff \ + goon.dff \ + grassyknoll2.dff \ + graveyard.dff \ + gser_portacabin02.dff \ + hangar_boxes.dff \ + hangar_frate.dff \ + hangardoor1.dff \ + hangardoor2.dff \ + health.dff \ + helipolice.dff \ + heliradio.dff \ + helirock.dff \ + helix_barrier.dff \ + helix_road1.dff \ + helix_road2.dff \ + helix_road3.dff \ + helix_road4.dff \ + hghwysgn01.dff \ + hghwysgn02.dff \ + hghwysgn_sub.dff \ + hill_tunnel1.dff \ + hill_tunnel2.dff \ + hoods.dff \ + hos_man.dff \ + hos_wom.dff \ + hosp_land2.dff \ + hospitaland.dff \ + hotel_outsidelight.dff \ + idaho.dff \ + impex_door.dff \ + impexpgrgesub.dff \ + impexpsubgrgdoor.dff \ + indNcoast1.dff \ + indNcoast2.dff \ + indNcoast3.dff \ + indNcoast5.dff \ + indNcoast6.dff \ + ind_brgrd1way.dff \ + ind_customnew1.dff \ + ind_customrd042.dff \ + ind_customroad001.dff \ + ind_customroad002.dff \ + ind_customroad003.dff \ + ind_customroad004.dff \ + ind_customroad005.dff \ + ind_customroad006.dff \ + ind_customroad008.dff \ + ind_customroad009.dff \ + ind_customroad010.dff \ + ind_customroad011.dff \ + ind_customroad012.dff \ + ind_customroad013.dff \ + ind_customroad014.dff \ + ind_customroad015.dff \ + ind_customroad016.dff \ + ind_customroad017.dff \ + ind_customroad018.dff \ + ind_customroad019.dff \ + ind_customroad020.dff \ + ind_customroad021.dff \ + ind_customroad022.dff \ + ind_customroad023.dff \ + ind_customroad024.dff \ + ind_customroad025.dff \ + ind_customroad026.dff \ + ind_customroad028.dff \ + ind_customroad029.dff \ + ind_customroad033.dff \ + ind_customroad034.dff \ + ind_customroad035.dff \ + ind_customroad036.dff \ + ind_customroad037.dff \ + ind_customroad038.dff \ + ind_customroad039.dff \ + ind_customroad040.dff \ + ind_customroad041.dff \ + ind_customroad042.dff \ + ind_customroad043.dff \ + ind_customroad044.dff \ + ind_customroad045.dff \ + ind_customroad046.dff \ + ind_customroad047.dff \ + ind_customroad048.dff \ + ind_customroad049.dff \ + ind_customroad050.dff \ + ind_customroad051.dff \ + ind_customroad052.dff \ + ind_customroad053.dff \ + ind_customroad054.dff \ + ind_customroad055.dff \ + ind_customroad056.dff \ + ind_customroad057.dff \ + ind_customroad058.dff \ + ind_customroad059.dff \ + ind_customroad060.dff \ + ind_customroad061.dff \ + ind_customroad062.dff \ + ind_customroad063.dff \ + ind_customroad064.dff \ + ind_customroad065.dff \ + ind_customroad066.dff \ + ind_customroad067.dff \ + ind_customroad068.dff \ + ind_customroad069.dff \ + ind_customroad070.dff \ + ind_customroad071.dff \ + ind_customroad072.dff \ + ind_customroad073.dff \ + ind_customroad074.dff \ + ind_customroad075.dff \ + ind_customroad076.dff \ + ind_customroad077.dff \ + ind_customroad078.dff \ + ind_customroad079.dff \ + ind_customroad080.dff \ + ind_customroad081.dff \ + ind_customroad082.dff \ + ind_customroad083.dff \ + ind_customroad084.dff \ + ind_customroad085.dff \ + ind_customroad086.dff \ + ind_customroad087.dff \ + ind_customroad088.dff \ + ind_customroad089.dff \ + ind_customroad090.dff \ + ind_customroad091.dff \ + ind_customroad093.dff \ + ind_customroad094.dff \ + ind_customroad095.dff \ + ind_customroad096.dff \ + ind_customroad097.dff \ + ind_customroad098.dff \ + ind_customroad099.dff \ + ind_customroad0bb.dff \ + ind_customroad102.dff \ + ind_customroad103.dff \ + ind_customroad104.dff \ + ind_customroad106.dff \ + ind_customroad107.dff \ + ind_customroad108.dff \ + ind_customroad109.dff \ + ind_customroad110.dff \ + ind_customroad111.dff \ + ind_customroad112.dff \ + ind_customroad113.dff \ + ind_customroad114.dff \ + ind_customroad115.dff \ + ind_customroad116.dff \ + ind_customroad117.dff \ + ind_customroad118.dff \ + ind_customroad119.dff \ + ind_customroad120.dff \ + ind_fuckedroad.dff \ + ind_inland667.dff \ + ind_jetnoside02.dff \ + ind_land006.dff \ + ind_land010.dff \ + ind_land012.dff \ + ind_land014.dff \ + ind_land015.dff \ + ind_land019.dff \ + ind_land020.dff \ + ind_land021.dff \ + ind_land025.dff \ + ind_land026.dff \ + ind_land028.dff \ + ind_land029.dff \ + ind_land030.dff \ + ind_land031.dff \ + ind_land033.dff \ + ind_land034.dff \ + ind_land037.dff \ + ind_land038.dff \ + ind_land039.dff \ + ind_land040.dff \ + ind_land041.dff \ + ind_land043.dff \ + ind_land044.dff \ + ind_land045.dff \ + ind_land047.dff \ + ind_land048.dff \ + ind_land049.dff \ + ind_land052.dff \ + ind_land053.dff \ + ind_land054.dff \ + ind_land055.dff \ + ind_land056.dff \ + ind_land057.dff \ + ind_land058.dff \ + ind_land059.dff \ + ind_land063i.dff \ + ind_land065.dff \ + ind_land067.dff \ + ind_land068.dff \ + ind_land070.dff \ + ind_land078.dff \ + ind_land082.dff \ + ind_land084.dff \ + ind_land085.dff \ + ind_land086.dff \ + ind_land089a.dff \ + ind_land089b.dff \ + ind_land089c.dff \ + ind_land090.dff \ + ind_land091.dff \ + ind_land092.dff \ + ind_land095.dff \ + ind_land097.dff \ + ind_land098.dff \ + ind_land099.dff \ + ind_land100.dff \ + ind_land101.dff \ + ind_land102.dff \ + ind_land103.dff \ + ind_land104.dff \ + ind_land105.dff \ + ind_land106.dff \ + ind_land107.dff \ + ind_land108.dff \ + ind_land109.dff \ + ind_land110.dff \ + ind_land111.dff \ + ind_land112.dff \ + ind_land113.dff \ + ind_land116.dff \ + ind_land118.dff \ + ind_land120.dff \ + ind_land122.dff \ + ind_land123.dff \ + ind_land124.dff \ + ind_land125ind.dff \ + ind_land127.dff \ + ind_land128.dff \ + ind_land129.dff \ + ind_land130.dff \ + ind_land131.dff \ + ind_land137.dff \ + ind_land139.dff \ + ind_land141.dff \ + ind_land143.dff \ + ind_land144.dff \ + ind_land145.dff \ + ind_land146.dff \ + ind_land147.dff \ + ind_land148.dff \ + ind_land149.dff \ + ind_land150.dff \ + ind_land154.dff \ + ind_land155.dff \ + ind_land156.dff \ + ind_land157.dff \ + ind_land158.dff \ + ind_land159.dff \ + ind_land160.dff \ + ind_land161.dff \ + ind_maindrag1.dff \ + ind_maindrag2.dff \ + ind_mainten1.dff \ + ind_mainten2.dff \ + ind_mainten2b.dff \ + ind_mainten3.dff \ + ind_mainten5.dff \ + ind_maintglas.dff \ + ind_newbuilds01.dff \ + ind_newbuilds02.dff \ + ind_newbuilds03.dff \ + ind_newbuilds06.dff \ + ind_newbuilds07.dff \ + ind_newradio.dff \ + ind_newrizzos.dff \ + ind_newtenement01.dff \ + ind_newtenement02.dff \ + ind_newtenement03.dff \ + ind_newtenement04.dff \ + ind_newtenement05.dff \ + ind_newtenement06.dff \ + ind_newtenement07.dff \ + ind_newtenement08.dff \ + ind_newtenementx.dff \ + ind_plyrwoor.dff \ + ind_roadcust335.dff \ + ind_roadcust338.dff \ + ind_roadcustom333.dff \ + ind_roadcustom334.dff \ + ind_slidedoor.dff \ + ind_spaces5.dff \ + ind_tendragblk.dff \ + ind_tendragblk2.dff \ + indalleytransd.dff \ + indatree03.dff \ + indbakland.dff \ + indbigbuild.dff \ + indbilbridge1.dff \ + inddockcrrate2.dff \ + indfrate03.dff \ + indfrate09.dff \ + indfrate10.dff \ + indfrate11.dff \ + indhelix_barrier.dff \ + indhibuild1.dff \ + indhibuild10.dff \ + indhibuild2.dff \ + indhibuild3.dff \ + indhibuild3B.dff \ + indhibuild4.dff \ + indhibuild5.dff \ + indhibuild9.dff \ + indmaincj1way.dff \ + indmaintjunc1w.dff \ + indmnrdtjuncb.dff \ + indnewroad1way.dff \ + indnwpath.dff \ + indnwpath01.dff \ + indnwpath02.dff \ + indnwpath03.dff \ + indroad1wayna.dff \ + indsub_gate03.dff \ + indsubway03.dff \ + indtrainshad.dff \ + indtreepatch06f.dff \ + indtreepatch5.dff \ + industSdirt01.dff \ + industSdirt02.dff \ + industSdirt03.dff \ + industpatch05.dff \ + indwindows2.dff \ + indy_tunl_start.dff \ + infernus.dff \ + info.dff \ + inswdirt01.dff \ + inswdirt02.dff \ + inswdirt03.dff \ + inswdirt04.dff \ + inswdirt05.dff \ + inwindows11.dff \ + isubntrnce_b.dff \ + iten3block.dff \ + iten_alleygun.dff \ + iten_alleysteps.dff \ + iten_alleysteps03.dff \ + iten_baker01.dff \ + iten_balcony.dff \ + iten_block01.dff \ + iten_block06.dff \ + iten_block07.dff \ + iten_block09.dff \ + iten_box01.dff \ + iten_chinatown2.dff \ + iten_chinatown37.dff \ + iten_chinatown38.dff \ + iten_chinatown4.dff \ + iten_chntown37b.dff \ + iten_club01.dff \ + iten_details7.dff \ + iten_fern01.dff \ + iten_garage01.dff \ + iten_hoteltop.dff \ + iten_singbuild02.dff \ + iten_urn01.dff \ + iten_wall03.dff \ + iten_wall04.dff \ + iten_washline01.dff \ + iten_washline02.dff \ + iten_washline03.dff \ + iten_windowbox.dff \ + jamesgrge_kb.dff \ + joedoor.DFF \ + joey.DFF \ + joey2.DFF \ + joey_door1.dff \ + joey_door2.dff \ + joeyh.dff \ + joeysdoor.dff \ + jogarageext.dff \ + jogarageint.dff \ + jolightbeams.dff \ + jump_box1.dff \ + junc_spesh.dff \ + junk_bit5.dff \ + junk_bit6.dff \ + junk_tyre.dff \ + junk_tyre_cubebig.dff \ + junk_tyre_cubemid.dff \ + junk_tyre_cubeteeny.dff \ + junk_tyre_cubewee.dff \ + junk_tyre_pilebig.dff \ + junk_tyre_pilemid.dff \ + kb_ofis1.dff \ + kb_ofis2.dff \ + kb_scrap_5.dff \ + kb_scrap_6.dff \ + kb_underpass.dff \ + kbplanter4.dff \ + kbunderpas2.dff \ + keeper.dff \ + keeperh.dff \ + kenji.dff \ + kenjih.dff \ + killfrenzy.dff \ + kmricndo01.dff \ + kmricndo02.dff \ + kuruma.dff \ + lampost_coast.dff \ + lamppost1.dff \ + lamppost2.dff \ + lamppost3.dff \ + landpart10.dff \ + landpart100.dff \ + landpart101.dff \ + landpart102.dff \ + landpart103.dff \ + landpart104.dff \ + landpart105.dff \ + landpart106.dff \ + landpart107.dff \ + landpart11.dff \ + landpart110.dff \ + landpart12.dff \ + landpart13.dff \ + landpart14.dff \ + landpart15.dff \ + landpart16.dff \ + landpart17.dff \ + landpart18.dff \ + landpart18a.dff \ + landpart19.dff \ + landpart21.dff \ + landpart23.dff \ + landpart25.dff \ + landpart26.dff \ + landpart30.dff \ + landpart7.dff \ + landpart85.dff \ + landpart86.dff \ + landpart87.dff \ + landpart88.dff \ + landpart89.dff \ + landpart90.dff \ + landpart91.dff \ + landpart92.dff \ + landpart93.dff \ + landpart94.dff \ + landpart95.dff \ + landpart96.dff \ + landpart97.dff \ + landpart98.dff \ + landpart99.dff \ + landpart_mount1.dff \ + landpart_mount2.dff \ + landpart_mount22.dff \ + landpart_mount3.dff \ + landpart_mount33.dff \ + landpart_mount4.dff \ + landpart_mount44.dff \ + landstal.dff \ + laundrtdoor1.dff \ + learjet.dff \ + leveldoor2.dff \ + lhouse_barrier1.dff \ + lhouse_barrier2.dff \ + lhouse_barrier3.dff \ + li_man1.dff \ + li_man2.dff \ + li_wom1.dff \ + li_wom2.dff \ + lift.DFF \ + liftdoor_left.dff \ + line.dff \ + linerun.dff \ + lips.DFF \ + listeningposts.dff \ + lofucked_car.dff \ + lounger_shadows.dff \ + love.dff \ + love2.dff \ + loveh.dff \ + ludoor.DFF \ + luggage.dff \ + luigi.DFF \ + luigih.dff \ + luigiineerclub.dff \ + mafia.dff \ + magnet.dff \ + mailbox1.dff \ + mailbox2.dff \ + mailbox5.dff \ + mailbox6.dff \ + mak_Watertank.dff \ + mak_Watertank2.dff \ + mak_Watertank3.dff \ + mak_billboard.dff \ + mak_billboardsrvc.dff \ + mak_billsmall.dff \ + mak_bomb01.dff \ + mak_bulbsmall.dff \ + mak_fratea.dff \ + mak_frateb.dff \ + mak_fratec.dff \ + mak_semtech.dff \ + mak_ventchim.dff \ + male01.DFF \ + male02.DFF \ + male03.DFF \ + mallA.dff \ + mallB.dff \ + mallcanopy.dff \ + mallground02.dff \ + mallground03.dff \ + mallgroundb.dff \ + manana.dff \ + mansion1.dff \ + mansion2.dff \ + mansion3.dff \ + mansion4.dff \ + mansion5.dff \ + mansion6.dff \ + maria.DFF \ + mariah.dff \ + medic.DFF \ + micky.DFF \ + mickyh.dff \ + mid_rock.dff \ + miguel.dff \ + miguelh.dff \ + mine.dff \ + minnote.dff \ + misty.DFF \ + mistyh.dff \ + mod_man.dff \ + mod_wom.dff \ + moonbeam.dff \ + mount5.dff \ + mount6.dff \ + mount7.dff \ + mount8.dff \ + mrngstarbuild.dff \ + mrwhoop.dff \ + mrwongs.dff \ + mscp_barrier.dff \ + mscp_barrier01.dff \ + mscp_barriersup.dff \ + mscp_grndfloor.dff \ + mscp_midlfloor.dff \ + mscp_rampMDL.dff \ + mscp_topfloor.dff \ + mule.dff \ + museum.dff \ + nbbridgcabls01.dff \ + nbbridgerda.dff \ + nbbridgerdb.dff \ + nbbridgfk2.dff \ + nbcom_roadkb01.dff \ + new_GOD_DAM.dff \ + new_carprktrees.dff \ + new_carprktrees4.dff \ + new_chinaawning.dff \ + newairportwall1.dff \ + newairportwall2.dff \ + newairportwall3.dff \ + newairportwall4.dff \ + newairportwall5.dff \ + newairportwall6.dff \ + newblokb.dff \ + newbuildind.dff \ + newcoasttrees1.dff \ + newcoasttrees2.dff \ + newcoasttrees3.dff \ + newcpark_roadext.dff \ + newdockbuilding.dff \ + newdockbuilding2.dff \ + newfence.dff \ + newfence1.dff \ + newramp.dff \ + newroadsec01.dff \ + newroadsec02.dff \ + newroadsec1.dff \ + newsstand1.dff \ + newtowerdoor1.dff \ + newtrackaaa.dff \ + newtreepatch_sub.dff \ + newtrees1_sub.dff \ + newuni_gz.dff \ + newunigrnd.dff \ + newunitrepatch.dff \ + noodlesbox.dff \ + noparkingsign1.dff \ + note.dff \ + novy.dff \ + nrailsteps.dff \ + nrailstepswest.dff \ + nullpathnodeinsw.dff \ + nullpathpointcom1.dff \ + nullpathpointcom2.dff \ + nullpathpointcom3.dff \ + nullpathpointcom4.dff \ + nullpathpointcom5.dff \ + nullpathpointcom6.dff \ + nullpathpointcom7.dff \ + nullpathpointcom8.dff \ + nullpathpointcom9.dff \ + observatory_temp.dff \ + oddjgaragdoor.dff \ + ofis_bildkb_4.dff \ + ojg.dff \ + ojg2.dff \ + ojg_p.dff \ + opera_house.dff \ + overpass_comse.dff \ + overpasscom04.dff \ + overpasscom05.dff \ + overpasscom06.dff \ + overpasscom07.dff \ + overpassind.dff \ + overpassshadow.dff \ + p_man1.DFF \ + p_man2.DFF \ + p_wom1.DFF \ + p_wom2.DFF \ + package1.dff \ + packagelarge.dff \ + palette.dff \ + panlant.dff \ + papermachn01.dff \ + parkbench1.dff \ + parkinglines1.dff \ + parkinglines2.dff \ + parkinglines3.dff \ + parkingmeter.dff \ + parkpath1.dff \ + parktable1.dff \ + parsign1.dff \ + parsign2.dff \ + pathnodepoly.dff \ + pathpark2.dff \ + pathpoints02.dff \ + pathpoints03.dff \ + pathpoints04.dff \ + pathpoints05.dff \ + pathpoints06.dff \ + pathpoints07.dff \ + pathpoints08.dff \ + pathpoints09.dff \ + pathpoints10.dff \ + pathpoints11.dff \ + pathpoints12.dff \ + pathpoints13.dff \ + pathpoints14.dff \ + pathpoints15.dff \ + pathpoints16.dff \ + pathpoints17.dff \ + pathpoints18.dff \ + pathpoints19.dff \ + pathpoints20.dff \ + pathpoints21b.dff \ + patriot.dff \ + ped_tunnel.dff \ + peren.dff \ + petfoodfactory.dff \ + petrolpump.dff \ + petrolstation.dff \ + pharmas.dff \ + phil_crates.dff \ + phils_compnd_gate.dff \ + phils_lockers.dff \ + philsbox.dff \ + phonebooth1.dff \ + phonesign.dff \ + pimp.dff \ + pinetree_narrow.dff \ + pinetree_wide.dff \ + pipe2.dff \ + pipe_start_box.dff \ + plane_chassis.dff \ + planter_long.dff \ + planter_short.dff \ + planterbtm_1.dff \ + planterbtm_2.dff \ + planterbtm_3.dff \ + player.DFF \ + playerh.dff \ + playerp.DFF \ + playersdoor.dff \ + playerwarehouseint.dff \ + playerx.DFF \ + plnt_chimgrad.dff \ + plnt_pipepart01.dff \ + plnt_pipjoin4way04.dff \ + plnt_pylon01.dff \ + plyrsv_htl.dff \ + plysav_lftdr_lft.dff \ + plysav_lftdr_rght.dff \ + plysve_gragedoor.dff \ + police.DFF \ + police_celhole.dff \ + police_cell.dff \ + police_cell_wall.dff \ + police_com.dff \ + policeally.dff \ + policeballdoor.dff \ + policeballhall.dff \ + policetenkb1.dff \ + policetenkb2.dff \ + pony.dff \ + porthangerclosed.dff \ + posh_shop.dff \ + postbox1.dff \ + powerbox1.dff \ + predator.dff \ + proj_garage01.dff \ + proj_garage02.dff \ + proj_garage03.dff \ + proj_garage04.dff \ + proj_garage05.dff \ + proj_garage06.dff \ + proj_garage07.dff \ + proj_garage08.dff \ + proj_garage09.dff \ + projbridgewall1.dff \ + projbridgewall2.dff \ + projectland1.dff \ + projwall1.dff \ + projwall12.dff \ + projwall13.dff \ + projwall2.dff \ + projwall21.dff \ + projwall22.dff \ + prostitute.dff \ + prostitute2.dff \ + rail_platform.dff \ + rail_platformw.dff \ + railtracks.dff \ + railtracks_join.dff \ + railtrax_2b.dff \ + railtrax_bentl.dff \ + railtrax_bentr.dff \ + railtrax_bentrb.dff \ + railtrax_lo4b.dff \ + railtrax_ske2b.dff \ + railtrax_skew.dff \ + railtrax_skew5.dff \ + railtrax_skewp.dff \ + railtrax_straight.dff \ + railtrax_straightss.dff \ + railtrax_strtshort.dff \ + ramp.dff \ + ray.dff \ + rayh.dff \ + rcbandit.dff \ + rcyclbank01.dff \ + rd_Corner1.dff \ + rd_Corner2.dff \ + rd_CrossRda1rw22.dff \ + rd_CrossRda1w22.dff \ + rd_CrossRoads11.dff \ + rd_CrossRoads12.dff \ + rd_CrossRoads13.dff \ + rd_CrossRoads22.dff \ + rd_CrossRoads222.dff \ + rd_CrossRoads23.dff \ + rd_CrossRoadsa22.dff \ + rd_CrossRoadsa24.dff \ + rd_Road1A10.dff \ + rd_Road1A20.dff \ + rd_Road1A25.dff \ + rd_Road1A30.dff \ + rd_Road1A5.dff \ + rd_Road1A50.dff \ + rd_Road1A6.dff \ + rd_Road1B50.dff \ + rd_Road1C50.dff \ + rd_Road1C50kb.dff \ + rd_Road1wayA10.dff \ + rd_Road2A10.dff \ + rd_Road2A20.dff \ + rd_Road2A25.dff \ + rd_Road2A30.dff \ + rd_Road2A5.dff \ + rd_Road2A50.dff \ + rd_Road2B50.dff \ + rd_Road2B50s.dff \ + rd_Road2C50.dff \ + rd_Road3A10.dff \ + rd_Road3A20.dff \ + rd_Road3A50.dff \ + rd_RoadInd1way40.dff \ + rd_RoadInd1way50.dff \ + rd_RoadS1.dff \ + rd_SlRoad1A50.dff \ + rd_SlRoad1B50.dff \ + rd_SlRoad1B51.dff \ + rd_SlRoad1B52.dff \ + rd_SlRoad2A20.dff \ + rd_SlRoad2A50.dff \ + rd_SrRoad2A10.dff \ + rd_SrRoad2A20.dff \ + rd_SrRoad2A50.dff \ + rd_SrRoad2C50.dff \ + rd_SrRoad3A50.dff \ + rd_SrRoad3B50.dff \ + rd_TJunction11.dff \ + rd_TJunction11way.dff \ + rd_TJunction12.dff \ + rd_TJunction13.dff \ + rd_TJunction22.dff \ + rd_cusxRoadsgm.dff \ + rd_cusxRoadsgm_2.dff \ + rd_opera_custma.dff \ + rd_opera_custmb.dff \ + rd_specialrd01b.dff \ + rd_specialrd11.dff \ + rd_specialrdmall.dff \ + rd_turn4.dff \ + rd_warehsearea.dff \ + rdsign02bk.dff \ + rdsign06.dff \ + rdsign14.dff \ + rdsign15.dff \ + rdsign17.dff \ + rdsign18bk.dff \ + rdsign19.dff \ + redlightbuild03.dff \ + redlightbuild05_dy.dff \ + redlightbuild05_nt.dff \ + redlightbuild06b.dff \ + redlightbuild06e.dff \ + redlightbuild06v.dff \ + redlightbuild08a.dff \ + redlightbuild09.dff \ + redlightbuild10.dff \ + redlightbuild11.dff \ + redlightbuild12.dff \ + redlightbuild13.dff \ + redlightfront.dff \ + reefer.dff \ + rhino.dff \ + rifle.DFF \ + road_broadway01.dff \ + road_broadway02.dff \ + road_broadway04.dff \ + road_under.dff \ + roadcustc1w01.dff \ + roadcustc1w010.dff \ + roadcustc1w02.dff \ + roadincustmn.dff \ + roadplanterkb1.dff \ + roadplanterkb2.dff \ + roadplanterkb3.dff \ + roadtun_entrance.dff \ + roadworkbarrier1.dff \ + robber.dff \ + rockpatch03.dff \ + rockpatch1.dff \ + round_bit.dff \ + rubbish01.dff \ + rubble01.dff \ + rubble02.dff \ + rumpo.dff \ + rustship_structure.dff \ + s_guard.dff \ + safehouse.dff \ + salvatordoor.dff \ + salvatorpatiodr.dff \ + salvs_door1.dff \ + salvs_door2.dff \ + salvsdetail.dff \ + salvstrans.dff \ + sam.dff \ + sandbag.dff \ + sawmill.dff \ + sbwy_tunl_bend.dff \ + sbwy_tunl_bit.dff \ + sbwy_tunl_cstm1.dff \ + sbwy_tunl_cstm10.dff \ + sbwy_tunl_cstm11.dff \ + sbwy_tunl_cstm2.dff \ + sbwy_tunl_cstm3.dff \ + sbwy_tunl_cstm4.dff \ + sbwy_tunl_cstm5.dff \ + sbwy_tunl_cstm6.dff \ + sbwy_tunl_cstm7.dff \ + sbwy_tunl_cstm8.dff \ + sbwy_tunl_cstm9.dff \ + sbwy_tunl_start.dff \ + sbwy_tunl_start2.dff \ + scaffold_pole.dff \ + scaffoldlift.dff \ + scraperkb3_nit.dff \ + scum_man.dff \ + scum_wom.dff \ + se_treeshad01.dff \ + se_treeshad02.dff \ + se_treeshad03.dff \ + se_treeshad04.dff \ + se_treeshad05.dff \ + se_treeshad06.dff \ + securica.dff \ + sentinel.dff \ + ser_firetower.dff \ + sfhousegrge_kb.dff \ + shdoor.dff \ + shedgaragedoor.dff \ + shedge1.dff \ + shedge2.dff \ + shedge3.dff \ + shedge4.dff \ + shedge5.dff \ + shedge6.dff \ + ship.DFF \ + shop_bit.dff \ + shopper1.dff \ + shopper2.dff \ + shopper3.dff \ + shp_boats2.dff \ + shp_wlkway.dff \ + sidebarrier_gaz1.dff \ + sidebarrier_gaz2.dff \ + sidegate.dff \ + sign2sub.dff \ + singlelight.dff \ + singlelight2.dff \ + skyscrapenew.dff \ + skyscrpunbuilt2.dff \ + slab_block.dff \ + smal_outsidelight.dff \ + smal_outsidelight2.dff \ + smal_outsidelight3.dff \ + small_rock.dff \ + smashbar.dff \ + smashbarpost.dff \ + snipe_build_kb.dff \ + special_pathbity.dff \ + speeder.dff \ + splay_tunent01.dff \ + splay_tunent02.dff \ + spraydoor.dff \ + st_junction24.dff \ + st_man.dff \ + st_wom.dff \ + stallion.dff \ + stepsmidbb.dff \ + stinger.dff \ + stretch.dff \ + strtbarrier01.dff \ + stud_man.dff \ + stud_wom.dff \ + sub_CrossRoads23.dff \ + sub_SlRoad2A50.dff \ + sub_billboard1.dff \ + sub_floodlite.dff \ + sub_ind03.dff \ + sub_ind26.dff \ + sub_indfrate2108.dff \ + sub_indfrate2109.dff \ + sub_indfrate2110.dff \ + sub_indland01.dff \ + sub_indland02.dff \ + sub_indland03.dff \ + sub_indland04.dff \ + sub_indland05.dff \ + sub_indland06.dff \ + sub_indland07.dff \ + sub_indland08.dff \ + sub_roadbarrier.dff \ + sub_roadleft.dff \ + sub_roadright.dff \ + sub_subway.dff \ + sub_tripbboard.dff \ + sub_turn4.dff \ + subbridge01.dff \ + subbridge07.dff \ + subbridge19.dff \ + subbridge20.dff \ + subbridge_lift.dff \ + subentrance.dff \ + subentrance_sub1.dff \ + subfraightback02.dff \ + subfraightback03.dff \ + subfraightback04.dff \ + subind_build01.dff \ + subind_build02.dff \ + subind_build03.dff \ + subind_build04.dff \ + subind_build05.dff \ + subind_build06.dff \ + subind_build07.dff \ + sublandpart20.dff \ + sublandpart22.dff \ + sublandpart24.dff \ + sublightsb.dff \ + subnewrds02.dff \ + subnewrds03.dff \ + subnewrds04.dff \ + subnewrds05.dff \ + subnewrds07.dff \ + subnewrds08.dff \ + subnewrds09.dff \ + subnewrds10.dff \ + subnewrds11.dff \ + subnewrds12.dff \ + subnewrds13.dff \ + subnewrds14.dff \ + subnewrds15.dff \ + subnewrds16.dff \ + subnewrds17.dff \ + subnewrds18.dff \ + subnewrds19.dff \ + subnewrds1972.dff \ + subnewrds20.dff \ + subnewrdsbg2.dff \ + subnewrdsbg20.dff \ + subntrance_sub2.dff \ + subntrnce_b.dff \ + subntrnce_n.dff \ + subntrnce_n2.dff \ + subplatform.dff \ + subplatform_n.dff \ + subplatform_n2.dff \ + subplatform_sub.dff \ + subposter01.dff \ + subposter03.dff \ + subposter04.dff \ + subposter05.dff \ + subposter06.dff \ + subposter07.dff \ + subposter08.dff \ + subposter09.dff \ + subposter10.dff \ + subposter11.dff \ + subposter12.dff \ + subposter14.dff \ + subpro_bridge.dff \ + subsign1.dff \ + suburbansigns1.dff \ + suburbansigns2.dff \ + suburbansigns3.dff \ + suburbansigns4.dff \ + suburbansigns5.dff \ + suburbansigns6.dff \ + suburbansigns7.dff \ + suburbbridge1.dff \ + subway_footbridge.dff \ + subwaygate.dff \ + suby_tunl_start.dff \ + suitcases.dff \ + suittrailer.dff \ + surd1wwee.dff \ + swank_inside.dff \ + swat.DFF \ + t_junction_2x2.dff \ + t_junction_2x222.dff \ + t_junction_2x4.dff \ + t_junction_2x444.dff \ + t_junction_4x4.dff \ + t_junction_4x4_2.dff \ + t_junction_pump.dff \ + tag1.dff \ + tag2.dff \ + tag3.dff \ + tag4.dff \ + tag5.dff \ + tall_fence.dff \ + tanner.dff \ + taxi.DFF \ + taxi_d.dff \ + taxigarage.dff \ + tcsky_skyscrp6.dff \ + telepole.dff \ + telepole01.dff \ + telepole02.dff \ + telepole03.dff \ + telepole04.dff \ + telgrphpole02.dff \ + temnt5ad.dff \ + tenament1ad.dff \ + tenament3ad.dff \ + tenement2ad.dff \ + tenkb_builds01.dff \ + tenkb_builds03.dff \ + tenkb_builds04.dff \ + tenkb_builds05.dff \ + tenkb_builds06.dff \ + tenkb_builds11.dff \ + tenkb_ground.dff \ + tenkb_raised.dff \ + tenmnt4ad.dff \ + tenmnt6ad.dff \ + tiny_rock.dff \ + toilet.dff \ + toilet_cubicle_dr.dff \ + toilet_cubicle_dr2.dff \ + toilet_door01.dff \ + tony.DFF \ + tonyh.dff \ + toplandit.dff \ + topwarejunc.dff \ + towercenter.dff \ + towerflat.dff \ + towerflat26.dff \ + towerflat27.dff \ + towerflat28.dff \ + towerflat29.dff \ + towergaragedoor1.dff \ + towergaragedoor2.dff \ + towergaragedoor3.dff \ + towernew1.dff \ + townhall_nite.dff \ + townhallbig.dff \ + toyz.dff \ + trackshad01.dff \ + trackshad02.dff \ + trackshad03.dff \ + trackshad04.dff \ + trackshad04b.dff \ + trackshad05.dff \ + trackshad05a.dff \ + trackshad05b.dff \ + trackshad05cc.dff \ + trackshad06.dff \ + trackshad06b.dff \ + trackshad07.dff \ + trackshad08.dff \ + trackshad09.dff \ + trackshad10.dff \ + trackshad11.dff \ + trackshad12.dff \ + trackshad13.dff \ + trackshad14.dff \ + trafficcone.dff \ + trafficlight1.dff \ + trailer.dff \ + trailermetal.dff \ + train.dff \ + train_rust.dff \ + trainsleeper.dff \ + trash.dff \ + treencom2.dff \ + treepatch.dff \ + treepatch01_sub.dff \ + treepatch02_sub.dff \ + treepatch03.dff \ + treepatch03_sub.dff \ + treepatch04_sub.dff \ + treepatch05_sub.dff \ + treepatch06_sub.dff \ + treepatch07_sub.dff \ + treepatch08_sub.dff \ + treepatch09_sub.dff \ + treepatch10_sub.dff \ + treepatch11_sub.dff \ + treepatch12_sub.dff \ + treepatch13_sub.dff \ + treepatch14_sub.dff \ + treepatch152_sub.dff \ + treepatch153_sub.dff \ + treepatch15_sub.dff \ + treepatch16_sub.dff \ + treepatch171_sub.dff \ + treepatch172_sub.dff \ + treepatch173_sub.dff \ + treepatch17_sub.dff \ + treepatch18_sub.dff \ + treepatch19_sub.dff \ + treepatch2.dff \ + treepatch20_sub.dff \ + treepatch212_sub.dff \ + treepatch213_sub.dff \ + treepatch214_sub.dff \ + treepatch21_sub.dff \ + treepatch22_sub.dff \ + treepatch23_sub.dff \ + treepatch24_sub.dff \ + treepatch25_sub.dff \ + treepatch26_sub.dff \ + treepatch27_sub.dff \ + treepatch28_sub.dff \ + treepatch29_sub.dff \ + treepatch2b.dff \ + treepatch30_sub.dff \ + treepatch31_sub.dff \ + treepatch32_sub.dff \ + treepatch34_sub.dff \ + treepatch35_sub.dff \ + treepatch69.dff \ + treepatcha.dff \ + treepatchb.dff \ + treepatchcomtop1.dff \ + treepatchd.dff \ + treepatche.dff \ + treepatchh.dff \ + treepatchindaa2.dff \ + treepatchindnew.dff \ + treepatchindnew2.dff \ + treepatchk.dff \ + treepatchkb4.dff \ + treepatchkb5.dff \ + treepatchkb6.dff \ + treepatchkb7.dff \ + treepatchkb9.dff \ + treepatchl.dff \ + treepatchm.dff \ + treepatchnew_sub.dff \ + treepatchttwrs.dff \ + treeshads01.dff \ + treeshads02.dff \ + treeshads03.dff \ + treeshads04.dff \ + treeshads05.dff \ + treesuni1.dff \ + trepatchindaa1.dff \ + troll.dff \ + tshrorckgrdn.dff \ + tshrorckgrdn_alfas.dff \ + tunent3.dff \ + tunent4.dff \ + tunent5.dff \ + tunl_2.dff \ + tunl_206.dff \ + tunl_2_bend.dff \ + tunl_2_cus1.dff \ + tunl_2_cus2.dff \ + tunl_2_cus3.dff \ + tunl_2_cus4.dff \ + tunl_4.dff \ + tunl_4_Cus1.dff \ + tunl_4_Cus10.dff \ + tunl_4_Cus11.dff \ + tunl_4_Cus12.dff \ + tunl_4_Cus14.dff \ + tunl_4_Cus15.dff \ + tunl_4_Cus2.dff \ + tunl_4_Cus3.dff \ + tunl_4_Cus4.dff \ + tunl_4_Cus9.dff \ + tunl_T4.dff \ + tunl_T4n.dff \ + tunl_T4s.dff \ + tunl_bend.dff \ + tunl_t2.dff \ + tunl_t2n.dff \ + tunl_t2s.dff \ + tunlentind_4.dff \ + tunnel1.dff \ + tunnel_section.dff \ + tunnelentrance.dff \ + tunnelsupport1.dff \ + turnr5x50.dff \ + tw@_pedpath_tri.dff \ + tw@t_cafe.dff \ + twnhl_lndnite.dff \ + undergroSD1.dff \ + undergroundS1.dff \ + undergroundS2.dff \ + underground_over1.dff \ + underground_over10.dff \ + underground_over11.dff \ + underground_over12.dff \ + underground_over13.dff \ + underground_over14.dff \ + underground_over15.dff \ + underground_over2.dff \ + underground_over3.dff \ + underground_over4.dff \ + underground_over5.dff \ + underground_over6.dff \ + underground_over7.dff \ + underground_over8.dff \ + underground_over9.dff \ + underlight1.dff \ + underlight2.dff \ + underlight3.dff \ + undersubridge1.dff \ + undgd_bit_side3.dff \ + undrd_bit_side4.dff \ + uni_grndkb.dff \ + usdcrdlrbuild01.dff \ + usedcardelrflgs01.dff \ + usedcardelrsign01.dff \ + veg_bush14.dff \ + veg_bush2.dff \ + veg_tree1.dff \ + veg_tree3.dff \ + veg_treea1.dff \ + veg_treea3.dff \ + veg_treeb1.dff \ + veg_treenew01.dff \ + veg_treenew03.dff \ + veg_treenew05.dff \ + veg_treenew06.dff \ + veg_treenew08.dff \ + veg_treenew09.dff \ + veg_treenew10.dff \ + veg_treenew16.dff \ + veg_treenew17.dff \ + vegclubtree01.dff \ + vegclubtree02.dff \ + vegclubtree03.dff \ + vegcrate04.dff \ + vegcrate06.dff \ + vegcrate07.dff \ + vegpathtree.dff \ + vendmach.dff \ + verticalift_bridg2.dff \ + verticalift_bridge.dff \ + vheistlocdoor.dff \ + view_carpark.dff \ + wal_1.dff \ + wal_2.dff \ + wallbustop.dff \ + wallsawmill.dff \ + warehouse1z.dff \ + warehouse1z2.dff \ + warehouse1z3.dff \ + warehouse1z4.dff \ + warehouse2z.dff \ + warehouse3z.dff \ + warehousedoor.dff \ + washer.dff \ + wastebin.dff \ + westpostsign.dff \ + whip.dff \ + wire_shed.dff \ + wlst_skyscrp1.dff \ + wood_block.dff \ + woodenbox.dff \ + worker1.dff \ + worker2.dff \ + wrckdhse01.dff \ + wrckdhse020.dff \ + yakuza.dff \ + yankee.dff \ + yardie.dff \ No newline at end of file diff --git a/dreamcast/mp3list.mk b/dreamcast/mp3list.mk new file mode 100644 index 00000000..03e949b7 --- /dev/null +++ b/dreamcast/mp3list.mk @@ -0,0 +1,71 @@ +STREAM_MP3 = \ + BET.mp3 \ + END.mp3 \ + JB.mp3 \ + c1_tex.mp3 \ + d1_stog.mp3 \ + d2_kk.mp3 \ + d3_ado.mp3 \ + d4_gta.mp3 \ + d4_gta2.mp3 \ + d5_es.mp3 \ + d6_sts.mp3 \ + d7_mld.mp3 \ + el_ph1.mp3 \ + el_ph2.mp3 \ + el_ph3.mp3 \ + el_ph4.mp3 \ + hd_ph1.mp3 \ + hd_ph2.mp3 \ + hd_ph3.mp3 \ + hd_ph4.mp3 \ + hd_ph5.mp3 \ + j0_dm2.mp3 \ + j1_lfl.mp3 \ + j2_kcl.mp3 \ + j3_vh.mp3 \ + j4_eth.mp3 \ + j5_dst.mp3 \ + j6_tbj.mp3 \ + k1_kbo.mp3 \ + k2_gis.mp3 \ + k3_ds.mp3 \ + k4_shi.mp3 \ + k4_shi2.mp3 \ + k5_sd.mp3 \ + l1_lg.mp3 \ + l2_dsb.mp3 \ + l3_dm.mp3 \ + l4_pap.mp3 \ + l5_tfb.mp3 \ + mt_ph1.mp3 \ + mt_ph2.mp3 \ + mt_ph3.mp3 \ + mt_ph4.mp3 \ + r0_pdr2.mp3 \ + r1_sw.mp3 \ + r2_ap.mp3 \ + r3_ed.mp3 \ + r4_gf.mp3 \ + r5_pb.mp3 \ + r6_mm.mp3 \ + s0_mas.mp3 \ + s1_pf.mp3 \ + s2_ctg.mp3 \ + s2_ctg2.mp3 \ + s3_rtc.mp3 \ + s4_bdba.mp3 \ + s4_bdbb.mp3 \ + s4_bdbd.mp3 \ + s5_lrq.mp3 \ + s5_lrqb.mp3 \ + s5_lrqc.mp3 \ + t1_tol.mp3 \ + t2_tpu.mp3 \ + t3_mas.mp3 \ + t4_tat.mp3 \ + t5_bf.mp3 \ + yd_ph1.mp3 \ + yd_ph2.mp3 \ + yd_ph3.mp3 \ + yd_ph4.mp3 \ No newline at end of file diff --git a/dreamcast/pack-sfx.cpp b/dreamcast/pack-sfx.cpp new file mode 100644 index 00000000..37653ff5 --- /dev/null +++ b/dreamcast/pack-sfx.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include // C++17 +#include + +#ifdef _MSC_VER + #define PACKED_STRUCT __pragma(pack(push,1)) struct __pragma(pack(pop)) +#else + #define PACKED_STRUCT struct __attribute__((packed)) +#endif + +// Structures as specified: + +// Matches what the SDT file contains +PACKED_STRUCT tSampleSDT { + uint32_t nOffset; // Not necessarily used for writing new data, but read from SDT + uint32_t nSize; // Not necessarily used for writing new data, but read from SDT + uint32_t nFrequency; // We'll store this into the DSC + uint32_t nLoopStartInBytes; // We'll convert this to "samples" in DSC + int32_t nLoopEnd; // Not used directly in the DSC, but read from SDT +}; + +// Matches what we want in the DSC file +PACKED_STRUCT tSample { + uint32_t nFileOffset; // in bytes + uint32_t nByteSize; // in bytes + uint32_t nFrequency; // in hz + uint32_t nLoopStartSample; // in samples, 0 if no separate loop data + uint32_t nLoopFileOffset; // in bytes, 0 if none + uint32_t nLoopByteSize; // in bytes, 0 if none +}; + + +static void usage(const char* progName) { + std::cerr << "Usage: " << progName << " \n"; + std::cerr << "Example:\n"; + std::cerr << " " << progName << " data.sdt merged.raw output.dsc sfx_folder\n"; +} + +// Helper function to read the entire contents of a binary file into a vector of bytes +static std::vector readFile(const std::string& filename) +{ + std::ifstream ifs(filename, std::ios::binary | std::ios::ate); + if (!ifs.is_open()) { + throw std::runtime_error("Could not open file: " + filename); + } + std::streamsize size = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + + std::vector buffer(size); + if (!ifs.read(buffer.data(), size)) { + throw std::runtime_error("Failed to read file: " + filename); + } + return buffer; +} + +// Helper function to write a buffer to a binary file (append mode or from start) +static void writeFileAppend(const std::string& filename, const char* data, size_t size, bool append = true) +{ + std::ios_base::openmode mode = std::ios::binary; + if (append) { + mode |= std::ios::app; + } else { + mode |= std::ios::trunc; + } + std::ofstream ofs(filename, mode); + if (!ofs.is_open()) { + throw std::runtime_error("Could not open file for writing: " + filename); + } + ofs.write(data, size); +} + +int main(int argc, char** argv) +{ + if (argc != 5) { + usage(argv[0]); + return 1; + } + + std::string sdtPath = argv[1]; // input SDT file + std::string rawPath = argv[2]; // output RAW file + std::string dscPath = argv[3]; // output DSC file + std::string sfxFolder = argv[4]; // SFX_DIR folder + + // Read the entire SDT file into memory + std::vector sdtData; + try { + sdtData = readFile(sdtPath); + } catch(const std::exception& e) { + std::cerr << "Error reading SDT file: " << e.what() << std::endl; + return 1; + } + + // Determine how many tSampleSDT entries are in the SDT file + size_t totalBytes = sdtData.size(); + if (totalBytes % sizeof(tSampleSDT) != 0) { + std::cerr << "SDT file size is not a multiple of tSampleSDT struct size.\n"; + return 1; + } + size_t numSamples = totalBytes / sizeof(tSampleSDT); + + // Parse all tSampleSDT entries + std::vector sdtEntries(numSamples); + std::memcpy(sdtEntries.data(), sdtData.data(), totalBytes); + + // We'll build DSC descriptors in memory first + std::vector dscEntries(numSamples); + + // Truncate (or create) the RAW file before we start appending + { + std::ofstream ofs(rawPath, std::ios::binary | std::ios::trunc); + if (!ofs.is_open()) { + std::cerr << "Error creating/truncating RAW file: " << rawPath << std::endl; + return 1; + } + } + + // Now process each entry in the SDT, find the corresponding sfx_.pcm / sfx__loop.pcm + // Concatenate them into the RAW, and fill in the DSC. + uint64_t currentOffset = 0; // Keep track of where we are in the RAW file + + for (size_t i = 0; i < numSamples; ++i) + { + tSampleSDT& sdt = sdtEntries[i]; + tSample& desc = dscEntries[i]; + + // Prepare file paths + std::string basePcm = sfxFolder + "/sfx_" + std::to_string(i) + ".pcm"; + std::string loopPcm = sfxFolder + "/sfx_" + std::to_string(i) + "_loop.pcm"; + + // Read main PCM (sfx_.pcm) + uint32_t mainOffset = 0; + uint32_t mainByteSize = 0; + uint32_t loopOffset = 0; + uint32_t loopByteSize = 0; + uint32_t loopStartSamp = 0; + + // For the main PCM + try { + if (std::filesystem::exists(basePcm)) { + std::vector buffer = readFile(basePcm); + mainByteSize = static_cast(buffer.size()); + mainOffset = static_cast(currentOffset); + if (buffer.size() & 3) { + // Pad to 4-byte boundary + size_t padSize = 4 - (buffer.size() & 3); + buffer.insert(buffer.end(), padSize, 0); + // std::cerr << "Warning: Padded main PCM for index " << i << " with " << padSize << " bytes" << std::endl; + } + // Write to RAW + writeFileAppend(rawPath, buffer.data(), buffer.size(), true); + + // Advance current offset + currentOffset += buffer.size(); + } else { + // If the main PCM doesn't exist, you could decide to throw an error or just keep zero + // For now, let's throw an error + throw std::runtime_error("Missing PCM file: " + basePcm); + } + } catch(const std::exception& e) { + std::cerr << "Error processing main PCM for index " << i << ": " << e.what() << std::endl; + return 1; + } + + // For the loop PCM (sfx__loop.pcm); it might not exist + if (std::filesystem::exists(loopPcm)) { + try { + std::vector bufferLoop = readFile(loopPcm); + loopByteSize = static_cast(bufferLoop.size()); + loopOffset = static_cast(currentOffset); + + if (bufferLoop.size() & 3) { + // Pad to 4-byte boundary + size_t padSize = 4 - (bufferLoop.size() & 3); + bufferLoop.insert(bufferLoop.end(), padSize, 0); + // std::cerr << "Warning: Padded loop PCM for index " << i << " with " << padSize << " bytes." << std::endl; + } + + // Write to RAW + writeFileAppend(rawPath, bufferLoop.data(), bufferLoop.size(), true); + + // Advance current offset + currentOffset += bufferLoop.size(); + } catch(const std::exception& e) { + std::cerr << "Error processing loop PCM for index " << i << ": " << e.what() << std::endl; + return 1; + } + + // Convert loopStartInBytes from SDT to samples + // According to the note: "Note each sample in the SDT is indicated by two bytes" + // So if the SDT says 'nLoopStartInBytes', to get the loop start in samples, divide by 2 + loopStartSamp = sdt.nLoopStartInBytes / 2; + } else { + // If there's no loop file, we leave loopOffset, loopByteSize, and loopStartSamp = 0 + } + + // Fill in the tSample descriptor + desc.nFileOffset = mainOffset; + desc.nByteSize = mainByteSize; + desc.nFrequency = sdt.nFrequency; + desc.nLoopStartSample = loopStartSamp; + desc.nLoopFileOffset = loopOffset; + desc.nLoopByteSize = loopByteSize; + } + + // Finally, write the DSC file as a binary array of tSample + { + std::ofstream dscOut(dscPath, std::ios::binary | std::ios::trunc); + if (!dscOut.is_open()) { + std::cerr << "Error creating DSC file: " << dscPath << std::endl; + return 1; + } + dscOut.write(reinterpret_cast(dscEntries.data()), + dscEntries.size() * sizeof(tSample)); + } + + std::cout << "Successfully packed " << numSamples << " samples.\n"; + std::cout << "Output RAW: " << rawPath << std::endl; + std::cout << "Output DSC: " << dscPath << std::endl; + return 0; +} diff --git a/dreamcast/pvrtex/.gitignore b/dreamcast/pvrtex/.gitignore new file mode 100644 index 00000000..6832ea6b --- /dev/null +++ b/dreamcast/pvrtex/.gitignore @@ -0,0 +1,3 @@ +*.o +pvrtex +pvrtex.exe \ No newline at end of file diff --git a/dreamcast/pvrtex/Makefile b/dreamcast/pvrtex/Makefile new file mode 100644 index 00000000..9fee5492 --- /dev/null +++ b/dreamcast/pvrtex/Makefile @@ -0,0 +1,42 @@ +#Uncomment when debugging +#DEBUGBUILD = true + +TARGET = pvrtex +OBJS = elbg.o mem.o log.o bprint.o avstring.o lfg.o crc.o md5.o stb_image_impl.o \ + stb_image_write_impl.o stb_image_resize_impl.o optparse_impl.o pvr_texture.o \ + dither.o tddither.o vqcompress.o mycommon.o file_common.o \ + file_pvr.o file_tex.o file_dctex.o pvr_texture_encoder.o main.o + +ifdef $(DEBUGBUILD) + OPTMODE= -Og -pg -g +else + OPTMODE= -O3 -flto +endif + +MYFLAGS=-Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Ilibavutil -I. -DCONFIG_MEMORY_POISONING=0 -DHAVE_FAST_UNALIGNED=0 +MYCPPFLAGS=$(MYFLAGS) +MYCFLAGS=$(MYFLAGS) -Wno-pointer-sign + + +.PHONY: all clean + +%.o: %.c + gcc $(CFLAGS) $(MYCFLAGS) $(OPTMODE) -c $< -o $@ + +%.o: %.cpp + gcc $(CFLAGS) $(MYCPPFLAGS) $(CXXFLAGS) $(OPTMODE) -c $< -o $@ + + +$(TARGET): $(OBJS) + gcc $(OPTMODE) -o $(TARGET) \ + $(OBJS) $(PROGMAIN) -lm -lstdc++ + +clean: + rm -f $(TARGET) $(OBJS) README + +README: readme_unformatted.txt + fmt -s readme_unformatted.txt > README + +all: $(TARGET) README + + diff --git a/dreamcast/pvrtex/README b/dreamcast/pvrtex/README new file mode 100644 index 00000000..7d4f6e51 --- /dev/null +++ b/dreamcast/pvrtex/README @@ -0,0 +1,495 @@ +pvrtex converts images to Dreamcast PowerVR textures. + +It is designed to work similarly to tvspelsfreak's texconv, so it can +be used in place of texconv will minimal changes. It might be helpful +to read the readme for texconv for additional information not covered +here. In particular, there are explainations of the types of textures +supported by the Dreamcast. + +Compared to texconv, pvrtex has the following enhancements: + + * Faster texture compression and palette generation + * Can generate small codebook VQ textures + * No Qt dependency + * Support for compressed stride textures + * Better mipmap generation + * Dithering + * Support for additional output file types (adds .PVR and .DT) + +-------------------------------------------------------------------------- + +Usage Examples: + +pvrtex -i source.png -o texture.dt + Converts a PNG file to a DT file that is twiddled, uncompressed + texture, without mipmaps. The format is automatically chosen + depending on alpha content of source.png (See description for + AUTO texture format in command option listing). + +pvrtex -i source.png -o texture.dt -f argb4444 -d -c 64 -m quality -r -R + Converts a PNG file to a DT file that is twiddled, compressed + texture, with mipmaps. The texture will use the ARGB4444 color + format, and dithered. If the source image is not already a + square power-of-two, it will be resized to be the nearest square + power-of-two. The codebook used by the compression will be limited + to 64 entries out of the potential 256; this reduces quality, but + reduces the size of the texture by 1.5 KB, and improves fillrate. + +pvrtex -i source.png -o texture.dt -f normal -m + Converts a PNG file containing a normal map to a DT file, + with mipmaps. + +pvrtex -i source.png -o texture.dt -s + Converts a PNG file to a nontwiddled texture. source.png is not + required to be a power-of-two width, and can also be any multiple + of 32 that is <= 1024. + +pvrtex -i source.png -o texture.dt -f pal8bpp -C 64 -d -p preview.png + Converts a PNG file to a DT file with 8-bit color. The resulting + image will not use more than 64 colors out of the potential 256, + and will be dithered. The pallete for the texture will be written + to texture.dt.pal. A preview of the resulting texture will be + written to preview.png. + +pvrtex -i mip256.png -i mip128.png -i mip64.png -i mip32.png -i mip16.png +-o texture.dt -m + Generates a mipmapped texture, using the different input images + as user defined mipmap levels instead of automatically generating + all of them. If a mipmap level is not defined by the user, it + will be generated from a higher level. By default, the higher + level will not be the level above, but three levels above; if you + want to use the level above, use fast mipmaps (-m fast) instead. + +-------------------------------------------------------------------------- + +Building: + + Run "make". + + :-| + + To generate the proper README with linebreaks, from + readme_unformatted.txt, run "make README" or "make all". Requires + "fmt". + +-------------------------------------------------------------------------- + +Command Line Options: + +--help, -h + Displays help + +--version, -V + Displays version + +--in [filename], -i [filename] + Input image file. This option is required. + + If multiple input images are specified, they are currently + assumed to be different mipmap levels for a single texture. Resize + options can not be used for custom mipmaps, so all images must + be a square power-of-two. + + Uses stb_image library for reading the image. The supported + formats are: + JPEG, PNG, TGA, BMP, PSD, GIF, HDR, PIC, PMN + +--out [filename], -o [filename] + Sets the file name of the converted texture. The extension of + this filename controls the file format. + + The supported formats are: + + .PVR + Official PowerVR texture. This is incomplete as was + created to help test the output of the converter, by + using PC PVR viewing programs to check the resulting + texture. There are likely incompatiblities with .PVR + handling of official games (for example, pvrtex does + not add a GBIX chunk). + .TEX + Format used by tvspelsfreak's texconv. pvrtex will + generate certain formats not supported by texconv but + representable in the file format (like compressed stride + textures). + .DT + New file format used by this program. Supports small + codebook VQ, and texture data is aligned to a 32-byte + boundry to make DMA easier. + + It's possible to specify no output file if only a preview image + is desired. + +--format [type], -f [type] + Sets the pixel format of the resulting texture. + + [type] can be one of the following: + + RGB565 + Best color out of standard formats without sacrificing + speed, but can have rainbowing on grayscale images. + ARGB1555 + Allows for fully transparent texels. Better choice for + grayscale than RGB565, which can have rainbowing. + ARGB4444 + Allows for alpha gradients, but poorest color depth with + noticable banding. + YUV422 / YUV + Better than RGB565 or ARGB1555 for gradients, but + bi/trilinear filtering has worse performance. + PAL8BPP + Maximum of 256 colors. Palette is generated as a seperate + file. Must be twiddled. If compression, mipmapping, + and bi/trilinear are used, a hardware bug causes some + texels to be filtered incorrectly on the top left/bottom + right corners of a 4x2 block. + PAL4BPP + Maximum of 16 colors. Palette is generated as a seperate + file. Must be twiddled. If compression, mipmapping, + and bi/trilinear are used, a hardware bug causes some + texels to be filtered incorrectly on the top left/bottom + right corners of a 4x4 block + BUMPMAP + Generates PVR normal map. Source image is treated as a + height map. + NORMAL + Generates PVR normal map. Source image is treated as a + DOT3 normal map, with RGB channels corresponding to the + normal's XYZ. + AUTO + Selects RGB565, ARGB1555, or ARGB4444 depending on alpha + content of input image. If fully opaque, RGB565 is used, + all pixels have either fully opaque or fully transparent + alpha, ARGB1555 is used, and if some pixels have non-fully + opaque or transparent pixels, ARGB4444 is used. + AUTOYUV + Same as AUTO, but usage of RGB565 is replaced with YUV422. + +--preview [filename], -p [filename] + Generates a preview of the resulting texture file. You can + see the results of bit depth reduction, dithering, mipmaps, + and compression. + + The preview is can be a PNG, JPG, BMP, or TGA file. Preview + JPGs are generally not a good choice do to the lack of alpha, + and possiblility of compression artifacts. + +--compress [codebook_size / "small"], -c [codebook_size / "small"] + Generates a VQ compressed texture. + + codebook_size is an optional parameter adjusts the size of the + codebook generated for the texture. Reducing the codebook_size + can improve fillrate, and, with .PVR and .DT files, improve the + compression ratio of small textures. By default, a full codebook + is used to generate the best quality texture. codebook_size can + be a number from 1 to 256, or the string "small". + + For .PVR files, using a number will never reduce the size of + the texture, but can improve fillrate. Specifying "small" as the + codebook size will reduce the texture size for certain textures + smaller than 64x64 without mipmaps, or 32x32 with mipmaps. + + For .TEX files, codebook_size will never reduce the size of the + texture, but can still improve performance. + + For .DT files, reducing the codebook_size will reduce the size + of the texture. Specifying "small" as the codebook size will + select a smaller codebook automatically for textures that are + 128x128 or smaller, with a size of 192 for a 128x128 mipmapped + texture, down to 10 for an 8x8 non-mipped texture. + +--max-color [colors], -C [colors] + This limits the number of colors used for a PAL8BPP or PAL4BPP + format texture. This option could be used to generate an 8BPP + texture that only uses 64 colors, and the unused colors could + be used for other textures. + +--mipmap ["fast"], -m ["fast"] + With this option, the resulting file will have mipmaps. + + By default, a Mitchell-Netravalli filter will be used on a level + 3 steps above. (e.g. 64x64 will be generated from 512x512) + + Adding the parameter "fast" to this option, each mipmap level + is generated by downsampling the level above. (e.g. 64x64 level + will be generated from 128x128) This speeds up mipmap generation + for large textures. + + When generating high quality mipmaps for a resized image, + the largest mips will be generated directly from the source + image. (i.e. with a 1600x500 source image, converted with "-m + -r near -R x2", pvrtex will create a 1024x1024 texture, with + mipmap levels 512x512 and 256x256 generated directly from the + 1600x500 source, and not the 1024x1024 top most level). + +--no-mip-shift, -S + When generating mipmaps, by default, pvrtex will preform a + subpixel adjustment during downsampling to ensure the mipmaps + line up correctly. This option will disable this. + +--perfect-mip [levels], -M [levels] + When using mipmaps and compression, small mipmap levels will be + loselessly compressed. + + The levels parameter controls how many levels are lossless. 1 + means only the 1x1 level will be losslessly compressed, 2 means + 1x1 and 2x2 will be loselessly compressed, and so on. + + Generating lossless mipmaps use up VQ codebook slots. These are + the total number of codebook entries used for a given number of + lossless mipmaps: + + 16-bit 8-bit 4-bit + 1 level (1x1) 1 1 1 + 2 levels (2x2) 2 1 1 + 3 levels (4x4) 6 3 2 + 4 levels (8x8) 22 11 6 + 5 levels (16x16) 86 43 22 + 6 levels (32x32) -- 171 86 + +--high-weight [levels], -H [levels] + When using mipmaps and compression, this increases the weight + the compressor gives smaller mipmap levels, to encourage the + compressor to generate them at higher quality, at the cost of + lower quality higher levels. Not currently supported for 4BPP + textures. + + The levels parameter controls how many levels below the largest + have extra weight. A value of 1 means every level besides the + top has boosted weight, a value of 2 means the two largest levels + have normal weight, while every smaller level is boosted. + +--dither [amount], -d [amount] + Enables dithering. Currently, Floyd-Steinberg is used. + + Amount is an optional parameter that adjusts the amount of + dithering, and is a decimal value from 0 to 1. 0 will result in + no dithering, while 1 results in full dithering. If dithering + is enabled but an amount is not specified, full dithering is used. + + This option has no effect on YUV textures, but is valid on + all others. + +--stride, -s + Output a non-twiddled texture. This also allows for + non-power-of-two sized textures. Width must still 8, 16, or + a multiple of 32 less than or equal to 1024. Any height can be + used, from 1 to 1024. + + If a texture has a power-of-two dimensions and --stride is + used, the resulting texture will be a nontwiddled texture that + can be rendered without stride, for formats that support such + textures. (.DT and .TEX support this, .PVR does not.) + + Stride textures do not wrap as normal if the width is not + a power-of-two, and have worse rendering performance than + twiddled textures (especially when filtered or rotated). It + is not possible to generate palettized or normal textures with + stride. .PVR files cannnot use stride. + + Valid widths for stride texture: + 8, 16, 32, 64, 96, 128, 160, 192, 224, 256, 320, 352, + 384, 416, 480, 512, 544, 576, 608, 640, 672, 704, 736, + 768, 800, 832, 864, 896, 928, 960, 992, 1024 + +--resize [method], -r [method] + Resize a input image that is not a supported PVR texture size + to a valid size. + + If the texture is not strided, the texture will be resized to + a power-of-two on both dimensions. For stride textures, width + will be adjusted to an appropriate stride size, and the height + will always be resized to a power-of-two. + + Method controls how the image will be resized. + + NONE + Generates an error if input image is not a valid + size. This is the default. + NEAR + Round size up or down to nearest valid size. If resize is + enabled, but no method is specified, this is the default. + UP + Round size up to next valid size + DOWN + Round size down to next valid size + + Examples for non-stride textures: + Source size NONE NEAR UP DOWN + 256x256 256x256 256x256 256x256 256x256 + 260x260 Error 256x256 512x512 256x256 + 200x200 Error 256x256 256x256 128x128 + 200x260 Error 256x256 256x512 128x256 + 2000x2000 Error 1024x1024 1024x1024 1024x1024 + 1x1 Error 8x8 8x8 8x8 + +--mip-resize [method], -R [method] + When using mipmaps, resizes nonsquare images to be square. This + option does nothing if not using mipmaps or the image is already + square (after --resize). This new size calculation occurs after + the standard --resize. Source images are only resized once. This + option will not resize the image to a power-of-two size if it's + not already (use --resize for that). + + Method controls how the image will be resized. + + NONE + Generates an error if input image is not a valid mipmap + size. This is the default. + X2 + Doubles the narrower dimension. A 256x32 image will + be resized to 64x64. If mip-resize is enabled, but no + method is specified, this is the default. + X4 + Quaduples or doubles the narrower dimension. A 256x32 + image will be resized to 128x128. + UP + Resizes the narrower dimension to be the same size as + the wider. A 256x32 image will be resized to 256x256. + DOWN + Resizes the wider dimension to be the same size as the + narrower. A 256x32 image will be resized to 32x32. + + Examples: + + Source size X2 X4 UP DOWN + 256x256 256x256 256x256 256x256 256x256 + 256x128 256x256 256x256 256x256 128x128 + 256x64 128x128 256x256 256x256 64x64 + 256x32 64x64 128x128 256x256 32x32 + 1024x8 16x16 32x32 1024x1024 8x8 + +--edge [type], -e [type] + Controls how the edges of the image are handled when + resizing. This also affects height map to normal map conversion. + + Valid options: + + CLAMP + Samples are clamped to edge of image, default if not + mipmapped. + WRAP + Samples wrap around to other side of image, default if + mipmaps are used. Is works well when the texture is used + to that it repeats, but might cause noticble bleeding + around the edge of the texture in certain situations. For + example, a poster or sign that doesn't repeat across + the polygon. In that cause, CLAMP should be used. + REFLECT + Samples reflect off edge of image back into valid area. If + you use are planning on using UV mirroring instead of + wrapping, use this instead of wrap. + ZERO + Outside of image is treated as transparent blackness. This + is not currently supported for images with a --type + of BUMPMAP. + + The default is CLAMP if not using mipmaps, or WRAP if mipmaps + are used. + +--bilinear, -b + In texconv, this was used to generate mipmaps with a box + filter. This option is ignored in pvrtex, which currently always + uses a Mitchell-Netravalli filter. + +--nearest, -n + In texconv, this was used to generate mipmaps by point + sampling. This option is not supported in pvrtex, and will cause + pvrtex to abort. + +--vqcodeusage (Not supported) + This option from texconv is not recognized at all by pvrtex. + +-------------------------------------------------------------------------- + +.DT File Format + + See file_dctex.h for documentation. file_dctex.h can also be used + as a library to help access information from the file's header. + +-------------------------------------------------------------------------- + +Known bugs + + High weight compressed mips (--high-weight) does not currently + work with 4BPP textures; the parameter will be ignored if + specified. + + There is a weird pathological performance issue with compression + on certain textures. With a 1024x1024 texture with mips that + is ff000000 on the left side, and ffffffff on the right side, + performance drops by around 15x. It is apparently cache missing + constantly, according to cachegrind, in a function that reads + memory linearly (distance_limited in elbg.c). Doesn't make sense. + +-------------------------------------------------------------------------- + +Future Ideas + + * Code clean up + + * Add Yliluoma dithering + + * Improve error checking + + * It might be possible to improve VQ quality by compressing + 4/5/6 bit color instead of 8 bit (so the compressor won't waste + codebook space on colors that are too similar to distinguish at + given bit depth) + + * Speed up compression by not processing alpha for opaque textures + + * Add ability to generate a single palette to be shared for + multiple textures + + * Allow specifying palette format + + * Allow specifiying custom external palette for texture + + * Add ability to generate animated VQ textures with shared + codebook for all frames + + * Auto generate output name (e.g. -i image.png -o $.dt will + output image.dt) + + * Add wildcard input files (e.g. -i *.png) + + * Add VQ index dithering + + * Add KIMG output support + + * Allow texture formats (PVR/TEX/DT/KMG) to be used as input + formats, to transcode or recompress textures + + * Add per-axis edge sampling control + + * Rework code so that this can be used as a library + + * Allow sizing to arbitary size (i.e. --resize 256x256) + + * Add a filter to try to hide the palettized compressed mipmap bug + +-------------------------------------------------------------------------- + +History: + + Version 1.01 + Program now displays error message when an error occurs + loading a source image. Previously, the program would + hit an assertion. + + Fixed "--resize down" option. Previously, the program + would round down sizes that were already a power-of-two, + now sizes that are already POT are left unchanged. + + Included a missing FFmpeg header file. + + Version 1.0 + Initial release + +-------------------------------------------------------------------------- + +License: + This uses code from FFmpeg, which is LGPL, so this project is + also LGPL. Files not originating from FFmpeg can also be used + as public domain code/BSD/MIT/whatever. diff --git a/dreamcast/pvrtex/README.md b/dreamcast/pvrtex/README.md new file mode 100644 index 00000000..856ed37c --- /dev/null +++ b/dreamcast/pvrtex/README.md @@ -0,0 +1 @@ +# pvrtex diff --git a/dreamcast/pvrtex/avstring.c b/dreamcast/pvrtex/avstring.c new file mode 100644 index 00000000..e460b5be --- /dev/null +++ b/dreamcast/pvrtex/avstring.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard + * Copyright (c) 2007 Mans Rullgard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include "config.h" +#include "mem.h" +#include "avassert.h" +#include "avstring.h" +#include "bprint.h" +#include "error.h" +#include "macros.h" +#include "version.h" + +int av_strstart(const char *str, const char *pfx, const char **ptr) +{ + while (*pfx && *pfx == *str) { + pfx++; + str++; + } + if (!*pfx && ptr) + *ptr = str; + return !*pfx; +} + +int av_stristart(const char *str, const char *pfx, const char **ptr) +{ + while (*pfx && av_toupper((unsigned)*pfx) == av_toupper((unsigned)*str)) { + pfx++; + str++; + } + if (!*pfx && ptr) + *ptr = str; + return !*pfx; +} + +char *av_stristr(const char *s1, const char *s2) +{ + if (!*s2) + return (char*)(intptr_t)s1; + + do + if (av_stristart(s1, s2, NULL)) + return (char*)(intptr_t)s1; + while (*s1++); + + return NULL; +} + +char *av_strnstr(const char *haystack, const char *needle, size_t hay_length) +{ + size_t needle_len = strlen(needle); + if (!needle_len) + return (char*)haystack; + while (hay_length >= needle_len) { + hay_length--; + if (!memcmp(haystack, needle, needle_len)) + return (char*)haystack; + haystack++; + } + return NULL; +} + +size_t av_strlcpy(char *dst, const char *src, size_t size) +{ + size_t len = 0; + while (++len < size && *src) + *dst++ = *src++; + if (len <= size) + *dst = 0; + return len + strlen(src) - 1; +} + +size_t av_strlcat(char *dst, const char *src, size_t size) +{ + size_t len = strlen(dst); + if (size <= len + 1) + return len + strlen(src); + return len + av_strlcpy(dst + len, src, size - len); +} + +size_t av_strlcatf(char *dst, size_t size, const char *fmt, ...) +{ + size_t len = strlen(dst); + va_list vl; + + va_start(vl, fmt); + len += vsnprintf(dst + len, size > len ? size - len : 0, fmt, vl); + va_end(vl); + + return len; +} + +char *av_asprintf(const char *fmt, ...) +{ + char *p = NULL; + va_list va; + int len; + + va_start(va, fmt); + len = vsnprintf(NULL, 0, fmt, va); + va_end(va); + if (len < 0) + goto end; + + p = av_malloc(len + 1); + if (!p) + goto end; + + va_start(va, fmt); + len = vsnprintf(p, len + 1, fmt, va); + va_end(va); + if (len < 0) + av_freep(&p); + +end: + return p; +} + +#define WHITESPACES " \n\t\r" + +char *av_get_token(const char **buf, const char *term) +{ + char *out = av_malloc(strlen(*buf) + 1); + char *ret = out, *end = out; + const char *p = *buf; + if (!out) + return NULL; + p += strspn(p, WHITESPACES); + + while (*p && !strspn(p, term)) { + char c = *p++; + if (c == '\\' && *p) { + *out++ = *p++; + end = out; + } else if (c == '\'') { + while (*p && *p != '\'') + *out++ = *p++; + if (*p) { + p++; + end = out; + } + } else { + *out++ = c; + } + } + + do + *out-- = 0; + while (out >= end && strspn(out, WHITESPACES)); + + *buf = p; + + return ret; +} + +char *av_strtok(char *s, const char *delim, char **saveptr) +{ + char *tok; + + if (!s && !(s = *saveptr)) + return NULL; + + /* skip leading delimiters */ + s += strspn(s, delim); + + /* s now points to the first non delimiter char, or to the end of the string */ + if (!*s) { + *saveptr = NULL; + return NULL; + } + tok = s++; + + /* skip non delimiters */ + s += strcspn(s, delim); + if (*s) { + *s = 0; + *saveptr = s+1; + } else { + *saveptr = NULL; + } + + return tok; +} + +int av_strcasecmp(const char *a, const char *b) +{ + uint8_t c1, c2; + do { + c1 = av_tolower(*a++); + c2 = av_tolower(*b++); + } while (c1 && c1 == c2); + return c1 - c2; +} + +int av_strncasecmp(const char *a, const char *b, size_t n) +{ + uint8_t c1, c2; + if (n <= 0) + return 0; + do { + c1 = av_tolower(*a++); + c2 = av_tolower(*b++); + } while (--n && c1 && c1 == c2); + return c1 - c2; +} + +char *av_strireplace(const char *str, const char *from, const char *to) +{ + char *ret = NULL; + const char *pstr2, *pstr = str; + size_t tolen = strlen(to), fromlen = strlen(from); + AVBPrint pbuf; + + av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); + while ((pstr2 = av_stristr(pstr, from))) { + av_bprint_append_data(&pbuf, pstr, pstr2 - pstr); + pstr = pstr2 + fromlen; + av_bprint_append_data(&pbuf, to, tolen); + } + av_bprint_append_data(&pbuf, pstr, strlen(pstr)); + if (!av_bprint_is_complete(&pbuf)) { + av_bprint_finalize(&pbuf, NULL); + } else { + av_bprint_finalize(&pbuf, &ret); + } + + return ret; +} + +const char *av_basename(const char *path) +{ + char *p; +#if HAVE_DOS_PATHS + char *q, *d; +#endif + + if (!path || *path == '\0') + return "."; + + p = strrchr(path, '/'); +#if HAVE_DOS_PATHS + q = strrchr(path, '\\'); + d = strchr(path, ':'); + p = FFMAX3(p, q, d); +#endif + + if (!p) + return path; + + return p + 1; +} + +const char *av_dirname(char *path) +{ + char *p = path ? strrchr(path, '/') : NULL; + +#if HAVE_DOS_PATHS + char *q = path ? strrchr(path, '\\') : NULL; + char *d = path ? strchr(path, ':') : NULL; + + d = d ? d + 1 : d; + + p = FFMAX3(p, q, d); +#endif + + if (!p) + return "."; + + *p = '\0'; + + return path; +} + +char *av_append_path_component(const char *path, const char *component) +{ + size_t p_len, c_len; + char *fullpath; + + if (!path) + return av_strdup(component); + if (!component) + return av_strdup(path); + + p_len = strlen(path); + c_len = strlen(component); + if (p_len > SIZE_MAX - c_len || p_len + c_len > SIZE_MAX - 2) + return NULL; + fullpath = av_malloc(p_len + c_len + 2); + if (fullpath) { + if (p_len) { + av_strlcpy(fullpath, path, p_len + 1); + if (c_len) { + if (fullpath[p_len - 1] != '/' && component[0] != '/') + fullpath[p_len++] = '/'; + else if (fullpath[p_len - 1] == '/' && component[0] == '/') + p_len--; + } + } + av_strlcpy(&fullpath[p_len], component, c_len + 1); + fullpath[p_len + c_len] = 0; + } + return fullpath; +} + +int av_escape(char **dst, const char *src, const char *special_chars, + enum AVEscapeMode mode, int flags) +{ + AVBPrint dstbuf; + int ret; + + av_bprint_init(&dstbuf, 1, INT_MAX); /* (int)dstbuf.len must be >= 0 */ + av_bprint_escape(&dstbuf, src, special_chars, mode, flags); + + if (!av_bprint_is_complete(&dstbuf)) { + av_bprint_finalize(&dstbuf, NULL); + return AVERROR(ENOMEM); + } + if ((ret = av_bprint_finalize(&dstbuf, dst)) < 0) + return ret; + return dstbuf.len; +} + +int av_match_name(const char *name, const char *names) +{ + const char *p; + int len, namelen; + + if (!name || !names) + return 0; + + namelen = strlen(name); + while (*names) { + int negate = '-' == *names; + p = strchr(names, ','); + if (!p) + p = names + strlen(names); + names += negate; + len = FFMAX(p - names, namelen); + if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names))) + return !negate; + names = p + (*p == ','); + } + return 0; +} + +int av_utf8_decode(int32_t *codep, const uint8_t **bufp, const uint8_t *buf_end, + unsigned int flags) +{ + const uint8_t *p = *bufp; + uint32_t top; + uint64_t code; + int ret = 0, tail_len; + uint32_t overlong_encoding_mins[6] = { + 0x00000000, 0x00000080, 0x00000800, 0x00010000, 0x00200000, 0x04000000, + }; + + if (p >= buf_end) + return 0; + + code = *p++; + + /* first sequence byte starts with 10, or is 1111-1110 or 1111-1111, + which is not admitted */ + if ((code & 0xc0) == 0x80 || code >= 0xFE) { + ret = AVERROR(EILSEQ); + goto end; + } + top = (code & 128) >> 1; + + tail_len = 0; + while (code & top) { + int tmp; + tail_len++; + if (p >= buf_end) { + (*bufp) ++; + return AVERROR(EILSEQ); /* incomplete sequence */ + } + + /* we assume the byte to be in the form 10xx-xxxx */ + tmp = *p++ - 128; /* strip leading 1 */ + if (tmp>>6) { + (*bufp) ++; + return AVERROR(EILSEQ); + } + code = (code<<6) + tmp; + top <<= 5; + } + code &= (top << 1) - 1; + + /* check for overlong encodings */ + av_assert0(tail_len <= 5); + if (code < overlong_encoding_mins[tail_len]) { + ret = AVERROR(EILSEQ); + goto end; + } + + if (code >= 1U<<31) { + ret = AVERROR(EILSEQ); /* out-of-range value */ + goto end; + } + + *codep = code; + + if (code > 0x10FFFF && + !(flags & AV_UTF8_FLAG_ACCEPT_INVALID_BIG_CODES)) + ret = AVERROR(EILSEQ); + if (code < 0x20 && code != 0x9 && code != 0xA && code != 0xD && + flags & AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES) + ret = AVERROR(EILSEQ); + if (code >= 0xD800 && code <= 0xDFFF && + !(flags & AV_UTF8_FLAG_ACCEPT_SURROGATES)) + ret = AVERROR(EILSEQ); + if ((code == 0xFFFE || code == 0xFFFF) && + !(flags & AV_UTF8_FLAG_ACCEPT_NON_CHARACTERS)) + ret = AVERROR(EILSEQ); + +end: + *bufp = p; + return ret; +} + +int av_match_list(const char *name, const char *list, char separator) +{ + const char *p, *q; + + for (p = name; p && *p; ) { + for (q = list; q && *q; ) { + int k; + for (k = 0; p[k] == q[k] || (p[k]*q[k] == 0 && p[k]+q[k] == separator); k++) + if (k && (!p[k] || p[k] == separator)) + return 1; + q = strchr(q, separator); + q += !!q; + } + p = strchr(p, separator); + p += !!p; + } + + return 0; +} diff --git a/dreamcast/pvrtex/bprint.c b/dreamcast/pvrtex/bprint.c new file mode 100644 index 00000000..5b540ebc --- /dev/null +++ b/dreamcast/pvrtex/bprint.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2012 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include "avstring.h" +#include "bprint.h" +#include "compat/va_copy.h" +#include "error.h" +#include "macros.h" +#include "mem.h" + +#define av_bprint_room(buf) ((buf)->size - FFMIN((buf)->len, (buf)->size)) +#define av_bprint_is_allocated(buf) ((buf)->str != (buf)->reserved_internal_buffer) + +static int av_bprint_alloc(AVBPrint *buf, unsigned room) +{ + char *old_str, *new_str; + unsigned min_size, new_size; + + if (buf->size == buf->size_max) + return AVERROR(EIO); + if (!av_bprint_is_complete(buf)) + return AVERROR_INVALIDDATA; /* it is already truncated anyway */ + min_size = buf->len + 1 + FFMIN(UINT_MAX - buf->len - 1, room); + new_size = buf->size > buf->size_max / 2 ? buf->size_max : buf->size * 2; + if (new_size < min_size) + new_size = FFMIN(buf->size_max, min_size); + old_str = av_bprint_is_allocated(buf) ? buf->str : NULL; + new_str = av_realloc(old_str, new_size); + if (!new_str) + return AVERROR(ENOMEM); + if (!old_str) + memcpy(new_str, buf->str, buf->len + 1); + buf->str = new_str; + buf->size = new_size; + return 0; +} + +static void av_bprint_grow(AVBPrint *buf, unsigned extra_len) +{ + /* arbitrary margin to avoid small overflows */ + extra_len = FFMIN(extra_len, UINT_MAX - 5 - buf->len); + buf->len += extra_len; + if (buf->size) + buf->str[FFMIN(buf->len, buf->size - 1)] = 0; +} + +void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max) +{ + unsigned size_auto = (char *)buf + sizeof(*buf) - + buf->reserved_internal_buffer; + + if (size_max == 1) + size_max = size_auto; + buf->str = buf->reserved_internal_buffer; + buf->len = 0; + buf->size = FFMIN(size_auto, size_max); + buf->size_max = size_max; + *buf->str = 0; + if (size_init > buf->size) + av_bprint_alloc(buf, size_init - 1); +} + +void av_bprint_init_for_buffer(AVBPrint *buf, char *buffer, unsigned size) +{ + buf->str = buffer; + buf->len = 0; + buf->size = size; + buf->size_max = size; + *buf->str = 0; +} + +void av_bprintf(AVBPrint *buf, const char *fmt, ...) +{ + unsigned room; + char *dst; + va_list vl; + int extra_len; + + while (1) { + room = av_bprint_room(buf); + dst = room ? buf->str + buf->len : NULL; + va_start(vl, fmt); + extra_len = vsnprintf(dst, room, fmt, vl); + va_end(vl); + if (extra_len <= 0) + return; + if (extra_len < room) + break; + if (av_bprint_alloc(buf, extra_len)) + break; + } + av_bprint_grow(buf, extra_len); +} + +void av_vbprintf(AVBPrint *buf, const char *fmt, va_list vl_arg) +{ + unsigned room; + char *dst; + int extra_len; + va_list vl; + + while (1) { + room = av_bprint_room(buf); + dst = room ? buf->str + buf->len : NULL; + va_copy(vl, vl_arg); + extra_len = vsnprintf(dst, room, fmt, vl); + va_end(vl); + if (extra_len <= 0) + return; + if (extra_len < room) + break; + if (av_bprint_alloc(buf, extra_len)) + break; + } + av_bprint_grow(buf, extra_len); +} + +void av_bprint_chars(AVBPrint *buf, char c, unsigned n) +{ + unsigned room, real_n; + + while (1) { + room = av_bprint_room(buf); + if (n < room) + break; + if (av_bprint_alloc(buf, n)) + break; + } + if (room) { + real_n = FFMIN(n, room - 1); + memset(buf->str + buf->len, c, real_n); + } + av_bprint_grow(buf, n); +} + +void av_bprint_append_data(AVBPrint *buf, const char *data, unsigned size) +{ + unsigned room, real_n; + + while (1) { + room = av_bprint_room(buf); + if (size < room) + break; + if (av_bprint_alloc(buf, size)) + break; + } + if (room) { + real_n = FFMIN(size, room - 1); + memcpy(buf->str + buf->len, data, real_n); + } + av_bprint_grow(buf, size); +} + +void av_bprint_strftime(AVBPrint *buf, const char *fmt, const struct tm *tm) +{ + unsigned room; + size_t l; + + if (!*fmt) + return; + while (1) { + room = av_bprint_room(buf); + if (room && (l = strftime(buf->str + buf->len, room, fmt, tm))) + break; + /* strftime does not tell us how much room it would need: let us + retry with twice as much until the buffer is large enough */ + room = !room ? strlen(fmt) + 1 : + room <= INT_MAX / 2 ? room * 2 : INT_MAX; + if (av_bprint_alloc(buf, room)) { + /* impossible to grow, try to manage something useful anyway */ + room = av_bprint_room(buf); + if (room < 1024) { + /* if strftime fails because the buffer has (almost) reached + its maximum size, let us try in a local buffer; 1k should + be enough to format any real date+time string */ + char buf2[1024]; + if ((l = strftime(buf2, sizeof(buf2), fmt, tm))) { + av_bprintf(buf, "%s", buf2); + return; + } + } + if (room) { + /* if anything else failed and the buffer is not already + truncated, let us add a stock string and force truncation */ + static const char txt[] = "[truncated strftime output]"; + memset(buf->str + buf->len, '!', room); + memcpy(buf->str + buf->len, txt, FFMIN(sizeof(txt) - 1, room)); + av_bprint_grow(buf, room); /* force truncation */ + } + return; + } + } + av_bprint_grow(buf, l); +} + +void av_bprint_get_buffer(AVBPrint *buf, unsigned size, + unsigned char **mem, unsigned *actual_size) +{ + if (size > av_bprint_room(buf)) + av_bprint_alloc(buf, size); + *actual_size = av_bprint_room(buf); + *mem = *actual_size ? buf->str + buf->len : NULL; +} + +void av_bprint_clear(AVBPrint *buf) +{ + if (buf->len) { + *buf->str = 0; + buf->len = 0; + } +} + +int av_bprint_finalize(AVBPrint *buf, char **ret_str) +{ + unsigned real_size = FFMIN(buf->len + 1, buf->size); + char *str; + int ret = 0; + + if (ret_str) { + if (av_bprint_is_allocated(buf)) { + str = av_realloc(buf->str, real_size); + if (!str) + str = buf->str; + buf->str = NULL; + } else { + str = av_memdup(buf->str, real_size); + if (!str) + ret = AVERROR(ENOMEM); + } + *ret_str = str; + } else { + if (av_bprint_is_allocated(buf)) + av_freep(&buf->str); + } + buf->size = real_size; + return ret; +} + +#define WHITESPACES " \n\t\r" + +void av_bprint_escape(AVBPrint *dstbuf, const char *src, const char *special_chars, + enum AVEscapeMode mode, int flags) +{ + const char *src0 = src; + + if (mode == AV_ESCAPE_MODE_AUTO) + mode = AV_ESCAPE_MODE_BACKSLASH; /* TODO: implement a heuristic */ + + switch (mode) { + case AV_ESCAPE_MODE_QUOTE: + /* enclose the string between '' */ + av_bprint_chars(dstbuf, '\'', 1); + for (; *src; src++) { + if (*src == '\'') + av_bprintf(dstbuf, "'\\''"); + else + av_bprint_chars(dstbuf, *src, 1); + } + av_bprint_chars(dstbuf, '\'', 1); + break; + + case AV_ESCAPE_MODE_XML: + /* escape XML non-markup character data as per 2.4 by default: */ + /* [^<&]* - ([^<&]* ']]>' [^<&]*) */ + + /* additionally, given one of the AV_ESCAPE_FLAG_XML_* flags, */ + /* escape those specific characters as required. */ + for (; *src; src++) { + switch (*src) { + case '&' : av_bprintf(dstbuf, "%s", "&"); break; + case '<' : av_bprintf(dstbuf, "%s", "<"); break; + case '>' : av_bprintf(dstbuf, "%s", ">"); break; + case '\'': + if (!(flags & AV_ESCAPE_FLAG_XML_SINGLE_QUOTES)) + goto XML_DEFAULT_HANDLING; + + av_bprintf(dstbuf, "%s", "'"); + break; + case '"' : + if (!(flags & AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES)) + goto XML_DEFAULT_HANDLING; + + av_bprintf(dstbuf, "%s", """); + break; +XML_DEFAULT_HANDLING: + default: av_bprint_chars(dstbuf, *src, 1); + } + } + break; + + /* case AV_ESCAPE_MODE_BACKSLASH or unknown mode */ + default: + /* \-escape characters */ + for (; *src; src++) { + int is_first_last = src == src0 || !*(src+1); + int is_ws = !!strchr(WHITESPACES, *src); + int is_strictly_special = special_chars && strchr(special_chars, *src); + int is_special = + is_strictly_special || strchr("'\\", *src) || + (is_ws && (flags & AV_ESCAPE_FLAG_WHITESPACE)); + + if (is_strictly_special || + (!(flags & AV_ESCAPE_FLAG_STRICT) && + (is_special || (is_ws && is_first_last)))) + av_bprint_chars(dstbuf, '\\', 1); + av_bprint_chars(dstbuf, *src, 1); + } + break; + } +} diff --git a/dreamcast/pvrtex/compat/va_copy.h b/dreamcast/pvrtex/compat/va_copy.h new file mode 100644 index 00000000..a40bbe66 --- /dev/null +++ b/dreamcast/pvrtex/compat/va_copy.h @@ -0,0 +1,34 @@ +/* + * MSVC Compatible va_copy macro + * Copyright (c) 2012 Derek Buitenhuis + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef COMPAT_VA_COPY_H +#define COMPAT_VA_COPY_H + +#include + +#if !defined(va_copy) && defined(_MSC_VER) +#define va_copy(dst, src) ((dst) = (src)) +#endif +#if !defined(va_copy) && defined(__GNUC__) && __GNUC__ < 3 +#define va_copy(dst, src) __va_copy(dst, src) +#endif + +#endif /* COMPAT_VA_COPY_H */ diff --git a/dreamcast/pvrtex/config.h b/dreamcast/pvrtex/config.h new file mode 100644 index 00000000..e69de29b diff --git a/dreamcast/pvrtex/crc.c b/dreamcast/pvrtex/crc.c new file mode 100644 index 00000000..703b56f4 --- /dev/null +++ b/dreamcast/pvrtex/crc.c @@ -0,0 +1,415 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "thread.h" +#include "avassert.h" +#include "bswap.h" +#include "crc.h" +#include "error.h" + +#if CONFIG_HARDCODED_TABLES +static const AVCRC av_crc_table[AV_CRC_MAX][257] = { + [AV_CRC_8_ATM] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, + 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, + 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, + 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, + 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, + 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, + 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, + 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, + 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, + 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7, + 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, + 0xFA, 0xFD, 0xF4, 0xF3, 0x01 + }, + [AV_CRC_8_EBU] = { + 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, + 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, 0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, + 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, + 0xF3, 0xEE, 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, + 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, 0xA2, 0xBF, 0x98, 0x85, + 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, + 0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, + 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, + 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, + 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, + 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, + 0x52, 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, + 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, 0x03, 0x1E, 0x39, 0x24, + 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, + 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, + 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, + 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, + 0xA9, 0xB4, 0x93, 0x8E, 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, + 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95, + 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, + 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0, + 0xE3, 0xFE, 0xD9, 0xC4, 0x01 + }, + [AV_CRC_16_ANSI] = { + 0x0000, 0x0580, 0x0F80, 0x0A00, 0x1B80, 0x1E00, 0x1400, 0x1180, + 0x3380, 0x3600, 0x3C00, 0x3980, 0x2800, 0x2D80, 0x2780, 0x2200, + 0x6380, 0x6600, 0x6C00, 0x6980, 0x7800, 0x7D80, 0x7780, 0x7200, + 0x5000, 0x5580, 0x5F80, 0x5A00, 0x4B80, 0x4E00, 0x4400, 0x4180, + 0xC380, 0xC600, 0xCC00, 0xC980, 0xD800, 0xDD80, 0xD780, 0xD200, + 0xF000, 0xF580, 0xFF80, 0xFA00, 0xEB80, 0xEE00, 0xE400, 0xE180, + 0xA000, 0xA580, 0xAF80, 0xAA00, 0xBB80, 0xBE00, 0xB400, 0xB180, + 0x9380, 0x9600, 0x9C00, 0x9980, 0x8800, 0x8D80, 0x8780, 0x8200, + 0x8381, 0x8601, 0x8C01, 0x8981, 0x9801, 0x9D81, 0x9781, 0x9201, + 0xB001, 0xB581, 0xBF81, 0xBA01, 0xAB81, 0xAE01, 0xA401, 0xA181, + 0xE001, 0xE581, 0xEF81, 0xEA01, 0xFB81, 0xFE01, 0xF401, 0xF181, + 0xD381, 0xD601, 0xDC01, 0xD981, 0xC801, 0xCD81, 0xC781, 0xC201, + 0x4001, 0x4581, 0x4F81, 0x4A01, 0x5B81, 0x5E01, 0x5401, 0x5181, + 0x7381, 0x7601, 0x7C01, 0x7981, 0x6801, 0x6D81, 0x6781, 0x6201, + 0x2381, 0x2601, 0x2C01, 0x2981, 0x3801, 0x3D81, 0x3781, 0x3201, + 0x1001, 0x1581, 0x1F81, 0x1A01, 0x0B81, 0x0E01, 0x0401, 0x0181, + 0x0383, 0x0603, 0x0C03, 0x0983, 0x1803, 0x1D83, 0x1783, 0x1203, + 0x3003, 0x3583, 0x3F83, 0x3A03, 0x2B83, 0x2E03, 0x2403, 0x2183, + 0x6003, 0x6583, 0x6F83, 0x6A03, 0x7B83, 0x7E03, 0x7403, 0x7183, + 0x5383, 0x5603, 0x5C03, 0x5983, 0x4803, 0x4D83, 0x4783, 0x4203, + 0xC003, 0xC583, 0xCF83, 0xCA03, 0xDB83, 0xDE03, 0xD403, 0xD183, + 0xF383, 0xF603, 0xFC03, 0xF983, 0xE803, 0xED83, 0xE783, 0xE203, + 0xA383, 0xA603, 0xAC03, 0xA983, 0xB803, 0xBD83, 0xB783, 0xB203, + 0x9003, 0x9583, 0x9F83, 0x9A03, 0x8B83, 0x8E03, 0x8403, 0x8183, + 0x8002, 0x8582, 0x8F82, 0x8A02, 0x9B82, 0x9E02, 0x9402, 0x9182, + 0xB382, 0xB602, 0xBC02, 0xB982, 0xA802, 0xAD82, 0xA782, 0xA202, + 0xE382, 0xE602, 0xEC02, 0xE982, 0xF802, 0xFD82, 0xF782, 0xF202, + 0xD002, 0xD582, 0xDF82, 0xDA02, 0xCB82, 0xCE02, 0xC402, 0xC182, + 0x4382, 0x4602, 0x4C02, 0x4982, 0x5802, 0x5D82, 0x5782, 0x5202, + 0x7002, 0x7582, 0x7F82, 0x7A02, 0x6B82, 0x6E02, 0x6402, 0x6182, + 0x2002, 0x2582, 0x2F82, 0x2A02, 0x3B82, 0x3E02, 0x3402, 0x3182, + 0x1382, 0x1602, 0x1C02, 0x1982, 0x0802, 0x0D82, 0x0782, 0x0202, + 0x0001 + }, + [AV_CRC_16_CCITT] = { + 0x0000, 0x2110, 0x4220, 0x6330, 0x8440, 0xA550, 0xC660, 0xE770, + 0x0881, 0x2991, 0x4AA1, 0x6BB1, 0x8CC1, 0xADD1, 0xCEE1, 0xEFF1, + 0x3112, 0x1002, 0x7332, 0x5222, 0xB552, 0x9442, 0xF772, 0xD662, + 0x3993, 0x1883, 0x7BB3, 0x5AA3, 0xBDD3, 0x9CC3, 0xFFF3, 0xDEE3, + 0x6224, 0x4334, 0x2004, 0x0114, 0xE664, 0xC774, 0xA444, 0x8554, + 0x6AA5, 0x4BB5, 0x2885, 0x0995, 0xEEE5, 0xCFF5, 0xACC5, 0x8DD5, + 0x5336, 0x7226, 0x1116, 0x3006, 0xD776, 0xF666, 0x9556, 0xB446, + 0x5BB7, 0x7AA7, 0x1997, 0x3887, 0xDFF7, 0xFEE7, 0x9DD7, 0xBCC7, + 0xC448, 0xE558, 0x8668, 0xA778, 0x4008, 0x6118, 0x0228, 0x2338, + 0xCCC9, 0xEDD9, 0x8EE9, 0xAFF9, 0x4889, 0x6999, 0x0AA9, 0x2BB9, + 0xF55A, 0xD44A, 0xB77A, 0x966A, 0x711A, 0x500A, 0x333A, 0x122A, + 0xFDDB, 0xDCCB, 0xBFFB, 0x9EEB, 0x799B, 0x588B, 0x3BBB, 0x1AAB, + 0xA66C, 0x877C, 0xE44C, 0xC55C, 0x222C, 0x033C, 0x600C, 0x411C, + 0xAEED, 0x8FFD, 0xECCD, 0xCDDD, 0x2AAD, 0x0BBD, 0x688D, 0x499D, + 0x977E, 0xB66E, 0xD55E, 0xF44E, 0x133E, 0x322E, 0x511E, 0x700E, + 0x9FFF, 0xBEEF, 0xDDDF, 0xFCCF, 0x1BBF, 0x3AAF, 0x599F, 0x788F, + 0x8891, 0xA981, 0xCAB1, 0xEBA1, 0x0CD1, 0x2DC1, 0x4EF1, 0x6FE1, + 0x8010, 0xA100, 0xC230, 0xE320, 0x0450, 0x2540, 0x4670, 0x6760, + 0xB983, 0x9893, 0xFBA3, 0xDAB3, 0x3DC3, 0x1CD3, 0x7FE3, 0x5EF3, + 0xB102, 0x9012, 0xF322, 0xD232, 0x3542, 0x1452, 0x7762, 0x5672, + 0xEAB5, 0xCBA5, 0xA895, 0x8985, 0x6EF5, 0x4FE5, 0x2CD5, 0x0DC5, + 0xE234, 0xC324, 0xA014, 0x8104, 0x6674, 0x4764, 0x2454, 0x0544, + 0xDBA7, 0xFAB7, 0x9987, 0xB897, 0x5FE7, 0x7EF7, 0x1DC7, 0x3CD7, + 0xD326, 0xF236, 0x9106, 0xB016, 0x5766, 0x7676, 0x1546, 0x3456, + 0x4CD9, 0x6DC9, 0x0EF9, 0x2FE9, 0xC899, 0xE989, 0x8AB9, 0xABA9, + 0x4458, 0x6548, 0x0678, 0x2768, 0xC018, 0xE108, 0x8238, 0xA328, + 0x7DCB, 0x5CDB, 0x3FEB, 0x1EFB, 0xF98B, 0xD89B, 0xBBAB, 0x9ABB, + 0x754A, 0x545A, 0x376A, 0x167A, 0xF10A, 0xD01A, 0xB32A, 0x923A, + 0x2EFD, 0x0FED, 0x6CDD, 0x4DCD, 0xAABD, 0x8BAD, 0xE89D, 0xC98D, + 0x267C, 0x076C, 0x645C, 0x454C, 0xA23C, 0x832C, 0xE01C, 0xC10C, + 0x1FEF, 0x3EFF, 0x5DCF, 0x7CDF, 0x9BAF, 0xBABF, 0xD98F, 0xF89F, + 0x176E, 0x367E, 0x554E, 0x745E, 0x932E, 0xB23E, 0xD10E, 0xF01E, + 0x0001 + }, + [AV_CRC_24_IEEE] = { + 0x000000, 0xFB4C86, 0x0DD58A, 0xF6990C, 0xE1E693, 0x1AAA15, 0xEC3319, + 0x177F9F, 0x3981A1, 0xC2CD27, 0x34542B, 0xCF18AD, 0xD86732, 0x232BB4, + 0xD5B2B8, 0x2EFE3E, 0x894EC5, 0x720243, 0x849B4F, 0x7FD7C9, 0x68A856, + 0x93E4D0, 0x657DDC, 0x9E315A, 0xB0CF64, 0x4B83E2, 0xBD1AEE, 0x465668, + 0x5129F7, 0xAA6571, 0x5CFC7D, 0xA7B0FB, 0xE9D10C, 0x129D8A, 0xE40486, + 0x1F4800, 0x08379F, 0xF37B19, 0x05E215, 0xFEAE93, 0xD050AD, 0x2B1C2B, + 0xDD8527, 0x26C9A1, 0x31B63E, 0xCAFAB8, 0x3C63B4, 0xC72F32, 0x609FC9, + 0x9BD34F, 0x6D4A43, 0x9606C5, 0x81795A, 0x7A35DC, 0x8CACD0, 0x77E056, + 0x591E68, 0xA252EE, 0x54CBE2, 0xAF8764, 0xB8F8FB, 0x43B47D, 0xB52D71, + 0x4E61F7, 0xD2A319, 0x29EF9F, 0xDF7693, 0x243A15, 0x33458A, 0xC8090C, + 0x3E9000, 0xC5DC86, 0xEB22B8, 0x106E3E, 0xE6F732, 0x1DBBB4, 0x0AC42B, + 0xF188AD, 0x0711A1, 0xFC5D27, 0x5BEDDC, 0xA0A15A, 0x563856, 0xAD74D0, + 0xBA0B4F, 0x4147C9, 0xB7DEC5, 0x4C9243, 0x626C7D, 0x9920FB, 0x6FB9F7, + 0x94F571, 0x838AEE, 0x78C668, 0x8E5F64, 0x7513E2, 0x3B7215, 0xC03E93, + 0x36A79F, 0xCDEB19, 0xDA9486, 0x21D800, 0xD7410C, 0x2C0D8A, 0x02F3B4, + 0xF9BF32, 0x0F263E, 0xF46AB8, 0xE31527, 0x1859A1, 0xEEC0AD, 0x158C2B, + 0xB23CD0, 0x497056, 0xBFE95A, 0x44A5DC, 0x53DA43, 0xA896C5, 0x5E0FC9, + 0xA5434F, 0x8BBD71, 0x70F1F7, 0x8668FB, 0x7D247D, 0x6A5BE2, 0x911764, + 0x678E68, 0x9CC2EE, 0xA44733, 0x5F0BB5, 0xA992B9, 0x52DE3F, 0x45A1A0, + 0xBEED26, 0x48742A, 0xB338AC, 0x9DC692, 0x668A14, 0x901318, 0x6B5F9E, + 0x7C2001, 0x876C87, 0x71F58B, 0x8AB90D, 0x2D09F6, 0xD64570, 0x20DC7C, + 0xDB90FA, 0xCCEF65, 0x37A3E3, 0xC13AEF, 0x3A7669, 0x148857, 0xEFC4D1, + 0x195DDD, 0xE2115B, 0xF56EC4, 0x0E2242, 0xF8BB4E, 0x03F7C8, 0x4D963F, + 0xB6DAB9, 0x4043B5, 0xBB0F33, 0xAC70AC, 0x573C2A, 0xA1A526, 0x5AE9A0, + 0x74179E, 0x8F5B18, 0x79C214, 0x828E92, 0x95F10D, 0x6EBD8B, 0x982487, + 0x636801, 0xC4D8FA, 0x3F947C, 0xC90D70, 0x3241F6, 0x253E69, 0xDE72EF, + 0x28EBE3, 0xD3A765, 0xFD595B, 0x0615DD, 0xF08CD1, 0x0BC057, 0x1CBFC8, + 0xE7F34E, 0x116A42, 0xEA26C4, 0x76E42A, 0x8DA8AC, 0x7B31A0, 0x807D26, + 0x9702B9, 0x6C4E3F, 0x9AD733, 0x619BB5, 0x4F658B, 0xB4290D, 0x42B001, + 0xB9FC87, 0xAE8318, 0x55CF9E, 0xA35692, 0x581A14, 0xFFAAEF, 0x04E669, + 0xF27F65, 0x0933E3, 0x1E4C7C, 0xE500FA, 0x1399F6, 0xE8D570, 0xC62B4E, + 0x3D67C8, 0xCBFEC4, 0x30B242, 0x27CDDD, 0xDC815B, 0x2A1857, 0xD154D1, + 0x9F3526, 0x6479A0, 0x92E0AC, 0x69AC2A, 0x7ED3B5, 0x859F33, 0x73063F, + 0x884AB9, 0xA6B487, 0x5DF801, 0xAB610D, 0x502D8B, 0x475214, 0xBC1E92, + 0x4A879E, 0xB1CB18, 0x167BE3, 0xED3765, 0x1BAE69, 0xE0E2EF, 0xF79D70, + 0x0CD1F6, 0xFA48FA, 0x01047C, 0x2FFA42, 0xD4B6C4, 0x222FC8, 0xD9634E, + 0xCE1CD1, 0x355057, 0xC3C95B, 0x3885DD, 0x000001, + }, + [AV_CRC_32_IEEE] = { + 0x00000000, 0xB71DC104, 0x6E3B8209, 0xD926430D, 0xDC760413, 0x6B6BC517, + 0xB24D861A, 0x0550471E, 0xB8ED0826, 0x0FF0C922, 0xD6D68A2F, 0x61CB4B2B, + 0x649B0C35, 0xD386CD31, 0x0AA08E3C, 0xBDBD4F38, 0x70DB114C, 0xC7C6D048, + 0x1EE09345, 0xA9FD5241, 0xACAD155F, 0x1BB0D45B, 0xC2969756, 0x758B5652, + 0xC836196A, 0x7F2BD86E, 0xA60D9B63, 0x11105A67, 0x14401D79, 0xA35DDC7D, + 0x7A7B9F70, 0xCD665E74, 0xE0B62398, 0x57ABE29C, 0x8E8DA191, 0x39906095, + 0x3CC0278B, 0x8BDDE68F, 0x52FBA582, 0xE5E66486, 0x585B2BBE, 0xEF46EABA, + 0x3660A9B7, 0x817D68B3, 0x842D2FAD, 0x3330EEA9, 0xEA16ADA4, 0x5D0B6CA0, + 0x906D32D4, 0x2770F3D0, 0xFE56B0DD, 0x494B71D9, 0x4C1B36C7, 0xFB06F7C3, + 0x2220B4CE, 0x953D75CA, 0x28803AF2, 0x9F9DFBF6, 0x46BBB8FB, 0xF1A679FF, + 0xF4F63EE1, 0x43EBFFE5, 0x9ACDBCE8, 0x2DD07DEC, 0x77708634, 0xC06D4730, + 0x194B043D, 0xAE56C539, 0xAB068227, 0x1C1B4323, 0xC53D002E, 0x7220C12A, + 0xCF9D8E12, 0x78804F16, 0xA1A60C1B, 0x16BBCD1F, 0x13EB8A01, 0xA4F64B05, + 0x7DD00808, 0xCACDC90C, 0x07AB9778, 0xB0B6567C, 0x69901571, 0xDE8DD475, + 0xDBDD936B, 0x6CC0526F, 0xB5E61162, 0x02FBD066, 0xBF469F5E, 0x085B5E5A, + 0xD17D1D57, 0x6660DC53, 0x63309B4D, 0xD42D5A49, 0x0D0B1944, 0xBA16D840, + 0x97C6A5AC, 0x20DB64A8, 0xF9FD27A5, 0x4EE0E6A1, 0x4BB0A1BF, 0xFCAD60BB, + 0x258B23B6, 0x9296E2B2, 0x2F2BAD8A, 0x98366C8E, 0x41102F83, 0xF60DEE87, + 0xF35DA999, 0x4440689D, 0x9D662B90, 0x2A7BEA94, 0xE71DB4E0, 0x500075E4, + 0x892636E9, 0x3E3BF7ED, 0x3B6BB0F3, 0x8C7671F7, 0x555032FA, 0xE24DF3FE, + 0x5FF0BCC6, 0xE8ED7DC2, 0x31CB3ECF, 0x86D6FFCB, 0x8386B8D5, 0x349B79D1, + 0xEDBD3ADC, 0x5AA0FBD8, 0xEEE00C69, 0x59FDCD6D, 0x80DB8E60, 0x37C64F64, + 0x3296087A, 0x858BC97E, 0x5CAD8A73, 0xEBB04B77, 0x560D044F, 0xE110C54B, + 0x38368646, 0x8F2B4742, 0x8A7B005C, 0x3D66C158, 0xE4408255, 0x535D4351, + 0x9E3B1D25, 0x2926DC21, 0xF0009F2C, 0x471D5E28, 0x424D1936, 0xF550D832, + 0x2C769B3F, 0x9B6B5A3B, 0x26D61503, 0x91CBD407, 0x48ED970A, 0xFFF0560E, + 0xFAA01110, 0x4DBDD014, 0x949B9319, 0x2386521D, 0x0E562FF1, 0xB94BEEF5, + 0x606DADF8, 0xD7706CFC, 0xD2202BE2, 0x653DEAE6, 0xBC1BA9EB, 0x0B0668EF, + 0xB6BB27D7, 0x01A6E6D3, 0xD880A5DE, 0x6F9D64DA, 0x6ACD23C4, 0xDDD0E2C0, + 0x04F6A1CD, 0xB3EB60C9, 0x7E8D3EBD, 0xC990FFB9, 0x10B6BCB4, 0xA7AB7DB0, + 0xA2FB3AAE, 0x15E6FBAA, 0xCCC0B8A7, 0x7BDD79A3, 0xC660369B, 0x717DF79F, + 0xA85BB492, 0x1F467596, 0x1A163288, 0xAD0BF38C, 0x742DB081, 0xC3307185, + 0x99908A5D, 0x2E8D4B59, 0xF7AB0854, 0x40B6C950, 0x45E68E4E, 0xF2FB4F4A, + 0x2BDD0C47, 0x9CC0CD43, 0x217D827B, 0x9660437F, 0x4F460072, 0xF85BC176, + 0xFD0B8668, 0x4A16476C, 0x93300461, 0x242DC565, 0xE94B9B11, 0x5E565A15, + 0x87701918, 0x306DD81C, 0x353D9F02, 0x82205E06, 0x5B061D0B, 0xEC1BDC0F, + 0x51A69337, 0xE6BB5233, 0x3F9D113E, 0x8880D03A, 0x8DD09724, 0x3ACD5620, + 0xE3EB152D, 0x54F6D429, 0x7926A9C5, 0xCE3B68C1, 0x171D2BCC, 0xA000EAC8, + 0xA550ADD6, 0x124D6CD2, 0xCB6B2FDF, 0x7C76EEDB, 0xC1CBA1E3, 0x76D660E7, + 0xAFF023EA, 0x18EDE2EE, 0x1DBDA5F0, 0xAAA064F4, 0x738627F9, 0xC49BE6FD, + 0x09FDB889, 0xBEE0798D, 0x67C63A80, 0xD0DBFB84, 0xD58BBC9A, 0x62967D9E, + 0xBBB03E93, 0x0CADFF97, 0xB110B0AF, 0x060D71AB, 0xDF2B32A6, 0x6836F3A2, + 0x6D66B4BC, 0xDA7B75B8, 0x035D36B5, 0xB440F7B1, 0x00000001 + }, + [AV_CRC_32_IEEE_LE] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, 0x00000001 + }, + [AV_CRC_16_ANSI_LE] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, + 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, + 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, + 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, + 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, + 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, + 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, + 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, + 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, + 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, + 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, + 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, + 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, + 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, + 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, + 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, + 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, + 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, + 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, + 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, + 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, + 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, + 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, + 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, + 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040, + 0x0001 + }, +}; +#else +#if CONFIG_SMALL +#define CRC_TABLE_SIZE 257 +#else +#define CRC_TABLE_SIZE 1024 +#endif +static AVCRC av_crc_table[AV_CRC_MAX][CRC_TABLE_SIZE]; + +#define DECLARE_CRC_INIT_TABLE_ONCE(id, le, bits, poly) \ +static AVOnce id ## _once_control = AV_ONCE_INIT; \ +static void id ## _init_table_once(void) \ +{ \ + av_assert0(av_crc_init(av_crc_table[id], le, bits, poly, sizeof(av_crc_table[id])) >= 0); \ +} + +#define CRC_INIT_TABLE_ONCE(id) ff_thread_once(&id ## _once_control, id ## _init_table_once) + +DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_8_ATM, 0, 8, 0x07) +DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_8_EBU, 0, 8, 0x1D) +DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_16_ANSI, 0, 16, 0x8005) +DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_16_CCITT, 0, 16, 0x1021) +DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_24_IEEE, 0, 24, 0x864CFB) +DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_32_IEEE, 0, 32, 0x04C11DB7) +DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_32_IEEE_LE, 1, 32, 0xEDB88320) +DECLARE_CRC_INIT_TABLE_ONCE(AV_CRC_16_ANSI_LE, 1, 16, 0xA001) +#endif + +int av_crc_init(AVCRC *ctx, int le, int bits, uint32_t poly, int ctx_size) +{ + unsigned i, j; + uint32_t c; + + if (bits < 8 || bits > 32 || poly >= (1LL << bits)) + return AVERROR(EINVAL); + if (ctx_size != sizeof(AVCRC) * 257 && ctx_size != sizeof(AVCRC) * 1024) + return AVERROR(EINVAL); + + for (i = 0; i < 256; i++) { + if (le) { + for (c = i, j = 0; j < 8; j++) + c = (c >> 1) ^ (poly & (-(c & 1))); + ctx[i] = c; + } else { + for (c = i << 24, j = 0; j < 8; j++) + c = (c << 1) ^ ((poly << (32 - bits)) & (((int32_t) c) >> 31)); + ctx[i] = av_bswap32(c); + } + } + ctx[256] = 1; +#if !CONFIG_SMALL + if (ctx_size >= sizeof(AVCRC) * 1024) + for (i = 0; i < 256; i++) + for (j = 0; j < 3; j++) + ctx[256 * (j + 1) + i] = + (ctx[256 * j + i] >> 8) ^ ctx[ctx[256 * j + i] & 0xFF]; +#endif + + return 0; +} + +const AVCRC *av_crc_get_table(AVCRCId crc_id) +{ +#if !CONFIG_HARDCODED_TABLES + switch (crc_id) { + case AV_CRC_8_ATM: CRC_INIT_TABLE_ONCE(AV_CRC_8_ATM); break; + case AV_CRC_8_EBU: CRC_INIT_TABLE_ONCE(AV_CRC_8_EBU); break; + case AV_CRC_16_ANSI: CRC_INIT_TABLE_ONCE(AV_CRC_16_ANSI); break; + case AV_CRC_16_CCITT: CRC_INIT_TABLE_ONCE(AV_CRC_16_CCITT); break; + case AV_CRC_24_IEEE: CRC_INIT_TABLE_ONCE(AV_CRC_24_IEEE); break; + case AV_CRC_32_IEEE: CRC_INIT_TABLE_ONCE(AV_CRC_32_IEEE); break; + case AV_CRC_32_IEEE_LE: CRC_INIT_TABLE_ONCE(AV_CRC_32_IEEE_LE); break; + case AV_CRC_16_ANSI_LE: CRC_INIT_TABLE_ONCE(AV_CRC_16_ANSI_LE); break; + default: av_assert0(0); + } +#endif + return av_crc_table[crc_id]; +} + +uint32_t av_crc(const AVCRC *ctx, uint32_t crc, + const uint8_t *buffer, size_t length) +{ + const uint8_t *end = buffer + length; + +#if !CONFIG_SMALL + if (!ctx[256]) { + while (((intptr_t) buffer & 3) && buffer < end) + crc = ctx[((uint8_t) crc) ^ *buffer++] ^ (crc >> 8); + + while (buffer < end - 3) { + crc ^= av_le2ne32(*(const uint32_t *) buffer); buffer += 4; + crc = ctx[3 * 256 + ( crc & 0xFF)] ^ + ctx[2 * 256 + ((crc >> 8 ) & 0xFF)] ^ + ctx[1 * 256 + ((crc >> 16) & 0xFF)] ^ + ctx[0 * 256 + ((crc >> 24) )]; + } + } +#endif + while (buffer < end) + crc = ctx[((uint8_t) crc) ^ *buffer++] ^ (crc >> 8); + + return crc; +} diff --git a/dreamcast/pvrtex/dither.cpp b/dreamcast/pvrtex/dither.cpp new file mode 100644 index 00000000..054762fa --- /dev/null +++ b/dreamcast/pvrtex/dither.cpp @@ -0,0 +1,493 @@ +//From https://bisqwit.iki.fi/story/howto/dither/jy/ + +//~ #include +#include +#include +#include /* For std::sort() */ +#include +#include /* For associative container, std::map<> */ + +//////////////////////////////////////////////////// +#if 0 +#include // for std::pair +#include "alloc/FSBAllocator.hh" + +/* kd-tree implementation translated to C++ + * from java implementation in VideoMosaic + * at http://www.intelegance.net/video/videomosaic.shtml. + */ + +template +class KDTree { +public: + struct KDPoint { + double coord[K]; + + KDPoint() { } + + KDPoint(double a,double b,double c) { + coord[0] = a; + coord[1] = b; + coord[2] = c; + } + + KDPoint(double v[K]) { + for(unsigned n=0; n= max.coord[i]) + p.coord[i] = max.coord[i]; + else + p.coord[i] = t.coord[i]; + return p; + } + void MakeInfinite() { + for(unsigned i=0; ik) + return 0; /* key duplicate */ + else if(key.coord[lev] > t->k.coord[lev]) + return ins(key, val, t->right, (lev+1)%K); + else + return ins(key, val, t->left, (lev+1)%K); + } + struct Nearest { + const KDNode* kd; + double dist_sqd; + }; + // Method Nearest Neighbor from Andrew Moore's thesis. Numbered + // comments are direct quotes from there. Step "SDL" is added to + // make the algorithm work correctly. + static void nnbr(const KDNode* kd, const KDPoint& target, + KDRect& hr, // in-param and temporary; not an out-param. + int lev, + Nearest& nearest) { + // 1. if kd is empty then set dist-sqd to infinity and exit. + if (!kd) return; + + // 2. s := split field of kd + int s = lev % K; + + // 3. pivot := dom-elt field of kd + const KDPoint& pivot = kd->k; + double pivot_to_target = pivot.sqrdist(target); + + // 4. Cut hr into to sub-hyperrectangles left-hr and right-hr. + // The cut plane is through pivot and perpendicular to the s + // dimension. + KDRect& left_hr = hr; // optimize by not cloning + KDRect right_hr = hr; + left_hr.max.coord[s] = pivot.coord[s]; + right_hr.min.coord[s] = pivot.coord[s]; + + // 5. target-in-left := target_s <= pivot_s + bool target_in_left = target.coord[s] < pivot.coord[s]; + + const KDNode* nearer_kd; + const KDNode* further_kd; + KDRect nearer_hr; + KDRect further_hr; + + // 6. if target-in-left then nearer is left, further is right + if (target_in_left) { + nearer_kd = kd->left; + nearer_hr = left_hr; + further_kd = kd->right; + further_hr = right_hr; + } + // 7. if not target-in-left then nearer is right, further is left + else { + nearer_kd = kd->right; + nearer_hr = right_hr; + further_kd = kd->left; + further_hr = left_hr; + } + + // 8. Recursively call Nearest Neighbor with parameters + // (nearer-kd, target, nearer-hr, max-dist-sqd), storing the + // results in nearest and dist-sqd + nnbr(nearer_kd, target, nearer_hr, lev + 1, nearest); + + // 10. A nearer point could only lie in further-kd if there were some + // part of further-hr within distance sqrt(max-dist-sqd) of + // target. If this is the case then + const KDPoint closest = further_hr.bound(target); + if (closest.sqrdist(target) < nearest.dist_sqd) { + // 10.1 if (pivot-target)^2 < dist-sqd then + if (pivot_to_target < nearest.dist_sqd) { + // 10.1.1 nearest := (pivot, range-elt field of kd) + nearest.kd = kd; + // 10.1.2 dist-sqd = (pivot-target)^2 + nearest.dist_sqd = pivot_to_target; + } + + // 10.2 Recursively call Nearest Neighbor with parameters + // (further-kd, target, further-hr, max-dist_sqd) + nnbr(further_kd, target, further_hr, lev + 1, nearest); + } + // SDL: otherwise, current point is nearest + else if (pivot_to_target < nearest.dist_sqd) { + nearest.kd = kd; + nearest.dist_sqd = pivot_to_target; + } + } + private: + void operator=(const KDNode&); + public: + KDNode(const KDNode& b) + : k(b.k), v(b.v), + left( b.left ? new KDNode(*b.left) : 0), + right( b.right ? new KDNode(*b.right) : 0 ) { } + }; +private: + KDNode* m_root; +public: + KDTree() : m_root(0) { } + virtual ~KDTree() { + delete (m_root); + } + + bool insert(const KDPoint& key, const V& val) { + return KDNode::ins(key, val, m_root, 0); + } + + const std::pair nearest(const KDPoint& key) const { + KDRect hr; + hr.MakeInfinite(); + + typename KDNode::Nearest nn; + nn.kd = 0; + nn.dist_sqd = 1e99; + KDNode::nnbr(m_root, key, hr, 0, nn); + if(!nn.kd) return std::pair ( V(), 1e99 ); + return std::pair ( nn.kd->v, nn.dist_sqd); + } +public: + KDTree& operator=(const KDTree&b) { + if(this != &b) { + if(m_root) delete (m_root); + m_root = b.m_root ? new KDNode(*b.m_root) : 0; + m_count = b.m_count; + } + return *this; + } + KDTree(const KDTree& b) + : m_root( b.m_root ? new KDNode(*b.m_root) : 0 ), + m_count( b.m_count ) { } +}; +#endif +//////////////////////////////////////////////////// + +#define COMPARE_RGB 1 + +/* 8x8 threshold map */ +static const unsigned char map[8*8] = { + 0,48,12,60, 3,51,15,63, + 32,16,44,28,35,19,47,31, + 8,56, 4,52,11,59, 7,55, + 40,24,36,20,43,27,39,23, + 2,50,14,62, 1,49,13,61, + 34,18,46,30,33,17,45,29, + 10,58, 6,54, 9,57, 5,53, + 42,26,38,22,41,25,37,21 +}; + +static const double Gamma = 2.2; // Gamma correction we use. + +double GammaCorrect(double v) { + return pow(v, Gamma); +} +double GammaUncorrect(double v) { + return pow(v, 1.0 / Gamma); +} + +/* CIE C illuminant */ +static const double illum[3*3] = { + 0.488718, 0.176204, 0.000000, + 0.310680, 0.812985, 0.0102048, + 0.200602, 0.0108109, 0.989795 +}; +struct LabItem { // CIE L*a*b* color value with C and h added. + double L,a,b,C,h; + + LabItem() { } + LabItem(double R,double G,double B) { + Set(R,G,B); + } + void Set(double R,double G,double B) { + const double* const i = illum; + double X = i[0]*R + i[3]*G + i[6]*B, x = X / (i[0] + i[1] + i[2]); + double Y = i[1]*R + i[4]*G + i[7]*B, y = Y / (i[3] + i[4] + i[5]); + double Z = i[2]*R + i[5]*G + i[8]*B, z = Z / (i[6] + i[7] + i[8]); + const double threshold1 = (6*6*6.0)/(29*29*29.0); + const double threshold2 = (29*29.0)/(6*6*3.0); + double x1 = (x > threshold1) ? pow(x, 1.0/3.0) : (threshold2*x)+(4/29.0); + double y1 = (y > threshold1) ? pow(y, 1.0/3.0) : (threshold2*y)+(4/29.0); + double z1 = (z > threshold1) ? pow(z, 1.0/3.0) : (threshold2*z)+(4/29.0); + L = (29*4)*y1 - (4*4); + a = (500*(x1-y1) ); + b = (200*(y1-z1) ); + C = sqrt(a*a + b+b); + h = atan2(b, a); + } + LabItem(unsigned rgb) { + Set(rgb); + } + void Set(unsigned rgb) { + Set( (rgb>>16)/255.0, ((rgb>>8)&0xFF)/255.0, (rgb&0xFF)/255.0 ); + } +}; + +/* From the paper "The CIEDE2000 Color-Difference Formula: Implementation Notes, */ +/* Supplementary Test Data, and Mathematical Observations", by */ +/* Gaurav Sharma, Wencheng Wu and Edul N. Dalal, */ +/* Color Res. Appl., vol. 30, no. 1, pp. 21-30, Feb. 2005. */ +/* Return the CIEDE2000 Delta E color difference measure squared, for two Lab values */ +double ColorCompare(const LabItem& lab1, const LabItem& lab2) { +#define RAD2DEG(xx) (180.0/M_PI * (xx)) +#define DEG2RAD(xx) (M_PI/180.0 * (xx)) + /* Compute Cromanance and Hue angles */ + double C1,C2, h1,h2; + { + double Cab = 0.5 * (lab1.C + lab2.C); + double Cab7 = pow(Cab,7.0); + double G = 0.5 * (1.0 - sqrt(Cab7/(Cab7 + 6103515625.0))); + double a1 = (1.0 + G) * lab1.a; + double a2 = (1.0 + G) * lab2.a; + C1 = sqrt(a1 * a1 + lab1.b * lab1.b); + C2 = sqrt(a2 * a2 + lab2.b * lab2.b); + + if (C1 < 1e-9) + h1 = 0.0; + else { + h1 = RAD2DEG(atan2(lab1.b, a1)); + if (h1 < 0.0) + h1 += 360.0; + } + + if (C2 < 1e-9) + h2 = 0.0; + else { + h2 = RAD2DEG(atan2(lab2.b, a2)); + if (h2 < 0.0) + h2 += 360.0; + } + } + + /* Compute delta L, C and H */ + double dL = lab2.L - lab1.L, dC = C2 - C1, dH; + { + double dh; + if (C1 < 1e-9 || C2 < 1e-9) { + dh = 0.0; + } else { + dh = h2 - h1; + /**/ if (dh > 180.0) dh -= 360.0; + else if (dh < -180.0) dh += 360.0; + } + + dH = 2.0 * sqrt(C1 * C2) * sin(DEG2RAD(0.5 * dh)); + } + + double h; + double L = 0.5 * (lab1.L + lab2.L); + double C = 0.5 * (C1 + C2); + if (C1 < 1e-9 || C2 < 1e-9) { + h = h1 + h2; + } else { + h = h1 + h2; + if (fabs(h1 - h2) > 180.0) { + /**/ if (h < 360.0) h += 360.0; + else if (h >= 360.0) h -= 360.0; + } + h *= 0.5; + } + double T = 1.0 + - 0.17 * cos(DEG2RAD(h - 30.0)) + + 0.24 * cos(DEG2RAD(2.0 * h)) + + 0.32 * cos(DEG2RAD(3.0 * h + 6.0)) + - 0.2 * cos(DEG2RAD(4.0 * h - 63.0)); + double hh = (h - 275.0)/25.0; + double ddeg = 30.0 * exp(-hh * hh); + double C7 = pow(C,7.0); + double RC = 2.0 * sqrt(C7/(C7 + 6103515625.0)); + double L50sq = (L - 50.0) * (L - 50.0); + double SL = 1.0 + (0.015 * L50sq) / sqrt(20.0 + L50sq); + double SC = 1.0 + 0.045 * C; + double SH = 1.0 + 0.015 * C * T; + double RT = -sin(DEG2RAD(2 * ddeg)) * RC; + double dLsq = dL/SL, dCsq = dC/SC, dHsq = dH/SH; + return dLsq*dLsq + dCsq*dCsq + dHsq*dHsq + RT*dCsq*dHsq; +#undef RAD2DEG +#undef DEG2RAD +} + +double ColorCompare(int r1,int g1,int b1, int r2,int g2,int b2) { + double luma1 = (r1*299 + g1*587 + b1*114) / (255.0*1000); + double luma2 = (r2*299 + g2*587 + b2*114) / (255.0*1000); + double lumadiff = luma1-luma2; + double diffR = (r1-r2)/255.0, diffG = (g1-g2)/255.0, diffB = (b1-b2)/255.0; + return (diffR*diffR*0.299 + diffG*diffG*0.587 + diffB*diffB*0.114)*0.75 + + lumadiff*lumadiff; +} + + +/* Palette */ +static const unsigned palettesize = 16; +static const unsigned pal[palettesize] = { + 0x080000,0x201A0B,0x432817,0x492910, 0x234309,0x5D4F1E,0x9C6B20,0xA9220F, + 0x2B347C,0x2B7409,0xD0CA40,0xE8A077, 0x6A94AB,0xD5C4B3,0xFCE76E,0xFCFAE2 +}; + +/* Luminance for each palette entry, to be initialized as soon as the program begins */ +static unsigned luma[palettesize]; +static LabItem meta[palettesize]; +static double pal_g[palettesize][3]; // Gamma-corrected palette entry + +inline bool PaletteCompareLuma(unsigned index1, unsigned index2) { + return luma[index1] < luma[index2]; +} + +typedef std::vector MixingPlan; +MixingPlan DeviseBestMixingPlan(unsigned color, size_t limit) { + // Input color in RGB + int input_rgb[3] = { (int)((color>>16)&0xFF), + (int)((color>>8)&0xFF), + (int)(color&0xFF) + }; + + // Input color in CIE L*a*b* + LabItem input(color); + + // Tally so far (gamma-corrected) + double so_far[3] = { 0,0,0 }; + + MixingPlan result; + while(result.size() < limit) { + unsigned chosen_amount = 1; + unsigned chosen = 0; + + const unsigned max_test_count = result.empty() ? 1 : result.size(); + + double least_penalty = -1; + for(unsigned index=0; index>16, g = (pal[c]>>8) & 0xFF, b = pal[c] & 0xFF; + gdImageColorAllocate(im, r,g,b); + luma[c] = r*299 + g*587 + b*114; + meta[c].Set(pal[c]); + pal_g[c][0] = GammaCorrect(r/255.0); + pal_g[c][1] = GammaCorrect(g/255.0); + pal_g[c][2] = GammaCorrect(b/255.0); + } + #pragma omp parallel for + for(unsigned y=0; y + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Codebook Generator using the ELBG algorithm + */ + +#include + +#include "libavutil/avassert.h" +#include "libavutil/common.h" +#include "libavutil/lfg.h" +#include "elbg.h" + +#define DELTA_ERR_MAX 0.1 ///< Precision of the ELBG algorithm (as percentage error) + +/** + * In the ELBG jargon, a cell is the set of points that are closest to a + * codebook entry. Not to be confused with a RoQ Video cell. */ +typedef struct cell_s { + int index; + struct cell_s *next; +} cell; + +/** + * ELBG internal data + */ +typedef struct ELBGContext { + int error; + int dim; + int num_cb; + int *codebook; + cell **cells; + int *utility; + int *utility_inc; + int *nearest_cb; + int *points; + int *temp_points; + int *size_part; + AVLFG *rand_state; + int *scratchbuf; + cell *cell_buffer; + + /* Sizes for the buffers above. Pointers without such a field + * are not allocated by us and only valid for the duration + * of a single call to avpriv_elbg_do(). */ + unsigned utility_allocated; + unsigned utility_inc_allocated; + unsigned size_part_allocated; + unsigned cells_allocated; + unsigned scratchbuf_allocated; + unsigned cell_buffer_allocated; + unsigned temp_points_allocated; +} ELBGContext; + +static inline int distance_limited(int *a, int *b, int dim, int limit) +{ + int i, dist=0; + for (i=0; i= limit - distance) + return limit; + dist += distance; + } + + return dist; +} + +static inline void vect_division(int *res, int *vect, int div, int dim) +{ + int i; + if (div > 1) + for (i=0; inext) { + int distance = distance_limited(centroid, elbg->points + cells->index*elbg->dim, elbg->dim, INT_MAX); + if (error >= INT_MAX - distance) + return INT_MAX; + error += distance; + } + + return error; +} + +static int get_closest_codebook(ELBGContext *elbg, int index) +{ + int pick = 0; + for (int i = 0, diff_min = INT_MAX; i < elbg->num_cb; i++) + if (i != index) { + int diff; + diff = distance_limited(elbg->codebook + i*elbg->dim, elbg->codebook + index*elbg->dim, elbg->dim, diff_min); + if (diff < diff_min) { + pick = i; + diff_min = diff; + } + } + return pick; +} + +static int get_high_utility_cell(ELBGContext *elbg) +{ + int i=0; + /* Using linear search, do binary if it ever turns to be speed critical */ + uint64_t r; + + if (elbg->utility_inc[elbg->num_cb - 1] < INT_MAX) { + r = av_lfg_get(elbg->rand_state) % (unsigned int)elbg->utility_inc[elbg->num_cb - 1] + 1; + } else { + r = av_lfg_get(elbg->rand_state); + r = (av_lfg_get(elbg->rand_state) + (r<<32)) % elbg->utility_inc[elbg->num_cb - 1] + 1; + } + + while (elbg->utility_inc[i] < r) { + i++; + } + + av_assert2(elbg->cells[i]); + + return i; +} + +/** + * Implementation of the simple LBG algorithm for just two codebooks + */ +static int simple_lbg(ELBGContext *elbg, + int dim, + int *centroid[3], + int newutility[3], + int *points, + cell *cells) +{ + int i, idx; + int numpoints[2] = {0,0}; + int *newcentroid[2] = { + elbg->scratchbuf + 3*dim, + elbg->scratchbuf + 4*dim + }; + cell *tempcell; + + memset(newcentroid[0], 0, 2 * dim * sizeof(*newcentroid[0])); + + newutility[0] = + newutility[1] = 0; + + for (tempcell = cells; tempcell; tempcell=tempcell->next) { + idx = distance_limited(centroid[0], points + tempcell->index*dim, dim, INT_MAX)>= + distance_limited(centroid[1], points + tempcell->index*dim, dim, INT_MAX); + numpoints[idx]++; + for (i=0; iindex*dim + i]; + } + + vect_division(centroid[0], newcentroid[0], numpoints[0], dim); + vect_division(centroid[1], newcentroid[1], numpoints[1], dim); + + for (tempcell = cells; tempcell; tempcell=tempcell->next) { + int dist[2] = {distance_limited(centroid[0], points + tempcell->index*dim, dim, INT_MAX), + distance_limited(centroid[1], points + tempcell->index*dim, dim, INT_MAX)}; + int idx = dist[0] > dist[1]; + if (newutility[idx] >= INT_MAX - dist[idx]) + newutility[idx] = INT_MAX; + else + newutility[idx] += dist[idx]; + } + + return (newutility[0] >= INT_MAX - newutility[1]) ? INT_MAX : newutility[0] + newutility[1]; +} + +static void get_new_centroids(ELBGContext *elbg, int huc, int *newcentroid_i, + int *newcentroid_p) +{ + cell *tempcell; + int *min = newcentroid_i; + int *max = newcentroid_p; + int i; + + for (i=0; i< elbg->dim; i++) { + min[i]=INT_MAX; + max[i]=0; + } + + for (tempcell = elbg->cells[huc]; tempcell; tempcell = tempcell->next) + for(i=0; idim; i++) { + min[i]=FFMIN(min[i], elbg->points[tempcell->index*elbg->dim + i]); + max[i]=FFMAX(max[i], elbg->points[tempcell->index*elbg->dim + i]); + } + + for (i=0; idim; i++) { + int ni = min[i] + (max[i] - min[i])/3; + int np = min[i] + (2*(max[i] - min[i]))/3; + newcentroid_i[i] = ni; + newcentroid_p[i] = np; + } +} + +/** + * Add the points in the low utility cell to its closest cell. Split the high + * utility cell, putting the separated points in the (now empty) low utility + * cell. + * + * @param elbg Internal elbg data + * @param indexes {luc, huc, cluc} + * @param newcentroid A vector with the position of the new centroids + */ +static void shift_codebook(ELBGContext *elbg, int *indexes, + int *newcentroid[3]) +{ + cell *tempdata; + cell **pp = &elbg->cells[indexes[2]]; + + while(*pp) + pp= &(*pp)->next; + + *pp = elbg->cells[indexes[0]]; + + elbg->cells[indexes[0]] = NULL; + tempdata = elbg->cells[indexes[1]]; + elbg->cells[indexes[1]] = NULL; + + while(tempdata) { + cell *tempcell2 = tempdata->next; + int idx = distance_limited(elbg->points + tempdata->index*elbg->dim, + newcentroid[0], elbg->dim, INT_MAX) > + distance_limited(elbg->points + tempdata->index*elbg->dim, + newcentroid[1], elbg->dim, INT_MAX); + + tempdata->next = elbg->cells[indexes[idx]]; + elbg->cells[indexes[idx]] = tempdata; + tempdata = tempcell2; + } +} + +static void evaluate_utility_inc(ELBGContext *elbg) +{ + int64_t inc=0; + + for (int i = 0; i < elbg->num_cb; i++) { + if (elbg->num_cb * (int64_t)elbg->utility[i] > elbg->error) + inc += elbg->utility[i]; + elbg->utility_inc[i] = FFMIN(inc, INT_MAX); + } +} + + +static void update_utility_and_n_cb(ELBGContext *elbg, int idx, int newutility) +{ + cell *tempcell; + + elbg->utility[idx] = newutility; + for (tempcell=elbg->cells[idx]; tempcell; tempcell=tempcell->next) + elbg->nearest_cb[tempcell->index] = idx; +} + +/** + * Evaluate if a shift lower the error. If it does, call shift_codebooks + * and update elbg->error, elbg->utility and elbg->nearest_cb. + * + * @param elbg Internal elbg data + * @param idx {luc (low utility cell, huc (high utility cell), cluc (closest cell to low utility cell)} + */ +static void try_shift_candidate(ELBGContext *elbg, int idx[3]) +{ + int j, k, cont=0, tmp; + int64_t olderror=0, newerror; + int newutility[3]; + int *newcentroid[3] = { + elbg->scratchbuf, + elbg->scratchbuf + elbg->dim, + elbg->scratchbuf + 2*elbg->dim + }; + cell *tempcell; + + for (j=0; j<3; j++) + olderror += elbg->utility[idx[j]]; + + memset(newcentroid[2], 0, elbg->dim*sizeof(int)); + + for (k=0; k<2; k++) + for (tempcell=elbg->cells[idx[2*k]]; tempcell; tempcell=tempcell->next) { + cont++; + for (j=0; jdim; j++) + newcentroid[2][j] += elbg->points[tempcell->index*elbg->dim + j]; + } + + vect_division(newcentroid[2], newcentroid[2], cont, elbg->dim); + + get_new_centroids(elbg, idx[1], newcentroid[0], newcentroid[1]); + + newutility[2] = eval_error_cell(elbg, newcentroid[2], elbg->cells[idx[0]]); + tmp = eval_error_cell(elbg, newcentroid[2], elbg->cells[idx[2]]); + newutility[2] = (tmp >= INT_MAX - newutility[2]) ? INT_MAX : newutility[2] + tmp; + + newerror = newutility[2]; + + tmp = simple_lbg(elbg, elbg->dim, newcentroid, newutility, elbg->points, + elbg->cells[idx[1]]); + if (tmp >= INT_MAX - newerror) + newerror = INT_MAX; + else + newerror += tmp; + + if (olderror > newerror) { + shift_codebook(elbg, idx, newcentroid); + + elbg->error += newerror - olderror; + + for (j=0; j<3; j++) + update_utility_and_n_cb(elbg, idx[j], newutility[j]); + + evaluate_utility_inc(elbg); + } + } + +/** + * Implementation of the ELBG block + */ +static void do_shiftings(ELBGContext *elbg) +{ + int idx[3]; + + evaluate_utility_inc(elbg); + + for (idx[0]=0; idx[0] < elbg->num_cb; idx[0]++) + if (elbg->num_cb * (int64_t)elbg->utility[idx[0]] < elbg->error) { + if (elbg->utility_inc[elbg->num_cb - 1] == 0) + return; + + idx[1] = get_high_utility_cell(elbg); + idx[2] = get_closest_codebook(elbg, idx[0]); + + if (idx[1] != idx[0] && idx[1] != idx[2]) + try_shift_candidate(elbg, idx); + } +} + +static void do_elbg(ELBGContext *av_restrict elbg, int *points, int numpoints, + int max_steps) +{ + int *const size_part = elbg->size_part; + int i, j, steps = 0; + int best_idx = 0; + int last_error; + + elbg->error = INT_MAX; + elbg->points = points; + + do { + cell *free_cells = elbg->cell_buffer; + last_error = elbg->error; + steps++; + memset(elbg->utility, 0, elbg->num_cb * sizeof(*elbg->utility)); + memset(elbg->cells, 0, elbg->num_cb * sizeof(*elbg->cells)); + + elbg->error = 0; + + /* This loop evaluate the actual Voronoi partition. It is the most + costly part of the algorithm. */ + for (i=0; i < numpoints; i++) { + int best_dist = distance_limited(elbg->points + i * elbg->dim, + elbg->codebook + best_idx * elbg->dim, + elbg->dim, INT_MAX); + for (int k = 0; k < elbg->num_cb; k++) { + int dist = distance_limited(elbg->points + i * elbg->dim, + elbg->codebook + k * elbg->dim, + elbg->dim, best_dist); + if (dist < best_dist) { + best_dist = dist; + best_idx = k; + } + } + elbg->nearest_cb[i] = best_idx; + elbg->error = (elbg->error >= INT_MAX - best_dist) ? INT_MAX : elbg->error + best_dist; + elbg->utility[elbg->nearest_cb[i]] = (elbg->utility[elbg->nearest_cb[i]] >= INT_MAX - best_dist) ? + INT_MAX : elbg->utility[elbg->nearest_cb[i]] + best_dist; + free_cells->index = i; + free_cells->next = elbg->cells[elbg->nearest_cb[i]]; + elbg->cells[elbg->nearest_cb[i]] = free_cells; + free_cells++; + } + + do_shiftings(elbg); + + memset(size_part, 0, elbg->num_cb * sizeof(*size_part)); + + memset(elbg->codebook, 0, elbg->num_cb * elbg->dim * sizeof(*elbg->codebook)); + + for (i=0; i < numpoints; i++) { + size_part[elbg->nearest_cb[i]]++; + for (j=0; j < elbg->dim; j++) + elbg->codebook[elbg->nearest_cb[i]*elbg->dim + j] += + elbg->points[i*elbg->dim + j]; + } + + for (int i = 0; i < elbg->num_cb; i++) + vect_division(elbg->codebook + i*elbg->dim, + elbg->codebook + i*elbg->dim, size_part[i], elbg->dim); + + } while(((last_error - elbg->error) > DELTA_ERR_MAX*elbg->error) && + (steps < max_steps)); +} + +#define BIG_PRIME 433494437LL + +/** + * Initialize the codebook vector for the elbg algorithm. + * If numpoints <= 24 * num_cb this function fills codebook with random numbers. + * If not, it calls do_elbg for a (smaller) random sample of the points in + * points. + */ +static void init_elbg(ELBGContext *av_restrict elbg, int *points, int *temp_points, + int numpoints, int max_steps) +{ + int dim = elbg->dim; + + if (numpoints > 24LL * elbg->num_cb) { + /* ELBG is very costly for a big number of points. So if we have a lot + of them, get a good initial codebook to save on iterations */ + for (int i = 0; i < numpoints / 8; i++) { + int k = (i*BIG_PRIME) % numpoints; + memcpy(temp_points + i*dim, points + k*dim, dim * sizeof(*temp_points)); + } + + /* If anything is changed in the recursion parameters, + * the allocated size of temp_points will also need to be updated. */ + init_elbg(elbg, temp_points, temp_points + numpoints / 8 * dim, + numpoints / 8, 2 * max_steps); + do_elbg(elbg, temp_points, numpoints / 8, 2 * max_steps); + } else // If not, initialize the codebook with random positions + for (int i = 0; i < elbg->num_cb; i++) + memcpy(elbg->codebook + i * dim, points + ((i*BIG_PRIME)%numpoints)*dim, + dim * sizeof(*elbg->codebook)); +} + +int avpriv_elbg_do(ELBGContext **elbgp, int *points, int dim, int numpoints, + int *codebook, int num_cb, int max_steps, + int *closest_cb, AVLFG *rand_state, uintptr_t flags) +{ + ELBGContext *const av_restrict elbg = *elbgp ? *elbgp : av_mallocz(sizeof(*elbg)); + + if (!elbg) + return AVERROR(ENOMEM); + *elbgp = elbg; + + elbg->nearest_cb = closest_cb; + elbg->rand_state = rand_state; + elbg->codebook = codebook; + elbg->num_cb = num_cb; + elbg->dim = dim; + +#define ALLOCATE_IF_NECESSARY(field, new_elements, multiplicator) \ + if (elbg->field ## _allocated < new_elements) { \ + av_freep(&elbg->field); \ + elbg->field = av_malloc_array(new_elements, \ + multiplicator * sizeof(*elbg->field)); \ + if (!elbg->field) { \ + elbg->field ## _allocated = 0; \ + return AVERROR(ENOMEM); \ + } \ + elbg->field ## _allocated = new_elements; \ + } + /* Allocating the buffers for do_elbg() here once relies + * on their size being always the same even when do_elbg() + * is called from init_elbg(). It also relies on do_elbg() + * never calling itself recursively. */ + ALLOCATE_IF_NECESSARY(cells, num_cb, 1) + ALLOCATE_IF_NECESSARY(utility, num_cb, 1) + ALLOCATE_IF_NECESSARY(utility_inc, num_cb, 1) + ALLOCATE_IF_NECESSARY(size_part, num_cb, 1) + ALLOCATE_IF_NECESSARY(cell_buffer, numpoints, 1) + ALLOCATE_IF_NECESSARY(scratchbuf, dim, 5) + if (numpoints > 24LL * elbg->num_cb) { + /* The first step in the recursion in init_elbg() needs a buffer with + * (numpoints / 8) * dim elements; the next step needs numpoints / 8 / 8 + * * dim elements etc. The geometric series leads to an upper bound of + * numpoints / 8 * 8 / 7 * dim elements. */ + uint64_t prod = dim * (uint64_t)(numpoints / 7U); + if (prod > INT_MAX) + return AVERROR(ERANGE); + ALLOCATE_IF_NECESSARY(temp_points, prod, 1) + } + + init_elbg(elbg, points, elbg->temp_points, numpoints, max_steps); + do_elbg (elbg, points, numpoints, max_steps); + return 0; +} + +av_cold void avpriv_elbg_free(ELBGContext **elbgp) +{ + ELBGContext *elbg = *elbgp; + if (!elbg) + return; + + av_freep(&elbg->size_part); + av_freep(&elbg->utility); + av_freep(&elbg->cell_buffer); + av_freep(&elbg->cells); + av_freep(&elbg->utility_inc); + av_freep(&elbg->scratchbuf); + av_freep(&elbg->temp_points); + + av_freep(elbgp); +} diff --git a/dreamcast/pvrtex/elbg.h b/dreamcast/pvrtex/elbg.h new file mode 100644 index 00000000..bb218048 --- /dev/null +++ b/dreamcast/pvrtex/elbg.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 Vitor Sessak + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_ELBG_H +#define AVCODEC_ELBG_H + +#include +#include "libavutil/lfg.h" + +#define av_restrict restrict + +struct ELBGContext; + +/** + * Implementation of the Enhanced LBG Algorithm + * Based on the paper "Neural Networks 14:1219-1237" that can be found in + * http://citeseer.ist.psu.edu/patan01enhanced.html . + * + * @param ctx A pointer to a pointer to an already allocated ELBGContext + * or a pointer to NULL. In the latter case, this function + * will allocate an ELBGContext and put a pointer to it in `*ctx`. + * @param points Input points. + * @param dim Dimension of the points. + * @param numpoints Num of points in **points. + * @param codebook Pointer to the output codebook. Must be allocated. + * @param num_cb Number of points in the codebook. + * @param num_steps The maximum number of steps. One step is already a good compromise between time and quality. + * @param closest_cb Return the closest codebook to each point. Must be allocated. + * @param rand_state A random number generator state. Should be already initialized by av_lfg_init(). + * @param flags Currently unused; must be set to 0. + * @return < 0 in case of error, 0 otherwise + */ +int avpriv_elbg_do(struct ELBGContext **ctx, int *points, int dim, + int numpoints, int *codebook, int num_cb, int num_steps, + int *closest_cb, AVLFG *rand_state, uintptr_t flags); + +/** + * Free an ELBGContext and reset the pointer to it. + */ +void avpriv_elbg_free(struct ELBGContext **ctx); + +#endif /* AVCODEC_ELBG_H */ diff --git a/dreamcast/pvrtex/file_common.c b/dreamcast/pvrtex/file_common.c new file mode 100644 index 00000000..ba597517 --- /dev/null +++ b/dreamcast/pvrtex/file_common.c @@ -0,0 +1,74 @@ +#include +#include +#include "file_common.h" +#include "pvr_texture_encoder.h" + +void CheckedFwrite(const void *data, size_t size, FILE *f) { + int writeamt = fwrite(data, 1, size, f); + if (writeamt != size) { + perror(""); + ErrorExit("write error, wanted to write %i, but only wrote %i\n", size, writeamt); + } +} + +void WriteFourCC(const char *fourcc, FILE *f) { + assert(strlen(fourcc) == 4); + CheckedFwrite(fourcc, 4, f); +} +void Write8(unsigned int val, FILE *f) { + char vb[1] = {val}; + CheckedFwrite(vb, 1, f); +} +void Write32LE(unsigned int val, FILE *f) { + char vb[4] = {val, val >> 8, val >> 16, val >> 24}; + CheckedFwrite(vb, 4, f); +} +void Write16LE(unsigned int val, FILE *f) { + char vb[2] = {val, val >> 8}; + CheckedFwrite(vb, 2, f); +} +void WritePadZero(size_t len, FILE *f) { + static char paddingarea[64] = {0}; + + assert(f); + assert(len < sizeof(paddingarea)); + + CheckedFwrite(&paddingarea, len, f); +} +void WritePvrTexEncoder(const PvrTexEncoder *pte, FILE *f, ptewSmallVQType svq, int mip_skip) { + assert(pte); + assert(pte->pvr_tex); + assert(f); + + unsigned texsize = CalcTextureSize(pte->w, pte->h, pte->pixel_format, pteHasMips(pte), pteIsCompressed(pte), 0); + + if (pteIsCompressed(pte)) { + assert(pte->pvr_codebook); + + //Write CB + unsigned cbsize = pte->codebook_size * PVR_CODEBOOK_ENTRY_SIZE_BYTES; + if (svq == PTEW_NO_SMALL_VQ) + cbsize = PVR_CODEBOOK_SIZE_BYTES; + pteLog(LOG_DEBUG, "Writing %u bytes for codebook\n", (unsigned)cbsize); + CheckedFwrite(pte->pvr_codebook + pte->pvr_idx_offset * PVR_CODEBOOK_ENTRY_SIZE_BYTES, cbsize, f); + } + + if (!pteIsCompressed(pte) && pteHasMips(pte)) { + CheckedFwrite(pte->pvr_tex + mip_skip, texsize-mip_skip, f); + } else { + CheckedFwrite(pte->pvr_tex, texsize, f); + } +} + +int FileSize(const char *fname) { + assert(fname); + + FILE *f = fopen(fname, "r"); + if (f == NULL) + return -1; + fseek(f, 0, SEEK_END); + int size = ftell(f); + fclose(f); + return size; +} + diff --git a/dreamcast/pvrtex/file_common.h b/dreamcast/pvrtex/file_common.h new file mode 100644 index 00000000..684c978d --- /dev/null +++ b/dreamcast/pvrtex/file_common.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include "pvr_texture_encoder.h" + +typedef enum { + PTEW_NO_SMALL_VQ, + PTEW_FILE_PVR_SMALL_VQ, + PTEW_FILE_DCTEX_SMALL_VQ, +} ptewSmallVQType; + +void WriteFourCC(const char *fourcc, FILE *f); +void Write32LE(unsigned int val, FILE *f); +void Write16LE(unsigned int val, FILE *f); +void Write8(unsigned int val, FILE *f); +//If mip_pad_override is less than zero, add normal padding to uncompressed mipmapped textures +//If mip_pad_override is >= 0, use mip_pad_override as the number of padding bytes for uncompressed mipmapped textures +void WritePvrTexEncoder(const PvrTexEncoder *td, FILE *f, ptewSmallVQType svq, int mip_pad_override); +void WritePadZero(size_t len, FILE *f); + +//Returns size of file, returns -1 if file does not exist +int FileSize(const char *fname); diff --git a/dreamcast/pvrtex/file_dctex.c b/dreamcast/pvrtex/file_dctex.c new file mode 100644 index 00000000..bf589c52 --- /dev/null +++ b/dreamcast/pvrtex/file_dctex.c @@ -0,0 +1,88 @@ +#include +#include +#include "pvr_texture_encoder.h" +#include "file_common.h" +#include "file_dctex.h" + +static int convert_size(int size) { + if (size > 512) + return 7; + else if (size > 256) + return 6; + else if (size > 128) + return 5; + else if (size > 64) + return 4; + else if (size > 32) + return 3; + else if (size > 16) + return 2; + else if (size > 8) + return 1; + else + return 0; + +} + +void fDtWrite(const PvrTexEncoder *pte, const char *outfname) { + assert(pte); + + FILE *f = fopen(outfname, "w"); + assert(f); + + unsigned textype = 0; + textype |= pteHasMips(pte) << FDT_MIPMAP_SHIFT; + textype |= pteIsCompressed(pte) << FDT_VQ_SHIFT; + textype |= pte->pixel_format << FDT_PIXEL_FORMAT_SHIFT; + textype |= !pte->raw_is_twiddled << FDT_NOT_TWIDDLED_SHIFT; + + //If the width is a power of two, we don't need the stride bit set + textype |= (pteIsStrided(pte) && !IsPow2(pte->w)) << FDT_STRIDE_SHIFT; + + textype |= ((pte->w / 32) & FDT_STRIDE_VAL_MASK) << FDT_STRIDE_VAL_SHIFT; + textype |= !IsPow2(pte->h) << FDT_PARTIAL_SHIFT; + textype |= convert_size(pte->w) << FDT_WIDTH_SHIFT; + textype |= convert_size(pte->h) << FDT_HEIGHT_SHIFT; + + //Include size of header + unsigned origsize = 32 + CalcTextureSize(pte->w, pte->h, pte->pixel_format, pteHasMips(pte), pteIsCompressed(pte), pte->codebook_size * 8); + unsigned size = ROUND_UP_POW2(origsize, 32); + unsigned paddingamt = size - origsize; + pteLog(LOG_DEBUG, "File size: %u orig + %u pad = %u total\n", origsize, paddingamt, size); + + WriteFourCC("DcTx", f); + Write32LE(size, f); + Write8(0, f); //Version + Write8(0, f); //Header size is (32 bytes / 32 - 1) = 0 + Write8(pteIsCompressed(pte) ? pte->codebook_size - 1 : 0, f); + Write8(pteIsPalettized(pte) ? pte->palette_size - 1 : 0, f); + Write16LE(pte->w, f); + Write16LE(pte->h, f); + Write32LE(textype, f); + Write32LE(0, f); + Write32LE(0, f); + Write32LE(0, f); + + WritePvrTexEncoder(pte, f, PTEW_FILE_DCTEX_SMALL_VQ, 0); + + //Pad to 32 bytes + WritePadZero(paddingamt, f); + fclose(f); + + //Validate resulting file + unsigned resultsize = FileSize(outfname); + ErrorExitOn(resultsize != size, "Size of file written for \"%s\" was incorrect. Expected file to be %u bytes, but result was %u bytes.\n", outfname, size, resultsize); + + f = fopen(outfname, "r"); + void *readbuff = malloc(size); + if (fread(readbuff, size, 1, f) == 1) { + if (!fDtValidateHeader(readbuff)) { + pteLog(LOG_WARNING, "**Error validating output for .DT**\n"); + } + } else { + pteLog(LOG_WARNING, "**Error reading file during validation check for .DT output**\n"); + } + fclose(f); + free(readbuff); +} + diff --git a/dreamcast/pvrtex/file_dctex.h b/dreamcast/pvrtex/file_dctex.h new file mode 100644 index 00000000..c2497587 --- /dev/null +++ b/dreamcast/pvrtex/file_dctex.h @@ -0,0 +1,560 @@ +#pragma once + +#include +#include +#include + +//These match up with the PVR hardware texture format bits +typedef enum fdtPixelFormat { + FDT_FMT_ARGB1555, + FDT_FMT_RGB565, + FDT_FMT_ARGB4444, + FDT_FMT_YUV, + FDT_FMT_NORMAL, + FDT_FMT_PALETTE_4BPP, + FDT_FMT_PALETTE_8BPP, +} fdtPixelFormat; + +#define FDT_PVR_SIZE_MASK 0x0000003F +#define FDT_PVR_MODE_MASK 0xFC000000 +#define FDT_PVR_MODE_PAL_MASK 0xFFE00000 +#define FDT_CODEBOOK_MAX_SIZE_BYTES 2048 + +#define FDT_MIPMAP_SHIFT 31 +#define FDT_MIPMAP_MASK 1 +#define FDT_VQ_SHIFT 30 +#define FDT_VQ_MASK 1 +#define FDT_PIXEL_FORMAT_MASK 0x7 +#define FDT_PIXEL_FORMAT_SHIFT 27 +#define FDT_NOT_TWIDDLED_SHIFT 26 +#define FDT_NOT_TWIDDLED_MASK 1 +#define FDT_STRIDE_SHIFT 25 +#define FDT_STRIDE_MASK 1 +#define FDT_PIXEL_FORMAT_MASK 0x7 +#define FDT_PARTIAL_SHIFT 11 +#define FDT_PARTIAL_MASK 1 +#define FDT_STRIDE_VAL_SHIFT 6 +#define FDT_STRIDE_VAL_MASK 0x1F +#define FDT_WIDTH_SHIFT 3 +#define FDT_WIDTH_MASK 0x7 +#define FDT_HEIGHT_SHIFT 0 +#define FDT_HEIGHT_MASK 0x7 + +typedef struct { + /* + All data is little endian (same as the Dreamcast's SH-4) + + Header is 32 bytes large + */ + + /* + Four character code to identify file format. + Always equal to "DcTx" + */ + char fourcc[4]; + + /* + Size of file, including header. This is different to IFF, which does not include the size of the fourcc and size fields. + + Size is always rounded up to 32 bytes + */ + uint32_t chunk_size; + + /* + File format version of this texture. + + Currently, the only version is 0 + */ + uint8_t version; + + /* + Size of the header in 32-byte units, minus one. This is how far from the start of the header the texture data starts. + The header can be 32 bytes to 8KB large. + + A header_size of 0 means the texture data starts 32 bytes after the start of the header, a size of 3 means 128 bytes... + + This allows for backwards compatible changes to the size of the header, or adding additional user data. + */ + uint8_t header_size; + + /* + Values range from 0-255, mapping to 1-256. To get the real number of codebook entries or colors, + add one to this value. (e.g. if colors_used == 15, then there are 16 different colors, if, + colors_used == 2, then 2 colors are used) + + The value of codebook_size is undefined if not compressed, and should not be relied upon. + colors_used is similarly undefined if pixel format is not 8BPP or 4BPP. + + The functions fDtGetColorsUsed and fDtGetColorsUsed can be used to preform the checks and offseting. + They return 0 if not palletized or compressed + */ + uint8_t codebook_size, colors_used; + + /* + Height and width of the texture in pixels. + + This might not match up with the sizes in pvr_size. + + Stride textures will have the true width here, but the size in pvr_size + will be rounded up to the next power of two. + + It's possible to have non-twiddled textures with a height that is not a power of two, + the size in pvr_size will be rounded up to the next power of two. The PVR does not + have a texture format that supports this, but if you don't display any texels from + outside the real area, it will still work. + */ + uint16_t width_pixels, height_pixels; + + /* + The bottom 6 bits match up with the third long in the PVR's triangle/quad command, + and the top 10 bits match up with the fourth long. + + Bit 31: + Mipmapped (0 = no mipmaps, 1 = has mipmaps) + + Bit 30: + VQ compression (0 = not compressed, 1 = is compressed) + + Bits 29-27: + Pixel format + 0: ARGB1555 + 1: RGB565 + 2: ARGB4444 + 3: YUV422 + 4: Spherical normal + 5: 4-bit palette + 6: 8-bit palette + + For pixel formats other than palettized: + Bit 26: + Not twiddled (0 = twiddled, 1 = not twiddled) + + Bit 25: + Strided (0 = width is power of two, 1 = width is not power of two) + + For palettized pixel formats: + Bit 26-21: + Palette hint (This is currently always 0, but could be used in the future) + + Palettized textures are always assumed to be twiddled and never strided by the hardware + + Bit 11: + Partial texture (0 = not partial, 1 = partial) + + A partial texture is a texture that does not have all data to fill the PVRs view of the texture. + This saves video RAM, but can result in the PVR reading garbage if you try to display texels from outside + the defined range. The undefined data in a partial texture is always at the end of the texture, never the + middle or start. (Small codebook VQ textures would techincally qualify as a type of partical texture with + some of the data at the start of a texture missing, they are not considered to be partial for the purposes + of this bit.) + + For nontwiddled data, a partial texture results in the bottom rows of the texture being undefined. For + twiddled textures, this results in the topmost mip level having data in the right half being undefined. + + For example, a 640x480 texture can at best be seen by the PVR as a 640x512 texture. Trying to display + data from below the 480th row will result in the PVR reading undefined data. + + For a twiddled, mipmapped texture, a partial texture might be missing the bottom right corner or + or entire right half the highest mip level, or other shapes. This type is not currently supported by the + converter. + + Bits 10-6: + Stride value. This is the width of the texture divided by 32. + Only valid on texture will stride bit set. + Place this value in the bottom 5 bits of the PVR register PVR_TEXTURE_MODULO (at address 0xA05F80E4). + Only one stride value can be used per frame. + + Bits 5-3: + Texture width + Value Pixels + 0 8 + 1 16 + 2 32 + 3 64 + 4 128 + 5 256 + 6 512 + 7 1024 + + For stride textures, this will be set to the next size larger than the stride value. + For example, a 640 pixel wide texture will have a width of 7 (1024). + + Bits 2-0: + Texture height + Value Pixels + 0 8 + 1 16 + 2 32 + 3 64 + 4 128 + 5 256 + 6 512 + 7 1024 + + For textures with a height that is not a power of two, the value here will be rounded up. + For example, a 480 pixel high texture will have a height of 6 (512). + + */ + uint32_t pvr_type; + + /* + Pad header out to 32 bytes + + DMA requires 32 byte alignment. + + Padding might be used in future versions as an identifer or hash of the texture, + to help track identical textures, or store other user data like material properties. + */ + uint32_t pad1; + + uint32_t pad2; + + uint32_t pad3; + + //Texture data follows... +} fDtHeader; + +/* + Returns true if the fourcc matches +*/ +static inline bool fDtFourccMatches(const fDtHeader *tex) { + const int *fourcc = (const int *)&tex->fourcc; + return *fourcc == 0x78546344; //'DxTc' + + /* return tex->fourcc[0] == 'D' && + tex->fourcc[1] == 'c' && + tex->fourcc[2] == 'T' && + tex->fourcc[3] == 'x'; */ +} + +/* + Returns version of texture +*/ +static inline int fDtGetVersion(const fDtHeader *tex) { + return tex->version; +} + +/* + Returns size of file, including header and texture data. Will always be a multiple of 32 (assuming valid texture). +*/ +static inline size_t fDtGetTotalSize(const fDtHeader *tex) { + return tex->chunk_size; +} + +/* + Returns the size of the header in bytes +*/ +static inline size_t fDtGetHeaderSize(const fDtHeader *tex) { + return (tex->header_size+1) * 32; +} + +/* + Returns size of texture data. Does not include header. Will always be a multiple of 32 (assuming valid texture). +*/ +static inline size_t fDtGetTextureSize(const fDtHeader *tex) { + return fDtGetTotalSize(tex) - fDtGetHeaderSize(tex); +} + +/* + Returns pointer to end of texture (byte after final byte of texture) +*/ +static inline void * fDtGetNextChunk(const fDtHeader *tex) { + return (void*)tex + fDtGetTotalSize(tex); +} + + +/* + Returns true width of texture in pixels. + + The value that needs to be passed to the PVR might be different than this. Use + fDtGetPvrWidthBits to get the value that should be given to the PVR to correctly + use the texture. + +*/ +static inline unsigned fDtGetWidth(const fDtHeader *tex) { + return tex->width_pixels; +} +/* + Returns true height of texture in pixels. + + The value that needs to be passed to the PVR might be different than this. Use + fDtGetPvrHeightBits to get the value that should be given to the PVR to correctly + use the texture. +*/ +static inline unsigned fDtGetHeight(const fDtHeader *tex) { + return tex->height_pixels; +} + +/* + Returns the PVR pixel format of the texture. You can pass this directly to the PVR as the texture format. +*/ +static inline unsigned fDtGetPixelFormat(const fDtHeader *tex) { + return (tex->pvr_type >> FDT_PIXEL_FORMAT_SHIFT) & FDT_PIXEL_FORMAT_MASK; +} + +/* + Returns true if the texture is palettized (i.e. texture is 4BPP or 8BPP). +*/ +static inline int fDtIsPalettized(const fDtHeader *tex) { + unsigned fmt = fDtGetPixelFormat(tex); + return fmt == FDT_FMT_PALETTE_8BPP || fmt == FDT_FMT_PALETTE_4BPP; +} + +/* + Returns the stride value of the texture. + + Result is undefined if texture is not strided. +*/ +static inline unsigned int fDtGetStride(const fDtHeader *tex) { + return ((tex->pvr_type >> FDT_STRIDE_VAL_SHIFT) & FDT_STRIDE_VAL_MASK); +} + +/* + Returns true if the texture is a partial texture (see format description for expination of partial textures), + and returns false if the texture is complete. +*/ +static inline int fDtIsPartial(const fDtHeader *tex) { + return (tex->pvr_type & (1<pvr_type & (1<pvr_type & (1<<26)) == 0); +} + +/* + Returns true if the texture is compressed, or false if it is not. +*/ +static inline int fDtIsCompressed(const fDtHeader *tex) { + return (tex->pvr_type & (1<pvr_type) < 0; +} + +/* + Returns the width value to written to the TA command to use for the texture. +*/ +static inline unsigned fDtGetPvrWidthBits(const fDtHeader *tex) { + return (tex->pvr_type >> FDT_WIDTH_SHIFT) & FDT_WIDTH_MASK; +} +/* + Returns the height value to written to the TA command to use for the texture. +*/ +static inline unsigned fDtGetPvrHeightBits(const fDtHeader *tex) { + return (tex->pvr_type >> FDT_HEIGHT_SHIFT) & FDT_HEIGHT_MASK; +} + +/* + Returns how wide/high the PVR thinks the texture is in pixels. + + This is needed to figure out the correct U value for strided textures. + You can use fDtGetUWidth or fDtGetVHeight to find the correct value for + the edge of the texture. +*/ +static inline unsigned fDtGetPvrWidth(const fDtHeader *tex) { + return 1 << (fDtGetPvrWidthBits(tex) + 3); +} +static inline unsigned fDtGetPvrHeight(const fDtHeader *tex) { + return 1 << (fDtGetPvrHeightBits(tex) + 3); +} + +/* + Returns U value for right edge of texture. + + This can be used to map the entire valid area of the texture to a polygon, with + the top left UV coord being (0, 0), and the bottom right UV coord + being (fDtGetUWidth(tex), fDtGetVHeight(tex)) +*/ +static inline float fDtGetUWidth(const fDtHeader *tex) { + return (float)fDtGetWidth(tex) / fDtGetPvrWidth(tex); +} + +/* + Returns V value for bottom edge of texture + + See fDtGetUWidth description +*/ +static inline float fDtGetVHeight(const fDtHeader *tex) { + return (float)fDtGetHeight(tex) / fDtGetPvrHeight(tex); +} + +/* + Returns number of colors used by palettized texture. + + Returns 0 if texture does not use a palette. +*/ +static inline unsigned fDtGetColorsUsed(const fDtHeader *tex) { + return fDtIsPalettized(tex) ? tex->colors_used+1 : 0; +} + +/* + Returns size of codebook for compressed texture in bytes. + + Returns 0 if texture is not compressed. +*/ +static inline unsigned fDtGetCodebookSizeBytes(const fDtHeader *tex) { + return fDtIsCompressed(tex) ? (tex->codebook_size+1) * 8 : 0; +} + +/* + Returns pointer to pixel data +*/ +static inline void * fDtGetPvrTexData(const fDtHeader *tex) { + return (void*)tex + fDtGetHeaderSize(tex); +} + +/* + Preforms sanity checking on header to guess if it is valid. + + Checks fourcc, version, size, and texture format. + + Returns true if checks passes, or false if any failed. + + This is too large to be made static inline like the other functions in here, + but seperating out one function to a .c file would be a pain, so it's + just static. The compiler can get rid of it if it's not used. + The unused attribute is to disable any warnings if this is not used. +*/ +static bool __attribute__((unused)) fDtValidateHeader(const fDtHeader *tex) { + bool valid = true; + + //Check fourcc matches + valid &= fDtFourccMatches(tex); + + //Currently, only version is 0. There will probably not be more than 50 versions, + //so anything more than that is suspicious + valid &= fDtGetVersion(tex) < 50; + + //Size should be multiple of 32 + valid &= (fDtGetTotalSize(tex) % 32) == 0; + + //Check texture dimensions + valid &= fDtGetWidth(tex) >= 8; + valid &= fDtGetWidth(tex) <= 1024; + valid &= fDtGetPvrWidth(tex) >= fDtGetWidth(tex); + valid &= fDtGetHeight(tex) > 0; + valid &= fDtGetHeight(tex) <= 1024; + valid &= fDtGetPvrHeight(tex) >= fDtGetHeight(tex); + + //Check size is expected value + unsigned size = fDtGetWidth(tex)*fDtGetHeight(tex)*2; + + //Calculate size of texture + if (fDtIsMipmapped(tex)) + size = size * 4/3 + 6; + if (fDtGetPixelFormat(tex) == FDT_FMT_PALETTE_8BPP) + size /= 2; + else if (fDtGetPixelFormat(tex) == FDT_FMT_PALETTE_4BPP) + size /= 4; + if (fDtIsCompressed(tex)) + size = (size+7)/8 + fDtGetCodebookSizeBytes(tex); + //Round up to 32 bytes + size = (size + 31) & ~0x1f; + //Add header size + size += fDtGetHeaderSize(tex); + valid &= fDtGetTotalSize(tex) == size; + + //Check valid pixel format + valid &= fDtGetPixelFormat(tex) <= FDT_FMT_PALETTE_8BPP; + + //If strided texture has height is not equal to PVR height, must be partial + if (fDtIsStrided(tex) && (fDtGetHeight(tex) != fDtGetPvrHeight(tex))) + valid &= fDtIsPartial(tex); + + return valid; +} + +#ifdef _arch_dreamcast +/* + Small codebook compressed textures require an adjustment to the texture pointer for the PVR to + render it correctly. This function will preform the adjustment. + + For compressed textures, returns adjusted pointer to video RAM + For uncompressed textures, returns pointer unchanged. + + This does not modify the texture or it's allocation in any way, so when freeing the texture, be + sure to use the original, unadjusted pvr_ptr_t pointer. +*/ +static inline pvr_ptr_t fDtAdjustPVRPointer(const fDtHeader * texheader, pvr_ptr_t pvr) { + if (fDtIsCompressed(texheader)) { + return pvr - FDT_CODEBOOK_MAX_SIZE_BYTES + fDtGetCodebookSizeBytes(texheader); + } else { + return pvr; + } +} + +/* + Set PVR hardware register for stride to value required for this texture + + You want to avoid calling this while rendering, but KOS doesn't really provide a way to do that... + A work around would be to only set the stride value on a frame wher no strided texture is being rendered. +*/ +static inline void fDtSetPvrStride(const fDtHeader *tex) { + if (fDtIsStrided(tex)) + PVR_SET(PVR_TEXTURE_MODULO, fDtGetStride(tex)); +} + +/* + These set the texture for a KOS compiled polygon header struct. + + The format of the texture is taken from tex, and the actual address of the texture is + set by video_ram_addr. +*/ +static inline void fDtSetTAParameters(pvr_poly_hdr_t *dst, const fDtHeader *tex, pvr_ptr_t video_ram_addr) { + dst->mode2 = (dst->mode2 & ~FDT_PVR_SIZE_MASK) | (tex->pvr_type & FDT_PVR_SIZE_MASK); + dst->mode3 = (tex->pvr_type & FDT_PVR_MODE_PAL_MASK); + dst->mode3 |= 0x1ffffff & ((unsigned)fDtAdjustPVRPointer(tex, video_ram_addr) >> 3); +} +static inline void fDtSetTAParametersIC(pvr_poly_ic_hdr_t *dst, const fDtHeader *tex, pvr_ptr_t video_ram_addr) { + dst->mode2 = (dst->mode2 & ~FDT_PVR_SIZE_MASK) | (tex->pvr_type & FDT_PVR_SIZE_MASK); + dst->mode3 = (tex->pvr_type & FDT_PVR_MODE_PAL_MASK); + dst->mode3 |= 0x1ffffff & ((unsigned)fDtAdjustPVRPointer(tex, video_ram_addr) >> 3); +} +static inline void fDtSetTAParametersSprite(pvr_sprite_hdr_t *dst, const fDtHeader *tex, pvr_ptr_t video_ram_addr) { + dst->mode2 = (dst->mode2 & ~FDT_PVR_SIZE_MASK) | (tex->pvr_type & FDT_PVR_SIZE_MASK); + dst->mode3 = (tex->pvr_type & FDT_PVR_MODE_PAL_MASK); + dst->mode3 |= 0x1ffffff & ((unsigned)fDtAdjustPVRPointer(tex, video_ram_addr) >> 3); +} +static inline void fDtSetTAParametersMod(pvr_poly_mod_hdr_t *dst, const fDtHeader *tex, pvr_ptr_t video_ram_addr, int param) { + if (param == 0) { + dst->mode2_0 = (dst->mode2_0 & ~FDT_PVR_SIZE_MASK) | (tex->pvr_type & FDT_PVR_SIZE_MASK); + dst->mode3_0 = (tex->pvr_type & FDT_PVR_MODE_PAL_MASK); + dst->mode3_0 |= 0x1ffffff & ((unsigned)fDtAdjustPVRPointer(tex, video_ram_addr) >> 3); + } else { + dst->mode2_1 = (dst->mode2_1 & ~FDT_PVR_SIZE_MASK) | (tex->pvr_type & FDT_PVR_SIZE_MASK); + dst->mode3_1 = (tex->pvr_type & FDT_PVR_MODE_PAL_MASK); + dst->mode3_1 |= 0x1ffffff & ((unsigned)fDtAdjustPVRPointer(tex, video_ram_addr)); + } +} + +#ifdef PVR_CXT_GUARD +/* + This is for my pvr_context library. pvr_cxt.h must be included beforehand. +*/ +static inline void fDtSetPvrContextMod(pvr_context_submodes *dst, const fDtHeader *tex, pvr_ptr_t video_ram_addr) { + dst->mode2 = (dst->mode2 & ~FDT_PVR_SIZE_MASK) | (tex->pvr_type & FDT_PVR_SIZE_MASK); + dst->tex = (tex->pvr_type & FDT_PVR_MODE_PAL_MASK); + pc_set_texture_address_mod(dst, fDtAdjustPVRPointer(tex, video_ram_addr)); +} +static inline void fDtSetPvrContext(pvr_context *dst, const fDtHeader *tex, pvr_ptr_t video_ram_addr) { + pc_set_textured(dst, 1); + fDtSetPvrContextMod(pc_no_mod(dst), tex, video_ram_addr); +} +#endif + +#endif diff --git a/dreamcast/pvrtex/file_pvr.c b/dreamcast/pvrtex/file_pvr.c new file mode 100644 index 00000000..bc0ac548 --- /dev/null +++ b/dreamcast/pvrtex/file_pvr.c @@ -0,0 +1,101 @@ +#include +#include +#include "pvr_texture_encoder.h" +#include "file_common.h" +#include "file_pvr.h" + +int fPvrSmallVQCodebookSize(int texsize_pixels, int mip) { + // 16x16 = 256 bytes Small VQ + if (texsize_pixels <= 32) + return 24; + // 16x32 or 32x16 = 512 bytes Small VQ + if(texsize_pixels == 48) + return 48; + // 32x32 = 512 bytes Small VQ + if(texsize_pixels == 64) + return 32; + // 64x32 or 32x64 = 1024 bytes Small VQ + if(texsize_pixels == 96) + return 64; + // 64xx64 = 1536 bytes Small VQ + if(texsize_pixels == 128) + return 64; + // 128x64 or 64x128 = 3584 bytes Small VQ + if(texsize_pixels == 192) + return 192; + + return 256; +} + +void fPvrWrite(const PvrTexEncoder *pte, const char *outfname) { + assert(pte); + assert(pte->pvr_tex); + assert(outfname); + + FILE *f = fopen(outfname, "wb"); + assert(f); + + //Write header + unsigned chunksize = 16; + + unsigned pvrfmt = FILE_PVR_SQUARE; + if (pteIsCompressed(pte)) { + pvrfmt = FILE_PVR_VQ; + unsigned cb_size = 2048; + unsigned int idxcnt = pte->w * pte->h / 4; + if (pteHasMips(pte)) + idxcnt = idxcnt * 4/3 + 1; + + if (pte->auto_small_vq) { + //We only generate real small VQ textures when small_vq is set + pvrfmt = FILE_PVR_SMALL_VQ; + cb_size = pte->codebook_size * 8; + } + + if (pteIsPalettized(pte)) + ErrorExit(".PVR format does not support compressed palettized textures\n"); + // JP - Rectangle VQ certainly does work on real hardware + //if (pte->w != pte->h) + // ErrorExit(".PVR format does not support non-square compressed textures\n"); + + chunksize += idxcnt+cb_size; + } else { + chunksize += CalcTextureSize(pte->w, pte->h, pte->pixel_format, pteHasMips(pte), 0, 0); + + if (pte->pixel_format == PTE_PALETTE_8B) { + pvrfmt = FILE_PVR_8BPP; + } else if (pte->pixel_format == PTE_PALETTE_4B) { + pvrfmt = FILE_PVR_4BPP; + } + + //.PVR does not store first 4 padding bytes of uncompressed mipmapped texture + if (pteHasMips(pte)) + chunksize -= 4; + + if (pte->w != pte->h) { + pvrfmt = FILE_PVR_RECT; + assert(!pteHasMips(pte)); + } + } + + if (pteHasMips(pte)) + pvrfmt += FILE_PVR_MIP_ADD; + + if(pte->codebook_size != 256) { + // Write codebook size to GBIX (hack) + WriteFourCC("GBIX", f); + Write32LE(0, f); + Write32LE(pte->codebook_size, f); + } + + WriteFourCC("PVRT", f); + Write32LE(chunksize, f); //chunk size + Write32LE(pvrfmt | pte->pixel_format, f); //pixel format, type + Write16LE(pte->w, f); + Write16LE(pte->h, f); + + WritePvrTexEncoder(pte, f, pte->auto_small_vq ? PTEW_FILE_PVR_SMALL_VQ : PTEW_NO_SMALL_VQ, 4); + + fclose(f); + assert(chunksize + (pte->codebook_size != 256 ? 12 : 0) == FileSize(outfname)); +} diff --git a/dreamcast/pvrtex/file_pvr.h b/dreamcast/pvrtex/file_pvr.h new file mode 100644 index 00000000..80a19b36 --- /dev/null +++ b/dreamcast/pvrtex/file_pvr.h @@ -0,0 +1,21 @@ +#include +#include +#include "pvr_texture_encoder.h" + +#define FILE_PVR_SQUARE (1<<8) +#define FILE_PVR_SQUARE_MIP (2<<8) +#define FILE_PVR_VQ (3<<8) +#define FILE_PVR_VQ_MIP (4<<8) +#define FILE_PVR_4BPP (5<<8) //Assumed +#define FILE_PVR_4BPP_MIP (6<<8) //Assumed +#define FILE_PVR_8BPP (7<<8) +#define FILE_PVR_8BPP_MIP (8<<8) //Assumed +#define FILE_PVR_RECT (9<<8) +#define FILE_PVR_RECT_MIP (10<<8) //Not supported by hardware, but implied +#define FILE_PVR_SMALL_VQ (16<<8) +#define FILE_PVR_SMALL_VQ_MIP (17<<8) + +#define FILE_PVR_MIP_ADD (1<<8) + +void fPvrWrite(const PvrTexEncoder *td, const char *outfname); +int fPvrSmallVQCodebookSize(int texsize_pixels, int mip); diff --git a/dreamcast/pvrtex/file_tex.c b/dreamcast/pvrtex/file_tex.c new file mode 100644 index 00000000..dbd1e8dc --- /dev/null +++ b/dreamcast/pvrtex/file_tex.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include "pvr_texture_encoder.h" +#include "file_common.h" +#include "file_tex.h" + +void fTexWrite(const PvrTexEncoder *pte, const char *outfname) { + assert(pte); + + FILE *f = fopen(outfname, "w"); + assert(f); + + unsigned textype = 0; + textype |= pteHasMips(pte) ? (1<<31) : 0; + textype |= pteIsCompressed(pte) ? (1<<30) : 0; + textype |= pte->pixel_format << 27; + textype |= !pte->raw_is_twiddled << 26; + textype |= pteIsStrided(pte) ? (1<<25) : 0; + textype |= (pte->w / 32) & 0x1f; + + //Size does not include size of header + unsigned origsize = CalcTextureSize(pte->w, pte->h, pte->pixel_format, pteHasMips(pte), pteIsCompressed(pte), PVR_CODEBOOK_SIZE_BYTES); + unsigned size = ROUND_UP_POW2(origsize, 32); + unsigned paddingamt = size - origsize; + pteLog(LOG_DEBUG, "File DTEX size: (%u + %u) %u\n", origsize, paddingamt, size); + + WriteFourCC("DTEX", f); + Write16LE(RoundUpPow2(pte->w), f); + Write16LE(pte->h, f); + Write32LE(textype, f); + Write32LE(size, f); + + WritePvrTexEncoder(pte, f, PTEW_NO_SMALL_VQ, 0); + + //Pad to 32 bytes + static const unsigned char padding[32] = {0}; + pteLog(LOG_DEBUG, "Padding %u\n", paddingamt); + int ret = fwrite(padding, 1, paddingamt, f); + assert(ret == paddingamt); + + fclose(f); +} + +void fTexWritePaletteAppendPal(const PvrTexEncoder *pte, const char *outfname) { + char palette_name[1024]; + size_t namelen = strlen(outfname); + assert(namelen < (sizeof(palette_name) - 5)); + memcpy(palette_name, outfname, namelen); + memcpy(palette_name + namelen, ".pal", 4); + palette_name[namelen + 4] = '\0'; + + fTexWritePalette(pte, palette_name); +} + +void fTexWritePalette(const PvrTexEncoder *pte, const char *outfname) { + assert(pte); + assert(pte->palette); + assert(pte->palette_size > 0); + assert(pte->palette_size <= 256); + + pteLog(LOG_COMPLETION, "Writing .PAL to \"%s\"...\n", outfname); + + FILE *f = fopen(outfname, "w"); + assert(f); + + WriteFourCC("DPAL", f); + Write32LE(pte->palette_size, f); + + for(unsigned i = 0; i < pte->palette_size; i++) { + Write32LE(pxlConvertABGR8888toARGB8888(pte->palette[i]).argb, f); + } + + fclose(f); + +} diff --git a/dreamcast/pvrtex/file_tex.h b/dreamcast/pvrtex/file_tex.h new file mode 100644 index 00000000..ddd320c4 --- /dev/null +++ b/dreamcast/pvrtex/file_tex.h @@ -0,0 +1,8 @@ +#include +#include +#include "pvr_texture_encoder.h" +#include "file_common.h" + +void fTexWrite(const PvrTexEncoder *td, const char *outfname); +void fTexWritePalette(const PvrTexEncoder *td, const char *outfname); +void fTexWritePaletteAppendPal(const PvrTexEncoder *td, const char *outfname); diff --git a/dreamcast/pvrtex/lfg.c b/dreamcast/pvrtex/lfg.c new file mode 100644 index 00000000..46b04d24 --- /dev/null +++ b/dreamcast/pvrtex/lfg.c @@ -0,0 +1,87 @@ +/* + * Lagged Fibonacci PRNG + * Copyright (c) 2008 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include "lfg.h" +#include "crc.h" +#include "md5.h" +#include "error.h" +#include "intreadwrite.h" +#include "attributes.h" + +av_cold void av_lfg_init(AVLFG *c, unsigned int seed) +{ + uint8_t tmp[16] = { 0 }; + int i; + + for (i = 8; i < 64; i += 4) { + AV_WL32(tmp, seed); + tmp[4] = i; + av_md5_sum(tmp, tmp, 16); + c->state[i ] = AV_RL32(tmp); + c->state[i + 1] = AV_RL32(tmp + 4); + c->state[i + 2] = AV_RL32(tmp + 8); + c->state[i + 3] = AV_RL32(tmp + 12); + } + c->index = 0; +} + +void av_bmg_get(AVLFG *lfg, double out[2]) +{ + double x1, x2, w; + + do { + x1 = 2.0 / UINT_MAX * av_lfg_get(lfg) - 1.0; + x2 = 2.0 / UINT_MAX * av_lfg_get(lfg) - 1.0; + w = x1 * x1 + x2 * x2; + } while (w >= 1.0); + + w = sqrt((-2.0 * log(w)) / w); + out[0] = x1 * w; + out[1] = x2 * w; +} + +int av_lfg_init_from_data(AVLFG *c, const uint8_t *data, unsigned int length) { + unsigned int beg, end, segm; + const AVCRC *avcrc; + uint32_t crc = 1; + + /* avoid integer overflow in the loop below. */ + if (length > (UINT_MAX / 128U)) return AVERROR(EINVAL); + + c->index = 0; + avcrc = av_crc_get_table(AV_CRC_32_IEEE); /* This can't fail. It's a well-defined table in crc.c */ + + /* across 64 segments of the incoming data, + * do a running crc of each segment and store the crc as the state for that slot. + * this works even if the length of the segment is 0 bytes. */ + beg = 0; + for (segm = 0;segm < 64;segm++) { + end = (((segm + 1) * length) / 64); + crc = av_crc(avcrc, crc, data + beg, end - beg); + c->state[segm] = (unsigned int)crc; + beg = end; + } + + return 0; +} diff --git a/dreamcast/pvrtex/libavcodec/elbg.h b/dreamcast/pvrtex/libavcodec/elbg.h new file mode 100644 index 00000000..bb218048 --- /dev/null +++ b/dreamcast/pvrtex/libavcodec/elbg.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 Vitor Sessak + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_ELBG_H +#define AVCODEC_ELBG_H + +#include +#include "libavutil/lfg.h" + +#define av_restrict restrict + +struct ELBGContext; + +/** + * Implementation of the Enhanced LBG Algorithm + * Based on the paper "Neural Networks 14:1219-1237" that can be found in + * http://citeseer.ist.psu.edu/patan01enhanced.html . + * + * @param ctx A pointer to a pointer to an already allocated ELBGContext + * or a pointer to NULL. In the latter case, this function + * will allocate an ELBGContext and put a pointer to it in `*ctx`. + * @param points Input points. + * @param dim Dimension of the points. + * @param numpoints Num of points in **points. + * @param codebook Pointer to the output codebook. Must be allocated. + * @param num_cb Number of points in the codebook. + * @param num_steps The maximum number of steps. One step is already a good compromise between time and quality. + * @param closest_cb Return the closest codebook to each point. Must be allocated. + * @param rand_state A random number generator state. Should be already initialized by av_lfg_init(). + * @param flags Currently unused; must be set to 0. + * @return < 0 in case of error, 0 otherwise + */ +int avpriv_elbg_do(struct ELBGContext **ctx, int *points, int dim, + int numpoints, int *codebook, int num_cb, int num_steps, + int *closest_cb, AVLFG *rand_state, uintptr_t flags); + +/** + * Free an ELBGContext and reset the pointer to it. + */ +void avpriv_elbg_free(struct ELBGContext **ctx); + +#endif /* AVCODEC_ELBG_H */ diff --git a/dreamcast/pvrtex/libavutil/attributes.h b/dreamcast/pvrtex/libavutil/attributes.h new file mode 100644 index 00000000..04c615c9 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/attributes.h @@ -0,0 +1,173 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Macro definitions for various function/variable attributes + */ + +#ifndef AVUTIL_ATTRIBUTES_H +#define AVUTIL_ATTRIBUTES_H + +#ifdef __GNUC__ +# define AV_GCC_VERSION_AT_LEAST(x,y) (__GNUC__ > (x) || __GNUC__ == (x) && __GNUC_MINOR__ >= (y)) +# define AV_GCC_VERSION_AT_MOST(x,y) (__GNUC__ < (x) || __GNUC__ == (x) && __GNUC_MINOR__ <= (y)) +#else +# define AV_GCC_VERSION_AT_LEAST(x,y) 0 +# define AV_GCC_VERSION_AT_MOST(x,y) 0 +#endif + +#ifdef __has_builtin +# define AV_HAS_BUILTIN(x) __has_builtin(x) +#else +# define AV_HAS_BUILTIN(x) 0 +#endif + +#ifndef av_always_inline +#if AV_GCC_VERSION_AT_LEAST(3,1) +# define av_always_inline __attribute__((always_inline)) inline +#elif defined(_MSC_VER) +# define av_always_inline __forceinline +#else +# define av_always_inline inline +#endif +#endif + +#ifndef av_extern_inline +#if defined(__ICL) && __ICL >= 1210 || defined(__GNUC_STDC_INLINE__) +# define av_extern_inline extern inline +#else +# define av_extern_inline inline +#endif +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,4) +# define av_warn_unused_result __attribute__((warn_unused_result)) +#else +# define av_warn_unused_result +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,1) +# define av_noinline __attribute__((noinline)) +#elif defined(_MSC_VER) +# define av_noinline __declspec(noinline) +#else +# define av_noinline +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,1) || defined(__clang__) +# define av_pure __attribute__((pure)) +#else +# define av_pure +#endif + +#if AV_GCC_VERSION_AT_LEAST(2,6) || defined(__clang__) +# define av_const __attribute__((const)) +#else +# define av_const +#endif + +#if AV_GCC_VERSION_AT_LEAST(4,3) || defined(__clang__) +# define av_cold __attribute__((cold)) +#else +# define av_cold +#endif + +#if AV_GCC_VERSION_AT_LEAST(4,1) && !defined(__llvm__) +# define av_flatten __attribute__((flatten)) +#else +# define av_flatten +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,1) +# define attribute_deprecated __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define attribute_deprecated __declspec(deprecated) +#else +# define attribute_deprecated +#endif + +/** + * Disable warnings about deprecated features + * This is useful for sections of code kept for backward compatibility and + * scheduled for removal. + */ +#ifndef AV_NOWARN_DEPRECATED +#if AV_GCC_VERSION_AT_LEAST(4,6) || defined(__clang__) +# define AV_NOWARN_DEPRECATED(code) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \ + code \ + _Pragma("GCC diagnostic pop") +#elif defined(_MSC_VER) +# define AV_NOWARN_DEPRECATED(code) \ + __pragma(warning(push)) \ + __pragma(warning(disable : 4996)) \ + code; \ + __pragma(warning(pop)) +#else +# define AV_NOWARN_DEPRECATED(code) code +#endif +#endif + +#if defined(__GNUC__) || defined(__clang__) +# define av_unused __attribute__((unused)) +#else +# define av_unused +#endif + +/** + * Mark a variable as used and prevent the compiler from optimizing it + * away. This is useful for variables accessed only from inline + * assembler without the compiler being aware. + */ +#if AV_GCC_VERSION_AT_LEAST(3,1) || defined(__clang__) +# define av_used __attribute__((used)) +#else +# define av_used +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,3) || defined(__clang__) +# define av_alias __attribute__((may_alias)) +#else +# define av_alias +#endif + +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__INTEL_COMPILER) +# define av_uninit(x) x=x +#else +# define av_uninit(x) x +#endif + +#if defined(__GNUC__) || defined(__clang__) +# define av_builtin_constant_p __builtin_constant_p +# define av_printf_format(fmtpos, attrpos) __attribute__((__format__(__printf__, fmtpos, attrpos))) +#else +# define av_builtin_constant_p(x) 0 +# define av_printf_format(fmtpos, attrpos) +#endif + +#if AV_GCC_VERSION_AT_LEAST(2,5) || defined(__clang__) +# define av_noreturn __attribute__((noreturn)) +#else +# define av_noreturn +#endif + +#endif /* AVUTIL_ATTRIBUTES_H */ diff --git a/dreamcast/pvrtex/libavutil/attributes_internal.h b/dreamcast/pvrtex/libavutil/attributes_internal.h new file mode 100644 index 00000000..3df1ee6a --- /dev/null +++ b/dreamcast/pvrtex/libavutil/attributes_internal.h @@ -0,0 +1,34 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_ATTRIBUTES_INTERNAL_H +#define AVUTIL_ATTRIBUTES_INTERNAL_H + +#include "attributes.h" + +#if (AV_GCC_VERSION_AT_LEAST(4,0) || defined(__clang__)) && (defined(__ELF__) || defined(__MACH__)) +# define attribute_visibility_hidden __attribute__((visibility("hidden"))) +# define FF_VISIBILITY_PUSH_HIDDEN _Pragma("GCC visibility push(hidden)") +# define FF_VISIBILITY_POP_HIDDEN _Pragma("GCC visibility pop") +#else +# define attribute_visibility_hidden +# define FF_VISIBILITY_PUSH_HIDDEN +# define FF_VISIBILITY_POP_HIDDEN +#endif + +#endif /* AVUTIL_ATTRIBUTES_INTERNAL_H */ diff --git a/dreamcast/pvrtex/libavutil/avassert.h b/dreamcast/pvrtex/libavutil/avassert.h new file mode 100644 index 00000000..51e462bb --- /dev/null +++ b/dreamcast/pvrtex/libavutil/avassert.h @@ -0,0 +1,75 @@ +/* + * copyright (c) 2010 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * simple assert() macros that are a bit more flexible than ISO C assert(). + * @author Michael Niedermayer + */ + +#ifndef AVUTIL_AVASSERT_H +#define AVUTIL_AVASSERT_H + +#include +#include "log.h" +#include "macros.h" + +/** + * assert() equivalent, that is always enabled. + */ +#define av_assert0(cond) do { \ + if (!(cond)) { \ + av_log(NULL, AV_LOG_PANIC, "Assertion %s failed at %s:%d\n", \ + AV_STRINGIFY(cond), __FILE__, __LINE__); \ + abort(); \ + } \ +} while (0) + + +/** + * assert() equivalent, that does not lie in speed critical code. + * These asserts() thus can be enabled without fearing speed loss. + */ +#if defined(ASSERT_LEVEL) && ASSERT_LEVEL > 0 +#define av_assert1(cond) av_assert0(cond) +#else +#define av_assert1(cond) ((void)0) +#endif + + +/** + * assert() equivalent, that does lie in speed critical code. + */ +#if defined(ASSERT_LEVEL) && ASSERT_LEVEL > 1 +#define av_assert2(cond) av_assert0(cond) +#define av_assert2_fpu() av_assert0_fpu() +#else +#define av_assert2(cond) ((void)0) +#define av_assert2_fpu() ((void)0) +#endif + +/** + * Assert that floating point operations can be executed. + * + * This will av_assert0() that the cpu is not in MMX state on X86 + */ +void av_assert0_fpu(void); + +#endif /* AVUTIL_AVASSERT_H */ diff --git a/dreamcast/pvrtex/libavutil/avconfig.h b/dreamcast/pvrtex/libavutil/avconfig.h new file mode 100644 index 00000000..c289fbb5 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/avconfig.h @@ -0,0 +1,6 @@ +/* Generated by ffmpeg configure */ +#ifndef AVUTIL_AVCONFIG_H +#define AVUTIL_AVCONFIG_H +#define AV_HAVE_BIGENDIAN 0 +#define AV_HAVE_FAST_UNALIGNED 1 +#endif /* AVUTIL_AVCONFIG_H */ diff --git a/dreamcast/pvrtex/libavutil/avstring.h b/dreamcast/pvrtex/libavutil/avstring.h new file mode 100644 index 00000000..e2602637 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/avstring.h @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2007 Mans Rullgard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_AVSTRING_H +#define AVUTIL_AVSTRING_H + +#include +#include +#include "attributes.h" +#include "version.h" + +/** + * @addtogroup lavu_string + * @{ + */ + +/** + * Return non-zero if pfx is a prefix of str. If it is, *ptr is set to + * the address of the first character in str after the prefix. + * + * @param str input string + * @param pfx prefix to test + * @param ptr updated if the prefix is matched inside str + * @return non-zero if the prefix matches, zero otherwise + */ +int av_strstart(const char *str, const char *pfx, const char **ptr); + +/** + * Return non-zero if pfx is a prefix of str independent of case. If + * it is, *ptr is set to the address of the first character in str + * after the prefix. + * + * @param str input string + * @param pfx prefix to test + * @param ptr updated if the prefix is matched inside str + * @return non-zero if the prefix matches, zero otherwise + */ +int av_stristart(const char *str, const char *pfx, const char **ptr); + +/** + * Locate the first case-independent occurrence in the string haystack + * of the string needle. A zero-length string needle is considered to + * match at the start of haystack. + * + * This function is a case-insensitive version of the standard strstr(). + * + * @param haystack string to search in + * @param needle string to search for + * @return pointer to the located match within haystack + * or a null pointer if no match + */ +char *av_stristr(const char *haystack, const char *needle); + +/** + * Locate the first occurrence of the string needle in the string haystack + * where not more than hay_length characters are searched. A zero-length + * string needle is considered to match at the start of haystack. + * + * This function is a length-limited version of the standard strstr(). + * + * @param haystack string to search in + * @param needle string to search for + * @param hay_length length of string to search in + * @return pointer to the located match within haystack + * or a null pointer if no match + */ +char *av_strnstr(const char *haystack, const char *needle, size_t hay_length); + +/** + * Copy the string src to dst, but no more than size - 1 bytes, and + * null-terminate dst. + * + * This function is the same as BSD strlcpy(). + * + * @param dst destination buffer + * @param src source string + * @param size size of destination buffer + * @return the length of src + * + * @warning since the return value is the length of src, src absolutely + * _must_ be a properly 0-terminated string, otherwise this will read beyond + * the end of the buffer and possibly crash. + */ +size_t av_strlcpy(char *dst, const char *src, size_t size); + +/** + * Append the string src to the string dst, but to a total length of + * no more than size - 1 bytes, and null-terminate dst. + * + * This function is similar to BSD strlcat(), but differs when + * size <= strlen(dst). + * + * @param dst destination buffer + * @param src source string + * @param size size of destination buffer + * @return the total length of src and dst + * + * @warning since the return value use the length of src and dst, these + * absolutely _must_ be a properly 0-terminated strings, otherwise this + * will read beyond the end of the buffer and possibly crash. + */ +size_t av_strlcat(char *dst, const char *src, size_t size); + +/** + * Append output to a string, according to a format. Never write out of + * the destination buffer, and always put a terminating 0 within + * the buffer. + * @param dst destination buffer (string to which the output is + * appended) + * @param size total size of the destination buffer + * @param fmt printf-compatible format string, specifying how the + * following parameters are used + * @return the length of the string that would have been generated + * if enough space had been available + */ +size_t av_strlcatf(char *dst, size_t size, const char *fmt, ...) av_printf_format(3, 4); + +/** + * Get the count of continuous non zero chars starting from the beginning. + * + * @param s the string whose length to count + * @param len maximum number of characters to check in the string, that + * is the maximum value which is returned by the function + */ +static inline size_t av_strnlen(const char *s, size_t len) +{ + size_t i; + for (i = 0; i < len && s[i]; i++) + ; + return i; +} + +/** + * Print arguments following specified format into a large enough auto + * allocated buffer. It is similar to GNU asprintf(). + * @param fmt printf-compatible format string, specifying how the + * following parameters are used. + * @return the allocated string + * @note You have to free the string yourself with av_free(). + */ +char *av_asprintf(const char *fmt, ...) av_printf_format(1, 2); + +/** + * Unescape the given string until a non escaped terminating char, + * and return the token corresponding to the unescaped string. + * + * The normal \ and ' escaping is supported. Leading and trailing + * whitespaces are removed, unless they are escaped with '\' or are + * enclosed between ''. + * + * @param buf the buffer to parse, buf will be updated to point to the + * terminating char + * @param term a 0-terminated list of terminating chars + * @return the malloced unescaped string, which must be av_freed by + * the user, NULL in case of allocation failure + */ +char *av_get_token(const char **buf, const char *term); + +/** + * Split the string into several tokens which can be accessed by + * successive calls to av_strtok(). + * + * A token is defined as a sequence of characters not belonging to the + * set specified in delim. + * + * On the first call to av_strtok(), s should point to the string to + * parse, and the value of saveptr is ignored. In subsequent calls, s + * should be NULL, and saveptr should be unchanged since the previous + * call. + * + * This function is similar to strtok_r() defined in POSIX.1. + * + * @param s the string to parse, may be NULL + * @param delim 0-terminated list of token delimiters, must be non-NULL + * @param saveptr user-provided pointer which points to stored + * information necessary for av_strtok() to continue scanning the same + * string. saveptr is updated to point to the next character after the + * first delimiter found, or to NULL if the string was terminated + * @return the found token, or NULL when no token is found + */ +char *av_strtok(char *s, const char *delim, char **saveptr); + +/** + * Locale-independent conversion of ASCII isdigit. + */ +static inline av_const int av_isdigit(int c) +{ + return c >= '0' && c <= '9'; +} + +/** + * Locale-independent conversion of ASCII isgraph. + */ +static inline av_const int av_isgraph(int c) +{ + return c > 32 && c < 127; +} + +/** + * Locale-independent conversion of ASCII isspace. + */ +static inline av_const int av_isspace(int c) +{ + return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || + c == '\v'; +} + +/** + * Locale-independent conversion of ASCII characters to uppercase. + */ +static inline av_const int av_toupper(int c) +{ + if (c >= 'a' && c <= 'z') + c ^= 0x20; + return c; +} + +/** + * Locale-independent conversion of ASCII characters to lowercase. + */ +static inline av_const int av_tolower(int c) +{ + if (c >= 'A' && c <= 'Z') + c ^= 0x20; + return c; +} + +/** + * Locale-independent conversion of ASCII isxdigit. + */ +static inline av_const int av_isxdigit(int c) +{ + c = av_tolower(c); + return av_isdigit(c) || (c >= 'a' && c <= 'f'); +} + +/** + * Locale-independent case-insensitive compare. + * @note This means only ASCII-range characters are case-insensitive + */ +int av_strcasecmp(const char *a, const char *b); + +/** + * Locale-independent case-insensitive compare. + * @note This means only ASCII-range characters are case-insensitive + */ +int av_strncasecmp(const char *a, const char *b, size_t n); + +/** + * Locale-independent strings replace. + * @note This means only ASCII-range characters are replace + */ +char *av_strireplace(const char *str, const char *from, const char *to); + +/** + * Thread safe basename. + * @param path the string to parse, on DOS both \ and / are considered separators. + * @return pointer to the basename substring. + * If path does not contain a slash, the function returns a copy of path. + * If path is a NULL pointer or points to an empty string, a pointer + * to a string "." is returned. + */ +const char *av_basename(const char *path); + +/** + * Thread safe dirname. + * @param path the string to parse, on DOS both \ and / are considered separators. + * @return A pointer to a string that's the parent directory of path. + * If path is a NULL pointer or points to an empty string, a pointer + * to a string "." is returned. + * @note the function may modify the contents of the path, so copies should be passed. + */ +const char *av_dirname(char *path); + +/** + * Match instances of a name in a comma-separated list of names. + * List entries are checked from the start to the end of the names list, + * the first match ends further processing. If an entry prefixed with '-' + * matches, then 0 is returned. The "ALL" list entry is considered to + * match all names. + * + * @param name Name to look for. + * @param names List of names. + * @return 1 on match, 0 otherwise. + */ +int av_match_name(const char *name, const char *names); + +/** + * Append path component to the existing path. + * Path separator '/' is placed between when needed. + * Resulting string have to be freed with av_free(). + * @param path base path + * @param component component to be appended + * @return new path or NULL on error. + */ +char *av_append_path_component(const char *path, const char *component); + +enum AVEscapeMode { + AV_ESCAPE_MODE_AUTO, ///< Use auto-selected escaping mode. + AV_ESCAPE_MODE_BACKSLASH, ///< Use backslash escaping. + AV_ESCAPE_MODE_QUOTE, ///< Use single-quote escaping. + AV_ESCAPE_MODE_XML, ///< Use XML non-markup character data escaping. +}; + +/** + * Consider spaces special and escape them even in the middle of the + * string. + * + * This is equivalent to adding the whitespace characters to the special + * characters lists, except it is guaranteed to use the exact same list + * of whitespace characters as the rest of libavutil. + */ +#define AV_ESCAPE_FLAG_WHITESPACE (1 << 0) + +/** + * Escape only specified special characters. + * Without this flag, escape also any characters that may be considered + * special by av_get_token(), such as the single quote. + */ +#define AV_ESCAPE_FLAG_STRICT (1 << 1) + +/** + * Within AV_ESCAPE_MODE_XML, additionally escape single quotes for single + * quoted attributes. + */ +#define AV_ESCAPE_FLAG_XML_SINGLE_QUOTES (1 << 2) + +/** + * Within AV_ESCAPE_MODE_XML, additionally escape double quotes for double + * quoted attributes. + */ +#define AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES (1 << 3) + + +/** + * Escape string in src, and put the escaped string in an allocated + * string in *dst, which must be freed with av_free(). + * + * @param dst pointer where an allocated string is put + * @param src string to escape, must be non-NULL + * @param special_chars string containing the special characters which + * need to be escaped, can be NULL + * @param mode escape mode to employ, see AV_ESCAPE_MODE_* macros. + * Any unknown value for mode will be considered equivalent to + * AV_ESCAPE_MODE_BACKSLASH, but this behaviour can change without + * notice. + * @param flags flags which control how to escape, see AV_ESCAPE_FLAG_ macros + * @return the length of the allocated string, or a negative error code in case of error + * @see av_bprint_escape() + */ +av_warn_unused_result +int av_escape(char **dst, const char *src, const char *special_chars, + enum AVEscapeMode mode, int flags); + +#define AV_UTF8_FLAG_ACCEPT_INVALID_BIG_CODES 1 ///< accept codepoints over 0x10FFFF +#define AV_UTF8_FLAG_ACCEPT_NON_CHARACTERS 2 ///< accept non-characters - 0xFFFE and 0xFFFF +#define AV_UTF8_FLAG_ACCEPT_SURROGATES 4 ///< accept UTF-16 surrogates codes +#define AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES 8 ///< exclude control codes not accepted by XML + +#define AV_UTF8_FLAG_ACCEPT_ALL \ + AV_UTF8_FLAG_ACCEPT_INVALID_BIG_CODES|AV_UTF8_FLAG_ACCEPT_NON_CHARACTERS|AV_UTF8_FLAG_ACCEPT_SURROGATES + +/** + * Read and decode a single UTF-8 code point (character) from the + * buffer in *buf, and update *buf to point to the next byte to + * decode. + * + * In case of an invalid byte sequence, the pointer will be updated to + * the next byte after the invalid sequence and the function will + * return an error code. + * + * Depending on the specified flags, the function will also fail in + * case the decoded code point does not belong to a valid range. + * + * @note For speed-relevant code a carefully implemented use of + * GET_UTF8() may be preferred. + * + * @param codep pointer used to return the parsed code in case of success. + * The value in *codep is set even in case the range check fails. + * @param bufp pointer to the address the first byte of the sequence + * to decode, updated by the function to point to the + * byte next after the decoded sequence + * @param buf_end pointer to the end of the buffer, points to the next + * byte past the last in the buffer. This is used to + * avoid buffer overreads (in case of an unfinished + * UTF-8 sequence towards the end of the buffer). + * @param flags a collection of AV_UTF8_FLAG_* flags + * @return >= 0 in case a sequence was successfully read, a negative + * value in case of invalid sequence + */ +av_warn_unused_result +int av_utf8_decode(int32_t *codep, const uint8_t **bufp, const uint8_t *buf_end, + unsigned int flags); + +/** + * Check if a name is in a list. + * @returns 0 if not found, or the 1 based index where it has been found in the + * list. + */ +int av_match_list(const char *name, const char *list, char separator); + +/** + * See libc sscanf manual for more information. + * Locale-independent sscanf implementation. + */ +int av_sscanf(const char *string, const char *format, ...); + +/** + * @} + */ + +#endif /* AVUTIL_AVSTRING_H */ diff --git a/dreamcast/pvrtex/libavutil/avutil.h b/dreamcast/pvrtex/libavutil/avutil.h new file mode 100644 index 00000000..64b68bdb --- /dev/null +++ b/dreamcast/pvrtex/libavutil/avutil.h @@ -0,0 +1,371 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_AVUTIL_H +#define AVUTIL_AVUTIL_H + +/** + * @file + * @ingroup lavu + * Convenience header that includes @ref lavu "libavutil"'s core. + */ + +/** + * @mainpage + * + * @section ffmpeg_intro Introduction + * + * This document describes the usage of the different libraries + * provided by FFmpeg. + * + * @li @ref libavc "libavcodec" encoding/decoding library + * @li @ref lavfi "libavfilter" graph-based frame editing library + * @li @ref libavf "libavformat" I/O and muxing/demuxing library + * @li @ref lavd "libavdevice" special devices muxing/demuxing library + * @li @ref lavu "libavutil" common utility library + * @li @ref lswr "libswresample" audio resampling, format conversion and mixing + * @li @ref lpp "libpostproc" post processing library + * @li @ref libsws "libswscale" color conversion and scaling library + * + * @section ffmpeg_versioning Versioning and compatibility + * + * Each of the FFmpeg libraries contains a version.h header, which defines a + * major, minor and micro version number with the + * LIBRARYNAME_VERSION_{MAJOR,MINOR,MICRO} macros. The major version + * number is incremented with backward incompatible changes - e.g. removing + * parts of the public API, reordering public struct members, etc. The minor + * version number is incremented for backward compatible API changes or major + * new features - e.g. adding a new public function or a new decoder. The micro + * version number is incremented for smaller changes that a calling program + * might still want to check for - e.g. changing behavior in a previously + * unspecified situation. + * + * FFmpeg guarantees backward API and ABI compatibility for each library as long + * as its major version number is unchanged. This means that no public symbols + * will be removed or renamed. Types and names of the public struct members and + * values of public macros and enums will remain the same (unless they were + * explicitly declared as not part of the public API). Documented behavior will + * not change. + * + * In other words, any correct program that works with a given FFmpeg snapshot + * should work just as well without any changes with any later snapshot with the + * same major versions. This applies to both rebuilding the program against new + * FFmpeg versions or to replacing the dynamic FFmpeg libraries that a program + * links against. + * + * However, new public symbols may be added and new members may be appended to + * public structs whose size is not part of public ABI (most public structs in + * FFmpeg). New macros and enum values may be added. Behavior in undocumented + * situations may change slightly (and be documented). All those are accompanied + * by an entry in doc/APIchanges and incrementing either the minor or micro + * version number. + */ + +/** + * @defgroup lavu libavutil + * Common code shared across all FFmpeg libraries. + * + * @note + * libavutil is designed to be modular. In most cases, in order to use the + * functions provided by one component of libavutil you must explicitly include + * the specific header containing that feature. If you are only using + * media-related components, you could simply include libavutil/avutil.h, which + * brings in most of the "core" components. + * + * @{ + * + * @defgroup lavu_crypto Crypto and Hashing + * + * @{ + * @} + * + * @defgroup lavu_math Mathematics + * @{ + * + * @} + * + * @defgroup lavu_string String Manipulation + * + * @{ + * + * @} + * + * @defgroup lavu_mem Memory Management + * + * @{ + * + * @} + * + * @defgroup lavu_data Data Structures + * @{ + * + * @} + * + * @defgroup lavu_video Video related + * + * @{ + * + * @} + * + * @defgroup lavu_audio Audio related + * + * @{ + * + * @} + * + * @defgroup lavu_error Error Codes + * + * @{ + * + * @} + * + * @defgroup lavu_log Logging Facility + * + * @{ + * + * @} + * + * @defgroup lavu_misc Other + * + * @{ + * + * @defgroup preproc_misc Preprocessor String Macros + * + * @{ + * + * @} + * + * @defgroup version_utils Library Version Macros + * + * @{ + * + * @} + */ + + +/** + * @addtogroup lavu_ver + * @{ + */ + +/** + * Return the LIBAVUTIL_VERSION_INT constant. + */ +unsigned avutil_version(void); + +/** + * Return an informative version string. This usually is the actual release + * version number or a git commit description. This string has no fixed format + * and can change any time. It should never be parsed by code. + */ +const char *av_version_info(void); + +/** + * Return the libavutil build-time configuration. + */ +const char *avutil_configuration(void); + +/** + * Return the libavutil license. + */ +const char *avutil_license(void); + +/** + * @} + */ + +/** + * @addtogroup lavu_media Media Type + * @brief Media Type + */ + +enum AVMediaType { + AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATA + AVMEDIA_TYPE_VIDEO, + AVMEDIA_TYPE_AUDIO, + AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous + AVMEDIA_TYPE_SUBTITLE, + AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse + AVMEDIA_TYPE_NB +}; + +/** + * Return a string describing the media_type enum, NULL if media_type + * is unknown. + */ +const char *av_get_media_type_string(enum AVMediaType media_type); + +/** + * @defgroup lavu_const Constants + * @{ + * + * @defgroup lavu_enc Encoding specific + * + * @note those definition should move to avcodec + * @{ + */ + +#define FF_LAMBDA_SHIFT 7 +#define FF_LAMBDA_SCALE (1< + +#include "attributes.h" +#include "avstring.h" + +/** + * @defgroup lavu_avbprint AVBPrint + * @ingroup lavu_data + * + * A buffer to print data progressively + * @{ + */ + +/** + * Define a structure with extra padding to a fixed size + * This helps ensuring binary compatibility with future versions. + */ + +#define FF_PAD_STRUCTURE(name, size, ...) \ +struct ff_pad_helper_##name { __VA_ARGS__ }; \ +typedef struct name { \ + __VA_ARGS__ \ + char reserved_padding[size - sizeof(struct ff_pad_helper_##name)]; \ +} name; + +/** + * Buffer to print data progressively + * + * The string buffer grows as necessary and is always 0-terminated. + * The content of the string is never accessed, and thus is + * encoding-agnostic and can even hold binary data. + * + * Small buffers are kept in the structure itself, and thus require no + * memory allocation at all (unless the contents of the buffer is needed + * after the structure goes out of scope). This is almost as lightweight as + * declaring a local `char buf[512]`. + * + * The length of the string can go beyond the allocated size: the buffer is + * then truncated, but the functions still keep account of the actual total + * length. + * + * In other words, AVBPrint.len can be greater than AVBPrint.size and records + * the total length of what would have been to the buffer if there had been + * enough memory. + * + * Append operations do not need to be tested for failure: if a memory + * allocation fails, data stop being appended to the buffer, but the length + * is still updated. This situation can be tested with + * av_bprint_is_complete(). + * + * The AVBPrint.size_max field determines several possible behaviours: + * - `size_max = -1` (= `UINT_MAX`) or any large value will let the buffer be + * reallocated as necessary, with an amortized linear cost. + * - `size_max = 0` prevents writing anything to the buffer: only the total + * length is computed. The write operations can then possibly be repeated in + * a buffer with exactly the necessary size + * (using `size_init = size_max = len + 1`). + * - `size_max = 1` is automatically replaced by the exact size available in the + * structure itself, thus ensuring no dynamic memory allocation. The + * internal buffer is large enough to hold a reasonable paragraph of text, + * such as the current paragraph. + */ + +FF_PAD_STRUCTURE(AVBPrint, 1024, + char *str; /**< string so far */ + unsigned len; /**< length so far */ + unsigned size; /**< allocated memory */ + unsigned size_max; /**< maximum allocated memory */ + char reserved_internal_buffer[1]; +) + +/** + * @name Max size special values + * Convenience macros for special values for av_bprint_init() size_max + * parameter. + * @{ + */ + +/** + * Buffer will be reallocated as necessary, with an amortized linear cost. + */ +#define AV_BPRINT_SIZE_UNLIMITED ((unsigned)-1) +/** + * Use the exact size available in the AVBPrint structure itself. + * + * Thus ensuring no dynamic memory allocation. The internal buffer is large + * enough to hold a reasonable paragraph of text, such as the current paragraph. + */ +#define AV_BPRINT_SIZE_AUTOMATIC 1 +/** + * Do not write anything to the buffer, only calculate the total length. + * + * The write operations can then possibly be repeated in a buffer with + * exactly the necessary size (using `size_init = size_max = AVBPrint.len + 1`). + */ +#define AV_BPRINT_SIZE_COUNT_ONLY 0 +/** @} */ + +/** + * Init a print buffer. + * + * @param buf buffer to init + * @param size_init initial size (including the final 0) + * @param size_max maximum size; + * - `0` means do not write anything, just count the length + * - `1` is replaced by the maximum value for automatic storage + * any large value means that the internal buffer will be + * reallocated as needed up to that limit + * - `-1` is converted to `UINT_MAX`, the largest limit possible. + * Check also `AV_BPRINT_SIZE_*` macros. + */ +void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max); + +/** + * Init a print buffer using a pre-existing buffer. + * + * The buffer will not be reallocated. + * + * @param buf buffer structure to init + * @param buffer byte buffer to use for the string data + * @param size size of buffer + */ +void av_bprint_init_for_buffer(AVBPrint *buf, char *buffer, unsigned size); + +/** + * Append a formatted string to a print buffer. + */ +void av_bprintf(AVBPrint *buf, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Append a formatted string to a print buffer. + */ +void av_vbprintf(AVBPrint *buf, const char *fmt, va_list vl_arg); + +/** + * Append char c n times to a print buffer. + */ +void av_bprint_chars(AVBPrint *buf, char c, unsigned n); + +/** + * Append data to a print buffer. + * + * param buf bprint buffer to use + * param data pointer to data + * param size size of data + */ +void av_bprint_append_data(AVBPrint *buf, const char *data, unsigned size); + +struct tm; +/** + * Append a formatted date and time to a print buffer. + * + * param buf bprint buffer to use + * param fmt date and time format string, see strftime() + * param tm broken-down time structure to translate + * + * @note due to poor design of the standard strftime function, it may + * produce poor results if the format string expands to a very long text and + * the bprint buffer is near the limit stated by the size_max option. + */ +void av_bprint_strftime(AVBPrint *buf, const char *fmt, const struct tm *tm); + +/** + * Allocate bytes in the buffer for external use. + * + * @param[in] buf buffer structure + * @param[in] size required size + * @param[out] mem pointer to the memory area + * @param[out] actual_size size of the memory area after allocation; + * can be larger or smaller than size + */ +void av_bprint_get_buffer(AVBPrint *buf, unsigned size, + unsigned char **mem, unsigned *actual_size); + +/** + * Reset the string to "" but keep internal allocated data. + */ +void av_bprint_clear(AVBPrint *buf); + +/** + * Test if the print buffer is complete (not truncated). + * + * It may have been truncated due to a memory allocation failure + * or the size_max limit (compare size and size_max if necessary). + */ +static inline int av_bprint_is_complete(const AVBPrint *buf) +{ + return buf->len < buf->size; +} + +/** + * Finalize a print buffer. + * + * The print buffer can no longer be used afterwards, + * but the len and size fields are still valid. + * + * @arg[out] ret_str if not NULL, used to return a permanent copy of the + * buffer contents, or NULL if memory allocation fails; + * if NULL, the buffer is discarded and freed + * @return 0 for success or error code (probably AVERROR(ENOMEM)) + */ +int av_bprint_finalize(AVBPrint *buf, char **ret_str); + +/** + * Escape the content in src and append it to dstbuf. + * + * @param dstbuf already inited destination bprint buffer + * @param src string containing the text to escape + * @param special_chars string containing the special characters which + * need to be escaped, can be NULL + * @param mode escape mode to employ, see AV_ESCAPE_MODE_* macros. + * Any unknown value for mode will be considered equivalent to + * AV_ESCAPE_MODE_BACKSLASH, but this behaviour can change without + * notice. + * @param flags flags which control how to escape, see AV_ESCAPE_FLAG_* macros + */ +void av_bprint_escape(AVBPrint *dstbuf, const char *src, const char *special_chars, + enum AVEscapeMode mode, int flags); + +/** @} */ + +#endif /* AVUTIL_BPRINT_H */ diff --git a/dreamcast/pvrtex/libavutil/bswap.h b/dreamcast/pvrtex/libavutil/bswap.h new file mode 100644 index 00000000..4840ab43 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/bswap.h @@ -0,0 +1,111 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * byte swapping routines + */ + +#ifndef AVUTIL_BSWAP_H +#define AVUTIL_BSWAP_H + +#include +#include "libavutil/avconfig.h" +#include "attributes.h" + +#ifdef HAVE_AV_CONFIG_H + +#include "config.h" + +#if ARCH_AARCH64 +# include "aarch64/bswap.h" +#elif ARCH_ARM +# include "arm/bswap.h" +#elif ARCH_AVR32 +# include "avr32/bswap.h" +#elif ARCH_RISCV +# include "riscv/bswap.h" +#elif ARCH_SH4 +# include "sh4/bswap.h" +#elif ARCH_X86 +# include "x86/bswap.h" +#endif + +#endif /* HAVE_AV_CONFIG_H */ + +#define AV_BSWAP16C(x) (((x) << 8 & 0xff00) | ((x) >> 8 & 0x00ff)) +#define AV_BSWAP32C(x) (AV_BSWAP16C(x) << 16 | AV_BSWAP16C((x) >> 16)) +#define AV_BSWAP64C(x) (AV_BSWAP32C(x) << 32 | AV_BSWAP32C((x) >> 32)) + +#define AV_BSWAPC(s, x) AV_BSWAP##s##C(x) + +#ifndef av_bswap16 +static av_always_inline av_const uint16_t av_bswap16(uint16_t x) +{ + x= (x>>8) | (x<<8); + return x; +} +#endif + +#ifndef av_bswap32 +static av_always_inline av_const uint32_t av_bswap32(uint32_t x) +{ + return AV_BSWAP32C(x); +} +#endif + +#ifndef av_bswap64 +static inline uint64_t av_const av_bswap64(uint64_t x) +{ + return (uint64_t)av_bswap32(x) << 32 | av_bswap32(x >> 32); +} +#endif + +// be2ne ... big-endian to native-endian +// le2ne ... little-endian to native-endian + +#if AV_HAVE_BIGENDIAN +#define av_be2ne16(x) (x) +#define av_be2ne32(x) (x) +#define av_be2ne64(x) (x) +#define av_le2ne16(x) av_bswap16(x) +#define av_le2ne32(x) av_bswap32(x) +#define av_le2ne64(x) av_bswap64(x) +#define AV_BE2NEC(s, x) (x) +#define AV_LE2NEC(s, x) AV_BSWAPC(s, x) +#else +#define av_be2ne16(x) av_bswap16(x) +#define av_be2ne32(x) av_bswap32(x) +#define av_be2ne64(x) av_bswap64(x) +#define av_le2ne16(x) (x) +#define av_le2ne32(x) (x) +#define av_le2ne64(x) (x) +#define AV_BE2NEC(s, x) AV_BSWAPC(s, x) +#define AV_LE2NEC(s, x) (x) +#endif + +#define AV_BE2NE16C(x) AV_BE2NEC(16, x) +#define AV_BE2NE32C(x) AV_BE2NEC(32, x) +#define AV_BE2NE64C(x) AV_BE2NEC(64, x) +#define AV_LE2NE16C(x) AV_LE2NEC(16, x) +#define AV_LE2NE32C(x) AV_LE2NEC(32, x) +#define AV_LE2NE64C(x) AV_LE2NEC(64, x) + +#endif /* AVUTIL_BSWAP_H */ diff --git a/dreamcast/pvrtex/libavutil/common.h b/dreamcast/pvrtex/libavutil/common.h new file mode 100644 index 00000000..fd1404be --- /dev/null +++ b/dreamcast/pvrtex/libavutil/common.h @@ -0,0 +1,578 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * common internal and external API header + */ + +#ifndef AVUTIL_COMMON_H +#define AVUTIL_COMMON_H + +#if defined(__cplusplus) && !defined(__STDC_CONSTANT_MACROS) && !defined(UINT64_C) +#error missing -D__STDC_CONSTANT_MACROS / #define __STDC_CONSTANT_MACROS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "attributes.h" +#include "macros.h" + +//rounded division & shift +#define RSHIFT(a,b) ((a) > 0 ? ((a) + ((1<<(b))>>1))>>(b) : ((a) + ((1<<(b))>>1)-1)>>(b)) +/* assume b>0 */ +#define ROUNDED_DIV(a,b) (((a)>=0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b)) +/* Fast a/(1<=0 and b>=0 */ +#define AV_CEIL_RSHIFT(a,b) (!av_builtin_constant_p(b) ? -((-(a)) >> (b)) \ + : ((a) + (1<<(b)) - 1) >> (b)) +/* Backwards compat. */ +#define FF_CEIL_RSHIFT AV_CEIL_RSHIFT + +#define FFUDIV(a,b) (((a)>0 ?(a):(a)-(b)+1) / (b)) +#define FFUMOD(a,b) ((a)-(b)*FFUDIV(a,b)) + +/** + * Absolute value, Note, INT_MIN / INT64_MIN result in undefined behavior as they + * are not representable as absolute values of their type. This is the same + * as with *abs() + * @see FFNABS() + */ +#define FFABS(a) ((a) >= 0 ? (a) : (-(a))) +#define FFSIGN(a) ((a) > 0 ? 1 : -1) + +/** + * Negative Absolute value. + * this works for all integers of all types. + * As with many macros, this evaluates its argument twice, it thus must not have + * a sideeffect, that is FFNABS(x++) has undefined behavior. + */ +#define FFNABS(a) ((a) <= 0 ? (a) : (-(a))) + +/** + * Unsigned Absolute value. + * This takes the absolute value of a signed int and returns it as a unsigned. + * This also works with INT_MIN which would otherwise not be representable + * As with many macros, this evaluates its argument twice. + */ +#define FFABSU(a) ((a) <= 0 ? -(unsigned)(a) : (unsigned)(a)) +#define FFABS64U(a) ((a) <= 0 ? -(uint64_t)(a) : (uint64_t)(a)) + +/* misc math functions */ + +#ifdef HAVE_AV_CONFIG_H +# include "config.h" +# include "intmath.h" +#endif + +#ifndef av_ceil_log2 +# define av_ceil_log2 av_ceil_log2_c +#endif +#ifndef av_clip +# define av_clip av_clip_c +#endif +#ifndef av_clip64 +# define av_clip64 av_clip64_c +#endif +#ifndef av_clip_uint8 +# define av_clip_uint8 av_clip_uint8_c +#endif +#ifndef av_clip_int8 +# define av_clip_int8 av_clip_int8_c +#endif +#ifndef av_clip_uint16 +# define av_clip_uint16 av_clip_uint16_c +#endif +#ifndef av_clip_int16 +# define av_clip_int16 av_clip_int16_c +#endif +#ifndef av_clipl_int32 +# define av_clipl_int32 av_clipl_int32_c +#endif +#ifndef av_clip_intp2 +# define av_clip_intp2 av_clip_intp2_c +#endif +#ifndef av_clip_uintp2 +# define av_clip_uintp2 av_clip_uintp2_c +#endif +#ifndef av_mod_uintp2 +# define av_mod_uintp2 av_mod_uintp2_c +#endif +#ifndef av_sat_add32 +# define av_sat_add32 av_sat_add32_c +#endif +#ifndef av_sat_dadd32 +# define av_sat_dadd32 av_sat_dadd32_c +#endif +#ifndef av_sat_sub32 +# define av_sat_sub32 av_sat_sub32_c +#endif +#ifndef av_sat_dsub32 +# define av_sat_dsub32 av_sat_dsub32_c +#endif +#ifndef av_sat_add64 +# define av_sat_add64 av_sat_add64_c +#endif +#ifndef av_sat_sub64 +# define av_sat_sub64 av_sat_sub64_c +#endif +#ifndef av_clipf +# define av_clipf av_clipf_c +#endif +#ifndef av_clipd +# define av_clipd av_clipd_c +#endif +#ifndef av_popcount +# define av_popcount av_popcount_c +#endif +#ifndef av_popcount64 +# define av_popcount64 av_popcount64_c +#endif +#ifndef av_parity +# define av_parity av_parity_c +#endif + +#ifndef av_log2 +av_const int av_log2(unsigned v); +#endif + +#ifndef av_log2_16bit +av_const int av_log2_16bit(unsigned v); +#endif + +/** + * Clip a signed integer value into the amin-amax range. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const int av_clip_c(int a, int amin, int amax) +{ +#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2 + if (amin > amax) abort(); +#endif + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +/** + * Clip a signed 64bit integer value into the amin-amax range. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax) +{ +#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2 + if (amin > amax) abort(); +#endif + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +/** + * Clip a signed integer value into the 0-255 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const uint8_t av_clip_uint8_c(int a) +{ + if (a&(~0xFF)) return (~a)>>31; + else return a; +} + +/** + * Clip a signed integer value into the -128,127 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const int8_t av_clip_int8_c(int a) +{ + if ((a+0x80U) & ~0xFF) return (a>>31) ^ 0x7F; + else return a; +} + +/** + * Clip a signed integer value into the 0-65535 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const uint16_t av_clip_uint16_c(int a) +{ + if (a&(~0xFFFF)) return (~a)>>31; + else return a; +} + +/** + * Clip a signed integer value into the -32768,32767 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const int16_t av_clip_int16_c(int a) +{ + if ((a+0x8000U) & ~0xFFFF) return (a>>31) ^ 0x7FFF; + else return a; +} + +/** + * Clip a signed 64-bit integer value into the -2147483648,2147483647 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const int32_t av_clipl_int32_c(int64_t a) +{ + if ((a+0x80000000u) & ~UINT64_C(0xFFFFFFFF)) return (int32_t)((a>>63) ^ 0x7FFFFFFF); + else return (int32_t)a; +} + +/** + * Clip a signed integer into the -(2^p),(2^p-1) range. + * @param a value to clip + * @param p bit position to clip at + * @return clipped value + */ +static av_always_inline av_const int av_clip_intp2_c(int a, int p) +{ + if (((unsigned)a + (1 << p)) & ~((2 << p) - 1)) + return (a >> 31) ^ ((1 << p) - 1); + else + return a; +} + +/** + * Clip a signed integer to an unsigned power of two range. + * @param a value to clip + * @param p bit position to clip at + * @return clipped value + */ +static av_always_inline av_const unsigned av_clip_uintp2_c(int a, int p) +{ + if (a & ~((1<> 31 & ((1<= 0) + return INT64_MAX ^ (b >> 63); + return s; +#endif +} + +/** + * Subtract two signed 64-bit values with saturation. + * + * @param a one value + * @param b another value + * @return difference with signed saturation + */ +static av_always_inline int64_t av_sat_sub64_c(int64_t a, int64_t b) { +#if (!defined(__INTEL_COMPILER) && AV_GCC_VERSION_AT_LEAST(5,1)) || AV_HAS_BUILTIN(__builtin_sub_overflow) + int64_t tmp; + return !__builtin_sub_overflow(a, b, &tmp) ? tmp : (tmp < 0 ? INT64_MAX : INT64_MIN); +#else + if (b <= 0 && a >= INT64_MAX + b) + return INT64_MAX; + if (b >= 0 && a <= INT64_MIN + b) + return INT64_MIN; + return a - b; +#endif +} + +/** + * Clip a float value into the amin-amax range. + * If a is nan or -inf amin will be returned. + * If a is +inf amax will be returned. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const float av_clipf_c(float a, float amin, float amax) +{ +#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2 + if (amin > amax) abort(); +#endif + return FFMIN(FFMAX(a, amin), amax); +} + +/** + * Clip a double value into the amin-amax range. + * If a is nan or -inf amin will be returned. + * If a is +inf amax will be returned. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const double av_clipd_c(double a, double amin, double amax) +{ +#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2 + if (amin > amax) abort(); +#endif + return FFMIN(FFMAX(a, amin), amax); +} + +/** Compute ceil(log2(x)). + * @param x value used to compute ceil(log2(x)) + * @return computed ceiling of log2(x) + */ +static av_always_inline av_const int av_ceil_log2_c(int x) +{ + return av_log2((x - 1U) << 1); +} + +/** + * Count number of bits set to one in x + * @param x value to count bits of + * @return the number of bits set to one in x + */ +static av_always_inline av_const int av_popcount_c(uint32_t x) +{ + x -= (x >> 1) & 0x55555555; + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + x += x >> 8; + return (x + (x >> 16)) & 0x3F; +} + +/** + * Count number of bits set to one in x + * @param x value to count bits of + * @return the number of bits set to one in x + */ +static av_always_inline av_const int av_popcount64_c(uint64_t x) +{ + return av_popcount((uint32_t)x) + av_popcount((uint32_t)(x >> 32)); +} + +static av_always_inline av_const int av_parity_c(uint32_t v) +{ + return av_popcount(v) & 1; +} + +/** + * Convert a UTF-8 character (up to 4 bytes) to its 32-bit UCS-4 encoded form. + * + * @param val Output value, must be an lvalue of type uint32_t. + * @param GET_BYTE Expression reading one byte from the input. + * Evaluated up to 7 times (4 for the currently + * assigned Unicode range). With a memory buffer + * input, this could be *ptr++, or if you want to make sure + * that *ptr stops at the end of a NULL terminated string then + * *ptr ? *ptr++ : 0 + * @param ERROR Expression to be evaluated on invalid input, + * typically a goto statement. + * + * @warning ERROR should not contain a loop control statement which + * could interact with the internal while loop, and should force an + * exit from the macro code (e.g. through a goto or a return) in order + * to prevent undefined results. + */ +#define GET_UTF8(val, GET_BYTE, ERROR)\ + val= (GET_BYTE);\ + {\ + uint32_t top = (val & 128) >> 1;\ + if ((val & 0xc0) == 0x80 || val >= 0xFE)\ + {ERROR}\ + while (val & top) {\ + unsigned int tmp = (GET_BYTE) - 128;\ + if(tmp>>6)\ + {ERROR}\ + val= (val<<6) + tmp;\ + top <<= 5;\ + }\ + val &= (top << 1) - 1;\ + } + +/** + * Convert a UTF-16 character (2 or 4 bytes) to its 32-bit UCS-4 encoded form. + * + * @param val Output value, must be an lvalue of type uint32_t. + * @param GET_16BIT Expression returning two bytes of UTF-16 data converted + * to native byte order. Evaluated one or two times. + * @param ERROR Expression to be evaluated on invalid input, + * typically a goto statement. + */ +#define GET_UTF16(val, GET_16BIT, ERROR)\ + val = (GET_16BIT);\ + {\ + unsigned int hi = val - 0xD800;\ + if (hi < 0x800) {\ + val = (GET_16BIT) - 0xDC00;\ + if (val > 0x3FFU || hi > 0x3FFU)\ + {ERROR}\ + val += (hi<<10) + 0x10000;\ + }\ + }\ + +/** + * @def PUT_UTF8(val, tmp, PUT_BYTE) + * Convert a 32-bit Unicode character to its UTF-8 encoded form (up to 4 bytes long). + * @param val is an input-only argument and should be of type uint32_t. It holds + * a UCS-4 encoded Unicode character that is to be converted to UTF-8. If + * val is given as a function it is executed only once. + * @param tmp is a temporary variable and should be of type uint8_t. It + * represents an intermediate value during conversion that is to be + * output by PUT_BYTE. + * @param PUT_BYTE writes the converted UTF-8 bytes to any proper destination. + * It could be a function or a statement, and uses tmp as the input byte. + * For example, PUT_BYTE could be "*output++ = tmp;" PUT_BYTE will be + * executed up to 4 times for values in the valid UTF-8 range and up to + * 7 times in the general case, depending on the length of the converted + * Unicode character. + */ +#define PUT_UTF8(val, tmp, PUT_BYTE)\ + {\ + int bytes, shift;\ + uint32_t in = val;\ + if (in < 0x80) {\ + tmp = in;\ + PUT_BYTE\ + } else {\ + bytes = (av_log2(in) + 4) / 5;\ + shift = (bytes - 1) * 6;\ + tmp = (256 - (256 >> bytes)) | (in >> shift);\ + PUT_BYTE\ + while (shift >= 6) {\ + shift -= 6;\ + tmp = 0x80 | ((in >> shift) & 0x3f);\ + PUT_BYTE\ + }\ + }\ + } + +/** + * @def PUT_UTF16(val, tmp, PUT_16BIT) + * Convert a 32-bit Unicode character to its UTF-16 encoded form (2 or 4 bytes). + * @param val is an input-only argument and should be of type uint32_t. It holds + * a UCS-4 encoded Unicode character that is to be converted to UTF-16. If + * val is given as a function it is executed only once. + * @param tmp is a temporary variable and should be of type uint16_t. It + * represents an intermediate value during conversion that is to be + * output by PUT_16BIT. + * @param PUT_16BIT writes the converted UTF-16 data to any proper destination + * in desired endianness. It could be a function or a statement, and uses tmp + * as the input byte. For example, PUT_BYTE could be "*output++ = tmp;" + * PUT_BYTE will be executed 1 or 2 times depending on input character. + */ +#define PUT_UTF16(val, tmp, PUT_16BIT)\ + {\ + uint32_t in = val;\ + if (in < 0x10000) {\ + tmp = in;\ + PUT_16BIT\ + } else {\ + tmp = 0xD800 | ((in - 0x10000) >> 10);\ + PUT_16BIT\ + tmp = 0xDC00 | ((in - 0x10000) & 0x3FF);\ + PUT_16BIT\ + }\ + }\ + + + +#include "mem.h" + +#ifdef HAVE_AV_CONFIG_H +# include "internal.h" +#endif /* HAVE_AV_CONFIG_H */ + +#endif /* AVUTIL_COMMON_H */ diff --git a/dreamcast/pvrtex/libavutil/crc.h b/dreamcast/pvrtex/libavutil/crc.h new file mode 100644 index 00000000..7f59812a --- /dev/null +++ b/dreamcast/pvrtex/libavutil/crc.h @@ -0,0 +1,102 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_crc32 + * Public header for CRC hash function implementation. + */ + +#ifndef AVUTIL_CRC_H +#define AVUTIL_CRC_H + +#include +#include +#include "attributes.h" + +/** + * @defgroup lavu_crc32 CRC + * @ingroup lavu_hash + * CRC (Cyclic Redundancy Check) hash function implementation. + * + * This module supports numerous CRC polynomials, in addition to the most + * widely used CRC-32-IEEE. See @ref AVCRCId for a list of available + * polynomials. + * + * @{ + */ + +typedef uint32_t AVCRC; + +typedef enum { + AV_CRC_8_ATM, + AV_CRC_16_ANSI, + AV_CRC_16_CCITT, + AV_CRC_32_IEEE, + AV_CRC_32_IEEE_LE, /*< reversed bitorder version of AV_CRC_32_IEEE */ + AV_CRC_16_ANSI_LE, /*< reversed bitorder version of AV_CRC_16_ANSI */ + AV_CRC_24_IEEE, + AV_CRC_8_EBU, + AV_CRC_MAX, /*< Not part of public API! Do not use outside libavutil. */ +}AVCRCId; + +/** + * Initialize a CRC table. + * @param ctx must be an array of size sizeof(AVCRC)*257 or sizeof(AVCRC)*1024 + * @param le If 1, the lowest bit represents the coefficient for the highest + * exponent of the corresponding polynomial (both for poly and + * actual CRC). + * If 0, you must swap the CRC parameter and the result of av_crc + * if you need the standard representation (can be simplified in + * most cases to e.g. bswap16): + * av_bswap32(crc << (32-bits)) + * @param bits number of bits for the CRC + * @param poly generator polynomial without the x**bits coefficient, in the + * representation as specified by le + * @param ctx_size size of ctx in bytes + * @return <0 on failure + */ +int av_crc_init(AVCRC *ctx, int le, int bits, uint32_t poly, int ctx_size); + +/** + * Get an initialized standard CRC table. + * @param crc_id ID of a standard CRC + * @return a pointer to the CRC table or NULL on failure + */ +const AVCRC *av_crc_get_table(AVCRCId crc_id); + +/** + * Calculate the CRC of a block. + * @param ctx initialized AVCRC array (see av_crc_init()) + * @param crc CRC of previous blocks if any or initial value for CRC + * @param buffer buffer whose CRC to calculate + * @param length length of the buffer + * @return CRC updated with the data from the given block + * + * @see av_crc_init() "le" parameter + */ +uint32_t av_crc(const AVCRC *ctx, uint32_t crc, + const uint8_t *buffer, size_t length) av_pure; + +/** + * @} + */ + +#endif /* AVUTIL_CRC_H */ diff --git a/dreamcast/pvrtex/libavutil/dynarray.h b/dreamcast/pvrtex/libavutil/dynarray.h new file mode 100644 index 00000000..3a7e1464 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/dynarray.h @@ -0,0 +1,70 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpeg; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_DYNARRAY_H +#define AVUTIL_DYNARRAY_H + +#include "log.h" +#include "mem.h" + +/** + * Add an element to a dynamic array. + * + * The array is reallocated when its number of elements reaches powers of 2. + * Therefore, the amortized cost of adding an element is constant. + * + * In case of success, the pointer to the array is updated in order to + * point to the new grown array, and the size is incremented. + * + * @param av_size_max maximum size of the array, usually the MAX macro of + * the type of the size + * @param av_elt_size size of the elements in the array, in bytes + * @param av_array pointer to the array, must be a lvalue + * @param av_size size of the array, must be an integer lvalue + * @param av_success statement to execute on success; at this point, the + * size variable is not yet incremented + * @param av_failure statement to execute on failure; if this happens, the + * array and size are not changed; the statement can end + * with a return or a goto + */ +#define FF_DYNARRAY_ADD(av_size_max, av_elt_size, av_array, av_size, \ + av_success, av_failure) \ + do { \ + size_t av_size_new = (av_size); \ + if (!((av_size) & ((av_size) - 1))) { \ + av_size_new = (av_size) ? (av_size) << 1 : 1; \ + if (av_size_new > (av_size_max) / (av_elt_size)) { \ + av_size_new = 0; \ + } else { \ + void *av_array_new = \ + av_realloc((av_array), av_size_new * (av_elt_size)); \ + if (!av_array_new) \ + av_size_new = 0; \ + else \ + (av_array) = av_array_new; \ + } \ + } \ + if (av_size_new) { \ + { av_success } \ + (av_size)++; \ + } else { \ + av_failure \ + } \ + } while (0) + +#endif /* AVUTIL_DYNARRAY_H */ diff --git a/dreamcast/pvrtex/libavutil/error.h b/dreamcast/pvrtex/libavutil/error.h new file mode 100644 index 00000000..0d3269aa --- /dev/null +++ b/dreamcast/pvrtex/libavutil/error.h @@ -0,0 +1,128 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * error code definitions + */ + +#ifndef AVUTIL_ERROR_H +#define AVUTIL_ERROR_H + +#include +#include + +#include "macros.h" + +/** + * @addtogroup lavu_error + * + * @{ + */ + + +/* error handling */ +#if EDOM > 0 +#define AVERROR(e) (-(e)) ///< Returns a negative error code from a POSIX error code, to return from library functions. +#define AVUNERROR(e) (-(e)) ///< Returns a POSIX error code from a library function error return value. +#else +/* Some platforms have E* and errno already negated. */ +#define AVERROR(e) (e) +#define AVUNERROR(e) (e) +#endif + +#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d)) + +#define AVERROR_BSF_NOT_FOUND FFERRTAG(0xF8,'B','S','F') ///< Bitstream filter not found +#define AVERROR_BUG FFERRTAG( 'B','U','G','!') ///< Internal bug, also see AVERROR_BUG2 +#define AVERROR_BUFFER_TOO_SMALL FFERRTAG( 'B','U','F','S') ///< Buffer too small +#define AVERROR_DECODER_NOT_FOUND FFERRTAG(0xF8,'D','E','C') ///< Decoder not found +#define AVERROR_DEMUXER_NOT_FOUND FFERRTAG(0xF8,'D','E','M') ///< Demuxer not found +#define AVERROR_ENCODER_NOT_FOUND FFERRTAG(0xF8,'E','N','C') ///< Encoder not found +#define AVERROR_EOF FFERRTAG( 'E','O','F',' ') ///< End of file +#define AVERROR_EXIT FFERRTAG( 'E','X','I','T') ///< Immediate exit was requested; the called function should not be restarted +#define AVERROR_EXTERNAL FFERRTAG( 'E','X','T',' ') ///< Generic error in an external library +#define AVERROR_FILTER_NOT_FOUND FFERRTAG(0xF8,'F','I','L') ///< Filter not found +#define AVERROR_INVALIDDATA FFERRTAG( 'I','N','D','A') ///< Invalid data found when processing input +#define AVERROR_MUXER_NOT_FOUND FFERRTAG(0xF8,'M','U','X') ///< Muxer not found +#define AVERROR_OPTION_NOT_FOUND FFERRTAG(0xF8,'O','P','T') ///< Option not found +#define AVERROR_PATCHWELCOME FFERRTAG( 'P','A','W','E') ///< Not yet implemented in FFmpeg, patches welcome +#define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O') ///< Protocol not found + +#define AVERROR_STREAM_NOT_FOUND FFERRTAG(0xF8,'S','T','R') ///< Stream not found +/** + * This is semantically identical to AVERROR_BUG + * it has been introduced in Libav after our AVERROR_BUG and with a modified value. + */ +#define AVERROR_BUG2 FFERRTAG( 'B','U','G',' ') +#define AVERROR_UNKNOWN FFERRTAG( 'U','N','K','N') ///< Unknown error, typically from an external library +#define AVERROR_EXPERIMENTAL (-0x2bb2afa8) ///< Requested feature is flagged experimental. Set strict_std_compliance if you really want to use it. +#define AVERROR_INPUT_CHANGED (-0x636e6701) ///< Input changed between calls. Reconfiguration is required. (can be OR-ed with AVERROR_OUTPUT_CHANGED) +#define AVERROR_OUTPUT_CHANGED (-0x636e6702) ///< Output changed between calls. Reconfiguration is required. (can be OR-ed with AVERROR_INPUT_CHANGED) +/* HTTP & RTSP errors */ +#define AVERROR_HTTP_BAD_REQUEST FFERRTAG(0xF8,'4','0','0') +#define AVERROR_HTTP_UNAUTHORIZED FFERRTAG(0xF8,'4','0','1') +#define AVERROR_HTTP_FORBIDDEN FFERRTAG(0xF8,'4','0','3') +#define AVERROR_HTTP_NOT_FOUND FFERRTAG(0xF8,'4','0','4') +#define AVERROR_HTTP_OTHER_4XX FFERRTAG(0xF8,'4','X','X') +#define AVERROR_HTTP_SERVER_ERROR FFERRTAG(0xF8,'5','X','X') + +#define AV_ERROR_MAX_STRING_SIZE 64 + +/** + * Put a description of the AVERROR code errnum in errbuf. + * In case of failure the global variable errno is set to indicate the + * error. Even in case of failure av_strerror() will print a generic + * error message indicating the errnum provided to errbuf. + * + * @param errnum error code to describe + * @param errbuf buffer to which description is written + * @param errbuf_size the size in bytes of errbuf + * @return 0 on success, a negative value if a description for errnum + * cannot be found + */ +int av_strerror(int errnum, char *errbuf, size_t errbuf_size); + +/** + * Fill the provided buffer with a string containing an error string + * corresponding to the AVERROR code errnum. + * + * @param errbuf a buffer + * @param errbuf_size size in bytes of errbuf + * @param errnum error code to describe + * @return the buffer in input, filled with the error description + * @see av_strerror() + */ +static inline char *av_make_error_string(char *errbuf, size_t errbuf_size, int errnum) +{ + av_strerror(errnum, errbuf, errbuf_size); + return errbuf; +} + +/** + * Convenience macro, the return value should be used only directly in + * function arguments but never stand-alone. + */ +#define av_err2str(errnum) \ + av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, AV_ERROR_MAX_STRING_SIZE, errnum) + +/** + * @} + */ + +#endif /* AVUTIL_ERROR_H */ diff --git a/dreamcast/pvrtex/libavutil/internal.h b/dreamcast/pvrtex/libavutil/internal.h new file mode 100644 index 00000000..00110314 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/internal.h @@ -0,0 +1,189 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * common internal API header + */ + +#ifndef AVUTIL_INTERNAL_H +#define AVUTIL_INTERNAL_H + +#if !defined(DEBUG) && !defined(NDEBUG) +# define NDEBUG +#endif + +// This can be enabled to allow detection of additional integer overflows with ubsan +//#define CHECKED + +#include +#include +#include +#include +#include +#include "config.h" +#include "attributes.h" +#include "macros.h" +#include "pixfmt.h" + +#if ARCH_X86 +# include "x86/emms.h" +#endif + +#ifndef emms_c +# define emms_c() do {} while(0) +#endif + +#ifndef attribute_align_arg +#if ARCH_X86_32 && AV_GCC_VERSION_AT_LEAST(4,2) +# define attribute_align_arg __attribute__((force_align_arg_pointer)) +#else +# define attribute_align_arg +#endif +#endif + +#if defined(_WIN32) && CONFIG_SHARED && !defined(BUILDING_avutil) +# define av_export_avutil __declspec(dllimport) +#else +# define av_export_avutil +#endif + +#if HAVE_PRAGMA_DEPRECATED +# if defined(__ICL) || defined (__INTEL_COMPILER) +# define FF_DISABLE_DEPRECATION_WARNINGS __pragma(warning(push)) __pragma(warning(disable:1478)) +# define FF_ENABLE_DEPRECATION_WARNINGS __pragma(warning(pop)) +# elif defined(_MSC_VER) +# define FF_DISABLE_DEPRECATION_WARNINGS __pragma(warning(push)) __pragma(warning(disable:4996)) +# define FF_ENABLE_DEPRECATION_WARNINGS __pragma(warning(pop)) +# else +# define FF_DISABLE_DEPRECATION_WARNINGS _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +# define FF_ENABLE_DEPRECATION_WARNINGS _Pragma("GCC diagnostic pop") +# endif +#else +# define FF_DISABLE_DEPRECATION_WARNINGS +# define FF_ENABLE_DEPRECATION_WARNINGS +#endif + + +#define FF_MEMORY_POISON 0x2a + +/* Check if the hard coded offset of a struct member still matches reality. + * Induce a compilation failure if not. + */ +#define AV_CHECK_OFFSET(s, m, o) struct check_##o { \ + int x_##o[offsetof(s, m) == o? 1: -1]; \ + } + + +#define FF_ALLOC_TYPED_ARRAY(p, nelem) (p = av_malloc_array(nelem, sizeof(*p))) +#define FF_ALLOCZ_TYPED_ARRAY(p, nelem) (p = av_calloc(nelem, sizeof(*p))) + +#define FF_PTR_ADD(ptr, off) ((off) ? (ptr) + (off) : (ptr)) + +/** + * Access a field in a structure by its offset. + */ +#define FF_FIELD_AT(type, off, obj) (*(type *)((char *)&(obj) + (off))) + +#include "libm.h" + +/** + * Return NULL if CONFIG_SMALL is true, otherwise the argument + * without modification. Used to disable the definition of strings. + */ +#if CONFIG_SMALL +# define NULL_IF_CONFIG_SMALL(x) NULL +#else +# define NULL_IF_CONFIG_SMALL(x) x +#endif + +/** + * Log a generic warning message about a missing feature. + * + * @param[in] avc a pointer to an arbitrary struct of which the first + * field is a pointer to an AVClass struct + * @param[in] msg string containing the name of the missing feature + */ +void avpriv_report_missing_feature(void *avc, + const char *msg, ...) av_printf_format(2, 3); + +/** + * Log a generic warning message about a missing feature. + * Additionally request that a sample showcasing the feature be uploaded. + * + * @param[in] avc a pointer to an arbitrary struct of which the first field is + * a pointer to an AVClass struct + * @param[in] msg string containing the name of the missing feature + */ +void avpriv_request_sample(void *avc, + const char *msg, ...) av_printf_format(2, 3); + +#if HAVE_LIBC_MSVCRT +#include +#if defined(_VC_CRT_MAJOR_VERSION) && _VC_CRT_MAJOR_VERSION < 14 +#pragma comment(linker, "/include:" EXTERN_PREFIX "avpriv_strtod") +#pragma comment(linker, "/include:" EXTERN_PREFIX "avpriv_snprintf") +#endif + +#define PTRDIFF_SPECIFIER "Id" +#define SIZE_SPECIFIER "Iu" +#else +#define PTRDIFF_SPECIFIER "td" +#define SIZE_SPECIFIER "zu" +#endif + +#ifdef DEBUG +# define ff_dlog(ctx, ...) av_log(ctx, AV_LOG_DEBUG, __VA_ARGS__) +#else +# define ff_dlog(ctx, ...) do { if (0) av_log(ctx, AV_LOG_DEBUG, __VA_ARGS__); } while (0) +#endif + +#ifdef TRACE +# define ff_tlog(ctx, ...) av_log(ctx, AV_LOG_TRACE, __VA_ARGS__) +#else +# define ff_tlog(ctx, ...) do { } while(0) +#endif + +// For debuging we use signed operations so overflows can be detected (by ubsan) +// For production we use unsigned so there are no undefined operations +#ifdef CHECKED +#define SUINT int +#define SUINT32 int32_t +#else +#define SUINT unsigned +#define SUINT32 uint32_t +#endif + +int avpriv_set_systematic_pal2(uint32_t pal[256], enum AVPixelFormat pix_fmt); + +static av_always_inline av_const int avpriv_mirror(int x, int w) +{ + if (!w) + return 0; + + while ((unsigned)x > (unsigned)w) { + x = -x; + if (x < 0) + x += 2 * w; + } + return x; +} + +#endif /* AVUTIL_INTERNAL_H */ diff --git a/dreamcast/pvrtex/libavutil/intfloat.h b/dreamcast/pvrtex/libavutil/intfloat.h new file mode 100644 index 00000000..fe3d7ec4 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/intfloat.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011 Mans Rullgard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_INTFLOAT_H +#define AVUTIL_INTFLOAT_H + +#include +#include "attributes.h" + +union av_intfloat32 { + uint32_t i; + float f; +}; + +union av_intfloat64 { + uint64_t i; + double f; +}; + +/** + * Reinterpret a 32-bit integer as a float. + */ +static av_always_inline float av_int2float(uint32_t i) +{ + union av_intfloat32 v; + v.i = i; + return v.f; +} + +/** + * Reinterpret a float as a 32-bit integer. + */ +static av_always_inline uint32_t av_float2int(float f) +{ + union av_intfloat32 v; + v.f = f; + return v.i; +} + +/** + * Reinterpret a 64-bit integer as a double. + */ +static av_always_inline double av_int2double(uint64_t i) +{ + union av_intfloat64 v; + v.i = i; + return v.f; +} + +/** + * Reinterpret a double as a 64-bit integer. + */ +static av_always_inline uint64_t av_double2int(double f) +{ + union av_intfloat64 v; + v.f = f; + return v.i; +} + +#endif /* AVUTIL_INTFLOAT_H */ diff --git a/dreamcast/pvrtex/libavutil/intreadwrite.h b/dreamcast/pvrtex/libavutil/intreadwrite.h new file mode 100644 index 00000000..4c8413a5 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/intreadwrite.h @@ -0,0 +1,644 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_INTREADWRITE_H +#define AVUTIL_INTREADWRITE_H + +#include +#include "libavutil/avconfig.h" +#include "attributes.h" +#include "bswap.h" + +typedef union { + uint64_t u64; + uint32_t u32[2]; + uint16_t u16[4]; + uint8_t u8 [8]; + double f64; + float f32[2]; +} av_alias av_alias64; + +typedef union { + uint32_t u32; + uint16_t u16[2]; + uint8_t u8 [4]; + float f32; +} av_alias av_alias32; + +typedef union { + uint16_t u16; + uint8_t u8 [2]; +} av_alias av_alias16; + +/* + * Arch-specific headers can provide any combination of + * AV_[RW][BLN](16|24|32|48|64) and AV_(COPY|SWAP|ZERO)(64|128) macros. + * Preprocessor symbols must be defined, even if these are implemented + * as inline functions. + * + * R/W means read/write, B/L/N means big/little/native endianness. + * The following macros require aligned access, compared to their + * unaligned variants: AV_(COPY|SWAP|ZERO)(64|128), AV_[RW]N[8-64]A. + * Incorrect usage may range from abysmal performance to crash + * depending on the platform. + * + * The unaligned variants are AV_[RW][BLN][8-64] and AV_COPY*U. + */ + +#ifdef HAVE_AV_CONFIG_H + +#include "config.h" + +#if ARCH_ARM +# include "arm/intreadwrite.h" +#elif ARCH_AVR32 +# include "avr32/intreadwrite.h" +#elif ARCH_MIPS +# include "mips/intreadwrite.h" +#elif ARCH_PPC +# include "ppc/intreadwrite.h" +#elif ARCH_TOMI +# include "tomi/intreadwrite.h" +#elif ARCH_X86 +# include "x86/intreadwrite.h" +#endif + +#endif /* HAVE_AV_CONFIG_H */ + +/* + * Map AV_RNXX <-> AV_R[BL]XX for all variants provided by per-arch headers. + */ + +#if AV_HAVE_BIGENDIAN + +# if defined(AV_RN16) && !defined(AV_RB16) +# define AV_RB16(p) AV_RN16(p) +# elif !defined(AV_RN16) && defined(AV_RB16) +# define AV_RN16(p) AV_RB16(p) +# endif + +# if defined(AV_WN16) && !defined(AV_WB16) +# define AV_WB16(p, v) AV_WN16(p, v) +# elif !defined(AV_WN16) && defined(AV_WB16) +# define AV_WN16(p, v) AV_WB16(p, v) +# endif + +# if defined(AV_RN24) && !defined(AV_RB24) +# define AV_RB24(p) AV_RN24(p) +# elif !defined(AV_RN24) && defined(AV_RB24) +# define AV_RN24(p) AV_RB24(p) +# endif + +# if defined(AV_WN24) && !defined(AV_WB24) +# define AV_WB24(p, v) AV_WN24(p, v) +# elif !defined(AV_WN24) && defined(AV_WB24) +# define AV_WN24(p, v) AV_WB24(p, v) +# endif + +# if defined(AV_RN32) && !defined(AV_RB32) +# define AV_RB32(p) AV_RN32(p) +# elif !defined(AV_RN32) && defined(AV_RB32) +# define AV_RN32(p) AV_RB32(p) +# endif + +# if defined(AV_WN32) && !defined(AV_WB32) +# define AV_WB32(p, v) AV_WN32(p, v) +# elif !defined(AV_WN32) && defined(AV_WB32) +# define AV_WN32(p, v) AV_WB32(p, v) +# endif + +# if defined(AV_RN48) && !defined(AV_RB48) +# define AV_RB48(p) AV_RN48(p) +# elif !defined(AV_RN48) && defined(AV_RB48) +# define AV_RN48(p) AV_RB48(p) +# endif + +# if defined(AV_WN48) && !defined(AV_WB48) +# define AV_WB48(p, v) AV_WN48(p, v) +# elif !defined(AV_WN48) && defined(AV_WB48) +# define AV_WN48(p, v) AV_WB48(p, v) +# endif + +# if defined(AV_RN64) && !defined(AV_RB64) +# define AV_RB64(p) AV_RN64(p) +# elif !defined(AV_RN64) && defined(AV_RB64) +# define AV_RN64(p) AV_RB64(p) +# endif + +# if defined(AV_WN64) && !defined(AV_WB64) +# define AV_WB64(p, v) AV_WN64(p, v) +# elif !defined(AV_WN64) && defined(AV_WB64) +# define AV_WN64(p, v) AV_WB64(p, v) +# endif + +#else /* AV_HAVE_BIGENDIAN */ + +# if defined(AV_RN16) && !defined(AV_RL16) +# define AV_RL16(p) AV_RN16(p) +# elif !defined(AV_RN16) && defined(AV_RL16) +# define AV_RN16(p) AV_RL16(p) +# endif + +# if defined(AV_WN16) && !defined(AV_WL16) +# define AV_WL16(p, v) AV_WN16(p, v) +# elif !defined(AV_WN16) && defined(AV_WL16) +# define AV_WN16(p, v) AV_WL16(p, v) +# endif + +# if defined(AV_RN24) && !defined(AV_RL24) +# define AV_RL24(p) AV_RN24(p) +# elif !defined(AV_RN24) && defined(AV_RL24) +# define AV_RN24(p) AV_RL24(p) +# endif + +# if defined(AV_WN24) && !defined(AV_WL24) +# define AV_WL24(p, v) AV_WN24(p, v) +# elif !defined(AV_WN24) && defined(AV_WL24) +# define AV_WN24(p, v) AV_WL24(p, v) +# endif + +# if defined(AV_RN32) && !defined(AV_RL32) +# define AV_RL32(p) AV_RN32(p) +# elif !defined(AV_RN32) && defined(AV_RL32) +# define AV_RN32(p) AV_RL32(p) +# endif + +# if defined(AV_WN32) && !defined(AV_WL32) +# define AV_WL32(p, v) AV_WN32(p, v) +# elif !defined(AV_WN32) && defined(AV_WL32) +# define AV_WN32(p, v) AV_WL32(p, v) +# endif + +# if defined(AV_RN48) && !defined(AV_RL48) +# define AV_RL48(p) AV_RN48(p) +# elif !defined(AV_RN48) && defined(AV_RL48) +# define AV_RN48(p) AV_RL48(p) +# endif + +# if defined(AV_WN48) && !defined(AV_WL48) +# define AV_WL48(p, v) AV_WN48(p, v) +# elif !defined(AV_WN48) && defined(AV_WL48) +# define AV_WN48(p, v) AV_WL48(p, v) +# endif + +# if defined(AV_RN64) && !defined(AV_RL64) +# define AV_RL64(p) AV_RN64(p) +# elif !defined(AV_RN64) && defined(AV_RL64) +# define AV_RN64(p) AV_RL64(p) +# endif + +# if defined(AV_WN64) && !defined(AV_WL64) +# define AV_WL64(p, v) AV_WN64(p, v) +# elif !defined(AV_WN64) && defined(AV_WL64) +# define AV_WN64(p, v) AV_WL64(p, v) +# endif + +#endif /* !AV_HAVE_BIGENDIAN */ + +/* + * Define AV_[RW]N helper macros to simplify definitions not provided + * by per-arch headers. + */ + +#if defined(__GNUC__) + +union unaligned_64 { uint64_t l; } __attribute__((packed)) av_alias; +union unaligned_32 { uint32_t l; } __attribute__((packed)) av_alias; +union unaligned_16 { uint16_t l; } __attribute__((packed)) av_alias; + +# define AV_RN(s, p) (((const union unaligned_##s *) (p))->l) +# define AV_WN(s, p, v) ((((union unaligned_##s *) (p))->l) = (v)) + +#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_X64) || defined(_M_ARM64)) && AV_HAVE_FAST_UNALIGNED + +# define AV_RN(s, p) (*((const __unaligned uint##s##_t*)(p))) +# define AV_WN(s, p, v) (*((__unaligned uint##s##_t*)(p)) = (v)) + +#elif AV_HAVE_FAST_UNALIGNED + +# define AV_RN(s, p) (((const av_alias##s*)(p))->u##s) +# define AV_WN(s, p, v) (((av_alias##s*)(p))->u##s = (v)) + +#else + +#ifndef AV_RB16 +# define AV_RB16(x) \ + ((((const uint8_t*)(x))[0] << 8) | \ + ((const uint8_t*)(x))[1]) +#endif +#ifndef AV_WB16 +# define AV_WB16(p, val) do { \ + uint16_t d = (val); \ + ((uint8_t*)(p))[1] = (d); \ + ((uint8_t*)(p))[0] = (d)>>8; \ + } while(0) +#endif + +#ifndef AV_RL16 +# define AV_RL16(x) \ + ((((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL16 +# define AV_WL16(p, val) do { \ + uint16_t d = (val); \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + } while(0) +#endif + +#ifndef AV_RB32 +# define AV_RB32(x) \ + (((uint32_t)((const uint8_t*)(x))[0] << 24) | \ + (((const uint8_t*)(x))[1] << 16) | \ + (((const uint8_t*)(x))[2] << 8) | \ + ((const uint8_t*)(x))[3]) +#endif +#ifndef AV_WB32 +# define AV_WB32(p, val) do { \ + uint32_t d = (val); \ + ((uint8_t*)(p))[3] = (d); \ + ((uint8_t*)(p))[2] = (d)>>8; \ + ((uint8_t*)(p))[1] = (d)>>16; \ + ((uint8_t*)(p))[0] = (d)>>24; \ + } while(0) +#endif + +#ifndef AV_RL32 +# define AV_RL32(x) \ + (((uint32_t)((const uint8_t*)(x))[3] << 24) | \ + (((const uint8_t*)(x))[2] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL32 +# define AV_WL32(p, val) do { \ + uint32_t d = (val); \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + ((uint8_t*)(p))[3] = (d)>>24; \ + } while(0) +#endif + +#ifndef AV_RB64 +# define AV_RB64(x) \ + (((uint64_t)((const uint8_t*)(x))[0] << 56) | \ + ((uint64_t)((const uint8_t*)(x))[1] << 48) | \ + ((uint64_t)((const uint8_t*)(x))[2] << 40) | \ + ((uint64_t)((const uint8_t*)(x))[3] << 32) | \ + ((uint64_t)((const uint8_t*)(x))[4] << 24) | \ + ((uint64_t)((const uint8_t*)(x))[5] << 16) | \ + ((uint64_t)((const uint8_t*)(x))[6] << 8) | \ + (uint64_t)((const uint8_t*)(x))[7]) +#endif +#ifndef AV_WB64 +# define AV_WB64(p, val) do { \ + uint64_t d = (val); \ + ((uint8_t*)(p))[7] = (d); \ + ((uint8_t*)(p))[6] = (d)>>8; \ + ((uint8_t*)(p))[5] = (d)>>16; \ + ((uint8_t*)(p))[4] = (d)>>24; \ + ((uint8_t*)(p))[3] = (d)>>32; \ + ((uint8_t*)(p))[2] = (d)>>40; \ + ((uint8_t*)(p))[1] = (d)>>48; \ + ((uint8_t*)(p))[0] = (d)>>56; \ + } while(0) +#endif + +#ifndef AV_RL64 +# define AV_RL64(x) \ + (((uint64_t)((const uint8_t*)(x))[7] << 56) | \ + ((uint64_t)((const uint8_t*)(x))[6] << 48) | \ + ((uint64_t)((const uint8_t*)(x))[5] << 40) | \ + ((uint64_t)((const uint8_t*)(x))[4] << 32) | \ + ((uint64_t)((const uint8_t*)(x))[3] << 24) | \ + ((uint64_t)((const uint8_t*)(x))[2] << 16) | \ + ((uint64_t)((const uint8_t*)(x))[1] << 8) | \ + (uint64_t)((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL64 +# define AV_WL64(p, val) do { \ + uint64_t d = (val); \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + ((uint8_t*)(p))[3] = (d)>>24; \ + ((uint8_t*)(p))[4] = (d)>>32; \ + ((uint8_t*)(p))[5] = (d)>>40; \ + ((uint8_t*)(p))[6] = (d)>>48; \ + ((uint8_t*)(p))[7] = (d)>>56; \ + } while(0) +#endif + +#if AV_HAVE_BIGENDIAN +# define AV_RN(s, p) AV_RB##s(p) +# define AV_WN(s, p, v) AV_WB##s(p, v) +#else +# define AV_RN(s, p) AV_RL##s(p) +# define AV_WN(s, p, v) AV_WL##s(p, v) +#endif + +#endif /* HAVE_FAST_UNALIGNED */ + +#ifndef AV_RN16 +# define AV_RN16(p) AV_RN(16, p) +#endif + +#ifndef AV_RN32 +# define AV_RN32(p) AV_RN(32, p) +#endif + +#ifndef AV_RN64 +# define AV_RN64(p) AV_RN(64, p) +#endif + +#ifndef AV_WN16 +# define AV_WN16(p, v) AV_WN(16, p, v) +#endif + +#ifndef AV_WN32 +# define AV_WN32(p, v) AV_WN(32, p, v) +#endif + +#ifndef AV_WN64 +# define AV_WN64(p, v) AV_WN(64, p, v) +#endif + +#if AV_HAVE_BIGENDIAN +# define AV_RB(s, p) AV_RN##s(p) +# define AV_WB(s, p, v) AV_WN##s(p, v) +# define AV_RL(s, p) av_bswap##s(AV_RN##s(p)) +# define AV_WL(s, p, v) AV_WN##s(p, av_bswap##s(v)) +#else +# define AV_RB(s, p) av_bswap##s(AV_RN##s(p)) +# define AV_WB(s, p, v) AV_WN##s(p, av_bswap##s(v)) +# define AV_RL(s, p) AV_RN##s(p) +# define AV_WL(s, p, v) AV_WN##s(p, v) +#endif + +#define AV_RB8(x) (((const uint8_t*)(x))[0]) +#define AV_WB8(p, d) do { ((uint8_t*)(p))[0] = (d); } while(0) + +#define AV_RL8(x) AV_RB8(x) +#define AV_WL8(p, d) AV_WB8(p, d) + +#ifndef AV_RB16 +# define AV_RB16(p) AV_RB(16, p) +#endif +#ifndef AV_WB16 +# define AV_WB16(p, v) AV_WB(16, p, v) +#endif + +#ifndef AV_RL16 +# define AV_RL16(p) AV_RL(16, p) +#endif +#ifndef AV_WL16 +# define AV_WL16(p, v) AV_WL(16, p, v) +#endif + +#ifndef AV_RB32 +# define AV_RB32(p) AV_RB(32, p) +#endif +#ifndef AV_WB32 +# define AV_WB32(p, v) AV_WB(32, p, v) +#endif + +#ifndef AV_RL32 +# define AV_RL32(p) AV_RL(32, p) +#endif +#ifndef AV_WL32 +# define AV_WL32(p, v) AV_WL(32, p, v) +#endif + +#ifndef AV_RB64 +# define AV_RB64(p) AV_RB(64, p) +#endif +#ifndef AV_WB64 +# define AV_WB64(p, v) AV_WB(64, p, v) +#endif + +#ifndef AV_RL64 +# define AV_RL64(p) AV_RL(64, p) +#endif +#ifndef AV_WL64 +# define AV_WL64(p, v) AV_WL(64, p, v) +#endif + +#ifndef AV_RB24 +# define AV_RB24(x) \ + ((((const uint8_t*)(x))[0] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[2]) +#endif +#ifndef AV_WB24 +# define AV_WB24(p, d) do { \ + ((uint8_t*)(p))[2] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[0] = (d)>>16; \ + } while(0) +#endif + +#ifndef AV_RL24 +# define AV_RL24(x) \ + ((((const uint8_t*)(x))[2] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL24 +# define AV_WL24(p, d) do { \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + } while(0) +#endif + +#ifndef AV_RB48 +# define AV_RB48(x) \ + (((uint64_t)((const uint8_t*)(x))[0] << 40) | \ + ((uint64_t)((const uint8_t*)(x))[1] << 32) | \ + ((uint64_t)((const uint8_t*)(x))[2] << 24) | \ + ((uint64_t)((const uint8_t*)(x))[3] << 16) | \ + ((uint64_t)((const uint8_t*)(x))[4] << 8) | \ + (uint64_t)((const uint8_t*)(x))[5]) +#endif +#ifndef AV_WB48 +# define AV_WB48(p, darg) do { \ + uint64_t d = (darg); \ + ((uint8_t*)(p))[5] = (d); \ + ((uint8_t*)(p))[4] = (d)>>8; \ + ((uint8_t*)(p))[3] = (d)>>16; \ + ((uint8_t*)(p))[2] = (d)>>24; \ + ((uint8_t*)(p))[1] = (d)>>32; \ + ((uint8_t*)(p))[0] = (d)>>40; \ + } while(0) +#endif + +#ifndef AV_RL48 +# define AV_RL48(x) \ + (((uint64_t)((const uint8_t*)(x))[5] << 40) | \ + ((uint64_t)((const uint8_t*)(x))[4] << 32) | \ + ((uint64_t)((const uint8_t*)(x))[3] << 24) | \ + ((uint64_t)((const uint8_t*)(x))[2] << 16) | \ + ((uint64_t)((const uint8_t*)(x))[1] << 8) | \ + (uint64_t)((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL48 +# define AV_WL48(p, darg) do { \ + uint64_t d = (darg); \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + ((uint8_t*)(p))[3] = (d)>>24; \ + ((uint8_t*)(p))[4] = (d)>>32; \ + ((uint8_t*)(p))[5] = (d)>>40; \ + } while(0) +#endif + +/* + * The AV_[RW]NA macros access naturally aligned data + * in a type-safe way. + */ + +#define AV_RNA(s, p) (((const av_alias##s*)(p))->u##s) +#define AV_WNA(s, p, v) (((av_alias##s*)(p))->u##s = (v)) + +#ifndef AV_RN16A +# define AV_RN16A(p) AV_RNA(16, p) +#endif + +#ifndef AV_RN32A +# define AV_RN32A(p) AV_RNA(32, p) +#endif + +#ifndef AV_RN64A +# define AV_RN64A(p) AV_RNA(64, p) +#endif + +#ifndef AV_WN16A +# define AV_WN16A(p, v) AV_WNA(16, p, v) +#endif + +#ifndef AV_WN32A +# define AV_WN32A(p, v) AV_WNA(32, p, v) +#endif + +#ifndef AV_WN64A +# define AV_WN64A(p, v) AV_WNA(64, p, v) +#endif + +#if AV_HAVE_BIGENDIAN +# define AV_RLA(s, p) av_bswap##s(AV_RN##s##A(p)) +# define AV_WLA(s, p, v) AV_WN##s##A(p, av_bswap##s(v)) +#else +# define AV_RLA(s, p) AV_RN##s##A(p) +# define AV_WLA(s, p, v) AV_WN##s##A(p, v) +#endif + +#ifndef AV_RL64A +# define AV_RL64A(p) AV_RLA(64, p) +#endif +#ifndef AV_WL64A +# define AV_WL64A(p, v) AV_WLA(64, p, v) +#endif + +/* + * The AV_COPYxxU macros are suitable for copying data to/from unaligned + * memory locations. + */ + +#define AV_COPYU(n, d, s) AV_WN##n(d, AV_RN##n(s)); + +#ifndef AV_COPY16U +# define AV_COPY16U(d, s) AV_COPYU(16, d, s) +#endif + +#ifndef AV_COPY32U +# define AV_COPY32U(d, s) AV_COPYU(32, d, s) +#endif + +#ifndef AV_COPY64U +# define AV_COPY64U(d, s) AV_COPYU(64, d, s) +#endif + +#ifndef AV_COPY128U +# define AV_COPY128U(d, s) \ + do { \ + AV_COPY64U(d, s); \ + AV_COPY64U((char *)(d) + 8, (const char *)(s) + 8); \ + } while(0) +#endif + +/* Parameters for AV_COPY*, AV_SWAP*, AV_ZERO* must be + * naturally aligned. They may be implemented using MMX, + * so emms_c() must be called before using any float code + * afterwards. + */ + +#define AV_COPY(n, d, s) \ + (((av_alias##n*)(d))->u##n = ((const av_alias##n*)(s))->u##n) + +#ifndef AV_COPY16 +# define AV_COPY16(d, s) AV_COPY(16, d, s) +#endif + +#ifndef AV_COPY32 +# define AV_COPY32(d, s) AV_COPY(32, d, s) +#endif + +#ifndef AV_COPY64 +# define AV_COPY64(d, s) AV_COPY(64, d, s) +#endif + +#ifndef AV_COPY128 +# define AV_COPY128(d, s) \ + do { \ + AV_COPY64(d, s); \ + AV_COPY64((char*)(d)+8, (char*)(s)+8); \ + } while(0) +#endif + +#define AV_SWAP(n, a, b) FFSWAP(av_alias##n, *(av_alias##n*)(a), *(av_alias##n*)(b)) + +#ifndef AV_SWAP64 +# define AV_SWAP64(a, b) AV_SWAP(64, a, b) +#endif + +#define AV_ZERO(n, d) (((av_alias##n*)(d))->u##n = 0) + +#ifndef AV_ZERO16 +# define AV_ZERO16(d) AV_ZERO(16, d) +#endif + +#ifndef AV_ZERO32 +# define AV_ZERO32(d) AV_ZERO(32, d) +#endif + +#ifndef AV_ZERO64 +# define AV_ZERO64(d) AV_ZERO(64, d) +#endif + +#ifndef AV_ZERO128 +# define AV_ZERO128(d) \ + do { \ + AV_ZERO64(d); \ + AV_ZERO64((char*)(d)+8); \ + } while(0) +#endif + +#endif /* AVUTIL_INTREADWRITE_H */ diff --git a/dreamcast/pvrtex/libavutil/lfg.c b/dreamcast/pvrtex/libavutil/lfg.c new file mode 100644 index 00000000..46b04d24 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/lfg.c @@ -0,0 +1,87 @@ +/* + * Lagged Fibonacci PRNG + * Copyright (c) 2008 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include "lfg.h" +#include "crc.h" +#include "md5.h" +#include "error.h" +#include "intreadwrite.h" +#include "attributes.h" + +av_cold void av_lfg_init(AVLFG *c, unsigned int seed) +{ + uint8_t tmp[16] = { 0 }; + int i; + + for (i = 8; i < 64; i += 4) { + AV_WL32(tmp, seed); + tmp[4] = i; + av_md5_sum(tmp, tmp, 16); + c->state[i ] = AV_RL32(tmp); + c->state[i + 1] = AV_RL32(tmp + 4); + c->state[i + 2] = AV_RL32(tmp + 8); + c->state[i + 3] = AV_RL32(tmp + 12); + } + c->index = 0; +} + +void av_bmg_get(AVLFG *lfg, double out[2]) +{ + double x1, x2, w; + + do { + x1 = 2.0 / UINT_MAX * av_lfg_get(lfg) - 1.0; + x2 = 2.0 / UINT_MAX * av_lfg_get(lfg) - 1.0; + w = x1 * x1 + x2 * x2; + } while (w >= 1.0); + + w = sqrt((-2.0 * log(w)) / w); + out[0] = x1 * w; + out[1] = x2 * w; +} + +int av_lfg_init_from_data(AVLFG *c, const uint8_t *data, unsigned int length) { + unsigned int beg, end, segm; + const AVCRC *avcrc; + uint32_t crc = 1; + + /* avoid integer overflow in the loop below. */ + if (length > (UINT_MAX / 128U)) return AVERROR(EINVAL); + + c->index = 0; + avcrc = av_crc_get_table(AV_CRC_32_IEEE); /* This can't fail. It's a well-defined table in crc.c */ + + /* across 64 segments of the incoming data, + * do a running crc of each segment and store the crc as the state for that slot. + * this works even if the length of the segment is 0 bytes. */ + beg = 0; + for (segm = 0;segm < 64;segm++) { + end = (((segm + 1) * length) / 64); + crc = av_crc(avcrc, crc, data + beg, end - beg); + c->state[segm] = (unsigned int)crc; + beg = end; + } + + return 0; +} diff --git a/dreamcast/pvrtex/libavutil/lfg.h b/dreamcast/pvrtex/libavutil/lfg.h new file mode 100644 index 00000000..e75a986f --- /dev/null +++ b/dreamcast/pvrtex/libavutil/lfg.h @@ -0,0 +1,81 @@ +/* + * Lagged Fibonacci PRNG + * Copyright (c) 2008 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_LFG_H +#define AVUTIL_LFG_H + +#include + +/** + * Context structure for the Lagged Fibonacci PRNG. + * The exact layout, types and content of this struct may change and should + * not be accessed directly. Only its `sizeof()` is guaranteed to stay the same + * to allow easy instanciation. + */ +typedef struct AVLFG { + unsigned int state[64]; + int index; +} AVLFG; + +void av_lfg_init(AVLFG *c, unsigned int seed); + +/** + * Seed the state of the ALFG using binary data. + * + * @return 0 on success, negative value (AVERROR) on failure. + */ +int av_lfg_init_from_data(AVLFG *c, const uint8_t *data, unsigned int length); + +/** + * Get the next random unsigned 32-bit number using an ALFG. + * + * Please also consider a simple LCG like state= state*1664525+1013904223, + * it may be good enough and faster for your specific use case. + */ +static inline unsigned int av_lfg_get(AVLFG *c){ + unsigned a = c->state[c->index & 63] = c->state[(c->index-24) & 63] + c->state[(c->index-55) & 63]; + c->index += 1U; + return a; +} + +/** + * Get the next random unsigned 32-bit number using a MLFG. + * + * Please also consider av_lfg_get() above, it is faster. + */ +static inline unsigned int av_mlfg_get(AVLFG *c){ + unsigned int a= c->state[(c->index-55) & 63]; + unsigned int b= c->state[(c->index-24) & 63]; + a = c->state[c->index & 63] = 2*a*b+a+b; + c->index += 1U; + return a; +} + +/** + * Get the next two numbers generated by a Box-Muller Gaussian + * generator using the random numbers issued by lfg. + * + * @param lfg pointer to the contex structure + * @param out array where the two generated numbers are placed + */ +void av_bmg_get(AVLFG *lfg, double out[2]); + +#endif /* AVUTIL_LFG_H */ diff --git a/dreamcast/pvrtex/libavutil/libm.h b/dreamcast/pvrtex/libavutil/libm.h new file mode 100644 index 00000000..e02fe344 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/libm.h @@ -0,0 +1,472 @@ +/* + * erf function: Copyright (c) 2006 John Maddock + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Replacements for frequently missing libm functions + */ + +#if 0 +//#ifndef AVUTIL_LIBM_H +#define AVUTIL_LIBM_H + +#include +#include "config.h" +#include "attributes.h" +#include "intfloat.h" +#include "mathematics.h" + +#if HAVE_MIPSFPU && HAVE_INLINE_ASM +#include "libavutil/mips/libm_mips.h" +#endif /* HAVE_MIPSFPU && HAVE_INLINE_ASM*/ + +#if !HAVE_ATANF +#undef atanf +#define atanf(x) ((float)atan(x)) +#endif /* HAVE_ATANF */ + +#if !HAVE_ATAN2F +#undef atan2f +#define atan2f(y, x) ((float)atan2(y, x)) +#endif /* HAVE_ATAN2F */ + +#if !HAVE_POWF +#undef powf +#define powf(x, y) ((float)pow(x, y)) +#endif /* HAVE_POWF */ + +#if !HAVE_CBRT +static av_always_inline double cbrt(double x) +{ + return x < 0 ? -pow(-x, 1.0 / 3.0) : pow(x, 1.0 / 3.0); +} +#endif /* HAVE_CBRT */ + +#if !HAVE_CBRTF +static av_always_inline float cbrtf(float x) +{ + return x < 0 ? -powf(-x, 1.0 / 3.0) : powf(x, 1.0 / 3.0); +} +#endif /* HAVE_CBRTF */ + +#if !HAVE_COPYSIGN +static av_always_inline double copysign(double x, double y) +{ + uint64_t vx = av_double2int(x); + uint64_t vy = av_double2int(y); + return av_int2double((vx & UINT64_C(0x7fffffffffffffff)) | (vy & UINT64_C(0x8000000000000000))); +} +#endif /* HAVE_COPYSIGN */ + +#if !HAVE_COSF +#undef cosf +#define cosf(x) ((float)cos(x)) +#endif /* HAVE_COSF */ + +#if !HAVE_ERF +static inline double ff_eval_poly(const double *coeff, int size, double x) { + double sum = coeff[size-1]; + int i; + for (i = size-2; i >= 0; --i) { + sum *= x; + sum += coeff[i]; + } + return sum; +} + +/** + * erf function + * Algorithm taken from the Boost project, source: + * http://www.boost.org/doc/libs/1_46_1/boost/math/special_functions/erf.hpp + * Use, modification and distribution are subject to the + * Boost Software License, Version 1.0 (see notice below). + * Boost Software License - Version 1.0 - August 17th, 2003 +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + */ +static inline double erf(double z) +{ +#ifndef FF_ARRAY_ELEMS +#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) +#endif + double result; + + /* handle the symmetry: erf(-x) = -erf(x) */ + if (z < 0) + return -erf(-z); + + /* branch based on range of z, and pick appropriate approximation */ + if (z == 0) + return 0; + else if (z < 1e-10) + return z * 1.125 + z * 0.003379167095512573896158903121545171688; + else if (z < 0.5) { + // Maximum Deviation Found: 1.561e-17 + // Expected Error Term: 1.561e-17 + // Maximum Relative Change in Control Points: 1.155e-04 + // Max Error found at double precision = 2.961182e-17 + + static const double y = 1.044948577880859375; + static const double p[] = { + 0.0834305892146531832907, + -0.338165134459360935041, + -0.0509990735146777432841, + -0.00772758345802133288487, + -0.000322780120964605683831, + }; + static const double q[] = { + 1, + 0.455004033050794024546, + 0.0875222600142252549554, + 0.00858571925074406212772, + 0.000370900071787748000569, + }; + double zz = z * z; + return z * (y + ff_eval_poly(p, FF_ARRAY_ELEMS(p), zz) / ff_eval_poly(q, FF_ARRAY_ELEMS(q), zz)); + } + /* here onwards compute erfc */ + else if (z < 1.5) { + // Maximum Deviation Found: 3.702e-17 + // Expected Error Term: 3.702e-17 + // Maximum Relative Change in Control Points: 2.845e-04 + // Max Error found at double precision = 4.841816e-17 + static const double y = 0.405935764312744140625; + static const double p[] = { + -0.098090592216281240205, + 0.178114665841120341155, + 0.191003695796775433986, + 0.0888900368967884466578, + 0.0195049001251218801359, + 0.00180424538297014223957, + }; + static const double q[] = { + 1, + 1.84759070983002217845, + 1.42628004845511324508, + 0.578052804889902404909, + 0.12385097467900864233, + 0.0113385233577001411017, + 0.337511472483094676155e-5, + }; + result = y + ff_eval_poly(p, FF_ARRAY_ELEMS(p), z - 0.5) / ff_eval_poly(q, FF_ARRAY_ELEMS(q), z - 0.5); + result *= exp(-z * z) / z; + return 1 - result; + } + else if (z < 2.5) { + // Max Error found at double precision = 6.599585e-18 + // Maximum Deviation Found: 3.909e-18 + // Expected Error Term: 3.909e-18 + // Maximum Relative Change in Control Points: 9.886e-05 + static const double y = 0.50672817230224609375; + static const double p[] = { + -0.0243500476207698441272, + 0.0386540375035707201728, + 0.04394818964209516296, + 0.0175679436311802092299, + 0.00323962406290842133584, + 0.000235839115596880717416, + }; + static const double q[] = { + 1, + 1.53991494948552447182, + 0.982403709157920235114, + 0.325732924782444448493, + 0.0563921837420478160373, + 0.00410369723978904575884, + }; + result = y + ff_eval_poly(p, FF_ARRAY_ELEMS(p), z - 1.5) / ff_eval_poly(q, FF_ARRAY_ELEMS(q), z - 1.5); + result *= exp(-z * z) / z; + return 1 - result; + } + else if (z < 4.5) { + // Maximum Deviation Found: 1.512e-17 + // Expected Error Term: 1.512e-17 + // Maximum Relative Change in Control Points: 2.222e-04 + // Max Error found at double precision = 2.062515e-17 + static const double y = 0.5405750274658203125; + static const double p[] = { + 0.00295276716530971662634, + 0.0137384425896355332126, + 0.00840807615555585383007, + 0.00212825620914618649141, + 0.000250269961544794627958, + 0.113212406648847561139e-4, + }; + static const double q[] = { + 1, + 1.04217814166938418171, + 0.442597659481563127003, + 0.0958492726301061423444, + 0.0105982906484876531489, + 0.000479411269521714493907, + }; + result = y + ff_eval_poly(p, FF_ARRAY_ELEMS(p), z - 3.5) / ff_eval_poly(q, FF_ARRAY_ELEMS(q), z - 3.5); + result *= exp(-z * z) / z; + return 1 - result; + } + /* differ from Boost here, the claim of underflow of erfc(x) past 5.8 is + * slightly incorrect, change to 5.92 + * (really somewhere between 5.9125 and 5.925 is when it saturates) */ + else if (z < 5.92) { + // Max Error found at double precision = 2.997958e-17 + // Maximum Deviation Found: 2.860e-17 + // Expected Error Term: 2.859e-17 + // Maximum Relative Change in Control Points: 1.357e-05 + static const double y = 0.5579090118408203125; + static const double p[] = { + 0.00628057170626964891937, + 0.0175389834052493308818, + -0.212652252872804219852, + -0.687717681153649930619, + -2.5518551727311523996, + -3.22729451764143718517, + -2.8175401114513378771, + }; + static const double q[] = { + 1, + 2.79257750980575282228, + 11.0567237927800161565, + 15.930646027911794143, + 22.9367376522880577224, + 13.5064170191802889145, + 5.48409182238641741584, + }; + result = y + ff_eval_poly(p, FF_ARRAY_ELEMS(p), 1 / z) / ff_eval_poly(q, FF_ARRAY_ELEMS(q), 1 / z); + result *= exp(-z * z) / z; + return 1 - result; + } + /* handle the nan case, but don't use isnan for max portability */ + else if (z != z) + return z; + /* finally return saturated result */ + else + return 1; +} +#endif /* HAVE_ERF */ + +#if !HAVE_EXPF +#undef expf +#define expf(x) ((float)exp(x)) +#endif /* HAVE_EXPF */ + +#if !HAVE_EXP2 +#undef exp2 +#define exp2(x) exp((x) * M_LN2) +#endif /* HAVE_EXP2 */ + +#if !HAVE_EXP2F +#undef exp2f +#define exp2f(x) ((float)exp2(x)) +#endif /* HAVE_EXP2F */ + +#if !HAVE_ISINF +#undef isinf +/* Note: these do not follow the BSD/Apple/GNU convention of returning -1 for +-Inf, +1 for Inf, 0 otherwise, but merely follow the POSIX/ISO mandated spec of +returning a non-zero value for +/-Inf, 0 otherwise. */ +static av_always_inline av_const int avpriv_isinff(float x) +{ + uint32_t v = av_float2int(x); + if ((v & 0x7f800000) != 0x7f800000) + return 0; + return !(v & 0x007fffff); +} + +static av_always_inline av_const int avpriv_isinf(double x) +{ + uint64_t v = av_double2int(x); + if ((v & 0x7ff0000000000000) != 0x7ff0000000000000) + return 0; + return !(v & 0x000fffffffffffff); +} + +#define isinf(x) \ + (sizeof(x) == sizeof(float) \ + ? avpriv_isinff(x) \ + : avpriv_isinf(x)) +#endif /* HAVE_ISINF */ + +#if !HAVE_ISNAN +static av_always_inline av_const int avpriv_isnanf(float x) +{ + uint32_t v = av_float2int(x); + if ((v & 0x7f800000) != 0x7f800000) + return 0; + return v & 0x007fffff; +} + +static av_always_inline av_const int avpriv_isnan(double x) +{ + uint64_t v = av_double2int(x); + if ((v & 0x7ff0000000000000) != 0x7ff0000000000000) + return 0; + return (v & 0x000fffffffffffff) && 1; +} + +#define isnan(x) \ + (sizeof(x) == sizeof(float) \ + ? avpriv_isnanf(x) \ + : avpriv_isnan(x)) +#endif /* HAVE_ISNAN */ + +#if !HAVE_ISFINITE +static av_always_inline av_const int avpriv_isfinitef(float x) +{ + uint32_t v = av_float2int(x); + return (v & 0x7f800000) != 0x7f800000; +} + +static av_always_inline av_const int avpriv_isfinite(double x) +{ + uint64_t v = av_double2int(x); + return (v & 0x7ff0000000000000) != 0x7ff0000000000000; +} + +#define isfinite(x) \ + (sizeof(x) == sizeof(float) \ + ? avpriv_isfinitef(x) \ + : avpriv_isfinite(x)) +#endif /* HAVE_ISFINITE */ + +#if !HAVE_HYPOT +static inline av_const double hypot(double x, double y) +{ + double ret, temp; + x = fabs(x); + y = fabs(y); + + if (isinf(x) || isinf(y)) + return av_int2double(0x7ff0000000000000); + if (x == 0 || y == 0) + return x + y; + if (x < y) { + temp = x; + x = y; + y = temp; + } + + y = y/x; + return x*sqrt(1 + y*y); +} +#endif /* HAVE_HYPOT */ + +#if !HAVE_LDEXPF +#undef ldexpf +#define ldexpf(x, exp) ((float)ldexp(x, exp)) +#endif /* HAVE_LDEXPF */ + +#if !HAVE_LLRINT +#undef llrint +#define llrint(x) ((long long)rint(x)) +#endif /* HAVE_LLRINT */ + +#if !HAVE_LLRINTF +#undef llrintf +#define llrintf(x) ((long long)rint(x)) +#endif /* HAVE_LLRINT */ + +#if !HAVE_LOG2 +#undef log2 +#define log2(x) (log(x) * 1.44269504088896340736) +#endif /* HAVE_LOG2 */ + +#if !HAVE_LOG2F +#undef log2f +#define log2f(x) ((float)log2(x)) +#endif /* HAVE_LOG2F */ + +#if !HAVE_LOG10F +#undef log10f +#define log10f(x) ((float)log10(x)) +#endif /* HAVE_LOG10F */ + +#if !HAVE_SINF +#undef sinf +#define sinf(x) ((float)sin(x)) +#endif /* HAVE_SINF */ + +#if !HAVE_RINT +static inline double rint(double x) +{ + return x >= 0 ? floor(x + 0.5) : ceil(x - 0.5); +} +#endif /* HAVE_RINT */ + +#if !HAVE_LRINT +static av_always_inline av_const long int lrint(double x) +{ + return rint(x); +} +#endif /* HAVE_LRINT */ + +#if !HAVE_LRINTF +static av_always_inline av_const long int lrintf(float x) +{ + return (int)(rint(x)); +} +#endif /* HAVE_LRINTF */ + +#if !HAVE_ROUND +static av_always_inline av_const double round(double x) +{ + return (x > 0) ? floor(x + 0.5) : ceil(x - 0.5); +} +#endif /* HAVE_ROUND */ + +#if !HAVE_ROUNDF +static av_always_inline av_const float roundf(float x) +{ + return (x > 0) ? floor(x + 0.5) : ceil(x - 0.5); +} +#endif /* HAVE_ROUNDF */ + +#if !HAVE_TRUNC +static av_always_inline av_const double trunc(double x) +{ + return (x > 0) ? floor(x) : ceil(x); +} +#endif /* HAVE_TRUNC */ + +#if !HAVE_TRUNCF +static av_always_inline av_const float truncf(float x) +{ + return (x > 0) ? floor(x) : ceil(x); +} +#endif /* HAVE_TRUNCF */ + +#endif /* AVUTIL_LIBM_H */ diff --git a/dreamcast/pvrtex/libavutil/log.h b/dreamcast/pvrtex/libavutil/log.h new file mode 100644 index 00000000..ab7ceabe --- /dev/null +++ b/dreamcast/pvrtex/libavutil/log.h @@ -0,0 +1,387 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_LOG_H +#define AVUTIL_LOG_H + +#include +#include "attributes.h" +#include "version.h" + +typedef enum { + AV_CLASS_CATEGORY_NA = 0, + AV_CLASS_CATEGORY_INPUT, + AV_CLASS_CATEGORY_OUTPUT, + AV_CLASS_CATEGORY_MUXER, + AV_CLASS_CATEGORY_DEMUXER, + AV_CLASS_CATEGORY_ENCODER, + AV_CLASS_CATEGORY_DECODER, + AV_CLASS_CATEGORY_FILTER, + AV_CLASS_CATEGORY_BITSTREAM_FILTER, + AV_CLASS_CATEGORY_SWSCALER, + AV_CLASS_CATEGORY_SWRESAMPLER, + AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40, + AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT, + AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT, + AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT, + AV_CLASS_CATEGORY_DEVICE_OUTPUT, + AV_CLASS_CATEGORY_DEVICE_INPUT, + AV_CLASS_CATEGORY_NB ///< not part of ABI/API +}AVClassCategory; + +#define AV_IS_INPUT_DEVICE(category) \ + (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) || \ + ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT) || \ + ((category) == AV_CLASS_CATEGORY_DEVICE_INPUT)) + +#define AV_IS_OUTPUT_DEVICE(category) \ + (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT) || \ + ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT) || \ + ((category) == AV_CLASS_CATEGORY_DEVICE_OUTPUT)) + +struct AVOptionRanges; + +/** + * Describe the class of an AVClass context structure. That is an + * arbitrary struct of which the first field is a pointer to an + * AVClass struct (e.g. AVCodecContext, AVFormatContext etc.). + */ +typedef struct AVClass { + /** + * The name of the class; usually it is the same name as the + * context structure type to which the AVClass is associated. + */ + const char* class_name; + + /** + * A pointer to a function which returns the name of a context + * instance ctx associated with the class. + */ + const char* (*item_name)(void* ctx); + + /** + * a pointer to the first option specified in the class if any or NULL + * + * @see av_set_default_options() + */ + const struct AVOption *option; + + /** + * LIBAVUTIL_VERSION with which this structure was created. + * This is used to allow fields to be added without requiring major + * version bumps everywhere. + */ + + int version; + + /** + * Offset in the structure where log_level_offset is stored. + * 0 means there is no such variable + */ + int log_level_offset_offset; + + /** + * Offset in the structure where a pointer to the parent context for + * logging is stored. For example a decoder could pass its AVCodecContext + * to eval as such a parent context, which an av_log() implementation + * could then leverage to display the parent context. + * The offset can be NULL. + */ + int parent_log_context_offset; + + /** + * Category used for visualization (like color) + * This is only set if the category is equal for all objects using this class. + * available since version (51 << 16 | 56 << 8 | 100) + */ + AVClassCategory category; + + /** + * Callback to return the category. + * available since version (51 << 16 | 59 << 8 | 100) + */ + AVClassCategory (*get_category)(void* ctx); + + /** + * Callback to return the supported/allowed ranges. + * available since version (52.12) + */ + int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags); + + /** + * Return next AVOptions-enabled child or NULL + */ + void* (*child_next)(void *obj, void *prev); + + /** + * Iterate over the AVClasses corresponding to potential AVOptions-enabled + * children. + * + * @param iter pointer to opaque iteration state. The caller must initialize + * *iter to NULL before the first call. + * @return AVClass for the next AVOptions-enabled child or NULL if there are + * no more such children. + * + * @note The difference between child_next and this is that child_next + * iterates over _already existing_ objects, while child_class_iterate + * iterates over _all possible_ children. + */ + const struct AVClass* (*child_class_iterate)(void **iter); +} AVClass; + +/** + * @addtogroup lavu_log + * + * @{ + * + * @defgroup lavu_log_constants Logging Constants + * + * @{ + */ + +/** + * Print no output. + */ +#define AV_LOG_QUIET -8 + +/** + * Something went really wrong and we will crash now. + */ +#define AV_LOG_PANIC 0 + +/** + * Something went wrong and recovery is not possible. + * For example, no header was found for a format which depends + * on headers or an illegal combination of parameters is used. + */ +#define AV_LOG_FATAL 8 + +/** + * Something went wrong and cannot losslessly be recovered. + * However, not all future data is affected. + */ +#define AV_LOG_ERROR 16 + +/** + * Something somehow does not look correct. This may or may not + * lead to problems. An example would be the use of '-vstrict -2'. + */ +#define AV_LOG_WARNING 24 + +/** + * Standard information. + */ +#define AV_LOG_INFO 32 + +/** + * Detailed information. + */ +#define AV_LOG_VERBOSE 40 + +/** + * Stuff which is only useful for libav* developers. + */ +#define AV_LOG_DEBUG 48 + +/** + * Extremely verbose debugging, useful for libav* development. + */ +#define AV_LOG_TRACE 56 + +#define AV_LOG_MAX_OFFSET (AV_LOG_TRACE - AV_LOG_QUIET) + +/** + * @} + */ + +/** + * Sets additional colors for extended debugging sessions. + * @code + av_log(ctx, AV_LOG_DEBUG|AV_LOG_C(134), "Message in purple\n"); + @endcode + * Requires 256color terminal support. Uses outside debugging is not + * recommended. + */ +#define AV_LOG_C(x) ((x) << 8) + +/** + * Send the specified message to the log if the level is less than or equal + * to the current av_log_level. By default, all logging messages are sent to + * stderr. This behavior can be altered by setting a different logging callback + * function. + * @see av_log_set_callback + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct or NULL if general log. + * @param level The importance level of the message expressed using a @ref + * lavu_log_constants "Logging Constant". + * @param fmt The format string (printf-compatible) that specifies how + * subsequent arguments are converted to output. + */ +void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4); + +/** + * Send the specified message to the log once with the initial_level and then with + * the subsequent_level. By default, all logging messages are sent to + * stderr. This behavior can be altered by setting a different logging callback + * function. + * @see av_log + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct or NULL if general log. + * @param initial_level importance level of the message expressed using a @ref + * lavu_log_constants "Logging Constant" for the first occurance. + * @param subsequent_level importance level of the message expressed using a @ref + * lavu_log_constants "Logging Constant" after the first occurance. + * @param fmt The format string (printf-compatible) that specifies how + * subsequent arguments are converted to output. + * @param state a variable to keep trak of if a message has already been printed + * this must be initialized to 0 before the first use. The same state + * must not be accessed by 2 Threads simultaneously. + */ +void av_log_once(void* avcl, int initial_level, int subsequent_level, int *state, const char *fmt, ...) av_printf_format(5, 6); + + +/** + * Send the specified message to the log if the level is less than or equal + * to the current av_log_level. By default, all logging messages are sent to + * stderr. This behavior can be altered by setting a different logging callback + * function. + * @see av_log_set_callback + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct. + * @param level The importance level of the message expressed using a @ref + * lavu_log_constants "Logging Constant". + * @param fmt The format string (printf-compatible) that specifies how + * subsequent arguments are converted to output. + * @param vl The arguments referenced by the format string. + */ +void av_vlog(void *avcl, int level, const char *fmt, va_list vl); + +/** + * Get the current log level + * + * @see lavu_log_constants + * + * @return Current log level + */ +int av_log_get_level(void); + +/** + * Set the log level + * + * @see lavu_log_constants + * + * @param level Logging level + */ +void av_log_set_level(int level); + +/** + * Set the logging callback + * + * @note The callback must be thread safe, even if the application does not use + * threads itself as some codecs are multithreaded. + * + * @see av_log_default_callback + * + * @param callback A logging function with a compatible signature. + */ +void av_log_set_callback(void (*callback)(void*, int, const char*, va_list)); + +/** + * Default logging callback + * + * It prints the message to stderr, optionally colorizing it. + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct. + * @param level The importance level of the message expressed using a @ref + * lavu_log_constants "Logging Constant". + * @param fmt The format string (printf-compatible) that specifies how + * subsequent arguments are converted to output. + * @param vl The arguments referenced by the format string. + */ +void av_log_default_callback(void *avcl, int level, const char *fmt, + va_list vl); + +/** + * Return the context name + * + * @param ctx The AVClass context + * + * @return The AVClass class_name + */ +const char* av_default_item_name(void* ctx); +AVClassCategory av_default_get_category(void *ptr); + +/** + * Format a line of log the same way as the default callback. + * @param line buffer to receive the formatted line + * @param line_size size of the buffer + * @param print_prefix used to store whether the prefix must be printed; + * must point to a persistent integer initially set to 1 + */ +void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl, + char *line, int line_size, int *print_prefix); + +/** + * Format a line of log the same way as the default callback. + * @param line buffer to receive the formatted line; + * may be NULL if line_size is 0 + * @param line_size size of the buffer; at most line_size-1 characters will + * be written to the buffer, plus one null terminator + * @param print_prefix used to store whether the prefix must be printed; + * must point to a persistent integer initially set to 1 + * @return Returns a negative value if an error occurred, otherwise returns + * the number of characters that would have been written for a + * sufficiently large buffer, not including the terminating null + * character. If the return value is not less than line_size, it means + * that the log message was truncated to fit the buffer. + */ +int av_log_format_line2(void *ptr, int level, const char *fmt, va_list vl, + char *line, int line_size, int *print_prefix); + +/** + * Skip repeated messages, this requires the user app to use av_log() instead of + * (f)printf as the 2 would otherwise interfere and lead to + * "Last message repeated x times" messages below (f)printf messages with some + * bad luck. + * Also to receive the last, "last repeated" line if any, the user app must + * call av_log(NULL, AV_LOG_QUIET, "%s", ""); at the end + */ +#define AV_LOG_SKIP_REPEATED 1 + +/** + * Include the log severity in messages originating from codecs. + * + * Results in messages such as: + * [rawvideo @ 0xDEADBEEF] [error] encode did not produce valid pts + */ +#define AV_LOG_PRINT_LEVEL 2 + +void av_log_set_flags(int arg); +int av_log_get_flags(void); + +/** + * @} + */ + +#endif /* AVUTIL_LOG_H */ diff --git a/dreamcast/pvrtex/libavutil/macros.h b/dreamcast/pvrtex/libavutil/macros.h new file mode 100644 index 00000000..2a7567c3 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/macros.h @@ -0,0 +1,80 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu + * Utility Preprocessor macros + */ + +#ifndef AVUTIL_MACROS_H +#define AVUTIL_MACROS_H + +#include "libavutil/avconfig.h" + +#if AV_HAVE_BIGENDIAN +# define AV_NE(be, le) (be) +#else +# define AV_NE(be, le) (le) +#endif + +/** + * Comparator. + * For two numerical expressions x and y, gives 1 if x > y, -1 if x < y, and 0 + * if x == y. This is useful for instance in a qsort comparator callback. + * Furthermore, compilers are able to optimize this to branchless code, and + * there is no risk of overflow with signed types. + * As with many macros, this evaluates its argument multiple times, it thus + * must not have a side-effect. + */ +#define FFDIFFSIGN(x,y) (((x)>(y)) - ((x)<(y))) + +#define FFMAX(a,b) ((a) > (b) ? (a) : (b)) +#define FFMAX3(a,b,c) FFMAX(FFMAX(a,b),c) +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) +#define FFMIN3(a,b,c) FFMIN(FFMIN(a,b),c) + +#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0) +#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) + +#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24)) +#define MKBETAG(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24)) + +/** + * @addtogroup preproc_misc Preprocessor String Macros + * + * String manipulation macros + * + * @{ + */ + +#define AV_STRINGIFY(s) AV_TOSTRING(s) +#define AV_TOSTRING(s) #s + +#define AV_GLUE(a, b) a ## b +#define AV_JOIN(a, b) AV_GLUE(a, b) + +/** + * @} + */ + +#define AV_PRAGMA(s) _Pragma(#s) + +#define FFALIGN(x, a) (((x)+(a)-1)&~((a)-1)) + +#endif /* AVUTIL_MACROS_H */ diff --git a/dreamcast/pvrtex/libavutil/mathematics.c b/dreamcast/pvrtex/libavutil/mathematics.c new file mode 100644 index 00000000..61aeb7c0 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/mathematics.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2005-2012 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * miscellaneous math routines and tables + */ + +#include +#include + +#include "avutil.h" +#include "mathematics.h" +#include "libavutil/intmath.h" +#include "libavutil/common.h" +#include "avassert.h" + +/* Stein's binary GCD algorithm: + * https://en.wikipedia.org/wiki/Binary_GCD_algorithm */ +int64_t av_gcd(int64_t a, int64_t b) { + int za, zb, k; + int64_t u, v; + if (a == 0) + return b; + if (b == 0) + return a; + za = ff_ctzll(a); + zb = ff_ctzll(b); + k = FFMIN(za, zb); + u = llabs(a >> za); + v = llabs(b >> zb); + while (u != v) { + if (u > v) + FFSWAP(int64_t, v, u); + v -= u; + v >>= ff_ctzll(v); + } + return (uint64_t)u << k; +} + +int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) +{ + int64_t r = 0; + av_assert2(c > 0); + av_assert2(b >=0); + av_assert2((unsigned)(rnd&~AV_ROUND_PASS_MINMAX)<=5 && (rnd&~AV_ROUND_PASS_MINMAX)!=4); + + if (c <= 0 || b < 0 || !((unsigned)(rnd&~AV_ROUND_PASS_MINMAX)<=5 && (rnd&~AV_ROUND_PASS_MINMAX)!=4)) + return INT64_MIN; + + if (rnd & AV_ROUND_PASS_MINMAX) { + if (a == INT64_MIN || a == INT64_MAX) + return a; + rnd -= AV_ROUND_PASS_MINMAX; + } + + if (a < 0) + return -(uint64_t)av_rescale_rnd(-FFMAX(a, -INT64_MAX), b, c, rnd ^ ((rnd >> 1) & 1)); + + if (rnd == AV_ROUND_NEAR_INF) + r = c / 2; + else if (rnd & 1) + r = c - 1; + + if (b <= INT_MAX && c <= INT_MAX) { + if (a <= INT_MAX) + return (a * b + r) / c; + else { + int64_t ad = a / c; + int64_t a2 = (a % c * b + r) / c; + if (ad >= INT32_MAX && b && ad > (INT64_MAX - a2) / b) + return INT64_MIN; + return ad * b + a2; + } + } else { +#if 1 + uint64_t a0 = a & 0xFFFFFFFF; + uint64_t a1 = a >> 32; + uint64_t b0 = b & 0xFFFFFFFF; + uint64_t b1 = b >> 32; + uint64_t t1 = a0 * b1 + a1 * b0; + uint64_t t1a = t1 << 32; + int i; + + a0 = a0 * b0 + t1a; + a1 = a1 * b1 + (t1 >> 32) + (a0 < t1a); + a0 += r; + a1 += a0 < r; + + for (i = 63; i >= 0; i--) { + a1 += a1 + ((a0 >> i) & 1); + t1 += t1; + if (c <= a1) { + a1 -= c; + t1++; + } + } + if (t1 > INT64_MAX) + return INT64_MIN; + return t1; +#else + /* reference code doing (a*b + r) / c, requires libavutil/integer.h */ + AVInteger ai; + ai = av_mul_i(av_int2i(a), av_int2i(b)); + ai = av_add_i(ai, av_int2i(r)); + + return av_i2int(av_div_i(ai, av_int2i(c))); +#endif + } +} + +int64_t av_rescale(int64_t a, int64_t b, int64_t c) +{ + return av_rescale_rnd(a, b, c, AV_ROUND_NEAR_INF); +} + +int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq, + enum AVRounding rnd) +{ + int64_t b = bq.num * (int64_t)cq.den; + int64_t c = cq.num * (int64_t)bq.den; + return av_rescale_rnd(a, b, c, rnd); +} + +int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) +{ + return av_rescale_q_rnd(a, bq, cq, AV_ROUND_NEAR_INF); +} + +int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b) +{ + int64_t a = tb_a.num * (int64_t)tb_b.den; + int64_t b = tb_b.num * (int64_t)tb_a.den; + if ((FFABS64U(ts_a)|a|FFABS64U(ts_b)|b) <= INT_MAX) + return (ts_a*a > ts_b*b) - (ts_a*a < ts_b*b); + if (av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b) + return -1; + if (av_rescale_rnd(ts_b, b, a, AV_ROUND_DOWN) < ts_a) + return 1; + return 0; +} + +int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod) +{ + int64_t c = (a - b) & (mod - 1); + if (c > (mod >> 1)) + c -= mod; + return c; +} + +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb){ + int64_t a, b, this; + + av_assert0(in_ts != AV_NOPTS_VALUE); + av_assert0(duration >= 0); + + if (*last == AV_NOPTS_VALUE || !duration || in_tb.num*(int64_t)out_tb.den <= out_tb.num*(int64_t)in_tb.den) { +simple_round: + *last = av_rescale_q(in_ts, in_tb, fs_tb) + duration; + return av_rescale_q(in_ts, in_tb, out_tb); + } + + a = av_rescale_q_rnd(2*in_ts-1, in_tb, fs_tb, AV_ROUND_DOWN) >>1; + b = (av_rescale_q_rnd(2*in_ts+1, in_tb, fs_tb, AV_ROUND_UP )+1)>>1; + if (*last < 2*a - b || *last > 2*b - a) + goto simple_round; + + this = av_clip64(*last, a, b); + *last = this + duration; + + return av_rescale_q(this, fs_tb, out_tb); +} + +int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc) +{ + int64_t m, d; + + if (inc != 1) + inc_tb = av_mul_q(inc_tb, (AVRational) {inc, 1}); + + m = inc_tb.num * (int64_t)ts_tb.den; + d = inc_tb.den * (int64_t)ts_tb.num; + + if (m % d == 0 && ts <= INT64_MAX - m / d) + return ts + m / d; + if (m < d) + return ts; + + { + int64_t old = av_rescale_q(ts, ts_tb, inc_tb); + int64_t old_ts = av_rescale_q(old, inc_tb, ts_tb); + + if (old == INT64_MAX || old == AV_NOPTS_VALUE || old_ts == AV_NOPTS_VALUE) + return ts; + + return av_sat_add64(av_rescale_q(old + 1, inc_tb, ts_tb), ts - old_ts); + } +} + +static inline double eval_poly(const double *coeff, int size, double x) { + double sum = coeff[size-1]; + int i; + for (i = size-2; i >= 0; --i) { + sum *= x; + sum += coeff[i]; + } + return sum; +} + +/** + * 0th order modified bessel function of the first kind. + * Algorithm taken from the Boost project, source: + * https://searchcode.com/codesearch/view/14918379/ + * Use, modification and distribution are subject to the + * Boost Software License, Version 1.0 (see notice below). + * Boost Software License - Version 1.0 - August 17th, 2003 +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + */ + +double av_bessel_i0(double x) { +// Modified Bessel function of the first kind of order zero +// minimax rational approximations on intervals, see +// Blair and Edwards, Chalk River Report AECL-4928, 1974 + static const double p1[] = { + -2.2335582639474375249e+15, + -5.5050369673018427753e+14, + -3.2940087627407749166e+13, + -8.4925101247114157499e+11, + -1.1912746104985237192e+10, + -1.0313066708737980747e+08, + -5.9545626019847898221e+05, + -2.4125195876041896775e+03, + -7.0935347449210549190e+00, + -1.5453977791786851041e-02, + -2.5172644670688975051e-05, + -3.0517226450451067446e-08, + -2.6843448573468483278e-11, + -1.5982226675653184646e-14, + -5.2487866627945699800e-18, + }; + static const double q1[] = { + -2.2335582639474375245e+15, + 7.8858692566751002988e+12, + -1.2207067397808979846e+10, + 1.0377081058062166144e+07, + -4.8527560179962773045e+03, + 1.0, + }; + static const double p2[] = { + -2.2210262233306573296e-04, + 1.3067392038106924055e-02, + -4.4700805721174453923e-01, + 5.5674518371240761397e+00, + -2.3517945679239481621e+01, + 3.1611322818701131207e+01, + -9.6090021968656180000e+00, + }; + static const double q2[] = { + -5.5194330231005480228e-04, + 3.2547697594819615062e-02, + -1.1151759188741312645e+00, + 1.3982595353892851542e+01, + -6.0228002066743340583e+01, + 8.5539563258012929600e+01, + -3.1446690275135491500e+01, + 1.0, + }; + double y, r, factor; + if (x == 0) + return 1.0; + x = fabs(x); + if (x <= 15) { + y = x * x; + return eval_poly(p1, FF_ARRAY_ELEMS(p1), y) / eval_poly(q1, FF_ARRAY_ELEMS(q1), y); + } + else { + y = 1 / x - 1.0 / 15; + r = eval_poly(p2, FF_ARRAY_ELEMS(p2), y) / eval_poly(q2, FF_ARRAY_ELEMS(q2), y); + factor = exp(x) / sqrt(x); + return factor * r; + } +} diff --git a/dreamcast/pvrtex/libavutil/mathematics.h b/dreamcast/pvrtex/libavutil/mathematics.h new file mode 100644 index 00000000..e213bab6 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/mathematics.h @@ -0,0 +1,300 @@ +/* + * copyright (c) 2005-2012 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @addtogroup lavu_math + * Mathematical utilities for working with timestamp and time base. + */ + +#ifndef AVUTIL_MATHEMATICS_H +#define AVUTIL_MATHEMATICS_H + +#include +#include +#include "attributes.h" +#include "rational.h" +#include "intfloat.h" + +#ifndef M_E +#define M_E 2.7182818284590452354 /* e */ +#endif +#ifndef M_Ef +#define M_Ef 2.7182818284590452354f /* e */ +#endif +#ifndef M_LN2 +#define M_LN2 0.69314718055994530942 /* log_e 2 */ +#endif +#ifndef M_LN2f +#define M_LN2f 0.69314718055994530942f /* log_e 2 */ +#endif +#ifndef M_LN10 +#define M_LN10 2.30258509299404568402 /* log_e 10 */ +#endif +#ifndef M_LN10f +#define M_LN10f 2.30258509299404568402f /* log_e 10 */ +#endif +#ifndef M_LOG2_10 +#define M_LOG2_10 3.32192809488736234787 /* log_2 10 */ +#endif +#ifndef M_LOG2_10f +#define M_LOG2_10f 3.32192809488736234787f /* log_2 10 */ +#endif +#ifndef M_PHI +#define M_PHI 1.61803398874989484820 /* phi / golden ratio */ +#endif +#ifndef M_PHIf +#define M_PHIf 1.61803398874989484820f /* phi / golden ratio */ +#endif +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif +#ifndef M_PIf +#define M_PIf 3.14159265358979323846f /* pi */ +#endif +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif +#ifndef M_PI_2f +#define M_PI_2f 1.57079632679489661923f /* pi/2 */ +#endif +#ifndef M_PI_4 +#define M_PI_4 0.78539816339744830962 /* pi/4 */ +#endif +#ifndef M_PI_4f +#define M_PI_4f 0.78539816339744830962f /* pi/4 */ +#endif +#ifndef M_1_PI +#define M_1_PI 0.31830988618379067154 /* 1/pi */ +#endif +#ifndef M_1_PIf +#define M_1_PIf 0.31830988618379067154f /* 1/pi */ +#endif +#ifndef M_2_PI +#define M_2_PI 0.63661977236758134308 /* 2/pi */ +#endif +#ifndef M_2_PIf +#define M_2_PIf 0.63661977236758134308f /* 2/pi */ +#endif +#ifndef M_2_SQRTPI +#define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ +#endif +#ifndef M_2_SQRTPIf +#define M_2_SQRTPIf 1.12837916709551257390f /* 2/sqrt(pi) */ +#endif +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ +#endif +#ifndef M_SQRT1_2f +#define M_SQRT1_2f 0.70710678118654752440f /* 1/sqrt(2) */ +#endif +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif +#ifndef M_SQRT2f +#define M_SQRT2f 1.41421356237309504880f /* sqrt(2) */ +#endif +#ifndef NAN +#define NAN av_int2float(0x7fc00000) +#endif +#ifndef INFINITY +#define INFINITY av_int2float(0x7f800000) +#endif + +/** + * @addtogroup lavu_math + * + * @{ + */ + +/** + * Rounding methods. + */ +enum AVRounding { + AV_ROUND_ZERO = 0, ///< Round toward zero. + AV_ROUND_INF = 1, ///< Round away from zero. + AV_ROUND_DOWN = 2, ///< Round toward -infinity. + AV_ROUND_UP = 3, ///< Round toward +infinity. + AV_ROUND_NEAR_INF = 5, ///< Round to nearest and halfway cases away from zero. + /** + * Flag telling rescaling functions to pass `INT64_MIN`/`MAX` through + * unchanged, avoiding special cases for #AV_NOPTS_VALUE. + * + * Unlike other values of the enumeration AVRounding, this value is a + * bitmask that must be used in conjunction with another value of the + * enumeration through a bitwise OR, in order to set behavior for normal + * cases. + * + * @code{.c} + * av_rescale_rnd(3, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX); + * // Rescaling 3: + * // Calculating 3 * 1 / 2 + * // 3 / 2 is rounded up to 2 + * // => 2 + * + * av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX); + * // Rescaling AV_NOPTS_VALUE: + * // AV_NOPTS_VALUE == INT64_MIN + * // AV_NOPTS_VALUE is passed through + * // => AV_NOPTS_VALUE + * @endcode + */ + AV_ROUND_PASS_MINMAX = 8192, +}; + +/** + * Compute the greatest common divisor of two integer operands. + * + * @param a Operand + * @param b Operand + * @return GCD of a and b up to sign; if a >= 0 and b >= 0, return value is >= 0; + * if a == 0 and b == 0, returns 0. + */ +int64_t av_const av_gcd(int64_t a, int64_t b); + +/** + * Rescale a 64-bit integer with rounding to nearest. + * + * The operation is mathematically equivalent to `a * b / c`, but writing that + * directly can overflow. + * + * This function is equivalent to av_rescale_rnd() with #AV_ROUND_NEAR_INF. + * + * @see av_rescale_rnd(), av_rescale_q(), av_rescale_q_rnd() + */ +int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const; + +/** + * Rescale a 64-bit integer with specified rounding. + * + * The operation is mathematically equivalent to `a * b / c`, but writing that + * directly can overflow, and does not support different rounding methods. + * If the result is not representable then INT64_MIN is returned. + * + * @see av_rescale(), av_rescale_q(), av_rescale_q_rnd() + */ +int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const; + +/** + * Rescale a 64-bit integer by 2 rational numbers. + * + * The operation is mathematically equivalent to `a * bq / cq`. + * + * This function is equivalent to av_rescale_q_rnd() with #AV_ROUND_NEAR_INF. + * + * @see av_rescale(), av_rescale_rnd(), av_rescale_q_rnd() + */ +int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const; + +/** + * Rescale a 64-bit integer by 2 rational numbers with specified rounding. + * + * The operation is mathematically equivalent to `a * bq / cq`. + * + * @see av_rescale(), av_rescale_rnd(), av_rescale_q() + */ +int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq, + enum AVRounding rnd) av_const; + +/** + * Compare two timestamps each in its own time base. + * + * @return One of the following values: + * - -1 if `ts_a` is before `ts_b` + * - 1 if `ts_a` is after `ts_b` + * - 0 if they represent the same position + * + * @warning + * The result of the function is undefined if one of the timestamps is outside + * the `int64_t` range when represented in the other's timebase. + */ +int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b); + +/** + * Compare the remainders of two integer operands divided by a common divisor. + * + * In other words, compare the least significant `log2(mod)` bits of integers + * `a` and `b`. + * + * @code{.c} + * av_compare_mod(0x11, 0x02, 0x10) < 0 // since 0x11 % 0x10 (0x1) < 0x02 % 0x10 (0x2) + * av_compare_mod(0x11, 0x02, 0x20) > 0 // since 0x11 % 0x20 (0x11) > 0x02 % 0x20 (0x02) + * @endcode + * + * @param a Operand + * @param b Operand + * @param mod Divisor; must be a power of 2 + * @return + * - a negative value if `a % mod < b % mod` + * - a positive value if `a % mod > b % mod` + * - zero if `a % mod == b % mod` + */ +int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod); + +/** + * Rescale a timestamp while preserving known durations. + * + * This function is designed to be called per audio packet to scale the input + * timestamp to a different time base. Compared to a simple av_rescale_q() + * call, this function is robust against possible inconsistent frame durations. + * + * The `last` parameter is a state variable that must be preserved for all + * subsequent calls for the same stream. For the first call, `*last` should be + * initialized to #AV_NOPTS_VALUE. + * + * @param[in] in_tb Input time base + * @param[in] in_ts Input timestamp + * @param[in] fs_tb Duration time base; typically this is finer-grained + * (greater) than `in_tb` and `out_tb` + * @param[in] duration Duration till the next call to this function (i.e. + * duration of the current packet/frame) + * @param[in,out] last Pointer to a timestamp expressed in terms of + * `fs_tb`, acting as a state variable + * @param[in] out_tb Output timebase + * @return Timestamp expressed in terms of `out_tb` + * + * @note In the context of this function, "duration" is in term of samples, not + * seconds. + */ +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb); + +/** + * Add a value to a timestamp. + * + * This function guarantees that when the same value is repeatly added that + * no accumulation of rounding errors occurs. + * + * @param[in] ts Input timestamp + * @param[in] ts_tb Input timestamp time base + * @param[in] inc Value to be added + * @param[in] inc_tb Time base of `inc` + */ +int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc); + +/** + * 0th order modified bessel function of the first kind. + */ +double av_bessel_i0(double x); + +/** + * @} + */ + +#endif /* AVUTIL_MATHEMATICS_H */ diff --git a/dreamcast/pvrtex/libavutil/md5.h b/dreamcast/pvrtex/libavutil/md5.h new file mode 100644 index 00000000..fc2eabdb --- /dev/null +++ b/dreamcast/pvrtex/libavutil/md5.h @@ -0,0 +1,89 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_md5 + * Public header for MD5 hash function implementation. + */ + +#ifndef AVUTIL_MD5_H +#define AVUTIL_MD5_H + +#include +#include + +#include "attributes.h" + +/** + * @defgroup lavu_md5 MD5 + * @ingroup lavu_hash + * MD5 hash function implementation. + * + * @{ + */ + +extern const int av_md5_size; + +struct AVMD5; + +/** + * Allocate an AVMD5 context. + */ +struct AVMD5 *av_md5_alloc(void); + +/** + * Initialize MD5 hashing. + * + * @param ctx pointer to the function context (of size av_md5_size) + */ +void av_md5_init(struct AVMD5 *ctx); + +/** + * Update hash value. + * + * @param ctx hash function context + * @param src input data to update hash with + * @param len input data length + */ +void av_md5_update(struct AVMD5 *ctx, const uint8_t *src, size_t len); + +/** + * Finish hashing and output digest value. + * + * @param ctx hash function context + * @param dst buffer where output digest value is stored + */ +void av_md5_final(struct AVMD5 *ctx, uint8_t *dst); + +/** + * Hash an array of data. + * + * @param dst The output buffer to write the digest into + * @param src The data to hash + * @param len The length of the data, in bytes + */ +void av_md5_sum(uint8_t *dst, const uint8_t *src, size_t len); + +/** + * @} + */ + +#endif /* AVUTIL_MD5_H */ diff --git a/dreamcast/pvrtex/libavutil/mem.c b/dreamcast/pvrtex/libavutil/mem.c new file mode 100644 index 00000000..36b8940a --- /dev/null +++ b/dreamcast/pvrtex/libavutil/mem.c @@ -0,0 +1,568 @@ +/* + * default memory allocator for libavutil + * Copyright (c) 2002 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * default memory allocator for libavutil + */ + +#define _XOPEN_SOURCE 600 + +#include "config.h" + +#include +#include +#include +#include +#include +#if HAVE_MALLOC_H +#include +#endif + +#include "attributes.h" +#include "avassert.h" +#include "dynarray.h" +#include "error.h" +#include "internal.h" +#include "intreadwrite.h" +#include "macros.h" +#include "mem.h" + +#ifdef MALLOC_PREFIX + +#define malloc AV_JOIN(MALLOC_PREFIX, malloc) +#define memalign AV_JOIN(MALLOC_PREFIX, memalign) +#define posix_memalign AV_JOIN(MALLOC_PREFIX, posix_memalign) +#define realloc AV_JOIN(MALLOC_PREFIX, realloc) +#define free AV_JOIN(MALLOC_PREFIX, free) + +void *malloc(size_t size); +void *memalign(size_t align, size_t size); +int posix_memalign(void **ptr, size_t align, size_t size); +void *realloc(void *ptr, size_t size); +void free(void *ptr); + +#endif /* MALLOC_PREFIX */ + +#define ALIGN (HAVE_AVX512 ? 64 : (HAVE_AVX ? 32 : 16)) + +/* NOTE: if you want to override these functions with your own + * implementations (not recommended) you have to link libav* as + * dynamic libraries and remove -Wl,-Bsymbolic from the linker flags. + * Note that this will cost performance. */ + +static atomic_size_t max_alloc_size = ATOMIC_VAR_INIT(INT_MAX); + +void av_max_alloc(size_t max){ + atomic_store_explicit(&max_alloc_size, max, memory_order_relaxed); +} + +static int size_mult(size_t a, size_t b, size_t *r) +{ + size_t t; + +#if (!defined(__INTEL_COMPILER) && AV_GCC_VERSION_AT_LEAST(5,1)) || AV_HAS_BUILTIN(__builtin_mul_overflow) + if (__builtin_mul_overflow(a, b, &t)) + return AVERROR(EINVAL); +#else + t = a * b; + /* Hack inspired from glibc: don't try the division if nelem and elsize + * are both less than sqrt(SIZE_MAX). */ + if ((a | b) >= ((size_t)1 << (sizeof(size_t) * 4)) && a && t / a != b) + return AVERROR(EINVAL); +#endif + *r = t; + return 0; +} + +void *av_malloc(size_t size) +{ + void *ptr = NULL; + + if (size > atomic_load_explicit(&max_alloc_size, memory_order_relaxed)) + return NULL; + +#if HAVE_POSIX_MEMALIGN + if (size) //OS X on SDK 10.6 has a broken posix_memalign implementation + if (posix_memalign(&ptr, ALIGN, size)) + ptr = NULL; +#elif HAVE_ALIGNED_MALLOC + ptr = _aligned_malloc(size, ALIGN); +#elif HAVE_MEMALIGN +#ifndef __DJGPP__ + ptr = memalign(ALIGN, size); +#else + ptr = memalign(size, ALIGN); +#endif + /* Why 64? + * Indeed, we should align it: + * on 4 for 386 + * on 16 for 486 + * on 32 for 586, PPro - K6-III + * on 64 for K7 (maybe for P3 too). + * Because L1 and L2 caches are aligned on those values. + * But I don't want to code such logic here! + */ + /* Why 32? + * For AVX ASM. SSE / NEON needs only 16. + * Why not larger? Because I did not see a difference in benchmarks ... + */ + /* benchmarks with P3 + * memalign(64) + 1 3071, 3051, 3032 + * memalign(64) + 2 3051, 3032, 3041 + * memalign(64) + 4 2911, 2896, 2915 + * memalign(64) + 8 2545, 2554, 2550 + * memalign(64) + 16 2543, 2572, 2563 + * memalign(64) + 32 2546, 2545, 2571 + * memalign(64) + 64 2570, 2533, 2558 + * + * BTW, malloc seems to do 8-byte alignment by default here. + */ +#else + ptr = malloc(size); +#endif + if(!ptr && !size) { + size = 1; + ptr= av_malloc(1); + } +#if CONFIG_MEMORY_POISONING + if (ptr) + memset(ptr, FF_MEMORY_POISON, size); +#endif + return ptr; +} + +void *av_realloc(void *ptr, size_t size) +{ + void *ret; + if (size > atomic_load_explicit(&max_alloc_size, memory_order_relaxed)) + return NULL; + +#if HAVE_ALIGNED_MALLOC + ret = _aligned_realloc(ptr, size + !size, ALIGN); +#else + ret = realloc(ptr, size + !size); +#endif +#if CONFIG_MEMORY_POISONING + if (ret && !ptr) + memset(ret, FF_MEMORY_POISON, size); +#endif + return ret; +} + +void *av_realloc_f(void *ptr, size_t nelem, size_t elsize) +{ + size_t size; + void *r; + + if (size_mult(elsize, nelem, &size)) { + av_free(ptr); + return NULL; + } + r = av_realloc(ptr, size); + if (!r) + av_free(ptr); + return r; +} + +int av_reallocp(void *ptr, size_t size) +{ + void *val; + + if (!size) { + av_freep(ptr); + return 0; + } + + memcpy(&val, ptr, sizeof(val)); + val = av_realloc(val, size); + + if (!val) { + av_freep(ptr); + return AVERROR(ENOMEM); + } + + memcpy(ptr, &val, sizeof(val)); + return 0; +} + +void *av_malloc_array(size_t nmemb, size_t size) +{ + size_t result; + if (size_mult(nmemb, size, &result) < 0) + return NULL; + return av_malloc(result); +} + +void *av_realloc_array(void *ptr, size_t nmemb, size_t size) +{ + size_t result; + if (size_mult(nmemb, size, &result) < 0) + return NULL; + return av_realloc(ptr, result); +} + +int av_reallocp_array(void *ptr, size_t nmemb, size_t size) +{ + void *val; + + memcpy(&val, ptr, sizeof(val)); + val = av_realloc_f(val, nmemb, size); + memcpy(ptr, &val, sizeof(val)); + if (!val && nmemb && size) + return AVERROR(ENOMEM); + + return 0; +} + +void av_free(void *ptr) +{ +#if HAVE_ALIGNED_MALLOC + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +void av_freep(void *arg) +{ + void *val; + + memcpy(&val, arg, sizeof(val)); + memcpy(arg, &(void *){ NULL }, sizeof(val)); + av_free(val); +} + +void *av_mallocz(size_t size) +{ + void *ptr = av_malloc(size); + if (ptr) + memset(ptr, 0, size); + return ptr; +} + +void *av_calloc(size_t nmemb, size_t size) +{ + size_t result; + if (size_mult(nmemb, size, &result) < 0) + return NULL; + return av_mallocz(result); +} + +char *av_strdup(const char *s) +{ + char *ptr = NULL; + if (s) { + size_t len = strlen(s) + 1; + ptr = av_realloc(NULL, len); + if (ptr) + memcpy(ptr, s, len); + } + return ptr; +} + +char *av_strndup(const char *s, size_t len) +{ + char *ret = NULL, *end; + + if (!s) + return NULL; + + end = memchr(s, 0, len); + if (end) + len = end - s; + + ret = av_realloc(NULL, len + 1); + if (!ret) + return NULL; + + memcpy(ret, s, len); + ret[len] = 0; + return ret; +} + +void *av_memdup(const void *p, size_t size) +{ + void *ptr = NULL; + if (p) { + ptr = av_malloc(size); + if (ptr) + memcpy(ptr, p, size); + } + return ptr; +} + +int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem) +{ + void **tab; + memcpy(&tab, tab_ptr, sizeof(tab)); + + FF_DYNARRAY_ADD(INT_MAX, sizeof(*tab), tab, *nb_ptr, { + tab[*nb_ptr] = elem; + memcpy(tab_ptr, &tab, sizeof(tab)); + }, { + return AVERROR(ENOMEM); + }); + return 0; +} + +void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem) +{ + void **tab; + memcpy(&tab, tab_ptr, sizeof(tab)); + + FF_DYNARRAY_ADD(INT_MAX, sizeof(*tab), tab, *nb_ptr, { + tab[*nb_ptr] = elem; + memcpy(tab_ptr, &tab, sizeof(tab)); + }, { + *nb_ptr = 0; + av_freep(tab_ptr); + }); +} + +void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size, + const uint8_t *elem_data) +{ + uint8_t *tab_elem_data = NULL; + + FF_DYNARRAY_ADD(INT_MAX, elem_size, *tab_ptr, *nb_ptr, { + tab_elem_data = (uint8_t *)*tab_ptr + (*nb_ptr) * elem_size; + if (elem_data) + memcpy(tab_elem_data, elem_data, elem_size); + else if (CONFIG_MEMORY_POISONING) + memset(tab_elem_data, FF_MEMORY_POISON, elem_size); + }, { + av_freep(tab_ptr); + *nb_ptr = 0; + }); + return tab_elem_data; +} + +static void fill16(uint8_t *dst, int len) +{ + uint32_t v = AV_RN16(dst - 2); + + v |= v << 16; + + while (len >= 4) { + AV_WN32(dst, v); + dst += 4; + len -= 4; + } + + while (len--) { + *dst = dst[-2]; + dst++; + } +} + +static void fill24(uint8_t *dst, int len) +{ +#if HAVE_BIGENDIAN + uint32_t v = AV_RB24(dst - 3); + uint32_t a = v << 8 | v >> 16; + uint32_t b = v << 16 | v >> 8; + uint32_t c = v << 24 | v; +#else + uint32_t v = AV_RL24(dst - 3); + uint32_t a = v | v << 24; + uint32_t b = v >> 8 | v << 16; + uint32_t c = v >> 16 | v << 8; +#endif + + while (len >= 12) { + AV_WN32(dst, a); + AV_WN32(dst + 4, b); + AV_WN32(dst + 8, c); + dst += 12; + len -= 12; + } + + if (len >= 4) { + AV_WN32(dst, a); + dst += 4; + len -= 4; + } + + if (len >= 4) { + AV_WN32(dst, b); + dst += 4; + len -= 4; + } + + while (len--) { + *dst = dst[-3]; + dst++; + } +} + +static void fill32(uint8_t *dst, int len) +{ + uint32_t v = AV_RN32(dst - 4); + +#if HAVE_FAST_64BIT + uint64_t v2= v + ((uint64_t)v<<32); + while (len >= 32) { + AV_WN64(dst , v2); + AV_WN64(dst+ 8, v2); + AV_WN64(dst+16, v2); + AV_WN64(dst+24, v2); + dst += 32; + len -= 32; + } +#endif + + while (len >= 4) { + AV_WN32(dst, v); + dst += 4; + len -= 4; + } + + while (len--) { + *dst = dst[-4]; + dst++; + } +} + +void av_memcpy_backptr(uint8_t *dst, int back, int cnt) +{ + const uint8_t *src = &dst[-back]; + if (!back) + return; + + if (back == 1) { + memset(dst, *src, cnt); + } else if (back == 2) { + fill16(dst, cnt); + } else if (back == 3) { + fill24(dst, cnt); + } else if (back == 4) { + fill32(dst, cnt); + } else { + if (cnt >= 16) { + int blocklen = back; + while (cnt > blocklen) { + memcpy(dst, src, blocklen); + dst += blocklen; + cnt -= blocklen; + blocklen <<= 1; + } + memcpy(dst, src, cnt); + return; + } + if (cnt >= 8) { + AV_COPY32U(dst, src); + AV_COPY32U(dst + 4, src + 4); + src += 8; + dst += 8; + cnt -= 8; + } + if (cnt >= 4) { + AV_COPY32U(dst, src); + src += 4; + dst += 4; + cnt -= 4; + } + if (cnt >= 2) { + AV_COPY16U(dst, src); + src += 2; + dst += 2; + cnt -= 2; + } + if (cnt) + *dst = *src; + } +} + +void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size) +{ + size_t max_size; + + if (min_size <= *size) + return ptr; + + max_size = atomic_load_explicit(&max_alloc_size, memory_order_relaxed); + /* *size is an unsigned, so the real maximum is <= UINT_MAX. */ + max_size = FFMIN(max_size, UINT_MAX); + + if (min_size > max_size) { + *size = 0; + return NULL; + } + + min_size = FFMIN(max_size, FFMAX(min_size + min_size / 16 + 32, min_size)); + + ptr = av_realloc(ptr, min_size); + /* we could set this to the unmodified min_size but this is safer + * if the user lost the ptr and uses NULL now + */ + if (!ptr) + min_size = 0; + + *size = min_size; + + return ptr; +} + +static inline void fast_malloc(void *ptr, unsigned int *size, size_t min_size, int zero_realloc) +{ + size_t max_size; + void *val; + + memcpy(&val, ptr, sizeof(val)); + if (min_size <= *size) { + av_assert0(val || !min_size); + return; + } + + max_size = atomic_load_explicit(&max_alloc_size, memory_order_relaxed); + /* *size is an unsigned, so the real maximum is <= UINT_MAX. */ + max_size = FFMIN(max_size, UINT_MAX); + + if (min_size > max_size) { + av_freep(ptr); + *size = 0; + return; + } + min_size = FFMIN(max_size, FFMAX(min_size + min_size / 16 + 32, min_size)); + av_freep(ptr); + val = zero_realloc ? av_mallocz(min_size) : av_malloc(min_size); + memcpy(ptr, &val, sizeof(val)); + if (!val) + min_size = 0; + *size = min_size; + return; +} + +void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size) +{ + fast_malloc(ptr, size, min_size, 0); +} + +void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size) +{ + fast_malloc(ptr, size, min_size, 1); +} + +int av_size_mult(size_t a, size_t b, size_t *r) +{ + return size_mult(a, b, r); +} diff --git a/dreamcast/pvrtex/libavutil/mem.h b/dreamcast/pvrtex/libavutil/mem.h new file mode 100644 index 00000000..62b4ca6e --- /dev/null +++ b/dreamcast/pvrtex/libavutil/mem.h @@ -0,0 +1,609 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_mem + * Memory handling functions + */ + +#ifndef AVUTIL_MEM_H +#define AVUTIL_MEM_H + +#include +#include + +#include "attributes.h" +#include "avutil.h" +#include "version.h" + +/** + * @addtogroup lavu_mem + * Utilities for manipulating memory. + * + * FFmpeg has several applications of memory that are not required of a typical + * program. For example, the computing-heavy components like video decoding and + * encoding can be sped up significantly through the use of aligned memory. + * + * However, for each of FFmpeg's applications of memory, there might not be a + * recognized or standardized API for that specific use. Memory alignment, for + * instance, varies wildly depending on operating systems, architectures, and + * compilers. Hence, this component of @ref libavutil is created to make + * dealing with memory consistently possible on all platforms. + * + * @{ + */ + +/** + * @defgroup lavu_mem_attrs Function Attributes + * Function attributes applicable to memory handling functions. + * + * These function attributes can help compilers emit more useful warnings, or + * generate better code. + * @{ + */ + +/** + * @def av_malloc_attrib + * Function attribute denoting a malloc-like function. + * + * @see Function attribute `malloc` in GCC's documentation + */ + +#if AV_GCC_VERSION_AT_LEAST(3,1) + #define av_malloc_attrib __attribute__((__malloc__)) +#else + #define av_malloc_attrib +#endif + +/** + * @def av_alloc_size(...) + * Function attribute used on a function that allocates memory, whose size is + * given by the specified parameter(s). + * + * @code{.c} + * void *av_malloc(size_t size) av_alloc_size(1); + * void *av_calloc(size_t nmemb, size_t size) av_alloc_size(1, 2); + * @endcode + * + * @param ... One or two parameter indexes, separated by a comma + * + * @see Function attribute `alloc_size` in GCC's documentation + */ + +#if AV_GCC_VERSION_AT_LEAST(4,3) + #define av_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) +#else + #define av_alloc_size(...) +#endif + +/** + * @} + */ + +/** + * @defgroup lavu_mem_funcs Heap Management + * Functions responsible for allocating, freeing, and copying memory. + * + * All memory allocation functions have a built-in upper limit of `INT_MAX` + * bytes. This may be changed with av_max_alloc(), although exercise extreme + * caution when doing so. + * + * @{ + */ + +/** + * Allocate a memory block with alignment suitable for all memory accesses + * (including vectors if available on the CPU). + * + * @param size Size in bytes for the memory block to be allocated + * @return Pointer to the allocated block, or `NULL` if the block cannot + * be allocated + * @see av_mallocz() + */ +void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1); + +/** + * Allocate a memory block with alignment suitable for all memory accesses + * (including vectors if available on the CPU) and zero all the bytes of the + * block. + * + * @param size Size in bytes for the memory block to be allocated + * @return Pointer to the allocated block, or `NULL` if it cannot be allocated + * @see av_malloc() + */ +void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1); + +/** + * Allocate a memory block for an array with av_malloc(). + * + * The allocated memory will have size `size * nmemb` bytes. + * + * @param nmemb Number of element + * @param size Size of a single element + * @return Pointer to the allocated block, or `NULL` if the block cannot + * be allocated + * @see av_malloc() + */ +av_alloc_size(1, 2) void *av_malloc_array(size_t nmemb, size_t size); + +/** + * Allocate a memory block for an array with av_mallocz(). + * + * The allocated memory will have size `size * nmemb` bytes. + * + * @param nmemb Number of elements + * @param size Size of the single element + * @return Pointer to the allocated block, or `NULL` if the block cannot + * be allocated + * + * @see av_mallocz() + * @see av_malloc_array() + */ +void *av_calloc(size_t nmemb, size_t size) av_malloc_attrib av_alloc_size(1, 2); + +/** + * Allocate, reallocate, or free a block of memory. + * + * If `ptr` is `NULL` and `size` > 0, allocate a new block. Otherwise, expand or + * shrink that block of memory according to `size`. + * + * @param ptr Pointer to a memory block already allocated with + * av_realloc() or `NULL` + * @param size Size in bytes of the memory block to be allocated or + * reallocated + * + * @return Pointer to a newly-reallocated block or `NULL` if the block + * cannot be reallocated + * + * @warning Unlike av_malloc(), the returned pointer is not guaranteed to be + * correctly aligned. The returned pointer must be freed after even + * if size is zero. + * @see av_fast_realloc() + * @see av_reallocp() + */ +void *av_realloc(void *ptr, size_t size) av_alloc_size(2); + +/** + * Allocate, reallocate, or free a block of memory through a pointer to a + * pointer. + * + * If `*ptr` is `NULL` and `size` > 0, allocate a new block. If `size` is + * zero, free the memory block pointed to by `*ptr`. Otherwise, expand or + * shrink that block of memory according to `size`. + * + * @param[in,out] ptr Pointer to a pointer to a memory block already allocated + * with av_realloc(), or a pointer to `NULL`. The pointer + * is updated on success, or freed on failure. + * @param[in] size Size in bytes for the memory block to be allocated or + * reallocated + * + * @return Zero on success, an AVERROR error code on failure + * + * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be + * correctly aligned. + */ +av_warn_unused_result +int av_reallocp(void *ptr, size_t size); + +/** + * Allocate, reallocate, or free a block of memory. + * + * This function does the same thing as av_realloc(), except: + * - It takes two size arguments and allocates `nelem * elsize` bytes, + * after checking the result of the multiplication for integer overflow. + * - It frees the input block in case of failure, thus avoiding the memory + * leak with the classic + * @code{.c} + * buf = realloc(buf); + * if (!buf) + * return -1; + * @endcode + * pattern. + */ +void *av_realloc_f(void *ptr, size_t nelem, size_t elsize); + +/** + * Allocate, reallocate, or free an array. + * + * If `ptr` is `NULL` and `nmemb` > 0, allocate a new block. + * + * @param ptr Pointer to a memory block already allocated with + * av_realloc() or `NULL` + * @param nmemb Number of elements in the array + * @param size Size of the single element of the array + * + * @return Pointer to a newly-reallocated block or NULL if the block + * cannot be reallocated + * + * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be + * correctly aligned. The returned pointer must be freed after even if + * nmemb is zero. + * @see av_reallocp_array() + */ +av_alloc_size(2, 3) void *av_realloc_array(void *ptr, size_t nmemb, size_t size); + +/** + * Allocate, reallocate an array through a pointer to a pointer. + * + * If `*ptr` is `NULL` and `nmemb` > 0, allocate a new block. + * + * @param[in,out] ptr Pointer to a pointer to a memory block already + * allocated with av_realloc(), or a pointer to `NULL`. + * The pointer is updated on success, or freed on failure. + * @param[in] nmemb Number of elements + * @param[in] size Size of the single element + * + * @return Zero on success, an AVERROR error code on failure + * + * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be + * correctly aligned. *ptr must be freed after even if nmemb is zero. + */ +int av_reallocp_array(void *ptr, size_t nmemb, size_t size); + +/** + * Reallocate the given buffer if it is not large enough, otherwise do nothing. + * + * If the given buffer is `NULL`, then a new uninitialized buffer is allocated. + * + * If the given buffer is not large enough, and reallocation fails, `NULL` is + * returned and `*size` is set to 0, but the original buffer is not changed or + * freed. + * + * A typical use pattern follows: + * + * @code{.c} + * uint8_t *buf = ...; + * uint8_t *new_buf = av_fast_realloc(buf, ¤t_size, size_needed); + * if (!new_buf) { + * // Allocation failed; clean up original buffer + * av_freep(&buf); + * return AVERROR(ENOMEM); + * } + * @endcode + * + * @param[in,out] ptr Already allocated buffer, or `NULL` + * @param[in,out] size Pointer to the size of buffer `ptr`. `*size` is + * updated to the new allocated size, in particular 0 + * in case of failure. + * @param[in] min_size Desired minimal size of buffer `ptr` + * @return `ptr` if the buffer is large enough, a pointer to newly reallocated + * buffer if the buffer was not large enough, or `NULL` in case of + * error + * @see av_realloc() + * @see av_fast_malloc() + */ +void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size); + +/** + * Allocate a buffer, reusing the given one if large enough. + * + * Contrary to av_fast_realloc(), the current buffer contents might not be + * preserved and on error the old buffer is freed, thus no special handling to + * avoid memleaks is necessary. + * + * `*ptr` is allowed to be `NULL`, in which case allocation always happens if + * `size_needed` is greater than 0. + * + * @code{.c} + * uint8_t *buf = ...; + * av_fast_malloc(&buf, ¤t_size, size_needed); + * if (!buf) { + * // Allocation failed; buf already freed + * return AVERROR(ENOMEM); + * } + * @endcode + * + * @param[in,out] ptr Pointer to pointer to an already allocated buffer. + * `*ptr` will be overwritten with pointer to new + * buffer on success or `NULL` on failure + * @param[in,out] size Pointer to the size of buffer `*ptr`. `*size` is + * updated to the new allocated size, in particular 0 + * in case of failure. + * @param[in] min_size Desired minimal size of buffer `*ptr` + * @see av_realloc() + * @see av_fast_mallocz() + */ +void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size); + +/** + * Allocate and clear a buffer, reusing the given one if large enough. + * + * Like av_fast_malloc(), but all newly allocated space is initially cleared. + * Reused buffer is not cleared. + * + * `*ptr` is allowed to be `NULL`, in which case allocation always happens if + * `size_needed` is greater than 0. + * + * @param[in,out] ptr Pointer to pointer to an already allocated buffer. + * `*ptr` will be overwritten with pointer to new + * buffer on success or `NULL` on failure + * @param[in,out] size Pointer to the size of buffer `*ptr`. `*size` is + * updated to the new allocated size, in particular 0 + * in case of failure. + * @param[in] min_size Desired minimal size of buffer `*ptr` + * @see av_fast_malloc() + */ +void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size); + +/** + * Free a memory block which has been allocated with a function of av_malloc() + * or av_realloc() family. + * + * @param ptr Pointer to the memory block which should be freed. + * + * @note `ptr = NULL` is explicitly allowed. + * @note It is recommended that you use av_freep() instead, to prevent leaving + * behind dangling pointers. + * @see av_freep() + */ +void av_free(void *ptr); + +/** + * Free a memory block which has been allocated with a function of av_malloc() + * or av_realloc() family, and set the pointer pointing to it to `NULL`. + * + * @code{.c} + * uint8_t *buf = av_malloc(16); + * av_free(buf); + * // buf now contains a dangling pointer to freed memory, and accidental + * // dereference of buf will result in a use-after-free, which may be a + * // security risk. + * + * uint8_t *buf = av_malloc(16); + * av_freep(&buf); + * // buf is now NULL, and accidental dereference will only result in a + * // NULL-pointer dereference. + * @endcode + * + * @param ptr Pointer to the pointer to the memory block which should be freed + * @note `*ptr = NULL` is safe and leads to no action. + * @see av_free() + */ +void av_freep(void *ptr); + +/** + * Duplicate a string. + * + * @param s String to be duplicated + * @return Pointer to a newly-allocated string containing a + * copy of `s` or `NULL` if the string cannot be allocated + * @see av_strndup() + */ +char *av_strdup(const char *s) av_malloc_attrib; + +/** + * Duplicate a substring of a string. + * + * @param s String to be duplicated + * @param len Maximum length of the resulting string (not counting the + * terminating byte) + * @return Pointer to a newly-allocated string containing a + * substring of `s` or `NULL` if the string cannot be allocated + */ +char *av_strndup(const char *s, size_t len) av_malloc_attrib; + +/** + * Duplicate a buffer with av_malloc(). + * + * @param p Buffer to be duplicated + * @param size Size in bytes of the buffer copied + * @return Pointer to a newly allocated buffer containing a + * copy of `p` or `NULL` if the buffer cannot be allocated + */ +void *av_memdup(const void *p, size_t size); + +/** + * Overlapping memcpy() implementation. + * + * @param dst Destination buffer + * @param back Number of bytes back to start copying (i.e. the initial size of + * the overlapping window); must be > 0 + * @param cnt Number of bytes to copy; must be >= 0 + * + * @note `cnt > back` is valid, this will copy the bytes we just copied, + * thus creating a repeating pattern with a period length of `back`. + */ +void av_memcpy_backptr(uint8_t *dst, int back, int cnt); + +/** + * @} + */ + +/** + * @defgroup lavu_mem_dynarray Dynamic Array + * + * Utilities to make an array grow when needed. + * + * Sometimes, the programmer would want to have an array that can grow when + * needed. The libavutil dynamic array utilities fill that need. + * + * libavutil supports two systems of appending elements onto a dynamically + * allocated array, the first one storing the pointer to the value in the + * array, and the second storing the value directly. In both systems, the + * caller is responsible for maintaining a variable containing the length of + * the array, as well as freeing of the array after use. + * + * The first system stores pointers to values in a block of dynamically + * allocated memory. Since only pointers are stored, the function does not need + * to know the size of the type. Both av_dynarray_add() and + * av_dynarray_add_nofree() implement this system. + * + * @code + * type **array = NULL; //< an array of pointers to values + * int nb = 0; //< a variable to keep track of the length of the array + * + * type to_be_added = ...; + * type to_be_added2 = ...; + * + * av_dynarray_add(&array, &nb, &to_be_added); + * if (nb == 0) + * return AVERROR(ENOMEM); + * + * av_dynarray_add(&array, &nb, &to_be_added2); + * if (nb == 0) + * return AVERROR(ENOMEM); + * + * // Now: + * // nb == 2 + * // &to_be_added == array[0] + * // &to_be_added2 == array[1] + * + * av_freep(&array); + * @endcode + * + * The second system stores the value directly in a block of memory. As a + * result, the function has to know the size of the type. av_dynarray2_add() + * implements this mechanism. + * + * @code + * type *array = NULL; //< an array of values + * int nb = 0; //< a variable to keep track of the length of the array + * + * type to_be_added = ...; + * type to_be_added2 = ...; + * + * type *addr = av_dynarray2_add((void **)&array, &nb, sizeof(*array), NULL); + * if (!addr) + * return AVERROR(ENOMEM); + * memcpy(addr, &to_be_added, sizeof(to_be_added)); + * + * // Shortcut of the above. + * type *addr = av_dynarray2_add((void **)&array, &nb, sizeof(*array), + * (const void *)&to_be_added2); + * if (!addr) + * return AVERROR(ENOMEM); + * + * // Now: + * // nb == 2 + * // to_be_added == array[0] + * // to_be_added2 == array[1] + * + * av_freep(&array); + * @endcode + * + * @{ + */ + +/** + * Add the pointer to an element to a dynamic array. + * + * The array to grow is supposed to be an array of pointers to + * structures, and the element to add must be a pointer to an already + * allocated structure. + * + * The array is reallocated when its size reaches powers of 2. + * Therefore, the amortized cost of adding an element is constant. + * + * In case of success, the pointer to the array is updated in order to + * point to the new grown array, and the number pointed to by `nb_ptr` + * is incremented. + * In case of failure, the array is freed, `*tab_ptr` is set to `NULL` and + * `*nb_ptr` is set to 0. + * + * @param[in,out] tab_ptr Pointer to the array to grow + * @param[in,out] nb_ptr Pointer to the number of elements in the array + * @param[in] elem Element to add + * @see av_dynarray_add_nofree(), av_dynarray2_add() + */ +void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem); + +/** + * Add an element to a dynamic array. + * + * Function has the same functionality as av_dynarray_add(), + * but it doesn't free memory on fails. It returns error code + * instead and leave current buffer untouched. + * + * @return >=0 on success, negative otherwise + * @see av_dynarray_add(), av_dynarray2_add() + */ +av_warn_unused_result +int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem); + +/** + * Add an element of size `elem_size` to a dynamic array. + * + * The array is reallocated when its number of elements reaches powers of 2. + * Therefore, the amortized cost of adding an element is constant. + * + * In case of success, the pointer to the array is updated in order to + * point to the new grown array, and the number pointed to by `nb_ptr` + * is incremented. + * In case of failure, the array is freed, `*tab_ptr` is set to `NULL` and + * `*nb_ptr` is set to 0. + * + * @param[in,out] tab_ptr Pointer to the array to grow + * @param[in,out] nb_ptr Pointer to the number of elements in the array + * @param[in] elem_size Size in bytes of an element in the array + * @param[in] elem_data Pointer to the data of the element to add. If + * `NULL`, the space of the newly added element is + * allocated but left uninitialized. + * + * @return Pointer to the data of the element to copy in the newly allocated + * space + * @see av_dynarray_add(), av_dynarray_add_nofree() + */ +void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size, + const uint8_t *elem_data); + +/** + * @} + */ + +/** + * @defgroup lavu_mem_misc Miscellaneous Functions + * + * Other functions related to memory allocation. + * + * @{ + */ + +/** + * Multiply two `size_t` values checking for overflow. + * + * @param[in] a Operand of multiplication + * @param[in] b Operand of multiplication + * @param[out] r Pointer to the result of the operation + * @return 0 on success, AVERROR(EINVAL) on overflow + */ +int av_size_mult(size_t a, size_t b, size_t *r); + +/** + * Set the maximum size that may be allocated in one block. + * + * The value specified with this function is effective for all libavutil's @ref + * lavu_mem_funcs "heap management functions." + * + * By default, the max value is defined as `INT_MAX`. + * + * @param max Value to be set as the new maximum size + * + * @warning Exercise extreme caution when using this function. Don't touch + * this if you do not understand the full consequence of doing so. + */ +void av_max_alloc(size_t max); + +/** + * @} + * @} + */ + +#endif /* AVUTIL_MEM_H */ diff --git a/dreamcast/pvrtex/libavutil/pixfmt.h b/dreamcast/pvrtex/libavutil/pixfmt.h new file mode 100644 index 00000000..63e07ba6 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/pixfmt.h @@ -0,0 +1,699 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_PIXFMT_H +#define AVUTIL_PIXFMT_H + +/** + * @file + * pixel format definitions + */ + +#include "libavutil/avconfig.h" +#include "version.h" + +#define AVPALETTE_SIZE 1024 +#define AVPALETTE_COUNT 256 + +/** + * Pixel format. + * + * @note + * AV_PIX_FMT_RGB32 is handled in an endian-specific manner. An RGBA + * color is put together as: + * (A << 24) | (R << 16) | (G << 8) | B + * This is stored as BGRA on little-endian CPU architectures and ARGB on + * big-endian CPUs. + * + * @note + * If the resolution is not a multiple of the chroma subsampling factor + * then the chroma plane resolution must be rounded up. + * + * @par + * When the pixel format is palettized RGB32 (AV_PIX_FMT_PAL8), the palettized + * image data is stored in AVFrame.data[0]. The palette is transported in + * AVFrame.data[1], is 1024 bytes long (256 4-byte entries) and is + * formatted the same as in AV_PIX_FMT_RGB32 described above (i.e., it is + * also endian-specific). Note also that the individual RGB32 palette + * components stored in AVFrame.data[1] should be in the range 0..255. + * This is important as many custom PAL8 video codecs that were designed + * to run on the IBM VGA graphics adapter use 6-bit palette components. + * + * @par + * For all the 8 bits per pixel formats, an RGB32 palette is in data[1] like + * for pal8. This palette is filled in automatically by the function + * allocating the picture. + */ +enum AVPixelFormat { + AV_PIX_FMT_NONE = -1, + AV_PIX_FMT_YUV420P, ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples) + AV_PIX_FMT_YUYV422, ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr + AV_PIX_FMT_RGB24, ///< packed RGB 8:8:8, 24bpp, RGBRGB... + AV_PIX_FMT_BGR24, ///< packed RGB 8:8:8, 24bpp, BGRBGR... + AV_PIX_FMT_YUV422P, ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples) + AV_PIX_FMT_YUV444P, ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples) + AV_PIX_FMT_YUV410P, ///< planar YUV 4:1:0, 9bpp, (1 Cr & Cb sample per 4x4 Y samples) + AV_PIX_FMT_YUV411P, ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples) + AV_PIX_FMT_GRAY8, ///< Y , 8bpp + AV_PIX_FMT_MONOWHITE, ///< Y , 1bpp, 0 is white, 1 is black, in each byte pixels are ordered from the msb to the lsb + AV_PIX_FMT_MONOBLACK, ///< Y , 1bpp, 0 is black, 1 is white, in each byte pixels are ordered from the msb to the lsb + AV_PIX_FMT_PAL8, ///< 8 bits with AV_PIX_FMT_RGB32 palette + AV_PIX_FMT_YUVJ420P, ///< planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV420P and setting color_range + AV_PIX_FMT_YUVJ422P, ///< planar YUV 4:2:2, 16bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV422P and setting color_range + AV_PIX_FMT_YUVJ444P, ///< planar YUV 4:4:4, 24bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV444P and setting color_range + AV_PIX_FMT_UYVY422, ///< packed YUV 4:2:2, 16bpp, Cb Y0 Cr Y1 + AV_PIX_FMT_UYYVYY411, ///< packed YUV 4:1:1, 12bpp, Cb Y0 Y1 Cr Y2 Y3 + AV_PIX_FMT_BGR8, ///< packed RGB 3:3:2, 8bpp, (msb)2B 3G 3R(lsb) + AV_PIX_FMT_BGR4, ///< packed RGB 1:2:1 bitstream, 4bpp, (msb)1B 2G 1R(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits + AV_PIX_FMT_BGR4_BYTE, ///< packed RGB 1:2:1, 8bpp, (msb)1B 2G 1R(lsb) + AV_PIX_FMT_RGB8, ///< packed RGB 3:3:2, 8bpp, (msb)2R 3G 3B(lsb) + AV_PIX_FMT_RGB4, ///< packed RGB 1:2:1 bitstream, 4bpp, (msb)1R 2G 1B(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits + AV_PIX_FMT_RGB4_BYTE, ///< packed RGB 1:2:1, 8bpp, (msb)1R 2G 1B(lsb) + AV_PIX_FMT_NV12, ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V) + AV_PIX_FMT_NV21, ///< as above, but U and V bytes are swapped + + AV_PIX_FMT_ARGB, ///< packed ARGB 8:8:8:8, 32bpp, ARGBARGB... + AV_PIX_FMT_RGBA, ///< packed RGBA 8:8:8:8, 32bpp, RGBARGBA... + AV_PIX_FMT_ABGR, ///< packed ABGR 8:8:8:8, 32bpp, ABGRABGR... + AV_PIX_FMT_BGRA, ///< packed BGRA 8:8:8:8, 32bpp, BGRABGRA... + + AV_PIX_FMT_GRAY16BE, ///< Y , 16bpp, big-endian + AV_PIX_FMT_GRAY16LE, ///< Y , 16bpp, little-endian + AV_PIX_FMT_YUV440P, ///< planar YUV 4:4:0 (1 Cr & Cb sample per 1x2 Y samples) + AV_PIX_FMT_YUVJ440P, ///< planar YUV 4:4:0 full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV440P and setting color_range + AV_PIX_FMT_YUVA420P, ///< planar YUV 4:2:0, 20bpp, (1 Cr & Cb sample per 2x2 Y & A samples) + AV_PIX_FMT_RGB48BE, ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as big-endian + AV_PIX_FMT_RGB48LE, ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as little-endian + + AV_PIX_FMT_RGB565BE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), big-endian + AV_PIX_FMT_RGB565LE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), little-endian + AV_PIX_FMT_RGB555BE, ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), big-endian , X=unused/undefined + AV_PIX_FMT_RGB555LE, ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), little-endian, X=unused/undefined + + AV_PIX_FMT_BGR565BE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb), big-endian + AV_PIX_FMT_BGR565LE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb), little-endian + AV_PIX_FMT_BGR555BE, ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb), big-endian , X=unused/undefined + AV_PIX_FMT_BGR555LE, ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb), little-endian, X=unused/undefined + + /** + * Hardware acceleration through VA-API, data[3] contains a + * VASurfaceID. + */ + AV_PIX_FMT_VAAPI, + + AV_PIX_FMT_YUV420P16LE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV420P16BE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV422P16LE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_YUV422P16BE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV444P16LE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_YUV444P16BE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_DXVA2_VLD, ///< HW decoding through DXVA2, Picture.data[3] contains a LPDIRECT3DSURFACE9 pointer + + AV_PIX_FMT_RGB444LE, ///< packed RGB 4:4:4, 16bpp, (msb)4X 4R 4G 4B(lsb), little-endian, X=unused/undefined + AV_PIX_FMT_RGB444BE, ///< packed RGB 4:4:4, 16bpp, (msb)4X 4R 4G 4B(lsb), big-endian, X=unused/undefined + AV_PIX_FMT_BGR444LE, ///< packed BGR 4:4:4, 16bpp, (msb)4X 4B 4G 4R(lsb), little-endian, X=unused/undefined + AV_PIX_FMT_BGR444BE, ///< packed BGR 4:4:4, 16bpp, (msb)4X 4B 4G 4R(lsb), big-endian, X=unused/undefined + AV_PIX_FMT_YA8, ///< 8 bits gray, 8 bits alpha + + AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8, ///< alias for AV_PIX_FMT_YA8 + AV_PIX_FMT_GRAY8A= AV_PIX_FMT_YA8, ///< alias for AV_PIX_FMT_YA8 + + AV_PIX_FMT_BGR48BE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as big-endian + AV_PIX_FMT_BGR48LE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as little-endian + + /** + * The following 12 formats have the disadvantage of needing 1 format for each bit depth. + * Notice that each 9/10 bits sample is stored in 16 bits with extra padding. + * If you want to support multiple bit depths, then using AV_PIX_FMT_YUV420P16* with the bpp stored separately is better. + */ + AV_PIX_FMT_YUV420P9BE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV420P9LE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV420P10BE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV420P10LE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV422P10BE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV422P10LE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_YUV444P9BE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_YUV444P9LE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_YUV444P10BE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_YUV444P10LE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_YUV422P9BE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV422P9LE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_GBRP, ///< planar GBR 4:4:4 24bpp + AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP, // alias for #AV_PIX_FMT_GBRP + AV_PIX_FMT_GBRP9BE, ///< planar GBR 4:4:4 27bpp, big-endian + AV_PIX_FMT_GBRP9LE, ///< planar GBR 4:4:4 27bpp, little-endian + AV_PIX_FMT_GBRP10BE, ///< planar GBR 4:4:4 30bpp, big-endian + AV_PIX_FMT_GBRP10LE, ///< planar GBR 4:4:4 30bpp, little-endian + AV_PIX_FMT_GBRP16BE, ///< planar GBR 4:4:4 48bpp, big-endian + AV_PIX_FMT_GBRP16LE, ///< planar GBR 4:4:4 48bpp, little-endian + AV_PIX_FMT_YUVA422P, ///< planar YUV 4:2:2 24bpp, (1 Cr & Cb sample per 2x1 Y & A samples) + AV_PIX_FMT_YUVA444P, ///< planar YUV 4:4:4 32bpp, (1 Cr & Cb sample per 1x1 Y & A samples) + AV_PIX_FMT_YUVA420P9BE, ///< planar YUV 4:2:0 22.5bpp, (1 Cr & Cb sample per 2x2 Y & A samples), big-endian + AV_PIX_FMT_YUVA420P9LE, ///< planar YUV 4:2:0 22.5bpp, (1 Cr & Cb sample per 2x2 Y & A samples), little-endian + AV_PIX_FMT_YUVA422P9BE, ///< planar YUV 4:2:2 27bpp, (1 Cr & Cb sample per 2x1 Y & A samples), big-endian + AV_PIX_FMT_YUVA422P9LE, ///< planar YUV 4:2:2 27bpp, (1 Cr & Cb sample per 2x1 Y & A samples), little-endian + AV_PIX_FMT_YUVA444P9BE, ///< planar YUV 4:4:4 36bpp, (1 Cr & Cb sample per 1x1 Y & A samples), big-endian + AV_PIX_FMT_YUVA444P9LE, ///< planar YUV 4:4:4 36bpp, (1 Cr & Cb sample per 1x1 Y & A samples), little-endian + AV_PIX_FMT_YUVA420P10BE, ///< planar YUV 4:2:0 25bpp, (1 Cr & Cb sample per 2x2 Y & A samples, big-endian) + AV_PIX_FMT_YUVA420P10LE, ///< planar YUV 4:2:0 25bpp, (1 Cr & Cb sample per 2x2 Y & A samples, little-endian) + AV_PIX_FMT_YUVA422P10BE, ///< planar YUV 4:2:2 30bpp, (1 Cr & Cb sample per 2x1 Y & A samples, big-endian) + AV_PIX_FMT_YUVA422P10LE, ///< planar YUV 4:2:2 30bpp, (1 Cr & Cb sample per 2x1 Y & A samples, little-endian) + AV_PIX_FMT_YUVA444P10BE, ///< planar YUV 4:4:4 40bpp, (1 Cr & Cb sample per 1x1 Y & A samples, big-endian) + AV_PIX_FMT_YUVA444P10LE, ///< planar YUV 4:4:4 40bpp, (1 Cr & Cb sample per 1x1 Y & A samples, little-endian) + AV_PIX_FMT_YUVA420P16BE, ///< planar YUV 4:2:0 40bpp, (1 Cr & Cb sample per 2x2 Y & A samples, big-endian) + AV_PIX_FMT_YUVA420P16LE, ///< planar YUV 4:2:0 40bpp, (1 Cr & Cb sample per 2x2 Y & A samples, little-endian) + AV_PIX_FMT_YUVA422P16BE, ///< planar YUV 4:2:2 48bpp, (1 Cr & Cb sample per 2x1 Y & A samples, big-endian) + AV_PIX_FMT_YUVA422P16LE, ///< planar YUV 4:2:2 48bpp, (1 Cr & Cb sample per 2x1 Y & A samples, little-endian) + AV_PIX_FMT_YUVA444P16BE, ///< planar YUV 4:4:4 64bpp, (1 Cr & Cb sample per 1x1 Y & A samples, big-endian) + AV_PIX_FMT_YUVA444P16LE, ///< planar YUV 4:4:4 64bpp, (1 Cr & Cb sample per 1x1 Y & A samples, little-endian) + + AV_PIX_FMT_VDPAU, ///< HW acceleration through VDPAU, Picture.data[3] contains a VdpVideoSurface + + AV_PIX_FMT_XYZ12LE, ///< packed XYZ 4:4:4, 36 bpp, (msb) 12X, 12Y, 12Z (lsb), the 2-byte value for each X/Y/Z is stored as little-endian, the 4 lower bits are set to 0 + AV_PIX_FMT_XYZ12BE, ///< packed XYZ 4:4:4, 36 bpp, (msb) 12X, 12Y, 12Z (lsb), the 2-byte value for each X/Y/Z is stored as big-endian, the 4 lower bits are set to 0 + AV_PIX_FMT_NV16, ///< interleaved chroma YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples) + AV_PIX_FMT_NV20LE, ///< interleaved chroma YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_NV20BE, ///< interleaved chroma YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + + AV_PIX_FMT_RGBA64BE, ///< packed RGBA 16:16:16:16, 64bpp, 16R, 16G, 16B, 16A, the 2-byte value for each R/G/B/A component is stored as big-endian + AV_PIX_FMT_RGBA64LE, ///< packed RGBA 16:16:16:16, 64bpp, 16R, 16G, 16B, 16A, the 2-byte value for each R/G/B/A component is stored as little-endian + AV_PIX_FMT_BGRA64BE, ///< packed RGBA 16:16:16:16, 64bpp, 16B, 16G, 16R, 16A, the 2-byte value for each R/G/B/A component is stored as big-endian + AV_PIX_FMT_BGRA64LE, ///< packed RGBA 16:16:16:16, 64bpp, 16B, 16G, 16R, 16A, the 2-byte value for each R/G/B/A component is stored as little-endian + + AV_PIX_FMT_YVYU422, ///< packed YUV 4:2:2, 16bpp, Y0 Cr Y1 Cb + + AV_PIX_FMT_YA16BE, ///< 16 bits gray, 16 bits alpha (big-endian) + AV_PIX_FMT_YA16LE, ///< 16 bits gray, 16 bits alpha (little-endian) + + AV_PIX_FMT_GBRAP, ///< planar GBRA 4:4:4:4 32bpp + AV_PIX_FMT_GBRAP16BE, ///< planar GBRA 4:4:4:4 64bpp, big-endian + AV_PIX_FMT_GBRAP16LE, ///< planar GBRA 4:4:4:4 64bpp, little-endian + /** + * HW acceleration through QSV, data[3] contains a pointer to the + * mfxFrameSurface1 structure. + * + * Before FFmpeg 5.0: + * mfxFrameSurface1.Data.MemId contains a pointer when importing + * the following frames as QSV frames: + * + * VAAPI: + * mfxFrameSurface1.Data.MemId contains a pointer to VASurfaceID + * + * DXVA2: + * mfxFrameSurface1.Data.MemId contains a pointer to IDirect3DSurface9 + * + * FFmpeg 5.0 and above: + * mfxFrameSurface1.Data.MemId contains a pointer to the mfxHDLPair + * structure when importing the following frames as QSV frames: + * + * VAAPI: + * mfxHDLPair.first contains a VASurfaceID pointer. + * mfxHDLPair.second is always MFX_INFINITE. + * + * DXVA2: + * mfxHDLPair.first contains IDirect3DSurface9 pointer. + * mfxHDLPair.second is always MFX_INFINITE. + * + * D3D11: + * mfxHDLPair.first contains a ID3D11Texture2D pointer. + * mfxHDLPair.second contains the texture array index of the frame if the + * ID3D11Texture2D is an array texture, or always MFX_INFINITE if it is a + * normal texture. + */ + AV_PIX_FMT_QSV, + /** + * HW acceleration though MMAL, data[3] contains a pointer to the + * MMAL_BUFFER_HEADER_T structure. + */ + AV_PIX_FMT_MMAL, + + AV_PIX_FMT_D3D11VA_VLD, ///< HW decoding through Direct3D11 via old API, Picture.data[3] contains a ID3D11VideoDecoderOutputView pointer + + /** + * HW acceleration through CUDA. data[i] contain CUdeviceptr pointers + * exactly as for system memory frames. + */ + AV_PIX_FMT_CUDA, + + AV_PIX_FMT_0RGB, ///< packed RGB 8:8:8, 32bpp, XRGBXRGB... X=unused/undefined + AV_PIX_FMT_RGB0, ///< packed RGB 8:8:8, 32bpp, RGBXRGBX... X=unused/undefined + AV_PIX_FMT_0BGR, ///< packed BGR 8:8:8, 32bpp, XBGRXBGR... X=unused/undefined + AV_PIX_FMT_BGR0, ///< packed BGR 8:8:8, 32bpp, BGRXBGRX... X=unused/undefined + + AV_PIX_FMT_YUV420P12BE, ///< planar YUV 4:2:0,18bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV420P12LE, ///< planar YUV 4:2:0,18bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV420P14BE, ///< planar YUV 4:2:0,21bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV420P14LE, ///< planar YUV 4:2:0,21bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV422P12BE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV422P12LE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_YUV422P14BE, ///< planar YUV 4:2:2,28bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV422P14LE, ///< planar YUV 4:2:2,28bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_YUV444P12BE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_YUV444P12LE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_YUV444P14BE, ///< planar YUV 4:4:4,42bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_YUV444P14LE, ///< planar YUV 4:4:4,42bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_GBRP12BE, ///< planar GBR 4:4:4 36bpp, big-endian + AV_PIX_FMT_GBRP12LE, ///< planar GBR 4:4:4 36bpp, little-endian + AV_PIX_FMT_GBRP14BE, ///< planar GBR 4:4:4 42bpp, big-endian + AV_PIX_FMT_GBRP14LE, ///< planar GBR 4:4:4 42bpp, little-endian + AV_PIX_FMT_YUVJ411P, ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples) full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV411P and setting color_range + + AV_PIX_FMT_BAYER_BGGR8, ///< bayer, BGBG..(odd line), GRGR..(even line), 8-bit samples + AV_PIX_FMT_BAYER_RGGB8, ///< bayer, RGRG..(odd line), GBGB..(even line), 8-bit samples + AV_PIX_FMT_BAYER_GBRG8, ///< bayer, GBGB..(odd line), RGRG..(even line), 8-bit samples + AV_PIX_FMT_BAYER_GRBG8, ///< bayer, GRGR..(odd line), BGBG..(even line), 8-bit samples + AV_PIX_FMT_BAYER_BGGR16LE, ///< bayer, BGBG..(odd line), GRGR..(even line), 16-bit samples, little-endian + AV_PIX_FMT_BAYER_BGGR16BE, ///< bayer, BGBG..(odd line), GRGR..(even line), 16-bit samples, big-endian + AV_PIX_FMT_BAYER_RGGB16LE, ///< bayer, RGRG..(odd line), GBGB..(even line), 16-bit samples, little-endian + AV_PIX_FMT_BAYER_RGGB16BE, ///< bayer, RGRG..(odd line), GBGB..(even line), 16-bit samples, big-endian + AV_PIX_FMT_BAYER_GBRG16LE, ///< bayer, GBGB..(odd line), RGRG..(even line), 16-bit samples, little-endian + AV_PIX_FMT_BAYER_GBRG16BE, ///< bayer, GBGB..(odd line), RGRG..(even line), 16-bit samples, big-endian + AV_PIX_FMT_BAYER_GRBG16LE, ///< bayer, GRGR..(odd line), BGBG..(even line), 16-bit samples, little-endian + AV_PIX_FMT_BAYER_GRBG16BE, ///< bayer, GRGR..(odd line), BGBG..(even line), 16-bit samples, big-endian + +#if FF_API_XVMC + AV_PIX_FMT_XVMC,///< XVideo Motion Acceleration via common packet passing +#endif + + AV_PIX_FMT_YUV440P10LE, ///< planar YUV 4:4:0,20bpp, (1 Cr & Cb sample per 1x2 Y samples), little-endian + AV_PIX_FMT_YUV440P10BE, ///< planar YUV 4:4:0,20bpp, (1 Cr & Cb sample per 1x2 Y samples), big-endian + AV_PIX_FMT_YUV440P12LE, ///< planar YUV 4:4:0,24bpp, (1 Cr & Cb sample per 1x2 Y samples), little-endian + AV_PIX_FMT_YUV440P12BE, ///< planar YUV 4:4:0,24bpp, (1 Cr & Cb sample per 1x2 Y samples), big-endian + AV_PIX_FMT_AYUV64LE, ///< packed AYUV 4:4:4,64bpp (1 Cr & Cb sample per 1x1 Y & A samples), little-endian + AV_PIX_FMT_AYUV64BE, ///< packed AYUV 4:4:4,64bpp (1 Cr & Cb sample per 1x1 Y & A samples), big-endian + + AV_PIX_FMT_VIDEOTOOLBOX, ///< hardware decoding through Videotoolbox + + AV_PIX_FMT_P010LE, ///< like NV12, with 10bpp per component, data in the high bits, zeros in the low bits, little-endian + AV_PIX_FMT_P010BE, ///< like NV12, with 10bpp per component, data in the high bits, zeros in the low bits, big-endian + + AV_PIX_FMT_GBRAP12BE, ///< planar GBR 4:4:4:4 48bpp, big-endian + AV_PIX_FMT_GBRAP12LE, ///< planar GBR 4:4:4:4 48bpp, little-endian + + AV_PIX_FMT_GBRAP10BE, ///< planar GBR 4:4:4:4 40bpp, big-endian + AV_PIX_FMT_GBRAP10LE, ///< planar GBR 4:4:4:4 40bpp, little-endian + + AV_PIX_FMT_MEDIACODEC, ///< hardware decoding through MediaCodec + + AV_PIX_FMT_GRAY12BE, ///< Y , 12bpp, big-endian + AV_PIX_FMT_GRAY12LE, ///< Y , 12bpp, little-endian + AV_PIX_FMT_GRAY10BE, ///< Y , 10bpp, big-endian + AV_PIX_FMT_GRAY10LE, ///< Y , 10bpp, little-endian + + AV_PIX_FMT_P016LE, ///< like NV12, with 16bpp per component, little-endian + AV_PIX_FMT_P016BE, ///< like NV12, with 16bpp per component, big-endian + + /** + * Hardware surfaces for Direct3D11. + * + * This is preferred over the legacy AV_PIX_FMT_D3D11VA_VLD. The new D3D11 + * hwaccel API and filtering support AV_PIX_FMT_D3D11 only. + * + * data[0] contains a ID3D11Texture2D pointer, and data[1] contains the + * texture array index of the frame as intptr_t if the ID3D11Texture2D is + * an array texture (or always 0 if it's a normal texture). + */ + AV_PIX_FMT_D3D11, + + AV_PIX_FMT_GRAY9BE, ///< Y , 9bpp, big-endian + AV_PIX_FMT_GRAY9LE, ///< Y , 9bpp, little-endian + + AV_PIX_FMT_GBRPF32BE, ///< IEEE-754 single precision planar GBR 4:4:4, 96bpp, big-endian + AV_PIX_FMT_GBRPF32LE, ///< IEEE-754 single precision planar GBR 4:4:4, 96bpp, little-endian + AV_PIX_FMT_GBRAPF32BE, ///< IEEE-754 single precision planar GBRA 4:4:4:4, 128bpp, big-endian + AV_PIX_FMT_GBRAPF32LE, ///< IEEE-754 single precision planar GBRA 4:4:4:4, 128bpp, little-endian + + /** + * DRM-managed buffers exposed through PRIME buffer sharing. + * + * data[0] points to an AVDRMFrameDescriptor. + */ + AV_PIX_FMT_DRM_PRIME, + /** + * Hardware surfaces for OpenCL. + * + * data[i] contain 2D image objects (typed in C as cl_mem, used + * in OpenCL as image2d_t) for each plane of the surface. + */ + AV_PIX_FMT_OPENCL, + + AV_PIX_FMT_GRAY14BE, ///< Y , 14bpp, big-endian + AV_PIX_FMT_GRAY14LE, ///< Y , 14bpp, little-endian + + AV_PIX_FMT_GRAYF32BE, ///< IEEE-754 single precision Y, 32bpp, big-endian + AV_PIX_FMT_GRAYF32LE, ///< IEEE-754 single precision Y, 32bpp, little-endian + + AV_PIX_FMT_YUVA422P12BE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), 12b alpha, big-endian + AV_PIX_FMT_YUVA422P12LE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), 12b alpha, little-endian + AV_PIX_FMT_YUVA444P12BE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), 12b alpha, big-endian + AV_PIX_FMT_YUVA444P12LE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), 12b alpha, little-endian + + AV_PIX_FMT_NV24, ///< planar YUV 4:4:4, 24bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V) + AV_PIX_FMT_NV42, ///< as above, but U and V bytes are swapped + + /** + * Vulkan hardware images. + * + * data[0] points to an AVVkFrame + */ + AV_PIX_FMT_VULKAN, + + AV_PIX_FMT_Y210BE, ///< packed YUV 4:2:2 like YUYV422, 20bpp, data in the high bits, big-endian + AV_PIX_FMT_Y210LE, ///< packed YUV 4:2:2 like YUYV422, 20bpp, data in the high bits, little-endian + + AV_PIX_FMT_X2RGB10LE, ///< packed RGB 10:10:10, 30bpp, (msb)2X 10R 10G 10B(lsb), little-endian, X=unused/undefined + AV_PIX_FMT_X2RGB10BE, ///< packed RGB 10:10:10, 30bpp, (msb)2X 10R 10G 10B(lsb), big-endian, X=unused/undefined + AV_PIX_FMT_X2BGR10LE, ///< packed BGR 10:10:10, 30bpp, (msb)2X 10B 10G 10R(lsb), little-endian, X=unused/undefined + AV_PIX_FMT_X2BGR10BE, ///< packed BGR 10:10:10, 30bpp, (msb)2X 10B 10G 10R(lsb), big-endian, X=unused/undefined + + AV_PIX_FMT_P210BE, ///< interleaved chroma YUV 4:2:2, 20bpp, data in the high bits, big-endian + AV_PIX_FMT_P210LE, ///< interleaved chroma YUV 4:2:2, 20bpp, data in the high bits, little-endian + + AV_PIX_FMT_P410BE, ///< interleaved chroma YUV 4:4:4, 30bpp, data in the high bits, big-endian + AV_PIX_FMT_P410LE, ///< interleaved chroma YUV 4:4:4, 30bpp, data in the high bits, little-endian + + AV_PIX_FMT_P216BE, ///< interleaved chroma YUV 4:2:2, 32bpp, big-endian + AV_PIX_FMT_P216LE, ///< interleaved chroma YUV 4:2:2, 32bpp, little-endian + + AV_PIX_FMT_P416BE, ///< interleaved chroma YUV 4:4:4, 48bpp, big-endian + AV_PIX_FMT_P416LE, ///< interleaved chroma YUV 4:4:4, 48bpp, little-endian + + AV_PIX_FMT_VUYA, ///< packed VUYA 4:4:4, 32bpp, VUYAVUYA... + + AV_PIX_FMT_RGBAF16BE, ///< IEEE-754 half precision packed RGBA 16:16:16:16, 64bpp, RGBARGBA..., big-endian + AV_PIX_FMT_RGBAF16LE, ///< IEEE-754 half precision packed RGBA 16:16:16:16, 64bpp, RGBARGBA..., little-endian + + AV_PIX_FMT_VUYX, ///< packed VUYX 4:4:4, 32bpp, Variant of VUYA where alpha channel is left undefined + + AV_PIX_FMT_P012LE, ///< like NV12, with 12bpp per component, data in the high bits, zeros in the low bits, little-endian + AV_PIX_FMT_P012BE, ///< like NV12, with 12bpp per component, data in the high bits, zeros in the low bits, big-endian + + AV_PIX_FMT_Y212BE, ///< packed YUV 4:2:2 like YUYV422, 24bpp, data in the high bits, zeros in the low bits, big-endian + AV_PIX_FMT_Y212LE, ///< packed YUV 4:2:2 like YUYV422, 24bpp, data in the high bits, zeros in the low bits, little-endian + + AV_PIX_FMT_XV30BE, ///< packed XVYU 4:4:4, 32bpp, (msb)2X 10V 10Y 10U(lsb), big-endian, variant of Y410 where alpha channel is left undefined + AV_PIX_FMT_XV30LE, ///< packed XVYU 4:4:4, 32bpp, (msb)2X 10V 10Y 10U(lsb), little-endian, variant of Y410 where alpha channel is left undefined + + AV_PIX_FMT_XV36BE, ///< packed XVYU 4:4:4, 48bpp, data in the high bits, zeros in the low bits, big-endian, variant of Y412 where alpha channel is left undefined + AV_PIX_FMT_XV36LE, ///< packed XVYU 4:4:4, 48bpp, data in the high bits, zeros in the low bits, little-endian, variant of Y412 where alpha channel is left undefined + + AV_PIX_FMT_RGBF32BE, ///< IEEE-754 single precision packed RGB 32:32:32, 96bpp, RGBRGB..., big-endian + AV_PIX_FMT_RGBF32LE, ///< IEEE-754 single precision packed RGB 32:32:32, 96bpp, RGBRGB..., little-endian + + AV_PIX_FMT_RGBAF32BE, ///< IEEE-754 single precision packed RGBA 32:32:32:32, 128bpp, RGBARGBA..., big-endian + AV_PIX_FMT_RGBAF32LE, ///< IEEE-754 single precision packed RGBA 32:32:32:32, 128bpp, RGBARGBA..., little-endian + + AV_PIX_FMT_P212BE, ///< interleaved chroma YUV 4:2:2, 24bpp, data in the high bits, big-endian + AV_PIX_FMT_P212LE, ///< interleaved chroma YUV 4:2:2, 24bpp, data in the high bits, little-endian + + AV_PIX_FMT_P412BE, ///< interleaved chroma YUV 4:4:4, 36bpp, data in the high bits, big-endian + AV_PIX_FMT_P412LE, ///< interleaved chroma YUV 4:4:4, 36bpp, data in the high bits, little-endian + + AV_PIX_FMT_NB ///< number of pixel formats, DO NOT USE THIS if you want to link with shared libav* because the number of formats might differ between versions +}; + +#if AV_HAVE_BIGENDIAN +# define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##be +#else +# define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##le +#endif + +#define AV_PIX_FMT_RGB32 AV_PIX_FMT_NE(ARGB, BGRA) +#define AV_PIX_FMT_RGB32_1 AV_PIX_FMT_NE(RGBA, ABGR) +#define AV_PIX_FMT_BGR32 AV_PIX_FMT_NE(ABGR, RGBA) +#define AV_PIX_FMT_BGR32_1 AV_PIX_FMT_NE(BGRA, ARGB) +#define AV_PIX_FMT_0RGB32 AV_PIX_FMT_NE(0RGB, BGR0) +#define AV_PIX_FMT_0BGR32 AV_PIX_FMT_NE(0BGR, RGB0) + +#define AV_PIX_FMT_GRAY9 AV_PIX_FMT_NE(GRAY9BE, GRAY9LE) +#define AV_PIX_FMT_GRAY10 AV_PIX_FMT_NE(GRAY10BE, GRAY10LE) +#define AV_PIX_FMT_GRAY12 AV_PIX_FMT_NE(GRAY12BE, GRAY12LE) +#define AV_PIX_FMT_GRAY14 AV_PIX_FMT_NE(GRAY14BE, GRAY14LE) +#define AV_PIX_FMT_GRAY16 AV_PIX_FMT_NE(GRAY16BE, GRAY16LE) +#define AV_PIX_FMT_YA16 AV_PIX_FMT_NE(YA16BE, YA16LE) +#define AV_PIX_FMT_RGB48 AV_PIX_FMT_NE(RGB48BE, RGB48LE) +#define AV_PIX_FMT_RGB565 AV_PIX_FMT_NE(RGB565BE, RGB565LE) +#define AV_PIX_FMT_RGB555 AV_PIX_FMT_NE(RGB555BE, RGB555LE) +#define AV_PIX_FMT_RGB444 AV_PIX_FMT_NE(RGB444BE, RGB444LE) +#define AV_PIX_FMT_RGBA64 AV_PIX_FMT_NE(RGBA64BE, RGBA64LE) +#define AV_PIX_FMT_BGR48 AV_PIX_FMT_NE(BGR48BE, BGR48LE) +#define AV_PIX_FMT_BGR565 AV_PIX_FMT_NE(BGR565BE, BGR565LE) +#define AV_PIX_FMT_BGR555 AV_PIX_FMT_NE(BGR555BE, BGR555LE) +#define AV_PIX_FMT_BGR444 AV_PIX_FMT_NE(BGR444BE, BGR444LE) +#define AV_PIX_FMT_BGRA64 AV_PIX_FMT_NE(BGRA64BE, BGRA64LE) + +#define AV_PIX_FMT_YUV420P9 AV_PIX_FMT_NE(YUV420P9BE , YUV420P9LE) +#define AV_PIX_FMT_YUV422P9 AV_PIX_FMT_NE(YUV422P9BE , YUV422P9LE) +#define AV_PIX_FMT_YUV444P9 AV_PIX_FMT_NE(YUV444P9BE , YUV444P9LE) +#define AV_PIX_FMT_YUV420P10 AV_PIX_FMT_NE(YUV420P10BE, YUV420P10LE) +#define AV_PIX_FMT_YUV422P10 AV_PIX_FMT_NE(YUV422P10BE, YUV422P10LE) +#define AV_PIX_FMT_YUV440P10 AV_PIX_FMT_NE(YUV440P10BE, YUV440P10LE) +#define AV_PIX_FMT_YUV444P10 AV_PIX_FMT_NE(YUV444P10BE, YUV444P10LE) +#define AV_PIX_FMT_YUV420P12 AV_PIX_FMT_NE(YUV420P12BE, YUV420P12LE) +#define AV_PIX_FMT_YUV422P12 AV_PIX_FMT_NE(YUV422P12BE, YUV422P12LE) +#define AV_PIX_FMT_YUV440P12 AV_PIX_FMT_NE(YUV440P12BE, YUV440P12LE) +#define AV_PIX_FMT_YUV444P12 AV_PIX_FMT_NE(YUV444P12BE, YUV444P12LE) +#define AV_PIX_FMT_YUV420P14 AV_PIX_FMT_NE(YUV420P14BE, YUV420P14LE) +#define AV_PIX_FMT_YUV422P14 AV_PIX_FMT_NE(YUV422P14BE, YUV422P14LE) +#define AV_PIX_FMT_YUV444P14 AV_PIX_FMT_NE(YUV444P14BE, YUV444P14LE) +#define AV_PIX_FMT_YUV420P16 AV_PIX_FMT_NE(YUV420P16BE, YUV420P16LE) +#define AV_PIX_FMT_YUV422P16 AV_PIX_FMT_NE(YUV422P16BE, YUV422P16LE) +#define AV_PIX_FMT_YUV444P16 AV_PIX_FMT_NE(YUV444P16BE, YUV444P16LE) + +#define AV_PIX_FMT_GBRP9 AV_PIX_FMT_NE(GBRP9BE , GBRP9LE) +#define AV_PIX_FMT_GBRP10 AV_PIX_FMT_NE(GBRP10BE, GBRP10LE) +#define AV_PIX_FMT_GBRP12 AV_PIX_FMT_NE(GBRP12BE, GBRP12LE) +#define AV_PIX_FMT_GBRP14 AV_PIX_FMT_NE(GBRP14BE, GBRP14LE) +#define AV_PIX_FMT_GBRP16 AV_PIX_FMT_NE(GBRP16BE, GBRP16LE) +#define AV_PIX_FMT_GBRAP10 AV_PIX_FMT_NE(GBRAP10BE, GBRAP10LE) +#define AV_PIX_FMT_GBRAP12 AV_PIX_FMT_NE(GBRAP12BE, GBRAP12LE) +#define AV_PIX_FMT_GBRAP16 AV_PIX_FMT_NE(GBRAP16BE, GBRAP16LE) + +#define AV_PIX_FMT_BAYER_BGGR16 AV_PIX_FMT_NE(BAYER_BGGR16BE, BAYER_BGGR16LE) +#define AV_PIX_FMT_BAYER_RGGB16 AV_PIX_FMT_NE(BAYER_RGGB16BE, BAYER_RGGB16LE) +#define AV_PIX_FMT_BAYER_GBRG16 AV_PIX_FMT_NE(BAYER_GBRG16BE, BAYER_GBRG16LE) +#define AV_PIX_FMT_BAYER_GRBG16 AV_PIX_FMT_NE(BAYER_GRBG16BE, BAYER_GRBG16LE) + +#define AV_PIX_FMT_GBRPF32 AV_PIX_FMT_NE(GBRPF32BE, GBRPF32LE) +#define AV_PIX_FMT_GBRAPF32 AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE) + +#define AV_PIX_FMT_GRAYF32 AV_PIX_FMT_NE(GRAYF32BE, GRAYF32LE) + +#define AV_PIX_FMT_YUVA420P9 AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE) +#define AV_PIX_FMT_YUVA422P9 AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE) +#define AV_PIX_FMT_YUVA444P9 AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE) +#define AV_PIX_FMT_YUVA420P10 AV_PIX_FMT_NE(YUVA420P10BE, YUVA420P10LE) +#define AV_PIX_FMT_YUVA422P10 AV_PIX_FMT_NE(YUVA422P10BE, YUVA422P10LE) +#define AV_PIX_FMT_YUVA444P10 AV_PIX_FMT_NE(YUVA444P10BE, YUVA444P10LE) +#define AV_PIX_FMT_YUVA422P12 AV_PIX_FMT_NE(YUVA422P12BE, YUVA422P12LE) +#define AV_PIX_FMT_YUVA444P12 AV_PIX_FMT_NE(YUVA444P12BE, YUVA444P12LE) +#define AV_PIX_FMT_YUVA420P16 AV_PIX_FMT_NE(YUVA420P16BE, YUVA420P16LE) +#define AV_PIX_FMT_YUVA422P16 AV_PIX_FMT_NE(YUVA422P16BE, YUVA422P16LE) +#define AV_PIX_FMT_YUVA444P16 AV_PIX_FMT_NE(YUVA444P16BE, YUVA444P16LE) + +#define AV_PIX_FMT_XYZ12 AV_PIX_FMT_NE(XYZ12BE, XYZ12LE) +#define AV_PIX_FMT_NV20 AV_PIX_FMT_NE(NV20BE, NV20LE) +#define AV_PIX_FMT_AYUV64 AV_PIX_FMT_NE(AYUV64BE, AYUV64LE) +#define AV_PIX_FMT_P010 AV_PIX_FMT_NE(P010BE, P010LE) +#define AV_PIX_FMT_P012 AV_PIX_FMT_NE(P012BE, P012LE) +#define AV_PIX_FMT_P016 AV_PIX_FMT_NE(P016BE, P016LE) + +#define AV_PIX_FMT_Y210 AV_PIX_FMT_NE(Y210BE, Y210LE) +#define AV_PIX_FMT_Y212 AV_PIX_FMT_NE(Y212BE, Y212LE) +#define AV_PIX_FMT_XV30 AV_PIX_FMT_NE(XV30BE, XV30LE) +#define AV_PIX_FMT_XV36 AV_PIX_FMT_NE(XV36BE, XV36LE) +#define AV_PIX_FMT_X2RGB10 AV_PIX_FMT_NE(X2RGB10BE, X2RGB10LE) +#define AV_PIX_FMT_X2BGR10 AV_PIX_FMT_NE(X2BGR10BE, X2BGR10LE) + +#define AV_PIX_FMT_P210 AV_PIX_FMT_NE(P210BE, P210LE) +#define AV_PIX_FMT_P410 AV_PIX_FMT_NE(P410BE, P410LE) +#define AV_PIX_FMT_P212 AV_PIX_FMT_NE(P212BE, P212LE) +#define AV_PIX_FMT_P412 AV_PIX_FMT_NE(P412BE, P412LE) +#define AV_PIX_FMT_P216 AV_PIX_FMT_NE(P216BE, P216LE) +#define AV_PIX_FMT_P416 AV_PIX_FMT_NE(P416BE, P416LE) + +#define AV_PIX_FMT_RGBAF16 AV_PIX_FMT_NE(RGBAF16BE, RGBAF16LE) + +#define AV_PIX_FMT_RGBF32 AV_PIX_FMT_NE(RGBF32BE, RGBF32LE) +#define AV_PIX_FMT_RGBAF32 AV_PIX_FMT_NE(RGBAF32BE, RGBAF32LE) + +/** + * Chromaticity coordinates of the source primaries. + * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.1 and ITU-T H.273. + */ +enum AVColorPrimaries { + AVCOL_PRI_RESERVED0 = 0, + AVCOL_PRI_BT709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 / SMPTE RP 177 Annex B + AVCOL_PRI_UNSPECIFIED = 2, + AVCOL_PRI_RESERVED = 3, + AVCOL_PRI_BT470M = 4, ///< also FCC Title 47 Code of Federal Regulations 73.682 (a)(20) + + AVCOL_PRI_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM + AVCOL_PRI_SMPTE170M = 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC + AVCOL_PRI_SMPTE240M = 7, ///< identical to above, also called "SMPTE C" even though it uses D65 + AVCOL_PRI_FILM = 8, ///< colour filters using Illuminant C + AVCOL_PRI_BT2020 = 9, ///< ITU-R BT2020 + AVCOL_PRI_SMPTE428 = 10, ///< SMPTE ST 428-1 (CIE 1931 XYZ) + AVCOL_PRI_SMPTEST428_1 = AVCOL_PRI_SMPTE428, + AVCOL_PRI_SMPTE431 = 11, ///< SMPTE ST 431-2 (2011) / DCI P3 + AVCOL_PRI_SMPTE432 = 12, ///< SMPTE ST 432-1 (2010) / P3 D65 / Display P3 + AVCOL_PRI_EBU3213 = 22, ///< EBU Tech. 3213-E (nothing there) / one of JEDEC P22 group phosphors + AVCOL_PRI_JEDEC_P22 = AVCOL_PRI_EBU3213, + AVCOL_PRI_NB ///< Not part of ABI +}; + +/** + * Color Transfer Characteristic. + * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.2. + */ +enum AVColorTransferCharacteristic { + AVCOL_TRC_RESERVED0 = 0, + AVCOL_TRC_BT709 = 1, ///< also ITU-R BT1361 + AVCOL_TRC_UNSPECIFIED = 2, + AVCOL_TRC_RESERVED = 3, + AVCOL_TRC_GAMMA22 = 4, ///< also ITU-R BT470M / ITU-R BT1700 625 PAL & SECAM + AVCOL_TRC_GAMMA28 = 5, ///< also ITU-R BT470BG + AVCOL_TRC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 or 625 / ITU-R BT1358 525 or 625 / ITU-R BT1700 NTSC + AVCOL_TRC_SMPTE240M = 7, + AVCOL_TRC_LINEAR = 8, ///< "Linear transfer characteristics" + AVCOL_TRC_LOG = 9, ///< "Logarithmic transfer characteristic (100:1 range)" + AVCOL_TRC_LOG_SQRT = 10, ///< "Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)" + AVCOL_TRC_IEC61966_2_4 = 11, ///< IEC 61966-2-4 + AVCOL_TRC_BT1361_ECG = 12, ///< ITU-R BT1361 Extended Colour Gamut + AVCOL_TRC_IEC61966_2_1 = 13, ///< IEC 61966-2-1 (sRGB or sYCC) + AVCOL_TRC_BT2020_10 = 14, ///< ITU-R BT2020 for 10-bit system + AVCOL_TRC_BT2020_12 = 15, ///< ITU-R BT2020 for 12-bit system + AVCOL_TRC_SMPTE2084 = 16, ///< SMPTE ST 2084 for 10-, 12-, 14- and 16-bit systems + AVCOL_TRC_SMPTEST2084 = AVCOL_TRC_SMPTE2084, + AVCOL_TRC_SMPTE428 = 17, ///< SMPTE ST 428-1 + AVCOL_TRC_SMPTEST428_1 = AVCOL_TRC_SMPTE428, + AVCOL_TRC_ARIB_STD_B67 = 18, ///< ARIB STD-B67, known as "Hybrid log-gamma" + AVCOL_TRC_NB ///< Not part of ABI +}; + +/** + * YUV colorspace type. + * These values match the ones defined by ISO/IEC 23091-2_2019 subclause 8.3. + */ +enum AVColorSpace { + AVCOL_SPC_RGB = 0, ///< order of coefficients is actually GBR, also IEC 61966-2-1 (sRGB), YZX and ST 428-1 + AVCOL_SPC_BT709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 xvYCC709 / derived in SMPTE RP 177 Annex B + AVCOL_SPC_UNSPECIFIED = 2, + AVCOL_SPC_RESERVED = 3, ///< reserved for future use by ITU-T and ISO/IEC just like 15-255 are + AVCOL_SPC_FCC = 4, ///< FCC Title 47 Code of Federal Regulations 73.682 (a)(20) + AVCOL_SPC_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM / IEC 61966-2-4 xvYCC601 + AVCOL_SPC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC / functionally identical to above + AVCOL_SPC_SMPTE240M = 7, ///< derived from 170M primaries and D65 white point, 170M is derived from BT470 System M's primaries + AVCOL_SPC_YCGCO = 8, ///< used by Dirac / VC-2 and H.264 FRext, see ITU-T SG16 + AVCOL_SPC_YCOCG = AVCOL_SPC_YCGCO, + AVCOL_SPC_BT2020_NCL = 9, ///< ITU-R BT2020 non-constant luminance system + AVCOL_SPC_BT2020_CL = 10, ///< ITU-R BT2020 constant luminance system + AVCOL_SPC_SMPTE2085 = 11, ///< SMPTE 2085, Y'D'zD'x + AVCOL_SPC_CHROMA_DERIVED_NCL = 12, ///< Chromaticity-derived non-constant luminance system + AVCOL_SPC_CHROMA_DERIVED_CL = 13, ///< Chromaticity-derived constant luminance system + AVCOL_SPC_ICTCP = 14, ///< ITU-R BT.2100-0, ICtCp + AVCOL_SPC_NB ///< Not part of ABI +}; + +/** + * Visual content value range. + * + * These values are based on definitions that can be found in multiple + * specifications, such as ITU-T BT.709 (3.4 - Quantization of RGB, luminance + * and colour-difference signals), ITU-T BT.2020 (Table 5 - Digital + * Representation) as well as ITU-T BT.2100 (Table 9 - Digital 10- and 12-bit + * integer representation). At the time of writing, the BT.2100 one is + * recommended, as it also defines the full range representation. + * + * Common definitions: + * - For RGB and luma planes such as Y in YCbCr and I in ICtCp, + * 'E' is the original value in range of 0.0 to 1.0. + * - For chroma planes such as Cb,Cr and Ct,Cp, 'E' is the original + * value in range of -0.5 to 0.5. + * - 'n' is the output bit depth. + * - For additional definitions such as rounding and clipping to valid n + * bit unsigned integer range, please refer to BT.2100 (Table 9). + */ +enum AVColorRange { + AVCOL_RANGE_UNSPECIFIED = 0, + + /** + * Narrow or limited range content. + * + * - For luma planes: + * + * (219 * E + 16) * 2^(n-8) + * + * F.ex. the range of 16-235 for 8 bits + * + * - For chroma planes: + * + * (224 * E + 128) * 2^(n-8) + * + * F.ex. the range of 16-240 for 8 bits + */ + AVCOL_RANGE_MPEG = 1, + + /** + * Full range content. + * + * - For RGB and luma planes: + * + * (2^n - 1) * E + * + * F.ex. the range of 0-255 for 8 bits + * + * - For chroma planes: + * + * (2^n - 1) * E + 2^(n - 1) + * + * F.ex. the range of 1-255 for 8 bits + */ + AVCOL_RANGE_JPEG = 2, + AVCOL_RANGE_NB ///< Not part of ABI +}; + +/** + * Location of chroma samples. + * + * Illustration showing the location of the first (top left) chroma sample of the + * image, the left shows only luma, the right + * shows the location of the chroma sample, the 2 could be imagined to overlay + * each other but are drawn separately due to limitations of ASCII + * + * 1st 2nd 1st 2nd horizontal luma sample positions + * v v v v + * ______ ______ + *1st luma line > |X X ... |3 4 X ... X are luma samples, + * | |1 2 1-6 are possible chroma positions + *2nd luma line > |X X ... |5 6 X ... 0 is undefined/unknown position + */ +enum AVChromaLocation { + AVCHROMA_LOC_UNSPECIFIED = 0, + AVCHROMA_LOC_LEFT = 1, ///< MPEG-2/4 4:2:0, H.264 default for 4:2:0 + AVCHROMA_LOC_CENTER = 2, ///< MPEG-1 4:2:0, JPEG 4:2:0, H.263 4:2:0 + AVCHROMA_LOC_TOPLEFT = 3, ///< ITU-R 601, SMPTE 274M 296M S314M(DV 4:1:1), mpeg2 4:2:2 + AVCHROMA_LOC_TOP = 4, + AVCHROMA_LOC_BOTTOMLEFT = 5, + AVCHROMA_LOC_BOTTOM = 6, + AVCHROMA_LOC_NB ///< Not part of ABI +}; + +#endif /* AVUTIL_PIXFMT_H */ diff --git a/dreamcast/pvrtex/libavutil/rational.c b/dreamcast/pvrtex/libavutil/rational.c new file mode 100644 index 00000000..eb148ddb --- /dev/null +++ b/dreamcast/pvrtex/libavutil/rational.c @@ -0,0 +1,193 @@ +/* + * rational numbers + * Copyright (c) 2003 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * rational numbers + * @author Michael Niedermayer + */ + +#include "avassert.h" +#include + +#include "common.h" +#include "mathematics.h" +#include "rational.h" + +int av_reduce(int *dst_num, int *dst_den, + int64_t num, int64_t den, int64_t max) +{ + AVRational a0 = { 0, 1 }, a1 = { 1, 0 }; + int sign = (num < 0) ^ (den < 0); + int64_t gcd = av_gcd(FFABS(num), FFABS(den)); + + if (gcd) { + num = FFABS(num) / gcd; + den = FFABS(den) / gcd; + } + if (num <= max && den <= max) { + a1 = (AVRational) { num, den }; + den = 0; + } + + while (den) { + uint64_t x = num / den; + int64_t next_den = num - den * x; + int64_t a2n = x * a1.num + a0.num; + int64_t a2d = x * a1.den + a0.den; + + if (a2n > max || a2d > max) { + if (a1.num) x = (max - a0.num) / a1.num; + if (a1.den) x = FFMIN(x, (max - a0.den) / a1.den); + + if (den * (2 * x * a1.den + a0.den) > num * a1.den) + a1 = (AVRational) { x * a1.num + a0.num, x * a1.den + a0.den }; + break; + } + + a0 = a1; + a1 = (AVRational) { a2n, a2d }; + num = den; + den = next_den; + } + av_assert2(av_gcd(a1.num, a1.den) <= 1U); + av_assert2(a1.num <= max && a1.den <= max); + + *dst_num = sign ? -a1.num : a1.num; + *dst_den = a1.den; + + return den == 0; +} + +AVRational av_mul_q(AVRational b, AVRational c) +{ + av_reduce(&b.num, &b.den, + b.num * (int64_t) c.num, + b.den * (int64_t) c.den, INT_MAX); + return b; +} + +AVRational av_div_q(AVRational b, AVRational c) +{ + return av_mul_q(b, (AVRational) { c.den, c.num }); +} + +AVRational av_add_q(AVRational b, AVRational c) { + av_reduce(&b.num, &b.den, + b.num * (int64_t) c.den + + c.num * (int64_t) b.den, + b.den * (int64_t) c.den, INT_MAX); + return b; +} + +AVRational av_sub_q(AVRational b, AVRational c) +{ + return av_add_q(b, (AVRational) { -c.num, c.den }); +} + +AVRational av_d2q(double d, int max) +{ + AVRational a; + int exponent; + int64_t den; + if (isnan(d)) + return (AVRational) { 0,0 }; + if (fabs(d) > INT_MAX + 3LL) + return (AVRational) { d < 0 ? -1 : 1, 0 }; + frexp(d, &exponent); + exponent = FFMAX(exponent-1, 0); + den = 1LL << (61 - exponent); + // (int64_t)rint() and llrint() do not work with gcc on ia64 and sparc64, + // see Ticket2713 for affected gcc/glibc versions + av_reduce(&a.num, &a.den, floor(d * den + 0.5), den, max); + if ((!a.num || !a.den) && d && max>0 && max n => a*d/b > n */ + int64_t x_up = av_rescale_rnd(a, q.den, b, AV_ROUND_UP); + + /* rnd_down(a*d/b) < n => a*d/b < n */ + int64_t x_down = av_rescale_rnd(a, q.den, b, AV_ROUND_DOWN); + + return ((x_up > q.num) - (x_down < q.num)) * av_cmp_q(q2, q1); +} + +int av_find_nearest_q_idx(AVRational q, const AVRational* q_list) +{ + int i, nearest_q_idx = 0; + for (i = 0; q_list[i].den; i++) + if (av_nearer_q(q, q_list[i], q_list[nearest_q_idx]) > 0) + nearest_q_idx = i; + + return nearest_q_idx; +} + +uint32_t av_q2intfloat(AVRational q) { + int64_t n; + int shift; + int sign = 0; + + if (q.den < 0) { + q.den *= -1; + q.num *= -1; + } + if (q.num < 0) { + q.num *= -1; + sign = 1; + } + + if (!q.num && !q.den) return 0xFFC00000; + if (!q.num) return 0; + if (!q.den) return 0x7F800000 | (q.num & 0x80000000); + + shift = 23 + av_log2(q.den) - av_log2(q.num); + if (shift >= 0) n = av_rescale(q.num, 1LL<= (1<<24); + shift += n < (1<<23); + + if (shift >= 0) n = av_rescale(q.num, 1LL<= (1<<23)); + + return sign<<31 | (150-shift)<<23 | (n - (1<<23)); +} + +AVRational av_gcd_q(AVRational a, AVRational b, int max_den, AVRational def) +{ + int64_t gcd, lcm; + + gcd = av_gcd(a.den, b.den); + lcm = (a.den / gcd) * b.den; + return lcm < max_den ? av_make_q(av_gcd(a.num, b.num), lcm) : def; +} diff --git a/dreamcast/pvrtex/libavutil/rational.h b/dreamcast/pvrtex/libavutil/rational.h new file mode 100644 index 00000000..8cbfc8e0 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/rational.h @@ -0,0 +1,221 @@ +/* + * rational numbers + * Copyright (c) 2003 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_math_rational + * Utilties for rational number calculation. + * @author Michael Niedermayer + */ + +#ifndef AVUTIL_RATIONAL_H +#define AVUTIL_RATIONAL_H + +#include +#include +#include "attributes.h" + +/** + * @defgroup lavu_math_rational AVRational + * @ingroup lavu_math + * Rational number calculation. + * + * While rational numbers can be expressed as floating-point numbers, the + * conversion process is a lossy one, so are floating-point operations. On the + * other hand, the nature of FFmpeg demands highly accurate calculation of + * timestamps. This set of rational number utilities serves as a generic + * interface for manipulating rational numbers as pairs of numerators and + * denominators. + * + * Many of the functions that operate on AVRational's have the suffix `_q`, in + * reference to the mathematical symbol "â„š" (Q) which denotes the set of all + * rational numbers. + * + * @{ + */ + +/** + * Rational number (pair of numerator and denominator). + */ +typedef struct AVRational{ + int num; ///< Numerator + int den; ///< Denominator +} AVRational; + +/** + * Create an AVRational. + * + * Useful for compilers that do not support compound literals. + * + * @note The return value is not reduced. + * @see av_reduce() + */ +static inline AVRational av_make_q(int num, int den) +{ + AVRational r = { num, den }; + return r; +} + +/** + * Compare two rationals. + * + * @param a First rational + * @param b Second rational + * + * @return One of the following values: + * - 0 if `a == b` + * - 1 if `a > b` + * - -1 if `a < b` + * - `INT_MIN` if one of the values is of the form `0 / 0` + */ +static inline int av_cmp_q(AVRational a, AVRational b){ + const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den; + + if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1; + else if(b.den && a.den) return 0; + else if(a.num && b.num) return (a.num>>31) - (b.num>>31); + else return INT_MIN; +} + +/** + * Convert an AVRational to a `double`. + * @param a AVRational to convert + * @return `a` in floating-point form + * @see av_d2q() + */ +static inline double av_q2d(AVRational a){ + return a.num / (double) a.den; +} + +/** + * Reduce a fraction. + * + * This is useful for framerate calculations. + * + * @param[out] dst_num Destination numerator + * @param[out] dst_den Destination denominator + * @param[in] num Source numerator + * @param[in] den Source denominator + * @param[in] max Maximum allowed values for `dst_num` & `dst_den` + * @return 1 if the operation is exact, 0 otherwise + */ +int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max); + +/** + * Multiply two rationals. + * @param b First rational + * @param c Second rational + * @return b*c + */ +AVRational av_mul_q(AVRational b, AVRational c) av_const; + +/** + * Divide one rational by another. + * @param b First rational + * @param c Second rational + * @return b/c + */ +AVRational av_div_q(AVRational b, AVRational c) av_const; + +/** + * Add two rationals. + * @param b First rational + * @param c Second rational + * @return b+c + */ +AVRational av_add_q(AVRational b, AVRational c) av_const; + +/** + * Subtract one rational from another. + * @param b First rational + * @param c Second rational + * @return b-c + */ +AVRational av_sub_q(AVRational b, AVRational c) av_const; + +/** + * Invert a rational. + * @param q value + * @return 1 / q + */ +static av_always_inline AVRational av_inv_q(AVRational q) +{ + AVRational r = { q.den, q.num }; + return r; +} + +/** + * Convert a double precision floating point number to a rational. + * + * In case of infinity, the returned value is expressed as `{1, 0}` or + * `{-1, 0}` depending on the sign. + * + * @param d `double` to convert + * @param max Maximum allowed numerator and denominator + * @return `d` in AVRational form + * @see av_q2d() + */ +AVRational av_d2q(double d, int max) av_const; + +/** + * Find which of the two rationals is closer to another rational. + * + * @param q Rational to be compared against + * @param q1 Rational to be tested + * @param q2 Rational to be tested + * @return One of the following values: + * - 1 if `q1` is nearer to `q` than `q2` + * - -1 if `q2` is nearer to `q` than `q1` + * - 0 if they have the same distance + */ +int av_nearer_q(AVRational q, AVRational q1, AVRational q2); + +/** + * Find the value in a list of rationals nearest a given reference rational. + * + * @param q Reference rational + * @param q_list Array of rationals terminated by `{0, 0}` + * @return Index of the nearest value found in the array + */ +int av_find_nearest_q_idx(AVRational q, const AVRational* q_list); + +/** + * Convert an AVRational to a IEEE 32-bit `float` expressed in fixed-point + * format. + * + * @param q Rational to be converted + * @return Equivalent floating-point value, expressed as an unsigned 32-bit + * integer. + * @note The returned value is platform-indepedant. + */ +uint32_t av_q2intfloat(AVRational q); + +/** + * Return the best rational so that a and b are multiple of it. + * If the resulting denominator is larger than max_den, return def. + */ +AVRational av_gcd_q(AVRational a, AVRational b, int max_den, AVRational def); + +/** + * @} + */ + +#endif /* AVUTIL_RATIONAL_H */ diff --git a/dreamcast/pvrtex/libavutil/thread.h b/dreamcast/pvrtex/libavutil/thread.h new file mode 100644 index 00000000..2f5e7e1c --- /dev/null +++ b/dreamcast/pvrtex/libavutil/thread.h @@ -0,0 +1,204 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// This header should only be used to simplify code where +// threading is optional, not as a generic threading abstraction. + +#ifndef AVUTIL_THREAD_H +#define AVUTIL_THREAD_H + +#include "config.h" + +#if HAVE_PRCTL +#include +#endif + +#include "error.h" + +#if HAVE_PTHREADS || HAVE_W32THREADS || HAVE_OS2THREADS + +#if HAVE_PTHREADS +#include + +#if defined(ASSERT_LEVEL) && ASSERT_LEVEL > 1 + +#include + +#include "log.h" +#include "macros.h" + +#define ASSERT_PTHREAD_ABORT(func, ret) do { \ + char errbuf[AV_ERROR_MAX_STRING_SIZE] = ""; \ + av_log(NULL, AV_LOG_FATAL, AV_STRINGIFY(func) \ + " failed with error: %s\n", \ + av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, \ + AVERROR(ret))); \ + abort(); \ +} while (0) + +#define ASSERT_PTHREAD_NORET(func, ...) do { \ + int ret = func(__VA_ARGS__); \ + if (ret) \ + ASSERT_PTHREAD_ABORT(func, ret); \ +} while (0) + +#define ASSERT_PTHREAD(func, ...) do { \ + ASSERT_PTHREAD_NORET(func, __VA_ARGS__); \ + return 0; \ +} while (0) + +static inline int strict_pthread_join(pthread_t thread, void **value_ptr) +{ + ASSERT_PTHREAD(pthread_join, thread, value_ptr); +} + +static inline int strict_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) +{ + if (attr) { + ASSERT_PTHREAD_NORET(pthread_mutex_init, mutex, attr); + } else { + pthread_mutexattr_t local_attr; + ASSERT_PTHREAD_NORET(pthread_mutexattr_init, &local_attr); + ASSERT_PTHREAD_NORET(pthread_mutexattr_settype, &local_attr, PTHREAD_MUTEX_ERRORCHECK); + ASSERT_PTHREAD_NORET(pthread_mutex_init, mutex, &local_attr); + ASSERT_PTHREAD_NORET(pthread_mutexattr_destroy, &local_attr); + } + return 0; +} + +static inline int strict_pthread_mutex_destroy(pthread_mutex_t *mutex) +{ + ASSERT_PTHREAD(pthread_mutex_destroy, mutex); +} + +static inline int strict_pthread_mutex_lock(pthread_mutex_t *mutex) +{ + ASSERT_PTHREAD(pthread_mutex_lock, mutex); +} + +static inline int strict_pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + ASSERT_PTHREAD(pthread_mutex_unlock, mutex); +} + +static inline int strict_pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) +{ + ASSERT_PTHREAD(pthread_cond_init, cond, attr); +} + +static inline int strict_pthread_cond_destroy(pthread_cond_t *cond) +{ + ASSERT_PTHREAD(pthread_cond_destroy, cond); +} + +static inline int strict_pthread_cond_signal(pthread_cond_t *cond) +{ + ASSERT_PTHREAD(pthread_cond_signal, cond); +} + +static inline int strict_pthread_cond_broadcast(pthread_cond_t *cond) +{ + ASSERT_PTHREAD(pthread_cond_broadcast, cond); +} + +static inline int strict_pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + ASSERT_PTHREAD(pthread_cond_wait, cond, mutex); +} + +static inline int strict_pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, + const struct timespec *abstime) +{ + int ret = pthread_cond_timedwait(cond, mutex, abstime); + if (ret && ret != ETIMEDOUT) + ASSERT_PTHREAD_ABORT(pthread_cond_timedwait, ret); + return ret; +} + +static inline int strict_pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) +{ + ASSERT_PTHREAD(pthread_once, once_control, init_routine); +} + +#define pthread_join strict_pthread_join +#define pthread_mutex_init strict_pthread_mutex_init +#define pthread_mutex_destroy strict_pthread_mutex_destroy +#define pthread_mutex_lock strict_pthread_mutex_lock +#define pthread_mutex_unlock strict_pthread_mutex_unlock +#define pthread_cond_init strict_pthread_cond_init +#define pthread_cond_destroy strict_pthread_cond_destroy +#define pthread_cond_signal strict_pthread_cond_signal +#define pthread_cond_broadcast strict_pthread_cond_broadcast +#define pthread_cond_wait strict_pthread_cond_wait +#define pthread_cond_timedwait strict_pthread_cond_timedwait +#define pthread_once strict_pthread_once +#endif + +#elif HAVE_OS2THREADS +#include "compat/os2threads.h" +#else +#include "compat/w32pthreads.h" +#endif + +#define AVMutex pthread_mutex_t +#define AV_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER + +#define ff_mutex_init pthread_mutex_init +#define ff_mutex_lock pthread_mutex_lock +#define ff_mutex_unlock pthread_mutex_unlock +#define ff_mutex_destroy pthread_mutex_destroy + +#define AVOnce pthread_once_t +#define AV_ONCE_INIT PTHREAD_ONCE_INIT + +#define ff_thread_once(control, routine) pthread_once(control, routine) + +#else + +#define AVMutex char +#define AV_MUTEX_INITIALIZER 0 + +static inline int ff_mutex_init(AVMutex *mutex, const void *attr){ return 0; } +static inline int ff_mutex_lock(AVMutex *mutex){ return 0; } +static inline int ff_mutex_unlock(AVMutex *mutex){ return 0; } +static inline int ff_mutex_destroy(AVMutex *mutex){ return 0; } + +#define AVOnce char +#define AV_ONCE_INIT 0 + +static inline int ff_thread_once(char *control, void (*routine)(void)) +{ + if (!*control) { + routine(); + *control = 1; + } + return 0; +} + +#endif + +static inline int ff_thread_setname(const char *name) +{ +#if HAVE_PRCTL + return AVERROR(prctl(PR_SET_NAME, name)); +#endif + + return AVERROR(ENOSYS); +} + +#endif /* AVUTIL_THREAD_H */ diff --git a/dreamcast/pvrtex/libavutil/version.h b/dreamcast/pvrtex/libavutil/version.h new file mode 100644 index 00000000..0d843449 --- /dev/null +++ b/dreamcast/pvrtex/libavutil/version.h @@ -0,0 +1,128 @@ +/* + * copyright (c) 2003 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu + * Libavutil version macros + */ + +#ifndef AVUTIL_VERSION_H +#define AVUTIL_VERSION_H + +#include "macros.h" + +/** + * @addtogroup version_utils + * + * Useful to check and match library version in order to maintain + * backward compatibility. + * + * The FFmpeg libraries follow a versioning sheme very similar to + * Semantic Versioning (http://semver.org/) + * The difference is that the component called PATCH is called MICRO in FFmpeg + * and its value is reset to 100 instead of 0 to keep it above or equal to 100. + * Also we do not increase MICRO for every bugfix or change in git master. + * + * Prior to FFmpeg 3.2 point releases did not change any lib version number to + * avoid aliassing different git master checkouts. + * Starting with FFmpeg 3.2, the released library versions will occupy + * a separate MAJOR.MINOR that is not used on the master development branch. + * That is if we branch a release of master 55.10.123 we will bump to 55.11.100 + * for the release and master will continue at 55.12.100 after it. Each new + * point release will then bump the MICRO improving the usefulness of the lib + * versions. + * + * @{ + */ + +#define AV_VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c)) +#define AV_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define AV_VERSION(a, b, c) AV_VERSION_DOT(a, b, c) + +/** + * Extract version components from the full ::AV_VERSION_INT int as returned + * by functions like ::avformat_version() and ::avcodec_version() + */ +#define AV_VERSION_MAJOR(a) ((a) >> 16) +#define AV_VERSION_MINOR(a) (((a) & 0x00FF00) >> 8) +#define AV_VERSION_MICRO(a) ((a) & 0xFF) + +/** + * @} + */ + +/** + * @defgroup lavu_ver Version and Build diagnostics + * + * Macros and function useful to check at compiletime and at runtime + * which version of libavutil is in use. + * + * @{ + */ + +#define LIBAVUTIL_VERSION_MAJOR 58 +#define LIBAVUTIL_VERSION_MINOR 12 +#define LIBAVUTIL_VERSION_MICRO 100 + +#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ + LIBAVUTIL_VERSION_MINOR, \ + LIBAVUTIL_VERSION_MICRO) +#define LIBAVUTIL_VERSION AV_VERSION(LIBAVUTIL_VERSION_MAJOR, \ + LIBAVUTIL_VERSION_MINOR, \ + LIBAVUTIL_VERSION_MICRO) +#define LIBAVUTIL_BUILD LIBAVUTIL_VERSION_INT + +#define LIBAVUTIL_IDENT "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION) + +/** + * @defgroup lavu_depr_guards Deprecation Guards + * FF_API_* defines may be placed below to indicate public API that will be + * dropped at a future version bump. The defines themselves are not part of + * the public API and may change, break or disappear at any time. + * + * @note, when bumping the major version it is recommended to manually + * disable each FF_API_* in its own commit instead of disabling them all + * at once through the bump. This improves the git bisect-ability of the change. + * + * @{ + */ + +#define FF_API_FIFO_PEEK2 (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_FIFO_OLD_API (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_XVMC (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_OLD_CHANNEL_LAYOUT (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_AV_FOPEN_UTF8 (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_PKT_DURATION (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_REORDERED_OPAQUE (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_FRAME_PICTURE_NUMBER (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_HDR_VIVID_THREE_SPLINE (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_FRAME_PKT (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_INTERLACED_FRAME (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_FRAME_KEY (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_PALETTE_HAS_CHANGED (LIBAVUTIL_VERSION_MAJOR < 59) +#define FF_API_VULKAN_CONTIGUOUS_MEMORY (LIBAVUTIL_VERSION_MAJOR < 59) + +/** + * @} + * @} + */ + +#endif /* AVUTIL_VERSION_H */ diff --git a/dreamcast/pvrtex/log.c b/dreamcast/pvrtex/log.c new file mode 100644 index 00000000..41b4fba5 --- /dev/null +++ b/dreamcast/pvrtex/log.c @@ -0,0 +1,492 @@ +/* + * log functions + * Copyright (c) 2003 Michel Bardiaux + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * logging functions + */ + +#include "config.h" + +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_IO_H +#include +#endif +#include +#include +#include +#include +#include +#include "bprint.h" +#include "common.h" +#include "internal.h" +#include "log.h" +#include "thread.h" + +static AVMutex mutex = AV_MUTEX_INITIALIZER; + +#define LINE_SZ 1024 + +#if HAVE_VALGRIND_VALGRIND_H +#include +/* this is the log level at which valgrind will output a full backtrace */ +#define BACKTRACE_LOGLEVEL AV_LOG_ERROR +#endif + +static int av_log_level = AV_LOG_INFO; +static int flags; + +#define NB_LEVELS 8 +#if defined(_WIN32) && HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE +#include +static const uint8_t color[16 + AV_CLASS_CATEGORY_NB] = { + [AV_LOG_PANIC /8] = 12, + [AV_LOG_FATAL /8] = 12, + [AV_LOG_ERROR /8] = 12, + [AV_LOG_WARNING/8] = 14, + [AV_LOG_INFO /8] = 7, + [AV_LOG_VERBOSE/8] = 10, + [AV_LOG_DEBUG /8] = 10, + [AV_LOG_TRACE /8] = 8, + [16+AV_CLASS_CATEGORY_NA ] = 7, + [16+AV_CLASS_CATEGORY_INPUT ] = 13, + [16+AV_CLASS_CATEGORY_OUTPUT ] = 5, + [16+AV_CLASS_CATEGORY_MUXER ] = 13, + [16+AV_CLASS_CATEGORY_DEMUXER ] = 5, + [16+AV_CLASS_CATEGORY_ENCODER ] = 11, + [16+AV_CLASS_CATEGORY_DECODER ] = 3, + [16+AV_CLASS_CATEGORY_FILTER ] = 10, + [16+AV_CLASS_CATEGORY_BITSTREAM_FILTER] = 9, + [16+AV_CLASS_CATEGORY_SWSCALER ] = 7, + [16+AV_CLASS_CATEGORY_SWRESAMPLER ] = 7, + [16+AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT ] = 13, + [16+AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT ] = 5, + [16+AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT ] = 13, + [16+AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT ] = 5, + [16+AV_CLASS_CATEGORY_DEVICE_OUTPUT ] = 13, + [16+AV_CLASS_CATEGORY_DEVICE_INPUT ] = 5, +}; + +static int16_t background, attr_orig; +static HANDLE con; +#else + +static const uint32_t color[16 + AV_CLASS_CATEGORY_NB] = { + [AV_LOG_PANIC /8] = 52 << 16 | 196 << 8 | 0x41, + [AV_LOG_FATAL /8] = 208 << 8 | 0x41, + [AV_LOG_ERROR /8] = 196 << 8 | 0x11, + [AV_LOG_WARNING/8] = 226 << 8 | 0x03, + [AV_LOG_INFO /8] = 253 << 8 | 0x09, + [AV_LOG_VERBOSE/8] = 40 << 8 | 0x02, + [AV_LOG_DEBUG /8] = 34 << 8 | 0x02, + [AV_LOG_TRACE /8] = 34 << 8 | 0x07, + [16+AV_CLASS_CATEGORY_NA ] = 250 << 8 | 0x09, + [16+AV_CLASS_CATEGORY_INPUT ] = 219 << 8 | 0x15, + [16+AV_CLASS_CATEGORY_OUTPUT ] = 201 << 8 | 0x05, + [16+AV_CLASS_CATEGORY_MUXER ] = 213 << 8 | 0x15, + [16+AV_CLASS_CATEGORY_DEMUXER ] = 207 << 8 | 0x05, + [16+AV_CLASS_CATEGORY_ENCODER ] = 51 << 8 | 0x16, + [16+AV_CLASS_CATEGORY_DECODER ] = 39 << 8 | 0x06, + [16+AV_CLASS_CATEGORY_FILTER ] = 155 << 8 | 0x12, + [16+AV_CLASS_CATEGORY_BITSTREAM_FILTER] = 192 << 8 | 0x14, + [16+AV_CLASS_CATEGORY_SWSCALER ] = 153 << 8 | 0x14, + [16+AV_CLASS_CATEGORY_SWRESAMPLER ] = 147 << 8 | 0x14, + [16+AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT ] = 213 << 8 | 0x15, + [16+AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT ] = 207 << 8 | 0x05, + [16+AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT ] = 213 << 8 | 0x15, + [16+AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT ] = 207 << 8 | 0x05, + [16+AV_CLASS_CATEGORY_DEVICE_OUTPUT ] = 213 << 8 | 0x15, + [16+AV_CLASS_CATEGORY_DEVICE_INPUT ] = 207 << 8 | 0x05, +}; + +#endif +static int use_color = -1; + +#if defined(_WIN32) && HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE +static void win_console_puts(const char *str) +{ + const uint8_t *q = str; + uint16_t line[LINE_SZ]; + + while (*q) { + uint16_t *buf = line; + DWORD nb_chars = 0; + DWORD written; + + while (*q && nb_chars < LINE_SZ - 1) { + uint32_t ch; + uint16_t tmp; + + GET_UTF8(ch, *q ? *q++ : 0, ch = 0xfffd; goto continue_on_invalid;) +continue_on_invalid: + PUT_UTF16(ch, tmp, *buf++ = tmp; nb_chars++;) + } + + WriteConsoleW(con, line, nb_chars, &written, NULL); + } +} +#endif + +static void check_color_terminal(void) +{ + char *term = getenv("TERM"); + +#if defined(_WIN32) && HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE + CONSOLE_SCREEN_BUFFER_INFO con_info; + DWORD dummy; + con = GetStdHandle(STD_ERROR_HANDLE); + if (con != INVALID_HANDLE_VALUE && !GetConsoleMode(con, &dummy)) + con = INVALID_HANDLE_VALUE; + if (con != INVALID_HANDLE_VALUE) { + GetConsoleScreenBufferInfo(con, &con_info); + attr_orig = con_info.wAttributes; + background = attr_orig & 0xF0; + } +#endif + + if (getenv("AV_LOG_FORCE_NOCOLOR")) { + use_color = 0; + } else if (getenv("AV_LOG_FORCE_COLOR")) { + use_color = 1; + } else { +#if defined(_WIN32) && HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE + use_color = (con != INVALID_HANDLE_VALUE); +#elif HAVE_ISATTY + use_color = (term && isatty(2)); +#else + use_color = 0; +#endif + } + + if (getenv("AV_LOG_FORCE_256COLOR") || (term && strstr(term, "256color"))) + use_color *= 256; +} + +static void ansi_fputs(int level, int tint, const char *str, int local_use_color) +{ + if (local_use_color == 1) { + fprintf(stderr, + "\033[%"PRIu32";3%"PRIu32"m%s\033[0m", + (color[level] >> 4) & 15, + color[level] & 15, + str); + } else if (tint && use_color == 256) { + fprintf(stderr, + "\033[48;5;%"PRIu32"m\033[38;5;%dm%s\033[0m", + (color[level] >> 16) & 0xff, + tint, + str); + } else if (local_use_color == 256) { + fprintf(stderr, + "\033[48;5;%"PRIu32"m\033[38;5;%"PRIu32"m%s\033[0m", + (color[level] >> 16) & 0xff, + (color[level] >> 8) & 0xff, + str); + } else + fputs(str, stderr); +} + +static void colored_fputs(int level, int tint, const char *str) +{ + int local_use_color; + if (!*str) + return; + + if (use_color < 0) + check_color_terminal(); + + if (level == AV_LOG_INFO/8) local_use_color = 0; + else local_use_color = use_color; + +#if defined(_WIN32) && HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE + if (con != INVALID_HANDLE_VALUE) { + if (local_use_color) + SetConsoleTextAttribute(con, background | color[level]); + win_console_puts(str); + if (local_use_color) + SetConsoleTextAttribute(con, attr_orig); + } else { + ansi_fputs(level, tint, str, local_use_color); + } +#else + ansi_fputs(level, tint, str, local_use_color); +#endif + +} + +const char *av_default_item_name(void *ptr) +{ + return (*(AVClass **) ptr)->class_name; +} + +AVClassCategory av_default_get_category(void *ptr) +{ + return (*(AVClass **) ptr)->category; +} + +static void sanitize(uint8_t *line){ + while(*line){ + if(*line < 0x08 || (*line > 0x0D && *line < 0x20)) + *line='?'; + line++; + } +} + +static int get_category(void *ptr){ + AVClass *avc = *(AVClass **) ptr; + if( !avc + || (avc->version&0xFF)<100 + || avc->version < (51 << 16 | 59 << 8) + || avc->category >= AV_CLASS_CATEGORY_NB) return AV_CLASS_CATEGORY_NA + 16; + + if(avc->get_category) + return avc->get_category(ptr) + 16; + + return avc->category + 16; +} + +static const char *get_level_str(int level) +{ + switch (level) { + case AV_LOG_QUIET: + return "quiet"; + case AV_LOG_DEBUG: + return "debug"; + case AV_LOG_TRACE: + return "trace"; + case AV_LOG_VERBOSE: + return "verbose"; + case AV_LOG_INFO: + return "info"; + case AV_LOG_WARNING: + return "warning"; + case AV_LOG_ERROR: + return "error"; + case AV_LOG_FATAL: + return "fatal"; + case AV_LOG_PANIC: + return "panic"; + default: + return ""; + } +} + +static void format_line(void *avcl, int level, const char *fmt, va_list vl, + AVBPrint part[4], int *print_prefix, int type[2]) +{ + AVClass* avc = avcl ? *(AVClass **) avcl : NULL; + av_bprint_init(part+0, 0, AV_BPRINT_SIZE_AUTOMATIC); + av_bprint_init(part+1, 0, AV_BPRINT_SIZE_AUTOMATIC); + av_bprint_init(part+2, 0, AV_BPRINT_SIZE_AUTOMATIC); + av_bprint_init(part+3, 0, 65536); + + if(type) type[0] = type[1] = AV_CLASS_CATEGORY_NA + 16; + if (*print_prefix && avc) { + if (avc->parent_log_context_offset) { + AVClass** parent = *(AVClass ***) (((uint8_t *) avcl) + + avc->parent_log_context_offset); + if (parent && *parent) { + av_bprintf(part+0, "[%s @ %p] ", + (*parent)->item_name(parent), parent); + if(type) type[0] = get_category(parent); + } + } + av_bprintf(part+1, "[%s @ %p] ", + avc->item_name(avcl), avcl); + if(type) type[1] = get_category(avcl); + } + + if (*print_prefix && (level > AV_LOG_QUIET) && (flags & AV_LOG_PRINT_LEVEL)) + av_bprintf(part+2, "[%s] ", get_level_str(level)); + + av_vbprintf(part+3, fmt, vl); + + if(*part[0].str || *part[1].str || *part[2].str || *part[3].str) { + char lastc = part[3].len && part[3].len <= part[3].size ? part[3].str[part[3].len - 1] : 0; + *print_prefix = lastc == '\n' || lastc == '\r'; + } +} + +void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl, + char *line, int line_size, int *print_prefix) +{ + av_log_format_line2(ptr, level, fmt, vl, line, line_size, print_prefix); +} + +int av_log_format_line2(void *ptr, int level, const char *fmt, va_list vl, + char *line, int line_size, int *print_prefix) +{ + AVBPrint part[4]; + int ret; + + format_line(ptr, level, fmt, vl, part, print_prefix, NULL); + ret = snprintf(line, line_size, "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str); + av_bprint_finalize(part+3, NULL); + return ret; +} + +void av_log_default_callback(void* ptr, int level, const char* fmt, va_list vl) +{ + static int print_prefix = 1; + static int count; + static char prev[LINE_SZ]; + AVBPrint part[4]; + char line[LINE_SZ]; + static int is_atty; + int type[2]; + unsigned tint = 0; + + if (level >= 0) { + tint = level & 0xff00; + level &= 0xff; + } + + if (level > av_log_level) + return; + ff_mutex_lock(&mutex); + + format_line(ptr, level, fmt, vl, part, &print_prefix, type); + snprintf(line, sizeof(line), "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str); + +#if HAVE_ISATTY + if (!is_atty) + is_atty = isatty(2) ? 1 : -1; +#endif + + if (print_prefix && (flags & AV_LOG_SKIP_REPEATED) && !strcmp(line, prev) && + *line && line[strlen(line) - 1] != '\r'){ + count++; + if (is_atty == 1) + fprintf(stderr, " Last message repeated %d times\r", count); + goto end; + } + if (count > 0) { + fprintf(stderr, " Last message repeated %d times\n", count); + count = 0; + } + strcpy(prev, line); + sanitize(part[0].str); + colored_fputs(type[0], 0, part[0].str); + sanitize(part[1].str); + colored_fputs(type[1], 0, part[1].str); + sanitize(part[2].str); + colored_fputs(av_clip(level >> 3, 0, NB_LEVELS - 1), tint >> 8, part[2].str); + sanitize(part[3].str); + colored_fputs(av_clip(level >> 3, 0, NB_LEVELS - 1), tint >> 8, part[3].str); + +#if CONFIG_VALGRIND_BACKTRACE + if (level <= BACKTRACE_LOGLEVEL) + VALGRIND_PRINTF_BACKTRACE("%s", ""); +#endif +end: + av_bprint_finalize(part+3, NULL); + ff_mutex_unlock(&mutex); +} + +static void (*av_log_callback)(void*, int, const char*, va_list) = + av_log_default_callback; + +void av_log(void* avcl, int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + av_vlog(avcl, level, fmt, vl); + va_end(vl); +} + +void av_log_once(void* avcl, int initial_level, int subsequent_level, int *state, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + av_vlog(avcl, *state ? subsequent_level : initial_level, fmt, vl); + va_end(vl); + *state = 1; +} + +void av_vlog(void* avcl, int level, const char *fmt, va_list vl) +{ + AVClass* avc = avcl ? *(AVClass **) avcl : NULL; + void (*log_callback)(void*, int, const char*, va_list) = av_log_callback; + if (avc && avc->version >= (50 << 16 | 15 << 8 | 2) && + avc->log_level_offset_offset && level >= AV_LOG_FATAL) + level += *(int *) (((uint8_t *) avcl) + avc->log_level_offset_offset); + if (log_callback) + log_callback(avcl, level, fmt, vl); +} + +int av_log_get_level(void) +{ + return av_log_level; +} + +void av_log_set_level(int level) +{ + av_log_level = level; +} + +void av_log_set_flags(int arg) +{ + flags = arg; +} + +int av_log_get_flags(void) +{ + return flags; +} + +void av_log_set_callback(void (*callback)(void*, int, const char*, va_list)) +{ + av_log_callback = callback; +} + +static void missing_feature_sample(int sample, void *avc, const char *msg, + va_list argument_list) +{ + av_vlog(avc, AV_LOG_WARNING, msg, argument_list); + av_log(avc, AV_LOG_WARNING, " is not implemented. Update your FFmpeg " + "version to the newest one from Git. If the problem still " + "occurs, it means that your file has a feature which has not " + "been implemented.\n"); + if (sample) + av_log(avc, AV_LOG_WARNING, "If you want to help, upload a sample " + "of this file to https://streams.videolan.org/upload/ " + "and contact the ffmpeg-devel mailing list. (ffmpeg-devel@ffmpeg.org)\n"); +} + +void avpriv_request_sample(void *avc, const char *msg, ...) +{ + va_list argument_list; + + va_start(argument_list, msg); + missing_feature_sample(1, avc, msg, argument_list); + va_end(argument_list); +} + +void avpriv_report_missing_feature(void *avc, const char *msg, ...) +{ + va_list argument_list; + + va_start(argument_list, msg); + missing_feature_sample(0, avc, msg, argument_list); + va_end(argument_list); +} diff --git a/dreamcast/pvrtex/main.c b/dreamcast/pvrtex/main.c new file mode 100644 index 00000000..72e30f44 --- /dev/null +++ b/dreamcast/pvrtex/main.c @@ -0,0 +1,446 @@ +#include +#include +#include +#include +#include +#include + +#include "stb_image_write.h" +#include "pvr_texture_encoder.h" +#include "optparse.h" +#include "mycommon.h" +#include "file_pvr.h" +#include "file_tex.h" + +int log_level = LOG_PROGRESS; +void pteLogLocV(unsigned level, const char *file, unsigned line, const char *fmt, va_list args) { + static const char * logtypes[] = { + [LOG_ALL] = "ALL", + [LOG_DEBUG] = "DEBUG", + [LOG_INFO] = "INFO", + [LOG_PROGRESS] = "PROGRESS", + [LOG_WARNING] = "WARNING", + [LOG_COMPLETION] = "COMPLETION", + [LOG_NONE] = "NONE" + }; + + if (level > log_level) + return; + + if (log_level == LOG_DEBUG) { + if (level >= LOG_DEBUG) + level = LOG_DEBUG; + if (file == NULL) + file = "unk"; + fprintf(stderr, "[%s, ln %i] %s: ", file, line, logtypes[level]); + } + vfprintf(stderr, fmt, args); +} + +void pteLogLoc(unsigned level, const char *file, unsigned line, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + pteLogLocV(level, file, line, fmt, args); + va_end(args); +} + +void ErrorExitV(const char *fmt, va_list args) { + fprintf(stderr, "Error: "); + vfprintf(stderr, fmt, args); + exit(1); +} + +void ErrorExit(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ErrorExitV(fmt, args); + va_end(args); + + exit(1); +} + +void ErrorExitOn(int cond, const char *fmt, ...) { + if (!cond) + return; + + va_list args; + va_start(args, fmt); + ErrorExitV(fmt, args); + va_end(args); +} + +//https://cfengine.com/blog/2021/optional-arguments-with-getopt-long/ +#define OPTARG_FIX_UP do { \ + if (options.optarg == NULL && options.optind < argc && options.argv[options.optind][0] != '-') \ + options.optarg = options.argv[options.optind++]; \ + } while(0) + +typedef struct { + const char *name; + int value; +} OptionMap; + +static const OptionMap supported_pixel_formats[] = { + {"RGB565", PTE_RGB565}, + {"ARGB1555", PTE_ARGB1555}, + {"ARGB4444", PTE_ARGB4444}, + {"YUV", PTE_YUV}, + {"YUV422", PTE_YUV}, + {"PAL8BPP", PTE_PALETTE_8B}, + {"PAL4BPP", PTE_PALETTE_4B}, + {"BUMPMAP", PTE_BUMP}, + {"NORMAL", PTE_NORMAL}, + {"AUTO", PTE_AUTO}, + {"AUTOYUV", PTE_AUTO_YUV}, +}; +static const OptionMap resize_options[] = { + {"none", PTE_FIX_NONE}, + {"near", PTE_FIX_NEAREST}, + {"nearest", PTE_FIX_NEAREST}, + {"up", PTE_FIX_UP}, + {"down", PTE_FIX_DOWN}, +}; +static const OptionMap mip_resize_options[] = { + {"none", PTE_FIX_MIP_NONE}, + {"x2", PTE_FIX_MIP_NARROW_X2}, + {"x4", PTE_FIX_MIP_NARROW_X4}, + {"up", PTE_FIX_MIP_MAX}, + {"down", PTE_FIX_MIP_MIN}, +}; +static const OptionMap edge_options[] = { + {"clamp", STBIR_EDGE_CLAMP}, + {"reflect", STBIR_EDGE_REFLECT}, + {"wrap", STBIR_EDGE_WRAP}, + {"zero", STBIR_EDGE_ZERO}, +}; + +//Search through OptionMap for match and return it's value. +//If name is not found and invalid_msg is NULL, it returns default_value +//If name is not found and invalid_msg is not NULL, it prints invalid_msg and exits +int GetOptMap(const OptionMap *map, size_t mapsize, const char *name, int default_value, const char *invalid_msg) { + if (name == NULL) { + if (invalid_msg) + ErrorExit("%s", invalid_msg); + return default_value; + } + + for(size_t i = 0; i < mapsize; i++) { + if (!strcasecmp(map[i].name, name)) { + return map[i].value; + } + } + if (invalid_msg) + ErrorExit("%s", invalid_msg); + return default_value; +} + + + +int main(int argc, char **argv) { + PvrTexEncoder pte; + pteInit(&pte); + + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {"out", 'o', OPTPARSE_REQUIRED}, + {"in", 'i', OPTPARSE_REQUIRED}, + {"format", 'f', OPTPARSE_REQUIRED}, + {"gamma", 'g', OPTPARSE_REQUIRED}, + {"gamma-alpha", 'G', OPTPARSE_REQUIRED}, + {"compress", 'c', OPTPARSE_OPTIONAL}, + {"max-color", 'C', OPTPARSE_REQUIRED}, + {"mipmap", 'm', OPTPARSE_OPTIONAL}, + {"perfect-mip", 'M', OPTPARSE_OPTIONAL}, + {"high-weight", 'H', OPTPARSE_REQUIRED}, + {"preview", 'p', OPTPARSE_REQUIRED}, + {"bilinear", 'b', OPTPARSE_NONE}, + {"dither", 'd', OPTPARSE_OPTIONAL}, + {"nearest", 'n', OPTPARSE_NONE}, + {"verbose", 'v', OPTPARSE_NONE}, + {"version", 'V', OPTPARSE_NONE}, + {"no-mip-shift", 'S', OPTPARSE_NONE}, + {"resize", 'r', OPTPARSE_OPTIONAL}, + {"mip-resize", 'R', OPTPARSE_OPTIONAL}, + {"stride", 's', OPTPARSE_NONE}, + {"edge", 'e', OPTPARSE_REQUIRED}, + {0} + }; + + #define MAX_FNAMES 11 + const char *fnames[MAX_FNAMES]; + unsigned fname_cnt = 0; + const char *outname = ""; + const char *prevname = ""; + + //Parse command line parameters + struct optparse options; + int option; + optparse_init(&options, argv); + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch(option) { + case 'h': + printf("No help yet\n"); + return 0; + break; + case 'i': + ErrorExitOn(fname_cnt >= MAX_FNAMES, "Too many input files have been specified\n"); + fnames[fname_cnt++] = options.optarg; + break; + case 'o': + outname = options.optarg; + break; + case 'f': + pte.pixel_format = GetOptMap(supported_pixel_formats, ARR_SIZE(supported_pixel_formats), options.optarg, -1, "invalid pixel format\n"); + break; + case 'g': + if (sscanf(options.optarg, "%f", &pte.rgb_gamma) != 1) + ErrorExit("invalid gamma\n"); + break; + case 'G': + if (sscanf(options.optarg, "%f", &pte.alpha_gamma) != 1) + ErrorExit("invalid alpha gamma\n"); + break; + case 'r': + OPTARG_FIX_UP; + pte.resize = PTE_FIX_NEAREST; + if (options.optarg) { + pte.resize = GetOptMap(resize_options, ARR_SIZE(resize_options), options.optarg, PTE_FIX_UP, "invalid resize value\n"); + } + break; + case 'R': + OPTARG_FIX_UP; + pte.mipresize = PTE_FIX_MIP_NARROW_X2; + if (options.optarg) { + pte.mipresize = GetOptMap(mip_resize_options, ARR_SIZE(mip_resize_options), options.optarg, PTE_FIX_MIP_NARROW_X2, "invalid mip resize value\n"); + } + break; + case 'p': + prevname = options.optarg; + break; + case 'S': + pte.mip_shift_correction = false; + break; + case 's': + pte.stride = true; + break; + case 'e': + pte.edge_method = GetOptMap(edge_options, ARR_SIZE(edge_options), options.optarg, -0, "invalid edge handling method\n"); + break; + case 'H': + if (sscanf(options.optarg, "%u", &pte.high_weight_mips) != 1) { + ErrorExit("invalid high weight parameter, must be an integer between 1 and the number of mipmap levels\n"); + } + break; + case 'n': + ErrorExit("Option -%c not supported yet\n", option); + break; + case 'v': + log_level = LOG_INFO; + + //If someone runs this with only -v as a parameter, they probably want the version + if (argc != 2) + break; + //Fallthrough + case 'V': + printf("pvrtex - Dreamcast Texture Encoder - Version 1.01\n"); + return 0; + case 'b': + pteLog(LOG_WARNING, "Option --bilinear does nothing\n"); + break; + case 'd': { + OPTARG_FIX_UP; + pte.dither = 1.0f; + if (options.optarg) { + if ((sscanf(options.optarg, "%f", &pte.dither) != 1) || (pte.dither < 0) || (pte.dither > 1)) { + ErrorExit("invalid dither amount parameter, should be in the range [0, 1]\n"); + } + } + } break; + case 'c': { + OPTARG_FIX_UP; + unsigned cbsize = 256; + if (options.optarg) { + if (!strcasecmp(options.optarg, "small") || !strcasecmp(options.optarg, "sm")) { + pte.auto_small_vq = true; + } else if ((sscanf(options.optarg, "%u", &cbsize) != 1) || (cbsize <= 0) || (cbsize > 256)) { + ErrorExit("invalid compression parameter (%s)\n", options.optarg); + } + else if(cbsize > 0 && cbsize < 256) { + pte.auto_small_vq = true; // output smaller codebook! + } + } + pteSetCompressed(&pte, cbsize); + } break; + case 'm': + OPTARG_FIX_UP; + + pte.want_mips = PTE_MIP_QUALITY; + if (options.optarg) { + if (!strcasecmp(options.optarg, "fast")) + pte.want_mips = PTE_MIP_FAST; + else if (!strcasecmp(options.optarg, "quality")) + ; //default + else + ErrorExit("Unknown mipmap parameter (%s)\n", options.optarg); + } + break; + case 'M': + OPTARG_FIX_UP; + pte.perfect_mips = 3; + if (options.optarg) { + if (sscanf(options.optarg, "%u", &pte.perfect_mips) != 1) { + ErrorExit("bad perfect mip value\n"); + } + } + break; + case 'C': + if ((sscanf(options.optarg, "%u", &pte.palette_size) != 1) || (pte.palette_size <= 1) || (pte.palette_size > 256)) { + ErrorExit("invalid max palette size parameter (should be [1, 16] for 4bpp, or [1, 256] for 8bpp)\n"); + } + break; + default: + ErrorExit("%s\n", options.errmsg); + } + } + + bool have_output = strlen(outname) > 0; + bool have_preview = strlen(prevname) > 0; + + //Get output extension + const char *extension = ""; + if (have_output) { + extension = strrchr(outname, '.'); + if (extension == NULL) + extension = ""; + } + + ErrorExitOn(!have_output && !have_preview, "No output or preview file name specified, nothing to do\n"); + ErrorExitOn(fname_cnt == 0, "No input files specified\n"); + + pteLog(LOG_PROGRESS, "Reading input...\n"); + pteLoadFromFiles(&pte, fnames, fname_cnt); + + //Check and fix up image size + pteSetSize(&pte); + + if (pte.pixel_format == PTE_AUTO || pte.pixel_format == PTE_AUTO_YUV) + pteAutoSelectPixelFormat(&pte); + + //Fix some stuff up for .PVR files + if (strcasecmp(extension, ".pvr") == 0) { + if (pteIsCompressed(&pte)) { + //.PVR seems to require square textures if compressed + // JP - Rectangle VQ certainly does work on real hardware + //pteMakeSquare(&pte); + + if (pte.auto_small_vq == true) { + //For other sizes, we make a full size codebook texture, but don't use all the entries + if(pte.codebook_size == 256) + pte.codebook_size = fPvrSmallVQCodebookSize(pte.w + pte.h, pte.want_mips); + // JP - Rectangle VQ certainly does work on real hardware + /* + if (pte.w != pte.h) { + pteLog(LOG_WARNING, ".PVR file does not support small VQ with non-square textures, using full size codebook\n"); + pte.auto_small_vq = false; + } else*/ if (pte.codebook_size < 256) { + pteLog(LOG_INFO, "Making small codebook .PVR VQ is CB size of %u\n", pte.codebook_size); + + }/* else { + pteLog(LOG_WARNING, ".PVR file does not support small VQ with current size/mipmap combination, using full size codebook\n"); + pte.auto_small_vq = false; + }*/ + } + } + } + + if (strcasecmp(extension, ".dt") == 0) { + if (pte.auto_small_vq) { + //8x8 no mips has 10 entries, 128x128 with mips has 192 entires + //Pick something in between + float small_uncomp = CalcTextureSize(8, 8, PTE_ARGB1555, 0, 0, 0); + float large_uncomp = CalcTextureSize(128, 128, PTE_ARGB1555, 1, 0, 0); + unsigned small_cbsize = 10; + unsigned large_cbsize = 192; + + unsigned idxsize = CalcTextureSize(pte.w, pte.h, PTE_ARGB1555, pteHasMips(&pte), 1, 0); + float uncompsize = CalcTextureSize(pte.w, pte.h, PTE_ARGB1555, pteHasMips(&pte), 0, 0); + + float ratio = (uncompsize - small_uncomp) / (large_uncomp - small_uncomp); + unsigned cbsize = lerp(ratio, small_cbsize, large_cbsize); + + //If size is less than 32, add extra entries to use any padding that would result + unsigned rndamt = 32; + unsigned size = idxsize + cbsize*8; + unsigned roundupsize = (size + rndamt - 1) & ~(rndamt-1); + unsigned extraroom = roundupsize - size; + pteLog(LOG_DEBUG, "Idx %u, CBsize %u, Extra %u\n", idxsize, cbsize, extraroom); + + pte.codebook_size = CLAMP(8, cbsize + extraroom/8, 256); + } + + //.DT supports codebook offsets for true reduced codebooks + pte.pvr_idx_offset = PVR_FULL_CODEBOOK - pte.codebook_size; + } + + //If no edge method is specified, use clamp if no using mipmaps, or wrap if we are + if (pte.edge_method == 0) { + if (pte.want_mips) + pte.edge_method = STBIR_EDGE_WRAP; + else + pte.edge_method = STBIR_EDGE_CLAMP; + } + + pteEncodeTexture(&pte); + + //Make preview + if (have_preview) { + const char *prevextension = strrchr(prevname, '.'); + if (prevextension != NULL) { + pteLog(LOG_PROGRESS, "Writing preview to \"%s\"...\n", prevname); + pteGeneratePreviews(&pte); + + //Write preview image to file + if (strcasecmp(prevextension, ".png") == 0) + stbi_write_png(prevname, pte.final_preview_w, pte.h, 4, pte.final_preview, 0); + else if (strcasecmp(prevextension, ".jpg") == 0 || strcasecmp(prevextension, ".jpeg") == 0) + stbi_write_jpg(prevname, pte.final_preview_w, pte.h, 4, pte.final_preview, 95); + else if (strcasecmp(prevextension, ".bmp") == 0) + stbi_write_bmp(prevname, pte.final_preview_w, pte.h, 4, pte.final_preview); + else if (strcasecmp(prevextension, ".tga") == 0) + stbi_write_tga(prevname, pte.final_preview_w, pte.h, 4, pte.final_preview); + else + pteLog(LOG_WARNING, "Skipping preview creation because of unknown file type (%s). Supported types are PNG, JPG, BMP, and TGA.\n", prevextension); + } else { + pteLog(LOG_WARNING, "No extension specified for preview, don't know what type to make. Supported types are PNG, JPG, BMP, and TGA.\n"); + } + } + + //Write resulting texture + if (have_output) { + if (strcasecmp(extension, ".pvr") == 0) { + pteLog(LOG_COMPLETION, "Writing .PVR to \"%s\"...\n", outname); + fPvrWrite(&pte, outname); + } else if (strcasecmp(extension, ".tex") == 0 || strcasecmp(extension, ".vq") == 0) { + pteLog(LOG_COMPLETION, "Writing texconv .TEX to \"%s\"...\n", outname); + fTexWrite(&pte, outname); + + if (pteIsPalettized(&pte)) + fTexWritePaletteAppendPal(&pte, outname); + } else if (strcasecmp(extension, ".dt") == 0) { + pteLog(LOG_COMPLETION, "Writing .DT to \"%s\"...\n", outname); + void fDtWrite(const PvrTexEncoder *pte, const char *outfname); + fDtWrite(&pte, outname); + + if (pteIsPalettized(&pte)) + fTexWritePaletteAppendPal(&pte, outname); + } else { + ErrorExit("Unsupported output file type: \"%s\"\n", extension); + } + } else { + pteLog(LOG_COMPLETION, "No output file specified\n"); + } + + pteFree(&pte); + + return 0; +} diff --git a/dreamcast/pvrtex/md5.c b/dreamcast/pvrtex/md5.c new file mode 100644 index 00000000..0170d8dd --- /dev/null +++ b/dreamcast/pvrtex/md5.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2006 Michael Niedermayer (michaelni@gmx.at) + * Copyright (C) 2003-2005 by Christopher R. Hertel (crh@ubiqx.mn.org) + * + * References: + * IETF RFC 1321: The MD5 Message-Digest Algorithm + * Ron Rivest. IETF, April, 1992 + * + * based on http://ubiqx.org/libcifs/source/Auth/MD5.c + * from Christopher R. Hertel (crh@ubiqx.mn.org) + * Simplified, cleaned and IMO redundant comments removed by Michael. + * + * If you use gcc, then version 4.1 or later and -fomit-frame-pointer is + * strongly recommended. + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "bswap.h" +#include "intreadwrite.h" +#include "mem.h" +#include "md5.h" + +typedef struct AVMD5 { + uint64_t len; + uint8_t block[64]; + uint32_t ABCD[4]; +} AVMD5; + +const int av_md5_size = sizeof(AVMD5); + +struct AVMD5 *av_md5_alloc(void) +{ + return av_mallocz(sizeof(struct AVMD5)); +} + +static const uint8_t S[4][4] = { + { 7, 12, 17, 22 }, /* round 1 */ + { 5, 9, 14, 20 }, /* round 2 */ + { 4, 11, 16, 23 }, /* round 3 */ + { 6, 10, 15, 21 } /* round 4 */ +}; + +static const uint32_t T[64] = { // T[i]= fabs(sin(i+1)<<32) + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, /* round 1 */ + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, /* round 2 */ + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, /* round 3 */ + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, /* round 4 */ + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, +}; + +#define CORE(i, a, b, c, d) \ + do { \ + t = S[i >> 4][i & 3]; \ + a += T[i]; \ + \ + if (i < 32) { \ + if (i < 16) \ + a += (d ^ (b & (c ^ d))) + AV_RL32(X+( i & 15));\ + else \ + a += ((d & b) | (~d & c)) + AV_RL32(X+((1 + 5*i) & 15));\ + } else { \ + if (i < 48) \ + a += (b ^ c ^ d) + AV_RL32(X+((5 + 3*i) & 15));\ + else \ + a += (c ^ (b | ~d)) + AV_RL32(X+(( 7*i) & 15));\ + } \ + a = b + (a << t | a >> (32 - t)); \ + } while (0) + +static void body(uint32_t ABCD[4], const uint8_t *src, size_t nblocks) +{ + const uint32_t *X; + uint32_t a, b, c, d, t; + + for (size_t n = 0; n < nblocks; n++) { + a = ABCD[3]; + b = ABCD[2]; + c = ABCD[1]; + d = ABCD[0]; + + X = (const uint32_t *)src + n * 16; + +#if CONFIG_SMALL + for (int i = 0; i < 64; i++) { + CORE(i, a, b, c, d); + t = d; + d = c; + c = b; + b = a; + a = t; + } +#else +#define CORE2(i) \ + CORE(i, a, b, c, d); CORE((i + 1), d, a, b, c); \ + CORE((i + 2), c, d, a, b); CORE((i + 3), b, c, d, a) +#define CORE4(i) CORE2(i); CORE2((i + 4)); CORE2((i + 8)); CORE2((i + 12)) + CORE4(0); + CORE4(16); + CORE4(32); + CORE4(48); +#endif + + ABCD[0] += d; + ABCD[1] += c; + ABCD[2] += b; + ABCD[3] += a; + } +} + +void av_md5_init(AVMD5 *ctx) +{ + ctx->len = 0; + + ctx->ABCD[0] = 0x10325476; + ctx->ABCD[1] = 0x98badcfe; + ctx->ABCD[2] = 0xefcdab89; + ctx->ABCD[3] = 0x67452301; +} + +void av_md5_update(AVMD5 *ctx, const uint8_t *src, size_t len) +{ + const uint8_t *end; + int j; + + j = ctx->len & 63; + ctx->len += len; + + if (j) { + int cnt = FFMIN(len, 64 - j); + memcpy(ctx->block + j, src, cnt); + src += cnt; + len -= cnt; + if (j + cnt < 64) + return; + body(ctx->ABCD, ctx->block, 1); + } + + end = src + (len & ~63); + if (!HAVE_FAST_UNALIGNED && ((intptr_t)src & 3)) { + while (src < end) { + memcpy(ctx->block, src, 64); + body(ctx->ABCD, ctx->block, 1); + src += 64; + } + } else { + size_t nblocks = len / 64; + body(ctx->ABCD, src, nblocks); + src = end; + } + len &= 63; + if (len > 0) + memcpy(ctx->block, src, len); +} + +void av_md5_final(AVMD5 *ctx, uint8_t *dst) +{ + int i; + uint64_t finalcount = av_le2ne64(ctx->len << 3); + + av_md5_update(ctx, "\200", 1); + while ((ctx->len & 63) != 56) + av_md5_update(ctx, "", 1); + + av_md5_update(ctx, (uint8_t *) &finalcount, 8); + + for (i = 0; i < 4; i++) + AV_WL32(dst + 4 * i, ctx->ABCD[3 - i]); +} + +void av_md5_sum(uint8_t *dst, const uint8_t *src, size_t len) +{ + AVMD5 ctx; + + av_md5_init(&ctx); + av_md5_update(&ctx, src, len); + av_md5_final(&ctx, dst); +} diff --git a/dreamcast/pvrtex/mem.c b/dreamcast/pvrtex/mem.c new file mode 100644 index 00000000..d07db343 --- /dev/null +++ b/dreamcast/pvrtex/mem.c @@ -0,0 +1,568 @@ +/* + * default memory allocator for libavutil + * Copyright (c) 2002 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * default memory allocator for libavutil + */ + +#define _XOPEN_SOURCE 600 + +#include "config.h" + +#include +#include +#include +#include +#include +#if HAVE_MALLOC_H +#include +#endif + +#include "attributes.h" +#include "avassert.h" +#include "dynarray.h" +#include "error.h" +#include "internal.h" +#include "intreadwrite.h" +#include "macros.h" +#include "mem.h" + +#ifdef MALLOC_PREFIX + +#define malloc AV_JOIN(MALLOC_PREFIX, malloc) +#define memalign AV_JOIN(MALLOC_PREFIX, memalign) +#define posix_memalign AV_JOIN(MALLOC_PREFIX, posix_memalign) +#define realloc AV_JOIN(MALLOC_PREFIX, realloc) +#define free AV_JOIN(MALLOC_PREFIX, free) + +void *malloc(size_t size); +void *memalign(size_t align, size_t size); +int posix_memalign(void **ptr, size_t align, size_t size); +void *realloc(void *ptr, size_t size); +void free(void *ptr); + +#endif /* MALLOC_PREFIX */ + +#define ALIGN (HAVE_AVX512 ? 64 : (HAVE_AVX ? 32 : 16)) + +/* NOTE: if you want to override these functions with your own + * implementations (not recommended) you have to link libav* as + * dynamic libraries and remove -Wl,-Bsymbolic from the linker flags. + * Note that this will cost performance. */ + +static atomic_size_t max_alloc_size = ATOMIC_VAR_INIT(INT_MAX); + +void av_max_alloc(size_t max){ + atomic_store_explicit(&max_alloc_size, max, memory_order_relaxed); +} + +static int size_mult(size_t a, size_t b, size_t *r) +{ + size_t t; + +#if (!defined(__INTEL_COMPILER) && AV_GCC_VERSION_AT_LEAST(5,1)) || AV_HAS_BUILTIN(__builtin_mul_overflow) + if (__builtin_mul_overflow(a, b, &t)) + return AVERROR(EINVAL); +#else + t = a * b; + /* Hack inspired from glibc: don't try the division if nelem and elsize + * are both less than sqrt(SIZE_MAX). */ + if ((a | b) >= ((size_t)1 << (sizeof(size_t) * 4)) && a && t / a != b) + return AVERROR(EINVAL); +#endif + *r = t; + return 0; +} + +void *av_malloc(size_t size) +{ + void *ptr = NULL; + + if (size > atomic_load_explicit(&max_alloc_size, memory_order_relaxed)) + return NULL; + +#if HAVE_POSIX_MEMALIGN + if (size) //OS X on SDK 10.6 has a broken posix_memalign implementation + if (posix_memalign(&ptr, ALIGN, size)) + ptr = NULL; +#elif HAVE_ALIGNED_MALLOC + ptr = _aligned_malloc(size, ALIGN); +#elif HAVE_MEMALIGN +#ifndef __DJGPP__ + ptr = memalign(ALIGN, size); +#else + ptr = memalign(size, ALIGN); +#endif + /* Why 64? + * Indeed, we should align it: + * on 4 for 386 + * on 16 for 486 + * on 32 for 586, PPro - K6-III + * on 64 for K7 (maybe for P3 too). + * Because L1 and L2 caches are aligned on those values. + * But I don't want to code such logic here! + */ + /* Why 32? + * For AVX ASM. SSE / NEON needs only 16. + * Why not larger? Because I did not see a difference in benchmarks ... + */ + /* benchmarks with P3 + * memalign(64) + 1 3071, 3051, 3032 + * memalign(64) + 2 3051, 3032, 3041 + * memalign(64) + 4 2911, 2896, 2915 + * memalign(64) + 8 2545, 2554, 2550 + * memalign(64) + 16 2543, 2572, 2563 + * memalign(64) + 32 2546, 2545, 2571 + * memalign(64) + 64 2570, 2533, 2558 + * + * BTW, malloc seems to do 8-byte alignment by default here. + */ +#else + ptr = malloc(size); +#endif + if(!ptr && !size) { + size = 1; + ptr= av_malloc(1); + } +#if CONFIG_MEMORY_POISONING + if (ptr) + memset(ptr, FF_MEMORY_POISON, size); +#endif + return ptr; +} + +void *av_realloc(void *ptr, size_t size) +{ + void *ret; + if (size > atomic_load_explicit(&max_alloc_size, memory_order_relaxed)) + return NULL; + +#if HAVE_ALIGNED_MALLOC + ret = _aligned_realloc(ptr, size + !size, ALIGN); +#else + ret = realloc(ptr, size + !size); +#endif +#if CONFIG_MEMORY_POISONING + if (ret && !ptr) + memset(ret, FF_MEMORY_POISON, size); +#endif + return ret; +} + +void *av_realloc_f(void *ptr, size_t nelem, size_t elsize) +{ + size_t size; + void *r; + + if (size_mult(elsize, nelem, &size)) { + av_free(ptr); + return NULL; + } + r = av_realloc(ptr, size); + if (!r) + av_free(ptr); + return r; +} + +int av_reallocp(void *ptr, size_t size) +{ + void *val; + + if (!size) { + av_freep(ptr); + return 0; + } + + memcpy(&val, ptr, sizeof(val)); + val = av_realloc(val, size); + + if (!val) { + av_freep(ptr); + return AVERROR(ENOMEM); + } + + memcpy(ptr, &val, sizeof(val)); + return 0; +} + +void *av_malloc_array(size_t nmemb, size_t size) +{ + size_t result; + if (size_mult(nmemb, size, &result) < 0) + return NULL; + return av_malloc(result); +} + +void *av_realloc_array(void *ptr, size_t nmemb, size_t size) +{ + size_t result; + if (size_mult(nmemb, size, &result) < 0) + return NULL; + return av_realloc(ptr, result); +} + +int av_reallocp_array(void *ptr, size_t nmemb, size_t size) +{ + void *val; + + memcpy(&val, ptr, sizeof(val)); + val = av_realloc_f(val, nmemb, size); + memcpy(ptr, &val, sizeof(val)); + if (!val && nmemb && size) + return AVERROR(ENOMEM); + + return 0; +} + +void av_free(void *ptr) +{ +#if HAVE_ALIGNED_MALLOC + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +void av_freep(void *arg) +{ + void *val; + + memcpy(&val, arg, sizeof(val)); + memcpy(arg, &(void *){ NULL }, sizeof(val)); + av_free(val); +} + +void *av_mallocz(size_t size) +{ + void *ptr = av_malloc(size); + if (ptr) + memset(ptr, 0, size); + return ptr; +} + +void *av_calloc(size_t nmemb, size_t size) +{ + size_t result; + if (size_mult(nmemb, size, &result) < 0) + return NULL; + return av_mallocz(result); +} + +char *av_strdup(const char *s) +{ + char *ptr = NULL; + if (s) { + size_t len = strlen(s) + 1; + ptr = av_realloc(NULL, len); + if (ptr) + memcpy(ptr, s, len); + } + return ptr; +} + +char *av_strndup(const char *s, size_t len) +{ + char *ret = NULL, *end; + + if (!s) + return NULL; + + end = memchr(s, 0, len); + if (end) + len = end - s; + + ret = av_realloc(NULL, len + 1); + if (!ret) + return NULL; + + memcpy(ret, s, len); + ret[len] = 0; + return ret; +} + +void *av_memdup(const void *p, size_t size) +{ + void *ptr = NULL; + if (p) { + ptr = av_malloc(size); + if (ptr) + memcpy(ptr, p, size); + } + return ptr; +} + +int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem) +{ + void **tab; + memcpy(&tab, tab_ptr, sizeof(tab)); + + FF_DYNARRAY_ADD(INT_MAX, sizeof(*tab), tab, *nb_ptr, { + tab[*nb_ptr] = elem; + memcpy(tab_ptr, &tab, sizeof(tab)); + }, { + return AVERROR(ENOMEM); + }); + return 0; +} + +void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem) +{ + void **tab; + memcpy(&tab, tab_ptr, sizeof(tab)); + + FF_DYNARRAY_ADD(INT_MAX, sizeof(*tab), tab, *nb_ptr, { + tab[*nb_ptr] = elem; + memcpy(tab_ptr, &tab, sizeof(tab)); + }, { + *nb_ptr = 0; + av_freep(tab_ptr); + }); +} + +void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size, + const uint8_t *elem_data) +{ + uint8_t *tab_elem_data = NULL; + + FF_DYNARRAY_ADD(INT_MAX, elem_size, *tab_ptr, *nb_ptr, { + tab_elem_data = (uint8_t *)*tab_ptr + (*nb_ptr) * elem_size; + if (elem_data) + memcpy(tab_elem_data, elem_data, elem_size); + else if (CONFIG_MEMORY_POISONING) + memset(tab_elem_data, FF_MEMORY_POISON, elem_size); + }, { + av_freep(tab_ptr); + *nb_ptr = 0; + }); + return tab_elem_data; +} + +static void fill16(uint8_t *dst, int len) +{ + uint32_t v = AV_RN16(dst - 2); + + v |= v << 16; + + while (len >= 4) { + AV_WN32(dst, v); + dst += 4; + len -= 4; + } + + while (len--) { + *dst = dst[-2]; + dst++; + } +} + +static void fill24(uint8_t *dst, int len) +{ +#if HAVE_BIGENDIAN + uint32_t v = AV_RB24(dst - 3); + uint32_t a = v << 8 | v >> 16; + uint32_t b = v << 16 | v >> 8; + uint32_t c = v << 24 | v; +#else + uint32_t v = AV_RL24(dst - 3); + uint32_t a = v | v << 24; + uint32_t b = v >> 8 | v << 16; + uint32_t c = v >> 16 | v << 8; +#endif + + while (len >= 12) { + AV_WN32(dst, a); + AV_WN32(dst + 4, b); + AV_WN32(dst + 8, c); + dst += 12; + len -= 12; + } + + if (len >= 4) { + AV_WN32(dst, a); + dst += 4; + len -= 4; + } + + if (len >= 4) { + AV_WN32(dst, b); + dst += 4; + len -= 4; + } + + while (len--) { + *dst = dst[-3]; + dst++; + } +} + +static void fill32(uint8_t *dst, int len) +{ + uint32_t v = AV_RN32(dst - 4); + +#if HAVE_FAST_64BIT + uint64_t v2= v + ((uint64_t)v<<32); + while (len >= 32) { + AV_WN64(dst , v2); + AV_WN64(dst+ 8, v2); + AV_WN64(dst+16, v2); + AV_WN64(dst+24, v2); + dst += 32; + len -= 32; + } +#endif + + while (len >= 4) { + AV_WN32(dst, v); + dst += 4; + len -= 4; + } + + while (len--) { + *dst = dst[-4]; + dst++; + } +} + +void av_memcpy_backptr(uint8_t *dst, int back, int cnt) +{ + const uint8_t *src = &dst[-back]; + if (!back) + return; + + if (back == 1) { + memset(dst, *src, cnt); + } else if (back == 2) { + fill16(dst, cnt); + } else if (back == 3) { + fill24(dst, cnt); + } else if (back == 4) { + fill32(dst, cnt); + } else { + if (cnt >= 16) { + int blocklen = back; + while (cnt > blocklen) { + memcpy(dst, src, blocklen); + dst += blocklen; + cnt -= blocklen; + blocklen <<= 1; + } + memcpy(dst, src, cnt); + return; + } + if (cnt >= 8) { + AV_COPY32U(dst, src); + AV_COPY32U(dst + 4, src + 4); + src += 8; + dst += 8; + cnt -= 8; + } + if (cnt >= 4) { + AV_COPY32U(dst, src); + src += 4; + dst += 4; + cnt -= 4; + } + if (cnt >= 2) { + AV_COPY16U(dst, src); + src += 2; + dst += 2; + cnt -= 2; + } + if (cnt) + *dst = *src; + } +} + +void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size) +{ + size_t max_size; + + if (min_size <= *size) + return ptr; + + max_size = atomic_load_explicit(&max_alloc_size, memory_order_relaxed); + /* *size is an unsigned, so the real maximum is <= UINT_MAX. */ + max_size = FFMIN(max_size, UINT_MAX); + + if (min_size > max_size) { + *size = 0; + return NULL; + } + + min_size = FFMIN(max_size, FFMAX(min_size + min_size / 16 + 32, min_size)); + + ptr = av_realloc(ptr, min_size); + /* we could set this to the unmodified min_size but this is safer + * if the user lost the ptr and uses NULL now + */ + if (!ptr) + min_size = 0; + + *size = min_size; + + return ptr; +} + +static inline void fast_malloc(void *ptr, unsigned int *size, size_t min_size, int zero_realloc) +{ + size_t max_size; + void *val; + + memcpy(&val, ptr, sizeof(val)); + if (min_size <= *size) { + //av_assert0(val || !min_size); + return; + } + + max_size = atomic_load_explicit(&max_alloc_size, memory_order_relaxed); + /* *size is an unsigned, so the real maximum is <= UINT_MAX. */ + max_size = FFMIN(max_size, UINT_MAX); + + if (min_size > max_size) { + av_freep(ptr); + *size = 0; + return; + } + min_size = FFMIN(max_size, FFMAX(min_size + min_size / 16 + 32, min_size)); + av_freep(ptr); + val = zero_realloc ? av_mallocz(min_size) : av_malloc(min_size); + memcpy(ptr, &val, sizeof(val)); + if (!val) + min_size = 0; + *size = min_size; + return; +} + +void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size) +{ + fast_malloc(ptr, size, min_size, 0); +} + +void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size) +{ + fast_malloc(ptr, size, min_size, 1); +} + +int av_size_mult(size_t a, size_t b, size_t *r) +{ + return size_mult(a, b, r); +} diff --git a/dreamcast/pvrtex/mycommon.c b/dreamcast/pvrtex/mycommon.c new file mode 100644 index 00000000..85da31a0 --- /dev/null +++ b/dreamcast/pvrtex/mycommon.c @@ -0,0 +1,32 @@ +#include "mycommon.h" + +unsigned RoundUpPow2(unsigned val) { + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + return val+1; +} +unsigned RoundDownPow2(unsigned val) { + if (IsPow2(val)) + return val; + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + return (val+1)>>1; +} + +int SelectNearest(int down, int val, int up) { + return (abs(val - up) < abs(val - down)) ? up : down; +} + +unsigned RoundNearest(unsigned val, unsigned round) { + val = val + round/2; + val /= round; + return val * round; +} diff --git a/dreamcast/pvrtex/mycommon.h b/dreamcast/pvrtex/mycommon.h new file mode 100644 index 00000000..cf8cc6ee --- /dev/null +++ b/dreamcast/pvrtex/mycommon.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#define ARR_SIZE(array) (sizeof(array) / sizeof(array[0])) + +static inline bool IsPow2(uint32_t val) { + return ((val - 1) & val) == 0; +} +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define CLAMP(small,num,big) (MAX((small),MIN((num),(big)))) + +#define ROUND_UP_POW2(val, pow_of_2_amt) (((val) + ((pow_of_2_amt)-1)) & ~((pow_of_2_amt)-1)) + +static inline float lerp(float ratio, float a, float b) { + return ratio * b + (a - ratio * a); +} + +#if 1 +#define SAFE_FREE(ptr) \ + if (*(ptr) != NULL) { free(*(ptr)); *(ptr) = NULL; } +#define SMART_ALLOC(ptr, size) \ + do { SAFE_FREE(ptr); *ptr = calloc(size, 1); } while(0) +#else +static void SafeFree(void **ptr) { + if (*ptr != NULL) { + free(*ptr); + *ptr = NULL; + } +} +#endif + +extern unsigned RoundUpPow2(unsigned val); +extern unsigned RoundDownPow2(unsigned val); +unsigned RoundNearest(unsigned val, unsigned round); +int SelectNearest(int down, int val, int up); +void ErrorExit(const char *fmt, ...); diff --git a/dreamcast/pvrtex/nvmath.h b/dreamcast/pvrtex/nvmath.h new file mode 100644 index 00000000..d02a8554 --- /dev/null +++ b/dreamcast/pvrtex/nvmath.h @@ -0,0 +1,722 @@ +#ifndef NVMATH_H +#define NVMATH_H + +#include +#include + +#ifdef __CPLUSPLUS +#include +extern "C" { +#endif + +typedef int32_t nvint; + +typedef union v2i v2i; +typedef union v3i v3i; +typedef union v4i v4i; +typedef union v2f v2f; +typedef union v3f v3f; +typedef union v4f v4f; + +union v2i { + struct { + nvint x, y; + }; + nvint v[2]; +}; + +union v3i { + struct { + nvint x, y, z; + }; + nvint v[3]; + v2i xy; +}; +union v4i { + struct { + nvint x, y, z, w; + }; + nvint v[4]; + v3i xyz; +}; + +union v2f { + struct { + float x, y; + }; + float v[2]; + #ifdef __CPLUSPLUS + inline bool operator <(const v2f &b) const { return memcmp(this, &b, sizeof(b)) < 0; } + #endif +}; + +union v3f { + struct { + float x, y, z; + }; + float v[3]; + v2f xy; + #ifdef __CPLUSPLUS + inline bool operator <(const v3f &b) const { return memcmp(this, &b, sizeof(b)) < 0; } + #endif +}; + +union v4f { + struct { + float x, y, z, w; + }; + float v[4]; + v3f xyz; + v2f xy; +}; + +typedef v4f vqf; + +//~ #define VMATH_USE_XMTRX +#ifdef VMATH_USE_XMTRX +#include "xmtrx.h" +#endif + +typedef union { + struct { + float e00, e01, e02, e03; + float e10, e11, e12, e13; + float e20, e21, e22, e23; + float e30, e31, e32, e33; + }; + struct { + v4f c[4]; + }; + float m[16]; +#ifdef VMATH_USE_XMTRX + xMatrix xm; +#endif +} m4x4f; + +/* + Select how to preform square roots. + + On KOS, sqrt is somehow set up to always to go a library call. Need to look + into why. You can force GCC built-in sqrt handling (recommended), or use + inline asm. Built-ins are recommended since the compiler can optimize + constant values. + + **GCC is getting passed fno-builtin from $KOS_CFLAGS defined in environ_base.sh** + + With the right compile options, GCC can even generate FSRRA instructions. + + Turn these options on for fast builtins: + -ffast-math -ffp-contract=fast -mfsrra -mfsca +*/ +#define NVMATH_TYPE_BUILTIN (0) +#define NVMATH_TYPE_LIBRARY (1) +#define NVMATH_TYPE_SH4_ASM (2) + +#define NVMATH_METHOD NVMATH_TYPE_BUILTIN + + +#define NVMATH_MIN(a, b) ((a) < (b) ? (a) : (b)) +#define NVMATH_MAX(a, b) ((a) > (b) ? (a) : (b)) +#define NVMATH_MIN3(a, b, c) NVMATH_MIN(NVMATH_MIN(a, b), c) +#define NVMATH_MAX3(a, b, c) NVMATH_MAX(NVMATH_MAX(a, b), c) + +#if NVMATH_METHOD == NVMATH_TYPE_LIBRARY + #define NVMATH_SQRT(a) sqrt(a) + #define NVMATH_RSQRT(a) (1.0f/sqrt(a)) + #define NVMATH_SIN(rad) sinf(rad) + #define NVMATH_COS(rad) cosf(rad) + #define NVMATH_SINCOS(rad, s, c) sincosf(rad, s, c) + #define NVMATH_ACOS(rad) acosf(rad) + #define NVMATH_ABS(v) fabsf(v) + #define NVMATH_ABSI(v) abs(v) +#elif NVMATH_METHOD == NVMATH_TYPE_BUILTIN + #define NVMATH_SQRT(a) __builtin_sqrt(a) + #define NVMATH_RSQRT(a) (1.0f/__builtin_sqrt(a)) + #define NVMATH_SIN(rad) __builtin_sinf(rad) + #define NVMATH_COS(rad) __builtin_cosf(rad) + #define NVMATH_SINCOS(rad, s, c) __builtin_sincosf(rad, s, c) + #define NVMATH_ACOS(rad) __builtin_acosf(rad) + #define NVMATH_ABS(v) __builtin_fabsf(v) + #define NVMATH_ABSI(v) __builtin_abs(v) +#elif NVMATH_METHOD == NVMATH_TYPE_SH4_ASM + static inline float NVMFsqrt(float a) { + __asm__( "fsqrt %0\n\t" + : "=f" (a) + : "0" (a) + : ); + + return a; + } + static inline float NVMFsrra(float a) { + __asm__( "fsrra %0\n\t" + : "=f" (a) + : "0" (a) + : ); + + return a; + } + #define NVMATH_SQRT(a) NVMFsqrt(a) + #define NVMATH_RSQRT(a) NVMFsrra(a) + + static inline void NVMATH_SINCOS_I(int angle, float *sine, float *cosine) + { + register float __s __asm__("fr2"); + register float __c __asm__("fr3"); + + asm( "lds %2,fpul\n\t" + "fsca fpul,dr2\n\t" + : "=f" (__s), "=f" (__c) + : "r" (angle) + : "fpul"); + + *sine = __s; *cosine = __c; + } + #define NVMATH_SINCOS(rad, s, c) NVMATH_SINCOS_I((rad) * 10430.37835f, s, c) +#else + #error NVMATH_METHOD not set +#endif + +/* + vqSlerp uses an NVMATH_ASIN call. This normally requires a function call. + Defineing NVMMATH_APPROX_ASIN_ACOS replaces NVMATH_ASIN and NVMATH_ACOS + with approximate functions. +*/ + +#define NVMMATH_APPROX_ASIN_ACOS +#ifdef NVMMATH_APPROX_ASIN_ACOS + #undef NVMATH_ASIN + #undef NVMATH_ACOS + + inline float NVMATH_ASIN(float x) { + const float scale_factor = .5707963268f; + float x5 = x * x; + x5 *= x5; + x5 *= x; + return x + scale_factor*x5; + } + + inline float NVMATH_ACOS(float x) { + return M_PI_2 - NVMATH_ASIN(x); + } + +#endif + +#define v2Init(x,y) {{x, y}} +#define v3Init(x,y,z) {{x, y, z}} +#define v4Init(x,y,z,w) {{x, y, z, w }} +#define v2Pass(vec) (vec).x, (vec).y +#define v3Pass(vec) (vec).x, (vec).y, (vec).z +#define v4Pass(vec) (vec).x, (vec).y, (vec).z, (vec).w +//~ #define mat_elem(mat,col,row) mat[row * 4 + col] + + +//////////////////////////////////// +#define NVM_FF4I(name, paramx, paramy, paramz, paramw, opx, opy, opz, opw) \ + static inline v2i v2 ## i ## name(paramx, paramy) { \ + v2i d; opx; opy; return d; } \ + static inline v3i v3 ## i ## name(paramx, paramy, paramz) { \ + v3i d; opx; opy; opz; return d; } \ + static inline v4i v4 ## i ## name(paramx, paramy, paramz, paramw) { \ + v4i d; opx; opy; opz; opw; return d; } + +#define NVM_FF4(name, paramx, paramy, paramz, paramw, opx, opy, opz, opw) \ + static inline v2f v2 ## name(paramx, paramy) { \ + v2f d; opx; opy; return d; } \ + static inline v3f v3 ## name(paramx, paramy, paramz) { \ + v3f d; opx; opy; opz; return d; } \ + static inline v4f v4 ## name(paramx, paramy, paramz, paramw) { \ + v4f d; opx; opy; opz; opw; return d; } + +#define NVM_FF1I(name, param, opx, opy, opz, opw) \ + static inline v2i v2 ## i ## name(param) { \ + v2i d; opx; opy; return d; } \ + static inline v3i v3 ## i ## name(param) { \ + v3i d; opx; opy; opz; return d; } \ + static inline v4i v4 ## i ## name(param) { \ + v4i d; opx; opy; opz; opw; return d; } + +#define NVM_FF1(name, param, opx, opy, opz, opw) \ + static inline v2f v2 ## name(param) { \ + v2f d; opx; opy; return d; } \ + static inline v3f v3 ## name(param) { \ + v3f d; opx; opy; opz; return d; } \ + static inline v4f v4 ## name(param) { \ + v4f d; opx; opy; opz; opw; return d; } + +#define NVM_FFU4(name, opx, opy, opz, opw) \ + static inline v2f v2 ## name(v2f v) { \ + v2f d; opx; opy; return d; } \ + static inline v3f v3 ## name(v3f v) { \ + v3f d; opx; opy; opz; return d; } \ + static inline v4f v4 ## name(v4f v) { \ + v4f d; opx; opy; opz; opw; return d; } + +#define NVM_FFB4(name, opx, opy, opz, opw) \ + static inline v2i v2 ## i ## name(v2i l, v2i r) { \ + v2i d; opx; opy; return d; } \ + static inline v3i v3 ## i ## name(v3i l, v3i r) { \ + v3i d; opx; opy; opz; return d; } \ + static inline v4i v4 ## i ## name(v4i l, v4i r) { \ + v4i d; opx; opy; opz; opw; return d; } \ + static inline v2f v2 ## name(v2f l, v2f r) { \ + v2f d; opx; opy; return d; } \ + static inline v3f v3 ## name(v3f l, v3f r) { \ + v3f d; opx; opy; opz; return d; } \ + static inline v4f v4 ## name(v4f l, v4f r) { \ + v4f d; opx; opy; opz; opw; return d; } + + +#define NVM_FFB4MACV(name, opx, opy, opz, opw) \ + static inline v2i v2 ## i ## name(v2i l, v2i r, v2i a) { \ + v2i d; nvint aa = a.x; nvint rr = r.x; opx; aa = a.y; rr = r.y; opy; return d; } \ + static inline v3i v3 ## i ## name(v3i l, v3i r, v3i a) { \ + v3i d; nvint aa = a.x; nvint rr = r.x; opx; aa = a.y; rr = r.y; opy; aa = a.z; rr = r.z; opz; return d; } \ + static inline v4i v4 ## i ## name(v4i l, v4i r, v4i a) { \ + v4i d; nvint aa = a.x; nvint rr = r.x; opx; aa = a.y; rr = r.y; opy; aa = a.z; rr = r.z; opz; aa = a.w; rr = r.w; opw; return d; } \ + static inline v2f v2 ## name(v2f l, v2f r, v2f a) { \ + v2f d; float aa = a.x; float rr = r.x; opx; aa = a.y; rr = r.y; opy; return d; } \ + static inline v3f v3 ## name(v3f l, v3f r, v3f a) { \ + v3f d; float aa = a.x; float rr = r.x; opx; aa = a.y; rr = r.y; opy; aa = a.z; rr = r.z; opz; return d; } \ + static inline v4f v4 ## name(v4f l, v4f r, v4f a) { \ + v4f d; float aa = a.x; float rr = r.x; opx; aa = a.y; rr = r.y; opy; aa = a.z; rr = r.z; opz; aa = a.w; rr = r.w; opw; return d; } + +#define NVM_FFB4MACS(name, opx, opy, opz, opw) \ + static inline v2i v2 ## i ## name(v2i l, v2i r, float a) { \ + v2i d; float aa = a; nvint rr = r.x; opx; rr = r.y; opy; return d; } \ + static inline v3i v3 ## i ## name(v3i l, v3i r, float a) { \ + v3i d; float aa = a; nvint rr = r.x; opx; rr = r.y; opy; rr = r.z; opz; return d; } \ + static inline v4i v4 ## i ## name(v4i l, v4i r, float a) { \ + v4i d; float aa = a; nvint rr = r.x; opx; rr = r.y; opy; rr = r.z; opz; rr = r.w; opw; return d; } \ + static inline v2f v2 ## name(v2f l, v2f r, float a) { \ + v2f d; float aa = a; float rr = r.x; opx; rr = r.y; opy; return d; } \ + static inline v3f v3 ## name(v3f l, v3f r, float a) { \ + v3f d; float aa = a; float rr = r.x; opx; rr = r.y; opy; rr = r.z; opz; return d; } \ + static inline v4f v4 ## name(v4f l, v4f r, float a) { \ + v4f d; float aa = a; float rr = r.x; opx; rr = r.y; opy; rr = r.z; opz; rr = r.w; opw; return d; } + +#define NVM_FFB4MACSX(name, opx, opy, opz, opw) \ + static inline v2i v2 ## i ## name(v2i l, float r, v2i a) { \ + v2i d; float aa = a.x; nvint rr = r; opx; aa = a.y; opy; return d; } \ + static inline v3i v3 ## i ## name(v3i l, float r, v3i a) { \ + v3i d; float aa = a.x; nvint rr = r; opx; aa = a.y; opy; aa = a.z; opz; return d; } \ + static inline v2f v2 ## name(v2f l, float r, v2f a) { \ + v2f d; float aa = a.x; float rr = r; opx; aa = a.y; opy; return d; } \ + static inline v3f v3 ## name(v3f l, float r, v3f a) { \ + v3f d; float aa = a.x; float rr = r; opx; aa = a.y; opy; aa = a.z; opz; return d; } \ + static inline v4f v4 ## name(v4f l, float r, v4f a) { \ + v4f d; float aa = a.x; float rr = r; opx; aa = a.y; opy; aa = a.z; opz; aa = a.w; opw; return d; } + +#define NVM_FFB4MAC(name, opx, opy, opz, opw) \ + NVM_FFB4MACV(name, opx, opy, opz, opw) \ + NVM_FFB4MACSX(name ## V, opx, opy, opz, opw) \ + NVM_FFB4MACS(name ## S, opx, opy, opz, opw) + +#define NVM_FFB4_COMB(name, opx, opy, opz, opw) \ + static inline float v2 ## name(v2f l, v2f r) { \ + return opx opy; } \ + static inline float v3 ## name(v3f l, v3f r) { \ + return opx opy opz; } \ + static inline float v4 ## name(v4f l, v4f r) { \ + return opx opy opz opw; } + +#define NVM_FFB4_COMB_I(name, opx, opy, opz, opw) \ + static inline float v2 ## name(v2f l, v2f r) { \ + return opx opy; } \ + static inline float v3 ## name(v3f l, v3f r) { \ + return opx opy opz; } \ + static inline float v4 ## name(v4f l, v4f r) { \ + return opx opy opz opw; } + +#define NVM_FFB4S(name, opx, opy, opz, opw) \ + static inline v2i v2 ## i ## name(v2i l, nvint r) { \ + v2i d; opx; opy; return d; } \ + static inline v3i v3 ## i ## name(v3i l, nvint r) { \ + v3i d; opx; opy; opz; return d; } \ + static inline v4i v4 ## i ## name(v4i l, nvint r) { \ + v4i d; opx; opy; opz; opw; return d; } \ + static inline v2f v2 ## name(v2f l, float r) { \ + v2f d; opx; opy; return d; } \ + static inline v3f v3 ## name(v3f l, float r) { \ + v3f d; opx; opy; opz; return d; } \ + static inline v4f v4 ## name(v4f l, float r) { \ + v4f d; opx; opy; opz; opw; return d; } + +#define NVM_FFB4RS(name, opx, opy, opz, opw) \ + static inline v2i v2 ## i ## name ## RS(nvint l, v2i r) { \ + v2i d; opx; opy; return d; } \ + static inline v3i v3 ## i ## name(nvint l, v3i r) { \ + v3i d; opx; opy; opz; return d; } \ + static inline v2f v2 ## name ## RS(float l, v2f r) { \ + v2f d; opx; opy; return d; } \ + static inline v3f v3 ## name(float l, v3f r) { \ + v3f d; opx; opy; opz; return d; } \ + static inline v4f v4 ## name(float l, v4f r) { \ + v4f d; opx; opy; opz; opw; return d; } + +//Binary op, associative +#define NVM_FFB4B(name, op) \ + NVM_FFB4(name, \ + d.x = l.x op r.x, \ + d.y = l.y op r.y, \ + d.z = l.z op r.z, \ + d.w = l.w op r.w) \ + NVM_FFB4S(name ## S, \ + d.x = l.x op r, \ + d.y = l.y op r, \ + d.z = l.z op r, \ + d.w = l.w op r) + +//Binary op, not associative +#define NVM_FFB4BR(name, op) \ + NVM_FFB4B(name, op) \ + NVM_FFB4RS(name ## RS, \ + d.x = l op r.x, \ + d.y = l op r.y, \ + d.z = l op r.z, \ + d.w = l op r.w) + +//***OPERATIONS*** +/* + v?Set(float, float, float, float) + Sets vector + v?SetR(float) + Sets vector with value Repeated into all dimensions + + v?Get(float*) + Creates vector from array of floats + + v?Abs(v?f v) + |v| + v?Negate(v?f v) + -v + v?Recip(v?f v) + 1.0f / v + + v?(Add/Sub/Mul/Div)(v?f l , v?f r) + l + r + l - r + l * r + l / r + Adds, subtracts, multiplies or divides two vectors. + + v?(Add/Sub/Mul/Div)S(v?f l, float r) + l + r + l - r + l * r + l / r + Adds, subtracts, multiplies or divides a left vector and a right scalar + + v?(Sub/Div)SR(float l, v?f r) + l - r + l / r + Subtracts or divides a left scalar and a right vector + + v?Mac(v?f l, v?f r, v?f accum) + l * r + accum + v?MacV(v?f l, float r, v?f accum) + l * r + accum + v?MacS(v?f l, v?f r, float accum) + l * r + accum + + v?Mdc(v?f l, v?f r, v?f accum) + l * r - accum + v?MdcV(v?f l, float r, v?f accum) + l * r - accum + v?MdcS(v?f l, v?f r, float accum) + l * r - accum + + v?Nms(v?f l, v?f r, v?f accum) + accum - l * r + v?NmsV(v?f l, float r, v?f accum) + accum - l * r + v?NmsS(v?f l, v?f r, float accum) + accum - l * r + + v?Lerp(v?f start, v?f end, v?f weight) + start + weight * (end - start) + v?LerpS(v?f start, v?f end, float weight) + start + weight * (end - start) + + v?Min(v?f a, v?f b) + min(a, b) + v?Max(v?f a, v?f b) + max(a, b) + v?MaxE(v?f a) + max(a.x, a.y, a.z, a.w) + + v?Length(v?f v) + length of v + v?SqrLength(v?f v) + squared length of v + v?Distance(v?f a, v3f b) + distance between a and b + v?SqrDistance(v?f a, v3f b) + square distance between a and b + v?Normalize(v?f v) + v with unit length (Will divide by zero if vector is of length 0) + v?NormalizeS(v?f v) + v with unit length (returns zero vector if length zero) + + v?Dot(v?f l, v?f r) + l dot r + v2Cross(v2f l, v2f r) + l cross r + v3Cross(v3f l, v3f r) + l cross r + v3Triple(v3f a, v3f b, v3f c) + a dot (b cross c) +*/ + +NVM_FF4I(Set, nvint x, nvint y, nvint z, nvint w, + d.x = x, d.y = y, d.z = z, d.w = w) +NVM_FF4(Set, float x, float y, float z, float w, + d.x = x, d.y = y, d.z = z, d.w = w) + +NVM_FF1I(Get, const nvint *f, + d.x = f[0], d.y = f[1], d.z = f[2], d.w = f[3]) +NVM_FF1(Get, const float *f, + d.x = f[0], d.y = f[1], d.z = f[2], d.w = f[3]) +NVM_FF1I(SetR, int v, + d.x = v, d.y = v, d.z = v, d.w = v) +NVM_FF1(SetR, float v, + d.x = v, d.y = v, d.z = v, d.w = v) + +static inline v3f v2Extv3(v2f v, float z) { + return v3Set(v.x, v.y, z); +} +static inline v4f v2Extv4(v2f v, float z, float w) { + return v4Set(v.x, v.y, z, w); +} +static inline v4f v3Extv4(v3f v, float w) { + return v4Set(v.x, v.y, v.z, w); +} + +#define v2Zero() v2Set(0, 0) +#define v3Zero() v3Set(0, 0, 0) +#define v4Zero() v4Set(0, 0, 0, 0) + +NVM_FFU4(Abs, + d.x = NVMATH_ABS(v.x), d.y = NVMATH_ABS(v.y), d.z = NVMATH_ABS(v.z), d.w = NVMATH_ABS(v.w)) +NVM_FFU4(Negate, + d.x = -v.x, d.y = -v.y, d.z = -v.z, d.w = -v.w) +NVM_FFU4(Recip, + d.x = 1.0f/v.x, d.y = 1.0f/v.y, d.z = 1.0f/v.z, d.w = 1.0f/v.w) +NVM_FFB4B(Add, +) +NVM_FFB4BR(Sub, -) +NVM_FFB4B(Mul, *) +NVM_FFB4BR(Div, /) +NVM_FFB4_COMB(Dot, + l.x * r.x, + l.y * r.y, + l.z * r.z, + l.w * r.w) +NVM_FFB4MAC(Mac, + d.x = l.x * rr + aa, + d.y = l.y * rr + aa, + d.z = l.z * rr + aa, + d.w = l.w * rr + aa) +NVM_FFB4MAC(Mdc, + d.x = l.x * rr - aa, + d.y = l.y * rr - aa, + d.z = l.z * rr - aa, + d.w = l.w * rr - aa) +NVM_FFB4MAC(Nms, + d.x = aa - l.x * rr, + d.y = aa - l.y * rr, + d.z = aa - l.z * rr, + d.w = aa - l.w * rr) +NVM_FFB4MAC(Lerp, + d.x = l.x + aa * (rr - l.x), + d.y = l.y + aa * (rr - l.y), + d.z = l.z + aa * (rr - l.z), + d.w = l.w + aa * (rr - l.w)) + +NVM_FFB4(Min, + d.x = l.x < r.x ? l.x : r.x, + d.y = l.y < r.y ? l.y : r.y, + d.z = l.z < r.z ? l.z : r.z, + d.w = l.w < r.w ? l.w : r.w) +NVM_FFB4(Max, + d.x = l.x > r.x ? l.x : r.x, + d.y = l.y > r.y ? l.y : r.y, + d.z = l.z > r.z ? l.z : r.z, + d.w = l.w > r.w ? l.w : r.w) +NVM_FFB4S(MinS, + d.x = l.x < r ? l.x : r, + d.y = l.y < r ? l.y : r, + d.z = l.z < r ? l.z : r, + d.w = l.w < r ? l.w : r) +NVM_FFB4S(MaxS, + d.x = l.x > r ? l.x : r, + d.y = l.y > r ? l.y : r, + d.z = l.z > r ? l.z : r, + d.w = l.w > r ? l.w : r) + +static inline float v2SqrLength(v2f v) { + return v2Dot(v, v); + } + static inline float v3SqrLength(v3f v) { + return v3Dot(v, v); + } + static inline float v4SqrLength(v4f v) { + return v4Dot(v, v); +} + +static inline nvint v2iMinE(v2i v) { + return NVMATH_MIN(v.x, v.y); +} +static inline nvint v3iMinE(v3i v) { + return NVMATH_MIN(v.x, NVMATH_MIN(v.y, v.z)); +} +static inline float v2MinE(v2f v) { + return NVMATH_MIN(v.x, v.y); +} +static inline float v3MinE(v3f v) { + return NVMATH_MIN(v.x, NVMATH_MIN(v.y, v.z)); +} +static inline float v4MinE(v4f v) { + return NVMATH_MIN(v.x, NVMATH_MIN(v.y, NVMATH_MIN(v.z, v.w))); +} +static inline nvint v2iMaxE(v2i v) { + return NVMATH_MAX(v.x, v.y); +} +static inline nvint v3iMaxE(v3i v) { + return NVMATH_MAX(v.x, NVMATH_MAX(v.y, v.z)); +} +static inline float v2MaxE(v2f v) { + return NVMATH_MAX(v.x, v.y); +} +static inline float v3MaxE(v3f v) { + return NVMATH_MAX(v.x, NVMATH_MAX(v.y, v.z)); +} +static inline float v4MaxE(v4f v) { + return NVMATH_MAX(v.x, NVMATH_MAX(v.y, NVMATH_MAX(v.z, v.w))); +} + +static inline float v2Sum(v2f v) { + return v2Dot(v,v2SetR(1)); +} +static inline float v3Sum(v3f v) { + return v3Dot(v,v3SetR(1)); +} +static inline float v4Sum(v4f v) { + return v4Dot(v,v4SetR(1)); +} + + +static inline float v2Length(v2f v) { + return NVMATH_SQRT(v2Dot(v,v)); +} +static inline float v3Length(v3f v) { + return NVMATH_SQRT(v3Dot(v,v)); +} +static inline float v4Length(v4f v) { + return NVMATH_SQRT(v4Dot(v,v)); +} +static inline float v2Distance(v2f a, v2f b) { + v2f v = v2Sub(a, b); + return NVMATH_SQRT(v2Dot(v,v)); +} +static inline float v3Distance(v3f a, v3f b) { + v3f v = v3Sub(a, b); + return NVMATH_SQRT(v3Dot(v,v)); +} +static inline float v4Distance(v4f a, v4f b) { + v4f v = v4Sub(a, b); + return NVMATH_SQRT(v4Dot(v,v)); +} +static inline float v2SqrDistance(v2f a, v2f b) { + v2f v = v2Sub(a, b); + return v2Dot(v,v); +} +static inline float v3SqrDistance(v3f a, v3f b) { + v3f v = v3Sub(a, b); + return v3Dot(v,v); +} +static inline float v4SqrDistance(v4f a, v4f b) { + v4f v = v4Sub(a, b); + return v4Dot(v,v); +} + +static inline v3f v3Normalize(v3f v) { + return v3MulS(v, NVMATH_RSQRT(v3Dot(v,v))); +} +static inline v4f v4Normalize(v4f v) { + return v4MulS(v, NVMATH_RSQRT(v4Dot(v,v))); +} +static inline v2f v2NormalizeS(v2f v) { + float rs = NVMATH_RSQRT(v2Dot(v,v)); + return rs ? v2MulS(v, rs) : v; +} +static inline v3f v3NormalizeS(v3f v) { + float rs = NVMATH_RSQRT(v3Dot(v,v)); + return rs ? v3MulS(v, rs) : v; +} + +static inline v4f v4Float(v4i v) { + v4f r = { {v4Pass(v)} }; + return r; +} + +static inline v4i v4Int(v4f v) { + v4i r = { {v4Pass(v)} }; + return r; +} +static inline v4i v4IntRnd(v4f v) { + v4i r = { {v4Pass(v4AddS(v, 0.5))} }; + return r; +} + +static inline float v2Cross(v2f l, v2f r) { + return l.x*r.y - l.y*r.x; +} +static inline v3f v3Cross(v3f l, v3f r) { + v3f d; + d.x = l.y*r.z - l.z*r.y; + d.y = l.z*r.x - l.x*r.z; + d.z = l.x*r.y - l.y*r.x; + return d; +} +static inline float v3Triple(v3f a, v3f b, v3f c) { + return v3Dot(a, v3Cross(b, c)); +} + +#ifndef VMATH_USE_XMTRX + static inline float m44Mul4Row(m4x4f m, v4f v, int row) { + return v.x * m.c[0].v[row] + v.y * m.c[1].v[row] + v.z * m.c[2].v[row] + v.w * m.c[3].v[row]; + } + static inline v4f v4MulMat(m4x4f *m, v4f v) { + v4f r; + r.x = m44Mul4Row(*m, v, 0); r.y = m44Mul4Row(*m, v, 1); r.z = m44Mul4Row(*m, v, 2); r.w = m44Mul4Row(*m, v, 3); + return r; + } +#else + static inline v4f v4MulMat(m4x4f *m, v4f v) { + xmtrxLoad((xMatrix*)m); + register float x __asm__("fr0") = v.x; + register float y __asm__("fr1") = v.y; + register float z __asm__("fr2") = v.z; + register float w __asm__("fr3") = v.w; + __asm__ __volatile__( + "ftrv xmtrx,fv0\n" + : "=f" (x), "=f" (y), "=f" (z), "=f" (w) + : "0" (x), "1" (y), "2" (z), "3" (w) ); + return v4Set(x, y, z, w); + } + + static inline void m44Mul4x4(m4x4f * restrict l, m4x4f * restrict r, m4x4f * restrict d) { + xmtrxLoadMultiply((xMatrix*)l, (xMatrix*)r); + xmtrxStore((xMatrix*)d); + } + + static inline void m44Identity(m4x4f * m) { + xmtrxIdentity(d); + xmtrxStore(d); + } +#endif + + + + +#ifdef __CPLUSPLUS +} +#endif + +#endif diff --git a/dreamcast/pvrtex/optparse.h b/dreamcast/pvrtex/optparse.h new file mode 100644 index 00000000..8d6c0a94 --- /dev/null +++ b/dreamcast/pvrtex/optparse.h @@ -0,0 +1,403 @@ +/* Optparse --- portable, reentrant, embeddable, getopt-like option parser + * + * This is free and unencumbered software released into the public domain. + * + * To get the implementation, define OPTPARSE_IMPLEMENTATION. + * Optionally define OPTPARSE_API to control the API's visibility + * and/or linkage (static, __attribute__, __declspec). + * + * The POSIX getopt() option parser has three fatal flaws. These flaws + * are solved by Optparse. + * + * 1) Parser state is stored entirely in global variables, some of + * which are static and inaccessible. This means only one thread can + * use getopt(). It also means it's not possible to recursively parse + * nested sub-arguments while in the middle of argument parsing. + * Optparse fixes this by storing all state on a local struct. + * + * 2) The POSIX standard provides no way to properly reset the parser. + * This means for portable code that getopt() is only good for one + * run, over one argv with one option string. It also means subcommand + * options cannot be processed with getopt(). Most implementations + * provide a method to reset the parser, but it's not portable. + * Optparse provides an optparse_arg() function for stepping over + * subcommands and continuing parsing of options with another option + * string. The Optparse struct itself can be passed around to + * subcommand handlers for additional subcommand option parsing. A + * full reset can be achieved by with an additional optparse_init(). + * + * 3) Error messages are printed to stderr. This can be disabled with + * opterr, but the messages themselves are still inaccessible. + * Optparse solves this by writing an error message in its errmsg + * field. The downside to Optparse is that this error message will + * always be in English rather than the current locale. + * + * Optparse should be familiar with anyone accustomed to getopt(), and + * it could be a nearly drop-in replacement. The option string is the + * same and the fields have the same names as the getopt() global + * variables (optarg, optind, optopt). + * + * Optparse also supports GNU-style long options with optparse_long(). + * The interface is slightly different and simpler than getopt_long(). + * + * By default, argv is permuted as it is parsed, moving non-option + * arguments to the end. This can be disabled by setting the `permute` + * field to 0 after initialization. + */ +#ifndef OPTPARSE_H +#define OPTPARSE_H + +#ifndef OPTPARSE_API +# define OPTPARSE_API +#endif + +struct optparse { + char **argv; + int permute; + int optind; + int optopt; + char *optarg; + char errmsg[64]; + int subopt; +}; + +enum optparse_argtype { + OPTPARSE_NONE, + OPTPARSE_REQUIRED, + OPTPARSE_OPTIONAL +}; + +struct optparse_long { + const char *longname; + int shortname; + enum optparse_argtype argtype; +}; + +/** + * Initializes the parser state. + */ +OPTPARSE_API +void optparse_init(struct optparse *options, char **argv); + +/** + * Read the next option in the argv array. + * @param optstring a getopt()-formatted option string. + * @return the next option character, -1 for done, or '?' for error + * + * Just like getopt(), a character followed by no colons means no + * argument. One colon means the option has a required argument. Two + * colons means the option takes an optional argument. + */ +OPTPARSE_API +int optparse(struct optparse *options, const char *optstring); + +/** + * Handles GNU-style long options in addition to getopt() options. + * This works a lot like GNU's getopt_long(). The last option in + * longopts must be all zeros, marking the end of the array. The + * longindex argument may be NULL. + */ +OPTPARSE_API +int optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex); + +/** + * Used for stepping over non-option arguments. + * @return the next non-option argument, or NULL for no more arguments + * + * Argument parsing can continue with optparse() after using this + * function. That would be used to parse the options for the + * subcommand returned by optparse_arg(). This function allows you to + * ignore the value of optind. + */ +OPTPARSE_API +char *optparse_arg(struct optparse *options); + +/* Implementation */ +#ifdef OPTPARSE_IMPLEMENTATION + +#define OPTPARSE_MSG_INVALID "invalid option" +#define OPTPARSE_MSG_MISSING "option requires an argument" +#define OPTPARSE_MSG_TOOMANY "option takes no arguments" + +static int +optparse_error(struct optparse *options, const char *msg, const char *data) +{ + unsigned p = 0; + const char *sep = " -- '"; + while (*msg) + options->errmsg[p++] = *msg++; + while (*sep) + options->errmsg[p++] = *sep++; + while (p < sizeof(options->errmsg) - 2 && *data) + options->errmsg[p++] = *data++; + options->errmsg[p++] = '\''; + options->errmsg[p++] = '\0'; + return '?'; +} + +OPTPARSE_API +void +optparse_init(struct optparse *options, char **argv) +{ + options->argv = argv; + options->permute = 1; + options->optind = argv[0] != 0; + options->subopt = 0; + options->optarg = 0; + options->errmsg[0] = '\0'; +} + +static int +optparse_is_dashdash(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; +} + +static int +optparse_is_shortopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; +} + +static int +optparse_is_longopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; +} + +static void +optparse_permute(struct optparse *options, int index) +{ + char *nonoption = options->argv[index]; + int i; + for (i = index; i < options->optind - 1; i++) + options->argv[i] = options->argv[i + 1]; + options->argv[options->optind - 1] = nonoption; +} + +static int +optparse_argtype(const char *optstring, char c) +{ + int count = OPTPARSE_NONE; + if (c == ':') + return -1; + for (; *optstring && c != *optstring; optstring++); + if (!*optstring) + return -1; + if (optstring[1] == ':') + count += optstring[2] == ':' ? 2 : 1; + return count; +} + +OPTPARSE_API +int +optparse(struct optparse *options, const char *optstring) +{ + int type; + char *next; + char *option = options->argv[options->optind]; + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + if (option == 0) { + return -1; + } else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (!optparse_is_shortopt(option)) { + if (options->permute) { + int index = options->optind++; + int r = optparse(options, optstring); + optparse_permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + option += options->subopt + 1; + options->optopt = option[0]; + type = optparse_argtype(optstring, option[0]); + next = options->argv[options->optind + 1]; + switch (type) { + case -1: { + char str[2] = {0, 0}; + str[0] = option[0]; + options->optind++; + return optparse_error(options, OPTPARSE_MSG_INVALID, str); + } + case OPTPARSE_NONE: + if (option[1]) { + options->subopt++; + } else { + options->subopt = 0; + options->optind++; + } + return option[0]; + case OPTPARSE_REQUIRED: + options->subopt = 0; + options->optind++; + if (option[1]) { + options->optarg = option + 1; + } else if (next != 0) { + options->optarg = next; + options->optind++; + } else { + char str[2] = {0, 0}; + str[0] = option[0]; + options->optarg = 0; + return optparse_error(options, OPTPARSE_MSG_MISSING, str); + } + return option[0]; + case OPTPARSE_OPTIONAL: + options->subopt = 0; + options->optind++; + if (option[1]) + options->optarg = option + 1; + else + options->optarg = 0; + return option[0]; + } + return 0; +} + +OPTPARSE_API +char * +optparse_arg(struct optparse *options) +{ + char *option = options->argv[options->optind]; + options->subopt = 0; + if (option != 0) + options->optind++; + return option; +} + +static int +optparse_longopts_end(const struct optparse_long *longopts, int i) +{ + return !longopts[i].longname && !longopts[i].shortname; +} + +static void +optparse_from_long(const struct optparse_long *longopts, char *optstring) +{ + char *p = optstring; + int i; + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + if (longopts[i].shortname && longopts[i].shortname < 127) { + int a; + *p++ = longopts[i].shortname; + for (a = 0; a < (int)longopts[i].argtype; a++) + *p++ = ':'; + } + } + *p = '\0'; +} + +/* Unlike strcmp(), handles options containing "=". */ +static int +optparse_longopts_match(const char *longname, const char *option) +{ + const char *a = option, *n = longname; + if (longname == 0) + return 0; + for (; *a && *n && *a != '='; a++, n++) + if (*a != *n) + return 0; + return *n == '\0' && (*a == '\0' || *a == '='); +} + +/* Return the part after "=", or NULL. */ +static char * +optparse_longopts_arg(char *option) +{ + for (; *option && *option != '='; option++); + if (*option == '=') + return option + 1; + else + return 0; +} + +static int +optparse_long_fallback(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + int result; + char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ + optparse_from_long(longopts, optstring); + result = optparse(options, optstring); + if (longindex != 0) { + *longindex = -1; + if (result != -1) { + int i; + for (i = 0; !optparse_longopts_end(longopts, i); i++) + if (longopts[i].shortname == options->optopt) + *longindex = i; + } + } + return result; +} + +OPTPARSE_API +int +optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + int i; + char *option = options->argv[options->optind]; + if (option == 0) { + return -1; + } else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (optparse_is_shortopt(option)) { + return optparse_long_fallback(options, longopts, longindex); + } else if (!optparse_is_longopt(option)) { + if (options->permute) { + int index = options->optind++; + int r = optparse_long(options, longopts, longindex); + optparse_permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + + /* Parse as long option. */ + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + option += 2; /* skip "--" */ + options->optind++; + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + const char *name = longopts[i].longname; + if (optparse_longopts_match(name, option)) { + char *arg; + if (longindex) + *longindex = i; + options->optopt = longopts[i].shortname; + arg = optparse_longopts_arg(option); + if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { + return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); + } if (arg != 0) { + options->optarg = arg; + } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { + options->optarg = options->argv[options->optind]; + if (options->optarg == 0) + return optparse_error(options, OPTPARSE_MSG_MISSING, name); + else + options->optind++; + } + return options->optopt; + } + } + return optparse_error(options, OPTPARSE_MSG_INVALID, option); +} + +#endif /* OPTPARSE_IMPLEMENTATION */ +#endif /* OPTPARSE_H */ diff --git a/dreamcast/pvrtex/optparse_impl.c b/dreamcast/pvrtex/optparse_impl.c new file mode 100644 index 00000000..834faa51 --- /dev/null +++ b/dreamcast/pvrtex/optparse_impl.c @@ -0,0 +1,2 @@ +#define OPTPARSE_IMPLEMENTATION +#include "optparse.h" diff --git a/dreamcast/pvrtex/pixel.h b/dreamcast/pvrtex/pixel.h new file mode 100644 index 00000000..e8806752 --- /dev/null +++ b/dreamcast/pvrtex/pixel.h @@ -0,0 +1,492 @@ +#pragma once + +#include +#include +#include "mycommon.h" +#include "nvmath.h" + +typedef union { + int rgba[4]; + struct { + int r, g, b, a; + }; + v4i v; +} pxlRGBA32; + +typedef union { + //Assumes little endian + uint32_t argb; + struct { + uint8_t b, g, r, a; + }; +} pxlARGB8888; + +typedef union { + //Assumes little endian + uint32_t rgba; + struct { + uint8_t a, b, g, r; + }; +} pxlRGBA8888; + +typedef union { + //Assumes little endian + uint32_t abgr; + struct { + uint8_t r,g,b,a; + }; +} pxlABGR8888; + +typedef struct { + uint8_t r, g, b; +} pxlRGB888; + +typedef union { + uint16_t v; + struct { + uint16_t b : 5; + uint16_t g : 6; + uint16_t r : 5; + }; +} pxlRGB565; + +typedef union { + uint16_t v; + struct { + uint16_t b : 5; + uint16_t g : 5; + uint16_t r : 5; + uint16_t a : 1; + }; +} pxlARGB1555; + +typedef union { + uint16_t v; + struct{ + uint16_t b : 4; + uint16_t g : 4; + uint16_t r : 4; + uint16_t a : 4; + }; +} pxlARGB4444; + +typedef union { + pxlRGB565 rgb565; + pxlARGB1555 argb1555; + pxlARGB4444 argb4444; +} pxlColor16; + +typedef union { + pxlRGB888 rgb888; +} pxlColor24; + +typedef union { + pxlARGB8888 argb; + pxlRGBA8888 rgba; +} pxlColor32; + +static inline float pxlSatF(float val) { + return MIN(MAX(val, 0.0f), 1.0f); +} + +static inline unsigned int pxlReduceRnd(unsigned int val, unsigned int rshift) { + unsigned rndup = rshift > 1 ? (1 << rshift)-1 : 0; + rndup >>= 1; + rndup = 0; + return MIN(val + rndup, 255) >> rshift; +} +static inline unsigned int pxlExpand(unsigned int val, unsigned int srcwidth) { + assert(srcwidth == 1 || srcwidth >= 4); + assert(srcwidth <= 8); + if (srcwidth == 1) + return val ? 0xff : 0; + //~ return val << (8 - srcwidth); + //~ if (srcwidth == 1) + //~ return val ? 0xff : 0; + //~ else + unsigned v = (val << (8 - srcwidth)); + return v | (v >> srcwidth); +} + +static inline float pxlU8toF(unsigned val) { + //Maps 0-255 to [0.0f, 1.0f] + return MIN((int)val, 255) / 255.0f; +} +static inline float pxlU8BtoF(unsigned val) { + //Maps 1-255 to [-1.0f, 1.0f] + //128 maps to 0.0 + return (MIN((int)val, 255) - 128) / 127.0f; +} +static inline unsigned pxlFtoU8B(float val) { + //Maps [-1.0f, 1.0f] to [1, 255] + //0.0f maps to 128 + return (CLAMP(-1, val, 1) * 127.0f) + 128.0f; +} + + +static inline unsigned pxlFloattoSpherical(float fx, float fy, float fz) { + v3f norm = v3Set(fx,fy,fz); + + norm = v3NormalizeS(norm); + + float azimuth = 0xff; + azimuth = atan2(norm.y, norm.x); + + float altitude = acosf(norm.z); + const float rnd = 0.5; + + int fixed_azimuth = (uint8_t)(azimuth / (2*M_PI) * 255 + rnd); + int fixed_altitude = (uint8_t)(altitude / M_PI * 255 + rnd) ^ 0xff; + + return (fixed_altitude << 8) | fixed_azimuth; + +} +static inline unsigned pxlRGBtoSpherical(unsigned x, unsigned y, unsigned z) { + float fx = pxlU8BtoF(x); + float fy = pxlU8BtoF(y); + float fz = pxlU8BtoF(z); + + return pxlFloattoSpherical(fx, fy, fz); +} + +static inline pxlABGR8888 pxlSphericaltoABGR8888(unsigned norm) { + float azimuth = (norm & 0xff) / 256.0f * (2*M_PI); + float altitude = (((norm >> 8)& 0xff) ^ 0xff) / 255.0f * M_PI; + + + pxlABGR8888 pxl; + pxl.r = pxlFtoU8B(sinf(altitude) * cosf(azimuth)); + pxl.g = pxlFtoU8B(sinf(altitude) * sinf(azimuth)); + pxl.b = pxlFtoU8B(cosf(altitude)); + pxl.a = 255; + + return pxl; +} + +static inline pxlARGB4444 pxlSetARGB4444(float r, float g, float b, float a) { + pxlARGB4444 ret; + ret.a = CLAMP(0,a*15, 15); + ret.r = CLAMP(0,r*15, 15); + ret.g = CLAMP(0,g*15, 15); + ret.b = CLAMP(0,b*15, 15); + //~ ret.r = pxlSatF(r)*15; + //~ ret.g = pxlSatF(g)*15; + //~ ret.b = pxlSatF(b)*15; + return ret; +} +static inline pxlARGB1555 pxlSetARGB1555(float r, float g, float b, float a) { + pxlARGB1555 ret; + ret.a = pxlSatF(a+0.5); + ret.r = pxlSatF(r)*31; + ret.g = pxlSatF(g)*31; + ret.b = pxlSatF(b)*31; + return ret; +} +static inline pxlRGB565 pxlSetRGB565(float r, float g, float b) { + pxlRGB565 ret; + ret.r = pxlSatF(r)*31; + ret.g = pxlSatF(g)*63; + ret.b = pxlSatF(b)*31; + return ret; +} +static inline pxlRGBA8888 pxlSetRGBA8888(float r, float g, float b, float a) { + pxlRGBA8888 ret; + ret.r = pxlSatF(r)*255; + ret.g = pxlSatF(g)*255; + ret.b = pxlSatF(b)*255; + ret.a = pxlSatF(a)*255; + return ret; +} +static inline pxlABGR8888 pxlSetABGR8888(float r, float g, float b, float a) { + pxlABGR8888 ret; + ret.r = pxlSatF(r)*255; + ret.g = pxlSatF(g)*255; + ret.b = pxlSatF(b)*255; + ret.a = pxlSatF(a)*255; + return ret; +} + +#define PXL_ADD_SATURATE_8888(format) \ +static inline format pxlAddSaturate ## format (format l, format r) { \ + format ret; \ + ret.r = CLAMP(0, l.r + r.r, 255); \ + ret.g = CLAMP(0, l.r + r.r, 255); \ + ret.b = CLAMP(0, l.r + r.r, 255); \ + ret.a = CLAMP(0, l.r + r.r, 255); \ + return ret; \ +} + +PXL_ADD_SATURATE_8888(pxlRGBA8888) +PXL_ADD_SATURATE_8888(pxlARGB8888) +PXL_ADD_SATURATE_8888(pxlABGR8888) + +static inline pxlRGBA32 pxlSubRGBA32andABGR888(pxlRGBA32 l, pxlABGR8888 r) { + pxlRGBA32 ret; + ret.r = l.r - r.r; + ret.b = l.b - r.b; + ret.g = l.g - r.g; + ret.a = l.a - r.a; + return ret; +} +static inline pxlRGBA32 pxlAddRGBA32(pxlRGBA32 l, pxlRGBA32 r) { + pxlRGBA32 ret; + ret.r = l.r + r.r; + ret.b = l.b + r.b; + ret.g = l.g + r.g; + ret.a = l.a + r.a; + return ret; +} +static inline pxlRGBA32 pxlSubRGBA32(pxlRGBA32 l, pxlRGBA32 r) { + pxlRGBA32 ret; + ret.r = l.r - r.r; + ret.b = l.b - r.b; + ret.g = l.g - r.g; + ret.a = l.a - r.a; + return ret; +} +static inline pxlRGBA32 pxlMulRGBA32Float(pxlRGBA32 l, float r) { + pxlRGBA32 ret; + ret.r = l.r * r; + ret.r = l.g * r; + ret.r = l.b * r; + ret.r = l.a * r; + return ret; +} + +static inline pxlRGB565 pxlConvertRGBA8888toRGB565(pxlRGBA8888 color) { + pxlRGB565 ret; + ret.r = pxlReduceRnd(color.r, 3); + ret.g = pxlReduceRnd(color.g, 2); + ret.b = pxlReduceRnd(color.b, 3); + return ret; +} + +static inline pxlRGB565 pxlConvertARGB8888toRGB565(pxlARGB8888 color) { + pxlRGB565 ret; + ret.r = pxlReduceRnd(color.r, 3); + ret.g = pxlReduceRnd(color.g, 2); + ret.b = pxlReduceRnd(color.b, 3); + return ret; +} +static inline pxlRGB565 pxlConvertABGR8888toRGB565(pxlABGR8888 color) { + pxlRGB565 ret; + ret.r = pxlReduceRnd(color.r, 3); + ret.g = pxlReduceRnd(color.g, 2); + ret.b = pxlReduceRnd(color.b, 3); + return ret; +} +static inline pxlRGB565 pxlConvertRGBA32toRGB565(pxlRGBA32 color) { + pxlRGB565 ret; + ret.r = pxlReduceRnd(CLAMP(0,color.r,255), 3); + ret.g = pxlReduceRnd(CLAMP(0,color.g,255), 2); + ret.b = pxlReduceRnd(CLAMP(0,color.b,255), 3); + return ret; +} + +#define pxlConvertToRGB565(X) _Generic((X), \ + pxlRGBA8888: pxlConvertRGBA8888toRGB565, \ + pxlARGB8888: pxlConvertARGB8888toRGB565, \ + )(X) + +static inline pxlARGB4444 pxlConvertRGBA8888toARGB4444(pxlRGBA8888 color) { + pxlARGB4444 ret; + ret.a = pxlReduceRnd(color.a, 4); + ret.r = pxlReduceRnd(color.r, 4); + ret.g = pxlReduceRnd(color.g, 4); + ret.b = pxlReduceRnd(color.b, 4); + return ret; +} +static inline pxlARGB4444 pxlConvertARGB8888toARGB4444(pxlARGB8888 color) { + pxlARGB4444 ret; + ret.a = pxlReduceRnd(color.a, 4); + ret.r = pxlReduceRnd(color.r, 4); + ret.g = pxlReduceRnd(color.g, 4); + ret.b = pxlReduceRnd(color.b, 4); + return ret; +} +static inline pxlARGB4444 pxlConvertABGR8888toARGB4444(pxlABGR8888 color) { + pxlARGB4444 ret; + ret.a = pxlReduceRnd(color.a, 4); + ret.r = pxlReduceRnd(color.r, 4); + ret.g = pxlReduceRnd(color.g, 4); + ret.b = pxlReduceRnd(color.b, 4); + return ret; +} +static inline pxlARGB4444 pxlConvertRGBA32toARGB4444(pxlRGBA32 color) { + pxlARGB4444 ret; + ret.r = pxlReduceRnd(CLAMP(0,color.r,255), 4); + ret.g = pxlReduceRnd(CLAMP(0,color.g,255), 4); + ret.b = pxlReduceRnd(CLAMP(0,color.b,255), 4); + ret.a = pxlReduceRnd(CLAMP(0,color.a,255), 4); + return ret; +} +#define pxlConvertToARGB4444(X) _Generic((X), \ + pxlRGBA8888: pxlConvertRGBA8888toARGB4444, \ + pxlARGB8888: pxlConvertARGB8888toARGB4444, \ + )(X) + +static inline pxlARGB1555 pxlConvertRGBA8888toARGB1555(pxlRGBA8888 color) { + pxlARGB1555 ret; + ret.a = pxlReduceRnd(color.a, 7); + ret.r = pxlReduceRnd(color.r, 3); + ret.g = pxlReduceRnd(color.g, 3); + ret.b = pxlReduceRnd(color.b, 3); + return ret; +} +static inline pxlARGB1555 pxlConvertARGB8888toARGB1555(pxlARGB8888 color) { + pxlARGB1555 ret; + ret.a = pxlReduceRnd(color.a, 7); + ret.r = pxlReduceRnd(color.r, 3); + ret.g = pxlReduceRnd(color.g, 3); + ret.b = pxlReduceRnd(color.b, 3); + return ret; +} +static inline pxlARGB1555 pxlConvertABGR8888toARGB1555(pxlABGR8888 color) { + pxlARGB1555 ret; + ret.a = pxlReduceRnd(color.a, 7); + ret.r = pxlReduceRnd(color.r, 3); + ret.g = pxlReduceRnd(color.g, 3); + ret.b = pxlReduceRnd(color.b, 3); + return ret; +} +#define pxlConvertToARGB1555(X) _Generic((X), \ + pxlRGBA8888: pxlConvertRGBA8888toARGB1555, \ + pxlARGB8888: pxlConvertARGB8888toARGB1555, \ + )(X) + +static inline pxlRGBA8888 pxlConvertRGB565toRGBA8888(pxlRGB565 color) { + pxlRGBA8888 ret; + ret.r = pxlExpand(color.r, 5); + ret.g = pxlExpand(color.g, 6); + ret.b = pxlExpand(color.b, 5); + ret.a = 0xff; + return ret; +} +static inline pxlABGR8888 pxlConvertRGB565toABGR8888(pxlRGB565 color) { + pxlABGR8888 ret; + ret.r = pxlExpand(color.r, 5); + ret.g = pxlExpand(color.g, 6); + ret.b = pxlExpand(color.b, 5); + ret.a = 0xff; + return ret; +} +static inline pxlRGBA8888 pxlConvertARGB4444toRGBA8888(pxlARGB4444 color) { + pxlRGBA8888 ret; + ret.r = pxlExpand(color.r, 4); + ret.g = pxlExpand(color.g, 4); + ret.b = pxlExpand(color.b, 4); + ret.a = pxlExpand(color.a, 4); + return ret; +} +static inline pxlABGR8888 pxlConvertARGB4444toABGR8888(pxlARGB4444 color) { + pxlABGR8888 ret; + ret.r = pxlExpand(color.r, 4); + ret.g = pxlExpand(color.g, 4); + ret.b = pxlExpand(color.b, 4); + ret.a = pxlExpand(color.a, 4); + return ret; +} +static inline pxlRGBA8888 pxlConvertARGB1555toRGBA8888(pxlARGB1555 color) { + pxlRGBA8888 ret; + ret.r = pxlExpand(color.r, 5); + ret.g = pxlExpand(color.g, 5); + ret.b = pxlExpand(color.b, 5); + ret.a = pxlExpand(color.a, 1); + return ret; +} +static inline pxlABGR8888 pxlConvertARGB1555toABGR8888(pxlARGB1555 color) { + pxlABGR8888 ret; + ret.r = pxlExpand(color.r, 5); + ret.g = pxlExpand(color.g, 5); + ret.b = pxlExpand(color.b, 5); + ret.a = pxlExpand(color.a, 1); + return ret; +} +#define pxlConvertToRGBA8888(X) _Generic((X), \ + pxlRGB565: pxlConvertRGB565toRGBA8888, \ + pxlARGB4444: pxlConvertARGB4444toRGBA8888, \ + pxlARGB1555: pxlConvertARGB1555toRGBA8888, \ + )(X) + +static inline pxlARGB8888 pxlConvertRGB565toARGB8888(pxlRGB565 color) { + pxlARGB8888 ret; + ret.r = pxlExpand(color.r, 5); + ret.g = pxlExpand(color.g, 6); + ret.b = pxlExpand(color.b, 5); + ret.a = 0xff; + return ret; +} +static inline pxlARGB8888 pxlConvertARGB4444toARGB8888(pxlARGB4444 color) { + pxlARGB8888 ret; + ret.r = pxlExpand(color.r, 4); + ret.g = pxlExpand(color.g, 4); + ret.b = pxlExpand(color.b, 4); + ret.a = pxlExpand(color.a, 4); + return ret; +} +static inline pxlARGB8888 pxlConvertARGB1555toARGB8888(pxlARGB1555 color) { + pxlARGB8888 ret; + ret.r = pxlExpand(color.r, 5); + ret.g = pxlExpand(color.g, 5); + ret.b = pxlExpand(color.b, 5); + ret.a = pxlExpand(color.a, 1); + return ret; +} +#define pxlConvertToARGB8888(X) _Generic((X), \ + pxlRGB565: pxlConvertRGB565toARGB8888, \ + pxlARGB4444: pxlConvertARGB4444toARGB8888, \ + pxlARGB1555: pxlConvertARGB1555toARGB8888, \ + )(X) + +static inline pxlRGBA32 pxlConvertABGR8888toRGBA32(pxlABGR8888 color) { + pxlRGBA32 ret; + ret.r = color.r; + ret.g = color.g; + ret.b = color.b; + ret.a = color.a; + return ret; +} +static inline pxlARGB8888 pxlConvertABGR8888toARGB8888(pxlABGR8888 color) { + pxlARGB8888 ret; + ret.r = color.r; + ret.g = color.g; + ret.b = color.b; + ret.a = color.a; + return ret; +} +static inline pxlRGBA32 pxlConvertRGB565toRGBA32(pxlRGB565 color) { + pxlRGBA32 ret; + ret.r = pxlExpand(color.r, 5); + ret.g = pxlExpand(color.g, 6); + ret.b = pxlExpand(color.b, 5); + ret.a = 0xff; + return ret; +} +static inline pxlRGBA32 pxlConvertARGB4444toRGBA32(pxlARGB4444 color) { + pxlRGBA32 ret; + ret.r = pxlExpand(color.r, 4); + ret.g = pxlExpand(color.g, 4); + ret.b = pxlExpand(color.b, 4); + ret.a = pxlExpand(color.a, 4); + return ret; +} + +#define PXL_COLOR_WEIGHTS v4Set(.3,.59,.11, .7) +static inline unsigned pxlFindClosestColor(const pxlABGR8888 src, const pxlABGR8888 *pal, size_t palsize) { + float bestdist = 1e100; + unsigned best = 0; + v4f srcf = v4Mul(v4Set(src.r, src.g, src.b, src.a), PXL_COLOR_WEIGHTS); + + for(unsigned i = 0; i < palsize; i++) { + pxlABGR8888 c = pal[i]; + v4f colorf = v4Mul(v4Set(c.r, c.g, c.b, c.a), PXL_COLOR_WEIGHTS); + + float dist = v4SqrDistance(colorf, srcf); + if (dist < bestdist) { + bestdist = dist; + best = i; + } + } + + return best; +} diff --git a/dreamcast/pvrtex/pvr_texture.c b/dreamcast/pvrtex/pvr_texture.c new file mode 100644 index 00000000..c4dea182 --- /dev/null +++ b/dreamcast/pvrtex/pvr_texture.c @@ -0,0 +1,480 @@ +#include +#include +#include +#include +#include + +#include "pvr_texture.h" + +#define make_mask(width) ((width != 0) ? (0xffffffffu >> (32 - (width))) : 0) + +typedef struct { + unsigned int x_mask, y_mask; + unsigned int x_inc, y_inc; +} Morton2D; + +#define M2DIncX(m2d, v) (((v) + (m2d).x_inc) & (m2d).x_mask) +#define M2DIncY(m2d, v) (((v) + (m2d).y_inc) & (m2d).y_mask) + +#define BAD_PIXEL() assert((0 && "Bad pixel format")) + +static void AssertPixelFormat(ptPixelFormat fmt) { + assert((fmt >= PT_ARGB1555 && fmt <= PT_PALETTE_8B) || fmt == PT_YUV_TWID); +} + +static inline Morton2D Morton2DInit(unsigned int x_bits, unsigned int y_bits) { + Morton2D m2d; + + int shared = y_bits < x_bits ? y_bits : x_bits; + int x_extra = x_bits - shared; + int y_extra = y_bits - shared; + + shared = (shared-1)*2; + m2d.x_mask = 0xAAAAAAAA & make_mask(shared); + m2d.x_mask |= (make_mask(x_extra) << (shared)); + m2d.x_inc = 0x2 | ~m2d.x_mask; + + m2d.y_mask = 0x55555555 & make_mask(shared); + m2d.y_mask |= (make_mask(y_extra) << shared); + m2d.y_inc = 0x1 | ~m2d.y_mask; + + return m2d; +} + +float BytesPerPixel(ptPixelFormat format) { + switch(format) { + case PT_ARGB1555: + case PT_RGB565: + case PT_ARGB4444: + case PT_YUV: + case PT_NORMAL: + case PT_YUV_TWID: + return 2; + case PT_PALETTE_8B: + return 1; + case PT_PALETTE_4B: + return 0.5; + default: + BAD_PIXEL(); + } +} +size_t UncompressedMipSize(unsigned w, unsigned h, ptPixelFormat format) { + switch(format) { + case PT_ARGB1555: + case PT_RGB565: + case PT_ARGB4444: + case PT_YUV: + case PT_YUV_TWID: + case PT_NORMAL: + return w*h*2; + case PT_PALETTE_8B: + return w*h; + case PT_PALETTE_4B: + return w*h/2; + default: + BAD_PIXEL(); + } +} +unsigned VectorArea(ptPixelFormat format) { + switch(format) { + case PT_ARGB1555: + case PT_RGB565: + case PT_ARGB4444: + case PT_YUV: + case PT_YUV_TWID: + case PT_NORMAL: + return 4; + case PT_PALETTE_8B: + return 8; + case PT_PALETTE_4B: + return 16; + default: + BAD_PIXEL(); + } +} + +size_t TotalMipSize(ptPixelFormat format, int vq, int level) { + AssertPixelFormat(format); + return level ? CalcTextureSize(1<<(level-1), 1<<(level-1), format, 1, vq, 0) : 0; +} + +size_t CalcTextureSize(int u, int v, ptPixelFormat format, int mipmap, int vq, int codebook_size_bytes) { + AssertPixelFormat(format); + + if (mipmap) + v = u; + + size_t texsize = u * v; + + if (mipmap) + texsize = texsize *4/3 + 3; + + if ((unsigned)format <= PT_NORMAL || format == PT_YUV_TWID) + texsize *= 2; + else if (format == PT_PALETTE_4B) + texsize /= 2; + else if (format == PT_PALETTE_8B) + ; + else + BAD_PIXEL(); + + if (vq) + texsize = (texsize+7) / 8 + codebook_size_bytes; + + return texsize; +} + +size_t MipMapOffset(ptPixelFormat format, int vq, int level) { + AssertPixelFormat(format); + + static size_t const ofs[] = { + 0x00006, //1 6 + 0x00008, //2 8 + 0x00010, //4 16 + 0x00030, //8 48 + 0x000B0, //16 176 + 0x002B0, //32 688 + 0x00AB0, //64 2736 + 0x02AB0, //128 10928 + 0x0AAB0, //256 42696 + 0x2AAB0, //512 174768 + 0xAAAB0, //1024 699056 + }; + + assert(level >= 0); + assert(level <= 10); + + size_t ret = ofs[level]; + + if (vq) + return ret/8; + + switch(format) { + case PT_ARGB1555: + case PT_RGB565: + case PT_ARGB4444: + case PT_YUV: + case PT_YUV_TWID: + case PT_NORMAL: + return ret; + case PT_PALETTE_4B: + return ret/4; + case PT_PALETTE_8B: + return ret/2; + default: + BAD_PIXEL(); + } + + return 0; +} + +void MakeTwiddled8(void *pix, int w, int h) { + char *cpy = malloc(w*h), *dst = pix; + char *src = cpy; + memcpy(cpy, pix, w*h); + + Morton2D m2d = Morton2DInit(__builtin_ffs(w), __builtin_ffs(h)); + + int xmorton = 0, ymorton = 0; + int i, j; + for(j = 0; j < h; j++) { + for(i = 0, xmorton = 0; i < w; i++) { + dst[xmorton | ymorton] = *src++; + xmorton = M2DIncX(m2d, xmorton); + } + ymorton = M2DIncY(m2d, ymorton); + } + free(cpy); +} + +void MakeTwiddled16(void *pix, int w, int h) { + uint16_t *cpy = malloc(w*h*2), *dst = pix; + uint16_t *src = cpy; + memcpy(cpy, pix, w*h*2); + + Morton2D m2d = Morton2DInit(__builtin_ffs(w), __builtin_ffs(h)); + + int xmorton = 0, ymorton = 0; + int i, j; + for(j = 0; j < h; j++) { + for(i = 0, xmorton = 0; i < w; i++) { + dst[xmorton | ymorton] = *src++; + xmorton = M2DIncX(m2d, xmorton); + } + ymorton = M2DIncY(m2d, ymorton); + } + free(cpy); +} + +void MakeTwiddled32(void *pix, int w, int h) { + uint32_t *cpy = malloc(w*h*4), *dst = pix; + uint32_t *src = cpy; + memcpy(cpy, pix, w*h*4); + + Morton2D m2d = Morton2DInit(__builtin_ffs(w), __builtin_ffs(h)); + + int xmorton = 0, ymorton = 0; + int i, j; + for(j = 0; j < h; j++) { + for(i = 0, xmorton = 0; i < w; i++) { + dst[xmorton | ymorton] = *src++; + xmorton = M2DIncX(m2d, xmorton); + } + ymorton = M2DIncY(m2d, ymorton); + } + free(cpy); +} + +void MakeDetwiddled32(void *pix, int w, int h) { + uint32_t *cpy = malloc(w*h*4), *dst = pix; + memcpy(cpy, pix, w*h*4); + + Morton2D m2d = Morton2DInit(__builtin_ffs(w), __builtin_ffs(h)); + + int xmorton = 0, ymorton = 0; + int i, j; + for(j = 0; j < h; j++) { + for(i = 0, xmorton = 0; i < w; i++) { + *dst++ = cpy[xmorton | ymorton]; + xmorton = M2DIncX(m2d, xmorton); + } + ymorton = M2DIncY(m2d, ymorton); + } + free(cpy); +} +void MakeDetwiddled8(void *pix, int w, int h) { + char *cpy = malloc(w*h), *dst = pix; + memcpy(cpy, pix, w*h); + + Morton2D m2d = Morton2DInit(__builtin_ffs(w), __builtin_ffs(h)); + + int xmorton = 0, ymorton = 0; + int i, j; + for(j = 0; j < h; j++) { + for(i = 0, xmorton = 0; i < w; i++) { + *dst++ = cpy[xmorton | ymorton]; + xmorton = M2DIncX(m2d, xmorton); + } + ymorton = M2DIncY(m2d, ymorton); + } + free(cpy); +} + +void DecompressVQ(const uint8_t *indicies, int index_cnt, const void *codebook, int cb_offset, void *dst, int auto_small_vq, int codebook_size) { + const uint64_t *cb = codebook; + uint64_t *d = dst; + int i; + for(i = 0; i < index_cnt; i++) { + short codebook_offset = 0; + if(auto_small_vq) { + codebook_offset = 256 - codebook_size; + } + d[i] = cb[(indicies[i] - codebook_offset) + cb_offset]; + } +} + +void ConvertFromFormatToBGRA8888(const void *src, int pixel_format, pxlABGR8888 *pal, unsigned w, unsigned h, pxlABGR8888 *dst) { + AssertPixelFormat(pixel_format); + assert(src); + assert(dst); + + size_t cnt = w*h; + switch(pixel_format) { + case PT_RGB565: { + const pxlRGB565 *psrc = src; + for(size_t i = 0; i < cnt; i++) + dst[i] = pxlConvertRGB565toABGR8888(psrc[i]); + } break; + case PT_ARGB4444: { + const pxlARGB4444 *psrc = src; + for(size_t i = 0; i < cnt; i++) + dst[i] = pxlConvertARGB4444toABGR8888(psrc[i]); + } break; + case PT_ARGB1555: { + const pxlARGB1555 *psrc = src; + for(size_t i = 0; i < cnt; i++) + dst[i] = pxlConvertARGB1555toABGR8888(psrc[i]); + } break; + case PT_NORMAL: { + const uint16_t *psrc = src; + for(size_t i = 0; i < cnt; i++) { + dst[i] = pxlSphericaltoABGR8888(psrc[i]); + + } + } break; + case PT_YUV_TWID: { + //YUV pixels always come in pairs + assert((cnt % 2) == 0); + + const uint16_t *psrc = src; + for(size_t i = 0; i < cnt; i += 4) { + pxlABGR8888 dec[4]; + ConvFromYUV(psrc[i+0], psrc[i+2], dec); + ConvFromYUV(psrc[i+1], psrc[i+3], dec+2); + dst[i+0] = dec[0]; + dst[i+1] = dec[2]; + dst[i+2] = dec[1]; + dst[i+3] = dec[3]; + } + } break; + case PT_YUV: { + //YUV pixels always come in pairs + assert((cnt % 2) == 0); + + const uint16_t *psrc = src; + for(size_t i = 0; i < cnt; i += 2) { + ConvFromYUV(psrc[i+0], psrc[i+1], dst + i); + } + } break; + case PT_PALETTE_8B: { + assert(pal); + const uint8_t *psrc = src; + for(size_t i = 0; i < cnt; i++) { + dst[i] = pal[psrc[i]]; + + } + } break; + case PT_PALETTE_4B: { + assert(pal); + assert((cnt & 1) == 0); + assert(cnt > 1); + const uint8_t *psrc = src; + for(size_t i = 0; i < cnt/2; i++) { + dst[i*2+0] = pal[psrc[i] & 0xf]; + dst[i*2+1] = pal[psrc[i] >> 4]; + + } + } break; + default: + BAD_PIXEL(); + } +} + +void ptConvertToTargetFormat(const pxlABGR8888 *src, unsigned w, unsigned h, pxlABGR8888 *pal, size_t palsize, void *dst, ptPixelFormat pixel_format) { + AssertPixelFormat(pixel_format); + assert(src); + assert(dst); + size_t cnt = w*h; + switch(pixel_format) { + case PT_RGB565: { + pxlRGB565 *pdst = dst; + for(size_t i = 0; i < cnt; i++) + pdst[i] = pxlConvertABGR8888toRGB565(src[i]); + } break; + case PT_ARGB4444: { + pxlARGB4444 *pdst = dst; + for(size_t i = 0; i < cnt; i++) + pdst[i] = pxlConvertABGR8888toARGB4444(src[i]); + } break; + case PT_ARGB1555: { + pxlARGB1555 *pdst = dst; + for(size_t i = 0; i < cnt; i++) + pdst[i] = pxlConvertABGR8888toARGB1555(src[i]); + } break; + case PT_NORMAL: { + uint16_t *pdst = dst; + for(size_t i = 0; i < cnt; i++) { + pdst[i] = pxlRGBtoSpherical(src[i].r, src[i].g, src[i].b); + + } + } break; + case PT_YUV: { + //Untwiddled YUV + uint16_t *pdst = dst; + + //YUV always encodes pairs + assert((cnt % 2) == 0); + for(size_t i = 0; i < cnt; i+=2) { + uint32_t yuv = ConvToYUV(src[i+0], src[i+1]); + pdst[i+0] = yuv>>16; + pdst[i+1] = yuv; + } + } break; + case PT_YUV_TWID: { + //Twiddled YUV + uint16_t *pdst = dst; + + //YUV always encodes pairs + assert((cnt % 2) == 0); + for(size_t i = 0; i < cnt; i+=4) { + uint32_t yuv_top = ConvToYUV(src[i+0], src[i+2]); + uint32_t yuv_bottom = ConvToYUV(src[i+1], src[i+3]); + pdst[i+0] = yuv_top>>16; + pdst[i+1] = yuv_bottom>>16; + pdst[i+2] = yuv_top; + pdst[i+3] = yuv_bottom; + } + } break; + case PT_PALETTE_8B: { + assert(pal); + assert(palsize <= 256); + assert(palsize > 0); + uint8_t *pdst = dst; + for(size_t i = 0; i < cnt; i++) { + pdst[i] = pxlFindClosestColor(src[i], pal, palsize); + + } + } break; + case PT_PALETTE_4B: { + assert(pal); + assert(palsize <= 16); + assert(palsize > 0); + uint8_t *pdst = dst; + for(size_t i = 0; i < cnt/2; i++) { + pdst[i] = (pxlFindClosestColor(src[i*2+1], pal, palsize) << 4) | pxlFindClosestColor(src[i*2], pal, palsize); + + } + } break; + default: + BAD_PIXEL(); + } +} + +bool IsValidStrideWidth(unsigned size) { + if (size > 1024 || size == 0) + return 0; + if (size == 8 || size == 16) + return 1; + if ((size % 32) == 0) + return 1; + return 0; +} + +unsigned MipLevels(int size) { + if (size == 1024) + return 11; + else if (size == 512) + return 10; + else if (size == 256) + return 9; + else if (size == 128) + return 8; + else if (size == 64) + return 7; + else if (size == 32) + return 6; + else if (size == 16) + return 5; + else if (size == 8) + return 4; + else if (size == 4) + return 3; + else if (size == 2) + return 2; + else if (size == 1) + return 1; + else + return 0; + assert(0); +} + +const char * ptGetPixelFormatString(unsigned format) { + const char *name[] = { + "ARGB1555", "RGB565", "ARGB4444", "YUV422", "NORMAL", "PAL4BPP", "PAL8BPP", "INVALID" + }; + if (format > PT_YUV_TWID) + format = PT_YUV; + if (format > 7) + format = 7; + return name[format]; +} diff --git a/dreamcast/pvrtex/pvr_texture.h b/dreamcast/pvrtex/pvr_texture.h new file mode 100644 index 00000000..9610f4cf --- /dev/null +++ b/dreamcast/pvrtex/pvr_texture.h @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include "nvmath.h" +#include "pixel.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PVR_MAX_TEXTURE_WIDTH 1024 +#define PVR_MAX_TEXTURE_HEIGHT 1024 +#define PVR_MAX_MIPMAPS (11) + +#define CHANNEL_CNT_ARGB 4 +#define VECTOR_W 2 +#define VECTOR_H 2 +#define VECTOR_AREA (VECTOR_W * VECTOR_H) +#define VECTOR_SIZE (CHANNEL_CNT_ARGB * VECTOR_AREA) + +#define PVR_CODEBOOK_ENTRY_SIZE_BYTES (8) +#define PVR_CODEBOOK_SIZE_BYTES (2048) +#define PVR_FULL_CODEBOOK (256) + +typedef enum { + PT_SIZE_8, + PT_SIZE_16, + PT_SIZE_32, + PT_SIZE_64, + PT_SIZE_128, + PT_SIZE_256, + PT_SIZE_512, + PT_SIZE_1024, +} ptTextureSize; + +typedef enum { + //The values for following seven formats match up with the values used by the hardware + PT_ARGB1555, + PT_RGB565, + PT_ARGB4444, + PT_YUV, + PT_NORMAL, + PT_PALETTE_4B, + PT_PALETTE_8B, + + //This is not a real PVR pixel format. It's a stand in for a YUV texture that is twiddled, + //which is not encoded the same way as other twiddled formats. + //It exists so that ConvertFromFormatToBGRA8888 can know that a texture is twiddled. + PT_YUV_TWID = PT_YUV + 8, + + //Don't get this confused with ptePixelFormat +} ptPixelFormat; +#define PT_PIXEL_OFFSET PT_PALETTE_8B + +//Returns the number of pixels in a codebook entry for a compressed texture of a given format +unsigned VectorArea(ptPixelFormat format); + +size_t TotalMipSize(ptPixelFormat format, int vq, int level); +size_t UncompressedMipSize(unsigned w, unsigned h, ptPixelFormat format); +size_t MipMapOffset(ptPixelFormat format, int vq, int level); +size_t CalcTextureSize(int u, int v, ptPixelFormat format, int mipmap, int vq, int codebook_size_bytes); +void MakeTwiddled32(void *pix, int w, int h); +void MakeTwiddled16(void *pix, int w, int h); +void MakeTwiddled8(void *pix, int w, int h); +void MakeDetwiddled8(void *pix, int w, int h); +void MakeDetwiddled32(void *pix, int w, int h); +void DecompressVQ(const uint8_t *indicies, int index_cnt, const void *codebook, int cb_offset, void *dst, int auto_small_vq, int codebook_size); +unsigned MipLevels(int size); +bool IsValidStrideWidth(unsigned size); +float BytesPerPixel(ptPixelFormat format); +void ConvertFromFormatToBGRA8888(const void *src, int pixel_format, pxlABGR8888 *pal, unsigned w, unsigned h, pxlABGR8888 *dst); +void ptConvertToTargetFormat(const pxlABGR8888 *src, unsigned w, unsigned h, pxlABGR8888 *pal, size_t palsize, void *dst, ptPixelFormat pixel_format); +const char * ptGetPixelFormatString(unsigned format); + +static inline unsigned ConvToYUV(pxlABGR8888 l, pxlABGR8888 r) { + const float avgR = (l.r + r.r) / 2; + const float avgG = (l.g + r.g) / 2; + const float avgB = (l.b + r.b) / 2; + + //compute each pixel's Y + int Y0 = CLAMP(0, (int)(0.299 * l.r + 0.587 * l.g + 0.114 * l.b), 255); + int Y1 = CLAMP(0, (int)(0.299 * r.r + 0.587 * r.g + 0.114 * r.b), 255); + + int U = CLAMP(0, (int)(-0.169 * avgR - 0.331 * avgG + 0.4990 * avgB + 128), 255); + int V = CLAMP(0, (int)( 0.499 * avgR - 0.418 * avgG - 0.0813 * avgB + 128), 255); + + unsigned yuv1 = ((uint8_t)Y0) << 8 | (uint8_t)U; + unsigned yuv2 = ((uint8_t)Y1) << 8 | (uint8_t)V; + + return (yuv1 << 16) | yuv2; +} + +//Writes two pixels, to dst[0] and dst[1] +static inline void ConvFromYUV(unsigned yuv1, unsigned yuv2, pxlABGR8888 *dst) { + const int Y0 = (yuv1 & 0xFF00) >> 8; + const int Y1 = (yuv2 & 0xFF00) >> 8; + const int U = (int)(yuv1 & 0xFF) - 128; + const int V = (int)(yuv2 & 0xFF) - 128; + int r, g, b; + + r = CLAMP(0, (int)(Y0 + 1.375 * V), 255); + g = CLAMP(0, (int)(Y0 - 0.34375 * U - 0.6875 * V), 255); + b = CLAMP(0, (int)(Y0 + 1.71875 * U), 255); + dst[0] = pxlSetABGR8888(pxlU8toF(r), pxlU8toF(g), pxlU8toF(b), 1); + + r = CLAMP(0, (int)(Y1 + 1.375 * V), 255); + g = CLAMP(0, (int)(Y1 - 0.34375 * U - 0.6875 * V), 255); + b = CLAMP(0, (int)(Y1 + 1.71875 * U), 255); + dst[1] = pxlSetABGR8888(pxlU8toF(r), pxlU8toF(g), pxlU8toF(b), 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/dreamcast/pvrtex/pvr_texture_encoder.c b/dreamcast/pvrtex/pvr_texture_encoder.c new file mode 100644 index 00000000..91c143b4 --- /dev/null +++ b/dreamcast/pvrtex/pvr_texture_encoder.c @@ -0,0 +1,932 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "vqcompress.h" +#include "pixel.h" +#include "stb_image.h" +#include "stb_image_resize.h" +#include "pvr_texture.h" +#include "pvr_texture_encoder.h" +#include "mycommon.h" +#include "tddither.h" + +/* + Returns the pixel format for a given mipmap level + YUV can change depending on level and if the texture is twiddled, this function + returns the correct format. + + Passing 1 for miplevel will can be used to get the format for any level that + isn't the 1x1 level +*/ +unsigned pteGetConvertFormat(PvrTexEncoder *pte, int miplevel) { + assert(pte); + + unsigned format = pte->pixel_format; + if (format == PTE_YUV) { + if (miplevel == 0 && pteHasMips(pte)) + return PTE_RGB565; + if (pte->raw_is_twiddled) + return PTE_YUV_TWID; + } + return format; +} + + +void pteInit(PvrTexEncoder *pte) { + assert(pte); + + memset(pte, 0, sizeof(*pte)); + pte->mip_cnt = 0; + pte->rgb_gamma = 1.0; + pte->alpha_gamma = 1.0; + pte->codebook_size = 0; + pte->resize = PTE_FIX_NONE; + pte->mipresize = PTE_FIX_MIP_NONE; + pte->pixel_format = PTE_AUTO; + pte->auto_small_vq = false; + pte->edge_method = 0; + pte->mip_shift_correction = true; +} + +void pteFree(PvrTexEncoder *pte) { + assert(pte); + SAFE_FREE(&pte->pvr_codebook); + SAFE_FREE(&pte->palette); + SAFE_FREE(&pte->final_preview); + SAFE_FREE(&pte->pvr_tex); + SAFE_FREE(&pte->pvr_tex32); + for(int i = 0; i < PVR_MAX_MIPMAPS; i++) { + SAFE_FREE(&pte->src_imgs[i].pixels); + SAFE_FREE(&pte->raw_mips[i]); + SAFE_FREE(&pte->pvr_mips[i]); + SAFE_FREE(&pte->preview_mips[i]); + } +} + +void pteLoadFromFiles(PvrTexEncoder *pte, const char **fnames, unsigned filecnt) { + assert(filecnt < PVR_MAX_MIPMAPS); + + unsigned maxw = 0, maxh = 0; + for(unsigned i = 0; i < filecnt ; i++) { + pteImage *img = pte->src_imgs + i; + img->w = img->h = 0; + img->channels = 4; + img->pixels = (void*)stbi_load(fnames[i], &img->w, &img->h, &img->channels, 4); + ErrorExitOn(img->pixels == NULL, + "Could not load image \"%s\", exiting\n", fnames[i]); + + if (filecnt > 1) { + ErrorExitOn(!IsPow2(img->w) || !IsPow2(img->h), + "When using custom mipmaps, the size of all levels must be a power of two" + " (resize is not supported). %s has a size of %ux%u\n", fnames[i], + img->w, img->h); + ErrorExitOn(img->w != img->h, + "When using custom mipmaps, all levels must be square" + " (resize is not supported). %s has a size of %ux%u\n", fnames[i], + img->w, img->h); + } + + maxw = MAX(maxw, img->w); + maxh = MAX(maxh, img->h); + } + pte->src_img_cnt = filecnt; + pte->w = maxw; + pte->h = maxh; +} + +void pteMakeSquare(PvrTexEncoder *pte) { + assert(pte); + + unsigned smaller = MIN(pte->h, pte->w); + unsigned larger = MAX(pte->h, pte->w); + + switch(pte->mipresize) { + case PTE_FIX_MIP_NONE: + break; + case PTE_FIX_MIP_MAX: + pte->h = pte->w = MAX(pte->h, pte->w); + break; + case PTE_FIX_MIP_MIN: + pte->h = pte->w = MIN(pte->h, pte->w); + break; + case PTE_FIX_MIP_NARROW_X2: + pte->w = pte->h = MIN(smaller*2, larger); + break; + case PTE_FIX_MIP_NARROW_X4: + pte->w = pte->h = MIN(smaller*4, larger); + break; + default: + assert(0); + } + + //Clamp width and height to PVR limits + pte->w = CLAMP(8, pte->w, 1024); + pte->h = CLAMP(8, pte->h, 1024); +} + +int pteSetSize(PvrTexEncoder *pte) { + assert(pte); + assert(pte->w); + assert(pte->h); + + switch(pte->resize) { + case PTE_FIX_NONE: + //Fail if width or height aren't valid + if (pte->w > 1024 || pte->w < 8 || pte->h > 1024 || pte->h < 8) + ErrorExit("Width and height must be between 8 and 1024, and no resize is set. Dimensions are %ix%i\n", pte->w, pte->h); + + if (!pteIsStrided(pte)) { + if (!IsPow2(pte->w) || !IsPow2(pte->h)) + ErrorExit("Width and height must be a power of two for non-stride textures. Dimensions are %ix%i\n", pte->w, pte->h); + } else { + if (!IsValidStrideWidth(pte->w)) + ErrorExit("Width must be either 8, 16, or a multiple of 32 for stride textures. Width is %i\n", pte->w); + //~ if (!pteIsPartial(pte)) + //~ assert(IsPow2(pte->h)); + } + return 0; + break; + case PTE_FIX_UP: + if (!pteIsStrided(pte)) { + //Round width and height up to next higher power of two + pte->w = RoundUpPow2(pte->w); + } else { + if (pte->w > 16) + pte->w = (pte->w + 31) & ~0x1f; + else if (pte->w > 8) + pte->w = 16; + else + pte->w = 8; + } + pte->h = RoundUpPow2(pte->h); + break; + case PTE_FIX_DOWN: + if (!pteIsStrided(pte)) { + //Round width and height down to next higher power of two + pte->w = RoundDownPow2(pte->w); + } else { + if (pte->w >= 32) + pte->w = pte->w & ~0x1f; + else if (pte->w >= 16) + pte->w = 16; + else + pte->w = 8; + } + pte->h = RoundDownPow2(pte->h); + break; + case PTE_FIX_NEAREST: { + if (!pteIsStrided(pte)) { + //Round width to nearest power of two + pte->w = SelectNearest(RoundDownPow2(pte->w), pte->w, RoundUpPow2(pte->w)); + + } else { + if (pte->w >= 24) + pte->w = RoundNearest(pte->w, 32); + else if (pte->w >= 12) + pte->w = 16; + else + pte->w = 8; + } + //Round height to nearest power of two + pte->h = SelectNearest(RoundDownPow2(pte->h), pte->h, RoundUpPow2(pte->h)); + } break; + default: + assert(0); + } + + //Clamp width and height to PVR limits + pte->w = CLAMP(8, pte->w, 1024); + pte->h = CLAMP(8, pte->h, 1024); + + assert((pte->w % 4) == 0); + + pteLog(LOG_INFO, "Texture size: %ix%i\n", pte->w, pte->h); + + return 0; +} + +void pteSetCompressed(PvrTexEncoder *pte, int codebook_size) { + pte->codebook_size = codebook_size; +} + +void pteConvertRawToTwiddled(PvrTexEncoder *pte) { + assert(pte); + assert(pte->stride == false); + assert(pte->mip_cnt > 0); + assert(pte->mip_cnt <= PVR_MAX_MIPMAPS); + assert(IsPow2(pte->w)); + assert(IsPow2(pte->h)); + + + FOR_EACH_MIP(pte, i) { + assert(pte->raw_mips[i]); + MakeTwiddled32(pte->raw_mips[i], mw, mh); + } + + pte->raw_is_twiddled = true; +} + +void pteCombineABGRData(PvrTexEncoder *pte) { + assert(pte); + assert(pte->mip_cnt > 0); + assert(pte->mip_cnt <= PVR_MAX_MIPMAPS); + + SMART_ALLOC(&pte->pvr_tex32, CalcTextureSize(pte->w, pte->h, PTE_RGB565, pteHasMips(pte), 0, 0) * 2); + if (!pteIsCompressed(pte)) + SMART_ALLOC(&pte->pvr_tex, CalcTextureSize(pte->w, pte->h, PTE_RGB565, pteHasMips(pte), 0, 0)); + + if (pteHasMips(pte)) { + assert(pte->w == pte->h); + FOR_EACH_MIP(pte, i) { + assert(pte->raw_mips[i] != NULL); + + if (i == 0) { + //For 1x1 mip, fill padding with pixel + memcpy(pte->pvr_tex32 + 0, pte->raw_mips[i], mw * mh * sizeof(pxlABGR8888)); + memcpy(pte->pvr_tex32 + 1, pte->raw_mips[i], mw * mh * sizeof(pxlABGR8888)); + memcpy(pte->pvr_tex32 + 2, pte->raw_mips[i], mw * mh * sizeof(pxlABGR8888)); + memcpy(pte->pvr_tex32 + 3, pte->raw_mips[i], mw * mh * sizeof(pxlABGR8888)); + } else { + memcpy(pte->pvr_tex32 + MipMapOffset(PT_PIXEL_OFFSET, 0, i), pte->raw_mips[i], mw * mh * sizeof(pxlABGR8888)); + } + } + } else { + assert(pte->raw_mips[0] != NULL); + memcpy(pte->pvr_tex32, pte->raw_mips[0], pte->w * pte->h * sizeof(pxlABGR8888)); + } +} + +void pteGenerateUncompressed(PvrTexEncoder *pte) { + assert(pte); + assert(pte->mip_cnt > 0); + assert(pte->mip_cnt <= PVR_MAX_MIPMAPS); + + //Handle first 4 pixels seperately in case we have mipped YUV. pteGetConvertFormat will figure out the correct format from pte + ptConvertToTargetFormat(pte->pvr_tex32, 2, 2, pte->palette, pte->palette_size, pte->pvr_tex, pteGetConvertFormat(pte, 0)); + //Convert the rest + ptConvertToTargetFormat(pte->pvr_tex32+4, CalcTextureSize(pte->w, pte->h, PT_PIXEL_OFFSET, pteHasMips(pte), 0, 0) - 4, 1, + pte->palette, pte->palette_size, pte->pvr_tex+(int)(4*BytesPerPixel(pte->pixel_format)), pteGetConvertFormat(pte, 1)); + +} + +void pteDitherRaws(PvrTexEncoder *pte, float dither_amt) { + assert(pte); + assert(pte->mip_cnt > 0); + assert(pte->mip_cnt <= PVR_MAX_MIPMAPS); + assert(!pte->raw_is_twiddled); + + FOR_EACH_MIP(pte, i) { + assert(pte->raw_mips[i] != NULL); + + pteDither((void*)pte->raw_mips[i], mw, mh, 4, dither_amt, pteGetFindNearest(pte->pixel_format), pte->palette, pte->palette_size, pte->raw_mips[i], PTE_ABGR8888); + } +} + + +void pteGeneratePalette(PvrTexEncoder *pte) { + assert(pte); + assert(pte->mip_cnt > 0); + assert(pte->mip_cnt <= PVR_MAX_MIPMAPS); + assert(pte->palette == NULL); + assert(pte->palette_size > 0); + assert(pte->palette_size <= 256); + + VQCompressor vqc; + vqcInit(&vqc, VQC_UINT8, 4, 1, pte->palette_size, pte->auto_small_vq); + vqcSetRGBAGamma(&vqc, pte->rgb_gamma, pte->alpha_gamma); + + //Add mipmaps to compressor input + FOR_EACH_MIP(pte, i) { + uint32_t pixelcnt = mw * mh; + vqcAddPoints(&vqc, pte->raw_mips[i], pixelcnt); + } + + //Do compression and save resulting palette + vqcResults result = vqcCompress(&vqc, 8); + assert(result.codebook); + pte->palette = result.codebook; + free(result.indices); + + //~ for(unsigned i = 0; i < 256; i++) { + //~ printf("(%i, %i, %i) ", pte->palette[i].r, pte->palette[i].g, pte->palette[i].b, pte->palette[i].a); + //~ } +} + + +typedef uint64_t CBVector; +typedef pxlABGR8888 PerfCodebook[PVR_FULL_CODEBOOK][16]; +//If vec is in cb, returns idx, otherwise adds to cb and returns cb_used +static unsigned AddFindVector(PvrTexEncoder *pte, CBVector *cb, pxlABGR8888 *vec, unsigned vectorarea, unsigned cb_used, unsigned offset, unsigned format) { + unsigned match = 0; + + CBVector vecconv = 0; + assert(offset < vectorarea); + ptConvertToTargetFormat(vec, vectorarea , 1, pte->palette, pte->palette_size, &vecconv, format); + //~ printf("AddFindVector %016lx \n", vecconv); + + + CBVector compare_mask = (CBVector)-1ll; + unsigned bits_per_pixel = BytesPerPixel(format) * 8; + compare_mask <<= offset * bits_per_pixel; + CBVector compvec = vecconv; + + for(match = 0; match < cb_used; match++) { + //Look for a matching entry in the perfect codebook + //~ printf("Comp %016lx to %016lx (%016lx)\n", compvec, cb[match], compare_mask); + if ((compvec & compare_mask) == (cb[match] & compare_mask)) + break; + } + assert(match < PVR_FULL_CODEBOOK); //Don't overflow perfect CB + if (match == cb_used) { + //~ printf("Added %016lx at %u\n", vecconv, match); + cb[match] = vecconv; + } + return match; +} + +void pteCompress(PvrTexEncoder *pte) { + assert(pte); + assert(pte->mip_cnt > 0); + assert(pte->mip_cnt <= PVR_MAX_MIPMAPS); + assert(pte->codebook_size > 0); + assert(pte->pvr_tex32 != NULL); + if (!pteIsStrided(pte)) { + assert(IsPow2(pte->w)); + assert(IsPow2(pte->h)); + assert(pte->raw_is_twiddled); + } + + unsigned cbsize = pte->codebook_size; + const unsigned vectorarea = VectorArea(pte->pixel_format); + + pteLog(LOG_DEBUG, "Codebook size is %i\n", cbsize); + + //Calculate codebook size after taking out perfect mip codes + unsigned gen_perfect_mip_vectors = 0; + CBVector perfect_cb[PVR_FULL_CODEBOOK]; + assert(vectorarea <= 16); + + if (pteHasMips(pte)) { + if ((pte->pixel_format == PTE_PALETTE_4B || pte->pixel_format == PTE_PALETTE_8B) && pte->perfect_mips < 2) { + pteLog(LOG_DEBUG, "Need some perfect mips, so adding some\n"); + pte->perfect_mips = 2; + } if (pte->pixel_format == PTE_YUV && pte->perfect_mips < 1) { + pteLog(LOG_DEBUG, "Need some perfect mips, so adding some\n"); + pte->perfect_mips = 1; + } + } else { + if (pte->perfect_mips != 0) { + pteLog(LOG_WARNING, "Got --perfect-mips option, but not using any mipmaps."); + pte->perfect_mips = 0; + } + } + + //Number of pixels we generate perfect mips for + const unsigned perf_mip_size_pix = TotalMipSize(PT_PIXEL_OFFSET, 0, pte->perfect_mips); + //Number of vectors we need to create. Round up for 4bpp mips + const unsigned perfect_mip_idx = (perf_mip_size_pix + vectorarea - 1) / vectorarea; + + //Go through every perfect mip level and add each vector to a perfect codebook + //We do not want to add duplicate vectors + //Start at the highest level and work down, this way any incomplete levels, like the + //1x1 mip level, can potentially reuse any vectors from higher mip levels if they are the same + //If we start with the 1x1 mip and have junk in the unused part, it's unlikely we will + //find a match in the higher levels + for(int i = (perfect_mip_idx-1)*vectorarea; i >= 0; i -= vectorarea) { + pxlABGR8888 *src = pte->pvr_tex32 + i; + unsigned offset = 0; + if (pteHasMips(pte) && i == 0) { + //The start of the texture contains the smallest mip(s), which does not use the entire vector + //offset is always 3 pixels + offset = 3; + } + + unsigned match = AddFindVector(pte, perfect_cb, src, vectorarea, gen_perfect_mip_vectors, offset, pteGetConvertFormat(pte, i)); + if (match >= gen_perfect_mip_vectors) { + gen_perfect_mip_vectors++; + } + } + + pteLog(LOG_DEBUG, "Made %u perfect vectors\n", gen_perfect_mip_vectors); + assert(gen_perfect_mip_vectors < pte->codebook_size); + + cbsize -= gen_perfect_mip_vectors; + + assert(cbsize <= PVR_FULL_CODEBOOK); + assert(cbsize > 0); + VQCompressor vqc; + vqcInit(&vqc, VQC_UINT8, 4, vectorarea, cbsize, pte->auto_small_vq); + vqcSetRGBAGamma(&vqc, pte->rgb_gamma, pte->alpha_gamma); + + //Add uncompressed data + const unsigned perfect_mip_pixels = perfect_mip_idx * vectorarea; + const unsigned pxlcnt = CalcTextureSize(pte->w, pte->h, PT_PIXEL_OFFSET, pteHasMips(pte), 0, 0) - perfect_mip_pixels; + const unsigned inperfveccnt = (pxlcnt+vectorarea-1) / vectorarea; + + if (pxlcnt % vectorarea) { + //Because of the way compressed mipmapped 4bpp works, the bottom right index + //is only partially used (2x4 is used out of 4x4), and the number of pixels + //in the texture is not a multiple of the vector size. + //~ printf("Have incomplete vector\n"); + assert(pteHasMips(pte) && pte->pixel_format == PTE_PALETTE_4B); + + unsigned npxlcnt = pxlcnt - pxlcnt % vectorarea; //Round down to whole vector + vqcAddPoints(&vqc, pte->pvr_tex32 + perfect_mip_pixels, npxlcnt); + //~ inperfveccnt = npxlcnt+vectorarea-1) / vectorarea; + + //Expand the last 4x2 area into 4x4 by duplicating the 4x2 area + pxlABGR8888 last[4*4]; + pxlABGR8888 *src = pte->pvr_tex32 + MipMapOffset(pte->pixel_format, 0, pteTopMipLvl(pte)) + pte->w*pte->h - 8; + memcpy(last, src, 8 * sizeof(pxlABGR8888)); + memcpy(last+8, src, 8 * sizeof(pxlABGR8888)); + vqcAddPoints(&vqc, last, 16); + } else { + //We can go ahead and add everything + //~ printf("No incomplete vectors\n"); + assert(!(pteHasMips(pte) && pte->pixel_format == PTE_PALETTE_4B)); + vqcAddPoints(&vqc, pte->pvr_tex32 + perfect_mip_pixels, pxlcnt); + } + + //Re-add some points to increase their weight + //TODO We don't handle partial vectors which are required for 4BPP mipped, so turn off perfect mips for 4bpp mipped + if (pte->high_weight_mips && pteHasMips(pte) && pte->pixel_format == PTE_PALETTE_4B) { + pteLog(LOG_WARNING, "***Compressed mipmapped 4BPP does not currently support high weight mips***\nCreating texture without high weight mips\n"); + pte->high_weight_mips = 0; + } + const unsigned highlvl = pte->mip_cnt - pte->high_weight_mips; + if (highlvl > 0 && highlvl < pte->mip_cnt) { + unsigned high_start = perfect_mip_pixels; + unsigned high_end = MipMapOffset(PT_PIXEL_OFFSET, 0, highlvl); + pteLog(LOG_DEBUG, "High weight up and including to %u\n", 1u<<(highlvl-1)); + pteLog(LOG_DEBUG, "Re-adding bytes from %u to %u\n", high_start, high_end); + if (high_end > high_start && high_end < pxlcnt) + vqcAddPoints(&vqc, pte->pvr_tex32 + high_start, high_end - high_start); + else + pteLog(LOG_DEBUG, "Can't add high weight mips (start %u, end %u, pxlcnt %u)\n", high_start, high_end, pxlcnt); + } + + //Do compression and save results + pteLog(LOG_DEBUG, "Doing compression %u...\n", vqc.point_cnt); fflush(stdout); + vqcResults result = vqcCompress(&vqc, 200); + pteLog(LOG_DEBUG, "Done!\n"); fflush(stdout); + assert(result.indices); + assert(result.codebook); + + //Create PVR codebook + SMART_ALLOC(&pte->pvr_codebook, PVR_CODEBOOK_SIZE_BYTES); + ptConvertToTargetFormat(result.codebook, cbsize, vectorarea, pte->palette, pte->palette_size, + pte->pvr_codebook + pte->pvr_idx_offset*8, pteGetConvertFormat(pte, 1)); + + //Add any perfect CB vectors to end of generated CB + unsigned perfectcbofs = pte->pvr_idx_offset + pte->codebook_size - gen_perfect_mip_vectors; + void *cbend = pte->pvr_codebook + perfectcbofs * 8; + memcpy(cbend, perfect_cb, gen_perfect_mip_vectors * 8); + + //Build up indices for texture + SMART_ALLOC(&pte->pvr_tex, CalcTextureSize(pte->w, pte->h, pte->pixel_format, pteHasMips(pte), 1, PVR_CODEBOOK_SIZE_BYTES)); + uint8_t *texdst = pte->pvr_tex; + + //Add any perfect mips + for(int i = 0; i < perfect_mip_idx; i++) { + //Go though each vector and find the matching codebook entry + pxlABGR8888 *src = pte->pvr_tex32 + i*vectorarea; + unsigned offset = 0; + if (pteHasMips(pte) && i == 0) { + //The start of the texture contains the smallest mip(s), which does not use the entire vector + //offset is always 3 pixels + offset = 3; + } + + unsigned format = pteGetConvertFormat(pte, i); + + unsigned match = AddFindVector(pte, perfect_cb, src, vectorarea, gen_perfect_mip_vectors, offset, format); + assert(match < gen_perfect_mip_vectors); //We should always find a match + texdst[i] = match + perfectcbofs; + } + + //Convert data from VQCompressor (int32 to uint8) + for(int d = perfect_mip_idx, s = 0; s < inperfveccnt; d++, s++) { + texdst[d] = result.indices[s] + pte->pvr_idx_offset; + } + + free(result.indices); +} + +static pteImage * pteHighestSrcMip(PvrTexEncoder *pte) { + unsigned highest = 0; + unsigned highestw = pte->src_imgs[0].w; + + for(unsigned i = 1; i < pte->src_img_cnt; i++) { + if (pte->src_imgs[i].w > highestw) { + highestw = pte->src_imgs[i].w; + highest = i; + } + } + + return pte->src_imgs + highest; +} + +//w is the width in pixels of the mip level to generate an image for +static pteImage pteGetShrinkLevel(PvrTexEncoder *pte, int w) { + assert(pte); + assert(pte->src_img_cnt > 0); + assert(pte->src_imgs[0].pixels); + + unsigned level = MipLevels(w); + + if (pte->want_mips == PTE_MIP_QUALITY) + level += 2; + + int desired_width = 1 << (level); + + //Default to first src_img, since it's known good + pteImage best = pte->src_imgs[0]; + + //If the desired width is for a level small enough that we have already created it, + //use the existing level + if (desired_width <= pte->w) { + pteLog(LOG_DEBUG, "using existing mip %i (%i)\n", desired_width, level); + best.w = best.h = desired_width; + best.pixels = pte->raw_mips[level]; + } + + //Check rest of src_imgs for an exact match with w, this overrides everything + for(unsigned i = 0; i < pte->src_img_cnt; i++) { + pteImage *cur = pte->src_imgs + i; + if (cur->w == w && cur->h == w) { + //Have exact match + pteLog(LOG_DEBUG, "Match\n"); + best = *cur; + break; + } + } + + return best; +} + +void pteGenerateMips(PvrTexEncoder *pte) { + assert(pte); + assert(!pte->raw_is_twiddled); //can't generate if already twiddled, twiddle afterwards + assert(pte->mip_cnt == 0); //have no mips + + //Fix size if resizing is enabled + pteMakeSquare(pte); + + ErrorExitOn(pteIsStrided(pte), "Mipmapped textures must be twiddled, but have stride parameter\n"); + ErrorExitOn(pte->w != pte->h, "Image must be square, but dimensions are (%ux%u)\n", pte->w, pte->h); + ErrorExitOn(!IsPow2(pte->w) || !IsPow2(pte->h), "Height and width must be a power of two, but dimensions are (%ux%u)\n", pte->w, pte->h); + + assert(pte->w == pte->h); //have square + assert(IsPow2(pte->w)); + assert(IsPow2(pte->h)); + assert(pte->w >= 8); + + pte->mip_cnt = MipLevels(pte->w); + + assert(pte->mip_cnt > 0); + assert(pte->mip_cnt <= 11); + + FOR_EACH_MIP_REV(pte, i) { + //Allocate space for mip level + unsigned pixelcnt = mw * mh; + assert(pte->raw_mips[i] == NULL); //Should have no raws yet + SMART_ALLOC(&pte->raw_mips[i], pixelcnt * sizeof(pxlABGR8888)); + + pteImage src = pteGetShrinkLevel(pte, mw); + pteLog(LOG_INFO, "Making %ux%u mip from %ux%u image\n", mw, mh, src.w, src.h); + if (src.w == mw && src.h == mh) { + memcpy(pte->raw_mips[i], src.pixels, mw * mh * sizeof(pxlABGR8888)); + } else { + float shift = 0; + if (pte->mip_shift_correction) + shift = -0.5 + (float)mw / pte->w / 2; + + stbir_resize_subpixel(src.pixels, src.w, src.h, 0, + pte->raw_mips[i], mw, mh, 0, + STBIR_TYPE_UINT8, //format + 4, //channels + 3, //alpha + 0, //alpha flags (use default handling) + pte->edge_method, pte->edge_method, + + STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + + STBIR_COLORSPACE_SRGB, + //~ STBIR_COLORSPACE_LINEAR, + NULL, //alloc + (float)mw / src.w, (float)mw / src.h, //xscale, yscale, + shift, shift //xoffset, yoffset + ); + } + } +} + + +int pteGenerateRawFromSource(PvrTexEncoder *pte) { + assert(pte); + assert(pte->src_img_cnt >= 1); + assert(pte->src_imgs[0].pixels); + assert(pte->w >= 8); + assert(pte->h >= 8); + assert(pte->w <= 1024); + assert(pte->h <= 1024); + + size_t tex_raw_size = pte->w * pte->h * sizeof(pxlABGR8888); + SMART_ALLOC(&pte->raw_mips[0], tex_raw_size); + + //If no input mips, set raw_mips to one level, with one image from source data + pteImage *h = pteHighestSrcMip(pte); + if (h->w == pte->w && h->h == pte->h) { + //If src size is equal to texture size, just copy the data over + pteLog(LOG_INFO, "Source size matches texture size\n"); + memcpy(pte->raw_mips[0], h->pixels, tex_raw_size); + pte->mip_cnt = 1; + } else { + //If src size is different to texture size, resize source to fit texture + pteLog(LOG_INFO, "Source is getting resized from %ux%u to %ux%u\n", h->w, h->h, pte->w, pte->h); + pte->mip_cnt = 1; + stbir_resize(h->pixels, h->w, h->h, 0, + pte->raw_mips[0], pte->w, pte->h, 0, + STBIR_TYPE_UINT8, //format + 4, //channels + 3, //alpha + 0, //alpha flags (use default handling) + pte->edge_method, pte->edge_method, + STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_COLORSPACE_SRGB, + //~ STBIR_COLORSPACE_LINEAR, + NULL //alloc + ); + } + return 0; +} + +void pteGeneratePreviews(PvrTexEncoder *pte) { + assert(pte); + assert(pte->pvr_tex); + + unsigned size_pixels = CalcTextureSize(pte->w, pte->h, PT_PIXEL_OFFSET, pteHasMips(pte), 0, 0); + void *src = pte->pvr_tex; + if (pteIsCompressed(pte)) { + //For compressed mipmapped 4bpp textures, the number of indices is not + //a multiple of the texture size. Round up the index count, and allocate + //room for an extra vector worth of pixels + src = malloc(size_pixels * 2 + 16); + unsigned vecarea = VectorArea(pte->pixel_format); + unsigned idxs = (size_pixels+vecarea-1) / vecarea; + DecompressVQ(pte->pvr_tex, idxs, pte->pvr_codebook, 0, src, pte->auto_small_vq, pte->codebook_size); + } + + FOR_EACH_MIP(pte, i) { + unsigned format = pteGetConvertFormat(pte, i); + + unsigned w = mw; + //For 1x1 4bpp, we need to convert two pixels in a byte, so up the size + if (format == PTE_PALETTE_4B && mw == 1) + w = 2; + + //Allocate buffer for unconverted mip level + pxlABGR8888 *prev = malloc(w * mh * sizeof(pxlABGR8888)); + + //Get pixel data for current mip level + void *pixels = src; + if (pteHasMips(pte)) { + //We already decompressed the image, so we always pass 0 for compression here + unsigned ofs = MipMapOffset(pte->pixel_format, 0, i); + pixels += ofs; + } + + //Convert image from pixels, storing in prev + ConvertFromFormatToBGRA8888(pixels, format, pte->palette, w, mh, prev); + + //For 4bpp, the pixel we need is stored as the second pixel of the two from the byte we converted + if (format == PTE_PALETTE_4B && mw == 0) + prev[0] = prev[1]; + + //Detwiddle if using twiddled format + if (pte->raw_is_twiddled) + MakeDetwiddled32(prev, mw, mh); + + //Save deconverted image to pte + SAFE_FREE(&pte->preview_mips[i]); + pte->preview_mips[i] = prev; + } + + if (pteHasMips(pte)) { + //Create preview image with combined mips + assert(pte->final_preview == NULL); + unsigned mp_w = pte->w * 1.5; + + pte->final_preview_w = mp_w; + SMART_ALLOC(&pte->final_preview, pte->h * mp_w * sizeof(pxlABGR8888)); + pxlABGR8888 *mp = pte->final_preview; + unsigned mipy = pte->h - 1; + + if (1) { + //Create preview with all mips + FOR_EACH_MIP(pte, i) { + assert(pte->preview_mips[i]); + + //The highest mip level goes to the top left, the rest are on the right + //For the non-top levels, we start at the bottom then go up + unsigned mipx = pte->w; + mipy -= mh; + if (i == pteTopMipLvl(pte)) { + mipy = 0; + mipx = 0; + } + + //Copy rows from preview_mips to final_preview + for(unsigned y = 0; y < mh; y++) { + memcpy(mp + (y + mipy)*mp_w + mipx, pte->preview_mips[i] + y*mw, mw * sizeof(pxlABGR8888)); + } + + + } + } else { + //Create preview of top mip level + int i = pteTopMipLvl(pte); + for(unsigned y = 0; y < pte->h; y++) { + memcpy(mp + y*mp_w, pte->preview_mips[i] + y*pte->w, pte->w * sizeof(pxlABGR8888)); + } + } + } else { + //Create preview image without mips + pte->final_preview_w = pte->w; + SMART_ALLOC(&pte->final_preview, pte->h * pte->w * sizeof(pxlABGR8888)); + + assert(pte->preview_mips[0]); + memcpy(pte->final_preview, pte->preview_mips[0], pte->w * pte->h * sizeof(pxlABGR8888)); + } + + if (pteIsCompressed(pte)) { + free(src); + } +} + +void pteConvertRawHeightToNormals(PvrTexEncoder *pte) { + assert(pte); + assert(!pte->raw_is_twiddled); + + FOR_EACH_MIP(pte, i) { + assert(pte->raw_mips[i]); + + v3f *norms = malloc(mw * mw * sizeof(*norms)); + + const pxlABGR8888 *src = pte->raw_mips[i]; + + //Calculate normals from height map + for(int y = 0; y < mh; y++) { + for(int x = 0; x < mw; x++) { + //TODO change this so that it copies the image to a larger temp buffer with the correct + //edges, to avoid these per pixel checks and get STBIR_EDGE_ZERO working + + //Wrap around offsets + unsigned l, r, u, d; + if (pte->edge_method == STBIR_EDGE_WRAP) { + l = x == 0 ? mw-1 : x-1; + r = x == (mw-1) ? 0 : x+1; + u = y == 0 ? mh-1 : y-1; + d = y == (mh-1) ? 0 : y+1; + } else if (pte->edge_method == STBIR_EDGE_CLAMP) { + l = x == 0 ? x : x-1; + r = x == (mw-1) ? x : x+1; + u = y == 0 ? y : y-1; + d = y == (mh-1) ? y : y+1; + } else if (pte->edge_method == STBIR_EDGE_REFLECT) { + l = x == 0 ? x+1 : x-1; + r = x == (mw-1) ? x-1 : x+1; + u = y == 0 ? y+1 : y-1; + d = y == (mh-1) ? y-1 : y+1; + } else { + ErrorExit("Zero edge method not supported for height maps"); + } + + unsigned ofs = y*mw+x; + + norms[ofs].x = pxlU8toF(src[y*mw + l].r) - pxlU8toF(src[y*mw + r].r); + norms[ofs].y = pxlU8toF(src[d*mw + x].r) - pxlU8toF(src[u*mw + x].r); + norms[ofs].z = sqrtf(1 - norms[ofs].x*norms[ofs].x + norms[ofs].y*norms[ofs].y); + //Gotten some weirdness on some heightmaps if we normalize here, so it's disabled for now + //~ norms[ofs] = v3NormalizeS(norms[ofs]); + } + } + + //Replace raw_mips with normals + for(int y = 0; y < mh; y++) { + for(int x = 0; x < mw; x++) { + unsigned ofs = y*mw+x; + pte->raw_mips[i][ofs].r = pxlFtoU8B(norms[ofs].x); + pte->raw_mips[i][ofs].g = pxlFtoU8B(norms[ofs].y); + pte->raw_mips[i][ofs].b = pxlFtoU8B(norms[ofs].z); + pte->raw_mips[i][ofs].a = 255; + } + } + + + free(norms); + } +} + +void pteAutoSelectPixelFormat(PvrTexEncoder *pte) { + assert(pte); + assert(pte->src_img_cnt); + + unsigned clearpix = 0, halfpix = 0, opaquepix = 0; + + for(int j = 0; j < pte->src_img_cnt; j++) { + pteImage *img = pte->src_imgs + j; + + for(int i = 0; i < img->w * img->h; i++) { + int a = img->pixels[i].a; + if (a == 0) + clearpix++; + else if (a == 0xff) + opaquepix++; + else + halfpix++; + } + } + + if (halfpix > 0) + pte->pixel_format = PTE_ARGB4444; + else if (clearpix > 0) + pte->pixel_format = PTE_ARGB1555; + else + pte->pixel_format = pte->pixel_format == PTE_AUTO_YUV ? PTE_YUV : PTE_RGB565; + pteLog(LOG_INFO, "Selected pixel format %s\n", ptGetPixelFormatString(pte->pixel_format)); +} + +void pteEncodeTexture(PvrTexEncoder *pte) { + //Generate resized ABGR data from source image + if (pte->want_mips) { + pteLog(LOG_PROGRESS, "Generating mipmaps...\n"); + pteGenerateMips(pte); + } else if (pte->src_img_cnt == 1) { + pteGenerateRawFromSource(pte); + } else { + ErrorExit("Multiple source images have been specified, but mipmaps have not been requested\n"); + } + + //If the source image is a height map, convert it to a normal map + if (pte->pixel_format == PTE_BUMP) { + pteConvertRawHeightToNormals(pte); + pte->pixel_format = PTE_NORMAL; + } + + //Generate palette + if (pte->pixel_format == PTE_PALETTE_4B || pte->pixel_format == PTE_PALETTE_8B) { + if (pte->pixel_format == PTE_PALETTE_8B) { + if (pte->palette_size == 0) { + pte->palette_size = 256; + } else if (pte->palette_size > 256) { + ErrorExit("palette size must be 256 or less for 8bpp textures\n"); + } + } else if (pte->pixel_format == PTE_PALETTE_4B) { + if (pte->palette_size == 0) { + pte->palette_size = 16; + } else if (pte->palette_size > 16) { + ErrorExit("palette size must be 16 or less for 4bpp textures\n"); + } + } + pteLog(LOG_PROGRESS, "Generating palette...\n"); + pteGeneratePalette(pte); + } + + //Do dithering + if (pte->dither && pte->pixel_format != PTE_YUV) { + pteLog(LOG_PROGRESS, "Dithering...\n"); + pteDitherRaws(pte, pte->dither); + } + + //Twiddle if texture is not strided + if (!pte->stride) { + pteLog(LOG_PROGRESS, "Twiddling...\n"); + pteConvertRawToTwiddled(pte); + } else { + //Stride textures cannot be these formats + //Normal maps kill PVR if bilinear is used, and strided palettes can't be set + if (pte->pixel_format == PTE_NORMAL || pte->pixel_format == PTE_PALETTE_4B || pte->pixel_format == PTE_PALETTE_8B) + ErrorExit("Stride textures cannot be normal maps or palettized textures\n"); + } + + //Convert from internal ABGR8888 to final output format + pteCombineABGRData(pte); + if (pteIsCompressed(pte)) { + pteLog(LOG_PROGRESS, "Compressing...\n"); + pteCompress(pte); + pteLog(LOG_PROGRESS, "Compressed...\n"); + + float uncompsize = CalcTextureSize(pte->w, pte->h, PTE_RGB565, pteHasMips(pte), 0, 0); + float compsize = CalcTextureSize(pte->w, pte->h, pte->pixel_format, pteHasMips(pte), 1, pte->codebook_size*8); + pteLog(LOG_INFO, "Compression ratio: %f\n", uncompsize / compsize); + } else { + pteLog(LOG_PROGRESS, "Converting as uncompressed...\n"); + pteGenerateUncompressed(pte); + } +} diff --git a/dreamcast/pvrtex/pvr_texture_encoder.h b/dreamcast/pvrtex/pvr_texture_encoder.h new file mode 100644 index 00000000..e2b76a7b --- /dev/null +++ b/dreamcast/pvrtex/pvr_texture_encoder.h @@ -0,0 +1,226 @@ +#pragma once + +#include +#include + +#include "pixel.h" +#include "pvr_texture.h" +#include "stb_image_resize.h" + +typedef enum { + PTE_MIP_NONE, + PTE_MIP_QUALITY, + PTE_MIP_FAST, +} pteMipGen; + +typedef enum { + PTE_FIX_NONE, + PTE_FIX_UP, + PTE_FIX_DOWN, + PTE_FIX_NEAREST, +} pteFixSizeMethod; +typedef enum { + PTE_FIX_MIP_NONE, //Must be square + PTE_FIX_MIP_NARROW_X2, //If not square, take narrower dimension and double it (512x32->64x64) + PTE_FIX_MIP_NARROW_X4, //If not square, take narrower dimension and quadruple it (512x32->128x128) + PTE_FIX_MIP_MAX, //Take largest dimension (512x32->512x512) + PTE_FIX_MIP_MIN, //Take narrowest dimension (512x32->32x32) +} pteFixMipSizeMethod; + + +typedef uint8_t VQIndex; + +typedef enum { + //The values for following seven formats match up with the values used by the hardware + PTE_ARGB1555, + PTE_RGB565, + PTE_ARGB4444, + PTE_YUV, + PTE_NORMAL, + PTE_PALETTE_4B, + PTE_PALETTE_8B, + + //The following are not real, supported PVR formats, but used internally by some functions + PTE_YUV_TWID = PT_YUV_TWID, + + //The following cannot be used as a ptPixelFormat. They are used by pte* functions only + PTE_ABGR8888, + PTE_BUMP, //Signals input is height map that needs to be converted to normal map + PTE_AUTO, //Selects RGB565, ARGB1555, or ARGB4444 automatically based on alpha of input image + PTE_AUTO_YUV, //Selects YUV, ARGB1555, or ARGB4444 automatically based on alpha of input image +} ptePixelFormat; + +typedef struct { + unsigned w, h; + int channels; + pxlABGR8888 *pixels; +} pteImage; +static inline size_t pteImgPixelCnt(const pteImage *img) { + return img->w * img->h; +} +static inline size_t pteImgSize(const pteImage *img) { + return img->w * img->h * sizeof(pxlABGR8888); +} + +typedef struct PvrTexEncoder { +// +// The following should be set by the user before using the encoder +// + + //If true, generating a nontwiddled stride texture. + //Texture width can 8, 16, or be any multiple of 32 that is less than or equal to 1024 + //If false, texture will be twiddled + bool stride; + + //Method of generating mipmaps, or no mipmaps generated + pteMipGen want_mips; + + //Number of mipmap levels, always 1 if no mipmaps + //Not set to final mipmap count until mipmaps have been generated + unsigned mip_cnt; + + //Color format of texture + ptePixelFormat pixel_format; + + //Size of codebook in indicies, ranges from 0 to 256 + //0 means uncompressed, >0 will result in a VQ compressed texture + unsigned codebook_size; + + //Offset (in entries) into full codebook where the first element is + //If you have a 128 entry codebook, and want to use the first half, set this to zero + //If you want to the last half, set this to 128 + //codebook_size + pvr_idx_offset must be <= 256 + unsigned pvr_idx_offset; + + unsigned perfect_mips; //number of mipmap levels to generate losslessly + bool mip_shift_correction; //preform correction for mipmap shifting + + //mips between top-high_weight and perfect_mips are given extra weight (quality) when compressing, + //0 means no high weight, 1 means all mips below highest have extra priority, + //2 means all mips below second highest are given prio, etc. + unsigned high_weight_mips; + + //Resize method to nearest valid size + pteFixSizeMethod resize; + + //Resize method to make square + pteFixMipSizeMethod mipresize; + + //Amount of dithering, 0 is none, 1 is full + float dither; + + //How to downsample on the edges + stbir_edge edge_method; + + //Generate small codebook size based on texture dimensions + bool auto_small_vq; + + //Unprocessed source images specified by user + unsigned src_img_cnt; + pteImage src_imgs[PVR_MAX_MIPMAPS]; + + float rgb_gamma; + float alpha_gamma; + +// +// Below here is used internally by encoder. User should avoid messing with most of these. +// + //Width and height of texture in pixels (if we have mipmaps, this is largest mip level) + unsigned w, h; + + //If true, raw_mips contains twiddled data, otherwise data is normal, linear + bool raw_is_twiddled; + + unsigned palette_size; //size in colors, ranges from 0 to 256 + pxlABGR8888 *palette; + + //Preview with all mips in one image + //height is h + unsigned final_preview_w; + pxlABGR8888 *final_preview; + + //Codebook in pvr format, uses pixel_format pixel + void *pvr_codebook; + + //PVR texture data, in the same format used by the pvr, including all mipmaps laid out in order, including padding + //If compressed, this is just the indices, and does NOT include the codebook + void *pvr_tex; + + //Uncompressed PVR texture data, as pvr_tex, but in 32-bit color. This is generated first, then pvr_tex is generated from it + pxlABGR8888 *pvr_tex32; + + //For the following three *_mips arrays... + //If mip_cnt > 1, 0 is 1x1, 1 is 2x2, 3 is 4x4... + //If mip_cnt == 1, 0 is only level, and its size is equal to this->w, this->h + + //Uncompressed source, 4-bytes per pixel + pxlABGR8888 *raw_mips[PVR_MAX_MIPMAPS]; + + //Raw PVR data + //If texture is not compresed, this is in pixel_format + //If texture is compressed, these are indicies + uint8_t *pvr_mips[PVR_MAX_MIPMAPS]; + + //Output preview + //What you get after compressing and reducing color depth + pxlABGR8888 *preview_mips[PVR_MAX_MIPMAPS]; +} PvrTexEncoder; + +//For both FOR_EACH, width and height of current mipmap level are in variables mw and mh + +//Go through each level from small to large +#define FOR_EACH_MIP(pte, mipidx) \ + for(int mipidx = 0, mw = pteHasMips(pte) ? 1 : pte->w, mh = pteHasMips(pte) ? 1 : pte->h; mipidx < pte->mip_cnt; mipidx++, mw <<= 1, mh <<= 1) + +//Go through each level from large to small +#define FOR_EACH_MIP_REV(pte, mipidx) \ + for(int mipidx = pte->mip_cnt-1, mw = pte->w, mh = pte->h; mipidx >= 0; mipidx--, mw >>= 1, mh >>= 1) + +static inline unsigned pteTopMipLvl(const PvrTexEncoder *pte) { + return pte->mip_cnt - 1; +} + +static inline bool pteHasMips(const PvrTexEncoder *pte) { + return pte->want_mips != PTE_MIP_NONE; +} +static inline bool pteIsCompressed(const PvrTexEncoder *pte) { + return pte->codebook_size > 0; +} +static inline bool pteIsStrided(const PvrTexEncoder *pte) { + return pte->stride; +} +static inline bool pteIsPalettized(const PvrTexEncoder *pte) { + return pte->pixel_format == PTE_PALETTE_4B || pte->pixel_format == PTE_PALETTE_8B; +} + +void pteInit(PvrTexEncoder *pte); +void pteFree(PvrTexEncoder *pte); +void pteLoadFromFiles(PvrTexEncoder *pte, const char **fnames, unsigned filecnt); +void pteEncodeTexture(PvrTexEncoder *pte); +void pteMakeSquare(PvrTexEncoder *pte); +int pteSetSize(PvrTexEncoder *pte); +void pteSetCompressed(PvrTexEncoder *pte, int codebook_size); +void pteGeneratePreviews(PvrTexEncoder *pte); +void pteAutoSelectPixelFormat(PvrTexEncoder *pte); + +/////////// +void ErrorExitOn(int cond, const char *fmt, ...); +void ErrorExit(const char *fmt, ...) __attribute__((noreturn)); + +typedef enum pteLogLevel { + //When changing these, make sure to update logtypes[] in pteLogLocV + LOG_NONE, //Disables logs, do not log to this type + + LOG_WARNING, //Important warnings or errors + LOG_COMPLETION, //Encode completion + LOG_PROGRESS, //Progress of encoding + LOG_INFO, //Useful info on encoding + + LOG_ALL, //Prints all normal user visible logs, do not log to this type + + LOG_DEBUG, //Debug info. Must be the highest level, used as bounds check in pteLogLocV +} pteLogLevel; + +#define pteLog(level, ...) //pteLogLoc(level, __FILE__, __LINE__, __VA_ARGS__) +extern void pteLogLoc(unsigned level, const char *file, unsigned line, const char *fmt, ...); + diff --git a/dreamcast/pvrtex/readme_unformatted.txt b/dreamcast/pvrtex/readme_unformatted.txt new file mode 100644 index 00000000..1ef9187b --- /dev/null +++ b/dreamcast/pvrtex/readme_unformatted.txt @@ -0,0 +1,319 @@ +pvrtex converts images to Dreamcast PowerVR textures. + +It is designed to work similarly to tvspelsfreak's texconv, so it can be used in place of texconv will minimal changes. It might be helpful to read the readme for texconv for additional information not covered here. In particular, there are explainations of the types of textures supported by the Dreamcast. + +Compared to texconv, pvrtex has the following enhancements: + + * Faster texture compression and palette generation + * Can generate small codebook VQ textures + * No Qt dependency + * Support for compressed stride textures + * Better mipmap generation + * Dithering + * Support for additional output file types (adds .PVR and .DT) + +-------------------------------------------------------------------------- + +Usage Examples: + +pvrtex -i source.png -o texture.dt + Converts a PNG file to a DT file that is twiddled, uncompressed texture, without mipmaps. The format is automatically chosen depending on alpha content of source.png (See description for AUTO texture format in command option listing). + +pvrtex -i source.png -o texture.dt -f argb4444 -d -c 64 -m quality -r -R + Converts a PNG file to a DT file that is twiddled, compressed texture, with mipmaps. The texture will use the ARGB4444 color format, and dithered. If the source image is not already a square power-of-two, it will be resized to be the nearest square power-of-two. The codebook used by the compression will be limited to 64 entries out of the potential 256; this reduces quality, but reduces the size of the texture by 1.5 KB, and improves fillrate. + +pvrtex -i source.png -o texture.dt -f normal -m + Converts a PNG file containing a normal map to a DT file, with mipmaps. + +pvrtex -i source.png -o texture.dt -s + Converts a PNG file to a nontwiddled texture. source.png is not required to be a power-of-two width, and can also be any multiple of 32 that is <= 1024. + +pvrtex -i source.png -o texture.dt -f pal8bpp -C 64 -d -p preview.png + Converts a PNG file to a DT file with 8-bit color. The resulting image will not use more than 64 colors out of the potential 256, and will be dithered. The pallete for the texture will be written to texture.dt.pal. A preview of the resulting texture will be written to preview.png. + +pvrtex -i mip256.png -i mip128.png -i mip64.png -i mip32.png -i mip16.png -o texture.dt -m + Generates a mipmapped texture, using the different input images as user defined mipmap levels instead of automatically generating all of them. If a mipmap level is not defined by the user, it will be generated from a higher level. By default, the higher level will not be the level above, but three levels above; if you want to use the level above, use fast mipmaps (-m fast) instead. + +-------------------------------------------------------------------------- + +Building: + + Run "make". + + :-| + + To generate the proper README with linebreaks, from readme_unformatted.txt, run "make README" or "make all". Requires "fmt". + +-------------------------------------------------------------------------- + +Command Line Options: + +--help, -h + Displays help + +--version, -V + Displays version + +--in [filename], -i [filename] + Input image file. This option is required. + + If multiple input images are specified, they are currently assumed to be different mipmap levels for a single texture. Resize options can not be used for custom mipmaps, so all images must be a square power-of-two. + + Uses stb_image library for reading the image. The supported formats are: + JPEG, PNG, TGA, BMP, PSD, GIF, HDR, PIC, PMN + +--out [filename], -o [filename] + Sets the file name of the converted texture. The extension of this filename controls the file format. + + The supported formats are: + + .PVR + Official PowerVR texture. This is incomplete as was created to help test the output of the converter, by using PC PVR viewing programs to check the resulting texture. There are likely incompatiblities with .PVR handling of official games (for example, pvrtex does not add a GBIX chunk). + .TEX + Format used by tvspelsfreak's texconv. pvrtex will generate certain formats not supported by texconv but representable in the file format (like compressed stride textures). + .DT + New file format used by this program. Supports small codebook VQ, and texture data is aligned to a 32-byte boundry to make DMA easier. + + It's possible to specify no output file if only a preview image is desired. + +--format [type], -f [type] + Sets the pixel format of the resulting texture. + + [type] can be one of the following: + + RGB565 + Best color out of standard formats without sacrificing speed, but can have rainbowing on grayscale images. + ARGB1555 + Allows for fully transparent texels. Better choice for grayscale than RGB565, which can have rainbowing. + ARGB4444 + Allows for alpha gradients, but poorest color depth with noticable banding. + YUV422 / YUV + Better than RGB565 or ARGB1555 for gradients, but bi/trilinear filtering has worse performance. + PAL8BPP + Maximum of 256 colors. Palette is generated as a seperate file. Must be twiddled. If compression, mipmapping, and bi/trilinear are used, a hardware bug causes some texels to be filtered incorrectly on the top left/bottom right corners of a 4x2 block. + PAL4BPP + Maximum of 16 colors. Palette is generated as a seperate file. Must be twiddled. If compression, mipmapping, and bi/trilinear are used, a hardware bug causes some texels to be filtered incorrectly on the top left/bottom right corners of a 4x4 block + BUMPMAP + Generates PVR normal map. Source image is treated as a height map. + NORMAL + Generates PVR normal map. Source image is treated as a DOT3 normal map, with RGB channels corresponding to the normal's XYZ. + AUTO + Selects RGB565, ARGB1555, or ARGB4444 depending on alpha content of input image. If fully opaque, RGB565 is used, all pixels have either fully opaque or fully transparent alpha, ARGB1555 is used, and if some pixels have non-fully opaque or transparent pixels, ARGB4444 is used. + AUTOYUV + Same as AUTO, but usage of RGB565 is replaced with YUV422. + +--preview [filename], -p [filename] + Generates a preview of the resulting texture file. You can see the results of bit depth reduction, dithering, mipmaps, and compression. + + The preview is can be a PNG, JPG, BMP, or TGA file. Preview JPGs are generally not a good choice do to the lack of alpha, and possiblility of compression artifacts. + +--compress [codebook_size / "small"], -c [codebook_size / "small"] + Generates a VQ compressed texture. + + codebook_size is an optional parameter adjusts the size of the codebook generated for the texture. Reducing the codebook_size can improve fillrate, and, with .PVR and .DT files, improve the compression ratio of small textures. By default, a full codebook is used to generate the best quality texture. codebook_size can be a number from 1 to 256, or the string "small". + + For .PVR files, using a number will never reduce the size of the texture, but can improve fillrate. Specifying "small" as the codebook size will reduce the texture size for certain textures smaller than 64x64 without mipmaps, or 32x32 with mipmaps. + + For .TEX files, codebook_size will never reduce the size of the texture, but can still improve performance. + + For .DT files, reducing the codebook_size will reduce the size of the texture. Specifying "small" as the codebook size will select a smaller codebook automatically for textures that are 128x128 or smaller, with a size of 192 for a 128x128 mipmapped texture, down to 10 for an 8x8 non-mipped texture. + +--max-color [colors], -C [colors] + This limits the number of colors used for a PAL8BPP or PAL4BPP format texture. This option could be used to generate an 8BPP texture that only uses 64 colors, and the unused colors could be used for other textures. + +--mipmap ["fast"], -m ["fast"] + With this option, the resulting file will have mipmaps. + + By default, a Mitchell-Netravalli filter will be used on a level 3 steps above. (e.g. 64x64 will be generated from 512x512) + + Adding the parameter "fast" to this option, each mipmap level is generated by downsampling the level above. (e.g. 64x64 level will be generated from 128x128) This speeds up mipmap generation for large textures. + + When generating high quality mipmaps for a resized image, the largest mips will be generated directly from the source image. (i.e. with a 1600x500 source image, converted with "-m -r near -R x2", pvrtex will create a 1024x1024 texture, with mipmap levels 512x512 and 256x256 generated directly from the 1600x500 source, and not the 1024x1024 top most level). + +--no-mip-shift, -S + When generating mipmaps, by default, pvrtex will preform a subpixel adjustment during downsampling to ensure the mipmaps line up correctly. This option will disable this. + +--perfect-mip [levels], -M [levels] + When using mipmaps and compression, small mipmap levels will be loselessly compressed. + + The levels parameter controls how many levels are lossless. 1 means only the 1x1 level will be losslessly compressed, 2 means 1x1 and 2x2 will be loselessly compressed, and so on. + + Generating lossless mipmaps use up VQ codebook slots. These are the total number of codebook entries used for a given number of lossless mipmaps: + + 16-bit 8-bit 4-bit + 1 level (1x1) 1 1 1 + 2 levels (2x2) 2 1 1 + 3 levels (4x4) 6 3 2 + 4 levels (8x8) 22 11 6 + 5 levels (16x16) 86 43 22 + 6 levels (32x32) -- 171 86 + +--high-weight [levels], -H [levels] + When using mipmaps and compression, this increases the weight the compressor gives smaller mipmap levels, to encourage the compressor to generate them at higher quality, at the cost of lower quality higher levels. Not currently supported for 4BPP textures. + + The levels parameter controls how many levels below the largest have extra weight. A value of 1 means every level besides the top has boosted weight, a value of 2 means the two largest levels have normal weight, while every smaller level is boosted. + +--dither [amount], -d [amount] + Enables dithering. Currently, Floyd-Steinberg is used. + + Amount is an optional parameter that adjusts the amount of dithering, and is a decimal value from 0 to 1. 0 will result in no dithering, while 1 results in full dithering. If dithering is enabled but an amount is not specified, full dithering is used. + + This option has no effect on YUV textures, but is valid on all others. + +--stride, -s + Output a non-twiddled texture. This also allows for non-power-of-two sized textures. Width must still 8, 16, or a multiple of 32 less than or equal to 1024. Any height can be used, from 1 to 1024. + + If a texture has a power-of-two dimensions and --stride is used, the resulting texture will be a nontwiddled texture that can be rendered without stride, for formats that support such textures. (.DT and .TEX support this, .PVR does not.) + + Stride textures do not wrap as normal if the width is not a power-of-two, and have worse rendering performance than twiddled textures (especially when filtered or rotated). It is not possible to generate palettized or normal textures with stride. .PVR files cannnot use stride. + + Valid widths for stride texture: + 8, 16, 32, 64, 96, 128, 160, 192, 224, 256, 320, 352, 384, 416, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024 + +--resize [method], -r [method] + Resize a input image that is not a supported PVR texture size to a valid size. + + If the texture is not strided, the texture will be resized to a power-of-two on both dimensions. For stride textures, width will be adjusted to an appropriate stride size, and the height will always be resized to a power-of-two. + + Method controls how the image will be resized. + + NONE + Generates an error if input image is not a valid size. This is the default. + NEAR + Round size up or down to nearest valid size. If resize is enabled, but no method is specified, this is the default. + UP + Round size up to next valid size + DOWN + Round size down to next valid size + + Examples for non-stride textures: + Source size NONE NEAR UP DOWN + 256x256 256x256 256x256 256x256 256x256 + 260x260 Error 256x256 512x512 256x256 + 200x200 Error 256x256 256x256 128x128 + 200x260 Error 256x256 256x512 128x256 + 2000x2000 Error 1024x1024 1024x1024 1024x1024 + 1x1 Error 8x8 8x8 8x8 + +--mip-resize [method], -R [method] + When using mipmaps, resizes nonsquare images to be square. This option does nothing if not using mipmaps or the image is already square (after --resize). This new size calculation occurs after the standard --resize. Source images are only resized once. This option will not resize the image to a power-of-two size if it's not already (use --resize for that). + + Method controls how the image will be resized. + + NONE + Generates an error if input image is not a valid mipmap size. This is the default. + X2 + Doubles the narrower dimension. A 256x32 image will be resized to 64x64. If mip-resize is enabled, but no method is specified, this is the default. + X4 + Quaduples or doubles the narrower dimension. A 256x32 image will be resized to 128x128. + UP + Resizes the narrower dimension to be the same size as the wider. A 256x32 image will be resized to 256x256. + DOWN + Resizes the wider dimension to be the same size as the narrower. A 256x32 image will be resized to 32x32. + + Examples: + + Source size X2 X4 UP DOWN + 256x256 256x256 256x256 256x256 256x256 + 256x128 256x256 256x256 256x256 128x128 + 256x64 128x128 256x256 256x256 64x64 + 256x32 64x64 128x128 256x256 32x32 + 1024x8 16x16 32x32 1024x1024 8x8 + +--edge [type], -e [type] + Controls how the edges of the image are handled when resizing. This also affects height map to normal map conversion. + + Valid options: + + CLAMP + Samples are clamped to edge of image, default if not mipmapped. + WRAP + Samples wrap around to other side of image, default if mipmaps are used. Is works well when the texture is used to that it repeats, but might cause noticble bleeding around the edge of the texture in certain situations. For example, a poster or sign that doesn't repeat across the polygon. In that cause, CLAMP should be used. + REFLECT + Samples reflect off edge of image back into valid area. If you use are planning on using UV mirroring instead of wrapping, use this instead of wrap. + ZERO + Outside of image is treated as transparent blackness. This is not currently supported for images with a --type of BUMPMAP. + + The default is CLAMP if not using mipmaps, or WRAP if mipmaps are used. + +--bilinear, -b + In texconv, this was used to generate mipmaps with a box filter. This option is ignored in pvrtex, which currently always uses a Mitchell-Netravalli filter. + +--nearest, -n + In texconv, this was used to generate mipmaps by point sampling. This option is not supported in pvrtex, and will cause pvrtex to abort. + +--vqcodeusage (Not supported) + This option from texconv is not recognized at all by pvrtex. + +-------------------------------------------------------------------------- + +.DT File Format + + See file_dctex.h for documentation. file_dctex.h can also be used as a library to help access information from the file's header. + +-------------------------------------------------------------------------- + +Known bugs + + High weight compressed mips (--high-weight) does not currently work with 4BPP textures; the parameter will be ignored if specified. + + There is a weird pathological performance issue with compression on certain textures. With a 1024x1024 texture with mips that is ff000000 on the left side, and ffffffff on the right side, performance drops by around 15x. It is apparently cache missing constantly, according to cachegrind, in a function that reads memory linearly (distance_limited in elbg.c). Doesn't make sense. + +-------------------------------------------------------------------------- + +Future Ideas + + * Code clean up + + * Add Yliluoma dithering + + * Improve error checking + + * It might be possible to improve VQ quality by compressing 4/5/6 bit color instead of 8 bit (so the compressor won't waste codebook space on colors that are too similar to distinguish at given bit depth) + + * Speed up compression by not processing alpha for opaque textures + + * Add ability to generate a single palette to be shared for multiple textures + + * Allow specifying palette format + + * Allow specifiying custom external palette for texture + + * Add ability to generate animated VQ textures with shared codebook for all frames + + * Auto generate output name (e.g. -i image.png -o $.dt will output image.dt) + + * Add wildcard input files (e.g. -i *.png) + + * Add VQ index dithering + + * Add KIMG output support + + * Allow texture formats (PVR/TEX/DT/KMG) to be used as input formats, to transcode or recompress textures + + * Add per-axis edge sampling control + + * Rework code so that this can be used as a library + + * Allow sizing to arbitary size (i.e. --resize 256x256) + + * Add a filter to try to hide the palettized compressed mipmap bug + +-------------------------------------------------------------------------- + +History: + + Version 1.01 + Program now displays error message when an error occurs loading a source image. Previously, the program would hit an assertion. + + Fixed "--resize down" option. Previously, the program would round down sizes that were already a power-of-two, now sizes that are already POT are left unchanged. + + Included a missing FFmpeg header file. + + Version 1.0 + Initial release + +-------------------------------------------------------------------------- + +License: + This uses code from FFmpeg, which is LGPL, so this project is also LGPL. Files not originating from FFmpeg can also be used as public domain code/BSD/MIT/whatever. diff --git a/dreamcast/pvrtex/stb_image.h b/dreamcast/pvrtex/stb_image.h new file mode 100644 index 00000000..d60371b9 --- /dev/null +++ b/dreamcast/pvrtex/stb_image.h @@ -0,0 +1,7897 @@ +/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + return -1; /* report error for unexpected end of data. */ + } + stbi__fill_bits(a); + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/dreamcast/pvrtex/stb_image_impl.c b/dreamcast/pvrtex/stb_image_impl.c new file mode 100644 index 00000000..6e5aa1cf --- /dev/null +++ b/dreamcast/pvrtex/stb_image_impl.c @@ -0,0 +1,3 @@ +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + diff --git a/dreamcast/pvrtex/stb_image_resize.h b/dreamcast/pvrtex/stb_image_resize.h new file mode 100644 index 00000000..ef9e6fe8 --- /dev/null +++ b/dreamcast/pvrtex/stb_image_resize.h @@ -0,0 +1,2634 @@ +/* stb_image_resize - v0.97 - public domain image resizing + by Jorge L Rodriguez (@VinoBS) - 2014 + http://github.com/nothings/stb + + Written with emphasis on usability, portability, and efficiency. (No + SIMD or threads, so it be easily outperformed by libs that use those.) + Only scaling and translation is supported, no rotations or shears. + Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation. + + COMPILING & LINKING + In one C/C++ file that #includes this file, do this: + #define STB_IMAGE_RESIZE_IMPLEMENTATION + before the #include. That will create the implementation in that file. + + QUICKSTART + stbir_resize_uint8( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, num_channels) + stbir_resize_float(...) + stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0) + stbir_resize_uint8_srgb_edgemode( + input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP) + // WRAP/REFLECT/ZERO + + FULL API + See the "header file" section of the source for API documentation. + + ADDITIONAL DOCUMENTATION + + SRGB & FLOATING POINT REPRESENTATION + The sRGB functions presume IEEE floating point. If you do not have + IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use + a slower implementation. + + MEMORY ALLOCATION + The resize functions here perform a single memory allocation using + malloc. To control the memory allocation, before the #include that + triggers the implementation, do: + + #define STBIR_MALLOC(size,context) ... + #define STBIR_FREE(ptr,context) ... + + Each resize function makes exactly one call to malloc/free, so to use + temp memory, store the temp memory in the context and return that. + + ASSERT + Define STBIR_ASSERT(boolval) to override assert() and not use assert.h + + OPTIMIZATION + Define STBIR_SATURATE_INT to compute clamp values in-range using + integer operations instead of float operations. This may be faster + on some platforms. + + DEFAULT FILTERS + For functions which don't provide explicit control over what filters + to use, you can change the compile-time defaults with + + #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something + #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something + + See stbir_filter in the header-file section for the list of filters. + + NEW FILTERS + A number of 1D filter kernels are used. For a list of + supported filters see the stbir_filter enum. To add a new filter, + write a filter function and add it to stbir__filter_info_table. + + PROGRESS + For interactive use with slow resize operations, you can install + a progress-report callback: + + #define STBIR_PROGRESS_REPORT(val) some_func(val) + + The parameter val is a float which goes from 0 to 1 as progress is made. + + For example: + + static void my_progress_report(float progress); + #define STBIR_PROGRESS_REPORT(val) my_progress_report(val) + + #define STB_IMAGE_RESIZE_IMPLEMENTATION + #include "stb_image_resize.h" + + static void my_progress_report(float progress) + { + printf("Progress: %f%%\n", progress*100); + } + + MAX CHANNELS + If your image has more than 64 channels, define STBIR_MAX_CHANNELS + to the max you'll have. + + ALPHA CHANNEL + Most of the resizing functions provide the ability to control how + the alpha channel of an image is processed. The important things + to know about this: + + 1. The best mathematically-behaved version of alpha to use is + called "premultiplied alpha", in which the other color channels + have had the alpha value multiplied in. If you use premultiplied + alpha, linear filtering (such as image resampling done by this + library, or performed in texture units on GPUs) does the "right + thing". While premultiplied alpha is standard in the movie CGI + industry, it is still uncommon in the videogame/real-time world. + + If you linearly filter non-premultiplied alpha, strange effects + occur. (For example, the 50/50 average of 99% transparent bright green + and 1% transparent black produces 50% transparent dark green when + non-premultiplied, whereas premultiplied it produces 50% + transparent near-black. The former introduces green energy + that doesn't exist in the source image.) + + 2. Artists should not edit premultiplied-alpha images; artists + want non-premultiplied alpha images. Thus, art tools generally output + non-premultiplied alpha images. + + 3. You will get best results in most cases by converting images + to premultiplied alpha before processing them mathematically. + + 4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the + resizer does not do anything special for the alpha channel; + it is resampled identically to other channels. This produces + the correct results for premultiplied-alpha images, but produces + less-than-ideal results for non-premultiplied-alpha images. + + 5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, + then the resizer weights the contribution of input pixels + based on their alpha values, or, equivalently, it multiplies + the alpha value into the color channels, resamples, then divides + by the resultant alpha value. Input pixels which have alpha=0 do + not contribute at all to output pixels unless _all_ of the input + pixels affecting that output pixel have alpha=0, in which case + the result for that pixel is the same as it would be without + STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for + input images in integer formats. For input images in float format, + input pixels with alpha=0 have no effect, and output pixels + which have alpha=0 will be 0 in all channels. (For float images, + you can manually achieve the same result by adding a tiny epsilon + value to the alpha channel of every image, and then subtracting + or clamping it at the end.) + + 6. You can suppress the behavior described in #5 and make + all-0-alpha pixels have 0 in all channels by #defining + STBIR_NO_ALPHA_EPSILON. + + 7. You can separately control whether the alpha channel is + interpreted as linear or affected by the colorspace. By default + it is linear; you almost never want to apply the colorspace. + (For example, graphics hardware does not apply sRGB conversion + to the alpha channel.) + + CONTRIBUTORS + Jorge L Rodriguez: Implementation + Sean Barrett: API design, optimizations + Aras Pranckevicius: bugfix + Nathan Reed: warning fixes + + REVISIONS + 0.97 (2020-02-02) fixed warning + 0.96 (2019-03-04) fixed warnings + 0.95 (2017-07-23) fixed warnings + 0.94 (2017-03-18) fixed warnings + 0.93 (2017-03-03) fixed bug with certain combinations of heights + 0.92 (2017-01-02) fix integer overflow on large (>2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. + + TODO + Don't decode all of the image data when only processing a partial tile + Don't use full-width decode buffers when only processing a partial tile + When processing wide images, break processing into tiles so data fits in L1 cache + Installable filters? + Resize that respects alpha test coverage + (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage: + https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp ) +*/ + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H + +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4) +// * input_w is input image width (x-axis), input_h is input image height (y-axis) +// * stride is the offset between successive rows of image data in memory, in bytes. you can +// specify 0 to mean packed continuously in memory +// * alpha channel is treated identically to other channels. +// * colorspace is linear or sRGB as specified by function name +// * returned result is 1 for success or 0 in case of an error. +// #define STBIR_ASSERT() to trigger an assert on parameter validation errors. +// * Memory required grows approximately linearly with input and output size, but with +// discontinuities at input_w == output_w and input_h == output_h. +// * These functions use a "default" resampling filter defined at compile time. To change the filter, +// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE +// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API. + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + + +// The following functions interpret image data as gamma-corrected sRGB. +// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel, +// or otherwise provide the index of the alpha channel. Flags value +// of 0 will probably do the right thing if you're not sure what +// the flags mean. + +#define STBIR_ALPHA_CHANNEL_NONE -1 + +// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will +// use alpha-weighted resampling (effectively premultiplying, resampling, +// then unpremultiplying). +#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0) +// The specified alpha channel should be handled as gamma-corrected value even +// when doing sRGB operations. +#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1) + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags); + + +typedef enum +{ + STBIR_EDGE_CLAMP = 1, + STBIR_EDGE_REFLECT = 2, + STBIR_EDGE_WRAP = 3, + STBIR_EDGE_ZERO = 4, +} stbir_edge; + +// This function adds the ability to specify how requests to sample off the edge of the image are handled. +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode); + +////////////////////////////////////////////////////////////////////////////// +// +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Alpha-channel can be processed separately +// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE +// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT) +// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED) +// * Filter can be selected explicitly +// * uint16 image type +// * sRGB colorspace available for all types +// * context parameter for passing to STBIR_MALLOC + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 +} stbir_filter; + +typedef enum +{ + STBIR_COLORSPACE_LINEAR, + STBIR_COLORSPACE_SRGB, + + STBIR_MAX_COLORSPACES, +} stbir_colorspace; + +// The following functions are all identical except for the type of the image data + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + + + +////////////////////////////////////////////////////////////////////////////// +// +// Full-complexity API +// +// This extends the medium API as follows: +// +// * uint32 image type +// * not typesafe +// * separate filter types for each axis +// * separate edge modes for each axis +// * can specify scale explicitly for subpixel correctness +// * can specify image source tile using texture coordinates + +typedef enum +{ + STBIR_TYPE_UINT8 , + STBIR_TYPE_UINT16, + STBIR_TYPE_UINT32, + STBIR_TYPE_FLOAT , + + STBIR_MAX_TYPES +} stbir_datatype; + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context); + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset); + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1); +// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use. + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H + + + + + +#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +// For memset +#include + +#include + +#ifndef STBIR_MALLOC +#include +// use comma operator to evaluate c, to avoid "unused parameter" warnings +#define STBIR_MALLOC(size,c) ((void)(c), malloc(size)) +#define STBIR_FREE(ptr,c) ((void)(c), free(ptr)) +#endif + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbir__inline inline +#else +#define stbir__inline +#endif +#else +#define stbir__inline __forceinline +#endif + + +// should produce compiler error if size is wrong +typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBIR__NOTUSED(v) (void)(v) +#else +#define STBIR__NOTUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + +#ifndef STBIR_PROGRESS_REPORT +#define STBIR_PROGRESS_REPORT(float_0_to_1) +#endif + +#ifndef STBIR_MAX_CHANNELS +#define STBIR_MAX_CHANNELS 64 +#endif + +#if STBIR_MAX_CHANNELS > 65536 +#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536." +// because we store the indices in 16-bit variables +#endif + +// This value is added to alpha just before premultiplication to avoid +// zeroing out color values. It is equivalent to 2^-80. If you don't want +// that behavior (it may interfere if you have floating point images with +// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to +// disable it. +#ifndef STBIR_ALPHA_EPSILON +#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) +#endif + + + +#ifdef _MSC_VER +#define STBIR__UNUSED_PARAM(v) (void)(v) +#else +#define STBIR__UNUSED_PARAM(v) (void)sizeof(v) +#endif + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1, // STBIR_TYPE_UINT8 + 2, // STBIR_TYPE_UINT16 + 4, // STBIR_TYPE_UINT32 + 4, // STBIR_TYPE_FLOAT +}; + +// Kernel function centered at 0 +typedef float (stbir__kernel_fn)(float x, float scale); +typedef float (stbir__support_fn)(float scale); + +typedef struct +{ + stbir__kernel_fn* kernel; + stbir__support_fn* support; +} stbir__filter_info; + +// When upsampling, the contributors are which source pixels contribute. +// When downsampling, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + const void* input_data; + int input_w; + int input_h; + int input_stride_bytes; + + void* output_data; + int output_w; + int output_h; + int output_stride_bytes; + + float s0, t0, s1, t1; + + float horizontal_shift; // Units: output pixels + float vertical_shift; // Units: output pixels + float horizontal_scale; + float vertical_scale; + + int channels; + int alpha_channel; + stbir_uint32 flags; + stbir_datatype type; + stbir_filter horizontal_filter; + stbir_filter vertical_filter; + stbir_edge edge_horizontal; + stbir_edge edge_vertical; + stbir_colorspace colorspace; + + stbir__contributors* horizontal_contributors; + float* horizontal_coefficients; + + stbir__contributors* vertical_contributors; + float* vertical_coefficients; + + int decode_buffer_pixels; + float* decode_buffer; + + float* horizontal_buffer; + + // cache these because ceil/floor are inexplicably showing up in profile + int horizontal_coefficient_width; + int vertical_coefficient_width; + int horizontal_filter_pixel_width; + int vertical_filter_pixel_width; + int horizontal_filter_pixel_margin; + int vertical_filter_pixel_margin; + int horizontal_num_contributors; + int vertical_num_contributors; + + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + float* ring_buffer; + + float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds. + + int horizontal_contributors_size; + int horizontal_coefficients_size; + int vertical_contributors_size; + int vertical_coefficients_size; + int decode_buffer_size; + int horizontal_buffer_size; + int ring_buffer_size; + int encode_buffer_size; +} stbir__info; + + +static const float stbir__max_uint8_as_float = 255.0f; +static const float stbir__max_uint16_as_float = 65535.0f; +static const double stbir__max_uint32_as_float = 4294967295.0; + + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline float stbir__saturate(float x) +{ + if (x < 0) + return 0; + + if (x > 1) + return 1; + + return x; +} + +#ifdef STBIR_SATURATE_INT +static stbir__inline stbir_uint8 stbir__saturate8(int x) +{ + if ((unsigned int) x <= 255) + return x; + + if (x < 0) + return 0; + + return 255; +} + +static stbir__inline stbir_uint16 stbir__saturate16(int x) +{ + if ((unsigned int) x <= 65535) + return x; + + if (x < 0) + return 0; + + return 65535; +} +#endif + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +static float stbir__srgb_to_linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return (float)pow((f + 0.055f) / 1.055f, 2.4f); +} + +static float stbir__linear_to_srgb(float f) +{ + if (f <= 0.0031308f) + return f * 12.92f; + else + return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f; +} + +#ifndef STBIR_NON_IEEE_FLOAT +// From https://gist.github.com/rygorous/2203834 + +typedef union +{ + stbir_uint32 u; + float f; +} stbir__FP32; + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + in = minval.f; + if (in > almostone.f) + in = almostone.f; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#else +// sRGB transition values, scaled by 1<<28 +static int stbir__srgb_offset_to_linear_scaled[256] = +{ + 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603, + 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926, + 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148, + 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856, + 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731, + 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369, + 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021, + 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073, + 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389, + 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552, + 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066, + 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490, + 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568, + 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316, + 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096, + 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700, + 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376, + 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912, + 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648, + 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512, + 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072, + 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544, + 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832, + 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528, + 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968, + 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184, + 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992, + 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968, + 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480, + 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656, + 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464, + 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float f) +{ + int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp + int v = 0; + int i; + + // Refine the guess with a short binary search. + i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + + return (stbir_uint8) v; +} +#endif + +static float stbir__filter_trapezoid(float x, float scale) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + + x = (float)fabs(x); + + if (x >= t) + return 0; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale) +{ + STBIR_ASSERT(scale <= 1); + return 0.5f + scale / 2; +} + +static float stbir__filter_triangle(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x <= 1.0f) + return 1 - x; + else + return 0; +} + +static float stbir__filter_cubic(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (4 + x*x*(3*x - 6))/6; + else if (x < 2.0f) + return (8 + x*(-12 + x*(6 - x)))/6; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return 1 - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2 - x*(4 + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (16 + x*x*(21 * x - 36))/18; + else if (x < 2.0f) + return (32 + x*(-60 + x*(36 - 7*x)))/18; + + return (0.0f); +} + +static float stbir__support_zero(float s) +{ + STBIR__UNUSED_PARAM(s); + return 0; +} + +static float stbir__support_one(float s) +{ + STBIR__UNUSED_PARAM(s); + return 1; +} + +static float stbir__support_two(float s) +{ + STBIR__UNUSED_PARAM(s); + return 2; +} + +static stbir__filter_info stbir__filter_info_table[] = { + { NULL, stbir__support_zero }, + { stbir__filter_trapezoid, stbir__support_trapezoid }, + { stbir__filter_triangle, stbir__support_one }, + { stbir__filter_cubic, stbir__support_two }, + { stbir__filter_catmullrom, stbir__support_two }, + { stbir__filter_mitchell, stbir__support_two }, +}; + +stbir__inline static int stbir__use_upsampling(float ratio) +{ + return ratio > 1; +} + +stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->horizontal_scale); +} + +stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->vertical_scale); +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter +static int stbir__get_filter_pixel_width(stbir_filter filter, float scale) +{ + STBIR_ASSERT(filter != 0); + STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale); +} + +// This is how much to expand buffers to account for filters seeking outside +// the image boundaries. +static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale) +{ + return stbir__get_filter_pixel_width(filter, scale) / 2; +} + +static int stbir__get_coefficient_width(stbir_filter filter, float scale) +{ + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2); +} + +static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size) +{ + if (stbir__use_upsampling(scale)) + return output_size; + else + return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2); +} + +static int stbir__get_total_horizontal_coefficients(stbir__info* info) +{ + return info->horizontal_num_contributors + * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); +} + +static int stbir__get_total_vertical_coefficients(stbir__info* info) +{ + return info->vertical_num_contributors + * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale); +} + +static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n) +{ + return &contributors[n]; +} + +// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample, +// if you change it here change it there too. +static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c) +{ + int width = stbir__get_coefficient_width(filter, scale); + return &coefficients[width*n + c]; +} + +static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max) +{ + switch (edge) + { + case STBIR_EDGE_ZERO: + return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later + + case STBIR_EDGE_CLAMP: + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED + + case STBIR_EDGE_REFLECT: + { + if (n < 0) + { + if (n < max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED + } + + case STBIR_EDGE_WRAP: + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } + // NOTREACHED + + default: + STBIR_ASSERT(!"Unimplemented edge type"); + return 0; + } +} + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow(edge, n, max); +} + +// What input pixels contribute to this output pixel? +static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out) +{ + float out_pixel_center = (float)n + 0.5f; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio; + + *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio; + *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5)); + *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5)); +} + +// What output pixels does this input pixel contribute to? +static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in) +{ + float in_pixel_center = (float)n + 0.5f; + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift; + + *out_center_of_in = in_pixel_center * scale_ratio - out_shift; + *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5)); + *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5)); +} + +static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + float total_filter = 0; + float filter_scale; + + STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = in_first_pixel; + contributor->n1 = in_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale); + + // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.) + if (i == 0 && !coefficient_group[i]) + { + contributor->n0 = ++in_first_pixel; + i--; + continue; + } + + total_filter += coefficient_group[i]; + } + + // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be. + // It would be true in exact math but is at best approximately true in floating-point math, + // and it would not make sense to try and put actual bounds on this here because it depends + // on the image aspect ratio which can get pretty extreme. + //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0); + + STBIR_ASSERT(total_filter > 0.9); + STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off. + + // Make sure the sum of all coefficients is 1. + filter_scale = 1 / total_filter; + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + coefficient_group[i] *= filter_scale; + + for (i = in_last_pixel - in_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + + STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = out_first_pixel; + contributor->n1 = out_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio; + } + + // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be. + // It would be true in exact math but is at best approximately true in floating-point math, + // and it would not make sense to try and put actual bounds on this here because it depends + // on the image aspect ratio which can get pretty extreme. + //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0); + + for (i = out_last_pixel - out_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size) +{ + int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio); + int i, j; + int skip; + + for (i = 0; i < output_size; i++) + { + float scale; + float total = 0; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + { + float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0); + total += coefficient; + } + else if (i < contributors[j].n0) + break; + } + + STBIR_ASSERT(total > 0.9f); + STBIR_ASSERT(total < 1.1f); + + scale = 1 / total; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale; + else if (i < contributors[j].n0) + break; + } + } + + // Optimize: Skip zero coefficients and contributions outside of image bounds. + // Do this after normalizing because normalization depends on the n0/n1 values. + for (j = 0; j < num_contributors; j++) + { + int range, max, width; + + skip = 0; + while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0) + skip++; + + contributors[j].n0 += skip; + + while (contributors[j].n0 < 0) + { + contributors[j].n0++; + skip++; + } + + range = contributors[j].n1 - contributors[j].n0 + 1; + max = stbir__min(num_coefficients, range); + + width = stbir__get_coefficient_width(filter, scale_ratio); + for (i = 0; i < max; i++) + { + if (i + skip >= width) + break; + + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip); + } + + continue; + } + + // Using min to avoid writing into invalid pixels. + for (i = 0; i < num_contributors; i++) + contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1); +} + +// Each scan line uses the same kernel values so we should calculate the kernel +// values once and then we can use them for every scan line. +static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size) +{ + int n; + int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + + if (stbir__use_upsampling(scale_ratio)) + { + float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio; + + // Looping through out pixels + for (n = 0; n < total_contributors; n++) + { + float in_center_of_out; // Center of the current out pixel in the in pixel space + int in_first_pixel, in_last_pixel; + + stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out); + + stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + } + else + { + float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio; + + // Looping through in pixels + for (n = 0; n < total_contributors; n++) + { + float out_center_of_in; // Center of the current out pixel in the in pixel space + int out_first_pixel, out_last_pixel; + int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio); + + stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in); + + stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + + stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size); + } +} + +static float* stbir__get_decode_buffer(stbir__info* stbir_info) +{ + // The 0 index of the decode buffer starts after the margin. This makes + // it okay to use negative indexes on the decode buffer. + return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels]; +} + +#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace)) + +static void stbir__decode_scanline(stbir__info* stbir_info, int n) +{ + int c; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int input_w = stbir_info->input_w; + size_t input_stride_bytes = stbir_info->input_stride_bytes; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir_edge edge_horizontal = stbir_info->edge_horizontal; + stbir_edge edge_vertical = stbir_info->edge_vertical; + size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes; + const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset; + int max_x = input_w + stbir_info->horizontal_filter_pixel_margin; + int decode = STBIR__DECODE(type, colorspace); + + int x = -stbir_info->horizontal_filter_pixel_margin; + + // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input, + // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO + if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h)) + { + for (; x < max_x; x++) + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + return; + } + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float)); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c]; + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel]; + } + + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++) + { + int decode_pixel_index = x * channels; + + // If the alpha value is 0 it will clobber the color values. Make sure it's not. + float alpha = decode_buffer[decode_pixel_index + alpha_channel]; +#ifndef STBIR_NO_ALPHA_EPSILON + if (stbir_info->type != STBIR_TYPE_FLOAT) { + alpha += STBIR_ALPHA_EPSILON; + decode_buffer[decode_pixel_index + alpha_channel] = alpha; + } +#endif + for (c = 0; c < channels; c++) + { + if (c == alpha_channel) + continue; + + decode_buffer[decode_pixel_index + c] *= alpha; + } + } + } + + if (edge_horizontal == STBIR_EDGE_ZERO) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + for (x = input_w; x < max_x; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + } +} + +static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length) +{ + return &ring_buffer[index * ring_buffer_length]; +} + +static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + stbir_info->ring_buffer_last_scanline = n; + + if (stbir_info->ring_buffer_begin_index < 0) + { + ring_buffer_index = stbir_info->ring_buffer_begin_index = 0; + stbir_info->ring_buffer_first_scanline = n; + } + else + { + ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index); + } + + ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float)); + memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes); + + return ring_buffer; +} + + +static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int output_w = stbir_info->output_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + + for (x = 0; x < output_w; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int out_pixel_index = x * channels; + int coefficient_group = coefficient_width * x; + int coefficient_counter = 0; + + STBIR_ASSERT(n1 >= n0); + STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + int c; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int input_w = stbir_info->input_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin; + int max_x = input_w + filter_pixel_margin * 2; + + STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info)); + + switch (channels) { + case 1: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 1; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + } + break; + + case 2: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 2; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + } + break; + + case 3: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 3; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + } + break; + + case 4: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 4; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + } + break; + + default: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * channels; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int c; + int out_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + } + break; + } +} + +static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + // Now resample it into the ring buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + else + stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float)); + + // Now resample it into the horizontal buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer); + else + stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer); + + // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers. +} + +// Get the specified scan line from the ring buffer. +static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length) +{ + int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries; + return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length); +} + + +static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode) +{ + int x; + int n; + int num_nonalpha; + stbir_uint16 nonalpha[STBIR_MAX_CHANNELS]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + float alpha = encode_buffer[pixel_index + alpha_channel]; + float reciprocal_alpha = alpha ? 1.0f / alpha : 0; + + // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb + for (n = 0; n < channels; n++) + if (n != alpha_channel) + encode_buffer[pixel_index + n] *= reciprocal_alpha; + + // We added in a small epsilon to prevent the color channel from being deleted with zero alpha. + // Because we only add it for integer types, it will automatically be discarded on integer + // conversion, so we don't need to subtract it back out (which would be problematic for + // numeric precision reasons). + } + } + + // build a table of all channels that need colorspace correction, so + // we don't perform colorspace correction on channels that don't need it. + for (x = 0, num_nonalpha = 0; x < channels; ++x) + { + if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + { + nonalpha[num_nonalpha++] = (stbir_uint16)x; + } + } + + #define STBIR__ROUND_INT(f) ((int) ((f)+0.5)) + #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5)) + + #ifdef STBIR__SATURATE_INT + #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float )) + #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float)) + #else + #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float ) + #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float) + #endif + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]); + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]); + } + + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((float*)output_buffer)[index] = encode_buffer[index]; + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel]; + } + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } +} + +static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + void* output_data = stbir_info->output_data; + float* encode_buffer = stbir_info->encode_buffer; + int decode = STBIR__DECODE(type, colorspace); + int coefficient_width = stbir_info->vertical_coefficient_width; + int coefficient_counter; + int contributor = n; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + int n0,n1, output_row_start; + int coefficient_group = coefficient_width * contributor; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + output_row_start = n * stbir_info->output_stride_bytes; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + memset(encode_buffer, 0, output_w * sizeof(float) * channels); + + // I tried reblocking this for better cache usage of encode_buffer + // (using x_outer, k, x_inner), but it lost speed. -- stb + + coefficient_counter = 0; + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 1; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + } + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 2; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + } + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 3; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + } + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 4; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient; + } + } + break; + default: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * channels; + int c; + for (c = 0; c < channels; c++) + encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient; + } + } + break; + } + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode); +} + +static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + float* horizontal_buffer = stbir_info->horizontal_buffer; + int coefficient_width = stbir_info->vertical_coefficient_width; + int contributor = n + stbir_info->vertical_filter_pixel_margin; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + int n0,n1; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (k = n0; k <= n1; k++) + { + int coefficient_index = k - n0; + int coefficient_group = coefficient_width * contributor; + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + + switch (channels) { + case 1: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 1; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 2; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 3; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 4; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * channels; + + int c; + for (c = 0; c < channels; c++) + ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__buffer_loop_upsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + for (y = 0; y < stbir_info->output_h; y++) + { + float in_center_of_out = 0; // Center of the current out scanline in the in scanline space + int in_first_scanline = 0, in_last_scanline = 0; + + stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out); + + STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (in_first_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__decode_and_resample_upsample(stbir_info, in_first_scanline); + + while (in_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now all buffers should be ready to write a row of vertical sampling. + stbir__resample_vertical_upsample(stbir_info, y); + + STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h); + } +} + +static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline) +{ + int output_stride_bytes = stbir_info->output_stride_bytes; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int output_w = stbir_info->output_w; + void* output_data = stbir_info->output_data; + int decode = STBIR__DECODE(type, colorspace); + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h) + { + int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes; + float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length); + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode); + STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h); + } + + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } +} + +static void stbir__buffer_loop_downsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + int output_h = stbir_info->output_h; + float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio; + int pixel_margin = stbir_info->vertical_filter_pixel_margin; + int max_y = stbir_info->input_h + pixel_margin; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (y = -pixel_margin; y < max_y; y++) + { + float out_center_of_in; // Center of the current out scanline in the in scanline space + int out_first_scanline, out_last_scanline; + + stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in); + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (out_last_scanline < 0 || out_first_scanline >= output_h) + continue; + + stbir__empty_ring_buffer(stbir_info, out_first_scanline); + + stbir__decode_and_resample_downsample(stbir_info, y); + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline); + + while (out_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now the horizontal buffer is ready to write to all ring buffer rows. + stbir__resample_vertical_downsample(stbir_info, y); + } + + stbir__empty_ring_buffer(stbir_info, stbir_info->output_h); +} + +static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels) +{ + info->input_w = input_w; + info->input_h = input_h; + info->output_w = output_w; + info->output_h = output_h; + info->channels = channels; +} + +static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform) +{ + info->s0 = s0; + info->t0 = t0; + info->s1 = s1; + info->t1 = t1; + + if (transform) + { + info->horizontal_scale = transform[0]; + info->vertical_scale = transform[1]; + info->horizontal_shift = transform[2]; + info->vertical_shift = transform[3]; + } + else + { + info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0); + info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0); + + info->horizontal_shift = s0 * info->output_w / (s1 - s0); + info->vertical_shift = t0 * info->output_h / (t1 - t0); + } +} + +static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter) +{ + if (h_filter == 0) + h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + if (v_filter == 0) + v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + info->horizontal_filter = h_filter; + info->vertical_filter = v_filter; +} + +static stbir_uint32 stbir__calculate_memory(stbir__info *info) +{ + int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale); + + info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w); + info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h); + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + info->ring_buffer_num_entries = filter_height + 1; + + info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors); + info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float); + info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors); + info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float); + info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float); + info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float); + info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float); + info->encode_buffer_size = info->output_w * info->channels * sizeof(float); + + STBIR_ASSERT(info->horizontal_filter != 0); + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + STBIR_ASSERT(info->vertical_filter != 0); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + + if (stbir__use_height_upsampling(info)) + // The horizontal buffer is for when we're downsampling the height and we + // can't output the result of sampling the decode buffer directly into the + // ring buffers. + info->horizontal_buffer_size = 0; + else + // The encode buffer is to retain precision in the height upsampling method + // and isn't used when height downsampling. + info->encode_buffer_size = 0; + + return info->horizontal_contributors_size + info->horizontal_coefficients_size + + info->vertical_contributors_size + info->vertical_coefficients_size + + info->decode_buffer_size + info->horizontal_buffer_size + + info->ring_buffer_size + info->encode_buffer_size; +} + +static int stbir__resize_allocated(stbir__info *info, + const void* input_data, int input_stride_in_bytes, + void* output_data, int output_stride_in_bytes, + int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace, + void* tempmem, size_t tempmem_size_in_bytes) +{ + size_t memory_required = stbir__calculate_memory(info); + + int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type]; + int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type]; + +#ifdef STBIR_DEBUG_OVERWRITE_TEST +#define OVERWRITE_ARRAY_SIZE 8 + unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE]; + + size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type]; + memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE); +#endif + + STBIR_ASSERT(info->channels >= 0); + STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS); + + if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS) + return 0; + + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + + if (alpha_channel < 0) + flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED; + + if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) { + STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels); + } + + if (alpha_channel >= info->channels) + return 0; + + STBIR_ASSERT(tempmem); + + if (!tempmem) + return 0; + + STBIR_ASSERT(tempmem_size_in_bytes >= memory_required); + + if (tempmem_size_in_bytes < memory_required) + return 0; + + memset(tempmem, 0, tempmem_size_in_bytes); + + info->input_data = input_data; + info->input_stride_bytes = width_stride_input; + + info->output_data = output_data; + info->output_stride_bytes = width_stride_output; + + info->alpha_channel = alpha_channel; + info->flags = flags; + info->type = type; + info->edge_horizontal = edge_horizontal; + info->edge_vertical = edge_vertical; + info->colorspace = colorspace; + + info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale ); + + info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float); + info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2; + +#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size) + + info->horizontal_contributors = (stbir__contributors *) tempmem; + info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float); + info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors); + info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float); + info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float); + + if (stbir__use_height_upsampling(info)) + { + info->horizontal_buffer = NULL; + info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float); + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + else + { + info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float); + info->encode_buffer = NULL; + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + +#undef STBIR__NEXT_MEMPTR + + // This signals that the ring buffer is empty + info->ring_buffer_begin_index = -1; + + stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w); + stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h); + + STBIR_PROGRESS_REPORT(0); + + if (stbir__use_height_upsampling(info)) + stbir__buffer_loop_upsample(info); + else + stbir__buffer_loop_downsample(info); + + STBIR_PROGRESS_REPORT(1); + +#ifdef STBIR_DEBUG_OVERWRITE_TEST + STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0); +#endif + + return 1; +} + + +static int stbir__resize_arbitrary( + void *alloc_context, + const void* input_data, int input_w, int input_h, int input_stride_in_bytes, + void* output_data, int output_w, int output_h, int output_stride_in_bytes, + float s0, float t0, float s1, float t1, float *transform, + int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_filter h_filter, stbir_filter v_filter, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace) +{ + stbir__info info; + int result; + size_t memory_required; + void* extra_memory; + + stbir__setup(&info, input_w, input_h, output_w, output_h, channels); + stbir__calculate_transform(&info, s0,t0,s1,t1,transform); + stbir__choose_filter(&info, h_filter, v_filter); + memory_required = stbir__calculate_memory(&info); + extra_memory = STBIR_MALLOC(memory_required, alloc_context); + + if (!extra_memory) + return 0; + + result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes, + output_data, output_stride_in_bytes, + alpha_channel, flags, type, + edge_horizontal, edge_vertical, + colorspace, extra_memory, memory_required); + + STBIR_FREE(extra_memory, alloc_context); + + return result; +} + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset) +{ + float transform[4]; + transform[0] = x_scale; + transform[1] = y_scale; + transform[2] = x_offset; + transform[3] = y_offset; + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/dreamcast/pvrtex/stb_image_resize_impl.c b/dreamcast/pvrtex/stb_image_resize_impl.c new file mode 100644 index 00000000..2ff28d8a --- /dev/null +++ b/dreamcast/pvrtex/stb_image_resize_impl.c @@ -0,0 +1,3 @@ +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize.h" + diff --git a/dreamcast/pvrtex/stb_image_write.h b/dreamcast/pvrtex/stb_image_write.h new file mode 100644 index 00000000..e4b32ed1 --- /dev/null +++ b/dreamcast/pvrtex/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/dreamcast/pvrtex/stb_image_write_impl.c b/dreamcast/pvrtex/stb_image_write_impl.c new file mode 100644 index 00000000..5c25d658 --- /dev/null +++ b/dreamcast/pvrtex/stb_image_write_impl.c @@ -0,0 +1,3 @@ +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + diff --git a/dreamcast/pvrtex/tddither.c b/dreamcast/pvrtex/tddither.c new file mode 100644 index 00000000..894052c6 --- /dev/null +++ b/dreamcast/pvrtex/tddither.c @@ -0,0 +1,231 @@ +#include +#include +#include +#include +#include +#include "pixel.h" +#include "pvr_texture_encoder.h" +#include "pvr_texture.h" +#include "tddither.h" + + + +void pteDNearestARGB4444(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst) { + (void)palette; + (void)sample_size; + v4f t = v4DivS(v4Float(v4Int(v4AddS(v4MulS(v4Get(sample), 16), 0.5))), 16); + nearest_dst[0] = t.x; + nearest_dst[1] = t.y; + nearest_dst[2] = t.z; + nearest_dst[3] = t.w; +} +void pteDNearestARGB1555(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst) { + (void)palette; (void)sample_size; + v4f t = v4Mul(v4Get(sample), v4Set(32,32,32,1)); //Scale by bit depth + t = v4Float(v4Int(v4AddS(t, 0.5))); //Round to nearest + t = v4Div(t, v4Set(32,32,32,1)); //Unscale by bit depth + + nearest_dst[0] = t.x; + nearest_dst[1] = t.y; + nearest_dst[2] = t.z; + nearest_dst[3] = t.w; +} + +void pteDNearestRGB565(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst) { + (void)palette; (void)sample_size; + v4f t = v4Mul(v4Get(sample), v4Set(32,64,32,0)); //Scale by bit depth + t = v4Float(v4IntRnd(t)); //Round to nearest + t = v4Div(t, v4Set(32,64,32,0.5)); //Unscale by bit depth + + nearest_dst[0] = t.x; + nearest_dst[1] = t.y; + nearest_dst[2] = t.z; + nearest_dst[3] = t.w; +} +void pteDNearestNorm(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst) { + int norm = pxlRGBtoSpherical(sample[0] * 255.0f, sample[1] * 255.0f, sample[2] * 255.0f); + + pxlABGR8888 n = pxlSphericaltoABGR8888(norm); + + nearest_dst[0] = pxlU8toF(n.r); + nearest_dst[1] = pxlU8toF(n.g); + nearest_dst[2] = pxlU8toF(n.b); + nearest_dst[3] = 1; +} + +void pteDNearest8BPP(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst) { + v4f cf = v4MulS(v4Get(sample), 1); + pxlABGR8888 c = pxlSetABGR8888(v4Pass(cf)); + unsigned idx = pxlFindClosestColor(c, palette, palette_size); + pxlABGR8888 *nc = ((pxlABGR8888*)palette) + idx; + nearest_dst[0] = nc->r / 255.; + nearest_dst[1] = nc->g / 255.; + nearest_dst[2] = nc->b / 255.; + nearest_dst[3] = nc->a / 255.; +} + + +void pteConvertFPtoARGB4444(const float *img, unsigned w, unsigned h, unsigned channels, const pxlABGR8888 *palette, size_t palette_size, void * restrict dst) { + assert(channels == 4); + assert(dst); + assert(img); + + pxlARGB4444 *cdst = dst; + for(unsigned y = 0; y < h; y++) { + for(unsigned x = 0; x < w; x++) { + int ofs = (y*w+x)*channels; + cdst[y*w+x] = pxlSetARGB4444(img[ofs + 0], img[ofs + 1], img[ofs + 2], img[ofs + 3]); + } + } +} +void pteConvertFPtoARGB1555(const float *img, unsigned w, unsigned h, unsigned channels, const pxlABGR8888 *palette, size_t palette_size, void * restrict dst) { + assert(channels == 4); + assert(dst); + assert(img); + + pxlARGB1555 *cdst = dst; + for(unsigned y = 0; y < h; y++) { + for(unsigned x = 0; x < w; x++) { + int ofs = (y*w+x)*channels; + cdst[y*w+x] = pxlSetARGB1555(img[ofs + 0], img[ofs + 1], img[ofs + 2], img[ofs + 3]); + } + } +} +void pteConvertFPtoRGB565(const float *img, unsigned w, unsigned h, unsigned channels, const pxlABGR8888 *palette, size_t palette_size, void * restrict dst) { + assert(channels == 4); + assert(dst); + assert(img); + + pxlRGB565 *cdst = dst; + for(unsigned y = 0; y < h; y++) { + for(unsigned x = 0; x < w; x++) { + int ofs = (y*w+x)*channels; + cdst[y*w+x] = pxlSetRGB565(img[ofs + 0], img[ofs + 1], img[ofs + 2]); + } + } +} + +void pteConvertFPtoABGR8888(const float *img, unsigned w, unsigned h, unsigned channels, const pxlABGR8888 *palette, size_t palette_size, void * restrict dst) { + assert(channels == 4); + assert(dst); + assert(img); + + pxlABGR8888 *cdst = dst; + for(unsigned y = 0; y < h; y++) { + for(unsigned x = 0; x < w; x++) { + int ofs = (y*w+x)*channels; + cdst[y*w+x] = pxlSetABGR8888(img[ofs + 0], img[ofs + 1], img[ofs + 2], img[ofs + 3]); + } + } +} + +#define MAX_CHANNELS (4*4*4) +#define VGAMMA ((float)(1)) +#define RVGAMMA (1.0f/VGAMMA) +void pteDither(const unsigned char *src, unsigned w, unsigned h, unsigned channels, float dither_amt, dithFindNearest nearest, const pxlABGR8888 *palette, size_t palette_size, void *dst, int dst_pixel_format) { + assert(src); + assert(dst); + assert(channels < MAX_CHANNELS); + + //Convert image to floats + float *imgf = malloc(sizeof(float) * w * h * channels); + //printf("Dither %ux%ux%u\n",w,h,channels); + //printf("%zu -> %p\n",sizeof(float) * w * h * channels, imgf); + for(unsigned y = 0; y < h; y++) { + for(unsigned x = 0; x < w; x++) { + int ofs = (y*w+x)*channels; + for(unsigned c = 0; c < channels; c++) { + //~ imgf[ofs+c] = src[ofs+c] / 255.0f; + imgf[ofs+c] = pow(src[ofs+c] / 255.0f, VGAMMA); + } + } + } + + if (dither_amt != 0) { + //Dither floating point image + float near[4*4*4]; + float err[4*4*4]; + for(unsigned y = 0; y < h; y++) { + for(unsigned x = 0; x < w; x++) { + float *cur = imgf + (y*w+x)*channels; + //~ nearest(cur, channels, palette, cur); + //~ continue; + nearest(cur, channels, palette, palette_size, &near[0]); + //~ continue; + for(int i = 0; i < channels; i++) { + err[i] = CLAMP(0, cur[i] - near[i], 1); + cur[i] = near[i]; + } + + /* + .0 + 123 + */ + #define diffuse(xo,yo, weight) do {\ + if ((x+(xo)) < w && (int)(x+(xo)) >= 0 && (y+(yo)) < h) \ + for(int i = 0; i < channels; i++) \ + cur[(w*(yo)+(xo)) * channels + i] += err[i] * (weight); \ + } while(0) + + if (1) { + diffuse(1, 0, 7/16. * dither_amt); + diffuse(-1, 1, 3/16. * dither_amt); + diffuse(0, 1, 5/16. * dither_amt); + diffuse(1, 1, 1/16. * dither_amt); + } else { + diffuse( 1, 0, 8/42. * dither_amt); + diffuse( 2, 0, 4/42. * dither_amt); + + diffuse(-2, 1, 2/42. * dither_amt); + diffuse(-1, 1, 4/42. * dither_amt); + diffuse( 0, 1, 8/42. * dither_amt); + diffuse( 1, 1, 4/42. * dither_amt); + diffuse( 2, 1, 2/42. * dither_amt); + + diffuse(-2, 2, 1/42. * dither_amt); + diffuse(-1, 2, 2/42. * dither_amt); + diffuse( 0, 2, 4/42. * dither_amt); + diffuse( 1, 2, 2/42. * dither_amt); + diffuse( 2, 2, 1/42. * dither_amt); + } + + } + } + } + + //Undo gamma correction + for(unsigned y = 0; y < h; y++) { + for(unsigned x = 0; x < w; x++) { + unsigned ofs = (y*w+x)*channels; + for(unsigned c = 0; c < channels; c++) { + //~ imgf[ofs+c] = pow(CLAMP(0, imgf[ofs+c], 1), RVGAMMA); + imgf[ofs+c] = pow(imgf[ofs+c], RVGAMMA); + } + } + } + + switch(dst_pixel_format) { + case PTE_ARGB4444: pteConvertFPtoARGB4444(imgf, w, h, channels, palette, palette_size, dst); break; + case PTE_ARGB1555: pteConvertFPtoARGB1555(imgf, w, h, channels, palette, palette_size, dst); break; + case PTE_RGB565: pteConvertFPtoRGB565(imgf, w, h, channels, palette, palette_size, dst); break; + case PTE_ABGR8888: pteConvertFPtoABGR8888(imgf, w, h, channels, palette, palette_size, dst); break; + } + + free(imgf); +} + +dithFindNearest pteGetFindNearest(ptePixelFormat format) { + static const dithFindNearest tbl[] = { + &pteDNearestARGB1555, + &pteDNearestRGB565, + &pteDNearestARGB4444, + NULL, + &pteDNearestNorm, + &pteDNearest8BPP, + &pteDNearest8BPP, + }; + + assert(format >= PTE_ARGB1555 && format <= PTE_PALETTE_8B && format != PTE_YUV); + + return tbl[format]; +} diff --git a/dreamcast/pvrtex/tddither.h b/dreamcast/pvrtex/tddither.h new file mode 100644 index 00000000..af1d0ff9 --- /dev/null +++ b/dreamcast/pvrtex/tddither.h @@ -0,0 +1,13 @@ +#pragma once + +#include "pvr_texture.h" + +//Dithering nearest converters +void tdDNearestARGB4444(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst); +void tdDNearestARGB1555(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst); +void tdDNearestRGB565(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst); + +typedef void (*dithFindNearest)(const float *sample, int sample_size, const pxlABGR8888 *palette, size_t palette_size, float *nearest_dst); + +dithFindNearest pteGetFindNearest(ptePixelFormat format); +void pteDither(const unsigned char *src, unsigned w, unsigned h, unsigned channels, float dither_amt, dithFindNearest nearest, const pxlABGR8888 *palette, size_t palette_size, void *dst, int dst_pixel_format); diff --git a/dreamcast/pvrtex/vqcompress.c b/dreamcast/pvrtex/vqcompress.c new file mode 100644 index 00000000..d34465dc --- /dev/null +++ b/dreamcast/pvrtex/vqcompress.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include +#include "vqcompress.h" +#include "elbg.h" +#include "mycommon.h" + +void vqcInit(VQCompressor *c, vqcFormat input_format, unsigned channels, unsigned pix_per_cb, unsigned cb_size, unsigned auto_small_vq) { + assert(c); + + memset(c, 0, sizeof(*c)); + c->format = input_format; + c->channels = channels; + c->pix_per_cb = pix_per_cb; + c->cb_size = cb_size; + c->auto_small_vq = auto_small_vq; + c->dimensions = pix_per_cb * channels; + for(int i = 0; i < VQC_MAX_CHANNELS; i++) + c->gamma[i] = 1.0f; +} + +/* + The idea was to cale the pixel values so that we don't loose detail when doing integer gamma correction. + Setting this too high can cause ELBG to select incorrect codebook entries. + It seems not scaling at all results in the best quality. +*/ +#define INT_SCALE (255.0f) + +void vqcAddPoints(VQCompressor *c, const void *src, size_t pixel_cnt) { + assert(c); + assert(src); + assert(c->format == VQC_UINT8); + assert((pixel_cnt % c->pix_per_cb) == 0); //Make sure pixel_cnt is multiple of codebook entry size + + size_t point_cnt = pixel_cnt / c->pix_per_cb; + + //Ensure room for new pixels + size_t space_needed = (c->point_cnt + point_cnt) * sizeof(int) * c->dimensions; + if (c->data_space <= space_needed) { + //At least 64KB, or the exact amount needed if we need more than that. + size_t more = space_needed < 64*1024 ? 64*1024 : space_needed; + void *newdata = realloc(c->data, c->data_space + more); + assert(newdata); + c->data = newdata; + } + + //Convert source data to ints for ELBG + const unsigned char *srcc = src; + int *dst = c->data + c->point_cnt * c->dimensions; + size_t elem_cnt = pixel_cnt * c->channels; + unsigned curchannel = 0; + for(size_t i = 0; i < elem_cnt; i++) { + //Get source value and convert to floating point + float v = srcc[i] / 255.0f; + + //Gamma correction + v = pow(v, c->gamma[curchannel++]); + if (curchannel >= c->channels) + curchannel = 0; + + //Scale to fixed point + dst[i] = v * INT_SCALE; + } + + c->point_cnt += point_cnt; +} + +void vqcSetChannelGamma(VQCompressor *c, unsigned channel, float val) { + assert(c); + assert(channel < c->channels); + assert(val > 0); + c->gamma[channel] = val; +} + +void vqcSetRGBAGamma(VQCompressor *c, float rgb, float alpha) { + assert(c); + assert(c->channels == 3 || c->channels == 4); + + c->gamma[0] = c->gamma[1] = c->gamma[2] = rgb; + if (c->channels == 4) + c->gamma[3] = alpha; +} + +void vqcSetARGBGamma(VQCompressor *c, float rgb, float alpha) { + assert(c); + assert(c->channels == 3 || c->channels == 4); + + if (c->channels == 4) { + c->gamma[0] = alpha; + c->gamma[1] = c->gamma[2] = c->gamma[3] = rgb; + } else { + c->gamma[0] = c->gamma[1] = c->gamma[2] = rgb; + } +} + +vqcResults vqcCompress(VQCompressor *c, int quality) { + assert(c); + assert(c->cb_size); + assert(c->dimensions); + assert(c->point_cnt > 0); + assert(c->data); + + //Allocate indicies and int codebook result buffer + vqcResults result; + const size_t cb_elem_cnt = c->cb_size * c->dimensions; + int *int_codebook = malloc(cb_elem_cnt * sizeof(int)); + result.indices = malloc(c->point_cnt * sizeof(int)); + assert(int_codebook); + assert(result.indices); + + //Run ELBG to generate codebook + struct ELBGContext *elbgcxt = 0; + struct AVLFG randcxt; + av_lfg_init(&randcxt, 1); + int errval = avpriv_elbg_do(&elbgcxt, c->data, c->dimensions, c->point_cnt, int_codebook, c->cb_size, quality, result.indices, &randcxt, 0); + + if(c->auto_small_vq) { + // Adjust the codebook indices for small VQ + short codebook_offset = 256 - c->cb_size; + for(int i = 0; i < c->point_cnt; i++) { + result.indices[i] += codebook_offset; + } + } + + assert(errval == 0); + avpriv_elbg_free(&elbgcxt); + + //Convert int_codebook to input_format + assert(c->format == VQC_UINT8); + char *dst = result.codebook = malloc(c->cb_size * c->dimensions); + assert(dst); + unsigned curchannel = 0; + + //Invert gamma values + float invgamma[VQC_MAX_CHANNELS]; + for(int i = 0; i < c->channels; i++) { + invgamma[i] = 1.0f / c->gamma[i]; + } + + for(size_t i = 0; i < cb_elem_cnt; i++) { + //Get source value and convert to floating point + float v = int_codebook[i] / INT_SCALE; + + //Undo gamma correction + v = pow(v, invgamma[curchannel++]); + if (curchannel >= c->channels) + curchannel = 0; + + //Scale to fixed point + dst[i] = v * 255.0f; + } + + //Clean up + free(int_codebook); + SAFE_FREE(&c->data); + c->data_space = 0; + + return result; +} + + diff --git a/dreamcast/pvrtex/vqcompress.h b/dreamcast/pvrtex/vqcompress.h new file mode 100644 index 00000000..8582610f --- /dev/null +++ b/dreamcast/pvrtex/vqcompress.h @@ -0,0 +1,38 @@ +#pragma once + +#define VQC_MAX_CHANNELS 4 + +typedef enum { + VQC_UINT8, +} vqcFormat; + +typedef struct { + vqcFormat format; + unsigned channels; //number of channels per pixel + unsigned pix_per_cb; //number of pixels per cb entry + unsigned point_cnt; //number of pixels / pix_per_cb + unsigned cb_size; //number of entries in cb + unsigned auto_small_vq; // auto small codebook + unsigned dimensions; //pix_per_cb * channels + + //channels can have different gammas (alpha could be 1.0, while RGB could be 2.2) + float gamma[VQC_MAX_CHANNELS]; + + size_t data_space; + int *data; //data to compress +} VQCompressor; + +typedef struct { + //Both codebook and indices may be NULL on error + void *codebook; + int *indices; +} vqcResults; + +void vqcInit(VQCompressor *c, vqcFormat input_format, unsigned channels, unsigned pix_per_cb, unsigned cb_size, unsigned auto_small_vq); +void vqcAddPoints(VQCompressor *c, const void *src, size_t pixel_cnt); +void vqcSetChannelGamma(VQCompressor *c, unsigned channel, float val); +void vqcSetRGBAGamma(VQCompressor *c, float rgb, float alpha); +void vqcSetARGBGamma(VQCompressor *c, float rgb, float alpha); +vqcResults vqcCompress(VQCompressor *c, int quality); + + diff --git a/dreamcast/sfxlist.mk b/dreamcast/sfxlist.mk new file mode 100644 index 00000000..d07d1ea8 --- /dev/null +++ b/dreamcast/sfxlist.mk @@ -0,0 +1,3033 @@ +SFX_WAV = \ + sfx_0.wav \ + sfx_1.wav \ + sfx_10.wav \ + sfx_100.wav \ + sfx_1000.wav \ + sfx_1001.wav \ + sfx_1002.wav \ + sfx_1003.wav \ + sfx_1004.wav \ + sfx_1005.wav \ + sfx_1006.wav \ + sfx_1007.wav \ + sfx_1008.wav \ + sfx_1009.wav \ + sfx_101.wav \ + sfx_1010.wav \ + sfx_1011.wav \ + sfx_1012.wav \ + sfx_1013.wav \ + sfx_1014.wav \ + sfx_1015.wav \ + sfx_1016.wav \ + sfx_1017.wav \ + sfx_1018.wav \ + sfx_1019.wav \ + sfx_102.wav \ + sfx_1020.wav \ + sfx_1021.wav \ + sfx_1022.wav \ + sfx_1023.wav \ + sfx_1024.wav \ + sfx_1025.wav \ + sfx_1026.wav \ + sfx_1027.wav \ + sfx_1028.wav \ + sfx_1029.wav \ + sfx_103.wav \ + sfx_1030.wav \ + sfx_1031.wav \ + sfx_1032.wav \ + sfx_1033.wav \ + sfx_1034.wav \ + sfx_1035.wav \ + sfx_1036.wav \ + sfx_1037.wav \ + sfx_1038.wav \ + sfx_1039.wav \ + sfx_104.wav \ + sfx_1040.wav \ + sfx_1041.wav \ + sfx_1042.wav \ + sfx_1043.wav \ + sfx_1044.wav \ + sfx_1045.wav \ + sfx_1046.wav \ + sfx_1047.wav \ + sfx_1048.wav \ + sfx_1049.wav \ + sfx_105.wav \ + sfx_1050.wav \ + sfx_1051.wav \ + sfx_1052.wav \ + sfx_1053.wav \ + sfx_1054.wav \ + sfx_1055.wav \ + sfx_1056.wav \ + sfx_1057.wav \ + sfx_1058.wav \ + sfx_1059.wav \ + sfx_106.wav \ + sfx_1060.wav \ + sfx_1061.wav \ + sfx_1062.wav \ + sfx_1063.wav \ + sfx_1064.wav \ + sfx_1065.wav \ + sfx_1066.wav \ + sfx_1067.wav \ + sfx_1068.wav \ + sfx_1069.wav \ + sfx_107.wav \ + sfx_1070.wav \ + sfx_1071.wav \ + sfx_1072.wav \ + sfx_1073.wav \ + sfx_1074.wav \ + sfx_1075.wav \ + sfx_1076.wav \ + sfx_1077.wav \ + sfx_1078.wav \ + sfx_1079.wav \ + sfx_108.wav \ + sfx_1080.wav \ + sfx_1081.wav \ + sfx_1082.wav \ + sfx_1083.wav \ + sfx_1084.wav \ + sfx_1085.wav \ + sfx_1086.wav \ + sfx_1087.wav \ + sfx_1088.wav \ + sfx_1089.wav \ + sfx_109.wav \ + sfx_1090.wav \ + sfx_1091.wav \ + sfx_1092.wav \ + sfx_1093.wav \ + sfx_1094.wav \ + sfx_1095.wav \ + sfx_1096.wav \ + sfx_1097.wav \ + sfx_1098.wav \ + sfx_1099.wav \ + sfx_11.wav \ + sfx_110.wav \ + sfx_1100.wav \ + sfx_1101.wav \ + sfx_1102.wav \ + sfx_1103.wav \ + sfx_1104.wav \ + sfx_1105.wav \ + sfx_1106.wav \ + sfx_1107.wav \ + sfx_1108.wav \ + sfx_1109.wav \ + sfx_111.wav \ + sfx_1110.wav \ + sfx_1111.wav \ + sfx_1112.wav \ + sfx_1113.wav \ + sfx_1114.wav \ + sfx_1115.wav \ + sfx_1116.wav \ + sfx_1117.wav \ + sfx_1118.wav \ + sfx_1119.wav \ + sfx_112.wav \ + sfx_1120.wav \ + sfx_1121.wav \ + sfx_1122.wav \ + sfx_1123.wav \ + sfx_1124.wav \ + sfx_1125.wav \ + sfx_1126.wav \ + sfx_1127.wav \ + sfx_1128.wav \ + sfx_1129.wav \ + sfx_113.wav \ + sfx_1130.wav \ + sfx_1131.wav \ + sfx_1132.wav \ + sfx_1133.wav \ + sfx_1134.wav \ + sfx_1135.wav \ + sfx_1136.wav \ + sfx_1137.wav \ + sfx_1138.wav \ + sfx_1139.wav \ + sfx_114.wav \ + sfx_1140.wav \ + sfx_1141.wav \ + sfx_1142.wav \ + sfx_1143.wav \ + sfx_1144.wav \ + sfx_1145.wav \ + sfx_1146.wav \ + sfx_1147.wav \ + sfx_1148.wav \ + sfx_1149.wav \ + sfx_115.wav \ + sfx_1150.wav \ + sfx_1151.wav \ + sfx_1152.wav \ + sfx_1153.wav \ + sfx_1154.wav \ + sfx_1155.wav \ + sfx_1156.wav \ + sfx_1157.wav \ + sfx_1158.wav \ + sfx_1159.wav \ + sfx_116.wav \ + sfx_1160.wav \ + sfx_1161.wav \ + sfx_1162.wav \ + sfx_1163.wav \ + sfx_1164.wav \ + sfx_1165.wav \ + sfx_1166.wav \ + sfx_1167.wav \ + sfx_1168.wav \ + sfx_1169.wav \ + sfx_117.wav \ + sfx_1170.wav \ + sfx_1171.wav \ + sfx_1172.wav \ + sfx_1173.wav \ + sfx_1174.wav \ + sfx_1175.wav \ + sfx_1176.wav \ + sfx_1177.wav \ + sfx_1178.wav \ + sfx_1179.wav \ + sfx_118.wav \ + sfx_1180.wav \ + sfx_1181.wav \ + sfx_1182.wav \ + sfx_1183.wav \ + sfx_1184.wav \ + sfx_1185.wav \ + sfx_1186.wav \ + sfx_1187.wav \ + sfx_1188.wav \ + sfx_1189.wav \ + sfx_119.wav \ + sfx_1190.wav \ + sfx_1191.wav \ + sfx_1192.wav \ + sfx_1193.wav \ + sfx_1194.wav \ + sfx_1195.wav \ + sfx_1196.wav \ + sfx_1197.wav \ + sfx_1198.wav \ + sfx_1199.wav \ + sfx_12.wav \ + sfx_120.wav \ + sfx_1200.wav \ + sfx_1201.wav \ + sfx_1202.wav \ + sfx_1203.wav \ + sfx_1204.wav \ + sfx_1205.wav \ + sfx_1206.wav \ + sfx_1207.wav \ + sfx_1208.wav \ + sfx_1209.wav \ + sfx_121.wav \ + sfx_1210.wav \ + sfx_1211.wav \ + sfx_1212.wav \ + sfx_1213.wav \ + sfx_1214.wav \ + sfx_1215.wav \ + sfx_1216.wav \ + sfx_1217.wav \ + sfx_1218.wav \ + sfx_1219.wav \ + sfx_122.wav \ + sfx_1220.wav \ + sfx_1221.wav \ + sfx_1222.wav \ + sfx_1223.wav \ + sfx_1224.wav \ + sfx_1225.wav \ + sfx_1226.wav \ + sfx_1227.wav \ + sfx_1228.wav \ + sfx_1229.wav \ + sfx_123.wav \ + sfx_1230.wav \ + sfx_1231.wav \ + sfx_1232.wav \ + sfx_1233.wav \ + sfx_1234.wav \ + sfx_1235.wav \ + sfx_1236.wav \ + sfx_1237.wav \ + sfx_1238.wav \ + sfx_1239.wav \ + sfx_124.wav \ + sfx_1240.wav \ + sfx_1241.wav \ + sfx_1242.wav \ + sfx_1243.wav \ + sfx_1244.wav \ + sfx_1245.wav \ + sfx_1246.wav \ + sfx_1247.wav \ + sfx_1248.wav \ + sfx_1249.wav \ + sfx_125.wav \ + sfx_1250.wav \ + sfx_1251.wav \ + sfx_1252.wav \ + sfx_1253.wav \ + sfx_1254.wav \ + sfx_1255.wav \ + sfx_1256.wav \ + sfx_1257.wav \ + sfx_1258.wav \ + sfx_1259.wav \ + sfx_126.wav \ + sfx_1260.wav \ + sfx_1261.wav \ + sfx_1262.wav \ + sfx_1263.wav \ + sfx_1264.wav \ + sfx_1265.wav \ + sfx_1266.wav \ + sfx_1267.wav \ + sfx_1268.wav \ + sfx_1269.wav \ + sfx_127.wav \ + sfx_1270.wav \ + sfx_1271.wav \ + sfx_1272.wav \ + sfx_1273.wav \ + sfx_1274.wav \ + sfx_1275.wav \ + sfx_1276.wav \ + sfx_1277.wav \ + sfx_1278.wav \ + sfx_1279.wav \ + sfx_128.wav \ + sfx_1280.wav \ + sfx_1281.wav \ + sfx_1282.wav \ + sfx_1283.wav \ + sfx_1284.wav \ + sfx_1285.wav \ + sfx_1286.wav \ + sfx_1287.wav \ + sfx_1288.wav \ + sfx_1289.wav \ + sfx_129.wav \ + sfx_1290.wav \ + sfx_1291.wav \ + sfx_1292.wav \ + sfx_1293.wav \ + sfx_1294.wav \ + sfx_1295.wav \ + sfx_1296.wav \ + sfx_1297.wav \ + sfx_1298.wav \ + sfx_1299.wav \ + sfx_13.wav \ + sfx_130.wav \ + sfx_1300.wav \ + sfx_1301.wav \ + sfx_1302.wav \ + sfx_1303.wav \ + sfx_1304.wav \ + sfx_1305.wav \ + sfx_1306.wav \ + sfx_1307.wav \ + sfx_1308.wav \ + sfx_1309.wav \ + sfx_131.wav \ + sfx_1310.wav \ + sfx_1311.wav \ + sfx_1312.wav \ + sfx_1313.wav \ + sfx_1314.wav \ + sfx_1315.wav \ + sfx_1316.wav \ + sfx_1317.wav \ + sfx_1318.wav \ + sfx_1319.wav \ + sfx_132.wav \ + sfx_1320.wav \ + sfx_1321.wav \ + sfx_1322.wav \ + sfx_1323.wav \ + sfx_1324.wav \ + sfx_1325.wav \ + sfx_1326.wav \ + sfx_1327.wav \ + sfx_1328.wav \ + sfx_1329.wav \ + sfx_133.wav \ + sfx_1330.wav \ + sfx_1331.wav \ + sfx_1332.wav \ + sfx_1333.wav \ + sfx_1334.wav \ + sfx_1335.wav \ + sfx_1336.wav \ + sfx_1337.wav \ + sfx_1338.wav \ + sfx_1339.wav \ + sfx_134.wav \ + sfx_1340.wav \ + sfx_1341.wav \ + sfx_1342.wav \ + sfx_1343.wav \ + sfx_1344.wav \ + sfx_1345.wav \ + sfx_1346.wav \ + sfx_1347.wav \ + sfx_1348.wav \ + sfx_1349.wav \ + sfx_135.wav \ + sfx_1350.wav \ + sfx_1351.wav \ + sfx_1352.wav \ + sfx_1353.wav \ + sfx_1354.wav \ + sfx_1355.wav \ + sfx_1356.wav \ + sfx_1357.wav \ + sfx_1358.wav \ + sfx_1359.wav \ + sfx_136.wav \ + sfx_1360.wav \ + sfx_1361.wav \ + sfx_1362.wav \ + sfx_1363.wav \ + sfx_1364.wav \ + sfx_1365.wav \ + sfx_1366.wav \ + sfx_1367.wav \ + sfx_1368.wav \ + sfx_1369.wav \ + sfx_137.wav \ + sfx_1370.wav \ + sfx_1371.wav \ + sfx_1372.wav \ + sfx_1373.wav \ + sfx_1374.wav \ + sfx_1375.wav \ + sfx_1376.wav \ + sfx_1377.wav \ + sfx_1378.wav \ + sfx_1379.wav \ + sfx_138.wav \ + sfx_1380.wav \ + sfx_1381.wav \ + sfx_1382.wav \ + sfx_1383.wav \ + sfx_1384.wav \ + sfx_1385.wav \ + sfx_1386.wav \ + sfx_1387.wav \ + sfx_1388.wav \ + sfx_1389.wav \ + sfx_139.wav \ + sfx_1390.wav \ + sfx_1391.wav \ + sfx_1392.wav \ + sfx_1393.wav \ + sfx_1394.wav \ + sfx_1395.wav \ + sfx_1396.wav \ + sfx_1397.wav \ + sfx_1398.wav \ + sfx_1399.wav \ + sfx_14.wav \ + sfx_140.wav \ + sfx_1400.wav \ + sfx_1401.wav \ + sfx_1402.wav \ + sfx_1403.wav \ + sfx_1404.wav \ + sfx_1405.wav \ + sfx_1406.wav \ + sfx_1407.wav \ + sfx_1408.wav \ + sfx_1409.wav \ + sfx_141.wav \ + sfx_1410.wav \ + sfx_1411.wav \ + sfx_1412.wav \ + sfx_1413.wav \ + sfx_1414.wav \ + sfx_1415.wav \ + sfx_1416.wav \ + sfx_1417.wav \ + sfx_1418.wav \ + sfx_1419.wav \ + sfx_142.wav \ + sfx_1420.wav \ + sfx_1421.wav \ + sfx_1422.wav \ + sfx_1423.wav \ + sfx_1424.wav \ + sfx_1425.wav \ + sfx_1426.wav \ + sfx_1427.wav \ + sfx_1428.wav \ + sfx_1429.wav \ + sfx_143.wav \ + sfx_1430.wav \ + sfx_1431.wav \ + sfx_1432.wav \ + sfx_1433.wav \ + sfx_1434.wav \ + sfx_1435.wav \ + sfx_1436.wav \ + sfx_1437.wav \ + sfx_1438.wav \ + sfx_1439.wav \ + sfx_144.wav \ + sfx_1440.wav \ + sfx_1441.wav \ + sfx_1442.wav \ + sfx_1443.wav \ + sfx_1444.wav \ + sfx_1445.wav \ + sfx_1446.wav \ + sfx_1447.wav \ + sfx_1448.wav \ + sfx_1449.wav \ + sfx_145.wav \ + sfx_1450.wav \ + sfx_1451.wav \ + sfx_1452.wav \ + sfx_1453.wav \ + sfx_1454.wav \ + sfx_1455.wav \ + sfx_1456.wav \ + sfx_1457.wav \ + sfx_1458.wav \ + sfx_1459.wav \ + sfx_146.wav \ + sfx_1460.wav \ + sfx_1461.wav \ + sfx_1462.wav \ + sfx_1463.wav \ + sfx_1464.wav \ + sfx_1465.wav \ + sfx_1466.wav \ + sfx_1467.wav \ + sfx_1468.wav \ + sfx_1469.wav \ + sfx_147.wav \ + sfx_1470.wav \ + sfx_1471.wav \ + sfx_1472.wav \ + sfx_1473.wav \ + sfx_1474.wav \ + sfx_1475.wav \ + sfx_1476.wav \ + sfx_1477.wav \ + sfx_1478.wav \ + sfx_1479.wav \ + sfx_148.wav \ + sfx_1480.wav \ + sfx_1481.wav \ + sfx_1482.wav \ + sfx_1483.wav \ + sfx_1484.wav \ + sfx_1485.wav \ + sfx_1486.wav \ + sfx_1487.wav \ + sfx_1488.wav \ + sfx_1489.wav \ + sfx_149.wav \ + sfx_1490.wav \ + sfx_1491.wav \ + sfx_1492.wav \ + sfx_1493.wav \ + sfx_1494.wav \ + sfx_1495.wav \ + sfx_1496.wav \ + sfx_1497.wav \ + sfx_1498.wav \ + sfx_1499.wav \ + sfx_15.wav \ + sfx_150.wav \ + sfx_1500.wav \ + sfx_1501.wav \ + sfx_1502.wav \ + sfx_1503.wav \ + sfx_1504.wav \ + sfx_1505.wav \ + sfx_1506.wav \ + sfx_1507.wav \ + sfx_1508.wav \ + sfx_1509.wav \ + sfx_151.wav \ + sfx_1510.wav \ + sfx_1511.wav \ + sfx_1512.wav \ + sfx_1513.wav \ + sfx_1514.wav \ + sfx_1515.wav \ + sfx_1516.wav \ + sfx_1517.wav \ + sfx_1518.wav \ + sfx_1519.wav \ + sfx_152.wav \ + sfx_1520.wav \ + sfx_1521.wav \ + sfx_1522.wav \ + sfx_1523.wav \ + sfx_1524.wav \ + sfx_1525.wav \ + sfx_1526.wav \ + sfx_1527.wav \ + sfx_1528.wav \ + sfx_1529.wav \ + sfx_153.wav \ + sfx_1530.wav \ + sfx_1531.wav \ + sfx_1532.wav \ + sfx_1533.wav \ + sfx_1534.wav \ + sfx_1535.wav \ + sfx_1536.wav \ + sfx_1537.wav \ + sfx_1538.wav \ + sfx_1539.wav \ + sfx_154.wav \ + sfx_1540.wav \ + sfx_1541.wav \ + sfx_1542.wav \ + sfx_1543.wav \ + sfx_1544.wav \ + sfx_1545.wav \ + sfx_1546.wav \ + sfx_1547.wav \ + sfx_1548.wav \ + sfx_1549.wav \ + sfx_155.wav \ + sfx_1550.wav \ + sfx_1551.wav \ + sfx_1552.wav \ + sfx_1553.wav \ + sfx_1554.wav \ + sfx_1555.wav \ + sfx_1556.wav \ + sfx_1557.wav \ + sfx_1558.wav \ + sfx_1559.wav \ + sfx_156.wav \ + sfx_1560.wav \ + sfx_1561.wav \ + sfx_1562.wav \ + sfx_1563.wav \ + sfx_1564.wav \ + sfx_1565.wav \ + sfx_1566.wav \ + sfx_1567.wav \ + sfx_1568.wav \ + sfx_1569.wav \ + sfx_157.wav \ + sfx_1570.wav \ + sfx_1571.wav \ + sfx_1572.wav \ + sfx_1573.wav \ + sfx_1574.wav \ + sfx_1575.wav \ + sfx_1576.wav \ + sfx_1577.wav \ + sfx_1578.wav \ + sfx_1579.wav \ + sfx_158.wav \ + sfx_1580.wav \ + sfx_1581.wav \ + sfx_1582.wav \ + sfx_1583.wav \ + sfx_1584.wav \ + sfx_1585.wav \ + sfx_1586.wav \ + sfx_1587.wav \ + sfx_1588.wav \ + sfx_1589.wav \ + sfx_159.wav \ + sfx_1590.wav \ + sfx_1591.wav \ + sfx_1592.wav \ + sfx_1593.wav \ + sfx_1594.wav \ + sfx_1595.wav \ + sfx_1596.wav \ + sfx_1597.wav \ + sfx_1598.wav \ + sfx_1599.wav \ + sfx_16.wav \ + sfx_160.wav \ + sfx_1600.wav \ + sfx_1601.wav \ + sfx_1602.wav \ + sfx_1603.wav \ + sfx_1604.wav \ + sfx_1605.wav \ + sfx_1606.wav \ + sfx_1607.wav \ + sfx_1608.wav \ + sfx_1609.wav \ + sfx_161.wav \ + sfx_1610.wav \ + sfx_1611.wav \ + sfx_1612.wav \ + sfx_1613.wav \ + sfx_1614.wav \ + sfx_1615.wav \ + sfx_1616.wav \ + sfx_1617.wav \ + sfx_1618.wav \ + sfx_1619.wav \ + sfx_162.wav \ + sfx_1620.wav \ + sfx_1621.wav \ + sfx_1622.wav \ + sfx_1623.wav \ + sfx_1624.wav \ + sfx_1625.wav \ + sfx_1626.wav \ + sfx_1627.wav \ + sfx_1628.wav \ + sfx_1629.wav \ + sfx_163.wav \ + sfx_1630.wav \ + sfx_1631.wav \ + sfx_1632.wav \ + sfx_1633.wav \ + sfx_1634.wav \ + sfx_1635.wav \ + sfx_1636.wav \ + sfx_1637.wav \ + sfx_1638.wav \ + sfx_1639.wav \ + sfx_164.wav \ + sfx_1640.wav \ + sfx_1641.wav \ + sfx_1642.wav \ + sfx_1643.wav \ + sfx_1644.wav \ + sfx_1645.wav \ + sfx_1646.wav \ + sfx_1647.wav \ + sfx_1648.wav \ + sfx_1649.wav \ + sfx_165.wav \ + sfx_1650.wav \ + sfx_1651.wav \ + sfx_1652.wav \ + sfx_1653.wav \ + sfx_1654.wav \ + sfx_1655.wav \ + sfx_1656.wav \ + sfx_1657.wav \ + sfx_1658.wav \ + sfx_1659.wav \ + sfx_166.wav \ + sfx_1660.wav \ + sfx_1661.wav \ + sfx_1662.wav \ + sfx_1663.wav \ + sfx_1664.wav \ + sfx_1665.wav \ + sfx_1666.wav \ + sfx_1667.wav \ + sfx_1668.wav \ + sfx_1669.wav \ + sfx_167.wav \ + sfx_1670.wav \ + sfx_1671.wav \ + sfx_1672.wav \ + sfx_1673.wav \ + sfx_1674.wav \ + sfx_1675.wav \ + sfx_1676.wav \ + sfx_1677.wav \ + sfx_1678.wav \ + sfx_1679.wav \ + sfx_168.wav \ + sfx_1680.wav \ + sfx_1681.wav \ + sfx_1682.wav \ + sfx_1683.wav \ + sfx_1684.wav \ + sfx_1685.wav \ + sfx_1686.wav \ + sfx_1687.wav \ + sfx_1688.wav \ + sfx_1689.wav \ + sfx_169.wav \ + sfx_1690.wav \ + sfx_1691.wav \ + sfx_1692.wav \ + sfx_1693.wav \ + sfx_1694.wav \ + sfx_1695.wav \ + sfx_1696.wav \ + sfx_1697.wav \ + sfx_1698.wav \ + sfx_1699.wav \ + sfx_17.wav \ + sfx_170.wav \ + sfx_1700.wav \ + sfx_1701.wav \ + sfx_1702.wav \ + sfx_1703.wav \ + sfx_1704.wav \ + sfx_1705.wav \ + sfx_1706.wav \ + sfx_1707.wav \ + sfx_1708.wav \ + sfx_1709.wav \ + sfx_171.wav \ + sfx_1710.wav \ + sfx_1711.wav \ + sfx_1712.wav \ + sfx_1713.wav \ + sfx_1714.wav \ + sfx_1715.wav \ + sfx_1716.wav \ + sfx_1717.wav \ + sfx_1718.wav \ + sfx_1719.wav \ + sfx_172.wav \ + sfx_1720.wav \ + sfx_1721.wav \ + sfx_1722.wav \ + sfx_1723.wav \ + sfx_1724.wav \ + sfx_1725.wav \ + sfx_1726.wav \ + sfx_1727.wav \ + sfx_1728.wav \ + sfx_1729.wav \ + sfx_173.wav \ + sfx_1730.wav \ + sfx_1731.wav \ + sfx_1732.wav \ + sfx_1733.wav \ + sfx_1734.wav \ + sfx_1735.wav \ + sfx_1736.wav \ + sfx_1737.wav \ + sfx_1738.wav \ + sfx_1739.wav \ + sfx_174.wav \ + sfx_1740.wav \ + sfx_1741.wav \ + sfx_1742.wav \ + sfx_1743.wav \ + sfx_1744.wav \ + sfx_1745.wav \ + sfx_1746.wav \ + sfx_1747.wav \ + sfx_1748.wav \ + sfx_1749.wav \ + sfx_175.wav \ + sfx_1750.wav \ + sfx_1751.wav \ + sfx_1752.wav \ + sfx_1753.wav \ + sfx_1754.wav \ + sfx_1755.wav \ + sfx_1756.wav \ + sfx_1757.wav \ + sfx_1758.wav \ + sfx_1759.wav \ + sfx_176.wav \ + sfx_1760.wav \ + sfx_1761.wav \ + sfx_1762.wav \ + sfx_1763.wav \ + sfx_1764.wav \ + sfx_1765.wav \ + sfx_1766.wav \ + sfx_1767.wav \ + sfx_1768.wav \ + sfx_1769.wav \ + sfx_177.wav \ + sfx_1770.wav \ + sfx_1771.wav \ + sfx_1772.wav \ + sfx_1773.wav \ + sfx_1774.wav \ + sfx_1775.wav \ + sfx_1776.wav \ + sfx_1777.wav \ + sfx_1778.wav \ + sfx_1779.wav \ + sfx_178.wav \ + sfx_1780.wav \ + sfx_1781.wav \ + sfx_1782.wav \ + sfx_1783.wav \ + sfx_1784.wav \ + sfx_1785.wav \ + sfx_1786.wav \ + sfx_1787.wav \ + sfx_1788.wav \ + sfx_1789.wav \ + sfx_179.wav \ + sfx_1790.wav \ + sfx_1791.wav \ + sfx_1792.wav \ + sfx_1793.wav \ + sfx_1794.wav \ + sfx_1795.wav \ + sfx_1796.wav \ + sfx_1797.wav \ + sfx_1798.wav \ + sfx_1799.wav \ + sfx_18.wav \ + sfx_180.wav \ + sfx_1800.wav \ + sfx_1801.wav \ + sfx_1802.wav \ + sfx_1803.wav \ + sfx_1804.wav \ + sfx_1805.wav \ + sfx_1806.wav \ + sfx_1807.wav \ + sfx_1808.wav \ + sfx_1809.wav \ + sfx_181.wav \ + sfx_1810.wav \ + sfx_1811.wav \ + sfx_1812.wav \ + sfx_1813.wav \ + sfx_1814.wav \ + sfx_1815.wav \ + sfx_1816.wav \ + sfx_1817.wav \ + sfx_1818.wav \ + sfx_1819.wav \ + sfx_182.wav \ + sfx_1820.wav \ + sfx_1821.wav \ + sfx_1822.wav \ + sfx_1823.wav \ + sfx_1824.wav \ + sfx_1825.wav \ + sfx_1826.wav \ + sfx_1827.wav \ + sfx_1828.wav \ + sfx_1829.wav \ + sfx_183.wav \ + sfx_1830.wav \ + sfx_1831.wav \ + sfx_1832.wav \ + sfx_1833.wav \ + sfx_1834.wav \ + sfx_1835.wav \ + sfx_1836.wav \ + sfx_1837.wav \ + sfx_1838.wav \ + sfx_1839.wav \ + sfx_184.wav \ + sfx_1840.wav \ + sfx_1841.wav \ + sfx_1842.wav \ + sfx_1843.wav \ + sfx_1844.wav \ + sfx_1845.wav \ + sfx_1846.wav \ + sfx_1847.wav \ + sfx_1848.wav \ + sfx_1849.wav \ + sfx_185.wav \ + sfx_1850.wav \ + sfx_1851.wav \ + sfx_1852.wav \ + sfx_1853.wav \ + sfx_1854.wav \ + sfx_1855.wav \ + sfx_1856.wav \ + sfx_1857.wav \ + sfx_1858.wav \ + sfx_1859.wav \ + sfx_186.wav \ + sfx_1860.wav \ + sfx_1861.wav \ + sfx_1862.wav \ + sfx_1863.wav \ + sfx_1864.wav \ + sfx_1865.wav \ + sfx_1866.wav \ + sfx_1867.wav \ + sfx_1868.wav \ + sfx_1869.wav \ + sfx_187.wav \ + sfx_1870.wav \ + sfx_1871.wav \ + sfx_1872.wav \ + sfx_1873.wav \ + sfx_1874.wav \ + sfx_1875.wav \ + sfx_1876.wav \ + sfx_1877.wav \ + sfx_1878.wav \ + sfx_1879.wav \ + sfx_188.wav \ + sfx_1880.wav \ + sfx_1881.wav \ + sfx_1882.wav \ + sfx_1883.wav \ + sfx_1884.wav \ + sfx_1885.wav \ + sfx_1886.wav \ + sfx_1887.wav \ + sfx_1888.wav \ + sfx_1889.wav \ + sfx_189.wav \ + sfx_1890.wav \ + sfx_1891.wav \ + sfx_1892.wav \ + sfx_1893.wav \ + sfx_1894.wav \ + sfx_1895.wav \ + sfx_1896.wav \ + sfx_1897.wav \ + sfx_1898.wav \ + sfx_1899.wav \ + sfx_19.wav \ + sfx_190.wav \ + sfx_1900.wav \ + sfx_1901.wav \ + sfx_1902.wav \ + sfx_1903.wav \ + sfx_1904.wav \ + sfx_1905.wav \ + sfx_1906.wav \ + sfx_1907.wav \ + sfx_1908.wav \ + sfx_1909.wav \ + sfx_191.wav \ + sfx_1910.wav \ + sfx_1911.wav \ + sfx_1912.wav \ + sfx_1913.wav \ + sfx_1914.wav \ + sfx_1915.wav \ + sfx_1916.wav \ + sfx_1917.wav \ + sfx_1918.wav \ + sfx_1919.wav \ + sfx_192.wav \ + sfx_1920.wav \ + sfx_1921.wav \ + sfx_1922.wav \ + sfx_1923.wav \ + sfx_1924.wav \ + sfx_1925.wav \ + sfx_1926.wav \ + sfx_1927.wav \ + sfx_1928.wav \ + sfx_1929.wav \ + sfx_193.wav \ + sfx_1930.wav \ + sfx_1931.wav \ + sfx_1932.wav \ + sfx_1933.wav \ + sfx_1934.wav \ + sfx_1935.wav \ + sfx_1936.wav \ + sfx_1937.wav \ + sfx_1938.wav \ + sfx_1939.wav \ + sfx_194.wav \ + sfx_1940.wav \ + sfx_1941.wav \ + sfx_1942.wav \ + sfx_1943.wav \ + sfx_1944.wav \ + sfx_1945.wav \ + sfx_1946.wav \ + sfx_1947.wav \ + sfx_1948.wav \ + sfx_1949.wav \ + sfx_195.wav \ + sfx_1950.wav \ + sfx_1951.wav \ + sfx_1952.wav \ + sfx_1953.wav \ + sfx_1954.wav \ + sfx_1955.wav \ + sfx_1956.wav \ + sfx_1957.wav \ + sfx_1958.wav \ + sfx_1959.wav \ + sfx_196.wav \ + sfx_1960.wav \ + sfx_1961.wav \ + sfx_1962.wav \ + sfx_1963.wav \ + sfx_1964.wav \ + sfx_1965.wav \ + sfx_1966.wav \ + sfx_1967.wav \ + sfx_1968.wav \ + sfx_1969.wav \ + sfx_197.wav \ + sfx_1970.wav \ + sfx_1971.wav \ + sfx_1972.wav \ + sfx_1973.wav \ + sfx_1974.wav \ + sfx_1975.wav \ + sfx_1976.wav \ + sfx_1977.wav \ + sfx_1978.wav \ + sfx_1979.wav \ + sfx_198.wav \ + sfx_1980.wav \ + sfx_1981.wav \ + sfx_1982.wav \ + sfx_1983.wav \ + sfx_1984.wav \ + sfx_1985.wav \ + sfx_1986.wav \ + sfx_1987.wav \ + sfx_1988.wav \ + sfx_1989.wav \ + sfx_199.wav \ + sfx_1990.wav \ + sfx_1991.wav \ + sfx_1992.wav \ + sfx_1993.wav \ + sfx_1994.wav \ + sfx_1995.wav \ + sfx_1996.wav \ + sfx_1997.wav \ + sfx_1998.wav \ + sfx_1999.wav \ + sfx_2.wav \ + sfx_20.wav \ + sfx_200.wav \ + sfx_2000.wav \ + sfx_2001.wav \ + sfx_2002.wav \ + sfx_2003.wav \ + sfx_2004.wav \ + sfx_2005.wav \ + sfx_2006.wav \ + sfx_2007.wav \ + sfx_2008.wav \ + sfx_2009.wav \ + sfx_201.wav \ + sfx_2010.wav \ + sfx_2011.wav \ + sfx_2012.wav \ + sfx_2013.wav \ + sfx_2014.wav \ + sfx_2015.wav \ + sfx_2016.wav \ + sfx_2017.wav \ + sfx_2018.wav \ + sfx_2019.wav \ + sfx_202.wav \ + sfx_2020.wav \ + sfx_2021.wav \ + sfx_2022.wav \ + sfx_2023.wav \ + sfx_2024.wav \ + sfx_2025.wav \ + sfx_2026.wav \ + sfx_2027.wav \ + sfx_2028.wav \ + sfx_2029.wav \ + sfx_203.wav \ + sfx_2030.wav \ + sfx_2031.wav \ + sfx_2032.wav \ + sfx_2033.wav \ + sfx_2034.wav \ + sfx_2035.wav \ + sfx_2036.wav \ + sfx_2037.wav \ + sfx_2038.wav \ + sfx_2039.wav \ + sfx_204.wav \ + sfx_2040.wav \ + sfx_2041.wav \ + sfx_2042.wav \ + sfx_2043.wav \ + sfx_2044.wav \ + sfx_2045.wav \ + sfx_2046.wav \ + sfx_2047.wav \ + sfx_2048.wav \ + sfx_2049.wav \ + sfx_205.wav \ + sfx_2050.wav \ + sfx_2051.wav \ + sfx_2052.wav \ + sfx_2053.wav \ + sfx_2054.wav \ + sfx_2055.wav \ + sfx_2056.wav \ + sfx_2057.wav \ + sfx_2058.wav \ + sfx_2059.wav \ + sfx_206.wav \ + sfx_2060.wav \ + sfx_2061.wav \ + sfx_2062.wav \ + sfx_2063.wav \ + sfx_2064.wav \ + sfx_2065.wav \ + sfx_2066.wav \ + sfx_2067.wav \ + sfx_2068.wav \ + sfx_2069.wav \ + sfx_207.wav \ + sfx_2070.wav \ + sfx_2071.wav \ + sfx_2072.wav \ + sfx_2073.wav \ + sfx_2074.wav \ + sfx_2075.wav \ + sfx_2076.wav \ + sfx_2077.wav \ + sfx_2078.wav \ + sfx_2079.wav \ + sfx_208.wav \ + sfx_2080.wav \ + sfx_2081.wav \ + sfx_2082.wav \ + sfx_2083.wav \ + sfx_2084.wav \ + sfx_2085.wav \ + sfx_2086.wav \ + sfx_2087.wav \ + sfx_2088.wav \ + sfx_2089.wav \ + sfx_209.wav \ + sfx_2090.wav \ + sfx_2091.wav \ + sfx_2092.wav \ + sfx_2093.wav \ + sfx_2094.wav \ + sfx_2095.wav \ + sfx_2096.wav \ + sfx_2097.wav \ + sfx_2098.wav \ + sfx_2099.wav \ + sfx_21.wav \ + sfx_210.wav \ + sfx_2100.wav \ + sfx_2101.wav \ + sfx_2102.wav \ + sfx_2103.wav \ + sfx_2104.wav \ + sfx_2105.wav \ + sfx_2106.wav \ + sfx_2107.wav \ + sfx_2108.wav \ + sfx_2109.wav \ + sfx_211.wav \ + sfx_2110.wav \ + sfx_2111.wav \ + sfx_2112.wav \ + sfx_2113.wav \ + sfx_2114.wav \ + sfx_2115.wav \ + sfx_2116.wav \ + sfx_2117.wav \ + sfx_2118.wav \ + sfx_2119.wav \ + sfx_212.wav \ + sfx_2120.wav \ + sfx_2121.wav \ + sfx_2122.wav \ + sfx_2123.wav \ + sfx_2124.wav \ + sfx_2125.wav \ + sfx_2126.wav \ + sfx_2127.wav \ + sfx_2128.wav \ + sfx_2129.wav \ + sfx_213.wav \ + sfx_2130.wav \ + sfx_2131.wav \ + sfx_2132.wav \ + sfx_2133.wav \ + sfx_2134.wav \ + sfx_2135.wav \ + sfx_2136.wav \ + sfx_2137.wav \ + sfx_2138.wav \ + sfx_2139.wav \ + sfx_214.wav \ + sfx_2140.wav \ + sfx_2141.wav \ + sfx_2142.wav \ + sfx_2143.wav \ + sfx_2144.wav \ + sfx_2145.wav \ + sfx_2146.wav \ + sfx_2147.wav \ + sfx_2148.wav \ + sfx_2149.wav \ + sfx_215.wav \ + sfx_2150.wav \ + sfx_2151.wav \ + sfx_2152.wav \ + sfx_2153.wav \ + sfx_2154.wav \ + sfx_2155.wav \ + sfx_2156.wav \ + sfx_2157.wav \ + sfx_2158.wav \ + sfx_2159.wav \ + sfx_216.wav \ + sfx_2160.wav \ + sfx_2161.wav \ + sfx_2162.wav \ + sfx_2163.wav \ + sfx_2164.wav \ + sfx_2165.wav \ + sfx_2166.wav \ + sfx_2167.wav \ + sfx_2168.wav \ + sfx_2169.wav \ + sfx_217.wav \ + sfx_2170.wav \ + sfx_2171.wav \ + sfx_2172.wav \ + sfx_2173.wav \ + sfx_2174.wav \ + sfx_2175.wav \ + sfx_2176.wav \ + sfx_2177.wav \ + sfx_2178.wav \ + sfx_2179.wav \ + sfx_218.wav \ + sfx_2180.wav \ + sfx_2181.wav \ + sfx_2182.wav \ + sfx_2183.wav \ + sfx_2184.wav \ + sfx_2185.wav \ + sfx_2186.wav \ + sfx_2187.wav \ + sfx_2188.wav \ + sfx_2189.wav \ + sfx_219.wav \ + sfx_2190.wav \ + sfx_2191.wav \ + sfx_2192.wav \ + sfx_2193.wav \ + sfx_2194.wav \ + sfx_2195.wav \ + sfx_2196.wav \ + sfx_2197.wav \ + sfx_2198.wav \ + sfx_2199.wav \ + sfx_22.wav \ + sfx_220.wav \ + sfx_2200.wav \ + sfx_2201.wav \ + sfx_2202.wav \ + sfx_2203.wav \ + sfx_2204.wav \ + sfx_2205.wav \ + sfx_2206.wav \ + sfx_2207.wav \ + sfx_2208.wav \ + sfx_2209.wav \ + sfx_221.wav \ + sfx_2210.wav \ + sfx_2211.wav \ + sfx_2212.wav \ + sfx_2213.wav \ + sfx_2214.wav \ + sfx_2215.wav \ + sfx_2216.wav \ + sfx_2217.wav \ + sfx_2218.wav \ + sfx_2219.wav \ + sfx_222.wav \ + sfx_2220.wav \ + sfx_2221.wav \ + sfx_2222.wav \ + sfx_2223.wav \ + sfx_2224.wav \ + sfx_2225.wav \ + sfx_2226.wav \ + sfx_2227.wav \ + sfx_2228.wav \ + sfx_2229.wav \ + sfx_223.wav \ + sfx_2230.wav \ + sfx_2231.wav \ + sfx_2232.wav \ + sfx_2233.wav \ + sfx_2234.wav \ + sfx_2235.wav \ + sfx_2236.wav \ + sfx_2237.wav \ + sfx_2238.wav \ + sfx_2239.wav \ + sfx_224.wav \ + sfx_2240.wav \ + sfx_2241.wav \ + sfx_2242.wav \ + sfx_2243.wav \ + sfx_2244.wav \ + sfx_2245.wav \ + sfx_2246.wav \ + sfx_2247.wav \ + sfx_2248.wav \ + sfx_2249.wav \ + sfx_225.wav \ + sfx_2250.wav \ + sfx_2251.wav \ + sfx_2252.wav \ + sfx_2253.wav \ + sfx_2254.wav \ + sfx_2255.wav \ + sfx_2256.wav \ + sfx_2257.wav \ + sfx_2258.wav \ + sfx_2259.wav \ + sfx_226.wav \ + sfx_2260.wav \ + sfx_2261.wav \ + sfx_2262.wav \ + sfx_2263.wav \ + sfx_2264.wav \ + sfx_2265.wav \ + sfx_2266.wav \ + sfx_2267.wav \ + sfx_2268.wav \ + sfx_2269.wav \ + sfx_227.wav \ + sfx_2270.wav \ + sfx_2271.wav \ + sfx_2272.wav \ + sfx_2273.wav \ + sfx_2274.wav \ + sfx_2275.wav \ + sfx_2276.wav \ + sfx_2277.wav \ + sfx_2278.wav \ + sfx_2279.wav \ + sfx_228.wav \ + sfx_2280.wav \ + sfx_2281.wav \ + sfx_2282.wav \ + sfx_2283.wav \ + sfx_2284.wav \ + sfx_2285.wav \ + sfx_2286.wav \ + sfx_2287.wav \ + sfx_2288.wav \ + sfx_2289.wav \ + sfx_229.wav \ + sfx_2290.wav \ + sfx_2291.wav \ + sfx_2292.wav \ + sfx_2293.wav \ + sfx_2294.wav \ + sfx_2295.wav \ + sfx_2296.wav \ + sfx_2297.wav \ + sfx_2298.wav \ + sfx_2299.wav \ + sfx_23.wav \ + sfx_230.wav \ + sfx_2300.wav \ + sfx_2301.wav \ + sfx_2302.wav \ + sfx_2303.wav \ + sfx_2304.wav \ + sfx_2305.wav \ + sfx_2306.wav \ + sfx_2307.wav \ + sfx_2308.wav \ + sfx_2309.wav \ + sfx_231.wav \ + sfx_2310.wav \ + sfx_2311.wav \ + sfx_2312.wav \ + sfx_2313.wav \ + sfx_2314.wav \ + sfx_2315.wav \ + sfx_2316.wav \ + sfx_2317.wav \ + sfx_2318.wav \ + sfx_2319.wav \ + sfx_232.wav \ + sfx_2320.wav \ + sfx_2321.wav \ + sfx_2322.wav \ + sfx_2323.wav \ + sfx_2324.wav \ + sfx_2325.wav \ + sfx_2326.wav \ + sfx_2327.wav \ + sfx_2328.wav \ + sfx_2329.wav \ + sfx_233.wav \ + sfx_2330.wav \ + sfx_2331.wav \ + sfx_2332.wav \ + sfx_2333.wav \ + sfx_2334.wav \ + sfx_2335.wav \ + sfx_2336.wav \ + sfx_2337.wav \ + sfx_2338.wav \ + sfx_2339.wav \ + sfx_234.wav \ + sfx_2340.wav \ + sfx_2341.wav \ + sfx_2342.wav \ + sfx_2343.wav \ + sfx_2344.wav \ + sfx_2345.wav \ + sfx_2346.wav \ + sfx_2347.wav \ + sfx_2348.wav \ + sfx_2349.wav \ + sfx_235.wav \ + sfx_2350.wav \ + sfx_2351.wav \ + sfx_2352.wav \ + sfx_2353.wav \ + sfx_2354.wav \ + sfx_2355.wav \ + sfx_2356.wav \ + sfx_2357.wav \ + sfx_2358.wav \ + sfx_2359.wav \ + sfx_236.wav \ + sfx_2360.wav \ + sfx_2361.wav \ + sfx_2362.wav \ + sfx_2363.wav \ + sfx_2364.wav \ + sfx_2365.wav \ + sfx_2366.wav \ + sfx_2367.wav \ + sfx_2368.wav \ + sfx_2369.wav \ + sfx_237.wav \ + sfx_2370.wav \ + sfx_2371.wav \ + sfx_2372.wav \ + sfx_2373.wav \ + sfx_2374.wav \ + sfx_2375.wav \ + sfx_2376.wav \ + sfx_2377.wav \ + sfx_2378.wav \ + sfx_2379.wav \ + sfx_238.wav \ + sfx_2380.wav \ + sfx_2381.wav \ + sfx_2382.wav \ + sfx_2383.wav \ + sfx_2384.wav \ + sfx_2385.wav \ + sfx_2386.wav \ + sfx_2387.wav \ + sfx_2388.wav \ + sfx_2389.wav \ + sfx_239.wav \ + sfx_2390.wav \ + sfx_2391.wav \ + sfx_2392.wav \ + sfx_2393.wav \ + sfx_2394.wav \ + sfx_2395.wav \ + sfx_2396.wav \ + sfx_2397.wav \ + sfx_2398.wav \ + sfx_2399.wav \ + sfx_24.wav \ + sfx_240.wav \ + sfx_2400.wav \ + sfx_2401.wav \ + sfx_2402.wav \ + sfx_2403.wav \ + sfx_2404.wav \ + sfx_2405.wav \ + sfx_2406.wav \ + sfx_2407.wav \ + sfx_2408.wav \ + sfx_2409.wav \ + sfx_241.wav \ + sfx_2410.wav \ + sfx_2411.wav \ + sfx_2412.wav \ + sfx_2413.wav \ + sfx_2414.wav \ + sfx_2415.wav \ + sfx_2416.wav \ + sfx_2417.wav \ + sfx_2418.wav \ + sfx_2419.wav \ + sfx_242.wav \ + sfx_2420.wav \ + sfx_2421.wav \ + sfx_2422.wav \ + sfx_2423.wav \ + sfx_2424.wav \ + sfx_2425.wav \ + sfx_2426.wav \ + sfx_2427.wav \ + sfx_2428.wav \ + sfx_2429.wav \ + sfx_243.wav \ + sfx_2430.wav \ + sfx_2431.wav \ + sfx_2432.wav \ + sfx_2433.wav \ + sfx_2434.wav \ + sfx_2435.wav \ + sfx_2436.wav \ + sfx_2437.wav \ + sfx_2438.wav \ + sfx_2439.wav \ + sfx_244.wav \ + sfx_2440.wav \ + sfx_2441.wav \ + sfx_2442.wav \ + sfx_2443.wav \ + sfx_2444.wav \ + sfx_2445.wav \ + sfx_2446.wav \ + sfx_2447.wav \ + sfx_2448.wav \ + sfx_2449.wav \ + sfx_245.wav \ + sfx_2450.wav \ + sfx_2451.wav \ + sfx_2452.wav \ + sfx_2453.wav \ + sfx_2454.wav \ + sfx_2455.wav \ + sfx_2456.wav \ + sfx_2457.wav \ + sfx_2458.wav \ + sfx_2459.wav \ + sfx_246.wav \ + sfx_2460.wav \ + sfx_2461.wav \ + sfx_2462.wav \ + sfx_2463.wav \ + sfx_2464.wav \ + sfx_2465.wav \ + sfx_2466.wav \ + sfx_2467.wav \ + sfx_2468.wav \ + sfx_2469.wav \ + sfx_247.wav \ + sfx_2470.wav \ + sfx_2471.wav \ + sfx_2472.wav \ + sfx_2473.wav \ + sfx_2474.wav \ + sfx_2475.wav \ + sfx_2476.wav \ + sfx_2477.wav \ + sfx_2478.wav \ + sfx_2479.wav \ + sfx_248.wav \ + sfx_2480.wav \ + sfx_2481.wav \ + sfx_2482.wav \ + sfx_2483.wav \ + sfx_2484.wav \ + sfx_2485.wav \ + sfx_2486.wav \ + sfx_2487.wav \ + sfx_2488.wav \ + sfx_2489.wav \ + sfx_249.wav \ + sfx_2490.wav \ + sfx_2491.wav \ + sfx_2492.wav \ + sfx_2493.wav \ + sfx_2494.wav \ + sfx_2495.wav \ + sfx_2496.wav \ + sfx_2497.wav \ + sfx_2498.wav \ + sfx_2499.wav \ + sfx_25.wav \ + sfx_250.wav \ + sfx_2500.wav \ + sfx_2501.wav \ + sfx_2502.wav \ + sfx_2503.wav \ + sfx_2504.wav \ + sfx_2505.wav \ + sfx_2506.wav \ + sfx_2507.wav \ + sfx_2508.wav \ + sfx_2509.wav \ + sfx_251.wav \ + sfx_2510.wav \ + sfx_2511.wav \ + sfx_2512.wav \ + sfx_2513.wav \ + sfx_2514.wav \ + sfx_2515.wav \ + sfx_2516.wav \ + sfx_2517.wav \ + sfx_2518.wav \ + sfx_2519.wav \ + sfx_252.wav \ + sfx_2520.wav \ + sfx_2521.wav \ + sfx_2522.wav \ + sfx_2523.wav \ + sfx_2524.wav \ + sfx_2525.wav \ + sfx_2526.wav \ + sfx_2527.wav \ + sfx_2528.wav \ + sfx_2529.wav \ + sfx_253.wav \ + sfx_2530.wav \ + sfx_2531.wav \ + sfx_2532.wav \ + sfx_2533.wav \ + sfx_2534.wav \ + sfx_2535.wav \ + sfx_2536.wav \ + sfx_2537.wav \ + sfx_2538.wav \ + sfx_2539.wav \ + sfx_254.wav \ + sfx_2540.wav \ + sfx_2541.wav \ + sfx_2542.wav \ + sfx_2543.wav \ + sfx_2544.wav \ + sfx_2545.wav \ + sfx_2546.wav \ + sfx_2547.wav \ + sfx_2548.wav \ + sfx_2549.wav \ + sfx_255.wav \ + sfx_2550.wav \ + sfx_2551.wav \ + sfx_2552.wav \ + sfx_2553.wav \ + sfx_2554.wav \ + sfx_2555.wav \ + sfx_2556.wav \ + sfx_2557.wav \ + sfx_2558.wav \ + sfx_2559.wav \ + sfx_256.wav \ + sfx_2560.wav \ + sfx_2561.wav \ + sfx_2562.wav \ + sfx_2563.wav \ + sfx_2564.wav \ + sfx_2565.wav \ + sfx_2566.wav \ + sfx_2567.wav \ + sfx_2568.wav \ + sfx_2569.wav \ + sfx_257.wav \ + sfx_2570.wav \ + sfx_2571.wav \ + sfx_2572.wav \ + sfx_2573.wav \ + sfx_2574.wav \ + sfx_2575.wav \ + sfx_2576.wav \ + sfx_2577.wav \ + sfx_2578.wav \ + sfx_2579.wav \ + sfx_258.wav \ + sfx_2580.wav \ + sfx_2581.wav \ + sfx_2582.wav \ + sfx_2583.wav \ + sfx_2584.wav \ + sfx_2585.wav \ + sfx_2586.wav \ + sfx_2587.wav \ + sfx_2588.wav \ + sfx_2589.wav \ + sfx_259.wav \ + sfx_2590.wav \ + sfx_2591.wav \ + sfx_2592.wav \ + sfx_2593.wav \ + sfx_2594.wav \ + sfx_2595.wav \ + sfx_2596.wav \ + sfx_2597.wav \ + sfx_2598.wav \ + sfx_2599.wav \ + sfx_26.wav \ + sfx_260.wav \ + sfx_2600.wav \ + sfx_2601.wav \ + sfx_2602.wav \ + sfx_2603.wav \ + sfx_2604.wav \ + sfx_2605.wav \ + sfx_2606.wav \ + sfx_2607.wav \ + sfx_2608.wav \ + sfx_2609.wav \ + sfx_261.wav \ + sfx_2610.wav \ + sfx_2611.wav \ + sfx_2612.wav \ + sfx_2613.wav \ + sfx_2614.wav \ + sfx_2615.wav \ + sfx_2616.wav \ + sfx_2617.wav \ + sfx_2618.wav \ + sfx_2619.wav \ + sfx_262.wav \ + sfx_2620.wav \ + sfx_2621.wav \ + sfx_2622.wav \ + sfx_2623.wav \ + sfx_2624.wav \ + sfx_2625.wav \ + sfx_2626.wav \ + sfx_2627.wav \ + sfx_2628.wav \ + sfx_2629.wav \ + sfx_263.wav \ + sfx_2630.wav \ + sfx_2631.wav \ + sfx_2632.wav \ + sfx_2633.wav \ + sfx_2634.wav \ + sfx_2635.wav \ + sfx_2636.wav \ + sfx_2637.wav \ + sfx_2638.wav \ + sfx_2639.wav \ + sfx_264.wav \ + sfx_2640.wav \ + sfx_2641.wav \ + sfx_2642.wav \ + sfx_2643.wav \ + sfx_2644.wav \ + sfx_2645.wav \ + sfx_2646.wav \ + sfx_2647.wav \ + sfx_2648.wav \ + sfx_2649.wav \ + sfx_265.wav \ + sfx_2650.wav \ + sfx_2651.wav \ + sfx_2652.wav \ + sfx_2653.wav \ + sfx_2654.wav \ + sfx_2655.wav \ + sfx_2656.wav \ + sfx_2657.wav \ + sfx_2658.wav \ + sfx_2659.wav \ + sfx_266.wav \ + sfx_2660.wav \ + sfx_2661.wav \ + sfx_2662.wav \ + sfx_2663.wav \ + sfx_2664.wav \ + sfx_2665.wav \ + sfx_2666.wav \ + sfx_2667.wav \ + sfx_2668.wav \ + sfx_2669.wav \ + sfx_267.wav \ + sfx_2670.wav \ + sfx_2671.wav \ + sfx_2672.wav \ + sfx_2673.wav \ + sfx_2674.wav \ + sfx_2675.wav \ + sfx_2676.wav \ + sfx_2677.wav \ + sfx_2678.wav \ + sfx_2679.wav \ + sfx_268.wav \ + sfx_2680.wav \ + sfx_2681.wav \ + sfx_2682.wav \ + sfx_2683.wav \ + sfx_2684.wav \ + sfx_2685.wav \ + sfx_2686.wav \ + sfx_2687.wav \ + sfx_2688.wav \ + sfx_2689.wav \ + sfx_269.wav \ + sfx_2690.wav \ + sfx_2691.wav \ + sfx_2692.wav \ + sfx_2693.wav \ + sfx_2694.wav \ + sfx_2695.wav \ + sfx_2696.wav \ + sfx_2697.wav \ + sfx_2698.wav \ + sfx_2699.wav \ + sfx_27.wav \ + sfx_270.wav \ + sfx_2700.wav \ + sfx_2701.wav \ + sfx_2702.wav \ + sfx_2703.wav \ + sfx_2704.wav \ + sfx_2705.wav \ + sfx_2706.wav \ + sfx_2707.wav \ + sfx_2708.wav \ + sfx_2709.wav \ + sfx_271.wav \ + sfx_2710.wav \ + sfx_2711.wav \ + sfx_2712.wav \ + sfx_2713.wav \ + sfx_2714.wav \ + sfx_2715.wav \ + sfx_2716.wav \ + sfx_2717.wav \ + sfx_2718.wav \ + sfx_2719.wav \ + sfx_272.wav \ + sfx_2720.wav \ + sfx_2721.wav \ + sfx_2722.wav \ + sfx_2723.wav \ + sfx_2724.wav \ + sfx_2725.wav \ + sfx_2726.wav \ + sfx_2727.wav \ + sfx_2728.wav \ + sfx_2729.wav \ + sfx_273.wav \ + sfx_2730.wav \ + sfx_2731.wav \ + sfx_2732.wav \ + sfx_2733.wav \ + sfx_2734.wav \ + sfx_2735.wav \ + sfx_2736.wav \ + sfx_2737.wav \ + sfx_2738.wav \ + sfx_2739.wav \ + sfx_274.wav \ + sfx_2740.wav \ + sfx_2741.wav \ + sfx_2742.wav \ + sfx_2743.wav \ + sfx_2744.wav \ + sfx_2745.wav \ + sfx_2746.wav \ + sfx_2747.wav \ + sfx_2748.wav \ + sfx_2749.wav \ + sfx_275.wav \ + sfx_2750.wav \ + sfx_2751.wav \ + sfx_2752.wav \ + sfx_2753.wav \ + sfx_2754.wav \ + sfx_2755.wav \ + sfx_2756.wav \ + sfx_2757.wav \ + sfx_2758.wav \ + sfx_2759.wav \ + sfx_276.wav \ + sfx_2760.wav \ + sfx_2761.wav \ + sfx_2762.wav \ + sfx_2763.wav \ + sfx_2764.wav \ + sfx_2765.wav \ + sfx_2766.wav \ + sfx_2767.wav \ + sfx_2768.wav \ + sfx_2769.wav \ + sfx_277.wav \ + sfx_2770.wav \ + sfx_2771.wav \ + sfx_2772.wav \ + sfx_2773.wav \ + sfx_2774.wav \ + sfx_2775.wav \ + sfx_2776.wav \ + sfx_2777.wav \ + sfx_2778.wav \ + sfx_2779.wav \ + sfx_278.wav \ + sfx_2780.wav \ + sfx_2781.wav \ + sfx_2782.wav \ + sfx_2783.wav \ + sfx_2784.wav \ + sfx_2785.wav \ + sfx_2786.wav \ + sfx_2787.wav \ + sfx_2788.wav \ + sfx_2789.wav \ + sfx_279.wav \ + sfx_2790.wav \ + sfx_2791.wav \ + sfx_2792.wav \ + sfx_2793.wav \ + sfx_2794.wav \ + sfx_2795.wav \ + sfx_2796.wav \ + sfx_2797.wav \ + sfx_2798.wav \ + sfx_2799.wav \ + sfx_28.wav \ + sfx_280.wav \ + sfx_2800.wav \ + sfx_2801.wav \ + sfx_2802.wav \ + sfx_2803.wav \ + sfx_2804.wav \ + sfx_2805.wav \ + sfx_2806.wav \ + sfx_2807.wav \ + sfx_2808.wav \ + sfx_2809.wav \ + sfx_281.wav \ + sfx_2810.wav \ + sfx_2811.wav \ + sfx_2812.wav \ + sfx_2813.wav \ + sfx_2814.wav \ + sfx_2815.wav \ + sfx_2816.wav \ + sfx_2817.wav \ + sfx_2818.wav \ + sfx_2819.wav \ + sfx_282.wav \ + sfx_2820.wav \ + sfx_2821.wav \ + sfx_2822.wav \ + sfx_2823.wav \ + sfx_2824.wav \ + sfx_2825.wav \ + sfx_2826.wav \ + sfx_2827.wav \ + sfx_2828.wav \ + sfx_2829.wav \ + sfx_283.wav \ + sfx_2830.wav \ + sfx_2831.wav \ + sfx_2832.wav \ + sfx_2833.wav \ + sfx_2834.wav \ + sfx_2835.wav \ + sfx_2836.wav \ + sfx_2837.wav \ + sfx_2838.wav \ + sfx_2839.wav \ + sfx_284.wav \ + sfx_2840.wav \ + sfx_2841.wav \ + sfx_2842.wav \ + sfx_2843.wav \ + sfx_2844.wav \ + sfx_2845.wav \ + sfx_2846.wav \ + sfx_2847.wav \ + sfx_2848.wav \ + sfx_2849.wav \ + sfx_285.wav \ + sfx_2850.wav \ + sfx_2851.wav \ + sfx_2852.wav \ + sfx_2853.wav \ + sfx_2854.wav \ + sfx_2855.wav \ + sfx_2856.wav \ + sfx_2857.wav \ + sfx_2858.wav \ + sfx_2859.wav \ + sfx_286.wav \ + sfx_2860.wav \ + sfx_2861.wav \ + sfx_2862.wav \ + sfx_2863.wav \ + sfx_2864.wav \ + sfx_2865.wav \ + sfx_2866.wav \ + sfx_2867.wav \ + sfx_2868.wav \ + sfx_2869.wav \ + sfx_287.wav \ + sfx_2870.wav \ + sfx_2871.wav \ + sfx_2872.wav \ + sfx_2873.wav \ + sfx_2874.wav \ + sfx_2875.wav \ + sfx_2876.wav \ + sfx_2877.wav \ + sfx_2878.wav \ + sfx_2879.wav \ + sfx_288.wav \ + sfx_2880.wav \ + sfx_2881.wav \ + sfx_2882.wav \ + sfx_2883.wav \ + sfx_2884.wav \ + sfx_2885.wav \ + sfx_2886.wav \ + sfx_2887.wav \ + sfx_2888.wav \ + sfx_2889.wav \ + sfx_289.wav \ + sfx_2890.wav \ + sfx_2891.wav \ + sfx_2892.wav \ + sfx_2893.wav \ + sfx_2894.wav \ + sfx_2895.wav \ + sfx_2896.wav \ + sfx_2897.wav \ + sfx_2898.wav \ + sfx_2899.wav \ + sfx_29.wav \ + sfx_290.wav \ + sfx_2900.wav \ + sfx_2901.wav \ + sfx_2902.wav \ + sfx_2903.wav \ + sfx_2904.wav \ + sfx_2905.wav \ + sfx_2906.wav \ + sfx_2907.wav \ + sfx_2908.wav \ + sfx_2909.wav \ + sfx_291.wav \ + sfx_2910.wav \ + sfx_2911.wav \ + sfx_2912.wav \ + sfx_2913.wav \ + sfx_2914.wav \ + sfx_2915.wav \ + sfx_2916.wav \ + sfx_2917.wav \ + sfx_2918.wav \ + sfx_2919.wav \ + sfx_292.wav \ + sfx_2920.wav \ + sfx_2921.wav \ + sfx_2922.wav \ + sfx_2923.wav \ + sfx_2924.wav \ + sfx_2925.wav \ + sfx_2926.wav \ + sfx_2927.wav \ + sfx_2928.wav \ + sfx_2929.wav \ + sfx_293.wav \ + sfx_2930.wav \ + sfx_2931.wav \ + sfx_2932.wav \ + sfx_2933.wav \ + sfx_2934.wav \ + sfx_2935.wav \ + sfx_2936.wav \ + sfx_2937.wav \ + sfx_2938.wav \ + sfx_2939.wav \ + sfx_294.wav \ + sfx_2940.wav \ + sfx_2941.wav \ + sfx_2942.wav \ + sfx_2943.wav \ + sfx_2944.wav \ + sfx_2945.wav \ + sfx_2946.wav \ + sfx_2947.wav \ + sfx_2948.wav \ + sfx_2949.wav \ + sfx_295.wav \ + sfx_2950.wav \ + sfx_2951.wav \ + sfx_2952.wav \ + sfx_2953.wav \ + sfx_2954.wav \ + sfx_2955.wav \ + sfx_2956.wav \ + sfx_2957.wav \ + sfx_2958.wav \ + sfx_2959.wav \ + sfx_296.wav \ + sfx_2960.wav \ + sfx_2961.wav \ + sfx_2962.wav \ + sfx_2963.wav \ + sfx_2964.wav \ + sfx_2965.wav \ + sfx_2966.wav \ + sfx_2967.wav \ + sfx_2968.wav \ + sfx_2969.wav \ + sfx_297.wav \ + sfx_2970.wav \ + sfx_2971.wav \ + sfx_2972.wav \ + sfx_2973.wav \ + sfx_2974.wav \ + sfx_2975.wav \ + sfx_2976.wav \ + sfx_2977.wav \ + sfx_2978.wav \ + sfx_2979.wav \ + sfx_298.wav \ + sfx_2980.wav \ + sfx_2981.wav \ + sfx_2982.wav \ + sfx_2983.wav \ + sfx_2984.wav \ + sfx_2985.wav \ + sfx_2986.wav \ + sfx_2987.wav \ + sfx_2988.wav \ + sfx_2989.wav \ + sfx_299.wav \ + sfx_2990.wav \ + sfx_2991.wav \ + sfx_2992.wav \ + sfx_2993.wav \ + sfx_2994.wav \ + sfx_2995.wav \ + sfx_2996.wav \ + sfx_2997.wav \ + sfx_2998.wav \ + sfx_2999.wav \ + sfx_3.wav \ + sfx_30.wav \ + sfx_300.wav \ + sfx_3000.wav \ + sfx_3001.wav \ + sfx_3002.wav \ + sfx_3003.wav \ + sfx_3004.wav \ + sfx_3005.wav \ + sfx_3006.wav \ + sfx_3007.wav \ + sfx_3008.wav \ + sfx_3009.wav \ + sfx_301.wav \ + sfx_3010.wav \ + sfx_3011.wav \ + sfx_3012.wav \ + sfx_3013.wav \ + sfx_3014.wav \ + sfx_3015.wav \ + sfx_3016.wav \ + sfx_3017.wav \ + sfx_3018.wav \ + sfx_3019.wav \ + sfx_302.wav \ + sfx_3020.wav \ + sfx_3021.wav \ + sfx_3022.wav \ + sfx_3023.wav \ + sfx_3024.wav \ + sfx_3025.wav \ + sfx_3026.wav \ + sfx_3027.wav \ + sfx_3028.wav \ + sfx_3029.wav \ + sfx_303.wav \ + sfx_3030.wav \ + sfx_3031.wav \ + sfx_304.wav \ + sfx_305.wav \ + sfx_306.wav \ + sfx_307.wav \ + sfx_308.wav \ + sfx_309.wav \ + sfx_31.wav \ + sfx_310.wav \ + sfx_311.wav \ + sfx_312.wav \ + sfx_313.wav \ + sfx_314.wav \ + sfx_315.wav \ + sfx_316.wav \ + sfx_317.wav \ + sfx_318.wav \ + sfx_319.wav \ + sfx_32.wav \ + sfx_320.wav \ + sfx_321.wav \ + sfx_322.wav \ + sfx_323.wav \ + sfx_324.wav \ + sfx_325.wav \ + sfx_326.wav \ + sfx_327.wav \ + sfx_328.wav \ + sfx_329.wav \ + sfx_33.wav \ + sfx_330.wav \ + sfx_331.wav \ + sfx_332.wav \ + sfx_333.wav \ + sfx_334.wav \ + sfx_335.wav \ + sfx_336.wav \ + sfx_337.wav \ + sfx_338.wav \ + sfx_339.wav \ + sfx_34.wav \ + sfx_340.wav \ + sfx_341.wav \ + sfx_342.wav \ + sfx_343.wav \ + sfx_344.wav \ + sfx_345.wav \ + sfx_346.wav \ + sfx_347.wav \ + sfx_348.wav \ + sfx_349.wav \ + sfx_35.wav \ + sfx_350.wav \ + sfx_351.wav \ + sfx_352.wav \ + sfx_353.wav \ + sfx_354.wav \ + sfx_355.wav \ + sfx_356.wav \ + sfx_357.wav \ + sfx_358.wav \ + sfx_359.wav \ + sfx_36.wav \ + sfx_360.wav \ + sfx_361.wav \ + sfx_362.wav \ + sfx_363.wav \ + sfx_364.wav \ + sfx_365.wav \ + sfx_366.wav \ + sfx_367.wav \ + sfx_368.wav \ + sfx_369.wav \ + sfx_37.wav \ + sfx_370.wav \ + sfx_371.wav \ + sfx_372.wav \ + sfx_373.wav \ + sfx_374.wav \ + sfx_375.wav \ + sfx_376.wav \ + sfx_377.wav \ + sfx_378.wav \ + sfx_379.wav \ + sfx_38.wav \ + sfx_380.wav \ + sfx_381.wav \ + sfx_382.wav \ + sfx_383.wav \ + sfx_384.wav \ + sfx_385.wav \ + sfx_386.wav \ + sfx_387.wav \ + sfx_388.wav \ + sfx_389.wav \ + sfx_39.wav \ + sfx_390.wav \ + sfx_391.wav \ + sfx_392.wav \ + sfx_393.wav \ + sfx_394.wav \ + sfx_395.wav \ + sfx_396.wav \ + sfx_397.wav \ + sfx_398.wav \ + sfx_399.wav \ + sfx_4.wav \ + sfx_40.wav \ + sfx_400.wav \ + sfx_401.wav \ + sfx_402.wav \ + sfx_403.wav \ + sfx_404.wav \ + sfx_405.wav \ + sfx_406.wav \ + sfx_407.wav \ + sfx_408.wav \ + sfx_409.wav \ + sfx_41.wav \ + sfx_410.wav \ + sfx_411.wav \ + sfx_412.wav \ + sfx_413.wav \ + sfx_414.wav \ + sfx_415.wav \ + sfx_416.wav \ + sfx_417.wav \ + sfx_418.wav \ + sfx_419.wav \ + sfx_42.wav \ + sfx_420.wav \ + sfx_421.wav \ + sfx_422.wav \ + sfx_423.wav \ + sfx_424.wav \ + sfx_425.wav \ + sfx_426.wav \ + sfx_427.wav \ + sfx_428.wav \ + sfx_429.wav \ + sfx_43.wav \ + sfx_430.wav \ + sfx_431.wav \ + sfx_432.wav \ + sfx_433.wav \ + sfx_434.wav \ + sfx_435.wav \ + sfx_436.wav \ + sfx_437.wav \ + sfx_438.wav \ + sfx_439.wav \ + sfx_44.wav \ + sfx_440.wav \ + sfx_441.wav \ + sfx_442.wav \ + sfx_443.wav \ + sfx_444.wav \ + sfx_445.wav \ + sfx_446.wav \ + sfx_447.wav \ + sfx_448.wav \ + sfx_449.wav \ + sfx_45.wav \ + sfx_450.wav \ + sfx_451.wav \ + sfx_452.wav \ + sfx_453.wav \ + sfx_454.wav \ + sfx_455.wav \ + sfx_456.wav \ + sfx_457.wav \ + sfx_458.wav \ + sfx_459.wav \ + sfx_46.wav \ + sfx_460.wav \ + sfx_461.wav \ + sfx_462.wav \ + sfx_463.wav \ + sfx_464.wav \ + sfx_465.wav \ + sfx_466.wav \ + sfx_467.wav \ + sfx_468.wav \ + sfx_469.wav \ + sfx_47.wav \ + sfx_470.wav \ + sfx_471.wav \ + sfx_472.wav \ + sfx_473.wav \ + sfx_474.wav \ + sfx_475.wav \ + sfx_476.wav \ + sfx_477.wav \ + sfx_478.wav \ + sfx_479.wav \ + sfx_48.wav \ + sfx_480.wav \ + sfx_481.wav \ + sfx_482.wav \ + sfx_483.wav \ + sfx_484.wav \ + sfx_485.wav \ + sfx_486.wav \ + sfx_487.wav \ + sfx_488.wav \ + sfx_489.wav \ + sfx_49.wav \ + sfx_490.wav \ + sfx_491.wav \ + sfx_492.wav \ + sfx_493.wav \ + sfx_494.wav \ + sfx_495.wav \ + sfx_496.wav \ + sfx_497.wav \ + sfx_498.wav \ + sfx_499.wav \ + sfx_5.wav \ + sfx_50.wav \ + sfx_500.wav \ + sfx_501.wav \ + sfx_502.wav \ + sfx_503.wav \ + sfx_504.wav \ + sfx_505.wav \ + sfx_506.wav \ + sfx_507.wav \ + sfx_508.wav \ + sfx_509.wav \ + sfx_51.wav \ + sfx_510.wav \ + sfx_511.wav \ + sfx_512.wav \ + sfx_513.wav \ + sfx_514.wav \ + sfx_515.wav \ + sfx_516.wav \ + sfx_517.wav \ + sfx_518.wav \ + sfx_519.wav \ + sfx_52.wav \ + sfx_520.wav \ + sfx_521.wav \ + sfx_522.wav \ + sfx_523.wav \ + sfx_524.wav \ + sfx_525.wav \ + sfx_526.wav \ + sfx_527.wav \ + sfx_528.wav \ + sfx_529.wav \ + sfx_53.wav \ + sfx_530.wav \ + sfx_531.wav \ + sfx_532.wav \ + sfx_533.wav \ + sfx_534.wav \ + sfx_535.wav \ + sfx_536.wav \ + sfx_537.wav \ + sfx_538.wav \ + sfx_539.wav \ + sfx_54.wav \ + sfx_540.wav \ + sfx_541.wav \ + sfx_542.wav \ + sfx_543.wav \ + sfx_544.wav \ + sfx_545.wav \ + sfx_546.wav \ + sfx_547.wav \ + sfx_548.wav \ + sfx_549.wav \ + sfx_55.wav \ + sfx_550.wav \ + sfx_551.wav \ + sfx_552.wav \ + sfx_553.wav \ + sfx_554.wav \ + sfx_555.wav \ + sfx_556.wav \ + sfx_557.wav \ + sfx_558.wav \ + sfx_559.wav \ + sfx_56.wav \ + sfx_560.wav \ + sfx_561.wav \ + sfx_562.wav \ + sfx_563.wav \ + sfx_564.wav \ + sfx_565.wav \ + sfx_566.wav \ + sfx_567.wav \ + sfx_568.wav \ + sfx_569.wav \ + sfx_57.wav \ + sfx_570.wav \ + sfx_571.wav \ + sfx_572.wav \ + sfx_573.wav \ + sfx_574.wav \ + sfx_575.wav \ + sfx_576.wav \ + sfx_577.wav \ + sfx_578.wav \ + sfx_579.wav \ + sfx_58.wav \ + sfx_580.wav \ + sfx_581.wav \ + sfx_582.wav \ + sfx_583.wav \ + sfx_584.wav \ + sfx_585.wav \ + sfx_586.wav \ + sfx_587.wav \ + sfx_588.wav \ + sfx_589.wav \ + sfx_59.wav \ + sfx_590.wav \ + sfx_591.wav \ + sfx_592.wav \ + sfx_593.wav \ + sfx_594.wav \ + sfx_595.wav \ + sfx_596.wav \ + sfx_597.wav \ + sfx_598.wav \ + sfx_599.wav \ + sfx_6.wav \ + sfx_60.wav \ + sfx_600.wav \ + sfx_601.wav \ + sfx_602.wav \ + sfx_603.wav \ + sfx_604.wav \ + sfx_605.wav \ + sfx_606.wav \ + sfx_607.wav \ + sfx_608.wav \ + sfx_609.wav \ + sfx_61.wav \ + sfx_610.wav \ + sfx_611.wav \ + sfx_612.wav \ + sfx_613.wav \ + sfx_614.wav \ + sfx_615.wav \ + sfx_616.wav \ + sfx_617.wav \ + sfx_618.wav \ + sfx_619.wav \ + sfx_62.wav \ + sfx_620.wav \ + sfx_621.wav \ + sfx_622.wav \ + sfx_623.wav \ + sfx_624.wav \ + sfx_625.wav \ + sfx_626.wav \ + sfx_627.wav \ + sfx_628.wav \ + sfx_629.wav \ + sfx_63.wav \ + sfx_630.wav \ + sfx_631.wav \ + sfx_632.wav \ + sfx_633.wav \ + sfx_634.wav \ + sfx_635.wav \ + sfx_636.wav \ + sfx_637.wav \ + sfx_638.wav \ + sfx_639.wav \ + sfx_64.wav \ + sfx_640.wav \ + sfx_641.wav \ + sfx_642.wav \ + sfx_643.wav \ + sfx_644.wav \ + sfx_645.wav \ + sfx_646.wav \ + sfx_647.wav \ + sfx_648.wav \ + sfx_649.wav \ + sfx_65.wav \ + sfx_650.wav \ + sfx_651.wav \ + sfx_652.wav \ + sfx_653.wav \ + sfx_654.wav \ + sfx_655.wav \ + sfx_656.wav \ + sfx_657.wav \ + sfx_658.wav \ + sfx_659.wav \ + sfx_66.wav \ + sfx_660.wav \ + sfx_661.wav \ + sfx_662.wav \ + sfx_663.wav \ + sfx_664.wav \ + sfx_665.wav \ + sfx_666.wav \ + sfx_667.wav \ + sfx_668.wav \ + sfx_669.wav \ + sfx_67.wav \ + sfx_670.wav \ + sfx_671.wav \ + sfx_672.wav \ + sfx_673.wav \ + sfx_674.wav \ + sfx_675.wav \ + sfx_676.wav \ + sfx_677.wav \ + sfx_678.wav \ + sfx_679.wav \ + sfx_68.wav \ + sfx_680.wav \ + sfx_681.wav \ + sfx_682.wav \ + sfx_683.wav \ + sfx_684.wav \ + sfx_685.wav \ + sfx_686.wav \ + sfx_687.wav \ + sfx_688.wav \ + sfx_689.wav \ + sfx_69.wav \ + sfx_690.wav \ + sfx_691.wav \ + sfx_692.wav \ + sfx_693.wav \ + sfx_694.wav \ + sfx_695.wav \ + sfx_696.wav \ + sfx_697.wav \ + sfx_698.wav \ + sfx_699.wav \ + sfx_7.wav \ + sfx_70.wav \ + sfx_700.wav \ + sfx_701.wav \ + sfx_702.wav \ + sfx_703.wav \ + sfx_704.wav \ + sfx_705.wav \ + sfx_706.wav \ + sfx_707.wav \ + sfx_708.wav \ + sfx_709.wav \ + sfx_71.wav \ + sfx_710.wav \ + sfx_711.wav \ + sfx_712.wav \ + sfx_713.wav \ + sfx_714.wav \ + sfx_715.wav \ + sfx_716.wav \ + sfx_717.wav \ + sfx_718.wav \ + sfx_719.wav \ + sfx_72.wav \ + sfx_720.wav \ + sfx_721.wav \ + sfx_722.wav \ + sfx_723.wav \ + sfx_724.wav \ + sfx_725.wav \ + sfx_726.wav \ + sfx_727.wav \ + sfx_728.wav \ + sfx_729.wav \ + sfx_73.wav \ + sfx_730.wav \ + sfx_731.wav \ + sfx_732.wav \ + sfx_733.wav \ + sfx_734.wav \ + sfx_735.wav \ + sfx_736.wav \ + sfx_737.wav \ + sfx_738.wav \ + sfx_739.wav \ + sfx_74.wav \ + sfx_740.wav \ + sfx_741.wav \ + sfx_742.wav \ + sfx_743.wav \ + sfx_744.wav \ + sfx_745.wav \ + sfx_746.wav \ + sfx_747.wav \ + sfx_748.wav \ + sfx_749.wav \ + sfx_75.wav \ + sfx_750.wav \ + sfx_751.wav \ + sfx_752.wav \ + sfx_753.wav \ + sfx_754.wav \ + sfx_755.wav \ + sfx_756.wav \ + sfx_757.wav \ + sfx_758.wav \ + sfx_759.wav \ + sfx_76.wav \ + sfx_760.wav \ + sfx_761.wav \ + sfx_762.wav \ + sfx_763.wav \ + sfx_764.wav \ + sfx_765.wav \ + sfx_766.wav \ + sfx_767.wav \ + sfx_768.wav \ + sfx_769.wav \ + sfx_77.wav \ + sfx_770.wav \ + sfx_771.wav \ + sfx_772.wav \ + sfx_773.wav \ + sfx_774.wav \ + sfx_775.wav \ + sfx_776.wav \ + sfx_777.wav \ + sfx_778.wav \ + sfx_779.wav \ + sfx_78.wav \ + sfx_780.wav \ + sfx_781.wav \ + sfx_782.wav \ + sfx_783.wav \ + sfx_784.wav \ + sfx_785.wav \ + sfx_786.wav \ + sfx_787.wav \ + sfx_788.wav \ + sfx_789.wav \ + sfx_79.wav \ + sfx_790.wav \ + sfx_791.wav \ + sfx_792.wav \ + sfx_793.wav \ + sfx_794.wav \ + sfx_795.wav \ + sfx_796.wav \ + sfx_797.wav \ + sfx_798.wav \ + sfx_799.wav \ + sfx_8.wav \ + sfx_80.wav \ + sfx_800.wav \ + sfx_801.wav \ + sfx_802.wav \ + sfx_803.wav \ + sfx_804.wav \ + sfx_805.wav \ + sfx_806.wav \ + sfx_807.wav \ + sfx_808.wav \ + sfx_809.wav \ + sfx_81.wav \ + sfx_810.wav \ + sfx_811.wav \ + sfx_812.wav \ + sfx_813.wav \ + sfx_814.wav \ + sfx_815.wav \ + sfx_816.wav \ + sfx_817.wav \ + sfx_818.wav \ + sfx_819.wav \ + sfx_82.wav \ + sfx_820.wav \ + sfx_821.wav \ + sfx_822.wav \ + sfx_823.wav \ + sfx_824.wav \ + sfx_825.wav \ + sfx_826.wav \ + sfx_827.wav \ + sfx_828.wav \ + sfx_829.wav \ + sfx_83.wav \ + sfx_830.wav \ + sfx_831.wav \ + sfx_832.wav \ + sfx_833.wav \ + sfx_834.wav \ + sfx_835.wav \ + sfx_836.wav \ + sfx_837.wav \ + sfx_838.wav \ + sfx_839.wav \ + sfx_84.wav \ + sfx_840.wav \ + sfx_841.wav \ + sfx_842.wav \ + sfx_843.wav \ + sfx_844.wav \ + sfx_845.wav \ + sfx_846.wav \ + sfx_847.wav \ + sfx_848.wav \ + sfx_849.wav \ + sfx_85.wav \ + sfx_850.wav \ + sfx_851.wav \ + sfx_852.wav \ + sfx_853.wav \ + sfx_854.wav \ + sfx_855.wav \ + sfx_856.wav \ + sfx_857.wav \ + sfx_858.wav \ + sfx_859.wav \ + sfx_86.wav \ + sfx_860.wav \ + sfx_861.wav \ + sfx_862.wav \ + sfx_863.wav \ + sfx_864.wav \ + sfx_865.wav \ + sfx_866.wav \ + sfx_867.wav \ + sfx_868.wav \ + sfx_869.wav \ + sfx_87.wav \ + sfx_870.wav \ + sfx_871.wav \ + sfx_872.wav \ + sfx_873.wav \ + sfx_874.wav \ + sfx_875.wav \ + sfx_876.wav \ + sfx_877.wav \ + sfx_878.wav \ + sfx_879.wav \ + sfx_88.wav \ + sfx_880.wav \ + sfx_881.wav \ + sfx_882.wav \ + sfx_883.wav \ + sfx_884.wav \ + sfx_885.wav \ + sfx_886.wav \ + sfx_887.wav \ + sfx_888.wav \ + sfx_889.wav \ + sfx_89.wav \ + sfx_890.wav \ + sfx_891.wav \ + sfx_892.wav \ + sfx_893.wav \ + sfx_894.wav \ + sfx_895.wav \ + sfx_896.wav \ + sfx_897.wav \ + sfx_898.wav \ + sfx_899.wav \ + sfx_9.wav \ + sfx_90.wav \ + sfx_900.wav \ + sfx_901.wav \ + sfx_902.wav \ + sfx_903.wav \ + sfx_904.wav \ + sfx_905.wav \ + sfx_906.wav \ + sfx_907.wav \ + sfx_908.wav \ + sfx_909.wav \ + sfx_91.wav \ + sfx_910.wav \ + sfx_911.wav \ + sfx_912.wav \ + sfx_913.wav \ + sfx_914.wav \ + sfx_915.wav \ + sfx_916.wav \ + sfx_917.wav \ + sfx_918.wav \ + sfx_919.wav \ + sfx_92.wav \ + sfx_920.wav \ + sfx_921.wav \ + sfx_922.wav \ + sfx_923.wav \ + sfx_924.wav \ + sfx_925.wav \ + sfx_926.wav \ + sfx_927.wav \ + sfx_928.wav \ + sfx_929.wav \ + sfx_93.wav \ + sfx_930.wav \ + sfx_931.wav \ + sfx_932.wav \ + sfx_933.wav \ + sfx_934.wav \ + sfx_935.wav \ + sfx_936.wav \ + sfx_937.wav \ + sfx_938.wav \ + sfx_939.wav \ + sfx_94.wav \ + sfx_940.wav \ + sfx_941.wav \ + sfx_942.wav \ + sfx_943.wav \ + sfx_944.wav \ + sfx_945.wav \ + sfx_946.wav \ + sfx_947.wav \ + sfx_948.wav \ + sfx_949.wav \ + sfx_95.wav \ + sfx_950.wav \ + sfx_951.wav \ + sfx_952.wav \ + sfx_953.wav \ + sfx_954.wav \ + sfx_955.wav \ + sfx_956.wav \ + sfx_957.wav \ + sfx_958.wav \ + sfx_959.wav \ + sfx_96.wav \ + sfx_960.wav \ + sfx_961.wav \ + sfx_962.wav \ + sfx_963.wav \ + sfx_964.wav \ + sfx_965.wav \ + sfx_966.wav \ + sfx_967.wav \ + sfx_968.wav \ + sfx_969.wav \ + sfx_97.wav \ + sfx_970.wav \ + sfx_971.wav \ + sfx_972.wav \ + sfx_973.wav \ + sfx_974.wav \ + sfx_975.wav \ + sfx_976.wav \ + sfx_977.wav \ + sfx_978.wav \ + sfx_979.wav \ + sfx_98.wav \ + sfx_980.wav \ + sfx_981.wav \ + sfx_982.wav \ + sfx_983.wav \ + sfx_984.wav \ + sfx_985.wav \ + sfx_986.wav \ + sfx_987.wav \ + sfx_988.wav \ + sfx_989.wav \ + sfx_99.wav \ + sfx_990.wav \ + sfx_991.wav \ + sfx_992.wav \ + sfx_993.wav \ + sfx_994.wav \ + sfx_995.wav \ + sfx_996.wav \ + sfx_997.wav \ + sfx_998.wav \ + sfx_999.wav \ No newline at end of file diff --git a/dreamcast/sfxlooplist.mk b/dreamcast/sfxlooplist.mk new file mode 100644 index 00000000..2e579a85 --- /dev/null +++ b/dreamcast/sfxlooplist.mk @@ -0,0 +1,23 @@ +SFX_LOOP_WAV = \ + sfx_0_loop.wav \ + sfx_185_loop.wav \ + sfx_19_loop.wav \ + sfx_1_loop.wav \ + sfx_2_loop.wav \ + sfx_311_loop.wav \ + sfx_331_loop.wav \ + sfx_346_loop.wav \ + sfx_349_loop.wav \ + sfx_352_loop.wav \ + sfx_355_loop.wav \ + sfx_358_loop.wav \ + sfx_361_loop.wav \ + sfx_364_loop.wav \ + sfx_367_loop.wav \ + sfx_3_loop.wav \ + sfx_438_loop.wav \ + sfx_4_loop.wav \ + sfx_5_loop.wav \ + sfx_6_loop.wav \ + sfx_7_loop.wav \ + sfx_82_loop.wav diff --git a/dreamcast/sim.mk b/dreamcast/sim.mk new file mode 100644 index 00000000..883fd032 --- /dev/null +++ b/dreamcast/sim.mk @@ -0,0 +1,67 @@ +TARGET ?= dca3-sim.elf + +IS_MAC := $(shell uname -s | grep -i "darwin" > /dev/null && echo "yes" || echo "no") + + +include common.mk + +OBJS = $(RE3_OBJS) $(RW_OBJS) \ + ../src/audio/sampman_null.o + +OBJS_SIM=$(OBJS:.o=.sim.o) \ + ../vendor/koshle/hlekos.sim.o \ + ../vendor/koshle/hlematrix3d.sim.o \ + ../vendor/koshle/hlepvr_mem.sim.o \ + ../vendor/koshle/hlepvr_prim.sim.o \ + ../vendor/koshle/hlepvr_scene.sim.o \ + ../vendor/koshle/hlepvr_misc.sim.o \ + ../vendor/koshle/hlepvr_init_term.sim.o \ + ../vendor/koshle/hlepvr_buffers.sim.o \ + ../vendor/koshle/hlepvr_irq.sim.o \ + ../vendor/koshle/hlepvr_fog.sim.o \ + \ + ../vendor/emu/emu/window.sim.o \ + \ + ../vendor/emu/lxdream/tacore.sim.o3 \ + \ + ../vendor/emu/refsw/pvr_mem.sim.o3 \ + ../vendor/emu/refsw/pvr_regs.sim.o3 \ + ../vendor/emu/refsw/refsw_lists.sim.o3 \ + ../vendor/emu/refsw/refsw_tile.sim.o3 \ + ../vendor/emu/refsw/TexUtils.sim.o3 \ + +DEPS_SIM1=$(OBJS_SIM:.o=.d) +DEPS_SIM=$(DEPS_SIM1:.o3=.d) + +CXXFLAGS+= -MMD -MP + +ifeq ($(IS_MAC), yes) +%.sim.o: %.c + $(CC) -c -O0 -g -fno-pic -no-pie -o $@ $(CFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 -DMACOS64 $< +%.sim.o: %.cpp + $(CXX) -c -O0 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 -DMACOS64 $< +%.sim.o3: %.cpp + $(CXX) -c -O3 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 -DMACOS64 $< +else +# Using sse2 here for valgrind compatibility +%.sim.o: %.c + $(CC) -msse2 -mfpmath=sse -c -O0 -g -fno-pic -no-pie -o $@ $(CFLAGS) -I../vendor/koshle -I../vendor/emu -m32 -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 $< +%.sim.o: %.cpp + $(CXX) -msse2 -mfpmath=sse -c -O0 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -m32 -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 $< +%.sim.o3: %.cpp + $(CXX) -msse2 -mfpmath=sse -c -O3 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -m32 -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 $< +endif + +all: $(TARGET) + +clean: + -rm -f $(OBJS_SIM) $(TARGET) + +ifeq ($(IS_MAC), yes) +$(TARGET): $(OBJS_SIM) + $(CXX) -fno-pic -no-pie -o $(TARGET) $(OBJS_SIM) -lX11 +else +$(TARGET): $(OBJS_SIM) + $(CXX) -m32 -fno-pic -no-pie -o $(TARGET) $(OBJS_SIM) -lX11 +endif +-include $(DEPS_SIM) \ No newline at end of file diff --git a/dreamcast/texconv.cpp b/dreamcast/texconv.cpp new file mode 100644 index 00000000..d1aec04f --- /dev/null +++ b/dreamcast/texconv.cpp @@ -0,0 +1,321 @@ +#include +#include +#include + +#include + +#include +using namespace std; + +void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func) +{ + printf("\nRE3 ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); + fflush(stdout); + assert(false); +} + +#include "dc_hle_types.h" + +#define debug(f, ...) + +#include "rwcore.h" +#include "rwplcore.h" +#include "rpworld.h" +#include "skeleton.h" +#include "TxdStore.h" +#include "rpskin.h" +#include "rphanim.h" +#include "RpAnimBlend.h" +#include "rpmatfx.h" +#include "NodeName.h" +#include "rpanisot.h" +#include "common.h" +#include "VisibilityPlugins.h" + +#include "src/dc/rwdc.h" +#include "src/dc/tex-util.h" + +#include + +#define debug(...) + +// stubs +uint8_t emu_vram[PVR_RAM_SIZE]; + +void emu_init() {} +void emu_term() {} +void emu_pump_events() {} +void pvr_queue_interrupt(int interrupt) {} +uint32 pvrRegRead(uint32 A) {return 0;} +void pvrRegWrite(uint32 A, uint32 D) {} +uint32_t pvr_map32(uint32_t offset32) {return 0;} +void Hackpresent() { } +void re3RemoveLeastUsedModel() { assert(false); } +void RwTexDictionaryGtaStreamRead1(rw::Stream*){ assert(false); } +void RwTexDictionaryGtaStreamRead2(rw::Stream*, rw::TexDictionary*) { assert(false); } +void pvr_ta_data(void* data, int size) { + assert(false); +} + +namespace rw::dc { + extern int32 maxRasterWidth; + extern int32 maxRasterHeight; + extern int32 downsampleMode; + extern int32 pvrEncoder; +} + +RwTexDictionary *LoadTxd(RwStream *stream); +void StoreTxd(RwTexDictionary *texDict, RwStream *stream); + + + +#ifdef LIBRW +#define READNATIVE(stream, tex, size) rwNativeTextureHackRead(stream, tex, size) +#else +#define READNATIVE(stream, tex, size) RWSRCGLOBAL(stdFunc[rwSTANDARDNATIVETEXTUREREAD](stream, tex, size)) +#endif + +void RwTextureGtaStreamWrite(RwStream *stream, RwTexture* texture) +{ + auto fheader = stream->tell(); + // size will be written later + rw::writeChunkHeader(stream, rwID_TEXTURENATIVE, 0); + auto fbegin = stream->tell(); + + texture->streamWriteNative(stream); + + // rewrite header with correct size + auto fend = stream->tell(); + stream->seek(fheader, 0); + rw::writeChunkHeader(stream, rwID_TEXTURENATIVE, fend - fbegin); + stream->seek(fend, 0); +} +RwTexture* +RwTextureGtaStreamRead(RwStream *stream) +{ + RwUInt32 size, version; + RwTexture *tex; + + if(!RwStreamFindChunk(stream, rwID_TEXTURENATIVE, &size, &version)) + return nil; + + if(!READNATIVE(stream, &tex, size)) + return nil; + + return tex; +} + +void +RwTexDictionaryGtaStreamWrite(RwStream *stream, RwTexDictionary *texDict) { + rw::writeChunkHeader(stream, rwID_STRUCT, 4); + stream->writeI32(texDict->count()); + + RwTexDictionaryForAllTextures(texDict, [](RwTexture *texture, void* vstream) { + RwTextureGtaStreamWrite((RwStream*)vstream, texture); + return texture; + }, stream); +} + +RwTexDictionary* +RwTexDictionaryGtaStreamRead(RwStream *stream) +{ + RwUInt32 size, version; + RwInt32 numTextures; + RwTexDictionary *texDict; + RwTexture *tex; + + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + if(RwStreamRead(stream, &numTextures, size) != size) + return nil; + + texDict = RwTexDictionaryCreate(); + if(texDict == nil) + return nil; + + while(numTextures--){ + tex = RwTextureGtaStreamRead(stream); + if(tex == nil){ + return nil; + } + RwTexDictionaryAddTexture(texDict, tex); + } + + return texDict; +} + +RpClump *LoadModelFile(const char *filename) +{ + RwStream *stream; + RpClump *clump = nullptr; + + debug("Loading model file %s\n", filename); + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + clump = RpClumpStreamRead(stream); + } + RwStreamClose(stream, nil); + assert(clump); + return clump; +} + +void StoreModelFile(const char *filename, RpClump *clump) +{ + RwStream *stream; + + debug("Storing model file %s\n", filename); + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, filename); + + // on write it includes rwCLUMP + auto ok = RpClumpStreamWrite(clump, stream); + assert(ok); + + RwStreamClose(stream, nil); + assert(clump); +} + + +static RwBool +PluginAttach(void) +{ + if( !RpWorldPluginAttach() ) + { + printf("Couldn't attach world plugin\n"); + + return FALSE; + } + + if( !RpSkinPluginAttach() ) + { + printf("Couldn't attach RpSkin plugin\n"); + + return FALSE; + } + + if( !RpHAnimPluginAttach() ) + { + printf("Couldn't attach RpHAnim plugin\n"); + + return FALSE; + } + + if( !NodeNamePluginAttach() ) + { + printf("Couldn't attach node name plugin\n"); + + return FALSE; + } + + if( !CVisibilityPlugins::PluginAttach() ) + { + printf("Couldn't attach visibility plugins\n"); + + return FALSE; + } + + if( !RpAnimBlendPluginAttach() ) + { + printf("Couldn't attach RpAnimBlend plugin\n"); + + return FALSE; + } + + if( !RpMatFXPluginAttach() ) + { + printf("Couldn't attach RpMatFX plugin\n"); + + return FALSE; + } +#ifdef ANISOTROPIC_FILTERING + RpAnisotPluginAttach(); +#endif +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeRegister(); +#endif + + return TRUE; +} + +const char* currentFile; + +int main(int argc, const char** argv) { + if (argc >= 5) { + int width = atoi(argv[3]); + int height = atoi(argv[4]); + if(width >= 16 && width <= 1024) + rw::dc::maxRasterWidth = width; + if(height >= 16 && height <= 1024) + rw::dc::maxRasterHeight = height; + } + for (int i = 0; i < argc; i++) { + if (argv[i] != nullptr) { + // Downsample Parameter + if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "-D") == 0) { + if (i + 1 < argc) { + const char* param = argv[i + 1]; + if (strcmp(param, "half") == 0 || strcmp(param, "HALF") == 0 || strcmp(param, "h") == 0) { + rw::dc::downsampleMode = HALF; + } else if (strcmp(param, "quarter") == 0 || strcmp(param, "QUARTER") == 0 || strcmp(param, "q") == 0) { + rw::dc::downsampleMode = QUARTER; + } + } + } + // PVR Encoder parameter + if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "-E") == 0) { + if (i + 1 < argc) { + const char* param = argv[i + 1]; + if (strcmp(param, "pvrtool") == 0 || strcmp(param, "PVRTOOL") == 0) { + rw::dc::pvrEncoder = PVRTOOL; + } else if (strcmp(param, "pvrtex") == 0 || strcmp(param, "PVRTEX") == 0) { + rw::dc::pvrEncoder = PVRTEX; + } + } + } + } + } + + printf("REPACK TXD: %s, Width: %i, Height: %i, Downsample: %s, Encoder: %s\n", + argv[1], + rw::dc::maxRasterWidth, + rw::dc::maxRasterHeight, + rw::dc::downsampleMode == NONE ? "NONE" : rw::dc::downsampleMode == HALF ? "HALF" : "QUARTER", + rw::dc::pvrEncoder == PVRTOOL ? "PVRTOOL" : "PVRTEX"); + + RwEngineInit(nullptr, 0, rsRESOURCESDEFAULTARENASIZE); + RwEngineOpenParams openParams = { 0 }; + + assert(RwEngineOpen(&openParams)); + assert(PluginAttach() == TRUE); + assert(RwEngineStart()); + currentFile = argv[1]; + + if (strstr(argv[1], ".txd") || strstr(argv[1], ".TXD")) { + auto stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, argv[1]); + assert(stream && "failed to open input"); + + auto texDict = LoadTxd(stream); + assert(texDict && "Failed to LoadTxd"); + + RwStreamClose(stream, nil); + + auto streamOut = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, argv[2]); + assert(streamOut && "failed to open output"); + + StoreTxd(texDict, streamOut); + + RwStreamClose(streamOut, nil); + } else if (strstr(argv[1], ".dff") || strstr(argv[1], ".DFF")) { + rw::Texture::setLoadTextures(false); + rw::Texture::setCreateDummies(true); + auto clump = LoadModelFile(argv[1]); + FORLIST(lnk, clump->atomics) { + rw::Atomic::fromClump(lnk)->instance(); + } + StoreModelFile(argv[2], clump); + } else { + printf("Invalid format: %s\n", argv[1]); + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/dreamcast/texlist.mk b/dreamcast/texlist.mk new file mode 100644 index 00000000..008f5fef --- /dev/null +++ b/dreamcast/texlist.mk @@ -0,0 +1,718 @@ +IMG_TEXTURES = \ + 3d8ball.txd \ + 8ballfence.txd \ + 8balls.txd \ + CHANGEME.txd \ + GTAElift.txd \ + Italynrth.txd \ + TRUCKTRAIL.txd \ + aircarpark.txd \ + airgrndb.txd \ + airplane.txd \ + airport.txd \ + airportbits.txd \ + airporterminal.txd \ + airporterminal2.txd \ + airporterminal3.txd \ + airporterminal4.txd \ + airportfence.txd \ + airpshadow.txd \ + airstuff2.txd \ + amco.txd \ + archedbuilding.txd \ + area5b.txd \ + asukah.txd \ + avery.txd \ + barrelexpos.txd \ + barrier.txd \ + base.txd \ + basketballcrt.txd \ + basmntcrpark.txd \ + bball.txd \ + beach.txd \ + benches.txd \ + bigbuild.txd \ + bigsign.txd \ + bilboard.txd \ + billboard.txd \ + block4grn.txd \ + boats.txd \ + bodycast.txd \ + boigalarm.txd \ + bombdoor.txd \ + bonus.txd \ + boxes.txd \ + breifcase.txd \ + brgwall.txd \ + bridge.txd \ + bridgesupport.txd \ + bridgesupports.txd \ + bridgewee.txd \ + brokenbridge.txd \ + buildingsite.txd \ + buildinsite.txd \ + buildmainten5.txd \ + buildsite.txd \ + bullion.txd \ + buoy.txd \ + busdepot.txd \ + canopy.txd \ + cardbox.txd \ + carpark.txd \ + carparkbooth.txd \ + carparkfence.txd \ + casino.txd \ + casinogarden.txd \ + cath.txd \ + cempirest.txd \ + chimney.txd \ + china2big.txd \ + chinabanner.txd \ + chinaedge.txd \ + chinatowns.txd \ + chlight.txd \ + church.txd \ + clif22.txd \ + cliffwall.txd \ + coffee.txd \ + col1h.txd \ + colcomp4.txd \ + colcomp4b.txd \ + colcomp_new.txd \ + colmansion.txd \ + comSW_LOD.txd \ + com_centrebit.txd \ + com_police.txd \ + comartgaly.txd \ + combomb.txd \ + combridgesign.txd \ + combroadwy2.txd \ + combroadwyrd.txd \ + comdocks.txd \ + comhilo1.txd \ + comhilo3b.txd \ + comhilo3e.txd \ + comhilo3h.txd \ + comhilo3shps.txd \ + comhilo_tags.txd \ + comhospital.txd \ + comjetty.txd \ + comlights.txd \ + comlod1.txd \ + comlod2.txd \ + commuseumkb.txd \ + compark.txd \ + compier.txd \ + compolice.txd \ + compound.txd \ + comsbilboard02.txd \ + comsbilboard05.txd \ + comseroadbits.txd \ + comshopinc.txd \ + comsigns.txd \ + comsky3.txd \ + comsky5z.txd \ + comstadium.txd \ + comsub_b.txd \ + comsub_c.txd \ + comtenblock6b.txd \ + comtoilet.txd \ + comtop_jetty.txd \ + comtownhall.txd \ + comtunel.txd \ + comtunnels.txd \ + comunation.txd \ + comuni2.txd \ + condo.txd \ + cranes.txd \ + crap_jetty.txd \ + crate.txd \ + csitecutscene.txd \ + cskydark.txd \ + cskytosh.txd \ + ctenblock3c.txd \ + cunt.txd \ + curlyh.txd \ + cutbank.txd \ + cutbankgrafiti.txd \ + cutbuild1.txd \ + cutbuild2.txd \ + cutbuild3.txd \ + cutrearbuild.txd \ + cutroads.txd \ + cutrubbish.txd \ + cutscenecameras.txd \ + cutscenecar3.txd \ + cutwatertanks.txd \ + dam.txd \ + dambase.txd \ + damentrance.txd \ + damfence.txd \ + damissionfence.txd \ + dirtpass.txd \ + doccrane.txd \ + docentrance.txd \ + docfactories.txd \ + dockbit.txd \ + docklight.txd \ + docks.txd \ + docksbuild.txd \ + dockvats.txd \ + dockwall.txd \ + dogfact.txd \ + dogfood.txd \ + door.txd \ + drugs.txd \ + dynbarrels.txd \ + dynbuket.txd \ + dyncones.txd \ + dynhydrent.txd \ + dynjunk.txd \ + dynnewstnd.txd \ + dynnewstnd2.txd \ + dynphn.txd \ + dynpostbx.txd \ + dynrecycle.txd \ + dynsigns.txd \ + dyntraffic.txd \ + eighth.txd \ + electricgate.txd \ + escape.txd \ + factory.txd \ + factorym.txd \ + fdr_underpass.txd \ + fences.txd \ + fire_esc.txd \ + firescape.txd \ + firstbridge.txd \ + fishfactory.txd \ + fishmarket.txd \ + fishwall.txd \ + flatiron1b.txd \ + flatironew.txd \ + frankh.txd \ + frankiesbase.txd \ + frate.txd \ + fuckedcar.txd \ + fuzzball.txd \ + garagewood.txd \ + graveyard.txd \ + greasyjoes.txd \ + groundbitties.txd \ + heli1.txd \ + heli2.txd \ + helipad.txd \ + helixbarrier.txd \ + helixroad.txd \ + highbridge.txd \ + hospital2.txd \ + hotelkb.txd \ + icons.txd \ + importexp.txd \ + indalley.txd \ + indamcooffc.txd \ + indbar.txd \ + indbeach.txd \ + indbrownbil.txd \ + indchinablocks.txd \ + indchinabuild.txd \ + indchinaredgreen.txd \ + indchinassrt.txd \ + indclub.txd \ + inddbridge.txd \ + inddiner.txd \ + inddockland.txd \ + indhibuildns.txd \ + indhospital.txd \ + inditaly.txd \ + inditaly1.txd \ + indjos_door.txd \ + indjunk.txd \ + indland124.txd \ + indpier.txd \ + indpj.txd \ + indpolice.txd \ + indpoliceball.txd \ + indradio.txd \ + indredlitblk1b.txd \ + indredlitblk2.txd \ + indredlitblk4.txd \ + indredlitgrey.txd \ + indsupasave.txd \ + indtaxi.txd \ + indtunlrail.txd \ + indtunnel.txd \ + indust1.txd \ + indust2.txd \ + introbits.txd \ + islandLODsubCOM.txd \ + islandcomind.txd \ + islandcomsub.txd \ + islandlodind.txd \ + islandsubcom.txd \ + italyeast.txd \ + italyfalg.txd \ + italymisc.txd \ + italysht.txd \ + jetty.txd \ + joeyh.txd \ + joeys.txd \ + joeysext.txd \ + jumpbox.txd \ + junk.txd \ + keeperh.txd \ + kenjih.txd \ + kmricndo.txd \ + landsectioncomn.txd \ + laundrette.txd \ + lighthouse.txd \ + lititly.txd \ + localsignsub.txd \ + loveh.txd \ + lovies.txd \ + luggage.txd \ + luigiclub.txd \ + luigih.txd \ + maindrag.txd \ + maindragn.txd \ + maindragsb1.txd \ + maindragsb2.txd \ + mallab.txd \ + mariah.txd \ + masacre.txd \ + metal.txd \ + metals.txd \ + mickyh.txd \ + miguelh.txd \ + mikesairstuff.txd \ + mine.txd \ + mistyh.txd \ + mscpbarrier.txd \ + newbuildind.txd \ + newprojectland.txd \ + newramp.txd \ + newtownhall.txd \ + nipple.txd \ + noodle.txd \ + oddgar.txd \ + opera.txd \ + operahouse.txd \ + othersubside.txd \ + owaywall.txd \ + package.txd \ + petrol.txd \ + pharmas.txd \ + pinetrees.txd \ + pipes.txd \ + pipesc.txd \ + pjects.txd \ + pjs.txd \ + planters.txd \ + playerh.txd \ + playerint.txd \ + policind.txd \ + porn.txd \ + portabarrier.txd \ + portacabin.txd \ + posh_shopshit.txd \ + postersub.txd \ + powerbox.txd \ + projbits.txd \ + pumphouse.txd \ + pylon.txd \ + radar00.txd \ + radar01.txd \ + radar02.txd \ + radar03.txd \ + radar04.txd \ + radar05.txd \ + radar06.txd \ + radar07.txd \ + radar08.txd \ + radar09.txd \ + radar10.txd \ + radar11.txd \ + radar12.txd \ + radar13.txd \ + radar14.txd \ + radar15.txd \ + radar16.txd \ + radar17.txd \ + radar18.txd \ + radar19.txd \ + radar20.txd \ + radar21.txd \ + radar22.txd \ + radar23.txd \ + radar24.txd \ + radar25.txd \ + radar26.txd \ + radar27.txd \ + radar28.txd \ + radar29.txd \ + radar30.txd \ + radar31.txd \ + radar32.txd \ + radar33.txd \ + radar34.txd \ + radar35.txd \ + radar36.txd \ + radar37.txd \ + radar38.txd \ + radar39.txd \ + radar40.txd \ + radar41.txd \ + radar42.txd \ + radar43.txd \ + radar44.txd \ + radar45.txd \ + radar46.txd \ + radar47.txd \ + radar48.txd \ + radar49.txd \ + radar50.txd \ + radar51.txd \ + radar52.txd \ + radar53.txd \ + radar54.txd \ + radar55.txd \ + radar56.txd \ + radar57.txd \ + radar58.txd \ + radar59.txd \ + radar60.txd \ + radar61.txd \ + radar62.txd \ + radar63.txd \ + railway.txd \ + railwayg.txd \ + ramp2.txd \ + rayh.txd \ + rdpuddle.txd \ + rdsign02bk.txd \ + rdsign06.txd \ + rdsign14.txd \ + rdsign15.txd \ + rdsign17.txd \ + rdsign18bk.txd \ + rdsign19bk.txd \ + roadsign.txd \ + roadtunnel.txd \ + roadtunnelent.txd \ + rock.txd \ + rocky.txd \ + rubbish.txd \ + safe.txd \ + sal_inside.txd \ + salvatory.txd \ + sawmill.txd \ + sawmillground.txd \ + schoolbus.txd \ + securityhut.txd \ + ships.txd \ + signs.txd \ + singlelight.txd \ + smalllit.txd \ + smashbarr.txd \ + station.txd \ + sub_probridge.txd \ + sub_projects.txd \ + sub_villas.txd \ + subcanister.txd \ + subcarparc.txd \ + subcrates.txd \ + subentr.txd \ + subestbuild.txd \ + subfrates.txd \ + subglass.txd \ + subhangbox.txd \ + subhangfrate.txd \ + subhospital.txd \ + subind1.txd \ + subind2.txd \ + subind26.txd \ + subind3.txd \ + subind34.txd \ + subind4.txd \ + subind5.txd \ + subind6.txd \ + subind7.txd \ + subind8.txd \ + subindgate.txd \ + submonument.txd \ + subonramp.txd \ + subparts.txd \ + subpolice.txd \ + subsign1.txd \ + subsignpost.txd \ + subtunl10.txd \ + subuild1.txd \ + subuild10.txd \ + subuild2.txd \ + subuild3.txd \ + subuild4.txd \ + subuild5.txd \ + subuild6.txd \ + subuild7.txd \ + subuild8.txd \ + suburb1.txd \ + suburb2.txd \ + subvillabits.txd \ + subway.txd \ + subway1.txd \ + subway2.txd \ + subway_comtop.txd \ + subwayind.txd \ + subwayindt.txd \ + telegraph.txd \ + tenblk4.txd \ + tenblock6a.txd \ + tenblock6aa.txd \ + tenblocknrcp.txd \ + tenement_ground.txd \ + toilet.txd \ + tonyh.txd \ + tonys.txd \ + towerdoor.txd \ + towerlights.txd \ + trackshad.txd \ + trainstair.txd \ + trainstairst.txd \ + traintracks.txd \ + trainyard.txd \ + trees.txd \ + trees2.txd \ + treeshadow.txd \ + tunlentnew.txd \ + tunnel.txd \ + tunnels.txd \ + tw@t.txd \ + tyres.txd \ + tyrestack.txd \ + unclebj.txd \ + usedcar.txd \ + vege.txd \ + vend.txd \ + vertbridge.txd \ + walkway.txd \ + wall.txd \ + warehouse.txd \ + warehouses.txd \ + warehousesc.txd \ + washin.txd \ + wastebin.txd \ + watertower.txd \ + weebridge2.txd \ + windowlit.txd \ + wood.txd \ + woodpanels.txd \ + AIRTRAIN.TXD \ + AMBULAN.TXD \ + ARMY.TXD \ + ASUKA.TXD \ + BANKD.TXD \ + BANSHEE.TXD \ + BARRACKS.TXD \ + BELLYUP.TXD \ + BFINJECT.TXD \ + BLISTA.TXD \ + BOBCAT.TXD \ + BOGDOOR.TXD \ + BOMBER.TXD \ + BORGNINE.TXD \ + BRBOMB.TXD \ + BUS.TXD \ + BUTLER.TXD \ + B_MAN1.TXD \ + B_MAN2.TXD \ + B_MAN3.TXD \ + B_WOM1.TXD \ + B_WOM2.TXD \ + B_WOM3.TXD \ + CABBIE.TXD \ + CAS_MAN.TXD \ + CAS_WOM.TXD \ + CAT.TXD \ + CHEETAH.TXD \ + CHOPPER.TXD \ + CHUNKY.TXD \ + COACH.TXD \ + COL1.TXD \ + COL2.TXD \ + COL3.TXD \ + COLROB.TXD \ + COLT1.TXD \ + COLT2.TXD \ + COLUMB.TXD \ + CONST1.TXD \ + CONST2.TXD \ + COP.TXD \ + COP2.TXD \ + CORPSE.TXD \ + CRIMINAL01.TXD \ + CRIMINAL02.TXD \ + CS_BAN.TXD \ + CS_BOMB.TXD \ + CS_LOOT.TXD \ + CS_TRUK.TXD \ + CT_MAN1.TXD \ + CT_MAN2.TXD \ + CT_WOM1.TXD \ + CT_WOM2.TXD \ + CURLY.TXD \ + D4PROPS.TXD \ + DARKEL.TXD \ + DEADDODO.TXD \ + DEALER.TXD \ + DIABLOS.TXD \ + DOCKER1.TXD \ + DOCKER2.TXD \ + DODO.TXD \ + DONKY.TXD \ + EIGHT.TXD \ + EIGHT2.TXD \ + EITDOOR.TXD \ + ENFORCER.TXD \ + ESPERANT.TXD \ + FAN_MAN1.TXD \ + FAN_MAN2.TXD \ + FAN_WOM.TXD \ + FATFEMALE01.TXD \ + FATFEMALE02.TXD \ + FATMALE01.TXD \ + FATMALE02.TXD \ + FBI.TXD \ + FBICAR.TXD \ + FEMALE01.TXD \ + FEMALE02.TXD \ + FEMALE03.TXD \ + FIREMAN.TXD \ + FIRETRUK.TXD \ + FLATBED.TXD \ + FRANKIE.TXD \ + FULCASE.TXD \ + G.TXD \ + GANG01.TXD \ + GANG02.TXD \ + GANG03.TXD \ + GANG04.TXD \ + GANG05.TXD \ + GANG06.TXD \ + GANG07.TXD \ + GANG08.TXD \ + GANG09.TXD \ + GANG10.TXD \ + GANG11.TXD \ + GANG12.TXD \ + GANG13.TXD \ + GANG14.TXD \ + GANGP.TXD \ + GHOST.TXD \ + GOON.TXD \ + HOODS.TXD \ + HOS_MAN.TXD \ + HOS_WOM.TXD \ + IDAHO.TXD \ + INFERNUS.TXD \ + ISLANDIND.TXD \ + JOEDOOR.TXD \ + JOEY.TXD \ + JOEY2.TXD \ + KEEPER.TXD \ + KENJI.TXD \ + KURUMA.TXD \ + LANDSTAL.TXD \ + LIFT.TXD \ + LINERUN.TXD \ + LIPS.TXD \ + LI_MAN1.TXD \ + LI_MAN2.TXD \ + LI_WOM1.TXD \ + LI_WOM2.TXD \ + LOVE.TXD \ + LOVE2.TXD \ + LUDOOR.TXD \ + LUIGI.TXD \ + MAFIA.TXD \ + MALE01.TXD \ + MALE02.TXD \ + MALE03.TXD \ + MANANA.TXD \ + MARIA.TXD \ + MEDIC.TXD \ + MICKY.TXD \ + MIGUEL.TXD \ + MINNOTE.TXD \ + MISTY.TXD \ + MOD_MAN.TXD \ + MOD_WOM.TXD \ + MOONBEAM.TXD \ + MRWHOOP.TXD \ + MRWONGS.TXD \ + MULE.TXD \ + NOTE.TXD \ + NOVY.TXD \ + OJG.TXD \ + OJG2.TXD \ + OJG_P.TXD \ + PANLANT.TXD \ + PATRIOT.TXD \ + PEREN.TXD \ + PIMP.TXD \ + PLASTER.TXD \ + PLAYER.TXD \ + PLAYERP.TXD \ + PLAYERX.TXD \ + POLICE.TXD \ + PONY.TXD \ + PREDATOR.TXD \ + PROSTITUTE.TXD \ + PROSTITUTE2.TXD \ + P_MAN1.TXD \ + P_MAN2.TXD \ + P_WOM1.TXD \ + P_WOM2.TXD \ + RAY.TXD \ + RCBANDIT.TXD \ + REBEL.TXD \ + REEFER.TXD \ + RHINO.TXD \ + RIFLE.TXD \ + ROBBER.TXD \ + RUMPO.TXD \ + SAM.TXD \ + SCUM_MAN.TXD \ + SCUM_WOM.TXD \ + SECURICA.TXD \ + SENTINEL.TXD \ + SHDOOR.TXD \ + SHIP.TXD \ + SHOPPER1.TXD \ + SHOPPER2.TXD \ + SHOPPER3.TXD \ + SPEEDER.TXD \ + STALLION.TXD \ + STINGER.TXD \ + STRETCH.TXD \ + STUD_MAN.TXD \ + STUD_WOM.TXD \ + STU_MAN.TXD \ + STU_WOM.TXD \ + ST_MAN.TXD \ + ST_WOM.TXD \ + SWAT.TXD \ + S_GUARD.TXD \ + TANNER.TXD \ + TAXI.TXD \ + TAXI_D.TXD \ + TONY.TXD \ + TOYZ.TXD \ + TRAIN.TXD \ + TRASH.TXD \ + TROLL.TXD \ + WHIP.TXD \ + WORKER1.TXD \ + WORKER2.TXD \ + YAKUZA.TXD \ + YANKEE.TXD \ + YARDIE.TXD diff --git a/dreamcast/wavlist.mk b/dreamcast/wavlist.mk new file mode 100644 index 00000000..ff8b3590 --- /dev/null +++ b/dreamcast/wavlist.mk @@ -0,0 +1,127 @@ +STREAM_WAV = \ + A1_a.wav \ + AMMU_A.wav \ + AMMU_C.wav \ + AMMU_b.wav \ + CHAT.wav \ + CLASS.wav \ + COMopen.wav \ + City.wav \ + FLASH.wav \ + GAME.wav \ + HEAD.wav \ + K3_A.wav \ + KJAH.wav \ + LIPS.wav \ + MSX.wav \ + Miscom.wav \ + RISE.wav \ + SUBopen.wav \ + Water.wav \ + YD2_A.wav \ + a1_sso.wav \ + a2_pp.wav \ + a3_a.wav \ + a3_ss.wav \ + a4_a.wav \ + a4_b.wav \ + a4_c.wav \ + a4_d.wav \ + a4_pdr.wav \ + a5_a.wav \ + a5_k2ft.wav \ + a6_bait.wav \ + a7_etg.wav \ + a8_ps.wav \ + a9_asd.wav \ + cat1.wav \ + door_1.wav \ + door_2.wav \ + door_3.wav \ + door_4.wav \ + door_5.wav \ + door_6.wav \ + el3_a.wav \ + h5_a.wav \ + h5_b.wav \ + h5_c.wav \ + j4_a.wav \ + j4_b.wav \ + j4_c.wav \ + j4_d.wav \ + j4_e.wav \ + j4_f.wav \ + j4t_1.wav \ + j4t_2.wav \ + j4t_3.wav \ + j4t_4.wav \ + j6_1.wav \ + j6_a.wav \ + j6_b.wav \ + j6_c.wav \ + j6_d.wav \ + k1_a.wav \ + k1_b.wav \ + l2_a.wav \ + lib_a.wav \ + lib_a1.wav \ + lib_a2.wav \ + lib_b.wav \ + lib_c.wav \ + lib_d.wav \ + lo2_a.wav \ + lo6_a.wav \ + mf1_a.wav \ + mf2_a.wav \ + mf3_a.wav \ + mf3_b.wav \ + mf3_b1.wav \ + mf3_c.wav \ + mf4_a.wav \ + mf4_b.wav \ + mf4_c.wav \ + police.wav \ + r1_a.wav \ + r2_a.wav \ + r2_b.wav \ + r2_c.wav \ + r2_d.wav \ + r2_e.wav \ + r2_f.wav \ + r2_g.wav \ + r2_h.wav \ + r5_a.wav \ + r6_a.wav \ + r6_a1.wav \ + r6_b.wav \ + s1_a.wav \ + s1_a1.wav \ + s1_b.wav \ + s1_c.wav \ + s1_c1.wav \ + s1_d.wav \ + s1_e.wav \ + s1_f.wav \ + s1_g.wav \ + s1_h.wav \ + s1_i.wav \ + s1_j.wav \ + s1_k.wav \ + s1_l.wav \ + s3_a.wav \ + s3_b.wav \ + t3_a.wav \ + t3_b.wav \ + t3_c.wav \ + t4_a.wav \ + yd2_ass.wav \ + yd2_b.wav \ + yd2_c.wav \ + yd2_c1.wav \ + yd2_d.wav \ + yd2_e.wav \ + yd2_f.wav \ + yd2_g.wav \ + yd2_h.wav \ + yd2_ok.wav + \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 00000000..bb91a4cd --- /dev/null +++ b/public/index.html @@ -0,0 +1,102 @@ + + + + + + DCA3 - A port of RE3 for Dreamcast + + + + + + + + + +
+
+

DCA3

+

The first-ever port of Grand Theft Auto III to the SEGA Dreamcast, built from the REGTA reverse engineering project.

+ Download Now +
+
+ + +
+

Key Features

+
+
+

Faithful Port

+

Experience GTA3 as it was meant to be, now on the Dreamcast with okay visuals and sluggish gameplay.

+
+
+

Open Source

+

Built on the REGTA reverse engineering project, ensuring accuracy and community-driven development.

+
+
+

First Release

+

Celebrating our first release with not very much optimized performance and compatibility for Dreamcast hardware.

+
+
+
+ + +
+

Download DCA3

+

Get started with the first release of and bring GTA3 to your Dreamcast. You'll need a PC copy of GTA3 to create your .cdi

+ Download Version 1.0 +
+ + +
+

No rights reserved. Made with love for the Dreamcast community.

+
+ + + + + diff --git a/res/images/regta3dc_logo_1024.png b/res/images/regta3dc_logo_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..9ea0bf35e7b03e6190aa97c1587f60e08ecaa8b2 GIT binary patch literal 18513 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 literal 0 HcmV?d00001 diff --git a/res/images/screenshot_01.jpg b/res/images/screenshot_01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0a1010134dc4873c0d2eedec622802a082cfc372 GIT binary patch literal 196869 zcmeFZcU)6Tw=ldxR5(~5C`iWuL8TLlbVQ^EP>>R;(mT?-qM{H%dPxXfiuB%#g%T;D z6FMj@bc9d@%lS6=JkLG%p8MSQ`@Y}%d;fZeo!L|O%&fI$tur%Cn%mNKH+3j`ku1 zLVJ;xmX4lUZihJ6-7DX`-_58jZDRlpX!ZQ{LtfP4=gp{|$4Q&3KXBzochiBf?+xa`?oZSUzp%OPj{P_o z`u<9e=6ge5kL%EjAK%lrEWb?5`mNgyZcNHzU@dR@+szNznX z?}XoJUF?~8v&l(P#w~AujS@R9w-xj)J$3J$YSQOlKD&eRlFN^=2CXs&E4RL~nh%^J zv8(JF{^(r&^;urjU$7zSgLaj_&YYV+*ydkpz^;Xq{XP>WiLq9h5N- zO_dw}v>g!oE^vN`?Zq~`S->xOx}J2T#kNYrqA&j&!u~I}wbvIl=jw?AvjRt9ZX&Et zus=E9vlYrS52aD(Wv=wePj0sqZ3!L55!?c9x9#0*`FiL&EF=D<<4C1fF4Awt)~AbA zQ3PBDq9U%zU5fZ}*HAdc^d~~4kmQ-UB{S>Vo+_ZPd3)eUJLTsU5=(I; za$+xC?LyuADyOG|pz`H{fM3Exv|GF$S&Eet}tObz4sQp+!X zDnmcanFr#YtG7=7h*}E`>+B=4mUO)2d@}Uyot^LFpepUW#d+@ww&;V$=l0kvr@z5x zeBaqGs^v+w2W`FBwN?HT5ZJTjtV|E{FTFawk-{bz^ZjAw$KgGj*{IR6n`TMclW77+ z$Uc_^uRhu&!+`Wtt5xR$Q_WUf^}Yo4pY4%L-M$#;7VtxMh*gx7`qK3Fq$I0HyB68z z4~D9p4woP}_HpgUfU>_Me2An{?z^S#0_KpBLz}dE6 z_x7H|19D%!{t48v>z)lEf?Pz9x|!Z*7}q?|Nc{FB`+^|#WK`nY&GyY= zduB4i_x0fc4%5GX1WV!_yLfp+;q2@E+nruVt?dU#ZVC)d* zF%rdExhke9&~lF0BlomLE+}gG%~DHgi=0>U2CgS><@tw)M_0B}1mF5uMlFADCH{(P zkdeq*i;~D$S4m#{9y9kdkDQCtLd6WYPz4v2#CJ`~;2K+#8JoLPM8= z_H(WF^bmdLumKLN+d3-ZxNH+N*7yt&A}{&#HrPVw!tk@23*J&uUhs>t>; z8D{|ObcZT`omhYTyUPE2(*MyMu+Ju6WvgBYEl%g;DfYWAYuOoPxMN9>LGMMuen*~?ONKuM*Dv}QXALtnQi*t;sV4{{?4?E>RIia_H~|= z-!wv&G~hR@1OQY|xgY>HM2#Xt6(F~UyKyRY>ctYQ6f!~(NLouVqd#&5PH2ZB_#Y&6 ztlFuDgpqS8(4)X0g7zxaW!1142%x|LC_Yen|Bqt-{fo@(YzApj;Utrcu&`IpaiNx{ z$o(G)zq`Z{PX+dhLlxjsx5?yw5e~V|Z3J^s!-iEM4HMd67L_7;c1G=)uW~%7E{E#{ zA9F8?_h^9Y_@k5n1o{A?c>E~6=OR}iSA;$QC=irrAQ<`}m=bne=6`U19R3O1BK|`Z zo+|EKb61cG65z3x`F!`J*149@K7SfeJH5*Vq2h>Fjqj?`$lyy%e(_r0nh%i(xo@cV zP%r%cM+B8&lL>*Jfd&FmZ);7arh}YRCtqZy*VM-&_{O?&+Zjt7YE~Uf^+KkFv&*`z z-uLN34ur&=?0ef0yd!sB<*Hg$JS^#jhSP^n3w1a$ZDhV3d#}e`(61+?__6R708v!> zJsL0oKF8lD#U2^?F$}qNQrEO*n(OAXcNz z`yicui}dC0S4$=hh2neAmxe{%fky*2PE&_cZr#Owx0P#>r#lyQct0E51xi*omW zgP+w!+c=hNXvIyMil_+YfzEnYE}%acI1ATxy=&cGWj#5zmKK0nZ^D@Vx}S=fx00$8 zvoSTAxC|jvP;I>8d}nNuxG251@K6gI7d_M80(rExoPa6obgmklj@DZX_LN3jFL&3#b+=PwFJ zmWa9M(EF7|Euo>S4cpEB14Dg_lG8Kta$}ZnVjXq0`$@d76Tcg-l%{+5n0#ZJZt*0v zuq8$i&W%hYrIr^C8rx1z%?ijRWz8oadTey($l6pAg+0Z#hqN%;c!bkuS-D*==|j=O zne-SBBBy)GcW0M{<*lT&C4V`YS-SL*#htCV@o#?u8Lnw*k6-p_%f~GFo57^EMUPm1 znY~^wPb9vGqc_Oqz= zOD*ubq`V!&zh05h$e!j=shn>#*O=RMYu~SmYHu+;YZl&} znwqnuVoBEPY++h#_D-m8FwalSPj0@KhdN`k6dtoBQ{KCH|dv|snJpHC+ylOK^rX?xOr|SkE!xI$t6#diU(G}sPd$!&8tnjegO!-2mmHb1w-FDJkE7ZTM`UrF zNrjcQE>8x&nXyWlqkj^$KK8Al{%rCqu516IbM0cyMgHp0dVa5FZJ8a`peM>rGd^YW zZ06e4J&uKKTaS|Jmjg%DWNp!tpL2e)N3E5XZa1^bq|T%qW$z5Fam$3BU6$k6Zhj)2 zP-Kn9jlIw3=F+?B?X({U0g^k(ZjHK~Ka$p;u!`N~0whHD zb(gVe0fMgLzand>N;Q*0C0LW)if(>q^4&1~c|K)}$2fd|AT{onG|Mk*!{@mhU?RPi zp1FXN>M{P|+~l>n8`v^)=p(4m4Q|w`&F2Lh4ugiB79|S$&y7JIfXXoRy2azx&=EkBaYW-Zjfye#AH7A=hPg zxdYRiyZiM>X}{gNpbPp^B*|(13v#H4+-ldfiZ3ZiLh#PgWD%9BwQVVR`O(VOV*m8wru$0T^*HhBK3EiHPfoW)X31zh;746?;!?G@8*8cVVlzY3z|>GnUV7=p zxc()7Us9I+%3{*_uuFr}ABK_!Bz?tBMqKcoi+^}RSSV_19qg!0>o&K@PZ9Ju{ki#X;HE~>}s1xxgAPGP;v_M|_W zjDw{|vWz~2+4QkM=UUTur## z<{XE=gFm|YJzV)D9ngj_hop%R-b7SsosAmH1cOdJdxnEV&1}=HfxhM-}}VHy~;~wwD^5@*X>rni2HmCIBP6HPc$WdwKdBPtnuA+BeIc- ztGY*N;{<}{)1SX76ia{8+KmhP!QzA@bks;W=vUG{$ly19Jo6;SX?1oly$@dB)a1SF zd*M$Y?eJX|t}w_Iv%glE|CrUE@u*qeyqCnxx{$*6(}dvDB-s4b>|JU|pz9Dgt5wo= z<$T}%Mwir7S}oV9yumqdy51MRlm4Mhsr7tFL(tYn)PPj-XeV$sU_r~$qPtM?t&TGt za@(uE+qr3aDC*7b#hWbwj;^(WDg6g*$P%Rq=1x<*A9Ba*f(^TNUp>hlm8}Ma2|N_) zp`hk~lhHy?2P-~2ZpaE3y?OkmXbV;aqUk{O9UudJ1rAUtRSHuLR(6sWx9&``9KfZA zzR4H3@Q%0G1`oH~=u0iE&d23x*qS@$viyp=IlS!AU`Pn8BYEK@R03`%9@JEO@4@YM zC69U|3AwX!SZSllAirh1Ez;rU_X*Zhj=DbDxsoJ0T<+#f?v|N<11p=i*Ylj7Rhtb< ze0YiEL_o5PzvFzf%+C^TvchWPbhn|eY@>VZAtg0(E~)I2F4|FRI0^n1+li4pV@!8m z{;s+79=q!mH zj9o;o2l~|UwEg6K>FfL_aA!cyqxDEIJW0i%xyOu{yw7$g=wc!1F?#FU*2DQ;+oGMv z#ojGXwtjG$@Uc2mje4lK>X@ZJLCw}LZ#T-=$cP&8_~+8nF$hB}pawz^az!;VTHyzj zXa%rejKT}j!Qh)r2=+i7Tw?$LjO!Q)WVAxyVuK153~-z;Hhry?JIf=H*H@j%R54rF zwxQ7&xeKBpzZOJ;wxfY#2~X+(Q8`^Db)cih;~{=DUE=nW+ew>Ol5IwR@7nW&o*<5kiC6@LslPZfJ=LzOx>%#>E>WFq* zcH1k(8bf`AIXgg1`iH-NlU!w=S7p$X=Sw{K@3((yy<}6CmneK9C?L7N=^2==GO)PM zDo@wA`&hnu^6Src<~{4a7lS@Ob{%Px6I=y{$=Q1(DD}S(XTdP8Qy5{3Z{H_jNwuWM zVSDvAC-O2UcUW&?)mTkUD`&Hc4Td2{%f6@}_5Z zKUZd>cyw+kN!~QHb8#_cpnuu&vA?V*o3iX#@+=sm(nsh+G{Php_41xjFx28b*s)_g zJOBVXaFu7F0DGVc0Jq=}Ao%xk4_y5zy(fnOjBZG{R+VS3iEoG5=*2fhd-iXlYNZ`@ z*=6^=B@<`-O^zIrmbYumOA@v88Z3_PJx@DoH0fTG4fon>ugIi^9Gk3j z?HjYE@BY4Gjc(Od`!{!2oJsKc63ufHB7NF%{gs*F=B&CJ;!Wu54s%o89%1T#;G#X; zysE1s^#xM!cC)2^o>kxPx^dQ{Zq`h_*3=?|-mb+ir(K-&mCyNFb}{@Y7+k%QTEcr1 zz@dTec%`-w404+C&ff`V%x@E$DWic4HZ{GNN@v89Gir?`g*Ebi-F_+Irms{owaNE@ z?vJ=e)T2gY>LuI!&g5<<K-{S;^ZFVEYe(9 zCVEi~BV*X;a=LR+iv?P8j$8D{EuMnOggSw5DinZFr3eNWy*3mX00>`>Sg6A9mHy@M zR|33@Vn%~J9)yU~z=fMa14E=JMk-vr8BN+#nZcIi! zU-go#-)&?`S02@_e4+{3_kQ znA13S#7b?z6E)ehlZNXE_~r8Xz?by`$W+WqZ>RSk%G)xqmv_tvdDXK%ZmM;&ZeQ9X ziAwCsK4wMu34d=1`gt~emu)LyHFWhI#c>-T1rTQyK!XVGCwZH|(cs9k9a)hm5#LmDfQ(U#5(mEDt0`I*}s_4bgM-&D_Hnr1SSr~SI$+?22* z-qdW13o1o%yco3_ZSrn?L`z3`^_V#ycT2?blXox|5R8VL2*`LUg?P1Vkf$5~g=#Pv z5U)xB8i97^ewl4)m2)&L22@)6V#DFiAQH?LRCDmCx)h zHOTB;4lJ#VdObG3#`N>Cig79bI#SrLIdJ;?#zpf`1J;$z!vj!*%ilOm8;_hV_;EUc z{yy@a^__pd2+4DYO=x+s@8K6Oj(`pS4UT6u^4ivUQvYR{JM2>i$hq8izsKJstVOy6 zmFGRrw=b~?`bgULDjJ0g{LDSPyxwinz3bEz$o9-uTRy4LZ^)QIaVYPiywOmeJcETX zX<8rz@Arc(?IKUop#J=esCUMF&+}G!{SHHOLgmHs*JMhX>Wc~Ej=@_WK1A-qKSpjK z=cMnsbwu``j&H<{ zEqQg{%o!E^qG=s*&w9vt9D}vg-QvyOm<0P^g%V055&<;yu4$ zkaVbMKnf$fM)i~f1PF~fMV1;FXcPqJXgn1M*geoLdhCP6j(v(&CR35>@K>6_fWE-B zx`K~jFoX-Z#;ME9oV@qG*`DakOnYTigj?ab5(w57kAcIF87E=j#BO&XNgFk$Ep_Q6 z>6G9bQd$H6aFaivgq$w4FLQA=)|?or+OQ8Q37 z7;yF~Q0ourLQ?6AldB8i)d`qKht2`11MQCMOB^F0Y!I`3xNP-SM?O--h6}u-E}^U5f^&Xkw^n zIl$J?zcJ3C4wBq8NHCZJP~81=pJ-=0d^HwD=$|BbjDc$(fxFA#MCR0z%yY$*hD>$# z^;oWMAE_7{TOwNVuj1k^!4pn#!2{pnD8h0)V%PV z+)6R~{Q6`y`yxDIcshYTjD!4!9^Cp)7S+vC<-ooS($9d_Oj`OL#ar|Ua7Kd<#WM&; zEJ)U9ItZE2?{WJzF;wNjB)wh zD?HHcL1CBP`-}74xdf3#_~&uWRm+bZL8*a>A{Gr+#on;W9*vCOow-bU9EVT%9sX}r zUlpS4mEW15PS00_@5)+E%yopjzfG{J8pNy7=_12p8R*C{V4y|K?e0gg^P9deN# zsZj+t4^|_^A4d<2u3dna>!ybmS}HbQ(KK@XXm4(Zm=n-vF5b4vz{L{T0#3zXvu=K8 zCCjOOP+Xoe7`JX0WpfTyWIJw~%2xq<@k)GtbVjqm+4zW7@XnP^maB&@e=BU>r>L>O}c zs=)O}F%Xd6!A6c}IrygJCcg_lTz{CW=5gARY3M6aY}y-^>SCIDW<;hO&9!Wfs+Q)= zZ((By$Y{&XY$p#l!!;290A>I5e6D7%@?mA@C067!{G&Nd-&Z12#Le#N%*w9XfXt3{ z40$l{^ck8CtJ+nWRL?_g`&LVtup+7TmN@H~>@zNSs<^@q0QASYuXE42VeWANe;f7R z=DSjcIz@&uUovd7P)*91#kAF?T(3JRz0OLPwhVRLD>?^DlT}0o?z)#F^2#zM`9P$6 ztO8(>n#cL^!JMnu0T=!|EYpG>^IE-KDj~f_t%ua|t)9ihab~VXfyXORqaJEkkBywK~CpAdDEV-U#^ldd&KXnu~t<3VH2i4yq^BNhs za12ny894)$_|(9uU%}MjnEplBewwN0*E;L(&!|a41zD=_8A#ear*0;R9pD5CDDu3^nzr$ugvSmR=nEjv}ap9@esFm;zl`HsaXkY&EZ zOOhZ49@;@tPZdA_c^n*~@`ZgTQiQGjjaa$6jU9`W<6%M4Bm9Ypw&Es?nob5qBVPM^ z{J|MuX#hCch5OIM2d;z2YnY^^DqJ1cEyc<#JK|pJ>_xoTcs1zdn4dh&Sgcjkh?tmN z@JiwU$c){!lGp<@uTOtDAeUS#@7i$Jy;P9YU1~AuKfvSQRpatHu=`E-s(at9I5vfo z7RhoNvmHKX@*Bn;3acv<%4U|`j>4m_jb1M`s_4s)ADSMXgzmodnj0kC=(O1#)AXGh z7Oa1hvhH@U%V6w!#&g-hfXNDHZ-RNG?pbElF$x)u%WH03O&M>2TX@x6NUN?wjdVs@ zSLc_zxIA7e*b}d+r4klUTU>`dmRo9YsIWBoY3BWiR$mk|8y(`fcJX$Z%cG zN4@7ZZru_^Jr+5u!*>#z`v<2T>dKdSbOqtrhP>FpMJ5kXb&I`6mdbKy8NDDJHKinNDYcC0WZeIhYZ~EyQDg(2PDDg_ zT@R7iwj8X8zgxwDK{4PwTR;=LgQ=6>BgV`1<>i@)Gda8YtY;!E9|qelc#8Ep6dMO zk3D#9$tslc!&oX&Rm7+^?oyk+(-=I}rU`FLs9_eJ8&SR)6L+Q4q)IJk8=dtqe|&Td z(GcLPOTSby9%O=6x5_R-*sHIvP4#@JsxL0)5x-ksvs(dMN+_rPQb9~XBEyo=8<&yE zUBu-T=fqaTb#BJKS2f0KhRQREHVqNtNt`q_iG)ju@p@m)Q1|Ic8rcLpGmKrOT|7Pz z$=DjXn_Q*o=pk{leh+rOBu_At5gi_fA}pY5c+=)pD!Z4(>e`a{zAzeQcbm9db-aIA z(?HB*x?3~aB2tsxp_WsGc_tY3ph^w4>Y$ud^Chwkw&Hc++e4a_QxV={!*%+$mF$n3=(L(sWo@y}m&;>WDw-;L7aY>M+RBo?{<)XE>G+2IaXL zC4Sv3qz!*LQwrm`{;B|J46Q97^ux%%#zF1jj-Njn)2m+&3%8~>%GFH$sadZS^J&#B z;;?GV!zG=k@;W5{jPx3#h{JkL^}d(^A{=%@gK&p|)(%ocKSr2Py4LGp&Zu!`seFQ? zuWxmyB;(p&fa}>{V)ifPondX8l)KOGSJ(OSk)~A(HQ$iS%cfor!|3qD&gW9j_AVYn z{7}5PqY2h<$F4iw6+3vTYHICsU+ybS=9Ic@P=&Xa^csI(9nDCkL&rBiE@JQMP$GjY zH!gn$8(-h{AIErn$ywC0RY`0{Otqx>z)iiz9X`~w$0v^&7`TKAvN?$(G{X#T=xhsX z4DMl{oare;o3fgwYKU0i!#V@qF_*c^x$P5$F-x_oMfW_E6nVLGwm&40-{V@y&USwI zsX_l$Qg&ygO=0X?L=3&iT(cF<$-ALjgVq6Zi$Ss1*(qN%g^Ng-aLhm@nH$K(TDJ;Y zwGD?TPS{)Yb1@{=bVgM3E14zn(`QCf+l-VmqYt15&$6s=?d#jYh2Hh4s&_J?mD6Up zK9oO8s4l+J<5jKRbMDnR*E3i>1FQSp8WXE-!;qfViiC;|DeDgcvv5cK)xV0tziyyu znMl%kAMLb1GTV4qd;8SQ!jKu?HbG|pl}JbTQQR3PWt+IFr(=4-D7L3sGnI((dOQWL97K?)V{>+-vW&z|oYpuwz#yoFR68W%pK zqnP+M*EKC}ViVTTMri-?8vd|~e}9(*n>O0nsFq;R)qRs^s7pojB1APlhKkwf zuxG;0?asCl$}g+(OR^F*(rs#aaY&=qSvHPC%rBLn^U{|7n>TOX(cVZ@uxKgJJzYL- zkK-b=vRGx|^DRacMdr(1TIYDosg~-ii|ED-^F$oCrE^YEwJW9hrQywpv%i^^AIi zSD6~LChG$i;sZm;ZA<-K2Aoy#hF3>5W0SrfoMOK7^qK>sB}*=$c)M$vwt*u2F+@bNhU?Ev#N+PG~JFhre|eejs71;bd@z zQ!e%ESaC*n4}WfUHCI^-U_=2fuEH%-oEBec!cgv$EK>`wGK;GVrl=K8ILyhW|l=9{W{>nT~2Q+FNxr-t9NGn350H!KYBoWrlFu)l!4>9MrNjAOzw5lSqmE* z^bymjSYK(G0d}C@r6)i|9ZfPbVhUn&S_^t@vQ{%bJj%y(h+KPNSL?y2Lu${jebIE! z0nc52#e&NBeqSV2b2ux^op9ClX0fq9@@srGbdyAt=;$mExhe31_%S6;95=jFGQ3X2LO2r_cz6`|0euei}e6tCU?)kR`>>Q`{}x#|TRWA+E;@{(kHd5$wiY}@tL z+o_sTIe+0|yGc)bt%c-?AT4vbyOKMSQ5+S2`Weqs^QgKdT&<>= znUm8d|GmaIgC~X~IDY|dcZ(5Y<8)iIIEIKv^%ai9;l9i?lu2D2625FWyXfLz-`xDB zT1@6A-d2|7=zCI4W+fw!#b}Z|TaTvwXt{DdoRK>lWfz{UZ*4d_%$yd#t(d+RA#$lhV$t#Cpfx_6%wNZ)){mrDrBF^@!70GYJYmpb z%zq;_tUcNBvb(xPXQYdB|( zvEP{K=ajV~zW#?c$XD*_b15&fK32k=^`Akma$JX8y%rMNBuBj1Nm`XjmpMVn?_-;w zUT?S%{2NE73%Ls|;MUE(O$!As9h#c+I`!B4inFeIY_2WM@?EZmH>g`YS|~E7ce1wS zx~$cXrf;i_Fwk?p&ClyzzgNjRZPaHj&*@g|5;J!%q1LB7(p_QNVTdwHnX2y}nw^iuO>qm+&OGEyM3s*pLf3FEM+#$8r_22^ z5S1s*e7$!9U*#}Ay$GjyIj2*o^v3pr(t}k3iP(Xl%W|~t3>4Q|VqTdQgNvn>uRC_% zpK48n+nN6{s4*C4Z-S^GP}^YL3?v^--k4$((IiNIDsn2!pBNVHd!d)B2S#gIX84Q7 zs*!hNAPFIFHOrzeUrUJRuLAdJ)F}%1Md|hQ?_;iHdXz;B1=r`1SAwR_$$PM=m9C9y z{q>G!n0Io%c%GiI?pxC)Ux>{^RLXXU%|pg{nhFSIbjS-GBZ{n*Yjtm+<@iOn3SO24 z0r6X}V+_a7@N53OAm__c_nUhEc36ncJ+U0>1a+iJh;Z)B>Nz5)qvCikO%bzisZ~QO z#f|ahi1EP7;yH=i$o-Fk8+SzXZ!~lXURGa>F!4zKrTIA}zNfW02bpXJrPV;owf~&;#5sdLy{85 z9jAtnFib-BxU>FcOah9Z+X)7}CyI`~Q-PwpXZQ3Pofvl(G=~D*f5R#bD`gdGCSdss zRy!JmMY7wvVKC%KTTksN3r^E=+NUXKM$}6W%{m^@YLGPBB{fj-bx6X4ltBw?QwuAU zaK#^*gn3h#*r27!J--O2S8t#b0>TxFme$XyId67+GI5HvNO{gQ7yhcqsOUxv6-}%G zc4+C*&igM5zajYF@aV|~W1|_vH^!Y;hSAE(BGx&lv!EKh=N3|K^U}hFK9r5B#U{K+ z{#5+A-uBdJ1gT_s+@V(7(u~u(sQb<64ry+*|HnJXJ89!@V)M(EaztVHKxdcQXnU_x z8>`e6FM?Hy`$+k;53;I6vXg%vizi5SiqBh%MY?Meu6H$bbUQw(EGetR*YU^*{IM7@ zm(-bLDmKZ#W#mm=G8Sj%&0w^FF7|eRV0#6bJ9TcSVdH5@Z~X_!qB=(WgNQXDIM>H+ z+Sk4|g+}ESz11(0>FBPtbs=#tSZC@UH(OHjNAC}lz0vd0uPAi!y=t57-$ z7*|LbHS^fNk8MJmV$0Ch0hLAYwf93(ZtYN+_Opu7b)ucq@H0-6%taUItZgsS4F{T z?Nq~|kTTI{kO$Ersu>v}092g@K|@0W9N#j9yg*!nKrP4tGDsLb0Lij=uSTCW5_bD0 zXwyOim{_CZx3`+kk+5)vxx`|#lXR~WGPzNzXj6mpE8XF8G7}TJqnZ2; zbTl-maN6q}3N*0_3Snf>LPbR-3Z;)qhCN)T6+#ePTu*PQ#w%I?Fs>T&9S&2{bkzZ49S#uy8J68S~qj$vro@5$o2cRd0K4npi{C3@`7k zjdc`AL}RP+^tgw+Tr!0nbP6Ufp}Rf4Bymam+c-LAS>C@2QAHt7dtekn^L81~1zsbL z=2D76jlGL#UA_cltyD23psY9XpMiqV-61Pyp^84cyL z;i5aO)J8@3N3zk|+mi7h_OHcIalx)}P%e7;nYprDl#f24h=1G*k(CpCD6CZ5af7%I-<-if{7yoW)}BAF_M#XmKWlhhi!Z|3gyIomf* zrrI2>mvtj;;);9Ep$|zBW28x|27gHERpGnnc4{b$QL(-!%L-CPMkQ?6(@-<0Gg4ro zicqCd;wsR%s-p<*C}fmf0~BF8dQ_JYswi%KZk^yn#bAI;=S3zLB{fAfrP_`77&-ksrPh55ThX1eE^ie7mg>)=d zs1&bm#tx~)2;wE1qTVarS2|#Zn0Cg8!uTv3=)h&D&A|OQT8J4hm0b~H;;P7V5g0c& zwX+z1Zk%&&oR=53OVOkgV;PY@)fsDJ;qT9d)xl2#@P*!xk<{2_sb<(|gk>!vj+uthIA>3ap}iJIhEBjsA&LJ4Wwr!e35uTi4{J!@j# zMjPUs6kxokw^b}bt!79K8TQQLoO-?hj@K`H(uzL;JVt`bSL(+;6jJ15tkSWn;8x61 z0Cy6g_ry^gwDch$Ms=`iViZ?tdfH%4DCBusD*Nn_7^wmyOiw08i0qjE7?M#^M4D@3 zh&u|T!xN|hCmiodi#P%9En_v%6w8A3sMe5Ed&2l&|64QXfiXp438yq+7iC7P9|5b)sZ9N#Ncu<5s zBC(cb0o&wM>Gf)ye%=aOtOFHchwKPqI zKTR_*6Td11SL(ge2YDE=BBXpQlo&hctsj69c+tn++Ec>EiN~=xiY5IsUJq+9k8zEj% z%h;~i{g0+$?@hK}Frx1zKBAL#-u-pSJ|=d|LYm;@;}@2QWq`Agh2Q)_Rt=Kwy5VLoCXz7cTK5FhpK_j zLDv(5t;1lmduC9^#-Oo{n0S7J^#`f@`fd=#Yi!sWajAjBwWG3d{wyjj-?gKp7E-1g}SAcz|C$dR3cYf zR|I0nkVj++k%MDYdnwX-(^Aw@v=d;bDER~o7fl$WYUW+Ek{Sp4JowKal)65thDFn$o^k<~{UBnrtgrElUWaV1mo7?x2Wmj~x78#0q!H%G>l02^YWkL4NH9ehbvp^?}n9VzK2Cnx;*X#5dK2)!n#8 z$X?K$ETdQ>b+7E?L7gmq3t@b+h@;!}+52s4I@g5~?}=xrQQcRnc*+r`)GCBlx{7(G zq)JW!RRgt}*!Ni$z%x}19Z;&N(xAe?V;gs`0qU3G$oyx|PKg2;6kHjxC_8d0xE_*D zA^*Bi7Gpjtc#7(QHF$>;bD!@jcSvv*5T^Q+i%ZEc0eR(H>I{1vaoF#%@5!it3wTfe zK(hwF&lx94bm1jpIovfiY?UScP?A{m@I8{cr)-<#Ne;elK5$^228l)EhoIS;XsNY zqccXz8;sxMymc3I70Pvw;sd!(xqmhcepM(M4Puv^La8k*R>?@fLZ>W-{FB;^gkW`Q zYekwaN4N;e;clV=6QGJ;2Q-DwWX#F;R5~O7eA3rZLpZ=;eq>*?s4Jq%r}JR?o-D(Q=14!*UNES^Tx?zJjT7F*y2Km-rA~>;zqY(4-2m( z<9n#$#Rzem)f`Mwdv0a3`XyAzGvNdq*aJjJ26SBX<3z3LYAuhCviHc_DShl?m-@}H zLEKR4S`zDhcgL}!z8g{l;1w84gTB{{ldZn+(HAO?uh2fg5E6`=0M$p({gjwD8B`c|FI142aWnw=<%Q_!Eu|hdxS0=?y;Gt z9H)SH3m5*&eP&7FDfF#tbO855Va1#*C>Qrq#fr7F)5~0qhxSJ8OXa<4wU-tg=2EI} zG)-Gpj8NYhR;@|atf{$aQsmuGrEf8DIA}4|E}V3l801!=K4+~jS?>@{pUctkj1;NS zE7{Q1L8#D@tmT@+y53xLy%f8rc_&=2tm9J;j?tpWLjr@7{bRScrbGSc=Z9u!`-|Kx zo6$$;ksRkMZHP}sUPT^m5Yy8DnnA}Uj!H1{I@f%JNg%11ElD!sdsO3*)`j!}zaK3c z>^V!may~GTCV6Q(0htV2t{YwWSd^hUFFm*JxiFZ;_@G8c6?lXE%twd?CvTxh7Z*b7 z6FNBYF3iFBSYBs!p>Ba^3l%acGGbacA2mIZJNrM-l6dha^)d8|iWu&>3aBbT5f-hg zPA#6@PGl^q46DyOH*Nc%5}9|dI^h*zA;qz%T(36S*vP0D2^&MK4g@CGttBQB#(9iB zm{j1AmZn0JT)9$9m4>!ACX6pF#y(O@7Md`Lx2|QePH1KP+$u@?+Ff7us(vy(Nv-Cd zGnK8gH3D06r~nTdfTT`YlxS%yX_z||qu%MwG|y-s$}S-3 zwSDCOVN&j3rM31EP%58I?Cx3p{)g{lpJ@LJJkJ+b350Fma{Q8!X88U0AzUNKaA?~E zgZr~CSPos8(z)UHX!P4~u7WlczghteBdA()fP@4tPV z{CD7|*Z@TVUru&R!5`|GLKMLOO)z+8CGM zGgbALz4|Me_fth?IV%`MMMMkA*}}7PLbq{aW7BPZF9t|9{c2IDepEzH)?(5??28J2 zcjdLEy{%>FnDdLhHLA9@OGY?!Z4;R~s8%{LBDyH9a!b6?qT_X&JG~0Ep=>0|^6296 z-^2%QCo6>3G8A5x&Z$2AJfJusSZh%!F66qt@3}v8lD_0|VzzSz)DSrCY_`Tth~D4m zpmVNdtQi{hv&;>e>>)MnigU`^t}n~+y(AcM32=0GbU+EYb&_&j`Zr_@*Kj#G6IOV* z6Y{3GLkWyFfwhPr%sHP$#hSf*-wT+0GMIPZwa=39OoMU3p?UZqi0h9m3C7V6N7o0x zbN#m{XN=#J1j)ld=<&1|dT;UKXTg}1J|bdMe|746tLd;45GOS?ePv?G_wILP?v!P1UCNHG2N9;02#GC0M6R@S zh0+*6aCaWADL!k@vP^0!vZQIB*5-+nrfw)@l&YZN7=D{J@n={%s+givVeoSgId3^` zT@(+&TCzCk+NazOb-FSkYei@xmQ_DqWFz)vdJW9Jg`lzu=ScZPvTj>2P! z_-l6Oobcj435bNnkFP)iUtP1RsObFp$#sxKTu5LJM{a(Na@u;bO^}Z81}xh-wvFvX z=XfEv@QnX{G3r-o|Iii6CB?t&=odbj-kv=sJoQtoiPBbJBd{T5NI?}Cz)RAVy*tE( zEibOm%wub4Ih>g{tWDeXqH(i%lyqhrw`&XsA>ENy^lqEwrUTGbBF181{HfI7)hD-c zMZS;qia_twPfM?5G=QZyP?Oj79A41)_|yE#`rqBP@a^XTZ$K~_NC6U01BQVx0FQi- z0uZoAkAX|u9UPjZI0#19&o#o!S$I#2mO8h$6@3Ck0{RXWE6Um>5>}h|O~rg@&}d2S zdIy5z9BGMTzRa|5IR zgpGQ$h45+3qR=lpx@A|+$b@ba4j9?Laf{MnunF=D7tghM@9x{aUr1MJ3IbK~q*8Ve zRc=Z!in01>Cm<^;7wozd*=!S#28zR*27U2FGx(k)`%x6THHI&nz4|2Zhp^PYA8ne} zHBD!tWVG#iqs!f#yR|0@H}9?-D`X64d%J$NT>VsDGf_k`O*U4i&$nvM0Q@wjF|wL# zprC{UL|==#EmANS!I`TLUdv~V?Iu`{8H0Zn{V!oh7|>)Hi5@)v{RpFt{kNkq9vb+L zCSLFznmaV~&+c$4gqDCoZ_)4IVf;CcajF5!O_NH8%UCVrR)Ah69QLL@%|QnOwd*2H zM_6=@#^m3ddMvJ56qk3EYZJA86aE(He=N{#3H~b%)fu^^8^K@MiKdob?)O~?i}=pv zW?Tp5pXeP0PF>OXiaq<43Lz$$0s)UtJ|-VtXSl-CMuwIZDBbh z`^htBem9szCi$mn+bg0HB5i{!HrMCtY(nRUF=E#>->ji`5M|WEA$IJTkAU=+3 zj-Oxe%T2u7zO;4364;i=k2DO_~ULvBoCN7qN4d*nV-^ZQRvmaD8UvF*v*q=67+>=Kg5^WA5Y@act_A!WF zxE-(ezF6J7ZVULFdg|wtkaBXhPw1eNrAZv^#*+*+!YD8xVz^so@Oc*2%a)Ni0Y_97 z)yH`!AGInVmt1?Niv!aT--*=pUwNfQJRGcaV`br0_838dVHb zO_kzC7QVA>cX_lr4&HLU0H}%ycGe?bg8s^Vev$}gT^^-Y)F->y9Ns97oN*PK+1?U5D$gwx9B`f=Lkmm?WnY*ajEkcYiumIS}p;udZ^OY3I=_?eCwv( zS|?qh$(VtW7Z&cv!fY9zcWX2Sd)`lM__V$i^a=jmHvdpTpH7SdO6p%n)&%p)o;G8Z0CJG-6fDlL}HM7`H1Hso89IAF&WOcrjG)0zvyctdC&PoZ}kSk8O8OBDSvnm5IRWG zp0HbSOynVrjF}tQg+dg6@UM5Ji(!dg+OK!!=8DWRHuy=xrL1XBpg!=r5}1+ltaqyy z!p^W@s%U{L#?UW*G$qt|WxY$WCGFB}*vT>5Pv`J$=^3CHwdJZ%{5-qU5Yar~$d9V- zrEf}ew6uW6E7Xq}45YYr=A>~RZz4*H^N(+>`!o}0m202@kBibukIwfK<8m!1rl$59 z4Sh33b2*}dq=Pg&kG5JySM&Bx`wS7@k?wr0Eb8ves0};!3BAQi!iQ-eZT z`pt9g(Yt;PU$R|1OB~=4Jq~5Girk&yuq{Cq5`k!BWFI!1&D6M&3LwCTk&U58}1#tQ$=@+TYSa zvcfcm95^}r1yB_NP0uowjiyX2OmZoG%7#`){dIR|rwW@rWfkWjy{>7*Z5-`2d=9!< z9?p{RV{32!b~cr0?dDMm@m`-^u`J<5^rbkAey@F|s1Ne#aJT_Z*FeAgQKIDcrZOF=&++Ub05FM3!{p(vK-G=mfkC~XeZ~b->T7-3?2K>__Uh-lj?WIwhw)`H(v+;exy3m{_z1A_8=*qHWA}B z;}akBV7ynHfJqz(!-dEf3=^GOqPkrQ^6;Jm_U-rJ_dHMW(H}6{=m<=hPuom=ADXUk z$v@+e-|`Dsgi83uuUuYd#kgc2iLs072UgBkU|skebvEQT48>ZxY@JFW>sT-u1Gr=r zZP4eVAp2&H!-`#**h0HH4r>SN?G%~YDQ-)}4k`wdAA?m6hf&&2ZEu=bv{9nu75;Sx z-@&UV$5VnuShM50;;fgS#(=r4;XNN%S;3+EWNRSCGdXs4A*>c%rX*{MO7VF8bvoC< z(aM+hj?v4RDjCs5$8^s1jGeWfAjD}!zCos{igOJ)fadxIjRP?GX$E9$+i~5g- zuH`k{qg%eB8c8;ItWpVvr_N_2TT!)vx&Fq=XjElUNe1loqFY4`)TpdRxW==()1kZ+ zYJd>%c#5hNM6Io!3Izs%{~y%qWIvejenqz{;E}jb01{5}>=pWH#6;Oqb zPH@4UPafbTyn3qe6#Nc7R5FphK1i)0zin_Uz@#Zo%%;J)FX_~ONv-Tv9DMkq;}2-t zhHum6mp;Dp05(5hopm;O^y8|!%sE>}XQmda<6zqyrTC>5t%PJhJXg$jXlSM0U~PNG z&T|L$M#b^XcxJM=Hht@+%r+fVQ(7I>1zQ#MN0cdc0 z8RVWq{h*{hqfq2*ji4u>DE9Y{9N~yLu&>cc0}RxnUhk{1(s(RTs}eMYt3g(Bl&t z`a8`VKTd6%Lrq-;*<@GpU7xS*Jy_n%)X`ATQ+~jzte9u6UuRj18Ylz6TH43M>a3J3 zL1@8f&p8CA^!H0q4MOP$9_xRk{_e4V!HxY*jAW}Tt4?^uiKl>v9{R@ejwAry5V=nl zzx}c|1B{8L0nv9TQ1LA2E_e`(^DG!71x_}th0y1ruXPD8NyEFg16Q z6{3yD5rv09m*a>F&$MBnXluiR1~B8YEXWkLt-`ls-813r*^YdgXh^B4dn$oc01l_) z;j*2ai%eJve?2Th8FP-Xt~?^!k=eRX@ofCSU}YeV0^#7ebLsEzuW*r@lQq>qbvhuB z1K-Q0%q=`A@5$Y)P+*x!yf_EbKYwQdX(g7@GT@?-?)0VMJ>F8&=HMGVdm_9Y1p z7d2g%_=c0fcCi~}S!IF-4)GN7(U1*|qh3aK!X;@A`mGaRtXuQjINicns;T^`_Nke@ z1@7%}ZK&P5Rt-9^PPK77X|Jfh>%p<@rq}Oj#J&?k^$U>TPDVq4HvIG zFIunL!9a9TG!xgjZ8a`cdegrJp*!$(G(cXZtwL)zFx+8q^Ylj>Pu$pf0L58q{v=T_ z2+OO8D^ATtb7*zdvh9|!Z@_k-Ye65;n(ny^-#5u_I&X-N^>y}SDcd;g(-0%hNA(4I zPy8Sgjg1pHHhG(=v#54=qn^!PAs*k{+$y39o~Hm~RaUM?_we-}w*Qd*jo>Nh*&Qn& z`Vdc+V%lxwp6l-p{Fm3@-|UV63Q@np6Pkoz7?=Tl{Oel+Kp9GF1%P1SwM}AvP^L}I zQ|<=PU(kU$LMFi?I+=;61eX&4!c?-Ea;aZ4_1bT!jH7a;3KGS^touYSZR(kJ=ftY!ilIvsyFNCA z=S>M)PtlbXqASIfNjZ;J<1Fqia#37oTnnvE&L9u2TRjl|M^p7a+Y?4P7SPAi=)J9F zeR0KeM}cvBx8at4fx!@slXO@aNrtNEN@KOf@+PvPN0fqVy+VyzzNp8e&7WM@|9UL- zkK)Wic!#lK?8$%cR!c`T$O-UkjzD= zS?AX5UznI2>^foB5efe$KRy=H-DYG4ZP=dD8$Pt`y!!Bt9zEOfwnLk z4&mYfI{$-bZ_$F&>+<-j;_)989t=6+(w#*j z#TxB*rQM}wW-K&-0LvB~3<43&OvbpBHErg^sg(|`@mvK2mbOyH$(5Bvwus#b(z$!K zkvlGr5ErUMeyu~c%Iv8ThL2emJ@v6}eW zybM!zrs&;v~ zUAghzXt!Qpd)|mQxhr?b+&86%sxF+fQ;%#dmr3C~JHCg*mEq!rpTOu#vgN8J`y@=DLctWWT76#kpR zlxT5mVX4$~={?|Skk*&XISP7@8+P$^R;L>Xyv&0D`cdta=0|pTG&BmaHSr7#cQCmK zm2_}mga(Q#Z>+mRyFnn#@naqQ0-5C4re#2Us|n*qG7-Oy!i$mt_4%8EKu>_iCFO$8 z%&!Zdo%o5XE>^l!H6+ZQT-^|fJ5S{!Q>py+i?W=jgeL0_SF_#b?o$yB#Xa$pu5`Ap z@0CbBayJ-RpbNq}KXu-6Uh1fXjcqLT91CY;tGZNf*{Xi8jN4d7%~xWRi8R-)Zk!8D z&E44S9jTJ+E;$toqlR6J++_tn4;aY0`I*+HR|7W778P4m&6G~-#h#{4>!tPN@aVCR zU1YNf$I6G7fsTXL*2wDNG6v-|cVcyRnS1i(DLM*7iVRxak<+aohK+9!y^5caOiyUh zUt>H2tVDx@go4!;BQ!QNKsEmZYfQ8-zOeKmtR)zYKIgftf6X6)2j7;X){&pnp8;^e zPm>KM;9+%uzZMN{*fe+`0kD@R5Ybyt5b9~u(n9Xw<6%6ZLGOn5`U2oEZxZhi3^2WJ zK}W?zGCUp7>e3Gey?^jKTR;h5Vxs~-t#AD?wsWfQ9~-1rIjQ+3v!l3B*=up5bW@lx z%6~sI<)YequdV+?@5cDMjSErWV(HhphT@jE*}n8O-)1qj#DmNDv5ifVx@O^=AV#&i zwYK|XS96}oT-^ryS-68!3I85cV+xA<8tb%zDI11Lw4h@3=~cAo>kHGV#}*Zy-}ajge^=^H@%!(r0)Q9Pq4mfcm|*3V#9tQ99Pe^3^RvM{*aC=5 z3Qw-j{f*|)MhvfAvng?QSU%7!Za?rwI)h7}1fM>jHT)b?O~d!wsOOmB%MiAxju z5e9}t;M@UZlq7fv+Q=}~dm zyBVi`zFwP40RlE^4Cgh%6BU`P_+@p5I!h3XzR?s%-{#vO{>3^E_vOl5{y1?(rJ+@r zBBUJhIo>L?(Ar$e93Rcx9B?W(H^oR}OV*)OSz!B)Pmo5GmTkv%caxw(ze^aU!{s}i zO6f&d8Sbr=N<#6M$JW2OV?(`_yaV4&x#IqUnF8VCD#ypMWBCadOQ_EspZ!=WzT)T) zQVevsFiD?S_%dH9S(2$#nNe3E`eInnS+zIik1wvLa6Xkx{c-$HcCAs&CCjlL5 z;zbK>9nu3BBEGt)GEK8dASGL9EpC*Bw_IM6sGv4R&YMux)L+oT7oy@ zwi#L|vD+pRB8{?J2~PbuHEnEiDIdk=^p(vG&-V-@QvI> z!b4UT;@8!B34nKe1WcRvy!N~?twA+8I?bCKT#tFRNB@ zxIX_b`aiuW|Dk)vbg|ucnS{Btf@pvh{uOQjntD_>NK~a!#Z;fjfJP z8~Gc16Q}l!Zz7Lx4a`QPg9*97vkh!qb~;g7v-Fmb*T1k>Xj$d;>HyGiBeolree~`Z z3q_$rjsiN-!u<<&FX1yCDjdZVkNj0p9;e-1c2<@4amBSMUowZpu)>&>S zo+5fE2m@VNJ}ns}KRG{zoXp~x^3keq;K6r#+x*Ly2%oGLRxIwr7MzKtyQJ`C>C5V; z`_#X!27h7W{eAWQy*dcY0}^{^1c(;=3{A;uD#Zh_)~e3=>IwQ2ywHRQYg3pc2p!V- zln57qf&qaDKpb2=c>uuU@U4pJ5?F>7 z7(Zmg1R$NbHzsr1>fOzTmZlJfyxUU_cNxsNl_OP)-1J0EL;+uj;CH~cDBdlq$BXx! z)vwj7H!v~>zrXVgO_5#_@Ya7$)S1DOPMmXt816P;G)RXvr^8TD>dJ0eR^>)^{GoQv zc1Np!m-B#W$0FfQQ%Yv(rjJUS>RW@-nNC90u@76eJ+i39@x3LTC7$Ysg zihDRup;q7Bvd@E_a9G8<8pVQ-%ZJwUo_$dnKdh&&m@UJi*O8M&Ttzt^Mz~nL8ZF$U z$JSrei}CD324 z@zBrnWGU%JVnaeg>&hnV)W9TuQ#@+q8exC)-6$^4=fg#Nhvb^anfF{*=Xo$r@UsU1 zEz$5EC_ogR!syXFOqHFWx?=0@=YnN9@sZXAZxg`4dZs~xhVct;1_#riJ{uJLK$74A zEYI#^9u5dV^J{o`dU)(UcWl5jK0wjp#j|nXeuWjkxi`~k7%|f~t}$~gTV%8_Z!9K; zjn$~V^L2hx*8_W_)i_i6V|o-Ol--%d$*N0^QOPg+IIWEx?&|7&IhR4SFl6|{lV`oJ z@?B46XR!m+Bh~TpWMZJBA!?|yuvj3y*w4h7VsmKR0qQ4iIOoMq$} zH6?b6$kVDR5o#qhN3ZE2vUIO=*W*Sp*_887JyT;R4my`L4hE>j>8^C=UggI41-XRi z=>Q$4n6l(pXLx=~VP7b=A<)Bs<6q2S)bf`dKqH-B-ujJPe*a!1Hd;BV{2~Cl5u|*X z+r2&g-AC<(|LzVI=Cs`K;d;Z|=J(6wo$DYI;>jbIibcuCZD^opp|obe)JBh!5Rvda zSPD-Ogb|@Ub5h=W@^!#xm>VD6szg6D0Z=qyJWCG-&@3M$+yMcwVIF9&9{?b`Z0HW( z?C9lzQc;TAm|)*_XhH`ntm?U)HEq~?*~Ow;MANvPtv6r#X2Y=)haJA#|L{OGg#=}H zDBaf_wu4V2H(weUf`;=m_OB9TJzPw{-KyAf3A|83Rcn(vaYGJqbJq9<(kEX;U@^QzjFpD+Ccv0 zKy2`v+051)P$u^buPD#9N2+0|{24~hBfXUEn(Rn{m*l#OD{J;=FUnE}_uX!=TG*zG zWyX(ddBv#0j9p;6qD$2~-@h*Il>@mS%TLeD9*G-~Q1qMJKkB&e7 zZ8iGK)~1U%93igh6=2)*ha5L?b+w+ivlwkOoEbB}xYA8rmdiQy6}MV+`q7c?yd?MK zS~b~d<+<7NJHT?tROzQ0$_L zFt(Dgsm$hYd6^xV8X`P@7KMMqqDA%ie&Jv^mdB$J%XM`$r@l!ZfN z{+o~LRLx^;Znnw9fva|}SsE*@VL(Jy6# z>jFqQxlbt?g8_2=g(mbz$ImrM3Y4S2%=R05+BIZ zU;vn~y;<}7bGm$+2`L#}q#rcY!FiC6Z|`s>(0-9J@8o{PgU>4|^$9Sr78MBrgRS3U zpvJN*1)_-gA*vLra!-~k#Wflctw`w8$CNN_Z8!!ajx2qyAQjKa1|^aLUI_TEW$j|x z$-l_LXC<>qGDJY1M^ig)e+;pTwsS6+lqveGo`I#Dl#*Oa81=B{y#o(exxdbd?TTV1 z=E{DSn+pz@UIf?*Vm3l$Pdy23!{9|JtP#p6l=a@odx2^X_37bGt8Jc3y&=?&&#+Pe z@neN8Bs^wL-{zr8l>IOu!ii(){L}BM2; zFWX13ZJ(^M-P-Hz0J`DCyiM%0VoqPN4q7q7*6EY15!bEF)i|TBjW{#-2JZOMao4-e z*glV*vW|vz^@Yie*h+_f)-z#aM?YgmLaMUV0o~e(qgB*gKX$RjKy1VNL-f!tt+ zo)qEC_Ln&mG2Kevcw1N6igHsajpNcOb)R|~zDE}Gy70;#LtIpYx95^n%aV-C-TTys zDCJl^DG-x?yVdZKE?vhiOA@YlzS9KU;+L)y0}pBvgh%9eh3LxHV*l$FF8T`PE@$BL zN5vX;%Uj{^&vu*ss9KHfiWR}_r?g$i&QB4J)!R{f>X{Wk<$mAzf zLuJuAzG#q?(&n2#Gxc?a}sM@ZbeIC%IpPq>~D z5E9XHa`WEfk$g^P3YOw?z0WTVp_dH?c7%Lz2YB}`+NW(1Cub&wX$Lyk{*zDk>&keq zo5PxmANF}6_1IarX+Iuj=yMG|oXrR-KG+#FXb2F3(VjEVnCm$?Hd;Rp)9L)fb#QUH zyvx2Ij^q=U@NBCKW_d}-_O)uNu{^9}l_BoRCAZlwa(HFtecM-$f{O5qx&yS!HR4*zFgA_)sNj z#)*rJf4&;H*8Lb;`%LTOAtus{@bkOCI}ueMx>y`s2OYWV!?C|BKLb7mA6dX3daANi zOZ5&M6(2@J@0PcU4_8)Te~06(904*&WX6m1vTE(=;Q=8vnCeK+XRD&;NZC zPk8qt6egald*}w0KYr}m;sM^o*Fk61 zHntuwCNu116kFPmZa+aw)R%v3T7_wx&5q`8{{YXJ!+ctc?zQ{04?nbx^1v+2NT5XN z@=dqDEaa_wkyht!mS%VQ$nZdF5u-)%n*(lH7j1di!woG3B{6;xbP1&a6LI2G9Y(gh zM2~orNpxPq{cP#3ym2bv72Vqih2m~q>Vj+`x#-Es03F?)C^gM6lx|nFxo2I1xf{}4 z(2Frge5dgsi@k)!^_yaKaff>^qU0;5UBQP>>6)+=q#&E8W*L`orm!@J)$%H~s9pH#(ruoKBxlB)OBnTB({wsHz*q=|s(zh$ue)sLuxp1b za%1ZGxCJiK{1mBRxRa&!5KhkOocvvuJHgO~I0{smQ7x*NU;SLGcv35=POnwnR8wF_ z(2jGqNcVYwnzpXAVjtb~Y;$wvhr^1Wpf1-dVNq-0EcW_RR^#gGqI`-$gK}w{?yAAr zX@s^Gq%fDbmppg{e=_Nmq=K`NFaUC7a}vm-t1tA%g3z-=<7_aFa7EKRF+h&qm@QP> zeEN-vkEB{$mu9kzQGgdyI7(M@rJIkWh=T#cM3YUL*-g}-_-;k1r_BkL2xeZ7eJj^% z5+_zcZzNT=C$DExV}Rv}r{EjQ(_~)Wv9WPL23dQYolKYKBsZ1_S8g)@3G%`Dc(X58 zM$*S6Ui=dj{PCbs=K%tX8ZU9%zTah$U(yp{ zmQp;%^qw5!LiEKn+XIlftavF=cdz1pf)uWHBGWId$-|y6_uT&p`f?SA|K^lhZ>5&= zYqBg|t$-B(89Iaky_LB`4IxzGs{vhMb`%r=o359JHtYDG^JbMvca z=j9$1UO{UscmpUmKb066y_(b<&{aQ;G(&#`p~=pahk9@o6E%cR%^HyAR6sj=ku+(!Nc z#Z>+T&6kuAHS8$v$eI$SwUesr8xdMFhMT`E(5}^6n$%8Co?JPbrzZN4Q&G92c+FgG z2*s|b3?aITyVzALqvqlA9oR(}l*>ITt|%Cc`U&!U+HtN)+{Mhol2_h+@J%%}4z*+$ z^vPVi4aUUfL~=%_QAe$EBV_rps)AZ)^(Tm`Xh@B$DKOPDxkK8d3wg3vmUrdcVsmmn z&MDSt1MFyXwS5|!(s$zA<66IbP{bTS-kVFbd}{q)l(<^xP!FJu$W_&Jy_yphW~|wc z4s6lEz033T7XA}-gQC|fQQshc;n8?5Kv}hPF$I!{{;Oo(IgA{xoVhkvN8KEKixg1lMzxX%9D1=N7$52uex|& zZg5}be`?Py`j*D$GI3#KrD8IXrF8+lE1uk$_quc-sz+k{LQ3+)nUs=& zqH)-u=&m$9A=gRqp`><{%3aw<3L14u69f7T&Hx>e7M*eCQ9vn*WEYIv0ivGS{BJ%_~F$^eE56Fr+Mxtgvv zY|1=AL+eYIzDbNl?wZ9tA*^wWTy?Wu6RCMsIH!NflXUS4reBwe(u|yZ+-`7wri?Wg zY~_o##>%l*mh*?A`~+6(mzlgIPAY-8Dz7xOG!eV%kze^pV(Ii1OhO#4d=1s`J`{Yi zos$eF0r<|v!@6=C6wZ)1+Io%aOJ){?VuVc>*BMf|@xx?kryHvjI$wU%vWj8~*v!bzrOXzAr z(q#~VO4h2sQCs zT}*yXHe)9@DJDOD`~=bXe=DKHw-=Ajwzv8T>gYJSKQa=9U)IGWsvVuf+^a@t!5E21 z?9m%oDYrb-;eJ_0`oBq#4bY5cxt58fqV0K%YaSkfknYxdzryPgRIoR#nhH|cJ{_bU|753!%V3?~r%KTGHy{kN~e7nO$PJoyg^|6lU3iE!iT7EE8E zP(H+F_TX{cn&zg^n1Mu7LX+`1Na{(TUZ3HGBw^PPSqj}yRM}ppr$(&2$}G@kZy>#-;Odd9wqj` zwpg^e$BTwHdZlXy9W5!4G6*YP!Fs3wIb$YcrU03i{Cv@`Bv$SxHEx9-)oU;hsZ4=P z#tg0T!Wd@9dEm@(1dE6k^{f;oAQaZSMBjuMXIj$D5cF;>nhCOC~W1M^3U%kaf_3?`H`s2@MAW-4!db7b~G z;cjYJ9lN0n?uvY#B6yH>hjox8+42kB-DsVMA>8{KEC}yPLfEe8|CNRTM|8x_6MvqL6OI$k<3hMA z+#3Tb37LF^tZdTieoENmUA0wvtRjEiD2r%5s&Sgt0}rd0eV7(}!3--H=Ri})_}fdr z-v09YYil|8utcPRKWcgR9*Ib+{<@i~{pb0QhyS>-+|yq1iDK&3(o~EHL(|`k92Wg2|3zMo-G-2H~lA!$N|t+No*4Q$&ria>+V#qPyI zr5Mxr0}nx=`!vgijhf}cl~O^kfipNL*dTa9$}W#rom`m5O5U+VC5EZ0h@W87rdWjc zzIEs~Wu3MPhmyS6X4&nag4Wbr$o{JoQl&WuF8jd2#Ym2(4$b|64}2l!l(@9|2hCkp z{2SAA*h$_kW5}+fDck`o#rRWV6+}HH6Hf&+%r+!*-9lEzjpplx+>f>gP&WiQzPY0u zL}a1k{(>wym!93~yP1r8Reo}o5t@w0by*hC3EVo5CH1(nUJ$aan#FvL=C_Ql`atJj zfdi4?qVG11k0SZNRrCrG*=eAsg##f!Px~Y|Kub-^ZiVYP`s)P}Nw=DpQJ;-10FU`(PHw#q@BA%)g8at+ z9NUBO=1#Iq!jmhfk0dZ7;A?Tx1zq^dzoB&Pg z_ek6cO%@2F?zKLAD_JNoXjMrFb5wH}7*%_)y85onh4{%I{?TSN%g;N9NtRaM+D=L= z#YTX-A^D45PE>ucu$7Mh(RVlw#R#!HeSbuDb~`cEBF?o!p(@G zpzt8>#71o0mmI<$A%9q&u4yBPtUsRd?AoO+Rjc8cKS}*PrSar0zfcbtzRC^MS{G6D zdZJE7^4g%3pz(xGiXSUeYRW31L#nD)oOB6yy`Zy9M92lZgB zHZB(@5gJ?%@>B@j-kktpbvp6y3A|}vuT|-lJEwDhf_fH5%#c4ZUsfkoKX2G-S0e2} zxTbJHH@%WEWqk#85Xp6z>`3*7E-3ct#kUJ>&iDeS zTc)?B-4a*J18&je)EK0nWRu`4DBQkJ@)wF@1HI}YI6)74IUZ!m9}epHxsD0#EVIA} z-ukGGa9EK- zr8%hhBQ1QeK&fEsSt!<%(CK8hpCEb!?7>zeU%ojJt_5LXj70a^@kI3+?qxyLkUnMp zt57a-d@Ab%eq+L75uushE^|bEQITh)ouiCIip{*L;2R}J&0;d!L3wtqP75OE^rMeZ z&sSlI^6+J?H)=~u?=HET*r$rpb3(E*#`jFbrk)m$P41nIHTF?Y%jU8SrZ3?O&^{kcyc%Sfleb z(4{YO$2R6JAtg)XxP%+yHu??AxL0Q2V|LoJ}j%;@CA8rI;Q)GGaJ!~3->2AJI zTWz_&U=sI_Udj#d%U0HGYBNw^$rjiNxRfn$VX{1K>hbQ+Ti4J``Yv_9uIPPHruNI-io1Y-}w{J8JV^#P@RI-JWzDpZWYp@OS8>9asbf2^ZzEeq_ z8$p3b#E*DDT1A#9jl}mqZ+h79B*v}8uszKQ#XzP)+wjJK(1b29ZVjBJVT>oway{ES zD6XF1_92K=*26ycHY&bRH<2UbTK!h9fbxmn;ddsZYqxV z$af9pxIfUj&t%jCrb`W3Tc*1xJ$!501HP1RC0QQfBaB+_yp+hy66>0DUCGTd?#J*^ zRaWHS8tN4I`s4Ex1_p^MTq_I78h)v&K8?vQKX?qQD%>#k5|auFLX==lKS8rG_@=^2 zJWK5QbIdAE|YYJ5mLBdRU_VmRH!%a{Yx`U%j9dr zUL&qG)L7$|>0kseGl!LiWXhhEH&(rmkhEP%9%HS0{IbAbl zMm_Lz`;NDsf9~LRqx_G2Lw-$1?GE*DC0l*<^}YMHjJaWK3?H~%gl!(198v8?t0$1W zI8%guSJ`xz^Qg&HE9wVc5_wif!ED{6d49h9BV+h|`6Me)Hw>ChH=IV~@g5l%PGlYH zr#1PyBgB%wUyYV|-l06=*k0(Iov!hLp{gU4Z1`O@G0E<)Uu02hZ${5iDGCOx<@|7} z6OAyPssM<$ei|#B@4bIMlvx`RZDk3mV^z15p;#Uastc8Tt|h^bdsp(g&HeJv5GO-< z1znt-h{@R+orM%9h>d;3UOZ(ze*Gt}X_>N!ZYxWKRBmrKD=oHE4|#mqES-#EzvE87 zV-1&_*!`!OliSy|+t>P2P5f$g-$L_!$|~aJV^pfiweVw~2OuwwZblV+IK*K~e+bf%Uc`Z>f+%9X6@4np!q9V6(vAKv%hV^d0S=KmBXiWZad5ArO-E1i~^0`-O zNSzj@=pYBRl!T9YPZSZW9>S?dTH)OnJ%okAyLuf~JGSJ8ay#I$c~8Sh#v_J=-p^lb z$tDyEsy5~w70Rsnxjs{j;SZa^DaQJP>cVP0`~3dL8r0o8(_|P+-{-nUP@% zS%?KWXp2=o#p^7GI;GZ9Kv(l|WIxK#k?tLXbJm#P(-=G+b77Id1c{*U=9^|@%nw5X z&PYNd0H05@2(kOQ-$HPFA4{$U_R^GeZVARsPqKAxi3Zz0$O22Qq0vl&C0GA^-E~=@ z7FY$b07Knb#&bWYeJw;6s#3??TUg%c3AM1~i*YX)R;Tpe_~HJK@-l0~>PQN9HAa5% zy4w2%O6`^iJe)pvGQWfgCSA;gfW(-P9#}sunHHkvmR^mQwMp%9hCvXUp^hbM6Bc?E zNeFBGApcJgd1ACm6NPo(kf@i-*6R5!z?{G41sY)K zbbMmk($cBkBcSQum;$8V!DMTlyyg=nQn~vWc7wA)N5IPCpD1ZgWUN49x3cvU)Z-pR zkI$~rbDhw&)@@O%RnBC_Y{unfpv#NE40;VY*5qGvgApgdbJWKA_HTv-nh4pEQZkY| zuQ{@TvPsOF(H!4=F}>AzOnb)Z&2&Hh2T4$Q$e>%E`&%E-7*G#^?=+tzSKr_Vx-gq* zH0f*N&rfN5_R{wh%0PeNtNslfL}Elz&3*8h+bq%5@&h9tu<`kSXAmm`$?8)|oHd!0 zo`es0ap2`W(m3;|xny2mh7660pu1$4g#83HJuBdzwX-=g=QbwOO{yW}Nz2&Hk&O(Y zx1jaSPkAdht>B;RZWMJTcmuo}o_&$u*XSN|{+<*lelJjug?o?JX^uE7?gQnfMucDW zix!hHGTndsh;&eLHE;zBOeLr;u(+T=P=?lHHw!GplW@t|9tUACP%!d?iz}0gY$E3d3hS>|UeHyOka)YEjJ+S=ZK6d$D^< z-gd&wTK^#>e@L5t2D9H9!Mav~)P#N#`EWAHEQI$EMnl()yXG0+nxRzuyC;d;w|YJ- z?DhN3)g4E*Og!oq3x0V{d~9eD-Vrh{B%j(p_3-;+*L6A*IxS@9j$p9dgIhd7-=rPd z7!4UJp6^(0v~d(qb3!uR)VC^tc_*?7Y+p1W+qb5d@`Ij`VnQQRhCkBGoq6OZ2=R|5 z;V%4B4Z#YiA%0a!fX>n}9tM_DAr04&n*K4EeSf8Pwq-WWnMozjUn*OJEKQ#lDOI8H zjw>U6m?YTYN z8c0sr%|E|gzpJr+z z(RJir2mcuBW@I6k8-aVQdd{hOshHf;WynCZt}mY4hiURVG1boU!wNGaBVO*W#qj5} zcn&iElHLB)%LweW?Lo<9_)DEAxNY)Sk1$y(Yh4o+tg<(dpX67aXtr~m@&vP&a7sl^kx~4XA~hA~ zq+>FH^PwHo*0q13P_O6>ZkeTdL=QR1#>25;qoQzf0Wu0rK8Se?Aj|(RzTO0$t?c~+ zp5IItr8i{$-VA5_uS_^=Q+>weU|(-wl{juT2=NXC+TVWO-a4hMuOq` ztk?f>xcldHJ>XD?%U2e%>i!>Ft31L;GwIRDOx3fS>qme^F4mkF^ZZ7as85x}4Wamc z=a4H=tE-L$d;-JI&pSI^QBx7O(LAN#rKBd{t-u1lfk}Mg=K11nFFhM?J=3j6#*XQ| zKgzjWiE*ca?}7E7ZD<_08A5Wc0i)V?dRX@U!%O!bxJ4ayqQ$B`{*U8k6>wBOgw`P) z1inp0A8YtXOeFBJVZ^~s_pXV!;stR-(V(+r7+Dg zxfG_#(6+4F#Lbh)W|d8eU<`aLW9)r_3c1iBdz}oL5`_8?478GNlc(Ff!z?EU# z{`?`;SSse#7l{1%wtfR8A}LbJs~$(xiiwaR%igXP(n?vi&^I^uBK^2*!7}&uNJCEY z-enO2cQE-kTZ>nlITrS*F}cykdY{)`iK)DJMM<1H)Kp^Y{jBO-aLQBI$qLDFn+?0vyHS7GX@ABCJJ@j3$L zV$3RSooSdzg(w&WQPL0MPb%beC z>egjO(n{lE4p=-OqO;APbYiRH-lUHXZ-l|;_0ieG@ zKH^W+zXrRX>;KR3CAa(Qe`GKv+}8!}KL5_Cx`kFLJNUf|MxTu3TXnPg|2}*_!YC9# zIi7^OJ)U~0(}fyBo;DErw4HG~A-5yo0qkmB%8h)*M2Pad$kQ_RqJJUf1{nGwcbMH~ zP6onZs^kW%ltN9F&jhQehev+?qbji3@Q5Lo)>LnuG;HHR@xNN?Vd#DP$nLo7eD9CO z{jCzs=~d{4y_1o6k=swC(jbj8X5YX~9@vxw1KeaNdcE9^lS0KTJniz&^pO$yKp zK^jH<>(_ik`f9GjGpqR04qMiynJ|83B&hTI9XsD3`+w|}Pb$g|K4Zk24zw4mQ^$PT z)E3#GLoA~M4e76wqiUsbI@)m^K<}TYj~@Yo;bl&5KEH|L-vZ7%;oQ;3eMcJq)ATru z4w=hO-Re2awz3W_QgK1TmW>0QtYXt?VNX9fHyg7mC)};;zN`F^=%=~T|?F`pKMfuI2XO3EneYKngZOc457RLgn$w70*pLX+iRi=Fa z63mN^&KIc!pdRVGO$EAw5#AI5R~HcIxW1vUq(7G3x4y~l!|2G#2S5yoaX<~B_mEjd zT72(AQH0U1sW+4r88EzXdsF?|U_DGJyFOsh2=G5VKYi7kW>f+gGwoL$r#V>wa|a5J z!t7Xmb%XqirsW4_LQA0@Av=X?hMvZ9eZf@@63(6~=eSG#U#f*n4n`2afHTkH`tHSQFJtq45eM^5BO$#{B0l zc21tRyz+V~-#R8mAb~SjO3@fNypMk}J~7TcR{l-CvIFR4&wa#NfNg1tb5SE@!+Ci{ z9MC=A2PJ!aNjAWojs%yECEa2#CE|Sg!=J|-BuTzbetG0$4>}0R3y&(R8S&36$H92wCagGIP zZ&C{t|8UzUk9;_)EIetS`jyuhb{ldYu-mc#yX_NO7;|LpA?JHtyap_|$5kJ9S;AW! zoa~yX*eI0Y?Th)t?MJ~Ms^H3}8Pp9&`M_z6U4%DPI1thTA+i4{go@SL6;SR)p!;DJ4V@9uDYrHmB zN45=dWW<&s^*cgq8jA0{bxXBeDHy;X=x-t&EYzFUdflXRK#0OO2AfHBH+hf)sonw> zX4Dat3%jcGjf)Y>vEm7zHdV%id};$YPko5UVxm_)tj&vfY(uc@sPobnkgU~5ZxgMA z!+auaMte^5#0~#@mLn@jNAY>@X^UF#9`&ZDPRy0q$>4JwQI6+Uuoi3_ zxfUoV5hmY#HQ=&>_|}yq_UdcvulVowV@9rTM8BKCb|jcpEp{w4eiDeO1snAnt-n%J zxaTp86=bGNWUF#Z!nN~D-$aq$%R`qA)vmJj?D;Ad-MD>p9?iP)bv1lN&;gQ6HgjS3 zB?pGbA$3kQn-Q)qDGqf*CO4D_Imm&ZYgbUT=Z_@eo;njW9DX95mhgoRa(D3@D7)rL>gU=#A;g*bgeGinuju*OohFS^D?Zz%8rlU z8ul~6(9bA0FDwn%ZZ3}78`cZsHA4MX0*O$M&(OJlZW>B7Y6OpoRD_8wIHqX_sS*5h z)9?n{!Of~^sJ8WsRfikUjF&4QyV5h4^LgZM2*YMnRpNNwRn8itWS-qVlGEYGxDUW= zbhqw~Mdpi$sXUDrJ7-QVo4<18(aUy+1U!E`Okme^lm8&QIK_(CMJ6?%H_2t3KrP}? z-naIi+ze;iU$A~t^4N2}XGe>EFJg%gYR|(w+)h#Q+c7(%&`|DqaTK)9<`J^8^knsY z7`O5269Bqi;U*lAM=M!2HD*q?jUX#ObDoTjukLziY=}0$2m-&TMHP*L7f~Otj5hjG zCyl*Fc*?0}MF}6cEaF^vM+F*r zep(dA*R#Q`C{_}dH~{Nck@eDFTYQFNk3d!{HaiFB1MgK*B+;t62+ACO7b z#WzqRlIDK;mpBQZ&PLfgioS>hZ|R$FM*atPVJ$J}K7GYt1HCu(e!$d|x3K`u9j}44 zqZAbv2sJ5*Pu|xE$RpgddnJ6%pDjPvw(x0@c#-TdYjR8;s3#RxhV9n8U7VuV5I2s_ z;7$y9Ea!$s&nVfC`d9ds1n|^`f?kpy2;gDfZ zQ9r|hCcHWLc>pjt<+pQhi;|f1{8fCBR5(bw48~+$%Xw5nz4B~TM7au%^1Sp`cX2v@ z1#r&)V^>O7OSZc+ItKEA;g??AhId%jv`#$?!ky!eQW{S8!VYlnRj1y<1mYT9*h>f( z={|I~8tlf7Nzemv3E;KJ6wIe9pVnrPb>?dEQu9aw6L5;JlDb~$xIZQe#bCXu$;3#m>#OuC-L*0+2(e2!Y0Nl>Z7w3{$S8?Ze<4(3FhvK# z{5ItA6%yeMwNe_=8tpz>)wp_9Z}TH}3W3SQHn<#4s#d%9CBJ}EXNC4 zpo>uN&9t9$n!9P$OVt^Ofbv$in)oZVi6#Z9yu&t;jS6-$x+7rJ zbA|<#d`&hG>8b$HmRUSGUPkPE-GXkzn0f$ortRcE-XoJPQJxHVIYKm;Pzj$5)XU$H zbr0lBBrYq;uhZ)N?PofAo1t-qU)@D8#0WKYP|UL+)` z347bkauMl>TQaZe-$7s=_H=9bv+Cwp3C^O3GWvik9?(|Bw~n<{jqpB#;C#w7FCBtP z&fZSo>%<4hBqPr!bndQ(Pbf{xh=unjc@>9uIS(uO)9y_Zhs?4-FRzA5K)7WF)uj_* z(%ws(%p@1)y$|VR^3-bhCXFvl=`I;w-}9x~(T+0^w>0a`p83Uj@k;R>{3-TK8`u|rff%6E)8kL+TJaIgyxcfi$?xwdkYw5Wz8=qn zLM01t`)zsx8D!Zcaul&d3QORPR#G32&);^?H0_?%u0oZeP$YJYLjz3(FBE%yj`Wf9 z)-?4FVbD$JJc_2NYV|deC*n(aXE(-nhVRvoJ89|Od5oPTPNDV$Vw!e;Y~bfQ{n>Lc zUyUg8efyOmg}6`g_BVthpS&uR4~dli^pvW&Amf!eg@e+XJD1Xh^>aU<{d=GqOa`0% znL0}%fEzc}$)>>{QqXbT$vN&@N*6MZH^INzJT7}JzRuyJi9qz?(Cj^giMRdb z4Ci=gy@3k4`bH-z8^J5ODO8Pq9Vcx#<>R3Cho-K0j>v`e+$!g~Sl=co{b*Az5#?#3 zx|j~qZj?RFs|{4QmRS^A&jD|)hJPXY_3n{ei|>>^k$m)}TTkn%>4ff+hy*xJ*!Gxkg-6fa z^^(fePn-2i7b+L3p|+QfV<5DyB7uTkgvRIY@%}*Rn2Ca!f{9!dwvqCz$E9~7f~Vgq z?(@C009}%EaS;8~#d5ApcA-Y1<+hRm z@38K8n~m zwzoS|g{{C_Yb#fx7SqLj6uw|xJT6l?z6d3OOa!C$lh`h&E?yy>n|4#2oR|}jM)(BI z9@61!tHOp?Z^~=R!$ZHWL>?HSN97MBWl1>Lm^uAQb9d4iZ*l@6bk#6i4T^vL7x6Ke0dIc3pvqi#TBe4*BR}d91xWm)7Cp~ z=Z1EVj$QIPdEd*xC4p(gC~CVcwYlCo(Y!>_GvldVO+@h9Zp;i%Q;@kX^@RBWnQ$Ji zE_Lk$x3G_`1e560Ywd0IX`A=IEb?aO>WKm~vjOU=Hk)1rAxj;L>NuJ!_nyKGQmg&w zoZ-Pww^YaPoUNF&pRmGOe6kEIg+sy3uGQ((I|~V`3Hj(cbdU$D`sAvjy*`+hpgbep z%nSvwL>o{k6VeAJGSkP~Iy|{bF``pu|KKylgvaEUX0Lg%3Z zy8^rtK9uvX%e+q4l4X=f*`!duFNS;}h><;xirJI9G{-&D-;1BkzVDSG!1?#x)5Si2 z1ZUp(orYE+!nl>WWm5Za@R^@Z@56=x;O(z9Gkn$a97xW=`#y9ezaTA+O{}`AEz{h? zXB0sz^5$f%z%?wL9lqZiU#S9e87^yhQA&$0D!&u#W5rokEsaIKM81h7bK+0 zg?*oQq$Qf4JZ>PBK4@I)4)y0BE{SZg>$uoCx3)*ir|F5l@Vt~37+NZjg5Ci2ibeA0 z53M6iKXNNFsX%)=yc=~kLuN;Vf+ls=rS(S){`}!3dS733)-xG{=vU&tjX`d&^0wjw z(0&mPcf&a(b<@TCi(iP8bCsV~_Dp=9(oj73v?btJnM#mrXin1I;w`+&jm>b$xXaQ! z5@4~#IaE47K64s{ohp?+LhBN)*3R*D$@fcmJrYS;ZG@Vz!l<~aZgLsr4fWSLOx;pO zZFnJF{~|rLnUk^tc|qqkAO~B$nUxN`vAGnW!CO@rH==>HUt5eVYP=L-+uzrvbi>&; z!B(8Dy1RT^n`Oqd`t~(nZhvaTZQf4yWRGBxe)3s4OZe@GF!LNAeuR@0H3g~ zNdKb;1Lc5cFV_q!LwU2K0z4bkVk!7@{U@iPpxoF_R3`tHcTv{grAEaNtIbaT{ zCT6r-b;LT5n`)7ORrSsU?;X*W1Th zu#tMJ6mVLYMX}bkoJDJ-ZZKs(%ToD5As>dH!wZzW}dI70VU z=JG#EH$tRqw$O@F9^(GwmVUG}e9M#i^V}CVA65-9;xgF0@dPYZPrPfYNCoSLO1?K8 zmm!6hxiE`f@9jzIPqNE%YjJSTFH5aL8dj?*2ToWhu*bJ1!^A|zMu+Q-dYjqgPkRm* zpC9Xe(E^=^Tzln5Vy^URIVHn=a02ADI3G^{U@ap>FA&9>l}k`jw-1{PBS<_+YTeMy z>FX@xZN)-EPD)D8(_MhbRPOg-l~%fC7W=3PfY!WP@)J3jYG7 zGXXz(KCec`C6&g>u}JVAs7HFN_CNEb;@f$XdifJk8NHugnQgk)an5y(R*m#@S&8$L zk_$>mSPSk<(8j5H*Qy&QmJ-G`yJ3b8AL^D?QM)U6X7A-rCosFmi#2D;Da%S+k82f3j=oQ^hk1{;bo?KO+CcSc}kit5&l1mqy5 zIOD!R#&zX)Df1-zFG;f`?w-OWAsF|3(Un;fxIAH2czg48rHf=iDk!GkI6cSs$6p4$ z`cK`{;!#i_nhnSN^}_Wl+~0#5$K=bzoQ83r4*y15)8<-PIpbnT!7H z&oPg-)40#s{`2_;(phA);i&jEswTgH-2hRy61k2kXa8_61^)$Y^_gk3C1d-OgiXei z)iE@08nXZPm0bR?5%J*FG-usj*UtNv(jx-3`sXVw6 z$-k`w`M7B-Xud3`xMhDXMB^3R$6`&0R#uf}D=t+UsWr`gTQ1IPS>}ZKe0eGkrUFNn zYug&T8+49I764NViupRRU}I4atxjyj)_45b*yqeMI2@C%EUT=WW_pBI?7c@#y0WQ4 z$wxFk$#%I9AFoC<+RT^#hg@&3#geR2wK&_MZF+K8>VD|cafxIJa#7vD0d8I%FO}N9 zG{2`Q@K{FCL&s4-B;98#Ds6$SpbzvwdjVEGSv=rjQ8Il81@{HzSY7J>_!rWU~P zXPyOT2H}D_&O>#sxK^pEsTp}#*mGK9yW$P*vP`qh_LlOKgpx*?tSvn*n(|>52RrXh&;;l^e=5MD%WwG z5dbm}ih&FSm9hLR@yjv1NxT7`b}!%}jm~urcbjc6Do>K8$0r9IQhahYs)!}SyOP$y zFvnSfU^0+)SYqs0>lkOVSe+zYj%(L4xP(7d)x(Bv+$g#nJRQ8ekluaw&UUSWMJ)*b zN3tM?CU@_Fn23s9CoFam!q0qk5diVK@SRu}UlLXnyU<=DpdHMm)xsfAI^vwBgfUfKf@2*0ZCo7P#;{RXEJ0 zu1p$<8~_G|ryo03zPWV*7yH*DEQ^b4+?}$}WK-0)bR>AwTvjb8EVwB^nwg>!#e!g{6#< zs8%_3bSxJ@hvWXck`2DDm4Cc4@TXZuRN2={KCdf%S0w(10JF39Kkot$e>Kr2x)&q?bWr}SgKt1=ME`V5qdMS+Tgl3Z zeCP7dq?1oRi}KkUeUtz=W@SwcNF&an9cTVn2Pt_KcX}L1sEQsQ=w|4~)*oW2XO~x| z8;J=zgN?pr-rsNizC_v8Ct;Uq)EF($@6JH&lXq~7@=vre08|14JReLbg@3`6fh}wqTK90#W`;x&YmYQH*u-8KZNp2K)(QF*T5! zQdZaFpYEOzaDpz0-uK<%4vd1a@?{PUbdJsAzgZWEkU=a{f!PN_7?1x~GC0Az?{8-L z#z_D3%c}&K@~JO?H`&wN$a7X$G0zu5bKM)lA2 z%eWZ~0~!Uyy}+yj6ac1;OhYpq$czAlh|;g&qnF!0;b3By>q)3b&_g~f*ADPv!0St8 z9Y6g$AsFKG@8MK>mEyT}ysRsIP38cEj1lyq_x><%c3#F!tC$$+2>4BXUf1XRqs?Cn zyb~J;nDsYlGgalB_p0SdZcR5zsDUyEXIeV!2kr(yj}{zfOR=HFe=Q2{C*NRrf<5unPB>>HgnQxX{cYSZw|ygrv+`en6n^u5LM|Wr$lE;N%V+1~XP$GWhRM z3dpE3?`60*{PVjAV3Zeaa6vmkTC#2YZ^g}k3asHhitniL=6OdKpO{rfCp1q81@jJV zlJWQ<{Gwye3IO?)#KaK6UX!ZCl?p{+Adjc;qs~`>P_gPWZWaG*B7I0C9|I)TWK7GR zDr8v(7)WYxOOnJ55yeTz)PTBMQ}6$l?(md)=cDjE(ad^qf{3W!`R?&jd=>Yy_Uxf| zAEkpzZe(f^Y+VFWVo)Kc1M7fT-!%EP0aZ%B z&&hr}&o&92^_5g(B>x7)w;9^vv~gQXOGX@b-d{>;WQTj?@z;t=t*%@ zR_WjsnthN!B@6AZxL=Y4X|H|3+dxdI zhFzs|#ug<#EyYOXaoUf##|-5UzVYil-nX#6)_}`w&>f^K;(*yh0Thf~R{vu-zeTCG z+%R65u4g_fXIJ`!?(aC)_I;X;71P!bhHN;B?jt5>d0ZTa5V9}S&&S|BVso6mKaM^$&piiI z%_g02&F5^?smf5`yIAe_ZaS`G;oy5Vr^6tC!4L-Mz+RD{bI3zXY2&HcChBPQ`a{IX zH20g8niVRSl4`1ar;*Y{*1AQxH=Y}U`HEdXXakXp7KUl4T5yMd_}ZpJ1Vf1uf88`| zVJ5HT_il?o(&|a^>YZ;#ePBgGu8g9yEnJ^Fr%i}eFtX>V49Me6_}2p8JN+A z%psRLxR!a#?38@>k$?G`zplKar*U^ihCMlTbz@P~NX>x?0`g6Cncb#9ArTRilz=w> z9sAA_RvBY}+`PLQ&7x+6AFdqA|7nnTW}dwUXH}^e53}AN~TjehUX{z*`;*;{NdNX1?PUK(5^*z zWl=)H`8N&{Tr&-2oSXi zlb#4-Dqf{xAWB@^I?%D!!XGSPEEFqbJ;VDlG%!`JPIMfb<5*10z`iRp6!(84;o~8d z)v?e(FiM5Z`NPh!b)_g=eLC!~yaW3m0kDr2+8t4>MCg)2LpV+4W1uwKl}iT#!Kpvz zkY4YzkLC_@qAOIXQb$ zh!0b_l$fU^lu}Q%>VkU5MatpwH=VZ|Zwq*jCR9XsPNUv8l9FnYr>8ejtth!VOLht7 z>5^zHb5y9(?7W?^Zb<{!#zWgMezb~_1r7v!M8cpA+4rpdw3bNp4)bICg(fr#2gy+t zraj#A@s_pVxf=tzb(NEayW+MUJT_f{Y?W1z4eLw{qIarJfLzH@l014$4Lh~T`(734 z4r+uQYH@?C?lR^8kbMo&BN7FxPcql7k5iBrsF>xS{!6wEvWog^1cuPIrwK?9=$ufd zyP|9N#MLTI0jR>&99B^YGlW*Vi^x`PzSV`W1#eT$*zPfla%Bq~N9=xCysltEn5Bp) z)=k*52S(_HOh^WKdrL{$v;~^G>&Y3Q%-I=gG^UIdOL*bKw$E zy=`_v`NLiuTv`5fG$@PDf=l1gii-!oSzrQ%C&SopQgC{k`k%Ub{FQ4$vNX%|#1)*; zkhGmaRI-ry!Yzt&u}kl~Xc0i8S?Zll@%XFXY(*fRbXPeuMABVBwAA)EAxIpVP(2?Y zrLB?2?+|I2fIdTXah$4`mWcL$HbDKg6z|0LIbrct>x;_SrkVWO#K{5LLZ<`xwyKe? z!UXit8@x`y(6=p2(^Jb69FwYk!CU@PUL2%7bi%#M-^)EpM2iZMyif5r`7yt0A*(MQ z)f#dh&MgFj#S3rr0b^qfOPTlFMYX?dt4KZQTs{;zQS+7zz#eFZQV%4bcPykvUknuV zq&>rrJWd2d->4Vga!%vi7=@BXrbwifaDl!?mbT?;9va8?PBeDNCgv`fL`qYp zzmOpG(mPNoi~%km)6O9*4iM2_rw`Cx4==LKyXEvw&m;A4yML*<3Td2j_QXKAGR2__Ul(%3`keT&&qOL2I%mJ6@T<`Hi`_emg&a4>~o5A!kQ zr8+6dC@Wr=u1qS{hvF0$ z5zV$&*_dhpLn1UUt*PIckhgq19_1tKE}DVU5NiqV=$&|tkQ8T*j=s!;5|q;9qhnBk z;w0!Ir;tX9;eJ|ubI~bBxchrmOm^MHtkRge(1!Z{d=BGIb9w>*L4<423rNqDuzzlIm>x>zkJ%`uSSLV-Q-%QqFw-oYM;wPd1_nsKFSu^iB8}1G$egLwSbstNgJJfT zn_{c;=}qnlHCp;iF;B+M1Co+ zYj_@WF*q)@-s{-9uc#ztaA8?Y1|GQa{g_i};hbx!E6aGVF(fOjRLv{u!%SNq)i#KQ z(BH2>ItM1OD~M<6HtMou#7za>TX6CEOn@*g!0(hq<#?6lcbYHdn&B9Bf%e_}lOFjn z>E)kbI%r@C+|bdf*9F2%_>3o4x2)CVRR!O1G{o`;TJ8|DYgorm(28yh#s`2{oELfM zci8eM4fC2O1`a3<>B6SQ#$M)EHi~UF^6nd3Rw6;r9jPY3Vz*3_^x!PGuW+SA^8)|o z>pEbjdyi^6;V1*)lsiT9Tq`~c(^UrSQjK}7udhpIHe|}xZ43y}0Dj(nn_4f8W>|87 z5lzK(-(UeF=B2hVe|#;kQq1Glv1AjwFSxuW=FeV=4&A}{>0Nj*!>%T6qD8bze!Pfr z&E3{iKe02dUXR$Z@K;*5t|;YHl6j^CnA-rC@R|q#&;U1}^oK_KE#r*?=ktu4lJTyK zv@|GxONtSV95i;hvXOL!Y>lz5e1Vy*XJeT2W5AS?=-ak8j!VA)&T_%!RWgYw%gN&D zT`yTWnx7OP;PPVPz9G&497TG%BTCox5&e>kIoO@k)MLJqUa;?mIl%Yx1LbwOiewW# ziACu^#gjA5(O4gTW}pIy1dKV}UmAd{k-P^2*~?rYCNrN^e_&S7>bqoDnO)$zj9NdnTJ-+v^9__&%@i|vw}vs)#kTq>+nj7hP2@^4R@7h z`%I77!~|HpazYSOMjGJRGycfomo7rez6Zhu_ZO)?4rDT!5!CR|;czo>Y%j3nNRIM{ z2gEhYHxwxe8qtb^#<;dZ(w^j4l8Cn?Qn_zXSPBle0hO}JNxzgk7x0DswKHe21$=bq zZ!uho8-_0S)FD&3{s@ya}6C!4n!r8l@FS%fPA^$XA7eH?BpeF9vs;xgML z!EN^10saHDw84ldRkS!n9Ge{856qK(#&BadA%J3y3@^i(MgYmSXb89VgB(EiJ&FDn zgHdFDZ^1%CsTp(J=_n9w_4E1LF8m^)(Zj{iX2j8Y* zo$IT1e2&8<01jxH&O$cqyj+?Kd^J2qE$Y5=-(u#SqbLQ)H462M(v#hl7M(Gx7}I(WR>eeWfB<_jjKwD4iWkNe_y|i`f?sBLZzw z#tHzqE$<)C`}2o-^QezZ`L&%qzRY#~F@m&Fi7S)EH*PL`=BVPYfa}hgyM#}LT5-VJ znWBU@T29Ou(csUaI~L(`t=sr81S;u-QTqDGi*UmSesR%iUXJJ9Y~mvUxy68(8=Ec9 zy&Q;B*>B6vk+V1GE4X=Cv9bTHgxEr$Bl|!X55nq$;Dx#XUYw@Q?G&R;;gB6G2=A5b zs8B+W9y968iUSR#bUV}Uvsx`-FhRYBhLe&s2gjLH(so}&R!wqfP*R3(y3 z!VOb6Fk{z^m|Myyc|`>~7D^ZsJ2|Ns(Q&)0p`F5c7s!sRVOi{fIU;gm3am--0z;TL z61N0kF2KCUt%w2-lr~rqeS5+rM@Q%r>$2m-mCoUExROlDum@BJGt%i_qMLsw{`LM1 z^)|{^;7sItF>^@}tKErGa>0H9QN<4^1ZdeVMkz`_T7OE`T_o6;eAtqRKj?N6A{%wlphDt1w$aHYjIXf|d)ZWdo6^WQp zsR;!i2e^ScIXY&OKhgdyA)${T^hkH+xA-*<_l~6J(};smw}Upbe@XV`{RaoOeH1-w z@05zTpM0lV4MlT_)+S-Np2Vu$A-&#uPkkpUdtfe5P9i01d}SGU$eptk%VYZ*v}5pU z^viznY7-u_`jJwqgDKbZFAqS9JFD61uU^>V9UbV0TMs$HZOB*sLK!(ql?~F;> z7C7h>ky}XSsETE=zpPUr_BmZ29f7Ksj> z;GW@6Fz#nol-D7+N+9JCe5u#^z`0310GKqqV28 zeo)}k%yU1Mr*B?*yLp-J2A8S+cOXa_2W4j6w8EKNXJ50M*CbDrG zIJP1+IZNeMOU;)$iY`xr-k@dhSfX?DNNgPbDz7Wvvo^Td5hE`3p!Vb&($mG>q_vv$ z4YpIHm7Dp|08JbqK9}*7X^qdWtz3#H9==@m*x*JOTdle&n2|au+5(#esHywBg-7mo8v7|wkljPPku?*j}&#*FPTX06k&48r`uthilAoxCeS7&M8qz$F_VrGH&p zvk}Er=>58#%-qnL9blK-{uoQw6c^M|Ed$YN`)b{B($SABbG*hg9g}1(viB|`ZJZGo zBz(MvIGc+H^{f*>LsZG!K?~NPW9ND8YJj=AhpN@lm;+hYU+(2RY?BuMQ%H@{XwSbpzU$J*b7g5AVo>CYE5If*I4o|E3 z1-j>2M+sY=C-Qj{Ps?t}y-cb%F>KaTi1wB`&aXOV&OSm90@xZF+j}L}$5l`*&PQvT z(=^l@4{Za-8Qa!V-wl*^8L!6RP*4EPAxK_-Huv$;d-2Rs_7ao0PxUMCO{f7cwqDR) z7Gr)wFe1gd`z?w6WofwDN5~2BirGv>2 zHJb!1z_>7u;3OOM_*jYi^W-dapn3T!$zIU6QYpPcW?OG#EVecYRj^$v7_z`Lt zcW0}h;K<+<)yA9G8zopUO41)?jA~x=Eh3;q6;V~3DR8R7R+hJsP?kK^;y5uEyhK|5 z4_sC6*hR~JdxtqH{X8aG)%p_$3eCzRQ(O~cLi9^IN3LPWfZ>7HP@P?;gG`PA7d!@mN2q22H?$;#50 zf<#8@9HfV`t;n08oGdGEeOIwAI+3+2;u@2{sCUpk}T~*K6xe zHSwgZKAzXn6CAX&{V_*;j~!<&_UbmL*_5H+HmQ}esgtz&ghF^y4D1Tu0^6Xo;Siwm zHUA%LNhf@N9e3`mcyaZaL9PxDb5e11P@E8_kCK>%r$)WR{x~BeiRQfO~aqzAwwvc3tsaC52qwnsVYZrdMA zJQQ6!*53)qo%!`aNX|^Rf!0Gce!jR9`DniMbAb7%{aZ}u=}M3in*!qX)rm*zG*(@N9(6BOt34Cvj}V}j@f_B#LiY6b9#^4e@&G+2<&FgDm3XrF%WYPf= zo0|O#ULys^n|0%PL#Lrbwma@=H!3;*3$Liu4Qb&RGqaar#Lqt{o5<_b-1 zOd?wPb*tS4NeI3?!@Jwl$I&mWIY2q3%D%v9z+0E04S1n}AKuX^H&`9Zd58&4!d1cd zYWSW8eo-u-O=ei2y^V89?8D`pXG8i`rd#(_`^UUlb83%Uve}<|f}5VgrLEO6ZU#bY zJR!?Y1`J6jshUwc%5Yb@?W}>{tx51lnS8k4v3f(r?4xd)}9U?hcUdI$PoCAr0w+U zA`mc>{OjxfN0mfYA#5lYu*lOZxIm7Ij`g@?&8NU^uI{{3LJ7imD`$d*JFNRpHmm?~ z5b0Hlu6J#pQ81m=l>qd>#t2UQY0B9sIjhL6Sd(OmS*S_0}0RO{qt16A@|b>h6g z;sOPoaDT|37}Fi-^cbkh~vADY{sAy(H}xF-IjGWJbkb ziMo;iBKG37q0WqL@XP3koi#_6T ztNYk{^L!UAr*vuHp^~eKFOw2u@v_z`8cC%Wo+Uku)o-|dbu0Dkk*Z*$dhR2?XF#_X z{foTsSecn%+-B5mhju_j#z-a>aCVn7+vxw_I`fRbzP1k zC2`(Kw*uIy6m`2NIC;fY35>1s#-Y-u%2E4K3dZm@C-(W*_kLSGL0gcO+@UE{N$Dya zX0h7sfgGJ>L7`#GtDJkCM9e$?AHz8O>Z>(M~N9p&_#{;F@@j+@C+FaX3DG zaplDooOkw8B0|FI^J7^v#~kK@JjA6%LCiS>->!8o=PB{C7^bC(P50fa2ujs+>a|;M z@0}9u)Wm7x+~dYPiwXRWcE=udq9~1}g!g(PJ_Ja4aC8F&?PoLdmdcnqa zAqpTp3g}Bwvx*@d4WB$hwVAxX3&dbBoL(gLgZDEbJOlLco!C0iyv(W)eMJBXy?Ofd z+A%D5qXZC>XvwJNbRRNF6bD+w_)X#-S;fCaAi0B1fS`Uj{>ZFXm$u$Ra1N@#-{mfId^8qi$S64cyhb-Tc3|+i z*HB*w08Dh?dukQCy$3f~*(k9>UhcvIY-i%NqMXiOX?Xe5miuW(Gm4d*u@unfF%jJz z0E>NPy6w&KkGZcu6s|M3DBcxz`kHqd$Zk8bR z=HLbfEJE!zh?|3o#CT@0!epkTIK7i+%Qn;Fcz(Kkq;jNU3Nw|u{CUy1@J{N^&d#U; zt!8_8&|-u}>z)kmVU@a;BAT+hna)vCm|G~4Ux0wuQ=21hVsP|756oX6MxPfki;^g0!r5dGd4U+zb)0I4U6iCX z#z0S<{SEe9Zak$p#svM}b@-jw;~(7rZ9WNfc8leA`~Nv%G(5iPr|wfYGD2U zI`zJ)_$Wi)I8S((-Q!Hn{qg@4{e<}T zIvLCUlOF%C3okx*U-y*`1VWA_iukOMfBx`=2N&59`ovU|>se?7eri6?CM_okQbL&DEiB!n0w{EwU~1sQ;f=*?5b+0t5)nv<=N{v z@w0!3tA?gI{y*%!hgTEb*C>n~5kYztq<4@|1q20B=me0CD7`6!E>a$qmV_31sFKj7 z7(fuDNGJ3lA_z)HKtOs4#cz0i_kG`Qt-J0YaM!wfCSit|IcMkWbI$Cu>t^?r=XBIQ zWA>xIyZwW&VpA(jG}ErU7DY8WQI`A$MbLe*V-4Xnxq1z1V9dB2n|l#oRN^E>?PZ$m zyGe`)8*Ks7tlg)hxOkpt?ds)A*DhYYbe^ycNV7&qfB6;zkMxy0cON{2GxCBAnT(!Z zW#*H4g21Tf@oN}+z1ozuBmA3c?b3w{Y8Pz&JU;;P#!q|dc+^|%Zje2HhaUf$RSi3E zd}e8@EF31DU(S7eK?0OiPx+K1%(n9Q-nn6(#3`h)4UA?@{gIw}RtxLssO@=A0xnP! zo}RB_M4ozJ?CB@XJ8IWGpB@H)59$G%_mB$oA&X>7KAo>OgL)zenXHT7aHjQ9JjQlU z^$Nm48ZHfzQL}UAR^}bgyVMtV8**k6ikbAadL2{+34|Mxwz?>tu%f<64e?G!4Zu_sGjwTv949E&NO zX{h9HTtvT6;DPn`qrc-py!{c9j@{BQ1Lt9HKAxU_gBKu>RF5}rPl^SGS#xhAUzXq8 z#kt7)2Igsb!3Q)SqdkL?;H}Uo8m?|CuC6+;sL?!0nGY@jwivX1ZY4RtWvi}>=8$~g z&_}1?U{1c7;1Cp(JEE1~fc563}&v*8nl)hZzxWG3cUI zSKs&L;2D=)x}rfaYlBa~&z9|pac+|{l3kZTc#N(NGbi0`J&FpF2M6$N zu@%NhVRDMHWyC#u-ur~Y(1Pb*z9=fgPIG^WoEF?q?QuN&+0{Yxh~!y|lt@S3aN@P% z@f7g0uaWi6GbvEVLy~liB-c=_)1mDEd#mx+d6$fV&#NL zZS~;DuAUtMT07A0@rR>Zt41su=JgWAr;eZ!*sf@jvejRTC^g#sfTN_dX;&+AD=i>DSDRK;z{yvw?a%&c0WJ2OmMZfq z3yM%d&H+r6&dANrKU1G~>^hs<|JbPG(dWbR-M;G9c{vyiAoWXh0*4}mci9jv4ItI~ z&7F_gqM9?UAd_p;j$f zREZX5vaEm}UsnHhni5j_Wq01W(MS9Dc9s{b%=2c?S)Ub0~~_=7EA}$(4c;k zg+b0Z#-=#N(+EgtcRTFRHTR~>tdoTJsX@4No6O9FQ9SK{mUY8V>xia08;G1Q*n&Eq z$NSc_v?`FrZ1*^^ntlOXaqPGuX=Pp)nfu0`+3vi+!ON%dk(kXY=0s>~?o|o8O@u1d z#gWz<2DJ9GvumPs>IDo@43Tl{cOk;STxR9YJZZDC^xQTf<>#M@v+T|vI!iw)F9fo1 z)DJt{3;5Al<0#nOqklZteVr9H+cOKB>6ww4O|R*0%cYKwL~fE)xp?u6UN~TzvqMV> zc`x>35Q;q=4GY9&PV?Ul9}@>s#Fw7Z zh{pk2lqr1Y{{SH1YcBfI2Vnz7y8iW6Md;<3ECvAo>urio*C=N>`69hK86*2>6z(d%6ot*^ zJ^1_|Fd1ZPzbnlL1MUd-iUZa~PS>bs)2XoSK&9g5=l1|__AUX8;}pS@y|d9OO{%u? zOP^~=y^#GIzBhbPd_Yr4W6{{AI#Z za4-ho8<>Com}K58Uz$di?r#6c-fb$Ay#loXVmWP z%IbhiGhVrL4vco9PM2N*WWD{r==-W2Aa*({Cve(N!`80o44la@TS7toHE3A%y;O`V z3w9@0dWQv*6~`a5;K(PBu*j6+T_a{eoSd8x)cvFk8Hfwe?H3P5$v~weC#B%<@Nm@D z(R0`4yZ{#OeI<<5IN{lv)k|h+fQ@jlddZrOX9QRYBocKOA}Dy3vIBt6pF05*o}4o- ztm&+*>A*+k5(5LnInDrw)9|6OHs`q*rqh}A5xfK3w{ap1R1#i08gdnTa&}GqfRDR+ z@{`?<-^}8D%=F^2|1}Z-mc`y|WwGQF8_UK*{{$9;2_>tmPk34?yb%tQ_NcY#+E7<} z))ITp&Wc+B)>g6Jz}@dsCv(056LDylpdhbRd4-?}nN8b#Kw9}Av!3<9!>X(96{70w zW^H}QhHV$&+0h{o0meXr?PHb?k~z4nqb>+|?tF0l$M;6;l*2W%<5#U7TTd^zQzm+l zr;AAg%^>S!`zdriQ_*9Vliv&T8QPAYoeKsXaAC+YvjQ6iTBfRR3gni3fi9f3(Ug{0 zA9~jEWx$$v{|TJ{40~B#M1sWTZsx9fsXa+5COtM5?&`kB09?0-%WN zt3{}l)t(hsNG{D3DjTb}^QP|Xn5l;8Nu~Q=3cDWH^2({&-=FJ4*Z+Obh1skuu6aw^ zw)hA-A!v9y82e-TBjg<%&>f}++^XvF`*%MrKS_IwC#QzS)O7q>b0%}b$Y?g-yLY9X z#FuQ+Bz;t$f~#)TA^7Nj9AnvNG>WxL)MNGn3}r`|sN8PUn?@+LSc>}VhAk>H?fR9d zB3kxU%}D8~-{)E0*!_gstn6oDGZ`m36pI)i(ePx*s288Ib*0sJIkN?!@T@U3((v>pYofHHGkg&Ymz8;X7wpDo`Nf;!(~n}-M0@&s&Y{m8n}}74OsjG< zUaI^u9>d6lpK+7*^`rU%sf@_4Y_qvy3L@)($(+LGmR4l6o=gN_AT!+p7#I>fui2~| za8wIA4c^Jhmz;Y>mjWd+F4wk1i~7!!*lswBv3#80F#2Me`-MXah^9-V=`TA2l2yFf zEc#0U2O?V^8vmsz2C{{o0C`eI9gbCH=YENN;+tnI^7$Sgf_4xZAg9eDgk7tzkd_`91e}P1$$#5SGqPM+t zvIf>RwFJ0bH410w@%wJpnf+Yv6vJFF|zG1P8l^y-B@S zZd$u}2j{-J*G1QBbJM@Ul4#GJ|7F+^yk_mu=Wm}xaK&d^dHBjusRlsa%t)*fsbs(HolG;73}qP zwb)8n6XHi)m*@MNVq)S717z=HDlNCPwc=g#!1euRCY-7vO1 zpl-^ZW8+6G6O&Z-IUjc%Qh3ZwBc`psA(z*5 za)z7`q7N0@Oh4{c4J`$TkYZqIAP@_pXBCPOc{k%GQgd|csGjCh0htxh`7$u_2lwtRmX z4lX;$&(ttwI3r}^=VFn;p2(1*gUB-xmbAPbX)r6E(avi@QTG$Dyuo-MEQFVtm9$$o zWVnG0gubTOd*fzLI;zl2TAaqw;lmk@7-V}(o>AhyqKhw78$wO+cW?mSONg5$Rdtj2 z8*BX*?FW-u`bOdTD25i^ic$;NcvQ)9Z%BdgUkW+K)h=A)cx zB5`?sW{eAqF$rm)F2PIrx$u>zQW`TQ$-7krgY+(5U&$uUUG6hwSensq4}ZEXhcwxQ z_3<1(Wg?@hg*w6E-jv!lD?K*M@9`ULbRd(A;E?a1W`K-k5$) zEMXn1Y0~r0$yVHYVmYUY_tCO?rK}pGe?szZQ5V;3CL*KjC0$I67NXRL1dlMQl5^iX zV+>c~5|;Co;EX+ZhKv1LS;P7L0D~_X6GJqERhI7}^LzCM)xW23=x?to)Rq=X%Y6`F zy7BUcv8;q`12Yf}e6G{y=0OED-nUk=&4BD&gAQXIl3a;=9*~6;y|xY zmFt+aBx5X*___2C@I4u%ua9JZ3>uHI>pzM$4zAq)*j*pGCt8tJ5pB}jp9hT+0?|dd zgvONM<8tDq>$EMT9%|`}!itSj>TkTfdEL|7o*Yd89TzoqL#Vyd$PVgJH2P+upbUDh zW&O67pXm8bO9JeS>zSjQA?k5GqhoC>%1V*mpOl{!Sp@179-T8mSvuX%9VbddR7Wvk zv<3q1jLt#|rzp34%NTvq+uw#YK`AaDyRo=R^emw|zo+SImp~JdaH?sT+Ee@cC4)ou z1Xsj?)z8lUIK1et0H0vb)N6mfD_oBI^)_3g!noWM`Ru;YuUfr$&$f7jYk~*|(;U-d z?Z*=L?D@1zyK>;~cAAU7eoJA$_aIwKPaOG47hfFnP3DSfOlzKXk1@jrEPxM9&aH4*nzOO{qCdCuD&KDJuh|qKsq~@=gbi%DM04Pw z1=DYKqEB5cz*hcIlPFrL*4h&SSLE5+ilWL~`rFg!6ELb!tJ(lQhvTRjVe`q(qlV0XResY z<}#oGmekj&j8>hm4SO^#JUm$p5n{+Iz^y>q^Yh(=m1+o()U44d2UN|WN4f6Wy2h+M zP>V9GJ7L4hm+6@>9kOd0$#^9GzG|2v%D0o6bYT<*y+rzZvlKV z{J~|dK}>v}5*kIJhDJhq-Oq&FH+S_xVW1Tt-2B@gKPH~?@ZdV^J?}K02!uKci^ggt zzRu`)N5ihK`^9y!W6m~VB$?eo_A3+A+hj8n?B@h8=!h7Kx_!YoIf%RCalZ9DZDw)t zV2E}Qt#=__?_qF@jDUDSzye>baJL7%U5Q1~pwDn+W-x_((;P0Y&$9fayJx9Ks*Y6h zTsO~({*iz_E@RoAYWUZ1IO8Ks$yW_Mj;f%;u>j4*6iDwcXE#@)=W<|MsDdL82}jnV zRhQsJFFh(R%s+GXH(7V8H;P52tUe z;ND59ZY16q61wQx4(A{;B#V?i0b4K?4EgoUo1Ap&fh}{5w+X@heFrw3(%Oq*Kx0ME zBi@CKF_n*oXD+3$4jyp+;?aC(%=q}s)lWPITs6tJ=!miEt_zJaVC$ajm3m)fA1@Qn zGnTZ|*jKgoNwTl{@*o4Fg+<`->OJLL!!jghp&~@b?Y4@4g`6jUFU1V>B;tkp(BqUae~aKX z0;5@C->!JG2^`Es{>bt6TSWR#;AmTk@ zJ0or1Q*$}ujOtmC@%qv^rqL~MyX86%3Lng9_)R!j){q3jgT28w8QsieBjXV~c?rTU zp{ClJ4)PvgWZE}k${u5M^?1CpMCyPy2ka+g`!q}8R$-$`6gSc?+^*%lVbq{HuX%@b z03A=XO$W?6SFUe5i7h^fJ075lCh{+FVf1)+(!^O9OKdK*KpzQL;rc^Qr2GUAq9$&w6Q zc%?!>uR0A~ZJ}T71t66>FQv6 zKc0W@dge`(29_{4ypz6dzHkpdPnw^YBkf#61}A#rwmrHgw>|pV*fRjcr4IX;EqI(B z&t*8ld{=r~(wpjOcm2M0Yy+HhTt5<5$G?!?bLx$=av6 zBW7!7i7n2%DaUI0l@q5zCs}Ik_&8H?F7q+pp}|gV*npXlO-xjMo9ao`X`u#Pf=#~z z&85E-ysEjKXFY!@{NOTojBL$LC0-E!Xpx&HDrQusdh@zrHn&3Be(+D* ziJcc=&I605x&QmAsu9l^gx&%gTEB7xB=ON+S%nW zvV4n#*x6KA8xIVtrW>_G#&eha4~4`Am#N7*Sv*pg$v2r<1Ik>MN#l3)ajeOK$(AxO$}UWr!YH$B+62X0L|0 zbjIdzFbf7k!vlHS;;(bjtH`><(v<@7$P@dvZ3`SzV=)ARW2%MA9J}ADnkDh8?^14^ z&|e}*pSsF6ej zrfP^}U>0KpCbAVxtrEL8o;}YFMW8tqE)yBrg8XCZtu~Dqvc|pIxjBajj!0l$a z3dN_wT|?Bvn*%q`LGK)32zIDR^j5|(_o!Oysm2NS{WBUqd5RuVWjv_;%;Wb7DG`6&j1A z9LmBWg$3*x$p98G7z~Gk6t*CP*ILw1Z|sRz^@&#<{o3E-E@<<2q|Md$0E(GL_zPFw zXBxn$i;V)OE|uyiu|6=bVzbNEgu3K(gD#ana@ z@{r{%x;efM$npfUt+G`1PKD>d(84e{*;>u{h!M_v1t8S<{s?syXaivT7cw&b zJ^1$z@Ri)_2rC3gZ%|B*E2x_AR>dt?$L2rdov2oDTARq$kp~cvmzQ8hOATdieSAKY za(17=hB~BoTT06Nyp6<&0NOxyfb)JMTwwwS^8!b)dhn7Gun!Ie(Ca@Wlaq5Q`-(Ly zJ{8xGotUF?>f0(B@17pA9@&Q5z6ox)eP8>r_6`YBSXgk5G0<=R8xU0Qw%+zh^;OG@ zSDn772v_|Bmh@Zk-J=(-g!FAs=k#q$dxyGu2a#!<5z6wq(oQl~}+)33T{h;McdruDXw@qvk zT?~y#1?EG9>QDUMz9Tth*`Q9``VY~%7;kJgSnntA1Q<78A1BB|84aL&h zoWXq0RRM=_9Y!ef_BH0lT{FBR(_yed-LDB?tUIP!E+h)$P2mPfu_!vADTVQsV&c{I z_I7>SaM)<%)niN-z858y&1jb0lN_Vy*rA>F)a!6mwj%tHQuY0^<4h5^yPO8v`p@X& z6ri~6WnZP+G-8vZVM(J^eE?~lqdOC}ZWYGUcaXHI%`^CP)&{uO#-O$&_o?LpJ#De< zY;89cAb)fqq?lAtin?KTN*-B$P*>CWnZURZ~H$Se<@xk ztqoXMuQj_T{c_yFH9Z%aJp5VMKR*70gUWM!%kfy{1((bLv*Sh}!KrT|C1ilttqJ>X z-#JQc_@nbia1A)}#yE)v^8qChCvn4*w0B#t*n*DmFVu<2b0QrS!=ki9eEj*Cm~@5* z+U@&GVRI^~914-KUyilomp5nCXa7tcz2IW}O93^uw>vp!CMH7T8P-?T=Ik14)r~{w z(3dmFos`h=aqCRn8sap4(;@eJar}jp)1AK*hhh3*f1Gy@&UVOvBgG5V>kprazkLFE3Kk#vhVkBGnYCejbf#2Qju zooAo0tw5rS@Jpy0-0Sc#v*R!Xe*vi+Ph%Qr&2 zEGVSe8cUcz#&#j%kttF&?Qv*%sno^=y~DsM>?Z}r0gO}EA~MyI9ugH9==7#5&|3~; zbV@_J`{;U>4$whn?Kyrq-$1IBYy;y=ULXNIG0{2~Gxn=vE;Tf&m7RP_8{_na89mhz z$3fK|`!LAb3Fz;oP0>Wv;y;6fB~!ZAeqlct^eh#BDsjq&5v8IL7TiHRk;WSuRMDty z^C;f?79G&wXLNt8%XG)btDnMr6r_stpFamwZ6EW|i^W^`n8*(&;r5|g2huGCx4}+j zq&Vv2>${ND;zN6Qd*$+c;y{uMIwCQESt0i)-#6prJu z?PrU9a}y`JyG`+tv(vI+-L}T^YH7sE>NO#a-wRGVsNx3I$5wtkC}D6W#|!JYxn*tb zJ;i3GKQ+4a)A$XSZrV+^sF-0mX0-Zg)%kQd5FxEQUNzD6GARB~vFpe@SLm8#m(&Mu za2hd5T|G%VO%zNe6p4HigY)czi)U+*2X`Vd=0%SL826BK>!Hp2D?|NIT0W~S3IiPnSc97>s7$<(IHMW=DrCl=FaV|f9L?t+< zOH~QbYkiD%hyx>jb%d0?S(bFEI&v`jOQBWaI9;5-KL*n&ffT;!N!4fG$Fyb zX*()3NXH|06ME1W9Y5l9Y-a@o10riHWlHNM_K3euw%D!0SQm;m5P>|DhZsg}0ftE8OmFT}W$|8M0XLv(s*3AKdpcM20ka{+y9j2FyVRwk z(G|hN2XH;k#`g9O0B@imkiw_Hw$2bye?MQ+d@IDX>j=RxCd4F|tTtnj9|&-};v~Cd zi!fZrgh$n2R(U3%g6a({o{-6d&83)aVdC@R1rrHN7Sc8RfniYnMFq4g{cAT;t7F*y zgcC%gI4wnue+VJXG{9^t4CT)uqPq=B;^nsyE{S5rY3IBZ{+}7eY%OBGLsmY#y$w$! z_|mhb9AbqJ!~GDp^8?lD0E4`?h4W}k^{^PucQaN*udJ#K>0}>mEqDRO;r-?xQahe< z68=&o2~oYD=kQAHnb5Knv=9BM%}&1!3+d?STgp}DQY3U9gIIl2J0>pBW-v%)vi46% zkVNPO7PpP(#4zTSMSbdtNeYaO%GXSkEbUOk_ z9|*E{^;6WX*|m3ey}o1pE<4e=o>74Kf<;M?sO%7o&fad;C62GjKetQ4pdRZEy&%z& zS*>HX>&ZGPX-gA1d?4*p=ut_~d*4AWr|VFm(PKPvK-_$Tzwp=B#1%d+7owk>G4S#m zCBRzvSdTHkz|=4u;yt?GgY`uV*7*)=TB)!Z@9~E1YRRzOj@i3fd(Iuz--eqM$=+wS zTzWyQ+h+s2%}v)N8mbH`1q46Q&23A0tArh6xMVn^d+FDX{KUt{Juu8dy&YzWL|D=U z(k+fpInDk>=n%7Qm5S#4M8bT_RLqO5p=z<-{-(&dekbZ2&b96g8JioR#SolVu;34i zL^mo-SMN3%vAejc)~B;+FCWj$zhevjRdsJ0TFuw37r@uudfF2gY!otJyyl|A7`-cQ zByKPuZ*QY}#Nn%JK15BT?e60t8wS&%aZ>Y~BwES42RJ$XiuJe@wET%jsx{A*M zuKorXAp$p)I(P3PZt#Z@352p~!uApCN2y8f>!^mLLnpS7rfZw^6+gh66Be{l1yk0N zikZa~ksd`1k{?#3^dIAQ^9H&{bq-CIP;Zg;#D7Ys9M`FWn2#WH+kSiFR|atjOn_$4=WZ7`)%=ekUY6 z@Dbml&ZZ*5EluZSOK{Mgn)*KcOLM2v?ia`}9zgrsW1CpC5_{64L((q$VtcTTK2v8) zi9bhkK6G%Pta2Xrr-ZVkWV&FG$^D@h-eY``1fp6QQ#N;N}!nLntc4% z996XmHW4xFD!@cFS)I|AjK1VHuT{mbs^;0M7LA5L&v!A2cu9ka zc}ErN$|-U@uySgN-ao$uzpA!+eL>QB%zCJ7Y=E_lxT6V?^$&@z)$yK^Kw@Q$g`M`tL#dB;RBXeK`2Yb2x=Y->dhgE;mO!YlpW{Hr zX{1-7>_Q;}Q3)1T#N-uJ9_kYMx4@u*p<7#*$=A25eL7UsC5QVKa>e9TG!U@y7nDnU ziQUqRDn8vzB#&yT@~yBFC$DLmm@N^D-8KBCzvI@`H4hKl5~}Ba(aG`f&*4?0Lv!b z9F7$MfgEf{dbX4JiCQS#wlmB=?~t8TGEy42`wp2NBSMFbG$n$J>@yXdL_)-JY~kB; zccCh>n0WSXCPwGkF*kzf-OcyyLc+(`LlMP94= zb7*$ty(Y|_!NoRiepn|- zF7Qsxt zD6lsUwWC<*f|3KgE=e!sAjO-cnU14Cy4klH)KAFXxP&>hQz!O5^z=uyykpgbN3BIQ zM@w6n&yHr)PiWF{y;?n4=D7Z)%(yz=)WM?ULQ_4oo~Dh(XDZFxrhC6--2jkLo7@6q z(ig8#QeL`pney`G%hxC_Pymh>mv7PkE29R!YgYCPfTik@|3{H=k*H1r0$)^ zZ)9xe^SWwN!k7fSOygKpp8y6^D@bmAzJbnhh^He)ci-WjYrc9H{yv%Y8Y9!=h*=de)=<{&(V_kDPhq25z(iWbC5x;dE-eAL+9;urW7x!n1-`^pXfB*)0 zL`F}|F@OFPX#s(|(pOy>M*cTmMvRuwUz8Erc%^vyQER1AvTRr{=kRe+&sOzoJZTp0 z%KyNl+yR+!POFMOtz9bVc@m$pP-#0gGI^Qw1T3k>Kh}XCVITUL#e2&`NgM*7MQS)C z$1-D%)CH6#?c35~E(M)GTpS+(LGKr*T{BPl2b~R#2_wqdcjF-b29F}y7(__8&nW9q zo%0`7HD>?)tnk5rzoh}rSlC2c#c;~5VX)qegg-M}daEJpvK|ZlR8Eb42z`Qw`nsGx zE(3W){1q|h>^@1d5Wck{;T$#5vR1-oYD8^&=*%Ov$iGYw9!)%8OG@~c0RK&i|91dT z?f*5x|D}!4yw3;;;{-9yOUK2#eU_0&KqSy}N_uuQcQoV0rUJcBLW13t#i7XycY{*x z@pL7$N4edH`PZ*$9VO=&p8o#eVM}~=ZQJVMNy?z>!$FDU?)mnx&z|JiXZT5iTR^W{ z=EWbMcyDi~#d>rb|F8>t#0-6nKDlxq7f_!S`l8Yy$ivdYr|(T}`(ic&e%r|-oYykv z>6?pb^r)jZ$xb4pMIT5WFm3N_Lzi6zivlCNNDWX{qc#QhWT#}>c?Ck6eVDJrV3pwh z9$fR?yg>8A-QO4L0)?v3+&rwS9o25OiNP){Cl9&^ufN1kSZ2ajDC?n}^4PBz^-fYy zQf^4lWRW9o`w_E>nPv3hgO4XWtlo6^W}YOro7Doo;Y#itH~8%OrH`v~8E>nvNQ0xC z!1cF&pFCoZ`-$~1AB~!D4;>`&biC^yWtU~qe*LWO!a+p2j9R&=OY%n(*oB58<>vy* zxQ2&oF}mXUisjF+@#(tD6>;JYR%|eWwa^pqEt4QAShu6-75)i_Cw-Td+bl41!#ML; zYD<<&UY!pLXb)qszd8*Yl#uitzv%BJ+&7?Fx#c3Oh~d6ajbv)ym4t?O-k!6w?ey6lo%LjRZ~=?Bkg#8`;k z(OHD|Olu*(zE%xYON=ro?5LUfR=<%(VQ|Va`P7)lFt7R0u*$Sh)e#(_^~6y$?j+0b z?l0rzO>VnbFn6$H#m;eQf-qwwP$zd%FRTC5L8zM)co%7pCtv}c3_q`&?o6(B-pK55 zac%UQ1Jl06jsc@5#&6iPa7u9T87Rt6GFB(*A)ek_gP-fMs*efkwp{r$$(R+@L7Hoq zOw4zMQ+eBd&ro+WT}_|m-3EDPE66bZy<%D=984#6c~Qjk0;rj(b?SBo@T16MW(>%;I+B!;7OOWM?8bFL$^?8Ey>kaj$@;Nh_{+vR)jKgKya=F*63 zHS}5CtmZG?4Km@bCM0NN-iTguTPEMWnzSEabYtoFh4Bk>>0~nKYOA<22FY@@ zn6Nd@o}Fp!R)3K|I>c2@d{-<~O&VE`rF55BTW2^K4MnZ6)NHa5FIOb`@M;p0BiL}J-2yVNLqwx=qSSHU zd1-%GNu@E!!SKeGT_Zr0bo#-|UZUbbHzjXVp}sM<-4lC`lvF6yqskgX*|3cuo&()F zef7SoMBV+=nT8%#Hab83aNaO=AM2;`J&iYt)Osr4FRI~BQ-5Hoom!lSOWI*SHfMrBA!Qj(jv&~u-?GeqDqA!HSs(ddE)nGG8}Sva%lvZ<9vxbW zJ!8lDMhK2-o9Z%u{78Ilet`(i_x&M3?oh|?tr1$%n6%y+u1Mv>#T|KAhDzq7mP$sW zu1CY?8qc(vct$pmu;Ay_79Nr9oc!6E&qO7-DF0F*lbdipogsLfNI}h#;w>w>!MdT> zc^&thhJS9lLYR44b|Fb5&#XzuR>Jkuhwx=5^ubMhROF<;%u?pf*Lp4qRI$Trs6)eS z>aI8ogv2I%ZkQTIe{u3zBJcIB4Ch0yrlgg@s8gO)^KbpL?q`5On*E!^UhQw@*uIQ> zW5gQ`HcH#h<4*P0;}zlG44XG=dM0YTOhf~qX>E0|TezwTo|&DJvO*{^4H=iGrU~ST zNTwcJ#q5ubZ3 zm=$ezN3Hk^V-}}LigJc6t)3)RSq;Y+SD*NQuD_qq&CGGQsyBX?vis{0m`D#E>8vSwneDVohs#j| zpTxt=2{Fjb!jg7#kOh!n$#=XD8Qi0897?8z2$Z`Mqg=~Bb8M9C=Kjow217$jlP(YQ zeGw+ur6){NSn>u_ngo(&LUPG6W$i*`9HD9%h0JqzR3AUmNZWTW;5qxfy!K)6Vd!AK zucKh%xWfYOvqvkp+LaMHS8)%8blsZI&d#4hQ3Brq7N_19UzC zKZL$tZN~HXOgn{fnr^qqN>tielXW!E?uExkP8I$rEzM_<3*H6QM^8%h3XPY`^NrCs z)ls;74)>mJuc8(Hbh7_MZ7oxmk9>t~+Dq+2*k1jWk+7Xglb+K{iMMVPu#U*xW;m{D zGMe3u=F6YEVSM^cCL#rf7?UkZ3lkvK2DXM&7>O0ks`5=jvt;PXKvE5}YSfQtQb-%Y zigfmr(r?q~zo?4FYU7QsFl}DS_x1Yn3RSUvTdi~i=f2e&w%4Xr##oQ z*?Y3>L-5Sj7g|B+TOAJaii!?D#Knrj`s%)y2Aq5fDa9KVkG6t?DONbM8y|5YM-V(C zPe%085gC*G%1cb3VBzwcXWI7W5XT(XsI}wq&i#CbGfg7`c#^X3=T9e-xsZ|Wbu7iRyZsPBn-+Nqwq zmo4=bf>)!AkuW&c8+!Xa>Q)$)?8iwr`+n!pz_6B#_!B+yS55TQFi(%C`%k0;q8GAR z=~p#j;UhYP+UyZ|oTCk`keH3dBLjd)--eDPqb0_w>4Wo2B0fgbrllAzo@T7z0f!;j z^rW;+grDVMJBzeAxrgGHi%ndD-Y?UiIfjv? zPW!~4Kx0qoO`p_~I^lVzk#futSA|gr-Sm=a6pW&NRB96|?=>^#-$cFAee2uAu|}Hq zRg4{tZgI6lrVtkv-QS5ez`M=A*vbtTG>qX4l3$~&V&4b=IabujD0C8Fb~#NhhIS6- zuI5e5tN(rx|3c}ir7VYS%k?p9H6Yqw%r4;58T>rS-ZWiD-ro4ZtcqDYShYS_)12P%|s zZuW4pxLDN-J?FrFFTg>>O;@44w+p4zX2vMY*c@A6bXhE~6E8pPwQu4w@U|maadu01 z@V;}6@OxpKn_B-2mVfdpOB!R5%Y(PPHFCWvp0urpea^|Cq#jS&a=FDp9gT&IOVjeN zdgVd$KgUvN9H)MlkkNgaZVtn`&J>?YX}os5$^k+=rPc}6PT=tk;f`|_AC@kw!artMkH9|K&F;r*G z^x4Gf;2496H1H_%$oRZtbpobEwt8O(mfj-h7u=hoTSy@yTQupzag6`R!etkh47KEK z-&$%=&tzv9ACr6kBu(C!(~-<`HMMR3nk7r`9xQ|%eXGj@WPWMNQ}^XlB~F*&@AlUW zxc7a6?D$~@?!5O?IjwvMtwuIb`)L+IjZ2#S{~vpA9Ti8@^@|cLz~Jt|eQ<~1?(VLG z1`QA(1cJM}y9f8+F2NxLm*5@<5F}>^dF*}Owaz-Xf!g+H?&}`sY!}4z1u~q(-nZGGZ)M!*=nCgAsbZvSoPpFZ|PR7 zP8Nq9flVl-7Q_kfGN6hwrcxZg53R+Vf<9RHI*U0MTR98FK5{}78YJxn7hU4`%vRR< z#mDs@4Cw;zi@-A8w!k);X@!E4Kj0C9*E7&vs{U+VAVSI^=}LaBsOvsXF^qiHg;T!r zZpJ>osE(E22zStEoN63KE>AK?>^)Lor*l;(zsO6X7kMRWg)9T4*id$IUUyhjS#n0^ zWbu26ipqHq8SozgiY`iKydOm$z|UZ*rmxmb5ygiw;B%u4i59dVOVYl#Q`irn&s*ya zwqlki2Kw@N&EV5B2XNipLY+y4}!{}9rM!APS;p?=JHCE6Jx9u z82KH(KH`Po6*!|=&+dx2bZ$-fPl$z?T~7Fn( znLU{$9l&iuPkONKNK6;1FB&11qr73Rpr!4+#N5o$QM;#>Y-S^5S3)ckMhrxzL=O>v ztpwL{;>HLco2^p9;4t@YQROZkL7po72M{^ju7*(Ft)n**T`nh-P!z7G&h%kd0Dhvw zLe;eK$`B^e_a{W|OL~QX=$a8!l27J&A%g6?lDT`;DYqZ|MuZA4Di|X{RoPWFh+H;! zXY{$nc5MwzUx?0;%5pYD{jXRFR_(ePxezs%U9FfEQ~+&+*;5@BDr72v_hiB~I+EiF zE}yqCdZ~p#D7B6~5o&+i3C$A?5)C1$&N4rKbtjl2|LX9EIu#!N1c9|=Z0}|DR$h;N^{Zeti&V@vKx9O@vh$;wotSqCYQv^std6Ojll@=O z0hb0-(}N!tE+WB5=42MEYy?KKn`=6C&cyLOwIW^T=f{yq%7Ee+n!0e~p0}K-7OT0g z$S7h{*;6K*J(*`Tc4urGITzkUtDcYw{wF6W-&KplcSF^9P1B4%b}OZQtIM9mw~Vvf z={+muAPz$y&Ao_tk9CB@DseH#E?t5COyU=YC0=SRq$_hRujO8R5Qa}J9VM?vvY;#H~o@bxki0)QFma6+4Z!RO7dp5d2$P?bV zaG*hx+PiepS2&HPcGLRiAr#RO{)SYn#?Z2iC#|sNhI*@g$YMFOe8+#zkm4?Bfnl+B zHvKe1Lw7Q!gxzV`oh#adj)7(JtT^7CMNG3dlH#BVqJ7{ycM3yBdB^cM7zfCRkU8jr zWH<-?sI~=TU&*L|PVz+jMo>w6=O{Kd*Gj-ZM-#o?oseAFO%^8N4VfU5r_1>68mZoK zO`W=cHu_xIbf`S&UIKj3u-`qhFQ$iI) zV8v|UMjF4Hzl`Mm@BmJU^}|-NNR{?e!2t8Q&{hwG?asR?0ZiO&s3ppHNGJKc$wjKD znJn}S$$U;b!D0bYR!q<~-xtbwGSyG7u93j+d5`9w2b~y9F|+}J`G%}t#J>T{4Ou^) zq1@D!yTXPc$1*j{3k=;zBXH1#55u5Oun2r#Tt07x^(A30MTDvwDveNckg~EXp{p=hqZ{zdD%yPhOv~VB9+ug(bTU!!o%R;MdYnX8!vpe5?I8ASrcguqIn4hp{J%u_|Cc2EPxR=&=pbi|w1e~E z@H*4V=qr|srGZUT56`(TFI%yTyb5cuj~2*oWCOPa2?$138hAO%AVwGZaJOU@eUkzP zoL@vld_75CG2Ls9>hC|l`ai!OW~oZ2492gA4aRR)iVHHp`DfVp`GOjoh6kV8`$Kmm%dt9 zfBy`K_8Dn_FM%;~VLf@4?go`jP%@ETy7{A0@ISx6bHf8SO9KsigS+3*wzVukjZRBM%M(1SN@faVsY8aZK; zj_^xY-_NfqA|sxrhIr5$*38!(m-^FBOxd@aKnji%T$%As6A`3iylCS*;BfB<%40hM zZrs(9{ajMqsR^r8EhB>?j+-n4+>&-vkEp_J_G3w&9x*lrQ>Cw|&UylGa4h*Uebc|e z`a6wsAh9Tv&)D$vWP%jN@iDSr@lWOuep}WawL3|H_i-_Wa#$8H_6a0nN2?#XQ$qYfCo1fG!f}>T)vxM zxc=Ji(xjrqlJnQy{xBGWx{YRGUl|FKrbs4MHD)OHUH#Lv% zfLqm-Ls*Ldr@?NH56+pM&L;BKeMxtm9VE$rrQssX#j0*n@Dj@U^}~w6HD9;EfiUY zGo63k>Ly}aAT4>)RHG)NzD@xn)8p|bIq>PV`l*WJEDP0iVD^>VT|mh{@^Q2ea8&~4 zrM5}6LW<4^_-FLvx=ny{BCk@AwoO1Kb`4I2d1Za~JOG3}0nFA}hnVJ@_{2u9Z3p=U zro4J0g70s2PHogDfNxf<^CTeEx+3+%eKrY|8r52}^b+7&2oEwfW=E{Q);oP^c1>5X z;A!Lz03X(>aB8V0#Qvki4%y%BdI=JIlMjE$8l84G_&g#wQ8vWA^o4klUnFaYvhS;8 zP5p}(BOT9u!og{s0)w~HCktSn3$;lZw50t|g zUJ?6t1bc-0E;UD_@#9rb+KG=b!ZT|W%JKfZTE)X)ef9mrjlT&lb)q9ZG@3%WQ?bV3pz8wTM>+6VtrPN3h{rd+zz+~RD}bh#KG0)D zhG9{tHa_Z2o#$(yWBi=Raw~-)t;15k|E~U!ppY|Z9%Y0kmV4^KrFvq^g=e(eHTXRv zZcwDv7(u`nw@3NbeR5Uuz$4)ftfx)KrlN0V0qSLH^qpl`p*XoA(ONLYtCMJEK#g+9 zWXN*JSpY;893Hf&!>9d{17}IP{)bTEybGNQoeGKdj>w-7SdTJcni4v|_Sm`zNp_H2 zlfADrx7VY3KY^jkQjw2C)H_G5a_dJ%&pyf#xUxReY%Va9Fr6g=)~;!*fGYJev-D~e z|Lq5iXxW<)iaUR)dXqX`)o2eJG@FA0UK4FOU2Gd;L6^*s;LAHNc4uVJL`UpLqbe|p z0$?5i^v{ioyI-jDpVUha9TUk*URuHw{x-1=p7wIpJTYC*8)x2*0NmJ*KUVwE*2PBS zSxQZ~6Qy0}NcU!Q1Ya5bH<2O>E?%ZH3xp{CE;Yq<$+s_%_%e;h@Nv$Uv&Ma1F?scW zdm%q+HB>%B_uAC?St;qwHfLV)m#jO!>ual}Z;$OvJaDSt{HtGEvk@kF1~e9&MK3Z< zozqmCUu~`zIgj$>iM+M;OrKru1grjk%JJ%Z3hOTG3Xw`{^%BZ}%|AdfXmcfKZC?-JUHakv0X}w12tWKG_b%pxBHp7&FLiLa{fPepB=g#UB zUn&bl|KsZaAiWlY09Kz#ePXlDSGjGy`oxxbFo@JA*39XQ5a0YkDmPzoI1Ne7sJ`n* z)&;%>n?iR6Y<#}kk8K~n5sZ3sv8-@Wza`@OFSciB6goRzrc7hzE7gJJP^dFnRR3zF$=v@cJ;u ztDwi3XlpgNKnX9la=$1$<{GQ*~Cm{Y{c{#@L^|u)Ahe4=oyyWf<^^zhIqG6k_LW?2g?2vw56%Xmo|@3q8^n zB^v9StuoDXGASyRGTM1wG{<(}9zF&lo3!Ntt*@opRo0s$&p5c&JhH7xW+{1#l!{S^ zFu%#q4#~30tS7V2=cTx+j;1DFD=+;6+vW^mi5pR6>SCoW9_JPf`Zlt3` zkEcGl9gmn%^b0e%Xm?g~@tv&s3cld&=XZt}eU_^7i_K04Tb|B~^Q zV~q%nM*44`!a-C@rDMV-)hpD^qo9J1Yf+BrBe&oRenE@XGriI{nGXD@an5__cPgr} zP3mtiX=zJ#KXjMd-0Vo3W;zE(>hVRIa*8$`pAw-v}#eA!xS+zL4h1Bu6ic?G391Z+@kVE9x zEKe{Z{$-)89xYV6RJnDtp<3wxS9zsoWr0p^&EX^mORKE#8{X$A31BN6BSv4Apjx`2 zc42m{Nug9~_zvkw3tB&w%>)=D6lss*70An!>8W4J>wOUAwAqd{yMmvi3a_QqPn@xi z1}LuT>IikTwS%qoEZW^{BwVsV-N*eJzfZ+to6m;1~y~2vPIZ0MfOrNxYHM-`GtcXi3 z$#ZgLq=7!2j7u3%j&_EV*cqve{wvJ=v@xi2oncFsxgdrGD((z3YP`xUXrWG85`71i zpfdIi`wl_mhIO#xkYAeK~jy0Z3B~yeqdba0ophQE!Bo zM$=EGk^UH@oQg`nwQVHT-`|6w13QBAwllLi4zV4Qf`e)#aYfnEjI{7>D>xaVK6AneItIK=TAI86b=ge~C7DV{B(Fl0*d zUJ|*TkD4W5w7Q$|FUU?>ANf~Q4%=H!usYfKK1B;U^}j&zX-MEd z`jdQT`v-`*)u(_b?*o4~`e`hnq6+xScOcLxkMU~PP1Y(`UyT6%h5EQ5zfqqLoCK=l z%=ZuP;QI3)IRAM6KUfj-8~vg>AL$br_sg@r=J}fkf<~|K%L93A|5Hf(IArm+VFO(w z-CqGaXT>kzVR@|RPe>2`1?f>;Dt8=Dol^9pzP5jANfykD9u08V@sAD}{yz8=!z+i2 zRdD0E>$%B!&aHDW2qOI+uyE@x_iP_ayHopOR>4O!K)m)q}R4#paT1@?Z=17 zeTGYO+8!DaCud^~-{p#UF}4Iti_ig;8c!n6eTo`*uV>w#aJ6*$BX&jB=7vJ85sm-r-F@EWyJxE z`!xiIpIKpYQWg?LI+cG*?5V!6RU1|L#~OXr)3T>_dvUx|(Mp)5_e<0x1(2xZP!jx7kvMl1?ypR2#1ICo#pmZV}=@B3?c(fyb+X8*jM)NpuF}I z%?}2{0Q{DD24{yQv&G?hT>{YCpd6%MUq z+3}*i)R%Nhnk)wRKFW43jS#+A2y(?WflN)oOUUbrmZVEG#R3^HQGV*sV8-vHtRIqG^F&J#lkD z0fOUeQAJVBI&*pVei`Gvb*7&@7pYg|y47lK-N}%wQz~B}!6$m6$tTPZ!0K?S>9b}g zfice&Rb=yX#Ff{>(ETP{fld$}T64%vggIf;p=OIWB5So8TX)FMCEE`5TlE5~wRQ`S zIlNE4HZ>8-TbQ`nJJF3)tyw1q+8KJ!epD0e{w}c6ON3JBU|M=WXs`)!D`MZ*B)zDB-a-REPzIAgcAhTP`mwtdK2h9bK93FWEzu zo>NPPdsFv~P5-w10AatNzm4zmHx&Wtm80mzd&iaTi_LN+h;&`X`XexYK-kUD=wP!7 z{-h|F8X;UKfHn3EZD@^3cY9O(r(<0nM-4@!mxT{$Gs6uE{Pd}rX~<{)LhVz{P^22n z?R+{IMQ2zoCM0YC!f~a>QiU!T*^a)Z8VQ-6$efC4B0bS`DFxN&viha^!k>sN9mzrs+7 zXyf?nADGTK$}o!U$ep9U|24D&h5_pfTGK)doeDi_HNGu);4K~>8G!bKL2@@xJ-^wS z$C^hcBhv;nTW6i@P@H>}ar{PlW^RO?`utDjqqU)=@H_>OP6U1 zJz1*KvgE1Y;={y$;h-xw(i+=an(2JeWZ(4a-x?%bYV1|pb-H5KP$0+MY%oMiv;!05 zvV=3vUF4)SANeiP@968l44oG#5bGL?r5*Xle%ZGI9{PZNdz>9@g$ezY-&XiEp>4<_ zv9*o2#?YNfPp!MxjpCw~_KQtx*B@H5U5K@dnqQ#4#P@NoO%t*IWS;&Cz(F*nKl3m1 zvrfxf1=-#Rr62pIx#qYWYZANVK$Pya((z5k=PT8Jb4kM^V1+}mzhqnInpJ3fT-v0D z@8*wctue&4OD_b_cs9yi;+CJgKAG#H?gD9p3Uv+&>)HXfucyl6P~sL<&z2>@#gE=d z@Aa_FZ&SGLL5rWS*3AJ~S$X0Yxx=pClDQ8SxzD?Eq`FMK~`iej-DYQBC6?&o8Kma+8tJ@nxFUWd0MFDD?Fe{eds*BUWHSY&SJ=fX$EO%%F4LQZv}MQ8WC+Sk2Zq(y3D3&#Svev zZ<9!8bd^}sMhjKQ3rM($eF60^t9f09McG|4X1apD!9G1X)O>#Z>|fk{5w?w_>DPu` zHQG|nmul)0eGT)Qd~C@&BOl(xpo0yAbsGbA&6$?Hr6O|$s8XlEomS_g&6qWoUzdJL zpzGr3ZNcwrxTBrgFH7s=yFRGf(0|Zrac+|JOCAFcb!i*EX|hD`RZ0-8XNKdZsH5Q4c&ZXbna^QjMLB$W+tbdzmw@8Gu(=A`Ea;yPihCTXP*v$yd zl8guMiGP7?YqH$Z`$eoNU6C3Gc#n;In-s0tcOMXn&Y)*XKk8sDr@XZXz`Pj{<~<(m zSPOiztjAt4@qzH;=*eoy)y8$PCouSi^BnLwmcnH1$37^7w6o0RsVBwpE?2X2nigZ9 z$t_X*e*2rvYbxv2TjL)np~aDh6w9MNU+PhZM%-ECWO2-Df>)Yz6(e&l->o2ATtb7u zE8)IF|ChcsZzvn^ezHxZJ>6K52|nwa{(!|10G>t?MAs`&o2e5DA7v}h)b6AY$hU0~ zR$OZU+opdyqMjXC-tMz*J?E7&9*vbHS|#aw+&a3H^Vk{_v>a>O^8Lc{HBS4kn%dKk zPW0mt0N==qP)5zNF-@$OL?e17i*z#L11&{MM^D^1V{ct7&3{YwFKoYtf4k$;2j4?JgL19Yv^Rj=0-uWSg@wyAKfte>k%|K_MOqW`qhG7l~S zAB&&+H8eVDhyD2ZT{fq)xWbdXOKtVXxjF~n%W}T}bLW3;{kq%S%HmUftY7`Q3{${^ zr&u@13<5MS;APtgp4>YxbB^DUM2fqHZPcW7)TFCOXEv&5u-H3{=4@2wcNz>)le%_B zv{JNEZ#lra`_I_-uxAJlvR)=ebJ*Hg#v1IBAA?9HQs1{r&FJO76aEQ-5qZc98M(`K z!W@Qc5t6a|(@NJFtqV&EzC&M>KUF8Q7dZtBcuqe# zV;00x-zsM+Kb$LU`wEZAH^X`*smZ%8Qkv^LdyZP}#c^Y${V}6&=>mBxqk!-LFJdYK z?rToioi6zDN99!nPf0#SIJJ~$Q{tmsnr3N}3EEt|7gA@c8e{|s*{?K>3hz*d;t4Tx z6Rs>SZ8lyX_pl#R3#rzLRD&DCWjrGC?c2Y7wAWcLf|;g~KHConDNiD=5!NyO?&4Z9 zJ=8LU<=WHSs>XA1Kky{%EWP@O@`&<*@`3t@`tY=8MBe>ovN7Me3i;W0VK}?4`lq1` z5cpXVJ#$J6dOM&cG_+PV)LR(`q6AAwLewC1Car6{Ay=e^>-u$$6gOU`6RA%7Y^R(x!;jv zNSFF#d%FIHkz|3;4qEH_*tEmtRLj+;0wj%%8yAx>0tWaz$%da0tvX*h8kRj`nJd~) zu!uPt@4ui{w1fG)z3AF610-Ag5nzX~?>Z2shoPOw7hr1N|ux3fwq9v7;nFJk4kbYS0-CVqT)m;%UYK=xd$RP&cD<8^%aa z5BOOw9XQc#uC=XT7xyyIqI>IC4`hSj>TzrMsBu9!U&gedODnbwB z`P6-3B5q|Pi3cXCM8gS~rjPTQ;JFW*ggU1$Ja&W)k>F;!0iEiHf0@?*_kQM@pWveW z-&OtzLw^EF%U{^?&&|KOQ~4GA5ANCV-?~_-F*)+csXsfKm-t@}pntW%tzpA`a#{rV z{3{$ze?puSiQcuRYQl}8Jgdb}PXEGB72Pe26zlsyVz`ADpEb@JeA*ur=UL#fl-+#c zu0Yw82xCEj-({vZ$uKOMPNlWbP^|m{*529TO`ixVo`uQnK{ZxG8YN<9kcGwVu&Dxc zQJ)zeKcXGsK=Q)_pqBUJmgqB}arIROZPF~KcGsj$RZ^#gY}v_w zyU@Ir1!(sW{AF+PCxmhTcr&%fT*IPybTiXY1u(*{_atIX*`j~TC`FnH1)%JOP(H64 zeN-NC&ILPJuveHzlaryK?Q|R%HS9s;9M|`qOv!(3zA_cfPn@ESA82FO*eGStVh>(^ zJavat@;iwRT+OwE*hi`*`CJaa@4-Rp)#vas81D^QKoannv8Si?6%7S?>|9ITo`m+Y zQZenG9o|8ob{B)1zU^sfH0Xq(^wnGPg;RPT*i|)oQ7+BFEozAhL%TjY1wq)IR^UzC2fC#$kbUY6LP*n-AU|KgAoTe|8TXs5UR3NU|4 z+A<&VVqpkHnoniR{V0>4ermQ6Z<{}yY^b^AwDJ8<2$U`?0k@wJTR$P9#C!!%JEHC( z&JXNrWn$;-@;l$4$$G`u7bF%K(lpQYMmJP(Y7Fl0{J0NnsB~Y!`LHJH`ThQwY1)bzkR2Z=nXv@ zlNwMK^>8IA{>>^!fnpln23S~WZIpxz_7$=7Nh`IcN;LcDR;15hnSey}gUZW>1J^By z@%$!E8{|3H8k0cQj=h4u9zH8&I^#HG(>IhqA;j#S%N%`B6+TFVU4hL+4%cG90jFbJ zp$JQmpL?BfNvw3JEtOuZ*TT)PvoA$}ssgL?df=V`9Vnx1Fp@{ZQ1uYK0mZ;ov32Ob z5;}UrpN00F4Er1Ih%Kp(IvL{?cTxjLG$a7Vt^_y!wf%X{7iL4I@xs6u^ADdpQC{@t zsrvX>Tuv!O4%sZ`eBH5G3_*HH5vmKP1M((}(k``;VAM#q*)OBC^)<89gz*zEk)>b2 zfU>}XwKKUsd)^t_!^iGs=ydR1LI3>-Cjf+5=ge-x2n45e8)DSeM$zL9cvpADjWccE zd&)g`zLV>)N8IRZ5G$`uncVc{R9x8tD+KN+0+9`${U^l7%I&Qf*;gX8p{a$FRjQ3! zNjER?Rc-=lYZO(uGf=Wy(O=&wW~HgXd&o?7@9p{IBpKE?9TIC-=?WF(rC=^Q<}!Tg zEoc7R6(Op__aWH#Ia+BoNW@_StIr$RrzN|T?U1nBX{a?HHwjzc;Kc-JY+EG&p!x8j zAh`{#5{X{)W(dno*|E4jaN~3H#WNO9B${pN=6%suk`nxhvLUxgZ)9UC{6GAJz*OpY zvP+|82+c!3`&T%_ z^+%w=5b5N4xpk80OAgK$OP+2CLoAy!kqNh1I%aMxC(9Lu>#-Xf6!OtU)n4xJ*QAXzQu#h~JirW1 zCk{aArmrguW+gvwEDmE(=+TUy4~%0*8*Qu-Ja||mkMVKv8MqTqq;zZ@c{O79gWb#r zSL_RWb3na}abb;Bn&?9dXhz%D>k2(-Acp4agY)tr!+CFRp%^{?2;nWAG+Ix?(Uv!7 z#hH^%an)=8YU@~^ydK2ce0i&Hnl77M{+_h!=HBu$6iO|Y+_FEMn1?zoD2`_@rCMUE zsjQAE1*_T1=`$$DdId9Gx@AzmzQrWBBA+@~GVQ^ye$RJ67c=ha9wtM@0rHvM92`0x z3EL&`nO#qBUmKCnZ&Ixb^IggXHZl@X$Az>Sic$bodOk7hDkIEAr8$1xjrl&J1pN)= zNP=*fI_$&od^Lf|KBvSMXWLj-b6G*6cMTV$ZZ+mn#0a;#S0FgCnFxQ!kHHsGKfX2_ zdz?y08Mba`>ivWmxq-t@L2Ti64EcC;l~m7gBi=;act?{!qVN7S4gJdzDK$25Vmp69 zR8e_6CjR`xE|oaNm1df^a_T6pJKg7N=-Dl@Rj#2?WeWMoA#3j#cGdjJ@w=?rEqBh4 zXAxgzcs^VUO;%D&LEmQS`k6kX$l_t3QklcbAmV97+tw4zRhuyXdg>X|%_$=-_Pv2> z#hzeLDOcv?H7!Rh?^3`aBT0Nf!~K|qjV?n0+X_xDC8y{t%PBzkTJfQ(m#^I$NYbdx zIJ$U$MLFaJPW|KQKkHum*IrQ~-du*40L89vXtd@z)QP94O*y7q1q+EDlfgVb5 z7j}oiNw|d>#Uh3hNbzRxXy$P0pDKUw^MRH=0_@V-O!s(_ZEnAw#KRU&l!@Dv?~^)! z`xsQ;u^TWz@F31Z{A2T+G7;laFxGB+y)6KNqVmf0tiVo+3|;dfhrKqd>efYuv2)Jx zdq=JKEgRnb3$s-4gecGI?20cQu(De@EjKxl6^opKo#$#_ED_D9pt~%Ys1Z9|z@_R-?Hh)4)Gy-Um4xA`UO)nAY4BU> z^sVbi+zSy104;<`hMKu(fM=V$rM+6*8=D3@8qrh2*w>EjOVNy|x=MOPCwx?rptLv{ zjLKKvvFn$Cg}`mX)0aiEHYIki9Mw>wUsv&+5sErZ|492z8G?F-q}FixMXF4#rwxsk zd?0W#n~e(Bz(fY(hEZDUawe?w zNEh;zP#nE1op<+JBI!e_y7T@(b3jCn{6NJPCFX_uNWq4)S#6WKL0B3s_8d3 zL@M6-;_Ni>jrL>$t9j0+HZsCP7MnQlt&Z1*oO3@R-o~fwDWLg>WX7-Kf&zU5Ux=Nv zzU=EZkVX0I)p5&S+ERl=?a$>XZLU#FO-HkMF32%eXly4IgcyS!R&Dm;LPtjSdsld# z@=HQGe%*bk+p;)1;TMgcwN|IGj+a}jvyUIPRjVJiszth94jXD2?3B=;32geuj)$E7 zKtfI4NJy$d6m@LZ&EW}1pQK$S$U(yW)`f|qaH4tX#$VG&dA1poo|FDP#RE6gZ4}lL z#?!xmc;;?j!gG_-c>> ziEQVoH4mC{oXdo+&m6sKT5233g8;M3TR8OCt|5U@Z(1wyY%hm90reYMpEqk3rWieA z;-_6VILpvVL;U>Ti7lw*7woiKQDvfoWG(JmmBAdEm@&_BI*c5{_x9;J*M6T_ZL@xZ z&6&5*n`#Gu-Tm%Jl_t_v<8jRg?AYsvv37P=F%AgoM~>=({A883C9~w-2p4qAC8RJq zc_zK-Xg!^V@JZ7PwYU73@(_+Lf2p(9KFCaEV<=#1M6N6I2=*|l0xDLT3CO!7ci+pL z&xs${(#Q-)5U_Z>O!b)vxPT1on2|LDfD+-#GtO6mm&xdmsa73vJ6X$u^I5P|?%@LF z$9-Ow&LwGy^>04AHysyVt3L!@-M`>^h)M$|&<1Dh$lLx2G5NnQ13#Ao1`qJ+3QuYM z!QZ9-_$wv}r6Gx>D=jT6Z}~^T<4&qoVe0;$M<4G{TKxMCp|rW7w1q7Pl`U4kz8^RK ztm0|o!}joBwf;xhzjyOf;^Ua2*8U%K6-*+MUT67`hTZGs<*eohSizfiA6TE4S-m^@ z=wpQe+_8oj2aMCTSAt!V-@wTUo5t71cWSTG59r;ysU{0X>`Q0^k;j*=!b-upNw%Bs z@b13*U}Q=5A0OV2pVH_2h?e{bLG}BZ9|Bx%md8Fpp^~!uK6i0~?*%i*H=HX&ET%=$ zr3FJSldKkQGZ$Qiq2C<(+?D+@vnRl(YYPK^>$Jr4S{bi2;V#ijrUJJs>`K z3Wkj5ZMwaFyHhKIBXf}*nY7RHJr>fe4`REwn^NeV2`UNFA@Dk{Q-@U}r<2|6-9m&g zl@AmZz5A7VZ?hUl6p`UJ3X+SOT~g`hP%@N5r`c9pYy@ zAd^;rT+EJywg@pLQ8#u?DC_W?NKE1|hk#e)ccO2loSYfy`lQRV4@rZtd>Bj-V^^n6`!DB3_7E!S-aoF>7-~(ouS+g$_~z1!|F0xrp`p z2b)k9h^Hx~#!~BX66A12*D2=MU}jUfz*LD(V#ag+z^T&d!s00P?SJWZLvxnya)wd_ zkpK^O!@Qr&NFcJ?s#ItQD;z-@48H_O7K+V{6xuzBZ%4}s9Y%k~SQF2Vj*_sUoH*lGLj|YA{_3T|DHJB<+O~S}ckwBurXv&8~YU zc`Y8L!1*O zAaJmqkyQe#&+oKZsET4rL#xWTGCP!71oIp(NX;`x=}bC5v?=tG)ObD*I*fn}8wQD~ zm?9vol$aV>po-a*!q0449Fo?Rh0=AacSS)p-qS*C_Sui;Gm#Lvqxkw=Cm;F$p2Z2c3 zlc1EOuhCa3g%2}q!o z*{mdU^RqYRVY@7yGMR~JQ6-2K*eGe6+&t&u{n!PbxLbYW@njh?Tx>W~ zF*l^|P|8~s#+QEJwrM~@x-0nmNsAMJ+bC|6j915*AIMJaClA0Sn+VFzApp?jHAyRJ z2T$9?A)=F%pjC?s376PYtVSShkq%B?!U_E#uYm)Rj90_aui|;X0AXsUox8;yOVDlM z$66b#ib&w=Tfc(wRy`SLB5BeeqAKVj#ajZnG*!xqlH=BxJISzYy^{A7kr)ho9_L__ zimsg6vl8)Xr-)dki5RA|XZkq&EfVG>{(&lm3-BYt@uZu^832 z?enx=<6SVh_hCk1mebkF3BT=0#)u95`3S8GVekWZQQ&SuWsS372M$beJ(Wyfevx^CQVUj~BHNybgr)R4KUDxG zu0+n6c(oJ85C)LZ%$-#)*o#n{S@Pw$heM-E>U zl?(9>tSer6TFwHK**VoIwp6dNJqE`cT>nv%!K4}`@pi^a9!{Zt58zfyGmTpmx$4Ik zWaZ&9b>j1qQL>WBAzok2r|ttEI0+&TD60c&?eF-E+A49Wtw;yA#_n0%$YnR z-vC@?!zt=l1xaG2TObrzKYuhkF-}T~ZYKrvP&<6IDS<2ppWdsLKahR$thH6pdDrAr zyAT<)1hIp&;erVHIXLdHTLd6O_bw>@MvMiQJR z#uX`?lyfq8kF)Dgyo;ZMd>l0An?Y_5rCEF|rmzJM05nk`;0Vz>>c-FrybsYbp4h^D z!;(EQieVnCEG0=E07{GuHWK6ZJ=fAe!<;hH&hN{lNJ~I(BQ)|_e}3xsLPrdpqd8_wOb9@;e@g`{iPB?dINgA+*f#B|2CXMv57Af z>%DD&w+gm#ul2SV){q(#sRO~M#3jovH4i$2cWDEZZ$+;pj|17RU?$=+!^v|RmKvR` zfJ5D^h&*KSh-odh;|nip1kK(K4s-3ZS=k%rob(q*Ac}>czl5V8Z$=ulr(E(y%fp8g zM=|m6~gNdutm0(S|o7+=pt${mHD-I`T zG6(mKO$mO6)(wA#C0bOeA!o5k_Z-=xD!0&9GjX?{L@Hlr?}PlV$c!V7N|!x<^>dR> z%-3Sxfk~paL~WC$SY3V$(p#7o)m_1V$&fzXxE*-z>f!mEe$k^=2>>|Q{aes?ZQ%tP zIECVas}Z``*A_8$MRRC1JVKmI>=2p7e7Zfs{%Wk&NOs??pDX~K4Aa|~f zhM6uvCB1;Ci7R12!ylwVc*;g`q@xC-9e$paWOX{B*Ta zl!VaTfg!oQI;h@5(ekVaumk|3PIh9S_g6k+49zZ$T8d4KksrHM0|YM)tju@)5d-y6 zk_Y=c+E`gG`AG!ODCAnv9QOS8MvDV2m5M3-FsYQ9d}c&!ZsFwS7yoT%1mL(MrBkVV z714C@9XIGvjPbk4M6>;9M4@Zgh2OxRF`$bFPgg%%jt-Y+l!B@DAYgp~asCtH{gwCr ziIWQ1On|qjq@=-GT@ixM-dy*1IcaJ5V}nMr@7H6ZO73F^345kl_3ulG12**iR?(InFk&Hpm^J!?r}8&tu$+r@mGknt=a$yXxJw<7I?Em3Xv$l}wwc_>(% zaGP1(NWEgFA}On+%yO#ao0d~x0}Z=w+b`WdpvcVPN#1Tp2L7gyvE>}+N-8fY57b6Zy&651Ix>}gFd}BHNGUPnmp0{6oyJovNe4yst)9?hT z`uRZp3U~wpUqHwC?K+ua!rL(^#|p!epHk04YrZzoW1Pe|RTL(~UuYuV#6*0zQ{ryV z9vJ8xp&{nKo;S4AK^KWwjhFad!9nzMP&LY{wU?`JTIT|DMXr19yCh&jQN6npf_))x z*L5hA)SdLg=!&cqsI2XbrGshn91MW5h@QT@VL!a3%%J!eQZ2M4bEofWvn;wE`b zEENCN+)pdaA&vNYRk^XrMvq=`P-o|qElXAn?E~1y+2G1&s_Kx^uDm6Ts_N_K#8_zP zu`gLO_;|tEc`j&X>oWtIpYV8?;O@SKBW>NKe;@--L2`E#Px1MBi00&SPni-OWO}Lc0|o6JBh08mHg@Y=EN{|#lY>(Xs^6dOiUFg?hrAe zsU#vXyNr=Pox|IA_s@PnT|D1KnRt;cDD=-=t+&bYjz_5Bz$m5KC(#m!idbH_%>qBK zKPsQ&gI*;v!5Q!#BO|2Ot{6TDc49gh8bCu$JrO5EsI?cvT@V<)aeX{RT_TSeRv*Lr z0U=_Nt+Rkxd@)x1bJ7P7@GGU~^sc-i_Ibjsgcl6e4t3h*lfo{~bg)#>52vIlVx?(3 zR75Ui{|K-XEE@v2sj38Oj=%x*uJM^RNkg>&GzzpuUA3hhd?HA0MVbM1L&2!7nu?c@ zib6<65u34EGzd{C5niTek&4h-&s1eWRP3DPa}uO0u47R`@kQTaoMNkI$6eUU%U+hf zK7z)HK}%6=Gp?091YB#@2PD8BFu3;wg9yK1a1{)L>Z-oU#Ztl+V(!>KwCQjQOMA7l zz-iPV=~U<(*q}mLLg*c4aY*dr3s5Cj`l5<;{iNspS9v?ukBbx2kw>jMTCN0dz^`cq zzQp6rA1>}oA>I$C87?&J%Kk@`{16CE4gsK#wvaC2E@#v=6tS%%dqtrszmS3|K3F@k z#m4GE?uXl)ini|&Bg4{Vn<2$Uu9t``8RLPn2aOj?<<#%PA+$UVUVCLrkBuBTJih7& zo;q$V`Lc2Vp4v#GI6L9@v48q7K48LX1-nw%+lNRu0Ta8On=w3CbG-)rqphu5lq{gP z%y>fv^@q095L0~jD7Y@M=;K0N$$gpAgY9=kYGg`A@C0sOjz36P#RM?05{U6CGOtH_ zvQ&EJ1S+xO7plcHg*j>RpbbWfIg!T@25(7{{pU`|eGV&#`NyA*b{SWG05`=G*MBt* zHiy9=d4ROIziA=^2?jq(3O{1&)f+i~a^(zY#U4fiV}`2G=XHG)vs`fOglrhahw`KF_h>Vm;C8>}~9qs3Ieh+m%-_Pg& zdi`G><9YG8pX%O-4b=|kF{@8>}lOmnKzaWPjwjEJ@rWui+SolA;=$i&QMgP%? z{~vac!MY?AD)igBf}d)l8Hw2)wmDmXyF%m6D=$I83J0C9C~bj)pHB|%_P{3Fo$Ngp z{yL{kdvKr={zi&7+i$xcJgD>7f$cW-Mr=$|calQYb^`~*2f*>hd*t^Ky=w#;jE z*t!C}AX%d?=ojAYec#eW&;Ir0|FruWuH?f&k$`3?m~{q^JdB$tgrG(`RKGs?o@+t~ z8&uv=dc6oI*4lBgr~TcihIpiU{}Fig!~a{+9sf7CGbSa-6eJG9^5AW(ecjr^=CS#Y z;wR*5fwP0ph;%yayVzL!Ajv)Z{`z~{N)@ckAtsC|K9lY=4sx>XfwY_QXZ=6!`zfgG zZ&&}fQUUhoW*}1xut;1A#fld@dcor8_PB(WoxlMdbu?JMUzXYMq~gsN^%*k`JMPs9 zKe~GB>K$zx(7*5Z_QLw1{I(&vTH|#8*RjP*=c6C-PyFI6QS`4^`Z>ZX1Zehj1>==v zHT%L+)FIG}>$a-3vMWddWL!Odv=D*v$KpsWd7DK`Qo zwfpdE@mJr%*8fM$e(y`EO+62ctrhP8W+K0;e*ckZ$Dz=Gf401{sqRxtSFn7bl5TlV zp~I-)^5dbW(n`AlAJyv$Hl;L$O7X9X3Xv_odswB0srt9kkJ!ZR?$A?{gwrt9?SY5S zoc&`J+P%kPoq{Q*=uV^#Eq>B6{bviT(W`z!y()x3KK*;3;Fjc7y?dQYoy&m~1!Q|( zOg7geJKC-F0{V;Al)P9K;(CF82C5rksafC}j0w0dm)m`P&J_O}Ed^e=Hue(F|-5RjG57+>VEgS_n>Pb(x^8e+>^uqMtA?&5`R5S zm!GbMORKw+R|_l!37f(k!)s1|TI=Mds99Y{Q8|asHtCxj@P`}aS z=jc>uzhIZ4@}%`DSc>^7V6zX;3W+D9sOw&3wUv1q;YrsD`}LtIb!Qgc{PF`bBiWUI zT=_V11F+D3sL}6}C!Ci@G3!AMsP&IpGQWM++ht#9ZlLRAYgQgCRVpZDtPWuoccI!% z*Q+5SVb?%h;T2|dx+Ub0sO)OZJ4}WEgD+~C7y6~g$n~@#>nGudjpQ4)CEDX4;dJZb z!bQ$Q{M=xCZ`*@o5gQ#olhWfT(C?l*X#*1~SrC=hh2QXh{9_&?%%1k;ZI;zASO{fzPh#Ap#p5carZqG!OMJgI} zhBHQHCaw|Y=}69051Di)$S+$c$RNmHfweTBAw9ayY7G;yzL(d!cuEG*#Twsj4ck2y zXCYB|_o4Q;jWE1@KtFaaCaJg3Ej;Ver@!ZTo^;*`NOSSNScgKd=I!6%V##z%Z<%A1 zXb)pz)MC8fDJKR+tTR|u%&AL||G>MpBCQmcC74ko{a!1t_sEb-YW zzkQDUv*?L0nU>65Jatn^MCcgOJrv&XBxxud%b&S?LgnP-V@K=rnJzYB{`71;w`Wcn zJR%(oxl?bDrXGj5jXIjC4DK44!@k$xtzqmS4XZAgT-mX+`(GEG07CVtqScR_b08*s z!y|nQB=!OO%@5S`%N$lHsrF&-ojfLUpTEhn0}iN!x4UBxe1xZu!ie?miG~>cw8+tl zbz$Jk&<0ii5p^{;N{mX9hsnBc1E!2#-|Zz%@Zna#ERnhur?ER%U(^@O4N91!PYzAV zHQv6yt;=(ibSwSuwzoaMuy0?g2avJha-%Ja8_3CTOLs_|?~r>4!RnLb#(kfA(#0Gt zk-iW3OA!K?i)@9bxV2C_DF{-gW!+_RhV;1LMs;F8Vwdr_IDV_8o|UNSKzd}m*TYQl z@J1~qt7~L75}7{f?XKB$G9Oe6W0NL*R(_S===%?t;(JyJTUgb z5kNj=&98B?sg~>gdreKqt*zEo+5jkZyIZz;QgNQspT3EV@X=litUpN-=XhVAMV^ur zcE8|K_lgZVI@Bo~+E482yRc#zt6&HfI*fDV>&ecCT_|tv!%Gc2j)cWAUlhS9kZWI^ zvYS-*F0L&Gli`Z_vDQ3ptnsiygaS_@bu>)eAU9rOA`Y~so8cgx;JwvFnB68#Qyw&C0dAdEk31-H&Fu|D{p;2Gy|A1N z&P)Y^EojG8WIxSc;wFnq6&|9tqK@Nq3Pev=uE{yD6X#C{>c={$M4yhK?Xgi`P~YtF z|MGRRe@2Vavf0VK%zRcGW&l<&=&tm^ysg_GD>c`Y(IRUA94l$*;eK6tDJa7RWLTql z*0=g&^~fcc2zS$lctVsqv7}gwJjylHxMXZf{(Hi!N-$`LL* zw~IXNr>9(IFU(%DMyztyj{Opp&XGlh^mF_*c+z?=U=AgSwEt+es^;?t#T%}=5IGgM zh)k|n7INNn3gKaNUh-hNtoj7h>66${Ks$p)jVI5*wDff2i;j6m%#LDIR%2~QylS7h zyu?U#X|0_T`IsZZ*8w4{$@##CYMOh8UET+^-y(}{R?=fI=3M_k5AGql-?O+lv-%40 zB+I78KoiXdWR|g^8isT}^ozMt%zgoLJxi=BzD~ilCgVwcr`7;@&6YW;Iw2+~jC#&- zkQa3d#9{0^?oacjsz)a2^bQ@(7)+s4my|s zZPlkn2@aF8ZZV&V!G}|?$)n!WFpg`j0cchw=te;wI;4XVwwG@t8!1ly)bY`=Dgfm& zIKqq_GxuE9SNdw{mlf{^R<=>t!MLKUuw&8Rh?GOZ_Z2ai^c`oMW8qFUwMj&GpS$aY zJb!h63Z%RyRJ`Gs%(WqyWE=h%z7zq~^CfzkJDq_jLZ;qR{4?r&Z*KLf_`}^ktym@S5pFnfmI(&jX5(CsW=Gn)`# zVWllmHQsNb$RKmIkFWD9hU%dxC-zn}%8CTSC*-p#beeXSAyRY-Tr(uNMnMsG0DV85 z(#G)YoP5sL+=MsMv5)!J76%f#)`u98kfKZYU7!LALA`ZUD5{uw4l_T%`w%3YvThv1 zi^p3o`NewZ8Fe=B)S-;-rv~Th^Tc@{j@ug*zK~qHk|P2eK@7;P&9^`vX9;Fxrs1l| zWz`8tMP9B^MTr~1n;KcMEW_3wDEVYz7L~VU3~fDHEC$nRwKUgH^#aQ|4|gQyc*Wi# zdV(zM-C!o1usxO7$`dw8?mpF17TBKf-7>>NH?cX+E=GBhBuVDUGx@h-@tytVW)~9i z*@Z%ktJkj31F>9cWVc{1vvsr%Su1-}d*8n%&RoG0aTX6L8*rHHPI+*i{eZQY<`V~# zCAV?v)6kX~p>~iwR0zCx_;oU8s!1RDCuT0yBha!(#xduzOi*W0jNg2<l=%26Wwm6q4BWfK08|MfFbNCL{=3s zYJpN$M=P6&jTf9yf{}$6X3OEp80sX-Cb3JH1=AGX$*`=kdpvL)EVPBz)zK$ zHUXQX$a6SXG4}$@2pTx4%D@PsU#9o4Wk1eR6k5~PfATGl8BiY<*m_7p@Z2FUakzc4 zjC95R1m{Zg0W)1~hP2M4nT2_Dud(?%O1QXs4cnXQX@V&udu?N=`>luHGoT2tQXC@= zc@)ulkklWrKYWK|PUfs+b>$RxzYEe+bSjV>uY_4}t_>JHsyukb zq+H(3i8JV_Cf=8jcO#2xkqSAWqL}|Nlh7Y7k8vB|)!{4S4ixdzXGQ%qdZpTKUtfiB zsp?)XKj*Aj&irJ{nH6=PKV60bmg2ZO*fVi)w5KkXM&&zYs{c#Z4jgR&B55f)EFejw zC+T9F>T;v`%I;#^#R;YsQ+4P_X2Mb4sB*&_?0ZO!vZD14OsTMDzM;kcs)pfwa>RniJz_UFm6r?91|~sdBM1F&TCw)w;^dT8qJkjg=?cGlp3T zSed(3=5Q{1N4LkMa~wz1i{a#Z9LR(_GjJD&M8nu;(WXr>R)#q}@KKgQuV3!6mD)*8 z@4d;Vv(&|CQpFgs>p`DRYsrN6%~on$l|2QuBCOCbhg;N6Q0bKl)HTmL5fg8~Z8i6L zgV|z6WL$ozTuWX$=9Eat6$85rZ&*^z%MqK92FY#%(oitOn>?pqzWYNi;aSN zTTk7sxtes;R9tw8^i;S`hj}Q}N3%0OBUuZEU*z* ztVO|mFlrqk8C;_JFg%xrJFloVrs4jh-439GMkNYdKJ{KS29Kt*u>E8bIfeQBpDo#6 zcG7}K_Lk1AcDj<6CuJ)T-X=84Bg}C1rK)WmSDEtUslvn7IDe6$$Ng0YdE{56AGAo zI#sGLn;-`kX4D~@$!A<29N)C{J1A4sK*jML`DdZLrPk1SZ9T5O@#$J|3oBQ&nx&pg z=IsGw1v<}M=~dr1x$&>XS_hy7!8sqX)!E|~UN4?@MR{D19}pXIarP1Hn`9WMv1!GZ zsINdFEba6|XT^F)DRX3x2aB<}bX+sBDXs$I4m0 z!R@+;5fVlRW;rlTCA#@ve=R%j!X>_b`TKb4!P0Up$&K(~| zjg1YOI;txM>^1W;!5e+km-g`?yG6`Kj@4!-Z!nURwd)q>k~`?Uw5B{LJcJS%ObDXW zwVy8)Lr>X2vS}VHEAe24p!=Rse#{f6n|L9aNH)DuliM0`&~wcyIhU9hAR^W+J9VT& zCmG{HyMVm1I>iMKWJmz(8rzWZ);WQz>f{^vC+ERaG>y4xpWUoQx2%vsy)w6L0vJ~G-YBmuXotLoym(%3Y5x0wzlt>Q=}G%h zY!en@J~c1gFTsM^A}%M)Fs5S60?pMjFyr~%rEkb@Gj?LUMXIj95oQ(0p22P-)Tp8a zRR0%qrXEM7w8l%}bmrn%aS|pL*V19G6p(HtI4Jb7wD5Gyfdn>ngg{Oe267gvfFT3Sd zcvLX&AjNAZ&$hrF^O!8or)(Zm8g<< z&QQFVFweY?TWez?Gn9y-M%!SuuNYA7KSAx^JdU3N3J~D55QJZ`sOkKRU%AWcrRNE1KVkd z^^Svaot19jed4?D0aoc=S6X!E2oELO>(gMP2UrVY?kfb#WzJ*CqYRmJ>3zoVmr5os zH$zy0BANYdSo+$Cu6}r>6!AcZRlK^JU^T{>XHk*R!n5Na(z%}o@9INA$?J*zSF$?? z$Bq}sUPFvn>Z120;QQE$K|Gp=d+T%*99|4h>6iN$3|2&KON^t4n|MNkgD)~-t_*nT zMQA~KnIU1AT2`|t{s>B{&-F}!5iwxDd9mV(%f4`*<2>T&LXn!cy3 z^Uc91?QLSh>FC(eGN3qV0QUe~9T+#<>kXU#0v9%?FE~NlZsl;>o_^Ff8@k5R80IE+ z9nst(x;m(S`SCAnF%+)D^MVNv_8D6lzXh zf}sefOfVA4BB2z(xlwEMO((L{2nmn3?2108W7hZ30E2|)TrQr?j~&lw&1^7cJe8#? z61t6EQjryfFXHB+X#E;6$3`_h=9}UBvto~tA(xRW1eZ!^^AM%|9{8CuzGNy!hmO+0DeyoZ@a_PMCZUud4H{ctYpT7|uV5mKx; ztR+Okr$YnFP1&o^uD!%SDTj z?|r^X%0$b`7Td*T?9;W~W?R)gmsDn`!JZ-c9MDTpLl*Xid>OI>ikW^9A$At z6@Tg@r>@HsdaQTJs^=5pqU>?lLE%An*1Y}(Qp~I;N54J&eU)m> zuuh8$q!vwX4u@>* z$&XV(npDcNtV2MV!Y`QJHLKm`t)+#Ft;1w`Y=C;^$ZRt+=>xXOw4PjtC64U%B$DQ` zba%+TaqoLv5z$Q`z*HUbzYY3yXsbJ9*{>MTXETs1ewd z8YSCg$8Ibm2f@P&WU5$ZY^R&;4)JQX5LE}kz?~($yJ9cUMKBZzm`$1X&-lM&y|9xb zatarZxSHE$${`jtW#;MD=LX+tsmmpwere}LJPka6PqDc#A*4fzNRE(ht|iyES(;%G zK-j>9CcFSl=!(DI7{)9}bZhw9V0g%#i#rG0=VCDmH6=6YqRSRelNum1dLQ6oZ6jeY zt+{ zaBV(Q51EWZ3Vq_g=tQ`M-6j88gw2IigQPpZE*E;W7D(Gzx=)3&?TvH;YDAbQ=X%HQ0~V z)C3SQJG16@S%9)jMeYsYLq<0iQ^2UQ`j3FU_J*Dfi1G|62XA=qI|s@tne%wzh_)+t zF-_Z*GyAeDbBkFf7{x-dfM@^zF4*|!zr`}s(Pq^b8a>gcdLk0XOzu6B=3{Clrp)84 zvl8;bgOT9=D#h_#Mnpb%*@`i%gF=;Akdnx3b&UP7+6QEHU$q!3#v5YdU8vLhxV^7; z>-k1NPgtzOzR>XD0}sSE-%LD z#`906e~>m`pe5#r5Lf zuWK@;m5pbBW$~9l;V(F{cmoS=`0^U*C0l!K7m%dTg~*g-X@L#5gN-qIeo_4_rS z_~4AgYe10}{E_Q^%rqGN`2}%9?rx&JbL&>FqOVWRe`DI29^yl!82qWw;s6@_%PfX> z0!WvrzD-$9d;R`DrTVwlB|shRNoLLg~Jqjd!SL7yS6je+I`(g zN_R@~!j0u7Mj>-UX>4kaMZ0wSvm_YulC4^Ag2l=C{c!Xpp!6%zHvqGk?hu|K@-Yv(J!WhIAzbB29OdJYN<86avJp zNF7V{(cZ>hI9hF0<4V&|KoFC5u#W)rR{-=A{1-0{PA_-@^yv7<0;x7D-avK}RwnHP z;M4{B_HPCWayNnmXFES^U-JP7z)KRhf=V`S#7F=^zJQ1@$i(|Ff;f2X&Mlk2*_6g; z0U}c`oBZ8DvsVnfMTjVDUnDb$b2nZB0pDZ1PdaasU>J|e`J>v~vIDrNWWc_V9VPLH z&aaJ>{|DXHlK(u|9hy|}cl2^BhLb|roRkXm`JciQ5Kh2K1%A2lF5q@)OD1?dUtxC3 zadkc-iw{QR_GuvjLN_iz4sZTca4$FQeDNQ*{%q$3Z1KvS;Mcnm875iV1lN&B^!-2T zHXcrI#cP>+*MbxP0kQFxP3Z@9n$X(1(dEs?(uIIfH`_Y1Npk94+YRac-HTS zNGBczeoJ9P|JlOK*qu_*qs;eHi2&evpj8TRIQlj%Dj-O^bsGROIh(Bk@BFDn0N!)o zL;(WR|7!}W%?%r8UZ^NkK3YwJKDkc#z+QcJFBIkL45X9fI&s0-Km!U(KbitO{hu9% z>TE`pH)0so_E^#~IJ&Sh-u-dWRwG%`%gf}w7tDexSFXFLmPP_%4(jR$b$yrQ{kx;D z_q)v|#6sssH>{n_r3e&;kbp&0HR8e3B{lL+_?di`75}yLmoIv+3LpPFDMfp1vTQah zPAf+?eA@3DB7}kDYi%q$;HrKqeI1@)V^vCQSnR1_;;WU?uZXY1;f*&7Tgo9I)*_U%CJCn)}@1(w27)da%pI6))hW1{*DTd#t;V$ zrVmwTC3V~G*PK^jsM!P+QnS-rUB7GmI}K-Yeon)q>b;C&-#vajteXzxd>=38W7MUY4xpESMXSrE zb&0cxkyniXsj)FP>E`YlHTSMFGA1y2d`k0V3~f+hK0`so4B3OA0m^l#a@)OX4_^=7 zc4$5PjOsK}l@FSRq>LJI&ic|P15uO}M3CCN;DEB@6l5~cmA5TtX4LBJ^Gio9{sa6s ze|=p{0<W|bPYxp-cN&C#HQ?YP(wPYwU?*{~TH>_S*-TBb1Sd$KdI)WS_ zCrF)os6eI5*C+-sug5V|8ru0K^~bi3s>cS)T9s;d{3ah8eBMx->HU9>w$)iLDrOx; z5%9c&bPBwjTNC8oYZUh87wFAvP8duloJRZ3AbC4f9f=OTGVYtlUnpgUlXpaJ`OKjtd zYL)2fVR=|Y!#VM3anJpHitb$?i|8R`XFkvI@lQ1Fur`qS^6*=q&EW$rQ@{9CV%WSi z6?iR#1iHJ-6{Z=%6*;q{vK(0rKH(rcDF^HxourCuUM$pP`M1eY64k~L;p|{2D|li} zW$T6N{Zh~gN5}>@QTPEB<7YJ2VNH%cqdp}nDi<;9HbfbF>)iscZERvs5q?%OQoh#` zdZePFEhb9*M&|G0K7ad}O#XIq^oLM-W%|Pf8}r^s%L|1ymOtE^%LnO|ch)gLk)dxgD@6H6B(*Q~8MXT2J9AX8I*IZHQ=p4gN!aCYoHqP(C>BMEM{wsc z_RFx{RhU`iL1i|N5LS`NHx8ECVLd9CiVL3{XsU%mX0i|W^4ydiZHaqHE-rr2K5{SM z)`2_vb!OEp=i^H_&U9`&$@F>Bf3Y9m<)1AKE;1hB^z|&&snQ%nx=!JZ#_N(k5)bpC z513zyDo)K@j>_R2l}po@E++l9H9_yI^!w~ncgGY+Ol(HrZ>_(c45aO-bq~^qrArsG zF+~IoZ49y_r@ftg2Jn%16u3?b1l#V0Z6`k=@yrHf()y%ZOzV{1M#TQj5Ij-PJ7RNK zF=+WAs>4d1#cFFE( z!=ydVSK(EbuZ{yK6N-ib3{35@cXdu*b*z_2QxRTVxb800ct1HYW~1<8q+xX@&nCV4JTRe2#Yp$z*>yv;-u_#YyCF_ z$7=EqX0RWdhHg|7wcnkraC`DaEz{?s$927{%5%liiiR>*E)Mw*VXhE=Zw#F>cE9WU z+)J3gLs|LRqrpWKDro(k+Zx|F=9JJpW!#m98~a_(NvPEcuNRXo(u{1yhB(7& z<3mb^t=hM51=5(Tkk?_OKlCxScy{qe=ybzC{$~r)%uU5b>yRb#^kRumV-GZg?x`b7 zsy9b;&_fje*HJ1<@l2$CL775L_)p;oi6>9Cf$=+--v(wF5MkLf(c@$Zi3!GR=j zOJZUUs9>V48xwFv z4(3F1b;Vz(GDje|nUkldH-_~;SBtl#_s@XkAMX_I1C)AcnPwe@2YT~D^$SQNPK;+p z^hz9PJ1KBh_EOnr)n_-tcPJorMjA&8I<@93^^|>jQ)J4ck?AJ;Z=>GTG#9tB%k!fS zwOXa4S7dtUWGglQ+!k+}k9{5MDg;y+txl9HU(CiQ`5%d73m(;d~-?LoK1$IL0x`k41;cZ(&W`en6r zsjvZ3t6o zambd9^JZh}lo_)uM^__X3y&9+usQYu#GJ+pH2T9;l-T-N_JH=7;$(rqmf+rbJg1^` z9!Qi*@?ZZ!`~Pt8=h;l3?lsLdv;&7tgiq+DsD@LvqJ5&O3MGzf&G2 z{7$;&c-40>{q3a!X?ofy#Q0-dVH8EwxKf7kq0S$&j5?F`&9q@5z;@y?G@B9N6wlL z>-DL6lyzGPftk1F&j`RDXN&y9*d`tI{b)}oPdcUzk;3nv=~ z8ncGDxcr2t_2gVG1WwitJP3LN+u6BGi_YN*>QHsjNwQ9h1*1532#;`q z%O0|Ukbuoc2yur8Xc}yky*LyPW_^_;jlvq7MFs4=f@KYA zrQka3cJ~y0Ey}b)_TFtxRv9tgZ52BpF{hV5EOzh)cTOPn4J(SH^GH_On)W(-V@}RN zz*~j$R@kfd5n$fVHz3dN;_3UOpZTmqD`3#SCDj`ivZRyo#cR_U*6_|15F9JvM!$@y zfo%o>t+vgQ#hYcnX7cVl%=#&}Q=L9UUZEzlT{k}n<{pWg#( zp0L$WFUt*r-arzQUF=wg2;;SFwn~z4FrczkTgd}8;luEa5Cec*l{K@l@M-4YYE#7Q zX3fEd#{~>(^EgH4p0r-A7*&GsjCgVZu9u>&fAFPfK5YfPlJud`b-DM+L((+heJnpSI*4Z6w?CIriY z+DUCbUd*Xens^B>P_gLQbrKwo;q6^~gE_kE$d9dye$p@KL+&;U)`IhNCT^_NvoH#H zaxd8_b}422#M>R8s@DRYws!ew{qMCaIW&83XK zZ`{F`M{HYYC8OSo6YLl46XDC&PY-`ZFYfIQmKKUu$@eItVbwPkN&y~*h8zNo&hvJv z2JlnM=@enPHsrB**j4jA1_P}KpYT@3?A>_Xa8ytqx-UXJODsZ0q?2RMrv!`o0Xd@M zS^&%QUErFX$5bB(H~HZ%rcSi}>)Kxm{O%Q2V&KpQhjb~XN>k;K1;s9&W|c99=9tI~ zDozkx>n@lQxlf4IgC4p;1*^{lZVlMG49APonBlh7Q#cgtKAP`&bJcy1RAK-;8Z~Wh zge?Ci<6AX;TE46I+sF77NhV8KqE<2o-$RWmZydZ5;*=pK!N64!&fF)HAIb3ncXQF# z>nO%M6m+12;(3ErYt|%+n!A~4!@HspjI`X?eZh&XkayKsMP=G$$`*7D!shLY$d5T6 z@`vZ>ij?8GBQBeXX4k_v6U~er^%)mun?Dvz_0S#<-KobEuI55M!a=pvHa!isTBd_k zpc(Fw-~O96JamfHeOv}Q1wZAHinlutH9(xew$8jqC2YURZBibQoC=G4D{0hs_7f=2 zlE3tKVw>YrAGH}z{fS*_mQ%m)vN?H6ct!gXURbQ_mZ(?p0DC2kFr%q^OuRC(dFpnU*- zdTSp8i`a1iRh?+otq2h4!kN4n9W(T@837ubt z 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ℜ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�z+I1C<1>CwDYg#*9){K9b*uty-H4x&Z_PJ zGxZ8JUc}18O_gpLQHq?&Opo?%+%X~+_+DoOI5o!cAP=dU9zC028={3@SU%p0X$IF? zA>Zvp4HLI-L|8@Ux$>2x7ic+3ohu3>2$F(+H5UP}9gH#)3_3Dp*yLF9};&CX8?E+r@vvnA? zVT*F_qv|^y_Rqq!SNQWC-{vuGjl&!H>>7NgyM@EA`Q?A69;pE2Sg(S* ziwq4~Hi>lZm}2b!QSKrYV>3p)bJ>j{`eqY}j`->rO4@yJSFAbcbrc0ZW7XjxqN;^8r|B;a+EuO@R?Zu5j_+g=~ zFd81ArRbwO&9!-(IAQ*o*K|#U zhot{ixbLdy`=j9b?`Gr>lvtY@Ag-@N{fP#BEnKoaNLmGb+~I?C7*`YmxXwZM?iycqdoTiMlp8qJTH$TEE!{X#{F39`$?f{^Ov>!C@o98#Jd3oKH zp6Ju^Soz4_2`hgOh@)P}wA)(W_-P^PjL6em*fiZxELz2kFzQ%f8$0`zqwx}$Ie>>* zFW0dwoAiaP!TD0YE3$MXXmLP&e1o;f)Z50P& z&Gc2HEH;$oU3RWy`io`+p76Y`SW+&HP~9fp$PqU3mcuVX{ijs#7SD*HWW%`M`8U|^ zQa%tl9VEh~AL^6%oM{>u7(7G~R>n7KP zRL7sxYqY1^H?KGmS{*8&SOV69P*HSEo|PucTM5<~NFdxEzm*p%wrN40ulp&W$ zg7CQLMrV4u$6J~?xn&PC&tpueMD}OlGiH&1Ys$vo3iSQ4RuY?54tZJ(7Bp}#qybIR z>HT31;bd!RW&uzzED4sNms&;2uz-mcezqWfk`F38)=a z3zE51CX{`B=_s1kzIH`q0LU;oIzGDQ2%9t(*LCBduKggMMK ztGASJbwGsT4gSXSoddm}USE3~-@Y0feSJ>U;^x%;-Gc5vC^=wFk{J&9p?qp*d?rTn zl1k%o1L^G6`mA}0UzA%c%2d+IWdT+X2v++GBh@WvIZyD6GW^PfkoE!Y2u%7(Z!gHdvN_<3NPsr$5*)|cSgZL%m!EF1&e%l5;M3bi0pzM7>PK{1W@#j}2ktKUKBc0X* zXVOpj8P^nyWnN(|o-s|rh9f8qf9*s_JJ^EOV1b0^JVlyu-Y0O};@g!8!DkVLS6zHX zh_u1uYq8oAs(8(80mVqK*g~;`NIdVRmf(O-zFUb-gTL^a3;H_`ziIEAE!S(pI2Q2_ z%ZqP`0=aZSvlW89&wp-xn+XC4KHZtCxFa3F(eM?{Nw4>-o6bA3I-c8d!s*%#suJBC(kRr?rJTn9q~yqEO( zykL6!OV|F%`8BN;k;hT3cR&;-yZ8QdssGERHC~B;;fo41{x(7%Aa)oxA2q!Q(H{jg zk@Kw~K01GnfP5qV_w39+Kg9IL_OsvRdm8Xb4~e>#0t&=O^0%!>=C1PA@UM@zd%j4z zHr`Ga1dH}wUZucO4~7W5I=ROD6BpB&cnqlJbnq!1NFbsk1224v#nx@4^<@SGf-N-q z*-NCFf@!B_&6X|C`rpLyu!2UR)?{qDJ{LaoOA_&JsLvbK726%#QCOwd zB!8h#MF8!nbrd*mb2qfUMl*j9+y6cZd##>BWWd=}uth{^(8MC}H1*k$(x4|CyH{t2 z839$Su;=X&ClsyCOovX=M{hs#bUT(krQ`TO0jJ>|C8Hd=~Kne8Nj;W(HE;tgL;^bV{pv(_dkEc)MkGU zy6`iIWi6T-H7_gE^s0nc-QmP@?ELqKVy|;!L|;D7xmE_~zR*JDuD^URU~>?`1M)9wB`M*lKt7-~W8=do6Gq zuI@knpB?ePKfEVHlgR!_*Y}zK@^6qj81f(AJNaDsGL5!kGL??spu-6K=})`eo_5Rj zcSGx~q?QTfDRGaerj&o7SFFB3RJE2ff4zX%+;+kkdw;xE6R5 sqtcJRp1sr#p(VWeIViSeY?e#s)LS)OKWa7C$r%xG@i!hierarchy); return new CAnimBlendAssociation(*anim); } @@ -72,7 +71,6 @@ CAnimBlendAssocGroup::CopyAnimation(const char *name) CAnimBlendAssociation *anim = GetAnimation(name); if(anim == nil) return nil; - CAnimManager::UncompressAnimation(anim->hierarchy); return new CAnimBlendAssociation(*anim); } diff --git a/src/animation/AnimBlendAssociation.cpp b/src/animation/AnimBlendAssociation.cpp index b03571b0..fcaa84fa 100644 --- a/src/animation/AnimBlendAssociation.cpp +++ b/src/animation/AnimBlendAssociation.cpp @@ -120,6 +120,12 @@ CAnimBlendAssociation::SetDeleteCallback(void (*cb)(CAnimBlendAssociation*, void callbackArg = arg; } +#if defined(DC_TEXCONV) +void +CAnimBlendAssociation::SetCurrentTime(float time) { + assert("false" && "Must not reach here"); +} +#else void CAnimBlendAssociation::SetCurrentTime(float time) { @@ -128,11 +134,11 @@ CAnimBlendAssociation::SetCurrentTime(float time) for(currentTime = time; currentTime >= hierarchy->totalLength; currentTime -= hierarchy->totalLength) if(!IsRepeating()) return; - CAnimManager::UncompressAnimation(hierarchy); for(i = 0; i < numNodes; i++) if(nodes[i].sequence) nodes[i].FindKeyFrame(currentTime); } +#endif void CAnimBlendAssociation::SyncAnimation(CAnimBlendAssociation *other) diff --git a/src/animation/AnimBlendHierarchy.cpp b/src/animation/AnimBlendHierarchy.cpp index ea669999..e87de493 100644 --- a/src/animation/AnimBlendHierarchy.cpp +++ b/src/animation/AnimBlendHierarchy.cpp @@ -7,17 +7,13 @@ CAnimBlendHierarchy::CAnimBlendHierarchy(void) { sequences = nil; numSequences = 0; - compressed = 0; totalLength = 0.0f; - linkPtr = nil; } void CAnimBlendHierarchy::Shutdown(void) { RemoveAnimSequences(); - compressed = 0; - linkPtr = nil; } void @@ -35,7 +31,7 @@ CAnimBlendHierarchy::CalcTotalTime(void) for(i = 0; i < numSequences; i++){ float seqTime = 0.0f; for(j = 0; j < sequences[i].numFrames; j++) - seqTime += sequences[i].GetKeyFrame(j)->deltaTime; + seqTime += sequences[i].GetDeltaTime(j); totalLength = Max(totalLength, seqTime); } } @@ -56,32 +52,6 @@ CAnimBlendHierarchy::RemoveAnimSequences(void) numSequences = 0; } -void -CAnimBlendHierarchy::Uncompress(void) -{ -#ifdef ANIM_COMPRESSION - int i; - assert(compressed); - for(i = 0; i < numSequences; i++) - sequences[i].Uncompress(); -#endif - if(totalLength == 0.0f) - CalcTotalTime(); - compressed = 0; -} - -void -CAnimBlendHierarchy::RemoveUncompressedData(void) -{ -#ifdef ANIM_COMPRESSION - int i; - assert(!compressed); - for(i = 0; i < numSequences; i++) - sequences[i].RemoveUncompressedData(); -#endif - compressed = 1; -} - #ifdef USE_CUSTOM_ALLOCATOR void CAnimBlendHierarchy::MoveMemory(bool onlyone) diff --git a/src/animation/AnimBlendHierarchy.h b/src/animation/AnimBlendHierarchy.h index 40d2731b..1d73213c 100644 --- a/src/animation/AnimBlendHierarchy.h +++ b/src/animation/AnimBlendHierarchy.h @@ -15,9 +15,7 @@ public: char name[24]; CAnimBlendSequence *sequences; int16 numSequences; - int16 compressed; float totalLength; - CLink *linkPtr; CAnimBlendHierarchy(void); void Shutdown(void); @@ -25,8 +23,6 @@ public: void CalcTotalTime(void); void RemoveQuaternionFlips(void); void RemoveAnimSequences(void); - void Uncompress(void); - void RemoveUncompressedData(void); void MoveMemory(bool onlyone = false); }; diff --git a/src/animation/AnimBlendNode.cpp b/src/animation/AnimBlendNode.cpp index df6cd1d5..8c3a6bc7 100644 --- a/src/animation/AnimBlendNode.cpp +++ b/src/animation/AnimBlendNode.cpp @@ -29,15 +29,18 @@ CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight) float blend = association->GetBlendAmount(weight); if(blend > 0.0f){ - KeyFrameTrans *kfA = (KeyFrameTrans*)sequence->GetKeyFrame(frameA); - KeyFrameTrans *kfB = (KeyFrameTrans*)sequence->GetKeyFrame(frameB); - float t = kfA->deltaTime == 0.0f ? 0.0f : (kfA->deltaTime - remainingTime)/kfA->deltaTime; + float kfAdt = sequence->GetDeltaTime(frameA); + float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt; if(sequence->type & CAnimBlendSequence::KF_TRANS){ - trans = kfB->translation + t*(kfA->translation - kfB->translation); + auto kfAt = sequence->GetTranslation(frameA); + auto kfBt = sequence->GetTranslation(frameB); + trans = kfBt + t*(kfAt - kfBt); trans *= blend; } if(sequence->type & CAnimBlendSequence::KF_ROT){ - rot.Slerp(kfB->rotation, kfA->rotation, theta, invSin, t); + auto kfAr = sequence->GetRotation(frameA); + auto kfBr = sequence->GetRotation(frameB); + rot.Slerp(kfBr, kfAr, theta, invSin, t); rot *= blend; } } @@ -71,7 +74,7 @@ CAnimBlendNode::NextKeyFrame(void) frameA = 0; } - remainingTime += sequence->GetKeyFrame(frameA)->deltaTime; + remainingTime += sequence->GetDeltaTime(frameA); } frameB = frameA - 1; @@ -96,8 +99,8 @@ CAnimBlendNode::FindKeyFrame(float t) frameA++; // advance until t is between frameB and frameA - while(t > sequence->GetKeyFrame(frameA)->deltaTime){ - t -= sequence->GetKeyFrame(frameA)->deltaTime; + while(t > sequence->GetDeltaTime(frameA)){ + t -= sequence->GetDeltaTime(frameA); frameB = frameA++; if(frameA >= sequence->numFrames){ // reached end of animation @@ -108,7 +111,7 @@ CAnimBlendNode::FindKeyFrame(float t) } } - remainingTime = sequence->GetKeyFrame(frameA)->deltaTime - t; + remainingTime = sequence->GetDeltaTime(frameA) - t; } CalcDeltas(); @@ -120,9 +123,9 @@ CAnimBlendNode::CalcDeltas(void) { if((sequence->type & CAnimBlendSequence::KF_ROT) == 0) return; - KeyFrame *kfA = sequence->GetKeyFrame(frameA); - KeyFrame *kfB = sequence->GetKeyFrame(frameB); - float cos = DotProduct(kfA->rotation, kfB->rotation); + auto kfAr = sequence->GetRotation(frameA); + auto kfBr = sequence->GetRotation(frameB); + float cos = DotProduct(kfAr, kfBr); if(cos > 1.0f) cos = 1.0f; theta = Acos(cos); @@ -136,11 +139,12 @@ CAnimBlendNode::GetCurrentTranslation(CVector &trans, float weight) float blend = association->GetBlendAmount(weight); if(blend > 0.0f){ - KeyFrameTrans *kfA = (KeyFrameTrans*)sequence->GetKeyFrame(frameA); - KeyFrameTrans *kfB = (KeyFrameTrans*)sequence->GetKeyFrame(frameB); - float t = (kfA->deltaTime - remainingTime)/kfA->deltaTime; + auto kfAdt = sequence->GetDeltaTime(frameA); + float t = (kfAdt - remainingTime)/kfAdt; if(sequence->type & CAnimBlendSequence::KF_TRANS){ - trans = kfB->translation + t*(kfA->translation - kfB->translation); + auto kfAt = sequence->GetTranslation(frameA); + auto kfBt = sequence->GetTranslation(frameB); + trans = kfBt + t*(kfAt - kfBt); trans *= blend; } } @@ -153,8 +157,7 @@ CAnimBlendNode::GetEndTranslation(CVector &trans, float weight) float blend = association->GetBlendAmount(weight); if(blend > 0.0f){ - KeyFrameTrans *kf = (KeyFrameTrans*)sequence->GetKeyFrame(sequence->numFrames-1); if(sequence->type & CAnimBlendSequence::KF_TRANS) - trans = kf->translation * blend; + trans = sequence->GetTranslation(sequence->numFrames-1) * blend; } } diff --git a/src/animation/AnimBlendSequence.cpp b/src/animation/AnimBlendSequence.cpp index 2ae150c1..0718f37c 100644 --- a/src/animation/AnimBlendSequence.cpp +++ b/src/animation/AnimBlendSequence.cpp @@ -8,7 +8,6 @@ CAnimBlendSequence::CAnimBlendSequence(void) type = 0; numFrames = 0; keyFrames = nil; - keyFramesCompressed = nil; #ifdef PED_SKIN boneTag = -1; #endif @@ -18,8 +17,6 @@ CAnimBlendSequence::~CAnimBlendSequence(void) { if(keyFrames) RwFree(keyFrames); - if(keyFramesCompressed) - RwFree(keyFramesCompressed); } void @@ -29,13 +26,18 @@ CAnimBlendSequence::SetName(char *name) } void -CAnimBlendSequence::SetNumFrames(int numFrames, bool translation) +CAnimBlendSequence::SetNumFrames(int numFrames, bool translation, bool compress) { int sz; if(translation){ - sz = sizeof(KeyFrameTrans); type |= KF_ROT | KF_TRANS; + if (compress) { + type |= KF_COMPRESSED; + sz = sizeof(KeyFrameTransCompressed); + } else { + sz = sizeof(KeyFrameTransUncompressed); + } }else{ sz = sizeof(KeyFrame); type |= KF_ROT; @@ -49,134 +51,19 @@ CAnimBlendSequence::RemoveQuaternionFlips(void) { int i; CQuaternion last; - KeyFrame *frame; if(numFrames < 2) return; - frame = GetKeyFrame(0); - last = frame->rotation; + last = GetRotation(0); for(i = 1; i < numFrames; i++){ - frame = GetKeyFrame(i); - if(DotProduct(last, frame->rotation) < 0.0f) - frame->rotation = -frame->rotation; - last = frame->rotation; + auto KFr = GetRotation(i); + if(DotProduct(last, KFr) < 0.0f) + SetRotation(i, -KFr); + last = GetRotation(i); } } -void -CAnimBlendSequence::Uncompress(void) -{ - int i; - - if(numFrames == 0) - return; - - PUSH_MEMID(MEMID_ANIMATION); - - float rotScale = 1.0f/4096.0f; - float timeScale = 1.0f/60.0f; - float transScale = 1.0f/128.0f; - if(type & KF_TRANS){ - void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameTrans)); - KeyFrameTransCompressed *ckf = (KeyFrameTransCompressed*)keyFramesCompressed; - KeyFrameTrans *kf = (KeyFrameTrans*)newKfs; - for(i = 0; i < numFrames; i++){ - kf->rotation.x = ckf->rot[0]*rotScale; - kf->rotation.y = ckf->rot[1]*rotScale; - kf->rotation.z = ckf->rot[2]*rotScale; - kf->rotation.w = ckf->rot[3]*rotScale; - kf->deltaTime = ckf->deltaTime*timeScale; - kf->translation.x = ckf->trans[0]*transScale; - kf->translation.y = ckf->trans[1]*transScale; - kf->translation.z = ckf->trans[2]*transScale; - kf++; - ckf++; - } - keyFrames = newKfs; - }else{ - void *newKfs = RwMalloc(numFrames * sizeof(KeyFrame)); - KeyFrameCompressed *ckf = (KeyFrameCompressed*)keyFramesCompressed; - KeyFrame *kf = (KeyFrame*)newKfs; - for(i = 0; i < numFrames; i++){ - kf->rotation.x = ckf->rot[0]*rotScale; - kf->rotation.y = ckf->rot[1]*rotScale; - kf->rotation.z = ckf->rot[2]*rotScale; - kf->rotation.w = ckf->rot[3]*rotScale; - kf->deltaTime = ckf->deltaTime*timeScale; - kf++; - ckf++; - } - keyFrames = newKfs; - } - REGISTER_MEMPTR(&keyFrames); - - RwFree(keyFramesCompressed); - keyFramesCompressed = nil; - - POP_MEMID(); -} - -void -CAnimBlendSequence::CompressKeyframes(void) -{ - int i; - - if(numFrames == 0) - return; - - PUSH_MEMID(MEMID_ANIMATION); - - float rotScale = 4096.0f; - float timeScale = 60.0f; - float transScale = 128.0f; - if(type & KF_TRANS){ - void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameTransCompressed)); - KeyFrameTransCompressed *ckf = (KeyFrameTransCompressed*)newKfs; - KeyFrameTrans *kf = (KeyFrameTrans*)keyFrames; - for(i = 0; i < numFrames; i++){ - ckf->rot[0] = kf->rotation.x*rotScale; - ckf->rot[1] = kf->rotation.y*rotScale; - ckf->rot[2] = kf->rotation.z*rotScale; - ckf->rot[3] = kf->rotation.w*rotScale; - ckf->deltaTime = kf->deltaTime*timeScale + 0.5f; - ckf->trans[0] = kf->translation.x*transScale; - ckf->trans[1] = kf->translation.y*transScale; - ckf->trans[2] = kf->translation.z*transScale; - kf++; - ckf++; - } - keyFramesCompressed = newKfs; - }else{ - void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameCompressed)); - KeyFrameCompressed *ckf = (KeyFrameCompressed*)newKfs; - KeyFrame *kf = (KeyFrame*)keyFrames; - for(i = 0; i < numFrames; i++){ - ckf->rot[0] = kf->rotation.x*rotScale; - ckf->rot[1] = kf->rotation.y*rotScale; - ckf->rot[2] = kf->rotation.z*rotScale; - ckf->rot[3] = kf->rotation.w*rotScale; - ckf->deltaTime = kf->deltaTime*timeScale + 0.5f; - kf++; - ckf++; - } - keyFramesCompressed = newKfs; - } - REGISTER_MEMPTR(&keyFramesCompressed); - - POP_MEMID(); -} - -void -CAnimBlendSequence::RemoveUncompressedData(void) -{ - if(numFrames == 0) - return; - CompressKeyframes(); - RwFree(keyFrames); - keyFrames = nil; -} - #ifdef USE_CUSTOM_ALLOCATOR bool CAnimBlendSequence::MoveMemory(void) diff --git a/src/animation/AnimBlendSequence.h b/src/animation/AnimBlendSequence.h index c6e70f22..bedc85ee 100644 --- a/src/animation/AnimBlendSequence.h +++ b/src/animation/AnimBlendSequence.h @@ -7,22 +7,66 @@ #endif // TODO: put them somewhere else? +static int16 checked_f2i16(float f) { + assert(f >= -32768 && f <= 32767); + return f; +} + +static uint16 checked_f2u16(float f) { + assert(f >= 0 && f <= 65535); + return f; +} + +#define KF_MINDELTA (1/256.f) + struct KeyFrame { - CQuaternion rotation; - float deltaTime; // relative to previous key frame -}; - -struct KeyFrameTrans : KeyFrame { - CVector translation; -}; - -struct KeyFrameCompressed { int16 rot[4]; // 4096 - int16 deltaTime; // 60 + uint16 dltTime; // 256 + + CQuaternion rotation_() { + return { rot[0] * (1/4096.f), rot[1] * (1/4096.f), rot[2] * (1/4096.f), rot[3] * (1/4096.f) }; + } + + void rotation_(const CQuaternion& q) { + rot[0] = checked_f2i16(q.x * 4096.0f); + rot[1] = checked_f2i16(q.y * 4096.0f); + rot[2] = checked_f2i16(q.z * 4096.0f); + rot[3] = checked_f2i16(q.w * 4096.0f); + } + + float deltaTime_() { + return dltTime * (1/256.0f); + } + + void deltaTime_(float t) { + dltTime = checked_f2u16(t * 256); // always round down + } }; -struct KeyFrameTransCompressed : KeyFrameCompressed { +struct KeyFrameTransUncompressed : KeyFrame { + // Some animations use bigger range, eg during the intro + CVector trans; + CVector translation_() { + return trans; + } + + void translation_(const CVector &v) { + trans = v; + } +}; + +struct KeyFrameTransCompressed : KeyFrame { int16 trans[3]; // 128 + + CVector translation_() { + return { trans[0] * (1/128.f), trans[1] * (1/128.f), trans[2] * (1/128.f)}; + } + + void translation_(const CVector &v) { + trans[0] = checked_f2i16(v.x * 128.f); + trans[1] = checked_f2i16(v.y * 128.f); + trans[2] = checked_f2i16(v.z * 128.f); + } }; @@ -32,7 +76,8 @@ class CAnimBlendSequence public: enum { KF_ROT = 1, - KF_TRANS = 2 + KF_TRANS = 2, + KF_COMPRESSED = 4, // only applicable for KF_TRANS }; int32 type; char name[24]; @@ -41,22 +86,75 @@ public: int16 boneTag; #endif void *keyFrames; - void *keyFramesCompressed; CAnimBlendSequence(void); virtual ~CAnimBlendSequence(void); void SetName(char *name); - void SetNumFrames(int numFrames, bool translation); + void SetNumFrames(int numFrames, bool translation, bool compress); void RemoveQuaternionFlips(void); - KeyFrame *GetKeyFrame(int n) { - return type & KF_TRANS ? - &((KeyFrameTrans*)keyFrames)[n] : - &((KeyFrame*)keyFrames)[n]; + + + void SetTranslation(int n, const CVector &v) { + if (type & KF_COMPRESSED) { + ((KeyFrameTransCompressed*)keyFrames)[n].translation_(v); + } else if (type & KF_TRANS) { + ((KeyFrameTransUncompressed*)keyFrames)[n].translation_(v); + } else { + assert(false && "SetTranslation called on sequence without translation"); + } } + + CVector GetTranslation(int n) { + if (type & KF_COMPRESSED) { + return ((KeyFrameTransCompressed*)keyFrames)[n].translation_(); + } else if (type & KF_TRANS) { + return ((KeyFrameTransUncompressed*)keyFrames)[n].translation_(); + } else { + assert(false && "GetTranslation called on sequence without translation"); + } + } + + void SetRotation(int n, const CQuaternion &q) { + if (type & KF_COMPRESSED) { + ((KeyFrameTransCompressed*)keyFrames)[n].rotation_(q); + } else if (type & KF_TRANS) { + ((KeyFrameTransUncompressed*)keyFrames)[n].rotation_(q); + } else { + ((KeyFrame*)keyFrames)[n].rotation_(q); + } + } + + CQuaternion GetRotation(int n) { + if (type & KF_COMPRESSED) { + return ((KeyFrameTransCompressed*)keyFrames)[n].rotation_(); + } else if (type & KF_TRANS) { + return ((KeyFrameTransUncompressed*)keyFrames)[n].rotation_(); + } else { + return ((KeyFrame*)keyFrames)[n].rotation_(); + } + } + + void SetDeltaTime(int n, float t) { + if (type & KF_COMPRESSED) { + ((KeyFrameTransCompressed*)keyFrames)[n].deltaTime_(t); + } else if (type & KF_TRANS) { + ((KeyFrameTransUncompressed*)keyFrames)[n].deltaTime_(t); + } else { + ((KeyFrame*)keyFrames)[n].deltaTime_(t); + } + } + + float GetDeltaTime(int n) { + if (type & KF_COMPRESSED) { + return ((KeyFrameTransCompressed*)keyFrames)[n].deltaTime_(); + } else if (type & KF_TRANS) { + return ((KeyFrameTransUncompressed*)keyFrames)[n].deltaTime_(); + } else { + return ((KeyFrame*)keyFrames)[n].deltaTime_(); + } + } + bool HasTranslation(void) { return !!(type & KF_TRANS); } - void Uncompress(void); - void CompressKeyframes(void); - void RemoveUncompressedData(void); bool MoveMemory(void); #ifdef PED_SKIN diff --git a/src/animation/AnimManager.cpp b/src/animation/AnimManager.cpp index c66997ce..b3889328 100644 --- a/src/animation/AnimManager.cpp +++ b/src/animation/AnimManager.cpp @@ -16,7 +16,6 @@ CAnimBlendHierarchy CAnimManager::ms_aAnimations[NUMANIMATIONS]; int32 CAnimManager::ms_numAnimBlocks; int32 CAnimManager::ms_numAnimations; CAnimBlendAssocGroup *CAnimManager::ms_aAnimAssocGroups; -CLinkList CAnimManager::ms_animCache; AnimAssocDesc aStdAnimDescs[] = { { ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK }, @@ -584,7 +583,6 @@ CAnimManager::Initialise(void) { ms_numAnimations = 0; ms_numAnimBlocks = 0; - ms_animCache.Init(25); // dumpanimdata(); } @@ -594,34 +592,12 @@ CAnimManager::Shutdown(void) { int i; - ms_animCache.Shutdown(); - for(i = 0; i < ms_numAnimations; i++) ms_aAnimations[i].Shutdown(); delete[] ms_aAnimAssocGroups; } -void -CAnimManager::UncompressAnimation(CAnimBlendHierarchy *hier) -{ - if(!hier->compressed){ - if(hier->linkPtr){ - hier->linkPtr->Remove(); - ms_animCache.head.Insert(hier->linkPtr); - } - }else{ - CLink *link = ms_animCache.Insert(hier); - if(link == nil){ - ms_animCache.tail.prev->item->RemoveUncompressedData(); - ms_animCache.Remove(ms_animCache.tail.prev); - link = ms_animCache.Insert(hier); - } - hier->linkPtr = link; - hier->Uncompress(); - } -} - CAnimBlock* CAnimManager::GetAnimationBlock(const char *name) { @@ -754,7 +730,8 @@ CAnimManager::BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId a found->blendAmount = 0.0f; found->blendDelta = delta; } - UncompressAnimation(found->hierarchy); + + assert(anim->hierarchy->totalLength != 0.0f); return found; } @@ -872,14 +849,16 @@ CAnimManager::LoadAnimFile(int fd, bool compress) CFileMgr::Read(fd, (char*)&info, sizeof(info)); if(!CGeneral::faststrncmp(info.ident, "KRTS", 4)) { hasScale = true; - seq->SetNumFrames(numFrames, true); + seq->SetNumFrames(numFrames, true, compress); }else if(!CGeneral::faststrncmp(info.ident, "KRT0", 4)) { hasTranslation = true; - seq->SetNumFrames(numFrames, true); + seq->SetNumFrames(numFrames, true, compress); }else if(!CGeneral::faststrncmp(info.ident, "KR00", 4)){ - seq->SetNumFrames(numFrames, false); + seq->SetNumFrames(numFrames, false, compress); } + float *frameTimes = (float*)RwMalloc(sizeof(float) * numFrames); + for(l = 0; l < numFrames; l++){ if(hasScale){ CFileMgr::Read(fd, buf, 0x2C); @@ -887,45 +866,47 @@ CAnimManager::LoadAnimFile(int fd, bool compress) rot.Invert(); CVector trans(fbuf[4], fbuf[5], fbuf[6]); - KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l); - kf->rotation = rot; - kf->translation = trans; + seq->SetRotation(l, rot); + seq->SetTranslation(l, trans); // scaling ignored - kf->deltaTime = fbuf[10]; // absolute time here + frameTimes[l] = fbuf[10]; // absolute time here }else if(hasTranslation){ CFileMgr::Read(fd, buf, 0x20); CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); rot.Invert(); CVector trans(fbuf[4], fbuf[5], fbuf[6]); - KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l); - kf->rotation = rot; - kf->translation = trans; - kf->deltaTime = fbuf[7]; // absolute time here + seq->SetRotation(l, rot); + seq->SetTranslation(l, trans); + frameTimes[l] = fbuf[7]; // absolute time here }else{ CFileMgr::Read(fd, buf, 0x14); CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); rot.Invert(); - KeyFrame *kf = (KeyFrame*)seq->GetKeyFrame(l); - kf->rotation = rot; - kf->deltaTime = fbuf[4]; // absolute time here + seq->SetRotation(l, rot); + frameTimes[l] = fbuf[4]; // absolute time here } } // convert absolute time to deltas - for(l = seq->numFrames-1; l > 0; l--){ - KeyFrame *kf1 = seq->GetKeyFrame(l); - KeyFrame *kf2 = seq->GetKeyFrame(l-1); - kf1->deltaTime -= kf2->deltaTime; + float running_sum = 0.0f; + for (l = 0; l < numFrames; l++) { + auto dt = frameTimes[l] - running_sum; + seq->SetDeltaTime(l, dt); + assert(seq->GetDeltaTime(l) <= dt); + running_sum += seq->GetDeltaTime(l); + // if (seq->GetDeltaTime(l) == 0.0f && dt != 0.0f) { + // seq->SetDeltaTime(l, KF_MINDELTA); + // } + + // assert(seq->GetDeltaTime(l) != 0.0f || dt == 0.0f); } + RwFree(frameTimes); } hier->RemoveQuaternionFlips(); - if(compress) - hier->RemoveUncompressedData(); - else - hier->CalcTotalTime(); + hier->CalcTotalTime(); } } } diff --git a/src/animation/AnimManager.h b/src/animation/AnimManager.h index 92192c71..136b2ae4 100644 --- a/src/animation/AnimManager.h +++ b/src/animation/AnimManager.h @@ -71,12 +71,10 @@ class CAnimManager static int32 ms_numAnimBlocks; static int32 ms_numAnimations; static CAnimBlendAssocGroup *ms_aAnimAssocGroups; - static CLinkList ms_animCache; public: static void Initialise(void); static void Shutdown(void); - static void UncompressAnimation(CAnimBlendHierarchy *anim); static CAnimBlock *GetAnimationBlock(const char *name); static CAnimBlendHierarchy *GetAnimation(const char *name, CAnimBlock *animBlock); static CAnimBlendHierarchy *GetAnimation(int32 n) { return &ms_aAnimations[n]; } diff --git a/src/animation/CutsceneMgr.cpp b/src/animation/CutsceneMgr.cpp index 83c4dbcb..ade0bba9 100644 --- a/src/animation/CutsceneMgr.cpp +++ b/src/animation/CutsceneMgr.cpp @@ -267,7 +267,7 @@ CCutsceneMgr::SetupCutsceneToStart(void) assert(RwObjectGetType(ms_pCutsceneObjects[i]->m_rwObject) == rpCLUMP); if (CAnimBlendAssociation *pAnimBlendAssoc = RpAnimBlendClumpGetFirstAssociation((RpClump*)ms_pCutsceneObjects[i]->m_rwObject)) { assert(pAnimBlendAssoc->hierarchy->sequences[0].HasTranslation()); - ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + ((KeyFrameTrans*)pAnimBlendAssoc->hierarchy->sequences[0].GetKeyFrame(0))->translation); + ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + pAnimBlendAssoc->hierarchy->sequences[0].GetTranslation(0)); CWorld::Add(ms_pCutsceneObjects[i]); pAnimBlendAssoc->SetRun(); } else { diff --git a/src/animation/RpAnimBlend.cpp b/src/animation/RpAnimBlend.cpp index e430e52a..c5e17a6a 100644 --- a/src/animation/RpAnimBlend.cpp +++ b/src/animation/RpAnimBlend.cpp @@ -143,6 +143,31 @@ FrameInitCBskin(AnimBlendFrameData *frameData, void*) } #ifdef PED_SKIN +#if defined(DC_TEXCONV) +// These are copied from RwHelper.cpp for linking reasons +static RpAtomic* +isSkinnedCb(RpAtomic *atomic, void *data) +{ + RpAtomic **pAtomic = (RpAtomic**)data; + if(*pAtomic) + return nil; // already found one + if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic))) + *pAtomic = atomic; // we could just return nil here directly... + return atomic; +} + +RpAtomic* +IsClumpSkinned(RpClump *clump) +{ + RpAtomic *atomic = nil; + RpClumpForAllAtomics(clump, isSkinnedCb, &atomic); + return atomic; +} +void +RpAnimBlendClumpInitSkinned(RpClump *clump) { + assert("false" && "Must not reach here"); +} +#else void RpAnimBlendClumpInitSkinned(RpClump *clump) { @@ -177,6 +202,7 @@ RpAnimBlendClumpInitSkinned(RpClump *clump) clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION; } #endif +#endif void RpAnimBlendClumpInitNotSkinned(RpClump *clump) @@ -365,6 +391,13 @@ FillFrameArrayCBnonskin(AnimBlendFrameData *frame, void *arg) } #ifdef PED_SKIN +#if defined(DC_TEXCONV) +void +RpAnimBlendClumpFillFrameArraySkin(RpClump *clump, AnimBlendFrameData **frames) +{ + assert("false" && "Must not reach here"); +} +#else void RpAnimBlendClumpFillFrameArraySkin(RpClump *clump, AnimBlendFrameData **frames) { @@ -375,6 +408,7 @@ RpAnimBlendClumpFillFrameArraySkin(RpClump *clump, AnimBlendFrameData **frames) frames[i] = &clumpData->frames[RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(i))]; } #endif +#endif void RpAnimBlendClumpFillFrameArray(RpClump *clump, AnimBlendFrameData **frames) @@ -421,6 +455,13 @@ RpAnimBlendClumpFindFrame(RpClump *clump, const char *name) return pFrameDataFound; } +#if defined(DC_TEXCONV) +void +RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta) +{ + assert("false" && "Must not reach here"); +} +#else void RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta) { @@ -442,7 +483,6 @@ RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta) next = link->next; CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); if(assoc->UpdateBlend(timeDelta)){ - CAnimManager::UncompressAnimation(assoc->hierarchy); updateData.nodes[i++] = assoc->GetNode(0); if(assoc->flags & ASSOC_MOVEMENT){ totalLength += assoc->hierarchy->totalLength/assoc->speed * assoc->blendAmount; @@ -467,3 +507,4 @@ RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta) } RwFrameUpdateObjects(RpClumpGetFrame(clump)); } +#endif \ No newline at end of file diff --git a/src/audio/AudioLogic.cpp b/src/audio/AudioLogic.cpp index 28602304..4624d83a 100644 --- a/src/audio/AudioLogic.cpp +++ b/src/audio/AudioLogic.cpp @@ -223,7 +223,7 @@ void cAudioManager::PreInitialiseGameSpecificSetup() { BankStartOffset[SFX_BANK_0] = SAMPLEBANK_START; -#ifdef GTA_PS2 +#if defined(GTA_PS2) || defined(RW_DC) BankStartOffset[SFX_BANK_PACARD] = SFX_CAR_ACCEL_1; BankStartOffset[SFX_BANK_PATHFINDER] = SFX_CAR_ACCEL_2; BankStartOffset[SFX_BANK_PORSCHE] = SFX_CAR_ACCEL_3; @@ -424,7 +424,7 @@ cAudioManager::ProcessReverb() VolR = Min(MAX_VOLUME, VolR + VolUp); if (OldVolL != VolL || OldVolR != VolR) { - SampleManager.UpdateReverb(VolL, VolR, 100, 15, 80); + SampleManager.UpdateReverb(/*VolL, VolR, 100, 15, 80*/); OldVolL = VolL; OldVolR = VolR; } diff --git a/src/audio/sampman.h b/src/audio/sampman.h index ad14e2b2..70f1b47a 100644 --- a/src/audio/sampman.h +++ b/src/audio/sampman.h @@ -6,14 +6,15 @@ #define MAX_FREQ DIGITALRATE struct tSample { - uint32 nOffset; - uint32 nSize; - uint32 nFrequency; - uint32 nLoopStart; - int32 nLoopEnd; + uint32 nFileOffset; // in bytes + uint32 nByteSize; // in bytes (*2 for adpcm samples) + uint32 nFrequency; // in hz + uint32 nLoopStartSample;// in samples, 0 if no separate loop data + uint32 nLoopFileOffset; // in bytes, 0 if none + uint32 nLoopByteSize; // in bytes, 0 if none (*2 for adpcm samples) }; -#ifdef GTA_PS2 +#if defined(GTA_PS2) || defined(RW_DC) #define PS2BANK(e) e #else #define PS2BANK(e) e = SFX_BANK_0 @@ -97,6 +98,7 @@ enum #define MAX_PEDSFX 7 #define PED_BLOCKSIZE 79000 +#define PED_BLOCKSIZE_ADPCM (PED_BLOCKSIZE/4) #define MAXPROVIDERS 64 @@ -136,7 +138,7 @@ class cSampleManager bool8 m_bInitialised; uint8 m_nNumberOfProviders; char *m_aAudioProviders[MAXPROVIDERS]; - tSample m_aSamples[TOTAL_AUDIO_SAMPLES]; + alignas(32) tSample m_aSamples[TOTAL_AUDIO_SAMPLES]; public: @@ -180,6 +182,7 @@ public: bool8 LoadSampleBank (uint8 nBank); void UnloadSampleBank (uint8 nBank); + void UnloadUnusedSampleBank(); int8 IsSampleBankLoaded(uint8 nBank); uint8 IsPedCommentLoaded(uint32 nComment); @@ -211,7 +214,7 @@ public: void StartChannel (uint32 nChannel); void StopChannel (uint32 nChannel); - void PreloadStreamedFile (uint8 nFile, uint8 nStream = 0); + void PreloadStreamedFile (uint8 nFile, uint8 nStream = 0, uint32_t seek_bytes_aligned = 0); void PauseStream (bool8 nPauseFlag, uint8 nStream = 0); void StartPreloadedStreamedFile (uint8 nStream = 0); bool8 StartStreamedFile (uint8 nFile, uint32 nPos, uint8 nStream = 0); @@ -670,4 +673,204 @@ static char StreamedNameTable[][25] = "AUDIO\\k1_b.WAV", "AUDIO\\cat1.WAV" }; + +static char DCStreamedNameTable[][25] = +{ + "stream/HEAD.APM", + "stream/CLASS.APM", + "stream/KJAH.APM", + "stream/RISE.APM", + "stream/LIPS.APM", + "stream/GAME.APM", + "stream/MSX.APM", + "stream/FLASH.APM", + "stream/CHAT.APM", + "stream/HEAD.APM", + "stream/POLICE.APM", + "stream/CITY.APM", + "stream/WATER.APM", + "stream/COMOPEN.APM", + "stream/SUBOPEN.APM", + "stream/JB.APM", + "stream/BET.APM", + "stream/L1_LG.APM", + "stream/L2_DSB.APM", + "stream/L3_DM.APM", + "stream/L4_PAP.APM", + "stream/L5_TFB.APM", + "stream/J0_DM2.APM", + "stream/J1_LFL.APM", + "stream/J2_KCL.APM", + "stream/J3_VH.APM", + "stream/J4_ETH.APM", + "stream/J5_DST.APM", + "stream/J6_TBJ.APM", + "stream/T1_TOL.APM", + "stream/T2_TPU.APM", + "stream/T3_MAS.APM", + "stream/T4_TAT.APM", + "stream/T5_BF.APM", + "stream/S0_MAS.APM", + "stream/S1_PF.APM", + "stream/S2_CTG.APM", + "stream/S3_RTC.APM", + "stream/S5_LRQ.APM", + "stream/S4_BDBA.APM", + "stream/S4_BDBB.APM", + "stream/S2_CTG2.APM", + "stream/S4_BDBD.APM", + "stream/S5_LRQB.APM", + "stream/S5_LRQC.APM", + "stream/A1_SSO.APM", + "stream/A2_PP.APM", + "stream/A3_SS.APM", + "stream/A4_PDR.APM", + "stream/A5_K2FT.APM", + "stream/K1_KBO.APM", + "stream/K2_GIS.APM", + "stream/K3_DS.APM", + "stream/K4_SHI.APM", + "stream/K5_SD.APM", + "stream/R0_PDR2.APM", + "stream/R1_SW.APM", + "stream/R2_AP.APM", + "stream/R3_ED.APM", + "stream/R4_GF.APM", + "stream/R5_PB.APM", + "stream/R6_MM.APM", + "stream/D1_STOG.APM", + "stream/D2_KK.APM", + "stream/D3_ADO.APM", + "stream/D5_ES.APM", + "stream/D7_MLD.APM", + "stream/D4_GTA.APM", + "stream/D4_GTA2.APM", + "stream/D6_STS.APM", + "stream/A6_BAIT.APM", + "stream/A7_ETG.APM", + "stream/A8_PS.APM", + "stream/A9_ASD.APM", + "stream/K4_SHI2.APM", + "stream/C1_TEX.APM", + "stream/EL_PH1.APM", + "stream/EL_PH2.APM", + "stream/EL_PH3.APM", + "stream/EL_PH4.APM", + "stream/YD_PH1.APM", + "stream/YD_PH2.APM", + "stream/YD_PH3.APM", + "stream/YD_PH4.APM", + "stream/HD_PH1.APM", + "stream/HD_PH2.APM", + "stream/HD_PH3.APM", + "stream/HD_PH4.APM", + "stream/HD_PH5.APM", + "stream/MT_PH1.APM", + "stream/MT_PH2.APM", + "stream/MT_PH3.APM", + "stream/MT_PH4.APM", + "stream/MISCOM.APM", + "stream/END.APM", + "stream/lib_a1.APM", + "stream/lib_a2.APM", + "stream/lib_a.APM", + "stream/lib_b.APM", + "stream/lib_c.APM", + "stream/lib_d.APM", + "stream/l2_a.APM", + "stream/j4t_1.APM", + "stream/j4t_2.APM", + "stream/j4t_3.APM", + "stream/j4t_4.APM", + "stream/j4_a.APM", + "stream/j4_b.APM", + "stream/j4_c.APM", + "stream/j4_d.APM", + "stream/j4_e.APM", + "stream/j4_f.APM", + "stream/j6_1.APM", + "stream/j6_a.APM", + "stream/j6_b.APM", + "stream/j6_c.APM", + "stream/j6_d.APM", + "stream/t4_a.APM", + "stream/s1_a.APM", + "stream/s1_a1.APM", + "stream/s1_b.APM", + "stream/s1_c.APM", + "stream/s1_c1.APM", + "stream/s1_d.APM", + "stream/s1_e.APM", + "stream/s1_f.APM", + "stream/s1_g.APM", + "stream/s1_h.APM", + "stream/s1_i.APM", + "stream/s1_j.APM", + "stream/s1_k.APM", + "stream/s1_l.APM", + "stream/s3_a.APM", + "stream/s3_b.APM", + "stream/el3_a.APM", + "stream/mf1_a.APM", + "stream/mf2_a.APM", + "stream/mf3_a.APM", + "stream/mf3_b.APM", + "stream/mf3_b1.APM", + "stream/mf3_c.APM", + "stream/mf4_a.APM", + "stream/mf4_b.APM", + "stream/mf4_c.APM", + "stream/a1_a.APM", + "stream/a3_a.APM", + "stream/a5_a.APM", + "stream/a4_a.APM", + "stream/a4_b.APM", + "stream/a4_c.APM", + "stream/a4_d.APM", + "stream/k1_a.APM", + "stream/k3_a.APM", + "stream/r1_a.APM", + "stream/r2_a.APM", + "stream/r2_b.APM", + "stream/r2_c.APM", + "stream/r2_d.APM", + "stream/r2_e.APM", + "stream/r2_f.APM", + "stream/r2_g.APM", + "stream/r2_h.APM", + "stream/r5_a.APM", + "stream/r6_a.APM", + "stream/r6_a1.APM", + "stream/r6_b.APM", + "stream/lo2_a.APM", + "stream/lo6_a.APM", + "stream/yd2_a.APM", + "stream/yd2_b.APM", + "stream/yd2_c.APM", + "stream/yd2_c1.APM", + "stream/yd2_d.APM", + "stream/yd2_e.APM", + "stream/yd2_f.APM", + "stream/yd2_g.APM", + "stream/yd2_h.APM", + "stream/yd2_ass.APM", + "stream/yd2_ok.APM", + "stream/h5_a.APM", + "stream/h5_b.APM", + "stream/h5_c.APM", + "stream/ammu_a.APM", + "stream/ammu_b.APM", + "stream/ammu_c.APM", + "stream/door_1.APM", + "stream/door_2.APM", + "stream/door_3.APM", + "stream/door_4.APM", + "stream/door_5.APM", + "stream/door_6.APM", + "stream/t3_a.APM", + "stream/t3_b.APM", + "stream/t3_c.APM", + "stream/k1_b.APM", + "stream/cat1.APM" +}; #endif \ No newline at end of file diff --git a/src/audio/sampman_dc.cpp b/src/audio/sampman_dc.cpp new file mode 100644 index 00000000..0f48a4b8 --- /dev/null +++ b/src/audio/sampman_dc.cpp @@ -0,0 +1,1342 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "crossplatform.h" + +#if !defined(AUDIO_OAL) && !defined(AUDIO_MSS) +#define verbosef(...) // dbglog(DBG_CRITICAL, __VA_ARGS__) +#define debugf(...) // dbglog(DBG_CRITICAL, __VA_ARGS__) + +#include "sampman.h" +#include "AudioManager.h" +#include "MusicManager.h" +#include "Frontend.h" +#include "Timer.h" + +#include +#include +#include + +#include +#include + +#include "CdStream.h" + +#define STREAM_STAGING_BUFFER_SIZE 16384 +#define STREAM_STAGING_READ_SIZE_STEREO 16384 +#define STREAM_STAGING_READ_SIZE_MONO (STREAM_STAGING_READ_SIZE_STEREO / 2) +#define STREAM_CHANNEL_BUFFER_SIZE (STREAM_STAGING_READ_SIZE_MONO * 2) // lower and upper halves +#define STREAM_CHANNEL_SAMPLE_COUNT (STREAM_CHANNEL_BUFFER_SIZE * 2) // 4 bit adpcm + +#define SPU_RAM_UNCACHED_BASE_U8 ((uint8_t *)SPU_RAM_UNCACHED_BASE) +// ************************************************************************************************ +// Begin AICA Driver stuff + +#define AICA_MEM_CHANNELS 0x020000 /* 64 * 16*4 = 4K */ + +/* Quick access to the AICA channels */ +#define AICA_CHANNEL(x) (AICA_MEM_CHANNELS + (x) * sizeof(aica_channel_t)) + +int aica_play_chn(int chn, int size, uint32_t aica_buffer, int fmt, int vol, int pan, int loop, int freq) { + // assert(size <= 65534); + // We gotta fix this at some point + if (size >= 65535) { + debugf("aica_play_chn: size too large for %p, %d, truncating to 65534\n", (void*)aica_buffer, size); + size = 65534; + } + + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_START; + chan->base = aica_buffer; + chan->type = fmt; + chan->length = size; + chan->loop = loop; + chan->loopstart = 0; + chan->loopend = size; + chan->freq = freq; + chan->vol = vol; + chan->pan = pan; + snd_sh4_to_aica(tmp, cmd->size); + return chn; +} + +void aica_stop_chn(int chn) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_STOP; + snd_sh4_to_aica(tmp, cmd->size); +} + +void aica_volpan_chn(int chn, int vol, int pan) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_PAN | AICA_CH_UPDATE_SET_VOL; + chan->vol = vol; + chan->pan = pan; + snd_sh4_to_aica(tmp, cmd->size); +} + + +void aica_snd_sfx_volume(int chn, int vol) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_VOL; + chan->vol = vol; + snd_sh4_to_aica(tmp, cmd->size); +} + +void aica_snd_sfx_pan(int chn, int pan) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_PAN; + chan->pan = pan; + snd_sh4_to_aica(tmp, cmd->size); +} + +void aica_snd_sfx_freq(int chn, int freq) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_FREQ; + chan->freq = freq; + snd_sh4_to_aica(tmp, cmd->size); +} + + +void aica_snd_sfx_freq_vol(int chn, int freq, int vol) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_FREQ | AICA_CH_UPDATE_SET_VOL; + chan->freq = freq; + chan->vol = vol; + snd_sh4_to_aica(tmp, cmd->size); +} + +// End of aica Driver stuff +// ************************************************************************************************ + +cSampleManager SampleManager; +bool8 _bSampmanInitialised = FALSE; +bool _dcAudioInitialized = false; + +uint32 BankStartOffset[MAX_SFX_BANKS]; +char SampleBankDescFilename[] = "sfx/sfx_all.dsc"; +char SampleBankDataFilename[] = "sfx/sfx_all.raw"; + +struct sfx_bank { + uintptr_t base; + std::vector effects; + std::vector effects_loop; +}; + +std::map sfx_banks; + +int nPedSlotSfx[MAX_PEDSFX]; +uintptr_t nPedSlotSfxAddr[MAX_PEDSFX]; +uint8_t nCurrentPedSlot; + +struct WavHeader { + // RIFF Header + char riff[4]; // RIFF Header Magic header + uint32_t chunkSize; // RIFF Chunk Size + char wave[4]; // WAVE Header + // "fmt" sub-chunk + char fmt[4]; // FMT header + uint32_t subchunk1Size; // Size of the fmt chunk + uint16_t audioFormat; // Audio format 1=PCM, other values indicate compression + uint16_t numOfChan; // Number of channels 1=Mono, 2=Stereo + uint32_t samplesPerSec; // Sampling Frequency in Hz + uint32_t bytesPerSec; // bytes per second + uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo + uint16_t bitsPerSample; // Number of bits per sample + // "data" sub-chunk + char data[4]; // "data" string + uint32_t dataSize; // Size of the data section +}; + +static inline uint8_t linearlize_volume(uint8_t vol) { + // uint8_t rv = powf(10.0f, (vol - MAX_VOLUME) / 42.0f) * 255; + uint8_t rv = vol * 255 / MAX_VOLUME; + // verbosef("linearlize_volume(%d) = %d\n", vol, rv); + return rv; +} +cSampleManager::cSampleManager(void) +{ + ; +} + +cSampleManager::~cSampleManager(void) +{ + +} + +bool8 +cSampleManager::IsMP3RadioChannelAvailable(void) +{ + return FALSE; +} + + +void cSampleManager::ReleaseDigitalHandle(void) +{ +} + +void cSampleManager::ReacquireDigitalHandle(void) +{ +} + +struct alignas(32) stream_info { + uint8_t buffer[STREAM_STAGING_BUFFER_SIZE + 128]; + std::mutex mtx; + file_t fd; + uint32_t aica_buffers[2]; // left, right + int mapped_ch[2]; // left, right + int rate; + int total_samples; + int played_samples; + int file_offset; + int vol; + uint8_t nPan; + uint8_t pan[2]; + + bool stereo; + bool playing; + bool next_is_upper_half; + bool first_refill; + bool paused; +}; + +stream_info streams[MAX_STREAMS]; + + +std::mutex channel_mtx; +static struct { + uintptr_t ptr; + uintptr_t ptr_loop; // or 0 if no loop + int nSfx; + int freq; + float distMin; + float distMax; + float fX; + float fY; + float fZ; + uint8_t attenuationVol; + uint8_t emittingVol; + uint8_t vol; + uint8_t pan; + char ch; + char mapped_ch; + bool loop; + bool in_hnd_loop; + int8_t nBank; +} channels[MAXCHANNELS+MAX2DCHANNELS]; + + +// static void * fill_audio_cb (snd_stream_hnd_t hnd, int smp_req, int *smp_recv) { +// auto si = (stream_info*)snd_stream_get_userdata(hnd); + +// assert(si->hnd == hnd); + +// verbosef("fill_audio_cb: %p %d\n", si, smp_req); + +// size_t read_bytes = 0; + +// { +// std::lock_guard lk(si->mtx); +// if (si->playing) { +// read_bytes = fs_read(si->fd, si->buffer, smp_req); +// if (read_bytes <= 0) { +// verbosef("fill_audio_cb: %p stream end\n", si); +// *smp_recv = 0; +// return NULL; +// } +// } +// } + +// *smp_recv = read_bytes; + +// if(si->seek_buf > 0) { +// size_t seek_bytes = si->seek_buf; +// si->seek_buf = 0; + +// while(seek_bytes & 3) { +// ++seek_bytes; +// } +// if(seek_bytes > 128) { +// seek_bytes = 128; +// } +// memset(si->buffer + sizeof(si->buffer) - seek_bytes, 0, seek_bytes); +// return si->buffer + seek_bytes; +// } + +// return si->buffer; +// } + +std::thread snd_thread; +bool8 +cSampleManager::Initialise(void) +{ + auto init = snd_init(); + assert(init >= 0); + // snd_stream_init_ex(2, STREAM_BUFFER_SIZE); + + for (int i = 0; i< MAX_STREAMS; i++) { + streams[i].mapped_ch[0] = snd_sfx_chn_alloc(); + streams[i].mapped_ch[1] = snd_sfx_chn_alloc(); + streams[i].aica_buffers[0] = snd_mem_malloc(STREAM_CHANNEL_BUFFER_SIZE); + streams[i].aica_buffers[1] = snd_mem_malloc(STREAM_CHANNEL_BUFFER_SIZE); + debugf("Stream %d mapped to: %d, %d\n", i, streams[i].mapped_ch[0], streams[i].mapped_ch[1]); + debugf("Stream %d buffers: %p, %p\n", i, (void*)streams[i].aica_buffers[0], (void*)streams[i].aica_buffers[1]); + assert(streams[i].mapped_ch[0] != -1); + assert(streams[i].mapped_ch[1] != -1); + streams[i].fd = -1; + + streams[i].vol = 255; + streams[i].nPan = 63; + streams[i].pan[0] = 128; + streams[i].pan[1] = 128; + } + + for (int i = 0; i < (MAXCHANNELS+MAX2DCHANNELS); i++) { + channels[i].mapped_ch = snd_sfx_chn_alloc(); + debugf("Channel %d mapped to %d\n", i, channels[i].mapped_ch); + assert(channels[i].mapped_ch != -1); + } + + if (!InitialiseSampleBanks()) + return FALSE; + + snd_thread = std::thread([]() { + for(;;) { + { + std::lock_guard lk(channel_mtx); + for (int i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++) { + if (channels[i].ch != -1) { + assert(channels[i].nSfx != -1); + + uint16_t channel_pos = (g2_read_32(SPU_RAM_UNCACHED_BASE + AICA_CHANNEL(channels[i].ch) + offsetof(aica_channel_t, pos)) & 0xffff); + // verbosef("Channel %d pos: %d\n", i, channel_pos); + if (!channels[i].loop) { + auto channel_looped = g2_read_32(SPU_RAM_UNCACHED_BASE + AICA_CHANNEL(channels[i].ch) + offsetof(aica_channel_t, looped)); + // the looped flag is set even for one shots and is a reliable way to know if the channel has finished playing + if (channel_looped) { + debugf("Auto stopping channel: %d -> %d\n", i, channels[i].ch); + channels[i].ch = -1; + } + } else if (channels[i].ptr_loop && !channels[i].in_hnd_loop) { + if (channel_pos >= SampleManager.m_aSamples[channels[i].nSfx].nLoopStartSample) { + channels[i].in_hnd_loop = true; + debugf("Starting loop section: for sfx_%d_loop.wav\n", channels[i].nSfx); + snd_sfx_stop(channels[i].ch); + channels[i].ch = aica_play_chn(channels[i].mapped_ch, SampleManager.m_aSamples[channels[i].nSfx].nLoopByteSize * 2, channels[i].ptr_loop, 2 /* ADPCM */, channels[i].vol, channels[i].pan, 1, channels[i].freq); + } + } + } + } + } + + for (int i = 0; i< MAX_STREAMS; i++) { + size_t do_read = 0; + { + std::lock_guard lk(streams[i].mtx); + if (streams[i].playing) { + // get channel pos + uint32_t channel_pos = g2_read_32(SPU_RAM_UNCACHED_BASE + AICA_CHANNEL(streams[i].mapped_ch[0]) + offsetof(aica_channel_t, pos)) & 0xffff; + uint32_t logical_pos = channel_pos; + if (logical_pos > STREAM_CHANNEL_SAMPLE_COUNT/2) { + logical_pos -= STREAM_CHANNEL_SAMPLE_COUNT/2; + } + verbosef("Stream %d pos: %d, log: %d, rem: %d\n", i, channel_pos, logical_pos, streams[i].played_samples); + + bool can_refill = (streams[i].played_samples + STREAM_CHANNEL_SAMPLE_COUNT/2) < streams[i].total_samples; + bool can_fetch = (streams[i].played_samples + STREAM_CHANNEL_SAMPLE_COUNT/2 + STREAM_CHANNEL_SAMPLE_COUNT/2 + STREAM_CHANNEL_SAMPLE_COUNT/2) < streams[i].total_samples; + // copy over data if needed from staging + if (channel_pos >= STREAM_CHANNEL_SAMPLE_COUNT/2 && !streams[i].next_is_upper_half) { + streams[i].next_is_upper_half = true; + if (can_refill) { // could we need a refill? + verbosef("Filling channel %d with lower half\n", i); + // fill lower half + spu_memload(streams[i].aica_buffers[0], streams[i].buffer, STREAM_CHANNEL_BUFFER_SIZE/2); + if (streams[i].stereo) { + spu_memload(streams[i].aica_buffers[1], streams[i].buffer + STREAM_STAGING_READ_SIZE_MONO, STREAM_CHANNEL_BUFFER_SIZE/2); + } + // queue next read to staging if any + if (can_fetch) { + do_read = streams[i].stereo ? STREAM_STAGING_READ_SIZE_STEREO : STREAM_STAGING_READ_SIZE_MONO; + } + } + assert(streams[i].first_refill == false); + streams[i].played_samples += STREAM_CHANNEL_SAMPLE_COUNT/2; + } else if (channel_pos < STREAM_CHANNEL_SAMPLE_COUNT/2 && streams[i].next_is_upper_half) { + streams[i].next_is_upper_half = false; + if (can_refill) { // could we need a refill? + verbosef("Filling channel %d with upper half\n", i); + // fill upper half + spu_memload(streams[i].aica_buffers[0] + STREAM_CHANNEL_BUFFER_SIZE/2, streams[i].buffer, STREAM_CHANNEL_BUFFER_SIZE/2); + if (streams[i].stereo) { + spu_memload(streams[i].aica_buffers[1] + STREAM_CHANNEL_BUFFER_SIZE/2, streams[i].buffer + STREAM_STAGING_READ_SIZE_MONO, STREAM_CHANNEL_BUFFER_SIZE/2); + } + // queue next read to staging, if any + if (can_fetch) { + do_read = streams[i].stereo ? STREAM_STAGING_READ_SIZE_STEREO : STREAM_STAGING_READ_SIZE_MONO; + } + } + if (streams[i].first_refill) { + streams[i].first_refill = false; + } else { + streams[i].played_samples += STREAM_CHANNEL_SAMPLE_COUNT/2; + } + } + // if end of file, stop + if ((streams[i].played_samples + logical_pos) > streams[i].total_samples) { + // stop channel + debugf("Auto stopping stream: %d -> {%d, %d}, %d total\n", i, streams[i].mapped_ch[0], streams[i].mapped_ch[1], streams[i].total_samples); + aica_stop_chn(streams[i].mapped_ch[0]); + aica_stop_chn(streams[i].mapped_ch[1]); + streams[i].playing = false; + } + } + + if (do_read) { + debugf("Queueing stream read: %d, file: %d, buffer: %p, size: %d, tell: %d\n", i, streams[i].fd, streams[i].buffer, do_read, fs_tell(streams[i].fd)); + CdStreamQueueAudioRead(streams[i].fd, streams[i].buffer, do_read, streams[i].file_offset); + streams[i].file_offset += do_read; + } + } + } + thd_sleep(50); + } + }); + + for ( int32 i = 0; i < MAX_PEDSFX; i++ ) + { + nPedSlotSfx[i] = -1; + nPedSlotSfxAddr[i] = snd_mem_malloc(PED_BLOCKSIZE_ADPCM); + debugf("PedSlot %d buffer: %p\n", i, (void*)nPedSlotSfxAddr[i]); + } + + nCurrentPedSlot = 0; + + _dcAudioInitialized = true; + return TRUE; +} + +void +cSampleManager::Terminate(void) +{ + +} + +bool8 cSampleManager::CheckForAnAudioFileOnCD(void) +{ + return TRUE; +} + +char cSampleManager::GetCDAudioDriveLetter(void) +{ + return '\0'; +} + +void +cSampleManager::UpdateEffectsVolume(void) +{ + // TODO +} + + +void +cSampleManager::SetEffectsMasterVolume(uint8 nVolume) +{ + m_nEffectsVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicMasterVolume(uint8 nVolume) +{ + m_nMusicVolume = nVolume; +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ + m_nEffectsFadeVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ + m_nMusicFadeVolume = nVolume; +} + +void +cSampleManager::SetMonoMode(uint8 nMode) +{ +} + +bool8 +cSampleManager::LoadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + ASSERT( nBank != SFX_BANK_PED_COMMENTS ); + verbosef("LoadSampleBank(%d)\n", nBank); + + auto it = sfx_banks.find(nBank); + + if (it == sfx_banks.end()) { + debugf("Loading bank %d\n", nBank); + sfx_bank bank; + int firstSfx = BankStartOffset[nBank]; + int nextBankSfx = BankStartOffset[nBank+1]; + assert(firstSfx= 0); + // this is very wasteful and temporary + void* stagingBuffer = memalign(32, 32 * 2048); + assert(stagingBuffer != 0); + + // Ideally, we'd suspend the CdStream thingy here or read via that instead + uintptr_t loadOffset = bank.base; + fs_seek(fd, fileStart, SEEK_SET); + + while (fileSize > 0) { + size_t readSize = fileSize > 32 * 2048 ? 32 * 2048 : fileSize; + int rs = fs_read(fd, stagingBuffer, readSize); + debugf("Read %d bytes, expected %d\n", rs, readSize); + assert(rs == readSize); + spu_memload(loadOffset, stagingBuffer, readSize); + loadOffset += readSize; + fileSize -= readSize; + debugf("Loaded %d bytes, %d remaining\n", readSize, fileSize); + } + fs_close(fd); + free(stagingBuffer); + + + for (int nSfx = BankStartOffset[nBank]; nSfx < BankStartOffset[nBank+1]; nSfx++) { + bank.effects.push_back(m_aSamples[nSfx].nFileOffset - fileStart + bank.base); + if (m_aSamples[nSfx].nLoopStartSample) { + bank.effects_loop.push_back(m_aSamples[nSfx].nLoopFileOffset - fileStart + bank.base); + } else { + bank.effects_loop.push_back(0); + } + debugf("Loaded sfx %d at %p, loop at %p\n", nSfx, (void*)bank.effects.back(), (void*)bank.effects_loop.back()); + } + + debugf("Loaded bank %d\n", nBank); + sfx_banks[nBank] = bank; + } + return TRUE; +} + +void +cSampleManager::UnloadUnusedSampleBank() +{ + auto bank = (--sfx_banks.end())->first; + assert(bank != SFX_BANK_0); // can't unload bank 0 for OOM + UnloadSampleBank(bank); +} + +void +cSampleManager::UnloadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + + verbosef("UnloadSampleBank(%d)\n", nBank); + + auto it = sfx_banks.find(nBank); + if (it != sfx_banks.end()) { + debugf("Unloading bank %d\n", nBank); + { + std::lock_guard lk(channel_mtx); + for (int i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++) { + if (channels[i].nBank == nBank) { + dbglog(DBG_CRITICAL, "Warning: Channel %d is in unloaded nBank %d\n", i, nBank); + if (channels[i].ch != -1) { + dbglog(DBG_CRITICAL, "Warning: Channel %d was activelly playing from bank %d\n", i, nBank); + snd_sfx_stop(channels[i].ch); + } + channels[i].ch = -1; + channels[i].nSfx = -1; + channels[i].nBank = -1; + channels[i].ptr = 0; + channels[i].ptr_loop = 0; + } + } + } + snd_mem_free(it->second.base); + sfx_banks.erase(it); + } +} + +int8 +cSampleManager::IsSampleBankLoaded(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + + return LOADING_STATUS_LOADED; +} + +uint8 +cSampleManager::IsPedCommentLoaded(uint32 nComment) +{ + int8 slot; + + for ( int32 i = 0; i < _TODOCONST(3); i++ ) + { + slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nComment == nPedSlotSfx[slot] ) + return LOADING_STATUS_LOADED; + } + + return LOADING_STATUS_NOT_LOADED; +} + + +int32 +cSampleManager::_GetPedCommentSlot(uint32 nComment) +{ + int8 slot; + + for ( int32 i = 0; i < _TODOCONST(3); i++ ) + { + slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nComment == nPedSlotSfx[slot] ) + return slot; + } + + return -1; +} + +bool8 +cSampleManager::LoadPedComment(uint32 nComment) +{ + verbosef("LoadPedComment %ld\n", nComment); + + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + ASSERT (nComment >= SAMPLEBANK_PED_START && nComment < SAMPLEBANK_PED_MAX); + + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + // no talking peds during cutsenes or the game end + if ( MusicManager.IsInitialised() ) + { + switch ( MusicManager.GetMusicMode() ) + { + case MUSICMODE_CUTSCENE: + { + return FALSE; + + break; + } + + case MUSICMODE_FRONTEND: + { + if ( MusicManager.GetNextTrack() == STREAMED_SOUND_GAME_COMPLETED ) + return FALSE; + + break; + } + } + } + + assert(m_aSamples[nComment].nByteSize < PED_BLOCKSIZE_ADPCM); + + file_t fd = fs_open(SampleBankDataFilename, O_RDONLY); + + assert(fd >= 0); + debugf("Loading ped comment %d, offset: %d, size: %d\n", nComment, m_aSamples[nComment].nFileOffset, m_aSamples[nComment].nByteSize); + fs_seek(fd, m_aSamples[nComment].nFileOffset, SEEK_SET); + + + // TODO: When we can dma directly to AICA, we can use this instead + // fs_read(fd, SPU_BASE_U8 + nPedSlotSfxAddr[nCurrentPedSlot], sizeof(nPedSlotSfxAddr)); + + void* stagingBuffer = memalign(32, m_aSamples[nComment].nByteSize); + assert(stagingBuffer != 0); + debugf("Allocated %d bytes at %p\n", m_aSamples[nComment].nByteSize, stagingBuffer); + int rs = fs_read(fd, stagingBuffer, m_aSamples[nComment].nByteSize); + debugf("Read %d bytes, expected %d\n", rs, m_aSamples[nComment].nByteSize); + assert(rs == m_aSamples[nComment].nByteSize); + + fs_close(fd); + + spu_memload(nPedSlotSfxAddr[nCurrentPedSlot], stagingBuffer, m_aSamples[nComment].nByteSize); + free(stagingBuffer); + nPedSlotSfx[nCurrentPedSlot] = nComment; + + if ( ++nCurrentPedSlot >= MAX_PEDSFX ) + nCurrentPedSlot = 0; + + return TRUE; +} + +int32 +cSampleManager::GetBankContainingSound(uint32 nSfx) +{ + assert(nSfx < TOTAL_AUDIO_SAMPLES); + + for (int i = MAX_SFX_BANKS-1; i >= 0; i--) + { + if ( nSfx >= BankStartOffset[i] ) + return i; + } + + return INVALID_SFX_BANK; +} + +uint32 +cSampleManager::GetSampleBaseFrequency(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nFrequency; +} + +// now in samples +uint32 +cSampleManager::GetSampleLoopStartOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nLoopStartSample; +} + +// always loop to end +int32 +cSampleManager::GetSampleLoopEndOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return -1; +} + +uint32 +cSampleManager::GetSampleLength(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nByteSize * 2; // adcpcm is 4 bit +} + +bool8 cSampleManager::UpdateReverb(void) +{ + return FALSE; +} + +void +cSampleManager::SetChannelReverbFlag(uint32 nChannel, bool8 nReverbFlag) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +bool8 +cSampleManager::InitialiseChannel(uint32 nChannel, uint32 nSfx, uint8 nBank) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + // printf("InitialiseChannel(nChannel: %d, nSfx: %d, nBank: %d)\n", nChannel, nSfx, nBank); + StopChannel(nChannel); + nBank = GetBankContainingSound(nSfx); + if (nBank != SFX_BANK_PED_COMMENTS) { + // Load the bank here, without holding channel_mtx as it may call UnloadSampleBank that locks it + LoadSampleBank(nBank); + } + + std::lock_guard lk(channel_mtx); + + verbosef("InitialiseChannel %ld %ld %d\n", nChannel, nSfx, nBank); + + if (nBank == SFX_BANK_PED_COMMENTS) { + int32 i; + for ( i = 0; i < _TODOCONST(3); i++ ) + { + int32 slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nSfx == nPedSlotSfx[slot] ) + { + channels[nChannel].ptr = nPedSlotSfxAddr[slot]; + break; + } + } + + if (i == _TODOCONST(3)) + return FALSE; + debugf("Channel %d is using ped comment %d, buffer %p, samples: %d\n", nChannel, nSfx, channels[nChannel].ptr, m_aSamples[nSfx].nByteSize*2); + } else { + channels[nChannel].ptr = sfx_banks[nBank].effects[nSfx - BankStartOffset[nBank]]; + } + + channels[nChannel].nSfx = nSfx; + channels[nChannel].nBank = nBank; + + if (m_aSamples[nSfx].nLoopStartSample != 0) { + channels[nChannel].ptr_loop = sfx_banks[nBank].effects_loop[nSfx - BankStartOffset[nBank]]; + } else { + channels[nChannel].ptr_loop = 0; + } + + channels[nChannel].freq = m_aSamples[nSfx].nFrequency; + channels[nChannel].attenuationVol = 255; + // channels[nChannel].vol = 255; + // channels[nChannel].pan = 128; + channels[nChannel].loop = 0; + + return TRUE; +} + +void updateVol(uint32 nChannel) { + + auto newVol = channels[nChannel].emittingVol * channels[nChannel].attenuationVol / 255; + // newVol = 255; + // printf("updateVol(nChannel: %d) vol: %d, newVol: %d\n", nChannel, channels[nChannel].vol, newVol); + if (channels[nChannel].vol != newVol) { + channels[nChannel].vol = newVol; + // printf("updateVol(nChannel: %d) vol: %d\n", nChannel, channels[nChannel].vol); + if (channels[nChannel].ch != -1) { + aica_snd_sfx_volume(channels[nChannel].ch, channels[nChannel].vol); + } + } +} + +void +cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) +{ + // ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + if (nVolume > MAX_VOLUME) + nVolume = MAX_VOLUME; + std::lock_guard lk(channel_mtx); + channels[nChannel].emittingVol = linearlize_volume(nVolume);// nVolume * 255 / MAX_VOLUME; + channels[nChannel].attenuationVol = 255; + + updateVol(nChannel); + verbosef("SetChannelVolume(nChannel: %d) vol: %d\n", nChannel, nVolume); +} + +void +cSampleManager::SetChannelPan(uint32 nChannel, uint32 gta_pan) +{ + // ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + // DC uses logarithmic pan + auto linear_gain = 1-abs(gta_pan-64.0f)/64.0f; + + auto db = 20.0f * std::log10(linear_gain); + if (db < -45) { + db = -45; + } + + db = fabs(db); + auto aica_paned_att = db / 0.355; + + int aica_pan; + + if (gta_pan < 64) { + aica_pan = 128 - aica_paned_att; + } else { + aica_pan = 128 + aica_paned_att; + } + + // nPan = nPan * 2; // 64 is center, for dc it is 128 + if (channels[nChannel].pan != aica_pan) { + channels[nChannel].pan = aica_pan; + if (channels[nChannel].ch != -1) { + aica_snd_sfx_pan(channels[nChannel].ch, channels[nChannel].pan); + } + } + // printf("SetChannelPan(nChannel: %d) pan: %d\n", nChannel, nPan); +} + +void +cSampleManager::SetChannelFrequency(uint32 nChannel, uint32 nFreq) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + // printf("SetChannelFrequency(nChannel: %d) freq: %d\n", nChannel, nFreq); + std::lock_guard lk(channel_mtx); + if (channels[nChannel].freq != nFreq) { + channels[nChannel].freq = nFreq; + if (channels[nChannel].ch != -1) { + aica_snd_sfx_freq(channels[nChannel].ch, channels[nChannel].freq); + } + } +} + +void +cSampleManager::SetChannelLoopPoints(uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + assert(channels[nChannel].nSfx != -1); + assert(m_aSamples[channels[nChannel].nSfx].nLoopStartSample == nLoopStart && -1 == nLoopEnd); +} + +void +cSampleManager::SetChannelLoopCount(uint32 nChannel, uint32 nLoopCount) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + assert(nLoopCount == 0 || nLoopCount == 1); + channels[nChannel].loop = nLoopCount == 0; + // printf("SetChannelLoopCount(nChannel: %d) loop: %d\n", nChannel, nLoopCount); +} + +bool8 +cSampleManager::GetChannelUsedFlag(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + return channels[nChannel].ch != -1; +} + +void +cSampleManager::StartChannel(uint32 nChannel) +{ + StopChannel(nChannel); + std::lock_guard lk(channel_mtx); + // printf("StartChannel(nChannel: %d) vol: %d, pan: %d, freq: %d, loop: %d, sfx: %d\n", nChannel, channels[nChannel].vol, channels[nChannel].pan, channels[nChannel].freq, channels[nChannel].loop, channels[nChannel].nSfx); + channels[nChannel].in_hnd_loop = false; + assert(channels[nChannel].nSfx != -1); + channels[nChannel].ch = aica_play_chn( + channels[nChannel].mapped_ch, + m_aSamples[channels[nChannel].nSfx].nByteSize * 2, + channels[nChannel].ptr, + 2 /* ADPCM */, + channels[nChannel].vol, + channels[nChannel].pan, + channels[nChannel].loop, + channels[nChannel].freq + ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::StopChannel(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + // printf("StopChannel(nChannel: %d)\n", nChannel); + if (channels[nChannel].ch != -1) { + snd_sfx_stop(channels[nChannel].ch); + channels[nChannel].ch = -1; + } +} + +void +cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream, uint32_t seek_bytes_aligned) +{ + ASSERT( nStream < MAX_STREAMS ); + file_t f = fs_open(DCStreamedNameTable[nFile], O_RDONLY); + debugf("PreloadStreamedFile(%p, %d, %d) is %s\n", f, nFile, nStream, DCStreamedNameTable[nFile]); + assert(f >= 0 ); + WavHeader hdr; + assert(fs_read(f, &hdr, sizeof(hdr)) == sizeof(hdr)); + + { + std::lock_guard lk(streams[nStream].mtx); + + // Do we gotta stop it here? + assert(!streams[nStream].playing); + streams[nStream].rate = hdr.samplesPerSec; + streams[nStream].stereo = hdr.numOfChan == 2; + if (streams[nStream].fd >= 0) { + CdStreamDiscardAudioRead(streams[nStream].fd); + fs_close(streams[nStream].fd); + } + streams[nStream].fd = f; + streams[nStream].playing = false; + streams[nStream].total_samples = hdr.dataSize * 2 / hdr.numOfChan; + streams[nStream].played_samples = 0; + streams[nStream].next_is_upper_half = true; + streams[nStream].first_refill = true; + + debugf("PreloadStreamedFile: %s: stream: %d, freq: %d, chans: %d, byte size: %d, played samples: %d\n", DCStreamedNameTable[nFile], nStream, hdr.samplesPerSec, hdr.numOfChan, hdr.dataSize, streams[nStream].played_samples); + + + // How to avoid the lock? + if (seek_bytes_aligned) { + streams[nStream].played_samples = seek_bytes_aligned * (streams[nStream].stereo ? 1 : 2); + debugf("Seeking aligned to: %d, played_samples: %d\n", seek_bytes_aligned, streams[nStream].played_samples); + fs_seek(streams[nStream].fd, 2048 + seek_bytes_aligned, SEEK_SET); + } else { + fs_seek(f, 2048, SEEK_SET); + } + + #if 0 + // Read directly in the future + fs_read(f, SPU_RAM_UNCACHED_BASE_U8 + streams[nStream].aica_buffers[0], STREAM_STAGING_READ_SIZE_MONO); + if (streams[nStream].stereo) { + fs_read(f, SPU_RAM_UNCACHED_BASE_U8 + streams[nStream].aica_buffers[1], STREAM_STAGING_READ_SIZE_MONO); + } + #else + // Stage to memory + fs_read(f, streams[nStream].buffer, streams[nStream].stereo ? STREAM_STAGING_READ_SIZE_STEREO : STREAM_STAGING_READ_SIZE_MONO); + spu_memload(streams[nStream].aica_buffers[0], streams[nStream].buffer, STREAM_CHANNEL_BUFFER_SIZE/2); + if (streams[nStream].stereo) { + spu_memload(streams[nStream].aica_buffers[1], streams[nStream].buffer + STREAM_STAGING_READ_SIZE_MONO, STREAM_CHANNEL_BUFFER_SIZE/2); + } + #endif + + if (streams[nStream].total_samples > STREAM_CHANNEL_SAMPLE_COUNT/2) { + // If more than one buffer, prefetch the next one + fs_read(f, streams[nStream].buffer, streams[nStream].stereo ? STREAM_STAGING_READ_SIZE_STEREO : STREAM_STAGING_READ_SIZE_MONO); + } + + streams[nStream].file_offset = fs_tell(f); + } + + verbosef("PreloadStreamedFile: %p %d - %s, %d, %d, \n", f, nFile, DCStreamedNameTable[nFile], streams[nStream].rate, streams[nStream].stereo); +} + +// we can't really pause a stream, so we just make it go very slow with zero volume +void +cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + { + std::lock_guard lk(streams[nStream].mtx); + + if (nPauseFlag != streams[nStream].paused) { + streams[nStream].paused = nPauseFlag; + if(nPauseFlag) { + // rate of 0 is 172 samples/second. not ideal but gets the job done. + aica_snd_sfx_freq_vol(streams[nStream].mapped_ch[0], 0, 0); + aica_snd_sfx_freq_vol(streams[nStream].mapped_ch[1], 0, 0); + } else { + aica_snd_sfx_freq_vol(streams[nStream].mapped_ch[0], streams[nStream].rate, streams[nStream].vol); + aica_snd_sfx_freq_vol(streams[nStream].mapped_ch[1], streams[nStream].rate, streams[nStream].vol); + } + } + } +} + +void +cSampleManager::StartPreloadedStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + debugf("StartPreloadedStreamedFile(%d)\n", nStream); + std::lock_guard lk(streams[nStream].mtx); + if (streams[nStream].playing) { + return; + } + debugf("StartPreloadedStreamedFile(%d) - actually starting stream\n", nStream); + // int aica_play_chn(int chn, int size, uint32_t aica_buffer, int fmt, int vol, int pan, int loop, int freq) + aica_play_chn( + streams[nStream].mapped_ch[0], + STREAM_CHANNEL_SAMPLE_COUNT, + streams[nStream].aica_buffers[0], + 3 /* adpcm long stream */, + streams[nStream].vol, + streams[nStream].pan[0], + 1, + streams[nStream].rate + ); + + aica_play_chn( + streams[nStream].mapped_ch[1], + STREAM_CHANNEL_SAMPLE_COUNT, + streams[nStream].aica_buffers[streams[nStream].stereo ? 1 : 0], + 3 /* adpcm long stream */, + streams[nStream].vol, + streams[nStream].pan[1], + 1, + streams[nStream].rate + ); + + streams[nStream].playing = true; + // printf("StartPreloadedStreamedFile(%d)\n", nStream); + // { + // std::lock_guard lk(streams[nStream].mtx); + // streams[nStream].playing = true; + // if (streams[nStream].active) { + // snd_stream_stop(streams[nStream].hnd); + // } + // streams[nStream].active = true; + // } + // snd_stream_start_adpcm(streams[nStream].hnd, streams[nStream].rate, streams[nStream].stereo); + // snd_stream_volume(streams[nStream].hnd, streams[nStream].vol); + verbosef("StartPreloadedStreamedFile(%d)\n", nStream); +} + +bool8 +cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + debugf("StartStreamedFile(%d, %d, %d)\n", nFile, nPos, nStream); + uint32_t seek_aligned = 0; + if (nPos) { + uint32 seek_bytes = nPos * streams[nStream].rate / (streams[nStream].stereo ? 1000: 2000); + seek_aligned = seek_bytes & ~(streams[nStream].stereo ? (STREAM_STAGING_READ_SIZE_STEREO-1) : (STREAM_STAGING_READ_SIZE_MONO-1)); + } + PreloadStreamedFile(nFile, nStream, seek_aligned); + StartPreloadedStreamedFile(nStream); + return TRUE; +} + +void +cSampleManager::StopStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + verbosef("StopStreamedFile(%d)\n", nStream); + std::lock_guard lk(streams[nStream].mtx); + streams[nStream].playing = false; + + aica_stop_chn(streams[nStream].mapped_ch[0]); + aica_stop_chn(streams[nStream].mapped_ch[1]); + + if (streams[nStream].fd >= 0) { + CdStreamDiscardAudioRead(streams[nStream].fd); + fs_close(streams[nStream].fd); + } + streams[nStream].fd = -1; +} + +int32 +cSampleManager::GetStreamedFilePosition(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + int32 rv = 0; + + return streams[nStream].played_samples * 1000 / streams[nStream].rate; + // if(streams[nStream].fd >= 0) { + // rv = fs_tell(streams[nStream].fd); + // } + debugf("GetStreamedFilePosition: %d %d\n", nStream, rv); + return rv; +} + +void +cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, uint8 nEffectFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + if (nVolume > MAX_VOLUME) + nVolume = MAX_VOLUME; + nVolume = linearlize_volume(nVolume); //nVolume * 255 / MAX_VOLUME; + if (streams[nStream].vol != nVolume || streams[nStream].nPan != nPan) { + streams[nStream].vol = nVolume; + streams[nStream].nPan = nPan; + + auto lpan = (int8_t)nPan - 63; + lpan = lpan < 0 ? 0 : lpan > 63 ? 63 : lpan; + + auto rpan = (int8_t)nPan + 63; + rpan = rpan < 64 ? 64 : rpan > 127 ? 127 : rpan; + + streams[nStream].pan[0] = lpan * 2; + streams[nStream].pan[1] = rpan * 2; + aica_volpan_chn(streams[nStream].mapped_ch[0], streams[nStream].vol, streams[nStream].pan[0]); + aica_volpan_chn(streams[nStream].mapped_ch[1], streams[nStream].vol, streams[nStream].pan[1]); + } + // verbosef("SetStreamedVolumeAndPan: %d %d %d %d\n", nStream, nVolume, nPan, nEffectFlag); +} + +int32 +cSampleManager::GetStreamedFileLength(uint8 nFile) +{ + ASSERT( nFile < TOTAL_STREAMED_SOUNDS ); + int32 rv = 1; // Look in MusicManager.cpp:268 + file_t fd = fs_open(DCStreamedNameTable[nFile], O_RDONLY); + assert(fd >= 0); + WavHeader hdr; + assert(fs_read(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); + + rv = hdr.dataSize * 2000 / hdr.numOfChan / hdr.samplesPerSec; + + fs_close(fd); + + debugf("GetStreamedFileLength: %d %d\n", nFile, rv); + return rv <= 0 ? 1 : rv; +} + +bool8 +cSampleManager::IsStreamPlaying(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + std::lock_guard lk(streams[nStream].mtx); + return streams[nStream].playing && !streams[nStream].paused; +} + +bool8 +cSampleManager::InitialiseSampleBanks(void) +{ + file_t fd = fs_open(SampleBankDescFilename, O_RDONLY); + if (fd < 0) + return FALSE; + + size_t rs = fs_read(fd, m_aSamples, sizeof(tSample)*TOTAL_AUDIO_SAMPLES); + + if (rs != sizeof(tSample)*TOTAL_AUDIO_SAMPLES) { + fs_close(fd); + return FALSE; + } + + fs_close(fd); + + for (int i = 0; i < (MAXCHANNELS+MAX2DCHANNELS); i++) { + channels[i].ptr = 0; + channels[i].ptr_loop = 0; + channels[i].ch = -1; + channels[i].nSfx = -1; + channels[i].nBank = -1; + } + + LoadSampleBank(SFX_BANK_0); + + return TRUE; +} + +#if defined(EXTERNAL_3D_SOUND) +void cSampleManager::SetSpeakerConfig(int32 nConfig) { + +} +uint32 cSampleManager::GetMaximumSupportedChannels(void) { + return MAXCHANNELS; +} + +uint32 cSampleManager::GetNum3DProvidersAvailable(void) { + return 32; +} +void cSampleManager::SetNum3DProvidersAvailable(uint32 num) { + +} + +char *cSampleManager::Get3DProviderName(uint8 id) { + return "dummy"; +} +void cSampleManager::Set3DProviderName(uint8 id, char *name) { + +} + +int8 cSampleManager::GetCurrent3DProviderIndex(void) { + return 0; +} +int8 cSampleManager::SetCurrent3DProvider(uint8 which) { + return 0; +} + +void cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) { + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + // printf("SetChannelEmittingVolume(nChannel: %d) vol: %d\n", nChannel, nVolume); + if (nVolume > MAX_VOLUME) + nVolume = MAX_VOLUME; + + std::lock_guard lk(channel_mtx); + channels[nChannel].emittingVol = linearlize_volume(nVolume); // nVolume * 255 / MAX_VOLUME; + updateVol(nChannel); +} + +float calculatePan(float x, float z) { + // Azimuth angle (in radians), range from -PI to +PI + float theta = Atan2(x, z); + + // Use the sine function for smooth panning + float pan = Sin(theta); // Use sine to map -PI to +PI to -1.0 to +1.0 + + return pan; +} + +float calculateAttenuation(float x, float y, float z, float mindist, float maxdist) { + // Distance calculation (Euclidean distance) + float distance = Sqrt(x * x + y * y + z * z); + + // If distance is less than or equal to mindist, return full volume (attenuation = 1) + if (distance <= mindist) { + return 1.0f; + } + + // If distance is greater than or equal to maxdist, return no sound (attenuation = 0) + if (distance >= maxdist) { + return 0.0f; + } + + // Calculate attenuation using inverse distance model and rolloff factor + // Attenuation = (referenceDistance / distance) ^ rolloffFactor + float attenuation = (mindist / distance); // rolloffFactor = 1.0f + + return attenuation; +} + + + +void cSampleManager::SetChannel3DPosition (uint32 nChannel, float fX, float fY, float fZ) { + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + // printf("SetChannel3DPosition(nChannel: %d) x: %f, y: %f, z: %f\n", nChannel, fX, fY, fZ); + { + std::lock_guard lk(channel_mtx); + channels[nChannel].fX = fX; + channels[nChannel].fY = fY; + channels[nChannel].fZ = fZ; + channels[nChannel].attenuationVol = calculateAttenuation(channels[nChannel].fX, channels[nChannel].fY, channels[nChannel].fZ, channels[nChannel].distMin, channels[nChannel].distMax) * 255; + updateVol(nChannel); + } + + SetChannelPan(nChannel, calculatePan(-fX, fZ) * 63 + 64); + +} +void cSampleManager::SetChannel3DDistances (uint32 nChannel, float fMax, float fMin) { + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + // printf("SetChannel3DDistances(nChannel: %d) min: %f, max: %f\n", nChannel, fMin, fMax); + channels[nChannel].distMin = fMin; + channels[nChannel].distMax = fMax; + channels[nChannel].attenuationVol = calculateAttenuation(channels[nChannel].fX, channels[nChannel].fY, channels[nChannel].fZ, channels[nChannel].distMin, channels[nChannel].distMax) * 255; + updateVol(nChannel); +} +#endif + +#endif diff --git a/src/audio/sampman_null.cpp b/src/audio/sampman_null.cpp index d607836d..82542b51 100644 --- a/src/audio/sampman_null.cpp +++ b/src/audio/sampman_null.cpp @@ -301,7 +301,7 @@ cSampleManager::StopChannel(uint32 nChannel) } void -cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) +cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream, uint32_t seek_bytes_aligned) { ASSERT( nStream < MAX_STREAMS ); } diff --git a/src/collision/ColModel.cpp b/src/collision/ColModel.cpp index fb90e7dd..19ffa3e2 100644 --- a/src/collision/ColModel.cpp +++ b/src/collision/ColModel.cpp @@ -51,8 +51,8 @@ CColModel::CalculateTrianglePlanes(void) { PUSH_MEMID(MEMID_COLLISION); - // HACK: allocate space for one more element to stuff the link pointer into - trianglePlanes = (CColTrianglePlane*)RwMalloc(sizeof(CColTrianglePlane) * (numTriangles+1)); + // HACK: allocate space for one more element plus 8 to stuff the link pointer into + trianglePlanes = (CColTrianglePlane*)RwMalloc(sizeof(CColTrianglePlane) * (numTriangles+1) + sizeof(void*)); REGISTER_MEMPTR(&trianglePlanes); for(int i = 0; i < numTriangles; i++) trianglePlanes[i].Set(vertices, triangles[i]); diff --git a/src/collision/ColTriangle.cpp b/src/collision/ColTriangle.cpp index 9120fcff..c025924a 100644 --- a/src/collision/ColTriangle.cpp +++ b/src/collision/ColTriangle.cpp @@ -10,7 +10,7 @@ CColTriangle::Set(const CompressedVector *, int a, int b, int c, uint8 surf, uin this->surface = surf; } -#ifdef VU_COLLISION +#if defined(VU_COLLISION) || defined(RW_DC) void CColTrianglePlane::Set(const CVector &va, const CVector &vb, const CVector &vc) { @@ -21,6 +21,15 @@ CColTrianglePlane::Set(const CVector &va, const CVector &vb, const CVector &vc) normal.y = norm.y*4096.0f; normal.z = norm.z*4096.0f; dist = d*128.0f; + + CVector an(Abs(normal.x), Abs(normal.y), Abs(normal.z)); + // find out largest component and its direction + if(an.x > an.y && an.x > an.z) + dir = normal.x < 0.0f ? DIR_X_NEG : DIR_X_POS; + else if(an.y > an.z) + dir = normal.y < 0.0f ? DIR_Y_NEG : DIR_Y_POS; + else + dir = normal.z < 0.0f ? DIR_Z_NEG : DIR_Z_POS; } #else void diff --git a/src/collision/ColTriangle.h b/src/collision/ColTriangle.h index 9e918e38..03299463 100644 --- a/src/collision/ColTriangle.h +++ b/src/collision/ColTriangle.h @@ -23,9 +23,10 @@ struct CColTriangle struct CColTrianglePlane { -#ifdef VU_COLLISION +#if defined(VU_COLLISION) || defined(RW_DC) CompressedVector normal; int16 dist; + uint8 dir; void Set(const CVector &va, const CVector &vb, const CVector &vc); void Set(const CompressedVector *v, CColTriangle &tri) { Set(v[tri.a].Get(), v[tri.b].Get(), v[tri.c].Get()); } diff --git a/src/collision/CompressedVector.h b/src/collision/CompressedVector.h index d54e49b1..caf07664 100644 --- a/src/collision/CompressedVector.h +++ b/src/collision/CompressedVector.h @@ -1,11 +1,11 @@ #pragma once - +#include struct CompressedVector { #ifdef COMPRESSED_COL_VECTORS int16 x, y, z; CVector Get(void) const { return CVector(x, y, z)/128.0f; }; - void Set(float x, float y, float z) { this->x = x*128.0f; this->y = y*128.0f; this->z = z*128.0f; }; + void Set(float x, float y, float z) { this->x = lroundf(x*128.0f); this->y = lroundf(y*128.0f); this->z = lroundf(z*128.0f); }; #ifdef GTA_PS2 void Unpack(uint128 &qword) const { __asm__ volatile ( diff --git a/src/control/CarCtrl.cpp b/src/control/CarCtrl.cpp index 2085681a..02367c4d 100644 --- a/src/control/CarCtrl.cpp +++ b/src/control/CarCtrl.cpp @@ -67,9 +67,9 @@ #define MIN_ANGLE_TO_APPLY_HANDBRAKE 0.7f #define MIN_SPEED_TO_APPLY_HANDBRAKE 0.3f -int CCarCtrl::NumLawEnforcerCars; -int CCarCtrl::NumAmbulancesOnDuty; -int CCarCtrl::NumFiretrucksOnDuty; +int32 CCarCtrl::NumLawEnforcerCars; +int32 CCarCtrl::NumAmbulancesOnDuty; +int32 CCarCtrl::NumFiretrucksOnDuty; bool CCarCtrl::bCarsGeneratedAroundCamera; float CCarCtrl::CarDensityMultiplier = 1.0f; int32 CCarCtrl::NumMissionCars; @@ -2710,7 +2710,7 @@ bool CCarCtrl::GenerateOneEmergencyServicesCar(uint32 mi, CVector vecPos) bool created = false; int attempts = 0; CVector spawnPos; - int curNode, nextNode; + int32 curNode, nextNode; float posBetweenNodes; while (!created && attempts < 5){ if (ThePaths.NewGenerateCarCreationCoors(pPlayerPos.x, pPlayerPos.y, 0.707f, 0.707f, diff --git a/src/control/PathFind.cpp b/src/control/PathFind.cpp index c6407820..25ec776f 100644 --- a/src/control/PathFind.cpp +++ b/src/control/PathFind.cpp @@ -328,7 +328,7 @@ CPathFind::StoreNodeInfoCar(int16 id, int16 node, int8 type, int8 next, int16 x, } void -CPathFind::CalcNodeCoors(int16 x, int16 y, int16 z, int id, CVector *out) +CPathFind::CalcNodeCoors(int16 x, int16 y, int16 z, int32 id, CVector *out) { CVector pos; pos.x = x / 16.0f; @@ -524,7 +524,7 @@ int32 TempListLength; void CPathFind::PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo, - float maxdist, CTempDetachedNode *detachednodes, int numDetached) + float maxdist, CTempDetachedNode *detachednodes, int32 numDetached) { static CVector CoorsXFormed; int i, j, k, l; diff --git a/src/control/Pickups.h b/src/control/Pickups.h index 4e1c7643..40185c47 100644 --- a/src/control/Pickups.h +++ b/src/control/Pickups.h @@ -124,7 +124,7 @@ class CPacManPickups static CPacManPickup aPMPickUps[NUMPACMANPICKUPS]; static CVector LastPickUpCoors; - static int PillsEatenInRace; + static int32 PillsEatenInRace; static bool bPMActive; public: static void Init(void); diff --git a/src/control/Script.cpp b/src/control/Script.cpp index f1c8b348..05d76be2 100644 --- a/src/control/Script.cpp +++ b/src/control/Script.cpp @@ -810,7 +810,7 @@ int8 CRunningScript::ProcessOneCommand() int8 CRunningScript::ProcessCommands0To99(int32 command) { float *fScriptVar1; - int *nScriptVar1; + int32 *nScriptVar1; switch (command) { case COMMAND_NOP: return 0; @@ -1515,7 +1515,7 @@ int8 CRunningScript::ProcessCommands0To99(int32 command) int8 CRunningScript::ProcessCommands100To199(int32 command) { float *fScriptVar1; - int *nScriptVar1; + int32 *nScriptVar1; switch (command) { case COMMAND_SUB_INT_LVAR_FROM_INT_VAR: nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); diff --git a/src/control/Script6.cpp b/src/control/Script6.cpp index c576e3c2..be93e2dd 100644 --- a/src/control/Script6.cpp +++ b/src/control/Script6.cpp @@ -637,7 +637,7 @@ int8 CRunningScript::ProcessCommands1000To1099(int32 command) } else { CGame::playingIntro = false; DMAudio.ChangeMusicMode(MUSICMODE_GAME); - int mi; + int32 mi; CModelInfo::GetModelInfo("bridgefukb", &mi); CStreaming::RequestModel(mi, STREAMFLAGS_DEPENDENCY); CStreaming::LoadAllRequestedModels(false); diff --git a/src/core/Cam.cpp b/src/core/Cam.cpp index d4be8585..ff35b984 100644 --- a/src/core/Cam.cpp +++ b/src/core/Cam.cpp @@ -30,7 +30,7 @@ bool PrintDebugCode = false; int16 DebugCamMode; #ifdef FREE_CAM -bool CCamera::bFreeCam = false; +bool CCamera::bFreeCam = true; int nPreviousMode = -1; #endif @@ -5179,14 +5179,14 @@ CCam::Process_FollowCar_SA(const CVector& CameraTarget, float TargetOrientation, if (Abs(AlphaSpeed) < 0.0001f) AlphaSpeed = 0.0f; - float alphaWithSpeedAccounted; - if (mouseChangesBeta) { - alphaWithSpeedAccounted = alphaSpeedFromStickY + targetAlpha; - Alpha += alphaSpeedFromStickY; - } else { - alphaWithSpeedAccounted = CTimer::GetTimeStep() * AlphaSpeed + targetAlpha; - Alpha += targetAlphaBlendAmount; - } + float alphaWithSpeedAccounted; + if (mouseChangesBeta) { + alphaWithSpeedAccounted = alphaSpeedFromStickY + targetAlpha; + Alpha += alphaSpeedFromStickY; + } else { + alphaWithSpeedAccounted = CTimer::GetTimeStep() * AlphaSpeed + targetAlpha; + Alpha += targetAlphaBlendAmount; + } if (Alpha <= maxAlphaAllowed) { float minAlphaAllowed = -CARCAM_SET[camSetArrPos][14]; diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index f3b41655..bad96834 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -2649,7 +2649,7 @@ CCamera::IsItTimeForNewcam(int32 obbeMode, int32 time) } bool -CCamera::TryToStartNewCamMode(int obbeMode) +CCamera::TryToStartNewCamMode(int32 obbeMode) { CVehicle *veh; CVector target, camPos, playerSpeed, fwd; @@ -3680,8 +3680,9 @@ CCamera::IsSphereVisible(const CVector ¢er, float radius) return IsSphereVisible(center, radius, &m_cameraMatrix); #else // ...and on PC they decided to call the other one with a default matrix. - CMatrix mat(GetCameraMatrix()); // this matrix construction is stupid and gone in VC - return IsSphereVisible(center, radius, &mat); + // CMatrix mat(GetCameraMatrix()); // this matrix construction is stupid and gone in VC + // I guess it is also gone in dca3 + return IsSphereVisible(center, radius, &GetCameraMatrix()); #endif } diff --git a/src/core/CdStream.h b/src/core/CdStream.h index 516cef48..420a3c8d 100644 --- a/src/core/CdStream.h +++ b/src/core/CdStream.h @@ -43,6 +43,9 @@ char *CdStreamGetImageName(int32 cd); void CdStreamRemoveImages(void); int32 CdStreamGetNumImages(void); +void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek); +void CdStreamDiscardAudioRead(int fd); + #ifdef FLUSHABLE_STREAMING extern bool flushStream[MAX_CDCHANNELS]; #endif diff --git a/src/core/CdStreamDC.cpp b/src/core/CdStreamDC.cpp new file mode 100644 index 00000000..04c02327 --- /dev/null +++ b/src/core/CdStreamDC.cpp @@ -0,0 +1,737 @@ +#if defined RW_DC +#include "common.h" +#include "crossplatform.h" +#include +#include +#if defined(DC_SIM) +#include +#include +#endif +#include +#include +#include +// #include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#include "CdStream.h" +#include "rwcore.h" +#include "MemoryMgr.h" +struct AudioReadCmd { + void* dest; + int fd; + size_t size; + size_t seek; +}; + +#define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) +#define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) + +#ifdef FLUSHABLE_STREAMING +bool flushStream[MAX_CDCHANNELS]; +#endif + +#if defined(DC_SIM) +#ifdef USE_UNNAMED_SEM + +#define RE3_SEM_OPEN(name, ...) re3_sem_open() +sem_t* +re3_sem_open(void) +{ + sem_t* sem = (sem_t*)malloc(sizeof(sem_t)); + if (sem_init(sem, 0, 1) == -1) { + sem = SEM_FAILED; + } + + return sem; +} + +#define RE3_SEM_CLOSE(sem, format, ...) re3_sem_close(sem) +void +re3_sem_close(sem_t* sem) +{ + sem_destroy(sem); + free(sem); +} + +#else +#define RE3_SEM_OPEN re3_sem_open +sem_t* +re3_sem_open(const char* format, ...) +{ + char semName[21]; + va_list va; + va_start(va, format); + vsprintf(semName, format, va); + + return sem_open(semName, O_CREAT, 0644, 1); +} + +#define RE3_SEM_CLOSE re3_sem_close +void +re3_sem_close(sem_t* sem, const char* format, ...) +{ + sem_close(sem); + + char semName[21]; + va_list va; + va_start(va, format); + vsprintf(semName, format, va); + + sem_unlink(semName); +} + +#endif +#else +#define sem_t semaphore_t +#define SEM_FAILED ((semaphore_t*)-1) + + +int sem_post(sem_t *sem) { + return sem_signal(sem); +} + +#define RE3_SEM_OPEN re3_sem_open +sem_t* +re3_sem_open(const char* format, ...) +{ + sem_t* rv = (sem_t*)malloc(sizeof(sem_t)); + assert(sem_init(rv, 1) == 0); + return rv; +} + +#define RE3_SEM_CLOSE re3_sem_close +void +re3_sem_close(sem_t* sem, const char* format, ...) +{ + assert(sem_destroy(sem) == 0); +} +#endif + +struct CdReadInfo +{ + uint32 nSectorOffset; + uint32 nSectorsToRead; + void *pBuffer; + bool bLocked; + bool bReading; + int32 nStatus; +#ifdef ONE_THREAD_PER_CHANNEL + int8 nThreadStatus; // 0: created 1:priority set up 2:abort now + pthread_t pChannelThread; + sem_t *pStartSemaphore; +#endif + sem_t *pDoneSemaphore; // used for CdStreamSync + int32 hFile; +}; + +char gCdImageNames[MAX_CDIMAGES+1][64]; +int32 gNumImages; +int32 gNumChannels; + +int32 gImgFiles[MAX_CDIMAGES]; // -1: error 0:unused otherwise: fd +char *gImgNames[MAX_CDIMAGES]; + +#ifndef ONE_THREAD_PER_CHANNEL +pthread_t _gCdStreamThread; +sem_t *gCdStreamSema; // released when we have new thing to read(so channel is set) +int8 gCdStreamThreadStatus; // 0: created 1:priority set up 2:abort now +Queue gChannelRequestQ; +bool _gbCdStreamOverlapped; +#endif + +CdReadInfo *gpReadInfo; + +int32 lastPosnRead; + +int _gdwCdStreamFlags; + +void *CdStreamThread(void* channelId); + +void +CdStreamInitThread(void) +{ + int status; +#ifndef ONE_THREAD_PER_CHANNEL + gChannelRequestQ.items = (int32 *)calloc(gNumChannels + 1, sizeof(int32)); + gChannelRequestQ.head = 0; + gChannelRequestQ.tail = 0; + gChannelRequestQ.size = gNumChannels + 1; + ASSERT(gChannelRequestQ.items != nil ); + gCdStreamSema = RE3_SEM_OPEN("/semaphore_cd_stream"); + + + if (gCdStreamSema == SEM_FAILED) { + CDTRACE("failed to create stream semaphore"); + ASSERT(0); + return; + } +#endif + + if ( gNumChannels > 0 ) + { + for ( int32 i = 0; i < gNumChannels; i++ ) + { + gpReadInfo[i].pDoneSemaphore = RE3_SEM_OPEN("/semaphore_done%d", i); + + if (gpReadInfo[i].pDoneSemaphore == SEM_FAILED) + { + CDTRACE("failed to create sync semaphore"); + ASSERT(0); + return; + } + +#ifdef ONE_THREAD_PER_CHANNEL + gpReadInfo[i].pStartSemaphore = RE3_SEM_OPEN("/semaphore_start%d", i); + + if (gpReadInfo[i].pStartSemaphore == SEM_FAILED) + { + CDTRACE("failed to create start semaphore"); + ASSERT(0); + return; + } + gpReadInfo[i].nThreadStatus = 0; + int *channelI = (int*)malloc(sizeof(int)); + *channelI = i; + status = pthread_create(&gpReadInfo[i].pChannelThread, NULL, CdStreamThread, (void*)channelI); + + if (status == -1) + { + CDTRACE("failed to create sync thread"); + ASSERT(0); + return; + } +#endif + } + } + +#ifndef ONE_THREAD_PER_CHANNEL + debug("Using one streaming thread for all channels\n"); + gCdStreamThreadStatus = 0; + status = pthread_create(&_gCdStreamThread, NULL, CdStreamThread, nil); + + if (status == -1) + { + CDTRACE("failed to create sync thread"); + ASSERT(0); + return; + } +#else + debug("Using separate streaming threads for each channel\n"); +#endif +} + +void +CdStreamInit(int32 numChannels) +{ + // struct statvfs fsInfo; + + // if((statvfs("models/gta3.img", &fsInfo)) < 0) + // { + // CDTRACE("can't get filesystem info"); + // ASSERT(0); + // return; + // } + // RwUInt32 block_size = 2048; +#ifdef __linux__ + _gdwCdStreamFlags = O_RDONLY | O_NOATIME; +#else + _gdwCdStreamFlags = O_RDONLY; +#endif + // People say it's slower +/* + if ( fsInfo.f_bsize <= CDSTREAM_SECTOR_SIZE ) + { + _gdwCdStreamFlags |= O_DIRECT; + debug("Using no buffered loading for streaming\n"); + } +*/ + // void *pBuffer = (void *)RwMallocAlign(CDSTREAM_SECTOR_SIZE, (RwUInt32)block_size); + // ASSERT( pBuffer != nil ); + + gNumImages = 0; + + gNumChannels = numChannels; + ASSERT( gNumChannels != 0 ); + + gpReadInfo = (CdReadInfo *)calloc(numChannels, sizeof(CdReadInfo)); + ASSERT( gpReadInfo != nil ); + + CDDEBUG("read info %p", gpReadInfo); + + CdStreamInitThread(); + + // ASSERT( pBuffer != nil ); + // RwFreeAlign(pBuffer); +} + +uint32 +GetGTA3ImgSize(void) +{ + ASSERT( gImgFiles[0] > 0 ); + struct stat statbuf; + + char path[PATH_MAX]; + realpath(gImgNames[0], path); + if (stat(path, &statbuf) == -1) { + // Try case-insensitivity + char* real = casepath(gImgNames[0], false); + if (real) + { + realpath(real, path); + free(real); + if (stat(path, &statbuf) != -1) + goto ok; + } + + CDTRACE("can't get size of gta3.img"); + ASSERT(0); + return 0; + } + ok: + return (uint32)statbuf.st_size; +} + +void +CdStreamShutdown(void) +{ + // Destroying semaphores and free(gpReadInfo) will be done at threads +#ifndef ONE_THREAD_PER_CHANNEL + gCdStreamThreadStatus = 2; + sem_post(gCdStreamSema); + pthread_join(_gCdStreamThread, nil); +#else + for ( int32 i = 0; i < gNumChannels; i++ ) { + gpReadInfo[i].nThreadStatus = 2; + sem_post(gpReadInfo[i].pStartSemaphore); + pthread_join(gpReadInfo[i].pChannelThread, nil); + } +#endif +} + + +int32 +CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size) +{ + ASSERT( channel < gNumChannels ); + ASSERT( buffer != nil ); + + lastPosnRead = size + offset; + + ASSERT( _GET_INDEX(offset) < MAX_CDIMAGES ); + int32 hImage = gImgFiles[_GET_INDEX(offset)]; + ASSERT( hImage > 0 ); + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + if ( pChannel->nSectorsToRead != 0 || pChannel->bReading ) { + if (pChannel->hFile == hImage - 1 && pChannel->nSectorOffset == _GET_OFFSET(offset) && pChannel->nSectorsToRead >= size) + return STREAM_SUCCESS; +#ifdef FLUSHABLE_STREAMING + flushStream[channel] = 1; + CdStreamSync(channel); +#else + return STREAM_NONE; +#endif + } + + pChannel->hFile = hImage - 1; + pChannel->nStatus = STREAM_NONE; + pChannel->nSectorOffset = _GET_OFFSET(offset); + pChannel->nSectorsToRead = size; + pChannel->pBuffer = buffer; + pChannel->bLocked = 0; + +#ifndef ONE_THREAD_PER_CHANNEL + AddToQueue(&gChannelRequestQ, channel); + if ( sem_post(gCdStreamSema) != 0 ) + printf("Signal Sema Error\n"); +#else + if ( sem_post(pChannel->pStartSemaphore) != 0 ) + printf("Signal Sema Error\n"); +#endif + + return STREAM_SUCCESS; +} + +int32 +CdStreamGetStatus(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + +#ifdef ONE_THREAD_PER_CHANNEL + if (pChannel->nThreadStatus == 2) + return STREAM_NONE; +#else + if (gCdStreamThreadStatus == 2) + return STREAM_NONE; +#endif + + if ( pChannel->bReading ) + return STREAM_READING; + + if ( pChannel->nSectorsToRead != 0 ) + return STREAM_WAITING; + + if ( pChannel->nStatus != STREAM_NONE ) + { + int32 status = pChannel->nStatus; + pChannel->nStatus = STREAM_NONE; + + return status; + } + + return STREAM_NONE; +} + +int32 +CdStreamGetLastPosn(void) +{ + return lastPosnRead; +} + +// wait for channel to finish reading +int32 +CdStreamSync(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + +#ifdef FLUSHABLE_STREAMING + if (flushStream[channel]) { + pChannel->nSectorsToRead = 0; +#ifdef ONE_THREAD_PER_CHANNEL + // This cancels the operation in the middle of read(), if any, does nothing otherwise + pthread_kill(pChannel->pChannelThread, SIGUSR1); + if (pChannel->bReading) { + pChannel->bLocked = true; +#else + if (pChannel->bReading) { + pChannel->bLocked = true; + // This cancels the operation in the middle of read(), if any, does nothing otherwise + pthread_kill(_gCdStreamThread, SIGUSR1); +#endif + while (pChannel->bLocked) + sem_wait(pChannel->pDoneSemaphore); + } + pChannel->bReading = false; + flushStream[channel] = false; + return STREAM_NONE; + } +#endif + + if ( pChannel->nSectorsToRead != 0 ) + { + pChannel->bLocked = true; + while (pChannel->bLocked && pChannel->nSectorsToRead != 0){ + sem_wait(pChannel->pDoneSemaphore); + } + pChannel->bLocked = false; + } + + pChannel->bReading = false; + + return pChannel->nStatus; +} + +void +AddToQueue(Queue *queue, int32 item) +{ + ASSERT( queue != nil ); + ASSERT( queue->items != nil ); + queue->items[queue->tail] = item; + + queue->tail = (queue->tail + 1) % queue->size; + + if ( queue->head == queue->tail ) + debug("Queue is full\n"); +} + +int32 +GetFirstInQueue(Queue *queue) +{ + ASSERT( queue != nil ); + if ( queue->head == queue->tail ) + return -1; + + ASSERT( queue->items != nil ); + return queue->items[queue->head]; +} + +void +RemoveFirstInQueue(Queue *queue) +{ + ASSERT( queue != nil ); + if ( queue->head == queue->tail ) + { + debug("Queue is empty\n"); + return; + } + + queue->head = (queue->head + 1) % queue->size; +} + +std::vector pendingAudioReads; +#if !defined(DC_SH4) +std::mutex pendingAudioReadsMutex; +#endif +// Will replace a previous read request for the same file descriptor +void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek) { + AudioReadCmd cmd = { pBuffer, fd, bytes, seek}; + { + #if !defined(DC_SH4) + std::lock_guard lock(pendingAudioReadsMutex); + #else + auto mask = irq_disable(); + #endif + for (auto it = pendingAudioReads.rbegin(); it != pendingAudioReads.rend(); ++it) { + if (it->fd == -1 || it->fd == fd) { + *it = cmd; + goto out; + } + } + pendingAudioReads.push_back(cmd); + out: + #if !defined(DC_SH4) + ; + #else + irq_restore(mask); + #endif + } + sem_post(gCdStreamSema); +} + +void CdStreamDiscardAudioRead(int fd) { + #if !defined(DC_SH4) + std::lock_guard lock(pendingAudioReadsMutex); + #else + auto mask = irq_disable(); + #endif + + for (auto &pending: pendingAudioReads) { + if (pending.fd == fd) { + pending.fd = -1; + } + } + #if defined(DC_SH4) + irq_restore(mask); + #endif +} + +AudioReadCmd CdStreamNextAudioRead() { + #if !defined(DC_SH4) + std::lock_guard lock(pendingAudioReadsMutex); + #else + auto mask = irq_disable(); + #endif + AudioReadCmd cmd = { nil, -1, 0, 0}; + for (auto &pending: pendingAudioReads) { + if (pending.fd != -1) { + cmd = pending; + pending.fd = -1; + break; + } + } + #if defined(DC_SH4) + irq_restore(mask); + #endif + return cmd; +} + +int read_loop(int fd, void* pBuffer, size_t bytes) { + size_t total_read = 0; + while (total_read < bytes) { + size_t to_read = bytes - total_read; + if (to_read > 64 * 1024) + to_read = 64 * 1024; + ssize_t read_bytes = read(fd, (char*)pBuffer + total_read, to_read); + if (read_bytes == -1) { + return -1; + } + total_read += read_bytes; + auto cmd = CdStreamNextAudioRead(); + while (cmd.fd != -1) { + lseek(cmd.fd, cmd.seek, SEEK_SET); + read(cmd.fd, cmd.dest, cmd.size); + cmd = CdStreamNextAudioRead(); + } + } + return total_read; +} +void *CdStreamThread(void *param) +{ + debug("Created cdstream thread\n"); + +#ifndef ONE_THREAD_PER_CHANNEL + while (gCdStreamThreadStatus != 2) { + sem_wait(gCdStreamSema); + + auto cmd = CdStreamNextAudioRead(); + while (cmd.fd != -1) { + lseek(cmd.fd, cmd.seek, SEEK_SET); + read(cmd.fd, cmd.dest, cmd.size); + cmd = CdStreamNextAudioRead(); + } + + int32 channel = GetFirstInQueue(&gChannelRequestQ); + + // spurious wakeup + if (channel == -1) + continue; +#else + int channel = *((int*)param); + while (gpReadInfo[channel].nThreadStatus != 2){ + sem_wait(gpReadInfo[channel].pStartSemaphore); +#endif + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + // spurious wakeup or we sent interrupt signal for flushing + if(pChannel->nSectorsToRead == 0) + continue; + + pChannel->bReading = true; + + // Not standard POSIX :shrug: +#ifdef __linux__ +#ifdef ONE_THREAD_PER_CHANNEL + if (gpReadInfo[channel].nThreadStatus == 0){ + gpReadInfo[channel].nThreadStatus = 1; +#else + if (gCdStreamThreadStatus == 0){ + gCdStreamThreadStatus = 1; +#endif + pid_t tid = syscall(SYS_gettid); + int ret = setpriority(PRIO_PROCESS, tid, getpriority(PRIO_PROCESS, getpid()) + 1); + } +#endif + if ( pChannel->nStatus == STREAM_NONE ) + { + ASSERT(pChannel->hFile >= 0); + ASSERT(pChannel->pBuffer != nil ); + + lseek(pChannel->hFile, (size_t)pChannel->nSectorOffset * (size_t)CDSTREAM_SECTOR_SIZE, SEEK_SET); + if (read_loop(pChannel->hFile, pChannel->pBuffer, pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE) == -1) { + // pChannel->nSectorsToRead == 0 at this point means we wanted to flush channel + // STREAM_WAITING is a little hack to make CStreaming not process this data + pChannel->nStatus = pChannel->nSectorsToRead == 0 ? STREAM_WAITING : STREAM_ERROR; + } else { + pChannel->nStatus = STREAM_NONE; + } + } + +#ifndef ONE_THREAD_PER_CHANNEL + RemoveFirstInQueue(&gChannelRequestQ); +#endif + + pChannel->nSectorsToRead = 0; + if ( pChannel->bLocked ) + { + pChannel->bLocked = 0; + sem_post(pChannel->pDoneSemaphore); + } + pChannel->bReading = false; + } + char semName[20]; +#ifndef ONE_THREAD_PER_CHANNEL + for ( int32 i = 0; i < gNumChannels; i++ ) + { + RE3_SEM_CLOSE(gpReadInfo[i].pDoneSemaphore, "/semaphore_done%d", i); + } + RE3_SEM_CLOSE(gCdStreamSema, "/semaphore_cd_stream"); + free(gChannelRequestQ.items); +#else + RE3_SEM_CLOSE(gpReadInfo[channel].pStartSemaphore, "/semaphore_start%d", channel); + + RE3_SEM_CLOSE(gpReadInfo[channel].pDoneSemaphore, "/semaphore_done%d", channel); +#endif + if (gpReadInfo) + free(gpReadInfo); + gpReadInfo = nil; + pthread_exit(nil); + return NULL; +} + +bool +CdStreamAddImage(char const *path) +{ + ASSERT(path != nil); + ASSERT(gNumImages < MAX_CDIMAGES); + + gImgFiles[gNumImages] = open(path, _gdwCdStreamFlags); + + // Fix case sensitivity and backslashes. + if (gImgFiles[gNumImages] == -1) { + char* real = casepath(path, false); + if (real) + { + gImgFiles[gNumImages] = open(real, _gdwCdStreamFlags); + free(real); + } + } + + if ( gImgFiles[gNumImages] == -1 ) { + assert(false); + return false; + } + + gImgNames[gNumImages] = strdup(path); + gImgFiles[gNumImages]++; // because -1: error 0: not used + + strcpy(gCdImageNames[gNumImages], path); + + gNumImages++; + + return true; +} + +char * +CdStreamGetImageName(int32 cd) +{ + ASSERT(cd < MAX_CDIMAGES); + if ( gImgFiles[cd] > 0) + return gCdImageNames[cd]; + + return nil; +} + +void +CdStreamRemoveImages(void) +{ + for ( int32 i = 0; i < gNumChannels; i++ ) { +#ifdef FLUSHABLE_STREAMING + flushStream[i] = 1; +#endif + CdStreamSync(i); + } + + for ( int32 i = 0; i < gNumImages; i++ ) + { + close(gImgFiles[i] - 1); + free(gImgNames[i]); + gImgFiles[i] = 0; + } + + gNumImages = 0; +} + +int32 +CdStreamGetNumImages(void) +{ + return gNumImages; +} +#endif diff --git a/src/core/ControllerConfig.cpp b/src/core/ControllerConfig.cpp index 8775792f..dcaaa8f2 100644 --- a/src/core/ControllerConfig.cpp +++ b/src/core/ControllerConfig.cpp @@ -199,36 +199,36 @@ void CControllerConfigManager::LoadSettings(int32 file) void CControllerConfigManager::InitDefaultControlConfiguration() { - SetControllerKeyAssociatedWithAction (VEHICLE_LOOKLEFT, rsPADEND, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKLEFT, ' ', KEYBOARD); SetControllerKeyAssociatedWithAction (VEHICLE_LOOKLEFT, 'Q', OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (VEHICLE_LOOKRIGHT, rsPADDOWN, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKRIGHT, ' ', KEYBOARD); SetControllerKeyAssociatedWithAction (VEHICLE_LOOKRIGHT, 'E', OPTIONAL_EXTRA); if ( _dwOperatingSystemVersion == OS_WIN98 ) - SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsPADLEFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? else { - SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsLSHIFT, OPTIONAL_EXTRA); + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsPADLEFT, OPTIONAL_EXTRA); SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsRSHIFT, KEYBOARD); } - SetControllerKeyAssociatedWithAction (VEHICLE_HANDBRAKE, rsRCTRL, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_HANDBRAKE, rsMINUS, KEYBOARD); SetControllerKeyAssociatedWithAction (VEHICLE_HANDBRAKE, ' ', OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (VEHICLE_ENTER_EXIT, rsENTER, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_ENTER_EXIT, rsPADDEL, KEYBOARD); SetControllerKeyAssociatedWithAction (VEHICLE_ENTER_EXIT, 'F', OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (VEHICLE_ACCELERATE, rsUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_ACCELERATE, ' ', KEYBOARD); SetControllerKeyAssociatedWithAction (VEHICLE_ACCELERATE, 'W', OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (VEHICLE_CHANGE_RADIO_STATION, rsINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_CHANGE_RADIO_STATION, rsPADDOWN, KEYBOARD); SetControllerKeyAssociatedWithAction (VEHICLE_CHANGE_RADIO_STATION, 'R', OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (VEHICLE_BRAKE, rsDOWN, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_BRAKE, ' ', KEYBOARD); SetControllerKeyAssociatedWithAction (VEHICLE_BRAKE, 'S', OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (TOGGLE_SUBMISSIONS, rsPLUS, KEYBOARD); + SetControllerKeyAssociatedWithAction (TOGGLE_SUBMISSIONS, rsPADRIGHT, KEYBOARD); SetControllerKeyAssociatedWithAction (TOGGLE_SUBMISSIONS, rsCAPSLK, OPTIONAL_EXTRA); SetControllerKeyAssociatedWithAction (GO_LEFT, rsLEFT, KEYBOARD); @@ -243,33 +243,33 @@ void CControllerConfigManager::InitDefaultControlConfiguration() SetControllerKeyAssociatedWithAction (GO_BACK, rsDOWN, KEYBOARD); SetControllerKeyAssociatedWithAction (GO_BACK, 'S', OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsPADEND, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsPADDOWN, KEYBOARD); SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsCAPSLK, OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (PED_FIREWEAPON, rsPADINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_FIREWEAPON, rsPADPGUP, KEYBOARD); SetControllerKeyAssociatedWithAction (PED_FIREWEAPON, rsLCTRL, OPTIONAL_EXTRA); #ifdef BIND_VEHICLE_FIREWEAPON - SetControllerKeyAssociatedWithAction (VEHICLE_FIREWEAPON, rsPADINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_FIREWEAPON, rsENTER, KEYBOARD); SetControllerKeyAssociatedWithAction (VEHICLE_FIREWEAPON, rsLCTRL, OPTIONAL_EXTRA); #endif - SetControllerKeyAssociatedWithAction (PED_CYCLE_WEAPON_LEFT, rsPADDEL, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_CYCLE_WEAPON_LEFT, rsPADLEFT, KEYBOARD); - SetControllerKeyAssociatedWithAction (PED_CYCLE_WEAPON_RIGHT, rsPADENTER, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + SetControllerKeyAssociatedWithAction (PED_CYCLE_WEAPON_RIGHT, rsPADRIGHT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? - SetControllerKeyAssociatedWithAction (PED_LOCK_TARGET, rsDEL, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_LOCK_TARGET, rsPADHOME, KEYBOARD); - SetControllerKeyAssociatedWithAction (PED_JUMPING, rsRCTRL, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_JUMPING, rsMINUS, KEYBOARD); SetControllerKeyAssociatedWithAction (PED_JUMPING, ' ', OPTIONAL_EXTRA); if ( _dwOperatingSystemVersion == OS_WIN98 ) - SetControllerKeyAssociatedWithAction(PED_SPRINT, rsSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsENTER, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? else { - SetControllerKeyAssociatedWithAction(PED_SPRINT, rsLSHIFT, OPTIONAL_EXTRA); + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsENTER, OPTIONAL_EXTRA); #ifndef FIX_BUGS - SetControllerKeyAssociatedWithAction(PED_SPRINT, rsRSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsENTER, OPTIONAL_EXTRA); // BUG: must be KEYBOARD #else - SetControllerKeyAssociatedWithAction(PED_SPRINT, rsRSHIFT, KEYBOARD); + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsENTER, KEYBOARD); #endif } @@ -277,7 +277,7 @@ void CControllerConfigManager::InitDefaultControlConfiguration() SetControllerKeyAssociatedWithAction (PED_CYCLE_TARGET_RIGHT, ']', OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? - SetControllerKeyAssociatedWithAction (PED_CENTER_CAMERA_BEHIND_PLAYER, '#', KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_CENTER_CAMERA_BEHIND_PLAYER, ' ', KEYBOARD); SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_IN, rsPGUP, KEYBOARD); SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_IN, 'Z', OPTIONAL_EXTRA); @@ -285,23 +285,23 @@ void CControllerConfigManager::InitDefaultControlConfiguration() SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_OUT, rsPGDN, KEYBOARD); SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_OUT, 'X', OPTIONAL_EXTRA); - SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_LEFT, rsPADLEFT, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_LEFT, ' ', KEYBOARD); - SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_RIGHT, rsPADRIGHT, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_RIGHT, ' ', KEYBOARD); - SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_UP, rsPADUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_UP, ' ', KEYBOARD); - SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_DOWN, rsPAD5, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_DOWN, ' ', KEYBOARD); - SetControllerKeyAssociatedWithAction (VEHICLE_TURRETLEFT, rsPADLEFT, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETLEFT, ' ', KEYBOARD); - SetControllerKeyAssociatedWithAction (VEHICLE_TURRETRIGHT, rsPAD5, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETRIGHT, ' ', KEYBOARD); - SetControllerKeyAssociatedWithAction (VEHICLE_TURRETUP, rsPADPGUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETUP, ' ', KEYBOARD); - SetControllerKeyAssociatedWithAction (VEHICLE_TURRETDOWN, rsPADRIGHT, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETDOWN, ' ', KEYBOARD); - SetControllerKeyAssociatedWithAction (CAMERA_CHANGE_VIEW_ALL_SITUATIONS, rsHOME, KEYBOARD); + SetControllerKeyAssociatedWithAction (CAMERA_CHANGE_VIEW_ALL_SITUATIONS, rsPADUP, KEYBOARD); SetControllerKeyAssociatedWithAction (CAMERA_CHANGE_VIEW_ALL_SITUATIONS, 'C', OPTIONAL_EXTRA); for (int32 i = 0; i < MAX_SIMS; i++) @@ -2026,349 +2026,664 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr return NULL; } -wchar *CControllerConfigManager::GetControllerSettingTextKeyBoard(e_ControllerAction action, eControllerType type) -{ - static wchar ActionText[50]; - static wchar NewStringWithNumber[30]; - for (int32 i = 0; i < ARRAY_SIZE(ActionText); i++) - ActionText[i] = '\0'; - - if (GetControllerKeyAssociatedWithAction(action, type) != rsNULL) +#ifdef RW_DC + wchar *CControllerConfigManager::GetControllerSettingTextKeyBoard(e_ControllerAction action, eControllerType type) { - if ( GetControllerKeyAssociatedWithAction(action, type) >= 0 - && GetControllerKeyAssociatedWithAction(action, type) <= 255) + // The Hints Messages use GetControllerSettingTextKeyBoard to get the Action Key associated for each Action + // As we manualy set them for CONTROL_CLASSIC and CONTROL_STANDARD we don't need to take into account a reassignment by the player + // [!] Please note that for the moment it's the same Layout for both CONTROL_CLASSIC and CONTROL_STANDARD + // CONTROL_STANDARD : more like GTA 3 on Original Xbox + // CONTROL_CLASSIC : more like GTA: Liberty City Stories on PlayStation Portable + + //static wchar ActionText[50]; + static wchar ActionText[24]; + for (int32 i = 0; i < ARRAY_SIZE(ActionText); i++) + ActionText[i] = '\0'; + + const int iLimitCopy = ARRAY_SIZE(ActionText)-1; + + static const char* Dreamcast_A = "A"; + static const char* Dreamcast_B = "B"; + static const char* Dreamcast_X = "X"; + static const char* Dreamcast_Y = "Y"; + static const char* Dreamcast_LeftTrigger = "Left Trigger"; + static const char* Dreamcast_RightTrigger = "Right Trigger"; + static const char* Dreamcast_Start = "Start"; + static const char* Dreamcast_DPad_Up = "D-Pad Up"; + static const char* Dreamcast_DPad_Down = "D-Pad Down"; + static const char* Dreamcast_DPad_Left = "D-Pad Left"; + static const char* Dreamcast_DPad_Right = "D-Pad Right"; + static const char* Dreamcast_LefAnalog_Up = "Joystick Up"; + static const char* Dreamcast_LefAnalog_Down = "Joystick Down"; + static const char* Dreamcast_LefAnalog_Left = "Joystick Left"; + static const char* Dreamcast_LefAnalog_Right = "Joystick Right"; + static const char* Dreamcast_NotAssigned = "Not Assigned"; + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) // CONTROL_CLASSIC { - char c = GetControllerKeyAssociatedWithAction(action, type); - if (c == ' ') - return TheText.Get("FEC_SPC"); // "SPC" + switch (action) + { + case VEHICLE_LOOKLEFT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_LOOKRIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_HORN: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_HANDBRAKE: + for (int i = 0; (ActionText[i] = Dreamcast_B[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_ENTER_EXIT: + for (int i = 0; (ActionText[i] = Dreamcast_Y[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_ACCELERATE: + for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_CHANGE_RADIO_STATION: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_BRAKE: + for (int i = 0; (ActionText[i] = Dreamcast_LeftTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case TOGGLE_SUBMISSIONS: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_FORWARD: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Up[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_BACK: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_FIREWEAPON: + for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_FIREWEAPON: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_WEAPON_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_WEAPON_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_LOCK_TARGET: + for (int i = 0; (ActionText[i] = Dreamcast_LeftTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_JUMPING: + for (int i = 0; (ActionText[i] = Dreamcast_X[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SPRINT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_TARGET_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_TARGET_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CENTER_CAMERA_BEHIND_PLAYER: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SNIPER_ZOOM_IN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SNIPER_ZOOM_OUT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_UP: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_DOWN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETLEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETRIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETUP: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETDOWN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case CAMERA_CHANGE_VIEW_ALL_SITUATIONS: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Up[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case NETWORK_TALK: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case _CONTROLLERACTION_36: // What is that??? + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case TOGGLE_DPAD: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case SWITCH_DEBUG_CAM_ON: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case TAKE_SCREEN_SHOT: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case SHOW_MOUSE_POINTER_TOGGLE: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case MAX_CONTROLLERACTIONS: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + default: + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + } + } + else // CONTROL_STANDARD + { + switch (action) + { + case VEHICLE_LOOKLEFT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_LOOKRIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_HORN: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_HANDBRAKE: + for (int i = 0; (ActionText[i] = Dreamcast_B[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_ENTER_EXIT: + for (int i = 0; (ActionText[i] = Dreamcast_Y[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_ACCELERATE: + for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_CHANGE_RADIO_STATION: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_BRAKE: + for (int i = 0; (ActionText[i] = Dreamcast_LeftTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case TOGGLE_SUBMISSIONS: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_FORWARD: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Up[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_BACK: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_FIREWEAPON: + for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_FIREWEAPON: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_WEAPON_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_WEAPON_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_LOCK_TARGET: + for (int i = 0; (ActionText[i] = Dreamcast_LeftTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_JUMPING: + for (int i = 0; (ActionText[i] = Dreamcast_X[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SPRINT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_TARGET_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_TARGET_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CENTER_CAMERA_BEHIND_PLAYER: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SNIPER_ZOOM_IN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SNIPER_ZOOM_OUT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_UP: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_DOWN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETLEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETRIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETUP: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETDOWN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case CAMERA_CHANGE_VIEW_ALL_SITUATIONS: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Up[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case NETWORK_TALK: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case _CONTROLLERACTION_36: // What is that??? + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case TOGGLE_DPAD: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case SWITCH_DEBUG_CAM_ON: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case TAKE_SCREEN_SHOT: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case SHOW_MOUSE_POINTER_TOGGLE: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case MAX_CONTROLLERACTIONS: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + default: + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + } + } + + return ActionText; + } +#else // not RW_DC + wchar *CControllerConfigManager::GetControllerSettingTextKeyBoard(e_ControllerAction action, eControllerType type) + { + static wchar ActionText[50]; + static wchar NewStringWithNumber[30]; + + for (int32 i = 0; i < ARRAY_SIZE(ActionText); i++) + ActionText[i] = '\0'; + + if (GetControllerKeyAssociatedWithAction(action, type) != rsNULL) + { + if ( GetControllerKeyAssociatedWithAction(action, type) >= 0 + && GetControllerKeyAssociatedWithAction(action, type) <= 255) + { + char c = GetControllerKeyAssociatedWithAction(action, type); + if (c == ' ') + return TheText.Get("FEC_SPC"); // "SPC" + else + { + ActionText[0] = CFont::character_code(c); + if (ActionText[0] == '\0') + ActionText[0] = CFont::character_code('#'); + ActionText[1] = '\0'; + return ActionText; + } + } else { - ActionText[0] = CFont::character_code(c); - if (ActionText[0] == '\0') - ActionText[0] = CFont::character_code('#'); - ActionText[1] = '\0'; - return ActionText; + switch (GetControllerKeyAssociatedWithAction(action, type)) + { + case rsF1: + case rsF2: + case rsF3: + case rsF4: + case rsF5: + case rsF6: + case rsF7: + case rsF8: + case rsF9: + case rsF10: + case rsF11: + case rsF12: + { + CMessages::InsertNumberInString(TheText.Get("FEC_FNC"), // "F~1~" + GetControllerKeyAssociatedWithAction(action, type) - rsESC, + -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsINS: + { + return TheText.Get("FEC_IRT"); // "INS" + break; + } + + case rsDEL: + { + return TheText.Get("FEC_DLL"); // "DEL" + break; + } + + case rsHOME: + { + return TheText.Get("FEC_HME"); // "HOME" + break; + } + + case rsEND: + { + return TheText.Get("FEC_END"); // "END" + break; + } + + case rsPGUP: + { + return TheText.Get("FEC_PGU"); // "PGUP" + break; + } + + case rsPGDN: + { + return TheText.Get("FEC_PGD"); // "PGDN" + break; + } + + case rsUP: + { + return TheText.Get("FEC_UPA"); // "UP" + break; + } + + case rsDOWN: + { + return TheText.Get("FEC_DWA"); // "DOWN" + break; + } + + case rsLEFT: + { + return TheText.Get("FEC_LFA"); // "LEFT" + break; + } + + case rsRIGHT: + { + return TheText.Get("FEC_RFA"); // "RIGHT" + break; + } + + case rsDIVIDE: + { + return TheText.Get("FEC_FWS"); // "NUM /" + break; + } + + case rsTIMES: + { + return TheText.Get("FEC_STR"); // "NUM STAR" + break; + } + + case rsPLUS: + { + return TheText.Get("FEC_PLS"); // "NUM +" + break; + } + + case rsMINUS: + { + return TheText.Get("FEC_MIN"); // "NUM -" + break; + } + + case rsPADDEL: + { + return TheText.Get("FEC_DOT"); // "NUM ." + break; + } + + case rsPADEND: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 1, -1, -1, -1, -1, -1, NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADDOWN: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 2, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADPGDN: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 3, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADLEFT: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 4, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPAD5: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 5, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsNUMLOCK: + { + return TheText.Get("FEC_NLK"); // "NUMLOCK" + break; + } + + case rsPADRIGHT: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 6, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADHOME: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 7, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADUP: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 8, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADPGUP: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 9, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADINS: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 0, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADENTER: + { + return TheText.Get("FEC_ETR"); // "ENT" + break; + } + + case rsSCROLL: + { + return TheText.Get("FEC_SLK"); // "SCROLL LOCK" + break; + } + + case rsPAUSE: + { + return TheText.Get("FEC_PSB"); // "BREAK" + break; + } + + case rsBACKSP: + { + return TheText.Get("FEC_BSP"); // "BSPACE" + break; + } + + case rsTAB: + { + return TheText.Get("FEC_TAB"); // "TAB" + break; + } + + case rsCAPSLK: + { + return TheText.Get("FEC_CLK"); // "CAPSLOCK" + break; + } + + case rsENTER: + { + return TheText.Get("FEC_RTN"); // "RET" + break; + } + + case rsLSHIFT: + { + return TheText.Get("FEC_LSF"); // "LSHIFT" + break; + } + + case rsRSHIFT: + { + return TheText.Get("FEC_RSF"); // "RSHIFT" + break; + } + + case rsLCTRL: + { + return TheText.Get("FEC_LCT"); // "LCTRL" + break; + } + + case rsRCTRL: + { + return TheText.Get("FEC_RCT"); // "RCTRL" + break; + } + + case rsLALT: + { + return TheText.Get("FEC_LAL"); // "LALT" + break; + } + + case rsRALT: + { + return TheText.Get("FEC_RAL"); // "RALT" + break; + } + + case rsLWIN: + { + return TheText.Get("FEC_LWD"); // "LWIN" + break; + } + + case rsRWIN: + { + return TheText.Get("FEC_RWD"); // "RWIN" + break; + } + + case rsAPPS: + { + return TheText.Get("FEC_WRC"); // "WINCLICK" + break; + } + + case rsSHIFT: + { + return TheText.Get("FEC_SFT"); // "SHIFT" + break; + } + default: break; + } } } - else - { - switch (GetControllerKeyAssociatedWithAction(action, type)) - { - case rsF1: - case rsF2: - case rsF3: - case rsF4: - case rsF5: - case rsF6: - case rsF7: - case rsF8: - case rsF9: - case rsF10: - case rsF11: - case rsF12: - { - CMessages::InsertNumberInString(TheText.Get("FEC_FNC"), // "F~1~" - GetControllerKeyAssociatedWithAction(action, type) - rsESC, - -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - case rsINS: - { - return TheText.Get("FEC_IRT"); // "INS" - break; - } - - case rsDEL: - { - return TheText.Get("FEC_DLL"); // "DEL" - break; - } - - case rsHOME: - { - return TheText.Get("FEC_HME"); // "HOME" - break; - } - - case rsEND: - { - return TheText.Get("FEC_END"); // "END" - break; - } - - case rsPGUP: - { - return TheText.Get("FEC_PGU"); // "PGUP" - break; - } - - case rsPGDN: - { - return TheText.Get("FEC_PGD"); // "PGDN" - break; - } - - case rsUP: - { - return TheText.Get("FEC_UPA"); // "UP" - break; - } - - case rsDOWN: - { - return TheText.Get("FEC_DWA"); // "DOWN" - break; - } - - case rsLEFT: - { - return TheText.Get("FEC_LFA"); // "LEFT" - break; - } - - case rsRIGHT: - { - return TheText.Get("FEC_RFA"); // "RIGHT" - break; - } - - case rsDIVIDE: - { - return TheText.Get("FEC_FWS"); // "NUM /" - break; - } - - case rsTIMES: - { - return TheText.Get("FEC_STR"); // "NUM STAR" - break; - } - - case rsPLUS: - { - return TheText.Get("FEC_PLS"); // "NUM +" - break; - } - - case rsMINUS: - { - return TheText.Get("FEC_MIN"); // "NUM -" - break; - } - - case rsPADDEL: - { - return TheText.Get("FEC_DOT"); // "NUM ." - break; - } - - case rsPADEND: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 1, -1, -1, -1, -1, -1, NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPADDOWN: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 2, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPADPGDN: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 3, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPADLEFT: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 4, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPAD5: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 5, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsNUMLOCK: - { - return TheText.Get("FEC_NLK"); // "NUMLOCK" - break; - } - - case rsPADRIGHT: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 6, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPADHOME: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 7, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPADUP: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 8, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPADPGUP: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 9, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPADINS: - { - CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" - 0, -1, -1, -1, -1, -1, - NewStringWithNumber); - return NewStringWithNumber; - break; - } - - case rsPADENTER: - { - return TheText.Get("FEC_ETR"); // "ENT" - break; - } - - case rsSCROLL: - { - return TheText.Get("FEC_SLK"); // "SCROLL LOCK" - break; - } - - case rsPAUSE: - { - return TheText.Get("FEC_PSB"); // "BREAK" - break; - } - - case rsBACKSP: - { - return TheText.Get("FEC_BSP"); // "BSPACE" - break; - } - - case rsTAB: - { - return TheText.Get("FEC_TAB"); // "TAB" - break; - } - - case rsCAPSLK: - { - return TheText.Get("FEC_CLK"); // "CAPSLOCK" - break; - } - - case rsENTER: - { - return TheText.Get("FEC_RTN"); // "RET" - break; - } - - case rsLSHIFT: - { - return TheText.Get("FEC_LSF"); // "LSHIFT" - break; - } - - case rsRSHIFT: - { - return TheText.Get("FEC_RSF"); // "RSHIFT" - break; - } - - case rsLCTRL: - { - return TheText.Get("FEC_LCT"); // "LCTRL" - break; - } - - case rsRCTRL: - { - return TheText.Get("FEC_RCT"); // "RCTRL" - break; - } - - case rsLALT: - { - return TheText.Get("FEC_LAL"); // "LALT" - break; - } - - case rsRALT: - { - return TheText.Get("FEC_RAL"); // "RALT" - break; - } - - case rsLWIN: - { - return TheText.Get("FEC_LWD"); // "LWIN" - break; - } - - case rsRWIN: - { - return TheText.Get("FEC_RWD"); // "RWIN" - break; - } - - case rsAPPS: - { - return TheText.Get("FEC_WRC"); // "WINCLICK" - break; - } - - case rsSHIFT: - { - return TheText.Get("FEC_SFT"); // "SHIFT" - break; - } - default: break; - } - } + return NULL; } - - return NULL; -} +#endif // endif RW_DC wchar *CControllerConfigManager::GetControllerSettingTextMouse(e_ControllerAction action) { @@ -2417,12 +2732,15 @@ wchar *CControllerConfigManager::GetControllerSettingTextJoystick(e_ControllerAc int32 CControllerConfigManager::GetNumOfSettingsForAction(e_ControllerAction action) { - int32 num = 0; - - if (m_aSettings[action][KEYBOARD].m_Key != rsNULL) num++; - if (m_aSettings[action][OPTIONAL_EXTRA].m_Key != rsNULL) num++; - if (m_aSettings[action][MOUSE].m_Key != 0) num++; - if (m_aSettings[action][JOYSTICK].m_Key != 0) num++; + #ifdef RW_DC + int32 num = 0; // No need to show alternative control in the Hint Messages for an action on Dreamcast (just like PS2 and Xbox) + #else + int32 num = 0; + if (m_aSettings[action][KEYBOARD].m_Key != rsNULL) num++; + if (m_aSettings[action][OPTIONAL_EXTRA].m_Key != rsNULL) num++; + if (m_aSettings[action][MOUSE].m_Key != 0) num++; + if (m_aSettings[action][JOYSTICK].m_Key != 0) num++; + #endif return num; } diff --git a/src/core/ControllerConfig.h b/src/core/ControllerConfig.h index 295f03b9..b201644c 100644 --- a/src/core/ControllerConfig.h +++ b/src/core/ControllerConfig.h @@ -132,6 +132,9 @@ public: #if defined RW_GL3 GlfwJoyState m_OldState; GlfwJoyState m_NewState; +#elif defined(RW_DC) + uint32 m_OldState; + uint32 m_NewState; #else DIJOYSTATE2 m_OldState; DIJOYSTATE2 m_NewState; diff --git a/src/core/Directory.cpp b/src/core/Directory.cpp index 05344065..149fa601 100644 --- a/src/core/Directory.cpp +++ b/src/core/Directory.cpp @@ -5,7 +5,7 @@ #include "Directory.h" CDirectory::CDirectory(int32 maxEntries) - : numEntries(0), maxEntries(maxEntries) + : maxEntries(maxEntries), numEntries(0) { entries = new DirectoryInfo[maxEntries]; } diff --git a/src/core/FileLoader.cpp b/src/core/FileLoader.cpp index afa2a66f..8a362c11 100644 --- a/src/core/FileLoader.cpp +++ b/src/core/FileLoader.cpp @@ -26,6 +26,8 @@ #include "FileLoader.h" #include "MemoryHeap.h" +#include + char CFileLoader::ms_line[256]; const char* @@ -294,10 +296,12 @@ CFileLoader::LoadCollisionModel(uint8 *buf, CColModel &model, char *modelname) REGISTER_MEMPTR(&model.vertices); for(i = 0; i < numVertices; i++){ model.vertices[i].Set(*(float*)buf, *(float*)(buf+4), *(float*)(buf+8)); - if(Abs(*(float*)buf) >= 256.0f || - Abs(*(float*)(buf+4)) >= 256.0f || - Abs(*(float*)(buf+8)) >= 256.0f) - printf("%s:Collision volume too big\n", modelname); + if(lroundf(Abs(*(float*)buf)) >= 256 || + lroundf(Abs(*(float*)(buf+4))) >= 256 || + lroundf(Abs(*(float*)(buf+8))) >= 256) { + dbglog(DBG_CRITICAL, "%s:Collision volume too big\n", modelname); + assert(false && "Collision volume too big"); + } buf += 12; } }else diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp index 666774fe..2045c34b 100644 --- a/src/core/Frontend.cpp +++ b/src/core/Frontend.cpp @@ -34,6 +34,7 @@ #include "Messages.h" #include "FileLoader.h" #include "frontendoption.h" +#include "IniFile.h" // Game has colors inlined in code. // For easier modification we collect them here: @@ -52,6 +53,8 @@ const CRGBA SCROLLBAR_COLOR = LABEL_COLOR; const CRGBA CONTSETUP_HIGHLIGHTBG_COLOR(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, 210); const CRGBA CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, 150); +// #define DISABLE_DRAWDIST_SETTING + // This is PS2 menu leftover, and variable name is original. They forgot it here and used in PrintBriefs once (but didn't use the output) #if defined(FIX_BUGS) && !defined(PS2_LIKE_MENU) const CRGBA TEXT_COLOR = LABEL_COLOR; @@ -100,7 +103,9 @@ int GetOptionCount(int screen) #define SETUP_SCROLLING(screen) #endif -#ifdef TRIANGLE_BACK_BUTTON +//APARENTLY THIS DEFINES WHICH BUTTON WILL BE THE BACK BUTTON + +/*#ifdef TRIANGLE_BACK_BUTTON #define GetBackJustUp GetTriangleJustUp #define GetBackJustDown GetTriangleJustDown #elif defined(CIRCLE_BACK_BUTTON) @@ -109,6 +114,11 @@ int GetOptionCount(int screen) #else #define GetBackJustUp GetSquareJustUp #define GetBackJustDown GetSquareJustDown +#endif*/ + +#ifdef RW_DC +#define GetBackJustUp GetBJustUp +#define GetBackJustDown GetBJustDown #endif #ifdef MENU_MAP @@ -906,14 +916,16 @@ CMenuManager::CheckSliderMovement(int value) m_PrefsBrightness += value * (512/MENUSLIDER_LOGICAL_BARS); m_PrefsBrightness = Clamp(m_PrefsBrightness, 0, 511); break; +#ifndef DISABLE_DRAWDIST_SETTING case MENUACTION_DRAWDIST: if(value > 0) - m_PrefsLOD += ((1.8f - 0.8f) / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD += ((1.8f - 0.4f) / MENUSLIDER_LOGICAL_BARS); else - m_PrefsLOD -= ((1.8f - 0.8f) / MENUSLIDER_LOGICAL_BARS); - m_PrefsLOD = Clamp(m_PrefsLOD, 0.8f, 1.8f); + m_PrefsLOD -= ((1.8f - 0.4f) / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD = Clamp(m_PrefsLOD, 0.4f, 1.8f); CRenderer::ms_lodDistScale = m_PrefsLOD; break; +#endif case MENUACTION_MUSICVOLUME: m_PrefsMusicVolume += value * (128/MENUSLIDER_LOGICAL_BARS); m_PrefsMusicVolume = Clamp(m_PrefsMusicVolume, 0, 127); @@ -1474,7 +1486,7 @@ CMenuManager::Draw() rightText = TheText.Get(m_PrefsFrameLimiter ? "FEM_ON" : "FEM_OFF"); break; case MENUACTION_TRAILS: - rightText = TheText.Get(CMBlur::BlurOn ? "FEM_ON" : "FEM_OFF"); + rightText = TheText.Get(CMBlur::BlurOn == 0 ? "FEM_OFF": CMBlur::BlurOn == 1 ? "FEM_V3" : "FEM_V1"); break; case MENUACTION_SUBTITLES: rightText = TheText.Get(m_PrefsShowSubtitles ? "FEM_ON" : "FEM_OFF"); @@ -1555,12 +1567,13 @@ CMenuManager::Draw() rightText = TheText.Get("FEA_NAH"); else { char *provider = DMAudio.Get3DProviderName(m_nPrefsAudio3DProviderIndex); - +#ifndef RW_DC if (!strcmp(strupr(provider), "DIRECTSOUND3D HARDWARE SUPPORT")) { strcpy(provider, "DSOUND3D HARDWARE SUPPORT"); } else if (!strcmp(strupr(provider), "DIRECTSOUND3D SOFTWARE EMULATION")) { strcpy(provider, "DSOUND3D SOFTWARE EMULATION"); } +#endif AsciiToUnicode(provider, unicodeTemp); rightText = unicodeTemp; } @@ -1806,9 +1819,11 @@ CMenuManager::Draw() case MENUACTION_BRIGHTNESS: ProcessSlider(m_PrefsBrightness / 512.0f, HOVEROPTION_INCREASE_BRIGHTNESS, HOVEROPTION_DECREASE_BRIGHTNESS, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); break; +#ifndef DISABLE_DRAWDIST_SETTING case MENUACTION_DRAWDIST: - ProcessSlider((m_PrefsLOD - 0.8f) * 1.0f, HOVEROPTION_INCREASE_DRAWDIST, HOVEROPTION_DECREASE_DRAWDIST, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + ProcessSlider((m_PrefsLOD - 0.4f) * 1.0f, HOVEROPTION_INCREASE_DRAWDIST, HOVEROPTION_DECREASE_DRAWDIST, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); break; +#endif case MENUACTION_MUSICVOLUME: ProcessSlider(m_PrefsMusicVolume / 128.0f, HOVEROPTION_INCREASE_MUSICVOLUME, HOVEROPTION_DECREASE_MUSICVOLUME, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); break; @@ -3125,15 +3140,15 @@ CMenuManager::DrawPlayerSetupScreen() for (int k = 0; m_pSelectedSkin->skinNameOriginal[k] != '\0'; ++k) { #endif if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "_", 1)) - strncpy(&m_pSelectedSkin->skinNameDisplayed[k], " ", 1); + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], " ", 2); if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "@", 1)) - strncpy(&m_pSelectedSkin->skinNameDisplayed[k], " ", 1); + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], " ", 2); if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "{", 1)) - strncpy(&m_pSelectedSkin->skinNameDisplayed[k], "(", 1); + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], "(", 2); if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "}", 1)) - strncpy(&m_pSelectedSkin->skinNameDisplayed[k], ")", 1); + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], ")", 2); if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "£", 1)) - strncpy(&m_pSelectedSkin->skinNameDisplayed[k], "$", 1); + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], "$", 2); } // Make letters after whitespace uppercase @@ -3800,7 +3815,9 @@ CMenuManager::LoadSettings() CFileMgr::Read(fileHandle, (char*)&m_nPrefsAudio3DProviderIndex, 1); CFileMgr::Read(fileHandle, (char*)&m_PrefsDMA, 1); CFileMgr::Read(fileHandle, (char*)&m_PrefsBrightness, 1); +#ifndef DISABLE_DRAWDIST_SETTING CFileMgr::Read(fileHandle, (char*)&m_PrefsLOD, 4); +#endif CFileMgr::Read(fileHandle, (char*)&m_PrefsShowSubtitles, 1); CFileMgr::Read(fileHandle, (char*)&m_PrefsUseWideScreen, 1); CFileMgr::Read(fileHandle, (char*)&m_PrefsVsyncDisp, 1); @@ -4522,6 +4539,7 @@ CMenuManager::ProcessButtonPresses(void) } SaveSettings(); break; +#ifndef DISABLE_DRAWDIST_SETTING case HOVEROPTION_INCREASE_DRAWDIST: m_PrefsLOD = m_PrefsLOD + (1.0f / MENUSLIDER_LOGICAL_BARS); m_PrefsLOD = min(1.8f, m_PrefsLOD); @@ -4534,6 +4552,7 @@ CMenuManager::ProcessButtonPresses(void) CRenderer::ms_lodDistScale = m_PrefsLOD; SaveSettings(); break; +#endif case HOVEROPTION_INCREASE_MUSICVOLUME: m_PrefsMusicVolume = m_PrefsMusicVolume + (128 / MENUSLIDER_LOGICAL_BARS); m_PrefsMusicVolume = Clamp(m_PrefsMusicVolume, 0, 127); @@ -4597,7 +4616,9 @@ CMenuManager::ProcessButtonPresses(void) #else switch (m_nHoverOption) { case HOVEROPTION_INCREASE_BRIGHTNESS: +#ifndef DISABLE_DRAWDIST_SETTING case HOVEROPTION_INCREASE_DRAWDIST: +#endif case HOVEROPTION_INCREASE_MUSICVOLUME: case HOVEROPTION_INCREASE_SFXVOLUME: case HOVEROPTION_INCREASE_MOUSESENS: @@ -4607,7 +4628,9 @@ CMenuManager::ProcessButtonPresses(void) CheckSliderMovement(1); break; case HOVEROPTION_DECREASE_BRIGHTNESS: +#ifndef DISABLE_DRAWDIST_SETTING case HOVEROPTION_DECREASE_DRAWDIST: +#endif case HOVEROPTION_DECREASE_MUSICVOLUME: case HOVEROPTION_DECREASE_SFXVOLUME: case HOVEROPTION_DECREASE_MOUSESENS: @@ -4644,7 +4667,10 @@ CMenuManager::ProcessButtonPresses(void) || CPad::GetPad(0)->GetAnaloguePadLeftJustUp() || CPad::GetPad(0)->GetAnaloguePadRightJustUp() || CPad::GetPad(0)->GetMouseWheelUpJustDown() || CPad::GetPad(0)->GetMouseWheelDownJustDown()) { int option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; - if (option == MENUACTION_BRIGHTNESS || option == MENUACTION_DRAWDIST + if (option == MENUACTION_BRIGHTNESS +#ifndef DISABLE_DRAWDIST_SETTING + || option == MENUACTION_DRAWDIST +#endif #ifdef CUSTOM_FRONTEND_OPTIONS || option == MENUACTION_CFO_SLIDER #endif @@ -4680,21 +4706,14 @@ CMenuManager::ProcessButtonPresses(void) // Centralized enter/back (except some conditions) #ifdef TIDY_UP_PBP - if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_RESUME) { - if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown() || - (isPlainTextScreen(m_nCurrScreen) && CPad::GetPad(0)->GetLeftMouseJustDown())) { + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown() || + (isPlainTextScreen(m_nCurrScreen) && CPad::GetPad(0)->GetLeftMouseJustDown())) { - if (!isPlainTextScreen(m_nCurrScreen)) + if (!isPlainTextScreen(m_nCurrScreen)) m_bShowMouse = false; optionSelected = true; } - } else { - if (CPad::GetPad(0)->GetEnterJustUp() || CPad::GetPad(0)->GetCrossJustUp()) { - m_bShowMouse = false; - optionSelected = true; - } - } if (!goDown && !goUp && !optionSelected) { if (m_nCurrScreen != MENUPAGE_START_MENU) { @@ -5099,9 +5118,16 @@ CMenuManager::ProcessButtonPresses(void) m_PrefsFrameLimiter = true; m_PrefsBrightness = 256; m_PrefsVsyncDisp = true; - m_PrefsLOD = 1.2f; +#ifdef DISABLE_DRAWDIST_SETTING + m_PrefsLOD = 0.7f; + CRenderer::ms_lodDistScale = 0.7f; +#else + m_PrefsLOD = 0.7f; + CRenderer::ms_lodDistScale = 0.7f; +#endif + CIniFile::PedNumberMultiplier = 0.6f; + CIniFile::CarNumberMultiplier = 0.6f; m_PrefsVsync = true; - CRenderer::ms_lodDistScale = 1.2f; #ifdef ASPECT_RATIO_SCALE m_PrefsUseWideScreen = AR_AUTO; #else @@ -5132,7 +5158,9 @@ CMenuManager::ProcessButtonPresses(void) ControlsManager.MakeControllerActionsBlank(); ControlsManager.InitDefaultControlConfiguration(); ControlsManager.InitDefaultControlConfigMouse(MousePointerStateHelper.GetMouseSetUp()); -#if !defined RW_GL3 +#if defined(RW_DC) + printf("TODO: implement this"); +#elif !defined RW_GL3 if (AllValidWinJoys.m_aJoys[JOYSTICK1].m_bInitialised) { DIDEVCAPS devCaps; devCaps.dwSize = sizeof(DIDEVCAPS); @@ -5523,7 +5551,10 @@ CMenuManager::ProcessOnOffMenuOptions() SaveSettings(); break; case MENUACTION_TRAILS: - CMBlur::BlurOn = !CMBlur::BlurOn; + CMBlur::BlurOn = CMBlur::BlurOn + 1; + if (CMBlur::BlurOn > 2) + CMBlur::BlurOn = 0; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); SaveSettings(); if (CMBlur::BlurOn) @@ -5716,7 +5747,7 @@ CMenuManager::SwitchMenuOnAndOff() m_bShutDownFrontEndRequested = false; DisplayComboButtonErrMsg = false; -#ifdef REGISTER_START_BUTTON +#ifdef REGISTER_START_BUTTON //START BUTTON IS DEFINED HERE, DIRECTLY COMPATIBLE WITH DC NOMENCLATURE AND KEYBINDINGS int16 start1 = CPad::GetPad(0)->PCTempJoyState.Start, start2 = CPad::GetPad(0)->PCTempKeyState.Start, start3 = CPad::GetPad(0)->OldState.Start, start4 = CPad::GetPad(0)->NewState.Start; #endif diff --git a/src/core/Frontend.h b/src/core/Frontend.h index 6e6c40f7..18f0f246 100644 --- a/src/core/Frontend.h +++ b/src/core/Frontend.h @@ -442,8 +442,13 @@ enum enum eControlMethod { - CONTROL_STANDARD = 0, - CONTROL_CLASSIC, + #ifdef RW_DC // Change to have Classic Control per default, credits to Frogbull + CONTROL_CLASSIC = 0, + CONTROL_STANDARD, + #else + CONTROL_STANDARD = 0, + CONTROL_CLASSIC, + #endif }; // Why?? diff --git a/src/core/Game.cpp b/src/core/Game.cpp index b3dd1eda..6521ac77 100644 --- a/src/core/Game.cpp +++ b/src/core/Game.cpp @@ -413,7 +413,7 @@ bool CGame::Initialise(const char* datFile) // Load density values from gta3.ini only if our re3.ini have them 1.f if (CIniFile::PedNumberMultiplier == 1.f && CIniFile::CarNumberMultiplier == 1.f) #endif - CIniFile::LoadIniFile(); + CIniFile::LoadIniFile(); // this is a NOP for us now #endif currLevel = LEVEL_INDUSTRIAL; diff --git a/src/core/General.h b/src/core/General.h index d4b941dd..ff333944 100644 --- a/src/core/General.h +++ b/src/core/General.h @@ -151,7 +151,17 @@ public: // Probably don't want to ever reach high static float GetRandomNumberInRange(float low, float high) { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } - + +#if !defined(INT32_IS_INT) static int32 GetRandomNumberInRange(int32 low, int32 high) { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } +#endif + + static int32 GetRandomNumberInRange(int low, int high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } + +#if !defined(INT32_IS_INT) + static int32 GetRandomNumberInRange(int low, const int32& high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } +#endif }; diff --git a/src/core/IniFile.cpp b/src/core/IniFile.cpp index 524632fe..1276edfa 100644 --- a/src/core/IniFile.cpp +++ b/src/core/IniFile.cpp @@ -7,12 +7,14 @@ #include "main.h" #include "Population.h" -float CIniFile::PedNumberMultiplier = 1.0f; -float CIniFile::CarNumberMultiplier = 1.0f; +float CIniFile::PedNumberMultiplier = 0.6f; // dreamcast default +float CIniFile::CarNumberMultiplier = 0.6f; // dreamcast default void CIniFile::LoadIniFile() { CFileMgr::SetDir(""); + // gta3.ini is ignored for now + #if 0 int f = CFileMgr::OpenFile("gta3.ini", "r"); if (f){ CFileMgr::ReadLine(f, gString, 200); @@ -23,6 +25,7 @@ void CIniFile::LoadIniFile() CarNumberMultiplier = Min(3.0f, Max(0.5f, CarNumberMultiplier)); CFileMgr::CloseFile(f); } + #endif CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS * PedNumberMultiplier; CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS * CarNumberMultiplier; } \ No newline at end of file diff --git a/src/core/MenuScreensCustom.cpp b/src/core/MenuScreensCustom.cpp index d33650f8..f99918ca 100644 --- a/src/core/MenuScreensCustom.cpp +++ b/src/core/MenuScreensCustom.cpp @@ -55,7 +55,7 @@ #endif #ifdef FREE_CAM - #define FREE_CAM_TOGGLE MENUACTION_CFO_SELECT, "FEC_FRC", { new CCFOSelect((int8*)&TheCamera.bFreeCam, "Display", "FreeCam", off_on, 2, false) }, + #define FREE_CAM_TOGGLE MENUACTION_CFO_SELECT, "FEC_FRC", { new CCFOSelect((int8*)&TheCamera.bFreeCam, "Display", "FreeCam", off_on, 2, true) }, #else #define FREE_CAM_TOGGLE #endif @@ -155,15 +155,17 @@ void RestoreDefDisplay(int8 action) { CMenuManager::m_PrefsCutsceneBorders = true; #endif #ifdef FREE_CAM - TheCamera.bFreeCam = false; + TheCamera.bFreeCam = true; #endif #ifdef PED_CAR_DENSITY_SLIDERS + CIniFile::PedNumberMultiplier = 0.6f; // dreamcast defaults + CIniFile::CarNumberMultiplier = 0.6f; // dreamcast defaults CIniFile::LoadIniFile(); #endif #ifdef GRAPHICS_MENU_OPTIONS // otherwise Frontend will handle those CMenuManager::m_PrefsBrightness = 256; - CMenuManager::m_PrefsLOD = 1.2f; - CRenderer::ms_lodDistScale = 1.2f; + CMenuManager::m_PrefsLOD = 0.7f; + CRenderer::ms_lodDistScale = 0.7f; CMenuManager::m_PrefsShowSubtitles = true; FrontEndMenuManager.SaveSettings(); #endif @@ -395,6 +397,9 @@ void ControllerTypeAfterChange(int8 before, int8 after) } #endif +extern bool doEnvironmentMaps; +extern bool bDisplayRate; + CMenuScreenCustom aScreens[MENUPAGES] = { // MENUPAGE_NONE = 0 { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, }, @@ -480,6 +485,10 @@ CMenuScreenCustom aScreens[MENUPAGES] = { DENSITY_SLIDERS CUTSCENE_BORDERS_TOGGLE FREE_CAM_TOGGLE + MENUACTION_CFO_SELECT, "FEC_FPS", { new CCFOSelect((int8*)&bDisplayRate, "Display", "FPS", off_on, 2, false) }, + #if defined(TIMEBARS) + MENUACTION_CFO_SELECT, "FEC_TB", { new CCFOSelect((int8*)&gbShowTimebars, "Display", "TB", off_on, 2, false) }, + #endif MENUACTION_SUBTITLES, "FED_SUB", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, MENUACTION_CFO_DYNAMIC, "FET_DEF", { new CCFODynamic(nil, nil, nil, nil, RestoreDefDisplay) }, MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, @@ -907,6 +916,7 @@ CMenuScreenCustom aScreens[MENUPAGES] = { POSTFX_SELECTORS #else MENUACTION_TRAILS, "FED_TRA", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_CFO_SELECT, "FEC_MFX", { new CCFOSelect((int8*)&doEnvironmentMaps, "Graphics", "MFX", off_on, 2, false) }, #endif // re3.cpp inserts here pipeline selectors if neo/neo.txd exists and EXTENDED_PIPELINES defined MENUACTION_CFO_DYNAMIC, "FET_DEF", { new CCFODynamic(nil, nil, nil, nil, RestoreDefGraphics) }, diff --git a/src/core/Pad.cpp b/src/core/Pad.cpp index 60bb7a76..e1e5913f 100644 --- a/src/core/Pad.cpp +++ b/src/core/Pad.cpp @@ -2,6 +2,15 @@ #include "common.h" #include "crossplatform.h" #include "platform.h" +#ifdef RW_DC + +#include +#include +#include + +auto contMaple = maple_enum_type(0, MAPLE_FUNC_CONTROLLER); +auto state = (cont_state_t *)maple_dev_status(contMaple); + #ifdef XINPUT #include #if !defined(PSAPI_VERSION) || (PSAPI_VERSION > 1) @@ -10,6 +19,7 @@ #pragma comment( lib, "Xinput.lib" ) #endif #endif +#endif #include "Pad.h" #include "ControllerConfig.h" @@ -526,6 +536,8 @@ CMouseControllerState CMousePointerStateHelper::GetMouseSetUp() state.WHEELUP = true; } } +#elif defined(RW_DC) + printf("TODO this %s\n", __func__); #else // It seems there is no way to get number of buttons on mouse, so assign all buttons if we have mouse. double xpos = 1.0f, ypos; @@ -585,6 +597,8 @@ void CPad::UpdateMouse() NewMouseControllerState = PCTempMouseControllerState; } } +#elif defined(RW_DC) + // TODO: Mouse input here? #else if ( IsForegroundApp() && PSGLOBAL(cursorIsInWindow) ) { @@ -1418,8 +1432,41 @@ void CPad::Update(int16 pad) if (!CRecordDataForGame::IsPlayingBack() && !CRecordDataForChase::ShouldThisPadBeLeftAlone(pad)) #endif { +#ifdef RW_DC + if (pad == 0) { + NewState.DPadUp = state->dpad_up; //This part could be inside a compiler directive to preserve the old code and just use this block if compil + NewState.DPadDown = state->dpad_down; //I also changed CControllerState inside Pad.h and created these values for DC controllers + NewState.DPadLeft = state->dpad_left; + NewState.DPadRight = state->dpad_right; + NewState.A = state->a; + NewState.B = state->b; + NewState.X = state->x; + NewState.Y = state->y; + NewState.Start = state->start; + NewState.RightTrigger = state->rtrig; + NewState.LeftTrigger = state->ltrig; + NewState.LeftStickX = state->joyx; + NewState.LeftStickY = state->joyy; + NewState.RightShock = state->dpad_left; + } else { + NewState.DPadUp = 0; + NewState.DPadDown = 0; + NewState.DPadLeft = 0; + NewState.DPadRight = 0; + NewState.A = 0; + NewState.B = 0; + NewState.X = 0; + NewState.Y = 0; + NewState.Start = 0; + NewState.RightTrigger = 0; + NewState.LeftTrigger = 0; + NewState.LeftStickX = 0; + NewState.LeftStickY = 0; + } +#else NewState = ReconcileTwoControllersInput(PCTempKeyState, PCTempJoyState); NewState = ReconcileTwoControllersInput(PCTempMouseState, NewState); +#endif } PCTempJoyState.Clear(); @@ -1520,11 +1567,21 @@ CPad *CPad::GetPad(int32 pad) #define CURMODE (Mode) #endif +//The next are the actuall functions that are checked and produce the values that are used by engine to make the char run, the car turn, etc +//Although initially I didn't want to change them, I think here is the best place to create the custom desired mapping and behavior for the DC inputs +//The switch statement using CURMODE could be used in the future to define diferent control configurations, depending on the type of controller and desired mapping (e.g. Xbox like or PS2 like) +//While i think its possible, creating a system to configure custom mappings inside the game menus like in the PC game is out of my scope in the moment, I don't know if this is really necessary +//Also, the interface controls are not defined here, they are defined in Frontend.cpp unfortunately, using CControllerState values like here; Because of that, the behavior of the Start button and the A button for selecting menu itens are not here int16 CPad::GetSteeringLeftRight(void) { if ( ArePlayerControlsDisabled() ) return 0; + +#ifdef RW_DC + if (NewState.X) + return 0; +#endif switch (CURMODE) { @@ -1558,6 +1615,11 @@ int16 CPad::GetSteeringUpDown(void) { if ( ArePlayerControlsDisabled() ) return 0; + +#ifdef RW_DC + if (NewState.X) + return 0; +#endif switch (CURMODE) { @@ -1591,24 +1653,44 @@ int16 CPad::GetCarGunUpDown(void) { if ( ArePlayerControlsDisabled() ) return 0; + +#ifdef RW_DC + if (!NewState.X) + return 0; +#endif switch (CURMODE) { case 0: - case 1: + case 1: +#ifdef RW_DC + case 2: + { + return NewState.LeftStickY; + + break; + } + case 3: + { + return NewState.LeftStickY; + + break; + } + +#else case 2: { return NewState.RightStickY; break; } - case 3: { return (NewState.DPadUp - NewState.DPadDown) / 2; break; } +#endif } return 0; @@ -1618,24 +1700,42 @@ int16 CPad::GetCarGunLeftRight(void) { if ( ArePlayerControlsDisabled() ) return 0; +#ifdef RW_DC + if (!NewState.X) + return 0; +#endif switch (CURMODE) { case 0: case 1: +#ifdef RW_DC + case 2: + { + return NewState.LeftStickX; + + break; + } + case 3: + { + return NewState.LeftStickX; + + break; + } +#else case 2: { return NewState.RightStickX; break; } - case 3: { return (NewState.DPadRight - NewState.DPadLeft) / 2; break; } +#endif } return 0; @@ -1645,10 +1745,23 @@ int16 CPad::GetPedWalkLeftRight(void) { if ( ArePlayerControlsDisabled() ) return 0; + +#ifdef RW_DC + if (NewState.X) + return 0; +#endif switch (CURMODE) { case 0: +#ifdef RW_DC + case 2: + { + return NewState.LeftStickX; + + break; + } +#else case 2: { int16 axis = NewState.LeftStickX; @@ -1661,7 +1774,7 @@ int16 CPad::GetPedWalkLeftRight(void) break; } - +#endif case 1: case 3: { @@ -1679,10 +1792,23 @@ int16 CPad::GetPedWalkUpDown(void) { if ( ArePlayerControlsDisabled() ) return 0; + +#ifdef RW_DC + if (NewState.X) + return 0; +#endif switch (CURMODE) { case 0: +#ifdef RW_DC + case 2: + { + return NewState.LeftStickY; + + break; + } +#else case 2: { int16 axis = NewState.LeftStickY; @@ -1695,7 +1821,7 @@ int16 CPad::GetPedWalkUpDown(void) break; } - +#endif case 1: case 3: { @@ -1713,6 +1839,14 @@ int16 CPad::GetAnalogueUpDown(void) switch (CURMODE) { case 0: +#ifdef RW_DC + case 2: + { + return NewState.LeftStickY; + + break; + } +#else case 2: { int16 axis = NewState.LeftStickY; @@ -1725,7 +1859,7 @@ int16 CPad::GetAnalogueUpDown(void) break; } - +#endif case 1: case 3: { @@ -1767,8 +1901,11 @@ bool CPad::GetLookBehindForPed(void) { if ( ArePlayerControlsDisabled() ) return false; - +#ifdef RW_DC + return NewState.DPadDown; +#else return !!NewState.RightShock; +#endif } bool CPad::GetHorn(void) @@ -1778,7 +1915,33 @@ bool CPad::GetHorn(void) switch (CURMODE) { - case 0: +#ifdef RW_DC + case 0: + { + return !!NewState.DPadRight; + + break; + } + case 1: + { + return !!NewState.DPadRight; + + break; + } + case 2: + { + return !!NewState.DPadRight; + + break; + } + case 3: + { + return !!NewState.DPadRight; + + break; + } +#else + case 0: { return !!NewState.LeftShock; @@ -1805,6 +1968,7 @@ bool CPad::GetHorn(void) break; } +#endif } return false; @@ -1859,6 +2023,21 @@ bool CPad::GetCarGunFired(void) { case 0: case 1: +#ifdef RW_DC + case 2: + { + return !!NewState.A; + + break; + } + + case 3: + { + return !!NewState.A; + + break; + } +#else case 2: { return !!NewState.Circle; @@ -1872,6 +2051,7 @@ bool CPad::GetCarGunFired(void) break; } +#endif } return false; @@ -1886,6 +2066,21 @@ bool CPad::CarGunJustDown(void) { case 0: case 1: +#ifdef RW_DC + case 2: + { + return !!NewState.A; + + break; + } + + case 3: + { + return !!NewState.A; + + break; + } +#else case 2: { return !!(NewState.Circle && !OldState.Circle); @@ -1899,6 +2094,7 @@ bool CPad::CarGunJustDown(void) break; } +#endif } return false; @@ -1908,9 +2104,32 @@ int16 CPad::GetHandBrake(void) { if ( ArePlayerControlsDisabled() ) return 0; - + switch (CURMODE) { +#ifdef RW_DC + case 0: + case 1: + { + return NewState.B; + + break; + } + + case 2: + { + return NewState.B; + + break; + } + + case 3: + { + return NewState.B; + + break; + } +#else case 0: case 1: { @@ -1932,6 +2151,7 @@ int16 CPad::GetHandBrake(void) break; } +#endif } return 0; @@ -1941,9 +2161,37 @@ int16 CPad::GetBrake(void) { if ( ArePlayerControlsDisabled() ) return 0; - + switch (CURMODE) { +#ifdef RW_DC + case 0: + case 2: + { + return NewState.LeftTrigger; + + break; + } + + case 1: + { + return NewState.LeftTrigger; + + break; + } + + case 3: + { + int16 axis = 2 * NewState.LeftTrigger; + + if ( axis < 0 ) + return 0; + else + return axis; + + break; + } +#else case 0: case 2: { @@ -1970,8 +2218,8 @@ int16 CPad::GetBrake(void) break; } +#endif } - return 0; } @@ -1979,9 +2227,26 @@ bool CPad::GetExitVehicle(void) { if ( ArePlayerControlsDisabled() ) return false; - + switch (CURMODE) { +#ifdef RW_DC + case 0: + case 1: + case 3: + { + return !!NewState.Y; + + break; + } + + case 2: + { + return !!NewState.Y; + + break; + } +#else case 0: case 1: case 3: @@ -1997,6 +2262,7 @@ bool CPad::GetExitVehicle(void) break; } +#endif } return false; @@ -2011,6 +2277,21 @@ bool CPad::ExitVehicleJustDown(void) { case 0: case 1: +#ifdef RW_DC + case 3: + { + return !!(NewState.Y && !OldState.Y); + + break; + } + + case 2: + { + return !!(NewState.Y && !OldState.Y); + + break; + } +#else case 3: { return !!(NewState.Triangle && !OldState.Triangle); @@ -2024,6 +2305,7 @@ bool CPad::ExitVehicleJustDown(void) break; } +#endif } return false; @@ -2037,26 +2319,46 @@ int32 CPad::GetWeapon(void) switch (CURMODE) { case 0: +#ifdef RW_DC case 1: { - return NewState.Circle; + if (NewState.RightTrigger > 128) + return true; break; } case 2: { - return NewState.Cross; + if (NewState.RightTrigger > 128) + return true; break; } case 3: { - return NewState.RightShoulder1; + if (NewState.RightTrigger > 128) + return true; break; } +#else + case 1: + { + return NewState.Circle; + } + + case 2: + { + return NewState.Cross; + } + + case 3: + { + return NewState.RightShoulder1; + } +#endif } return false; @@ -2070,26 +2372,46 @@ bool CPad::WeaponJustDown(void) switch (CURMODE) { case 0: +#ifdef RW_DC case 1: { - return !!(NewState.Circle && !OldState.Circle); + if (NewState.RightTrigger > 128) + return true; break; } case 2: { - return !!(NewState.Cross && !OldState.Cross); - + if (NewState.RightTrigger > 128) + return true; + break; } case 3: { - return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); - + if (NewState.RightTrigger > 128) + return true; + break; } +#else + case 1: + { + return !!(NewState.Circle && !OldState.Circle); + } + + case 2: + { + return !!(NewState.Cross && !OldState.Cross); + } + + case 3: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + } +#endif } return false; @@ -2099,10 +2421,37 @@ int16 CPad::GetAccelerate(void) { if ( ArePlayerControlsDisabled() ) return 0; - + switch (CURMODE) { case 0: +#ifdef RW_DC + case 2: + { + return NewState.RightTrigger; + + break; + } + + case 1: + { + return NewState.RightTrigger; + + break; + } + + case 3: + { + int16 axis = -2 * NewState.RightTrigger; + + if ( axis < 0 ) + return 0; + else + return axis; + + break; + } +#else case 2: { return NewState.Cross; @@ -2128,6 +2477,7 @@ int16 CPad::GetAccelerate(void) break; } +#endif } return 0; @@ -2139,13 +2489,21 @@ bool CPad::CycleCameraModeUpJustDown(void) { case 0: case 2: +#ifdef RW_DC + case 3: + { + return !!(NewState.DPadUp && !OldState.DPadUp); + + break; + } +#else case 3: { return !!(NewState.Select && !OldState.Select); break; } - +#endif case 1: { return !!(NewState.DPadUp && !OldState.DPadUp); @@ -2225,16 +2583,22 @@ bool CPad::CycleWeaponLeftJustDown(void) { if ( ArePlayerControlsDisabled() ) return false; - +#ifdef RW_DC + return !!(NewState.DPadLeft && !OldState.DPadLeft); +#else return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +#endif } bool CPad::CycleWeaponRightJustDown(void) { if ( ArePlayerControlsDisabled() ) return false; - +#ifdef RW_DC + return !!(NewState.DPadRight && !OldState.DPadRight); +#else return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +#endif } bool CPad::GetTarget(void) @@ -2246,19 +2610,32 @@ bool CPad::GetTarget(void) { case 0: case 1: +#ifdef RW_DC + case 2: + { + if (NewState.LeftTrigger > 128) + return true; + + break; + } + case 3: + { + if (NewState.LeftTrigger > 128) + return true; + break; + } +#else case 2: { return !!NewState.RightShoulder1; - break; } - case 3: { return !!NewState.LeftShoulder1; - break; - } + } +#endif } return false; @@ -2273,6 +2650,21 @@ bool CPad::TargetJustDown(void) { case 0: case 1: +#ifdef RW_DC + case 2: + { + if (NewState.LeftTrigger > 128) + return true; + + break; + } + case 3: + { + if (NewState.LeftTrigger > 128) + return true; + break; + } +#else case 2: { return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); @@ -2286,6 +2678,7 @@ bool CPad::TargetJustDown(void) break; } +#endif } return false; @@ -2295,19 +2688,37 @@ bool CPad::JumpJustDown(void) { if ( ArePlayerControlsDisabled() ) return false; - +#ifdef RW_DC + return !!(NewState.B && !OldState.B); +#else return !!(NewState.Square && !OldState.Square); +#endif } bool CPad::GetSprint(void) { if ( ArePlayerControlsDisabled() ) return false; - + switch (CURMODE) { case 0: case 1: +#ifdef RW_DC + case 3: + { + return NewState.A; + + break; + } + + case 2: + { + return NewState.A; + + break; + } +#else case 3: { return !!NewState.Cross; @@ -2321,6 +2732,7 @@ bool CPad::GetSprint(void) break; } +#endif } return false; @@ -2330,16 +2742,26 @@ bool CPad::ShiftTargetLeftJustDown(void) { if ( ArePlayerControlsDisabled() ) return false; +#ifdef RW_DC + + return !!(NewState.DPadLeft && !OldState.DPadLeft); +#else return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +#endif } bool CPad::ShiftTargetRightJustDown(void) { if ( ArePlayerControlsDisabled() ) return false; +#ifdef RW_DC + + return !!(NewState.DPadRight && !OldState.DPadRight); +#else return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +#endif } #ifdef FIX_BUGS @@ -2612,6 +3034,21 @@ bool CPad::SniperZoomIn(void) { case 0: case 1: +#ifdef RW_DC + case 3: + { + return !!NewState.X; + + break; + } + + case 2: + { + return !!NewState.X; + + break; + } +#else case 3: { return !!NewState.Square; @@ -2625,6 +3062,7 @@ bool CPad::SniperZoomIn(void) break; } +#endif } return false; @@ -2639,6 +3077,21 @@ bool CPad::SniperZoomOut(void) { case 0: case 1: +#ifdef RW_DC + case 3: + { + return !!NewState.A; + + break; + } + + case 2: + { + return !!NewState.A; + + break; + } +#else case 3: { return !!NewState.Cross; @@ -2652,6 +3105,7 @@ bool CPad::SniperZoomOut(void) break; } +#endif } return false; @@ -2696,7 +3150,13 @@ int16 CPad::SniperModeLookUpDown(void) int16 CPad::LookAroundLeftRight(void) { + if (!NewState.X) + return 0; +#ifdef RW_DC + float axis = GetPad(0)->NewState.LeftStickX; //I don't know why this is float and the UpDown is int16 +#else float axis = GetPad(0)->NewState.RightStickX; +#endif if ( Abs(axis) > 85 && !GetLookBehindForPed() ) return (int16) ( (axis + ( ( axis > 0 ) ? -85 : 85) ) @@ -2711,7 +3171,14 @@ int16 CPad::LookAroundLeftRight(void) int16 CPad::LookAroundUpDown(void) { + if (!NewState.X) + return 0; +#ifdef RW_DC + int16 axis = GetPad(0)->NewState.LeftStickY; +#else int16 axis = GetPad(0)->NewState.RightStickY; +#endif + #ifdef FIX_BUGS axis = -axis; diff --git a/src/core/Pad.h b/src/core/Pad.h index b37659cd..0421c88e 100644 --- a/src/core/Pad.h +++ b/src/core/Pad.h @@ -12,16 +12,18 @@ enum { PLAYERCONTROL_CUTSCENE = 128, }; -class CControllerState +class CControllerState //Need to be separated with ifdef for PS2 and DC in the future, dependent code must be tracked first { public: int16 LeftStickX, LeftStickY; int16 RightStickX, RightStickY; + int16 RightTrigger, LeftTrigger; int16 LeftShoulder1, LeftShoulder2; int16 RightShoulder1, RightShoulder2; int16 DPadUp, DPadDown, DPadLeft, DPadRight; int16 Start, Select; int16 Square, Triangle, Cross, Circle; + uint32_t A, B, C, X, Y, Z; int16 LeftShock, RightShock; int16 NetworkTalk; float GetLeftStickX(void) { return LeftStickX/32767.0f; }; @@ -408,8 +410,76 @@ public: bool GetLeftWin() { return NewKeyState.LWIN; } bool GetRightWin() { return NewKeyState.RWIN; } bool GetApps() { return NewKeyState.APPS; } - // pad + // pad -- All these functions could be costumized to make cheat codes available, they are the ones used +#ifdef RW_DC + bool GetTriangleJustDown() { return !!(NewState.Triangle && !OldState.Triangle); } + bool GetCircleJustDown() { return !!(NewState.Circle && !OldState.Circle); } + bool GetCrossJustDown() { return !!(NewState.A && !OldState.A); } //------------HACKY, CHANGE LATTER, GetCrossJustDown is used by Frontend.cpp to select items on the menus + bool GetSquareJustDown() { return !!(NewState.Square && !OldState.Square); } + bool GetLeftShoulder1JustDown() { return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); } + bool GetLeftShoulder2JustDown() { return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); } + bool GetRightShoulder1JustDown() { return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); } + bool GetRightShoulder2JustDown() { return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); } + bool GetLeftShockJustDown() { return !!(NewState.LeftShock && !OldState.LeftShock); } + bool GetRightShockJustDown() { return !!(NewState.RightShock && !OldState.RightShock); } + + bool GetTriangleJustUp() { return !!(!NewState.Triangle && OldState.Triangle); } + bool GetCircleJustUp() { return !!(!NewState.Circle && OldState.Circle); } + bool GetCrossJustUp() { return !!(!NewState.Cross && OldState.Cross); } + bool GetSquareJustUp() { return !!(!NewState.Square && OldState.Square); } + + bool GetTriangle() { return !!NewState.Triangle; } + bool GetCircle() { return !!NewState.Circle; } + bool GetCross() { return !!NewState.Cross; } + bool GetSquare() { return !!NewState.Square; } + bool GetLeftShoulder1(void) { return !!NewState.LeftShoulder1; } + bool GetLeftShoulder2(void) { return !!NewState.LeftShoulder2; } + bool GetRightShoulder1(void) { return !!NewState.RightShoulder1; } + bool GetRightShoulder2(void) { return !!NewState.RightShoulder2; } + + bool GetAJustDown() { return !!(NewState.A && !OldState.A); } + bool GetBJustDown() { return !!(NewState.B && !OldState.B); } + bool GetCJustDown() { return !!(NewState.C && !OldState.C); } + bool GetXJustDown() { return !!(NewState.X && !OldState.X); } + bool GetYJustDown() { return !!(NewState.Y && !OldState.Y); } + bool GetZJustDown() { return !!(NewState.Z && !OldState.Z); } + bool GetDPadUpJustDown() { return !!(NewState.DPadUp && !OldState.DPadUp); } + bool GetDPadDownJustDown() { return !!(NewState.DPadDown && !OldState.DPadDown); } + bool GetDPadLeftJustDown() { return !!(NewState.DPadLeft && !OldState.DPadLeft); } + bool GetDPadRightJustDown() { return !!(NewState.DPadRight && !OldState.DPadRight); } + bool GetStartJustDown() { return !!(NewState.Start && !OldState.Start); } + bool GetLeftStickXJustDown() { return !!(NewState.LeftStickX && !OldState.LeftStickX); } + bool GetLeftStickYJustDown() { return !!(NewState.LeftStickY && !OldState.LeftStickY); } + + bool GetAJustUp() { return !!(!NewState.A && OldState.A); } + bool GetBJustUp() { return !!(!NewState.B && OldState.B); } + bool GetCJustUp() { return !!(!NewState.C && OldState.C); } + bool GetXJustUp() { return !!(!NewState.X && OldState.X); } + bool GetYJustUp() { return !!(!NewState.Y && OldState.Y); } + bool GetZJustUp() { return !!(!NewState.Z && OldState.Z); } + bool GetDPadUpJustUp() { return !!(!NewState.DPadUp && OldState.DPadUp); } + bool GetDPadDownJustUp() { return !!(!NewState.DPadDown && OldState.DPadDown); } + bool GetDPadLeftJustUp() { return !!(!NewState.DPadLeft && OldState.DPadLeft); } + bool GetDPadRightJustUp() { return !!(!NewState.DPadRight && OldState.DPadRight); } + + bool GetA() { return !!NewState.A; } + bool GetB() { return !!NewState.B; } + bool GetC() { return !!NewState.C; } + bool GetX() { return !!NewState.X; } + bool GetY() { return !!NewState.Y; } + bool GetZ() { return !!NewState.Z; } + bool GetDPadUp() { return !!NewState.DPadUp; } + bool GetDPadDown() { return !!NewState.DPadDown; } + bool GetDPadLeft() { return !!NewState.DPadLeft; } + bool GetDPadRight() { return !!NewState.DPadRight; } + bool GetStart() { return !!NewState.Start; } + int16 GetLeftStickX(void) { return NewState.LeftStickX; } + int16 GetLeftStickY(void) { return NewState.LeftStickY; } + int16 GetRightStickX(void) { return NewState.RightStickX; } + int16 GetRightStickY(void) { return NewState.RightStickY; } + +#else bool GetTriangleJustDown() { return !!(NewState.Triangle && !OldState.Triangle); } bool GetCircleJustDown() { return !!(NewState.Circle && !OldState.Circle); } bool GetCrossJustDown() { return !!(NewState.Cross && !OldState.Cross); } @@ -454,6 +524,7 @@ public: int16 GetLeftStickY(void) { return NewState.LeftStickY; } int16 GetRightStickX(void) { return NewState.RightStickX; } int16 GetRightStickY(void) { return NewState.RightStickY; } +#endif bool ArePlayerControlsDisabled(void) { return DisablePlayerControls != PLAYERCONTROL_ENABLED; } void SetDisablePlayerControls(uint8 who) { DisablePlayerControls |= who; } diff --git a/src/core/PlayerInfo.cpp b/src/core/PlayerInfo.cpp index 91bd0691..2722c2d0 100644 --- a/src/core/PlayerInfo.cpp +++ b/src/core/PlayerInfo.cpp @@ -412,6 +412,8 @@ FindPlayerCoors(void) return TheCamera.GetPosition(); #endif CPlayerPed *ped = FindPlayerPed(); + if (!ped) + return TheCamera.GetPosition(); if(ped->InVehicle()) return ped->m_pMyVehicle->GetPosition(); else diff --git a/src/core/Radar.cpp b/src/core/Radar.cpp index b29c19eb..a40a421e 100644 --- a/src/core/Radar.cpp +++ b/src/core/Radar.cpp @@ -916,6 +916,13 @@ void CRadar::DrawRadarSection(int32 x, int32 y) } RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(texture)); CSprite2d::SetVertices(numVertices, (float*)screenPoly, (float*)texCoords, CRGBA(255, 255, 255, 255)); + + // Make the depth slightly less than the mask + // Not sure how this worked in the original code tbh + auto vtx = CSprite2d::GetVertices(); + for (i = 0; i < numVertices; i++) { + vtx[i].w *= 5; + } // check done above now // if(numVertices > 2) RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::GetVertices(), numVertices); @@ -1113,6 +1120,23 @@ CRadar::LoadTextures() RwRaster *raster = RwRasterCreate(16, 16, 0, rwRASTERTYPETEXTURE | rwRASTERFORMAT8888); + #if defined(RW_DC) + RwUInt16 *pixels = (RwUInt16 *)RwRasterLock(raster, 0, rwRASTERLOCKWRITE); + for(int x = 0; x < 16; x++) + for(int y = 0; y < 16; y++) + { + int x2 = x < 8 ? x : 7 - (x & 7); + int y2 = y < 8 ? y : 7 - (y & 7); + if ((y2 >= 4 && x2 >= 4) // square in the center is transparent + || (x2 < 2 && y2 == 0) // two pixels on each side of first/last line are transparent + || (x2 < 1 && y2 == 1)) // one pixel on each side of second to first/last line is transparent + pixels[x + y * 16] = 0; + else if((x2 == 2 && y2 >= 2)|| (y2 == 2 && x2 >= 2) )// colored square inside + pixels[x + y * 16] = (WAYPOINT_B>>4) | ((WAYPOINT_G>>4) << 4) | ((WAYPOINT_R>>4) << 8) | (15 << 12); + else + pixels[x + y * 16] = 0xF000; // black + } + #else RwUInt32 *pixels = (RwUInt32 *)RwRasterLock(raster, 0, rwRASTERLOCKWRITE); for(int x = 0; x < 16; x++) for(int y = 0; y < 16; y++) @@ -1132,6 +1156,7 @@ CRadar::LoadTextures() else pixels[x + y * 16] = 0xFF000000; // black } + #endif RwRasterUnlock(raster); WaypointSprite.m_pTexture = RwTextureCreate(raster); RwTextureSetFilterMode(WaypointSprite.m_pTexture, rwFILTERLINEAR); @@ -1185,7 +1210,7 @@ void CRadar::SetBlipSprite(int32 i, int32 icon) } } -int CRadar::SetCoordBlip(eBlipType type, CVector pos, int32 color, eBlipDisplay display) +int32 CRadar::SetCoordBlip(eBlipType type, CVector pos, int32 color, eBlipDisplay display) { int nextBlip; for (nextBlip = 0; nextBlip < NUMRADARBLIPS; nextBlip++) { @@ -1210,7 +1235,7 @@ int CRadar::SetCoordBlip(eBlipType type, CVector pos, int32 color, eBlipDisplay return CRadar::GetNewUniqueBlipIndex(nextBlip); } -int CRadar::SetEntityBlip(eBlipType type, int32 handle, int32 color, eBlipDisplay display) +int32 CRadar::SetEntityBlip(eBlipType type, int32 handle, int32 color, eBlipDisplay display) { int nextBlip; for (nextBlip = 0; nextBlip < NUMRADARBLIPS; nextBlip++) { diff --git a/src/core/Streaming.cpp b/src/core/Streaming.cpp index 9ac22096..d9f22245 100644 --- a/src/core/Streaming.cpp +++ b/src/core/Streaming.cpp @@ -85,6 +85,10 @@ int32 islandLODcomSub; int32 islandLODsubInd; int32 islandLODsubCom; +#define memory_logf(...) // printf(__VA_ARGS__) + +#define STREAMING_MEM_SIZE (4 * 1024 * 1024) + bool CStreamingInfo::GetCdPosnAndSize(uint32 &posn, uint32 &size) { @@ -203,11 +207,11 @@ CStreaming::Init2(void) // allocate streaming buffers if(ms_streamingBufferSize & 1) ms_streamingBufferSize++; #ifndef ONE_THREAD_PER_CHANNEL - ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE, CDSTREAM_SECTOR_SIZE); + ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE, 32); ms_streamingBufferSize /= 2; ms_pStreamingBuffer[1] = ms_pStreamingBuffer[0] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; #else - ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*2*CDSTREAM_SECTOR_SIZE, CDSTREAM_SECTOR_SIZE); + ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*2*CDSTREAM_SECTOR_SIZE, 32); ms_streamingBufferSize /= 2; ms_pStreamingBuffer[1] = ms_pStreamingBuffer[0] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; ms_pStreamingBuffer[2] = ms_pStreamingBuffer[1] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; @@ -217,17 +221,11 @@ CStreaming::Init2(void) // PC only, figure out how much memory we got #ifdef GTA_PC -#define MB (1024*1024) - - extern size_t _dwMemAvailPhys; - ms_memoryAvailable = (_dwMemAvailPhys - 10*MB)/2; - if(ms_memoryAvailable < 50*MB) - ms_memoryAvailable = 50*MB; - desiredNumVehiclesLoaded = (int32)((ms_memoryAvailable / MB - 50) / 3 + 12); + ms_memoryAvailable = STREAMING_MEM_SIZE; + desiredNumVehiclesLoaded = 12; //(int32)((ms_memoryAvailable / MB /*- 50*/) / 3 + 12); if(desiredNumVehiclesLoaded > MAXVEHICLESLOADED) desiredNumVehiclesLoaded = MAXVEHICLESLOADED; - debug("Memory allocated to Streaming is %zuMB", ms_memoryAvailable/MB); // original modifier was %d -#undef MB + debug("Memory allocated to Streaming is %.2fMB", ms_memoryAvailable/1024.f/1024.f); #endif // find island LODs @@ -372,17 +370,9 @@ CStreaming::LoadCdDirectory(void) #ifdef GTA_PC ms_imageOffsets[0] = 0; - ms_imageOffsets[1] = -1; - ms_imageOffsets[2] = -1; - ms_imageOffsets[3] = -1; - ms_imageOffsets[4] = -1; - ms_imageOffsets[5] = -1; - ms_imageOffsets[6] = -1; - ms_imageOffsets[7] = -1; - ms_imageOffsets[8] = -1; - ms_imageOffsets[9] = -1; - ms_imageOffsets[10] = -1; - ms_imageOffsets[11] = -1; + for(i = 1; i < NUMCDIMAGES; ++i) { + ms_imageOffsets[i] = -1; + } ms_imageSize = GetGTA3ImgSize(); // PS2 uses CFileMgr::GetCdFile on all IMG files to fill the array #endif @@ -390,7 +380,7 @@ CStreaming::LoadCdDirectory(void) i = CdStreamGetNumImages(); while(i-- >= 1){ strcpy(dirname, CdStreamGetImageName(i)); - strncpy(strrchr(dirname, '.') + 1, "DIR", 3); + strncpy(strrchr(dirname, '.') + 1, "DIR", 4); LoadCdDirectory(dirname, i); } @@ -399,10 +389,10 @@ CStreaming::LoadCdDirectory(void) } void -CStreaming::LoadCdDirectory(const char *dirname, int n) +CStreaming::LoadCdDirectory(const char *dirname, int32 n) { int fd, lastID, imgSelector; - int modelId, txdId; + int32 modelId, txdId; uint32 posn, size; CDirectory::DirectoryInfo direntry; char *dot; @@ -635,7 +625,11 @@ CStreaming::ConvertBufferToObject(int8 *buf, int32 streamId) if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED){ ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; #ifndef USE_CUSTOM_ALLOCATOR - ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + // Don't count textures here + if(streamId < STREAM_OFFSET_TXD) { + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + memory_logf("ConvertBufferToObject: Memory used: %d\n", ms_memoryUsed); + } #endif } @@ -701,7 +695,11 @@ CStreaming::FinishLoadingLargeFile(int8 *buf, int32 streamId) ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; // only done if success on PS2 #ifndef USE_CUSTOM_ALLOCATOR - ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + // Don't count textures here + if(streamId < STREAM_OFFSET_TXD) { + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + memory_logf("FinishLoadingLargeFile: Memory used: %d\n", ms_memoryUsed); + } #endif if(!success){ @@ -947,7 +945,10 @@ CStreaming::RemoveModel(int32 id) #ifdef USE_CUSTOM_ALLOCATOR UpdateMemoryUsed(); #else - ms_memoryUsed -= ms_aInfoForModel[id].GetCdSize()*CDSTREAM_SECTOR_SIZE; + if (id < STREAM_OFFSET_TXD) { + ms_memoryUsed -= ms_aInfoForModel[id].GetCdSize()*CDSTREAM_SECTOR_SIZE; + memory_logf("Remove Model: %d\n", ms_memoryUsed); + } #endif } @@ -1159,6 +1160,10 @@ found: return true; } +bool re3RemoveLeastUsedModel() { + return CStreaming::RemoveLeastUsedModel(); +} + bool CStreaming::RemoveLeastUsedModel(void) { @@ -1207,8 +1212,9 @@ CStreaming::RemoveReferencedTxds(size_t mem) CStreamingInfo *si; int streamId; - for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; si = si->m_prev){ + for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; ){ streamId = si - ms_aInfoForModel; + si = si->m_prev; if(streamId >= STREAM_OFFSET_TXD && CTxdStore::GetNumRefs(streamId-STREAM_OFFSET_TXD) == 0){ RemoveModel(streamId); @@ -1396,7 +1402,7 @@ CStreaming::LoadInitialPeds(void) void CStreaming::LoadInitialVehicles(void) { - int id; + int32 id; ms_numVehiclesLoaded = 0; ms_lastVehicleDeleted = 0; @@ -1410,7 +1416,7 @@ CStreaming::LoadInitialVehicles(void) void CStreaming::StreamVehiclesAndPeds(void) { - int i, model; + int32 i, model; static int timeBeforeNextLoad = 0; static int modelQualityClass = 0; @@ -2685,13 +2691,9 @@ void CStreaming::MakeSpaceFor(int32 size) { #ifdef FIX_BUGS -#define MB (1024 * 1024) if(ms_memoryAvailable == 0) { - extern size_t _dwMemAvailPhys; - ms_memoryAvailable = (_dwMemAvailPhys - 10 * MB) / 2; - if(ms_memoryAvailable < 50 * MB) ms_memoryAvailable = 50 * MB; + ms_memoryAvailable = STREAMING_MEM_SIZE; } -#undef MB #endif while(ms_memoryUsed >= ms_memoryAvailable - size) if(!RemoveLeastUsedModel()) { diff --git a/src/core/common.h b/src/core/common.h index 0d0528b1..f1b9cada 100644 --- a/src/core/common.h +++ b/src/core/common.h @@ -2,10 +2,8 @@ #define _CRT_SECURE_NO_WARNINGS #define _USE_MATH_DEFINES -#pragma warning(disable: 4244) // int to float -#pragma warning(disable: 4800) // int to bool -#pragma warning(disable: 4838) // narrowing conversion -#pragma warning(disable: 4996) // POSIX names + +#include "common_defines.h" #ifdef __MWERKS__ #define __STDC_LIMIT_MACROS // so we get UINT32_MAX etc @@ -17,7 +15,9 @@ #include #include -#include +#include +#include +#include #ifdef __MWERKS__ #define AUDIO_MSS @@ -82,12 +82,13 @@ #define rwVENDORID_ROCKSTAR 0x0253F2 -#define Max(a,b) ((a) > (b) ? (a) : (b)) -#define Min(a,b) ((a) < (b) ? (a) : (b)) +__always_inline auto Max(auto a, auto b) { return ((a > b)? a : b); } +__always_inline auto Min(auto a, auto b) { return ((a < b)? a : b); } // Use this to add const that wasn't there in the original code #define Const const +#ifndef RW_DC typedef uint8_t uint8; typedef int8_t int8; typedef uint16_t uint16; @@ -99,10 +100,14 @@ typedef int32_t int32; typedef unsigned int uint32; typedef int int32; #endif -typedef uintptr_t uintptr; -typedef intptr_t intptr; typedef uint64_t uint64; typedef int64_t int64; +#endif +#ifdef DC_SIM +#include "dc_hle_types.h" +#endif +typedef uintptr_t uintptr; +typedef intptr_t intptr; // hardcode ucs-2 typedef uint16_t wchar; @@ -139,12 +144,12 @@ typedef ptrdiff_t ssize_t; // PDP-10 like byte functions #define MASK(p, s) (((1<<(s))-1) << (p)) -inline uint32 dpb(uint32 b, uint32 p, uint32 s, uint32 w) +__always_inline uint32 dpb(uint32 b, uint32 p, uint32 s, uint32 w) { uint32 m = MASK(p,s); return (w & ~m) | ((b<>p & (1<(high) ? (high) : (v)) +template +__always_inline T Clamp(T v, auto low, auto high) { + return std::clamp(v, static_cast(low), static_cast(high)); +} -#define Clamp2(v, center, radius) ((v) > (center) ? Min(v, center + radius) : Max(v, center - radius)) +__always_inline auto Clamp2(auto v, auto center, auto radius) { + return (v > center) ? Min(v, center + radius) : Max(v, center - radius); +} -inline float sq(float x) { return x*x; } #define SQR(x) ((x) * (x)) +__always_inline auto sq(auto x) { return SQR(x); } #ifdef __MWERKS__ #define M_E 2.71828182845904523536 // e @@ -317,7 +327,10 @@ inline float sq(float x) { return x*x; } #define M_SQRT1_2 0.707106781186547524401 // 1/sqrt(2) #endif -#define PI (float)M_PI +#ifndef DC_SH4 +#define F_PI M_PI +#endif +#define PI (float)F_PI #define TWOPI (PI*2) #define HALFPI (PI/2) #define DEGTORAD(x) ((x) * PI / 180.0f) @@ -346,6 +359,14 @@ void re3_usererror(const char *format, ...); #define DEV(f, ...) re3_debug("[DEV]: " f, ## __VA_ARGS__) #endif +#ifndef WITH_LOGGING +#define printf(...) +#define perror(...) +#define re3_debug(...) +#define re3_trace(...) +#define re3_usererror(...) +#endif + #ifdef __MWERKS__ void debug(char *f, ...); void Error(char *f, ...); @@ -363,7 +384,10 @@ __inline__ void TRACE(char *f, ...) { } // this is re3 only, and so the function #endif #endif -#ifndef MASTER +#ifdef assert +#undef assert +#endif +#if !defined(MASTER) #define assert(_Expression) (void)( (!!(_Expression)) || (re3_assert(#_Expression, __FILE__, __LINE__, __FUNCTION__), 0) ) #else #define assert(_Expression) (_Expression) @@ -389,11 +413,18 @@ template struct check_size { #define PERCENT(x, p) ((float(x) * (float(p) / 100.0f))) #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#ifdef BIT +#undef BIT +#endif #define BIT(num) (1<<(num)) -#define ABS(a) (((a) < 0) ? (-(a)) : (a)) -#define norm(value, min, max) (((value) < (min)) ? 0 : (((value) > (max)) ? 1 : (((value) - (min)) / ((max) - (min))))) -#define lerp(norm, min, max) ( (norm) * ((max) - (min)) + (min) ) +#define ABS(a) std::abs(a) + +__always_inline auto norm(auto value, auto min, auto max) { + return (Clamp(value, min, max) - min) / (max - min); +} +// we use std::lerp now +//#define lerp(norm, min, max) ( (norm) * ((max) - (min)) + (min) ) #define STRINGIFY(x) #x #define STR(x) STRINGIFY(x) diff --git a/src/core/common_defines.h b/src/core/common_defines.h new file mode 100644 index 00000000..858a4077 --- /dev/null +++ b/src/core/common_defines.h @@ -0,0 +1,17 @@ +#pragma once + +#ifndef RW_DC +#pragma warning(disable: 4244) // int to float +#pragma warning(disable: 4800) // int to bool +#pragma warning(disable: 4838) // narrowing conversion +#pragma warning(disable: 4996) // POSIX names +#elif !defined(DC_TEXCONV) && !defined(DC_SIM) +#include +#define DC_SH4 +#else +#ifndef __always_inline +#define __always_inline inline +#endif +#define memcpy4 memcpy +#define dcache_pref_block(a) __builtin_prefetch(a) +#endif diff --git a/src/core/config.h b/src/core/config.h index 9f1981be..1da628f8 100644 --- a/src/core/config.h +++ b/src/core/config.h @@ -8,9 +8,9 @@ enum Config { NUMPLAYERS = 1, // 4 on PS2 - NUMCDIMAGES = 12, // gta3.img duplicates (not used on PC) - MAX_CDIMAGES = 8, // additional cdimages - MAX_CDCHANNELS = 5, + NUMCDIMAGES = 2, // gta3.img duplicates (not used on PC) + MAX_CDIMAGES = 2, // additional cdimages + MAX_CDCHANNELS = 2, MODELINFOSIZE = 5500, // 3150 on PS2 #ifdef VANILLA_DEFINES @@ -20,8 +20,11 @@ enum Config { #endif EXTRADIRSIZE = 128, CUTSCENEDIRSIZE = 512, - + #if !defined(RW_DC_SLOW) + SIMPLEMODELSIZE = 2916 /* RW_DC: is this enough? */, //5000, // 2910 on PS2 + #else SIMPLEMODELSIZE = 5000, // 2910 on PS2 + #endif MLOMODELSIZE = 1, MLOINSTANCESIZE = 1, TIMEMODELSIZE = 30, @@ -31,15 +34,26 @@ enum Config { XTRACOMPSMODELSIZE = 2, TWODFXSIZE = 2000, // 1210 on PS2 + #if !defined(RW_DC_SLOW) + MAXVEHICLESLOADED = 35, // 70 on mobile + #else MAXVEHICLESLOADED = 50, // 70 on mobile + #endif NUMOBJECTINFO = 168, // object.dat // Pool sizes NUMPTRNODES = 30000, // 26000 on PS2 NUMENTRYINFOS = 5400, // 3200 on PS2 + + #if !defined(RW_DC_SLOW) + NUMPEDS = 110, // 90 on PS2 + NUMVEHICLES = 90, // 70 on PS2 + #else NUMPEDS = 140, // 90 on PS2 NUMVEHICLES = 110, // 70 on PS2 + #endif + NUMBUILDINGS = 5500, // 4915 on PS2 NUMTREADABLES = 1214, NUMOBJECTS = 450, @@ -166,15 +180,14 @@ enum Config { # define RANDOMSPLASH # define USE_CUSTOM_ALLOCATOR # define VU_COLLISION -# define ANIM_COMPRESSION # define PS2_MENU #elif defined GTA_PC # define EXTERNAL_3D_SOUND -# define AUDIO_REFLECTIONS +// # define AUDIO_REFLECTIONS # ifndef GTA_HANDHELD # define PC_PLAYER_CONTROLS // mouse player/cam mode # endif -# define GTA_REPLAY +// # define GTA_REPLAY # define GTA_SCENE_EDIT # define PC_MENU #elif defined GTA_XBOX @@ -232,8 +245,7 @@ enum Config { // Memory allocation and compression // #define USE_CUSTOM_ALLOCATOR // use CMemoryHeap for allocation. use with care, not finished yet -//#define COMPRESSED_COL_VECTORS // use compressed vectors for collision vertices -//#define ANIM_COMPRESSION // only keep most recently used anims uncompressed +#define COMPRESSED_COL_VECTORS // use compressed vectors for collision vertices #if defined GTA_PC && defined GTA_PS2_STUFF # define USE_PS2_RAND @@ -265,9 +277,9 @@ enum Config { #endif #define FIX_BUGS // fixes bugs that we've came across during reversing. You can undefine this only on release builds. -#define MORE_LANGUAGES // Add more translations to the game +// #define MORE_LANGUAGES // Add more translations to the game #define COMPATIBLE_SAVES // this allows changing structs while keeping saves compatible, and keeps saves compatible between platforms, needs to be enabled on 64bit builds! -#define FIX_INCOMPATIBLE_SAVES // try to fix incompatible saves, requires COMPATIBLE_SAVES +// #define FIX_INCOMPATIBLE_SAVES // try to fix incompatible saves, requires COMPATIBLE_SAVES #define LOAD_INI_SETTINGS // as the name suggests. fundamental for CUSTOM_FRONTEND_OPTIONS #define NO_MOVIES // add option to disable intro videos @@ -291,16 +303,16 @@ enum Config { #endif // Rendering/display -//#define EXTRA_MODEL_FLAGS // from mobile to optimize rendering -//# define HARDCODED_MODEL_FLAGS // sets the flags enabled above from hardcoded model names. +#define EXTRA_MODEL_FLAGS // from mobile to optimize rendering +# define HARDCODED_MODEL_FLAGS // sets the flags enabled above from hardcoded model names. // NB: keep this enabled unless your map IDEs have these flags baked in #define ASPECT_RATIO_SCALE // Not just makes everything scale with aspect ratio, also adds support for all aspect ratios #define PROPER_SCALING // use original DEFAULT_SCREEN_WIDTH/DEFAULT_SCREEN_HEIGHT from PS2 instead of PC(R* changed HEIGHT here to make radar look better, but broke other hud elements aspect ratio). #define DEFAULT_NATIVE_RESOLUTION // Set default video mode to your native resolution (fixes Windows 10 launch) -#define USE_TXD_CDIMAGE // generate and load textures from txd.img -#define PS2_ALPHA_TEST // emulate ps2 alpha test +// #define USE_TXD_CDIMAGE // generate and load textures from txd.img +// #define PS2_ALPHA_TEST // emulate ps2 alpha test #define IMPROVED_VIDEOMODE // save and load videomode parameters instead of a magic number -#define DISABLE_LOADING_SCREEN // disable the loading screen which vastly improves the loading time +// #define DISABLE_LOADING_SCREEN // disable the loading screen which vastly improves the loading time #ifdef DISABLE_LOADING_SCREEN // enable the PC splash #undef RANDOMSPLASH @@ -309,10 +321,10 @@ enum Config { #define ANISOTROPIC_FILTERING // set all textures to max anisotropic filtering //#define USE_TEXTURE_POOL #ifdef LIBRW -#define EXTENDED_COLOURFILTER // more options for colour filter (replaces mblur) -#define EXTENDED_PIPELINES // custom render pipelines (includes Neo) -#define SCREEN_DROPLETS // neo water droplets -#define NEW_RENDERER // leeds-like world rendering, needs librw +// #define EXTENDED_COLOURFILTER // more options for colour filter (replaces mblur) +// #define EXTENDED_PIPELINES // custom render pipelines (includes Neo) +// #define SCREEN_DROPLETS // neo water droplets +//#define NEW_RENDERER // leeds-like world rendering, needs librw #endif #define FIX_SPRITES // fix sprites aspect ratio(moon, coronas, particle etc) @@ -382,7 +394,7 @@ enum Config { #define USE_MEASUREMENTS_IN_METERS // makes game use meters instead of feet in script #define USE_PRECISE_MEASUREMENT_CONVERTION // makes game convert feet to meeters more precisely #ifdef PC_MENU -# define MISSION_REPLAY // mobile feature +//# define MISSION_REPLAY // mobile feature #endif //#define SIMPLIER_MISSIONS // apply simplifications from mobile #define USE_ADVANCED_SCRIPT_DEBUG_OUTPUT @@ -427,15 +439,15 @@ enum Config { #define FREE_CAM // Rotating cam // Audio -#define EXTERNAL_3D_SOUND // use external engine to simulate 3d audio spatialization. OpenAL would not work without it (because it works in a 3d space +//#define EXTERNAL_3D_SOUND // use external engine to simulate 3d audio spatialization. OpenAL would not work without it (because it works in a 3d space // originally and making it work in 2d only requires more resource). Will not work on PS2 #define AUDIO_REFLECTIONS // Enable audio reflections. Disabled on mobile, didn't exist yet on PS2. #define RADIO_SCROLL_TO_PREV_STATION -#define AUDIO_CACHE +//#define AUDIO_CACHE #define PS2_AUDIO_CHANNELS // increases the maximum number of audio channels to PS2 value of 44 (PC has 28 originally) -#define PS2_AUDIO_PATHS // changes audio paths for cutscenes and radio to PS2 paths (needs vbdec on MSS builds) +// #define PS2_AUDIO_PATHS // changes audio paths for cutscenes and radio to PS2 paths (needs vbdec on MSS builds) //#define AUDIO_OAL_USE_SNDFILE // use libsndfile to decode WAVs instead of our internal decoder -#define AUDIO_OAL_USE_MPG123 // use mpg123 to support mp3 files +// #define AUDIO_OAL_USE_MPG123 // use mpg123 to support mp3 files #define PAUSE_RADIO_IN_FRONTEND // pause radio when game is paused #define ATTACH_RELEASING_SOUNDS_TO_ENTITIES // sounds would follow ped and vehicles coordinates if not being queued otherwise #define USE_TIME_SCALE_FOR_AUDIO // slow down/speed up sounds according to the speed of the game @@ -456,15 +468,16 @@ enum Config { // Streaming #if !defined(_WIN32) && !defined(__SWITCH__) //#define ONE_THREAD_PER_CHANNEL // Don't use if you're not on SSD/Flash - also not utilized too much right now(see commented LoadAllRequestedModels in Streaming.cpp) - #define FLUSHABLE_STREAMING // Make it possible to interrupt reading when processing file isn't needed anymore. + // Dreamcast doesn't support aborts on read, does it? + //#define FLUSHABLE_STREAMING // Make it possible to interrupt reading when processing file isn't needed anymore. #endif #define BIG_IMG // Not complete - allows to read larger img files -//#define SQUEEZE_PERFORMANCE +#define SQUEEZE_PERFORMANCE #ifdef SQUEEZE_PERFORMANCE #undef PS2_ALPHA_TEST #undef NO_ISLAND_LOADING - #undef PS2_AUDIO_CHANNELS + // #undef PS2_AUDIO_CHANNELS #undef EXTENDED_OFFSCREEN_DESPAWN_RANGE #define PC_PARTICLE #define VC_PED_PORTS // To not process collisions always. But should be tested if that's really beneficial diff --git a/src/core/main.cpp b/src/core/main.cpp index 2a0a77ca..4b536d3d 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -352,6 +352,7 @@ DoFade(void) } } +#if 0 // not used in dca3, RsGrabScreen is not working either way. bool RwGrabScreen(RwCamera *camera, RwChar *filename) { @@ -374,6 +375,7 @@ RwGrabScreen(RwCamera *camera, RwChar *filename) RwImageDestroy(pImage); return result; } +#endif #define TILE_WIDTH 576 #define TILE_HEIGHT 432 @@ -401,11 +403,13 @@ DoRWStuffEndOfFrame(void) } } #else +#if 0 if (CPad::GetPad(1)->GetLeftShockJustDown() || CPad::GetPad(0)->GetFJustDown(11)) { sprintf(s, "screen_%011lld.png", time(nil)); RwGrabScreen(Scene.camera, s); } #endif +#endif #endif // !MASTER } @@ -1090,11 +1094,12 @@ return; y += 12.0f; } +bool bDisplayPosn = false; +bool bDisplayRate = false; + void DisplayGameDebugText() { - static bool bDisplayPosn = false; - static bool bDisplayRate = false; #ifndef FINAL { SETTWEAKPATH("Debug"); @@ -1226,7 +1231,7 @@ DisplayGameDebugText() //NOTE: fps should be 30, but its 29 due to different fp2int conversion if ( bDisplayRate ) - sprintf(str, "X:%5.1f, Y:%5.1f, Z:%5.1f, F-%d, %s", pos.x, pos.y, pos.z, (int32)FramesPerSecond, ZonePrint[ZoneId].name); + sprintf(str, "X:%5.1f, Y:%5.1f, Z:%5.1f, F-%.2f, %s", pos.x, pos.y, pos.z, FramesPerSecond, ZonePrint[ZoneId].name); else sprintf(str, "X:%5.1f, Y:%5.1f, Z:%5.1f, %s", pos.x, pos.y, pos.z, ZonePrint[ZoneId].name); @@ -1886,6 +1891,7 @@ AppEventHandler(RsEvent event, void *param) } } +#if !defined(RW_DC) #ifndef MASTER void TheModelViewer(void) @@ -1917,6 +1923,7 @@ TheModelViewer(void) #endif } #endif +#endif #ifdef GTA_PS2 diff --git a/src/core/re3.cpp b/src/core/re3.cpp index a721c238..cf5036a7 100644 --- a/src/core/re3.cpp +++ b/src/core/re3.cpp @@ -1,4 +1,5 @@ #include +#include #define WITHWINDOWS #include "common.h" #if defined DETECT_JOYSTICK_MENU && defined XINPUT @@ -49,10 +50,14 @@ #include "crossplatform.h" #ifndef _WIN32 +#ifndef RW_DC #include "assert.h" +#endif #include #endif +#include "../vmu/vmu.h" + #ifdef RWLIBS extern "C" int vsprintf(char* const _Buffer, char const* const _Format, va_list _ArgList); #endif @@ -184,7 +189,13 @@ CustomFrontendOptionsPopulate(void) #define MINI_CASE_SENSITIVE #include "ini.h" -mINI::INIFile ini("re3.ini"); +mINI::INIFile ini( +#if defined(DC_SIM) + "re3.ini" +#else + "/vmu/" VMU_DEFAULT_PATH "/re3ini" +#endif +); mINI::INIStructure cfg; bool ReadIniIfExists(const char *cat, const char *key, uint32 *out) @@ -292,6 +303,13 @@ void StoreIni(const char *cat, const char *key, int8 val) cfg[cat][key] = temp; } +void StoreIni(const char *cat, const char *key, bool val) +{ + char temp[11]; + sprintf(temp, "%d", val); + cfg[cat][key] = temp; +} + void StoreIni(const char *cat, const char *key, float val) { char temp[50]; @@ -466,13 +484,19 @@ void SaveINIControllerSettings() #endif StoreIni("Controller", "PadButtonsInited", ControlsManager.ms_padButtonsInited); - ini.write(cfg); + { + RAIIVmuBeep(VMU_DEFAULT_PATH, 1.0f); + ini.write(cfg); + } } bool LoadINISettings() { - if (!ini.read(cfg)) - return false; + { + RAIIVmuBeep(VMU_DEFAULT_PATH, 1.0f); + if (!ini.read(cfg)) + return false; + } #ifdef IMPROVED_VIDEOMODE ReadIniIfExists("VideoMode", "Width", &FrontEndMenuManager.m_nPrefsWidth); @@ -793,7 +817,7 @@ ResetCamStatics(void) } #ifdef MISSION_SWITCHER -int8 nextMissionToSwitch = 0; +uint32 nextMissionToSwitch = 0; static void SwitchToMission(void) { @@ -915,8 +939,8 @@ DebugMenuPopulate(void) DebugMenuAddCmd("Cheats", "Strong grip", StrongGripCheat); DebugMenuAddCmd("Cheats", "Nasty limbs", NastyLimbsCheat); - static int spawnCarId = MI_LANDSTAL; - e = DebugMenuAddVar("Spawn", "Spawn Car ID", &spawnCarId, nil, 1, MI_LANDSTAL, MI_GHOST, carnames); + static int32 spawnCarId = MI_LANDSTAL; + e = DebugMenuAddVar("Spawn", "Spawn Car ID", (int32_t*)&spawnCarId, nil, 1, MI_LANDSTAL, MI_GHOST, carnames); DebugMenuEntrySetWrap(e, true); DebugMenuAddCmd("Spawn", "Spawn Car", [](){ if(spawnCarId == MI_TRAIN || @@ -959,8 +983,8 @@ DebugMenuPopulate(void) DebugMenuAddVarBool8("Render", "Fix Sprites", &CDraw::ms_bFixSprites, nil); #endif DebugMenuAddVarBool8("Render", "PS2 Alpha test Emu", &gPS2alphaTest, nil); - DebugMenuAddVarBool8("Render", "Frame limiter", &FrontEndMenuManager.m_PrefsFrameLimiter, nil); - DebugMenuAddVarBool8("Render", "VSynch", &FrontEndMenuManager.m_PrefsVsync, nil); + DebugMenuAddVarBool8("Render", "Frame limiter", (int8_t*)&FrontEndMenuManager.m_PrefsFrameLimiter, nil); + DebugMenuAddVarBool8("Render", "VSynch", (int8_t*)&FrontEndMenuManager.m_PrefsVsync, nil); DebugMenuAddVar("Render", "Max FPS", &RsGlobal.maxFPS, nil, 1, 1, 1000, nil); #ifdef NEW_RENDERER DebugMenuAddVarBool8("Render", "New Renderer", &gbNewRenderer, nil); @@ -988,7 +1012,7 @@ extern bool gbRenderWorld2; #ifdef EXTENDED_COLOURFILTER static const char *filternames[] = { "None", "Simple", "Normal", "Mobile" }; - e = DebugMenuAddVar("Render", "Colourfilter", &CPostFX::EffectSwitch, nil, 1, CPostFX::POSTFX_OFF, CPostFX::POSTFX_MOBILE, filternames); + e = DebugMenuAddVar("Render", "Colourfilter", (int32_t*)&CPostFX::EffectSwitch, nil, 1, CPostFX::POSTFX_OFF, CPostFX::POSTFX_MOBILE, filternames); DebugMenuEntrySetWrap(e, true); DebugMenuAddVar("Render", "Intensity", &CPostFX::Intensity, nil, 0.05f, 0, 10.0f); DebugMenuAddVarBool8("Render", "Motion Blur", &CPostFX::MotionBlurOn, nil); @@ -1080,7 +1104,7 @@ extern bool gbRenderWorld2; "Uzi Money", "Toyminator", "Rigged To Blow", "Bullion Run", "Rumble", "The Exchange" }; - missionEntry = DebugMenuAddVar("Game", "Select mission", &nextMissionToSwitch, nil, 1, 0, ARRAY_SIZE(missions) - 1, missions); + missionEntry = DebugMenuAddVar("Game", "Select mission", (uint32_t*)&nextMissionToSwitch, nil, 1, 0, ARRAY_SIZE(missions) - 1, missions); DebugMenuEntrySetWrap(missionEntry, true); DebugMenuAddCmd("Game", "Start selected mission ", SwitchToMission); #endif @@ -1110,6 +1134,8 @@ const int re3_buffsize = 1024; static char re3_buff[re3_buffsize]; #endif +extern void stacktrace(); + #ifndef MASTER void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func) { @@ -1160,13 +1186,27 @@ void re3_assert(const char *expr, const char *filename, unsigned int lineno, con abort(); #else // TODO - printf("\nRE3 ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); - assert(false); + fflush(stdout); + fflush(stderr); + dbglog(DBG_CRITICAL, "\nRE3 ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); + dbglog(DBG_CRITICAL, "POSIX error (may not be relevant): %s\n", strerror(errno)); + #if defined(DC_SIM) || defined(DC_TEXCONV) + for(;;); + #else + stacktrace(); + dbgio_dev_select("fb"); + sleep(1); + dbgio_printf("RE3 ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); + dbgio_printf("POSIX error (may not be relevant): %s\n", strerror(errno)); + stacktrace(); + dbgio_flush(); + abort(); + #endif #endif } #endif -void re3_debug(const char *format, ...) +void (re3_debug)(const char *format, ...) { #ifndef MASTER va_list va; @@ -1184,7 +1224,7 @@ void re3_debug(const char *format, ...) } #ifndef MASTER -void re3_trace(const char *filename, unsigned int lineno, const char *func, const char *format, ...) +void (re3_trace)(const char *filename, unsigned int lineno, const char *func, const char *format, ...) { char buff[re3_buffsize *2]; va_list va; @@ -1206,7 +1246,7 @@ void re3_trace(const char *filename, unsigned int lineno, const char *func, cons #endif #ifndef MASTER -void re3_usererror(const char *format, ...) +void (re3_usererror)(const char *format, ...) { va_list va; va_start(va, format); diff --git a/src/core/timebars.cpp b/src/core/timebars.cpp index 94051b25..f66dff52 100644 --- a/src/core/timebars.cpp +++ b/src/core/timebars.cpp @@ -97,7 +97,7 @@ void tbDisplay() AsciiToUnicode(temp, wtemp); CFont::SetColor(CRGBA(255, 255, 255, 255)); if (!CMenuManager::m_PrefsMarketing || !CMenuManager::m_PrefsDisableTutorials) { - CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * (4.0f / DEFAULT_SCREEN_HEIGHT), wtemp); + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * (12.0f / DEFAULT_SCREEN_HEIGHT), wtemp); #ifndef FINAL // Timers output (my own implementation) @@ -105,7 +105,7 @@ void tbDisplay() MaxTimes[i] = Max(MaxTimes[i], TimerBar.Timers[i].endTime - TimerBar.Timers[i].startTime); sprintf(temp, "%s: %.2f", &TimerBar.Timers[i].name[0], MaxTimes[i]); AsciiToUnicode(temp, wtemp); - CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * ((8.0f * (i + 2)) / DEFAULT_SCREEN_HEIGHT), wtemp); + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * ((12.0f * (i + 2)) / DEFAULT_SCREEN_HEIGHT), wtemp); } #ifdef FRAMETIME @@ -113,7 +113,7 @@ void tbDisplay() sprintf(temp, "Frame Time: %.2f", MaxFrameTime); AsciiToUnicode(temp, wtemp); - CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * ((8.0f * (TimerBar.count + 4)) / DEFAULT_SCREEN_HEIGHT), wtemp); + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * ((12.0f * (TimerBar.count + 4)) / DEFAULT_SCREEN_HEIGHT), wtemp); #endif // FRAMETIME #endif // !FINAL } diff --git a/src/extras/debugmenu.h b/src/extras/debugmenu.h index 45b65d04..b2c69dd4 100644 --- a/src/extras/debugmenu.h +++ b/src/extras/debugmenu.h @@ -156,21 +156,21 @@ void DebugMenuRender(void); // Some overloads for simplicity inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings) -{ return DebugMenuAddInt8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +{ return DebugMenuAddInt8(path, name, (int8*)ptr, triggerFunc, step, lowerBound, upperBound, strings); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings) { return DebugMenuAddInt16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings) -{ return DebugMenuAddInt32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +{ return DebugMenuAddInt32(path, name, (int32*)ptr, triggerFunc, step, lowerBound, upperBound, strings); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings) -{ return DebugMenuAddInt64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +{ return DebugMenuAddInt64(path, name, (int64*)ptr, triggerFunc, step, lowerBound, upperBound, strings); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings) { return DebugMenuAddUInt8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings) { return DebugMenuAddUInt16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings) -{ return DebugMenuAddUInt32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +{ return DebugMenuAddUInt32(path, name, (uint32*)ptr, triggerFunc, step, lowerBound, upperBound, strings); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings) -{ return DebugMenuAddUInt64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +{ return DebugMenuAddUInt64(path, name, (uint64*)ptr, triggerFunc, step, lowerBound, upperBound, strings); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound) { return DebugMenuAddFloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound) diff --git a/src/extras/re3_inttypes.h b/src/extras/re3_inttypes.h index bf0c53e2..362976c6 100644 --- a/src/extras/re3_inttypes.h +++ b/src/extras/re3_inttypes.h @@ -1,3 +1,5 @@ +#ifndef __DREAMCAST__ + #define PRId8 "hhd" #define PRId16 "hd" #define PRId32 "ld" @@ -213,4 +215,6 @@ #define SCNXLEAST64 "llX" #define SCNXMAX "llX" -#define SCNXPTR "llX" \ No newline at end of file +#define SCNXPTR "llX" + +#endif \ No newline at end of file diff --git a/src/fakerw/fake.cpp b/src/fakerw/fake.cpp index 6dfebb39..e4ed01ee 100644 --- a/src/fakerw/fake.cpp +++ b/src/fakerw/fake.cpp @@ -573,7 +573,7 @@ RwBool RwEngineOpen(RwEngineOpenParams *initParams) { return Engine::open(&openParams); } RwBool RwEngineStart(void) { - rw::d3d::isP8supported = false; + // rw::d3d::isP8supported = false; return Engine::start(); } RwBool RwEngineStop(void) { Engine::stop(); return true; } @@ -751,7 +751,7 @@ RwInt32 RpClumpGetNumAtomics(RpClump * clump) { return clump->countAtomics(); } //RwInt32 RpClumpGetNumLights(RpClump * clump); //RwInt32 RpClumpGetNumCameras(RpClump * clump); RpClump *RpClumpStreamRead(RwStream * stream) { return rw::Clump::streamRead(stream); } -//RpClump *RpClumpStreamWrite(RpClump * clump, RwStream * stream); +RwBool RpClumpStreamWrite(RpClump * clump, RwStream * stream) { return clump->streamWrite(stream); } RwInt32 RpClumpRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) { return Clump::registerPlugin(size, pluginID, constructCB, destructCB, (CopyConstructor)copyCB); } RwInt32 RpClumpRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) @@ -805,7 +805,7 @@ RwBool RpWorldPluginAttach(void) { registerMaterialRightsPlugin(); // not sure if this goes here - rw::xbox::registerVertexFormatPlugin(); + // rw::xbox::registerVertexFormatPlugin(); return true; } @@ -878,11 +878,11 @@ RpHAnimHierarchy *RpHAnimFrameGetHierarchy(RwFrame *frame) { return HAnimHierarc RpHAnimHierarchy *RpHAnimHierarchySetFlags(RpHAnimHierarchy *hierarchy, RpHAnimHierarchyFlag flags) { hierarchy->flags = flags; return hierarchy; } -RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim) { hierarchy->interpolator->setCurrentAnim(anim); return true; } -RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time) { hierarchy->interpolator->addTime(time); return true; } +RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim) { if (hierarchy) { hierarchy->interpolator->setCurrentAnim(anim); } return true; } +RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time) { if (hierarchy) { hierarchy->interpolator->addTime(time); } return true; } -RwMatrix *RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy *hierarchy) { return hierarchy->matrices; } -RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy *hierarchy) { hierarchy->updateMatrices(); return true; } +RwMatrix *RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy *hierarchy) { return hierarchy ? hierarchy->matrices : nullptr; } +RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy *hierarchy) { if (hierarchy) { hierarchy->updateMatrices(); } return true; } RpHAnimAnimation *RpHAnimAnimationCreate(RwInt32 typeID, RwInt32 numFrames, RwInt32 flags, RwReal duration) { return Animation::create(AnimInterpolatorInfo::find(typeID), numFrames, flags, duration); } @@ -909,6 +909,7 @@ RpSkin *RpSkinGeometryGetSkin( RpGeometry *geometry ) { return Skin::get(geometr RpAtomic *RpSkinAtomicSetHAnimHierarchy( RpAtomic *atomic, RpHAnimHierarchy *hierarchy ) { Skin::setHierarchy(atomic, hierarchy); return atomic; } RpHAnimHierarchy *RpSkinAtomicGetHAnimHierarchy( const RpAtomic *atomic ) { return Skin::getHierarchy(atomic); } +#if 0 // not used in dca3 RwImage * RtBMPImageWrite(RwImage *image, const RwChar *imageName) { @@ -945,7 +946,6 @@ RtBMPImageRead(const RwChar *imageName) #endif } - RwImage * RtPNGImageWrite(RwImage *image, const RwChar *imageName) { @@ -963,6 +963,7 @@ RtPNGImageWrite(RwImage *image, const RwChar *imageName) #endif return image; } + RwImage * RtPNGImageRead(const RwChar *imageName) { @@ -981,6 +982,7 @@ RtPNGImageRead(const RwChar *imageName) return rw::readPNG(imageName); #endif } +#endif #include "rtquat.h" diff --git a/src/fakerw/rpworld.h b/src/fakerw/rpworld.h index f10a3754..e5d4de97 100644 --- a/src/fakerw/rpworld.h +++ b/src/fakerw/rpworld.h @@ -251,7 +251,7 @@ RwInt32 RpClumpGetNumLights(RpClump * clump); RwInt32 RpClumpGetNumCameras(RpClump * clump); RwUInt32 RpClumpStreamGetSize(RpClump * clump); RpClump *RpClumpStreamRead(RwStream * stream); -RpClump *RpClumpStreamWrite(RpClump * clump, RwStream * stream); +RwBool RpClumpStreamWrite(RpClump * clump, RwStream * stream); RwInt32 RpClumpRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); RwInt32 RpClumpRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); RwInt32 RpClumpSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); diff --git a/src/fakerw/rtbmp.h b/src/fakerw/rtbmp.h index 896d276b..f761ef37 100644 --- a/src/fakerw/rtbmp.h +++ b/src/fakerw/rtbmp.h @@ -1,4 +1,6 @@ #pragma once +#if 0 // not used in dca3 RwImage *RtBMPImageWrite(RwImage * image, const RwChar * imageName); RwImage *RtBMPImageRead(const RwChar * imageName); +#endif \ No newline at end of file diff --git a/src/fakerw/rtpng.h b/src/fakerw/rtpng.h index 80f29020..aaadf998 100644 --- a/src/fakerw/rtpng.h +++ b/src/fakerw/rtpng.h @@ -1,4 +1,5 @@ #pragma once - +#if 0 // not used in dca3 RwImage *RtPNGImageWrite(RwImage * image, const RwChar * imageName); RwImage *RtPNGImageRead(const RwChar * imageName); +#endif \ No newline at end of file diff --git a/src/math/Matrix.cpp b/src/math/Matrix.cpp index b11e8a1c..88433b26 100644 --- a/src/math/Matrix.cpp +++ b/src/math/Matrix.cpp @@ -176,8 +176,7 @@ CMatrix::SetTranslate(float x, float y, float z) void CMatrix::SetRotateXOnly(float angle) { - float c = Cos(angle); - float s = Sin(angle); + auto [s, c] = SinCos(angle); rx = 1.0f; ry = 0.0f; @@ -195,8 +194,7 @@ CMatrix::SetRotateXOnly(float angle) void CMatrix::SetRotateYOnly(float angle) { - float c = Cos(angle); - float s = Sin(angle); + auto [s, c] = SinCos(angle); rx = c; ry = 0.0f; @@ -214,8 +212,7 @@ CMatrix::SetRotateYOnly(float angle) void CMatrix::SetRotateZOnly(float angle) { - float c = Cos(angle); - float s = Sin(angle); + auto [s, c] = SinCos(angle); rx = c; ry = s; @@ -261,12 +258,9 @@ CMatrix::SetRotateZ(float angle) void CMatrix::SetRotate(float xAngle, float yAngle, float zAngle) { - float cX = Cos(xAngle); - float sX = Sin(xAngle); - float cY = Cos(yAngle); - float sY = Sin(yAngle); - float cZ = Cos(zAngle); - float sZ = Sin(zAngle); + auto [sX, cX] = SinCos(xAngle); + auto [sY, cY] = SinCos(yAngle); + auto [sZ, cZ] = SinCos(zAngle); rx = cZ * cY - (sZ * sX) * sY; ry = (cZ * sX) * sY + sZ * cY; @@ -288,8 +282,12 @@ CMatrix::SetRotate(float xAngle, float yAngle, float zAngle) void CMatrix::RotateX(float x) { - float c = Cos(x); - float s = Sin(x); +#ifdef DC_SH4 + mat_load(reinterpret_cast(this)); + mat_rotate_x(x); + mat_store(reinterpret_cast(this)); +#else + auto [s, c] = SinCos(x); float ry = this->ry; float rz = this->rz; @@ -308,13 +306,18 @@ CMatrix::RotateX(float x) this->uz = c * az + s * ay; this->py = c * py - s * pz; this->pz = c * pz + s * py; +#endif } void CMatrix::RotateY(float y) { - float c = Cos(y); - float s = Sin(y); +#ifdef DC_SH4 + mat_load(reinterpret_cast(this)); + mat_rotate_y(y); + mat_store(reinterpret_cast(this)); +#else + auto [s, c] = SinCos(y); float rx = this->rx; float rz = this->rz; @@ -333,13 +336,18 @@ CMatrix::RotateY(float y) this->uz = c * az - s * ax; this->px = c * px + s * pz; this->pz = c * pz - s * px; +#endif } void CMatrix::RotateZ(float z) { - float c = Cos(z); - float s = Sin(z); +#ifdef DC_SH4 + mat_load(reinterpret_cast(this)); + mat_rotate_z(z); + mat_store(reinterpret_cast(this)); +#else + auto [s, c] = SinCos(z); float ry = this->ry; float rx = this->rx; @@ -358,18 +366,20 @@ CMatrix::RotateZ(float z) this->uy = c * ay + s * ax; this->px = c * px - s * py; this->py = c * py + s * px; - +#endif } void CMatrix::Rotate(float x, float y, float z) { - float cX = Cos(x); - float sX = Sin(x); - float cY = Cos(y); - float sY = Sin(y); - float cZ = Cos(z); - float sZ = Sin(z); +#ifdef DC_SH4 + mat_load(reinterpret_cast(this)); + mat_rotate(x, y, z); + mat_store(reinterpret_cast(this)); +#else + auto [sX, cX] = SinCos(x); + auto [sY, cY] = SinCos(y); + auto [sZ, cZ] = SinCos(z); float rx = this->rx; float ry = this->ry; @@ -406,6 +416,7 @@ CMatrix::Rotate(float x, float y, float z) this->px = x1 * px + y1 * py + z1 * pz; this->py = x2 * px + y2 * py + z2 * pz; this->pz = x3 * px + y3 * py + z3 * pz; +#endif } CMatrix & @@ -429,11 +440,71 @@ CMatrix::Reorthogonalise(void) f = CrossProduct(u, r); } +#ifdef DC_SH4 +static __always_inline void MATH_Load_Matrix_Product(const matrix_t* matrix1, const matrix_t* matrix2) +{ + unsigned int prefetch_scratch; + + asm volatile ( + "mov %[bmtrx], %[pref_scratch]\n\t" // (MT) + "add #32, %[pref_scratch]\n\t" // offset by 32 (EX - flow dependency, but 'add' is actually parallelized since 'mov Rm, Rn' is 0-cycle) + "fschg\n\t" // switch fmov to paired moves (note: only paired moves can access XDn regs) (FE) + "pref @%[pref_scratch]\n\t" // Get a head start prefetching the second half of the 64-byte data (LS) + // back matrix + "fmov.d @%[bmtrx]+, XD0\n\t" // (LS) + "fmov.d @%[bmtrx]+, XD2\n\t" + "fmov.d @%[bmtrx]+, XD4\n\t" + "fmov.d @%[bmtrx]+, XD6\n\t" + "pref @%[fmtrx]\n\t" // prefetch fmtrx now while we wait (LS) + "fmov.d @%[bmtrx]+, XD8\n\t" // bmtrx prefetch should work for here + "fmov.d @%[bmtrx]+, XD10\n\t" + "fmov.d @%[bmtrx]+, XD12\n\t" + "mov %[fmtrx], %[pref_scratch]\n\t" // (MT) + "add #32, %[pref_scratch]\n\t" // store offset by 32 in r0 (EX - flow dependency, but 'add' is actually parallelized since 'mov Rm, Rn' is 0-cycle) + "fmov.d @%[bmtrx], XD14\n\t" + "pref @%[pref_scratch]\n\t" // Get a head start prefetching the second half of the 64-byte data (LS) + // front matrix + // interleave loads and matrix multiply 4x4 + "fmov.d @%[fmtrx]+, DR0\n\t" + "fmov.d @%[fmtrx]+, DR2\n\t" + "fmov.d @%[fmtrx]+, DR4\n\t" // (LS) want to issue the next one before 'ftrv' for parallel exec + "ftrv XMTRX, FV0\n\t" // (FE) + + "fmov.d @%[fmtrx]+, DR6\n\t" + "fmov.d @%[fmtrx]+, DR8\n\t" + "ftrv XMTRX, FV4\n\t" + + "fmov.d @%[fmtrx]+, DR10\n\t" + "fmov.d @%[fmtrx]+, DR12\n\t" + "ftrv XMTRX, FV8\n\t" + + "fmov.d @%[fmtrx], DR14\n\t" // (LS, but this will stall 'ftrv' for 3 cycles) + "fschg\n\t" // switch back to single moves (and avoid stalling 'ftrv') (FE) + "ftrv XMTRX, FV12\n\t" // (FE) + // Save output in XF regs + "frchg\n" + : [bmtrx] "+&r" ((unsigned int)matrix1), [fmtrx] "+r" ((unsigned int)matrix2), [pref_scratch] "=&r" (prefetch_scratch) // outputs, "+" means r/w, "&" means it's written to before all inputs are consumed + : // no inputs + : "fr0", "fr1", "fr2", "fr3", "fr4", "fr5", "fr6", "fr7", "fr8", "fr9", "fr10", "fr11", "fr12", "fr13", "fr14", "fr15" // clobbers (GCC doesn't know about back bank, so writing to it isn't clobbered) + ); +} +#endif + CMatrix operator*(const CMatrix &m1, const CMatrix &m2) { // TODO: VU0 code CMatrix out; +#if defined(RW_DC) && 0 // THIS IS BROKEN, 4th element shouldn't be processed +# ifdef DC_SH4 + MATH_Load_Matrix_Product(reinterpret_cast(&m1), reinterpret_cast(&m2)); + +# elif defined(RW_DC) + mat_load(reinterpret_cast(&m2)); + mat_apply(reinterpret_cast(&m1)); +# endif + mat_store(reinterpret_cast(&out)); +#else out.rx = m1.rx * m2.rx + m1.fx * m2.ry + m1.ux * m2.rz; out.ry = m1.ry * m2.rx + m1.fy * m2.ry + m1.uy * m2.rz; out.rz = m1.rz * m2.rx + m1.fz * m2.ry + m1.uz * m2.rz; @@ -446,6 +517,7 @@ operator*(const CMatrix &m1, const CMatrix &m2) out.px = m1.rx * m2.px + m1.fx * m2.py + m1.ux * m2.pz + m1.px; out.py = m1.ry * m2.px + m1.fy * m2.py + m1.uy * m2.pz + m1.py; out.pz = m1.rz * m2.px + m1.fz * m2.py + m1.uz * m2.pz + m1.pz; +#endif return out; } diff --git a/src/math/Matrix.h b/src/math/Matrix.h index 6404b506..c1938449 100644 --- a/src/math/Matrix.h +++ b/src/math/Matrix.h @@ -5,8 +5,8 @@ class CMatrix public: union { - float f[4][4]; - struct + alignas(8) float f[4][4]; + struct alignas(8) { float rx, ry, rz, rw; float fx, fy, fz, fw; diff --git a/src/math/Quaternion.h b/src/math/Quaternion.h index 47c94f7c..365e988e 100644 --- a/src/math/Quaternion.h +++ b/src/math/Quaternion.h @@ -8,8 +8,14 @@ public: CQuaternion(void) {} CQuaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} - float Magnitude(void) const { return Sqrt(x*x + y*y + z*z + w*w); } - float MagnitudeSqr(void) const { return x*x + y*y + z*z + w*w; } + float Magnitude(void) const { return Sqrt(MagnitudeSqr()); } + float MagnitudeSqr(void) const { + #ifdef DC_SH4 + return fipr_magnitude_sqr(x, y, z, w); + #else + return x*x + y*y + z*z + w*w; + #endif + } void Normalise(void); void Multiply(const CQuaternion &q1, const CQuaternion &q2); void Invert(void){ // Conjugate would have been a better name diff --git a/src/math/Vector.cpp b/src/math/Vector.cpp index ee76e555..309da539 100644 --- a/src/math/Vector.cpp +++ b/src/math/Vector.cpp @@ -3,6 +3,10 @@ void CVector::Normalise(void) { +#ifdef DC_SH4_BROKEN + // TODO: This needs to handle zero vectors here + vec3f_normalize(x, y, z); +#else float sq = MagnitudeSqr(); if (sq > 0.0f) { float invsqrt = RecipSqrt(sq); @@ -11,9 +15,10 @@ CVector::Normalise(void) z *= invsqrt; } else x = 1.0f; +#endif } -CVector +CVector CrossProduct(const CVector &v1, const CVector &v2) { return CVector(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); @@ -22,10 +27,24 @@ CrossProduct(const CVector &v1, const CVector &v2) CVector Multiply3x3(const CMatrix &mat, const CVector &vec) { +#ifdef DC_SH4 + register float __x __asm__("fr12") = vec.x; + register float __y __asm__("fr13") = vec.y; + register float __z __asm__("fr14") = vec.z; + register float __w __asm__("fr15") = 0.0f; + + mat_load(reinterpret_cast(const_cast(&mat))); + + asm volatile( "ftrv xmtrx, fv12\n" + : "=f" (__x), "=f" (__y), "=f" (__z), "=f" (__w) + : "0" (__x), "1" (__y), "2" (__z), "3" (__w) ); + return { __x, __y, __z }; +#else // TODO: VU0 code return CVector(mat.rx * vec.x + mat.fx * vec.y + mat.ux * vec.z, mat.ry * vec.x + mat.fy * vec.y + mat.uy * vec.z, mat.rz * vec.x + mat.fz * vec.y + mat.uz * vec.z); +#endif } CVector @@ -39,8 +58,15 @@ Multiply3x3(const CVector &vec, const CMatrix &mat) CVector operator*(const CMatrix &mat, const CVector &vec) { +#ifdef DC_SH4 + CVector out; + mat_load(reinterpret_cast(const_cast(&mat))); + mat_trans_single3_nodiv_nomod(vec.x, vec.y, vec.z, out.x, out.y, out.z); + return out; +#else // TODO: VU0 code return CVector(mat.rx * vec.x + mat.fx * vec.y + mat.ux * vec.z + mat.px, mat.ry * vec.x + mat.fy * vec.y + mat.uy * vec.z + mat.py, mat.rz * vec.x + mat.fz * vec.y + mat.uz * vec.z + mat.pz); +#endif } diff --git a/src/math/Vector.h b/src/math/Vector.h index 776bfcfe..724c4571 100644 --- a/src/math/Vector.h +++ b/src/math/Vector.h @@ -1,9 +1,9 @@ -#pragma once +#pragma once class CVector : public RwV3d { public: - CVector(void) {} + CVector(void){} CVector(float x, float y, float z) { this->x = x; @@ -11,99 +11,118 @@ public: this->z = z; } - CVector(const RwV3d &v) - { - x = v.x; - y = v.y; - z = v.z; - } - // (0,1,0) means no rotation. So get right vector and its atan - float Heading(void) const { return Atan2(-x, y); } - float Magnitude(void) const { return Sqrt(x*x + y*y + z*z); } - float MagnitudeSqr(void) const { return x*x + y*y + z*z; } - float Magnitude2D(void) const { return Sqrt(x*x + y*y); } - float MagnitudeSqr2D(void) const { return x*x + y*y; } - void Normalise(void); - - void Normalise2D(void) { - float sq = MagnitudeSqr2D(); - float invsqrt = RecipSqrt(sq); - x *= invsqrt; - y *= invsqrt; - } + CVector(const RwV3d &v) + { + x = v.x; + y = v.y; + z = v.z; + } + // (0,1,0) means no rotation. So get right vector and its atan + __always_inline float Heading(void) const { return Atan2(-x, y); } + __always_inline float Magnitude(void) const { +#ifdef DC_SH4 + float w; + vec3f_length(x, y, z, w); + return w; +#else + return Sqrt(x*x + y*y + z*z); +#endif +} + __always_inline float MagnitudeSqr(void) const { +#ifdef DC_SH4 + return fipr_magnitude_sqr(x, y,z, 0.0f); +#else + return x*x + y*y + z*z; +#endif +} + __always_inline float Magnitude2D(void) const { return Sqrt(x*x + y*y); } + float MagnitudeSqr2D(void) const { return x*x + y*y; } + void Normalise(void); + + __always_inline void Normalise2D(void) { + float sq = MagnitudeSqr2D(); + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + } - const CVector &operator+=(CVector const &right) { - x += right.x; - y += right.y; - z += right.z; - return *this; - } + const CVector &operator+=(CVector const &right) { + x += right.x; + y += right.y; + z += right.z; + return *this; + } - const CVector &operator-=(CVector const &right) { - x -= right.x; - y -= right.y; - z -= right.z; - return *this; - } + const CVector &operator-=(CVector const &right) { + x -= right.x; + y -= right.y; + z -= right.z; + return *this; + } - const CVector &operator*=(float right) { - x *= right; - y *= right; - z *= right; - return *this; - } + const CVector &operator*=(float right) { + x *= right; + y *= right; + z *= right; + return *this; + } - const CVector &operator/=(float right) { - x /= right; - y /= right; - z /= right; - return *this; - } + const CVector &operator/=(float right) { + right = Invert(right); + x *= right; + y *= right; + z *= right; + return *this; + } - CVector operator-() const { - return CVector(-x, -y, -z); - } + CVector operator-() const { + return CVector(-x, -y, -z); + } - const bool operator==(CVector const &right) { - return x == right.x && y == right.y && z == right.z; - } + const bool operator==(CVector const &right) { + return x == right.x && y == right.y && z == right.z; + } - const bool operator!=(CVector const &right) { - return x != right.x || y != right.y || z != right.z; - } + const bool operator!=(CVector const &right) { + return x != right.x || y != right.y || z != right.z; + } - bool IsZero(void) const { return x == 0.0f && y == 0.0f && z == 0.0f; } + bool IsZero(void) const { return x == 0.0f && y == 0.0f && z == 0.0f; } }; inline CVector operator+(const CVector &left, const CVector &right) { - return CVector(left.x + right.x, left.y + right.y, left.z + right.z); + return CVector(left.x + right.x, left.y + right.y, left.z + right.z); } inline CVector operator-(const CVector &left, const CVector &right) { - return CVector(left.x - right.x, left.y - right.y, left.z - right.z); + return CVector(left.x - right.x, left.y - right.y, left.z - right.z); } inline CVector operator*(const CVector &left, float right) { - return CVector(left.x * right, left.y * right, left.z * right); + return CVector(left.x * right, left.y * right, left.z * right); } inline CVector operator*(float left, const CVector &right) { - return CVector(left * right.x, left * right.y, left * right.z); + return CVector(left * right.x, left * right.y, left * right.z); } inline CVector operator/(const CVector &left, float right) { - return CVector(left.x / right, left.y / right, left.z / right); + return CVector(left.x / right, left.y / right, left.z / right); } -inline float +__always_inline float DotProduct(const CVector &v1, const CVector &v2) { - return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +#ifdef DC_SH4 + return fipr(v1.x, v1.y, v1.z, 0.0f, v2.x, v2.y, v2.z, 0.0f); +#else + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +#endif } CVector CrossProduct(const CVector &v1, const CVector &v2); @@ -111,15 +130,21 @@ CVector CrossProduct(const CVector &v1, const CVector &v2); inline float Distance(const CVector &v1, const CVector &v2) { - return (v2 - v1).Magnitude(); + float w; +#ifdef DC_SH4 + vec3f_distance(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, w); + return w; +#else + return (v2 - v1).Magnitude(); +#endif } inline float Distance2D(const CVector &v1, const CVector &v2) { - float x = v2.x - v1.x; - float y = v2.y - v1.y; - return Sqrt(x*x + y*y); + float x = v2.x - v1.x; + float y = v2.y - v1.y; + return Sqrt(x*x + y*y); } class CMatrix; diff --git a/src/math/VuVector.h b/src/math/VuVector.h index 41584095..3ee84702 100644 --- a/src/math/VuVector.h +++ b/src/math/VuVector.h @@ -1,6 +1,8 @@ #pragma once -class TYPEALIGN(16) CVuVector : public CVector +#include "maths.h" + +class TYPEALIGN(8) CVuVector : public CVector { public: float w; @@ -26,7 +28,122 @@ public: // TODO: operator- }; -void TransformPoint(CVuVector &out, const CMatrix &mat, const CVuVector &in); -void TransformPoint(CVuVector &out, const CMatrix &mat, const RwV3d &in); -void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const RwV3d *in, int stride); -void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const CVuVector *in); +__always_inline void TransformPoint(CVuVector &out, const CMatrix &mat, const CVuVector &in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + lqc2 vf01,0x0(%2)\n\ + lqc2 vf02,0x0(%1)\n\ + lqc2 vf03,0x10(%1)\n\ + lqc2 vf04,0x20(%1)\n\ + lqc2 vf05,0x30(%1)\n\ + vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + sqc2 vf06,0x0(%0)\n\ + ": : "r" (&out) , "r" (&mat) ,"r" (&in): "memory"); +#elif defined(DC_SH4) + mat_load(reinterpret_cast(const_cast(&mat))); + mat_trans_nodiv_nomod(in.x, in.y, in.z, out.x, out.y, out.z, out.y); +#else + out = mat * in; +#endif +} + +__always_inline void TransformPoint(CVuVector &out, const CMatrix &mat, const RwV3d &in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + ldr $8,0x0(%2)\n\ + ldl $8,0x7(%2)\n\ + lw $9,0x8(%2)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + lqc2 vf02,0x0(%1)\n\ + lqc2 vf03,0x10(%1)\n\ + lqc2 vf04,0x20(%1)\n\ + lqc2 vf05,0x30(%1)\n\ + vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + sqc2 vf06,0x0(%0)\n\ + ": : "r" (&out) , "r" (&mat) ,"r" (&in): "memory"); +#elif defined(DC_SH4) + mat_load(reinterpret_cast(const_cast(&mat))); + mat_trans_nodiv_nomod(in.x, in.y, in.z, out.x, out.y, out.z, out.y); +#else + out = mat * in; +#endif +} + +__always_inline void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const RwV3d *in, int stride) +{ + assert(false); +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + paddub $3,%4,$0\n\ + lqc2 vf02,0x0(%2)\n\ + lqc2 vf03,0x10(%2)\n\ + lqc2 vf04,0x20(%2)\n\ + lqc2 vf05,0x30(%2)\n\ + ldr $8,0x0(%3)\n\ + ldl $8,0x7(%3)\n\ + lw $9,0x8(%3)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + 1: vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + add %3,%3,$3\n\ + ldr $8,0x0(%3)\n\ + ldl $8,0x7(%3)\n\ + lw $9,0x8(%3)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + addi %1,%1,-1\n\ + addiu %0,%0,0x10\n\ + sqc2 vf06,-0x10(%0)\n\ + bnez %1,1b\n\ + ": : "r" (out) , "r" (n), "r" (&mat), "r" (in), "r" (stride): "memory"); +#elif defined(DC_SH4) + mat_load(reinterpret_cast(const_cast(&mat))); + mat_transform(reinterpret_cast(const_cast(in)), + reinterpret_cast(out), + n, stride - sizeof(vector_t)); +#else + while(n--){ + *out = mat * *in; + in = (RwV3d*)((uint8*)in + stride); + out++; + } +#endif +} + +__always_inline void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const CVuVector *in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + lqc2 vf02,0x0(%2)\n\ + lqc2 vf03,0x10(%2)\n\ + lqc2 vf04,0x20(%2)\n\ + lqc2 vf05,0x30(%2)\n\ + lqc2 vf01,0x0(%3)\n\ + nop\n\ + 1: vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + lqc2 vf01,0x10(%3)\n\ + addiu %3,%3,0x10\n\ + addi %1,%1,-1\n\ + addiu %0,%0,0x10\n\ + sqc2 vf06,-0x10(%0)\n\ + bnez %1,1b\n\ + ": : "r" (out) , "r" (n), "r" (&mat) ,"r" (in): "memory"); +#else + TransformPoints(out, n, mat, in, sizeof(CVuVector)); +#endif +} diff --git a/src/math/float16.h b/src/math/float16.h new file mode 100644 index 00000000..25f32347 --- /dev/null +++ b/src/math/float16.h @@ -0,0 +1,66 @@ +#pragma once +#include + +struct float16 { + uint16_t raw = 0; + + float16() { + raw = 0; + } + + float16(float v) { + raw = ((uint32_t&)v)>>16; + } + + operator float() const { + /* Required workaround for GCC14.2.0 -Wuninitialized compiler warning bug. */ + union { + uint32_t fraw; + float fp; + }; + + fraw = (raw << 16); + + return fp; + } + + float16& operator+=(float other) { + *this = *this + other; + return *this; + } + + float16& operator-=(float other) { + *this = *this - other; + return *this; + } + float16& operator*=(float other) { + *this = *this * other; + return *this; + } + float16& operator/=(float other) { + *this = *this / other; + return *this; + } + + float16 operator-() const { + return - (float)*this; + } + + float operator+(float other) const { + return (float)*this + other; + } + float operator-(float other) const { + return (float)*this - other; + } + float operator*(float other) const { + return (float)*this * other; + } + + float operator/(float other) const { + return (float)*this / other; + } + + bool operator==(float16 other) const { + return raw == other.raw; + } +}; \ No newline at end of file diff --git a/src/math/math.cpp b/src/math/math.cpp index 8cb56dab..9269c82a 100644 --- a/src/math/math.cpp +++ b/src/math/math.cpp @@ -4,115 +4,3 @@ // TODO: move more stuff into here - -void TransformPoint(CVuVector &out, const CMatrix &mat, const CVuVector &in) -{ -#ifdef GTA_PS2 - __asm__ __volatile__("\n\ - lqc2 vf01,0x0(%2)\n\ - lqc2 vf02,0x0(%1)\n\ - lqc2 vf03,0x10(%1)\n\ - lqc2 vf04,0x20(%1)\n\ - lqc2 vf05,0x30(%1)\n\ - vmulax.xyz ACC, vf02,vf01\n\ - vmadday.xyz ACC, vf03,vf01\n\ - vmaddaz.xyz ACC, vf04,vf01\n\ - vmaddw.xyz vf06,vf05,vf00\n\ - sqc2 vf06,0x0(%0)\n\ - ": : "r" (&out) , "r" (&mat) ,"r" (&in): "memory"); -#else - out = mat * in; -#endif -} - -void TransformPoint(CVuVector &out, const CMatrix &mat, const RwV3d &in) -{ -#ifdef GTA_PS2 - __asm__ __volatile__("\n\ - ldr $8,0x0(%2)\n\ - ldl $8,0x7(%2)\n\ - lw $9,0x8(%2)\n\ - pcpyld $10,$9,$8\n\ - qmtc2 $10,vf01\n\ - lqc2 vf02,0x0(%1)\n\ - lqc2 vf03,0x10(%1)\n\ - lqc2 vf04,0x20(%1)\n\ - lqc2 vf05,0x30(%1)\n\ - vmulax.xyz ACC, vf02,vf01\n\ - vmadday.xyz ACC, vf03,vf01\n\ - vmaddaz.xyz ACC, vf04,vf01\n\ - vmaddw.xyz vf06,vf05,vf00\n\ - sqc2 vf06,0x0(%0)\n\ - ": : "r" (&out) , "r" (&mat) ,"r" (&in): "memory"); -#else - out = mat * in; -#endif -} - -void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const RwV3d *in, int stride) -{ -#ifdef GTA_PS3 - __asm__ __volatile__("\n\ - paddub $3,%4,$0\n\ - lqc2 vf02,0x0(%2)\n\ - lqc2 vf03,0x10(%2)\n\ - lqc2 vf04,0x20(%2)\n\ - lqc2 vf05,0x30(%2)\n\ - ldr $8,0x0(%3)\n\ - ldl $8,0x7(%3)\n\ - lw $9,0x8(%3)\n\ - pcpyld $10,$9,$8\n\ - qmtc2 $10,vf01\n\ - 1: vmulax.xyz ACC, vf02,vf01\n\ - vmadday.xyz ACC, vf03,vf01\n\ - vmaddaz.xyz ACC, vf04,vf01\n\ - vmaddw.xyz vf06,vf05,vf00\n\ - add %3,%3,$3\n\ - ldr $8,0x0(%3)\n\ - ldl $8,0x7(%3)\n\ - lw $9,0x8(%3)\n\ - pcpyld $10,$9,$8\n\ - qmtc2 $10,vf01\n\ - addi %1,%1,-1\n\ - addiu %0,%0,0x10\n\ - sqc2 vf06,-0x10(%0)\n\ - bnez %1,1b\n\ - ": : "r" (out) , "r" (n), "r" (&mat), "r" (in), "r" (stride): "memory"); -#else - while(n--){ - *out = mat * *in; - in = (RwV3d*)((uint8*)in + stride); - out++; - } -#endif -} - -void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const CVuVector *in) -{ -#ifdef GTA_PS2 - __asm__ __volatile__("\n\ - lqc2 vf02,0x0(%2)\n\ - lqc2 vf03,0x10(%2)\n\ - lqc2 vf04,0x20(%2)\n\ - lqc2 vf05,0x30(%2)\n\ - lqc2 vf01,0x0(%3)\n\ - nop\n\ - 1: vmulax.xyz ACC, vf02,vf01\n\ - vmadday.xyz ACC, vf03,vf01\n\ - vmaddaz.xyz ACC, vf04,vf01\n\ - vmaddw.xyz vf06,vf05,vf00\n\ - lqc2 vf01,0x10(%3)\n\ - addiu %3,%3,0x10\n\ - addi %1,%1,-1\n\ - addiu %0,%0,0x10\n\ - sqc2 vf06,-0x10(%0)\n\ - bnez %1,1b\n\ - ": : "r" (out) , "r" (n), "r" (&mat) ,"r" (in): "memory"); -#else - while(n--){ - *out = mat * *in; - in++; - out++; - } -#endif -} diff --git a/src/math/maths.h b/src/math/maths.h index 6a228036..35e34806 100644 --- a/src/math/maths.h +++ b/src/math/maths.h @@ -1,19 +1,73 @@ #pragma once -// wrapper around float versions of functions -// in gta they are in CMaths but that makes the code rather noisy +#include "common_defines.h" -inline float Sin(float x) { return sinf(x); } -inline float Asin(float x) { return asinf(x); } -inline float Cos(float x) { return cosf(x); } -inline float Acos(float x) { return acosf(x); } -inline float Tan(float x) { return tanf(x); } -inline float Atan(float x) { return atanf(x); } -inline float Atan2(float y, float x) { return atan2f(y, x); } -inline float Abs(float x) { return fabsf(x); } -inline float Sqrt(float x) { return sqrtf(x); } -inline float RecipSqrt(float x, float y) { return x/Sqrt(y); } -inline float RecipSqrt(float x) { return RecipSqrt(1.0f, x); } -inline float Pow(float x, float y) { return powf(x, y); } -inline float Floor(float x) { return floorf(x); } -inline float Ceil(float x) { return ceilf(x); } +#include +#include + +#ifdef DC_SH4 + +#define mat_trans_nodiv_nomod(x, y, z, x2, y2, z2, w2) do { \ + register float __x __asm__("fr12") = (x); \ + register float __y __asm__("fr13") = (y); \ + register float __z __asm__("fr14") = (z); \ + register float __w __asm__("fr15") = 1.0f; \ + __asm__ __volatile__( "ftrv xmtrx, fv12\n" \ + : "=f" (__x), "=f" (__y), "=f" (__z), "=f" (__w) \ + : "0" (__x), "1" (__y), "2" (__z), "3" (__w) ); \ + x2 = __x; y2 = __y; z2 = __z; w2 = __w; \ + } while(false) + +#define mat_trans_w_nodiv_nomod(x, y, z, w) do { \ + register float __x __asm__("fr12") = (x); \ + register float __y __asm__("fr13") = (y); \ + register float __z __asm__("fr14") = (z); \ + register float __w __asm__("fr15") = 1.0f; \ + __asm__ __volatile__( "ftrv xmtrx, fv12\n" \ + : "=f" (__x), "=f" (__y), "=f" (__z), "=f" (__w) \ + : "0" (__x), "1" (__y), "2" (__z), "3" (__w) ); \ + w = __w; \ + } while(false) + +__always_inline float Fmac(float a, float b, float c) { + asm volatile ("fmac fr0, %[floatb], %[floatc]\n" + : [floatc] "+f" (c) : "w" (a), [floatb] "f" (b) : ); + return c; +} + +#else + +#define mat_trans_nodiv_nomod(x_, y_, z_, x2, y2, z2, w2) do { \ + vector_t tmp = { x_, y_, z_, 1.0f }; \ + mat_transform(&tmp, &tmp, 1, 0); \ + x2 = tmp.x; y2 = tmp.y; z2 = tmp.z; w2 = tmp.w; \ + } while(false) + +#define mat_trans_w_nodiv_nomod(x_, y_, z_, w_) do { \ + vector_t tmp = { x_, y_, z_, 1.0f }; \ + mat_transform(&tmp, &tmp, 1, 0); \ + w_ = tmp.w; \ + } while(false) + +__always_inline float Fmac(float a, float b, float c) { return a * b + c; } + +#endif + +__always_inline float Sin(float x) { return __builtin_sinf(x); } +__always_inline float Cos(float x) { return __builtin_cosf(x); } +__always_inline auto SinCos(float x) { return std::pair { Sin(x), Cos(x) }; } +__always_inline float Tan(float x) { return __builtin_tanf(x); } +__always_inline float Abs(float x) { return __builtin_fabsf(x); } +__always_inline float Sqrt(float x) { return __builtin_sqrtf(x); } +__always_inline float RecipSqrt(float x) { return 1.0f / __builtin_sqrtf(x); } +__always_inline float Asin(float x) { return __builtin_asinf(x); } +__always_inline float Acos(float x) { return __builtin_acosf(x); } +__always_inline float Atan(float x) { return __builtin_atanf(x); } +__always_inline float Atan2(float y, float x) { return __builtin_atan2f(y, x); } +__always_inline float RecipSqrt(float x, float y) { return x / __builtin_sqrtf(y); /*y = RecipSqrt(y); return x * y * y;*/ } +__always_inline float Pow(float x, float y) { return __builtin_powf(x, y); } +__always_inline float Floor(float x) { return __builtin_floorf(x); } +__always_inline float Ceil(float x) { return __builtin_ceilf(x); } +__always_inline float Invert(float x) { return (((x) < 0.0f)? -1.0f : 1.0f) * RecipSqrt((x) * (x)); } +__always_inline float Div(float x, float y) { return x * Invert(y); } +__always_inline float Lerp(float a, float b, float t) { return Fmac(t, (b - a), a); } \ No newline at end of file diff --git a/src/modelinfo/ClumpModelInfo.cpp b/src/modelinfo/ClumpModelInfo.cpp index 44a62afb..360d944e 100644 --- a/src/modelinfo/ClumpModelInfo.cpp +++ b/src/modelinfo/ClumpModelInfo.cpp @@ -97,6 +97,8 @@ CClumpModelInfo::SetClump(RpClump *clump) hier = GetAnimHierarchyFromClump(clump); assert(hier); RpSkinAtomicSetHAnimHierarchy(IsClumpSkinned(clump), hier); + #if !defined(RW_DC) + // why is this here? should be already normalized ~ skmp skinAtomic = IsClumpSkinned(clump); assert(skinAtomic); @@ -110,6 +112,7 @@ CClumpModelInfo::SetClump(RpClump *clump) weights->w2 /= sum; weights->w3 /= sum; } + #endif RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS)); } if(strcmp(GetModelName(), "playerh") == 0){ diff --git a/src/modelinfo/ModelInfo.cpp b/src/modelinfo/ModelInfo.cpp index 7aa5fc8b..3ccb9922 100644 --- a/src/modelinfo/ModelInfo.cpp +++ b/src/modelinfo/ModelInfo.cpp @@ -187,7 +187,7 @@ CModelInfo::AddVehicleModel(int id) } CBaseModelInfo* -CModelInfo::GetModelInfo(const char *name, int *id) +CModelInfo::GetModelInfo(const char *name, int32 *id) { CBaseModelInfo *modelinfo; for(int i = 0; i < MODELINFOSIZE; i++){ diff --git a/src/modelinfo/ModelInfo.h b/src/modelinfo/ModelInfo.h index 4d24e78f..f50eb972 100644 --- a/src/modelinfo/ModelInfo.h +++ b/src/modelinfo/ModelInfo.h @@ -39,7 +39,7 @@ public: static CStore &Get2dEffectStore(void) { return ms_2dEffectStore; } static CStore &GetMloInstanceStore(void) { return ms_mloInstanceStore; } - static CBaseModelInfo *GetModelInfo(const char *name, int *id); + static CBaseModelInfo *GetModelInfo(const char *name, int32 *id); static CBaseModelInfo *GetModelInfo(int id){ return ms_modelInfoPtrs[id]; } diff --git a/src/modelinfo/PedModelInfo.cpp b/src/modelinfo/PedModelInfo.cpp index 2cce48f4..8c222d4d 100644 --- a/src/modelinfo/PedModelInfo.cpp +++ b/src/modelinfo/PedModelInfo.cpp @@ -270,7 +270,6 @@ CPedModelInfo::AnimatePedColModel(CColModel* colmodel, RwFrame* frame) { RwObjectNameAssociation nameAssoc; RwObjectIdAssociation idAssoc; - RwMatrix* mat = RwMatrixCreate(); CColSphere* spheres = colmodel->spheres; for (int i = 0; i < NUMPEDINFONODES; i++) { @@ -288,15 +287,16 @@ CPedModelInfo::AnimatePedColModel(CColModel* colmodel, RwFrame* frame) f = idAssoc.frame; } if (f) { - RwMatrixCopy(mat, RwFrameGetMatrix(f)); + RwMatrix mat; + RwMatrixCopy(&mat, RwFrameGetMatrix(f)); for (f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) { - RwMatrixTransform(mat, RwFrameGetMatrix(f), rwCOMBINEPOSTCONCAT); + RwMatrixTransform(&mat, RwFrameGetMatrix(f), rwCOMBINEPOSTCONCAT); if (RwFrameGetParent(f) == frame) break; } - spheres[i].center = mat->pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); + spheres[i].center = mat.pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); } } diff --git a/src/objects/CutsceneHead.cpp b/src/objects/CutsceneHead.cpp index 19b3a592..1ef52036 100644 --- a/src/objects/CutsceneHead.cpp +++ b/src/objects/CutsceneHead.cpp @@ -94,8 +94,10 @@ CCutsceneHead::ProcessControl(void) // PS2 only plays anims in cutscene, PC always plays anims if(!lastLoadedSKA || CCutsceneMgr::IsRunning()) #endif +if (hier) { RpHAnimHierarchyAddAnimTime(hier, CTimer::GetTimeStepNonClippedInSeconds()); } +} void CCutsceneHead::Render(void) diff --git a/src/peds/Ped.cpp b/src/peds/Ped.cpp index 6b28dcb0..e413df33 100644 --- a/src/peds/Ped.cpp +++ b/src/peds/Ped.cpp @@ -6863,7 +6863,7 @@ CPed::SeekCar(void) bool CPed::CheckForExplosions(CVector2D &area) { - int event = 0; + int32 event = 0; if (CEventList::FindClosestEvent(EVENT_EXPLOSION, GetPosition(), &event)) { area.x = gaEvent[event].posn.x; area.y = gaEvent[event].posn.y; @@ -6907,7 +6907,7 @@ CPed::CheckForExplosions(CVector2D &area) CPed * CPed::CheckForGunShots(void) { - int event; + int32 event; if (CEventList::FindClosestEvent(EVENT_GUNSHOT, GetPosition(), &event)) { if (gaEvent[event].entityType == EVENT_ENTITY_PED) { // Probably due to we don't want peds to go gunshot area? (same on VC) @@ -6922,7 +6922,7 @@ CPed::CheckForGunShots(void) CPed * CPed::CheckForDeadPeds(void) { - int event; + int32 event; if (CEventList::FindClosestEvent(EVENT_DEAD_PED, GetPosition(), &event)) { int pedHandle = gaEvent[event].entityRef; if (pedHandle && gaEvent[event].entityType == EVENT_ENTITY_PED) { diff --git a/src/peds/PedAI.cpp b/src/peds/PedAI.cpp index 8bd6791c..b6e575c9 100644 --- a/src/peds/PedAI.cpp +++ b/src/peds/PedAI.cpp @@ -4244,73 +4244,67 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void) CAnimBlendHierarchy *enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_JACKEDCAR_LHS)->hierarchy; CAnimBlendSequence *seq = enterAssoc->sequences; - CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { if (!seq->HasTranslation()) vecPedDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); - vecPedDraggedOutCarAnimOffset = lastFrame->translation; + vecPedDraggedOutCarAnimOffset = seq->GetTranslation(seq->numFrames - 1); } } enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LHS)->hierarchy; seq = enterAssoc->sequences; - CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { if (!seq->HasTranslation()) vecPedCarDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); - vecPedCarDoorAnimOffset = lastFrame->translation; + vecPedCarDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1); } } enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_LHS)->hierarchy; seq = enterAssoc->sequences; - CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { if (!seq->HasTranslation()) vecPedCarDoorLoAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); - vecPedCarDoorLoAnimOffset = lastFrame->translation; + vecPedCarDoorLoAnimOffset = seq->GetTranslation(seq->numFrames - 1); } } enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_QUICKJACKED)->hierarchy; seq = enterAssoc->sequences; - CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { if (!seq->HasTranslation()) vecPedQuickDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); - vecPedQuickDraggedOutCarAnimOffset = lastFrame->translation; + vecPedQuickDraggedOutCarAnimOffset = seq->GetTranslation(seq->numFrames - 1); } } enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_VAN_GET_IN_REAR_LHS)->hierarchy; seq = enterAssoc->sequences; - CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { if (!seq->HasTranslation()) vecPedVanRearDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); - vecPedVanRearDoorAnimOffset = lastFrame->translation; + vecPedVanRearDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1); } } enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_TRAIN_GETOUT)->hierarchy; seq = enterAssoc->sequences; - CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { if (!seq->HasTranslation()) vecPedTrainDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); - vecPedTrainDoorAnimOffset = lastFrame->translation; + vecPedTrainDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1); } } } diff --git a/src/peds/Population.cpp b/src/peds/Population.cpp index 5c80702f..d5794bb7 100644 --- a/src/peds/Population.cpp +++ b/src/peds/Population.cpp @@ -317,7 +317,7 @@ CPopulation::UpdatePedCount(ePedType pedType, bool decrease) } } -int +int32 CPopulation::ChooseGangOccupation(int gangId) { int8 modelOverride = CGangs::GetGangPedModelOverride(gangId); @@ -642,7 +642,7 @@ CPopulation::AddToPopulation(float minDist, float maxDist, float minDistOffScree pedAmount = 1; CVector generatedCoors; - int node1, node2; + int32 node1, node2; float randomPos; bool foundCoors = !!ThePaths.GeneratePedCreationCoors(playerCentreOfWorld.x, playerCentreOfWorld.y, minDist, maxDist, minDistOffScreen, maxDistOffScreen, &generatedCoors, &node1, &node2, &randomPos, nil); diff --git a/src/prof/profiler.cpp b/src/prof/profiler.cpp new file mode 100644 index 00000000..b7ca8e4d --- /dev/null +++ b/src/prof/profiler.cpp @@ -0,0 +1,376 @@ +/* + profiler.cpp + Copyright (C) 2020 Luke Benstead + Copyright (C) 2023, 2024 Ruslan Rostovtsev +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +perf_cntr_event_t profilerEvent = PMCR_PARALLEL_INSTRUCTION_ISSUED_MODE; +// perf_cntr_event_t profilerMode = PMCR_PIPELINE_FREEZE_BY_DCACHE_MISS_MODE; + +FILE *kernel_file = NULL; +static char KERNEL_OUTPUT_FILENAME[128]; +static char SECOND_OUTPUT_FILENAME[128]; +static kthread_t* THREAD; +static volatile bool PROFILER_RUNNING = false; +static volatile bool PROFILER_RECORDING = false; +// static tid_t KERNEL_TID = 0; +// static tid_t SECOND_TID = 0; +static int prof_count = 0; + +#define BASE_ADDRESS 0x8c010000 +#define BUCKET_SIZE 10000 + +#define INTERVAL_IN_MS 1 + +/* Simple hash table of samples. An array of Samples + * but, each sample in that array can be the head of + * a linked list of other samples */ +typedef struct Arc { + uint32_t pc; + uint32_t pr; // Caller return address + uint32_t count; + // tid_t tid; + struct Arc* next; +} Arc; + +static Arc ARCS[BUCKET_SIZE]; + +/* Hashing function for two uint32_ts */ +#define HASH_PAIR(x, y) ((x * 0x1f1f1f1f) ^ y) + +#define BUFFER_SIZE (1024 * 64) // 64K buffer + +const static size_t MAX_ARC_COUNT = BUFFER_SIZE / sizeof(Arc); +Arc arcbuf[MAX_ARC_COUNT]; + +static size_t ARC_COUNT = 0; + +static bool write_samples(tid_t tid, FILE *out); +static void clear_samples(); + +static Arc* new_arc(tid_t tid, uint32_t PC, uint32_t PR, uint32_t counter) { + Arc* s = &arcbuf[ARC_COUNT]; + s->count = counter; + s->pc = PC; + s->pr = PR; + // s->tid = tid; + s->next = NULL; + + ++ARC_COUNT; + + return s; +} + +static void record_thread(tid_t tid, uint32_t PC, uint32_t PR, uint32_t counter) { + uint32_t bucket = HASH_PAIR(PC, PR) % BUCKET_SIZE; + + Arc* s = &ARCS[bucket]; + // profileTick = (profileTick*3 + counter)/4; + + if(s->pc) { + /* Initialized sample in this bucket, + * does it match though? */ + while(s->pc != PC || s->pr != PR) { + if(s->next) { + s = s->next; + } else { + s->next = new_arc(tid, PC, PR, counter); + return; // We're done + } + } + + s->count+=counter; + } else { + /* Initialize this sample */ + s->count = counter; + s->pc = PC; + s->pr = PR; + // s->tid = tid; + s->next = NULL; + // ++ARC_COUNT; + } +} + + + +#define GMON_COOKIE "gmon" +#define GMON_VERSION 1 + +typedef struct { + char cookie[4]; // 'g','m','o','n' + int32_t version; // 1 + char spare[3 * 4]; // Padding +} GmonHeader; + +typedef struct { + uint32_t low_pc; + uint32_t high_pc; + uint32_t hist_size; + uint32_t prof_rate; + char dimen[15]; /* phys. dim., usually "seconds" */ + char dimen_abbrev; /* usually 's' for "seconds" */ +} GmonHistHeader; + +typedef struct { + unsigned char tag; // GMON_TAG_TIME_HIST = 0, GMON_TAG_CG_ARC = 1, GMON_TAG_BB_COUNT = 2 + size_t ncounts; // Number of address/count pairs in this sequence +} GmonBBHeader; + +typedef struct { + uint32_t from_pc; /* address within caller's body */ + uint32_t self_pc; /* address within callee's body */ + uint32_t count; /* number of arc traversals */ +} GmonArc; + +static bool init_sample_file(const char* path) { + + kernel_file = fopen(path, "w"); + if(!kernel_file) { + return false; + } + + /* Write the GMON header */ + + GmonHeader header; + memcpy(&header.cookie[0], GMON_COOKIE, sizeof(header.cookie)); + header.version = 1; + memset(header.spare, '\0', sizeof(header.spare)); + + fwrite(&header, sizeof(header), 1, kernel_file); + + return true; +} + +#define ROUNDDOWN(x,y) (((x)/(y))*(y)) +#define ROUNDUP(x,y) ((((x)+(y)-1)/(y))*(y)) + +static bool write_samples(tid_t tid, FILE *out) { + /* Appends the samples to the output file in gmon format + * + * We iterate the data twice, first generating arcs, then generating + * basic block counts. While we do that though we calculate the data + * for the histogram so we don't need a third iteration */ + + + // Seek to the end of the file + fseek(out, 0, SEEK_END); + + uint8_t tag = 1; + size_t written = 0; + + /* Write arcs */ + Arc* root = ARCS; + for(int i = 0; i < BUCKET_SIZE; ++i) { + if(root->pc) { + GmonArc arc; + arc.from_pc = root->pr; + arc.self_pc = root->pc; + arc.count = root->count; + + /* Write the root sample if it has a program counter */ + fwrite(&tag, sizeof(tag), 1, out); + fwrite(&arc, sizeof(GmonArc), 1, out); + + ++written; + + /* If there's a next pointer, traverse the list */ + Arc* s = root->next; + while(s) { + arc.from_pc = s->pr; + arc.self_pc = s->pc; + arc.count = s->count; + + /* Write the root sample if it has a program counter */ + fwrite(&tag, sizeof(tag), 1, out); + fwrite(&arc, sizeof(GmonArc), 1, out); + + ++written; + s = s->next; + } + } + + root++; + } + + + dbglog(DBG_INFO, "-- Written %d arcs\n", written); + + return true; +} + +static void prof_thd_timer_hnd(irq_context_t *context) { + + if (PROFILER_RECORDING) { + record_thread(1, context->pc, context->pr, 1); + + if(ARC_COUNT >= MAX_ARC_COUNT) { + PROFILER_RECORDING = 0; + } + } + + // replicate thd timer behaviour + { + /* Get the system time */ + uint64_t now = timer_ms_gettime64(); + + (void)context; + + //printf("timer woke at %d\n", (uint32_t)now); + + thd_schedule(0, now); + timer_primary_wakeup(PROFILER_RECORDING ? 1 : 10); + } +} + + +static void* run(void* args) { + dbglog(DBG_INFO, "-- Entered profiler thread!\n"); + + while(PROFILER_RUNNING){ + auto mask = irq_disable(); + if(!PROFILER_RECORDING) { + if (ARC_COUNT > 0) { + if(!write_samples(1, kernel_file)) { + dbglog(DBG_ERROR, "Error writing samples\n"); + } + fclose(kernel_file); + kernel_file = nullptr; + clear_samples(); + dbglog(DBG_INFO, "-- Profiler thread stopped recording\n"); + } + } else { + uint64_t counter = perf_cntr_count(PRFC1); + assert(counter <= UINT32_MAX); + + perf_cntr_clear(PRFC1); + + perf_cntr_resume(PRFC1); + + dbglog(DBG_INFO, "-- Profiler thread recording..., %d arcs, %u events\n", ARC_COUNT, (unsigned)counter); + } + irq_restore(mask); + + usleep(1000 * 1000); //usleep takes microseconds + } + + dbglog(DBG_INFO, "-- Profiler thread finished!\n"); + + return NULL; +} + +void profiler_init(const char* output) { + + timer_primary_set_callback(prof_thd_timer_hnd); + + /* Store the filenames */ + sprintf(KERNEL_OUTPUT_FILENAME, "%s/kernel_gmon_%d.out", output, ++prof_count); + // sprintf(SECOND_OUTPUT_FILENAME, "%s/second_gmon_%d.out", output, prof_count); + + /* Initialize the file */ + dbglog(DBG_INFO, "Creating profiler samples file for kernel thread...\n"); + if(!init_sample_file(KERNEL_OUTPUT_FILENAME)) { + dbglog(DBG_ERROR, "Can't create %s\n", KERNEL_OUTPUT_FILENAME); + return; + } + + // dbglog(DBG_INFO, "Creating profiler samples file for video thread...\n"); + // if(!init_sample_file(SECOND_OUTPUT_FILENAME)) { + // dbglog(DBG_ERROR, "Can't create %s\n", SECOND_OUTPUT_FILENAME); + // return; + // } + + dbglog(DBG_INFO, "Creating profiler thread...\n"); + // Initialize the samples to zero + memset(ARCS, 0, sizeof(ARCS)); + + PROFILER_RUNNING = true; + THREAD = thd_create(0, run, NULL); + + // /* Lower priority is... er, higher */ + thd_set_prio(THREAD, PRIO_DEFAULT / 2); + + dbglog(DBG_INFO, "Profiler thread started.\n"); +} + +void profiler_start() { + assert(PROFILER_RUNNING); + + if(PROFILER_RECORDING) { + return; + } + + auto mask = irq_disable(); + dbglog(DBG_INFO, "Starting profiling...\n"); + if (profilerEvent != PMCR_INIT_NO_MODE) { + dbglog(DBG_INFO, "Using event profiling...\n"); + perf_cntr_start(PRFC1, profilerEvent, PMCR_COUNT_CPU_CYCLES); + } + PROFILER_RECORDING = true; + clear_samples(); + irq_restore(mask); +} + +static void clear_samples() { + /* Free the samples we've collected to start again */ + + // Wipe the lot + memset(ARCS, 0, sizeof(ARCS)); + ARC_COUNT = 0; +} + +bool profiler_stop() { + if(!PROFILER_RECORDING) { + return false; + } + + bool rv = true; + + auto mask = irq_disable(); + + if (PROFILER_RECORDING) { + dbglog(DBG_INFO, "profiler_stop: Stopping profiling...\n"); + + PROFILER_RECORDING = false; + + if(!write_samples(1, kernel_file)) { + dbglog(DBG_ERROR, "ERROR WRITING SAMPLES (RO filesystem?)!\n"); + rv = false; + } + clear_samples(); + fclose(kernel_file); + kernel_file = nullptr; + } + irq_restore(mask); + + return rv; +} + +bool profiler_recording() { + return PROFILER_RECORDING; +} + +void profiler_clean_up() { + profiler_stop(); // Make sure everything is stopped + + PROFILER_RUNNING = false; + thd_join(THREAD, NULL); + + if(kernel_file != NULL) { + fclose(kernel_file); + } +} diff --git a/src/prof/profiler.h b/src/prof/profiler.h new file mode 100644 index 00000000..fc82be8b --- /dev/null +++ b/src/prof/profiler.h @@ -0,0 +1,27 @@ + +/* + profiler.h + Copyright (C) 2020 Luke Benstead + Copyright (C) 2023 Ruslan Rostovtsev +*/ + +/** \file src/profiler.h + \brief gprof compatible sampling profiler. + + The Dreamcast doesn't have any kind of profiling support from GCC + so this is a cumbersome sampling profiler that runs in a background thread. + Once profiling has begun, the background thread will regularly gather the PC and PR registers stored by + the other threads. + The way thread scheduling works is that when other threads are blocked their current program counter is stored + in a context. If the profiler thread is doing work then all the other threads aren't and so the stored program + counters will be up-to-date. + The profiling thread gathers PC/PR pairs and how often that pairing appears. +*/ + +#pragma once + +void profiler_init(const char* output); +void profiler_start(); +void profiler_stop(); +void profiler_clean_up(); +bool profiler_recording(); diff --git a/src/renderer/Clouds.cpp b/src/renderer/Clouds.cpp index 957844a5..ac0a2ce0 100644 --- a/src/renderer/Clouds.cpp +++ b/src/renderer/Clouds.cpp @@ -129,7 +129,7 @@ CClouds::Render(void) CCoronas::SunBlockedByClouds = false; RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); - RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); @@ -355,7 +355,7 @@ CClouds::RenderBackground(int16 topred, int16 topgreen, int16 topblue, } ms_colourBottom = ms_colourTop; CRect r(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); - CSprite2d::DrawRect(r, ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + CSprite2d::DrawRect(r, ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop, true); }else{ ms_horizonZ = CSprite::CalcHorizonCoors(); @@ -392,7 +392,7 @@ CClouds::RenderBackground(int16 topred, int16 topgreen, int16 topblue, toppos = 0.0f; } CSprite2d::DrawRect(CRect(0, toppos, SCREEN_WIDTH, botpos), - ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop, true); } // draw the small stripe (whatever it's supposed to be) @@ -402,7 +402,7 @@ CClouds::RenderBackground(int16 topred, int16 topgreen, int16 topblue, ms_colourTop.g = (topgreen + 2 * botgreen) / 3; ms_colourTop.b = (topblue + 2 * botblue) / 3; CSprite2d::DrawRect(CRect(0, ms_horizonZ, SCREEN_WIDTH, ms_horizonZ+SMALLSTRIPHEIGHT), - ms_colourTop, ms_colourTop, ms_colourTop, ms_colourTop); + ms_colourTop, ms_colourTop, ms_colourTop, ms_colourTop, true); } // Only top @@ -418,7 +418,7 @@ CClouds::RenderBackground(int16 topred, int16 topgreen, int16 topblue, botpos = Min(SCREEN_HEIGHT, topedge); CSprite2d::DrawRect(CRect(0, 0, SCREEN_WIDTH, botpos), - ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop, true); } // Set both to fog colour for RenderHorizon @@ -449,7 +449,7 @@ CClouds::RenderHorizon(void) float z1 = Min(ms_horizonZ + SMALLSTRIPHEIGHT, SCREEN_HEIGHT); CSprite2d::DrawRectXLU(CRect(0, ms_horizonZ, SCREEN_WIDTH, z1), - ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop, true); // This is just weird float a = SCREEN_HEIGHT/400.0f * HORIZSTRIPHEIGHT + @@ -460,7 +460,7 @@ CClouds::RenderHorizon(void) float z2 = z1 + (a + b)*TheCamera.LODDistMultiplier; z2 = Min(z2, SCREEN_HEIGHT); CSprite2d::DrawRect(CRect(0, z1, SCREEN_WIDTH, z2), - ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop, true); POP_RENDERGROUP(); } diff --git a/src/renderer/Font.cpp b/src/renderer/Font.cpp index 6a9944e1..cba58f54 100644 --- a/src/renderer/Font.cpp +++ b/src/renderer/Font.cpp @@ -282,7 +282,7 @@ wchar foreign_table[128] = { #ifdef BUTTON_ICONS CSprite2d CFont::ButtonSprite[MAX_BUTTON_ICONS]; int CFont::PS2Symbol = BUTTON_NONE; -int CFont::ButtonsSlot = -1; +int32 CFont::ButtonsSlot = -1; #endif // BUTTON_ICONS void diff --git a/src/renderer/Glass.cpp b/src/renderer/Glass.cpp index cc45648c..e57f1cf2 100644 --- a/src/renderer/Glass.cpp +++ b/src/renderer/Glass.cpp @@ -537,7 +537,7 @@ CGlass::CalcAlphaWithNormal(CVector *normal) float fwdDir = 2.0f * DotProduct(*normal, TheCamera.GetForward()); float fwdDot = DotProduct(TheCamera.GetForward()-fwdDir*(*normal), CVector(0.57f, 0.57f, -0.57f)); - return int32(lerp(fwdDot*fwdDot*fwdDot*fwdDot*fwdDot*fwdDot, 20.0f, 255.0f)); + return int32(Lerp(fwdDot*fwdDot*fwdDot*fwdDot*fwdDot*fwdDot, 20.0f, 255.0f)); } void diff --git a/src/renderer/MBlur.cpp b/src/renderer/MBlur.cpp index 8e5fba2a..359bcc79 100644 --- a/src/renderer/MBlur.cpp +++ b/src/renderer/MBlur.cpp @@ -14,10 +14,10 @@ // Originally taken from RW example 'mblur' -RwRaster *CMBlur::pFrontBuffer; +// RwRaster *CMBlur::pFrontBuffer; bool CMBlur::ms_bJustInitialised; bool CMBlur::ms_bScaledBlur; -bool CMBlur::BlurOn; +uint8_t CMBlur::BlurOn; static RwIm2DVertex Vertex[4]; static RwImVertexIndex Index[6] = { 0, 1, 2, 0, 2, 3 }; @@ -60,7 +60,7 @@ CMBlur::MotionBlurOpen(RwCamera *cam) } CreateImmediateModeData(cam, &rect); -#else +#elif defined(NOT_DC) RwRect rect = { 0, 0, 0, 0 }; if(pFrontBuffer) @@ -73,6 +73,8 @@ CMBlur::MotionBlurOpen(RwCamera *cam) _GetVideoMemInfo(&total, &avaible); debug("Available video memory %d\n", avaible); #endif + + BlurOn = false; if(BlurOn) { @@ -130,6 +132,10 @@ CMBlur::MotionBlurOpen(RwCamera *cam) CreateImmediateModeData(cam, &rect); } + return TRUE; +#else + RwRect rect = { 0, 0, 640, 480 }; + CreateImmediateModeData(cam, &rect); return TRUE; #endif #endif @@ -138,6 +144,7 @@ CMBlur::MotionBlurOpen(RwCamera *cam) RwBool CMBlur::MotionBlurClose(void) { +#if defined(NOT_DC) #ifdef EXTENDED_COLOURFILTER CPostFX::Close(); #else @@ -147,6 +154,7 @@ CMBlur::MotionBlurClose(void) return TRUE; } +#endif #endif return FALSE; } @@ -214,7 +222,7 @@ CMBlur::MotionBlurRender(RwCamera *cam, uint32 red, uint32 green, uint32 blue, u #ifdef GTA_PS2 if( pFrontBuffer ) OverlayRender(cam, pFrontBuffer, color, type, bluralpha); -#else +#elif defined(NOT_DC) if(BlurOn){ if(pFrontBuffer){ if(ms_bJustInitialised) @@ -228,11 +236,19 @@ CMBlur::MotionBlurRender(RwCamera *cam, uint32 red, uint32 green, uint32 blue, u }else{ OverlayRender(cam, nil, color, type, bluralpha); } +#else + OverlayRender(cam, nil, color, type, bluralpha); #endif POP_RENDERGROUP(); #endif } +namespace rw::dc { + // TODO: move to some header? + void dcMotionBlur_v1(uint8_t a, uint8_t r, uint8_t g, uint8_t b); + void dcMotionBlur_v3(uint8_t a, uint8_t r, uint8_t g, uint8_t b); +} + void CMBlur::OverlayRender(RwCamera *cam, RwRaster *raster, RwRGBA color, int32 type, int32 bluralpha) { @@ -301,20 +317,31 @@ CMBlur::OverlayRender(RwCamera *cam, RwRaster *raster, RwRGBA color, int32 type, RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); - RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); - - a = bluralpha/2; - if(a < 30) - a = 30; - - if(BlurOn && a != 0){ // the second condition should always be true - RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, a); - RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, a); - RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, a); - RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, a); + if (BlurOn) { + if (BlurOn == 1) { + rw::dc::dcMotionBlur_v3(a, r, g, b); + } else { + rw::dc::dcMotionBlur_v1(a, r, g, b); + } + } else { RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); } + if (BlurOn == 2) { + a = bluralpha/2; + if(a < 30) + a = 30; + + if(BlurOn && a != 0){ // the second condition should always be true + // RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, a); + // RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, a); + // RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, a); + // RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, a); + // RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); + rw::dc::dcMotionBlur_v1(a, 255, 255, 255); + } + } + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); diff --git a/src/renderer/MBlur.h b/src/renderer/MBlur.h index e2e5d38c..cb9de28e 100644 --- a/src/renderer/MBlur.h +++ b/src/renderer/MBlur.h @@ -3,10 +3,10 @@ class CMBlur { public: - static RwRaster *pFrontBuffer; + // static RwRaster *pFrontBuffer; static bool ms_bJustInitialised; static bool ms_bScaledBlur; - static bool BlurOn; + static uint8_t BlurOn; public: static RwBool MotionBlurOpen(RwCamera *cam); diff --git a/src/renderer/Particle.cpp b/src/renderer/Particle.cpp index 76ddde50..398b4b35 100644 --- a/src/renderer/Particle.cpp +++ b/src/renderer/Particle.cpp @@ -16,8 +16,11 @@ #include "soundlist.h" #include "debugmenu.h" - +#if !defined(RW_DC_SLOW) #define MAX_PARTICLES_ON_SCREEN (1000) +#else +#define MAX_PARTICLES_ON_SCREEN (1000) +#endif //(5) diff --git a/src/renderer/PlayerSkin.cpp b/src/renderer/PlayerSkin.cpp index f0fae45a..cac05fa8 100644 --- a/src/renderer/PlayerSkin.cpp +++ b/src/renderer/PlayerSkin.cpp @@ -90,7 +90,7 @@ CPlayerSkin::GetSkinTexture(const char *texName) { RwTexture *tex; RwRaster *raster; - int32 width, height, depth, format; + RwInt32 width, height, depth, format; CTxdStore::PushCurrentTxd(); CTxdStore::SetCurrentTxd(m_txdSlot); @@ -103,6 +103,7 @@ CPlayerSkin::GetSkinTexture(const char *texName) else sprintf(gString, "skins\\%s.bmp", texName); +#if 0 // we don't support .bmp custom skins in DCA3 if (RwImage *image = RtBMPImageRead(gString)) { RwImageFindRasterFormat(image, rwRASTERTYPETEXTURE, &width, &height, &depth, &format); raster = RwRasterCreate(width, height, depth, format); @@ -117,6 +118,7 @@ CPlayerSkin::GetSkinTexture(const char *texName) RwImageDestroy(image); } + #endif return tex; } diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index 334f3954..cfdae34a 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -82,7 +82,7 @@ CLinkList gSortedBuildings; CVector CRenderer::ms_vecCameraPosition; CVehicle *CRenderer::m_pFirstPersonVehicle; bool CRenderer::m_loadingPriority; -float CRenderer::ms_lodDistScale = 1.2f; +float CRenderer::ms_lodDistScale = 0.7f; // This Controls Engine Draw Distance Slider. Default = 1.2. Menu Manager Defines MIN as 0.8, MAX as 1.8 :-) // unused BlockedRange CRenderer::aBlockedRanges[16]; @@ -168,11 +168,11 @@ CRenderer::RenderOneRoad(CEntity *e) PUSH_RENDERGROUP(CModelInfo::GetModelInfo(e->GetModelIndex())->GetModelName()); #ifdef EXTRA_MODEL_FLAGS - if(!e->IsBuilding() || CModelInfo::GetModelInfo(e->GetModelIndex())->RenderDoubleSided()){ - BACKFACE_CULLING_OFF; - e->Render(); - BACKFACE_CULLING_ON; - }else + if(!e->IsBuilding() || CModelInfo::GetModelInfo(e->GetModelIndex())->RenderDoubleSided()){ + BACKFACE_CULLING_OFF; + e->Render(); + BACKFACE_CULLING_ON; + }else #endif e->Render(); diff --git a/src/renderer/Rubbish.h b/src/renderer/Rubbish.h index 37f895f3..6a16e73c 100644 --- a/src/renderer/Rubbish.h +++ b/src/renderer/Rubbish.h @@ -3,12 +3,15 @@ class CVehicle; enum { - // NB: not all values are allowed, check the code -#ifdef SQUEEZE_PERFORMANCE + +#ifdef RW_DC // Frogbull (not Dirty) Hack that allow less Debris on screen for the Dreamcast Port + NUM_RUBBISH_SHEETS = 16 // NUM_RUBBISH_SHEETS must be a multiple of 4 (4 or 8 is the minimum value I think, max value is 64) +#elif SQUEEZE_PERFORMANCE NUM_RUBBISH_SHEETS = 32 #else - NUM_RUBBISH_SHEETS = 64 + NUM_RUBBISH_SHEETS = 64 // NB: not all values are allowed, check the code #endif + }; class COneSheet diff --git a/src/renderer/Shadows.cpp b/src/renderer/Shadows.cpp index 3884d3bb..0b167f42 100644 --- a/src/renderer/Shadows.cpp +++ b/src/renderer/Shadows.cpp @@ -1578,7 +1578,7 @@ CStaticShadow::Free(void) while (pUsed->m_pNext != NULL) pUsed = pUsed->m_pNext; - pUsed->m_pNext = pFree; + pUsed->m_pNext = pFree; } m_pPolyBunch = NULL; diff --git a/src/renderer/Shadows.h b/src/renderer/Shadows.h index 8c909df3..6e4449e5 100644 --- a/src/renderer/Shadows.h +++ b/src/renderer/Shadows.h @@ -1,9 +1,16 @@ #pragma once -#define MAX_STOREDSHADOWS 48 -#define MAX_POLYBUNCHES 300 -#define MAX_STATICSHADOWS 64 -#define MAX_PERMAMENTSHADOWS 48 +#ifdef RW_DC + #define MAX_STOREDSHADOWS 12 // Frogbull (not Dirty) Hack ::: Default Value 48 + #define MAX_POLYBUNCHES 300 + #define MAX_STATICSHADOWS 16 // Frogbull (not Dirty) Hack ::: Default Value 64 + #define MAX_PERMAMENTSHADOWS 12 // Frogbull (not Dirty) Hack ::: Default Value 48 +#else + #define MAX_STOREDSHADOWS 48 + #define MAX_POLYBUNCHES 300 + #define MAX_STATICSHADOWS 64 + #define MAX_PERMAMENTSHADOWS 48 +#endif class CEntity; diff --git a/src/renderer/Sprite2d.cpp b/src/renderer/Sprite2d.cpp index 59622516..5214ef4e 100644 --- a/src/renderer/Sprite2d.cpp +++ b/src/renderer/Sprite2d.cpp @@ -32,6 +32,8 @@ CSprite2d::InitPerFrame(void) for(i = 0; i < 10; i++) mpBankTextures[i] = nil; #endif + + SetRecipNearClip(); } int32 @@ -176,6 +178,7 @@ CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const C }else{ screenz = RwIm2DGetNearScreenZ(); z = 1.0f/RecipNearClip; + RecipNearClip *= 1.1f; } recipz = 1.0f/z; float offset = 1.0f/1024.0f; @@ -230,6 +233,7 @@ CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const C screenz = RwIm2DGetNearScreenZ(); z = 1.0f/RecipNearClip; recipz = 1.0f/z; + RecipNearClip *= 1.1f; // This is what we draw: // 0---1 @@ -281,6 +285,7 @@ CSprite2d::SetVertices(float x1, float y1, float x2, float y2, float x3, float y screenz = RwIm2DGetNearScreenZ(); recipz = RecipNearClip; + RecipNearClip *= 1.1f; RwIm2DVertexSetScreenX(&maVertices[0], x3); RwIm2DVertexSetScreenY(&maVertices[0], y3); @@ -327,6 +332,7 @@ CSprite2d::SetVertices(int n, float *positions, float *uvs, const CRGBA &col) screenz = RwIm2DGetNearScreenZ(); recipz = RecipNearClip; + RecipNearClip *= 1.1f; z = RwCameraGetNearClipPlane(Scene.camera); // not done by game @@ -350,6 +356,7 @@ CSprite2d::SetMaskVertices(int n, float *positions) screenz = RwIm2DGetNearScreenZ(); recipz = RecipNearClip; + RecipNearClip *= 1.1f; z = RwCameraGetNearClipPlane(Scene.camera); // not done by game for(i = 0; i < n; i++){ @@ -374,6 +381,7 @@ CSprite2d::SetVertices(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, con screenz = RwIm2DGetNearScreenZ(); recipz = RecipNearClip; + RecipNearClip *= 1.1f; z = RwCameraGetNearClipPlane(Scene.camera); // not done by game RwIm2DVertexSetScreenX(&verts[0], r.left); @@ -448,11 +456,16 @@ CSprite2d::DrawRect(const CRect &r, const CRGBA &col) } void -CSprite2d::DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +CSprite2d::DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, bool far) { - SetVertices(r, c0, c1, c2, c3, false); + SetVertices(r, c0, c1, c2, c3, far); RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); - RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + if (far) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + } else { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + } + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4); @@ -461,11 +474,15 @@ CSprite2d::DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGB } void -CSprite2d::DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +CSprite2d::DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, bool far) { - SetVertices(r, c0, c1, c2, c3, false); + SetVertices(r, c0, c1, c2, c3, far); RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); - RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + if (far) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + } else { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + } RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); diff --git a/src/renderer/Sprite2d.h b/src/renderer/Sprite2d.h index 0e12d441..d9ed5f17 100644 --- a/src/renderer/Sprite2d.h +++ b/src/renderer/Sprite2d.h @@ -43,9 +43,9 @@ public: static void SetVertices(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); - static void DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, bool far=false); static void DrawRect(const CRect &r, const CRGBA &col); - static void DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, bool far=false); static void Draw2DPolygon(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &color); diff --git a/src/renderer/WaterLevel.cpp b/src/renderer/WaterLevel.cpp index 7001c0cf..37f38b9a 100644 --- a/src/renderer/WaterLevel.cpp +++ b/src/renderer/WaterLevel.cpp @@ -266,7 +266,8 @@ CWaterLevel::CreateWavyAtomic() wavyGeometry = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP |rpGEOMETRYTEXTURED |rpGEOMETRYPRELIT - |rpGEOMETRYMODULATEMATERIALCOLOR); + |rpGEOMETRYMODULATEMATERIALCOLOR + |rw::Geometry::HAS_TRIANGLES/* RW_DC specific */); ASSERT(wavyGeometry != nil); diff --git a/src/rw/RwHelper.h b/src/rw/RwHelper.h index 0e04aece..2b0d5ced 100644 --- a/src/rw/RwHelper.h +++ b/src/rw/RwHelper.h @@ -30,6 +30,7 @@ void RenderSkeleton(RpHAnimHierarchy *hier); RwTexDictionary *RwTexDictionaryGtaStreamRead(RwStream *stream); RwTexDictionary *RwTexDictionaryGtaStreamRead1(RwStream *stream); RwTexDictionary *RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict); +void RwTexDictionaryGtaStreamWrite(RwStream *stream, RwTexDictionary *texDict); void ReadVideoCardCapsFile(uint32&, uint32&, uint32&, uint32&); bool CheckVideoCardCaps(void); void WriteVideoCardCapsFile(void); diff --git a/src/rw/TexRead.cpp b/src/rw/TexRead.cpp index 98e7d180..20ec7a2e 100644 --- a/src/rw/TexRead.cpp +++ b/src/rw/TexRead.cpp @@ -1,6 +1,4 @@ -#pragma warning( push ) -#pragma warning( disable : 4005) -#pragma warning( pop ) + #define FORCE_PC_SCALING #include "common.h" #ifdef ANISOTROPIC_FILTERING @@ -34,6 +32,21 @@ int32 texNumLoaded; #define READNATIVE(stream, tex, size) RWSRCGLOBAL(stdFunc[rwSTANDARDNATIVETEXTUREREAD](stream, tex, size)) #endif +void RwTextureGtaStreamWrite(RwStream *stream, RwTexture* texture) +{ + auto fheader = stream->tell(); + // size will be written later + rw::writeChunkHeader(stream, rwID_TEXTURENATIVE, 0); + auto fbegin = stream->tell(); + + texture->streamWriteNative(stream); + + // rewrite header with correct size + auto fend = stream->tell(); + stream->seek(fheader, 0); + rw::writeChunkHeader(stream, rwID_TEXTURENATIVE, fend - fbegin); + stream->seek(fend, 0); +} RwTexture* RwTextureGtaStreamRead(RwStream *stream) { @@ -68,6 +81,17 @@ destroyTexture(RwTexture *texture, void *data) return texture; } +void +RwTexDictionaryGtaStreamWrite(RwStream *stream, RwTexDictionary *texDict) { + rw::writeChunkHeader(stream, rwID_STRUCT, 4); + stream->writeI32(texDict->count()); + + RwTexDictionaryForAllTextures(texDict, [](RwTexture *texture, void* vstream) { + RwTextureGtaStreamWrite((RwStream*)vstream, texture); + return texture; + }, stream); +} + RwTexDictionary* RwTexDictionaryGtaStreamRead(RwStream *stream) { diff --git a/src/rw/TxdStore.cpp b/src/rw/TxdStore.cpp index a9e29729..b2f3ed7a 100644 --- a/src/rw/TxdStore.cpp +++ b/src/rw/TxdStore.cpp @@ -113,8 +113,12 @@ CTxdStore::AddRef(int slot) void CTxdStore::RemoveRef(int slot) { + #if !defined(DC_TEXCONV) if(--GetSlot(slot)->refCount <= 0) CStreaming::RemoveTxd(slot); + #else + assert(false); + #endif } void @@ -123,6 +127,31 @@ CTxdStore::RemoveRefWithoutDelete(int slot) GetSlot(slot)->refCount--; } +void StoreTxd(RwTexDictionary *texDict, RwStream *stream) { + auto fheader = stream->tell(); + // size will be written later + writeChunkHeader(stream, rwID_TEXDICTIONARY, 4); + auto fbegin = stream->tell(); + + RwTexDictionaryGtaStreamWrite(stream, texDict); + + // rewrite header for correct length + auto fend = stream->tell(); + stream->seek(fheader, 0); + writeChunkHeader(stream, rwID_TEXDICTIONARY, fend - fbegin); + stream->seek(fend, 0); +} +RwTexDictionary * +LoadTxd(RwStream *stream) +{ + RwUInt32 len; + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, &len, nil)){ + return RwTexDictionaryGtaStreamRead(stream); + } + return nullptr; +} + + bool CTxdStore::LoadTxd(int slot, RwStream *stream) { diff --git a/src/rw/VisibilityPlugins.cpp b/src/rw/VisibilityPlugins.cpp index e6d4641d..866b8d23 100644 --- a/src/rw/VisibilityPlugins.cpp +++ b/src/rw/VisibilityPlugins.cpp @@ -35,6 +35,8 @@ float CVisibilityPlugins::ms_pedLod0Dist; float CVisibilityPlugins::ms_pedLod1Dist; float CVisibilityPlugins::ms_pedFadeDist; +#if !defined(DC_TEXCONV) + #ifdef GTA_PS2 // maybe something else? // if wanted, delete the original geometry data after rendering // and only keep the instanced data @@ -830,8 +832,28 @@ CVisibilityPlugins::VehicleVisibilityCB_BigVehicle(RpClump *clump) return FrustumSphereCB(clump); } - - +#else +bool +CVisibilityPlugins::MloVisibilityCB(RpClump *clump) { + assert(false && "How did we get here?"); + return false; +} +bool +CVisibilityPlugins::DefaultVisibilityCB(RpClump *clump) { + assert(false && "How did we get here?"); + return false; +} +bool +CVisibilityPlugins::VehicleVisibilityCB(RpClump *clump) { + assert(false && "How did we get here?"); + return false; +} +bool +CVisibilityPlugins::VehicleVisibilityCB_BigVehicle(RpClump *clump) { + assert(false && "How did we get here?"); + return false; +} +#endif // // RW Plugins @@ -875,20 +897,20 @@ CVisibilityPlugins::PluginAttach(void) // void* -CVisibilityPlugins::AtomicConstructor(void *object, int32, int32) +CVisibilityPlugins::AtomicConstructor(void *object, RwInt32, RwInt32) { ATOMICEXT(object)->modelInfo = nil; return object; } void* -CVisibilityPlugins::AtomicDestructor(void *object, int32, int32) +CVisibilityPlugins::AtomicDestructor(void *object, RwInt32, RwInt32) { return object; } void* -CVisibilityPlugins::AtomicCopyConstructor(void *dst, const void *src, int32, int32) +CVisibilityPlugins::AtomicCopyConstructor(void *dst, const void *src, RwInt32, RwInt32) { *ATOMICEXT(dst) = *ATOMICEXT(src); return dst; @@ -898,6 +920,7 @@ void CVisibilityPlugins::SetAtomicModelInfo(RpAtomic *atomic, CSimpleModelInfo *modelInfo) { + #if !defined(DC_TEXCONV) AtomicExt *ext = ATOMICEXT(atomic); ext->modelInfo = modelInfo; switch (modelInfo->GetModelType()) { @@ -907,6 +930,9 @@ CVisibilityPlugins::SetAtomicModelInfo(RpAtomic *atomic, SetAtomicRenderCallback(atomic, RenderObjNormalAtomic); default: break; } + #else + assert(false); + #endif } CSimpleModelInfo* @@ -942,9 +968,13 @@ CVisibilityPlugins::GetAtomicId(RpAtomic *atomic) void CVisibilityPlugins::SetAtomicRenderCallback(RpAtomic *atomic, RpAtomicCallBackRender cb) { + #if !defined(DC_TEXCONV) if(cb == nil) cb = RENDERCALLBACK; RpAtomicSetRenderCallBack(atomic, cb); + #else + assert(false && "How did we get here?"); + #endif } // @@ -952,20 +982,20 @@ CVisibilityPlugins::SetAtomicRenderCallback(RpAtomic *atomic, RpAtomicCallBackRe // void* -CVisibilityPlugins::FrameConstructor(void *object, int32, int32) +CVisibilityPlugins::FrameConstructor(void *object, RwInt32, RwInt32) { FRAMEEXT(object)->id = 0; return object; } void* -CVisibilityPlugins::FrameDestructor(void *object, int32, int32) +CVisibilityPlugins::FrameDestructor(void *object, RwInt32, RwInt32) { return object; } void* -CVisibilityPlugins::FrameCopyConstructor(void *dst, const void *src, int32, int32) +CVisibilityPlugins::FrameCopyConstructor(void *dst, const void *src, RwInt32, RwInt32) { *FRAMEEXT(dst) = *FRAMEEXT(src); return dst; @@ -989,7 +1019,7 @@ CVisibilityPlugins::GetFrameHierarchyId(RwFrame *frame) // void* -CVisibilityPlugins::ClumpConstructor(void *object, int32, int32) +CVisibilityPlugins::ClumpConstructor(void *object, RwInt32, RwInt32) { ClumpExt *ext = CLUMPEXT(object); ext->visibilityCB = DefaultVisibilityCB; @@ -998,13 +1028,13 @@ CVisibilityPlugins::ClumpConstructor(void *object, int32, int32) } void* -CVisibilityPlugins::ClumpDestructor(void *object, int32, int32) +CVisibilityPlugins::ClumpDestructor(void *object, RwInt32, RwInt32) { return object; } void* -CVisibilityPlugins::ClumpCopyConstructor(void *dst, const void *src, int32, int32) +CVisibilityPlugins::ClumpCopyConstructor(void *dst, const void *src, RwInt32, RwInt32) { CLUMPEXT(dst)->visibilityCB = CLUMPEXT(src)->visibilityCB; return dst; diff --git a/src/rw/VisibilityPlugins.h b/src/rw/VisibilityPlugins.h index f97fd589..982aaaf1 100644 --- a/src/rw/VisibilityPlugins.h +++ b/src/rw/VisibilityPlugins.h @@ -98,10 +98,10 @@ public: static int GetAtomicId(RpAtomic *atomic); static void SetAtomicRenderCallback(RpAtomic*, RpAtomicCallBackRender); - static void *AtomicConstructor(void *object, int32 offset, int32 len); - static void *AtomicDestructor(void *object, int32 offset, int32 len); + static void *AtomicConstructor(void *object, RwInt32 offset, RwInt32 len); + static void *AtomicDestructor(void *object, RwInt32 offset, RwInt32 len); static void *AtomicCopyConstructor(void *dst, const void *src, - int32 offset, int32 len); + RwInt32 offset, RwInt32 len); static int32 ms_atomicPluginOffset; struct FrameExt @@ -112,10 +112,10 @@ public: static void SetFrameHierarchyId(RwFrame *frame, intptr id); static intptr GetFrameHierarchyId(RwFrame *frame); - static void *FrameConstructor(void *object, int32 offset, int32 len); - static void *FrameDestructor(void *object, int32 offset, int32 len); + static void *FrameConstructor(void *object, RwInt32 offset, RwInt32 len); + static void *FrameDestructor(void *object, RwInt32 offset, RwInt32 len); static void *FrameCopyConstructor(void *dst, const void *src, - int32 offset, int32 len); + RwInt32 offset, RwInt32 len); static int32 ms_framePluginOffset; struct ClumpExt @@ -129,10 +129,10 @@ public: static int GetClumpAlpha(RpClump*); static bool IsClumpVisible(RpClump*); - static void *ClumpConstructor(void *object, int32 offset, int32 len); - static void *ClumpDestructor(void *object, int32 offset, int32 len); + static void *ClumpConstructor(void *object, RwInt32 offset, RwInt32 len); + static void *ClumpDestructor(void *object, RwInt32 offset, RwInt32 len); static void *ClumpCopyConstructor(void *dst, const void *src, - int32 offset, int32 len); + RwInt32 offset, RwInt32 len); static int32 ms_clumpPluginOffset; static bool PluginAttach(void); diff --git a/src/save/GenericGameStorage.cpp b/src/save/GenericGameStorage.cpp index e4ee4542..84238082 100644 --- a/src/save/GenericGameStorage.cpp +++ b/src/save/GenericGameStorage.cpp @@ -38,6 +38,8 @@ #include "World.h" #include "Zones.h" +#include "../vmu/vmu.h" + #define BLOCK_COUNT 20 #define SIZE_OF_SIMPLEVARS 0xBC @@ -70,11 +72,10 @@ uint32 TimeToStayFadedBeforeFadeOut = 1750; #define LoadSaveDataBlock()\ do {\ - if (!ReadDataFromFile(file, (uint8 *) &size, 4))\ - return false;\ - size = align4bytes(size);\ - if (!ReadDataFromFile(file, work_buff, size))\ - return false;\ + size = C_PcSave::PcClassLoadRoutine(file, work_buff); \ + if (!size) {\ + return false; \ + } \ buf = work_buff;\ } while (0) @@ -267,10 +268,10 @@ GenericLoad() CheckSum = 0; CDate dummy; // unused CPad::ResetCheats(); - if (!ReadInSizeofSaveFileBuffer(file, size)) - return false; - size = align4bytes(size); - ReadDataFromFile(file, work_buff, size); + file = CFileMgr::OpenFile(LoadFileName, "rb"); + assert(file != 0); + size = C_PcSave::PcClassLoadRoutine(file, work_buff); + assert(size != 0); buf = (work_buff + 0x40); ReadDataFromBufferPointer(buf, saveSize); #ifdef MISSION_REPLAY // a hack to keep compatibility but get new data from save @@ -473,8 +474,10 @@ void MakeValidSaveName(int32 slot) { ValidSaveName[0] = '\0'; - sprintf(ValidSaveName, "%s%i", DefaultPCSaveFileName, slot + 1); + sprintf(ValidSaveName, "%s%i%s", slot==7?"GTA3SF":DefaultPCSaveFileName, slot + 1, slot==7?".b": ""); + #if !defined(RW_DC) strncat(ValidSaveName, ".b", 5); + #endif } wchar * @@ -492,6 +495,7 @@ GetNameOfSavedGame(int32 slot) bool CheckDataNotCorrupt(int32 slot, char *name) { + RAIIVmuBeep(VMU_DEFAULT_PATH, 1.0f); #ifdef FIX_BUGS char filename[MAX_PATH]; #else @@ -502,20 +506,19 @@ CheckDataNotCorrupt(int32 slot, char *name) eLevelName level = LEVEL_GENERIC; CheckSum = 0; uint32 bytes_processed = 0; + #if !defined(RW_DC) sprintf(filename, "%s%i%s", DefaultPCSaveFileName, slot + 1, ".b"); + #else + sprintf(filename, "%s%i%s", slot==7?"GTA3SF":DefaultPCSaveFileName, slot + 1, slot==7?".b": ""); + #endif int file = CFileMgr::OpenFile(filename, "rb"); if (file == 0) return false; strcpy(name, filename); while (SIZE_OF_ONE_GAME_IN_BYTES - sizeof(uint32) > bytes_processed && blocknum < 40) { int32 blocksize; - if (!ReadDataFromFile(file, (uint8*)&blocksize, sizeof(blocksize))) { - CloseFile(file); - return false; - } - if (blocksize > align4bytes(sizeof(work_buff))) - blocksize = sizeof(work_buff) - sizeof(uint32); - if (!ReadDataFromFile(file, work_buff, align4bytes(blocksize))) { + blocksize = C_PcSave::PcClassLoadRoutine(file, work_buff); + if (blocksize == 0) { CloseFile(file); return false; } @@ -553,21 +556,23 @@ CheckDataNotCorrupt(int32 slot, char *name) bool RestoreForStartLoad() { - uint8 buf[999]; - int file = CFileMgr::OpenFile(LoadFileName, "rb"); if (file == 0) { PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; return false; } - ReadDataFromFile(file, buf, sizeof(buf)); + + uint32_t size = C_PcSave::PcClassLoadRoutine(file, work_buff); + assert(size != 0); + uint8 *buf = work_buff; + if (CFileMgr::GetErrorReadWrite(file)) { PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; if (!CloseFile(file)) PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; return false; } else { - uint8 *_buf = buf + sizeof(int32) + sizeof(wchar[24]) + sizeof(SYSTEMTIME) + sizeof(SIZE_OF_ONE_GAME_IN_BYTES); + uint8 *_buf = buf + sizeof(wchar[24]) + sizeof(SYSTEMTIME) + sizeof(SIZE_OF_ONE_GAME_IN_BYTES); ReadDataFromBufferPointer(_buf, CGame::currLevel); ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().x); ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().y); diff --git a/src/save/PCSave.cpp b/src/save/PCSave.cpp index 0c228a6d..e8ff416a 100644 --- a/src/save/PCSave.cpp +++ b/src/save/PCSave.cpp @@ -12,6 +12,11 @@ #include "PCSave.h" #include "Text.h" +#include "minilzo.h" +#include "main.h" + +#include "../vmu/vmu.h" + const char* _psGetUserFilesFolder(); C_PcSave PcSaveHelper; @@ -19,7 +24,11 @@ C_PcSave PcSaveHelper; void C_PcSave::SetSaveDirectory(const char *path) { + #if defined(RW_DC) + sprintf(DefaultPCSaveFileName, "%s/%s", path, "GTA3SF"); + #else sprintf(DefaultPCSaveFileName, "%s\\%s", path, "GTA3sf"); + #endif } bool @@ -32,7 +41,7 @@ C_PcSave::DeleteSlot(int32 slot) #endif PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL; - sprintf(FileName, "%s%i.b", DefaultPCSaveFileName, slot + 1); + sprintf(FileName, "%s%i%s", slot==7?"GTA3SF":DefaultPCSaveFileName, slot + 1, slot==7?".b": ""); DeleteFile(FileName); SlotSaveDate[slot][0] = '\0'; return true; @@ -41,6 +50,7 @@ C_PcSave::DeleteSlot(int32 slot) bool C_PcSave::SaveSlot(int32 slot) { + RAIIVmuBeep(VMU_DEFAULT_PATH, 1.0f); MakeValidSaveName(slot); PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL; _psGetUserFilesFolder(); @@ -62,17 +72,79 @@ C_PcSave::SaveSlot(int32 slot) return false; } +uint32_t C_PcSave::PcClassLoadRoutine(int32 file, uint8 *data) { + uint32 size; + CFileMgr::Read(file, (char*)&size, sizeof(size)); + + + assert(data == work_buff); + + if (!(size & 0x80000000)) { + assert(align4bytes(size) == size); + CFileMgr::Read(file, (char*)data, align4bytes(size)); + if (CFileMgr::GetErrorReadWrite(file)) { + return 0; + } + return size; + } else { + size &= ~0x80000000; + uint8* compressed = (uint8*)malloc(size); + CFileMgr::Read(file, (const char*)compressed, size); + if (CFileMgr::GetErrorReadWrite(file)) { + free(compressed); + return 0; + } + + lzo_uint decompressed_size = 0; + auto crv = lzo1x_decompress(compressed, size, data, &decompressed_size, NULL); + free(compressed); + if (crv != LZO_E_OK) { + return 0; + } + + if (align4bytes(decompressed_size) != decompressed_size) { + return 0; + } + + return decompressed_size; + } +} bool C_PcSave::PcClassSaveRoutine(int32 file, uint8 *data, uint32 size) { - CFileMgr::Write(file, (const char*)&size, sizeof(size)); - if (CFileMgr::GetErrorReadWrite(file)) { - nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; - strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + void* wrkmem = malloc(LZO1X_1_MEM_COMPRESS); + uint8* compressed = (uint8*)malloc(size*2); + lzo_uint compressed_size; + int crv = lzo1x_1_compress(data, size, compressed, &compressed_size, wrkmem); + free(wrkmem); + + if (crv == LZO_E_OK) { + uint32_t compressed_size32 = compressed_size | 0x80000000; + CFileMgr::Write(file, (const char*)&compressed_size32, sizeof(compressed_size32)); + if (CFileMgr::GetErrorReadWrite(file)) { + free(compressed); + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + + CFileMgr::Write(file, (const char*)compressed, compressed_size); + free(compressed); + } else if (crv == LZO_E_NOT_COMPRESSIBLE) { + free(compressed); + uint32_t compressed_size32 = size; + CFileMgr::Write(file, (const char*)&compressed_size32, sizeof(compressed_size32)); + if (CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + CFileMgr::Write(file, (const char*)data, align4bytes(size)); + } else { + free(compressed); return false; } - CFileMgr::Write(file, (const char*)data, align4bytes(size)); CheckSum += (uint8) size; CheckSum += (uint8) (size >> 8); CheckSum += (uint8) (size >> 16); @@ -92,6 +164,7 @@ C_PcSave::PcClassSaveRoutine(int32 file, uint8 *data, uint32 size) void C_PcSave::PopulateSlotInfo() { + RAIIVmuBeep(VMU_DEFAULT_PATH, 1.0f); for (int i = 0; i < SLOT_COUNT; i++) { Slots[i + 1] = SLOT_EMPTY; SlotFileName[i][0] = '\0'; @@ -103,19 +176,18 @@ C_PcSave::PopulateSlotInfo() #else char savename[52]; #endif - struct { - int size; + struct header_t { wchar FileName[24]; SYSTEMTIME SaveDateTime; } header; - sprintf(savename, "%s%i%s", DefaultPCSaveFileName, i + 1, ".b"); + sprintf(savename, "%s%i%s", i==7?"GTA3SF":DefaultPCSaveFileName, i + 1, i==7?".b": ""); int file = CFileMgr::OpenFile(savename, "rb"); + printf("file: %s: %d\n", savename, file); if (file != 0) { - CFileMgr::Read(file, (char*)&header, sizeof(header)); - if (strncmp((char*)&header, TopLineEmptyFile, sizeof(TopLineEmptyFile)-1) != 0) { + if (C_PcSave::PcClassLoadRoutine(file, (uint8*)work_buff)) { + header = *(header_t*)work_buff; Slots[i + 1] = SLOT_OK; memcpy(SlotFileName[i], &header.FileName, sizeof(header.FileName)); - SlotFileName[i][24] = '\0'; } CFileMgr::CloseFile(file); diff --git a/src/save/PCSave.h b/src/save/PCSave.h index 83471b5d..932c0c81 100644 --- a/src/save/PCSave.h +++ b/src/save/PCSave.h @@ -34,6 +34,7 @@ public: bool DeleteSlot(int32 slot); bool SaveSlot(int32 slot); bool PcClassSaveRoutine(int32 file, uint8 *data, uint32 size); + static uint32_t PcClassLoadRoutine(int32 file, uint8 *data); static void SetSaveDirectory(const char *path); }; diff --git a/src/skel/crossplatform.cpp b/src/skel/crossplatform.cpp index e69c22e1..564a7e5d 100644 --- a/src/skel/crossplatform.cpp +++ b/src/skel/crossplatform.cpp @@ -91,7 +91,9 @@ void GetDateFormat(int unused1, int unused2, SYSTEMTIME* in, int unused3, char* linuxTime.tm_hour = in->wHour; linuxTime.tm_min = in->wMinute; linuxTime.tm_sec = in->wSecond; - strftime(out, size, nl_langinfo(D_FMT), &linuxTime); + // strftime(out, size, nl_langinfo(D_FMT), &linuxTime); + printf("TODO: FIXME %s\n",__func__); + strcpy(out,"abc"); } void FileTimeToSystemTime(time_t* writeTime, SYSTEMTIME* out) { @@ -152,6 +154,9 @@ FILE* _fcaseopen(char const* filename, char const* mode) result = fopen(real, mode); free(real); } + if (!result) { + printf("FAILED: _fcaseopen(%s, %s)\n", filename, mode); + } return result; } @@ -182,14 +187,18 @@ int _caserename(const char *old_filename, const char *new_filename) // Returned string should freed manually (if exists) char* casepath(char const* path, bool checkPathFirst) { - if (checkPathFirst && access(path, F_OK) != -1) { + //TODO: Implement this + bool access_ok = false; //access(path, F_OK) != -1 + // printf("TODO: FIXME %s\n", __func__); + + if (checkPathFirst && access_ok ) { // File path is correct return nil; } size_t l = strlen(path); char* p = (char*)alloca(l + 1); - char* out = (char*)malloc(l + 3); // for extra ./ + char* out = (char*)malloc(l + 10); // for extra /cd/ strcpy(p, path); // my addon: linux doesn't handle filenames with spaces at the end nicely @@ -250,34 +259,39 @@ char* casepath(char const* path, bool checkPathFirst) } struct dirent* e; - while (e = readdir(d)) - { - if (strcasecmp(c, e->d_name) == 0) + errno = 0; + if (strcmp(c, ".") != 0) { + while (e = readdir(d)) { - strcpy(out + rl, e->d_name); - int reportedLen = (int)strlen(e->d_name); - rl += reportedLen; - assert(reportedLen == strlen(c) && "casepath: This is not good at all"); + // printf("casepath: looking for %s, got %s\n", c, e->d_name); + if (strcasecmp(c, e->d_name) == 0) + { + strcpy(out + rl, e->d_name); + int reportedLen = (int)strlen(e->d_name); + rl += reportedLen; + assert(reportedLen == strlen(c) && "casepath: This is not good at all"); - closedir(d); - d = opendir(out); + closedir(d); + d = opendir(out); - // Either it wasn't a folder, or permission error, I/O error etc. - if (!d) { - cantProceed = true; + // Either it wasn't a folder, or permission error, I/O error etc. + if (!d) { + cantProceed = true; + } + + break; } - - break; } - } - if (!e) - { - printf("casepath couldn't find dir/file \"%s\", full path was %s\n", c, path); - // No match, add original name and continue converting further slashes. - strcpy(out + rl, c); - rl += strlen(c); - cantProceed = true; + if (!e) + { + printf("casepath couldn't find dir/file \"%s\", full path was %s, errno %d\n", c, path, errno); + // No match, add original name and continue converting further slashes. + perror("casepath"); + strcpy(out + rl, c); + rl += strlen(c); + cantProceed = true; + } } } diff --git a/src/skel/crossplatform.h b/src/skel/crossplatform.h index 67bb4282..ca04fe05 100644 --- a/src/skel/crossplatform.h +++ b/src/skel/crossplatform.h @@ -27,6 +27,9 @@ enum eWinVersion #if defined RW_D3D9 || defined RWLIBS #include "win.h" #endif +#if defined(DC_TEXCONV) +#include +#endif extern DWORD _dwOperatingSystemVersion; #define fcaseopen fopen #define caserename rename @@ -75,6 +78,24 @@ void CapturePad(RwInt32 padID); void joysChangeCB(int jid, int event); #endif +#ifdef RW_DC +typedef struct +{ + RwBool fullScreen; + RwV2d lastMousePos; + double mouseWheel; // glfw doesn't cache it + bool cursorIsInWindow; + RwInt8 joy1id; + RwInt8 joy2id; +} +psGlobalType; + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +void CapturePad(RwInt32 padID); +void joysChangeCB(int jid, int event); +#endif + #ifdef DETECT_JOYSTICK_MENU extern char gSelectedJoystickName[128]; #endif @@ -106,6 +127,9 @@ RwBool IsForegroundApp(); // Codes compatible with Windows and Linux #ifndef _WIN32 +#if defined(DC_SIM) +#include +#endif #define DeleteFile unlink // Needed for save games @@ -131,8 +155,8 @@ void GetLocalTime_CP(SYSTEMTIME* out); #include #include #include -#include -#include +// #include +// #include typedef void* HANDLE; #define INVALID_HANDLE_VALUE NULL diff --git a/src/skel/dc/dc.cpp b/src/skel/dc/dc.cpp new file mode 100644 index 00000000..b3c8ac38 --- /dev/null +++ b/src/skel/dc/dc.cpp @@ -0,0 +1,2823 @@ +#if defined RW_DC + +#include "../vmu/vmu.h" +#include +#include +#include + +#if !defined(DC_SIM) +#include +KOS_INIT_FLAGS(INIT_IRQ | INIT_CONTROLLER | INIT_CDROM | INIT_VMU); +#include "../prof/profiler.h" +#endif + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include + +DWORD _dwOperatingSystemVersion; +#include "resource.h" +#else +long _dwOperatingSystemVersion; +#ifndef __SWITCH__ +#ifndef __APPLE__ +// #include +#else +#include +#include +#endif +#endif +#include +#include +#include +#include +#endif + +#include "common.h" +#if (defined(_MSC_VER)) +#include +#endif /* (defined(_MSC_VER)) */ +#include +#include "rwcore.h" +#include "skeleton.h" +#include "platform.h" +#include "crossplatform.h" + +#include "main.h" +#include "FileMgr.h" +#include "Text.h" +#include "Pad.h" +#include "Timer.h" +#include "DMAudio.h" +#include "ControllerConfig.h" +#include "Frontend.h" +#include "Game.h" +#include "PCSave.h" +#include "MemoryCard.h" +#include "Sprite2d.h" +#include "AnimViewer.h" +#include "Font.h" +#include "MemoryMgr.h" + +// This is defined on project-level, via premake5 or cmake +#ifdef GET_KEYBOARD_INPUT_FROM_X11 +#include +#include +#define GLFW_EXPOSE_NATIVE_X11 +#include +#endif + +#ifdef _WIN32 +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#endif + +#include + +#if !defined(DC_SIM) +# if defined(WITH_IDE) +#include +#include +#include +# endif + +#include +#include +#endif + +// //TODO: these are somehow missing +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC ((clockid_t) 4) +#endif +// #define CLOCK_MONOTONIC_RAW ((clockid_t) 5) +// #define CLOCK_MONOTONIC_COARSE ((clockid_t) 6) + + +#define MAX_SUBSYSTEMS (16) + +rw::EngineOpenParams openParams; + +static RwBool ForegroundApp = TRUE; +static RwBool WindowIconified = FALSE; +static RwBool WindowFocused = TRUE; + +static RwBool RwInitialised = FALSE; + +static RwSubSystemInfo GsubSysInfo[MAX_SUBSYSTEMS]; +static RwInt32 GnumSubSystems = 0; +static RwInt32 GcurSel = 0, GcurSelVM = 0; + +static RwBool useDefault; + +// What is that for anyway? +#ifndef IMPROVED_VIDEOMODE +static RwBool defaultFullscreenRes = TRUE; +#else +static RwBool defaultFullscreenRes = FALSE; +static RwInt32 bestWndMode = -1; +#endif + +static psGlobalType PsGlobal; + + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +size_t _dwMemAvailPhys; +RwUInt32 gGameState; + +#ifdef DETECT_JOYSTICK_MENU +char gSelectedJoystickName[128] = ""; +#endif + +#if !defined(DC_SIM) && defined(WITH_IDE) +static kos_blockdev_t ide_rv; + +static void ide_fat_init(void) +{ + uint8_t type; + int err; + + err = g1_ata_init(); + if (err) + return; + + err = g1_ata_blockdev_for_partition(0, 1, &ide_rv, &type); + if (err) + return; + + printf("Found IDE partition 0\n"); + + err = fs_fat_init(); + if (err) + return; + + err = fs_fat_mount("/ide", &ide_rv, FS_FAT_MOUNT_READONLY); + if (err) + return; + + printf("Mounted IDE partition 0 to /ide\n"); +} +#endif + +#if !defined(DC_SIM) +#if defined(WITH_PROF) +static bool profiling = false; +#endif +#if defined(WITH_SD) +static kos_blockdev_t sd_rv; + +static void sd_fat_init(void) +{ + uint8_t type; + int err; + + err = sd_init(); + if (err) + return; + + err = sd_blockdev_for_partition(0, &sd_rv, &type); + if (err) + return; + + printf("Found SD partition 0\n"); + + err = fs_fat_init(); + if (err) + return; + + err = fs_fat_mount("/sd", &sd_rv, FS_FAT_MOUNT_READWRITE); + if (err) + return; + + printf("Mounted SD partition 0 to /sd\n"); +} +#endif +#endif + +/* + ***************************************************************************** + */ +void _psCreateFolder(const char *path) +{ +#ifdef _WIN32 + HANDLE hfle = CreateFile(path, GENERIC_READ, + FILE_SHARE_READ, + nil, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, + nil); + + if ( hfle == INVALID_HANDLE_VALUE ) + CreateDirectory(path, nil); + else + CloseHandle(hfle); +#else + struct stat info; + char fullpath[PATH_MAX]; + realpath(path, fullpath); + + if (stat(fullpath, &info) != 0) { + if (errno == ENOENT || (errno != EACCES && !S_ISDIR(info.st_mode))) { + mkdir(fullpath, 0755); + } + } +#endif +} + +/* + ***************************************************************************** + */ +const char *_psGetUserFilesFolder() +{ +#if defined USE_MY_DOCUMENTS && defined _WIN32 + HKEY hKey = NULL; + + static CHAR szUserFiles[256]; + + if ( RegOpenKeyEx(HKEY_CURRENT_USER, + REGSTR_PATH_SPECIAL_FOLDERS, + REG_OPTION_RESERVED, + KEY_READ, + &hKey) == ERROR_SUCCESS ) + { + DWORD KeyType; + DWORD KeycbData = sizeof(szUserFiles); + if ( RegQueryValueEx(hKey, + "Personal", + NULL, + &KeyType, + (LPBYTE)szUserFiles, + &KeycbData) == ERROR_SUCCESS ) + { + RegCloseKey(hKey); + strcat(szUserFiles, "\\GTA3 User Files"); + _psCreateFolder(szUserFiles); + return szUserFiles; + } + + RegCloseKey(hKey); + } + + strcpy(szUserFiles, "data"); + return szUserFiles; +#else + static char szUserFiles[256]; + strcpy(szUserFiles, "/vmu/" VMU_DEFAULT_PATH); + // _psCreateFolder(szUserFiles); + return szUserFiles; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psCameraBeginUpdate(RwCamera *camera) +{ + if ( !RwCameraBeginUpdate(Scene.camera) ) + { + ForegroundApp = FALSE; + RsEventHandler(rsACTIVATE, (void *)FALSE); + return FALSE; + } + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +psCameraShowRaster(RwCamera *camera) +{ + if (CMenuManager::m_PrefsVsync) + RwCameraShowRaster(camera, NULL /*PSGLOBAL(window)*/, rwRASTERFLIPWAITVSYNC); + else + RwCameraShowRaster(camera, NULL /*PSGLOBAL(window)*/, rwRASTERFLIPDONTWAIT); + + return; +} + +/* + ***************************************************************************** + */ +RwImage * +psGrabScreen(RwCamera *pCamera) +{ +#ifndef LIBRW + RwRaster *pRaster = RwCameraGetRaster(pCamera); + if (RwImage *pImage = RwImageCreate(pRaster->width, pRaster->height, 32)) { + RwImageAllocatePixels(pImage); + RwImageSetFromRaster(pImage, pRaster); + return pImage; + } +#else + rw::Image *image = RwCameraGetRaster(pCamera)->toImage(); + image->removeMask(); + if(image) + return image; +#endif + return nil; +} + +/* + ***************************************************************************** + */ +#ifdef _WIN32 +#pragma comment( lib, "Winmm.lib" ) // Needed for time +RwUInt32 +psTimer(void) +{ + RwUInt32 time; + + TIMECAPS TimeCaps; + + timeGetDevCaps(&TimeCaps, sizeof(TIMECAPS)); + + timeBeginPeriod(TimeCaps.wPeriodMin); + + time = (RwUInt32) timeGetTime(); + + timeEndPeriod(TimeCaps.wPeriodMin); + + return time; +} +#else +double +psTimer(void) +{ +#if defined(DC_SH4) + // Clock off AICA + // + // according to purist, sh4 is 199.5MHz (KOS assumes 200 mhz) + // and the sh4 has a different clock domain from AICA + // + // This solves the sound drift issue in the part 2 of the intro + // + // N.B. This depends on the jiffies per second from AICA + // and only works after AICA has been initialized + #define AICA_MEM_CLOCK 0x021000 /* 4 bytes */ + uint32_t jiffies = g2_read_32(SPU_RAM_UNCACHED_BASE + AICA_MEM_CLOCK); + return jiffies / 4.410f; +#else + struct timespec start; +#if defined(CLOCK_MONOTONIC_RAW) + clock_gettime(CLOCK_MONOTONIC_RAW, &start); +#elif defined(CLOCK_MONOTONIC_FAST) + clock_gettime(CLOCK_MONOTONIC_FAST, &start); +#else + clock_gettime(CLOCK_MONOTONIC, &start); +#endif + return start.tv_sec * 1000.0 + start.tv_nsec/1000000.0; +#endif +} +#endif + + +/* + ***************************************************************************** + */ +void +psMouseSetPos(RwV2d *pos) +{ + //glfwSetCursorPos(PSGLOBAL(window), pos->x, pos->y); + + PSGLOBAL(lastMousePos.x) = (RwInt32)pos->x; + + PSGLOBAL(lastMousePos.y) = (RwInt32)pos->y; + + return; +} + +/* + ***************************************************************************** + */ +RwMemoryFunctions* +psGetMemoryFunctions(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + return &memFuncs; +#else + return nil; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psInstallFileSystem(void) +{ + return (TRUE); +} + + +/* + ***************************************************************************** + */ +RwBool +psNativeTextureSupport(void) +{ + return true; +} + +/* + ***************************************************************************** + */ +#ifdef UNDER_CE +#define CMDSTR LPWSTR +#else +#define CMDSTR LPSTR +#endif + +/* + ***************************************************************************** + */ + +#ifdef __SWITCH__ + +static HidVibrationValue SwitchVibrationValues[2]; +static HidVibrationDeviceHandle SwitchVibrationDeviceHandles[2][2]; +static HidVibrationDeviceHandle SwitchVibrationDeviceGC; + +static PadState SwitchPad; + +static Result HidInitializationResult[2]; +static Result HidInitializationGCResult; + +static void _psInitializeVibration() +{ + HidInitializationResult[0] = hidInitializeVibrationDevices(SwitchVibrationDeviceHandles[0], 2, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld); + if(R_FAILED(HidInitializationResult[0])) { + printf("Failed to initialize VibrationDevice for Handheld Mode\n"); + } + HidInitializationResult[1] = hidInitializeVibrationDevices(SwitchVibrationDeviceHandles[1], 2, HidNpadIdType_No1, HidNpadStyleSet_NpadFullCtrl); + if(R_FAILED(HidInitializationResult[1])) { + printf("Failed to initialize VibrationDevice for Detached Mode\n"); + } + HidInitializationGCResult = hidInitializeVibrationDevices(&SwitchVibrationDeviceGC, 1, HidNpadIdType_No1, HidNpadStyleTag_NpadGc); + if(R_FAILED(HidInitializationResult[1])) { + printf("Failed to initialize VibrationDevice for GC Mode\n"); + } + + SwitchVibrationValues[0].freq_low = 160.0f; + SwitchVibrationValues[0].freq_high = 320.0f; + + padConfigureInput(1, HidNpadStyleSet_NpadFullCtrl); + padInitializeDefault(&SwitchPad); +} + +static void _psHandleVibration() +{ + padUpdate(&SwitchPad); + + uint8 target_device = padIsHandheld(&SwitchPad) ? 0 : 1; + + if(R_SUCCEEDED(HidInitializationResult[target_device])) { + CPad* pad = CPad::GetPad(0); + + // value conversion based on SDL2 switch port + SwitchVibrationValues[0].amp_high = SwitchVibrationValues[0].amp_low = pad->ShakeFreq == 0 ? 0.0f : 320.0f; + SwitchVibrationValues[0].freq_low = pad->ShakeFreq == 0.0 ? 160.0f : (float)pad->ShakeFreq * 1.26f; + SwitchVibrationValues[0].freq_high = pad->ShakeFreq == 0.0 ? 320.0f : (float)pad->ShakeFreq * 1.26f; + + if (pad->ShakeDur < CTimer::GetTimeStepInMilliseconds()) + pad->ShakeDur = 0; + else + pad->ShakeDur -= CTimer::GetTimeStepInMilliseconds(); + if (pad->ShakeDur == 0) pad->ShakeFreq = 0; + + + if(target_device == 1 && R_SUCCEEDED(HidInitializationGCResult)) { + // gamecube rumble + hidSendVibrationGcErmCommand(SwitchVibrationDeviceGC, pad->ShakeFreq > 0 ? HidVibrationGcErmCommand_Start : HidVibrationGcErmCommand_Stop); + } + + memcpy(&SwitchVibrationValues[1], &SwitchVibrationValues[0], sizeof(HidVibrationValue)); + hidSendVibrationValues(SwitchVibrationDeviceHandles[target_device], SwitchVibrationValues, 2); + } +} +#else +static void _psInitializeVibration() {} +#ifndef __DREAMCAST__ +static void _psHandleVibration() {} +#endif +#endif + +/* + ***************************************************************************** + */ +RwBool +psInitialize(void) +{ + PsGlobal.lastMousePos.x = PsGlobal.lastMousePos.y = 0.0f; + + RsGlobal.ps = &PsGlobal; + + PsGlobal.fullScreen = FALSE; + PsGlobal.cursorIsInWindow = FALSE; + WindowFocused = TRUE; + WindowIconified = FALSE; + + PsGlobal.joy1id = -1; + PsGlobal.joy2id = -1; + + CFileMgr::Initialise(); + + CPad::Initialise(); + CPad::GetPad(0)->Mode = 0; + +#ifdef PS2_MENU + CPad::Initialise(); + CPad::GetPad(0)->Mode = 0; + + CGame::frenchGame = false; + CGame::germanGame = false; + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + +#ifndef _WIN32 + // Mandatory for Linux(Unix? Posix?) to set lang. to environment lang. + setlocale(LC_ALL, ""); + + char *systemLang, *keyboardLang; + + systemLang = setlocale (LC_ALL, NULL); + keyboardLang = setlocale (LC_CTYPE, NULL); + + short lang; + lang = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH : + !strncmp(systemLang, "de_",3) ? LANG_GERMAN : + !strncmp(systemLang, "en_",3) ? LANG_ENGLISH : + !strncmp(systemLang, "it_",3) ? LANG_ITALIAN : + !strncmp(systemLang, "es_",3) ? LANG_SPANISH : + LANG_OTHER; +#else + WORD lang = PRIMARYLANGID(GetSystemDefaultLCID()); +#endif + + if ( lang == LANG_ITALIAN ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + else if ( lang == LANG_SPANISH ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + else if ( lang == LANG_GERMAN ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + } + else if ( lang == LANG_FRENCH ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + } + else + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + + TheMemoryCard.Init(); +#else + C_PcSave::SetSaveDirectory(_psGetUserFilesFolder()); + + InitialiseLanguage(); + +#if GTA_VERSION < GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + _psInitializeVibration(); + + gGameState = GS_START_UP; + TRACE("gGameState = GS_START_UP"); +#ifdef _WIN32 + OSVERSIONINFO verInfo; + verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + GetVersionEx(&verInfo); + + _dwOperatingSystemVersion = OS_WIN95; + + if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + if ( verInfo.dwMajorVersion == 4 ) + { + debug("Operating System is WinNT\n"); + _dwOperatingSystemVersion = OS_WINNT; + } + else if ( verInfo.dwMajorVersion == 5 ) + { + debug("Operating System is Win2000\n"); + _dwOperatingSystemVersion = OS_WIN2000; + } + else if ( verInfo.dwMajorVersion > 5 ) + { + debug("Operating System is WinXP or greater\n"); + _dwOperatingSystemVersion = OS_WINXP; + } + } + else if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + if ( verInfo.dwMajorVersion > 4 || verInfo.dwMajorVersion == 4 && verInfo.dwMinorVersion != 0 ) + { + debug("Operating System is Win98\n"); + _dwOperatingSystemVersion = OS_WIN98; + } + else + { + debug("Operating System is Win95\n"); + _dwOperatingSystemVersion = OS_WIN95; + } + } +#else + _dwOperatingSystemVersion = OS_WINXP; // To fool other classes +#endif + + +#ifndef PS2_MENU + +#if GTA_VERSION >= GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + +#ifdef _WIN32 + MEMORYSTATUS memstats; + GlobalMemoryStatus(&memstats); + + _dwMemAvailPhys = memstats.dwAvailPhys; + + debug("Physical memory size %u\n", memstats.dwTotalPhys); + debug("Available physical memory %u\n", memstats.dwAvailPhys); +#elif defined (__APPLE__) + uint64_t size = 0; + uint64_t page_size = 0; + size_t uint64_len = sizeof(uint64_t); + size_t ull_len = sizeof(unsigned long long); + sysctl((int[]){CTL_HW, HW_PAGESIZE}, 2, &page_size, &ull_len, NULL, 0); + sysctl((int[]){CTL_HW, HW_MEMSIZE}, 2, &size, &uint64_len, NULL, 0); + vm_statistics_data_t vm_stat; + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vm_stat, &count); + _dwMemAvailPhys = (uint64_t)(vm_stat.free_count * page_size); + debug("Physical memory size %llu\n", _dwMemAvailPhys); + debug("Available physical memory %llu\n", size); +#elif defined (__SWITCH__) + svcGetInfo(&_dwMemAvailPhys, InfoType_UsedMemorySize, CUR_PROCESS_HANDLE, 0); + debug("Physical memory size %llu\n", _dwMemAvailPhys); +#else + // struct sysinfo systemInfo; + // sysinfo(&systemInfo); + _dwMemAvailPhys = 10 * 1024 * 1024 ; //systemInfo.freeram; + // debug("Physical memory size %u\n", systemInfo.totalram); + // debug("Available physical memory %u\n", systemInfo.freeram); +#endif + + TheText.Unload(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +void +psTerminate(void) +{ + return; +} + +/* + ***************************************************************************** + */ +static RwChar **_VMList; + +RwInt32 _psGetNumVideModes() +{ + return RwEngineGetNumVideoModes(); +} + +/* + ***************************************************************************** + */ +RwBool _psFreeVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + numModes = _psGetNumVideModes(); + + if ( _VMList == nil ) + return TRUE; + + for ( i = 0; i < numModes; i++ ) + { + RwFree(_VMList[i]); + } + + RwFree(_VMList); + + _VMList = nil; + + return TRUE; +} + +/* + ***************************************************************************** + */ +RwChar **_psGetVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + if ( _VMList != nil ) + { + return _VMList; + } + + numModes = RwEngineGetNumVideoModes(); + + _VMList = (RwChar **)RwCalloc(numModes, sizeof(RwChar*)); + + for ( i = 0; i < numModes; i++ ) + { + RwVideoMode vm; + + RwEngineGetVideoModeInfo(&vm, i); + + if ( vm.flags & rwVIDEOMODEEXCLUSIVE ) + { + _VMList[i] = (RwChar*)RwCalloc(100, sizeof(RwChar)); + rwsprintf(_VMList[i],"%d X %d X %d", vm.width, vm.height, vm.depth); + } + else + _VMList[i] = nil; + } + + return _VMList; +} + +/* + ***************************************************************************** + */ +void _psSelectScreenVM(RwInt32 videoMode) +{ + RwTexDictionarySetCurrent( nil ); + + FrontEndMenuManager.UnloadTextures(); + + if (!_psSetVideoMode(RwEngineGetCurrentSubSystem(), videoMode)) + { + RsGlobal.quit = TRUE; + + printf("ERROR: Failed to select new screen resolution\n"); + } + else + FrontEndMenuManager.LoadAllTextures(); +} + +/* + ***************************************************************************** + */ + +RwBool IsForegroundApp() +{ + return !!ForegroundApp; +} +/* +UINT GetBestRefreshRate(UINT width, UINT height, UINT depth) +{ + LPDIRECT3D8 d3d = Direct3DCreate8(D3D_SDK_VERSION); + + ASSERT(d3d != nil); + + UINT refreshRate = INT_MAX; + D3DFORMAT format; + + if ( depth == 32 ) + format = D3DFMT_X8R8G8B8; + else if ( depth == 24 ) + format = D3DFMT_R8G8B8; + else + format = D3DFMT_R5G6B5; + + UINT modeCount = d3d->GetAdapterModeCount(GcurSel); + + for ( UINT i = 0; i < modeCount; i++ ) + { + D3DDISPLAYMODE mode; + + d3d->EnumAdapterModes(GcurSel, i, &mode); + + if ( mode.Width == width && mode.Height == height && mode.Format == format ) + { + if ( mode.RefreshRate == 0 ) + return 0; + + if ( mode.RefreshRate < refreshRate && mode.RefreshRate >= 60 ) + refreshRate = mode.RefreshRate; + } + } + +#ifdef FIX_BUGS + d3d->Release(); +#endif + + if ( refreshRate == -1 ) + return -1; + + return refreshRate; +} +*/ +/* + ***************************************************************************** + */ +RwBool +psSelectDevice() +{ + RwVideoMode vm; + RwInt32 subSysNum; + RwInt32 AutoRenderer = 0; + + + RwBool modeFound = FALSE; + + if ( !useDefault ) + { + GnumSubSystems = RwEngineGetNumSubSystems(); + if ( !GnumSubSystems ) + { + return FALSE; + } + + /* Just to be sure ... */ + GnumSubSystems = (GnumSubSystems > MAX_SUBSYSTEMS) ? MAX_SUBSYSTEMS : GnumSubSystems; + + /* Get the names of all the sub systems */ + for (subSysNum = 0; subSysNum < GnumSubSystems; subSysNum++) + { + RwEngineGetSubSystemInfo(&GsubSysInfo[subSysNum], subSysNum); + } + + /* Get the default selection */ + GcurSel = RwEngineGetCurrentSubSystem(); +#ifdef IMPROVED_VIDEOMODE + if(FrontEndMenuManager.m_nPrefsSubsystem < GnumSubSystems) + GcurSel = FrontEndMenuManager.m_nPrefsSubsystem; +#endif + } + + /* Set the driver to use the correct sub system */ + if (!RwEngineSetSubSystem(GcurSel)) + { + return FALSE; + } + +#ifdef IMPROVED_VIDEOMODE + FrontEndMenuManager.m_nPrefsSubsystem = GcurSel; +#endif + +#ifndef IMPROVED_VIDEOMODE + if ( !useDefault ) + { + if ( _psGetVideoModeList()[FrontEndMenuManager.m_nDisplayVideoMode] && FrontEndMenuManager.m_nDisplayVideoMode ) + { + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + GcurSelVM = FrontEndMenuManager.m_nDisplayVideoMode; + } + else + { +#ifdef DEFAULT_NATIVE_RESOLUTION + // get the native video mode + HDC hDevice = GetDC(NULL); + int w = GetDeviceCaps(hDevice, HORZRES); + int h = GetDeviceCaps(hDevice, VERTRES); + int d = GetDeviceCaps(hDevice, BITSPIXEL); +#else + const int w = 640; + const int h = 480; + const int d = 16; +#endif + while ( !modeFound && GcurSelVM < RwEngineGetNumVideoModes() ) + { + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + if ( defaultFullscreenRes && vm.width != w + || vm.height != h + || vm.depth != d + || !(vm.flags & rwVIDEOMODEEXCLUSIVE) ) + ++GcurSelVM; + else + modeFound = TRUE; + } + + if ( !modeFound ) + { +#ifdef DEFAULT_NATIVE_RESOLUTION + GcurSelVM = 1; +#else + printf("WARNING: Cannot find 640x480 video mode, selecting device cancelled\n"); + return FALSE; +#endif + } + } + } +#else + if ( !useDefault ) + { + if(FrontEndMenuManager.m_nPrefsWidth == 0 || + FrontEndMenuManager.m_nPrefsHeight == 0 || + FrontEndMenuManager.m_nPrefsDepth == 0){ + // Defaults if nothing specified + //const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + FrontEndMenuManager.m_nPrefsWidth = 640; //mode->width; + FrontEndMenuManager.m_nPrefsHeight = 480; //mode->height; + FrontEndMenuManager.m_nPrefsDepth = 16; + FrontEndMenuManager.m_nPrefsWindowed = 0; + } + + // Find the videomode that best fits what we got from the settings file + RwInt32 bestFsMode = -1; + RwInt32 bestWidth = -1; + RwInt32 bestHeight = -1; + RwInt32 bestDepth = -1; + for(GcurSelVM = 0; GcurSelVM < RwEngineGetNumVideoModes(); GcurSelVM++){ + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + bestWndMode = GcurSelVM; + bestWidth = vm.width; + bestHeight = vm.height; + bestDepth = vm.depth; + bestFsMode = GcurSelVM; + break; + } + + if(bestFsMode < 0){ + printf("WARNING: Cannot find desired video mode, selecting device cancelled\n"); + return FALSE; + } + GcurSelVM = bestFsMode; + + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + + FrontEndMenuManager.m_nSelectedScreenMode = FrontEndMenuManager.m_nPrefsWindowed; + } +#endif + + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + +#ifdef IMPROVED_VIDEOMODE + if (FrontEndMenuManager.m_nPrefsWindowed) + GcurSelVM = bestWndMode; + + // Now GcurSelVM is 0 but vm has sizes(and fullscreen flag) of the video mode we want, that's why we changed the rwVIDEOMODEEXCLUSIVE conditions below + FrontEndMenuManager.m_nPrefsWidth = vm.width; + FrontEndMenuManager.m_nPrefsHeight = vm.height; + FrontEndMenuManager.m_nPrefsDepth = vm.depth; +#endif + +#ifndef PS2_MENU + FrontEndMenuManager.m_nCurrOption = 0; +#endif + + /* Set up the video mode and set the apps window + * dimensions to match */ + if (!RwEngineSetVideoMode(GcurSelVM)) + { + return FALSE; + } + /* + TODO + if (vm.flags & rwVIDEOMODEEXCLUSIVE) + { + debug("%dx%dx%d", vm.width, vm.height, vm.depth); + + UINT refresh = GetBestRefreshRate(vm.width, vm.height, vm.depth); + + if ( refresh != (UINT)-1 ) + { + debug("refresh %d", refresh); + RwD3D8EngineSetRefreshRate((RwUInt32)refresh); + } + } + */ +#ifndef IMPROVED_VIDEOMODE + if (vm.flags & rwVIDEOMODEEXCLUSIVE) + { + RsGlobal.maximumWidth = vm.width; + RsGlobal.maximumHeight = vm.height; + RsGlobal.width = vm.width; + RsGlobal.height = vm.height; + + PSGLOBAL(fullScreen) = TRUE; + } +#else + RsGlobal.maximumWidth = FrontEndMenuManager.m_nPrefsWidth; + RsGlobal.maximumHeight = FrontEndMenuManager.m_nPrefsHeight; + RsGlobal.width = FrontEndMenuManager.m_nPrefsWidth; + RsGlobal.height = FrontEndMenuManager.m_nPrefsHeight; + + PSGLOBAL(fullScreen) = !FrontEndMenuManager.m_nPrefsWindowed; +#endif + +#ifdef MULTISAMPLING + RwD3D8EngineSetMultiSamplingLevels(1 << FrontEndMenuManager.m_nPrefsMSAALevel); +#endif + return TRUE; +} + +// #ifndef GET_KEYBOARD_INPUT_FROM_X11 +// void keypressCB(GLFWwindow* window, int key, int scancode, int action, int mods); +// #endif +// void resizeCB(GLFWwindow* window, int width, int height); +// void scrollCB(GLFWwindow* window, double xoffset, double yoffset); +// void cursorCB(GLFWwindow* window, double xpos, double ypos); +// void cursorEnterCB(GLFWwindow* window, int entered); +// void windowFocusCB(GLFWwindow* window, int focused); +// void windowIconifyCB(GLFWwindow* window, int iconified); +// void joysChangeCB(int jid, int event); + +bool IsThisJoystickBlacklisted(int i) +{ +#ifndef DETECT_JOYSTICK_MENU + return false; +#else + if (glfwJoystickIsGamepad(i)) + return false; + + const char* joyname = glfwGetJoystickName(i); + + if (gSelectedJoystickName[0] != '\0' && + strncmp(joyname, gSelectedJoystickName, strlen(gSelectedJoystickName)) == 0) + return false; + + return true; +#endif +} + +void _InputInitialiseJoys() +{ + PSGLOBAL(joy1id) = -1; + PSGLOBAL(joy2id) = -1; + + // Load our gamepad mappings. +#define SDL_GAMEPAD_DB_PATH "gamecontrollerdb.txt" + FILE *f = fcaseopen(SDL_GAMEPAD_DB_PATH, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + size_t fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + char *db = (char*)malloc(fsize + 1); + if (fread(db, 1, fsize, f) == fsize) { + db[fsize] = '\0'; + + // if (glfwUpdateGamepadMappings(db) == GLFW_FALSE) + // Error("glfwUpdateGamepadMappings didn't succeed, check " SDL_GAMEPAD_DB_PATH ".\n"); + } else + Error("fread on " SDL_GAMEPAD_DB_PATH " wasn't successful.\n"); + + free(db); + fclose(f); + } else + printf("You don't seem to have copied " SDL_GAMEPAD_DB_PATH " file from re3/gamefiles to GTA3 directory. Some gamepads may not be recognized.\n"); + +#undef SDL_GAMEPAD_DB_PATH + + // But always overwrite it with the one in SDL_GAMECONTROLLERCONFIG. + char const* EnvControlConfig = getenv("SDL_GAMECONTROLLERCONFIG"); + if (EnvControlConfig != nil) { + //glfwUpdateGamepadMappings(EnvControlConfig); + } + + // for (int i = 0; i <= GLFW_JOYSTICK_LAST; i++) { + // if (glfwJoystickPresent(i) && !IsThisJoystickBlacklisted(i)) { + // if (PSGLOBAL(joy1id) == -1) + // PSGLOBAL(joy1id) = i; + // else if (PSGLOBAL(joy2id) == -1) + // PSGLOBAL(joy2id) = i; + // else + // break; + // } + // } + +// if (PSGLOBAL(joy1id) != -1) { +// int count; +// glfwGetJoystickButtons(PSGLOBAL(joy1id), &count); +// #ifdef DETECT_JOYSTICK_MENU +// strcpy(gSelectedJoystickName, glfwGetJoystickName(PSGLOBAL(joy1id))); +// #endif +// ControlsManager.InitDefaultControlConfigJoyPad(count); +// } +} + +long _InputInitialiseMouse() +{ + // glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + return 0; +} + +void psPostRWinit(void) +{ + RwVideoMode vm; + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + +// glfwSetFramebufferSizeCallback(PSGLOBAL(window), resizeCB); +// #ifndef IGNORE_MOUSE_KEYBOARD +// #ifndef GET_KEYBOARD_INPUT_FROM_X11 +// glfwSetKeyCallback(PSGLOBAL(window), keypressCB); +// #endif +// glfwSetScrollCallback(PSGLOBAL(window), scrollCB); +// glfwSetCursorPosCallback(PSGLOBAL(window), cursorCB); +// glfwSetCursorEnterCallback(PSGLOBAL(window), cursorEnterCB); +// #endif +// glfwSetWindowIconifyCallback(PSGLOBAL(window), windowIconifyCB); +// glfwSetWindowFocusCallback(PSGLOBAL(window), windowFocusCB); +// glfwSetJoystickCallback(joysChangeCB); + + _InputInitialiseJoys(); + _InputInitialiseMouse(); + + // if(!(vm.flags & rwVIDEOMODEEXCLUSIVE)) + // glfwSetWindowSize(PSGLOBAL(window), RsGlobal.maximumWidth, RsGlobal.maximumHeight); + + // Make sure all keys are released + CPad::GetPad(0)->Clear(true); + CPad::GetPad(1)->Clear(true); +} + +/* + ***************************************************************************** + */ +RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode) +{ + RwInitialised = FALSE; + + RsEventHandler(rsRWTERMINATE, nil); + + GcurSel = subSystem; + GcurSelVM = videoMode; + + useDefault = TRUE; + + if ( RsEventHandler(rsRWINITIALIZE, &openParams) == rsEVENTERROR ) + return FALSE; + + RwInitialised = TRUE; + useDefault = FALSE; + + RwRect r; + + r.x = 0; + r.y = 0; + r.w = RsGlobal.maximumWidth; + r.h = RsGlobal.maximumHeight; + + RsEventHandler(rsCAMERASIZE, &r); + + psPostRWinit(); + + return TRUE; +} + + +// /* +// ***************************************************************************** +// */ +// static RwChar ** +// CommandLineToArgv(RwChar *cmdLine, RwInt32 *argCount) +// { +// RwInt32 numArgs = 0; +// RwBool inArg, inString; +// RwInt32 i, len; +// RwChar *res, *str, **aptr; + +// len = strlen(cmdLine); + +// /* +// * Count the number of arguments... +// */ +// inString = FALSE; +// inArg = FALSE; + +// for(i=0; i<=len; i++) +// { +// if( cmdLine[i] == '"' ) +// { +// inString = !inString; +// } + +// if( (cmdLine[i] <= ' ' && !inString) || i == len ) +// { +// if( inArg ) +// { +// inArg = FALSE; + +// numArgs++; +// } +// } +// else if( !inArg ) +// { +// inArg = TRUE; +// } +// } + +// /* +// * Allocate memory for result... +// */ +// res = (RwChar *)malloc(sizeof(RwChar *) * numArgs + len + 1); +// str = res + sizeof(RwChar *) * numArgs; +// aptr = (RwChar **)res; + +// strcpy(str, cmdLine); + +// /* +// * Walk through cmdLine again this time setting pointer to each arg... +// */ +// inArg = FALSE; +// inString = FALSE; + +// for(i=0; i<=len; i++) +// { +// if( cmdLine[i] == '"' ) +// { +// inString = !inString; +// } + +// if( (cmdLine[i] <= ' ' && !inString) || i == len ) +// { +// if( inArg ) +// { +// if( str[i-1] == '"' ) +// { +// str[i-1] = '\0'; +// } +// else +// { +// str[i] = '\0'; +// } + +// inArg = FALSE; +// } +// } +// else if( !inArg && cmdLine[i] != '"' ) +// { +// inArg = TRUE; + +// *aptr++ = &str[i]; +// } +// } + +// *argCount = numArgs; + +// return (RwChar **)res; +// } + +/* + ***************************************************************************** + */ +void InitialiseLanguage() +{ +#ifndef _WIN32 + // Mandatory for Linux(Unix? Posix?) to set lang. to environment lang. + setlocale(LC_ALL, ""); + + char *systemLang, *keyboardLang; + + systemLang = setlocale (LC_ALL, NULL); + keyboardLang = setlocale (LC_CTYPE, NULL); + + short primUserLCID, primSystemLCID; + primUserLCID = primSystemLCID = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH : + !strncmp(systemLang, "de_",3) ? LANG_GERMAN : + !strncmp(systemLang, "en_",3) ? LANG_ENGLISH : + !strncmp(systemLang, "it_",3) ? LANG_ITALIAN : + !strncmp(systemLang, "es_",3) ? LANG_SPANISH : + LANG_OTHER; + + short primLayout = !strncmp(keyboardLang, "fr_",3) ? LANG_FRENCH : (!strncmp(keyboardLang, "de_",3) ? LANG_GERMAN : LANG_ENGLISH); + + short subUserLCID, subSystemLCID; + subUserLCID = subSystemLCID = !strncmp(systemLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER; + short subLayout = !strncmp(keyboardLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER; + +#else + WORD primUserLCID = PRIMARYLANGID(GetSystemDefaultLCID()); + WORD primSystemLCID = PRIMARYLANGID(GetUserDefaultLCID()); + WORD primLayout = PRIMARYLANGID((DWORD)GetKeyboardLayout(0)); + + WORD subUserLCID = SUBLANGID(GetSystemDefaultLCID()); + WORD subSystemLCID = SUBLANGID(GetUserDefaultLCID()); + WORD subLayout = SUBLANGID((DWORD)GetKeyboardLayout(0)); +#endif + if ( primUserLCID == LANG_GERMAN + || primSystemLCID == LANG_GERMAN + || primLayout == LANG_GERMAN ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::frenchGame = true; + } + + if ( subUserLCID == SUBLANG_ENGLISH_AUS + || subSystemLCID == SUBLANG_ENGLISH_AUS + || subLayout == SUBLANG_ENGLISH_AUS ) + CGame::noProstitutes = true; + +#ifdef NASTY_GAME + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + CGame::noProstitutes = false; +#endif + + int32 lang; + + switch ( primSystemLCID ) + { + case LANG_GERMAN: + { + lang = LANG_GERMAN; + break; + } + case LANG_FRENCH: + { + lang = LANG_FRENCH; + break; + } + case LANG_SPANISH: + { + lang = LANG_SPANISH; + break; + } + case LANG_ITALIAN: + { + lang = LANG_ITALIAN; + break; + } + default: + { + lang = ( subSystemLCID == SUBLANG_ENGLISH_AUS ) ? -99 : LANG_ENGLISH; + break; + } + } + + CMenuManager::OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + break; + } + } + +#ifndef _WIN32 + // TODO this is needed for strcasecmp to work correctly across all languages, but can these cause other problems?? + setlocale(LC_CTYPE, "C"); + setlocale(LC_COLLATE, "C"); + setlocale(LC_NUMERIC, "C"); +#endif + + TheText.Unload(); + TheText.Load(); +} + +/* + ***************************************************************************** + */ + +void HandleExit() +{ +#ifdef _WIN32 + MSG message; + while ( PeekMessage(&message, nil, 0U, 0U, PM_REMOVE|PM_NOYIELD) ) + { + if( message.message == WM_QUIT ) + { + RsGlobal.quit = TRUE; + } + else + { + TranslateMessage(&message); + DispatchMessage(&message); + } + } +#else + // We now handle terminate message always, why handle on some cases? + return; +#endif +} + +// #ifndef _WIN32 +// void terminateHandler(int sig, siginfo_t *info, void *ucontext) { +// RsGlobal.quit = TRUE; +// } + +// #ifdef FLUSHABLE_STREAMING +// void dummyHandler(int sig){ +// // Don't kill the app pls +// } +// #endif +// #endif + +// void resizeCB(GLFWwindow* window, int width, int height) { +// /* +// * Handle event to ensure window contents are displayed during re-size +// * as this can be disabled by the user, then if there is not enough +// * memory things don't work. +// */ +// /* redraw window */ + +// if (RwInitialised && gGameState == GS_PLAYING_GAME) +// { +// RsEventHandler(rsIDLE, (void *)TRUE); +// } + +// if (RwInitialised && height > 0 && width > 0) { +// RwRect r; + +// // TODO fix artifacts of resizing with mouse +// RsGlobal.maximumHeight = height; +// RsGlobal.maximumWidth = width; + +// r.x = 0; +// r.y = 0; +// r.w = width; +// r.h = height; + +// RsEventHandler(rsCAMERASIZE, &r); +// } +// // glfwSetWindowPos(window, 0, 0); +// } + +// void scrollCB(GLFWwindow* window, double xoffset, double yoffset) { +// PSGLOBAL(mouseWheel) = yoffset; +// } + +bool lshiftStatus = false; +bool rshiftStatus = false; + +#ifndef GET_KEYBOARD_INPUT_FROM_X11 +//int keymap[GLFW_KEY_LAST + 1]; + +static void +initkeymap(void) +{ + // int i; + // for (i = 0; i < GLFW_KEY_LAST + 1; i++) + // keymap[i] = rsNULL; + + // keymap[GLFW_KEY_SPACE] = ' '; + // keymap[GLFW_KEY_APOSTROPHE] = '\''; + // keymap[GLFW_KEY_COMMA] = ','; + // keymap[GLFW_KEY_MINUS] = '-'; + // keymap[GLFW_KEY_PERIOD] = '.'; + // keymap[GLFW_KEY_SLASH] = '/'; + // keymap[GLFW_KEY_0] = '0'; + // keymap[GLFW_KEY_1] = '1'; + // keymap[GLFW_KEY_2] = '2'; + // keymap[GLFW_KEY_3] = '3'; + // keymap[GLFW_KEY_4] = '4'; + // keymap[GLFW_KEY_5] = '5'; + // keymap[GLFW_KEY_6] = '6'; + // keymap[GLFW_KEY_7] = '7'; + // keymap[GLFW_KEY_8] = '8'; + // keymap[GLFW_KEY_9] = '9'; + // keymap[GLFW_KEY_SEMICOLON] = ';'; + // keymap[GLFW_KEY_EQUAL] = '='; + // keymap[GLFW_KEY_A] = 'A'; + // keymap[GLFW_KEY_B] = 'B'; + // keymap[GLFW_KEY_C] = 'C'; + // keymap[GLFW_KEY_D] = 'D'; + // keymap[GLFW_KEY_E] = 'E'; + // keymap[GLFW_KEY_F] = 'F'; + // keymap[GLFW_KEY_G] = 'G'; + // keymap[GLFW_KEY_H] = 'H'; + // keymap[GLFW_KEY_I] = 'I'; + // keymap[GLFW_KEY_J] = 'J'; + // keymap[GLFW_KEY_K] = 'K'; + // keymap[GLFW_KEY_L] = 'L'; + // keymap[GLFW_KEY_M] = 'M'; + // keymap[GLFW_KEY_N] = 'N'; + // keymap[GLFW_KEY_O] = 'O'; + // keymap[GLFW_KEY_P] = 'P'; + // keymap[GLFW_KEY_Q] = 'Q'; + // keymap[GLFW_KEY_R] = 'R'; + // keymap[GLFW_KEY_S] = 'S'; + // keymap[GLFW_KEY_T] = 'T'; + // keymap[GLFW_KEY_U] = 'U'; + // keymap[GLFW_KEY_V] = 'V'; + // keymap[GLFW_KEY_W] = 'W'; + // keymap[GLFW_KEY_X] = 'X'; + // keymap[GLFW_KEY_Y] = 'Y'; + // keymap[GLFW_KEY_Z] = 'Z'; + // keymap[GLFW_KEY_LEFT_BRACKET] = '['; + // keymap[GLFW_KEY_BACKSLASH] = '\\'; + // keymap[GLFW_KEY_RIGHT_BRACKET] = ']'; + // keymap[GLFW_KEY_GRAVE_ACCENT] = '`'; + // keymap[GLFW_KEY_ESCAPE] = rsESC; + // keymap[GLFW_KEY_ENTER] = rsENTER; + // keymap[GLFW_KEY_TAB] = rsTAB; + // keymap[GLFW_KEY_BACKSPACE] = rsBACKSP; + // keymap[GLFW_KEY_INSERT] = rsINS; + // keymap[GLFW_KEY_DELETE] = rsDEL; + // keymap[GLFW_KEY_RIGHT] = rsRIGHT; + // keymap[GLFW_KEY_LEFT] = rsLEFT; + // keymap[GLFW_KEY_DOWN] = rsDOWN; + // keymap[GLFW_KEY_UP] = rsUP; + // keymap[GLFW_KEY_PAGE_UP] = rsPGUP; + // keymap[GLFW_KEY_PAGE_DOWN] = rsPGDN; + // keymap[GLFW_KEY_HOME] = rsHOME; + // keymap[GLFW_KEY_END] = rsEND; + // keymap[GLFW_KEY_CAPS_LOCK] = rsCAPSLK; + // keymap[GLFW_KEY_SCROLL_LOCK] = rsSCROLL; + // keymap[GLFW_KEY_NUM_LOCK] = rsNUMLOCK; + // keymap[GLFW_KEY_PRINT_SCREEN] = rsNULL; + // keymap[GLFW_KEY_PAUSE] = rsPAUSE; + + // keymap[GLFW_KEY_F1] = rsF1; + // keymap[GLFW_KEY_F2] = rsF2; + // keymap[GLFW_KEY_F3] = rsF3; + // keymap[GLFW_KEY_F4] = rsF4; + // keymap[GLFW_KEY_F5] = rsF5; + // keymap[GLFW_KEY_F6] = rsF6; + // keymap[GLFW_KEY_F7] = rsF7; + // keymap[GLFW_KEY_F8] = rsF8; + // keymap[GLFW_KEY_F9] = rsF9; + // keymap[GLFW_KEY_F10] = rsF10; + // keymap[GLFW_KEY_F11] = rsF11; + // keymap[GLFW_KEY_F12] = rsF12; + // keymap[GLFW_KEY_F13] = rsNULL; + // keymap[GLFW_KEY_F14] = rsNULL; + // keymap[GLFW_KEY_F15] = rsNULL; + // keymap[GLFW_KEY_F16] = rsNULL; + // keymap[GLFW_KEY_F17] = rsNULL; + // keymap[GLFW_KEY_F18] = rsNULL; + // keymap[GLFW_KEY_F19] = rsNULL; + // keymap[GLFW_KEY_F20] = rsNULL; + // keymap[GLFW_KEY_F21] = rsNULL; + // keymap[GLFW_KEY_F22] = rsNULL; + // keymap[GLFW_KEY_F23] = rsNULL; + // keymap[GLFW_KEY_F24] = rsNULL; + // keymap[GLFW_KEY_F25] = rsNULL; + // keymap[GLFW_KEY_KP_0] = rsPADINS; + // keymap[GLFW_KEY_KP_1] = rsPADEND; + // keymap[GLFW_KEY_KP_2] = rsPADDOWN; + // keymap[GLFW_KEY_KP_3] = rsPADPGDN; + // keymap[GLFW_KEY_KP_4] = rsPADLEFT; + // keymap[GLFW_KEY_KP_5] = rsPAD5; + // keymap[GLFW_KEY_KP_6] = rsPADRIGHT; + // keymap[GLFW_KEY_KP_7] = rsPADHOME; + // keymap[GLFW_KEY_KP_8] = rsPADUP; + // keymap[GLFW_KEY_KP_9] = rsPADPGUP; + // keymap[GLFW_KEY_KP_DECIMAL] = rsPADDEL; + // keymap[GLFW_KEY_KP_DIVIDE] = rsDIVIDE; + // keymap[GLFW_KEY_KP_MULTIPLY] = rsTIMES; + // keymap[GLFW_KEY_KP_SUBTRACT] = rsMINUS; + // keymap[GLFW_KEY_KP_ADD] = rsPLUS; + // keymap[GLFW_KEY_KP_ENTER] = rsPADENTER; + // keymap[GLFW_KEY_KP_EQUAL] = rsNULL; + // keymap[GLFW_KEY_LEFT_SHIFT] = rsLSHIFT; + // keymap[GLFW_KEY_LEFT_CONTROL] = rsLCTRL; + // keymap[GLFW_KEY_LEFT_ALT] = rsLALT; + // keymap[GLFW_KEY_LEFT_SUPER] = rsLWIN; + // keymap[GLFW_KEY_RIGHT_SHIFT] = rsRSHIFT; + // keymap[GLFW_KEY_RIGHT_CONTROL] = rsRCTRL; + // keymap[GLFW_KEY_RIGHT_ALT] = rsRALT; + // keymap[GLFW_KEY_RIGHT_SUPER] = rsRWIN; + // keymap[GLFW_KEY_MENU] = rsNULL; +} + +// void +// keypressCB(GLFWwindow* window, int key, int scancode, int action, int mods) +// { +// if (key >= 0 && key <= GLFW_KEY_LAST && action != GLFW_REPEAT) { +// RsKeyCodes ks = (RsKeyCodes)keymap[key]; + +// if (key == GLFW_KEY_LEFT_SHIFT) +// lshiftStatus = action != GLFW_RELEASE; + +// if (key == GLFW_KEY_RIGHT_SHIFT) +// rshiftStatus = action != GLFW_RELEASE; + +// if (action == GLFW_RELEASE) RsKeyboardEventHandler(rsKEYUP, &ks); +// else if (action == GLFW_PRESS) RsKeyboardEventHandler(rsKEYDOWN, &ks); +// } +// } + +#else + +uint32 keymap[512]; // 256 ascii + 256 KeySyms between 0xff00 - 0xffff +bool keyStates[512]; +uint32 keyCodeToKeymapIndex[256]; // cache for physical keys + +#define KEY_MAP_OFFSET (0xff00 - 256) +static void +initkeymap(void) +{ + Display *display = glfwGetX11Display(); + int i; + + for (i = 0; i < ARRAY_SIZE(keymap); i++) + keymap[i] = rsNULL; + + // You can add new ASCII mappings to here freely (but beware that if right hand side of assignment isn't supported on CFont, it'll be blank/won't work on binding screen) + // Right hand side of assigments should always be uppercase counterpart of character + keymap[XK_space] = ' '; + keymap[XK_apostrophe] = '\''; + keymap[XK_ampersand] = '&'; + keymap[XK_percent] = '%'; + keymap[XK_dollar] = '$'; + keymap[XK_comma] = ','; + keymap[XK_minus] = '-'; + keymap[XK_period] = '.'; + keymap[XK_slash] = '/'; + keymap[XK_question] = '?'; + keymap[XK_exclam] = '!'; + keymap[XK_quotedbl] = '"'; + keymap[XK_colon] = ':'; + keymap[XK_semicolon] = ';'; + keymap[XK_equal] = '='; + keymap[XK_bracketleft] = '['; + keymap[XK_backslash] = '\\'; + keymap[XK_bracketright] = ']'; + keymap[XK_grave] = '`'; + keymap[XK_0] = '0'; + keymap[XK_1] = '1'; + keymap[XK_2] = '2'; + keymap[XK_3] = '3'; + keymap[XK_4] = '4'; + keymap[XK_5] = '5'; + keymap[XK_6] = '6'; + keymap[XK_7] = '7'; + keymap[XK_8] = '8'; + keymap[XK_9] = '9'; + keymap[XK_a] = 'A'; + keymap[XK_b] = 'B'; + keymap[XK_c] = 'C'; + keymap[XK_d] = 'D'; + keymap[XK_e] = 'E'; + keymap[XK_f] = 'F'; + keymap[XK_g] = 'G'; + keymap[XK_h] = 'H'; + keymap[XK_i] = 'I'; + keymap[XK_I] = 'I'; // Turkish I problem + keymap[XK_j] = 'J'; + keymap[XK_k] = 'K'; + keymap[XK_l] = 'L'; + keymap[XK_m] = 'M'; + keymap[XK_n] = 'N'; + keymap[XK_o] = 'O'; + keymap[XK_p] = 'P'; + keymap[XK_q] = 'Q'; + keymap[XK_r] = 'R'; + keymap[XK_s] = 'S'; + keymap[XK_t] = 'T'; + keymap[XK_u] = 'U'; + keymap[XK_v] = 'V'; + keymap[XK_w] = 'W'; + keymap[XK_x] = 'X'; + keymap[XK_y] = 'Y'; + keymap[XK_z] = 'Z'; + + // Some of regional but ASCII characters that GTA supports + keymap[XK_agrave] = 0x00c0; + keymap[XK_aacute] = 0x00c1; + keymap[XK_acircumflex] = 0x00c2; + keymap[XK_adiaeresis] = 0x00c4; + + keymap[XK_ae] = 0x00c6; + + keymap[XK_egrave] = 0x00c8; + keymap[XK_eacute] = 0x00c9; + keymap[XK_ecircumflex] = 0x00ca; + keymap[XK_ediaeresis] = 0x00cb; + + keymap[XK_igrave] = 0x00cc; + keymap[XK_iacute] = 0x00cd; + keymap[XK_icircumflex] = 0x00ce; + keymap[XK_idiaeresis] = 0x00cf; + + keymap[XK_ccedilla] = 0x00c7; + keymap[XK_odiaeresis] = 0x00d6; + keymap[XK_udiaeresis] = 0x00dc; + + // These are 0xff00 - 0xffff range of KeySym's, and subtracting KEY_MAP_OFFSET is needed + keymap[XK_Escape - KEY_MAP_OFFSET] = rsESC; + keymap[XK_Return - KEY_MAP_OFFSET] = rsENTER; + keymap[XK_Tab - KEY_MAP_OFFSET] = rsTAB; + keymap[XK_BackSpace - KEY_MAP_OFFSET] = rsBACKSP; + keymap[XK_Insert - KEY_MAP_OFFSET] = rsINS; + keymap[XK_Delete - KEY_MAP_OFFSET] = rsDEL; + keymap[XK_Right - KEY_MAP_OFFSET] = rsRIGHT; + keymap[XK_Left - KEY_MAP_OFFSET] = rsLEFT; + keymap[XK_Down - KEY_MAP_OFFSET] = rsDOWN; + keymap[XK_Up - KEY_MAP_OFFSET] = rsUP; + keymap[XK_Page_Up - KEY_MAP_OFFSET] = rsPGUP; + keymap[XK_Page_Down - KEY_MAP_OFFSET] = rsPGDN; + keymap[XK_Home - KEY_MAP_OFFSET] = rsHOME; + keymap[XK_End - KEY_MAP_OFFSET] = rsEND; + keymap[XK_Caps_Lock - KEY_MAP_OFFSET] = rsCAPSLK; + keymap[XK_Scroll_Lock - KEY_MAP_OFFSET] = rsSCROLL; + keymap[XK_Num_Lock - KEY_MAP_OFFSET] = rsNUMLOCK; + keymap[XK_Pause - KEY_MAP_OFFSET] = rsPAUSE; + + keymap[XK_F1 - KEY_MAP_OFFSET] = rsF1; + keymap[XK_F2 - KEY_MAP_OFFSET] = rsF2; + keymap[XK_F3 - KEY_MAP_OFFSET] = rsF3; + keymap[XK_F4 - KEY_MAP_OFFSET] = rsF4; + keymap[XK_F5 - KEY_MAP_OFFSET] = rsF5; + keymap[XK_F6 - KEY_MAP_OFFSET] = rsF6; + keymap[XK_F7 - KEY_MAP_OFFSET] = rsF7; + keymap[XK_F8 - KEY_MAP_OFFSET] = rsF8; + keymap[XK_F9 - KEY_MAP_OFFSET] = rsF9; + keymap[XK_F10 - KEY_MAP_OFFSET] = rsF10; + keymap[XK_F11 - KEY_MAP_OFFSET] = rsF11; + keymap[XK_F12 - KEY_MAP_OFFSET] = rsF12; + keymap[XK_F13 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F14 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F15 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F16 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F17 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F18 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F19 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F20 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F21 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F22 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F23 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F24 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F25 - KEY_MAP_OFFSET] = rsNULL; + + keymap[XK_KP_0 - KEY_MAP_OFFSET] = rsPADINS; + keymap[XK_KP_1 - KEY_MAP_OFFSET] = rsPADEND; + keymap[XK_KP_2 - KEY_MAP_OFFSET] = rsPADDOWN; + keymap[XK_KP_3 - KEY_MAP_OFFSET] = rsPADPGDN; + keymap[XK_KP_4 - KEY_MAP_OFFSET] = rsPADLEFT; + keymap[XK_KP_5 - KEY_MAP_OFFSET] = rsPAD5; + keymap[XK_KP_6 - KEY_MAP_OFFSET] = rsPADRIGHT; + keymap[XK_KP_7 - KEY_MAP_OFFSET] = rsPADHOME; + keymap[XK_KP_8 - KEY_MAP_OFFSET] = rsPADUP; + keymap[XK_KP_9 - KEY_MAP_OFFSET] = rsPADPGUP; + keymap[XK_KP_Insert - KEY_MAP_OFFSET] = rsPADINS; + keymap[XK_KP_End - KEY_MAP_OFFSET] = rsPADEND; + keymap[XK_KP_Down - KEY_MAP_OFFSET] = rsPADDOWN; + keymap[XK_KP_Page_Down - KEY_MAP_OFFSET] = rsPADPGDN; + keymap[XK_KP_Left - KEY_MAP_OFFSET] = rsPADLEFT; + keymap[XK_KP_Begin - KEY_MAP_OFFSET] = rsPAD5; + keymap[XK_KP_Right - KEY_MAP_OFFSET] = rsPADRIGHT; + keymap[XK_KP_Home - KEY_MAP_OFFSET] = rsPADHOME; + keymap[XK_KP_Up - KEY_MAP_OFFSET] = rsPADUP; + keymap[XK_KP_Page_Up - KEY_MAP_OFFSET] = rsPADPGUP; + + keymap[XK_KP_Decimal - KEY_MAP_OFFSET] = rsPADDEL; + keymap[XK_KP_Divide - KEY_MAP_OFFSET] = rsDIVIDE; + keymap[XK_KP_Multiply - KEY_MAP_OFFSET] = rsTIMES; + keymap[XK_KP_Subtract - KEY_MAP_OFFSET] = rsMINUS; + keymap[XK_KP_Add - KEY_MAP_OFFSET] = rsPLUS; + keymap[XK_KP_Enter - KEY_MAP_OFFSET] = rsPADENTER; + keymap[XK_KP_Equal - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_Shift_L - KEY_MAP_OFFSET] = rsLSHIFT; + keymap[XK_Control_L - KEY_MAP_OFFSET] = rsLCTRL; + keymap[XK_Alt_L - KEY_MAP_OFFSET] = rsLALT; + keymap[XK_Super_L - KEY_MAP_OFFSET] = rsLWIN; + keymap[XK_Shift_R - KEY_MAP_OFFSET] = rsRSHIFT; + keymap[XK_Control_R - KEY_MAP_OFFSET] = rsRCTRL; + keymap[XK_Alt_R - KEY_MAP_OFFSET] = rsRALT; + keymap[XK_Super_R - KEY_MAP_OFFSET] = rsRWIN; + keymap[XK_Menu - KEY_MAP_OFFSET] = rsNULL; + + // Cache the key codes' key symbol equivelants, otherwise we will have to do it on each frame + // KeyCode is always in [0,255], and represents a physical key + + int min_keycode, max_keycode, keysyms_per_keycode; + KeySym *keymap, *origkeymap; + + char *keyboardLang = setlocale (LC_CTYPE, NULL); + setlocale(LC_CTYPE, ""); + + XDisplayKeycodes(display, &min_keycode, &max_keycode); + origkeymap = XGetKeyboardMapping(display, min_keycode, (max_keycode - min_keycode + 1), &keysyms_per_keycode); + keymap = origkeymap; + for (int i = min_keycode; i <= max_keycode; i++) { + int j, lastKeysym; + + lastKeysym = keysyms_per_keycode - 1; + while ((lastKeysym >= 0) && (keymap[lastKeysym] == NoSymbol)) + lastKeysym--; + + for (j = 0; j <= lastKeysym; j++) { + KeySym ks = keymap[j]; + + if (ks == NoSymbol) + continue; + + if (ks < 256) { + keyCodeToKeymapIndex[i] = ks; + break; + } else if (ks >= 0xff00 && ks < 0xffff) { + keyCodeToKeymapIndex[i] = ks - KEY_MAP_OFFSET; + break; + } + } + keymap += keysyms_per_keycode; + } + XFree(origkeymap); + + setlocale(LC_CTYPE, keyboardLang); +} +#undef KEY_MAP_OFFSET + +void checkKeyPresses() +{ + Display *display = glfwGetX11Display(); + char keys[32]; + XQueryKeymap(display, keys); + for (int i = 0; i < sizeof(keys); i++) { + for (int j = 0; j < 8; j++) { + KeyCode keycode = 8 * i + j; + uint32 keymapIndex = keyCodeToKeymapIndex[keycode]; + if (keymapIndex != 0) { + int rsCode = keymap[keymapIndex]; + if (rsCode == rsNULL) + continue; + + bool pressed = WindowFocused && !!(keys[i] & (1 << j)); + + // idk why R* does that + if (rsCode == rsLSHIFT) + lshiftStatus = pressed; + else if (rsCode == rsRSHIFT) + rshiftStatus = pressed; + + if (keyStates[keymapIndex] != pressed) { + if (pressed) { + RsKeyboardEventHandler(rsKEYDOWN, &rsCode); + } else { + RsKeyboardEventHandler(rsKEYUP, &rsCode); + } + } + + keyStates[keymapIndex] = pressed; + } + } + } + +} +#endif + +// R* calls that in ControllerConfig, idk why +void +_InputTranslateShiftKeyUpDown(RsKeyCodes *rs) { + RsKeyboardEventHandler(lshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsLSHIFT)); + RsKeyboardEventHandler(rshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsRSHIFT)); +} + +// TODO this only works in frontend(and luckily only frontend use this). Fun fact: if I get pos manually in game, glfw reports that it's > 32000 +// void +// cursorCB(GLFWwindow* window, double xpos, double ypos) { +// if (!FrontEndMenuManager.m_bMenuActive) +// return; + +// int winw, winh; +// glfwGetWindowSize(PSGLOBAL(window), &winw, &winh); +// FrontEndMenuManager.m_nMouseTempPosX = xpos * (RsGlobal.maximumWidth / winw); +// FrontEndMenuManager.m_nMouseTempPosY = ypos * (RsGlobal.maximumHeight / winh); +// } + +// void +// cursorEnterCB(GLFWwindow* window, int entered) { +// PSGLOBAL(cursorIsInWindow) = !!entered; +// } + +// void +// windowFocusCB(GLFWwindow* window, int focused) { +// WindowFocused = !!focused; +// } + +// void +// windowIconifyCB(GLFWwindow* window, int iconified) { +// WindowIconified = !!iconified; +// } + +/* + ***************************************************************************** + */ +#ifdef _WIN32 +int PASCAL +WinMain(HINSTANCE instance, + HINSTANCE prevInstance __RWUNUSED__, + CMDSTR cmdLine, + int cmdShow) +{ + + RwInt32 argc; + RwChar** argv; + SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, SPIF_SENDCHANGE); + +#ifndef MASTER + if (strstr(cmdLine, "-console")) + { + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } +#endif + +#else +#if !defined(DC_SIM) +extern "C" const char etext[]; + +__attribute__((noinline)) void stacktrace() { + uint32 sp=0, pr=0; + __asm__ __volatile__( + "mov r15,%0\n" + "sts pr,%1\n" + : "+r" (sp), "+r" (pr) + : + : ); + dbgio_printf("Stack trace: %p ", (void*)pr); + int found = 0; + if(!(sp & 3) && sp > 0x8c000000 && sp < _arch_mem_top) { + char** sp_ptr = (char**)sp; + for (int so = 0; so < 16384; so++) { + if (uintptr_t(&sp_ptr[so]) >= _arch_mem_top) { + dbgio_printf("(@@%p) ", &sp_ptr[so]); + break; + } + if (sp_ptr[so] > (char*)0x8c000000 && sp_ptr[so] < etext) { + uintptr_t addr = uintptr_t(sp_ptr[so]); + // candidate return pointer + if (addr & 1) { + // dbglog(DBG_CRITICAL, "Stack trace: %p (@%p): misaligned\n", (void*)sp_ptr[so], &sp_ptr[so]); + continue; + } + + uint16_t* instrp = (uint16_t*)addr; + + uint16_t instr = instrp[-2]; + // BSR or BSRF or JSR @Rn ? + if (((instr & 0xf000) == 0xB000) || ((instr & 0xf0ff) == 0x0003) || ((instr & 0xf0ff) == 0x400B)) { + dbgio_printf("%p ", instrp); + if (found++ > 24) { + dbgio_printf("(@%p) ", &sp_ptr[so]); + break; + } + } else { + // dbglog(DBG_CRITICAL, "%p:%04X ", instrp, instr); + } + } else { + // dbglog(DBG_CRITICAL, "Stack trace: %p (@%p): out of range\n", (void*)sp_ptr[so], &sp_ptr[so]); + } + } + dbgio_printf("end\n"); + } else { + dbgio_printf("(@%p)\n", (void*)sp); + } +} +#endif +int +main(int argc, char *argv[]) +{ + #if !defined(DC_SIM) + std::set_terminate([]() { + fflush(stdout); + fflush(stderr); + dbglog(DBG_CRITICAL, "std::terminate() called\n"); + dbglog(DBG_CRITICAL, "POSIX error (may not be relevant): %s\n", strerror(errno)); + stacktrace(); + dbgio_dev_select("fb"); + sleep(1); + dbgio_printf("std::terminate() called\n"); + dbgio_printf("POSIX error (may not be relevant): %s\n", strerror(errno)); + stacktrace(); + dbgio_flush(); + + + abort(); + }); + #endif + + #if !defined(DC_SIM) + #if defined(WITH_IDE) + ide_fat_init(); + #ifndef DC_CHDIR + #define DC_CHDIR /ide/gta3 + #endif + #endif + + #ifndef DC_CHDIR + #define DC_CHDIR /cd + #endif + + /* Disable printf() buffering, because it's slow with latest Newlib/toolchain combo. */ + setvbuf(stdout, NULL, _IONBF, 0); +#if defined(MASTER) || defined(NDEBUG) + dbglog_set_level(DBG_CRITICAL); +#endif + + printf("sbrk: %p\n", sbrk(0)); + printf("Loading game assets from %s\n", STR(DC_CHDIR)); + chdir(STR(DC_CHDIR)); + +#if defined(WITH_PROF) + // dbgio_dev_select("null"); + #if defined(WITH_SD) + sd_fat_init(); + #endif + cont_btn_callback(0, CONT_A | CONT_B | CONT_X, [](uint8_t, uint32_t btns) { + if (profiler_recording()) + return; + if(profiling) { + profiling = false; + profiler_clean_up(); + } + else { + profiling = true; + #if defined(WITH_SD) + profiler_init("/sd"); + #else + profiler_init(WITH_PROF); + #endif + profiler_start(); + } + }); +#endif + + cont_btn_callback(0, CONT_RESET_BUTTONS, [](uint8_t, uint32_t) { + exit(EXIT_SUCCESS); + }); + + #else + chdir("./repack-data/gta3"); + #endif + + #endif + RwV2d pos; + RwInt32 i; + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + +#if !defined(_WIN32) && !defined(__SWITCH__) && !defined(RW_DC) + struct sigaction act; + act.sa_sigaction = terminateHandler; + act.sa_flags = SA_SIGINFO; + sigaction(SIGTERM, &act, NULL); +#ifdef FLUSHABLE_STREAMING + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_handler = dummyHandler; + sa.sa_flags = 0; + sigaction(SIGUSR1, &sa, NULL); +#endif +#endif + + /* + * Initialize the platform independent data. + * This will in turn initialize the platform specific data... + */ + if( RsEventHandler(rsINITIALIZE, nil) == rsEVENTERROR ) + { + return FALSE; + } + +#ifdef _WIN32 + /* + * Get proper command line params, cmdLine passed to us does not + * work properly under all circumstances... + */ + cmdLine = GetCommandLine(); + + /* + * Parse command line into standard (argv, argc) parameters... + */ + argv = CommandLineToArgv(cmdLine, &argc); + + + /* + * Parse command line parameters (except program name) one at + * a time BEFORE RenderWare initialization... + */ +#endif + + for(i=1; iGetLeftMouseJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetEnterJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetCharJustDown(' ')) +// ++gGameState; +// else if (CPad::GetPad(0)->GetAltJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetTabJustDown()) +// ++gGameState; + + break; + } + + case GS_INIT_INTRO_MPEG: + { +//#ifndef NO_MOVIES +// CloseClip(); +// CoUninitialize(); +//#endif +// +// if (CMenuManager::OS_Language == LANG_FRENCH || CMenuManager::OS_Language == LANG_GERMAN) +// PlayMovieInWindow(cmdShow, "movies\\GTAtitlesGER.mpg"); +// else +// PlayMovieInWindow(cmdShow, "movies\\GTAtitles.mpg"); + + gGameState = GS_INTRO_MPEG; + TRACE("gGameState = GS_INTRO_MPEG;"); + break; + } + + case GS_INTRO_MPEG: + { +// CPad::UpdatePads(); +// +// if (startupDeactivate || ControlsManager.GetJoyButtonJustDown() != 0) + ++gGameState; +// else if (CPad::GetPad(0)->GetLeftMouseJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetEnterJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetCharJustDown(' ')) +// ++gGameState; +// else if (CPad::GetPad(0)->GetAltJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetTabJustDown()) +// ++gGameState; + + break; + } + + case GS_INIT_ONCE: + { + //CoUninitialize(); + +#ifdef PS2_MENU + extern char version_name[64]; + if ( CGame::frenchGame || CGame::germanGame ) + LoadingScreen(NULL, version_name, "loadsc24"); + else + LoadingScreen(NULL, version_name, "loadsc0"); + + printf("Into TheGame!!!\n"); +#else + LoadingScreen(nil, nil, "loadsc0"); +#endif + if ( !CGame::InitialiseOnceAfterRW() ) + RsGlobal.quit = TRUE; + +#ifdef PS2_MENU + gGameState = GS_INIT_PLAYING_GAME; +#else + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); +#endif + break; + } + +#ifndef PS2_MENU + case GS_INIT_FRONTEND: + { + LoadingScreen(nil, nil, "loadsc0"); + + FrontEndMenuManager.m_bGameNotLoaded = true; + + CMenuManager::m_bStartUpFrontEndRequested = true; + + if ( defaultFullscreenRes ) + { + defaultFullscreenRes = FALSE; + FrontEndMenuManager.m_nPrefsVideoMode = GcurSelVM; + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + } + + gGameState = GS_FRONTEND; + TRACE("gGameState = GS_FRONTEND;"); + break; + } + + case GS_FRONTEND: + { + if(!WindowIconified) + RsEventHandler(rsFRONTENDIDLE, nil); + +#ifdef PS2_MENU + if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad ) +#else + if ( !FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bWantToLoad ) +#endif + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + +#ifdef PS2_MENU + if (TheMemoryCard.m_bWantToLoad ) +#else + if ( FrontEndMenuManager.m_bWantToLoad ) +#endif + { + InitialiseGame(); + FrontEndMenuManager.m_bGameNotLoaded = false; + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + } + break; + } +#endif + + case GS_INIT_PLAYING_GAME: + { +#ifdef PS2_MENU + CGame::Initialise("DATA\\GTA3.DAT"); + + //LoadingScreen("Starting Game", NULL, GetRandomSplashScreen()); + + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS + && TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir) + && TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true + && TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile)) + { + strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile); + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true; + + if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = (eLevelName)TheMemoryCard.GetLevelToLoad(); + } +#else + InitialiseGame(); + + FrontEndMenuManager.m_bGameNotLoaded = false; +#endif + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + break; + } + + case GS_PLAYING_GAME: + { + float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + if ( RwInitialised ) + { + if (!CMenuManager::m_PrefsFrameLimiter || (1000.0f / (float)RsGlobal.maxFPS) < ms) + RsEventHandler(rsIDLE, (void *)TRUE); + } + break; + } + } + } + else + { + if ( RwCameraBeginUpdate(Scene.camera) ) + { + RwCameraEndUpdate(Scene.camera); + ForegroundApp = TRUE; + RsEventHandler(rsACTIVATE, (void *)TRUE); + } + + } + } + + + /* + * About to shut down - block resize events again... + */ + RwInitialised = FALSE; + + FrontEndMenuManager.UnloadTextures(); +#ifdef PS2_MENU + if ( !(FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad)) + break; +#else + if ( !FrontEndMenuManager.m_bWantToRestart ) + break; +#endif + + CPad::ResetCheats(); + CPad::StopPadsShaking(); + + DMAudio.ChangeMusicMode(MUSICMODE_DISABLE); + +#ifdef PS2_MENU + CGame::ShutDownForRestart(); +#endif + + CTimer::Stop(); + +#ifdef PS2_MENU + if (FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + if (TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + FrontEndMenuManager.m_bWantToRestart = true; + TheMemoryCard.m_bWantToLoad = true; + } + + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + FrontEndMenuManager.m_bWantToRestart = false; + + continue; + } + + CGame::ShutDown(); + CTimer::Stop(); + + break; +#else + if ( FrontEndMenuManager.m_bWantToLoad ) + { + CGame::ShutDownForRestart(); + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + FrontEndMenuManager.m_bWantToLoad = false; + } + else + { +#if !defined(RW_DC) +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + CTimer::Stop(); + + if ( FrontEndMenuManager.m_bFirstTime == true ) + { + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); + } + else + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + } + + FrontEndMenuManager.m_bFirstTime = false; + FrontEndMenuManager.m_bWantToRestart = false; +#endif + } + +#if !defined(RW_DC) +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + DMAudio.Terminate(); + + _psFreeVideoModeList(); + + + /* + * Tidy up the 3D (RenderWare) components of the application... + */ + RsEventHandler(rsRWTERMINATE, nil); + + /* + * Free the platform dependent data... + */ + RsEventHandler(rsTERMINATE, nil); + +#ifdef _WIN32 + /* + * Free the argv strings... + */ + free(argv); + + SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &SavedStickyKeys, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETPOWEROFFACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETLOWPOWERACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SetErrorMode(0); +#endif + +#if defined(WITH_PROF) && !defined(DC_SIM) + if(profiling) { + profiling = false; + profiler_clean_up(); + } +#endif + + VmuProfiler::destroyInstance(); + return 0; +} + +/* + ***************************************************************************** + */ + +RwV2d leftStickPos; +RwV2d rightStickPos; + +void CapturePad(RwInt32 padID) +{ + // int8 glfwPad = -1; + + // if( padID == 0 ) + // glfwPad = PSGLOBAL(joy1id); + // else if( padID == 1) + // glfwPad = PSGLOBAL(joy2id); + // else + // assert("invalid padID"); + + // if ( glfwPad == -1 ) + // return; + + // int numButtons, numAxes; + // const uint8 *buttons = glfwGetJoystickButtons(glfwPad, &numButtons); + // const float *axes = glfwGetJoystickAxes(glfwPad, &numAxes); + // GLFWgamepadstate gamepadState; + + // if (ControlsManager.m_bFirstCapture == false) { + // memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState)); + // } else { + // // In case connected gamepad doesn't have L-R trigger axes. + // ControlsManager.m_NewState.mappedButtons[15] = ControlsManager.m_NewState.mappedButtons[16] = 0; + // } + + // ControlsManager.m_NewState.buttons = (uint8*)buttons; + // ControlsManager.m_NewState.numButtons = numButtons; + // ControlsManager.m_NewState.id = glfwPad; + // ControlsManager.m_NewState.isGamepad = glfwGetGamepadState(glfwPad, &gamepadState); + // if (ControlsManager.m_NewState.isGamepad) { + // memcpy(&ControlsManager.m_NewState.mappedButtons, gamepadState.buttons, sizeof(gamepadState.buttons)); + // float lt = gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER], rt = gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER]; + + // // glfw returns 0.0 for non-existent axises(which is bullocks) so we treat it as deadzone, and keep value of previous frame. + // // otherwise if this axis is present, -1 = released, 1 = pressed + // if (lt != 0.0f) + // ControlsManager.m_NewState.mappedButtons[15] = lt > -0.8f; + + // if (rt != 0.0f) + // ControlsManager.m_NewState.mappedButtons[16] = rt > -0.8f; + // } + // // TODO? L2-R2 axes(not buttons-that's fine) on joysticks that don't have SDL gamepad mapping AREN'T handled, and I think it's impossible to do without mapping. + + // if (ControlsManager.m_bFirstCapture == true) { + // memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState)); + + // ControlsManager.m_bFirstCapture = false; + // } + + // RsPadButtonStatus bs; + // bs.padID = padID; + + // RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + + // // Gamepad axes are guaranteed to return 0.0f if that particular gamepad doesn't have that axis. + // // And that's really good for sticks, because gamepads return 0.0 for them when sticks are in released state. + // if ( glfwPad != -1 ) { + // leftStickPos.x = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_X] : numAxes >= 1 ? axes[0] : 0.0f; + // leftStickPos.y = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y] : numAxes >= 2 ? axes[1] : 0.0f; + + // rightStickPos.x = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X] : numAxes >= 3 ? axes[2] : 0.0f; + // rightStickPos.y = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y] : numAxes >= 4 ? axes[3] : 0.0f; + // } + + // { + // if (CPad::m_bMapPadOneToPadTwo) + // bs.padID = 1; + + // RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + // RsPadEventHandler(rsPADBUTTONDOWN, (void *)&bs); + // } + + // { + // if (CPad::m_bMapPadOneToPadTwo) + // bs.padID = 1; + + // CPad *pad = CPad::GetPad(bs.padID); + + // if ( Abs(leftStickPos.x) > 0.3f ) + // pad->PCTempJoyState.LeftStickX = (int32)(leftStickPos.x * 128.0f); + + // if ( Abs(leftStickPos.y) > 0.3f ) + // pad->PCTempJoyState.LeftStickY = (int32)(leftStickPos.y * 128.0f); + + // if ( Abs(rightStickPos.x) > 0.3f ) + // pad->PCTempJoyState.RightStickX = (int32)(rightStickPos.x * 128.0f); + + // if ( Abs(rightStickPos.y) > 0.3f ) + // pad->PCTempJoyState.RightStickY = (int32)(rightStickPos.y * 128.0f); + // } + + // _psHandleVibration(); + + return; +} + +#if 0 +void joysChangeCB(int jid, int event) +{ + if (event == GLFW_CONNECTED && !IsThisJoystickBlacklisted(jid)) { + if (PSGLOBAL(joy1id) == -1) { + PSGLOBAL(joy1id) = jid; +#ifdef DETECT_JOYSTICK_MENU + strcpy(gSelectedJoystickName, glfwGetJoystickName(jid)); +#endif + // This is behind LOAD_INI_SETTINGS, because otherwise the Init call below will destroy/overwrite your bindings. +#ifdef LOAD_INI_SETTINGS + int count; + glfwGetJoystickButtons(PSGLOBAL(joy1id), &count); + ControlsManager.InitDefaultControlConfigJoyPad(count); +#endif + } else if (PSGLOBAL(joy2id) == -1) + PSGLOBAL(joy2id) = jid; + + } else if (event == GLFW_DISCONNECTED) { + if (PSGLOBAL(joy1id) == jid) { + PSGLOBAL(joy1id) = -1; + } else if (PSGLOBAL(joy2id) == jid) + PSGLOBAL(joy2id) = -1; + } +} +#endif + +#if (defined(_MSC_VER)) +int strcasecmp(const char* str1, const char* str2) +{ + return _strcmpi(str1, str2); +} +#endif +#endif diff --git a/src/skel/fcaseopen.h b/src/skel/fcaseopen.h new file mode 100644 index 00000000..d51279bf --- /dev/null +++ b/src/skel/fcaseopen.h @@ -0,0 +1,6 @@ +FILE *_fcaseopen(char const *filename, char const *mode); +#if defined(_WIN32) +#define fcaseopen fopen +#else +#define fcaseopen _fcaseopen +#endif \ No newline at end of file diff --git a/src/text/Pager.cpp b/src/text/Pager.cpp index 609c6860..230be70a 100644 --- a/src/text/Pager.cpp +++ b/src/text/Pager.cpp @@ -119,7 +119,7 @@ CPager::AddMessage(wchar *str, uint16 speed, uint16 priority, uint16 a5) m_messages[0].m_nNumber[4], m_messages[0].m_nNumber[5], nil); - return; + return; } } diff --git a/src/vehicles/Automobile.cpp b/src/vehicles/Automobile.cpp index 417dd140..06ff28fe 100644 --- a/src/vehicles/Automobile.cpp +++ b/src/vehicles/Automobile.cpp @@ -218,7 +218,9 @@ CAutomobile::SetModelIndex(uint32 id) CVector vecDAMAGE_ENGINE_POS_SMALL(-0.1f, -0.1f, 0.0f); CVector vecDAMAGE_ENGINE_POS_BIG(-0.5f, -0.3f, 0.0f); +#ifndef RW_DC #pragma optimize("", off) // that's what R* did +#endif void CAutomobile::ProcessControl(void) @@ -1222,7 +1224,9 @@ CAutomobile::ProcessControl(void) } } +#ifndef RW_DC #pragma optimize("", on) +#endif void CAutomobile::Teleport(CVector pos) diff --git a/src/vehicles/Train.cpp b/src/vehicles/Train.cpp index be546c70..b0ee97b3 100644 --- a/src/vehicles/Train.cpp +++ b/src/vehicles/Train.cpp @@ -433,7 +433,7 @@ CTrain::InitTrains(void) ReadAndInterpretTrackFile("data\\paths\\tracks2.dat", &pTrackNodes_S, &NumTrackNodes_S, 4, StationDist_S, &TotalLengthOfTrack_S, &TotalDurationOfTrack_S, aLineBits_S, true); - int trainId; + int32 trainId; CStreaming::LoadAllRequestedModels(false); if(CModelInfo::GetModelInfo("train", &trainId)) CStreaming::RequestModel(trainId, 0); diff --git a/src/vmu/vmu.cpp b/src/vmu/vmu.cpp new file mode 100644 index 00000000..e063012c --- /dev/null +++ b/src/vmu/vmu.cpp @@ -0,0 +1,169 @@ +#include "vmu.h" + +#if !defined(DC_TEXCONV) + +#ifdef DC_SH4 +# include +#endif + +# ifdef DC_SH4 +# include +# endif + +extern bool _dcAudioInitialized; + +// ====== STATIC METHODS ===== + +VmuProfiler *VmuProfiler::getInstance() { + if(!instance_) { + instance_ = std::unique_ptr(new VmuProfiler); + } + return instance_.get(); +} + +void VmuProfiler::destroyInstance() { + if(instance_) { + instance_->stopRequest_ = true; + instance_->join(); + instance_.reset(); + } +} + +float VmuProfiler::heapUtilization() { +#ifdef DC_SH4 + // Query heap manager/allocator for info + auto mallocInfo = mallinfo(); + + // Used bytes are as resported + size_t usedBlocks = mallocInfo.uordblks; + // First component of free bytes are as reported + size_t freeBlocks = mallocInfo.fordblks; + + // End address of region reserved for heap growth + size_t brkEnd = _arch_mem_top - THD_KERNEL_STACK_SIZE - 1; + // Amount of bytes the heap has yet to still grow + size_t brkFree = brkEnd - reinterpret_cast(sbrk(0)); + + // Total heap space available is free blocks from allocator + unclaimed sbrk() space + freeBlocks += brkFree; + + // Return total utilization as a % + return static_cast(usedBlocks) / + static_cast(usedBlocks + freeBlocks) * 100.0f; +#else + return 0.0f; +#endif +} + +float VmuProfiler::vertexBufferUtilization() { +#ifdef DC_SH4 + size_t start = PVR_GET(PVR_TA_VERTBUF_START); + size_t end = PVR_GET(PVR_TA_VERTBUF_END); + size_t pos = PVR_GET(PVR_TA_VERTBUF_POS); + + size_t used = pos - start; + size_t free = end - pos; + + return static_cast(used) / + static_cast(used + free) * 100.0f; +#else + return 0.0f; +#endif +} + +// ===== INSTANCE METHODS ===== + +VmuProfiler::VmuProfiler(): + std::thread(std::bind_front(&VmuProfiler::run, this)) +{} + +VmuProfiler::~VmuProfiler() { + stopRequest_ = true; +} + +void VmuProfiler::run() { + while(!stopRequest_) { + +#ifdef DC_SH4 + if(auto *dev = maple_enum_type(0, MAPLE_FUNC_MEMCARD); + dev && _dcAudioInitialized && updated_) + { + pvr_stats_t pvrStats; pvr_get_stats(&pvrStats); + uint32_t sramStats = snd_mem_available(); + size_t pvrAvail = pvr_mem_available(); + float fps = std::accumulate(std::begin(fps_), std::end(fps_), 0.0f) + / static_cast(fpsSamples); + + float sh4Mem = heapUtilization(); + float pvrMem = (8_MB - pvrAvail ) / 8_MB * 100.0f; + float armMem = (2_MB - sramStats) / 2_MB * 100.0f; + float vtxBuf = vertBuffUse_; + { + std::shared_lock lk(mtx_); + + vmu_printf("FPS :%6.2f\n" + "SH4 :%6.2f%%\n" + "PVR :%6.2f%%\n" + "ARM :%6.2f%%\n" + "VTX :%6.2f%%", + fps, sh4Mem, pvrMem, armMem, vtxBuf); + } + } +#endif + + std::this_thread::sleep_for(updateRate_); + } +} + +void VmuProfiler::updateVertexBufferUsage() { + std::unique_lock lk(mtx_); + updated_ = true; + +#ifdef DC_SH4 + vertBuffUse_ = vertexBufferUtilization(); + + pvr_stats_t pvrStats; + pvr_get_stats(&pvrStats); + fps_[fpsFrame_++] = pvrStats.frame_rate; + + if(fpsFrame_ >= fpsSamples) + fpsFrame_ = 0; +#endif +} + +#ifdef DC_SIM +# define vmu_beep_waveform(...) MAPLE_EOK +# define maple_enum_dev(...) nullptr +#endif + +RAIIVmuBeeper::RAIIVmuBeeper(maple_device_t *dev, float period, float duty): + device_(dev) +{ + assert(period >= duty); + +#ifdef WITH_BEEPS + if(!dev) return; + + uint8_t period_raw = static_cast(period * 255); + uint8_t duty_raw = static_cast(duty * 255); + + /* Give other threads a chance to unlock the maple frame, + and keep trying if we fail. */ + while(vmu_beep_waveform(dev, period_raw, duty_raw, 0, 0) != MAPLE_EOK) + std::this_thread::yield(); +#endif +} + +RAIIVmuBeeper::RAIIVmuBeeper(std::string_view address, float period, float duty): + RAIIVmuBeeper(maple_enum_dev(address[0] - 'a', address[1] - '0'), period, duty) +{} + +RAIIVmuBeeper::~RAIIVmuBeeper() { + if(!device_) return; + + /* Oh dear god, MAKE IT STOP, BETTER EVENTUALLY SUCCEED! */ + while(vmu_beep_waveform(device_, 0, 0, 0, 0) != MAPLE_EOK) + std::this_thread::yield(); +} + +#endif diff --git a/src/vmu/vmu.h b/src/vmu/vmu.h new file mode 100644 index 00000000..6df5694a --- /dev/null +++ b/src/vmu/vmu.h @@ -0,0 +1,88 @@ +#ifndef VMU_H_ +#define VMU_H_ + +#include "common.h" +#include "sampman.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef RW_DC +# include +#endif + +#ifndef VMU_DEFALT_PATH +# define VMU_DEFAULT_PATH "a1" +#endif + +#if (!defined(RW_DC) || !defined(DC_TEXCONV)) + +constexpr float operator "" _MB(unsigned long long value) { + return 1024.0f * 1024.0f * value; +} + +# define RAIIVmuBeep(...) RAIIVmuBeeper beep(__VA_ARGS__) + +class RAIIVmuBeeper { +private: + maple_device_t* device_; +public: + RAIIVmuBeeper(maple_device_t *dev, float period, float duty=0.5f); + RAIIVmuBeeper(std::string_view address, float period, float duty=0.5f); + ~RAIIVmuBeeper(); +}; + +class VmuProfiler: public std::thread { +private: + constexpr static auto updateRate_ = std::chrono::milliseconds(100); + constexpr static size_t fpsSamples = 20; + + static inline + std::unique_ptr instance_ = {}; + + mutable std::shared_mutex mtx_ = {}; + bool updated_ = false; + std::array fps_ = { 0.0f }; + size_t fpsFrame_ = 0; + float vertBuffUse_ = 0.0f; + std::atomic stopRequest_ = false; + +protected: + // Default constructor, spawns monitor thread + VmuProfiler(); + + // Main entry point and loop for the monitor thread + virtual void run(); + +public: + + // Returns total % of heap space currently in-use + static float heapUtilization(); + + // Returns the % of the PVR vertex buffer which is utilized + static float vertexBufferUtilization(); + + // Returns pointer to singleton, creating it + spawning monitor thread upon first call + static VmuProfiler *getInstance(); + static void destroyInstance(); + + // Automatically signals thread to exit + ~VmuProfiler(); + + // To be called every frame, so we can update FPS stats too! + void updateVertexBufferUsage(); +}; + +#else +# define RAIIVmuBeep(...) +#endif + +#endif diff --git a/vendor/TriStripper/README.md b/vendor/TriStripper/README.md new file mode 100644 index 00000000..7e84a7e8 --- /dev/null +++ b/vendor/TriStripper/README.md @@ -0,0 +1,21 @@ +See https://github.com/GPSnoopy/TriStripper/blob/master/README.md + +# Tri Stripper + +## Prologue + +This is a github import of one of my first open source project from 2002-2004. Most of the code has been left unchanged except for compilation errors or now redundant files (e.g. stdint.h). The documentation has been salvaged from a backup copy of its old website. + +## Introduction + +Drawing 3D models using triangle strips has always been more efficient than just using a bunch of triangles. Tri Stripper automatically generates triangle strips from raw triangles. Meant to be used in runtime, Tri Stripper is fast and efficient. It can take into consideration the presence of the vertex cache found on most 3D cards. + +It should also be noted that some of the algorithms and structures used by Tri Stripper can serve other purposes in other domains; for example, computing object silhouette when using "stencil" shadows. + +[See full change log.](doc/ChangeLog.md) + +## Design + +- [How does Tri Stripper work.](doc/How.md) +- [Comparison between Tri Stripper and NVIDIA's NVTriStrip.](doc/VsNvTriStrip.md) +- [How to use Tri Stripper and explanation of the test program.](doc/Manual.md) \ No newline at end of file diff --git a/vendor/TriStripper/include/detail/cache_simulator.h b/vendor/TriStripper/include/detail/cache_simulator.h new file mode 100644 index 00000000..9c297211 --- /dev/null +++ b/vendor/TriStripper/include/detail/cache_simulator.h @@ -0,0 +1,152 @@ +// +// Copyright (C) 2004 Tanguy Fautré. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#ifndef TRI_STRIPPER_HEADER_GUARD_CACHE_SIMULATOR_H +#define TRI_STRIPPER_HEADER_GUARD_CACHE_SIMULATOR_H + +#include +#include +#include + + + + +namespace triangle_stripper { + + namespace detail { + + + + +class cache_simulator +{ +public: + cache_simulator(); + + void clear(); + void resize(size_t Size); + void reset(); + void push_cache_hits(bool Enabled = true); + size_t size() const; + + void push(index i, bool CountCacheHit = false); + void merge(const cache_simulator & Backward, size_t PossibleOverlap); + + void reset_hitcount(); + size_t hitcount() const; + +protected: + typedef std::deque indices_deque; + + indices_deque m_Cache; + size_t m_NbHits; + bool m_PushHits; +}; + + + + + +////////////////////////////////////////////////////////////////////////// +// cache_simulator inline functions +////////////////////////////////////////////////////////////////////////// + +inline cache_simulator::cache_simulator() + : m_NbHits(0), + m_PushHits(true) +{ + +} + + +inline void cache_simulator::clear() +{ + reset_hitcount(); + m_Cache.clear(); +} + + +inline void cache_simulator::resize(const size_t Size) +{ + m_Cache.resize(Size, std::numeric_limits::max()); +} + + +inline void cache_simulator::reset() +{ + std::fill(m_Cache.begin(), m_Cache.end(), std::numeric_limits::max()); + reset_hitcount(); +} + + +inline void cache_simulator::push_cache_hits(bool Enabled) +{ + m_PushHits = Enabled; +} + + +inline size_t cache_simulator::size() const +{ + return m_Cache.size(); +} + + +inline void cache_simulator::push(const index i, const bool CountCacheHit) +{ + if (CountCacheHit || m_PushHits) { + + if (std::find(m_Cache.begin(), m_Cache.end(), i) != m_Cache.end()) { + + // Should we count the cache hits? + if (CountCacheHit) + ++m_NbHits; + + // Should we not push the index into the cache if it's a cache hit? + if (! m_PushHits) + return; + } + } + + // Manage the indices cache as a FIFO structure + m_Cache.push_front(i); + m_Cache.pop_back(); +} + + +inline void cache_simulator::merge(const cache_simulator & Backward, const size_t PossibleOverlap) +{ + const size_t Overlap = std::min(PossibleOverlap, size()); + + for (size_t i = 0; i < Overlap; ++i) + push(Backward.m_Cache[i], true); + + m_NbHits += Backward.m_NbHits; +} + + +inline void cache_simulator::reset_hitcount() +{ + m_NbHits = 0; +} + + +inline size_t cache_simulator::hitcount() const +{ + return m_NbHits; +} + + + + + } // namespace detail + +} // namespace triangle_stripper + + + + +#endif // TRI_STRIPPER_HEADER_GUARD_CACHE_SIMULATOR_H diff --git a/vendor/TriStripper/include/detail/connectivity_graph.h b/vendor/TriStripper/include/detail/connectivity_graph.h new file mode 100644 index 00000000..743aa41d --- /dev/null +++ b/vendor/TriStripper/include/detail/connectivity_graph.h @@ -0,0 +1,34 @@ +// +// Copyright (C) 2004 Tanguy Fautré. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#ifndef TRI_STRIPPER_HEADER_GUARD_CONNECTIVITY_GRAPH_H +#define TRI_STRIPPER_HEADER_GUARD_CONNECTIVITY_GRAPH_H + +#include "public_types.h" + +#include "graph_array.h" +#include "types.h" + + + + +namespace triangle_stripper +{ + + namespace detail + { + + void make_connectivity_graph(graph_array & Triangles, const indices & Indices); + + } + +} + + + + +#endif // TRI_STRIPPER_HEADER_GUARD_CONNECTIVITY_GRAPH_H diff --git a/vendor/TriStripper/include/detail/graph_array.h b/vendor/TriStripper/include/detail/graph_array.h new file mode 100644 index 00000000..73f26c60 --- /dev/null +++ b/vendor/TriStripper/include/detail/graph_array.h @@ -0,0 +1,461 @@ +// +// Copyright (C) 2004 Tanguy Fautré. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#ifndef TRI_STRIPPER_HEADER_GUARD_GRAPH_ARRAY_H +#define TRI_STRIPPER_HEADER_GUARD_GRAPH_ARRAY_H + +#include +#include +#include +#include +#include + + + + +namespace triangle_stripper { + + namespace detail { + + + + +// graph_array main class +template +class graph_array +{ +public: + + class arc; + class node; + + // New types + typedef size_t nodeid; + typedef nodetype value_type; + typedef std::vector node_vector; + typedef typename node_vector::iterator node_iterator; + typedef typename node_vector::const_iterator const_node_iterator; + typedef typename node_vector::reverse_iterator node_reverse_iterator; + typedef typename node_vector::const_reverse_iterator const_node_reverse_iterator; + + typedef graph_array graph_type; + + + // graph_array::arc class + class arc + { + public: + node_iterator terminal() const; + + protected: + friend class graph_array; + + arc(node_iterator Terminal); + + node_iterator m_Terminal; + }; + + + // New types + typedef std::vector arc_list; + typedef typename arc_list::iterator out_arc_iterator; + typedef typename arc_list::const_iterator const_out_arc_iterator; + + + // graph_array::node class + class node + { + public: + void mark(); + void unmark(); + bool marked() const; + + bool out_empty() const; + size_t out_size() const; + + out_arc_iterator out_begin(); + out_arc_iterator out_end(); + const_out_arc_iterator out_begin() const; + const_out_arc_iterator out_end() const; + + value_type & operator * (); + value_type * operator -> (); + const value_type & operator * () const; + const value_type * operator -> () const; + + value_type & operator = (const value_type & Elem); + + protected: + friend class graph_array; + friend class std::vector; + + node(arc_list & Arcs); + + arc_list & m_Arcs; + size_t m_Begin; + size_t m_End; + + value_type m_Elem; + bool m_Marker; + }; + + + graph_array(); + explicit graph_array(size_t NbNodes); + + // Node related member functions + bool empty() const; + size_t size() const; + + node & operator [] (nodeid i); + const node & operator [] (nodeid i) const; + + node_iterator begin(); + node_iterator end(); + const_node_iterator begin() const; + const_node_iterator end() const; + + node_reverse_iterator rbegin(); + node_reverse_iterator rend(); + const_node_reverse_iterator rbegin() const; + const_node_reverse_iterator rend() const; + + // Arc related member functions + out_arc_iterator insert_arc(nodeid Initial, nodeid Terminal); + out_arc_iterator insert_arc(node_iterator Initial, node_iterator Terminal); + + // Optimized (overloaded) functions + void swap(graph_type & Right); + friend void swap(graph_type & Left, graph_type & Right) { Left.swap(Right); } + +protected: + graph_array(const graph_type &); + graph_type & operator = (const graph_type &); + + node_vector m_Nodes; + arc_list m_Arcs; +}; + + + +// Additional "low level", graph related, functions +template +void unmark_nodes(graph_array & G); + + + + + +////////////////////////////////////////////////////////////////////////// +// graph_array::arc inline functions +////////////////////////////////////////////////////////////////////////// + +template +inline graph_array::arc::arc(node_iterator Terminal) + : m_Terminal(Terminal) { } + + +template +inline typename graph_array::node_iterator graph_array::arc::terminal() const +{ + return m_Terminal; +} + + + +////////////////////////////////////////////////////////////////////////// +// graph_array::node inline functions +////////////////////////////////////////////////////////////////////////// + +template +inline graph_array::node::node(arc_list & Arcs) + : m_Arcs(Arcs), + m_Begin(std::numeric_limits::max()), + m_End(std::numeric_limits::max()), + m_Marker(false) +{ + +} + + +template +inline void graph_array::node::mark() +{ + m_Marker = true; +} + + +template +inline void graph_array::node::unmark() +{ + m_Marker = false; +} + + +template +inline bool graph_array::node::marked() const +{ + return m_Marker; +} + + +template +inline bool graph_array::node::out_empty() const +{ + return (m_Begin == m_End); +} + + +template +inline size_t graph_array::node::out_size() const +{ + return (m_End - m_Begin); +} + + +template +inline typename graph_array::out_arc_iterator graph_array::node::out_begin() +{ + return (m_Arcs.begin() + m_Begin); +} + + +template +inline typename graph_array::out_arc_iterator graph_array::node::out_end() +{ + return (m_Arcs.begin() + m_End); +} + + +template +inline typename graph_array::const_out_arc_iterator graph_array::node::out_begin() const +{ + return (m_Arcs.begin() + m_Begin); +} + + +template +inline typename graph_array::const_out_arc_iterator graph_array::node::out_end() const +{ + return (m_Arcs.begin() + m_End); +} + + +template +inline N & graph_array::node::operator * () +{ + return m_Elem; +} + + +template +inline N * graph_array::node::operator -> () +{ + return &m_Elem; +} + + +template +inline const N & graph_array::node::operator * () const +{ + return m_Elem; +} + + +template +inline const N * graph_array::node::operator -> () const +{ + return &m_Elem; +} + + +template +inline N & graph_array::node::operator = (const N & Elem) +{ + return (m_Elem = Elem); +} + + + +////////////////////////////////////////////////////////////////////////// +// graph_array inline functions +////////////////////////////////////////////////////////////////////////// + +template +inline graph_array::graph_array() { } + + +template +inline graph_array::graph_array(const size_t NbNodes) + : m_Nodes(NbNodes, node(m_Arcs)) +{ + // optimisation: we consider that, averagely, a triangle may have at least 2 neighbours + // otherwise we are just wasting a bit of memory, but not that much + m_Arcs.reserve(NbNodes * 2); +} + + +template +inline bool graph_array::empty() const +{ + return m_Nodes.empty(); +} + + +template +inline size_t graph_array::size() const +{ + return m_Nodes.size(); +} + + +template +inline typename graph_array::node & graph_array::operator [] (const nodeid i) +{ + assert(i < size()); + + return m_Nodes[i]; +} + + +template +inline const typename graph_array::node & graph_array::operator [] (const nodeid i) const +{ + assert(i < size()); + + return m_Nodes[i]; +} + + +template +inline typename graph_array::node_iterator graph_array::begin() +{ + return m_Nodes.begin(); +} + + +template +inline typename graph_array::node_iterator graph_array::end() +{ + return m_Nodes.end(); +} + + +template +inline typename graph_array::const_node_iterator graph_array::begin() const +{ + return m_Nodes.begin(); +} + + +template +inline typename graph_array::const_node_iterator graph_array::end() const +{ + return m_Nodes.end(); +} + + +template +inline typename graph_array::node_reverse_iterator graph_array::rbegin() +{ + return m_Nodes.rbegin(); +} + + +template +inline typename graph_array::node_reverse_iterator graph_array::rend() +{ + return m_Nodes.rend(); +} + + +template +inline typename graph_array::const_node_reverse_iterator graph_array::rbegin() const +{ + return m_Nodes.rbegin(); +} + + +template +inline typename graph_array::const_node_reverse_iterator graph_array::rend() const +{ + return m_Nodes.rend(); +} + + +template +inline typename graph_array::out_arc_iterator graph_array::insert_arc(const nodeid Initial, const nodeid Terminal) +{ + assert(Initial < size()); + assert(Terminal < size()); + + return insert_arc(m_Nodes.begin() + Initial, m_Nodes.begin() + Terminal); +} + + +template +inline typename graph_array::out_arc_iterator graph_array::insert_arc(const node_iterator Initial, const node_iterator Terminal) +{ + assert((Initial >= begin()) && (Initial < end())); + assert((Terminal >= begin()) && (Terminal < end())); + + node & Node = * Initial; + + if (Node.out_empty()) { + + Node.m_Begin = m_Arcs.size(); + Node.m_End = m_Arcs.size() + 1; + + } else { + + // we optimise here for make_connectivity_graph() + // we know all the arcs for a given node are successively and sequentially added + assert(Node.m_End == m_Arcs.size()); + + ++(Node.m_End); + } + + m_Arcs.push_back(arc(Terminal)); + + out_arc_iterator it = m_Arcs.end(); + return (--it); +} + + +template +inline void graph_array::swap(graph_type & Right) +{ + std::swap(m_Nodes, Right.m_Nodes); + std::swap(m_Arcs, Right.m_Arcs); +} + + + +////////////////////////////////////////////////////////////////////////// +// additional functions +////////////////////////////////////////////////////////////////////////// + +template +inline void unmark_nodes(graph_array & G) +{ + for (auto& node : G) + { + node.unmark(); + } +} + + + + + } // namespace detail + +} // namespace triangle_stripper + + + + +#endif // TRI_STRIPPER_HEADER_GUARD_GRAPH_ARRAY_H diff --git a/vendor/TriStripper/include/detail/heap_array.h b/vendor/TriStripper/include/detail/heap_array.h new file mode 100644 index 00000000..4ed002c0 --- /dev/null +++ b/vendor/TriStripper/include/detail/heap_array.h @@ -0,0 +1,295 @@ +// +// Copyright (C) 2004 Tanguy Fautr�. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#ifndef TRI_STRIPPER_HEADER_GUARD_HEAP_ARRAY_H +#define TRI_STRIPPER_HEADER_GUARD_HEAP_ARRAY_H + +#include + + + + +namespace triangle_stripper { + + namespace detail { + + + + +// mutable heap +// can be interfaced pretty muck like an array +template > +class heap_array +{ +public: + + // Pre = PreCondition, Post = PostCondition + + heap_array() : m_Locked(false) { } // Post: ((size() == 0) && ! locked()) + + void clear(); // Post: ((size() == 0) && ! locked()) + + void reserve(size_t Size); + size_t size() const; + + bool empty() const; + bool locked() const; + bool removed(size_t i) const; // Pre: (valid(i)) + bool valid(size_t i) const; + + size_t position(size_t i) const; // Pre: (valid(i)) + + const T & top() const; // Pre: (! empty()) + const T & peek(size_t i) const; // Pre: (! removed(i)) + const T & operator [] (size_t i) const; // Pre: (! removed(i)) + + void lock(); // Pre: (! locked()) Post: (locked()) + size_t push(const T & Elem); // Pre: (! locked()) + + void pop(); // Pre: (locked() && ! empty()) + void erase(size_t i); // Pre: (locked() && ! removed(i)) + void update(size_t i, const T & Elem); // Pre: (locked() && ! removed(i)) + +protected: + + heap_array(const heap_array &); + heap_array & operator = (const heap_array &); + + class linker + { + public: + linker(const T & Elem, size_t i) + : m_Elem(Elem), m_Index(i) { } + + T m_Elem; + size_t m_Index; + }; + + typedef std::vector linked_heap; + typedef std::vector finder; + + void Adjust(size_t i); + void Swap(size_t a, size_t b); + bool Less(const linker & a, const linker & b) const; + + linked_heap m_Heap; + finder m_Finder; + CmpT m_Compare; + bool m_Locked; +}; + + + + + +////////////////////////////////////////////////////////////////////////// +// heap_indexed inline functions +////////////////////////////////////////////////////////////////////////// + +template +inline void heap_array::clear() +{ + m_Heap.clear(); + m_Finder.clear(); + m_Locked = false; +} + + +template +inline bool heap_array::empty() const +{ + return m_Heap.empty(); +} + + +template +inline bool heap_array::locked() const +{ + return m_Locked; +} + + +template +inline void heap_array::reserve(const size_t Size) +{ + m_Heap.reserve(Size); + m_Finder.reserve(Size); +} + + +template +inline size_t heap_array::size() const +{ + return m_Heap.size(); +} + + +template +inline const T & heap_array::top() const +{ + assert(! empty()); + + return m_Heap.front().m_Elem; +} + + +template +inline const T & heap_array::peek(const size_t i) const +{ + assert(! removed(i)); + + return (m_Heap[m_Finder[i]].m_Elem); +} + + +template +inline const T & heap_array::operator [] (const size_t i) const +{ + return peek(i); +} + + +template +inline void heap_array::pop() +{ + assert(locked()); + assert(! empty()); + + Swap(0, size() - 1); + m_Heap.pop_back(); + + if (! empty()) + Adjust(0); +} + + +template +inline void heap_array::lock() +{ + assert(! locked()); + + m_Locked =true; +} + + +template +inline size_t heap_array::push(const T & Elem) +{ + assert(! locked()); + + const size_t Id = size(); + m_Finder.push_back(Id); + m_Heap.push_back(linker(Elem, Id)); + Adjust(Id); + + return Id; +} + + +template +inline void heap_array::erase(const size_t i) +{ + assert(locked()); + assert(! removed(i)); + + const size_t j = m_Finder[i]; + Swap(j, size() - 1); + m_Heap.pop_back(); + + if (j != size()) + Adjust(j); +} + + +template +inline bool heap_array::removed(const size_t i) const +{ + assert(valid(i)); + + return (m_Finder[i] >= m_Heap.size()); +} + + +template +inline bool heap_array::valid(const size_t i) const +{ + return (i < m_Finder.size()); +} + + +template +inline size_t heap_array::position(const size_t i) const +{ + assert(valid(i)); + + return (m_Heap[i].m_Index); +} + + +template +inline void heap_array::update(const size_t i, const T & Elem) +{ + assert(locked()); + assert(! removed(i)); + + const size_t j = m_Finder[i]; + m_Heap[j].m_Elem = Elem; + Adjust(j); +} + + +template +inline void heap_array::Adjust(size_t i) +{ + assert(i < m_Heap.size()); + + size_t j; + + // Check the upper part of the heap + for (j = i; (j > 0) && (Less(m_Heap[(j - 1) / 2], m_Heap[j])); j = ((j - 1) / 2)) + Swap(j, (j - 1) / 2); + + // Check the lower part of the heap + for (i = j; (j = 2 * i + 1) < size(); i = j) { + if ((j + 1 < size()) && (Less(m_Heap[j], m_Heap[j + 1]))) + ++j; + + if (Less(m_Heap[j], m_Heap[i])) + return; + + Swap(i, j); + } +} + + +template +inline void heap_array::Swap(const size_t a, const size_t b) +{ + std::swap(m_Heap[a], m_Heap[b]); + + m_Finder[(m_Heap[a].m_Index)] = a; + m_Finder[(m_Heap[b].m_Index)] = b; +} + + +template +inline bool heap_array::Less(const linker & a, const linker & b) const +{ + return m_Compare(a.m_Elem, b.m_Elem); +} + + + + + } // namespace detail + +} // namespace triangle_stripper + + + + +#endif // TRI_STRIPPER_HEADER_GUARD_HEAP_ARRAY_H diff --git a/vendor/TriStripper/include/detail/policy.h b/vendor/TriStripper/include/detail/policy.h new file mode 100644 index 00000000..ceea53d8 --- /dev/null +++ b/vendor/TriStripper/include/detail/policy.h @@ -0,0 +1,64 @@ +// +// Copyright (C) 2004 Tanguy Fautré. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#ifndef TRI_STRIPPER_HEADER_GUARD_POLICY_H +#define TRI_STRIPPER_HEADER_GUARD_POLICY_H + +#include "public_types.h" +#include "types.h" + + + + +namespace triangle_stripper { + + namespace detail { + + + + +class policy +{ +public: + policy(size_t MinStripSize, bool Cache); + + strip BestStrip() const; + void Challenge(strip Strip, size_t Degree, size_t CacheHits); + +private: + strip m_Strip; + size_t m_Degree; + size_t m_CacheHits; + + const size_t m_MinStripSize; + const bool m_Cache; +}; + + + + + +inline policy::policy(size_t MinStripSize, bool Cache) +: m_Degree(0), m_CacheHits(0), m_MinStripSize(MinStripSize), m_Cache(Cache) { } + + +inline strip policy::BestStrip() const +{ + return m_Strip; +} + + + + + } // namespace detail + +} // namespace triangle_stripper + + + + +#endif // TRI_STRIPPER_HEADER_GUARD_POLICY_H diff --git a/vendor/TriStripper/include/detail/types.h b/vendor/TriStripper/include/detail/types.h new file mode 100644 index 00000000..f24223de --- /dev/null +++ b/vendor/TriStripper/include/detail/types.h @@ -0,0 +1,101 @@ +// +// Copyright (C) 2004 Tanguy Fautr�. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#ifndef TRI_STRIPPER_HEADER_GUARD_TYPES_H +#define TRI_STRIPPER_HEADER_GUARD_TYPES_H + +#include + + + +namespace triangle_stripper { + + namespace detail { + + + + +class triangle +{ +public: + triangle() { } + triangle(index A, index B, index C) + : m_A(A), m_B(B), m_C(C), m_StripID(0) { } + + void ResetStripID() { m_StripID = 0; } + void SetStripID(size_t StripID) { m_StripID = StripID; } + size_t StripID() const { return m_StripID; } + + index A() const { return m_A; } + index B() const { return m_B; } + index C() const { return m_C; } + +private: + index m_A; + index m_B; + index m_C; + + size_t m_StripID; +}; + + + +class triangle_edge +{ +public: + triangle_edge(index A, index B) + : m_A(A), m_B(B) { } + + index A() const { return m_A; } + index B() const { return m_B; } + + bool operator == (const triangle_edge & Right) const { + return ((A() == Right.A()) && (B() == Right.B())); + } + +private: + index m_A; + index m_B; +}; + + + +enum triangle_order { ABC, BCA, CAB }; + + + +class strip +{ +public: + strip() + : m_Start(0), m_Order(ABC), m_Size(0) { } + + strip(size_t Start, triangle_order Order, size_t Size, std::vector Indices) + : m_Start(Start), m_Order(Order), m_Size(Size), m_Indices(Indices) { } + + size_t Start() const { return m_Start; } + triangle_order Order() const { return m_Order; } + size_t Size() const { return m_Size; } + + std::vector m_Indices; +private: + size_t m_Start; + triangle_order m_Order; + size_t m_Size; +}; + + + + + } // namespace detail + +} // namespace triangle_stripper + + + + +#endif // TRI_STRIPPER_HEADER_GUARD_TYPES_H diff --git a/vendor/TriStripper/include/public_types.h b/vendor/TriStripper/include/public_types.h new file mode 100644 index 00000000..19b4a2fd --- /dev/null +++ b/vendor/TriStripper/include/public_types.h @@ -0,0 +1,40 @@ +// +// Copyright (C) 2004 Tanguy Fautr�. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#ifndef TRI_STRIPPER_HEADER_GUARD_PUBLIC_TYPES_H +#define TRI_STRIPPER_HEADER_GUARD_PUBLIC_TYPES_H + +#include +#include + + +namespace triangle_stripper +{ + + typedef size_t index; + typedef std::vector indices; + + enum primitive_type + { + TRIANGLES = 0x0004, // = GL_TRIANGLES + TRIANGLE_STRIP = 0x0005 // = GL_TRIANGLE_STRIP + }; + + struct primitive_group + { + indices Indices; + primitive_type Type; + }; + + typedef std::vector primitive_vector; + +} + + + + +#endif // TRI_STRIPPER_HEADER_GUARD_PUBLIC_TYPES_H diff --git a/vendor/TriStripper/include/tri_stripper.h b/vendor/TriStripper/include/tri_stripper.h new file mode 100644 index 00000000..d430fe1d --- /dev/null +++ b/vendor/TriStripper/include/tri_stripper.h @@ -0,0 +1,188 @@ + +////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2004 Tanguy Fautré. +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +// Tanguy Fautré +// tanguy@fautre.com +// +////////////////////////////////////////////////////////////////////// +// +// Tri Stripper +// ************ +// +// Post TnL cache aware triangle stripifier in O(n.log(n)). +// +// History: see ChangeLog +// +////////////////////////////////////////////////////////////////////// + +// Protection against old C habits +#if defined(max) +#error "'max' macro defined! It's against the C++ standard. Please use 'std::max' instead (undefine 'max' macro if it was defined in another library)." +#endif + +// Protection against old C habits +#if defined(min) +#error "'min' macro defined! It's against the C++ standard. Please use 'std::min' instead (undefine 'min' macro if it was defined in another library)." +#endif + + + +#ifndef TRI_STRIPPER_HEADER_GUARD_TRI_STRIPPER_H +#define TRI_STRIPPER_HEADER_GUARD_TRI_STRIPPER_H + +#include "public_types.h" + +#include "detail/cache_simulator.h" +#include "detail/graph_array.h" +#include "detail/heap_array.h" +#include "detail/types.h" + + + + +namespace triangle_stripper { + + + + +class tri_stripper +{ +public: + + tri_stripper(const indices & TriIndices); + + void Strip(primitive_vector * out_pPrimitivesVector); + + /* Stripifier Algorithm Settings */ + + // Set the post-T&L cache size (0 disables the cache optimizer). + void SetCacheSize(size_t CacheSize = 10); + + // Set the minimum size of a triangle strip (should be at least 2 triangles). + // The stripifier discard any candidate strips that does not satisfy the minimum size condition. + void SetMinStripSize(size_t MinStripSize = 2); + + // Set the backward search mode in addition to the forward search mode. + // In forward mode, the candidate strips are build with the current candidate triangle being the first + // triangle of the strip. When the backward mode is enabled, the stripifier also tests candidate strips + // where the current candidate triangle is the last triangle of the strip. + // Enable this if you want better results at the expense of being slightly slower. + // Note: Do *NOT* use this when the cache optimizer is enabled; it only gives worse results. + void SetBackwardSearch(bool Enabled = false); + + // Set the cache simulator FIFO behavior (does nothing if the cache optimizer is disabled). + // When enabled, the cache is simulated as a simple FIFO structure. However, when + // disabled, indices that trigger cache hits are not pushed into the FIFO structure. + // This allows simulating some GPUs that do not duplicate cache entries (e.g. NV25 or greater). + void SetPushCacheHits(bool Enabled = true); + + /* End Settings */ + +private: + + typedef detail::graph_array triangle_graph; + typedef detail::heap_array > triangle_heap; + typedef std::vector candidates; + typedef triangle_graph::node_iterator tri_iterator; + typedef triangle_graph::const_node_iterator const_tri_iterator; + typedef triangle_graph::out_arc_iterator link_iterator; + typedef triangle_graph::const_out_arc_iterator const_link_iterator; + + void InitTriHeap(); + void Stripify(); + void AddLeftTriangles(); + void ResetStripIDs(); + + detail::strip FindBestStrip(); + detail::strip ExtendToStrip(size_t Start, detail::triangle_order Order); + detail::strip BackExtendToStrip(size_t Start, detail::triangle_order Order, bool ClockWise); + const_link_iterator LinkToNeighbour(const_tri_iterator Node, bool ClockWise, detail::triangle_order & Order, bool NotSimulation, size_t triIndex); + const_link_iterator BackLinkToNeighbour(const_tri_iterator Node, bool ClockWise, detail::triangle_order & Order); + void BuildStrip(const detail::strip Strip); + void MarkTriAsTaken(size_t i); + void AddIndex(index i, bool NotSimulation); + void BackAddIndex(index i); + void AddTriangle(const detail::triangle & Tri, detail::triangle_order Order, bool NotSimulation); + void BackAddTriangle(const detail::triangle & Tri, detail::triangle_order Order); + + bool Cache() const; + size_t CacheSize() const; + + static detail::triangle_edge FirstEdge(const detail::triangle & Triangle, detail::triangle_order Order); + static detail::triangle_edge LastEdge(const detail::triangle & Triangle, detail::triangle_order Order); + + primitive_vector m_PrimitivesVector; + triangle_graph m_Triangles; + triangle_heap m_TriHeap; + candidates m_Candidates; + detail::cache_simulator m_Cache; + detail::cache_simulator m_BackCache; + size_t m_StripID; + size_t m_MinStripSize; + bool m_BackwardSearch; + bool m_FirstRun; +}; + + + + + +////////////////////////////////////////////////////////////////////////// +// tri_stripper inline functions +////////////////////////////////////////////////////////////////////////// + +inline void tri_stripper::SetCacheSize(const size_t CacheSize) +{ + m_Cache.resize(CacheSize); + m_BackCache.resize(CacheSize); +} + + +inline void tri_stripper::SetMinStripSize(const size_t MinStripSize) +{ + if (MinStripSize < 2) + m_MinStripSize = 2; + else + m_MinStripSize = MinStripSize; +} + + +inline void tri_stripper::SetBackwardSearch(const bool Enabled) +{ + m_BackwardSearch = Enabled; +} + + + +inline void tri_stripper::SetPushCacheHits(bool Enabled) +{ + m_Cache.push_cache_hits(Enabled); +} + + + + +} // namespace triangle_stripper + + + + +#endif // TRI_STRIPPER_HEADER_GUARD_TRI_STRIPPER_H diff --git a/vendor/TriStripper/src/connectivity_graph.cpp b/vendor/TriStripper/src/connectivity_graph.cpp new file mode 100644 index 00000000..6973fe20 --- /dev/null +++ b/vendor/TriStripper/src/connectivity_graph.cpp @@ -0,0 +1,130 @@ +// +// Copyright (C) 2004 Tanguy Fautré. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#include "detail/connectivity_graph.h" + +#include + + + + +namespace triangle_stripper { + + namespace detail { + + + + +namespace +{ + + class tri_edge : public triangle_edge + { + public: + tri_edge(index A, index B, size_t TriPos) + : triangle_edge(A, B), m_TriPos(TriPos) { } + + size_t TriPos() const { return m_TriPos; } + + private: + size_t m_TriPos; + }; + + + class cmp_tri_edge_lt + { + public: + bool operator() (const tri_edge & a, const tri_edge & b) const; + }; + + + typedef std::vector edge_map; + + + void LinkNeighbours(graph_array & Triangles, const edge_map & EdgeMap, const tri_edge Edge); + +} + + + + +void make_connectivity_graph(graph_array & Triangles, const indices & Indices) +{ + assert(Triangles.size() == (Indices.size() / 3)); + + // Fill the triangle data + for (size_t i = 0; i < Triangles.size(); ++i) + Triangles[i] = triangle(Indices[i * 3 + 0], Indices[i * 3 + 1], Indices[i * 3 + 2]); + + // Build an edge lookup table + edge_map EdgeMap; + EdgeMap.reserve(Triangles.size() * 3); + + for (size_t i = 0; i < Triangles.size(); ++i) { + + const triangle & Tri = * Triangles[i]; + + EdgeMap.push_back(tri_edge(Tri.A(), Tri.B(), i)); + EdgeMap.push_back(tri_edge(Tri.B(), Tri.C(), i)); + EdgeMap.push_back(tri_edge(Tri.C(), Tri.A(), i)); + } + + std::sort(EdgeMap.begin(), EdgeMap.end(), cmp_tri_edge_lt()); + + // Link neighbour triangles together using the lookup table + for (size_t i = 0; i < Triangles.size(); ++i) { + + const triangle & Tri = * Triangles[i]; + + LinkNeighbours(Triangles, EdgeMap, tri_edge(Tri.B(), Tri.A(), i)); + LinkNeighbours(Triangles, EdgeMap, tri_edge(Tri.C(), Tri.B(), i)); + LinkNeighbours(Triangles, EdgeMap, tri_edge(Tri.A(), Tri.C(), i)); + } +} + + + +namespace +{ + + inline bool cmp_tri_edge_lt::operator() (const tri_edge & a, const tri_edge & b) const + { + const index A1 = a.A(); + const index B1 = a.B(); + const index A2 = b.A(); + const index B2 = b.B(); + + if ((A1 < A2) || ((A1 == A2) && (B1 < B2))) + return true; + else + return false; + } + + + void LinkNeighbours(graph_array & Triangles, const edge_map & EdgeMap, const tri_edge Edge) + { + // Find the first edge equal to Edge + edge_map::const_iterator it = std::lower_bound(EdgeMap.begin(), EdgeMap.end(), Edge, cmp_tri_edge_lt()); + + // See if there are any other edges that are equal + // (if so, it means that more than 2 triangles are sharing the same edge, + // which is unlikely but not impossible) + for (; (it != EdgeMap.end()) && (Edge == (* it)); ++it) + Triangles.insert_arc(Edge.TriPos(), it->TriPos()); + + // Note: degenerated triangles will also point themselves as neighbour triangles + } + +} + + + + + } // namespace detail + +} // namespace detail + diff --git a/vendor/TriStripper/src/policy.cpp b/vendor/TriStripper/src/policy.cpp new file mode 100644 index 00000000..f3050cb9 --- /dev/null +++ b/vendor/TriStripper/src/policy.cpp @@ -0,0 +1,61 @@ +// +// Copyright (C) 2004 Tanguy Fautré. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#include "detail/policy.h" + + + + +namespace triangle_stripper { + + namespace detail { + + + + +void policy::Challenge(strip Strip, size_t Degree, size_t CacheHits) +{ + if (Strip.Size() < m_MinStripSize) + return; + + // Cache is disabled, take the longest strip + if (! m_Cache) { + + if (Strip.Size() > m_Strip.Size()) + m_Strip = Strip; + + // Cache simulator enabled + } else { + + // Priority 1: Keep the strip with the best cache hit count + if (CacheHits > m_CacheHits) { + m_Strip = Strip; + m_Degree = Degree; + m_CacheHits = CacheHits; + + } else if (CacheHits == m_CacheHits) { + + // Priority 2: Keep the strip with the loneliest start triangle + if ((m_Strip.Size() != 0) && (Degree < m_Degree)) { + m_Strip = Strip; + m_Degree = Degree; + + // Priority 3: Keep the longest strip + } else if (Strip.Size() > m_Strip.Size()) { + m_Strip = Strip; + m_Degree = Degree; + } + } + } +} + + + + + } // namespace detail + +} // namespace triangle_stripper diff --git a/vendor/TriStripper/src/tri_stripper.cpp b/vendor/TriStripper/src/tri_stripper.cpp new file mode 100644 index 00000000..7c2d8363 --- /dev/null +++ b/vendor/TriStripper/src/tri_stripper.cpp @@ -0,0 +1,585 @@ +// +// Copyright (C) 2004 Tanguy Fautr�. +// For conditions of distribution and use, +// see copyright notice in tri_stripper.h +// +////////////////////////////////////////////////////////////////////// + +#include "tri_stripper.h" + +#include "detail/connectivity_graph.h" +#include "detail/policy.h" + +#include +#include + + + +namespace triangle_stripper { + + using namespace detail; + + + + +tri_stripper::tri_stripper(const indices & TriIndices) + : m_Triangles(TriIndices.size() / 3), // Silently ignore extra indices if (Indices.size() % 3 != 0) + m_StripID(0), + m_FirstRun(true) +{ + SetCacheSize(); + SetMinStripSize(); + SetBackwardSearch(); + SetPushCacheHits(); + + make_connectivity_graph(m_Triangles, TriIndices); +} + + + +void tri_stripper::Strip(primitive_vector * out_pPrimitivesVector) +{ + assert(out_pPrimitivesVector); + + if (! m_FirstRun) { + unmark_nodes(m_Triangles); + ResetStripIDs(); + m_Cache.reset(); + m_TriHeap.clear(); + m_Candidates.clear(); + m_StripID = 0; + + m_FirstRun = false; + } + + out_pPrimitivesVector->clear(); + + InitTriHeap(); + + Stripify(); + AddLeftTriangles(); + + std::swap(m_PrimitivesVector, (* out_pPrimitivesVector)); +} + + + +void tri_stripper::InitTriHeap() +{ + m_TriHeap.reserve(m_Triangles.size()); + + // Set up the triangles priority queue + // The lower the number of available neighbour triangles, the higher the priority. + for (size_t i = 0; i < m_Triangles.size(); ++i) + m_TriHeap.push(m_Triangles[i].out_size()); + + // We're not going to add new elements anymore + m_TriHeap.lock(); + + // Remove useless triangles + // Note: we had to put all of them into the heap before to ensure coherency of the heap_array object + while ((! m_TriHeap.empty()) && (m_TriHeap.top() == 0)) + m_TriHeap.pop(); +} + + + +void tri_stripper::ResetStripIDs() +{ + for (triangle_graph::node_iterator it = m_Triangles.begin(); it != m_Triangles.end(); ++it) + (**it).ResetStripID(); +} + + + +void tri_stripper::Stripify() +{ + while (! m_TriHeap.empty()) { + + // There is no triangle in the candidates list, refill it with the loneliest triangle + const size_t HeapTop = m_TriHeap.position(0); + m_Candidates.push_back(HeapTop); + + while (! m_Candidates.empty()) { + + // Note: FindBestStrip empties the candidate list, while BuildStrip refills it + const strip TriStrip = FindBestStrip(); + + assert(TriStrip.Size() <= 42); + if (TriStrip.Size() >= m_MinStripSize) + BuildStrip(TriStrip); + } + + if (! m_TriHeap.removed(HeapTop)) + m_TriHeap.erase(HeapTop); + + // Eliminate all the triangles that have now become useless + while ((! m_TriHeap.empty()) && (m_TriHeap.top() == 0)) + m_TriHeap.pop(); + } +} + + + +inline strip tri_stripper::FindBestStrip() +{ + // Allow to restore the cache (modified by ExtendTriToStrip) and implicitly reset the cache hit count + const cache_simulator CacheBackup = m_Cache; + + policy Policy(m_MinStripSize, Cache()); + + while (! m_Candidates.empty()) { + + const size_t Candidate = m_Candidates.back(); + m_Candidates.pop_back(); + + // Discard useless triangles from the candidate list + if ((m_Triangles[Candidate].marked()) || (m_TriHeap[Candidate] == 0)) + continue; + + // Try to extend the triangle in the 3 possible forward directions + for (size_t i = 0; i < 3; ++i) { + + const strip Strip = ExtendToStrip(Candidate, triangle_order(i)); + assert(Strip.Size() <= 42); + if (Strip.Size()) + Policy.Challenge(Strip, m_TriHeap[Strip.Start()], m_Cache.hitcount()); + + m_Cache = CacheBackup; + } + + // Try to extend the triangle in the 6 possible backward directions + if (m_BackwardSearch) { + + for (size_t i = 0; i < 3; ++i) { + + const strip Strip = BackExtendToStrip(Candidate, triangle_order(i), false); + assert(Strip.Size() <= 42); + if (Strip.Size()) + Policy.Challenge(Strip, m_TriHeap[Strip.Start()], m_Cache.hitcount()); + + m_Cache = CacheBackup; + } + + for (size_t i = 0; i < 3; ++i) { + + const strip Strip = BackExtendToStrip(Candidate, triangle_order(i), true); + assert(Strip.Size() <= 42); + if (Strip.Size()) + Policy.Challenge(Strip, m_TriHeap[Strip.Start()], m_Cache.hitcount()); + + m_Cache = CacheBackup; + } + } + + } + + return Policy.BestStrip(); +} + + + +strip tri_stripper::ExtendToStrip(const size_t Start, triangle_order Order) +{ + const triangle_order StartOrder = Order; + + // Begin a new strip + m_Triangles[Start]->SetStripID(++m_StripID); + AddTriangle(* m_Triangles[Start], Order, false); + + size_t Size = 1; + bool ClockWise = false; + + std::vector Indices; + Indices.push_back(Start); + + // Loop while we can further extend the strip + for (tri_iterator Node = (m_Triangles.begin() + Start); + (Node != m_Triangles.end()) && (!Cache() || ((Size + 2) < CacheSize())); + ++Size) { + + const const_link_iterator Link = LinkToNeighbour(Node, ClockWise, Order, false, 0); + + // Is it the end of the strip? + if (Link == Node->out_end() || Size >= 42) { + + Node = m_Triangles.end(); + --Size; + + } else { + + Node = Link->terminal(); + Indices.push_back(Node - m_Triangles.begin()); + (* Node)->SetStripID(m_StripID); + ClockWise = ! ClockWise; + + } + } + + return strip(Start, StartOrder, Size, Indices); +} + + + +strip tri_stripper::BackExtendToStrip(size_t Start, triangle_order Order, bool ClockWise) +{ + // Begin a new strip + m_Triangles[Start]->SetStripID(++m_StripID); + BackAddIndex(LastEdge(* m_Triangles[Start], Order).B()); + size_t Size = 1; + + tri_iterator Node; + + std::vector Indices; + Indices.push_back(Start); + + // Loop while we can further extend the strip + for (Node = (m_Triangles.begin() + Start); + !Cache() || ((Size + 2) < CacheSize()); + ++Size) { + + const const_link_iterator Link = BackLinkToNeighbour(Node, ClockWise, Order); + + // Is it the end of the strip? + if (Link == Node->out_end() || Size >= 42) + break; + + else { + Node = Link->terminal(); + // Indices.push_back(Node - m_Triangles.begin()); + Indices.insert(Indices.begin(), Node - m_Triangles.begin()); + (* Node)->SetStripID(m_StripID); + ClockWise = ! ClockWise; + } + } + + // We have to start from a counterclockwise triangle. + // Simply return an empty strip in the case where the first triangle is clockwise. + // Even though we could discard the first triangle and start from the next counterclockwise triangle, + // this often leads to more lonely triangles afterward. + if (ClockWise) + return strip(); + + if (Cache()) { + m_Cache.merge(m_BackCache, Size); + m_BackCache.reset(); + } + + return strip(Node - m_Triangles.begin(), Order, Size, Indices); +} + + + +void tri_stripper::BuildStrip(const strip Strip) +{ + const size_t Start = Strip.Start(); + + bool ClockWise = false; + triangle_order Order = Strip.Order(); + + // Create a new strip + m_PrimitivesVector.push_back(primitive_group()); + m_PrimitivesVector.back().Type = TRIANGLE_STRIP; + AddTriangle(* m_Triangles[Start], Order, true); + MarkTriAsTaken(Start); + assert(Strip.m_Indices[0] == Start); + + // Loop while we can further extend the strip + tri_iterator Node = (m_Triangles.begin() + Start); + + for (size_t Size = 1; Size < Strip.Size(); ++Size) { + + const const_link_iterator Link = LinkToNeighbour(Node, ClockWise, Order, true, Strip.m_Indices[Size]); + + assert(Link != Node->out_end()); + + // Go to the next triangle + Node = Link->terminal(); + assert(Strip.m_Indices[Size] == Node - m_Triangles.begin()); + MarkTriAsTaken(Node - m_Triangles.begin()); + ClockWise = ! ClockWise; + } +} + + + +inline tri_stripper::const_link_iterator tri_stripper::LinkToNeighbour(const const_tri_iterator Node, const bool ClockWise, triangle_order & Order, const bool NotSimulation, size_t triIndex) +{ + const triangle_edge Edge = LastEdge(** Node, Order); + + for (const_link_iterator Link = Node->out_begin(); Link != Node->out_end(); ++Link) { + + // Get the reference to the possible next triangle + size_t gottenIndex = Link->terminal() - m_Triangles.begin(); + const triangle & Tri = ** Link->terminal(); + + // Check whether it's already been used + if (NotSimulation || (Tri.StripID() != m_StripID)) { + if (NotSimulation && gottenIndex != triIndex) { + continue; + } + if (! Link->terminal()->marked()) { + + // Does the current candidate triangle match the required position for the strip? + + if ((Edge.B() == Tri.A()) && (Edge.A() == Tri.B())) { + Order = (ClockWise) ? ABC : BCA; + AddIndex(Tri.C(), NotSimulation); + return Link; + } + + else if ((Edge.B() == Tri.B()) && (Edge.A() == Tri.C())) { + Order = (ClockWise) ? BCA : CAB; + AddIndex(Tri.A(), NotSimulation); + return Link; + } + + else if ((Edge.B() == Tri.C()) && (Edge.A() == Tri.A())) { + Order = (ClockWise) ? CAB : ABC; + AddIndex(Tri.B(), NotSimulation); + return Link; + } + } + } + + } + + return Node->out_end(); +} + + + +inline tri_stripper::const_link_iterator tri_stripper::BackLinkToNeighbour(const_tri_iterator Node, bool ClockWise, triangle_order & Order) +{ + const triangle_edge Edge = FirstEdge(** Node, Order); + + for (const_link_iterator Link = Node->out_begin(); Link != Node->out_end(); ++Link) { + + // Get the reference to the possible previous triangle + const triangle & Tri = ** Link->terminal(); + + // Check whether it's already been used + if ((Tri.StripID() != m_StripID) && ! Link->terminal()->marked()) { + + // Does the current candidate triangle match the required position for the strip? + + if ((Edge.B() == Tri.A()) && (Edge.A() == Tri.B())) { + Order = (ClockWise) ? CAB : BCA; + BackAddIndex(Tri.C()); + return Link; + } + + else if ((Edge.B() == Tri.B()) && (Edge.A() == Tri.C())) { + Order = (ClockWise) ? ABC : CAB; + BackAddIndex(Tri.A()); + return Link; + } + + else if ((Edge.B() == Tri.C()) && (Edge.A() == Tri.A())) { + Order = (ClockWise) ? BCA : ABC; + BackAddIndex(Tri.B()); + return Link; + } + } + + } + + return Node->out_end(); +} + + + +void tri_stripper::MarkTriAsTaken(const size_t i) +{ + typedef triangle_graph::node_iterator tri_node_iter; + typedef triangle_graph::out_arc_iterator tri_link_iter; + + // Mark the triangle node + m_Triangles[i].mark(); + + // Remove triangle from priority queue if it isn't yet + if (! m_TriHeap.removed(i)) + m_TriHeap.erase(i); + + // Adjust the degree of available neighbour triangles + for (tri_link_iter Link = m_Triangles[i].out_begin(); Link != m_Triangles[i].out_end(); ++Link) { + + const size_t j = Link->terminal() - m_Triangles.begin(); + + if ((! m_Triangles[j].marked()) && (! m_TriHeap.removed(j))) { + size_t NewDegree = m_TriHeap.peek(j); + NewDegree = NewDegree - 1; + m_TriHeap.update(j, NewDegree); + + // Update the candidate list if cache is enabled + if (Cache() && (NewDegree > 0)) + m_Candidates.push_back(j); + } + } +} + + + +inline triangle_edge tri_stripper::FirstEdge(const triangle & Triangle, const triangle_order Order) +{ + switch (Order) + { + case ABC: + return triangle_edge(Triangle.A(), Triangle.B()); + + case BCA: + return triangle_edge(Triangle.B(), Triangle.C()); + + case CAB: + return triangle_edge(Triangle.C(), Triangle.A()); + + default: + assert(false); + return triangle_edge(0, 0); + } +} + + + +inline triangle_edge tri_stripper::LastEdge(const triangle & Triangle, const triangle_order Order) +{ + switch (Order) + { + case ABC: + return triangle_edge(Triangle.B(), Triangle.C()); + + case BCA: + return triangle_edge(Triangle.C(), Triangle.A()); + + case CAB: + return triangle_edge(Triangle.A(), Triangle.B()); + + default: + assert(false); + return triangle_edge(0, 0); + } +} + + + +inline void tri_stripper::AddIndex(const index i, const bool NotSimulation) +{ + if (Cache()) + m_Cache.push(i, ! NotSimulation); + + if (NotSimulation) + m_PrimitivesVector.back().Indices.push_back(i); +} + + + +inline void tri_stripper::BackAddIndex(const index i) +{ + if (Cache()) + m_BackCache.push(i, true); +} + + + +inline void tri_stripper::AddTriangle(const triangle & Tri, const triangle_order Order, const bool NotSimulation) +{ + switch (Order) + { + case ABC: + AddIndex(Tri.A(), NotSimulation); + AddIndex(Tri.B(), NotSimulation); + AddIndex(Tri.C(), NotSimulation); + break; + + case BCA: + AddIndex(Tri.B(), NotSimulation); + AddIndex(Tri.C(), NotSimulation); + AddIndex(Tri.A(), NotSimulation); + break; + + case CAB: + AddIndex(Tri.C(), NotSimulation); + AddIndex(Tri.A(), NotSimulation); + AddIndex(Tri.B(), NotSimulation); + break; + } +} + + + +inline void tri_stripper::BackAddTriangle(const triangle & Tri, const triangle_order Order) +{ + switch (Order) + { + case ABC: + BackAddIndex(Tri.C()); + BackAddIndex(Tri.B()); + BackAddIndex(Tri.A()); + break; + + case BCA: + BackAddIndex(Tri.A()); + BackAddIndex(Tri.C()); + BackAddIndex(Tri.B()); + break; + + case CAB: + BackAddIndex(Tri.B()); + BackAddIndex(Tri.A()); + BackAddIndex(Tri.C()); + break; + } +} + + + +void tri_stripper::AddLeftTriangles() +{ + size_t start_tri = 0; + while(start_tri != m_Triangles.size()) { + // Create the last indices array and fill it with all the triangles that couldn't be stripped + primitive_group Primitives; + Primitives.Type = TRIANGLES; + m_PrimitivesVector.push_back(Primitives); + indices & Indices = m_PrimitivesVector.back().Indices; + std::set UniqIndices; + for (size_t i = start_tri; i < m_Triangles.size(); ++i) { + if (! m_Triangles[i].marked()) { + Indices.push_back(m_Triangles[i]->A()); + UniqIndices.insert(m_Triangles[i]->A()); + Indices.push_back(m_Triangles[i]->B()); + UniqIndices.insert(m_Triangles[i]->B()); + Indices.push_back(m_Triangles[i]->C()); + UniqIndices.insert(m_Triangles[i]->C()); + } + + start_tri = i + 1; + if (UniqIndices.size() >= 3) { + break; + } + } + + // Undo if useless + if (Indices.size() == 0) + m_PrimitivesVector.pop_back(); + } +} + + + +inline bool tri_stripper::Cache() const +{ + return (m_Cache.size() != 0); +} + + + +inline size_t tri_stripper::CacheSize() const +{ + return m_Cache.size(); +} + + + + +} // namespace triangle_stripper diff --git a/vendor/crypto/sha256.h b/vendor/crypto/sha256.h new file mode 100644 index 00000000..237681dd --- /dev/null +++ b/vendor/crypto/sha256.h @@ -0,0 +1,233 @@ +/////////////////////////////////////////////////////////////////// +// // +// Copyright Iliass Mahjoub 2022 - 2023. // +// Distributed under the Boost Software License, // +// Version 1.0. (See accompanying file LICENSE_1_0.txt // +// or copy at http://www.boost.org/LICENSE_1_0.txt) // +// // +/////////////////////////////////////////////////////////////////// + +#ifndef HASH_SHA256_2022_06_02_H + #define HASH_SHA256_2022_06_02_H + + #include + #include + #include + + using sha256_type = std::array; + + class hash_sha256 + { + public: + hash_sha256() = default; + hash_sha256(const hash_sha256&) = delete; + hash_sha256(hash_sha256&&) = delete; + virtual ~hash_sha256() = default; // LCOV_EXCL_LINE + + auto operator=(hash_sha256&&) -> hash_sha256& = delete; + auto operator=(const hash_sha256&) -> hash_sha256& = delete; + + auto sha256_init() -> void + { + datalen = 0U; + bitlen = 0U; + + init_hash_val[0U] = UINT32_C(0x6A09E667); + init_hash_val[1U] = UINT32_C(0xBB67AE85); + init_hash_val[2U] = UINT32_C(0x3C6EF372); + init_hash_val[3U] = UINT32_C(0xA54FF53A); + init_hash_val[4U] = UINT32_C(0x510E527F); + init_hash_val[5U] = UINT32_C(0x9B05688C); + init_hash_val[6U] = UINT32_C(0x1F83D9AB); + init_hash_val[7U] = UINT32_C(0x5BE0CD19); + } + + auto sha256_update(const std::uint8_t* msg, const size_t length) -> void + { + for (std::size_t i = 0U; i < length; ++i) + { + data[datalen] = msg[i]; + datalen++; + + if(datalen == 64U) + { + sha256_transform(); + datalen = 0U; + bitlen += 512U; + } + } + } + + auto sha256_final() -> sha256_type + { + std::size_t i = 0U; + sha256_type hash_result = {0U}; + i = datalen; + + // Pad whatever data is left in the buffer. + if(datalen < 56U) + { + data[i++] = 0x80U; + std::fill((data.begin() + i), (data.begin() + 56U), 0U); + } + + else + { + data[i++] = 0x80U; + std::fill((data.begin() + i), data.end(), 0U); + sha256_transform(); + std::fill_n(data.begin(), 56U, 0U); + } + + // Append to the padding the total message's length in bits and transform. + bitlen += static_cast(datalen * UINT8_C(8)); + + data[63U] = static_cast(bitlen >> UINT8_C( 0)); + data[62U] = static_cast(bitlen >> UINT8_C( 8)); + data[61U] = static_cast(bitlen >> UINT8_C(16)); + data[60U] = static_cast(bitlen >> UINT8_C(24)); + data[59U] = static_cast(bitlen >> UINT8_C(32)); + data[58U] = static_cast(bitlen >> UINT8_C(40)); + data[57U] = static_cast(bitlen >> UINT8_C(48)); + data[56U] = static_cast(bitlen >> UINT8_C(56)); + + sha256_transform(); + + // Since this implementation uses little endian byte ordering and SHA uses big endian, + // reverse all the bytes when copying the final init_hash_val to the output hash. + for(std::size_t j = 0U; j < 4U; ++j) + { + hash_result[j + 0U] = ((init_hash_val[0U] >> (24U - (j * 8U))) & UINT32_C(0x000000FF)); + hash_result[j + 4U] = ((init_hash_val[1U] >> (24U - (j * 8U))) & UINT32_C(0x000000FF)); + hash_result[j + 8U] = ((init_hash_val[2U] >> (24U - (j * 8U))) & UINT32_C(0x000000FF)); + hash_result[j + 12U] = ((init_hash_val[3U] >> (24U - (j * 8U))) & UINT32_C(0x000000FF)); + hash_result[j + 16U] = ((init_hash_val[4U] >> (24U - (j * 8U))) & UINT32_C(0x000000FF)); + hash_result[j + 20U] = ((init_hash_val[5U] >> (24U - (j * 8U))) & UINT32_C(0x000000FF)); + hash_result[j + 24U] = ((init_hash_val[6U] >> (24U - (j * 8U))) & UINT32_C(0x000000FF)); + hash_result[j + 28U] = ((init_hash_val[7U] >> (24U - (j * 8U))) & UINT32_C(0x000000FF)); + } + + return hash_result; + } + + private: + std::uint32_t datalen; + std::uint64_t bitlen; + std::array data; + std::array init_hash_val; + + static constexpr std::array K = + { + UINT32_C(0x428A2F98), UINT32_C(0x71374491), UINT32_C(0xB5C0FBCF), UINT32_C(0xE9B5DBA5), + UINT32_C(0x3956C25B), UINT32_C(0x59F111F1), UINT32_C(0x923F82A4), UINT32_C(0xAB1C5ED5), + UINT32_C(0xD807AA98), UINT32_C(0x12835B01), UINT32_C(0x243185BE), UINT32_C(0x550C7DC3), + UINT32_C(0x72BE5D74), UINT32_C(0x80DEB1FE), UINT32_C(0x9BDC06A7), UINT32_C(0xC19BF174), + UINT32_C(0xE49B69C1), UINT32_C(0xEFBE4786), UINT32_C(0x0FC19DC6), UINT32_C(0x240CA1CC), + UINT32_C(0x2DE92C6F), UINT32_C(0x4A7484AA), UINT32_C(0x5CB0A9DC), UINT32_C(0x76F988DA), + UINT32_C(0x983E5152), UINT32_C(0xA831C66D), UINT32_C(0xB00327C8), UINT32_C(0xBF597FC7), + UINT32_C(0xC6E00BF3), UINT32_C(0xD5A79147), UINT32_C(0x06CA6351), UINT32_C(0x14292967), + UINT32_C(0x27B70A85), UINT32_C(0x2E1B2138), UINT32_C(0x4D2C6DFC), UINT32_C(0x53380D13), + UINT32_C(0x650A7354), UINT32_C(0x766A0ABB), UINT32_C(0x81C2C92E), UINT32_C(0x92722C85), + UINT32_C(0xA2BFE8A1), UINT32_C(0xA81A664B), UINT32_C(0xC24B8B70), UINT32_C(0xC76C51A3), + UINT32_C(0xD192E819), UINT32_C(0xD6990624), UINT32_C(0xF40E3585), UINT32_C(0x106AA070), + UINT32_C(0x19A4C116), UINT32_C(0x1E376C08), UINT32_C(0x2748774C), UINT32_C(0x34B0BCB5), + UINT32_C(0x391C0CB3), UINT32_C(0x4ED8AA4A), UINT32_C(0x5B9CCA4F), UINT32_C(0x682E6FF3), + UINT32_C(0x748F82EE), UINT32_C(0x78A5636F), UINT32_C(0x84C87814), UINT32_C(0x8CC70208), + UINT32_C(0x90BEFFFA), UINT32_C(0xA4506CEB), UINT32_C(0xBEF9A3F7), UINT32_C(0xC67178F2) + }; + + auto sha256_transform() -> void + { + std::uint32_t tmp1 = 0U; + std::uint32_t tmp2 = 0U; + + std::array state = {0U}; + std::array m = {0U}; + + for(std::size_t i = 0U, j = 0U; i < 16U; ++i, j += 4U) + { + m[i] = static_cast + ( + static_cast(static_cast(data[j + 0U]) << 24U) + | static_cast(static_cast(data[j + 1U]) << 16U) + | static_cast(static_cast(data[j + 2U]) << 8U) + | static_cast(static_cast(data[j + 3U]) << 0U) + ); + } + + for(std::size_t i = 16U ; i < 64U; ++i) + { + m[i] = ssig1(m[i - 2U]) + m[i - 7U] + ssig0(m[i - 15U]) + m[i - 16U]; + } + + std::copy(init_hash_val.begin(), init_hash_val.end() , state.begin()); + + for(std::size_t i = 0U; i < 64U; ++i) + { + tmp1 = state[7U] + bsig1(state[4U]) + ch(state[4U], state[5U], state[6U]) + K[i] + m[i]; + + tmp2 = bsig0(state[0U]) + maj(state[0U], state[1U], state[2U]); + + state[7U] = state[6U]; + state[6U] = state[5U]; + state[5U] = state[4U]; + state[4U] = state[3U] + tmp1; + state[3U] = state[2U]; + state[2U] = state[1U]; + state[1U] = state[0U]; + state[0U] = tmp1 + tmp2; + } + + init_hash_val[0U] += state[0U]; + init_hash_val[1U] += state[1U]; + init_hash_val[2U] += state[2U]; + init_hash_val[3U] += state[3U]; + init_hash_val[4U] += state[4U]; + init_hash_val[5U] += state[5U]; + init_hash_val[6U] += state[6U]; + init_hash_val[7U] += state[7U]; + } + + // circular left shift ROTR^n(x) + static inline auto rotl(std::uint32_t a, std::uint32_t b) -> std::uint32_t + { + return (static_cast(a << b) | static_cast(a >> (32U - b))); + } + + // circular right shift ROTR^n(x) + static inline auto rotr(std::uint32_t a, std::uint32_t b) -> std::uint32_t + { + return (static_cast(a >> b) | static_cast(a << (32U - b))); + } + + static inline auto ch(std::uint32_t x, std::uint32_t y, std::uint32_t z) -> std::uint32_t + { + return (static_cast(x & y) ^ static_cast(~x & z)); + } + + static inline auto maj(std::uint32_t x, std::uint32_t y, std::uint32_t z) -> std::uint32_t + { + return (static_cast(x & y) ^ static_cast(x & z) ^ static_cast(y & z)); + } + + static inline auto bsig0(std::uint32_t x) -> std::uint32_t + { + return (rotr(x, 2U) ^ rotr(x, 13U) ^ rotr(x, 22U)); + } + + static inline auto bsig1(std::uint32_t x) -> std::uint32_t + { + return (rotr(x, 6U) ^ rotr(x, 11U) ^ rotr(x, 25U)); + } + + static inline auto ssig0(std::uint32_t x) -> std::uint32_t + { + return (rotr(x, 7U) ^ rotr(x, 18U) ^ (x >> 3U)); + } + + static inline auto ssig1(std::uint32_t x) -> std::uint32_t + { + return (rotr(x, 17U) ^ rotr(x, 19U) ^ (x >> 10U)); + } + }; +#endif // HASH_SHA256_2022_06_02_H \ No newline at end of file diff --git a/vendor/dca3-kos b/vendor/dca3-kos new file mode 160000 index 00000000..0b2de922 --- /dev/null +++ b/vendor/dca3-kos @@ -0,0 +1 @@ +Subproject commit 0b2de9228be7debb7f5d37ecf6341a9a78bbf3af diff --git a/vendor/emu/emu/emu.h b/vendor/emu/emu/emu.h new file mode 100644 index 00000000..7a245cf2 --- /dev/null +++ b/vendor/emu/emu/emu.h @@ -0,0 +1,10 @@ +#pragma once + +#include "dc/pvr.h" + +extern uint8_t emu_vram[PVR_RAM_SIZE]; + +void emu_init(); +void emu_term(); +void emu_pump_events(); +void pvr_queue_interrupt(int interrupt); \ No newline at end of file diff --git a/vendor/emu/emu/types.h b/vendor/emu/emu/types.h new file mode 100644 index 00000000..91fa43dc --- /dev/null +++ b/vendor/emu/emu/types.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +typedef int8_t s8; +typedef uint8_t u8; +typedef int16_t s16; +typedef uint16_t u16; +typedef int32_t s32; +typedef uint32_t u32; +typedef int64_t s64; +typedef uint64_t u64; +typedef float f32; +typedef double f64; + +#define verify assert + +#define die(...) assert(false && __VA_ARGS__) \ No newline at end of file diff --git a/vendor/emu/emu/window.cpp b/vendor/emu/emu/window.cpp new file mode 100644 index 00000000..3893f553 --- /dev/null +++ b/vendor/emu/emu/window.cpp @@ -0,0 +1,241 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dc/maple/controller.h" + +void * x11_disp; +void * x11_vis; +void * x11_win; + +int x11_width; +int x11_height; + +bool x11_fullscreen = false; +Atom wmDeleteMessage; + +extern cont_state_t mapleInput; +void x11_window_set_text(const char* text) +{ + if (x11_win) + { + XChangeProperty((Display*)x11_disp, (Window)x11_win, + XInternAtom((Display*)x11_disp, "WM_NAME", False), //WM_NAME, + XInternAtom((Display*)x11_disp, "UTF8_STRING", False), //UTF8_STRING, + 8, PropModeReplace, (const unsigned char *)text, strlen(text)); + } +} + +void x11_window_create() +{ + XInitThreads(); + // X11 variables + Window x11Window = 0; + Display* x11Display = 0; + long x11Screen = 0; + XVisualInfo* x11Visual = 0; + Colormap x11Colormap = 0; + + /* + Step 0 - Create a NativeWindowType that we can use it for OpenGL ES output + */ + Window sRootWindow; + XSetWindowAttributes sWA; + unsigned int ui32Mask; + int i32Depth; + + // Initializes the display and screen + x11Display = XOpenDisplay(NULL); + if (!x11Display && !(x11Display = XOpenDisplay(":0"))) + { + assert(false && "Error: Unable to open X display\n"); + return; + } + x11Screen = XDefaultScreen(x11Display); + float xdpi = (float)DisplayWidth(x11Display, x11Screen) / DisplayWidthMM(x11Display, x11Screen) * 25.4; + float ydpi = (float)DisplayHeight(x11Display, x11Screen) / DisplayHeightMM(x11Display, x11Screen) * 25.4; + auto screen_dpi = fmax(xdpi, ydpi); + + // Gets the window parameters + sRootWindow = RootWindow(x11Display, x11Screen); + + int depth = CopyFromParent; + + i32Depth = DefaultDepth(x11Display, x11Screen); + x11Visual = new XVisualInfo; + XMatchVisualInfo(x11Display, x11Screen, i32Depth, TrueColor, x11Visual); + if (!x11Visual) + { + printf("Error: Unable to acquire visual\n"); + return; + } + + x11Colormap = XCreateColormap(x11Display, sRootWindow, x11Visual->visual, AllocNone); + + sWA.colormap = x11Colormap; + + // Add to these for handling other events + sWA.event_mask = StructureNotifyMask | ExposureMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask; + sWA.event_mask |= PointerMotionMask | FocusChangeMask; + ui32Mask = CWBackPixel | CWBorderPixel | CWEventMask | CWColormap; + + x11_width = 640; + x11_height = 480; + x11_fullscreen = false; + + if (x11_width < 0 || x11_height < 0) + { + x11_width = XDisplayWidth(x11Display, x11Screen); + x11_height = XDisplayHeight(x11Display, x11Screen); + } + + // Creates the X11 window + x11Window = XCreateWindow(x11Display, RootWindow(x11Display, x11Screen), 640, 480, x11_width, x11_height, + 0, depth, InputOutput, x11Visual->visual, ui32Mask, &sWA); + + // Capture the close window event + wmDeleteMessage = XInternAtom(x11Display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(x11Display, x11Window, &wmDeleteMessage, 1); + + if(x11_fullscreen) + { + + // fullscreen + Atom wmState = XInternAtom(x11Display, "_NET_WM_STATE", False); + Atom wmFullscreen = XInternAtom(x11Display, "_NET_WM_STATE_FULLSCREEN", False); + XChangeProperty(x11Display, x11Window, wmState, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wmFullscreen, 1); + + XMapRaised(x11Display, x11Window); + } + else + { + XMapWindow(x11Display, x11Window); + } + + XFlush(x11Display); + + //(EGLNativeDisplayType)x11Display; + x11_disp = (void*)x11Display; + x11_win = (void*)x11Window; + x11_vis = (void*)x11Visual->visual; + + x11_window_set_text("GTA3dc"); +} + +void x11_window_destroy() +{ + // close XWindow + if (x11_win) + { + XDestroyWindow((Display*)x11_disp, (Window)x11_win); + x11_win = NULL; + } + if (x11_disp) + { + XCloseDisplay((Display*)x11_disp); + x11_disp = NULL; + } +} + +static std::map keymap = { + {XK_Left, CONT_DPAD_LEFT}, + {XK_Right, CONT_DPAD_RIGHT}, + {XK_Up, CONT_DPAD_UP}, + {XK_Down, CONT_DPAD_DOWN}, + + {XK_Return, CONT_START}, + + {XK_z, CONT_Y}, + {XK_x, CONT_X}, + {XK_c, CONT_B}, + {XK_v, CONT_A}, + + {XK_i, -1}, + {XK_k, -2}, + {XK_j, -3}, + {XK_l, -4}, + {XK_a, -5}, + {XK_s, -6}, +}; + +void event_x11_handle() +{ + XEvent event; + + while(XPending((Display *)x11_disp)) + { + XNextEvent((Display *)x11_disp, &event); + if (event.type == KeyPress || event.type == KeyRelease) { + KeySym keysym = XLookupKeysym(&event.xkey, 0); + + auto dckey = keymap.find(keysym); + if (dckey != keymap.end()) { + switch(dckey->second) { + case -1: + mapleInput.joyy = event.type == KeyPress ? -128 : 0; + break; + case -2: + mapleInput.joyy = event.type == KeyPress ? 128 : 0; + break; + case -3: + mapleInput.joyx = event.type == KeyPress ? -128 : 0; + break; + case -4: + mapleInput.joyx = event.type == KeyPress ? 128 : 0; + break; + case -5: + mapleInput.ltrig = event.type == KeyPress ? 255 : 0; + break; + case -6: + mapleInput.rtrig = event.type == KeyPress ? 255 : 0; + break; + default: + if (event.type == KeyPress) { + mapleInput.buttons |= dckey->second; + } else { + mapleInput.buttons &= ~dckey->second; + } + break; + } + } + } + // if (event.type == ClientMessage && + // event.xclient.data.l[0] == wmDeleteMessage) + // { + // if (virtualDreamcast && sh4_cpu->IsRunning()) { + // virtualDreamcast->Stop([] { + // g_GUIRenderer->Stop(); + // }); + // } + // else + // { + // g_GUIRenderer->Stop(); + // } + // } + // else if (event.type == ConfigureNotify) + // { + // x11_width = event.xconfigure.width; + // x11_height = event.xconfigure.height; + // } + } +} + +void pvrInit(); + +void emu_init() { + x11_window_create(); + pvrInit(); +} + +void emu_pump_events() { + event_x11_handle(); +} +void emu_term() { + x11_window_destroy(); +} \ No newline at end of file diff --git a/vendor/emu/license/bsd b/vendor/emu/license/bsd new file mode 100644 index 00000000..9a6b96b9 --- /dev/null +++ b/vendor/emu/license/bsd @@ -0,0 +1 @@ +// reicast's BSD license \ No newline at end of file diff --git a/vendor/emu/license/gpl b/vendor/emu/license/gpl new file mode 100644 index 00000000..507776ee --- /dev/null +++ b/vendor/emu/license/gpl @@ -0,0 +1 @@ +// lxdream's GPL license \ No newline at end of file diff --git a/vendor/emu/lxdream/tacore.cpp b/vendor/emu/lxdream/tacore.cpp new file mode 100644 index 00000000..d3b82efe --- /dev/null +++ b/vendor/emu/lxdream/tacore.cpp @@ -0,0 +1,1273 @@ +/* + Derived from lxdream, original copyright notice follows +*/ +#include + +/** + * $Id$ + * + * PVR2 Tile Accelerator implementation + * + * Copyright (c) 2005 Nathan Keynes. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include + +#include "tacore.h" +#include "refsw/pvr_regs.h" +#include "refsw/pvr_mem.h" + +#define PVR2_RAM_SIZE VRAM_SIZE +#define PVR2_RAM_MASK VRAM_MASK + +u8* pvr2_main_ram_hidden; +#define PVRRAM(addr) (*(uint32_t *)(pvr2_main_ram_hidden + (pvr_map32(addr)))) + +/* +#include "lxdream.h" +#include "pvr2/pvr2.h" +#include "pvr2/pvr2mmio.h" +#include "asic.h" +#include "dream.h" +*/ + +#define CLAMP(v, low, high) (v < low? low : (v > high? high: v)) +#define MIN(a, b) (a < b ? a : b) +#define MAX(a, b) (a > b ? a : b) + +#define TRUE true +#define FALSE false + +#define SEGMENT_END 0x80000000 +#define SEGMENT_ZCLEAR 0x40000000 +#define SEGMENT_SORT_TRANS 0x20000000 +#define SEGMENT_START 0x10000000 +#define SEGMENT_X(c) (((c) >> 2) & 0x3F) +#define SEGMENT_Y(c) (((c) >> 8) & 0x3F) +#define NO_POINTER 0x80000000 +#define IS_TILE_PTR(p) ( ((p)&NO_POINTER) == 0 ) +#define IS_LAST_SEGMENT(s) (((s)->control) & SEGMENT_END) + +struct tile_segment { + uint32_t control; + pvraddr_t opaque_ptr; + pvraddr_t opaquemod_ptr; + pvraddr_t trans_ptr; + pvraddr_t transmod_ptr; + pvraddr_t punchout_ptr; +}; + + +struct tile_bounds { + int32_t x1, y1, x2, y2; +}; + + +#define STATE_IDLE 0 +#define STATE_IN_LIST 1 +#define STATE_IN_POLYGON 2 +#define STATE_EXPECT_POLY_BLOCK2 3 +#define STATE_EXPECT_VERTEX_BLOCK2 4 +#define STATE_ERROR 5 +#define STATE_EXPECT_END_VERTEX_BLOCK2 7 + +#define TA_CMD(i) ( (i) >> 29 ) +#define TA_CMD_END_LIST 0 +#define TA_CMD_CLIP 1 +#define TA_CMD_POLYGON_CONTEXT 4 +#define TA_CMD_SPRITE_CONTEXT 5 +#define TA_CMD_VERTEX 7 + +#define TA_LIST_NONE -1 +#define TA_LIST_OPAQUE 0 +#define TA_LIST_OPAQUE_MOD 1 +#define TA_LIST_TRANS 2 +#define TA_LIST_TRANS_MOD 3 +#define TA_LIST_PUNCH_OUT 4 +#define TA_IS_MODIFIER_LIST(list) (list == TA_LIST_OPAQUE_MOD || list == TA_LIST_TRANS_MOD) + +#define TA_GROW_UP 0 +#define TA_GROW_DOWN 1 + +#define TA_VERTEX_NONE -1 +#define TA_VERTEX_PACKED 0x00 +#define TA_VERTEX_TEX_PACKED 0x08 +#define TA_VERTEX_TEX_SPEC_PACKED 0x0C +#define TA_VERTEX_TEX_UV16_PACKED 0x09 +#define TA_VERTEX_TEX_UV16_SPEC_PACKED 0x0D +#define TA_VERTEX_FLOAT 0x10 +#define TA_VERTEX_TEX_FLOAT 0x18 +#define TA_VERTEX_TEX_SPEC_FLOAT 0x1C +#define TA_VERTEX_TEX_UV16_FLOAT 0x19 +#define TA_VERTEX_TEX_UV16_SPEC_FLOAT 0x1D +#define TA_VERTEX_INTENSITY 0x20 +#define TA_VERTEX_TEX_INTENSITY 0x28 +#define TA_VERTEX_TEX_SPEC_INTENSITY 0x2C +#define TA_VERTEX_TEX_UV16_INTENSITY 0x29 +#define TA_VERTEX_TEX_UV16_SPEC_INTENSITY 0x2D +#define TA_VERTEX_PACKED_MOD 0x40 +#define TA_VERTEX_TEX_PACKED_MOD 0x48 +#define TA_VERTEX_TEX_SPEC_PACKED_MOD 0x4C +#define TA_VERTEX_TEX_UV16_PACKED_MOD 0x49 +#define TA_VERTEX_TEX_UV16_SPEC_PACKED_MOD 0x4D +#define TA_VERTEX_INTENSITY_MOD 0x60 +#define TA_VERTEX_TEX_INTENSITY_MOD 0x68 +#define TA_VERTEX_TEX_SPEC_INTENSITY_MOD 0x6C +#define TA_VERTEX_TEX_UV16_INTENSITY_MOD 0x69 +#define TA_VERTEX_TEX_UV16_SPEC_INTENSITY_MOD 0x6D +#define TA_VERTEX_SPRITE 0x80 +#define TA_VERTEX_TEX_SPRITE 0x88 +#define TA_VERTEX_MOD_VOLUME 0x81 +#define TA_VERTEX_LISTLESS 0xFF + +#define TA_IS_NORMAL_POLY() (ta_status.current_vertex_type < TA_VERTEX_SPRITE) + +static int strip_lengths[4] = {3,4,6,8}; /* in vertexes */ +#define TA_POLYCMD_LISTTYPE(i) ( ((i) >> 24) & 0x0F ) +#define TA_POLYCMD_USELENGTH(i) ( i & 0x00800000 ) +#define TA_POLYCMD_LENGTH(i) strip_lengths[((i >> 18) & 0x03)] +#define TA_POLYCMD_CLIP(i) ((i>>16)&0x03) +#define TA_POLYCMD_CLIP_NONE 0 +#define TA_POLYCMD_CLIP_INSIDE 2 +#define TA_POLYCMD_CLIP_OUTSIDE 3 +#define TA_POLYCMD_COLOURFMT(i) (i & 0x00000030) +#define TA_POLYCMD_COLOURFMT_ARGB32 0x00000000 +#define TA_POLYCMD_COLOURFMT_FLOAT 0x00000010 +#define TA_POLYCMD_COLOURFMT_INTENSITY 0x00000020 +#define TA_POLYCMD_COLOURFMT_LASTINT 0x00000030 + +#define TA_POLYCMD_MODIFIED 0x00000080 +#define TA_POLYCMD_FULLMOD 0x00000040 +#define TA_POLYCMD_TEXTURED 0x00000008 +#define TA_POLYCMD_SPECULAR 0x00000004 +#define TA_POLYCMD_SHADED 0x00000002 +#define TA_POLYCMD_UV16 0x00000001 + +#define TA_POLYCMD_IS_SPECULAR(i) ((i & 0x0000000C)==0x0000000C) /* Only applies to textured polys */ +#define TA_POLYCMD_IS_FULLMOD(i) ((i & 0x000000C0)==0x000000C0) + + +#define TA_IS_END_VERTEX(i) (i & 0x10000000) + +/** Note these are not the IEEE 754 definitions - the TA treats NANs + * as if they were INFs of the appropriate sign. + */ +#define TA_IS_INF(f) (((*((uint32_t *)&f)) & 0xFF800000) == 0x7F800000) +#define TA_IS_NINF(f) (((*((uint32_t *)&f)) & 0xFF800000) == 0xFF800000) + +#define MIN3( x1, x2, x3 ) ( (x1)<(x2)? ((x1)<(x3)?(x1):(x3)) : ((x2)<(x3)?(x2):(x3)) ) +#define MAX3( x1, x2, x3 ) ( (x1)>(x2)? ((x1)>(x3)?(x1):(x3)) : ((x2)>(x3)?(x2):(x3)) ) + +#define TILESLOT( x, y ) (ta_status.current_tile_matrix + (ta_status.current_tile_size * (y * ta_status.width+ x) << 2)) + +struct pvr2_ta_vertex { + float x,y,z; + uint32_t detail[8]; /* 0-8 detail words */ +}; + +struct pvr2_ta_status { + int32_t state; + int32_t width, height; /* Tile resolution, ie 20x15 */ + int32_t tilelist_dir; /* Growth direction of the tilelist, 0 = up, 1 = down */ + uint32_t tilelist_size; /* Size of the tilelist segments */ + uint32_t tilelist_start; /* Initial address of the tilelist */ + uint32_t polybuf_start; /* Initial bank address of the polygon buffer (ie &0x00F00000) */ + int32_t current_vertex_type; + uint32_t accept_vertexes; /* 0 = NO, 1 = YES */ + int32_t vertex_count; /* index of last start-vertex seen, or -1 if no vertexes + * are present + */ + uint32_t max_vertex; /* Maximum number of vertexes in the current polygon (3/4/6/8) */ + uint32_t current_list_type; + uint32_t current_tile_matrix; /* Memory location of the first tile for the current list. */ + uint32_t current_tile_size; /* Size of the tile matrix space in 32-bit words (0/8/16/32)*/ + uint32_t intensity1, intensity2; + struct tile_bounds clip; + int32_t clip_mode; + /** + * Current working object + */ + int32_t poly_context_size; + int32_t poly_vertex_size; + int32_t poly_parity; + uint32_t poly_context[5]; + uint32_t poly_pointer; + struct tile_bounds last_triangle_bounds; + struct pvr2_ta_vertex poly_vertex[8]; + uint32_t debug_output; +}; + +static struct pvr2_ta_status ta_status; + +static int tilematrix_sizes[4] = {0,8,16,32}; + +/** + * Convenience union - ta data is either 32-bit integer or 32-bit float. + */ +union ta_data { + unsigned int i; + float f; +}; + + +void lxd_ta_reset() { + ta_status.state = STATE_ERROR; /* State not valid until initialized */ + ta_status.debug_output = 0; +} + +void lxd_ta_init(u8* vram) { + pvr2_main_ram_hidden = vram; + + ta_status.state = STATE_IDLE; + ta_status.current_list_type = -1; + ta_status.current_vertex_type = -1; + ta_status.poly_parity = 0; + ta_status.vertex_count = 0; + ta_status.max_vertex = 3; + ta_status.current_vertex_type = TA_VERTEX_LISTLESS; + ta_status.poly_vertex_size = 0; + ta_status.poly_context[1] = 0; + ta_status.last_triangle_bounds.x1 = -1; + ta_status.accept_vertexes = TRUE; + ta_status.clip.x1 = 0; + ta_status.clip.y1 = 0; + ta_status.clip_mode = TA_POLYCMD_CLIP_NONE; + + uint32_t size = TA_GLOB_TILE_CLIP.full;// MMIO_READ( PVR2, TA_TILESIZE ); + ta_status.width = (size & 0xFFFF) + 1; + ta_status.height = (size >> 16) + 1; + ta_status.clip.x2 = ta_status.width-1; + ta_status.clip.y2 = ta_status.height-1; + uint32_t control = TA_ALLOC_CTRL; //MMIO_READ( PVR2, TA_TILECFG ); + ta_status.tilelist_dir = (control >> 20) & 0x01; + ta_status.tilelist_size = tilematrix_sizes[ (control & 0x03) ]; + TA_ISP_CURRENT = TA_ISP_BASE; + //MMIO_WRITE( PVR2, TA_POLYPOS, MMIO_READ( PVR2, TA_POLYBASE ) ); + uint32_t plistpos = TA_NEXT_OPB_INIT >> 2; //MMIO_READ( PVR2, TA_LISTBASE ) + if( ta_status.tilelist_dir == TA_GROW_DOWN ) { + plistpos -= ta_status.tilelist_size; + } + TA_NEXT_OPB = plistpos; + //MMIO_WRITE( PVR2, TA_LISTPOS, plistpos ); + ta_status.tilelist_start = plistpos; + ta_status.polybuf_start = TA_ISP_BASE & 0x00F00000; // MMIO_READ( PVR2, TA_POLYBASE ) +} + +static uint32_t parse_float_colour( float a, float r, float g, float b ) { + int ai,ri,gi,bi; + + if( TA_IS_INF(a) ) { + ai = 255; + } else { + ai = 256 * CLAMP(a,0.0,1.0) - 1; + if( ai < 0 ) ai = 0; + } + if( TA_IS_INF(r) ) { + ri = 255; + } else { + ri = 256 * CLAMP(r,0.0,1.0) - 1; + if( ri < 0 ) ri = 0; + } + if( TA_IS_INF(g) ) { + gi = 255; + } else { + gi = 256 * CLAMP(g,0.0,1.0) - 1; + if( gi < 0 ) gi = 0; + } + if( TA_IS_INF(b) ) { + bi = 255; + } else { + bi = 256 * CLAMP(b,0.0,1.0) - 1; + if( bi < 0 ) bi = 0; + } + return (ai << 24) | (ri << 16) | (gi << 8) | bi; +} + +static uint32_t parse_intensity_colour( uint32_t base, float intensity ) +{ + unsigned int i = (unsigned int)(256 * CLAMP(intensity, 0.0,1.0)); + + return + (((((base & 0xFF) * i) & 0xFF00) | + (((base & 0xFF00) * i) & 0xFF0000) | + (((base & 0xFF0000) * i) & 0xFF000000)) >> 8) | + (base & 0xFF000000); +} + +/** + * Initialize the specified TA list. + */ +static void ta_init_list( unsigned int listtype ) { + int config = TA_ALLOC_CTRL;//MMIO_READ( PVR2, TA_TILECFG ); + int tile_matrix = TA_OL_BASE; //MMIO_READ( PVR2, TA_TILEBASE ); + int list_end = TA_OL_LIMIT;//MMIO_READ( PVR2, TA_LISTEND ); + + ta_status.current_tile_matrix = tile_matrix; + + /* If the list grows down, the end must be < tile matrix start. + * If it grows up, the end must be > tile matrix start. + * Don't ask me why, it just does... + */ + if( ((ta_status.tilelist_dir == TA_GROW_DOWN && list_end <= tile_matrix) || + (ta_status.tilelist_dir == TA_GROW_UP && list_end >= tile_matrix )) && + listtype <= TA_LIST_PUNCH_OUT ) { + int i; + for( i=0; i < listtype; i++ ) { + int size = tilematrix_sizes[(config & 0x03)] << 2; + ta_status.current_tile_matrix += ta_status.width * ta_status.height * size; + config >>= 4; + } + ta_status.current_tile_size = tilematrix_sizes[(config & 0x03)]; + + /* Initialize each tile to 0xF0000000 */ + if( ta_status.current_tile_size != 0 ) { + pvraddr_t p = (ta_status.current_tile_matrix); + for( i=0; i< ta_status.width * ta_status.height; i++ ) { + PVRRAM(p) = 0xF0000000; + p += ta_status.current_tile_size * 4; + } + } + } else { + ta_status.current_tile_size = 0; + } + + if( tile_matrix == list_end ) { + ta_status.current_tile_size = 0; + } + + ta_status.state = STATE_IN_LIST; + ta_status.current_list_type = listtype; + ta_status.last_triangle_bounds.x1 = -1; +} + +#include +#include "emu/emu.h" + +static int list_events[5] = {ASIC_EVT_PVR_OPAQUEDONE, ASIC_EVT_PVR_OPAQUEMODDONE, + ASIC_EVT_PVR_TRANSDONE, ASIC_EVT_PVR_TRANSMODDONE, + ASIC_EVT_PVR_PTDONE }; + +/* +static int list_events[5] = {EVENT_PVR_OPAQUE_DONE, EVENT_PVR_OPAQUEMOD_DONE, + EVENT_PVR_TRANS_DONE, EVENT_PVR_TRANSMOD_DONE, + EVENT_PVR_PUNCHOUT_DONE }; +*/ +// static HollyInterruptID list_events[5] = {holly_OPAQUE, holly_OPAQUEMOD, +// holly_TRANS, holly_TRANSMOD, +// holly_PUNCHTHRU }; + +static void ta_end_list() { + if( ta_status.current_list_type != TA_LIST_NONE ) { + //asic_event( list_events[ta_status.current_list_type] ); + // asic_RaiseInterrupt(list_events[ta_status.current_list_type]); + // printf("asic_RaiseInterrupt(list_events[%d])\n", ta_status.current_list_type); + pvr_queue_interrupt(list_events[ta_status.current_list_type]); + } + ta_status.current_list_type = TA_LIST_NONE; + ta_status.current_vertex_type = TA_VERTEX_LISTLESS; + ta_status.poly_vertex_size = 0; + ta_status.poly_context[1] = 0; + ta_status.state = STATE_IDLE; +} + +static void ta_bad_input_error() { + // asic_RaiseInterrupt(holly_ILLEGAL_PARAM); + pvr_queue_interrupt(ASIC_EVT_PVR_TA_INPUT_ERR); + printf("TA error: holly_ILLEGAL_PARAM. Interrupt raised\n"); + //asic_event( EVENT_PVR_BAD_INPUT ); +} + +/** + * Write data out to the polygon buffer. + * If the end-of-buffer is reached, asserts EVENT_PVR_PRIM_ALLOC_FAIL + * @param data to be written + * @param length Number of 32-bit words to write. + * @return number of words actually written + */ +static int ta_write_polygon_buffer( uint32_t *data, int length ) +{ + int rv; + int posn = TA_ISP_CURRENT;// MMIO_READ( PVR2, TA_POLYPOS ); + int end = TA_ISP_LIMIT;// MMIO_READ( PVR2, TA_POLYEND ); + for( rv=0; rv < length; rv++ ) { + if( posn == end ) { + // asic_RaiseInterrupt(holly_PRIM_NOMEM); + pvr_queue_interrupt(ASIC_EVT_PVR_PARAM_OUTOFMEM); + printf("TA error: holly_PRIM_NOMEM. Interrupt Raised\n"); + //asic_event( EVENT_PVR_PRIM_ALLOC_FAIL ); + //// ta_status.state = STATE_ERROR; + break; + } + if( posn < PVR2_RAM_SIZE ) { + PVRRAM(posn) = *data++; + } + posn += 4; + } + + TA_ISP_CURRENT = posn; + //MMIO_WRITE( PVR2, TA_POLYPOS, posn ); + return rv; +} + +#define TA_NO_ALLOC 0xFFFFFFFF + +/** + * Allocate a new tile list block from the grow space and update the + * word at reference to be a link to the new block. + */ +static uint32_t ta_alloc_tilelist( uint32_t reference ) { + uint32_t posn = TA_NEXT_OPB;//MMIO_READ( PVR2, TA_LISTPOS ); + uint32_t limit = TA_OL_LIMIT >> 2;//MMIO_READ( PVR2, TA_LISTEND ) >> 2; + uint32_t newposn; + if( ta_status.tilelist_dir == TA_GROW_DOWN ) { + newposn = posn - ta_status.tilelist_size; + if( posn == limit ) { + PVRRAM(posn<<2) = 0xF0000000; + PVRRAM(reference) = 0xE0000000 | (posn<<2); + return TA_NO_ALLOC; + } else if( posn < limit ) { + PVRRAM(reference) = 0xE0000000 | (posn<<2); + return TA_NO_ALLOC; + } else if( newposn <= limit ) { + } else if( newposn <= (limit + ta_status.tilelist_size) ) { + // asic_RaiseInterrupt(holly_MATR_NOMEM); + pvr_queue_interrupt(ASIC_EVT_PVR_OPB_OUTOFMEM); + printf("TA error: holly_MATR_NOMEM. Interrupt raised\n"); + //asic_event( EVENT_PVR_MATRIX_ALLOC_FAIL ); + TA_NEXT_OPB = newposn; + //MMIO_WRITE( PVR2, TA_LISTPOS, newposn ); + } else { + TA_NEXT_OPB = newposn; + //MMIO_WRITE( PVR2, TA_LISTPOS, newposn ); + } + PVRRAM(reference) = 0xE0000000 | (posn<<2); + return posn << 2; + } else { + newposn = posn + ta_status.tilelist_size; + if( posn == limit ) { + PVRRAM(posn<<2) = 0xF0000000; + PVRRAM(reference) = 0xE0000000 | (posn<<2); + return TA_NO_ALLOC; + } else if ( posn > limit ) { + PVRRAM(reference) = 0xE0000000 | (posn<<2); + return TA_NO_ALLOC; + } else if( newposn >= limit ) { + } else if( newposn >= (limit - ta_status.tilelist_size) ) { + // asic_RaiseInterrupt(holly_MATR_NOMEM); + pvr_queue_interrupt(ASIC_EVT_PVR_OPB_OUTOFMEM); + printf("TA error: holly_MATR_NOMEM. Interrupt raised\n"); + //asic_event( EVENT_PVR_MATRIX_ALLOC_FAIL ); + TA_NEXT_OPB = newposn; + //MMIO_WRITE( PVR2, TA_LISTPOS, newposn ); + } else { + TA_NEXT_OPB = newposn; + //MMIO_WRITE( PVR2, TA_LISTPOS, newposn ); + } + PVRRAM(reference) = 0xE0000000 | (posn<<2); + return posn << 2; + } +} + +/** + * Write a tile entry out to the matrix. + */ +static void ta_write_tile_entry( int x, int y, uint32_t tile_entry ) { + uint32_t tile = TILESLOT(x,y); + uint32_t tilestart = tile; + uint32_t value; + uint32_t lasttri = 0; + int i; + + if( ta_status.clip_mode == TA_POLYCMD_CLIP_OUTSIDE && + x >= ta_status.clip.x1 && x <= ta_status.clip.x2 && + y >= ta_status.clip.y1 && y <= ta_status.clip.y2 ) { + /* Tile clipped out */ + return; + } + + if( (tile_entry & 0x80000000) && + ta_status.last_triangle_bounds.x1 != -1 && + ta_status.last_triangle_bounds.x1 <= x && + ta_status.last_triangle_bounds.x2 >= x && + ta_status.last_triangle_bounds.y1 <= y && + ta_status.last_triangle_bounds.y2 >= y ) { + /* potential for triangle stacking */ + lasttri = tile_entry & 0xE1E00000; + } + + + if( PVRRAM(tile) == 0xF0000000 ) { + PVRRAM(tile) = tile_entry; + PVRRAM(tile+4) = 0xF0000000; + return; + } + + while(1) { + value = PVRRAM(tile); + for( i=1; i (float)INT_MAX || TA_IS_INF(ta_status.poly_vertex[i].x) ) { + tx[i] = INT_MAX/32; + } else { + tx[i] = (int)(ta_status.poly_vertex[i].x / 32.0); + } + if( ta_status.poly_vertex[i].y < 0.0 || TA_IS_NINF(ta_status.poly_vertex[i].y)) { + ty[i] = -1; + } else if( ta_status.poly_vertex[i].y > (float)INT_MAX || TA_IS_INF(ta_status.poly_vertex[i].y) ) { + ty[i] = INT_MAX/32; + } else { + ty[i] = (int)(ta_status.poly_vertex[i].y / 32.0); + } + + } + + /* Compute bounding box for each triangle individually, as well + * as the overall polygon. + */ + + triangle_bound[0].x1 = MIN3(tx[0],tx[1],tx[2]); + triangle_bound[0].x2 = MAX3(tx[0],tx[1],tx[2]); + triangle_bound[0].y1 = MIN3(ty[0],ty[1],ty[2]); + triangle_bound[0].y2 = MAX3(ty[0],ty[1],ty[2]); + polygon_bound.x1 = triangle_bound[0].x1; + polygon_bound.y1 = triangle_bound[0].y1; + polygon_bound.x2 = triangle_bound[0].x2; + polygon_bound.y2 = triangle_bound[0].y2; + + for( i=1; i= ta_status.width ) polygon_bound.x2 = ta_status.width-1; + if( polygon_bound.y1 < 0 ) polygon_bound.y1 = 0; + if( polygon_bound.y2 >= ta_status.height ) polygon_bound.y2 = ta_status.height-1; + + /* Set the "single tile" flag if it's entirely contained in 1 tile */ + if( polygon_bound.x1 == polygon_bound.x2 && + polygon_bound.y1 == polygon_bound.y2 ) { + poly_context[0] |= 0x00200000; + } + + /* If the polygon is entirely clipped, don't even write the polygon data */ + switch( ta_status.clip_mode ) { + case TA_POLYCMD_CLIP_NONE: + if( polygon_bound.x2 < 0 || polygon_bound.x1 >= ta_status.width || + polygon_bound.y2 < 0 || polygon_bound.y1 >= ta_status.height ) { + return; + } + break; + case TA_POLYCMD_CLIP_INSIDE: + if( polygon_bound.x2 < ta_status.clip.x1 || polygon_bound.x1 > ta_status.clip.x2 || + polygon_bound.y2 < ta_status.clip.y1 || polygon_bound.y1 > ta_status.clip.y2 ) { + return; + } else { + /* Clamp to clip bounds */ + if( polygon_bound.x1 < ta_status.clip.x1 ) polygon_bound.x1 = ta_status.clip.x1; + if( polygon_bound.x2 > ta_status.clip.x2 ) polygon_bound.x2 = ta_status.clip.x2; + if( polygon_bound.y1 < ta_status.clip.y1 ) polygon_bound.y1 = ta_status.clip.y1; + if( polygon_bound.y2 > ta_status.clip.y2 ) polygon_bound.y2 = ta_status.clip.y2; + } + break; + case TA_POLYCMD_CLIP_OUTSIDE: + if( polygon_bound.x1 >= ta_status.clip.x1 && polygon_bound.x2 <= ta_status.clip.x2 && + polygon_bound.y1 >= ta_status.clip.y1 && polygon_bound.y2 <= ta_status.clip.y2 ) { + return; + } + break; + } + + /* Ok, we're good to go - write out the polygon first */ + uint32_t tile_entry = (TA_ISP_CURRENT/*MMIO_READ( PVR2, TA_POLYPOS )*/ - ta_status.polybuf_start) >> 2 | + ta_status.poly_pointer; + + int status = ta_write_polygon_buffer( poly_context, ta_status.poly_context_size ); + if( status == 0 ) { + /* No memory available - abort */ + return; + } else { + for( i=0; i= x && + triangle_bound[i].y1 <= y && triangle_bound[i].y2 >= y ) { + entry |= (0x40000000>>i); + } + } + ta_write_tile_entry( x, y, entry ); + } + } + ta_status.last_triangle_bounds.x1 = -1; + } +} + +/** + * Variant of ta_split_polygon called when vertex_count == max_vertex, but + * the client hasn't sent the LAST VERTEX flag. Commit the poly as normal + * first, then start a new poly with the first 2 vertexes taken from the + * current one. + */ +static void ta_split_polygon() { + ta_commit_polygon(); + if( TA_IS_NORMAL_POLY() ) { + /* This only applies to ordinary polys - Sprites + modifier lists are + * handled differently + */ + if( ta_status.vertex_count == 3 ) { + /* Triangles use an odd/even scheme */ + if( ta_status.poly_parity == 0 ) { + memcpy( &ta_status.poly_vertex[0], &ta_status.poly_vertex[2], + sizeof(struct pvr2_ta_vertex) ); + ta_status.poly_parity = 1; + } else { + memcpy( &ta_status.poly_vertex[1], &ta_status.poly_vertex[2], + sizeof(struct pvr2_ta_vertex) ); + ta_status.poly_parity = 0; + } + } else { + /* Everything else just uses the last 2 vertexes in order */ + memcpy( &ta_status.poly_vertex[0], &ta_status.poly_vertex[ta_status.vertex_count-2], + sizeof(struct pvr2_ta_vertex)*2 ); + ta_status.poly_parity = 0; + } + ta_status.vertex_count = 2; + } else { + ta_status.vertex_count = 0; + } +} + +/** + * Parse the polygon context block and setup the internal state to receive + * vertexes. + * @param data 32 bytes of parameter data. + */ +static void ta_parse_polygon_context( union ta_data *data ) { + int colourfmt = TA_POLYCMD_COLOURFMT(data[0].i); + if( TA_POLYCMD_USELENGTH(data[0].i) ) { + ta_status.max_vertex = TA_POLYCMD_LENGTH(data[0].i); + } + ta_status.clip_mode = TA_POLYCMD_CLIP(data[0].i); + if( ta_status.clip_mode == 1 ) { /* Reserved - treat as CLIP_INSIDE */ + ta_status.clip_mode = TA_POLYCMD_CLIP_INSIDE; + } + ta_status.vertex_count = 0; + ta_status.poly_context[0] = + (data[1].i & 0xFC1FFFFF) | ((data[0].i & 0x0B) << 22); + ta_status.poly_context[1] = data[2].i; + ta_status.poly_context[3] = data[4].i; + ta_status.poly_parity = 0; + if( data[0].i & TA_POLYCMD_TEXTURED ) { + ta_status.current_vertex_type = data[0].i & 0x0D; + ta_status.poly_context[2] = data[3].i; + ta_status.poly_context[4] = data[5].i; + if( data[0].i & TA_POLYCMD_SPECULAR ) { + ta_status.poly_context[0] |= 0x01000000; + ta_status.poly_vertex_size = 4; + } else { + ta_status.poly_vertex_size = 3; + } + if( data[0].i & TA_POLYCMD_UV16 ) { + ta_status.poly_vertex_size--; + } + } else { + ta_status.current_vertex_type = 0; + ta_status.poly_vertex_size = 1; + ta_status.poly_context[2] = 0; + ta_status.poly_context[4] = 0; + } + + ta_status.poly_pointer = (ta_status.poly_vertex_size << 21); + ta_status.poly_context_size = 3; + if( data[0].i & TA_POLYCMD_MODIFIED ) { + ta_status.poly_pointer |= 0x01000000; + if( data[0].i & TA_POLYCMD_FULLMOD ) { + ta_status.poly_context_size = 5; + ta_status.poly_vertex_size <<= 1; + ta_status.current_vertex_type |= 0x40; + /* Modified/float not supported - behaves as per last intensity */ + if( colourfmt == TA_POLYCMD_COLOURFMT_FLOAT ) { + colourfmt = TA_POLYCMD_COLOURFMT_LASTINT; + } + } + } + + if( colourfmt == TA_POLYCMD_COLOURFMT_INTENSITY ) { + if( TA_POLYCMD_IS_FULLMOD(data[0].i) || + TA_POLYCMD_IS_SPECULAR(data[0].i) ) { + ta_status.state = STATE_EXPECT_POLY_BLOCK2; + } else { + ta_status.intensity1 = + parse_float_colour( data[4].f, data[5].f, data[6].f, data[7].f ); + } + } else if( colourfmt == TA_POLYCMD_COLOURFMT_LASTINT ) { + colourfmt = TA_POLYCMD_COLOURFMT_INTENSITY; + } + + ta_status.current_vertex_type |= colourfmt; +} + +/** + * Parse the modifier volume context block and setup the internal state to + * receive modifier vertexes. + * @param data 32 bytes of parameter data. + */ +static void ta_parse_modifier_context( union ta_data *data ) { + ta_status.current_vertex_type = TA_VERTEX_MOD_VOLUME; + ta_status.poly_vertex_size = 0; + ta_status.clip_mode = TA_POLYCMD_CLIP(data[0].i); + if( ta_status.clip_mode == 1 ) { /* Reserved - treat as CLIP_INSIDE */ + ta_status.clip_mode = TA_POLYCMD_CLIP_INSIDE; + } + ta_status.poly_context_size = 3; + ta_status.poly_context[0] = (data[1].i & 0xFC1FFFFF) | + ((data[0].i & 0x0B)<<22); + if( TA_POLYCMD_IS_SPECULAR(data[0].i) ) { + ta_status.poly_context[0] |= 0x01000000; + } + ta_status.poly_context[1] = 0; + ta_status.poly_context[2] = 0; + ta_status.vertex_count = 0; + ta_status.max_vertex = 3; + ta_status.poly_pointer = 0; +} + +/** + * Parse the sprite context block and setup the internal state to receive + * vertexes. + * @param data 32 bytes of parameter data. + */ +static void ta_parse_sprite_context( union ta_data *data ) { + ta_status.poly_context_size = 3; + ta_status.poly_context[0] = (data[1].i & 0xFC1FFFFF) | + ((data[0].i & 0x0B)<<22) | 0x00400000; + ta_status.clip_mode = TA_POLYCMD_CLIP(data[0].i); + if( ta_status.clip_mode == 1 ) { /* Reserved - treat as CLIP_INSIDE */ + ta_status.clip_mode = TA_POLYCMD_CLIP_INSIDE; + } + if( TA_POLYCMD_IS_SPECULAR(data[0].i) ) { + ta_status.poly_context[0] |= 0x01000000; + } + ta_status.poly_context[1] = data[2].i; + ta_status.poly_context[2] = data[3].i; + if( data[0].i & TA_POLYCMD_TEXTURED ) { + ta_status.poly_vertex_size = 2; + ta_status.poly_vertex[2].detail[1] = data[4].i; + ta_status.current_vertex_type = TA_VERTEX_TEX_SPRITE; + } else { + ta_status.poly_vertex_size = 1; + ta_status.poly_vertex[2].detail[0] = data[4].i; + ta_status.current_vertex_type = TA_VERTEX_SPRITE; + } + ta_status.vertex_count = 0; + ta_status.max_vertex = 4; + ta_status.poly_pointer = (ta_status.poly_vertex_size << 21); +} + +/** + * Copy the last read vertex into all vertexes up to max_vertex. Used for + * Aborted polygons under some circumstances. + */ +static void ta_fill_vertexes( ) { + int i; + for( i=ta_status.vertex_count; ix = data[1].f; + vertex->y = data[2].f; + vertex->z = data[3].f; + + switch( ta_status.current_vertex_type ) { + case TA_VERTEX_PACKED: + vertex->detail[0] = data[6].i; + break; + case TA_VERTEX_FLOAT: + vertex->detail[0] = parse_float_colour( data[4].f, data[5].f, data[6].f, data[7].f ); + break; + case TA_VERTEX_INTENSITY: + vertex->detail[0] = parse_intensity_colour( ta_status.intensity1, data[6].f ); + break; + + case TA_VERTEX_TEX_SPEC_PACKED: + vertex->detail[3] = data[7].i; /* ARGB */ + /* Fallthrough */ + case TA_VERTEX_TEX_PACKED: + vertex->detail[0] = data[4].i; /* U */ + vertex->detail[1] = data[5].i; /* V */ + vertex->detail[2] = data[6].i; /* ARGB */ + break; + case TA_VERTEX_TEX_UV16_SPEC_PACKED: + vertex->detail[2] = data[7].i; /* ARGB */ + /* Fallthrough */ + case TA_VERTEX_TEX_UV16_PACKED: + vertex->detail[0] = data[4].i; /* UV */ + vertex->detail[1] = data[6].i; /* ARGB */ + break; + + case TA_VERTEX_TEX_FLOAT: + case TA_VERTEX_TEX_SPEC_FLOAT: + vertex->detail[0] = data[4].i; /* U */ + vertex->detail[1] = data[5].i; /* UV */ + ta_status.state = STATE_EXPECT_VERTEX_BLOCK2; + break; + case TA_VERTEX_TEX_UV16_FLOAT: + case TA_VERTEX_TEX_UV16_SPEC_FLOAT: + vertex->detail[0] = data[4].i; /* UV */ + ta_status.state = STATE_EXPECT_VERTEX_BLOCK2; + break; + + case TA_VERTEX_TEX_SPEC_INTENSITY: + vertex->detail[3] = parse_intensity_colour( ta_status.intensity2, data[7].f ); + /* Fallthrough */ + case TA_VERTEX_TEX_INTENSITY: + vertex->detail[0] = data[4].i; /* U */ + vertex->detail[1] = data[5].i; /* V */ + vertex->detail[2] = parse_intensity_colour( ta_status.intensity1, data[6].f ); + break; + case TA_VERTEX_TEX_UV16_SPEC_INTENSITY: + vertex->detail[2] = parse_intensity_colour( ta_status.intensity2, data[7].f ); + /* Fallthrough */ + case TA_VERTEX_TEX_UV16_INTENSITY: + vertex->detail[0] = data[4].i; /* UV */ + vertex->detail[1] = parse_intensity_colour( ta_status.intensity1, data[6].f ); + break; + + case TA_VERTEX_PACKED_MOD: + vertex->detail[0] = data[4].i; /* ARGB */ + vertex->detail[1] = data[5].i; /* ARGB */ + break; + case TA_VERTEX_INTENSITY_MOD: + vertex->detail[0] = parse_intensity_colour( ta_status.intensity1, data[4].f ); + vertex->detail[1] = parse_intensity_colour( ta_status.intensity2, data[5].f ); + break; + + case TA_VERTEX_TEX_SPEC_PACKED_MOD: + vertex->detail[3] = data[7].i; /* ARGB0 */ + /* Fallthrough */ + case TA_VERTEX_TEX_PACKED_MOD: + vertex->detail[0] = data[4].i; /* U0 */ + vertex->detail[1] = data[5].i; /* V0 */ + vertex->detail[2] = data[6].i; /* ARGB0 */ + ta_status.state = STATE_EXPECT_VERTEX_BLOCK2; + break; + case TA_VERTEX_TEX_UV16_SPEC_PACKED_MOD: + vertex->detail[2] = data[7].i; /* ARGB0 */ + /* Fallthrough */ + case TA_VERTEX_TEX_UV16_PACKED_MOD: + vertex->detail[0] = data[4].i; /* UV0 */ + vertex->detail[1] = data[6].i; /* ARGB0 */ + ta_status.state = STATE_EXPECT_VERTEX_BLOCK2; + break; + + case TA_VERTEX_TEX_SPEC_INTENSITY_MOD: + vertex->detail[3] = parse_intensity_colour( ta_status.intensity1, data[7].f ); + /* Fallthrough */ + case TA_VERTEX_TEX_INTENSITY_MOD: + vertex->detail[0] = data[4].i; /* U0 */ + vertex->detail[1] = data[5].i; /* V0 */ + vertex->detail[2] = parse_intensity_colour( ta_status.intensity1, data[6].f ); + ta_status.state = STATE_EXPECT_VERTEX_BLOCK2; + break; + case TA_VERTEX_TEX_UV16_SPEC_INTENSITY_MOD: + vertex->detail[2] = parse_intensity_colour( ta_status.intensity1, data[7].f ); + /* Fallthrough */ + case TA_VERTEX_TEX_UV16_INTENSITY_MOD: + vertex->detail[0] = data[4].i; /* UV0 */ + vertex->detail[1] = parse_intensity_colour( ta_status.intensity1, data[6].f ); + ta_status.state = STATE_EXPECT_VERTEX_BLOCK2; + break; + + case TA_VERTEX_SPRITE: + case TA_VERTEX_TEX_SPRITE: + case TA_VERTEX_MOD_VOLUME: + case TA_VERTEX_LISTLESS: + vertex++; + vertex->x = data[4].f; + vertex->y = data[5].f; + vertex->z = data[6].f; + vertex++; + vertex->x = data[7].f; + ta_status.vertex_count += 2; + ta_status.state = STATE_EXPECT_VERTEX_BLOCK2; + break; + } + ta_status.vertex_count++; +} + +static void ta_parse_vertex_block2( union ta_data *data ) { + struct pvr2_ta_vertex *vertex = &ta_status.poly_vertex[ta_status.vertex_count-1]; + + switch( ta_status.current_vertex_type ) { + case TA_VERTEX_TEX_SPEC_FLOAT: + vertex->detail[3] = parse_float_colour( data[4].f, data[5].f, data[6].f, data[7].f ); + /* Fallthrough */ + case TA_VERTEX_TEX_FLOAT: + vertex->detail[2] = parse_float_colour( data[0].f, data[1].f, data[2].f, data[3].f ); + break; + case TA_VERTEX_TEX_UV16_SPEC_FLOAT: + vertex->detail[2] = parse_float_colour( data[4].f, data[5].f, data[6].f, data[7].f ); + /* Fallthrough */ + case TA_VERTEX_TEX_UV16_FLOAT: + vertex->detail[1] = parse_float_colour( data[0].f, data[1].f, data[2].f, data[3].f ); + break; + case TA_VERTEX_TEX_PACKED_MOD: + vertex->detail[3] = data[0].i; /* U1 */ + vertex->detail[4] = data[1].i; /* V1 */ + vertex->detail[5] = data[2].i; /* ARGB1 */ + break; + case TA_VERTEX_TEX_SPEC_PACKED_MOD: + vertex->detail[4] = data[0].i; /* U1 */ + vertex->detail[5] = data[1].i; /* V1 */ + vertex->detail[6] = data[2].i; /* ARGB1 */ + vertex->detail[7] = data[3].i; /* ARGB1 */ + break; + case TA_VERTEX_TEX_UV16_PACKED_MOD: + vertex->detail[2] = data[0].i; /* UV1 */ + vertex->detail[3] = data[2].i; /* ARGB1 */ + break; + case TA_VERTEX_TEX_UV16_SPEC_PACKED_MOD: + vertex->detail[3] = data[0].i; /* UV1 */ + vertex->detail[4] = data[2].i; /* ARGB1 */ + vertex->detail[5] = data[3].i; /* ARGB1 */ + break; + + case TA_VERTEX_TEX_INTENSITY_MOD: + vertex->detail[3] = data[0].i; /* U1 */ + vertex->detail[4] = data[1].i; /* V1 */ + vertex->detail[5] = parse_intensity_colour( ta_status.intensity2, data[2].f ); /* ARGB1 */ + break; + case TA_VERTEX_TEX_SPEC_INTENSITY_MOD: + vertex->detail[4] = data[0].i; /* U1 */ + vertex->detail[5] = data[1].i; /* V1 */ + vertex->detail[6] = parse_intensity_colour( ta_status.intensity2, data[2].f ); /* ARGB1 */ + vertex->detail[7] = parse_intensity_colour( ta_status.intensity2, data[3].f ); /* ARGB1 */ + break; + case TA_VERTEX_TEX_UV16_INTENSITY_MOD: + vertex->detail[2] = data[0].i; /* UV1 */ + vertex->detail[3] = parse_intensity_colour( ta_status.intensity2, data[2].f ); /* ARGB1 */ + break; + case TA_VERTEX_TEX_UV16_SPEC_INTENSITY_MOD: + vertex->detail[3] = data[0].i; /* UV1 */ + vertex->detail[4] = parse_intensity_colour( ta_status.intensity2, data[2].f ); /* ARGB1 */ + vertex->detail[5] = parse_intensity_colour( ta_status.intensity2, data[3].f ); /* ARGB1 */ + break; + + case TA_VERTEX_SPRITE: + vertex->y = data[0].f; + vertex->z = data[1].f; + vertex++; + ta_status.vertex_count++; + vertex->x = data[2].f; + vertex->y = data[3].f; + vertex->z = 0; + vertex->detail[0] = 0; + ta_status.poly_vertex[0].detail[0] = 0; + ta_status.poly_vertex[1].detail[0] = 0; + break; + case TA_VERTEX_TEX_SPRITE: + vertex->y = data[0].f; + vertex->z = data[1].f; + vertex++; + ta_status.vertex_count++; + vertex->x = data[2].f; + vertex->y = data[3].f; + vertex->z = 0; + vertex->detail[0] = 0; + vertex->detail[1] = 0; + ta_status.poly_vertex[0].detail[0] = data[5].i; + ta_status.poly_vertex[0].detail[1] = 0; + ta_status.poly_vertex[1].detail[0] = data[6].i; + ta_status.poly_vertex[1].detail[1] = 0; + ta_status.poly_vertex[2].detail[0] = data[7].i; + break; + case TA_VERTEX_MOD_VOLUME: + case TA_VERTEX_LISTLESS: + vertex->y = data[0].f; + vertex->z = data[1].f; + break; + } + ta_status.state = STATE_IN_POLYGON; +} + +/** + * Process 1 32-byte block of ta data + */ +void pvr2_ta_process_block( unsigned char *input ) { + union ta_data *data = (union ta_data *)input; + + switch( ta_status.state ) { + case STATE_ERROR: + /* Fatal error raised - stop processing until reset */ + return; + + case STATE_EXPECT_POLY_BLOCK2: + /* This is always a pair of floating-point colours */ + ta_status.intensity1 = + parse_float_colour( data[0].f, data[1].f, data[2].f, data[3].f ); + ta_status.intensity2 = + parse_float_colour( data[4].f, data[5].f, data[6].f, data[7].f ); + ta_status.state = STATE_IN_LIST; + break; + + case STATE_EXPECT_VERTEX_BLOCK2: + ta_parse_vertex_block2( data ); + if( ta_status.vertex_count == ta_status.max_vertex ) { + ta_split_polygon(); + } + break; + + case STATE_EXPECT_END_VERTEX_BLOCK2: + ta_parse_vertex_block2( data ); + if( ta_status.vertex_count < 3 ) { + ta_bad_input_error(); + } else { + ta_commit_polygon(); + } + ta_status.vertex_count = 0; + ta_status.poly_parity = 0; + ta_status.state = STATE_IN_LIST; + break; + case STATE_IN_LIST: + case STATE_IN_POLYGON: + case STATE_IDLE: + switch( TA_CMD( data->i ) ) { + case TA_CMD_END_LIST: + if( ta_status.state == STATE_IN_POLYGON ) { + ta_bad_input_error(); + ta_end_list(); + ta_status.state = STATE_ERROR; /* Abort further processing */ + } else { + ta_end_list(); + } + break; + case TA_CMD_CLIP: + if( ta_status.state == STATE_IN_POLYGON ) { + ta_bad_input_error(); + ta_status.accept_vertexes = FALSE; + /* Enter stuffed up mode */ + } + ta_status.clip.x1 = data[4].i & 0x3F; + ta_status.clip.y1 = data[5].i & 0x0F; + ta_status.clip.x2 = data[6].i & 0x3F; + ta_status.clip.y2 = data[7].i & 0x0F; + if( ta_status.clip.x2 >= ta_status.width ) + ta_status.clip.x2 = ta_status.width - 1; + if( ta_status.clip.y2 >= ta_status.height ) + ta_status.clip.y2 = ta_status.height - 1; + break; + case TA_CMD_POLYGON_CONTEXT: + if( ta_status.state == STATE_IDLE ) { + ta_init_list( TA_POLYCMD_LISTTYPE( data->i ) ); + } + + if( ta_status.vertex_count != 0 ) { + /* Error, and not a very well handled one either */ + ta_bad_input_error(); + ta_status.accept_vertexes = FALSE; + } else { + if( TA_IS_MODIFIER_LIST( ta_status.current_list_type ) ) { + ta_parse_modifier_context(data); + } else { + ta_parse_polygon_context(data); + } + } + break; + case TA_CMD_SPRITE_CONTEXT: + if( ta_status.state == STATE_IDLE ) { + ta_init_list( TA_POLYCMD_LISTTYPE( data->i ) ); + } + + if( ta_status.vertex_count != 0 ) { + ta_fill_vertexes(); + ta_commit_polygon(); + } + + ta_parse_sprite_context(data); + break; + case TA_CMD_VERTEX: + ta_status.state = STATE_IN_POLYGON; + ta_parse_vertex(data); + + if( ta_status.state == STATE_EXPECT_VERTEX_BLOCK2 ) { + if( TA_IS_END_VERTEX(data[0].i) ) { + ta_status.state = STATE_EXPECT_END_VERTEX_BLOCK2; + } + } else if( TA_IS_END_VERTEX(data->i) ) { + if( ta_status.vertex_count < 3 ) { + ta_bad_input_error(); + } else { + ta_commit_polygon(); + } + ta_status.vertex_count = 0; + ta_status.poly_parity = 0; + ta_status.state = STATE_IN_LIST; + } else if( ta_status.vertex_count == ta_status.max_vertex ) { + ta_split_polygon(); + } + break; + default: + if( ta_status.state == STATE_IN_POLYGON ) { + ta_bad_input_error(); + } + } + break; + } + +} + +/** + * Find the first polygon or sprite context in the supplied buffer of TA + * data. + * @return A pointer to the context, or NULL if it cannot be found + */ +uint32_t *pvr2_ta_find_polygon_context( uint32_t *buf, uint32_t length ) +{ + uint32_t *poly; + for( poly = buf; poly < buf+(length>>2); poly += 8 ) { + if( TA_CMD(*poly) == TA_CMD_POLYGON_CONTEXT || + TA_CMD(*poly) == TA_CMD_SPRITE_CONTEXT ) { + return poly; + } + } + return NULL; +} + +/** + * Write a block of data to the tile accelerator, adding the data to the + * current scene. We don't make any particular attempt to interpret the data + * at this stage, deferring that until render time. + * + * Currently copies the data verbatim to the vertex buffer, processing only + * far enough to generate the correct end-of-list events. Tile buffer is + * entirely ignored. + */ +void lxd_ta_write( unsigned char *buf, uint32_t length ) +{ + for( ; length >=32; length -= 32 ) { + pvr2_ta_process_block( buf ); + buf += 32; + } +} + +void lxd_ta_write_burst( sh4addr_t addr, unsigned char *data ) +{ + pvr2_ta_process_block( data ); +} diff --git a/vendor/emu/lxdream/tacore.h b/vendor/emu/lxdream/tacore.h new file mode 100644 index 00000000..78fbeb0e --- /dev/null +++ b/vendor/emu/lxdream/tacore.h @@ -0,0 +1,11 @@ +#pragma once +#include "emu/types.h" + +typedef unsigned int pvraddr_t; +typedef unsigned int pvr64addr_t; +typedef unsigned int sh4addr_t; + +void lxd_ta_write( unsigned char *buf, uint32_t length ); +void lxd_ta_write_burst( sh4addr_t addr, unsigned char *data ); +void lxd_ta_reset(); +void lxd_ta_init(u8* vram); \ No newline at end of file diff --git a/vendor/emu/refsw/TexUtils.cpp b/vendor/emu/refsw/TexUtils.cpp new file mode 100644 index 00000000..88e41a80 --- /dev/null +++ b/vendor/emu/refsw/TexUtils.cpp @@ -0,0 +1,78 @@ +/* + This file is part of libswirl +*/ +#include "license/bsd" + +#include "emu/types.h" +#include "pvr_regs.h" +#include "TexUtils.h" + +#include + +#ifndef M_PI +#define M_PI (3.14159267) +#endif + +u32 detwiddle[2][11][1024]; +u8 BM_SIN90[256]; +u8 BM_COS90[256]; +u8 BM_COS360[256]; + +//input : address in the yyyyyxxxxx format +//output : address in the xyxyxyxy format +//U : x resolution , V : y resolution +//twiddle works on 64b words + + +u32 twiddle_slow(u32 x,u32 y,u32 x_sz,u32 y_sz) +{ + u32 rv=0;//low 2 bits are directly passed -> needs some misc stuff to work.However + //Pvr internally maps the 64b banks "as if" they were twiddled :p + + u32 sh=0; + x_sz>>=1; + y_sz>>=1; + while(x_sz!=0 || y_sz!=0) + { + if (y_sz) + { + u32 temp=y&1; + rv|=temp<>=1; + y>>=1; + sh++; + } + if (x_sz) + { + u32 temp=x&1; + rv|=temp<>=1; + x>>=1; + sh++; + } + } + return rv; +} + +void BuildTables() +{ + for (u32 s=0;s<11;s++) + { + u32 x_sz=1024; + u32 y_sz=1< + +extern u32 detwiddle[2][11][1024]; +extern u8 BM_SIN90[256]; +extern u8 BM_COS90[256]; +extern u8 BM_COS360[256]; + +void BuildTables(); + + +template +pixel_type cclamp(pixel_type minv, pixel_type maxv, pixel_type x) { + return std::min(maxv, std::max(minv, x)); +} + +// Unpack to 32-bit word + +#define ARGB1555_32( word ) ( ((word & 0x8000) ? 0xFF000000 : 0) | (((word>>0) & 0x1F)<<3) | (((word>>5) & 0x1F)<<11) | (((word>>10) & 0x1F)<<19) ) + +#define ARGB565_32( word ) ( (((word>>0)&0x1F)<<3) | (((word>>5)&0x3F)<<10) | (((word>>11)&0x1F)<<19) | 0xFF000000 ) + +#define ARGB4444_32( word ) ( (((word>>12)&0xF)<<28) | (((word>>0)&0xF)<<4) | (((word>>4)&0xF)<<12) | (((word>>8)&0xF)<<20) ) + +#define ARGB8888_32( word ) ( word ) + +static u32 packRGB(u8 R,u8 G,u8 B) +{ + return (R << 0) | (G << 8) | (B << 16) | 0xFF000000; +} + +static u32 YUV422(s32 Y,s32 Yu,s32 Yv) +{ + Yu-=128; + Yv-=128; + + //s32 B = (76283*(Y - 16) + 132252*(Yu - 128))>>16; + //s32 G = (76283*(Y - 16) - 53281 *(Yv - 128) - 25624*(Yu - 128))>>16; + //s32 R = (76283*(Y - 16) + 104595*(Yv - 128))>>16; + + s32 R = Y + Yv*11/8; // Y + (Yv-128) * (11/8) ? + s32 G = Y - (Yu*11 + Yv*22)/32; // Y - (Yu-128) * (11/8) * 0.25 - (Yv-128) * (11/8) * 0.5 ? + s32 B = Y + Yu*110/64; // Y + (Yu-128) * (11/8) * 1.25 ? + + return packRGB(cclamp(0, 255, R),cclamp(0, 255, G),cclamp(0, 255, B)); +} + +#define twop(x,y,bcx,bcy) (detwiddle[0][bcy+3][x]+detwiddle[1][bcx+3][y]) + +#define twop2(x,y,bcx,bcy) (detwiddle[0][bcy][x]+detwiddle[1][bcx][y]) diff --git a/vendor/emu/refsw/core_structs.h b/vendor/emu/refsw/core_structs.h new file mode 100644 index 00000000..7d76e386 --- /dev/null +++ b/vendor/emu/refsw/core_structs.h @@ -0,0 +1,182 @@ +/* + This file is part of libswirl +*/ +#include "license/bsd" + + +//structs were getting tooo many , so i moved em here ! + +#pragma once +#include "emu/types.h" + +//bits that affect drawing (for caching params) +#define PCW_DRAW_MASK (0x000000CE) + +#pragma pack(push, 1) // n = 1 +// Global Param/misc structs +//4B +union PCW +{ + struct + { + //Obj Control //affects drawing ? + u32 UV_16bit : 1; //0 + u32 Gouraud : 1; //1 + u32 Offset : 1; //1 + u32 Texture : 1; //1 + u32 Col_Type : 2; //00 + u32 Volume : 1; //1 + u32 Shadow : 1; //1 + + u32 Reserved : 8; //0000 0000 + + // Group Control + u32 User_Clip : 2; + u32 Strip_Len : 2; + u32 Res_2 : 3; + u32 Group_En : 1; + + // Para Control + u32 ListType : 3; + u32 Res_1 : 1; + u32 EndOfStrip : 1; + u32 ParaType : 3; + }; + u8 obj_ctrl; + struct + { + u32 padin : 8; + u32 S6X : 1; //set by TA preprocessing if sz64 + u32 padin2 : 19; + u32 PTEOS : 4; + }; + u32 full; +}; + + +//// ISP/TSP Instruction Word + +union ISP_TSP +{ + struct + { + u32 Reserved : 20; + u32 DCalcCtrl : 1; + u32 CacheBypass : 1; + u32 UV_16b : 1; //In TA they are replaced + u32 Gouraud : 1; //by the ones on PCW + u32 Offset : 1; // + u32 Texture : 1; // -- up to here -- + u32 ZWriteDis : 1; + u32 CullMode : 2; + u32 DepthMode : 3; + }; + struct + { + u32 res : 27; + u32 CullMode : 2; + u32 VolumeMode : 3; // 0 normal polygon, 1 inside last, 2 outside last + } modvol; + u32 full; +}; + +union ISP_Modvol +{ + struct + { + u32 id : 26; + u32 VolumeLast : 1; + u32 CullMode : 2; + u32 DepthMode : 3; + }; + u32 full; +}; + + +//// END ISP/TSP Instruction Word + + +//// TSP Instruction Word + +union TSP +{ + struct + { + u32 TexV : 3; + u32 TexU : 3; + u32 ShadInstr : 2; + u32 MipMapD : 4; + u32 SupSample : 1; + u32 FilterMode : 2; + u32 ClampV : 1; + u32 ClampU : 1; + u32 FlipV : 1; + u32 FlipU : 1; + u32 IgnoreTexA : 1; + u32 UseAlpha : 1; + u32 ColorClamp : 1; + u32 FogCtrl : 2; + u32 DstSelect : 1; // Secondary Accum + u32 SrcSelect : 1; // Primary Accum + u32 DstInstr : 3; + u32 SrcInstr : 3; + }; + u32 full; +} ; + + +//// END TSP Instruction Word + + +/// Texture Control Word +union TCW +{ + struct + { + u32 TexAddr :21; + u32 Reserved : 4; + u32 StrideSel : 1; + u32 ScanOrder : 1; + u32 PixelFmt : 3; + u32 VQ_Comp : 1; + u32 MipMapped : 1; + } ; + struct + { + u32 pading_0 :21; + u32 PalSelect : 6; + } ; + u32 full; +}; + +/// END Texture Control Word +#pragma pack(pop) + +//generic vertex storage type +struct Vertex +{ + float x,y,z; + + u8 col[4]; + u8 spc[4]; + + float u,v; + + // Two volumes format + u8 col1[4]; + u8 spc1[4]; + + float u1,v1; +}; + +enum PixelFormat +{ + Pixel1555 = 0, + Pixel565 = 1, + Pixel4444 = 2, + PixelYUV = 3, + PixelBumpMap = 4, + PixelPal4 = 5, + PixelPal8 = 6, + PixelReserved = 7 +}; diff --git a/vendor/emu/refsw/pvr_mem.cpp b/vendor/emu/refsw/pvr_mem.cpp new file mode 100644 index 00000000..ecd31094 --- /dev/null +++ b/vendor/emu/refsw/pvr_mem.cpp @@ -0,0 +1,69 @@ +/* + This file was part of libswirl +*/ +//#include "license/bsd" + +#include "emu/types.h" +#include "pvr_mem.h" + +//Regs + +//vram 32-64b +alignas(16*1024*1024) u8 emu_vram[VRAM_SIZE]; + +//read + +u16 pvr_read_area1_16(u32 addr) +{ + return *(u16*)&emu_vram[pvr_map32(addr)]; +} +u32 pvr_read_area1_32(u32 addr) +{ + return *(u32*)&emu_vram[pvr_map32(addr)]; +} + +//write + +void pvr_write_area1_16(u32 addr,u16 data) +{ + *(u16*)&emu_vram[pvr_map32(addr)]=data; +} +void pvr_write_area1_32(u32 addr,u32 data) +{ + *(u32*)&emu_vram[pvr_map32(addr)] = data; +} + +#define VRAM_BANK_BIT 0x400000 + +uint32_t pvr_map32(uint32_t offset32) +{ + //64b wide bus is achieved by interleaving the banks every 32 bits + //const u32 bank_bit = VRAM_BANK_BIT; + const u32 static_bits = (VRAM_MASK - (VRAM_BANK_BIT * 2 - 1)) | 3; + const u32 offset_bits = (VRAM_BANK_BIT - 1) & ~3; + + u32 bank = (offset32 & VRAM_BANK_BIT) / VRAM_BANK_BIT; + + u32 rv = offset32 & static_bits; + + rv |= (offset32 & offset_bits) * 2; + + rv |= bank * 4; + + return rv; +} + + +f32 vrf(u32 addr) +{ + return *(f32*)&emu_vram[pvr_map32(addr)]; +} +u32 vri(u32 addr) +{ + return *(u32*)&emu_vram[pvr_map32(addr)]; +} + +u32* vrp(u32 addr) +{ + return (u32*)&emu_vram[pvr_map32(addr)]; +} \ No newline at end of file diff --git a/vendor/emu/refsw/pvr_mem.h b/vendor/emu/refsw/pvr_mem.h new file mode 100644 index 00000000..952eb9cd --- /dev/null +++ b/vendor/emu/refsw/pvr_mem.h @@ -0,0 +1,28 @@ +/* + This file is part of libswirl +*/ +#include "license/bsd" + + +#pragma once +#include "emu/emu.h" +#include "emu/types.h" + +#define VRAM_SIZE PVR_RAM_SIZE +#define VRAM_MASK (VRAM_SIZE - 1) + +uint32_t pvr_map32(uint32_t offset32); +f32 vrf(u32 addr); +u32 vri(u32 addr); +u32* vrp(u32 addr); + +//read +u16 pvr_read_area1_16(u32 addr); +u32 pvr_read_area1_32(u32 addr); +//write + +void pvr_write_area1_16(u32 addr,u16 data); +void pvr_write_area1_32(u32 addr,u32 data); + +//registers +#define PVR_BASE 0x005F8000 \ No newline at end of file diff --git a/vendor/emu/refsw/pvr_regs.cpp b/vendor/emu/refsw/pvr_regs.cpp new file mode 100644 index 00000000..a63c598e --- /dev/null +++ b/vendor/emu/refsw/pvr_regs.cpp @@ -0,0 +1,194 @@ +/* + This file is part of libswirl +*/ +//#include "license/bsd" + +#include + +#include "emu/types.h" +#include "pvr_regs.h" + +#include "lxdream/tacore.h" + +#include "refsw_tile.h" +#include "TexUtils.h" + +#include +void pvr_int_handler(uint32 code, void *data); + +u8 pvr_regs[pvr_RegSize]; + +#include "dc/pvr.h" + + +uint32 pvrRegRead(uint32 addr) { + assert(addr < 0x8000 && !(addr&3)); + return PvrReg(addr, u32); +} + +volatile bool frameDump = false; + +void pvrRegWrite(uint32 addr, uint32 data) { + assert(addr < 0x8000 && !(addr&3)); + + // printf("pvrRegWrite: %4X <= %08X\n", addr, data); + + if (addr == ID_addr) + return;//read only + if (addr == REVISION_addr) + return;//read only + if (addr == TA_YUV_TEX_CNT_addr) + return;//read only + + if (addr == STARTRENDER_addr) + { + if (frameDump) { + frameDump = false; + printf("doing dump... likely to not work for 16mb vram ...\n"); + { + FILE* v0 = fopen("vram.bin", "wb"); + for (size_t i = 0; i < VRAM_SIZE; i += 4) + { + auto v = vri(i); + fwrite(&v, sizeof(v), 1, v0); + } + fclose(v0); + } + + { + FILE* regs = fopen("pvr_regs", "wb"); + fwrite(pvr_regs, sizeof(pvr_regs), 1, regs); + fclose(regs); + } + } + // //start render + // rend_start_render(vram); + RenderCORE(); + pvr_queue_interrupt(ASIC_EVT_PVR_RENDERDONE_TSP); + return; + } + + if (addr == TA_LIST_INIT_addr) + { + // FIXME: Fuller TA implementation + // This is needed because of how KOS sets the bgpoly + // TA_ISP_CURRENT = TA_ISP_BASE; + if (data >> 31) + { + lxd_ta_init(emu_vram); + } + } + + if (addr == SOFTRESET_addr) + { + if (data != 0) + { + if (data & 1) + { + lxd_ta_reset(); + } + data = 0; + } + } + + if (addr == TA_LIST_CONT_addr) + { + //a write of anything works ? + assert(false && "lxdream list_cont?"); + } + + // if (addr == SPG_CONTROL_addr || addr == SPG_LOAD_addr) + // { + // if (PvrReg(addr, u32) != data) + // { + // PvrReg(addr, u32) = data; + // spg->CalculateSync(); + // } + // return; + // } + // if (addr == FB_R_CTRL_addr) + // { + // bool vclk_div_changed = (PvrReg(addr, u32) ^ data) & (1 << 23); + // PvrReg(addr, u32) = data; + // if (vclk_div_changed) + // spg->CalculateSync(); + // return; + // } + // if (addr == FB_R_SIZE_addr) + // { + // if (PvrReg(addr, u32) != data) + // { + // PvrReg(addr, u32) = data; + // fb_dirty = false; + // pvr_update_framebuffer_watches(); + // } + // return; + // } + // if (addr == TA_YUV_TEX_BASE_addr || addr == TA_YUV_TEX_CTRL_addr) + // { + // PvrReg(addr, u32) = data; + // YUV_init(asic); + // return; + // } + + // if (addr >= PALETTE_RAM_START_addr && PvrReg(addr, u32) != data) + // { + // pal_needs_update = true; + // } + + // if (addr >= FOG_TABLE_START_addr && addr <= FOG_TABLE_END_addr && PvrReg(addr, u32) != data) + // { + // fog_needs_update = true; + // } + PvrReg(addr, u32) = data; + +} + +void pvrInit() { + BuildTables(); + ID = 0x17FD11DB; + REVISION = 0x00000011; + SOFTRESET = 0x00000007; + SPG_HBLANK_INT.full = 0x031D0000; + SPG_VBLANK_INT.full = 0x01500104; + FPU_PARAM_CFG.full = 0x0007DF77; + HALF_OFFSET.full = 0x00000007; + ISP_FEED_CFG.full = 0x00402000; + SDRAM_REFRESH = 0x00000020; + SDRAM_ARB_CFG = 0x0000001F; + SDRAM_CFG = 0x15F28997; + SPG_HBLANK.full = 0x007E0345; + SPG_LOAD.full = 0x01060359; + SPG_VBLANK.full = 0x01500104; + SPG_WIDTH.full = 0x07F1933F; + VO_CONTROL.full = 0x00000108; + VO_STARTX.full = 0x0000009D; + VO_STARTY.full = 0x00000015; + SCALER_CTL.full = 0x00000400; + FB_BURSTCTRL.full = 0x00090639; + PT_ALPHA_REF = 0x000000FF; +} + +void pvr_ta_data(void* data, int size) { + lxd_ta_write((unsigned char*)data, size); +} + +void pvr_int_handler(uint32 code, void *data); +#include +static std::queue interrupts; +static bool inInterrupt; +void pvr_queue_interrupt(int interrupt) { + if (inInterrupt == false) { + inInterrupt = true; + pvr_int_handler(interrupt, nullptr); + while(interrupts.size()) { + auto ni = interrupts.front(); + interrupts.pop(); + pvr_int_handler(ni, nullptr); + } + inInterrupt = false; + } else { + interrupts.push(interrupt); + } + +} \ No newline at end of file diff --git a/vendor/emu/refsw/pvr_regs.h b/vendor/emu/refsw/pvr_regs.h new file mode 100644 index 00000000..b2987437 --- /dev/null +++ b/vendor/emu/refsw/pvr_regs.h @@ -0,0 +1,571 @@ +/* + This file is part of libswirl +*/ +#include "license/bsd" + + +#pragma once +#include "emu/types.h" + +#define pvr_RegSize (0x2000) +#define pvr_RegMask (pvr_RegSize-1) + +#define PvrReg(x,t) (*(t*)&pvr_regs[(x) & pvr_RegMask]) + +extern u8 pvr_regs[pvr_RegSize]; + +// Reg addresses +#define ID_addr 0x00000000 // R Device ID +#define REVISION_addr 0x00000004 // R Revision number +#define SOFTRESET_addr 0x00000008 // RW CORE & TA software reset + +#define STARTRENDER_addr 0x00000014 // RW Drawing start +#define TEST_SELECT_addr 0x00000018 // RW Test (writing this register is prohibited) + +#define PARAM_BASE_addr 0x00000020 // RW Base address for ISP parameters + +#define REGION_BASE_addr 0x0000002C // RW Base address for Region Array +#define SPAN_SORT_CFG_addr 0x00000030 // RW Span Sorter control + +#define VO_BORDER_COL_addr 0x00000040 // RW Border area color +#define FB_R_CTRL_addr 0x00000044 // RW Frame buffer read control +#define FB_W_CTRL_addr 0x00000048 // RW Frame buffer write control +#define FB_W_LINESTRIDE_addr 0x0000004C // RW Frame buffer line stride +#define FB_R_SOF1_addr 0x00000050 // RW Read start address for field - 1/strip - 1 +#define FB_R_SOF2_addr 0x00000054 // RW Read start address for field - 2/strip - 2 + +#define FB_R_SIZE_addr 0x0000005C // RW Frame buffer XY size +#define FB_W_SOF1_addr 0x00000060 // RW Write start address for field - 1/strip - 1 +#define FB_W_SOF2_addr 0x00000064 // RW Write start address for field - 2/strip - 2 +#define FB_X_CLIP_addr 0x00000068 // RW Pixel clip X coordinate +#define FB_Y_CLIP_addr 0x0000006C // RW Pixel clip Y coordinate + + +#define FPU_SHAD_SCALE_addr 0x00000074 // RW Intensity Volume mode +#define FPU_CULL_VAL_addr 0x00000078 // RW Comparison value for culling +#define FPU_PARAM_CFG_addr 0x0000007C // RW Parameter read control +#define HALF_OFFSET_addr 0x00000080 // RW Pixel sampling control +#define FPU_PERP_VAL_addr 0x00000084 // RW Comparison value for perpendicular polygons +#define ISP_BACKGND_D_addr 0x00000088 // RW Background surface depth +#define ISP_BACKGND_T_addr 0x0000008C // RW Background surface tag + +#define ISP_FEED_CFG_addr 0x00000098 // RW Translucent polygon sort mode + +#define SDRAM_REFRESH_addr 0x000000A0 // RW Texture memory refresh counter +#define SDRAM_ARB_CFG_addr 0x000000A4 // RW Texture memory arbiter control +#define SDRAM_CFG_addr 0x000000A8 // RW Texture memory control + +#define FOG_COL_RAM_addr 0x000000B0 // RW Color for Look Up table Fog +#define FOG_COL_VERT_addr 0x000000B4 // RW Color for vertex Fog +#define FOG_DENSITY_addr 0x000000B8 // RW Fog scale value +#define FOG_CLAMP_MAX_addr 0x000000BC // RW Color clamping maximum value +#define FOG_CLAMP_MIN_addr 0x000000C0 // RW Color clamping minimum value +#define SPG_TRIGGER_POS_addr 0x000000C4 // RW External trigger signal HV counter value +#define SPG_HBLANK_INT_addr 0x000000C8 // RW H-blank interrupt control +#define SPG_VBLANK_INT_addr 0x000000CC // RW V-blank interrupt control +#define SPG_CONTROL_addr 0x000000D0 // RW Sync pulse generator control +#define SPG_HBLANK_addr 0x000000D4 // RW H-blank control +#define SPG_LOAD_addr 0x000000D8 // RW HV counter load value +#define SPG_VBLANK_addr 0x000000DC // RW V-blank control +#define SPG_WIDTH_addr 0x000000E0 // RW Sync width control +#define TEXT_CONTROL_addr 0x000000E4 // RW Texturing control +#define VO_CONTROL_addr 0x000000E8 // RW Video output control +#define VO_STARTX_addr 0x000000Ec // RW Video output start X position +#define VO_STARTY_addr 0x000000F0 // RW Video output start Y position +#define SCALER_CTL_addr 0x000000F4 // RW X & Y scaler control +#define PAL_RAM_CTRL_addr 0x00000108 // RW Palette RAM control +#define SPG_STATUS_addr 0x0000010C // R Sync pulse generator status +#define FB_BURSTCTRL_addr 0x00000110 // RW Frame buffer burst control +#define FB_C_SOF_addr 0x00000114 // R Current frame buffer start address +#define Y_COEFF_addr 0x00000118 // RW Y scaling coefficient + +#define PT_ALPHA_REF_addr 0x0000011C // RW Alpha value for Punch Through polygon comparison + +// TA REGS +#define TA_OL_BASE_addr 0x00000124 // RW Object list write start address +#define TA_ISP_BASE_addr 0x00000128 // RW ISP/TSP Parameter write start address +#define TA_OL_LIMIT_addr 0x0000012C // RW Start address of next Object Pointer Block +#define TA_ISP_LIMIT_addr 0x00000130 // RW Current ISP/TSP Parameter write address +#define TA_NEXT_OPB_addr 0x00000134 // R Global Tile clip control +#define TA_ISP_CURRENT_addr 0x00000138 // R Current ISP/TSP Parameter write address +#define TA_GLOB_TILE_CLIP_addr 0x0000013C // RW Global Tile clip control +#define TA_ALLOC_CTRL_addr 0x00000140 // RW Object list control +#define TA_LIST_INIT_addr 0x00000144 // RW TA initialization +#define TA_YUV_TEX_BASE_addr 0x00000148 // RW YUV422 texture write start address +#define TA_YUV_TEX_CTRL_addr 0x0000014C // RW YUV converter control +#define TA_YUV_TEX_CNT_addr 0x00000150 // R YUV converter macro block counter value + +#define TA_LIST_CONT_addr 0x00000160 // RW TA continuation processing +#define TA_NEXT_OPB_INIT_addr 0x00000164 // RW Additional OPB starting address + + + +#define FOG_TABLE_START_addr 0x00000200 // RW Look-up table Fog data +#define FOG_TABLE_END_addr 0x000003FC + +#define TA_OL_POINTERS_START_addr 0x00000600 // R TA object List Pointer data +#define TA_OL_POINTERS_END_addr 0x00000F5C + +#define PALETTE_RAM_START_addr 0x00001000 // RW Palette RAM +#define PALETTE_RAM_END_addr 0x00001FFC + +// Reg types + +union FB_R_CTRL_type +{ + struct + { + u32 fb_enable : 1; //0 + u32 fb_line_double : 1; //1 + u32 fb_depth : 2; //3-2 + u32 fb_concat : 3; //6-4 + u32 R : 1; //7 + u32 fb_chroma_threshold : 8; //15-8 + u32 fb_stripsize : 6; //21-16 + u32 fb_strip_buf_en : 1; //22 + u32 vclk_div : 1; //23 + u32 Reserved : 8; //31-24 + }; + u32 full; +}; +enum fb_depth_enum +{ + fbde_0555 = 0, //0555, lower 3 bits on fb_concat + fbde_565 = 1, //565, lower 3 bits on fb_concat, [1:0] for G + fbde_888 = 2, //888, packed + fbde_C888 = 3, //C888, first byte used for chroma +}; +union FB_R_SIZE_type +{ + struct + { + u32 fb_x_size : 10; //0 + u32 fb_y_size : 10; //10 + u32 fb_modulus : 10; //20 + u32 fb_res : 2; //30 + }; + u32 full; +}; +union VO_BORDER_COL_type +{ + struct + { + u32 Blue : 8; //0 + u32 Green : 8; //8 + u32 Red : 8; //16 + u32 Chroma : 1; //24 + u32 res : 7; //25 + }; + u32 full; +}; + + +union SPG_STATUS_type +{ + struct + { + u32 scanline : 10; //9-0 + u32 fieldnum : 1; //10 + u32 blank : 1; //11 + u32 hsync : 1; //12 + u32 vsync : 1; //13 + u32 res : 18; //31-14 + }; + u32 full; +}; + +union FB_BURSTCTRL_type +{ + struct + { + u32 vid_burst : 6; + u32 res : 2; + u32 vid_lat : 6; + u32 res1 : 2; + u32 wr_burst : 4; + u32 res2 : 12; + }; + u32 full; +}; + +union SPG_HBLANK_INT_type +{ + struct + { + u32 line_comp_val : 10; //9-0 + u32 res1 : 2; //10-11 + u32 hblank_int_mode: 2; //12-13 + u32 res2 : 2; //14-15 + u32 hblank_in_interrupt : 10; //25-16 + u32 res3 : 6; //31-26 + }; + u32 full; +}; + +union SPG_VBLANK_INT_type +{ + struct + { + u32 vblank_in_interrupt_line_number : 10;//9-0 + u32 res : 6 ; //15-10 + u32 vblank_out_interrupt_line_number : 10;//25-16 + u32 res1 : 6 ; //31-26 + }; + u32 full; +}; +union SPG_CONTROL_type +{ + struct + { + u32 mhsync_pol : 1; //0 + u32 mvsync_pol : 1; //1 + u32 mcsync_pol : 1; //2 + u32 spg_lock : 1; //3 + u32 interlace : 1; //4 + u32 force_field2 : 1; //5 + u32 NTSC : 1; //6 + u32 PAL : 1; //7 + u32 sync_direction : 1; //8 + u32 csync_on_h : 1; //9 + u32 Reserved : 22; //31-10 + }; + u32 full; +}; +union SPG_HBLANK_type +{ + struct + { + u32 hbstart : 10;//9-0 + u32 res : 6; //15-10 + u32 hbend : 10;//25-16 + u32 res1 : 6; //31-26 + }; + u32 full; +}; + +union SPG_LOAD_type +{ + struct + { + u32 hcount : 10; //9-0 + u32 res : 6 ; //15-10 + u32 vcount : 10; //25-16 + u32 res1 : 6 ; //31-26 + }; + u32 full; +}; + +union SPG_VBLANK_type +{ + struct + { + u32 vbstart : 10; //9-0 + u32 res : 6 ; //15-10 + u32 vbend : 10; //25-16 + u32 res1 : 6 ; //31-26 + }; + u32 full; +}; + +union SPG_WIDTH_type +{ + struct + { + u32 hswidth : 7; //6-0 + u32 res : 1; //7-7 + u32 vswidth : 4; //8-11 + u32 bpwidth : 10; //21-12 + u32 eqwidth : 10; //31-22 + }; + u32 full; +}; + +union SCALER_CTL_type +{ + struct + { + u32 vscalefactor : 16;//15-0 + u32 hscale : 1; //16-16 + u32 interlace : 1; //17-17 + u32 fieldselect : 1; //18-18 + }; + u32 full; +}; + +union FB_X_CLIP_type +{ + struct + { + u32 min : 11; + u32 pad1 : 5; + u32 max : 11; + u32 pad : 5; + }; + u32 full; +}; + +union FB_Y_CLIP_type +{ + struct + { + u32 min : 10; //15-0 + u32 pad1 : 6 ; //16-16 + u32 max : 10; //17-17 + u32 pad : 6; //18-18 + }; + u32 full; +}; + +union VO_CONTROL_type +{ + struct + { + u32 hsync_pol : 1; //0 + u32 vsync_pol : 1; //1 + u32 blank_pol : 1; //2 + u32 blank_video : 1; //3 + u32 field_mode : 4; //4 + u32 pixel_double : 1; //8 + u32 res_1 : 7; //9 + u32 pclk_delay : 6; //16 + u32 res_2 : 10; //22 + }; + u32 full; +}; + +union VO_STARTX_type +{ + struct + { + u32 HStart : 10; //0 + u32 res_1 : 22; //10 + }; + u32 full; +}; +union VO_STARTY_type +{ + struct + { + u32 VStart_field1:10; //0 + u32 res_1:6; //10 + u32 VStart_field2:10; //16 + u32 res_2:6; //26 + }; + u32 full; +}; + +union ISP_BACKGND_D_type +{ + u32 i; + f32 f; +}; + +union ISP_BACKGND_T_type +{ + struct + { + u32 tag_offset : 3; + u32 param_offs_in_words : 21; + u32 skip : 3; + u32 shadow : 1; + u32 cache_bypass : 1; + u32 _pad : 2; + u32 _invalid : 1; + }; + u32 full; +}; + +union ISP_FEED_CFG_type +{ + struct + { + u32 pre_sort : 1; + u32 res : 2; + u32 discard_mode : 1; + u32 pt_chunk_size : 10; + u32 tr_cache_size : 10; + u32 res2 : 8; + }; + u32 full; +}; + +union FB_W_CTRL_type +{ + struct + { + u32 fb_packmode : 3; + u32 fb_dither : 1; + u32 pad0 : 4; + u32 fb_kval : 8; + u32 fb_alpha_threshold : 8; + u32 pad1 : 8; + }; + u32 full; +}; + +union FB_W_LINESTRIDE_type +{ + struct + { + u32 stride : 9; + u32 pad0 : 23; + }; + u32 full; +}; + +union FPU_SHAD_SCALE_type +{ + struct + { + u32 scale_factor : 8; + u32 intensity_shadow : 1; + }; + u32 full; +}; + +union FPU_PARAM_CFG_type +{ + struct + { + u32 pointer_first_burst : 4; + u32 pointer_burst : 4; + u32 isp_param_burst_threshold : 6; + u32 tsp_param_burst_threshold : 6; + u32 res : 1; + u32 region_header_type : 1; + u32 res1 : 10; + }; + u32 full; +}; + +union HALF_OFFSET_type +{ + struct + { + u32 fpu_pixel_half_offset : 1; + u32 tsp_pixel_half_offset : 1; + u32 texure_pixel_half_offset : 1; + }; + u32 full; +}; + +union TA_GLOB_TILE_CLIP_type +{ + struct + { + u32 tile_x_num : 6; + u32 reserved : 10; + u32 tile_y_num : 4; + u32 reserved2 : 12; + }; + u32 full; +}; + +union TA_YUV_TEX_CTRL_type +{ + struct + { + u32 yuv_u_size : 6; + u32 reserved1 : 2; + u32 yuv_v_size : 6; + u32 reserved2 : 2; + u32 yuv_tex : 1; + u32 reserved3 : 7; + u32 yuv_form : 1; + u32 reserved4 : 7; + }; + u32 full; +}; + + +// Regs + +#define ID PvrReg(ID_addr,u32) // R Device ID +#define REVISION PvrReg(REVISION_addr,u32) // R Revision number +#define SOFTRESET PvrReg(SOFTRESET_addr,u32) // RW CORE & TA software reset + +#define STARTRENDER PvrReg(STARTRENDER_addr,u32) // RW Drawing start +#define TEST_SELECT PvrReg(TEST_SELECT_addr,u32) // RW Test (writing this register is prohibited) + +#define PARAM_BASE PvrReg(PARAM_BASE_addr,u32) // RW Base address for ISP parameters + +#define REGION_BASE PvrReg(REGION_BASE_addr,u32) // RW Base address for Region Array +#define SPAN_SORT_CFG PvrReg(SPAN_SORT_CFG_addr,u32) // RW Span Sorter control + +#define VO_BORDER_COL PvrReg(VO_BORDER_COL_addr,VO_BORDER_COL_type) // RW Border area color +#define FB_R_CTRL PvrReg(FB_R_CTRL_addr,FB_R_CTRL_type) // RW Frame buffer read control +#define FB_W_CTRL PvrReg(FB_W_CTRL_addr,FB_W_CTRL_type) // RW Frame buffer write control +#define FB_W_LINESTRIDE PvrReg(FB_W_LINESTRIDE_addr,FB_W_LINESTRIDE_type) // RW Frame buffer line stride +#define FB_R_SOF1 PvrReg(FB_R_SOF1_addr,u32) // RW Read start address for field - 1/strip - 1 +#define FB_R_SOF2 PvrReg(FB_R_SOF2_addr,u32) // RW Read start address for field - 2/strip - 2 + +#define FB_R_SIZE PvrReg(FB_R_SIZE_addr,FB_R_SIZE_type) // RW Frame buffer XY size +#define FB_W_SOF1 PvrReg(FB_W_SOF1_addr,u32) // RW Write start address for field - 1/strip - 1 +#define FB_W_SOF2 PvrReg(FB_W_SOF2_addr,u32) // RW Write start address for field - 2/strip - 2 +#define FB_X_CLIP PvrReg(FB_X_CLIP_addr,FB_X_CLIP_type) // RW Pixel clip X coordinate +#define FB_Y_CLIP PvrReg(FB_Y_CLIP_addr,FB_Y_CLIP_type) // RW Pixel clip Y coordinate + + +#define FPU_SHAD_SCALE PvrReg(FPU_SHAD_SCALE_addr,FPU_SHAD_SCALE_type) // RW Intensity Volume mode +#define FPU_CULL_VAL PvrReg(FPU_CULL_VAL_addr,f32) // RW Comparison value for culling +#define FPU_PARAM_CFG PvrReg(FPU_PARAM_CFG_addr,FPU_PARAM_CFG_type) // RW Parameter read control +#define HALF_OFFSET PvrReg(HALF_OFFSET_addr,HALF_OFFSET_type) // RW Pixel sampling control +#define FPU_PERP_VAL PvrReg(FPU_PERP_VAL_addr,u32) // RW Comparison value for perpendicular polygons +#define ISP_BACKGND_D PvrReg(ISP_BACKGND_D_addr,ISP_BACKGND_D_type) // RW Background surface depth +#define ISP_BACKGND_T PvrReg(ISP_BACKGND_T_addr,ISP_BACKGND_T_type) // RW Background surface tag + +#define ISP_FEED_CFG PvrReg(ISP_FEED_CFG_addr,ISP_FEED_CFG_type) // RW Translucent polygon sort mode + +#define SDRAM_REFRESH PvrReg(SDRAM_REFRESH_addr,u32) // RW Texture memory refresh counter +#define SDRAM_ARB_CFG PvrReg(SDRAM_ARB_CFG_addr,u32) // RW Texture memory arbiter control +#define SDRAM_CFG PvrReg(SDRAM_CFG_addr,u32) // RW Texture memory control + +#define FOG_COL_RAM PvrReg(FOG_COL_RAM_addr,u32) // RW Color for Look Up table Fog +#define FOG_COL_VERT PvrReg(FOG_COL_VERT_addr,u32) // RW Color for vertex Fog +#define FOG_DENSITY PvrReg(FOG_DENSITY_addr,u32) // RW Fog scale value +#define FOG_CLAMP_MAX PvrReg(FOG_CLAMP_MAX_addr,u32) // RW Color clamping maximum value +#define FOG_CLAMP_MIN PvrReg(FOG_CLAMP_MIN_addr,u32) // RW Color clamping minimum value +#define SPG_TRIGGER_POS PvrReg(SPG_TRIGGER_POS_addr,u32) // RW External trigger signal HV counter value +#define SPG_HBLANK_INT PvrReg(SPG_HBLANK_INT_addr,SPG_HBLANK_INT_type) // RW H-blank interrupt control +#define SPG_VBLANK_INT PvrReg(SPG_VBLANK_INT_addr,SPG_VBLANK_INT_type) // RW V-blank interrupt control +#define SPG_CONTROL PvrReg(SPG_CONTROL_addr,SPG_CONTROL_type) // RW pulse generator control +#define SPG_HBLANK PvrReg(SPG_HBLANK_addr,SPG_HBLANK_type) // RW H-blank control +#define SPG_LOAD PvrReg(SPG_LOAD_addr,SPG_LOAD_type) // RW HV counter load value +#define SPG_VBLANK PvrReg(SPG_VBLANK_addr,SPG_VBLANK_type) // RW V-blank control +#define SPG_WIDTH PvrReg(SPG_WIDTH_addr,SPG_WIDTH_type) // RW Sync width control +#define TEXT_CONTROL PvrReg(TEXT_CONTROL_addr,u32) // RW Texturing control +#define VO_CONTROL PvrReg(VO_CONTROL_addr,VO_CONTROL_type) // RW Video output control +#define VO_STARTX PvrReg(VO_STARTX_addr,VO_STARTX_type) // RW Video output start X position +#define VO_STARTY PvrReg(VO_STARTY_addr,VO_STARTY_type) // RW Video output start Y position +#define SCALER_CTL PvrReg(SCALER_CTL_addr,SCALER_CTL_type) // RW X & Y scaler control +#define PAL_RAM_CTRL PvrReg(PAL_RAM_CTRL_addr,u32) // RW Palette RAM control +#define SPG_STATUS PvrReg(SPG_STATUS_addr,SPG_STATUS_type) // R Sync pulse generator status +#define FB_BURSTCTRL PvrReg(FB_BURSTCTRL_addr,FB_BURSTCTRL_type) // RW Frame buffer burst control +#define FB_C_SOF PvrReg(FB_C_SOF_addr,u32) // R Current frame buffer start address +#define Y_COEFF PvrReg(Y_COEFF_addr,u32) // RW Y scaling coefficient + +#define PT_ALPHA_REF PvrReg(PT_ALPHA_REF_addr,u32) // RW Alpha value for Punch Through polygon comparison + + + +// TA REGS +#define TA_OL_BASE PvrReg(TA_OL_BASE_addr,u32) // RW Object list write start address +#define TA_ISP_BASE PvrReg(TA_ISP_BASE_addr,u32) // RW ISP/TSP Parameter write start address +#define TA_OL_LIMIT PvrReg(TA_OL_LIMIT_addr,u32) // RW Start address of next Object Pointer Block +#define TA_ISP_LIMIT PvrReg(TA_ISP_LIMIT_addr,u32) // RW Current ISP/TSP Parameter write address +#define TA_NEXT_OPB PvrReg(TA_NEXT_OPB_addr,u32) // R Global Tile clip control +#define TA_ISP_CURRENT PvrReg(TA_ISP_CURRENT_addr,u32) // R Current ISP/TSP Parameter write address +#define TA_GLOB_TILE_CLIP PvrReg(TA_GLOB_TILE_CLIP_addr, TA_GLOB_TILE_CLIP_type) // RW Global Tile clip control +#define TA_ALLOC_CTRL PvrReg(TA_ALLOC_CTRL_addr,u32) // RW Object list control +#define TA_LIST_INIT PvrReg(TA_LIST_INIT_addr,u32) // RW TA initialization +#define TA_YUV_TEX_BASE PvrReg(TA_YUV_TEX_BASE_addr,u32) // RW YUV422 texture write start address +#define TA_YUV_TEX_CTRL PvrReg(TA_YUV_TEX_CTRL_addr, TA_YUV_TEX_CTRL_type) // RW YUV converter control +#define TA_YUV_TEX_CNT PvrReg(TA_YUV_TEX_CNT_addr,u32) // R YUV converter macro block counter value + +#define TA_LIST_CONT PvrReg(TA_LIST_CONT_addr,u32) // RW TA continuation processing +#define TA_NEXT_OPB_INIT PvrReg(TA_NEXT_OPB_INIT_addr,u32) // RW Additional OPB starting address + + +#define FOG_TABLE (&PvrReg(FOG_TABLE_START_addr,u32)) // RW Look-up table Fog data +#define TA_OL_POINTERS (&PvrReg(TA_OL_POINTERS_START_addr),u32) // R TA object List Pointer data +#define PALETTE_RAM (&PvrReg(PALETTE_RAM_START_addr,u32)) // RW Palette RAM diff --git a/vendor/emu/refsw/refsw_lists.cpp b/vendor/emu/refsw/refsw_lists.cpp new file mode 100644 index 00000000..afe444e8 --- /dev/null +++ b/vendor/emu/refsw/refsw_lists.cpp @@ -0,0 +1,633 @@ +/* + This file is part of libswirl +*/ +//#include "license/bsd" + +/* + REFSW: Reference-style software rasterizer + + An attempt to model CLX2's CORE/SPG/RAMDAC at the lowest functional level + + Rasterizer structure + === + + Reads tile lists in CORE format, generated from a LLE TA implementation or software running from sh4, + renders them in 32x32 tiles, reads out to VRAM and displays framebuffer from VRAM. + + CORE high level overview + === + + CORE Renders based on the REGION ARRAY, which is a flag-terminated list of tiles. Each RegionArrayEntry + contains the TILE x/y position, control flags for Z clear/Write out/Presort and pointers to OBJECT LISTS. + + OBJECT LISTS are inline linked lists containing ObjectListEntries. Each ObjectListEntry has a small + descriptor for the entry type and vertex size, and a pointer to the OBJECT DATA. + + OBJECT DATA contains the PARAMETERS for the OBJECT (ISP, TSP, TCW, optional TSP2 and TCW2) and vertixes. + + There are 3 OBJECT DATA TYPES + - Triangle Strips (PARAMETERS, up to 8 VTXs) x 1 + - Triangle Arrays (PARAMETERS, 3 vtx) x Num_of_primitives + - Quad Arrays (PARAMETERS, 4 vtx) x Num_of_primitives + + CORE renders the OBJECTS to its internal TILE BUFFERS, scales and filters the output (SCL) + and writes out to VRAM. + + CORE Rendering details + === + + CORE has four main components, FPU (triangle setup) ISP (Rasterization, depth, stencil), TSP (Texutre + Shading) + and SCL (tile writeout + scaling). There are three color rendering modes: DEPTH FIRST, DEPTH + COLOR and LAYER PEELING. + + OPAQUE OBJECTS are rendered using the DEPTH FIRST mode. + PUNCH THROUGH OBJECTS are rendered using the DEPTH + COLOR mode. + TRANSPARENT OBJECTS are rendered using either the DEPTH + COLOR mode or the LAYER PEELING mode. + + DEPTH FIRST mode + --- + OBJECTS are first rendered by ISP in the depth and tag buffers, 32 pixels (?) at a time. then the SPAN SORTER collects spans with the + same tag and sends them to TSP for shading processing, one pixel at a time. + + DEPTH + COLOR mode + --- + OBJECTS are rendered by ISP and TSP at the same time, one pixel (?) at a time. ALPHA TEST feedback from TSP modifies the Z-write behavior. + + LAYER PEELING mode + --- + + OBJECTS are first rendered by ISP in the depth and tag buffers, using a depth pass and a depth test buffer. SPAN SORTER collects spans with + the same tag and sends them to TSP for shading processing. The process repeats itself until all layers have been indepedently rendered. On + each pass, only the pixels with the lowest depth value that pass the depth pass buffer are rendered. In case of identical depth values, the + tag buffer is used to sort the pixels by tag as well as depth in order to support co-planar polygons. +*/ + + +#include "pvr_mem.h" +#include "TexUtils.h" + +#include +#include + +#include +#include +#include +#include + +#include "pvr_regs.h" + +// #include + +#include "refsw_lists.h" + +#include "refsw_tile.h" + +#define JLOG(...) +#define JLOG2(...) +#define V(x) x + +void log_vertex(const Vertex& v) { + JLOG(ll, + V(v.x), V(v.y), V(v.z), + V(v.col[0]), V(v.col[1]), V(v.col[2]), V(v.col[3]), V(v.spc[0]), V(v.spc[1]), V(v.spc[2]), V(v.spc[3]), V(v.u), V(v.v), + V(v.col1[0]), V(v.col1[1]), V(v.col1[2]), V(v.col1[3]), V(v.spc1[0]), V(v.spc1[1]), V(v.spc1[2]), V(v.spc1[3]), V(v.u1), V(v.v1) + ); +} + +/* + Main renderer class +*/ + +void RenderTriangle(RenderMode render_mode, DrawParameters* params, parameter_tag_t tag, const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex* v4, taRECT* area) +{ + JLOG(ll, V(render_mode), V(tag), "tsp0", params->tsp[0].full, "tcw0", params->tcw[0].full, "tsp1", params->tsp[1].full, "tcw1", params->tcw[1].full); + + log_vertex(v1); + log_vertex(v2); + log_vertex(v3); + if (v4) { + log_vertex(*v4); + } + + RasterizeTriangle(render_mode, params, tag, v1, v2, v3, v4, area); + + if (render_mode == RM_MODIFIER) + { + // 0 normal polygon, 1 inside last, 2 outside last + if (params->isp.modvol.VolumeMode == 1 ) + { + SummarizeStencilOr(); + } + else if (params->isp.modvol.VolumeMode == 2) + { + SummarizeStencilAnd(); + } + } +} + +u32 ReadRegionArrayEntry(u32 base, RegionArrayEntry* entry) +{ + bool fmt_v1 = FPU_PARAM_CFG.region_header_type == 0; + + entry->control.full = vri(base); + entry->opaque.full = vri(base + 4); + entry->opaque_mod.full = vri(base + 8); + entry->trans.full = vri(base + 12); + entry->trans_mod.full = vri(base + 16); + + + u32 rv; + if (fmt_v1) + { + entry->puncht.full = 0x80000000; + rv = 5 * 4; + } + else + { + entry->puncht.full = vri(base + 20); + rv = 6 * 4; + } + + return rv; +} + +#define vert_packed_color_(to,src) \ + { \ + u32 t=src; \ + to[0] = (u8)(t);t>>=8;\ + to[1] = (u8)(t);t>>=8;\ + to[2] = (u8)(t);t>>=8;\ + to[3] = (u8)(t); \ + } + +ISP_BACKGND_T_type CoreTagFromDesc(u32 cache_bypass, u32 shadow, u32 skip, u32 param_offs_in_words, u32 tag_offset) { + ISP_BACKGND_T_type rv { + .tag_offset = tag_offset, + .param_offs_in_words = param_offs_in_words, + .skip = skip, + .shadow = shadow, + .cache_bypass = cache_bypass + }; + + return rv; +} + +// render a triangle strip object list entry +void RenderTriangleStrip(RenderMode render_mode, ObjectListEntry obj, taRECT* rect) +{ + JLOG(ll, V(render_mode), "obj", obj.full); + + Vertex vtx[8]; + DrawParameters params; + + u32 param_base = PARAM_BASE & 0xF00000; + + u32 tag_address = param_base + obj.tstrip.param_offs_in_words * 4; + + bool two_volumes = obj.tstrip.shadow & ~FPU_SHAD_SCALE.intensity_shadow; + decode_pvr_vertices(¶ms, tag_address, obj.tstrip.skip, two_volumes, vtx, 8, 0); + + for (int i = 0; i < 6; i++) + { + if (obj.tstrip.mask & (1 << (5-i))) + { + parameter_tag_t tag = CoreTagFromDesc(params.isp.CacheBypass, obj.tstrip.shadow, obj.tstrip.skip, obj.tstrip.param_offs_in_words, i).full; + + int not_even = i&1; + int even = not_even ^ 1; + + RenderTriangle(render_mode, ¶ms, tag, vtx[i+not_even], vtx[i+even], vtx[i+2], nullptr, rect); + } + } +} + + +// render a triangle array object list entry +void RenderTriangleArray(RenderMode render_mode, ObjectListEntry obj, taRECT* rect) +{ + JLOG(ll, V(render_mode), "obj", obj.full); + auto triangles = obj.tarray.prims + 1; + u32 param_base = PARAM_BASE & 0xF00000; + + + u32 param_ptr = param_base + obj.tarray.param_offs_in_words * 4; + bool two_volumes = obj.tstrip.shadow & ~FPU_SHAD_SCALE.intensity_shadow; + + for (int i = 0; i> 3) & 0x1F) << 0) | (((src[1] >> 2) & 0x3F) << 5) | (((src[2] >> 3) & 0x1F) << 11); + pvr_write_area1_16(dst, pixel); + } + else { + auto pixel = src[0] + src[1] * 256U + src[2] * 256U * 256U + src[3] * 256U * 256U * 256U; + pvr_write_area1_32(dst, pixel); + } + + + dst += bpp; + src += 4; // skip alpha + } + } + } + + // clear the tsp cache + ClearFpuEntries(); + } while (!entry.control.last_region); +} + +#include + +void Hackpresent() +{ + if (FB_R_SIZE.fb_x_size == 0 || FB_R_SIZE.fb_y_size == 0) + return; + + int width = (FB_R_SIZE.fb_x_size + 1) << 1; // in 16-bit words + int height = FB_R_SIZE.fb_y_size + 1; + int modulus = (FB_R_SIZE.fb_modulus - 1) << 1; + + int bpp; + switch (FB_R_CTRL.fb_depth) + { + case fbde_0555: + case fbde_565: + bpp = 2; + break; + case fbde_888: + bpp = 3; + //width = (width * 2) / 3; // in pixels + modulus = (modulus * 2) / 3; // in pixels + break; + case fbde_C888: + bpp = 4; + //width /= 2; // in pixels + modulus /= 2; // in pixels + break; + default: + die("Invalid framebuffer format\n"); + bpp = 4; + break; + } + + u32 addr = SCALER_CTL.interlace && SCALER_CTL.fieldselect ? FB_R_SOF2 : FB_R_SOF1; + + // printf("Presenting: %X\n", addr); + addr &= ~3; + + size_t pb_len = width * (SPG_CONTROL.interlace ? (height * 2 + 1) : height) * 4; + void* pb = calloc(1, pb_len); + + u8 *dst = (u8 *)pb; + + if (SPG_CONTROL.interlace & SPG_STATUS.fieldnum) { + dst += width * 4; + } + +#define RED_5 10 +#define BLUE_5 0 +#define RED_6 11 +#define BLUE_6 0 +#define RED_8 16 +#define BLUE_8 0 + + switch (FB_R_CTRL.fb_depth) + { + case fbde_0555: // 555 RGB + for (int y = 0; y < height; y++) + { + for (int i = 0; i < width; i++) + { + u16 src = pvr_read_area1_16(addr); + *dst++ = (((src >> BLUE_5) & 0x1F) << 3) + FB_R_CTRL.fb_concat; + *dst++ = (((src >> 5) & 0x1F) << 3) + FB_R_CTRL.fb_concat; + *dst++ = (((src >> RED_5) & 0x1F) << 3) + FB_R_CTRL.fb_concat; + *dst++ = 0xFF; + addr += bpp; + } + addr += modulus * bpp; + if (SPG_CONTROL.interlace) { + dst += width * 4; + } + } + break; + + case fbde_565: // 565 RGB + for (int y = 0; y < height; y++) + { + for (int i = 0; i < width; i++) + { + u16 src = pvr_read_area1_16(addr); + *dst++ = (((src >> BLUE_6) & 0x1F) << 3) + FB_R_CTRL.fb_concat; + *dst++ = (((src >> 5) & 0x3F) << 2) + (FB_R_CTRL.fb_concat >> 1); + *dst++ = (((src >> RED_6) & 0x1F) << 3) + FB_R_CTRL.fb_concat; + *dst++ = 0xFF; + addr += bpp; + } + addr += modulus * bpp; + + if (SPG_CONTROL.interlace) { + dst += width * 4; + } + } + break; + case fbde_888: // 888 RGB + for (int y = 0; y < height; y++) + { + for (int i = 0; i < width; i++) + { + if (addr & 1) + { + u32 src = pvr_read_area1_32(addr - 1); + *dst++ = src >> RED_8; + *dst++ = src >> 8; + *dst++ = src >> BLUE_8; + } + else + { + u32 src = pvr_read_area1_32(addr); + *dst++ = src >> (RED_8 + 8); + *dst++ = src >> 16; + *dst++ = src >> (BLUE_8 + 8); + } + *dst++ = 0xFF; + addr += bpp; + } + addr += modulus * bpp; + + if (SPG_CONTROL.interlace) { + dst += width * 4; + } + } + break; + case fbde_C888: // 0888 RGB + for (int y = 0; y < height; y++) + { + for (int i = 0; i < width; i++) + { + u32 src = pvr_read_area1_32(addr); + *dst++ = src >> RED_8; + *dst++ = src >> 8; + *dst++ = src >> BLUE_8; + *dst++ = 0xFF; + addr += bpp; + } + addr += modulus * bpp; + + if (SPG_CONTROL.interlace) { + dst += width * 4; + } + } + break; + } + { + extern Window x11_win; + extern Display* x11_disp; + extern Visual* x11_vis; + + extern int x11_width; + extern int x11_height; + XImage* ximage = XCreateImage(x11_disp, x11_vis, 24, ZPixmap, 0, (char*)pb, width, SPG_CONTROL.interlace ? height * 2 : height, 32, width * 4); + + GC gc = XCreateGC(x11_disp, x11_win, 0, 0); + XPutImage(x11_disp, x11_win, gc, ximage, 0, 0, (x11_width - width) / 2, (x11_height - (SPG_CONTROL.interlace ? height * 2 : height)) / 2, width, SPG_CONTROL.interlace ? height * 2 : height); + XFree(ximage); + XFreeGC(x11_disp, gc); + } + free(pb); +} + diff --git a/vendor/emu/refsw/refsw_lists.h b/vendor/emu/refsw/refsw_lists.h new file mode 100644 index 00000000..1cfce22f --- /dev/null +++ b/vendor/emu/refsw/refsw_lists.h @@ -0,0 +1,41 @@ +#pragma once +/* + This file is part of libswirl +*/ +#include "license/bsd" + +#include +#include "core_structs.h" // for ISP_TSP and co +#include "refsw_lists_regtypes.h" + +struct RegionArrayEntry { + RegionArrayEntryControl control; + ListPointer opaque; + ListPointer opaque_mod; + ListPointer trans; + ListPointer trans_mod; + ListPointer puncht; +}; + +struct DrawParameters +{ + ISP_TSP isp; + TSP tsp[2]; + TCW tcw[2]; +}; + +enum RenderMode { + RM_OPAQUE, + RM_PUNCHTHROUGH, + RM_OP_PT_MV, // OP and PT with modvol + RM_TRANSLUCENT, + RM_MODIFIER, +}; + +#define TAG_INVALID (1 << 31) + +typedef u32 parameter_tag_t; + +struct taRECT { + int left, top, right, bottom; +}; diff --git a/vendor/emu/refsw/refsw_lists_regtypes.h b/vendor/emu/refsw/refsw_lists_regtypes.h new file mode 100644 index 00000000..f13b030a --- /dev/null +++ b/vendor/emu/refsw/refsw_lists_regtypes.h @@ -0,0 +1,90 @@ +#pragma once +/* + This file is part of libswirl +*/ +#include "license/bsd" + +#include "emu/types.h" + +#pragma pack(push, 1) +union RegionArrayEntryControl { + struct { + u32 res0 : 2; + u32 tilex : 6; + u32 tiley : 6; + u32 res1 : 14; + u32 no_writeout : 1; + u32 pre_sort : 1; + u32 z_keep : 1; + u32 last_region : 1; + }; + u32 full; +}; + +typedef u32 pvr32addr_t; +typedef u32 pvr32words_t; +typedef u32 param_offset_words_t; + +union ListPointer { + struct + { + u32 pad0 : 2; + pvr32words_t ptr_in_words : 22; + u32 pad1 : 7; + u32 empty : 1; + }; + u32 full; +}; + +struct ObjectListTstrip { + param_offset_words_t param_offs_in_words : 21; + u32 skip : 3; + u32 shadow : 1; + u32 mask : 6; + u32 is_not_triangle_strip : 1; +}; + +struct ObjectListTarray { + param_offset_words_t param_offs_in_words : 21; + u32 skip : 3; + u32 shadow : 1; + u32 prims : 4; + u32 type : 3; +}; + +struct ObjectListQarray { + param_offset_words_t param_offs_in_words : 21; + u32 skip : 3; + u32 shadow : 1; + u32 prims : 4; + u32 type : 3; +}; + +struct ObjectListLink { + u32 pad3 : 2; + pvr32words_t next_block_ptr_in_words : 22; + u32 pad4 : 4; + u32 end_of_list : 1; + u32 type : 3; +}; + +union ObjectListEntry { + struct { + u32 pad0 : 31; + u32 is_not_triangle_strip : 1; + }; + + struct { + u32 pad1 : 29; + u32 type : 3; + }; + + ObjectListTstrip tstrip; + ObjectListTarray tarray; + ObjectListQarray qarray; + ObjectListLink link; + + u32 full; +}; + +#pragma pack(pop) \ No newline at end of file diff --git a/vendor/emu/refsw/refsw_tile.cpp b/vendor/emu/refsw/refsw_tile.cpp new file mode 100644 index 00000000..b57a6bcb --- /dev/null +++ b/vendor/emu/refsw/refsw_tile.cpp @@ -0,0 +1,1311 @@ +/* + This file is part of libswirl + + Implementes the Reference SoftWare renderer (RefRendInterface) backend for refrend_base. + This includes buffer operations and rasterization + + Pixel operations are in refsw_pixel.cpp (PixelPipeline interface) +*/ +#include "license/bsd" + +#include +#include +#include +#include +#include +#include + +#include "refsw_tile.h" +#include "TexUtils.h" + +parameter_tag_t tagBuffer[2] [MAX_RENDER_PIXELS]; +StencilType stencilBuffer[MAX_RENDER_PIXELS]; +u32 colorBuffer1 [MAX_RENDER_PIXELS]; +u32 colorBuffer2 [MAX_RENDER_PIXELS]; +ZType depthBuffer[2] [MAX_RENDER_PIXELS]; + +u32 tagBufferA; +u32 tagBufferB; +u32 depthBufferA; +u32 depthBufferB; + +static float mmin(float a, float b, float c, float d) +{ + float rv = std::min(a, b); + rv = std::min(c, rv); + return std::max(d, rv); +} + +static float mmax(float a, float b, float c, float d) +{ + float rv = std::max(a, b); + rv = std::max(c, rv); + return std::min(d, rv); +} + + +// Z buffer doesn't store sign, and has 19 bits of m +f32 mask_w(f32 w) { + // u32 wu = (u32&)w; + // wu = wu & 0x7FFFFFF8; + // return (f32&)wu; + return w; +} + +void ClearBuffers(u32 paramValue, float depthValue, u32 stencilValue) +{ + depthBufferA = 0; + depthBufferB = 1; + + tagBufferA = 0; + tagBufferB = 1; + + auto zb = depthBuffer[depthBufferA]; + auto stencil = stencilBuffer; + auto pb = tagBuffer[tagBufferA]; + + for (int i = 0; i < MAX_RENDER_PIXELS; i++) { + zb[i] = mask_w(depthValue); + stencil[i] = stencilValue; + pb[i] = paramValue; + } +} + +void ClearParamBuffer(parameter_tag_t paramValue) { + tagBufferA = 0; + tagBufferB = 1; + + auto pb = tagBuffer[tagBufferA]; + + for (int i = 0; i < MAX_RENDER_PIXELS; i++) { + pb[i] = paramValue; + } +} + +void PeelBuffersPTInitial(float depthValue) { + tagBufferA = 1; + tagBufferB = 0; + + auto zb2 = depthBuffer[depthBufferB]; + + for (int i = 0; i < MAX_RENDER_PIXELS; i++) { + zb2[i] = mask_w(depthValue);// set the "furthest" test to furthest value possible + tagBuffer[tagBufferA][i] = TAG_INVALID; + } +} + +void PeelBuffersPT() { + auto zb = depthBuffer[depthBufferA]; + auto zb2 = depthBuffer[depthBufferB]; + + for (int i = 0; i < MAX_RENDER_PIXELS; i++) { + zb2[i] = zb[i]; // keep old zb for + } +} + +void PeelBuffersPTAfterHoles() { + std::swap(tagBufferB, tagBufferA); + for (int i = 0; i < MAX_RENDER_PIXELS; i++) { + tagBuffer[tagBufferA][i] = TAG_INVALID; + } +} + +void PeelBuffers(float depthValue, u32 stencilValue) +{ + std::swap(depthBufferB, depthBufferA); + std::swap(tagBufferB, tagBufferA); + + auto zb = depthBuffer[depthBufferA]; + auto zb2 = depthBuffer[depthBufferB]; + auto stencil = stencilBuffer; + + for (int i = 0; i < MAX_RENDER_PIXELS; i++) { + zb[i] = mask_w(depthValue); // set the "closest" test to furthest value possible + tagBuffer[tagBufferA][i] = TAG_INVALID; + stencil[i] = stencilValue; + } +} + + +void SummarizeStencilOr() { + auto stencil = stencilBuffer; + + // post movdol merge INSIDE + for (int i = 0; i < MAX_RENDER_PIXELS; i++) { + if (stencil[i] & 0b100) { + stencil[i] |= (stencil[i] >>1); + stencil[i] &=0b001; // keep only status bit + } + } +} + +void SummarizeStencilAnd() { + auto stencil = stencilBuffer; + + for (int i = 0; i < MAX_RENDER_PIXELS; i++) { + // post movdol merge OUTSIDE + if (stencil[i] & 0b100) { + stencil[i] &= (stencil[i] >>1); + stencil[i] &=0b001; // keep only status bit + } + } +} + +bool MoreToDraw; +void ClearMoreToDraw() +{ + MoreToDraw = 0; +} + +bool GetMoreToDraw() +{ + return MoreToDraw; +} + + // Render to ACCUM from TAG buffer +// TAG holds references to trianes, ACCUM is the tile framebuffer +void RenderParamTags(RenderMode rm, int tileX, int tileY) { + float halfpixel = HALF_OFFSET.tsp_pixel_half_offset ? 0.5f : 0; + taRECT rect; + rect.left = tileX; + rect.top = tileY; + rect.bottom = rect.top + 32; + rect.right = rect.left + 32; + + for (int y = 0; y < 32; y++) { + for (int x = 0; x < 32; x++) { + auto index = y * 32 + x; + auto tag = tagBuffer[tagBufferA][index]; + ISP_BACKGND_T_type t { .full = tag & ~TAG_INVALID }; + bool InVolume = (stencilBuffer[index] & 0b001) == 0b001 && t.shadow; + bool TagValid = !(tag & TAG_INVALID); + + if (TagValid || (rm == RM_OP_PT_MV && InVolume)) { + auto Entry = GetFpuEntry(&rect, rm, t); + auto invW = Entry.ips.invW.Ip(x + halfpixel, y + halfpixel); + bool AlphaTestPassed = PixelFlush_tsp(rm == RM_PUNCHTHROUGH, &Entry, x + halfpixel, y + halfpixel, index, invW, InVolume); + + auto pb = tagBuffer[tagBufferA] + index; + + *pb |= TAG_INVALID; + + // can only happen when rm == RM_PUNCHTHROUGH + if (!AlphaTestPassed) { + MoreToDraw = true; + // Feedback Channel + depthBuffer[depthBufferA][index] = ISP_BACKGND_D.f; + } + } + } + } +} + +void ClearFpuEntries() { + +} + +f32 f16(u16 v) +{ + u32 z=v<<16; + return *(f32*)&z; +} + +#define vert_packed_color_(to,src) \ + { \ + u32 t=src; \ + to[0] = (u8)(t);t>>=8;\ + to[1] = (u8)(t);t>>=8;\ + to[2] = (u8)(t);t>>=8;\ + to[3] = (u8)(t); \ + } + +//decode a vertex in the native pvr format +void decode_pvr_vertex(DrawParameters* params, pvr32addr_t ptr, Vertex* cv, u32 two_volumes) +{ + //XYZ + //UV + //Base Col + //Offset Col + + //XYZ are _allways_ there :) + cv->x=vrf(ptr);ptr+=4; + cv->y=vrf(ptr);ptr+=4; + cv->z=vrf(ptr);ptr+=4; + + if (params->isp.Texture) + { //Do texture , if any + if (params->isp.UV_16b) + { + u32 uv=vri(ptr); + cv->u = f16((u16)(uv >>16)); + cv->v = f16((u16)(uv >> 0)); + ptr+=4; + } + else + { + cv->u=vrf(ptr);ptr+=4; + cv->v=vrf(ptr);ptr+=4; + } + } + + //Color + u32 col=vri(ptr);ptr+=4; + vert_packed_color_(cv->col,col); + if (params->isp.Offset) + { + //Intensity color + u32 col=vri(ptr);ptr+=4; + vert_packed_color_(cv->spc,col); + } + + if (two_volumes) { + if (params->isp.Texture) + { //Do texture , if any + if (params->isp.UV_16b) + { + u32 uv=vri(ptr); + cv->u1 = f16((u16)(uv >>16)); + cv->v1 = f16((u16)(uv >> 0)); + ptr+=4; + } + else + { + cv->u1=vrf(ptr);ptr+=4; + cv->v1=vrf(ptr);ptr+=4; + } + } + + //Color + u32 col1=vri(ptr);ptr+=4; + vert_packed_color_(cv->col1,col1); + if (params->isp.Offset) + { + //Intensity color + u32 col1=vri(ptr);ptr+=4; + vert_packed_color_(cv->spc1,col1); + } + } +} + +// decode an object (params + vertexes) +u32 decode_pvr_vertices(DrawParameters* params, pvr32addr_t base, u32 skip, u32 two_volumes, Vertex* vtx, int count, int offset) +{ + params->isp.full=vri(base); + params->tsp[0].full=vri(base+4); + params->tcw[0].full=vri(base+8); + + base += 12; + if (two_volumes) { + params->tsp[1].full=vri(base+0); + params->tcw[1].full=vri(base+4); + base += 8; + } + + for (int i = 0; i < offset; i++) { + base += (3 + skip * (two_volumes+1)) * 4; + } + + for (int i = 0; i < count; i++) { + decode_pvr_vertex(params,base, &vtx[i], two_volumes); + base += (3 + skip * (two_volumes+1)) * 4; + } + + return base; +} + +FpuEntry GetFpuEntry(taRECT *rect, RenderMode render_mode, ISP_BACKGND_T_type core_tag) +{ + FpuEntry entry = {}; + Vertex vtx[3]; + decode_pvr_vertices(&entry.params, PARAM_BASE + core_tag.param_offs_in_words * 4, core_tag.skip, core_tag.shadow & ~FPU_SHAD_SCALE.intensity_shadow, vtx, 3, core_tag.tag_offset); + + entry.ips.Setup(rect, &entry.params, vtx[0], vtx[1], vtx[2], core_tag.shadow & ~FPU_SHAD_SCALE.intensity_shadow); + + return entry; +} + +// Lookup/create cached TSP parameters, and call PixelFlush_tsp +bool PixelFlush_tsp(bool pp_AlphaTest, FpuEntry* entry, float x, float y, u32 index, float invW, bool InVolume) +{ + u32 two_voume_index = InVolume & !FPU_SHAD_SCALE.intensity_shadow; + return PixelFlush_tsp(entry->params.tsp[two_voume_index].UseAlpha, entry->params.isp.Texture, entry->params.isp.Offset, entry->params.tsp[two_voume_index].ColorClamp, entry->params.tsp[two_voume_index].FogCtrl, + entry->params.tsp[two_voume_index].IgnoreTexA, entry->params.tsp[two_voume_index].ClampU, entry->params.tsp[two_voume_index].ClampV, entry->params.tsp[two_voume_index].FlipU, entry->params.tsp[two_voume_index].FlipV, entry->params.tsp[two_voume_index].FilterMode, entry->params.tsp[two_voume_index].ShadInstr, + pp_AlphaTest, entry->params.tsp[two_voume_index].SrcSelect, entry->params.tsp[two_voume_index].DstSelect, entry->params.tsp[two_voume_index].SrcInstr, entry->params.tsp[two_voume_index].DstInstr, + entry, x, y, 1/invW, InVolume, index); +} + +// this is disabled for now, as it breaks game scenes +bool IsTopLeft(float x, float y) { + bool IsTop = y == 0 && x > 0; + bool IsLeft = y < 0; + + // return IsTop || IsLeft; + return true; +} + +// Rasterize a single triangle to ISP (or ISP+TSP for PT) +void RasterizeTriangle(RenderMode render_mode, DrawParameters* params, parameter_tag_t tag, const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex* v4, taRECT* area) +{ + const int stride_bytes = STRIDE_PIXEL_OFFSET * 4; + //Plane equation + +#define FLUSH_NAN(a) std::isnan(a) ? 0 : a + + const float Y1 = FLUSH_NAN(v1.y); + const float Y2 = FLUSH_NAN(v2.y); + const float Y3 = FLUSH_NAN(v3.y); + const float Y4 = v4 ? FLUSH_NAN(v4->y) : 0; + + const float X1 = FLUSH_NAN(v1.x); + const float X2 = FLUSH_NAN(v2.x); + const float X3 = FLUSH_NAN(v3.x); + const float X4 = v4 ? FLUSH_NAN(v4->x) : 0; + + int sgn = 1; + + float tri_area = ((X1 - X3) * (Y2 - Y3) - (Y1 - Y3) * (X2 - X3)); + + if (tri_area > 0) + sgn = -1; + + // cull + if (params->isp.CullMode != 0) { + //area: (X1-X3)*(Y2-Y3)-(Y1-Y3)*(X2-X3) + + float abs_area = fabsf(tri_area); + + if (abs_area < FPU_CULL_VAL) + return; + + if (params->isp.CullMode >= 2) { + u32 mode = params->isp.CullMode & 1; + + if ( + (mode == 0 && tri_area < 0) || + (mode == 1 && tri_area > 0)) { + return; + } + } + } + + // Bounding rectangle +// int minx = mmin(X1, X2, X3, area->left); +// int miny = mmin(Y1, Y2, Y3, area->top); + +// int spanx = mmax(X1+1, X2+1, X3+1, area->right - 1) - minx + 1; +// int spany = mmax(Y1+1, Y2+1, Y3+1, area->bottom - 1) - miny + 1; + + //Inside scissor area? +// if (spanx < 0 || spany < 0) +// return; + + // Half-edge constants + const float DX12 = sgn * (X1 - X2); + const float DX23 = sgn * (X2 - X3); + const float DX31 = v4 ? sgn * (X3 - X4) : sgn * (X3 - X1); + const float DX41 = v4 ? sgn * (X4 - X1) : 0; + + const float DY12 = sgn * (Y1 - Y2); + const float DY23 = sgn * (Y2 - Y3); + const float DY31 = v4 ? sgn * (Y3 - Y4) : sgn * (Y3 - Y1); + const float DY41 = v4 ? sgn * (Y4 - Y1) : 0; + + float C1 = DY12 * (X1 - area->left) - DX12 * (Y1 - area->top); + float C2 = DY23 * (X2 - area->left) - DX23 * (Y2 - area->top); + float C3 = DY31 * (X3 - area->left) - DX31 * (Y3 - area->top); + float C4 = v4 ? DY41 * (X4 - area->left) - DX41 * (Y4 - area->top) : 1; + + C1 += IsTopLeft(DX12, DY12) ? 0 : -1; + C2 += IsTopLeft(DX23, DY23) ? 0 : -1; + C3 += IsTopLeft(DX31, DY31) ? 0 : -1; + if (v4) { + C4 += IsTopLeft(DX41, DY41) ? 0 : -1; + } + PlaneStepper3 Z; + Z.Setup(area, v1, v2, v3, v1.z, v2.z, v3.z); + + float halfpixel = HALF_OFFSET.fpu_pixel_half_offset ? 0.5f : 0; + float y_ps = halfpixel; + float minx_ps = halfpixel; + + // Loop through ALL pixels in the tile (no smart clipping) + for (int y = 0; y < 32; y++) + { + float x_ps = minx_ps; + for (int x = 0; x < 32; x++) + { + float Xhs12 = C1 + DX12 * y_ps - DY12 * x_ps; + float Xhs23 = C2 + DX23 * y_ps - DY23 * x_ps; + float Xhs31 = C3 + DX31 * y_ps - DY31 * x_ps; + float Xhs41 = C4 + DX41 * y_ps - DY41 * x_ps; + + bool inTriangle = Xhs12 >= 0 && Xhs23 >= 0 && Xhs31 >= 0 && Xhs41 >= 0; + + if (inTriangle) { + u32 index = y * 32 + x; + float invW = Z.Ip(x_ps, y_ps); + PixelFlush_isp(render_mode, params->isp.DepthMode, params->isp.ZWriteDis, x_ps, y_ps, invW, index, tag); + + if (render_mode == RM_TRANSLUCENT && ISP_FEED_CFG.pre_sort && !(tagBuffer[tagBufferA][index] & TAG_INVALID)) { + ISP_BACKGND_T_type t { .full = tagBuffer[tagBufferA][index] }; + auto Entry = GetFpuEntry(area, RM_TRANSLUCENT, t); + PixelFlush_tsp(false, &Entry, x_ps, y_ps, index, invW, false); + tagBuffer[tagBufferA][index] |= TAG_INVALID; + } + } + + x_ps = x_ps + 1; + } + next_y: + y_ps = y_ps + 1; + } +} + +u8* GetColorOutputBuffer() { + return (u8*)colorBuffer1; +} + + +// Clamp and flip a texture coordinate +static int ClampFlip( bool pp_Clamp, bool pp_Flip + , int coord, int size) { + if (pp_Clamp) { // clamp + if (coord < 0) { + coord = 0; + } else if (coord >= size) { + coord = size-1; + } + } else if (pp_Flip) { // flip + coord &= size*2-1; + if (coord & size) { + coord ^= size*2-1; + } + } else { //wrap + coord &= size-1; + } + + return coord; +} + + +const u32 MipPoint[11] = +{ + 0x00003,//1 + 0x00001 * 4,//2 + 0x00002 * 4,//4 + 0x00006 * 4,//8 + 0x00016 * 4,//16 + 0x00056 * 4,//32 + 0x00156 * 4,//64 + 0x00556 * 4,//128 + 0x01556 * 4,//256 + 0x05556 * 4,//512 + 0x15556 * 4//1024 +}; + +#if 0 +static Color TextureFetchOld(TSP tsp, TCW tcw, int u, int v) { + auto textel_stride = 8 << tsp.TexU; + + u32 start_address = tcw.TexAddr << 3; + u32 base_address = start_address; + + u32 mip_bpp; + if (tcw.VQ_Comp) { + mip_bpp = 2; + } else if (tcw.PixelFmt == PixelPal8) { + mip_bpp = 8; + } + else if (tcw.PixelFmt == PixelPal4) { + mip_bpp = 4; + } + else { + mip_bpp = 16; + } + + if (tcw.MipMapped) { + base_address += MipPoint[tsp.TexU] * mip_bpp / 2; + } + + u32 offset; + if (tcw.VQ_Comp) { + offset = twop(u, v, tsp.TexU, tsp.TexV) / 4; + } else if (!tcw.ScanOrder) { + offset = twop(u, v, tsp.TexU, tsp.TexV); + } else { + offset = u + textel_stride * v; + } + + u16 memtel; + if (tcw.VQ_Comp) { + u8 index = emu_vram[(base_address + offset + 256*4*2) & VRAM_MASK]; + u16 *vq_book = (u16*)&emu_vram[start_address]; + memtel = vq_book[index * 4 + (u&1)*2 + (v&1) ]; + } else { + memtel = (u16&)emu_vram[(base_address + offset *2) & VRAM_MASK]; + } + + u32 textel; + switch (tcw.PixelFmt) + { + case PixelReserved: + case Pixel1555: textel = ARGB1555_32(memtel); break; + case Pixel565: textel = ARGB565_32(memtel); break; + case Pixel4444: textel = ARGB4444_32(memtel); break; + case PixelYUV: textel = 0xDE000EEF; break; + case PixelBumpMap: textel = 0xDEADBEEF; break; + case PixelPal4: textel = 0xFF00FFF0; break; + case PixelPal8: textel = 0xFF0FF0FF; break; + } + return { .raw = textel }; +} +#endif + +u32 ExpandToARGB8888(u32 color, u32 mode, bool ScanOrder /* TODO: Expansion Patterns */) { + switch(mode) + { + case 0: return ARGB1555_32(color); + case 1: return ARGB565_32(color); + case 2: return ARGB4444_32(color); + case 3: return ARGB8888_32(color); // this one just shuffles + } + return 0xDEADBEEF; +} + +u32 TexAddressGen(TCW tcw) { + u32 base_address = tcw.TexAddr << 3; + + if (tcw.VQ_Comp) { + base_address += 256 * 4 * 2; + } + + return base_address; +} + +u32 TexOffsetGen(TSP tsp, TCW tcw, bool ScanOrder, int u, int v, u32 stride, u32 MipLevel) { + u32 mip_offset; + + if (tcw.MipMapped) { + mip_offset = MipPoint[3 + tsp.TexU - MipLevel]; + } else { + mip_offset = 0; + } + + if (tcw.VQ_Comp || !ScanOrder) { + if (tcw.MipMapped) { + return mip_offset + twop(u, v, (tsp.TexU - MipLevel), (tsp.TexU - MipLevel)); + } else { + return mip_offset + twop(u, v, tsp.TexU, tsp.TexV); + } + } else { + return mip_offset + u + stride * v; + } +} + +// 4.1 format +u32 fBitsPerPixel(TCW tcw) { + u32 rv; + if (tcw.PixelFmt == PixelPal8) { + rv = 8; + } + else if (tcw.PixelFmt == PixelPal4) { + rv = 4; + } + else { + rv = 16; + } + + if (tcw.VQ_Comp) { + return 8 * 2 / (64 / rv); // 8 bpp / (pixels per 64 bits) + } else { + return rv * 2; + } +} + +u64 VQLookup(u32 start_address, u64 memtel, u32 offset) { + u8* memtel8 = (u8*)&memtel; + + u64 *vq_book = (u64*)&emu_vram[start_address & (VRAM_MASK-7)]; + u8 index = memtel8[offset & 7]; + return vq_book[index]; +} + +u32 TexStride(u32 TexU, u32 StrideSel, u32 ScanOrder, u32 MipLevel) { + if (StrideSel && ScanOrder) + return (TEXT_CONTROL&31)*32; + else + return (8U << TexU) >> MipLevel; +} + +u32 DecodeTextel(u32 PixelFmt, u32 PalSelect, u64 memtel, u32 offset) { + auto memtel_32 = (u32*)&memtel; + auto memtel_16 = (u16*)&memtel; + auto memtel_8 = (u8*)&memtel; + + switch (PixelFmt) + { + case PixelReserved: + case Pixel1555: + case Pixel565: + case Pixel4444: + case PixelBumpMap: + return memtel_16[offset & 3]; break; + + case PixelYUV: { + auto memtel_yuv = memtel_32[offset & 1]; + auto memtel_yuv8 = (u8*)&memtel_yuv; + return YUV422(memtel_yuv8[1 + (offset & 2)], memtel_yuv8[0], memtel_yuv8[2]); + } + + case PixelPal4: { + auto local_idx = (memtel >> (offset & 15)*4) & 15; + auto idx = PalSelect * 16 | local_idx; + return PALETTE_RAM[idx]; + } + break; + case PixelPal8: { + auto local_idx = memtel_8[offset & 7]; + auto idx = (PalSelect / 16) * 256 | local_idx; + return PALETTE_RAM[idx]; + } + break; + } + return 0xDEADBEEF; +} + +u32 GetExpandFormat(u32 PixelFmt) { + if (PixelFmt == PixelPal4 || PixelFmt == PixelPal8) { + return PAL_RAM_CTRL&3; + } else if (PixelFmt == PixelBumpMap || PixelFmt == PixelYUV) { + return 3; + } else { + return PixelFmt & 3; + } +} +Color MipDebugColor[11] = { + {.raw = 0xFF000060}, + {.raw = 0xFF000090}, + {.raw = 0xFF0000A0}, + + {.raw = 0xFF006000}, + {.raw = 0xFF009000}, + {.raw = 0xFF00A000}, + + {.raw = 0xFF600000}, + {.raw = 0xFF900000}, + {.raw = 0xFFA00000}, + + {.raw = 0xFFA0A0A0}, + {.raw = 0xFFF0F0F0}, +}; + +static Color TextureFetch(TSP tsp, TCW tcw, int u, int v, u32 MipLevel) { + + u32 PixelFmt = tcw.PixelFmt; + + if (MipLevel == (tsp.TexU + 3)) { + if (PixelFmt == PixelYUV) { + PixelFmt = Pixel565; + } + } + + // These are fixed to zero for pal4/pal8 + u32 ScanOrder = tcw.ScanOrder & ~(PixelFmt == PixelPal4 || PixelFmt == PixelPal8); + u32 StrideSel = tcw.StrideSel & ~(PixelFmt == PixelPal4 || PixelFmt == PixelPal8); + + u32 stride = TexStride(tsp.TexU, StrideSel, ScanOrder, MipLevel); + + u32 start_address = tcw.TexAddr << 3; + + auto fbpp = fBitsPerPixel(tcw); + + auto base_address = TexAddressGen(tcw); + auto offset = TexOffsetGen(tsp, tcw, ScanOrder, u, v, stride, MipLevel); + + u64 memtel = (u64&)emu_vram[(base_address + offset * fbpp / 16) & (VRAM_MASK-7)]; + + if (tcw.VQ_Comp) { + memtel = VQLookup(start_address, memtel, offset * fbpp / 16); + } + + u32 textel = DecodeTextel(PixelFmt, tcw.PalSelect, memtel, offset); + + u32 expand_format = GetExpandFormat(PixelFmt); + + + + textel = ExpandToARGB8888(textel, expand_format, tcw.ScanOrder); + + // auto old = TextureFetch2(texture, u, v); + // if (textel != old.raw) { + // textel = TextureFetch2(texture, u, v).raw; + // textel = TextureFetch(texture, u, v).raw; + // die("Missmatch"); + // } + // auto old = raw_GetTexture(tsp, tcw)[u + v * stride]; + // if (textel != old) { + // die("Missmatch"); + // } + // This uses the old path for debugging + // return { .raw = raw_GetTexture(tsp, tcw)[u + v * textel_stride] }; + return { .raw = textel }; + // return MipDebugColor[10-MipLevel]; +} +u32 to_u8_256(u8 v) { + return v + (v >> 7); +} +// Fetch pixels from UVs, interpolate +static Color TextureFilter( + bool pp_IgnoreTexA, bool pp_ClampU, bool pp_ClampV, bool pp_FlipU, bool pp_FlipV, u32 pp_FilterMode, + TSP tsp, TCW tcw, float u, float v, u32 MipLevel, f32 dTrilinear) { + + int halfpixel = HALF_OFFSET.texure_pixel_half_offset ? -127 : 0; + if (MipLevel >= (tsp.TexU + 3)) { + MipLevel = tsp.TexU+3; + } + int sizeU, sizeV; + + if (tcw.MipMapped) { + sizeU = (8 << tsp.TexU) >> MipLevel; + sizeV = (8 << tsp.TexU) >> MipLevel; + } else { + sizeU = 8 << tsp.TexU; + sizeV = 8 << tsp.TexV; + } + + int ui = u * sizeU * 256 + halfpixel; + int vi = v * sizeV * 256 + halfpixel; + + auto offset00 = TextureFetch(tsp, tcw, ClampFlip(pp_ClampU, pp_FlipU, (ui >> 8) + 1, sizeU), ClampFlip(pp_ClampV, pp_FlipV, (vi >> 8) + 1, sizeV), MipLevel); + auto offset01 = TextureFetch(tsp, tcw, ClampFlip(pp_ClampU, pp_FlipU, (ui >> 8) + 0, sizeU), ClampFlip(pp_ClampV, pp_FlipV, (vi >> 8) + 1, sizeV), MipLevel); + auto offset10 = TextureFetch(tsp, tcw, ClampFlip(pp_ClampU, pp_FlipU, (ui >> 8) + 1, sizeU), ClampFlip(pp_ClampV, pp_FlipV, (vi >> 8) + 0, sizeV), MipLevel); + auto offset11 = TextureFetch(tsp, tcw, ClampFlip(pp_ClampU, pp_FlipU, (ui >> 8) + 0, sizeU), ClampFlip(pp_ClampV, pp_FlipV, (vi >> 8) + 0, sizeV), MipLevel); + + Color textel = {0xAF674839}; + + if (pp_FilterMode == 0) { + // Point sampling + for (int i = 0; i < 4; i++) + { + textel = offset11; + } + } else if (pp_FilterMode == 1) { + // Bilinear filtering + int ublend = to_u8_256(ui & 255); + int vblend = (vi & 255); + int nublend = 256 - ublend; + int nvblend = 256 - vblend; + + for (int i = 0; i < 4; i++) + { + textel.bgra[i] = + (offset00.bgra[i] * ublend * vblend) / 65536 + + (offset01.bgra[i] * nublend * vblend) / 65536 + + (offset10.bgra[i] * ublend * nvblend) / 65536 + + (offset11.bgra[i] * nublend * nvblend) / 65536; + }; + } else { + // trilinear filtering A and B + die("pp_FilterMode is trilinear"); + } + + + if (pp_IgnoreTexA) + { + textel.a = 255; + } + + return textel; +} + +// Combine Base, Textel and Offset colors +static Color ColorCombiner( + bool pp_Texture, bool pp_Offset, u32 pp_ShadInstr, + Color base, Color textel, Color offset) { + + Color rv = base; + if (pp_Texture) + { + if (pp_ShadInstr == 0) + { + //color.rgb = texcol.rgb; + //color.a = texcol.a; + + rv = textel; + } + else if (pp_ShadInstr == 1) + { + //color.rgb *= texcol.rgb; + //color.a = texcol.a; + for (int i = 0; i < 3; i++) + { + rv.bgra[i] = textel.bgra[i] * to_u8_256(base.bgra[i]) / 256; + } + + rv.a = textel.a; + } + else if (pp_ShadInstr == 2) + { + //color.rgb=mix(color.rgb,texcol.rgb,texcol.a); + u32 tb = to_u8_256(textel.a); + u32 cb = 256 - tb; + + for (int i = 0; i < 3; i++) + { + rv.bgra[i] = (textel.bgra[i] * tb + base.bgra[i] * cb) / 256; + } + + rv.a = base.a; + } + else if (pp_ShadInstr == 3) + { + //color*=texcol + for (int i = 0; i < 4; i++) + { + rv.bgra[i] = textel.bgra[i] * to_u8_256(base.bgra[i]) / 256; + } + } + + if (pp_Offset) { + // mix only color, saturate + for (int i = 0; i < 3; i++) + { + rv.bgra[i] = std::min(rv.bgra[i] + offset.bgra[i], 255); + } + } + } + + return rv; +} + +static Color BumpMapper(Color textel, Color offset) { + u8 K1 = offset.a; + u8 K2 = offset.r; + u8 K3 = offset.g; + u8 Q = offset.b; + + u8 S = offset.b; + u8 R = offset.g; + + u8 I = u8(K1 + K2*BM_SIN90[S]/256 + K3*BM_COS90[S]*BM_COS360[(R - Q) & 255]/256/256); + + Color res; + res.b = 255; + res.g = 255; + res.r = 255; + res.a = I; + return res; +} + +// Interpolate the base color, also cheap shadows modifier +static Color InterpolateBase( + bool pp_UseAlpha, bool pp_CheapShadows, + const PlaneStepper3* Col, float x, float y, float W, bool InVolume) { + Color rv; + u32 mult = 256; + + if (pp_CheapShadows) { + if (InVolume) { + mult = to_u8_256(FPU_SHAD_SCALE.scale_factor); + } + } + + rv.bgra[0] = 0.5f + Col[0].Ip(x, y, W) * mult / 256; + rv.bgra[1] = 0.5f + Col[1].Ip(x, y, W) * mult / 256; + rv.bgra[2] = 0.5f + Col[2].Ip(x, y, W) * mult / 256; + rv.bgra[3] = 0.5f + Col[3].Ip(x, y, W) * mult / 256; + + if (!pp_UseAlpha) + { + rv.a = 255; + } + + return rv; +} + +// Interpolate the offset color, also cheap shadows modifier +static Color InterpolateOffs(bool pp_CheapShadows, + const PlaneStepper3* Ofs, float x, float y, float W, bool InVolume) { + Color rv; + u32 mult = 256; + + if (pp_CheapShadows) { + if (InVolume) { + mult = to_u8_256(FPU_SHAD_SCALE.scale_factor); + } + } + + rv.bgra[0] = 0.5f + Ofs[0].Ip(x, y, W) * mult / 256; + rv.bgra[1] = 0.5f + Ofs[1].Ip(x, y, W) * mult / 256; + rv.bgra[2] = 0.5f + Ofs[2].Ip(x, y, W) * mult / 256; + rv.bgra[3] = 0.5f + Ofs[3].Ip(x, y, W); + + return rv; +} + +// select/calculate blend coefficient for the blend unit +static Color BlendCoefs( + u32 pp_AlphaInst, bool srcOther, + Color src, Color dst) { + Color rv; + + switch(pp_AlphaInst>>1) { + // zero + case 0: rv.raw = 0; break; + // other color + case 1: rv = srcOther? src : dst; break; + // src alpha + case 2: for (int i = 0; i < 4; i++) rv.bgra[i] = src.a; break; + // dst alpha + case 3: for (int i = 0; i < 4; i++) rv.bgra[i] = dst.a; break; + } + + if (pp_AlphaInst & 1) { + for (int i = 0; i < 4; i++) + rv.bgra[i] = 255 - rv.bgra[i]; + } + + return rv; +} + +// Blending Unit implementation. Alpha blend, accum buffers and such +static bool BlendingUnit( + bool pp_AlphaTest, u32 pp_SrcSel, u32 pp_DstSel, u32 pp_SrcInst, u32 pp_DstInst, + u32 index, Color col) +{ + Color rv; + Color src = {.raw = pp_SrcSel ? colorBuffer2[index] : col.raw }; + Color dst = {.raw = pp_DstSel ? colorBuffer2[index] : colorBuffer1[index] }; + + Color src_blend = BlendCoefs(pp_SrcInst, false, src, dst); + Color dst_blend = BlendCoefs(pp_DstInst, true, src, dst); + + for (int j = 0; j < 4; j++) + { + rv.bgra[j] = std::min((src.bgra[j] * to_u8_256(src_blend.bgra[j]) + dst.bgra[j] * to_u8_256(dst_blend.bgra[j])) >> 8, 255U); + } + + (pp_DstSel ? colorBuffer2[index] : colorBuffer1[index]) = rv.raw; + + if (!pp_AlphaTest || src.a >= PT_ALPHA_REF) + { + return true; + } + else + { + return false; + } +} + +static u8 LookupFogTable(float invW) { + u8* fog_density=(u8*)&FOG_DENSITY; + float fog_den_mant=fog_density[1]/128.0f; //bit 7 -> x. bit, so [6:0] -> fraction -> /128 + s32 fog_den_exp=(s8)fog_density[0]; + + float fog_den = fog_den_mant*powf(2.0f,fog_den_exp); + + f32 fogW = fog_den * invW; + + fogW = std::max((float)fogW, 1.0f); + fogW = std::min((float)fogW, 255.999985f); + + union f32_fields { + f32 full; + struct { + u32 m: 23; + u32 e: 8; + u32 s: 1; + }; + }; + + f32_fields fog_fields = { fogW }; + + u32 index = (((fog_fields.e +1) & 7) << 4) | ((fog_fields.m>>19) & 15); + + u8 blendFactor = (fog_fields.m>>11) & 255; + u8 blend_inv = 255^blendFactor; + + auto fog_entry = (u8*)&FOG_TABLE[index]; + + u8 fog_alpha = (fog_entry[0] * to_u8_256(blendFactor) + fog_entry[1] * to_u8_256(blend_inv)) >> 8; + + return fog_alpha; +} + +// Color Clamp and Fog a pixel +static Color FogUnit(bool pp_Offset, bool pp_ColorClamp, u32 pp_FogCtrl, Color col, float invW, u8 offs_a) { + if (pp_ColorClamp) { + Color clamp_max = { FOG_CLAMP_MAX }; + Color clamp_min = { FOG_CLAMP_MIN }; + + for (int i = 0; i < 4; i++) + { + col.bgra[i] = std::min(col.bgra[i], clamp_max.bgra[i]); + col.bgra[i] = std::max(col.bgra[i], clamp_min.bgra[i]); + } + } + + switch(pp_FogCtrl) { + // Look up mode 1 + case 0b00: + // look up mode 2 + case 0b11: + { + u8 fog_alpha = LookupFogTable(invW); + + u8 fog_inv = 255^fog_alpha; + + Color col_ram = { FOG_COL_RAM }; + + if (pp_FogCtrl == 0b00) { + for (int i = 0; i < 3; i++) { + col.bgra[i] = (col.bgra[i] * to_u8_256(fog_inv) + col_ram.bgra[i] * to_u8_256(fog_alpha))>>8; + } + } else { + for (int i = 0; i < 3; i++) { + col.bgra[i] = col_ram.bgra[i]; + } + col.a = fog_alpha; + } + } + break; + + // Per Vertex + case 0b01: + if (pp_Offset) { + Color col_vert = { FOG_COL_VERT }; + u8 alpha = offs_a; + u8 inv = 255^alpha; + + for (int i = 0; i < 3; i++) + { + col.bgra[i] = (col.bgra[i] * to_u8_256(inv) + col_vert.bgra[i] * to_u8_256(alpha))>>8; + } + } + break; + + + // No Fog + case 0b10: + break; + } + + return col; +} + +void DumpTexture(TSP tsp, TCW tcw) { + char tex_dump[256]; + int max_mipmap = 0; + int texU = tsp.TexU + 3; + int texV = tsp.TexV + 3; + if (tcw.MipMapped) { + max_mipmap = texU; + texV = texU; + } + for (int mip=0; mip<=max_mipmap; mip++) { + int width = (1 << texU) >> mip; + int height = (1 << texV) >> mip; + snprintf(tex_dump, sizeof(tex_dump), "%s/texture-%08x-%08x-%d-%dx%d.png", dump_textures, tsp.full, tcw.full, mip, height, width); + Color* tex = new Color[width * height]; + + for (int t = 0; t < height; t++) { + for (int s = 0; s < width; s++) { + tex[t * width + s] = TextureFetch(tsp, tcw, s, t, mip); + std::swap(tex[t * width + s].r, tex[t * width + s].b); + } + } + + // stbi_write_png(tex_dump, width, height, 4, tex, width * 4); + delete[] tex; + } +} +const char* dump_textures = nullptr; +// Implement the full texture/shade pipeline for a pixel +bool PixelFlush_tsp( + bool pp_UseAlpha, bool pp_Texture, bool pp_Offset, bool pp_ColorClamp, u32 pp_FogCtrl, bool pp_IgnoreAlpha, bool pp_ClampU, bool pp_ClampV, bool pp_FlipU, bool pp_FlipV, u32 pp_FilterMode, u32 pp_ShadInstr, bool pp_AlphaTest, u32 pp_SrcSel, u32 pp_DstSel, u32 pp_SrcInst, u32 pp_DstInst, + const FpuEntry *entry, float x, float y, float W, bool InVolume, u32 index) +{ + u32 two_voume_index = InVolume & !FPU_SHAD_SCALE.intensity_shadow; + auto cb = (Color*)colorBuffer1 + index; + + Color base = { 0 }, textel = { 0 }, offs = { 0 }; + + base = InterpolateBase(pp_UseAlpha, FPU_SHAD_SCALE.intensity_shadow, entry->ips.Col[two_voume_index], x, y, W, InVolume); + + float dTrilinear; + u32 MipLevel; + if (pp_Texture) { + if (dump_textures) { + static std::set dumps; + + u64 uid = entry->params.tsp[two_voume_index].full; + uid = (uid << 32) | entry->params.tcw[two_voume_index].full; + + if (dumps.count(uid) == 0) { + dumps.insert(uid); + DumpTexture(entry->params.tsp[two_voume_index], entry->params.tcw[two_voume_index]); + } + } + float u = entry->ips.U[two_voume_index].Ip(x, y, W); + float v = entry->ips.V[two_voume_index].Ip(x, y, W); + + if (entry->params.tcw[two_voume_index].MipMapped) { + int sizeU = 8 << entry->params.tsp[two_voume_index].TexU; + // faux mip map cals + // these really don't follow hw + float ddx = (entry->ips.U[two_voume_index].ddx + entry->ips.V[two_voume_index].ddx); + float ddy = (entry->ips.U[two_voume_index].ddy + entry->ips.V[two_voume_index].ddy); + + float dMip = fminf(fabsf(ddx), fabsf(ddy)) * W * sizeU * entry->params.tsp[two_voume_index].MipMapD / 4.0f; + + MipLevel = 0; // biggest + while(dMip > 1.5 && MipLevel < 11) { + MipLevel ++; + dMip = dMip / 2; + } + dTrilinear = dMip; + } else { + dTrilinear = 0; + MipLevel = 0; + } + + textel = TextureFilter(pp_IgnoreAlpha, pp_ClampU, pp_ClampV, pp_FlipU, pp_FlipV, pp_FilterMode, entry->params.tsp[two_voume_index], entry->params.tcw[two_voume_index], u, v, MipLevel, dTrilinear); + if (pp_Offset) { + offs = InterpolateOffs(FPU_SHAD_SCALE.intensity_shadow, entry->ips.Ofs[two_voume_index], x, y, W, InVolume); + } + } + + Color col; + if (pp_Texture && pp_Offset && entry->params.tcw[two_voume_index].PixelFmt == PixelBumpMap) { + col = BumpMapper(textel, offs); + } else { + col = ColorCombiner(pp_Texture, pp_Offset, pp_ShadInstr, base, textel, offs); + } + + col = FogUnit(pp_Offset, pp_ColorClamp, pp_FogCtrl, col, 1/W, offs.a); + + // if (pp_Texture) { + // col = MipDebugColor[10-MipLevel]; + // } else { + // col = { .raw = 0 }; + // } + return BlendingUnit(pp_AlphaTest, pp_SrcSel, pp_DstSel, pp_SrcInst, pp_DstInst, index, col); +} + +// Depth processing for a pixel -- render_mode 0: OPAQ, 1: PT, 2: TRANS +void PixelFlush_isp(RenderMode render_mode, u32 depth_mode, u32 ZWriteDis, float x, float y, float invW, u32 index, parameter_tag_t tag) +{ + auto pb = tagBuffer[tagBufferA] + index; + auto pb2 = tagBuffer[tagBufferB] + index; + auto zb = depthBuffer[depthBufferA] + index; + auto zb2 = depthBuffer[depthBufferB] + index; + auto stencil = stencilBuffer + index; + + auto mode = depth_mode; + + if (render_mode == RM_PUNCHTHROUGH) + mode = 6; // TODO: FIXME + else if (render_mode == RM_TRANSLUCENT && !ISP_FEED_CFG.pre_sort) + mode = 3; + else if (render_mode == RM_MODIFIER) + mode = 6; + + switch(mode) { + // never + case 0: return; break; + // less + case 1: if (invW >= *zb) return; break; + // equal + case 2: if (invW != *zb) return; break; + // less or equal + case 3: if (invW > *zb) { + if (render_mode == RM_TRANSLUCENT && !ISP_FEED_CFG.pre_sort) { + MoreToDraw = true; + } + return; + }break; + // greater + case 4: if (invW <= *zb) return; break; + // not equal + case 5: if (invW == *zb) return; break; + // greater or equal + case 6: if (invW < *zb) return; break; + // always + case 7: break; + } + + switch (render_mode) + { + // OPAQ + case RM_OPAQUE: + { + // Z pre-pass only + if (!ZWriteDis) { + *zb = mask_w(invW); + } + *pb = tag; + } + break; + + case RM_MODIFIER: + { + // Flip on Z pass + + *stencil ^= 0b0010; + + // This pixel has valid stencil for summary + *stencil |= 0b100; + } + break; + + // PT + case RM_PUNCHTHROUGH: + { + + if (invW > *zb2) + return; + + if (invW == *zb2) { + auto tagRendered = *pb2 & ~TAG_INVALID; + + if (tag <= tagRendered) + return; + } + + *zb = mask_w(invW); + *pb = tag; + } + break; + + // Layer Peeling. zb2 holds the reference depth, zb is used to find closest to reference + case RM_TRANSLUCENT: + { + if (!ISP_FEED_CFG.pre_sort) { + if (invW < *zb2) + return; + + if (invW == *zb2) { + auto tagRendered = *pb2 & ~TAG_INVALID; + + if (tag >= tagRendered) + return; + } + + *zb = mask_w(invW); + + if (!(*pb & TAG_INVALID)) { + MoreToDraw = true; + } + *pb = tag; + } else { + if (!ZWriteDis) { + *zb = mask_w(invW); + } + *pb = tag; + } + } + break; + + case RM_OP_PT_MV: die("this is invalid here"); break; + } +} \ No newline at end of file diff --git a/vendor/emu/refsw/refsw_tile.h b/vendor/emu/refsw/refsw_tile.h new file mode 100644 index 00000000..b57fc360 --- /dev/null +++ b/vendor/emu/refsw/refsw_tile.h @@ -0,0 +1,183 @@ +#pragma once +/* + This file is part of libswirl +*/ +#include "license/bsd" + +#include "pvr_regs.h" +#include "pvr_mem.h" +#include "core_structs.h" + + + +#include "refsw_lists.h" + +// For texture cache + +#define MAX_RENDER_WIDTH 32 +#define MAX_RENDER_HEIGHT 32 +#define MAX_RENDER_PIXELS (MAX_RENDER_WIDTH * MAX_RENDER_HEIGHT) + +#define STRIDE_PIXEL_OFFSET MAX_RENDER_WIDTH + + +typedef float ZType; +typedef u8 StencilType; +typedef u32 ColorType; +/* + Surface equation solver +*/ +struct PlaneStepper3 +{ + float ddx, ddy; + float c; + + void Setup(taRECT *rect, const Vertex& v1, const Vertex& v2, const Vertex& v3, float v1_a, float v2_a, float v3_a) + { + float Aa = ((v3_a - v1_a) * (v2.y - v1.y) - (v2_a - v1_a) * (v3.y - v1.y)); + float Ba = ((v3.x - v1.x) * (v2_a - v1_a) - (v2.x - v1.x) * (v3_a - v1_a)); + + float C = ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)); + + ddx = -Aa / C; + ddy = -Ba / C; + + c = v1_a - ddx * (v1.x - rect->left) - ddy * (v1.y - rect->top); + } + + float Ip(float x, float y) const + { + return x * ddx + y * ddy + c; + } + + float Ip(float x, float y, float W) const + { + return Ip(x, y) * W; + } +}; + +/* + Interpolation helper +*/ +struct IPs3 +{ + PlaneStepper3 invW; + PlaneStepper3 U[2]; + PlaneStepper3 V[2]; + PlaneStepper3 Col[2][4]; + PlaneStepper3 Ofs[2][4]; + + void Setup(taRECT *rect, DrawParameters* params, const Vertex& v1, const Vertex& v2, const Vertex& v3, bool TwoVolumes) + { + invW.Setup(rect, v1, v2, v3, v1.z, v2.z, v3.z); + U[0].Setup(rect, v1, v2, v3, v1.u * v1.z, v2.u * v2.z, v3.u * v3.z); + V[0].Setup(rect, v1, v2, v3, v1.v * v1.z, v2.v * v2.z, v3.v * v3.z); + if (params->isp.Gouraud) { + for (int i = 0; i < 4; i++) + Col[0][i].Setup(rect, v1, v2, v3, v1.col[i] * v1.z, v2.col[i] * v2.z, v3.col[i] * v3.z); + + for (int i = 0; i < 4; i++) + Ofs[0][i].Setup(rect, v1, v2, v3, v1.spc[i] * v1.z, v2.spc[i] * v2.z, v3.spc[i] * v3.z); + } else { + for (int i = 0; i < 4; i++) + Col[0][i].Setup(rect, v1, v2, v3, v3.col[i] * v1.z, v3.col[i] * v2.z, v3.col[i] * v3.z); + + for (int i = 0; i < 4; i++) + Ofs[0][i].Setup(rect, v1, v2, v3, v3.spc[i] * v1.z, v3.spc[i] * v2.z, v3.spc[i] * v3.z); + } + + if (TwoVolumes) { + U[1].Setup(rect, v1, v2, v3, v1.u * v1.z, v2.u1 * v2.z, v3.u1 * v3.z); + V[1].Setup(rect, v1, v2, v3, v1.v * v1.z, v2.v1 * v2.z, v3.v1 * v3.z); + if (params->isp.Gouraud) { + for (int i = 0; i < 4; i++) + Col[1][i].Setup(rect, v1, v2, v3, v1.col1[i] * v1.z, v2.col1[i] * v2.z, v3.col1[i] * v3.z); + + for (int i = 0; i < 4; i++) + Ofs[1][i].Setup(rect, v1, v2, v3, v1.spc1[i] * v1.z, v2.spc1[i] * v2.z, v3.spc1[i] * v3.z); + } else { + for (int i = 0; i < 4; i++) + Col[1][i].Setup(rect, v1, v2, v3, v3.col1[i] * v1.z, v3.col1[i] * v2.z, v3.col1[i] * v3.z); + + for (int i = 0; i < 4; i++) + Ofs[1][i].Setup(rect, v1, v2, v3, v3.spc1[i] * v1.z, v3.spc1[i] * v2.z, v3.spc1[i] * v3.z); + } + } + } +}; + +// Used for deferred TSP processing lookups +struct FpuEntry +{ + IPs3 ips; + DrawParameters params; +}; + +union Color { + u32 raw; + u8 bgra[4]; + struct { + u8 b; + u8 g; + u8 r; + u8 a; + }; +}; + +extern u32 colorBuffer1 [MAX_RENDER_PIXELS]; +extern const char* dump_textures; + +void ClearBuffers(u32 paramValue, float depthValue, u32 stencilValue); +void ClearParamBuffer(parameter_tag_t paramValue); +void PeelBuffers(float depthValue, u32 stencilValue); +void PeelBuffersPT(); +void PeelBuffersPTAfterHoles(); +void PeelBuffersPTInitial(float depthValue); +void SummarizeStencilOr(); +void SummarizeStencilAnd(); +void ClearMoreToDraw(); +bool GetMoreToDraw(); + +// Render to ACCUM from TAG buffer +// TAG holds references to triangles, ACCUM is the tile framebuffer +void RenderParamTags(RenderMode rm, int tileX, int tileY); +void ClearFpuEntries(); + +f32 f16(u16 v); + +//decode a vertex in the native pvr format +void decode_pvr_vertex(DrawParameters* params, pvr32addr_t ptr,Vertex* cv, u32 shadow); +// decode an object (params + vertexes) +u32 decode_pvr_vertices(DrawParameters* params, pvr32addr_t base, u32 skip, u32 two_volumes, Vertex* vtx, int count, int offset); + +FpuEntry GetFpuEntry(taRECT *rect, RenderMode render_mode, ISP_BACKGND_T_type core_tag); +// Lookup/create cached TSP parameters, and call PixelFlush_tsp +bool PixelFlush_tsp(bool pp_AlphaTest, FpuEntry* entry, float x, float y, u32 index, float invW, bool InVolume); +// Rasterize a single triangle to ISP (or ISP+TSP for PT) +void RasterizeTriangle(RenderMode render_mode, DrawParameters* params, parameter_tag_t tag, const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex* v4, taRECT* area); +u8* GetColorOutputBuffer(); + +// Implement the full texture/shade pipeline for a pixel +bool PixelFlush_tsp( + bool pp_UseAlpha, bool pp_Texture, bool pp_Offset, bool pp_ColorClamp, u32 pp_FogCtrl, bool pp_IgnoreAlpha, bool pp_ClampU, bool pp_ClampV, bool pp_FlipU, bool pp_FlipV, u32 pp_FilterMode, u32 pp_ShadInstr, bool pp_AlphaTest, u32 pp_SrcSel, u32 pp_DstSel, u32 pp_SrcInst, u32 pp_DstInst, + const FpuEntry *entry, float x, float y, float W, bool InVolume, u32 index); + +// Depth processing for a pixel -- render_mode 0: OPAQ, 1: PT, 2: TRANS +void PixelFlush_isp(RenderMode render_mode, u32 depth_mode, u32 ZWriteDis, float x, float y, float invW, u32 index, parameter_tag_t tag); + + +/* + Main renderer class +*/ + +void RenderTriangle(RenderMode render_mode, DrawParameters* params, parameter_tag_t tag, const Vertex& v1, const Vertex& v2, const Vertex& v3, const Vertex* v4, taRECT* area); +// called on vblank +bool RenderFramebuffer(); +u32 ReadRegionArrayEntry(u32 base, RegionArrayEntry* entry); +ISP_BACKGND_T_type CoreTagFromDesc(u32 cache_bypass, u32 shadow, u32 skip, u32 param_offs_in_words, u32 tag_offset); +void RenderTriangleStrip(RenderMode render_mode, ObjectListEntry obj, taRECT* rect); +void RenderTriangleArray(RenderMode render_mode, ObjectListEntry obj, taRECT* rect); +void RenderQuadArray(RenderMode render_mode, ObjectListEntry obj, taRECT* rect); +void RenderObjectList(RenderMode render_mode, pvr32addr_t base, taRECT* rect); +void RenderCORE(); +void Hackpresent(); \ No newline at end of file diff --git a/vendor/koshle/arch/arch.h b/vendor/koshle/arch/arch.h new file mode 100644 index 00000000..719d1e30 --- /dev/null +++ b/vendor/koshle/arch/arch.h @@ -0,0 +1,2 @@ + +#define PAGESIZE 4096 \ No newline at end of file diff --git a/vendor/koshle/arch/spinlock.h b/vendor/koshle/arch/spinlock.h new file mode 100644 index 00000000..139ac2a4 --- /dev/null +++ b/vendor/koshle/arch/spinlock.h @@ -0,0 +1,111 @@ +/* KallistiOS ##version## + + arch/dreamcast/include/spinlock.h + Copyright (C) 2001 Megan Potter + +*/ + +/** \file arch/spinlock.h + \brief Simple locking. + \ingroup kthreads + + This file contains definitions for very simple locks. Most of the time, you + will probably not use such low-level locking, but will opt for something + more fully featured like mutexes, semaphores, reader-writer semaphores, or + recursive locks. + + \author Megan Potter + + \see kos/sem.h + \see kos/mutex.h + \see kos/rwsem.h + \see kos/recursive_lock.h +*/ + +#pragma once + +/* Defines processor specific spinlocks */ + +#include + +/* DC implementation uses threads most of the time */ + +/** \brief Spinlock data type. */ +using spinlock_t = std::atomic; + +/** \brief Spinlock initializer. + + All created spinlocks should be initialized with this initializer so that + they are in a sane state, ready to be used. +*/ +#define SPINLOCK_INITIALIZER {0} + +/** \brief Initialize a spinlock. + + This function-like macro abstracts initializing a spinlock, in case the + initializer is not applicable to what you are doing. + + \param A A pointer to the spinlock to be initialized. +*/ +#define spinlock_init(A) *(A) = SPINLOCK_INITIALIZER + +/* Note here that even if threads aren't enabled, we'll still set the + lock so that it can be used for anti-IRQ protection (e.g., malloc) */ + +/** \brief Spin on a lock. + + This macro will spin on the lock, and will not return until the lock has + been obtained for the calling thread. + + \param A A pointer to the spinlock to be locked. +*/ +#define spinlock_lock(A) do { \ + spinlock_t * __lock = A; \ + int __gotlock = 0; \ + while(1) { \ + int expected = 0; \ + __gotlock = __lock->compare_exchange_strong(expected, 1); \ + if(__gotlock) \ + break; \ + } \ + } while(0) + +/** \brief Try to lock, without spinning. + + This macro will attempt to lock the lock, but will not spin. Instead, it + will return whether the lock was obtained or not. + + \param A A pointer to the spinlock to be locked. + \return 0 if the lock is held by another thread. Non-zero if + the lock was successfully obtained. +*/ +#define spinlock_trylock(A) ({ \ + int __gotlock = 0; \ + do { \ + spinlock_t *__lock = A; \ + int expected = 0; \ + __gotlock = __lock->compare_exchange_strong(expected, 1); \ + } while(0); \ + __gotlock; \ + }) + +/** \brief Free a lock. + + This macro will unlock the lock that is currently held by the calling + thread. Do not use this macro unless you actually hold the lock! + + \param A A pointer to the spinlock to be unlocked. +*/ +#define spinlock_unlock(A) do { \ + *(A) = 0; \ + } while(0) + +/** \brief Determine if a lock is locked. + + This macro will return whether or not the lock specified is actually locked + when it is called. This is NOT a thread-safe way of determining if a lock + will be locked when you get around to locking it! + + \param A A pointer to the spinlock to be checked. +*/ +#define spinlock_is_locked(A) ( *(A) != 0 ) \ No newline at end of file diff --git a/vendor/koshle/dc/asic.h b/vendor/koshle/dc/asic.h new file mode 100644 index 00000000..dafd68ce --- /dev/null +++ b/vendor/koshle/dc/asic.h @@ -0,0 +1,277 @@ +/* KallistiOS ##version## + + dc/asic.h + Copyright (C) 2001-2002 Megan Potter + +*/ + +/** \file dc/asic.h + \brief Dreamcast ASIC event handling support. + \ingroup asic + + This file provides definitions of the events that the ASIC (a part of the + PVR) in the Dreamcast can trigger as IRQs, and ways to set responders for + those events. Pretty much, this covers all IRQs that aren't generated + internally in the SH4 (SCIF and the SH4 DMAC can generate their own IRQs, + as well as the trapa instruction, and various exceptions -- those are not + dealt with here). + + \author Megan Potter +*/ + +#ifndef __DC_ASIC_H +#define __DC_ASIC_H + + +#include + +/** \defgroup asic Events + \brief Events pertaining to the DC's System ASIC + \ingroup system + +*/ + +/* All event codes are two 8-bit integers; the top integer is the event code + register to look in to check the event (and to acknowledge it). The + register to check is 0xa05f6900+4*regnum. The bottom integer is the + bit index within that register. */ + +/** \defgroup asic_events Event Codes + \brief Values for various Holly event codes + \ingroup asic + @{ +*/ + +/** \defgroup asic_events_pvr PowerVR + \brief Event code values for PowerVR events + \ingroup asic_events + + These are events that the PVR itself generates that can be hooked. + @{ +*/ +#define ASIC_EVT_PVR_RENDERDONE_VIDEO 0x0000 /**< \brief Video render stage completed */ +#define ASIC_EVT_PVR_RENDERDONE_ISP 0x0001 /**< \brief ISP render stage completed */ +#define ASIC_EVT_PVR_RENDERDONE_TSP 0x0002 /**< \brief TSP render stage completed */ +#define ASIC_EVT_PVR_VBLANK_BEGIN 0x0003 /**< \brief VBLANK begin interrupt */ +#define ASIC_EVT_PVR_VBLANK_END 0x0004 /**< \brief VBLANK end interrupt */ +#define ASIC_EVT_PVR_HBLANK_BEGIN 0x0005 /**< \brief HBLANK begin interrupt */ + +#define ASIC_EVT_PVR_YUV_DONE 0x0006 /**< \brief YUV completed */ +#define ASIC_EVT_PVR_OPAQUEDONE 0x0007 /**< \brief Opaque list completed */ +#define ASIC_EVT_PVR_OPAQUEMODDONE 0x0008 /**< \brief Opaque modifiers completed */ +#define ASIC_EVT_PVR_TRANSDONE 0x0009 /**< \brief Transparent list completed */ +#define ASIC_EVT_PVR_TRANSMODDONE 0x000a /**< \brief Transparent modifiers completed */ + +#define ASIC_EVT_PVR_DMA 0x0013 /**< \brief PVR DMA complete */ +#define ASIC_EVT_PVR_PTDONE 0x0015 /**< \brief Punch-thrus completed */ + +#define ASIC_EVT_PVR_ISP_OUTOFMEM 0x0200 /**< \brief ISP out of memory */ +#define ASIC_EVT_PVR_STRIP_HALT 0x0201 /**< \brief Halt due to strip buffer error */ +#define ASIC_EVT_PVR_PARAM_OUTOFMEM 0x0202 /**< \brief Param out of memory */ +#define ASIC_EVT_PVR_OPB_OUTOFMEM 0x0203 /**< \brief OPB went past PVR_TA_OPB_END */ +#define ASIC_EVT_PVR_TA_INPUT_ERR 0x0204 /**< \brief Vertex input error */ +#define ASIC_EVT_PVR_TA_INPUT_OVERFLOW 0x0205 /**< \brief Vertex input overflowed queue */ +/** @} */ + +/** \defgroup asic_events_gd GD-ROM Drive + \brief Event code values for GD-ROM events + \ingroup asic_events + + These are events that the GD-ROM drive generates that can be hooked. + @{ +*/ +#define ASIC_EVT_GD_COMMAND 0x0100 /**< \brief GD-Rom Command Status */ +#define ASIC_EVT_GD_DMA 0x000e /**< \brief GD-Rom DMA complete */ +#define ASIC_EVT_GD_DMA_OVERRUN 0x020d /**< \brief GD-Rom DMA overrun */ +#define ASIC_EVT_GD_DMA_ILLADDR 0x020c /**< \brief GD-Rom DMA illegal address */ +/** @} */ + +/** \defgroup asic_events_maple Maple + \brief Event code values for Maple events + \ingroup asic_events + + These are events that Maple generates that can be hooked. + @{ +*/ +#define ASIC_EVT_MAPLE_DMA 0x000c /**< \brief Maple DMA complete */ +#define ASIC_EVT_MAPLE_ERROR 0x000d /**< \brief Maple error (?) */ +/** @} */ + +/** \defgroup asic_events_spu AICA + \brief Event code values for AICA events + \ingroup asic_events + + These are events that the SPU (AICA) generates that can be hooked. + @{ +*/ +#define ASIC_EVT_SPU_DMA 0x000f /**< \brief SPU (G2 channel 0) DMA complete */ +#define ASIC_EVT_SPU_IRQ 0x0101 /**< \brief SPU interrupt */ +/** @} */ + +/** \defgroup asic_events_g2dma G2 Bus DMA + \brief Event code values for G2 Bus events + \ingroup asic_events + + These are events that G2 bus DMA generates that can be hooked. + @{ +*/ +#define ASIC_EVT_G2_DMA0 0x000f /**< \brief G2 DMA channel 0 complete */ +#define ASIC_EVT_G2_DMA1 0x0010 /**< \brief G2 DMA channel 1 complete */ +#define ASIC_EVT_G2_DMA2 0x0011 /**< \brief G2 DMA channel 2 complete */ +#define ASIC_EVT_G2_DMA3 0x0012 /**< \brief G2 DMA channel 3 complete */ +/** @} */ + +/** \defgroup asic_events_ext External Port + \brief Event code values for external port events + \ingroup asic_events + + These are events that external devices generate that can be hooked. + @{ +*/ +#define ASIC_EVT_EXP_8BIT 0x0102 /**< \brief Modem / Lan Adapter */ +#define ASIC_EVT_EXP_PCI 0x0103 /**< \brief BBA IRQ */ +/** @} */ + +/** @} */ + +/** \defgroup asic_regs Registers + \brief Addresses for various ASIC eveng registers + \ingroup asic + + These are the locations in memory where the ASIC registers sit. + @{ +*/ +#define ASIC_ACK_A 0xa05f6900 /**< \brief IRQD ACK register */ +#define ASIC_ACK_B 0xa05f6904 /**< \brief IRQB ACK register */ +#define ASIC_ACK_C 0xa05f6908 /**< \brief IRQ9 ACK register */ + +#define ASIC_IRQD_A 0xa05f6910 /**< \brief IRQD first register */ +#define ASIC_IRQD_B 0xa05f6914 /**< \brief IRQD second register */ +#define ASIC_IRQD_C 0xa05f6918 /**< \brief IRQD third register */ +#define ASIC_IRQB_A 0xa05f6920 /**< \brief IRQB first register */ +#define ASIC_IRQB_B 0xa05f6924 /**< \brief IRQB second register */ +#define ASIC_IRQB_C 0xa05f6928 /**< \brief IRQB third register */ +#define ASIC_IRQ9_A 0xa05f6930 /**< \brief IRQ9 first register */ +#define ASIC_IRQ9_B 0xa05f6934 /**< \brief IRQ9 second register */ +#define ASIC_IRQ9_C 0xa05f6938 /**< \brief IRQ9 third register */ +/** @} */ + +/** \defgroup asic_irq_lv IRQ Levels + \brief values for the various ASIC event IRQ levels + \ingroup asic + + You can pick one at hook time, or don't choose anything and the default will + be used instead. + @{ +*/ +#define ASIC_IRQ9 0 /**< \brief IRQ level 9 */ +#define ASIC_IRQB 1 /**< \brief IRQ level B (11) */ +#define ASIC_IRQD 2 /**< \brief IRQ level D (13) */ + +#define ASIC_IRQ_MAX 3 /**< \brief Don't take irqs from here up */ +#define ASIC_IRQ_DEFAULT ASIC_IRQ9 /**< \brief Pick an IRQ level for me! */ +/** @} */ + +/** \brief ASIC event handler type. + \ingroup asic + + Any event handlers registered must be of this type. These will be run in an + interrupt context, so don't try anything funny. + + \param code The ASIC event code that generated this event. + \param data The user pointer that was passed to + \ref asic_evt_set_handler. + \see asic_events +*/ +typedef void (*asic_evt_handler)(uint32_t code, void *data); + +/** \brief Set or remove an ASIC handler. + \ingroup asic + + This function will register an event handler for a given event code, or if + the handler is NULL, unregister any that is currently registered. + + \param code The ASIC event code to hook (see \ref asic_events). + \param handler The function to call when the event happens. + \param data A user pointer that will be passed to the callback. + +*/ +void asic_evt_set_handler(uint16_t code, asic_evt_handler handler, void *data); + +/** \brief Register a threaded handler with the given ASIC event. + \ingroup asic + + This function will spawn a thread, that will sleep until notified when an + event happens. It will then call the handler. As the handler is not called + in an interrupt context, it can hold locks, and even sleep. + + \param code The ASIC event code to hook (see \ref asic_events). + \param handler The function to call when the event happens. + \param data A user pointer that will be passed to the callback. + \param ack_and_mask An optional function that will be called by the real + interrupt handler, to acknowledge and mask the + interrupt, so that it won't trigger again while the + threaded handler is running. + \param unmask An optional function that will be called by the + thread after the handler function returned, to + re-enable the interrupt. +*/ +int asic_evt_request_threaded_handler(uint16_t code, asic_evt_handler handler, + void *data, + void (*ack_and_mask)(uint16_t), + void (*unmask)(uint16_t)); + +/** \brief Unregister any handler set to the given ASIC event. + \ingroup asic + + \param code The ASIC event code to unhook (see + \ref asic_events). +*/ +void asic_evt_remove_handler(uint16_t code); + +/** \brief Disable all ASIC events. + \ingroup asic + + This function will disable hooks for every event that has been hooked. In + order to reinstate them, you must individually re-enable them. Not a very + good idea to be doing this normally. +*/ +void asic_evt_disable_all(void); + +/** \brief Disable one ASIC event. + \ingroup asic + + This function will disable the hook for a specified code that was registered + at the given IRQ level. Generally, you will never have to do this yourself + unless you're adding in some new functionality. + + \param code The ASIC event code to unhook (see + \ref asic_events). + \param irqlevel The IRQ level it was hooked on (see + \ref asic_irq_lv). +*/ +void asic_evt_disable(uint16_t code, uint8_t irqlevel); + +/** \brief Enable an ASIC event. + \ingroup asic + + This function will enable the hook for a specified code and register it at + the given IRQ level. You should only register each event at a max of one + IRQ level (this will not check that for you), and this does not actually set + the hook function for the event, you must do that separately with + asic_evt_set_handler(). Generally, unless you're adding in new + functionality, you'll never have to do this. + + \param code The ASIC event code to hook (see \ref asic_events). + \param irqlevel The IRQ level to hook on (see \ref asic_irq_lv). + */ +void asic_evt_enable(uint16_t code, uint8_t irqlevel); + +/** \cond Enable ASIC events */ +void asic_init(void); +/* Shutdown ASIC events, disabling all hooks. */ +void asic_shutdown(void); +/** \endcond */ + + +#endif /* __DC_ASIC_H */ diff --git a/vendor/koshle/dc/maple.h b/vendor/koshle/dc/maple.h new file mode 100644 index 00000000..b6fba138 --- /dev/null +++ b/vendor/koshle/dc/maple.h @@ -0,0 +1,860 @@ +#pragma once + +#include "dc_hle_types.h" + +/** \defgroup maple Maple Bus + \brief Driver for the Dreamcast's Maple Peripheral Bus + \ingroup peripherals +*/ + +/** \brief Enable Maple DMA debugging. + \ingroup maple + + Changing this to a 1 will add massive amounts of processing time to the + maple system in general, but it can help in verifying DMA errors. In + general, for most purposes this should stay disabled. +*/ +#define MAPLE_DMA_DEBUG 0 + +/** \brief Enable Maple IRQ debugging. + \ingroup maple + + Changing this to a 1 will turn on intra-interrupt debugging messages, which + may cause issues if you're using dcload rather than a raw serial debug + terminal. You probably will never have a good reason to enable this, so keep + it disabled for normal use. +*/ +#define MAPLE_IRQ_DEBUG 0 + +/** \defgroup maple_regs Registers + \brief Addresses for various maple registers + \ingroup maple + + These are various registers related to the Maple Bus. In general, you + probably won't ever need to mess with these directly. + + @{ +*/ +#define MAPLE_BASE 0xa05f6c00 /**< \brief Maple register base */ +#define MAPLE_DMAADDR (MAPLE_BASE+0x04) /**< \brief DMA address register */ +#define MAPLE_RESET2 (MAPLE_BASE+0x10) /**< \brief Reset register #2 */ +#define MAPLE_ENABLE (MAPLE_BASE+0x14) /**< \brief Enable register */ +#define MAPLE_STATE (MAPLE_BASE+0x18) /**< \brief Status register */ +#define MAPLE_SPEED (MAPLE_BASE+0x80) /**< \brief Speed register */ +#define MAPLE_RESET1 (MAPLE_BASE+0x8c) /**< \brief Reset register #1 */ +/** @} */ + +/** \defgroup maple_reg_values Register Values + \brief Values for various maple registers + \ingroup maple + + These are the values that are written to registers to get them to do their + thing. + + @{ +*/ +#define MAPLE_RESET2_MAGIC 0 /**< \brief 2nd reset value */ +#define MAPLE_ENABLE_ENABLED 1 /**< \brief Enable Maple */ +#define MAPLE_ENABLE_DISABLED 0 /**< \brief Disable Maple */ +#define MAPLE_STATE_IDLE 0 /**< \brief Idle state */ +#define MAPLE_STATE_DMA 1 /**< \brief DMA in-progress */ +#define MAPLE_SPEED_2MBPS 0 /**< \brief 2Mbps bus speed */ +#define MAPLE_SPEED_TIMEOUT(n) ((n) << 16) /**< \brief Bus timeout macro */ + +#ifndef _arch_sub_naomi +#define MAPLE_RESET1_MAGIC 0x6155404f /**< \brief First reset value */ +#else +#define MAPLE_RESET1_MAGIC 0x6155405f +#endif + +/** @} */ + +/** \defgroup maple_cmds Commands and Responses + \brief Maple command and response values + \ingroup maple + + These are all either commands or responses to commands sent to or from Maple + in normal operation. + + @{ +*/ +#define MAPLE_RESPONSE_FILEERR -5 /**< \brief File error */ +#define MAPLE_RESPONSE_AGAIN -4 /**< \brief Try again later */ +#define MAPLE_RESPONSE_BADCMD -3 /**< \brief Bad command sent */ +#define MAPLE_RESPONSE_BADFUNC -2 /**< \brief Bad function code */ +#define MAPLE_RESPONSE_NONE -1 /**< \brief No response */ +#define MAPLE_COMMAND_DEVINFO 1 /**< \brief Device info request */ +#define MAPLE_COMMAND_ALLINFO 2 /**< \brief All info request */ +#define MAPLE_COMMAND_RESET 3 /**< \brief Reset device request */ +#define MAPLE_COMMAND_KILL 4 /**< \brief Kill device request */ +#define MAPLE_RESPONSE_DEVINFO 5 /**< \brief Device info response */ +#define MAPLE_RESPONSE_ALLINFO 6 /**< \brief All info response */ +#define MAPLE_RESPONSE_OK 7 /**< \brief Command completed ok */ +#define MAPLE_RESPONSE_DATATRF 8 /**< \brief Data transfer */ +#define MAPLE_COMMAND_GETCOND 9 /**< \brief Get condition request */ +#define MAPLE_COMMAND_GETMINFO 10 /**< \brief Get memory information */ +#define MAPLE_COMMAND_BREAD 11 /**< \brief Block read */ +#define MAPLE_COMMAND_BWRITE 12 /**< \brief Block write */ +#define MAPLE_COMMAND_BSYNC 13 /**< \brief Block sync */ +#define MAPLE_COMMAND_SETCOND 14 /**< \brief Set condition request */ +#define MAPLE_COMMAND_MICCONTROL 15 /**< \brief Microphone control */ +#define MAPLE_COMMAND_CAMCONTROL 17 /**< \brief Camera control */ +/** @} */ + +/** \defgroup maple_functions Function Codes + \brief Values of maple "function" codes + \ingroup maple + + This is the list of maple device types (function codes). Each device must + have at least one function to actually do anything. + + @{ +*/ + +/* Function codes; most sources claim that these numbers are little + endian, and for all I know, they might be; but since it's a bitmask + it doesn't really make much different. We'll just reverse our constants + from the "big-endian" version. */ +#define MAPLE_FUNC_PURUPURU 0x00010000 /**< \brief Jump pack */ +#define MAPLE_FUNC_MOUSE 0x00020000 /**< \brief Mouse */ +#define MAPLE_FUNC_CAMERA 0x00080000 /**< \brief Camera (Dreameye) */ +#define MAPLE_FUNC_CONTROLLER 0x01000000 /**< \brief Controller */ +#define MAPLE_FUNC_MEMCARD 0x02000000 /**< \brief Memory card */ +#define MAPLE_FUNC_LCD 0x04000000 /**< \brief LCD screen */ +#define MAPLE_FUNC_CLOCK 0x08000000 /**< \brief Clock */ +#define MAPLE_FUNC_MICROPHONE 0x10000000 /**< \brief Microphone */ +#define MAPLE_FUNC_ARGUN 0x20000000 /**< \brief AR gun? */ +#define MAPLE_FUNC_KEYBOARD 0x40000000 /**< \brief Keyboard */ +#define MAPLE_FUNC_LIGHTGUN 0x80000000 /**< \brief Lightgun */ +/** @} */ + +/* \cond */ +/* Pre-define list/queue types */ +struct maple_frame; +// TAILQ_HEAD(maple_frame_queue, maple_frame); + +struct maple_driver; +// LIST_HEAD(maple_driver_list, maple_driver); + +struct maple_state_str; +/* \endcond */ + +/** \brief Maple frame to be queued for transport. + \ingroup maple + + Internal representation of a frame to be queued up for sending. + + \headerfile dc/maple.h +// */ +typedef struct maple_frame { + /** \brief Send queue handle. NOT A FUNCTION! */ + // TAILQ_ENTRY(maple_frame) frameq; + + int cmd; /**< \brief Command (see \ref maple_cmds) */ + int dst_port; /**< \brief Destination port */ + int dst_unit; /**< \brief Destination unit */ + int length; /**< \brief Data transfer length in 32-bit words */ + volatile int state; /**< \brief Has this frame been sent / responded to? */ + volatile int queued; /**< \brief Are we on the queue? */ + + void *send_buf; /**< \brief The data which will be sent (if any) */ + uint8 *recv_buf; /**< \brief Points into recv_buf_arr, but 32-byte aligned */ + + struct maple_device *dev; /**< \brief Does this belong to a device? */ + + void (*callback)(struct maple_state_str *, struct maple_frame *); /**< \brief Response callback */ + +#if MAPLE_DMA_DEBUG + uint8 recv_buf_arr[1024 + 1024 + 32]; /**< \brief Response receive area */ +#else + uint8 recv_buf_arr[1024 + 32]; /**< \brief Response receive area */ +#endif +} maple_frame_t; + +/** \defgroup maple_frame_states Frame States + \brief States for a maple frame + \ingroup maple + @{ +*/ +#define MAPLE_FRAME_VACANT 0 /**< \brief Ready to be used */ +#define MAPLE_FRAME_UNSENT 1 /**< \brief Ready to be sent */ +#define MAPLE_FRAME_SENT 2 /**< \brief Frame has been sent, but no response yet */ +#define MAPLE_FRAME_RESPONDED 3 /**< \brief Frame has a response */ +/** @} */ + +/** \brief Maple device info structure. + \ingroup maple + + This structure is used by the hardware to deliver the response to the device + info request. + + \note product_name and product_license are not guaranteed to be NULL terminated. + + \headerfile dc/maple.h +*/ +typedef struct maple_devinfo { + uint32 functions; /**< \brief Function codes supported */ + uint32 function_data[3]; /**< \brief Additional data per function */ + uint8 area_code; /**< \brief Region code */ + uint8 connector_direction; /**< \brief 0: UP (most controllers), 1: DOWN (lightgun, microphones) */ + char product_name[30]; /**< \brief Name of device */ + char product_license[60]; /**< \brief License statement */ + uint16 standby_power; /**< \brief Power consumption (standby) */ + uint16 max_power; /**< \brief Power consumption (max) */ +} maple_devinfo_t; + +/** \brief Maple response frame structure. + \ingroup maple + + This structure is used to deliver the actual response to a request placed. + The data field is where all the interesting stuff will be. + + \headerfile dc/maple.h +*/ +typedef struct maple_response { + int8 response; /**< \brief Response */ + uint8 dst_addr; /**< \brief Destination address */ + uint8 src_addr; /**< \brief Source address */ + uint8 data_len; /**< \brief Data length (in 32-bit words) */ + uint8 data[]; /**< \brief Data (if any) */ +} maple_response_t; + +/** \brief One maple device. + \ingroup maple + + Note that we duplicate the port/unit info which is normally somewhat + implicit so that we can pass around a pointer to a particular device struct. + + \headerfile dc/maple.h +*/ +typedef struct maple_device { + /* Public */ + int valid; /**< \brief Is this a valid device? */ + int port; /**< \brief Maple bus port connected to */ + int unit; /**< \brief Unit number, off of the port */ + maple_devinfo_t info; /**< \brief Device info struct */ + + /* Private */ + int dev_mask; /**< \brief Device-present mask for unit 0's */ + maple_frame_t frame; /**< \brief One rx/tx frame */ + struct maple_driver *drv; /**< \brief Driver which handles this device */ + + volatile int status_valid; /**< \brief Have we got our first status update? */ + uint8 status[1024]; /**< \brief Status buffer (for pollable devices) */ +} maple_device_t; + +#define MAPLE_PORT_COUNT 4 /**< \brief Number of ports on the bus */ +#define MAPLE_UNIT_COUNT 6 /**< \brief Max number of units per port */ + +/** \brief Internal representation of a Maple port. + \ingroup maple + + Each maple port can contain up to 6 devices, the first one of which is + always the port itself. + + \headerfile dc/maple.h +*/ +typedef struct maple_port { + int port; /**< \brief Port ID */ + maple_device_t units[MAPLE_UNIT_COUNT]; /**< \brief Pointers to active units */ +} maple_port_t; + +/** \brief A maple device driver. + \ingroup maple + + Anything which is added to this list is capable of handling one or more + maple device types. When a device of the given type is connected (includes + startup "connection"), the driver is invoked. This same process happens for + disconnection, response receipt, and on a periodic interval (for normal + updates). + + \headerfile dc/maple.h +*/ +typedef struct maple_driver { + /** \brief Driver list handle. NOT A FUNCTION! */ + // LIST_ENTRY(maple_driver) drv_list; + + uint32 functions; /**< \brief One or more MAPLE_FUNCs ORed together */ + const char *name; /**< \brief The driver name */ + + /* Callbacks, to be filled in by the driver */ + + /** \brief Periodic polling callback. + + This callback will be called to update the status of connected devices + periodically. + + \param drv This structure for the driver. + */ + void (*periodic)(struct maple_driver *drv); + + /** \brief Device attached callback. + + This callback will be called when a new device of this driver is + connected to the system. + + \param drv This structure for the driver. + \param dev The device that was connected. + \return 0 on success, <0 on error. + */ + int (*attach)(struct maple_driver *drv, maple_device_t *dev); + + /** \brief Device detached callback. + + This callback will be called when a device of this driver is disconnected + from the system. + + \param drv This structure for the driver. + \param dev The device that was detached. + */ + void (*detach)(struct maple_driver *drv, maple_device_t *dev); +} maple_driver_t; + +/** \brief Maple state structure. + \ingroup maple + + We put everything in here to keep from polluting the global namespace too + much. + + \headerfile dc/maple.h +*/ +typedef struct maple_state_str { + /** \brief Maple device driver list. Do not manipulate directly! */ + // struct maple_driver_list driver_list; + + /** \brief Maple frame submission queue. Do not manipulate directly! */ + // struct maple_frame_queue frame_queue; + + /** \brief Maple device info structure */ + maple_port_t ports[MAPLE_PORT_COUNT]; + + /** \brief DMA interrupt counter */ + volatile int dma_cntr; + + /** \brief VBlank interrupt counter */ + volatile int vbl_cntr; + + /** \brief DMA send buffer */ + uint8 *dma_buffer; + + /** \brief Is a DMA running now? */ + volatile int dma_in_progress; + + /** \brief Next port that will be auto-detected */ + int detect_port_next; + + /** \brief Next unit which will be auto-detected */ + int detect_unit_next; + + /** \brief Did the detect wrap? */ + volatile int detect_wrapped; + + /** \brief Our vblank handler handle */ + int vbl_handle; + + /** \brief The port to read for lightgun status, if any. */ + int gun_port; + + /** \brief The horizontal position of the lightgun signal. */ + int gun_x; + + /** \brief The vertical position of the lightgun signal. */ + int gun_y; +} maple_state_t; + +/** \brief Maple DMA buffer size. + \ingroup maple + + Increase if you do a _LOT_ of maple stuff on every periodic interrupt. +*/ +#define MAPLE_DMA_SIZE 16384 + +/* Maple memory read/write functions; these are just hooks in case + we need to do something else later */ +/** \brief Maple memory read macro. + \ingroup maple + */ +#define maple_read(A) ( *((vuint32*)(A)) ) + +/** \brief Maple memory write macro. + \ingroup maple + */ +#define maple_write(A, V) ( *((vuint32*)(A)) = (V) ) + +/** \defgroup maple_func_rvs Return Values + \brief Return codes from maple access functions + \ingroup maple + @{ +*/ +#define MAPLE_EOK 0 /**< \brief No error */ +#define MAPLE_EFAIL -1 /**< \brief Command failed */ +#define MAPLE_EAGAIN -2 /**< \brief Try again later */ +#define MAPLE_EINVALID -3 /**< \brief Invalid command */ +#define MAPLE_ENOTSUPP -4 /**< \brief Command not supported by device */ +#define MAPLE_ETIMEOUT -5 /**< \brief Command timed out */ +/** @} */ + +/**************************************************************************/ +/* maple_globals.c */ + +/** \cond Global state info. + + Do not manipulate this state yourself, as it will likely break things if you + do so. +*/ +extern maple_state_t maple_state; +/** \endcond */ + +/**************************************************************************/ +/* maple_utils.c */ + +/** \brief Enable the Maple bus. + \ingroup maple + + This will be done for you automatically at init time, and there's probably + not many reasons to be doing this during runtime. +*/ +void maple_bus_enable(void); + +/** \brief Disable the Maple bus. + \ingroup maple + + There's really not many good reasons to be mucking with this at runtime. +*/ +void maple_bus_disable(void); + +/** \brief Start a Maple DMA. + \ingroup maple + + This stuff will all be handled internally, so there's probably no reason to + be doing this yourself. +*/ +void maple_dma_start(void); + +/** \brief Stop a Maple DMA. + \ingroup maple + + This stuff will all be handled internally, so there's probably no reason to + be doing this yourself. +*/ +void maple_dma_stop(void); + +/** \brief Is a Maple DMA in progress? + \ingroup maple + + \return Non-zero if a DMA is in progress. +*/ +int maple_dma_in_progress(void); + +/** \brief Set the Maple DMA address. + \ingroup maple + + Once again, you should not muck around with this in your programs. +*/ +void maple_dma_addr(void *ptr); + +/** \brief Return a "maple address" for a port, unit pair. + \ingroup maple + + \param port The port to build the address for. + \param unit The unit to build the address for. + \return The Maple address of the pair. +*/ +uint8 maple_addr(int port, int unit); + +/** \brief Decompose a "maple address" into a port, unit pair. + \ingroup maple + + \warning + This function will not work with multi-cast addresses! + + \param addr The input address. + \param port Output space for the port of the address. + \param unit Output space for the unit of the address. +*/ +void maple_raddr(uint8 addr, int * port, int * unit); + +/** \brief Return a string with the capabilities of a given function code. + \ingroup maple + + This function is not re-entrant, and thus NOT THREAD SAFE. + + \param functions The list of function codes. + \return A string containing the capabilities. +*/ +const char * maple_pcaps(uint32 functions); + +/** \brief Return a string representing the maple response code. + \ingroup maple + + \param response The response code returned from the function. + \return A string containing a textual representation of the + response code. +*/ +const char * maple_perror(int response); + +/** \brief Determine if a given device is valid. + \ingroup maple + + \param p The port to check. + \param u The unit to check. + \return Non-zero if the device is valid. +*/ +int maple_dev_valid(int p, int u); + +/** \brief Enable light gun mode for this frame. + \ingroup maple + + This function enables light gun processing for the current frame of data. + Light gun mode will automatically be disabled when the data comes back for + this frame. + + \param port The port to enable light gun mode on. + \return MAPLE_EOK on success, MAPLE_EFAIL on error. +*/ +int maple_gun_enable(int port); + +/** \brief Disable light gun mode. + \ingroup maple + + There is probably very little reason to call this function. Light gun mode + is ordinarily disabled and is automatically disabled after the data has been + read from the device. The only reason to call this function is if you call + the maple_gun_enable() function, and then change your mind during the same + frame. +*/ +void maple_gun_disable(void); + +/** \brief Read the light gun position values. + \ingroup maple + + This function fetches the gun position values from the video hardware and + returns them via the parameters. These values are not normalized before + returning. + + \param x Storage for the horizontal position of the gun. + \param y Storage for the vertical position of the gun. + + \note The values returned from this function are the raw H and V counter + values from the video hardware where the gun registered its + position. The values, however, need a bit of massaging before they + correspond nicely to screen values. The y value is particularly odd + in interlaced modes due to the fact that you really have half as + many physical lines on the screen as you might expect. +*/ +void maple_gun_read_pos(int *x, int *y); + +#if MAPLE_DMA_DEBUG +/* Debugging help */ + +/** \brief Setup a sentinel for debugging DMA issues. + \ingroup maple + + \param buffer The buffer to add the sentinel to. + \param bufsize The size of the data in the buffer. +*/ +void maple_sentinel_setup(void * buffer, int bufsize); + +/** \brief Verify the presence of the sentine. + \ingroup maple + + \param bufname A string to recognize the buffer by. + \param buffer The buffer to check. + \param bufsize The size of the buffer. +*/ +void maple_sentinel_verify(const char * bufname, void * buffer, int bufsize); +#endif + +/**************************************************************************/ +/* maple_queue.c */ + +/** \brief Send all queued frames. + \ingroup maple + */ +void maple_queue_flush(void); + +/** \brief Submit a frame for queueing. + \ingroup maple + + This will generally be called inside the periodic interrupt; however, if you + need to do something asynchronously (e.g., VMU access) then it might cause + some problems. In this case, the function will automatically do locking by + disabling interrupts temporarily. In any case, the callback will be done + inside an IRQ context. + + \param frame The frame to queue up. + \retval 0 On success. + \retval -1 If the frame is already queued. +*/ +int maple_queue_frame(maple_frame_t *frame); + +/** \brief Remove a used frame from the queue. + \ingroup maple + + This will be done automatically when the frame is consumed. + + \param frame The frame to remove from the queue. + \retval 0 On success. + \retval -1 If the frame is not queued. +*/ +int maple_queue_remove(maple_frame_t *frame); + +/** \brief Initialize a new frame to prepare it to be placed on the queue. + \ingroup maple + + You should call this before you fill in the frame data. + + \param frame The frame to initialize. +*/ +void maple_frame_init(maple_frame_t *frame); + +/** \brief Lock a frame so that someone else can't use it in the mean time. + \ingroup maple + + \retval 0 On success. + \retval -1 If the frame is already locked. +*/ +int maple_frame_lock(maple_frame_t *frame); + +/** \brief Unlock a frame. + \ingroup maple + */ +void maple_frame_unlock(maple_frame_t *frame); + +/**************************************************************************/ +/* maple_driver.c */ + +/** \brief Register a maple device driver. + \ingroup maple + + This should be done before calling maple_init(). + + \retval 0 On success (no error conditions defined). +*/ +int maple_driver_reg(maple_driver_t *driver); + +/** \brief Unregister a maple device driver. + \ingroup maple + + \retval 0 On success (no error conditions defined). +*/ +int maple_driver_unreg(maple_driver_t *driver); + +/** \brief Attach a maple device to a driver, if possible. + \ingroup maple + + \param det The detection frame. + \retval 0 On success. + \retval -1 If no driver is available. +*/ +int maple_driver_attach(maple_frame_t *det); + +/** \brief Detach an attached maple device. + \ingroup maple + + \param p The port of the device to detach. + \param u The unit of the device to detach. + \retval 0 On success. + \retval -1 If the device wasn't valid. +*/ +int maple_driver_detach(int p, int u); + +/** \brief For each device which the given driver controls, call the callback. + \ingroup maple + + \param drv The driver to loop through devices of. + \param callback The function to call. The parameter is the device + that it is being called on. It should return 0 on + success, and <0 on failure. + \retval 0 On success. + \retval -1 If any callbacks return <0. +*/ +int maple_driver_foreach(maple_driver_t *drv, int (*callback)(maple_device_t *)); + +/** \brief Maple attach callback type. + \ingroup maple + + Functions of this type can be set with maple_attach_callback() to respond + automatically to the attachment of a maple device that supports specified + functions. +*/ +typedef void (*maple_attach_callback_t)(maple_device_t *dev); + +/** \brief Set an automatic maple attach callback. + \ingroup maple + + This function sets a callback function to be called when the specified + maple device that supports functions has been attached. + + \param functions The functions maple device must support. Set to + 0 to support all maple devices. + \param cb The callback to call when the maple is attached. +*/ +void maple_attach_callback(uint32 functions, maple_attach_callback_t cb); + +/** \brief Maple detach callback type. + \ingroup maple + + Functions of this type can be set with maple_detach_callback() to respond + automatically to the detachment of a maple device that supports specified + functions. +*/ +typedef void (*maple_detach_callback_t)(maple_device_t *dev); + +/** \brief Set an automatic maple detach callback. + \ingroup maple + + This function sets a callback function to be called when the specified + maple device that supports functions has been detached. + + \param functions The functions maple device must support. Set to + 0 to support all maple devices. + \param cb The callback to call when the maple is detached. +*/ +void maple_detach_callback(uint32 functions, maple_detach_callback_t cb); + +/**************************************************************************/ +/* maple_irq.c */ + +/** \brief Called on every VBL (~60fps). + \ingroup maple + + \param code The ASIC event code. + \param data The user pointer associated with this callback. +*/ +void maple_vbl_irq_hnd(uint32 code, void *data); + +/** \brief Called after a Maple DMA send / receive pair completes. + \ingroup maple + + \param code The ASIC event code. + \param data The user pointer associated with this callback. +*/ +void maple_dma_irq_hnd(uint32 code, void *data); + +/**************************************************************************/ +/* maple_enum.c */ + +/** \brief Return the number of connected devices. + \ingroup maple + + \return The number of devices connected. +*/ +int maple_enum_count(void); + +/** \brief Get a raw device info struct for the given device. + \ingroup maple + + \param p The port to look up. + \param u The unit to look up. + \return The device at that address, or NULL if no device is + there. +*/ +maple_device_t * maple_enum_dev(int p, int u); + +/** \brief Get the Nth device of the requested type (where N is zero-indexed). + \ingroup maple + + \param n The index to look up. + \param func The function code to look for. + \return The device found, if any. NULL otherwise. +*/ +maple_device_t * maple_enum_type(int n, uint32 func); + +/** \brief Return the Nth device that is of the requested type and supports the + list of capabilities given. + \ingroup maple + + Note, this only currently makes sense for controllers, since some devices + don't necessarily use the function data in the same manner that controllers + do (and controllers are the only devices where we have a list of what all + the bits mean at the moment). + + \param n The index to look up. + \param func The function code to look for. + \param cap Capabilities bits to look for. + \return The device found, if any. NULL otherwise. +*/ +maple_device_t * maple_enum_type_ex(int n, uint32 func, uint32 cap); + +/** \brief Get the status struct for the requested maple device. + \ingroup maple + + This function will wait until the status is valid before returning. + You should cast to the appropriate type you're expecting. + + \param dev The device to look up. + \return The device's status. +*/ +void * maple_dev_status(maple_device_t *dev); + +/**************************************************************************/ +/* maple_init.c */ + +/** \brief Initialize Maple. + \ingroup maple + */ +void maple_init(void); + +/** \brief Shutdown Maple. + \ingroup maple + */ +void maple_shutdown(void); + +/** \brief Wait for the initial bus scan to complete. + \ingroup maple + */ +void maple_wait_scan(void); + +/**************************************************************************/ +/* Convenience macros */ + +/* A "foreach" loop to scan all maple devices of a given type. It is used + like this: + + MAPLE_FOREACH_BEGIN(MAPLE_FUNC_CONTROLLER, cont_state_t, st) + if(st->buttons & CONT_START) + return -1; + MAPLE_FOREACH_END() + + The peripheral index can be obtained with __i, and the raw device struct + with __dev. The code inside the loop is guaranteed to be inside a block + (i.e., { code }) + */ + +/** \brief Begin a foreach loop over Maple devices. + \ingroup maple + + This macro (along with the MAPLE_FOREACH_END() one) implements a simple + foreach-style loop over the given type of devices. Essentially, it grabs the + status of the device, and leaves it to you to figure out what to do with it. + + The most common use of this would be to look for input on any controller. + + \param TYPE The function code of devices to look at. + \param VARTYPE The type to cast the return value of + maple_dev_status() to. + \param VAR The name of the result of maple_dev_status(). +*/ +#define MAPLE_FOREACH_BEGIN(TYPE, VARTYPE, VAR) \ + do { \ + maple_device_t * __dev; \ + VARTYPE * VAR; \ + int __i; \ + \ + __i = 0; \ + while( (__dev = maple_enum_type(__i, TYPE)) ) { \ + VAR = (VARTYPE *)maple_dev_status(__dev); \ + do { + +/** \brief End a foreach loop over Maple devices. + \ingroup maple + + Each MAPLE_FOREACH_BEGIN() must be paired with one of these after the loop + body. +*/ +#define MAPLE_FOREACH_END() \ + } while(0); \ + __i++; \ + } \ + } while(0); diff --git a/vendor/koshle/dc/maple/controller.h b/vendor/koshle/dc/maple/controller.h new file mode 100644 index 00000000..9a59ea73 --- /dev/null +++ b/vendor/koshle/dc/maple/controller.h @@ -0,0 +1,481 @@ + +#include "dc_hle_types.h" + +#include + +/** \defgroup controller Controller + \brief Controller Maple Device API + \ingroup peripherals + + This module contains the public API for the controller + maple driver. + + A standard, first-party Dreamcast controller has + the following button configuration: + + ___________ + / | __ | \ + L trigger ------| / | | | | \ |----- R trigger + _|__/ | |__| | \_|__ + | _ \____/ | + Joystick ----|-/ \ (Y) | + | \_/ (X) (B)| + | _ (A) | + | _| |_ | + D-Pad ----|-|_ _| | + | |_| /\ | + \ /__\ / + \ _____|_______ / + \ / | \ / + \/ | \/ + Start button + + You can grab a pointer to a connected controller by + using the following: + + maple_device_t *device = maple_enum_type(N, MAPLE_FUNC_CONTROLLER); + + if(device) printf("Controller found!\n"); + else printf("Controller not found!\n"); + + where N is the controller number. 0 would be the first + controller found, which may not necessarily be on port A. +*/ + +/** \defgroup controller_inputs Querying Inputs + \brief API used to query for input state + \ingroup controller + + The following API is used to check for a controller's input state. + + You can grab a controller's state structure, containing the state + of all of its inputs by using: + + cont_state_t *state = (cont_state_t *)maple_dev_status(device); + + Next you can check for the state of a particular button with: + + if(state->a) // Check via bitfield + printf("Pressed A".); + + or + + if(state->buttons & CONT_A) // Check via applying bitmask + printf("Pressed A.") +*/ + +/** \defgroup controller_input_masks Inputs + \brief Collection of all status masks for checking input + \ingroup controller_inputs + + A set of bitmasks representing each input source on a controller, used to + check its status. + + @{ +*/ +#define CONT_C (1<<0) /**< \brief C button Mask. */ +#define CONT_B (1<<1) /**< \brief B button Mask. */ +#define CONT_A (1<<2) /**< \brief A button Mask. */ +#define CONT_START (1<<3) /**< \brief Start button Mask. */ +#define CONT_DPAD_UP (1<<4) /**< \brief Main Dpad Up button Mask. */ +#define CONT_DPAD_DOWN (1<<5) /**< \brief Main Dpad Down button Mask. */ +#define CONT_DPAD_LEFT (1<<6) /**< \brief Main Dpad Left button Mask. */ +#define CONT_DPAD_RIGHT (1<<7) /**< \brief Main Dpad right button Mask. */ +#define CONT_Z (1<<8) /**< \brief Z button Mask. */ +#define CONT_Y (1<<9) /**< \brief Y button Mask. */ +#define CONT_X (1<<10) /**< \brief X button Mask. */ +#define CONT_D (1<<11) /**< \brief D button Mask. */ +#define CONT_DPAD2_UP (1<<12) /**< \brief Secondary Dpad Up button Mask. */ +#define CONT_DPAD2_DOWN (1<<13) /**< \brief Secondary Dpad Down button Mask. */ +#define CONT_DPAD2_LEFT (1<<14) /**< \brief Secondary Dpad Left button Mask. */ +#define CONT_DPAD2_RIGHT (1<<15) /**< \brief Secondary Dpad Right button Mask. */ +/** @} */ + +/** \brief Controller buttons for standard reset action + \ingroup controller_inputs + + Convenience macro providing the standard button combination + used as a reset mechanism by most retail games. +*/ +#define CONT_RESET_BUTTONS (CONT_A | CONT_B | CONT_X | CONT_Y | CONT_START) + +/** \brief Controller state structure. + \ingroup controller_inputs + + This structure contains information about the status of the controller + device and can be fetched by casting the result of maple_dev_status() to + this structure. + + A 1 bit in the buttons' bitfield indicates that a button is pressed, and the + joyx, joyy, joy2x, joy2 values are all 0 based (0 is centered). + + \note + Whether a particular field or button is actually used by the controller + depends upon its capabilities. See \ref controller_query_caps. + + \sa maple_dev_status +*/ +typedef struct cont_state { + union { + /** \brief bit-packed controller button states + \sa controller_buttons + */ + uint32_t buttons; + struct { + uint32_t c: 1; /**< \brief C button value. */ + uint32_t b: 1; /**< \brief B button value. */ + uint32_t a: 1; /**< \brief A button value. */ + uint32_t start: 1; /**< \brief Start button value. */ + uint32_t dpad_up: 1; /**< \brief Main Dpad Up button value. */ + uint32_t dpad_down: 1; /**< \brief Main Dpad Down button value. */ + uint32_t dpad_left: 1; /**< \brief Main Dpad Left button value. */ + uint32_t dpad_right: 1; /**< \brief Main Dpad Right button value. */ + uint32_t z: 1; /**< \brief Z button value. */ + uint32_t y: 1; /**< \brief Y button value. */ + uint32_t x: 1; /**< \brief X button value. */ + uint32_t d: 1; /**< \brief D button value. */ + uint32_t dpad2_up: 1; /**< \brief Secondary Dpad Up button value. */ + uint32_t dpad2_down: 1; /**< \brief Secondary Dpad Down button value. */ + uint32_t dpad2_left: 1; /**< \brief Secondary Dpad Left button value. */ + uint32_t dpad2_right: 1; /**< \brief Secondary Dpad Right button value. */ + uint32_t: 16; + }; + }; + + int ltrig; /**< \brief Left trigger value (0-255). */ + int rtrig; /**< \brief Right trigger value (0-255). */ + int joyx; /**< \brief Main joystick x-axis value (-128 - 127). */ + int joyy; /**< \brief Main joystick y-axis value. */ + int joy2x; /**< \brief Secondary joystick x-axis value. */ + int joy2y; /**< \brief Secondary joystick y-axis value. */ +} cont_state_t; + +/** \brief Controller automatic callback type. + \ingroup controller_inputs + + Functions of this type can be set with cont_btn_callback() to respond + automatically to the specified set of buttons being pressed. This can be + used, for instance, to implement the standard A+B+X+Y+Start method of ending + the program running. + + \param addr Maple BUS address to poll for the button mask + on, or 0 for all ports. + \param btns Mask of all buttons which should be pressed to + trigger the callback. + + \sa cont_btn_callback +*/ +typedef void (*cont_btn_callback_t)(uint8_t addr, uint32_t btns); + +/** \brief Set an automatic button press callback. + \ingroup controller_inputs + + This function sets a callback function to be called when the specified + controller has the set of buttons given pressed. + + \note + The callback gets invoked for the given maple port; however, providing + an address of '0' will cause it to be invoked for any port with a + device pressing the given buttons. Since you are passed back the address + of this device, You are free to implement your own filtering logic within + your callback. Any callback with addr==0 will be installed to the end of + the list of callbacks and will run after callbacks with the same btns but + a specified address. + + \param addr The controller to listen on (or 0 for all ports). + This value can be obtained by using maple_addr(). + \param btns The buttons bitmask to match. + \param cb The callback to call when the buttons are pressed. + Passing NULL will uninstall all callbacks on the + addr/btns combination. +*/ +int cont_btn_callback(uint8_t addr, uint32_t btns, cont_btn_callback_t cb); + +/** \defgroup controller_query_caps Querying Capabilities + \brief API used to query for a controller's capabilities + \ingroup controller + + The following API is used to query for the support of individual + or groups of capabilities by a particular device. +*/ + +/** \defgroup controller_caps Capabilities + \brief Bit masks used to identify controller capabilities + \ingroup controller_query_caps + + These bits will be set in the function_data for the controller's deviceinfo + if the controller supports the corresponding button/axis capability. + + \note + The ordering here is so that they match the order found in + \ref controller_input_masks. + + @{ +*/ +#define CONT_CAPABILITY_C (1<<24) /**< \brief C button capability mask. */ +#define CONT_CAPABILITY_B (1<<25) /**< \brief B button capability mask. */ +#define CONT_CAPABILITY_A (1<<26) /**< \brief A button capability mask. */ +#define CONT_CAPABILITY_START (1<<27) /**< \brief Start button capability mask. */ +#define CONT_CAPABILITY_DPAD_UP (1<<28) /**< \brief First Dpad up capability mask. */ +#define CONT_CAPABILITY_DPAD_DOWN (1<<29) /**< \brief First Dpad down capability mask. */ +#define CONT_CAPABILITY_DPAD_LEFT (1<<30) /**< \brief First Dpad left capability mask. */ +#define CONT_CAPABILITY_DPAD_RIGHT (1<<31) /**< \brief First Dpad right capability mask. */ +#define CONT_CAPABILITY_Z (1<<16) /**< \brief Z button capability mask. */ +#define CONT_CAPABILITY_Y (1<<17) /**< \brief Y button capability mask. */ +#define CONT_CAPABILITY_X (1<<18) /**< \brief X button capability mask. */ +#define CONT_CAPABILITY_D (1<<19) /**< \brief D button capability mask. */ +#define CONT_CAPABILITY_DPAD2_UP (1<<20) /**< \brief Second Dpad up capability mask. */ +#define CONT_CAPABILITY_DPAD2_DOWN (1<<21) /**< \brief Second Dpad down capability mask. */ +#define CONT_CAPABILITY_DPAD2_LEFT (1<<22) /**< \brief Second Dpad left capability mask. */ +#define CONT_CAPABILITY_DPAD2_RIGHT (1<<23) /**< \brief Second Dpad right capability mask. */ +#define CONT_CAPABILITY_RTRIG (1<<8) /**< \brief Right trigger capability mask. */ +#define CONT_CAPABILITY_LTRIG (1<<9) /**< \brief Left trigger capability mask. */ +#define CONT_CAPABILITY_ANALOG_X (1<<10) /**< \brief First analog X axis capability mask. */ +#define CONT_CAPABILITY_ANALOG_Y (1<<11) /**< \brief First analog Y axis capability mask. */ +#define CONT_CAPABILITY_ANALOG2_X (1<<12) /**< \brief Second analog X axis capability mask. */ +#define CONT_CAPABILITY_ANALOG2_Y (1<<13) /**< \brief Second analog Y axis capability mask. */ +/** @} */ + +/** \defgroup controller_caps_groups Capability Groups + \brief Bit masks representing common groups of capabilities + \ingroup controller_query_caps + + These are a sets of capabilities providing a + convenient way to test for high-level features, + such as dual-analog sticks or extra buttons. + + @{ +*/ +/** \brief Standard button (A, B, X, Y, Start) controller capabilities */ +#define CONT_CAPABILITIES_STANDARD_BUTTONS (CONT_CAPABILITY_A | \ + CONT_CAPABILITY_B | \ + CONT_CAPABILITY_X | \ + CONT_CAPABILITY_Y | \ + CONT_CAPABILITY_START) + +/** \brief Directional pad (up, down, left right) controller capabilities */ +#define CONT_CAPABILITIES_DPAD (CONT_CAPABILITY_DPAD_UP | \ + CONT_CAPABILITY_DPAD_DOWN | \ + CONT_CAPABILITY_DPAD_LEFT | \ + CONT_CAPABILITY_DPAD_RIGHT) + +/** \brief Analog stick (X, Y axes) controller capabilities */ +#define CONT_CAPABILITIES_ANALOG (CONT_CAPABILITY_ANALOG_X | \ + CONT_CAPABILITY_ANALOG_Y) + +/** \brief Trigger (L, R lever) controller capabilities */ +#define CONT_CAPABILITIES_TRIGGERS (CONT_CAPABILITY_LTRIG | \ + CONT_CAPABILITY_RTRIG) + +/** \brief Extended button (C, Z) controller capabilities */ +#define CONT_CAPABILITIES_EXTENDED_BUTTONS (CONT_CAPABILITY_C | \ + CONT_CAPABILITY_Z) + +/** \brief Secondary directional pad (up, down, left, right) controller capabilities */ +#define CONT_CAPABILITIES_SECONDARY_DPAD (CONT_CAPABILITY_DPAD2_UP | \ + CONT_CAPABILITY_DPAD2_DOWN | \ + CONT_CAPABILITY_DPAD2_LEFT | \ + CONT_CAPABILITY_DPAD2_RIGHT) + +/** \brief Secondary analog stick (X, Y axes) controller capabilities */ +#define CONT_CAPABILITIES_SECONDARY_ANALOG (CONT_CAPABILITY_ANALOG2_X | \ + CONT_CAPABILITY_ANALOG2_Y) + +/** \brief Both directional pads (up, down, left right) controller capabilities */ +#define CONT_CAPABILITIES_DUAL_DPAD (CONT_CAPABILITIES_DPAD | \ + CONT_CAPABILITIES_SECONDARY_DPAD) + +/** \brief Both analog sticks (X, Y axes) controller capabilities */ +#define CONT_CAPABILITIES_DUAL_ANALOG (CONT_CAPABILITIES_ANALOG | \ + CONT_CAPABILITIES_SECONDARY_ANALOG) + +/* Forward declaration */ +struct maple_device; + +/** \brief Check for controller capabilities + \ingroup controller_query_caps + + Checks whether or not a controller implements the capabilities + associated with the given type. + + \note + Controller capability reporting is an extremely generic mechanism, + such that many peripherals may implement the same capability in + completely different ways. For example, the Samba De Amigo maraca + controller will advertise itself as a dual-analog device, with each + maraca being an analog stick. + + \param cont Pointer to a Maple device structure which + implements the CONTROLLER function. + \param capabilities Capability mask the controller is expected + to implement + + \retval 1 The controller supports the given capabilities. + \retval 0 The controller doesn't support the given capabilities. + \retval -1 Invalid controller. + + \sa cont_is_type +*/ +int cont_has_capabilities(const struct maple_device *cont, uint32_t capabilities); +/** @} */ + +/** \defgroup controller_query_types Querying Types + \brief API for determining controller types + \ingroup controller + + The following API is for detecting between different types + of standard controllers. These controllers are not identified + by specific model but are instead identified solely by capabilities, + so that homebrew software can remain generic and future-proof to later + homebrew controllers or exotic, untested 3rd party peripherals. + + \warning + Usually you want to check if a controller supports the + capabilities of another controller, not whether it is has + the exact same capabilities of a controller. For example, + a controller that happens to come along supporting a dual analog + stick but is otherwise the same layout as a standard controller + would not match the standard controller type; however, it would + implement its capabilities. There exist 3rd party adapters for + connecting dual-analog PS2 controllers to DC which operate + like this today. + + \note + If you really want to hard-code the detection of a certain + exact model or brand of controller, instead of basing your + detection upon capabilities, check for its product_name + or license within the maple_devinfo structure. + + \sa cont_has_capabilities, maple_devinfo +*/ + +/** \defgroup controller_types Types + \brief Preconfigured capabilities for standard controllers + \ingroup controller_query_types + + Aggregate capability mask containing all capabilities + which are implemented for a particular controller type. + For example, the standard controller type is simply a + combination of the following capabilities: + - Standard buttons + - Triggers + - Dpad + - Analog + + \note + Because these are technically just capability masks, + a type may also be passed to cont_has_capabilities() + for detecting whether something has at least + the capabilities of a type. + + @{ +*/ +/** \brief Standard controller type */ +#define CONT_TYPE_STANDARD_CONTROLLER (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITIES_TRIGGERS | \ + CONT_CAPABILITIES_DPAD | \ + CONT_CAPABILITIES_ANALOG) + +/** \brief Dual analog controller type */ +#define CONT_TYPE_DUAL_ANALOG_CONTROLLER (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITIES_TRIGGERS | \ + CONT_CAPABILITIES_DPAD | \ + CONT_CAPABILITIES_DUAL_ANALOG) + +/** \brief ASCII fighting pad controller type */ +#define CONT_TYPE_ASCII_PAD (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITIES_EXTENDED_BUTTONS | \ + CONT_CAPABILITIES_DPAD) + +/** \brief Arcade stick controller type */ +#define CONT_TYPE_ARCADE_STICK (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITIES_EXTENDED_BUTTONS | \ + CONT_CAPABILITIES_DPAD) + +/** \brief Twin stick joystick controller type */ +#define CONT_TYPE_TWIN_STICK (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITIES_EXTENDED_BUTTONS | \ + CONT_CAPABILITY_D | \ + CONT_CAPABILITIES_DUAL_DPAD) + +/** \brief ASCII Mission Stick controller type */ +#define CONT_TYPE_ASCII_MISSION_STICK (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITIES_DUAL_DPAD | \ + CONT_CAPABILITIES_TRIGGERS | \ + CONT_CAPABILITIES_ANALOG) + +/** \brief Racing wheel/controller type */ +#define CONT_TYPE_RACING_CONTROLLER (CONT_CAPABILITY_DPAD_UP | \ + CONT_CAPABILITY_DPAD_DOWN | \ + CONT_CAPABILITY_A | \ + CONT_CAPABILITY_B | \ + CONT_CAPABILITY_START | \ + CONT_CAPABILITIES_TRIGGERS | \ + CONT_CAPABILITY_ANALOG_X \ + CONT_CAPABILITIES_SECONDARY_ANALOG) + +/** \brief Samba De Amigo maraca controller type */ +#define CONT_TYPE_MARACAS (CONT_CAPABILITY_A | \ + CONT_CAPABILITY_B | \ + CONT_CAPABILITY_D | \ + CONT_CAPABILITY_START | \ + CONT_CAPABILITIES_EXTENDED_BUTTONS | \ + CONT_CAPABILITIES_DUAL_ANALOG) + +/** \brief Dance Dance Revolution mat controller type */ +#define CONT_TYPE_DANCE_MAT (CONT_CAPABILITY_A | \ + CONT_CAPABILITY_B | \ + CONT_CAPABILITY_START | \ + CONT_CAPABILITIES_DPAD) + +/** \brief Fishing rod controller type */ +#define CONT_TYPE_FISHING_ROD (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITIES_DPAD | \ + CONT_CAPABILITIES_TRIGGERS | \ + CONT_CAPABILITIES_DUAL_ANALOG) + +/** \brief Pop'n'Music controller type */ +#define CONT_TYPE_POP_N_MUSIC (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITY_C | \ + CONT_CAPABILITIES_DPAD) + +/** \brief Densha de Go! controller type */ +#define CONT_TYPE_DENSHA_DE_GO (CONT_CAPABILITIES_STANDARD_BUTTONS | \ + CONT_CAPABILITIES_EXTENDED_BUTTONS | \ + CONT_CAPABILITY_D | \ + CONT_CAPABILITIES_DPAD) +/** @} */ + +/** \brief Check for controller type + \ingroup controller_query_types + + Checks whether or not a controller has the exact + capabilities associated with the given type. + + \warning + Just because a controller has all of the same capabilities of a + type does not mean that it's that exact type. For example, the + ASCII Pad and Arcade Stick both implement the same capabilities, + although they are not the same controllers. They would be + indistinguishable here, by design, so that you are able to + generalize to a collection of 1st or 3rd party controllers + easily. + + \param cont Pointer to a Maple device structure which + implements the CONTROLLER function. + \param type Type identifier or capability mask the + controller is expected to match + + \retval 1 The controller matches the given type. + \retval 0 The controller doesn't match the given type. + \retval -1 Invalid controller. + + \sa cont_has_capabilities +*/ +int cont_is_type(const struct maple_device *cont, uint32_t type); + +/* \cond */ +/* Init / Shutdown */ +void cont_init(void); +void cont_shutdown(void); +/* \endcond */ diff --git a/vendor/koshle/dc/matrix.h b/vendor/koshle/dc/matrix.h new file mode 100644 index 00000000..ba445039 --- /dev/null +++ b/vendor/koshle/dc/matrix.h @@ -0,0 +1,56 @@ +/* KallistiOS ##version## + + dc/matrix.h + Copyright (C) 2000 Megan Potter + Copyright (C) 2013, 2014 Josh "PH3NOM" Pearson + Copyright (C) 2018 Lawrence Sebald + +*/ + +/** \file dc/matrix.h + \brief Basic matrix operations. + \ingroup math_matrices + + This file contains various basic matrix math functionality for using the + SH4's matrix transformation unit. Higher level functionality, like the 3D + functionality is built off of these operations. + + \see dc/matrix3d.h + + \author Megan Potter + \author Josh "PH3NOM" Pearson +*/ + +#ifndef __DC_MATRIX_H +#define __DC_MATRIX_H + +typedef __attribute__ ((aligned (8))) float matrix_t[4][4]; + +typedef struct vec3f { + float x, y, z; +} vec3f_t; + +typedef struct vectorstr { + float x, y, z, w; +} vector_t; + +extern matrix_t XMTRX; + +void mat_store(matrix_t *out); +void mat_load(const matrix_t *out); +void mat_identity(void); +void mat_apply(const matrix_t *src); +void mat_transform(vector_t *invecs, vector_t *outvecs, int veccnt, int vecskip); + + +void mat_trans_single3_nodiv(float &x, float &y, float &z); + +#define mat_trans_single3_nomod(x_, y_, z_, x2, y2, z2) do { \ + vector_t tmp = { x_, y_, z_, 1.0f }; \ + mat_transform(&tmp, &tmp, 1, 0); \ + z2 = 1.0f / tmp.w; \ + x2 = tmp.x * z2; \ + y2 = tmp.y * z2; \ + } while(false) + +#endif /* !__DC_MATRIX_H */ \ No newline at end of file diff --git a/vendor/koshle/dc/pvr.h b/vendor/koshle/dc/pvr.h new file mode 100644 index 00000000..d2a152a7 --- /dev/null +++ b/vendor/koshle/dc/pvr.h @@ -0,0 +1,2549 @@ +#pragma once + +#include "dc_hle_types.h" +#include +#include +#include + +uint32 pvrRegRead(uint32 A); +void pvrRegWrite(uint32 A, uint32 D); +void pvr_ta_data(void*, int); +/** \defgroup pvr PowerVR API + \brief Low-level PowerVR GPU Driver. + \ingroup video +*/ + +/* Data types ********************************************************/ + +/** \brief PVR texture memory pointer. + \ingroup pvr_vram + + Unlike the old "TA" system, PVR pointers in the new system are actually SH-4 + compatible pointers and can be used directly in place of ta_txr_map(). + + Not that anyone probably even remembers the old TA system anymore... +*/ +typedef void *pvr_ptr_t; + +/** \defgroup pvr_lists Polygon Lists + \brief Types pertaining to PVR list types: opaque, pt, tr, etc + \ingroup pvr +*/ + +/** \brief PVR list specification. + \ingroup pvr_lists + + Each primitive in the PVR is submitted to one of the hardware primitive + lists. This type is an identifier for a list. + + \see pvr_lists +*/ +typedef uint32_t pvr_list_t; + +/** \defgroup pvr_geometry Geometry + \brief PVR API for managing scene geometry + \ingroup pvr +*/ + +/** \defgroup pvr_primitives Primitives + \brief Polygon and sprite management + \ingroup pvr_geometry +*/ + +/** \defgroup pvr_ctx Contexts + \brief User-friendly intermittent primitive representation + \ingroup pvr_primitives +*/ + +/** \brief PVR polygon context. + \ingroup pvr_ctx + + You should use this more human readable format for specifying your polygon + contexts, and then compile them into polygon headers when you are ready to + start using them. + + This has embedded structures in it for two reasons; the first reason is to + make it easier for me to add new stuff later without breaking existing code. + The second reason is to make it more readable and usable. + + Unfortunately, it seems that Doxygen chokes up a little bit on this + structure, and others like it. The documentation should still be mostly + understandable though... + + \headerfile dc/pvr.h +*/ +typedef struct { + int list_type; /**< \brief Primitive list + \see pvr_lists */ + struct { + int alpha; /**< \brief Enable or disable alpha outside modifier + \see pvr_alpha_switch */ + int shading; /**< \brief Shading type + \see pvr_shading_types */ + int fog_type; /**< \brief Fog type outside modifier + \see pvr_fog_types */ + int culling; /**< \brief Culling mode + \see pvr_cull_modes */ + int color_clamp; /**< \brief Color clamp enable/disable outside modifier + \see pvr_colclamp_switch */ + int clip_mode; /**< \brief Clipping mode + \see pvr_clip_modes */ + int modifier_mode; /**< \brief Modifier mode */ + int specular; /**< \brief Offset color enable/disable outside modifier + \see pvr_offset_switch */ + int alpha2; /**< \brief Enable/disable alpha inside modifier + \see pvr_alpha_switch */ + int fog_type2; /**< \brief Fog type inside modifier + \see pvr_fog_types */ + int color_clamp2; /**< \brief Color clamp enable/disable inside modifier + \see pvr_colclamp_switch */ + } gen; /**< \brief General parameters */ + struct { + int src; /**< \brief Source blending mode outside modifier + \see pvr_blend_modes */ + int dst; /**< \brief Dest blending mode outside modifier + \see pvr_blend_modes */ + int src_enable; /**< \brief Source blending enable outside modifier + \see pvr_blend_switch */ + int dst_enable; /**< \brief Dest blending enable outside modifier + \see pvr_blend_switch */ + int src2; /**< \brief Source blending mode inside modifier + \see pvr_blend_modes */ + int dst2; /**< \brief Dest blending mode inside modifier + \see pvr_blend_modes */ + int src_enable2; /**< \brief Source blending mode inside modifier + \see pvr_blend_switch */ + int dst_enable2; /**< \brief Dest blending mode inside modifier + \see pvr_blend_switch */ + } blend; /**< \brief Blending parameters */ + struct { + int color; /**< \brief Color format in vertex + \see pvr_color_fmts */ + int uv; /**< \brief U/V data format in vertex + \see pvr_uv_fmts */ + int modifier; /**< \brief Enable or disable modifier effect + \see pvr_mod_switch */ + } fmt; /**< \brief Format control */ + struct { + int comparison; /**< \brief Depth comparison mode + \see pvr_depth_modes */ + int write; /**< \brief Enable or disable depth writes + \see pvr_depth_switch */ + } depth; /**< \brief Depth comparison/write modes */ + struct { + int enable; /**< \brief Enable/disable texturing + \see pvr_txr_switch */ + int filter; /**< \brief Filtering mode + \see pvr_filter_modes */ + int mipmap; /**< \brief Enable/disable mipmaps + \see pvr_mip_switch */ + int mipmap_bias; /**< \brief Mipmap bias + \see pvr_mip_bias */ + int uv_flip; /**< \brief Enable/disable U/V flipping + \see pvr_uv_flip */ + int uv_clamp; /**< \brief Enable/disable U/V clamping + \see pvr_uv_clamp */ + int alpha; /**< \brief Enable/disable texture alpha + \see pvr_txralpha_switch */ + int env; /**< \brief Texture color contribution + \see pvr_txrenv_modes */ + int width; /**< \brief Texture width (requires a power of 2) */ + int height; /**< \brief Texture height (requires a power of 2) */ + int format; /**< \brief Texture format + \see pvr_txr_fmts */ + pvr_ptr_t base; /**< \brief Texture pointer */ + } txr; /**< \brief Texturing params outside modifier */ + struct { + int enable; /**< \brief Enable/disable texturing + \see pvr_txr_switch */ + int filter; /**< \brief Filtering mode + \see pvr_filter_modes */ + int mipmap; /**< \brief Enable/disable mipmaps + \see pvr_mip_switch */ + int mipmap_bias; /**< \brief Mipmap bias + \see pvr_mip_bias */ + int uv_flip; /**< \brief Enable/disable U/V flipping + \see pvr_uv_flip */ + int uv_clamp; /**< \brief Enable/disable U/V clamping + \see pvr_uv_clamp */ + int alpha; /**< \brief Enable/disable texture alpha + \see pvr_txralpha_switch */ + int env; /**< \brief Texture color contribution + \see pvr_txrenv_modes */ + int width; /**< \brief Texture width (requires a power of 2) */ + int height; /**< \brief Texture height (requires a power of 2) */ + int format; /**< \brief Texture format + \see pvr_txr_fmts */ + pvr_ptr_t base; /**< \brief Texture pointer */ + } txr2; /**< \brief Texturing params inside modifier */ +} pvr_poly_cxt_t; + +/** \brief PVR sprite context. + \ingroup pvr_ctx + + You should use this more human readable format for specifying your sprite + contexts, and then compile them into sprite headers when you are ready to + start using them. + + Unfortunately, it seems that Doxygen chokes up a little bit on this + structure, and others like it. The documentation should still be mostly + understandable though... + + \headerfile dc/pvr.h +*/ +typedef struct { + int list_type; /**< \brief Primitive list + \see pvr_lists */ + struct { + int alpha; /**< \brief Enable or disable alpha + \see pvr_alpha_switch */ + int fog_type; /**< \brief Fog type + \see pvr_fog_types */ + int culling; /**< \brief Culling mode + \see pvr_cull_modes */ + int color_clamp; /**< \brief Color clamp enable/disable + \see pvr_colclamp_switch */ + int clip_mode; /**< \brief Clipping mode + \see pvr_clip_modes */ + int specular; /**< \brief Offset color enable/disable + \see pvr_offset_switch */ + } gen; /**< \brief General parameters */ + struct { + int src; /**< \brief Source blending mode + \see pvr_blend_modes */ + int dst; /**< \brief Dest blending mode + \see pvr_blend_modes */ + int src_enable; /**< \brief Source blending enable + \see pvr_blend_switch */ + int dst_enable; /**< \brief Dest blending enable + \see pvr_blend_switch */ + } blend; + struct { + int comparison; /**< \brief Depth comparison mode + \see pvr_depth_modes */ + int write; /**< \brief Enable or disable depth writes + \see pvr_depth_switch */ + } depth; /**< \brief Depth comparison/write modes */ + struct { + int enable; /**< \brief Enable/disable texturing + \see pvr_txr_switch */ + int filter; /**< \brief Filtering mode + \see pvr_filter_modes */ + int mipmap; /**< \brief Enable/disable mipmaps + \see pvr_mip_switch */ + int mipmap_bias; /**< \brief Mipmap bias + \see pvr_mip_bias */ + int uv_flip; /**< \brief Enable/disable U/V flipping + \see pvr_uv_flip */ + int uv_clamp; /**< \brief Enable/disable U/V clamping + \see pvr_uv_clamp */ + int alpha; /**< \brief Enable/disable texture alpha + \see pvr_txralpha_switch */ + int env; /**< \brief Texture color contribution + \see pvr_txrenv_modes */ + int width; /**< \brief Texture width (requires a power of 2) */ + int height; /**< \brief Texture height (requires a power of 2) */ + int format; /**< \brief Texture format + \see pvr_txr_fmts */ + pvr_ptr_t base; /**< \brief Texture pointer */ + } txr; /**< \brief Texturing params */ +} pvr_sprite_cxt_t; + +/* Constants for the above structure; thanks to Benoit Miller for these */ + +/** \defgroup pvr_lists_types Types + \brief Values of various PVR polygon list types + \ingroup pvr_lists + + Each primitive submitted to the PVR must be placed in one of these lists, + depending on its characteristics. + + @{ +*/ +#define PVR_LIST_OP_POLY 0 /**< \brief Opaque polygon list */ +#define PVR_LIST_OP_MOD 1 /**< \brief Opaque modifier list */ +#define PVR_LIST_TR_POLY 2 /**< \brief Translucent polygon list */ +#define PVR_LIST_TR_MOD 3 /**< \brief Translucent modifier list*/ +#define PVR_LIST_PT_POLY 4 /**< \brief Punch-thru polygon list */ +/** @} */ + +/** \defgroup pvr_ctx_attrib Attributes + \brief PVR primitive context attributes + \ingroup pvr_ctx +*/ + +/** \defgroup pvr_shading_types Shading Modes + \brief PowerVR primitive context shading modes + \ingroup pvr_ctx_attrib + + Each polygon can define how it wants to be shaded, be it with flat or + Gouraud shading using these constants in the appropriate place in its + pvr_poly_cxt_t. + + @{ +*/ +#define PVR_SHADE_FLAT 0 /**< \brief Use flat shading */ +#define PVR_SHADE_GOURAUD 1 /**< \brief Use Gouraud shading */ +/** @} */ + +/** \defgroup pvr_ctx_depth Depth + \brief Depth attributes for PVR polygon contexts + \ingroup pvr_ctx_attrib +*/ + +/** \defgroup pvr_depth_modes Comparison Modes + \brief PowerVR depth comparison modes + \ingroup pvr_ctx_depth + + These set the depth function used for comparisons. + + @{ +*/ +#define PVR_DEPTHCMP_NEVER 0 /**< \brief Never pass */ +#define PVR_DEPTHCMP_LESS 1 /**< \brief Less than */ +#define PVR_DEPTHCMP_EQUAL 2 /**< \brief Equal to */ +#define PVR_DEPTHCMP_LEQUAL 3 /**< \brief Less than or equal to */ +#define PVR_DEPTHCMP_GREATER 4 /**< \brief Greater than */ +#define PVR_DEPTHCMP_NOTEQUAL 5 /**< \brief Not equal to */ +#define PVR_DEPTHCMP_GEQUAL 6 /**< \brief Greater than or equal to */ +#define PVR_DEPTHCMP_ALWAYS 7 /**< \brief Always pass */ +/** @} */ + +/** \defgroup pvr_cull_modes Culling Modes + \brief PowerVR primitive context culling modes + \ingroup pvr_ctx_attrib + + These culling modes can be set by polygons to determine when they are + culled. They work pretty much as you'd expect them to if you've ever used + any 3D hardware before. + + @{ +*/ +#define PVR_CULLING_NONE 0 /**< \brief Disable culling */ +#define PVR_CULLING_SMALL 1 /**< \brief Cull if small */ +#define PVR_CULLING_CCW 2 /**< \brief Cull if counterclockwise */ +#define PVR_CULLING_CW 3 /**< \brief Cull if clockwise */ +/** @} */ + +/** \defgroup pvr_depth_switch Write Toggle + \brief Enable or Disable Depth Writes. + \ingroup pvr_ctx_depth + @{ +*/ +#define PVR_DEPTHWRITE_ENABLE 0 /**< \brief Update the Z value */ +#define PVR_DEPTHWRITE_DISABLE 1 /**< \brief Do not update the Z value */ +/** @} */ + +/** \defgroup pvr_ctx_texture Texture + \brief Texture attributes for PVR polygon contexts + \ingroup pvr_ctx_attrib +*/ + +/** \defgroup pvr_txr_switch Toggle + \brief Enable or Disable Texturing on Polygons. + \ingroup pvr_ctx_texture + + @{ +*/ +#define PVR_TEXTURE_DISABLE 0 /**< \brief Disable texturing */ +#define PVR_TEXTURE_ENABLE 1 /**< \brief Enable texturing */ +/** @} */ + +/** \defgroup pvr_blend Blending + \brief Blending attributes for PVR primitive contexts + \ingroup pvr_ctx_attrib +*/ + +/** \defgroup pvr_blend_modes Blending Modes + \brief Blending modes for PowerVR primitive contexts + \ingroup pvr_blend + + These are all the blending modes that can be done with regard to alpha + blending on the PVR. + + @{ +*/ +#define PVR_BLEND_ZERO 0 /**< \brief None of this color */ +#define PVR_BLEND_ONE 1 /**< \brief All of this color */ +#define PVR_BLEND_DESTCOLOR 2 /**< \brief Destination color */ +#define PVR_BLEND_INVDESTCOLOR 3 /**< \brief Inverse of destination color */ +#define PVR_BLEND_SRCALPHA 4 /**< \brief Blend with source alpha */ +#define PVR_BLEND_INVSRCALPHA 5 /**< \brief Blend with inverse source alpha */ +#define PVR_BLEND_DESTALPHA 6 /**< \brief Blend with destination alpha */ +#define PVR_BLEND_INVDESTALPHA 7 /**< \brief Blend with inverse destination alpha */ +/** @} */ + +/** \defgroup pvr_blend_switch Blending Toggle + \brief Enable or Disable Blending. + \ingroup pvr_blend + + @{ +*/ +#define PVR_BLEND_DISABLE 0 /**< \brief Disable blending */ +#define PVR_BLEND_ENABLE 1 /**< \brief Enable blending */ +/** @} */ + +/** \defgroup pvr_fog_types Fog Modes + \brief PowerVR primitive context fog modes + \ingroup pvr_ctx_attrib + + Each polygon can decide what fog type is used with regard to it using these + constants in its pvr_poly_cxt_t. + + @{ +*/ +#define PVR_FOG_TABLE 0 /**< \brief Table fog */ +#define PVR_FOG_VERTEX 1 /**< \brief Vertex fog */ +#define PVR_FOG_DISABLE 2 /**< \brief Disable fog */ +#define PVR_FOG_TABLE2 3 /**< \brief Table fog mode 2 */ +/** @} */ + +/** \defgroup pvr_clip_modes Clipping Modes + \brief PowerVR primitive context clipping modes + \ingroup pvr_ctx_attrib + + These control how primitives are clipped against the user clipping area. + + @{ +*/ +#define PVR_USERCLIP_DISABLE 0 /**< \brief Disable clipping */ +#define PVR_USERCLIP_INSIDE 2 /**< \brief Enable clipping inside area */ +#define PVR_USERCLIP_OUTSIDE 3 /**< \brief Enable clipping outside area */ +/** @} */ + +/** \defgroup pvr_ctx_color Color + \brief Color attributes for PowerVR primitive contexts + \ingroup pvr_ctx_attrib +*/ + +/** \defgroup pvr_colclamp_switch Clamping Toggle + \brief Enable or Disable Color Clamping + \ingroup pvr_ctx_color + + Enabling color clamping will clamp colors between the minimum and maximum + values before any sort of fog processing. + + @{ +*/ +#define PVR_CLRCLAMP_DISABLE 0 /**< \brief Disable color clamping */ +#define PVR_CLRCLAMP_ENABLE 1 /**< \brief Enable color clamping */ +/** @} */ + +/** \defgroup pvr_offset_switch Offset Toggle + \brief Enable or Disable Offset Color + \ingroup pvr_ctx_color + + Enabling offset color calculation allows for "specular" like effects on a + per-vertex basis, by providing an additive color in the calculation of the + final pixel colors. In vertex types with a "oargb" parameter, that's what it + is for. + + \note + This must be enabled for bumpmap polygons in order to allow you to + specify the parameters in the oargb field of the vertices. + + @{ +*/ +#define PVR_SPECULAR_DISABLE 0 /**< \brief Disable offset colors */ +#define PVR_SPECULAR_ENABLE 1 /**< \brief Enable offset colors */ +/** @} */ + +/** \defgroup pvr_alpha_switch Alpha Toggle + \brief Enable or Disable Alpha Blending + \ingroup pvr_blend + + This causes the alpha value in the vertex color to be paid attention to. It + really only makes sense to enable this for translucent or punch-thru polys. + + @{ +*/ +#define PVR_ALPHA_DISABLE 0 /**< \brief Disable alpha blending */ +#define PVR_ALPHA_ENABLE 1 /**< \brief Enable alpha blending */ +/** @} */ + +/** \defgroup pvr_txralpha_switch Alpha Toggle + \brief Enable or Disable Texture Alpha Blending + \ingroup pvr_ctx_texture + + This causes the alpha value in the texel color to be paid attention to. It + really only makes sense to enable this for translucent or punch-thru polys. + + @{ +*/ +#define PVR_TXRALPHA_ENABLE 0 /**< \brief Enable alpha blending */ +#define PVR_TXRALPHA_DISABLE 1 /**< \brief Disable alpha blending */ +/** @} */ + +/** \defgroup pvr_uv_flip U/V Flip Mode + \brief Enable or disable U/V flipping on the PVR + \ingroup pvr_ctx_texture + + These flags determine what happens when U/V coordinate values exceed 1.0. + In any of the flipped cases, the specified coordinate value will flip around + after 1.0, essentially mirroring the image. So, if you displayed an image + with a U coordinate of 0.0 on the left hand side and 2.0 on the right hand + side with U flipping turned on, you'd have an image that was displayed twice + as if mirrored across the middle. This mirroring behavior happens at every + unit boundary (so at 2.0 it returns to normal, at 3.0 it flips, etc). + + The default case is to disable mirroring. In addition, clamping of the U/V + coordinates by PVR_UVCLAMP_U, PVR_UVCLAMP_V, or PVR_UVCLAMP_UV will disable + the mirroring behavior. + @{ +*/ +#define PVR_UVFLIP_NONE 0 /**< \brief No flipped coordinates */ +#define PVR_UVFLIP_V 1 /**< \brief Flip V only */ +#define PVR_UVFLIP_U 2 /**< \brief Flip U only */ +#define PVR_UVFLIP_UV 3 /**< \brief Flip U and V */ +/** @} */ + +/** \defgroup pvr_uv_clamp U/V Clamp Mode + \brief Enable or disable clamping of U/V on the PVR + \ingroup pvr_ctx_texture + + These flags determine whether clamping will be applied to U/V coordinate + values that exceed 1.0. If enabled, these modes will explicitly override the + flip/mirroring modes (PVR_UVFLIP_U, PVR_UVFLIP_V, and PVR_UVFLIP_UV), and + will instead ensure that the coordinate(s) in question never exceed 1.0. + @{ +*/ +#define PVR_UVCLAMP_NONE 0 /**< \brief Disable clamping */ +#define PVR_UVCLAMP_V 1 /**< \brief Clamp V only */ +#define PVR_UVCLAMP_U 2 /**< \brief Clamp U only */ +#define PVR_UVCLAMP_UV 3 /**< \brief Clamp U and V */ +/** @} */ + +/** \defgroup pvr_filter_modes Sampling Modes + \brief PowerVR texture sampling modes + \ingroup pvr_ctx_texture + + @{ +*/ +#define PVR_FILTER_NONE 0 /**< \brief No filtering (point sample) */ +#define PVR_FILTER_NEAREST 0 /**< \brief No filtering (point sample) */ +#define PVR_FILTER_BILINEAR 2 /**< \brief Bilinear interpolation */ +#define PVR_FILTER_TRILINEAR1 4 /**< \brief Trilinear interpolation pass 1 */ +#define PVR_FILTER_TRILINEAR2 6 /**< \brief Trilinear interpolation pass 2 */ +/** @} */ + +/** \defgroup pvr_mip_bias Mipmap Bias Modes + \brief Mipmap bias modes for PowerVR primitive contexts + \ingroup pvr_ctx_texture + + @{ +*/ +#define PVR_MIPBIAS_NORMAL PVR_MIPBIAS_1_00 /* txr_mipmap_bias */ +#define PVR_MIPBIAS_0_25 1 +#define PVR_MIPBIAS_0_50 2 +#define PVR_MIPBIAS_0_75 3 +#define PVR_MIPBIAS_1_00 4 +#define PVR_MIPBIAS_1_25 5 +#define PVR_MIPBIAS_1_50 6 +#define PVR_MIPBIAS_1_75 7 +#define PVR_MIPBIAS_2_00 8 +#define PVR_MIPBIAS_2_25 9 +#define PVR_MIPBIAS_2_50 10 +#define PVR_MIPBIAS_2_75 11 +#define PVR_MIPBIAS_3_00 12 +#define PVR_MIPBIAS_3_25 13 +#define PVR_MIPBIAS_3_50 14 +#define PVR_MIPBIAS_3_75 15 +/** @} */ + +/** \defgroup pvr_txrenv_modes Color Calculation Modes + \brief PowerVR texture color calculation modes + \ingroup pvr_ctx_texture + + @{ +*/ +#define PVR_TXRENV_REPLACE 0 /**< \brief C = Ct, A = At */ +#define PVR_TXRENV_MODULATE 1 /**< \brief C = Cs * Ct, A = At */ +#define PVR_TXRENV_DECAL 2 /**< \brief C = (Cs * At) + (Cs * (1-At)), A = As */ +#define PVR_TXRENV_MODULATEALPHA 3 /**< \brief C = Cs * Ct, A = As * At */ +/** @} */ + +/** \defgroup pvr_mip_switch Mipmap Toggle + \brief Enable or Disable Mipmap Processing + \ingroup pvr_ctx_texture + + @{ +*/ +#define PVR_MIPMAP_DISABLE 0 /**< \brief Disable mipmap processing */ +#define PVR_MIPMAP_ENABLE 1 /**< \brief Enable mipmap processing */ +/** @} */ + +/** \defgroup pvr_txr_fmts Formats + \brief PowerVR texture formats + \ingroup pvr_txr_mgmt + + These are the texture formats that the PVR supports. Note that some of + these, you can OR together with other values. + + @{ +*/ +#define PVR_TXRFMT_NONE 0 /**< \brief No texture */ +#define PVR_TXRFMT_VQ_DISABLE (0 << 30) /**< \brief Not VQ encoded */ +#define PVR_TXRFMT_VQ_ENABLE (1 << 30) /**< \brief VQ encoded */ +#define PVR_TXRFMT_ARGB1555 (0 << 27) /**< \brief 16-bit ARGB1555 */ +#define PVR_TXRFMT_RGB565 (1 << 27) /**< \brief 16-bit RGB565 */ +#define PVR_TXRFMT_ARGB4444 (2 << 27) /**< \brief 16-bit ARGB4444 */ +#define PVR_TXRFMT_YUV422 (3 << 27) /**< \brief YUV422 format */ +#define PVR_TXRFMT_BUMP (4 << 27) /**< \brief Bumpmap format */ +#define PVR_TXRFMT_PAL4BPP (5 << 27) /**< \brief 4BPP paletted format */ +#define PVR_TXRFMT_PAL8BPP (6 << 27) /**< \brief 8BPP paletted format */ +#define PVR_TXRFMT_TWIDDLED (0 << 26) /**< \brief Texture is twiddled */ +#define PVR_TXRFMT_NONTWIDDLED (1 << 26) /**< \brief Texture is not twiddled */ +#define PVR_TXRFMT_NOSTRIDE (0 << 25) /**< \brief Texture is not strided */ +#define PVR_TXRFMT_STRIDE (1 << 25) /**< \brief Texture is strided */ + +/* OR one of these into your texture format if you need it. Note that + these coincide with the twiddled/stride bits, so you can't have a + non-twiddled/strided texture that's paletted! */ + +/** \brief 8BPP palette selector + + \param x The palette index */ +#define PVR_TXRFMT_8BPP_PAL(x) ((x) << 25) + +/** \brief 4BPP palette selector + + \param x The palette index */ +#define PVR_TXRFMT_4BPP_PAL(x) ((x) << 21) +/** @} */ + +/** \defgroup pvr_color_fmts Vertex Formats + \brief Color formats for PowerVR vertices + \ingroup pvr_ctx_color + + These control how colors are represented in polygon data. + + @{ +*/ +#define PVR_CLRFMT_ARGBPACKED 0 /**< \brief 32-bit integer ARGB */ +#define PVR_CLRFMT_4FLOATS 1 /**< \brief 4 floating point values */ +#define PVR_CLRFMT_INTENSITY 2 /**< \brief Intensity color */ +#define PVR_CLRFMT_INTENSITY_PREV 3 /**< \brief Use last intensity */ +/** @} */ + +/** \defgroup pvr_uv_fmts U/V Data Format + \brief U/V data format for PVR textures + \ingroup pvr_ctx_texture + @{ +*/ +#define PVR_UVFMT_32BIT 0 /**< \brief 32-bit floating point U/V */ +#define PVR_UVFMT_16BIT 1 /**< \brief 16-bit floating point U/V */ +/** @} */ + +/** \defgroup pvr_ctx_modvol Modifier Volumes + \brief PowerVR modifier volume polygon context attributes + \ingroup pvr_ctx_attrib +*/ + +/** \defgroup pvr_mod_switch Toggle + \brief Enable or Disable Modifier Effects + \ingroup pvr_ctx_modvol + @{ +*/ +#define PVR_MODIFIER_DISABLE 0 /**< \brief Disable modifier effects */ +#define PVR_MODIFIER_ENABLE 1 /**< \brief Enable modifier effects */ +/** @} */ + +/** \defgroup pvr_mod_types Types + \brief Modifier volume types for PowerVR primitive contexts + \ingroup pvr_ctx_modvol + @{ +*/ +#define PVR_MODIFIER_CHEAP_SHADOW 0 +#define PVR_MODIFIER_NORMAL 1 +/** @} */ + +/** \defgroup pvr_mod_modes Modes + \brief Modifier volume modes for PowerVR primitive contexts + \ingroup pvr_ctx_modvol + + All triangles in a single modifier volume should be of the other poly type, + except for the last one. That should be either of the other two types, + depending on whether you want an inclusion or exclusion volume. + + @{ +*/ +#define PVR_MODIFIER_OTHER_POLY 0 /**< \brief Not the last polygon in the volume */ +#define PVR_MODIFIER_INCLUDE_LAST_POLY 1 /**< \brief Last polygon, inclusion volume */ +#define PVR_MODIFIER_EXCLUDE_LAST_POLY 2 /**< \brief Last polygon, exclusion volume */ +/** @} */ + +/** \defgroup pvr_primitives_headers Headers + \brief Compiled headers for polygons and sprites + \ingroup pvr_primitives + + @{ +*/ + +/** \brief PVR polygon header. + + This is the hardware equivalent of a rendering context; you'll create one of + these from your pvr_poly_cxt_t and use it for submission to the hardware. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t cmd; /**< \brief TA command */ + uint32_t mode1; /**< \brief Parameter word 1 */ + uint32_t mode2; /**< \brief Parameter word 2 */ + uint32_t mode3; /**< \brief Parameter word 3 */ + uint32_t d1; /**< \brief Dummy value */ + uint32_t d2; /**< \brief Dummy value */ + uint32_t d3; /**< \brief Dummy value */ + uint32_t d4; /**< \brief Dummy value */ +} pvr_poly_hdr_t; + +/** \brief PVR polygon header with intensity color. + + This is the equivalent of pvr_poly_hdr_t, but for use with intensity color. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t cmd; /**< \brief TA command */ + uint32_t mode1; /**< \brief Parameter word 1 */ + uint32_t mode2; /**< \brief Parameter word 2 */ + uint32_t mode3; /**< \brief Parameter word 3 */ + float a; /**< \brief Face color alpha component */ + float r; /**< \brief Face color red component */ + float g; /**< \brief Face color green component */ + float b; /**< \brief Face color blue component */ +} pvr_poly_ic_hdr_t; + +/** \brief PVR polygon header to be used with modifier volumes. + + This is the equivalent of a pvr_poly_hdr_t for use when a polygon is to be + used with modifier volumes. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t cmd; /**< \brief TA command */ + uint32_t mode1; /**< \brief Parameter word 1 */ + uint32_t mode2_0; /**< \brief Parameter word 2 (outside volume) */ + uint32_t mode3_0; /**< \brief Parameter word 3 (outside volume) */ + uint32_t mode2_1; /**< \brief Parameter word 2 (inside volume) */ + uint32_t mode3_1; /**< \brief Parameter word 3 (inside volume) */ + uint32_t d1; /**< \brief Dummy value */ + uint32_t d2; /**< \brief Dummy value */ +} pvr_poly_mod_hdr_t; + +/** \brief PVR polygon header specifically for sprites. + + This is the equivalent of a pvr_poly_hdr_t for use when a quad/sprite is to + be rendered. Note that the color data is here, not in the vertices. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t cmd; /**< \brief TA command */ + uint32_t mode1; /**< \brief Parameter word 1 */ + uint32_t mode2; /**< \brief Parameter word 2 */ + uint32_t mode3; /**< \brief Parameter word 3 */ + uint32_t argb; /**< \brief Sprite face color */ + uint32_t oargb; /**< \brief Sprite offset color */ + uint32_t d1; /**< \brief Dummy value */ + uint32_t d2; /**< \brief Dummy value */ +} pvr_sprite_hdr_t; + +/** \brief Modifier volume header. + + This is the header that should be submitted when dealing with setting a + modifier volume. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t cmd; /**< \brief TA command */ + uint32_t mode1; /**< \brief Parameter word 1 */ + uint32_t d1; /**< \brief Dummy value */ + uint32_t d2; /**< \brief Dummy value */ + uint32_t d3; /**< \brief Dummy value */ + uint32_t d4; /**< \brief Dummy value */ + uint32_t d5; /**< \brief Dummy value */ + uint32_t d6; /**< \brief Dummy value */ +} pvr_mod_hdr_t; + +/** @} */ + +/** \defgroup pvr_vertex_types Vertices + \brief PowerVR vertex types + \ingroup pvr_geometry + + @{ +*/ + +/** \brief Generic PVR vertex type. + + The PVR chip itself supports many more vertex types, but this is the main + one that can be used with both textured and non-textured polygons, and is + fairly fast. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t flags; /**< \brief TA command (vertex flags) */ + float x; /**< \brief X coordinate */ + float y; /**< \brief Y coordinate */ + float z; /**< \brief Z coordinate */ + float u; /**< \brief Texture U coordinate */ + float v; /**< \brief Texture V coordinate */ + uint32_t argb; /**< \brief Vertex color */ + uint32_t oargb; /**< \brief Vertex offset color */ +} pvr_vertex_t; + +/** \brief PVR vertex type: Non-textured, packed color, affected by modifier + volume. + + This vertex type has two copies of colors. The second color is used when + enclosed within a modifier volume. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t flags; /**< \brief TA command (vertex flags) */ + float x; /**< \brief X coordinate */ + float y; /**< \brief Y coordinate */ + float z; /**< \brief Z coordinate */ + uint32_t argb0; /**< \brief Vertex color (outside volume) */ + uint32_t argb1; /**< \brief Vertex color (inside volume) */ + uint32_t d1; /**< \brief Dummy value */ + uint32_t d2; /**< \brief Dummy value */ +} pvr_vertex_pcm_t; + +/** \brief PVR vertex type: Textured, packed color, affected by modifier volume. + + Note that this vertex type has two copies of colors, offset colors, and + texture coords. The second set of texture coords, colors, and offset colors + are used when enclosed within a modifier volume. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t flags; /**< \brief TA command (vertex flags) */ + float x; /**< \brief X coordinate */ + float y; /**< \brief Y coordinate */ + float z; /**< \brief Z coordinate */ + float u0; /**< \brief Texture U coordinate (outside) */ + float v0; /**< \brief Texture V coordinate (outside) */ + uint32_t argb0; /**< \brief Vertex color (outside) */ + uint32_t oargb0; /**< \brief Vertex offset color (outside) */ + float u1; /**< \brief Texture U coordinate (inside) */ + float v1; /**< \brief Texture V coordinate (inside) */ + uint32_t argb1; /**< \brief Vertex color (inside) */ + uint32_t oargb1; /**< \brief Vertex offset color (inside) */ + uint32_t d1; /**< \brief Dummy value */ + uint32_t d2; /**< \brief Dummy value */ + uint32_t d3; /**< \brief Dummy value */ + uint32_t d4; /**< \brief Dummy value */ +} pvr_vertex_tpcm_t; + +/** \brief PVR vertex type: Textured sprite. + + This vertex type is to be used with the sprite polygon header and the sprite + related commands to draw textured sprites. Note that there is no fourth Z + coordinate. I suppose it just gets interpolated? + + The U/V coordinates in here are in the 16-bit per coordinate form. Also, + like the fourth Z value, there is no fourth U or V, so it must get + interpolated from the others. + + \headerfile dc/pvr.h +*/ +typedef struct { + uint32_t flags; /**< \brief TA command (vertex flags) */ + float ax; /**< \brief First X coordinate */ + float ay; /**< \brief First Y coordinate */ + float az; /**< \brief First Z coordinate */ + float bx; /**< \brief Second X coordinate */ + float by; /**< \brief Second Y coordinate */ + float bz; /**< \brief Second Z coordinate */ + float cx; /**< \brief Third X coordinate */ + float cy; /**< \brief Third Y coordinate */ + float cz; /**< \brief Third Z coordinate */ + float dx; /**< \brief Fourth X coordinate */ + float dy; /**< \brief Fourth Y coordinate */ + uint32_t dummy; /**< \brief Dummy value */ + uint32_t auv; /**< \brief First U/V texture coordinates */ + uint32_t buv; /**< \brief Second U/V texture coordinates */ + uint32_t cuv; /**< \brief Third U/V texture coordinates */ +} pvr_sprite_txr_t; + +/** \brief PVR vertex type: Untextured sprite. + + This vertex type is to be used with the sprite polygon header and the sprite + related commands to draw untextured sprites (aka, quads). +*/ +typedef struct { + uint32_t flags; /**< \brief TA command (vertex flags) */ + float ax; /**< \brief First X coordinate */ + float ay; /**< \brief First Y coordinate */ + float az; /**< \brief First Z coordinate */ + float bx; /**< \brief Second X coordinate */ + float by; /**< \brief Second Y coordinate */ + float bz; /**< \brief Second Z coordinate */ + float cx; /**< \brief Third X coordinate */ + float cy; /**< \brief Third Y coordinate */ + float cz; /**< \brief Third Z coordinate */ + float dx; /**< \brief Fourth X coordinate */ + float dy; /**< \brief Fourth Y coordinate */ + uint32_t d1; /**< \brief Dummy value */ + uint32_t d2; /**< \brief Dummy value */ + uint32_t d3; /**< \brief Dummy value */ + uint32_t d4; /**< \brief Dummy value */ +} pvr_sprite_col_t; + +/** \brief PVR vertex type: Modifier volume. + + This vertex type is to be used with the modifier volume header to specify + triangular modifier areas. +*/ +typedef struct { + uint32_t flags; /**< \brief TA command (vertex flags) */ + float ax; /**< \brief First X coordinate */ + float ay; /**< \brief First Y coordinate */ + float az; /**< \brief First Z coordinate */ + float bx; /**< \brief Second X coordinate */ + float by; /**< \brief Second Y coordinate */ + float bz; /**< \brief Second Z coordinate */ + float cx; /**< \brief Third X coordinate */ + float cy; /**< \brief Third Y coordinate */ + float cz; /**< \brief Third Z coordinate */ + uint32_t d1; /**< \brief Dummy value */ + uint32_t d2; /**< \brief Dummy value */ + uint32_t d3; /**< \brief Dummy value */ + uint32_t d4; /**< \brief Dummy value */ + uint32_t d5; /**< \brief Dummy value */ + uint32_t d6; /**< \brief Dummy value */ +} pvr_modifier_vol_t; + +/** \brief Pack four floating point color values into a 32-bit integer form. + + All of the color values should be between 0 and 1. + + \param a Alpha value + \param r Red value + \param g Green value + \param b Blue value + \return The packed color value +*/ +#define PVR_PACK_COLOR(a, r, g, b) ( \ + ( ((uint8)( (a) * 255 ) ) << 24 ) | \ + ( ((uint8)( (r) * 255 ) ) << 16 ) | \ + ( ((uint8)( (g) * 255 ) ) << 8 ) | \ + ( ((uint8)( (b) * 255 ) ) << 0 ) ) + +/** \brief Pack two floating point coordinates into one 32-bit value, + truncating them to 16-bits each. + + \param u First coordinate to pack + \param v Second coordinate to pack + \return The packed coordinates +*/ +static inline uint32_t PVR_PACK_16BIT_UV(float u, float v) { + union { + float f; + uint32_t i; + } u2, v2; + + u2.f = u; + v2.f = v; + + return (u2.i & 0xFFFF0000) | (v2.i >> 16); +} + +/** @} */ + +/** \defgroup pvr_commands TA Command Values + \brief Command values for submitting data to the TA + \ingroup pvr_primitives_headers + + These are are appropriate values for TA commands. Use whatever goes with the + primitive type you're using. + + @{ +*/ +#define PVR_CMD_POLYHDR 0x80840000 /**< \brief PVR polygon header. +Striplength set to 2 */ +#define PVR_CMD_VERTEX 0xe0000000 /**< \brief PVR vertex data */ +#define PVR_CMD_VERTEX_EOL 0xf0000000 /**< \brief PVR vertex, end of strip */ +#define PVR_CMD_USERCLIP 0x20000000 /**< \brief PVR user clipping area */ +#define PVR_CMD_MODIFIER 0x80000000 /**< \brief PVR modifier volume */ +#define PVR_CMD_SPRITE 0xA0000000 /**< \brief PVR sprite header */ +/** @} */ + +/** \defgroup pvr_bitmasks Constants and Masks + \brief Polygon header constants and masks + \ingroup pvr_primitives_headers + + Note that thanks to the arrangement of constants, this is mainly a matter of + bit shifting to compile headers... + + @{ +*/ +#define PVR_TA_CMD_TYPE_SHIFT 24 +#define PVR_TA_CMD_TYPE_MASK (7 << PVR_TA_CMD_TYPE_SHIFT) + +#define PVR_TA_CMD_USERCLIP_SHIFT 16 +#define PVR_TA_CMD_USERCLIP_MASK (3 << PVR_TA_CMD_USERCLIP_SHIFT) + +#define PVR_TA_CMD_CLRFMT_SHIFT 4 +#define PVR_TA_CMD_CLRFMT_MASK (7 << PVR_TA_CMD_CLRFMT_SHIFT) + +#define PVR_TA_CMD_SPECULAR_SHIFT 2 +#define PVR_TA_CMD_SPECULAR_MASK (1 << PVR_TA_CMD_SPECULAR_SHIFT) + +#define PVR_TA_CMD_SHADE_SHIFT 1 +#define PVR_TA_CMD_SHADE_MASK (1 << PVR_TA_CMD_SHADE_SHIFT) + +#define PVR_TA_CMD_UVFMT_SHIFT 0 +#define PVR_TA_CMD_UVFMT_MASK (1 << PVR_TA_CMD_UVFMT_SHIFT) + +#define PVR_TA_CMD_MODIFIER_SHIFT 7 +#define PVR_TA_CMD_MODIFIER_MASK (1 << PVR_TA_CMD_MODIFIER_SHIFT) + +#define PVR_TA_CMD_MODIFIERMODE_SHIFT 6 +#define PVR_TA_CMD_MODIFIERMODE_MASK (1 << PVR_TA_CMD_MODIFIERMODE_SHIFT) + +#define PVR_TA_PM1_DEPTHCMP_SHIFT 29 +#define PVR_TA_PM1_DEPTHCMP_MASK (7 << PVR_TA_PM1_DEPTHCMP_SHIFT) + +#define PVR_TA_PM1_CULLING_SHIFT 27 +#define PVR_TA_PM1_CULLING_MASK (3 << PVR_TA_PM1_CULLING_SHIFT) + +#define PVR_TA_PM1_DEPTHWRITE_SHIFT 26 +#define PVR_TA_PM1_DEPTHWRITE_MASK (1 << PVR_TA_PM1_DEPTHWRITE_SHIFT) + +#define PVR_TA_PM1_TXRENABLE_SHIFT 25 +#define PVR_TA_PM1_TXRENABLE_MASK (1 << PVR_TA_PM1_TXRENABLE_SHIFT) + +#define PVR_TA_PM1_MODIFIERINST_SHIFT 29 +#define PVR_TA_PM1_MODIFIERINST_MASK (3 << PVR_TA_PM1_MODIFIERINST_SHIFT) + +#define PVR_TA_PM2_SRCBLEND_SHIFT 29 +#define PVR_TA_PM2_SRCBLEND_MASK (7 << PVR_TA_PM2_SRCBLEND_SHIFT) + +#define PVR_TA_PM2_DSTBLEND_SHIFT 26 +#define PVR_TA_PM2_DSTBLEND_MASK (7 << PVR_TA_PM2_DSTBLEND_SHIFT) + +#define PVR_TA_PM2_SRCENABLE_SHIFT 25 +#define PVR_TA_PM2_SRCENABLE_MASK (1 << PVR_TA_PM2_SRCENABLE_SHIFT) + +#define PVR_TA_PM2_DSTENABLE_SHIFT 24 +#define PVR_TA_PM2_DSTENABLE_MASK (1 << PVR_TA_PM2_DSTENABLE_SHIFT) + +#define PVR_TA_PM2_FOG_SHIFT 22 +#define PVR_TA_PM2_FOG_MASK (3 << PVR_TA_PM2_FOG_SHIFT) + +#define PVR_TA_PM2_CLAMP_SHIFT 21 +#define PVR_TA_PM2_CLAMP_MASK (1 << PVR_TA_PM2_CLAMP_SHIFT) + +#define PVR_TA_PM2_ALPHA_SHIFT 20 +#define PVR_TA_PM2_ALPHA_MASK (1 << PVR_TA_PM2_ALPHA_SHIFT) + +#define PVR_TA_PM2_TXRALPHA_SHIFT 19 +#define PVR_TA_PM2_TXRALPHA_MASK (1 << PVR_TA_PM2_TXRALPHA_SHIFT) + +#define PVR_TA_PM2_UVFLIP_SHIFT 17 +#define PVR_TA_PM2_UVFLIP_MASK (3 << PVR_TA_PM2_UVFLIP_SHIFT) + +#define PVR_TA_PM2_UVCLAMP_SHIFT 15 +#define PVR_TA_PM2_UVCLAMP_MASK (3 << PVR_TA_PM2_UVCLAMP_SHIFT) + +#define PVR_TA_PM2_FILTER_SHIFT 12 +#define PVR_TA_PM2_FILTER_MASK (7 << PVR_TA_PM2_FILTER_SHIFT) + +#define PVR_TA_PM2_MIPBIAS_SHIFT 8 +#define PVR_TA_PM2_MIPBIAS_MASK (15 << PVR_TA_PM2_MIPBIAS_SHIFT) + +#define PVR_TA_PM2_TXRENV_SHIFT 6 +#define PVR_TA_PM2_TXRENV_MASK (3 << PVR_TA_PM2_TXRENV_SHIFT) + +#define PVR_TA_PM2_USIZE_SHIFT 3 +#define PVR_TA_PM2_USIZE_MASK (7 << PVR_TA_PM2_USIZE_SHIFT) + +#define PVR_TA_PM2_VSIZE_SHIFT 0 +#define PVR_TA_PM2_VSIZE_MASK (7 << PVR_TA_PM2_VSIZE_SHIFT) + +#define PVR_TA_PM3_MIPMAP_SHIFT 31 +#define PVR_TA_PM3_MIPMAP_MASK (1 << PVR_TA_PM3_MIPMAP_SHIFT) + +#define PVR_TA_PM3_TXRFMT_SHIFT 0 +#define PVR_TA_PM3_TXRFMT_MASK 0xffffffff +/** @} */ + +/**** Register macros ***************************************************/ + +/** \defgroup pvr_registers Registers + \brief Direct PVR register and memory access + \ingroup pvr + @{ +*/ + +/* We use these macros to do all PVR register access, so that it's + simple later on to hook them for debugging or whatnot. */ + +/** \brief Retrieve a PVR register value + + \param REG The register to fetch. See \ref pvr_regs. + + \return The value of that register (32-bits) +*/ +#define PVR_GET(REG) pvrRegRead(REG) + +/** \brief Set a PVR register value + + \param REG The register to set. See \ref pvr_regs. + \param VALUE The value to set in the register (32-bits) +*/ +#define PVR_SET(REG, VALUE) pvrRegWrite(REG, VALUE) + +/** @} */ + +/** \defgroup pvr_regs Offsets + \brief PowerVR register offsets + \ingroup pvr_registers + + The registers themselves; these are from Maiwe's powervr-reg.txt. + + \note + 2D specific registers have been excluded for now (like + vsync, hsync, v/h size, etc) + + @{ +*/ + +#define PVR_ID 0x0000 /**< \brief Chip ID */ +#define PVR_REVISION 0x0004 /**< \brief Chip revision */ +#define PVR_RESET 0x0008 /**< \brief Reset pins */ + +#define PVR_ISP_START 0x0014 /**< \brief Start the ISP/TSP */ +#define PVR_UNK_0018 0x0018 /**< \brief ?? */ + +#define PVR_ISP_VERTBUF_ADDR 0x0020 /**< \brief Vertex buffer address for scene rendering */ + +#define PVR_ISP_TILEMAT_ADDR 0x002c /**< \brief Tile matrix address for scene rendering */ +#define PVR_SPANSORT_CFG 0x0030 /**< \brief ?? -- write 0x101 for now */ + +#define PVR_BORDER_COLOR 0x0040 /**< \brief Border Color in RGB888 */ +#define PVR_FB_CFG_1 0x0044 /**< \brief Framebuffer config 1 */ +#define PVR_FB_CFG_2 0x0048 /**< \brief Framebuffer config 2 */ +#define PVR_RENDER_MODULO 0x004c /**< \brief Render modulo */ +#define PVR_FB_ADDR 0x0050 /**< \brief Framebuffer start address */ +#define PVR_FB_IL_ADDR 0x0054 /**< \brief Framebuffer odd-field start address for interlace */ + +#define PVR_FB_SIZE 0x005c /**< \brief Framebuffer display size */ +#define PVR_RENDER_ADDR 0x0060 /**< \brief Render output address */ +#define PVR_RENDER_ADDR_2 0x0064 /**< \brief Output for strip-buffering */ +#define PVR_PCLIP_X 0x0068 /**< \brief Horizontal clipping area */ +#define PVR_PCLIP_Y 0x006c /**< \brief Vertical clipping area */ + +#define PVR_CHEAP_SHADOW 0x0074 /**< \brief Cheap shadow control */ +#define PVR_OBJECT_CLIP 0x0078 /**< \brief Distance for polygon culling */ +#define PVR_UNK_007C 0x007c /**< \brief ?? -- write 0x0027df77 for now */ +#define PVR_UNK_0080 0x0080 /**< \brief ?? -- write 7 for now */ +#define PVR_TEXTURE_CLIP 0x0084 /**< \brief Distance for texture clipping */ +#define PVR_BGPLANE_Z 0x0088 /**< \brief Distance for background plane */ +#define PVR_BGPLANE_CFG 0x008c /**< \brief Background plane config */ + +#define PVR_UNK_0098 0x0098 /**< \brief ?? -- write 0x00800408 for now */ + +#define PVR_UNK_00A0 0x00a0 /**< \brief ?? -- write 0x20 for now */ + +#define PVR_UNK_00A8 0x00a8 /**< \brief ?? -- write 0x15d1c951 for now */ + +#define PVR_FOG_TABLE_COLOR 0x00b0 /**< \brief Table fog color */ +#define PVR_FOG_VERTEX_COLOR 0x00b4 /**< \brief Vertex fog color */ +#define PVR_FOG_DENSITY 0x00b8 /**< \brief Fog density coefficient */ +#define PVR_COLOR_CLAMP_MAX 0x00bc /**< \brief RGB Color clamp max */ +#define PVR_COLOR_CLAMP_MIN 0x00c0 /**< \brief RGB Color clamp min */ +#define PVR_GUN_POS 0x00c4 /**< \brief Light gun position */ +#define PVR_HPOS_IRQ 0x00c8 /**< \brief Horizontal position IRQ */ +#define PVR_VPOS_IRQ 0x00cc /**< \brief Vertical position IRQ */ +#define PVR_IL_CFG 0x00d0 /**< \brief Interlacing config */ +#define PVR_BORDER_X 0x00d4 /**< \brief Window border X position */ +#define PVR_SCAN_CLK 0x00d8 /**< \brief Clock and scanline values */ +#define PVR_BORDER_Y 0x00dc /**< \brief Window border Y position */ + +#define PVR_TEXTURE_MODULO 0x00e4 /**< \brief Output texture width modulo */ +#define PVR_VIDEO_CFG 0x00e8 /**< \brief Misc video config */ +#define PVR_BITMAP_X 0x00ec /**< \brief Bitmap window X position */ +#define PVR_BITMAP_Y 0x00f0 /**< \brief Bitmap window Y position */ +#define PVR_SCALER_CFG 0x00f4 /**< \brief Smoothing scaler */ + +#define PVR_PALETTE_CFG 0x0108 /**< \brief Palette format */ +#define PVR_SYNC_STATUS 0x010c /**< \brief V/H blank status */ +#define PVR_UNK_0110 0x0110 /**< \brief ?? -- write 0x93f39 for now */ +#define PVR_UNK_0114 0x0114 /**< \brief ?? -- write 0x200000 for now */ +#define PVR_UNK_0118 0x0118 /**< \brief ?? -- write 0x8040 for now */ + +#define PVR_TA_OPB_START 0x0124 /**< \brief Object Pointer Buffer start for TA usage */ +#define PVR_TA_VERTBUF_START 0x0128 /**< \brief Vertex buffer start for TA usage */ +#define PVR_TA_OPB_END 0x012c /**< \brief OPB end for TA usage */ +#define PVR_TA_VERTBUF_END 0x0130 /**< \brief Vertex buffer end for TA usage */ +#define PVR_TA_OPB_POS 0x0134 /**< \brief Top used memory location in OPB for TA usage */ +#define PVR_TA_VERTBUF_POS 0x0138 /**< \brief Top used memory location in vertbuf for TA usage */ +#define PVR_TILEMAT_CFG 0x013c /**< \brief Tile matrix size config */ +#define PVR_OPB_CFG 0x0140 /**< \brief Active lists / list size */ +#define PVR_TA_INIT 0x0144 /**< \brief Initialize vertex reg. params */ +#define PVR_YUV_ADDR 0x0148 /**< \brief YUV conversion destination */ +#define PVR_YUV_CFG 0x014c /**< \brief YUV configuration */ +#define PVR_YUV_STAT 0x0150 /**< \brief The number of YUV macroblocks converted */ + +#define PVR_UNK_0160 0x0160 /**< \brief ?? */ +#define PVR_TA_OPB_INIT 0x0164 /**< \brief Object pointer buffer position init */ + +#define PVR_FOG_TABLE_BASE 0x0200 /**< \brief Base of the fog table */ + +#define PVR_PALETTE_TABLE_BASE 0x1000 /**< \brief Base of the palette table */ +/** @} */ + +/** \defgroup pvr_addresses Addresses and Constants + \brief Miscellaneous Addresses and Constants + \ingroup pvr_registers + + Useful PVR memory locations and values. + + @{ +*/ + +#define PVR_RAM_SIZE (8*1024*1024) /**< \brief RAM size in bytes */ + +extern uint8_t emu_vram[PVR_RAM_SIZE]; + +#define PVR_TA_INPUT 0x10000000 /**< \brief TA command input (64-bit, TA) */ +#define PVR_TA_YUV_CONV 0x10800000 /**< \brief YUV converter (64-bit, TA) */ +#define PVR_TA_TEX_MEM 0x11000000 /**< \brief VRAM 64-bit, TA=>VRAM */ +// #define PVR_TA_TEX_MEM_32 0x13000000 /**< \brief VRAM 32-bit, TA->VRAM */ +// #define PVR_RAM_BASE_32_P0 0x05000000 /**< \brief VRAM 32-bit, P0 area, PVR->VRAM */ +#define PVR_RAM_BASE_64_P0 ((uintptr_t)emu_vram) /**< \brief VRAM 64-bit, P0 area, PVR->VRAM */ +// #define PVR_RAM_BASE 0xa5000000 /**< \brief VRAM 32-bit, P2 area, PVR->VRAM */ +#define PVR_RAM_INT_BASE ((uintptr_t)emu_vram) /**< \brief VRAM 64-bit, P2 area, PVR->VRAM */ + +#define PVR_RAM_TOP (PVR_RAM_BASE + PVR_RAM_SIZE) /**< \brief Top of raw PVR RAM */ +#define PVR_RAM_INT_TOP (PVR_RAM_INT_BASE + PVR_RAM_SIZE) /**< \brief Top of int PVR RAM */ +/** @} */ + +/* Register content defines, as needed; these will be filled in over time + as the implementation requires them. There's too many to do otherwise. */ + +/** \defgroup pvr_reset_vals Reset Values + \brief Values used to reset parts of the PVR + \ingroup pvr_registers + + These values are written to the PVR_RESET register in order to reset the + system or to take it out of reset. + + @{ +*/ +#define PVR_RESET_ALL 0xffffffff /**< \brief Reset the whole PVR */ +#define PVR_RESET_NONE 0x00000000 /**< \brief Cancel reset state */ +#define PVR_RESET_TA 0x00000001 /**< \brief Reset only the TA */ +#define PVR_RESET_ISPTSP 0x00000002 /**< \brief Reset only the ISP/TSP */ +/** @} */ + +/** \defgroup pvr_go Init/Start Values + \brief Values to be written to registers to conform or start operations. + \ingroup pvr_registers + @{ +*/ +#define PVR_ISP_START_GO 0xffffffff /**< \brief Write to the PVR_ISP_START register to start rendering */ + +#define PVR_TA_INIT_GO 0x80000000 /**< \brief Write to the PVR_TA_INIT register to confirm settings */ +/** @} */ + +/* Initialization ****************************************************/ +/** \defgroup pvr_init Initialization + \brief Driver initialization and shutdown + \ingroup pvr + + Initialization and shutdown: stuff you should only ever have to do + once in your program. +*/ + +/** \defgroup pvr_binsizes Primitive Bin Sizes + \brief Available sizes for primitive bins + \ingroup pvr_init + @{ +*/ +#define PVR_BINSIZE_0 0 /**< \brief 0-length (disables the list) */ +#define PVR_BINSIZE_8 8 /**< \brief 8-word (32-byte) length */ +#define PVR_BINSIZE_16 16 /**< \brief 16-word (64-byte) length */ +#define PVR_BINSIZE_32 32 /**< \brief 32-word (128-byte) length */ +/** @} */ + +/** \brief PVR initialization structure + \ingroup pvr_init + + This structure defines how the PVR initializes various parts of the system, + including the primitive bin sizes, the vertex buffer size, and whether + vertex DMA will be enabled. + + You essentially fill one of these in, and pass it to pvr_init(). + + \headerfile dc/pvr.h +*/ +typedef struct { + /** \brief Bin sizes. + + The bins go in the following order: opaque polygons, opaque modifiers, + translucent polygons, translucent modifiers, punch-thrus + */ + int opb_sizes[5]; + + /** \brief Vertex buffer size (should be a nice round number) */ + int vertex_buf_size; + + /** \brief Enable vertex DMA? + + Set to non-zero if we want to enable vertex DMA mode. Note that if this + is set, then _all_ enabled lists need to have a vertex buffer assigned, + even if you never use that list for anything. + */ + int dma_enabled; + + /** \brief Enable horizontal scaling? + + Set to non-zero if horizontal scaling is to be enabled. By enabling this + setting and stretching your image to double the native screen width, you + can get horizontal full-screen anti-aliasing. */ + int fsaa_enabled; + + /** \brief Disable translucent polygon autosort? + + Set to non-zero to disable translucent polygon autosorting. By enabling + this setting, the PVR acts more like a traditional Z-buffered system + when rendering translucent polygons, meaning you must pre-sort them + yourself if you want them to appear in the right order. */ + int autosort_disabled; + + + /** \brief OPB Overflow Count. + + Preallocates this many extra OPBs (sets of tile bins), allowing the PVR + to use the extra space when there's too much geometry in the first OPB. + + Increasing this value can eliminate artifacts where pieces of geometry + flicker in and out of existence along the tile boundaries. */ + + int opb_overflow_count; + +} pvr_init_params_t; + +/** \brief Initialize the PVR chip to ready status. + \ingroup pvr_init + + This function enables the specified lists and uses the specified parameters. + Note that bins and vertex buffers come from the texture memory pool, so only + allocate what you actually need. Expects that a 2D mode was initialized + already using the vid_* API. + + \param params The set of parameters to initialize with + \retval 0 On success + \retval -1 If the PVR has already been initialized or the video + mode active is not suitable for 3D +*/ +int pvr_init(pvr_init_params_t *params); + +/** \brief Simple PVR initialization. + \ingroup pvr_init + + This simpler function initializes the PVR using 16/16 for the opaque + and translucent lists' bin sizes, and 0's for everything else. It sets 512KB + of vertex buffer. This is equivalent to the old ta_init_defaults() for now. + + \retval 0 On success + \retval -1 If the PVR has already been initialized or the video + mode active is not suitable for 3D +*/ +int pvr_init_defaults(void); + +/** \brief Shut down the PVR chip from ready status. + \ingroup pvr_init + + This essentially leaves the video system in 2D mode as it was before the + init. + + \retval 0 On success + \retval -1 If the PVR has not been initialized +*/ +int pvr_shutdown(void); + + +/* Misc parameters ***************************************************/ + +/** \defgroup pvr_global Global State + \brief PowerVR functionality which is managed globally + \ingroup pvr + + These are miscellaneous parameters you can set which affect the + rendering process. +*/ + +/** \brief Set the background plane color. + \ingroup pvr_global + + This function sets the color of the area of the screen not covered by any + other polygons. + + \param r Red component of the color to set + \param g Green component of the color to set + \param b Blue component of the color to set +*/ +void pvr_set_bg_color(float r, float g, float b); + +/** \brief Set cheap shadow parameters. + \ingroup pvr_global + + This function sets up the PVR cheap shadow parameters for use. You can only + specify one scale value per frame, so the effect that you can get from this + is somewhat limited, but if you want simple shadows, this is the easiest way + to do it. + + Polygons affected by a shadow modifier volume will effectively multiply + their final color by the scale value set here when shadows are enabled and + the polygon is inside the modifier (or outside for exclusion volumes). + + \param enable Set to non-zero to enable cheap shadow mode. + \param scale_value Floating point value (between 0 and 1) representing + how colors of polygons affected by and inside the + volume will be modified by the shadow volume. +*/ +void pvr_set_shadow_scale(int enable, float scale_value); + +/** \brief Set Z clipping depth. + \ingroup pvr_global + + This function sets the Z clipping depth. The default value for this is + 0.0001. + + \param zc The new value to set the z clip parameter to. +*/ +void pvr_set_zclip(float zc); + +/** \brief PVR Before render callback type. + \ingroup pvr_global + + Functions that act as callbacks for before render hook should be of this type. + These functions will be called right before writing PVR_ISP_START_GO to PVR_ISP_START. +*/ +typedef int (*pvr_before_render_hook_t)(); + + +/** \brief Sets the callback to be called right before render is started + \ingroup pvr_global + + This function sets the callback to be called right before PVR_ISP_START_GO + is written to PVR_ISP_START. This is useful for debugging and frame dumping + uses. + + + \param callback A function to call before the start of rendering + + \returns Previously set callback, or NULL +*/ +pvr_before_render_hook_t pvr_set_before_render_callback(pvr_before_render_hook_t callback); + + +/** \brief Retrieve the current VBlank count. + \ingroup pvr_stats + + This function retrieves the number of VBlank interrupts that have occurred + since the PVR was initialized. + + \return The number of VBlanks since init +*/ +int pvr_get_vbl_count(void); + +/** \defgroup pvr_stats Profiling + \brief Rendering stats and metrics for profiling + \ingroup pvr +*/ + +/** \brief PVR statistics structure. + \ingroup pvr_stats + + This structure is used to hold various statistics about the operation of the + PVR since initialization. + + \headerfile dc/pvr.h +*/ +typedef struct pvr_stats { + uint64_t frame_last_time; /**< \brief Ready-to-Ready length for the last frame in nanoseconds */ + uint64_t reg_last_time; /**< \brief Registration time for the last frame in nanoseconds */ + uint64_t rnd_last_time; /**< \brief Rendering time for the last frame in nanoseconds */ + uint64_t buf_last_time; /**< \brief DMA buffer file time for the last frame in nanoseconds */ + size_t frame_count; /**< \brief Total number of rendered/viewed frames */ + size_t vbl_count; /**< \brief VBlank count */ + size_t vtx_buffer_used; /**< \brief Number of bytes used in the vertex buffer for the last frame */ + size_t vtx_buffer_used_max; /**< \brief Number of bytes used in the vertex buffer for the largest frame */ + float frame_rate; /**< \brief Current frame rate (per second) */ + uint32_t enabled_list_mask; /**< \brief Which lists are enabled? */ + /* ... more later as it's implemented ... */ +} pvr_stats_t; + +/** \brief Get the current statistics from the PVR. + \ingroup pvr_stats + + This function fills in the pvr_stats_t structure passed in with the current + statistics of the system. + + \param stat The statistics structure to fill in. Must not be + NULL + \retval 0 On success + \retval -1 If the PVR is not initialized +*/ +int pvr_get_stats(pvr_stats_t *stat); + + +/* Palette management ************************************************/ +/** \defgroup pvr_pal_mgmt Palettes + \brief Color palette management API of the PowerVR + \ingroup pvr_global + + In addition to its 16-bit truecolor modes, the PVR also supports some + nice paletted modes. + + \remark + These aren't useful for super high quality images most of the time, + but they can be useful for doing some interesting special effects, + like the old cheap "worm hole". +*/ + +/** \defgroup pvr_palfmts Formats + \brief Color palette formats of the PowerVR + \ingroup pvr_pal_mgmt + + Entries in the PVR's palettes can be of any of these formats. Note that you + can only have one format active at a time. + + @{ +*/ +#define PVR_PAL_ARGB1555 0 /**< \brief 16-bit ARGB1555 palette format */ +#define PVR_PAL_RGB565 1 /**< \brief 16-bit RGB565 palette format */ +#define PVR_PAL_ARGB4444 2 /**< \brief 16-bit ARGB4444 palette format */ +#define PVR_PAL_ARGB8888 3 /**< \brief 32-bit ARGB8888 palette format */ +/** @} */ + +/** \brief Set the palette format. + \ingroup pvr_pal_mgmt + + This function sets the currently active palette format on the PVR. Each + entry in the palette table is 32-bits in length, regardless of what color + format is in use. + + Be sure to use care when using the PVR_PAL_ARGB8888 format. Rendering speed + is greatly affected (cut about in half) if you use any filtering with + paletted textures with ARGB8888 entries in the palette. + + \param fmt The format to use + \see pvr_palfmts +*/ +void pvr_set_pal_format(int fmt); + +/** \brief Set a palette value. + \ingroup pvr_pal_mgmt + + Note that while the color format is variable, each entry is still 32-bits in + length regardless (and you only get a total of 1024 of them). If using one + of the 16-bit palette formats, only the low-order 16-bits of the entry are + valid, and the high bits should be filled in with 0. + + \param idx The index to set to (0-1023) + \param value The color value to set in that palette entry +*/ +static inline void pvr_set_pal_entry(uint32_t idx, uint32_t value) { + PVR_SET(PVR_PALETTE_TABLE_BASE + 4 * idx, value); +} + + +/* Hardware Fog parameters *******************************************/ +/** \defgroup pvr_fog Fog + \brief Hardware Fog API for the PowerVR + \ingroup pvr_global + + \note + Thanks to Paul Boese for figuring this stuff out +*/ + +/** \brief Set the table fog color. + \ingroup pvr_fog + + This function sets the color of fog for table fog. 0-1 range for all colors. + + \param a Alpha value of the fog + \param r Red value of the fog + \param g Green value of the fog + \param b Blue value of the fog +*/ +void pvr_fog_table_color(float a, float r, float g, float b); + +/** \brief Set the vertex fog color. + \ingroup pvr_fog + + This function sets the fog color for vertex fog. 0-1 range for all colors. + This function is currently not implemented, as vertex fog is not supported + by KOS. Calling this function will cause an assertion failure. + + \param a Alpha value of the fog + \param r Red value of the fog + \param g Green value of the fog + \param b Blue value of the fog +*/ +void pvr_fog_vertex_color(float a, float r, float g, float b); + +/** \brief Set the fog far depth. + \ingroup pvr_fog + + This function sets the PVR_FOG_DENSITY register appropriately for the + specified value. + + \param d The depth to set +*/ +void pvr_fog_far_depth(float d); + +/** \brief Initialize the fog table using an exp2 algorithm (like GL_EXP2). + \ingroup pvr_fog + + This function will automatically set the PVR_FOG_DENSITY register to + 259.999999 as a part of its processing, then set up the fog table. + + \param density Fog density value +*/ +void pvr_fog_table_exp2(float density); + +/** \brief Initialize the fog table using an exp algorithm (like GL_EXP). + \ingroup pvr_fog + + This function will automatically set the PVR_FOG_DENSITY register to + 259.999999 as a part of its processing, then set up the fog table. + + \param density Fog density value +*/ +void pvr_fog_table_exp(float density); + +/** \brief Initialize the fog table using a linear algorithm (like GL_LINEAR). + \ingroup pvr_fog + + This function will set the PVR_FOG_DENSITY register to the as appropriate + for the end value, and initialize the fog table for perspectively correct + linear fog. + + \param start Fog start point + \param end Fog end point +*/ +void pvr_fog_table_linear(float start, float end); + +/** \brief Set a custom fog table from float values + \ingroup pvr_fog + + This function allows you to specify whatever values you need to for your fog + parameters. All values should be clamped between 0 and 1, and its your + responsibility to set up the PVR_FOG_DENSITY register by calling + pvr_fog_far_depth() with an appropriate value. The table passed in should + have 129 entries, where the 0th entry is farthest from the eye and the last + entry is nearest. Higher values = heavier fog. + + \param tbl1 The table of fog values to set +*/ +void pvr_fog_table_custom(float tbl1[]); + + +/* Memory management *************************************************/ + +/** \defgroup pvr_vram VRAM + \brief Video memory access and management + \ingroup pvr +*/ + +/** \defgroup pvr_mem_mgmt Allocator + \brief Memory management API for VRAM + \ingroup pvr_vram + + PVR memory management in KOS uses a modified dlmalloc; see the + source file pvr_mem_core.c for more info. +*/ + +/** \brief Allocate a chunk of memory from texture space. + \ingroup pvr_mem_mgmt + + This function acts as the memory allocator for the PVR texture RAM pool. It + acts exactly as one would expect a malloc() function to act, returning a + normal pointer that can be directly written to if one desires to do so. All + allocations will be aligned to a 32-byte boundary. + + \param size The amount of memory to allocate + + \return A pointer to the memory on success, NULL on error +*/ +pvr_ptr_t pvr_mem_malloc(size_t size); + +/** \brief Free a block of allocated memory in the PVR RAM pool. + \ingroup pvr_mem_mgmt + + This function frees memory previously allocated with pvr_mem_malloc(). + + \param chunk The location of the start of the block to free +*/ +void pvr_mem_free(pvr_ptr_t chunk); + +/** \brief Return the number of bytes available still in the PVR RAM pool. + \ingroup pvr_mem_mgmt + + \return The number of bytes available +*/ +uint32_t pvr_mem_available(void); + +/** \brief Reset the PVR RAM pool. + \ingroup pvr_mem_mgmt + + This will essentially free any blocks allocated within the pool. There's + generally not many good reasons for doing this. +*/ +void pvr_mem_reset(void); + +/** \brief Print the list of allocated blocks in the PVR RAM pool. + \ingroup pvr_mem_mgmt + + This function only works if you've enabled KM_DBG in pvr_mem.c. +*/ +void pvr_mem_print_list(void); + +/** \brief Print statistics about the PVR RAM pool. + \ingroup pvr_mem_mgmt + + This prints out statistics like what malloc_stats() provides. Also, if + KM_DBG is enabled in pvr_mem.c, it prints the list of allocated blocks. +*/ +void pvr_mem_stats(void); + +/* Scene rendering ***************************************************/ +/** \defgroup pvr_scene_mgmt Scene Submission + \brief PowerVR API for submitting scene geometry + \ingroup pvr + + This API is used to submit triangle strips to the PVR via the TA + interface in the chip. + + An important side note about the PVR is that all primitive types + must be submitted grouped together. If you have 10 polygons for each + list type, then the PVR must receive them via the TA by list type, + with a list delimiter in between. + + So there are two modes you can use here. The first mode allows you to + submit data directly to the TA. Your data will be forwarded to the + chip for processing as it is fed to the PVR module. If your data + is easily sorted into the primitive types, then this is the fastest + mode for submitting data. + + The second mode allows you to submit data via main-RAM vertex buffers, + which will be queued until the proper primitive type is active. In this + case, each piece of data is copied into the vertex buffer while the + wrong list is activated, and when the proper list becomes activated, + the data is all sent at once. Ideally this would be via DMA, right + now it is by store queues. This has the advantage of allowing you to + send data in any order and have the PVR functions resolve how it should + get sent to the hardware, but it is slower. + + The nice thing is that any combination of these modes can be used. You + can assign a vertex buffer for any list, and it will be used to hold the + incoming vertex data until the proper list has come up. Or if the proper + list is already up, the data will be submitted directly. So if most of + your polygons are opaque, and you only have a couple of translucents, + you can set a small buffer to gather translucent data and then it will + get sent when you do a pvr_end_scene(). + + Thanks to Mikael Kalms for the idea for this API. + + \note + Another somewhat subtle point that bears mentioning is that in the normal + case (interrupts enabled) an interrupt handler will automatically take + care of starting a frame rendering (after scene_finish()) and also + flipping pages when appropriate. +*/ + +/** \defgroup pvr_vertex_dma Vertex DMA + \brief Use the DMA to transfer inactive lists to the PVR + \ingroup pvr_scene_mgmt +*/ + +/** \brief Is vertex DMA enabled? + \ingroup pvr_vertex_dma + + \return Non-zero if vertex DMA was enabled at init time +*/ +int pvr_vertex_dma_enabled(void); + +/** \brief Setup a vertex buffer for one of the list types. + \ingroup pvr_list_mgmt + + If the specified list type already has a vertex buffer, it will be replaced + by the new one. + + \note + Each buffer should actually be twice as long as what you will need to hold + two frames worth of data). + + \warning + You should generally not try to do this at any time besides before a frame + is begun, or Bad Things May Happen. + + \param list The primitive list to set the buffer for. + \param buffer The location of the buffer in main RAM. This must be + aligned to a 32-byte boundary. + \param len The length of the buffer. This must be a multiple of + 64, and must be at least 128 (even if you're not + using the list). + + \return The old buffer location (if any) +*/ +void *pvr_set_vertbuf(pvr_list_t list, void *buffer, int len); + +/** \brief Retrieve a pointer to the current output location in the DMA buffer + for the requested list. + \ingroup pvr_vertex_dma + + Vertex DMA must globally be enabled for this to work. Data may be added to + this buffer by the user program directly; however, make sure to call + pvr_vertbuf_written() to notify the system of any such changes. + + \param list The primitive list to get the buffer for. + + \return The tail of that list's buffer. +*/ +void *pvr_vertbuf_tail(pvr_list_t list); + +/** \brief Notify the PVR system that data have been written into the output + buffer for the given list. + \ingroup pvr_vertex_dma + + This should always be done after writing data directly to these buffers or + it will get overwritten by other data. + + \param list The primitive list that was modified. + \param amt Number of bytes written. Must be a multiple of 32. +*/ +void pvr_vertbuf_written(pvr_list_t list, uint32_t amt); + +/** \brief Set the translucent polygon sort mode for the next frame. + \ingroup pvr_scene_mgmt + + This function sets the translucent polygon sort mode for the next frame of + output, potentially switching between autosort and presort mode. + + For most programs, you'll probably want to set this at initialization time + (with the autosort_disabled field in the pvr_init_params_t structure) and + not mess with it per-frame. It is recommended that if you do use this + function to change the mode that you should set it each frame to ensure that + the mode is set properly. + + \param presort Set to 1 to set the presort mode for translucent + polygons, set to 0 to use autosort mode. +*/ +void pvr_set_presort_mode(int presort); + +/** \brief Begin collecting data for a frame of 3D output to the off-screen + frame buffer. + \ingroup pvr_scene_mgmt + + You must call this function (or pvr_scene_begin_txr()) for ever frame of + output. +*/ +void pvr_scene_begin(void); + +/** \brief Begin collecting data for a frame of 3D output to the specified + texture. + \ingroup pvr_scene_mgmt + + This function currently only supports outputting at the same size as the + actual screen. Thus, make sure rx and ry are at least large enough for that. + For a 640x480 output, rx will generally be 1024 on input and ry 512, as + these are the smallest values that are powers of two and will hold the full + screen sized output. + + \param txr The texture to render to. + \param rx Width of the texture buffer (in pixels). + \param ry Height of the texture buffer (in pixels). +*/ +void pvr_scene_begin_txr(pvr_ptr_t txr, uint32_t *rx, uint32_t *ry); + + +/** \defgroup pvr_list_mgmt Polygon Lists + \brief PVR API for managing list submission + \ingroup pvr_scene_mgmt +*/ + +/** \brief Begin collecting data for the given list type. + \ingroup pvr_list_mgmt + + Lists do not have to be submitted in any particular order, but all types of + a list must be submitted at once (unless vertex DMA mode is enabled). + + Note that there is no need to call this function in DMA mode unless you want + to make use of pvr_prim() for compatibility. This function will + automatically call pvr_list_finish() if a list is already opened before + opening the new list. + + \param list The list to open. + \retval 0 On success. + \retval -1 If the specified list has already been closed. +*/ +int pvr_list_begin(pvr_list_t list); + +/** \brief End collecting data for the current list type. + \ingroup pvr_list_mgmt + + Lists can never be opened again within a single frame once they have been + closed. Thus submitting a primitive that belongs in a closed list is + considered an error. Closing a list that is already closed is also an error. + + Note that if you open a list but do not submit any primitives, a blank one + will be submitted to satisfy the hardware. If vertex DMA mode is enabled, + then this simply sets the current list pointer to no list, and none of the + above restrictions apply. + + \retval 0 On success. + \retval -1 On error. +*/ +int pvr_list_finish(void); + +/** \brief Submit a primitive of the current list type. + \ingroup pvr_list_mgmt + + Note that any values submitted in this fashion will go directly to the + hardware without any sort of buffering, and submitting a primitive of the + wrong type will quite likely ruin your scene. Note that this also will not + work if you haven't begun any list types (i.e., all data is queued). If DMA + is enabled, the primitive will be appended to the end of the currently + selected list's buffer. + + \param data The primitive to submit. + \param size The length of the primitive, in bytes. Must be a + multiple of 32. + + \retval 0 On success. + \retval -1 On error. +*/ +int pvr_prim(void *data, int size); + +/** \defgroup pvr_direct Direct Rendering + \brief API for using direct rendering with the PVR + \ingroup pvr_scene_mgmt + + @{ +*/ + +/** \brief Direct Rendering state variable type. */ +typedef uint32_t pvr_dr_state_t; + +/** \brief Initialize a state variable for Direct Rendering. + + Store Queues are used. + + \param vtx_buf_ptr A variable of type pvr_dr_state_t to init. +*/ +void pvr_dr_init(pvr_dr_state_t *vtx_buf_ptr); + +/** \brief Obtain the target address for Direct Rendering. + + \param vtx_buf_ptr State variable for Direct Rendering. Should be of + type pvr_dr_state_t, and must have been initialized + previously in the scene with pvr_dr_init(). + + \return A write-only destination address where a primitive + should be written to get ready to submit it to the + TA in DR mode. +*/ +pvr_vertex_t *pvr_dr_target(pvr_dr_state_t &state); + +/** \brief Commit a primitive written into the Direct Rendering target address. + + \param addr The address returned by pvr_dr_target(), after you + have written the primitive to it. +*/ +void pvr_dr_commit(void* addr); + +/** \brief Finish work with Direct Rendering. + + Called atomatically in pvr_scene_finish(). + Use it manually if you want to release Store Queues earlier. + +*/ +void pvr_dr_finish(void); + +/** @} */ + +/** \brief Submit a primitive of the given list type. + \ingroup pvr_list_mgmt + + Data will be queued in a vertex buffer, thus one must be available for the + list specified (will be asserted by the code). + + \param list The list to submit to. + \param data The primitive to submit. + \param size The size of the primitive in bytes. This must be a + multiple of 32. + + \retval 0 On success. + \retval -1 On error. +*/ +int pvr_list_prim(pvr_list_t list, void *data, int size); + +/** \brief Flush the buffered data of the given list type to the TA. + \ingroup pvr_list_mgmt + + This function is currently not implemented, and calling it will result in an + assertion failure. It is intended to be used later in a "hybrid" mode where + both direct and DMA TA submission is possible. + + \param list The list to flush. + + \retval -1 On error (it is not possible to succeed). +*/ +int pvr_list_flush(pvr_list_t list); + +/** \brief Call this after you have finished submitting all data for a frame. + \ingroup pvr_scene_mgmt + + Once this has been called, you can not submit any more data until one of the + pvr_scene_begin() or pvr_scene_begin_txr() functions is called again. + + \retval 0 On success. + \retval -1 On error (no scene started). +*/ +int pvr_scene_finish(void); + +/** \brief Block the caller until the PVR system is ready for another frame to + be submitted. + \ingroup pvr_scene_mgmt + + The PVR system allocates enough space for two frames: one in data collection + mode, and another in rendering mode. If a frame is currently rendering, and + another frame has already been closed, then the caller cannot do anything + else until the rendering frame completes. Note also that the new frame + cannot be activated except during a vertical blanking period, so this + essentially waits until a rendered frame is complete and a vertical blank + happens. + + \retval 0 On success. A new scene can be started now. + \retval -1 On error. Something is probably very wrong... +*/ +int pvr_wait_ready(void); + +/** \brief Check if the PVR system is ready for another frame to be submitted. + \ingroup pvr_scene_mgmt + + \retval 0 If the PVR is ready for a new scene. You must call + pvr_wait_ready() afterwards, before starting a new + scene. + \retval -1 If the PVR is not ready for a new scene yet. +*/ +int pvr_check_ready(void); + + +/* Primitive handling ************************************************/ + +/** \defgroup pvr_primitives_compilation Compilation + \brief API for compiling primitive contexts + into headers + \ingroup pvr_ctx +*/ + +/** \brief Compile a polygon context into a polygon header. + \ingroup pvr_primitives_compilation + + This function compiles a pvr_poly_cxt_t into the form needed by the hardware + for rendering. This is for use with normal polygon headers. + + \param dst Where to store the compiled header. + \param src The context to compile. +*/ +void pvr_poly_compile(pvr_poly_hdr_t *dst, pvr_poly_cxt_t *src); + +/** \defgroup pvr_ctx_init Initialization + \brief Functions for initializing PVR polygon contexts + \ingroup pvr_ctx +*/ + +/** \brief Fill in a polygon context for non-textured polygons. + \ingroup pvr_ctx_init + + This function fills in a pvr_poly_cxt_t with default parameters appropriate + for rendering a non-textured polygon in the given list. + + \param dst Where to store the polygon context. + \param list The primitive list to be used. +*/ +void pvr_poly_cxt_col(pvr_poly_cxt_t *dst, pvr_list_t list); + +/** \brief Fill in a polygon context for a textured polygon. + \ingroup pvr_ctx_init + + This function fills in a pvr_poly_cxt_t with default parameters appropriate + for rendering a textured polygon in the given list. + + \param dst Where to store the polygon context. + \param list The primitive list to be used. + \param textureformat The format of the texture used. + \param tw The width of the texture, in pixels. + \param th The height of the texture, in pixels. + \param textureaddr A pointer to the texture. + \param filtering The type of filtering to use. + + \see pvr_txr_fmts + \see pvr_filter_modes +*/ +void pvr_poly_cxt_txr(pvr_poly_cxt_t *dst, pvr_list_t list, + int textureformat, int tw, int th, pvr_ptr_t textureaddr, + int filtering); + +/** \brief Compile a sprite context into a sprite header. + \ingroup pvr_primitives_compilation + + This function compiles a pvr_sprite_cxt_t into the form needed by the + hardware for rendering. This is for use with sprite headers. + + \param dst Where to store the compiled header. + \param src The context to compile. +*/ +void pvr_sprite_compile(pvr_sprite_hdr_t *dst, + pvr_sprite_cxt_t *src); + +/** \brief Fill in a sprite context for non-textured sprites. + \ingroup pvr_ctx_init + + This function fills in a pvr_sprite_cxt_t with default parameters + appropriate for rendering a non-textured sprite in the given list. + + \param dst Where to store the sprite context. + \param list The primitive list to be used. +*/ +void pvr_sprite_cxt_col(pvr_sprite_cxt_t *dst, pvr_list_t list); + +/** \brief Fill in a sprite context for a textured sprite. + \ingroup pvr_ctx_init + + This function fills in a pvr_sprite_cxt_t with default parameters + appropriate for rendering a textured sprite in the given list. + + \param dst Where to store the sprite context. + \param list The primitive list to be used. + \param textureformat The format of the texture used. + \param tw The width of the texture, in pixels. + \param th The height of the texture, in pixels. + \param textureaddr A pointer to the texture. + \param filtering The type of filtering to use. + + \see pvr_txr_fmts + \see pvr_filter_modes +*/ +void pvr_sprite_cxt_txr(pvr_sprite_cxt_t *dst, pvr_list_t list, + int textureformat, int tw, int th, pvr_ptr_t textureaddr, + int filtering); + +/** \brief Create a modifier volume header. + \ingroup pvr_primitives_compilation + + This function fills in a modifier volume header with the parameters + specified. Note that unlike for polygons and sprites, there is no context + step for modifiers. + + \param dst Where to store the modifier header. + \param list The primitive list to be used. + \param mode The mode for this modifier. + \param cull The culling mode to use. + + \see pvr_mod_modes + \see pvr_cull_modes +*/ +void pvr_mod_compile(pvr_mod_hdr_t *dst, pvr_list_t list, uint32_t mode, + uint32_t cull); + +/** \brief Compile a polygon context into a polygon header that is affected by + modifier volumes. + \ingroup pvr_primitives_compilation + + This function works pretty similarly to pvr_poly_compile(), but compiles + into the header type that is affected by a modifier volume. The context + should have been created with either pvr_poly_cxt_col_mod() or + pvr_poly_cxt_txr_mod(). + + \param dst Where to store the compiled header. + \param src The context to compile. +*/ +void pvr_poly_mod_compile(pvr_poly_mod_hdr_t *dst, pvr_poly_cxt_t *src); + +/** \brief Fill in a polygon context for non-textured polygons affected by a + modifier volume. + \ingroup pvr_ctx_init + + This function fills in a pvr_poly_cxt_t with default parameters appropriate + for rendering a non-textured polygon in the given list that will be affected + by modifier volumes. + + \param dst Where to store the polygon context. + \param list The primitive list to be used. +*/ +void pvr_poly_cxt_col_mod(pvr_poly_cxt_t *dst, pvr_list_t list); + +/** \brief Fill in a polygon context for a textured polygon affected by + modifier volumes. + \ingroup pvr_ctx_init + + This function fills in a pvr_poly_cxt_t with default parameters appropriate + for rendering a textured polygon in the given list and being affected by + modifier volumes. + + \param dst Where to store the polygon context. + \param list The primitive list to be used. + \param textureformat The format of the texture used (outside). + \param tw The width of the texture, in pixels (outside). + \param th The height of the texture, in pixels (outside). + \param textureaddr A pointer to the texture (outside). + \param filtering The type of filtering to use (outside). + \param textureformat2 The format of the texture used (inside). + \param tw2 The width of the texture, in pixels (inside). + \param th2 The height of the texture, in pixels (inside). + \param textureaddr2 A pointer to the texture (inside). + \param filtering2 The type of filtering to use (inside). + + \see pvr_txr_fmts + \see pvr_filter_modes +*/ +void pvr_poly_cxt_txr_mod(pvr_poly_cxt_t *dst, pvr_list_t list, + int textureformat, int tw, int th, + pvr_ptr_t textureaddr, int filtering, + int textureformat2, int tw2, int th2, + pvr_ptr_t textureaddr2, int filtering2); + +/* Texture handling **************************************************/ +/** \defgroup pvr_txr_mgmt Texturing + \brief API for managing PowerVR textures + \ingroup pvr + + Helper functions for handling texture tasks of various kinds. +*/ + +/** \brief Load raw texture data from an SH-4 buffer into PVR RAM. + \ingroup pvr_txr_mgmt + + This essentially just acts as a memcpy() from main RAM to PVR RAM, using + the Store Queues and 64-bit TA bus. + + \param src The location in main RAM holding the texture. + \param dst The location in PVR RAM to copy to. + \param count The size of the texture in bytes (must be a multiple + of 32). +*/ +void pvr_txr_load(void *src, pvr_ptr_t dst, uint32_t count); + +/** \defgroup pvr_txrload_constants Flags + \brief Texture loading constants + \ingroup pvr_txr_mgmt + + These are constants for the flags parameter to pvr_txr_load_ex() or + pvr_txr_load_kimg(). + + @{ +*/ +#define PVR_TXRLOAD_4BPP 0x01 /**< \brief 4BPP format */ +#define PVR_TXRLOAD_8BPP 0x02 /**< \brief 8BPP format */ +#define PVR_TXRLOAD_16BPP 0x03 /**< \brief 16BPP format */ +#define PVR_TXRLOAD_FMT_MASK 0x0f /**< \brief Bits used for basic formats */ + +#define PVR_TXRLOAD_VQ_LOAD 0x10 /**< \brief Do VQ encoding (not supported yet, if ever) */ +#define PVR_TXRLOAD_INVERT_Y 0x20 /**< \brief Invert the Y axis while loading */ +#define PVR_TXRLOAD_FMT_VQ 0x40 /**< \brief Texture is already VQ encoded */ +#define PVR_TXRLOAD_FMT_TWIDDLED 0x80 /**< \brief Texture is already twiddled */ +#define PVR_TXRLOAD_FMT_NOTWIDDLE 0x80 /**< \brief Don't twiddle the texture while loading */ +#define PVR_TXRLOAD_DMA 0x8000 /**< \brief Use DMA to load the texture */ +#define PVR_TXRLOAD_NONBLOCK 0x4000 /**< \brief Use non-blocking loads (only for DMA) */ +#define PVR_TXRLOAD_SQ 0x2000 /**< \brief Use Store Queues to load */ + +/** @} */ + +/** \brief Load texture data from an SH-4 buffer into PVR RAM, twiddling it in + the process. + \ingroup pvr_txr_mgmt + + This function loads a texture to the PVR's RAM with the specified set of + flags. It will currently always twiddle the data, whether you ask it to or + not, and many of the parameters are just plain not supported at all... + Pretty much the only supported flag, other than the format ones is the + PVR_TXRLOAD_INVERT_Y one. + + This will be slower than using pvr_txr_load() in pretty much all cases, so + unless you need to twiddle your texture, just use that instead. + + \param src The location to copy from. + \param dst The location to copy to. + \param w The width of the texture, in pixels. + \param h The height of the texture, in pixels. + \param flags Some set of flags, ORed together. + + \see pvr_txrload_constants +*/ +void pvr_txr_load_ex(void *src, pvr_ptr_t dst, uint32_t w, uint32_t h, uint32_t flags); + +/** \brief Load a KOS Platform Independent Image (subject to constraint + checking). + \ingroup pvr_txr_mgmt + + This function loads a KOS Platform Independent image to the PVR's RAM with + the specified set of flags. This function, unlike pvr_txr_load_ex() supports + everything in the flags available, other than what's explicitly marked as + not supported. + + \param img The image to load. + \param dst The location to copy to. + \param flags Some set of flags, ORed together. + + \see pvr_txrload_constants + \note Unless you explicitly tell this function to not + twiddle the texture (by ORing + \ref PVR_TXRLOAD_FMT_NOTWIDDLE or it's equivalent + \ref PVR_TXRLOAD_FMT_TWIDDLED with flags), this + function will twiddle the texture while loading. + Keep that in mind when setting the texture format in + polygon headers later. + \note You cannot specify both + \ref PVR_TXRLOAD_FMT_NOTWIDDLE (or equivalently + \ref PVR_TXRLOAD_FMT_TWIDDLED) and + \ref PVR_TXRLOAD_INVERT_Y in the flags. + \note DMA and Store Queue based loading is not available + from this function if it twiddles the texture while + loading. +*/ +void pvr_txr_load_kimg(kos_img_t *img, pvr_ptr_t dst, uint32_t flags); + + +/* PVR DMA ***********************************************************/ +/** \defgroup pvr_dma DMA + \brief PowerVR DMA driver + \ingroup pvr +*/ + +/** \brief PVR DMA interrupt callback type. + \ingroup pvr_dma + + Functions that act as callbacks when DMA completes should be of this type. + These functions will be called inside an interrupt context, so don't try to + use anything that might stall. + + \param data User data passed in to the pvr_dma_transfer() + function. +*/ +typedef void (*pvr_dma_callback_t)(void *data); + +/** \brief Perform a DMA transfer to the PVR RAM over 64-bit TA bus. + \ingroup pvr_dma + + This function copies a block of data to the PVR or its memory via DMA. There + are all kinds of constraints that must be fulfilled to actually do this, so + make sure to read all the fine print with the parameter list. + + If a callback is specified, it will be called in an interrupt context, so + keep that in mind in writing the callback. + + \param src Where to copy from. Must be 32-byte aligned. + \param dest Where to copy to. Must be 32-byte aligned. + \param count The number of bytes to copy. Must be a multiple of + 32. + \param type The type of DMA transfer to do (see list of modes). + \param block Non-zero if you want the function to block until the + DMA completes. + \param callback A function to call upon completion of the DMA. + \param cbdata Data to pass to the callback function. + \retval 0 On success. + \retval -1 On failure. Sets errno as appropriate. + + \par Error Conditions: + \em EINPROGRESS - DMA already in progress \n + \em EFAULT - dest is not 32-byte aligned \n + \em EIO - I/O error + + \see pvr_dma_modes +*/ +int pvr_dma_transfer(void *src, uintptr_t dest, size_t count, int type, + int block, pvr_dma_callback_t callback, void *cbdata); + +/** \defgroup pvr_dma_modes Transfer Modes + \brief Transfer modes with TA/PVR DMA and Store Queues + \ingroup pvr_dma + + @{ +*/ +#define PVR_DMA_VRAM64 0 /**< \brief Transfer to VRAM using TA bus */ +#define PVR_DMA_VRAM32 1 /**< \brief Transfer to VRAM using TA bus */ +#define PVR_DMA_TA 2 /**< \brief Transfer to the tile accelerator */ +#define PVR_DMA_YUV 3 /**< \brief Transfer to the YUV converter (TA) */ +#define PVR_DMA_VRAM32_SB 4 /**< \brief Transfer to/from VRAM using PVR i/f */ +#define PVR_DMA_VRAM64_SB 5 /**< \brief Transfer to/from VRAM using PVR i/f */ +/** @} */ + +/** \brief Load a texture using TA DMA. + \ingroup pvr_dma + + This is essentially a convenience wrapper for pvr_dma_transfer(), so all + notes that apply to it also apply here. + + \param src Where to copy from. Must be 32-byte aligned. + \param dest Where to copy to. Must be 32-byte aligned. + \param count The number of bytes to copy. Must be a multiple of + 32. + \param block Non-zero if you want the function to block until the + DMA completes. + \param callback A function to call upon completion of the DMA. + \param cbdata Data to pass to the callback function. + \retval 0 On success. + \retval -1 On failure. Sets errno as appropriate. + + \par Error Conditions: + \em EINPROGRESS - DMA already in progress \n + \em EFAULT - dest is not 32-byte aligned \n + \em EIO - I/O error +*/ +int pvr_txr_load_dma(void *src, pvr_ptr_t dest, size_t count, int block, + pvr_dma_callback_t callback, void *cbdata); + +/** \brief Load vertex data to the TA using TA DMA. + \ingroup pvr_dma + + This is essentially a convenience wrapper for pvr_dma_transfer(), so all + notes that apply to it also apply here. + + \param src Where to copy from. Must be 32-byte aligned. + \param count The number of bytes to copy. Must be a multiple of + 32. + \param block Non-zero if you want the function to block until the + DMA completes. + \param callback A function to call upon completion of the DMA. + \param cbdata Data to pass to the callback function. + \retval 0 On success. + \retval -1 On failure. Sets errno as appropriate. + + \par Error Conditions: + \em EINPROGRESS - DMA already in progress \n + \em EFAULT - dest is not 32-byte aligned \n + \em EIO - I/O error + */ +int pvr_dma_load_ta(void *src, size_t count, int block, + pvr_dma_callback_t callback, void *cbdata); + +/** \brief Load yuv data to the YUV converter using TA DMA. + \ingroup pvr_dma + + This is essentially a convenience wrapper for pvr_dma_transfer(), so all + notes that apply to it also apply here. + + \param src Where to copy from. Must be 32-byte aligned. + \param count The number of bytes to copy. Must be a multiple of + 32. + \param block Non-zero if you want the function to block until the + DMA completes. + \param callback A function to call upon completion of the DMA. + \param cbdata Data to pass to the callback function. + \retval 0 On success. + \retval -1 On failure. Sets errno as appropriate. + + \par Error Conditions: + \em EINPROGRESS - DMA already in progress \n + \em EFAULT - dest is not 32-byte aligned \n + \em EIO - I/O error +*/ +int pvr_dma_yuv_conv(void *src, size_t count, int block, + pvr_dma_callback_t callback, void *cbdata); + +/** \brief Is PVR DMA is inactive? + \ingroup pvr_dma + \return Non-zero if there is no PVR DMA active, thus a DMA + can begin or 0 if there is an active DMA. +*/ +int pvr_dma_ready(void); + +/** \brief Initialize TA/PVR DMA. + \ingroup pvr_dma + */ +void pvr_dma_init(void); + +/** \brief Shut down TA/PVR DMA. + \ingroup pvr_dma + */ +void pvr_dma_shutdown(void); + +/** \brief Copy a block of memory to VRAM + \ingroup store_queues + + This function is similar to sq_cpy(), but it has been + optimized for writing to a destination residing within VRAM. + + \warning + This function cannot be used at the same time as a PVR DMA transfer. + + The dest pointer must be at least 32-byte aligned and reside + in video memory, the src pointer must be at least 8-byte aligned, + and n must be a multiple of 32. + + \param dest The address to copy to (32-byte aligned). + \param src The address to copy from (32-bit (8-byte) aligned). + \param n The number of bytes to copy (multiple of 32). + \param type The type of SQ/DMA transfer to do (see list of modes). + \return The original value of dest. + + \sa pvr_sq_set32() +*/ +void *pvr_sq_load(void *dest, const void *src, size_t n, int type); + +/** \brief Set a block of PVR memory to a 16-bit value. + \ingroup store_queues + + This function is similar to sq_set16(), but it has been + optimized for writing to a destination residing within VRAM. + + \warning + This function cannot be used at the same time as a PVR DMA transfer. + + The dest pointer must be at least 32-byte aligned and reside in video + memory, n must be a multiple of 32 and only the low 16-bits are used + from c. + + \param dest The address to begin setting at (32-byte aligned). + \param c The value to set (in the low 16-bits). + \param n The number of bytes to set (multiple of 32). + \param type The type of SQ/DMA transfer to do (see list of modes). + \return The original value of dest. + + \sa pvr_sq_set32() +*/ +void *pvr_sq_set16(void *dest, uint32_t c, size_t n, int type); + +/** \brief Set a block of PVR memory to a 32-bit value. + \ingroup store_queues + + This function is similar to sq_set32(), but it has been + optimized for writing to a destination residing within VRAM. + + \warning + This function cannot be used at the same time as a PVR DMA transfer. + + The dest pointer must be at least 32-byte aligned and reside in video + memory, n must be a multiple of 32. + + \param dest The address to begin setting at (32-byte aligned). + \param c The value to set. + \param n The number of bytes to set (multiple of 32). + \param type The type of SQ/DMA transfer to do (see list of modes). + \return The original value of dest. + + \sa pvr_sq_set16 +*/ +void *pvr_sq_set32(void *dest, uint32_t c, size_t n, int type); + +/*********************************************************************/ + + +#include "../pvr_internal.h" diff --git a/vendor/koshle/dc/sq.h b/vendor/koshle/dc/sq.h new file mode 100644 index 00000000..be8c507b --- /dev/null +++ b/vendor/koshle/dc/sq.h @@ -0,0 +1,224 @@ +/* KallistiOS ##version## + + kernel/arch/dreamcast/include/dc/sq.h + Copyright (C) 2000-2001 Andrew Kieschnick + Copyright (C) 2023 Falco Girgis + Copyright (C) 2023 Ruslan Rostovtsev + Copyright (C) 2023-2024 Andy Barajas +*/ + +/** \file dc/sq.h + \ingroup store_queues + \brief Functions to access the SH4 Store Queues. + + \author Andrew Kieschnick + \author Falco Girgis + \author Andy Barajas + \author Ruslan Rostovtsev +*/ + +/** \defgroup store_queues Store Queues + \brief SH4 CPU Peripheral for burst memory transactions. + \ingroup system + + The store queues are a way to do efficient burst transfers from the CPU to + external memory. They can be used in a variety of ways, such as to transfer + a texture to PVR memory. The transfers are in units of 32-bytes, and the + destinations must be 32-byte aligned. + + \note + Mastery over knowing when and how to utilize the store queues is + important when trying to push the limits of the Dreamcast, specifically + when transferring chunks of data between regions of memory. It is often + the case that the DMA is faster for transactions which are consistently + large; however, the store queues tend to have better performance and + have less configuration overhead when bursting smaller chunks of data. +*/ + +#ifndef __DC_SQ_H +#define __DC_SQ_H + +#include "dc_hle_types.h" + +#include +// #include +// #include +// #include + +/** \brief Mask dest to Store Queue area as address + \ingroup store_queues +*/ +#define SQ_MASK_DEST_ADDR(dest) \ + (MEM_AREA_SQ_BASE | ((uintptr_t)(dest) & 0x03ffffe0)) + +/** \brief Mask dest to Store Queue area as pointer + \ingroup store_queues +*/ +#define SQ_MASK_DEST(dest) \ + ((uint32_t *)(void *) SQ_MASK_DEST_ADDR(dest)) + +/** \brief Lock Store Queues + \ingroup store_queues + + Locks the store queues so that they cannot be used from another thread + until unlocked. + + \warning + This function is called automatically by the store queue API provided by KOS; + however, it must be called manually when driving the SQs directly from outside + of this API. + + \sa sq_unlock() +*/ +void sq_lock(void *dest); + +/** \brief Unlock Store Queues + \ingroup store_queues + + Unlocks the store queues so that they can be used from any thread. + + \note + sq_lock() should've already been called previously. + + \warning + sq_lock() and sq_unlock() are called automatically by the store queue API provided + by KOS; however, they must be called manually when driving the SQs directly from + outside this API. + + \sa sq_lock() +*/ +void sq_unlock(void); + +/** \brief Wait for both Store Queues to complete + \ingroup store_queues + + Wait for both store queues to complete by writing to SQ area. + + \sa sq_lock() +*/ +void sq_wait(void); + +/** \brief Write-back one Store Queue + \ingroup store_queues + + Initiates write-back from SQ buffer to external memory. + + \param dest The address to copy to (32-byte aligned). + + \sa sq_wait() +*/ +#define sq_flush(dest) dcache_wback_sq(dest) + +/** \brief Copy a block of memory. + \ingroup store_queues + + This function is similar to memcpy4(), but uses the store queues to do its + work. + + \warning + The dest pointer must be at least 32-byte aligned, the src pointer + must be at least 4-byte aligned (8-byte aligned uses fast path), + and n must be a multiple of 32! + + \param dest The address to copy to (32-byte aligned). + \param src The address to copy from (32-bit (4/8-byte) aligned). + \param n The number of bytes to copy (multiple of 32). + \return The original value of dest. + + \sa sq_fast_cpy() +*/ +void *sq_cpy(void *dest, const void *src, size_t n); + +/** \brief Copy a block of memory. + \ingroup store_queues + + This function is similar to sq_cpy() but expects the user to lock/unlock + the store queues before and after as well as having different requirements + for the params. + + \warning + The dest pointer must be at least 32-byte aligned that already has been + masked by SQ_MASK_DEST(), the src pointer must be at least 8-byte aligned, + and n must be the number of 32-byte blocks you want to copy. + + \param dest The store queue address to copy to (32-byte aligned). + \param src The address to copy from (8-byte aligned). + \param n The number of 32-byte blocks to copy. + \return The original value of dest. + + \sa sq_cpy() + */ +void *sq_fast_cpy(void *dest, const void *src, size_t n); + +/** \brief Set a block of memory to an 8-bit value. + \ingroup store_queues + + This function is similar to calling memset(), but uses the store queues to + do its work. + + \warning + The dest pointer must be a 32-byte aligned with n being a multiple of 32, + and only the low 8-bits are used from c. + + \param dest The address to begin setting at (32-byte aligned). + \param c The value to set (in the low 8-bits). + \param n The number of bytes to set (multiple of 32). + \return The original value of dest. + + \sa sq_set16(), sq_set32() +*/ +void *sq_set(void *dest, uint32_t c, size_t n); + +/** \brief Set a block of memory to a 16-bit value. + \ingroup store_queues + + This function is similar to calling memset2(), but uses the store queues to + do its work. + + \warning + The dest pointer must be a 32-byte aligned with n being a multiple of 32, + and only the low 16-bits are used from c. + + \param dest The address to begin setting at (32-byte aligned). + \param c The value to set (in the low 16-bits). + \param n The number of bytes to set (multiple of 32). + \return The original value of dest. + + \sa sq_set(), sq_set32() +*/ +void *sq_set16(void *dest, uint32_t c, size_t n); + +/** \brief Set a block of memory to a 32-bit value. + \ingroup store_queues + + This function is similar to calling memset4(), but uses the store queues to + do its work. + + \warning + The dest pointer must be a 32-byte aligned with n being a multiple of 32! + + \param dest The address to begin setting at (32-byte aligned). + \param c The value to set (all 32-bits). + \param n The number of bytes to set (multiple of 32). + \return The original value of dest. + + \sa sq_set(), sq_set16() +*/ +void *sq_set32(void *dest, uint32_t c, size_t n); + +/** \brief Clear a block of memory. + \ingroup store_queues + + This function is similar to calling memset() with a value to set of 0, but + uses the store queues to do its work. + + \warning + The dest pointer must be a 32-byte aligned with n being a multiple of 32! + + \param dest The address to begin clearing at (32-byte aligned). + \param n The number of bytes to clear (multiple of 32). +*/ +void sq_clr(void *dest, size_t n); + + +#endif diff --git a/vendor/koshle/dc_hle_types.h b/vendor/koshle/dc_hle_types.h new file mode 100644 index 00000000..a5c2f114 --- /dev/null +++ b/vendor/koshle/dc_hle_types.h @@ -0,0 +1,65 @@ +#pragma once + +#if ( (__LONG_MAX__ *2UL+1UL) == 18446744073709551615ULL) && ((__INT_MAX__ *2U +1U) == 4294967295ULL) +typedef unsigned long uint64; /**< \brief 64-bit unsigned integer */ +typedef unsigned int uint32; /**< \brief 32-bit unsigned integer */ +typedef unsigned short uint16; /**< \brief 16-bit unsigned integer */ +typedef unsigned char uint8; /**< \brief 8-bit unsigned integer */ +typedef long int64; /**< \brief 64-bit signed integer */ +typedef int int32; /**< \brief 32-bit signed integer */ +typedef short int16; /**< \brief 16-bit signed integer */ +typedef char int8; /**< \brief 8-bit signed integer */ + +typedef volatile unsigned long vuint64; /**< \brief 64-bit unsigned integer */ +typedef volatile unsigned int vuint32; /**< \brief 32-bit unsigned integer */ +typedef volatile unsigned short vuint16; /**< \brief 16-bit unsigned integer */ +typedef volatile unsigned char vuint8; /**< \brief 8-bit unsigned integer */ +typedef volatile long vint64; /**< \brief 64-bit signed integer */ +typedef volatile int vint32; /**< \brief 32-bit signed integer */ +typedef volatile short vint16; /**< \brief 16-bit signed integer */ +typedef volatile char vint8; /**< \brief 8-bit signed integer */ + +typedef uint64 ptr_t; +#define INT32_IS_INT +#elif ((__LONG_LONG_MAX__*2ULL+1ULL) == 18446744073709551615ULL) && ((__LONG_MAX__ *2UL+1UL) == 4294967295ULL) +// These are -m32 specific and try to follow KOS rules +typedef unsigned long long uint64; /**< \brief 64-bit unsigned integer */ +typedef unsigned long uint32; /**< \brief 32-bit unsigned integer */ +typedef unsigned short uint16; /**< \brief 16-bit unsigned integer */ +typedef unsigned char uint8; /**< \brief 8-bit unsigned integer */ +typedef long long int64; /**< \brief 64-bit signed integer */ +typedef long int32; /**< \brief 32-bit signed integer */ +typedef short int16; /**< \brief 16-bit signed integer */ +typedef char int8; /**< \brief 8-bit signed integer */ + +typedef volatile unsigned long long vuint64; /**< \brief 64-bit unsigned integer */ +typedef volatile unsigned long vuint32; /**< \brief 32-bit unsigned integer */ +typedef volatile unsigned short vuint16; /**< \brief 16-bit unsigned integer */ +typedef volatile unsigned char vuint8; /**< \brief 8-bit unsigned integer */ +typedef volatile long long vint64; /**< \brief 64-bit signed integer */ +typedef volatile long vint32; /**< \brief 32-bit signed integer */ +typedef volatile short vint16; /**< \brief 16-bit signed integer */ +typedef volatile char vint8; /**< \brief 8-bit signed integer */ + +typedef uint32 ptr_t; +#else +#error "Unable to detect basic types" +#endif + +static_assert(sizeof(uint64) == 8, "uint64 size is not 8 bytes"); +static_assert(sizeof(uint32) == 4, "uint32 size is not 4 bytes"); +static_assert(sizeof(uint16) == 2, "uint16 size is not 2 bytes"); +static_assert(sizeof(uint8) == 1, "uint8 size is not 1 byte"); +static_assert(sizeof(int64) == 8, "int64 size is not 8 bytes"); +static_assert(sizeof(int32) == 4, "int32 size is not 4 bytes"); +static_assert(sizeof(int16) == 2, "int16 size is not 2 bytes"); +static_assert(sizeof(int8) == 1, "int8 size is not 1 byte"); +static_assert(sizeof(vuint64) == 8, "vuint64 size is not 8 bytes"); +static_assert(sizeof(vuint32) == 4, "vuint32 size is not 4 bytes"); +static_assert(sizeof(vuint16) == 2, "vuint16 size is not 2 bytes"); +static_assert(sizeof(vuint8) == 1, "vuint8 size is not 1 byte"); +static_assert(sizeof(vint64) == 8, "vint64 size is not 8 bytes"); +static_assert(sizeof(vint32) == 4, "vint32 size is not 4 bytes"); +static_assert(sizeof(vint16) == 2, "vint16 size is not 2 bytes"); +static_assert(sizeof(vint8) == 1, "vint8 size is not 1 byte"); +static_assert(sizeof(ptr_t) == sizeof(void*), "ptr_t size is not equal to void* size"); \ No newline at end of file diff --git a/vendor/koshle/hlekos.cpp b/vendor/koshle/hlekos.cpp new file mode 100644 index 00000000..50f934f6 --- /dev/null +++ b/vendor/koshle/hlekos.cpp @@ -0,0 +1,75 @@ +#include "dc_hle_types.h" +#include "dc/pvr.h" +#include "dc/maple.h" +#include "dc/maple/controller.h" +#include "dc/asic.h" + +#include "emu/emu.h" + +#include +#include +#include +#include + +#include "refsw/refsw_tile.h" + +#include "pvr_internal.h" +volatile pvr_state_t pvr_state; + +static maple_device_t dev; +cont_state_t mapleInput; +void * maple_dev_status(maple_device*) { + return &mapleInput; +} +maple_device_t * maple_enum_type(int n, uint32 func) { + return &dev; +} + +int sem_wait_timed(semaphore_t *sem, int timeout) { + auto count = sem->count.load(); + + // This is a hack til vlbanks are raised in a nicer way + Hackpresent(); + pvr_queue_interrupt(ASIC_EVT_PVR_VBLANK_BEGIN); + + while(count <= 0 || !sem->count.compare_exchange_strong(count, count-1)) { + count = sem->count.load(); + } + + return 0; +} + +int sem_count(semaphore_t *sem) { + return sem->count.load(); +} + +int sem_init(semaphore_t *sm, int count) { + sm->count = count; + sm->initialized = 1; + return 0; +} + +int sem_destroy(semaphore_t *sm) { + sm->count = INT32_MAX; + sm->initialized = 0; + return 0; +} +int sem_signal(semaphore_t *sem) { + sem->count++; + return 0; +} + +void sq_lock(void *dest) { + +} + +void sq_unlock() { + +} + +void pvr_dma_init(void) { +} + +void pvr_dma_shutdown(void) { + +} \ No newline at end of file diff --git a/vendor/koshle/hlematrix3d.cpp b/vendor/koshle/hlematrix3d.cpp new file mode 100644 index 00000000..caa62509 --- /dev/null +++ b/vendor/koshle/hlematrix3d.cpp @@ -0,0 +1,200 @@ +/* KallistiOS ##version## + + matrix3d.c + Copyright (C) 2000-2002 Megan Potter and Jordan DeLong + Copyright (C) 2014 Josh Pearson + + Some 3D utils to use with the matrix functions + Based on example code by Marcus Comstedt +*/ + + +#include "common.h" +#include +#include +#include + +matrix_t XMTRX = { + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f } +}; + +void mat_identity() { + XMTRX[0][0] = 1.0f; XMTRX[1][0] = 0.0f; XMTRX[2][0] = 0.0f; XMTRX[3][0] = 0.0f; + XMTRX[0][1] = 0.0f; XMTRX[1][1] = 1.0f; XMTRX[2][1] = 0.0f; XMTRX[3][1] = 0.0f; + XMTRX[0][2] = 0.0f; XMTRX[1][2] = 0.0f; XMTRX[2][2] = 1.0f; XMTRX[3][2] = 0.0f; + XMTRX[0][3] = 0.0f; XMTRX[1][3] = 0.0f; XMTRX[2][3] = 0.0f; XMTRX[3][3] = 1.0f; +} + +void mat_load(const matrix_t *mat) { + memcpy(XMTRX, mat, sizeof(matrix_t)); +} + +void mat_store(matrix_t *mat) { + memcpy(mat, XMTRX, sizeof(matrix_t)); +} + +static void mat_mult(matrix_t dst, const matrix_t &src1, const matrix_t &src2) { + for(unsigned r = 0; r < 4; ++r) { + for(unsigned c = 0; c < 4; ++c) { + dst[r][c] = 0.0f; + + for(unsigned k = 0; k < 4; ++k) + dst[r][c] += src1[r][k] * src2[k][c]; + } + } +} + +void mat_apply(const matrix_t* mat) { + matrix_t result; + mat_mult(result, *mat, XMTRX); + mat_load(&result); +} + +void mat_transform(vector_t *invecs, vector_t *outvecs, int veccnt, int vecskip) { + for(unsigned v = 0; v < veccnt; ++v) { + auto offset = v * (sizeof(vector_t) + vecskip); + auto *in = reinterpret_cast(reinterpret_cast(invecs) + offset); + auto *out = reinterpret_cast(reinterpret_cast(outvecs) + offset); + + float x = XMTRX[0][0]*in->x + XMTRX[1][0]*in->y + XMTRX[2][0]*in->z + XMTRX[3][0]*in->w; + float y = XMTRX[0][1]*in->x + XMTRX[1][1]*in->y + XMTRX[2][1]*in->z + XMTRX[3][1]*in->w; + float z = XMTRX[0][2]*in->x + XMTRX[1][2]*in->y + XMTRX[2][2]*in->z + XMTRX[3][2]*in->w; + float w = XMTRX[0][3]*in->x + XMTRX[1][3]*in->y + XMTRX[2][3]*in->z + XMTRX[3][3]*in->w; + + out->x = x; out->y = y; out->z = z; out->w = w; + } +} + +void mat_trans_single3_nodiv(float &x, float &y, float &z) { + vector_t vec = { x, y, z, 1.0f }; + mat_transform(&vec, &vec, 1, 0); + x = vec.x; y = vec.y; z = vec.z; +} + +static matrix_t tr_m __attribute__((aligned(32))) = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; +void mat_translate(float x, float y, float z) { + tr_m[3][0] = x; + tr_m[3][1] = y; + tr_m[3][2] = z; + mat_apply(&tr_m); +} + +static matrix_t sc_m __attribute__((aligned(32))) = { + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; +void mat_scale(float xs, float ys, float zs) { + sc_m[0][0] = xs; + sc_m[1][1] = ys; + sc_m[2][2] = zs; + mat_apply(&sc_m); +} + +static matrix_t rx_m __attribute__((aligned(32))) = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; +void mat_rotate_x(float r) { + //__fsincosr(r, rx_m[2][1], rx_m[1][1]); + rx_m[2][2] = rx_m[1][1]; + rx_m[1][2] = -rx_m[2][1]; + mat_apply(&rx_m); +} + +static matrix_t ry_m __attribute__((aligned(32))) = { + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; +void mat_rotate_y(float r) { + // __fsincosr(r, ry_m[0][2], ry_m[0][0]); + ry_m[2][2] = ry_m[0][0]; + ry_m[2][0] = -ry_m[0][2]; + mat_apply(&ry_m); +} + +static matrix_t rz_m __attribute__((aligned(32))) = { + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; +void mat_rotate_z(float r) { + //__fsincosr(r, rz_m[1][0], rz_m[0][0]); + rz_m[1][1] = rz_m[0][0]; + rz_m[0][1] = -rz_m[1][0]; + mat_apply(&rz_m); +} + +void mat_rotate(float xr, float yr, float zr) { + mat_rotate_x(xr); + mat_rotate_y(yr); + mat_rotate_z(zr); +} + +/* Some #define's so we can keep the nice looking matrices for reference */ +#define XCENTER 0.0f +#define YCENTER 0.0f +#define COT_FOVY_2 1.0f +#define ZNEAR 1.0f +#define ZFAR 100.0f + +/* Screen view matrix (used to transform to screen space) */ +static matrix_t sv_mat = { + { YCENTER, 0.0f, 0.0f, 0.0f }, + { 0.0f, YCENTER, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { XCENTER, YCENTER, 0.0f, 1.0f } +}; + +/* Frustum matrix (does perspective) */ +static matrix_t fr_mat = { + { COT_FOVY_2, 0.0f, 0.0f, 0.0f }, + { 0.0f, COT_FOVY_2, 0.0f, 0.0f }, + { 0.0f, 0.0f, (ZFAR + ZNEAR) / (ZNEAR - ZFAR), -1.0f }, + { 0.0f, 0.0f, 2 * ZFAR*ZNEAR / (ZNEAR - ZFAR), 1.0f } +}; + +void mat_perspective(float xcenter, float ycenter, float cot_fovy_2, + float znear, float zfar) { + /* Setup the screenview matrix */ + sv_mat[0][0] = sv_mat[1][1] = sv_mat[3][1] = ycenter; + sv_mat[3][0] = xcenter; + mat_apply(&sv_mat); + + /* Setup the frustum matrix */ + assert((znear - zfar) != 0); + fr_mat[0][0] = fr_mat[1][1] = cot_fovy_2; + fr_mat[2][2] = (zfar + znear) / (znear - zfar); + fr_mat[3][2] = 2 * zfar * znear / (znear - zfar); + mat_apply(&fr_mat); +} + + +/* The following lookat code is based heavily on KGL's gluLookAt */ + +static inline void cross(const vec3f_t *v1, const vec3f_t *v2, vec3f_t *r) { + r->x = v1->y * v2->z - v1->z * v2->y; + r->y = v1->z * v2->x - v1->x * v2->z; + r->z = v1->x * v2->y - v1->y * v2->x; +} + +static matrix_t ml __attribute__((aligned(32))) = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; diff --git a/vendor/koshle/hlepvr_buffers.cpp b/vendor/koshle/hlepvr_buffers.cpp new file mode 100644 index 00000000..7e53a056 --- /dev/null +++ b/vendor/koshle/hlepvr_buffers.cpp @@ -0,0 +1,329 @@ +/* KallistiOS ##version## + + pvr_buffers.c + Copyright (C) 2002, 2004 Megan Potter + Copyright (C) 2014 Lawrence Sebald + + */ + +#include +#include +#include "dc/pvr.h" +// #include +#include "pvr_internal.h" + +#define dbglog(channel, ...) printf(__VA_ARGS__) +#define assert_msg(cond, txt) assert(cond && txt) + +/* + + This module handles buffer allocation for the structures that the + TA feed into, and which the ISP/TSP read from during the scene + rendering. + +*/ + + +/* There's quite a bit of byte vs word conversion in this file + * these macros just help make that more readable */ +#define BYTES_TO_WORDS(x) ((x) >> 2) +#define WORDS_TO_BYTES(x) ((x) << 2) + +#define IS_ALIGNED(x, m) ((x) % (m) == 0) + +#define LIST_ENABLED(i) (pvr_state.lists_enabled & (1 << (i))) + + +/* Fill Tile Matrix buffers. This function takes a base address and sets up + the rendering structures there. Each tile of the screen (32x32) receives + a small buffer space. */ +static void pvr_init_tile_matrix(int which, int presort) { + volatile pvr_ta_buffers_t *buf; + int x, y, tn; + b32_uint32 *vr; /* Note: We're working in 4-byte pointer maths in this function */ + volatile int *opb_sizes; + //uint32 matbase, opbbase; + + vr = (b32_uint32*)emu_vram; //(uint32*)PVR_RAM_BASE; + buf = pvr_state.ta_buffers + which; + opb_sizes = pvr_state.opb_size; + +#if 0 + matbase = buf->tile_matrix; + opbbase = buf->opb; +#endif + + /* + FIXME? Is this header necessary? If we're moving the tilematrix + register to after it, how does the Dreamcast know this is here? + */ + + /* Header of zeros */ + vr += BYTES_TO_WORDS(buf->tile_matrix); + + for(x = 0; x < 0x48; x += 4) + * vr++ = 0; + + /* Initial init tile */ + vr[0] = 0x10000000; + vr[1] = 0x80000000; + vr[2] = 0x80000000; + vr[3] = 0x80000000; + vr[4] = 0x80000000; + vr[5] = 0x80000000; + vr += 6; + + /* Must skip over zeroed header for actual usage */ + buf->tile_matrix += 0x48; + + /* Now the main tile matrix */ +#if 0 + dbglog(DBG_KDEBUG, " Using poly buffers %08lx/%08lx/%08lx/%08lx/%08lx\r\n", + buf->opb_type[0], + buf->opb_type[1], + buf->opb_type[2], + buf->opb_type[3], + buf->opb_type[4]); +#endif /* !NDEBUG */ + + /* + This sets up the addresses for each list, for each tile in the + memory we allocate in pvr_allocate_buffers. If a list isn't enabled + for a tile, then we set the address to 0x80000000 which tells the PVR + to ignore it. + + Memory for each frame is arranged sort-of like this: + + [vertex_buffer | object pointer buffers | tilematrix header | tile matrix] + + This is the tile matrix setup. + */ + + for(x = 0; x < pvr_state.tw; x++) { + for(y = 0; y < pvr_state.th; y++) { + tn = (pvr_state.tw * y) + x; + + /* Control word */ + vr[0] = (y << 8) | (x << 2) | (presort << 29); + + /* Opaque poly buffer */ + vr[1] = LIST_ENABLED(0) ? buf->opb_addresses[0] + (opb_sizes[0] * tn) : 0x80000000; + + /* Opaque volume mod buffer */ + vr[2] = LIST_ENABLED(1) ? buf->opb_addresses[1] + (opb_sizes[1] * tn) : 0x80000000; + + /* Translucent poly buffer */ + vr[3] = LIST_ENABLED(2) ? buf->opb_addresses[2] + (opb_sizes[2] * tn) : 0x80000000; + + /* Translucent volume mod buffer */ + vr[4] = LIST_ENABLED(3) ? buf->opb_addresses[3] + (opb_sizes[3] * tn) : 0x80000000; + + /* Punch-thru poly buffer */ + vr[5] = LIST_ENABLED(4) ? buf->opb_addresses[4] + (opb_sizes[4] * tn) : 0x80000000; + vr += 6; + } + } + + vr[-6] |= 1 << 31; +} + +/* Fill all tile matrices */ +void pvr_init_tile_matrices(int presort) { + int i; + + for(i = 0; i < 2; i++) + pvr_init_tile_matrix(i, presort); +} + +void pvr_set_presort_mode(int presort) { + pvr_init_tile_matrix(pvr_state.ta_target, presort); +} + + +/* Allocate PVR buffers given a set of parameters + +There's some confusion in here that is explained more fully in pvr_internal.h. + +The other confusing thing is that texture ram is a 64-bit multiplexed space +rather than a copy of the flat 32-bit VRAM. So in order to maximize the +available texture RAM, the PVR structures for the two frames are broken +up and placed at 0x000000 and 0x400000. + +*/ +#define BUF_ALIGN 128 +#define BUF_ALIGN_MASK (BUF_ALIGN - 1) +#define APPLY_ALIGNMENT(addr) (((addr) + BUF_ALIGN_MASK) & ~BUF_ALIGN_MASK) + +void pvr_allocate_buffers(pvr_init_params_t *params) { + volatile pvr_ta_buffers_t *buf; + volatile pvr_frame_buffers_t *fbuf; + int i, j; + uint32 outaddr, sconst, opb_size_accum, opb_total_size; + + /* Set screen sizes; pvr_init has ensured that we have a valid mode + and all that by now, so we can freely dig into the vid_mode + structure here. */ + pvr_state.w = 640; //vid_mode->width; + pvr_state.h = 480; //vid_mode->height; + pvr_state.tw = pvr_state.w / 32; + pvr_state.th = pvr_state.h / 32; + + /* FSAA -> double the tile buffer width */ + if(pvr_state.fsaa) + pvr_state.tw *= 2; + + /* We can actually handle non-mod-32 heights pretty easily -- just extend + the frame buffer a bit, but use a pixel clip for the real mode. */ + if(!IS_ALIGNED(pvr_state.h, 32)) { + pvr_state.h = (pvr_state.h + 32) & ~31; + pvr_state.th++; + } + + pvr_state.tsize_const = ((pvr_state.th - 1) << 16) + | ((pvr_state.tw - 1) << 0); + + /* Set clipping parameters */ + pvr_state.zclip = 0.0001f; + pvr_state.pclip_left = 0; + pvr_state.pclip_right = 639; //vid_mode->width - 1; + pvr_state.pclip_top = 0; + pvr_state.pclip_bottom = 479; //vid_mode->height - 1; + pvr_state.pclip_x = (pvr_state.pclip_right << 16) | (pvr_state.pclip_left); + pvr_state.pclip_y = (pvr_state.pclip_bottom << 16) | (pvr_state.pclip_top); + + /* Look at active lists and figure out how much to allocate + for each poly type */ + opb_total_size = 0; + + /* Previously, we specified 1 << 20 to say that the OPB grows "down" when + the TA needs more than one. To make it grow "up" instead (increasing addresses), + we set 0 as the default value. + */ +#if 0 + pvr_state.list_reg_mask = 1 << 20; +#else + pvr_state.list_reg_mask = 0; +#endif + + for(i = 0; i < PVR_OPB_COUNT; i++) { + pvr_state.opb_size[i] = WORDS_TO_BYTES(params->opb_sizes[i]); /* in bytes */ + + /* Calculate the total size of the OPBs for this list */ + opb_total_size += pvr_state.opb_size[i] * pvr_state.tw * pvr_state.th; + + switch(params->opb_sizes[i]) { + case PVR_BINSIZE_0: + sconst = 0; + break; + case PVR_BINSIZE_8: + sconst = 1; + break; + case PVR_BINSIZE_16: + sconst = 2; + break; + case PVR_BINSIZE_32: + sconst = 3; + break; + default: + assert_msg(0, "invalid poly_buf_size"); + sconst = 2; + break; + } + + if(sconst > 0) { + pvr_state.lists_enabled |= (1 << i); + pvr_state.list_reg_mask |= sconst << (4 * i); + } + } + + /* Initialize each buffer set */ + for(i = 0; i < 2; i++) { + /* Frame 0 goes at 0, Frame 1 goes at 0x400000 (half way) */ + if(i == 0) + outaddr = 0; + else + outaddr = 0x400000; + + /* Select a pvr_buffers_t. Note that there's no good reason + to allocate the frame buffers at the same time as the TA + buffers except that it's handy to do it all in one place. */ + buf = pvr_state.ta_buffers + i; + fbuf = pvr_state.frame_buffers + i; + + /* Vertex buffer */ + buf->vertex = outaddr; + buf->vertex_size = params->vertex_buf_size; + outaddr += buf->vertex_size; + /* N-byte align */ + outaddr = APPLY_ALIGNMENT(outaddr); + + /* Object Pointer Blocks */ + buf->opb = outaddr; + buf->opb_size = opb_total_size; + + /* Allocate extra space for overflow (when one OPB isn't big enough) */ + buf->opb_overflow_count = params->opb_overflow_count; + outaddr += opb_total_size * (1 + buf->opb_overflow_count); + + /* Set up the opb pointers to each section */ + opb_size_accum = 0; + for(j = 0; j < PVR_OPB_COUNT; j++) { + buf->opb_addresses[j] = buf->opb + opb_size_accum; + opb_size_accum += pvr_state.opb_size[j] * pvr_state.tw * pvr_state.th; + } + + assert(buf->opb_size == opb_size_accum); + + /* N-byte align */ + outaddr = APPLY_ALIGNMENT(outaddr); + + /* Tile Matrix */ + buf->tile_matrix = outaddr; + buf->tile_matrix_size = WORDS_TO_BYTES(18 + 6 * pvr_state.tw * pvr_state.th); + outaddr += buf->tile_matrix_size; + + /* N-byte align */ + outaddr = APPLY_ALIGNMENT(outaddr); + + /* Output buffer */ + fbuf->frame = outaddr; + fbuf->frame_size = pvr_state.w * pvr_state.h * 2; + outaddr += fbuf->frame_size; + + /* N-byte align */ + outaddr = APPLY_ALIGNMENT(outaddr); + } + + /* Texture ram is whatever is left */ + pvr_state.texture_base = (outaddr - 0x400000) * 2; + +#if 0 + dbglog(DBG_KDEBUG, "pvr: initialized PVR buffers:\n"); + dbglog(DBG_KDEBUG, " texture RAM begins at %08lx\n", pvr_state.texture_base); + + for(i = 0; i < 2; i++) { + buf = pvr_state.ta_buffers + i; + fbuf = pvr_state.frame_buffers + i; + dbglog(DBG_KDEBUG, " vertex/vertex_size: %08lx/%08lx\n", buf->vertex, buf->vertex_size); + dbglog(DBG_KDEBUG, " opb base/opb_size: %08lx/%08lx\n", buf->opb, buf->opb_size); + dbglog(DBG_KDEBUG, " opbs per type: %08lx %08lx %08lx %08lx %08lx\n", + buf->opb_type[0], + buf->opb_type[1], + buf->opb_type[2], + buf->opb_type[3], + buf->opb_type[4]); + dbglog(DBG_KDEBUG, " tile_matrix/tile_matrix_size: %08lx/%08lx\n", buf->tile_matrix, buf->tile_matrix_size); + dbglog(DBG_KDEBUG, " frame/frame_size: %08lx/%08lx\n", fbuf->frame, fbuf->frame_size); + } + + dbglog(DBG_KDEBUG, " list_mask %08lx\n", pvr_state.list_reg_mask); + dbglog(DBG_KDEBUG, " w/h = %d/%d, tw/th = %d/%d\n", pvr_state.w, pvr_state.h, + pvr_state.tw, pvr_state.th); + dbglog(DBG_KDEBUG, " zclip %08lx\n", *((uint32*)&pvr_state.zclip)); + dbglog(DBG_KDEBUG, " pclip_left/right %08lx/%08lx\n", pvr_state.pclip_left, pvr_state.pclip_right); + dbglog(DBG_KDEBUG, " pclip_top/bottom %08lx/%08lx\n", pvr_state.pclip_top, pvr_state.pclip_bottom); + dbglog(DBG_KDEBUG, " lists_enabled %08lx\n", pvr_state.lists_enabled); + dbglog(DBG_KDEBUG, "Free texture memory: %ld bytes\n", + 0x800000 - pvr_state.texture_base); +#endif /* !NDEBUG */ +} diff --git a/vendor/koshle/hlepvr_fog.cpp b/vendor/koshle/hlepvr_fog.cpp new file mode 100644 index 00000000..098d6eda --- /dev/null +++ b/vendor/koshle/hlepvr_fog.cpp @@ -0,0 +1,360 @@ +/* KallistiOS ##version## + + pvr_fog.c + (C)2002 Paul Boese + + */ + +#include +#include +#include "dc/pvr.h" +#include "pvr_internal.h" +#include "pvr_fog_tables.h" + +/* + + This module handles things related to the hardware fog capabilities of the + PVR chip in the DC. Thanks to Paul Boese for figuring this stuff out. + +*/ + +/* note: this is my interpretation of stuffing the fog table + * in an attempt to satisfy the GL specs for GL_FOG_EXP + * GL_FOG_EXP2, and GL_LINEAR. + * + * With regard to GL_EXP... + * + * Two values specified by the GL spec: d=density, and z=(0,0,0,1) + * use the formula f = exp(-(d*z)^2) where f is a value clamped + * to the range [0..1]. f is used to blend a fog color value with post + * textured pixels in the framebuffer. In this code f is multiplied by + * 255 so we can specify a range of values in each fog table entry from + * 0..255. Where 0 = no fog, and 255 = full opacity. There are 128 + * entries in the fog table each of which contains two 8 bit values. The + * second or low byte is setup to be the same as the first or high byte + * of the next entry (so that the fog density is continuous). + * + * sample fog table entries: + * + * address values + * 0xa05f8200 0x0000fffd first entry - farthest from eye + * 0xa05f8204 0x0000fdfa + * 0xa05f8208 0x0000faf6 + * 0xa05f82c0 0x0000f6f0 + * ... + * + * In Maiwe's docs there is register at 0xa05f80b8 called fog_density + * that takes a weird floating point value. From my experiments it seems + * to work more like a far depth value for the table where index 1 resides + * at the far depth and the last table index is at the frustum or eye. So it + * would seem to specify how deep the fog is from the eye to a specified far + * z distance. + * + * ----- + * According to Simon Fenney: + * + * There is a scale factor register which scales incoming per-pixel 1/w + * values. This value is then clamped to the range 1.0 (maximum distance fog) + * to 259.9999999 (minimum distance fg). + * + * (Remember that the with the original 1/w values, when 1/w == 1.0 USUALLY + * implies the foreground clip plane distance while 1/w==0 is at infinity. + * (Don't bother with far clipping, it's a waste of time and effort with a + * floating point 1/w buffer)) + * + * The fog table has 128 entries, each of which has two 8 bit fog values. Each + * entry represents a range of (scaled) 1/w (i.e. perspective depth) values. + * The second fog value of each pair is usually set up to be the same as the + * first of the next (so that the fog density is continuous). + * + * The (first?) entries at index "i" in the table + * represents the fog density at depth of: + * + * InverseW_Depth(i) = (pow(2.0, i>>4) * ((i&0xf) + 16)/16.0f)/FogScaleRegister + * + * The values you place in the table to assign fog densities is up to you + * but the usual way is to use a power function like: + * (1.0 - pow(depth, SMALL_VALUE)) + * ------ + * + * It appears that the FogScaleRegister is the same one referred to as + * fog_density by the Maiwe docs. So what's being said here is that each + * table value affects a range of 1/w depth. The FogScaleRegister controls + * the maximum depth and the size of each 1/w depth segment. + * + * The density value d is easy to deal with, but the eye distance to the + * center of a fragment z, where z=(0,0,0,1) in eye coords is a problem. The + * z values are ones you get after applying the projection and modelview + * matrix to the vertices, bot how do you relate those values to the 128 entries + * in the fog table? + * + * After some experiments with the InverseW_Depth function above and several + * days trying to load the fog table with values that correspond to the the + * OpenGL fog types of GL_EXP, GL_EXP2 and GL_LINEAR a way to produce + * perpectively correct fog table has been found. + * + * Two tables have been created that are used to scale fog z values so that + * we get perspectively correct looking fog. One table can be used directly + * with the linear fog table function. The other table uses the same values + * as the first only with each value multiplied by 259.999999. That value + * happens to correspond with the minimum fog distance. That table is used + * by the EXP and EXP2 fog table functions. + * + * For the EXP and EXP2 fog functions the FogScaleRegister gets set with + * the value of -259.999999. + * + * For LINEAR fog the FogScaleRegister is set the 'end' value. Where 'start' + * and 'end' specify in world coordinates where the fog starts and ends. + * + * Please don't take any of this as the gospel truth, or final word on table + * fog. This module seems to work, but any corrections and insights that + * would make it better would be appreciated. + * + * Special thanks goes to Simon Fenney for his explanation in the preceding + * text and to Brian Paul (Mesa3D) for his fast negative exp function. + * + * Pb + * --- + * + * Notes: + * A word about the fake alpha values fog_table_alpha. + * In my experiments with table fog I was could not seem to set an alpha + * value in the fog table color register. Therefore I created a fake one + * that effectively does the same thing as alpha. We simple multiply + * the values to be loaded into the fog table registers by the alpha + * value to get the desired effect. + * + * You should always specify the fog color before calling one of the + * three fog table functions. This insures that the alpha value is set. + * + * This module does NOT deal with VERTEX fog or TABLE COLOR (palette look + * up?) fog. + * + * You should only call these functions outside of the pvr_scene_begin, + * pvr_scene_finish. If you call to change fog parameters while the pvr + * is rendering the scene you will get artifacts in the image. + */ + +/* internal vars and stuff */ +static float fog_alpha; + +union ieee32_t { + float flt32; + uint32 uit32; +}; + +/* useful macros */ +#define ABS(n) ( (n)<(0.0f) ? (-n):(n) ) +#define NEG(n) ( (n)>(0.0f) ? (-n):(n) ) +#define CLAMP01(x) \ + ( (x)<(0.0f) ? ((x) = (0.0f)) : ((x)>(1.0f) ? ((x)=(1.0f)) : (x)) ) + +/* helper functions */ +#define FOG_EXP_TABLE_SIZE 256 +#define FOG_MAX (10.0f) +#define EXP_FOG_MAX .0006595f +#define FOG_INCR (FOG_MAX/FOG_EXP_TABLE_SIZE) + +/* A fast negative argument only exp function - That is it treats any + * argument as a negative number + */ +float neg_exp(float arg) { + + float result, f; + int k; + + f = (float)(ABS(arg) * (1.0f / FOG_INCR)); + k = (int) f; + + if(k > FOG_EXP_TABLE_SIZE - 2) + result = (float) EXP_FOG_MAX; + else + result = exp_table[k] + (f - k) * (exp_table[k + 1] - exp_table[k]); + + return result; +} + +/* This function converts an IEEE 754 32-bit floating point number to + 16-bit normalized floating point number and returns it in the + lower 16 bits of the 32 bit result. + + 1111111111222222222233 + 0 12345678 90123456789012345678901 + |-|--------|-----------------------| + |s|exponent|mantissa | IEEE 32-bit float + Bits |1| 8 | 23 | + |-|--------|-----------------------| + + x = sign * (1).mantissa * (2 ** (exponent+127)) + + 111111 + 0 1234567 89012345 + |-|-------|--------| + |1|mant. |exponent| 16bit normalized float + Bits |s| 7 | 8 | + |-|-------|--------| + + x = sign * (1).mantissa * (2 ** exponent) + + Notes: + The bias (exponent+127) used in the IEEE 32-bit float is subtracted + before we repack the bits. + + The mantissa is truncated, but it might be better to round up the + value instead. + + The special values for infinity and NaN are not dealt with at all. + + */ +uint32 float16(float f) { + union ieee32_t float32; + uint32 float16; + uint32 sign, exponent, mantissa; + + float32.flt32 = f; + sign = exponent = mantissa = float32.uit32; + + sign = (sign >> 16) & 0x8000; + exponent = (((exponent >> 23) & 0xff) - 127) & 0xff; + mantissa = (mantissa >> 8) & 0x7f00; + float16 = sign | mantissa | exponent; + return float16; +} + +/* Set the fog table color */ +void pvr_fog_table_color(float a, float r, float g, float b) { + PVR_SET(PVR_FOG_TABLE_COLOR, PVR_PACK_COLOR(a, r, g, b)); + fog_alpha = a; +} + +/* Set the fog vertex color */ +void pvr_fog_vertex_color(float a, float r, float g, float b) { + (void)a; + (void)r; + (void)g; + (void)b; + //assert_msg(0, "not implemented"); + /* PVR_SET(PVR_FOG_VERTEX_COLOR, PVR_PACK_COLOR(a, r, g, b)); */ +} + +/* Set the fog far depth */ +void pvr_fog_far_depth(float d) { + PVR_SET(PVR_FOG_DENSITY, float16(NEG(d))); +} + +#define TABLE_LEN (PVR_FOG_TABLE_BASE + 0x200) + +/* Initialize the fog table using an exp2 algorithm (like GL_EXP2) */ +void pvr_fog_table_exp2(float density) { + uint32 idx, i; + uint32 value, valh, vall; + float z = 259.999999f; + float d2 = density * density; + + pvr_fog_far_depth(259.999999f); + + valh = (uint32)(fog_alpha * 255.0f * (1.0f - neg_exp(-(z * z * d2)))) & 0xff; + + for(idx = PVR_FOG_TABLE_BASE, i = 0; idx < TABLE_LEN; idx += 4, i++) { + z = inverse_w_depth260[i]; + vall = (uint32)(fog_alpha * 255.0f * (1.0f - neg_exp(-(z * z * d2)))) & 0xff; + value = (((valh) << 8 & 0xff00) + vall); + PVR_SET(idx, value); + valh = vall; + } +} + +/* Initialize the fog table using an exp algorithm (like GL_EXP) */ +void pvr_fog_table_exp(float density) { + uint32 idx, i; + uint32 value, valh, vall; + float z = 259.999999f; + + pvr_fog_far_depth(259.999999f); + + valh = (uint32)(fog_alpha * 255.0f * (1.0f - neg_exp(-(z * density)))) & 0xff; + + for(idx = PVR_FOG_TABLE_BASE, i = 0; idx < TABLE_LEN; idx += 4, i++) { + z = inverse_w_depth260[i]; + vall = (uint32)(fog_alpha * 255.0f * (1.0f - neg_exp(-(z * density)))) & 0xff; + value = (((valh) << 8 & 0xff00) + vall); + PVR_SET(idx, value); + valh = vall; + } +} + +/* Initialize the fog table using a linear algorithm (like GL_LINEAR) + + Formula for GL_FOG_LINEAR + + end - z f is clamped [0,1] + f = ----------- z eye distance to center of fragment + end - start + + Truly linear entries in the fog table looks too heavy so we use + a pre-calculated power table to get the z values and make a nice + perspectively correct linear fog. +*/ + +void pvr_fog_table_linear(float start, float end) { + uint32 idx, i, tdx; + uint32 value, valh, vall; + uint32 table_start, non_zero_entries, step_size; + + start = ABS(start); + end = ABS(end); + + if(start >= end) { + //dbglog(DBG_DEBUG, "pvr_fog_table_linear: start >= end\n"); + return; + } + + /* ratio of start/end times number of fog table entries */ + table_start = (uint32)((start / end) * 128.0f); + /* number of entries to load with linear fog values */ + non_zero_entries = 128 - table_start; + /* size of steps to use as we walk the inverse_w_depth table */ + step_size = 128 / non_zero_entries; + + pvr_fog_far_depth(end); + + valh = (uint32)(255.0f * fog_alpha); /* f=1/1 full occlusion */ + + for(idx = PVR_FOG_TABLE_BASE, i = 0, tdx = 127; idx < TABLE_LEN; idx += 4, i += step_size, tdx--) { + if(tdx >= table_start) { + vall = (uint32)(inverse_w_depth[i] * 255.0f * fog_alpha); + } + else { + vall = 0x0; + } + + value = (((valh) << 8 & 0xff00) + vall); + PVR_SET(idx, value); + valh = vall; + } +} + +/* Set a custom fog table from float values + * + * Expects a table of floats with >>>129<<< entries with values + * clamped 0..1. It is the responsibility of the programmer to call + * pvr_fog_far_depth as part of proper fog table initialization. + * + * 0th entry is farthest from eye. Last entry is nearest to eye. + * The larger the value the heavier the fog. + */ +void pvr_fog_table_custom(float tbl1[]) { + uint32 idx, i; + uint32 value, valh, vall; + + valh = (uint32)(fog_alpha * 255.0f * CLAMP01(tbl1[0])) & 0xff; + + for(idx = PVR_FOG_TABLE_BASE, i = 1; idx < TABLE_LEN; idx += 4, i++) { + vall = (uint32)(fog_alpha * 255.0f * CLAMP01(tbl1[i])) & 0xff; + value = (((valh) << 8 & 0xff00) + vall); + PVR_SET(idx, value); + valh = vall; + } +} + + diff --git a/vendor/koshle/hlepvr_init_term.cpp b/vendor/koshle/hlepvr_init_term.cpp new file mode 100644 index 00000000..d82508c6 --- /dev/null +++ b/vendor/koshle/hlepvr_init_term.cpp @@ -0,0 +1,278 @@ +/* KallistiOS ##version## + + pvr_init_shutdown.c + Copyright (C) 2002, 2004 Megan Potter + + */ + +#include "emu/emu.h" + +#include +#include +#include +#include "dc/pvr.h" +#define dbglog(channel, ...) printf(__VA_ARGS__) + +// #include +// #include +// #include +#include "pvr_internal.h" + +/* + + Initialization and shutdown: stuff you should only ever have to do + once in your program. + +*/ + +/* Simpler function which initializes the PVR using 16/16 for the opaque + and translucent lists, and 0's for everything else; 512k of vertex + buffer. This is equivalent to the old ta_init_defaults() for now. */ +int pvr_init_defaults(void) { + pvr_init_params_t params = { + /* Enable opaque and translucent polygons with size 16 */ + { PVR_BINSIZE_16, PVR_BINSIZE_0, PVR_BINSIZE_16, PVR_BINSIZE_0, PVR_BINSIZE_0 }, + + /* Vertex buffer size 512K */ + 512 * 1024, + + /* No DMA */ + 0, + + /* No FSAA */ + 0, + + /* Translucent Autosort enabled. */ + 0, + + /* Extra OPBs */ + 3 + }; + + return pvr_init(¶ms); +} + +/* Initialize the PVR chip to ready status, enabling the specified lists + and using the specified parameters; note that bins and vertex buffers + come from the texture memory pool! Expects that a 2D mode was + initialized already using the vid_* API. */ +int pvr_init(pvr_init_params_t *params) { + emu_init(); + /* If we're already initialized, fail */ + if(pvr_state.valid == 1) { + dbglog(DBG_WARNING, "pvr: pvr_init called twice!\n"); + return -1; + } + + /* Make sure we got valid parameters */ + assert(params != NULL); + + // /* Make sure that a video mode has been initialized */ + // assert(vid_mode != NULL); + // assert(vid_mode->width != 0 && vid_mode->height != 0); + + // /* Check for compatibility with 3D stuff */ + // if((vid_mode->width % 32) != 0) { + // dbglog(DBG_WARNING, "pvr: mode %dx%d isn't usable for 3D (width not multiples of 32)\n", + // vid_mode->width, vid_mode->height); + // return -1; + // } + + /* Clear out video memory */ + // vid_empty(); + memset(emu_vram, 0, sizeof(emu_vram)); + + /* Reset all PVR systems (in case it's still doing something) */ + PVR_SET(PVR_RESET, PVR_RESET_ALL); + PVR_SET(PVR_RESET, PVR_RESET_NONE); + + /* Start off with a nice empty structure */ + memset((void *)&pvr_state, 0, sizeof(pvr_state)); + + // Enable DMA if the user wants that. + pvr_state.dma_mode = params->dma_enabled; + + // Copy over FSAA setting. + pvr_state.fsaa = params->fsaa_enabled; + + /* Everything's clear, do the initial buffer pointer setup */ + pvr_allocate_buffers(params); + + // Initialize tile matrices + pvr_init_tile_matrices(!!params->autosort_disabled); + + // Setup all pipeline targets. Yes, this is redundant. :) I just + // like to have it explicit. + pvr_state.ram_target = 0; + pvr_state.ta_target = 0; + pvr_state.view_target = 0; + + pvr_state.list_reg_open = -1; + + // Sync all the hardware registers with our pipeline state. + pvr_sync_view(); + pvr_sync_reg_buffer(); + + // Clear out our stats + pvr_state.vbl_count = 0; + pvr_state.frame_last_time = 0; + pvr_state.buf_start_time = 0; + pvr_state.reg_start_time = 0; + pvr_state.rnd_start_time = 0; + pvr_state.frame_last_len = -1; + pvr_state.buf_last_len = -1; + pvr_state.reg_last_len = -1; + pvr_state.rnd_last_len = -1; + pvr_state.vtx_buf_used = 0; + pvr_state.vtx_buf_used_max = 0; + pvr_state.dr_used = 0; + + /* If we're on a VGA box, disable vertical smoothing */ + if(1 /*vid_mode->cable_type == CT_VGA*/) { + #if !defined(DC_TEXCONV) + dbglog(DBG_KDEBUG, "pvr: disabling vertical scaling for VGA\n"); + #endif + + if(pvr_state.fsaa) + PVR_SET(PVR_SCALER_CFG, 0x10400); + else + PVR_SET(PVR_SCALER_CFG, 0x400); + + //TODO: these are some vid_ function? + PVR_SET(PVR_FB_SIZE, ((640-1)/2 << 0) | ((480-1) << 10) | (1 << 20 ) ); + PVR_SET(PVR_FB_CFG_1, (1<<0) | (0<<1) | (1 << 2)); + // PVR_SET(PVR_FB_ADDR); + } + else { + dbglog(DBG_KDEBUG, "pvr: enabling vertical scaling for non-VGA\n"); + + if(pvr_state.fsaa) + PVR_SET(PVR_SCALER_CFG, 0x10401); + else + PVR_SET(PVR_SCALER_CFG, 0x401); + } + + // TODO: hookup reicast's interrupts here + // /* Hook the PVR interrupt events on G2 */ + // pvr_state.vbl_handle = vblank_handler_add(pvr_int_handler, NULL); + + // asic_evt_set_handler(ASIC_EVT_PVR_OPAQUEDONE, pvr_int_handler, NULL); + // asic_evt_enable(ASIC_EVT_PVR_OPAQUEDONE, ASIC_IRQ_DEFAULT); + // asic_evt_set_handler(ASIC_EVT_PVR_OPAQUEMODDONE, pvr_int_handler, NULL); + // asic_evt_enable(ASIC_EVT_PVR_OPAQUEMODDONE, ASIC_IRQ_DEFAULT); + // asic_evt_set_handler(ASIC_EVT_PVR_TRANSDONE, pvr_int_handler, NULL); + // asic_evt_enable(ASIC_EVT_PVR_TRANSDONE, ASIC_IRQ_DEFAULT); + // asic_evt_set_handler(ASIC_EVT_PVR_TRANSMODDONE, pvr_int_handler, NULL); + // asic_evt_enable(ASIC_EVT_PVR_TRANSMODDONE, ASIC_IRQ_DEFAULT); + // asic_evt_set_handler(ASIC_EVT_PVR_PTDONE, pvr_int_handler, NULL); + // asic_evt_enable(ASIC_EVT_PVR_PTDONE, ASIC_IRQ_DEFAULT); + // asic_evt_set_handler(ASIC_EVT_PVR_RENDERDONE_TSP, pvr_int_handler, NULL); + // asic_evt_enable(ASIC_EVT_PVR_RENDERDONE_TSP, ASIC_IRQ_DEFAULT); + +#ifdef PVR_RENDER_DBG + /* Hook up interrupt handlers for error events */ + asic_evt_set_handler(ASIC_EVT_PVR_ISP_OUTOFMEM, pvr_int_handler, NULL); + asic_evt_enable(ASIC_EVT_PVR_ISP_OUTOFMEM, ASIC_IRQ_DEFAULT); + asic_evt_set_handler(ASIC_EVT_PVR_STRIP_HALT, pvr_int_handler, NULL); + asic_evt_enable(ASIC_EVT_PVR_STRIP_HALT, ASIC_IRQ_DEFAULT); + asic_evt_set_handler(ASIC_EVT_PVR_OPB_OUTOFMEM, pvr_int_handler, NULL); + asic_evt_enable(ASIC_EVT_PVR_OPB_OUTOFMEM, ASIC_IRQ_DEFAULT); + asic_evt_set_handler(ASIC_EVT_PVR_TA_INPUT_ERR, pvr_int_handler, NULL); + asic_evt_enable(ASIC_EVT_PVR_TA_INPUT_ERR, ASIC_IRQ_DEFAULT); + asic_evt_set_handler(ASIC_EVT_PVR_TA_INPUT_OVERFLOW, pvr_int_handler, NULL); + asic_evt_enable(ASIC_EVT_PVR_TA_INPUT_OVERFLOW, ASIC_IRQ_DEFAULT); +#endif + + /* 3d-specific parameters; these are all about rendering and + nothing to do with setting up the video; some stuff in here + is still unknown. */ + PVR_SET(PVR_UNK_00A8, 0x15d1c951); /* M (Unknown magic value) */ + PVR_SET(PVR_UNK_00A0, 0x00000020); /* M */ + PVR_SET(PVR_FB_CFG_2, 0x00000009); /* alpha config */ + PVR_SET(PVR_UNK_0110, 0x00093f39); /* M */ + PVR_SET(PVR_UNK_0098, 0x00800408 | (params->autosort_disabled?1:0)); /* M */ // this is a hack for refsw limitations + PVR_SET(PVR_TEXTURE_CLIP, 0x00000000); /* texture clip distance */ + PVR_SET(PVR_SPANSORT_CFG, 0x00000101); /* M */ + PVR_SET(PVR_FOG_TABLE_COLOR, 0x007f7f7f); /* Fog table color */ + PVR_SET(PVR_FOG_VERTEX_COLOR, 0x007f7f7f); /* Fog vertex color */ + PVR_SET(PVR_COLOR_CLAMP_MIN, 0x00000000); /* color clamp min */ + PVR_SET(PVR_COLOR_CLAMP_MAX, 0xffffffff); /* color clamp max */ + PVR_SET(PVR_UNK_0080, 0x00000007); /* M */ + PVR_SET(PVR_CHEAP_SHADOW, 0x00000001); /* cheap shadow */ + PVR_SET(PVR_UNK_007C, 0x0027df77); /* M */ + PVR_SET(PVR_TEXTURE_MODULO, 0x00000000); /* stride width */ + PVR_SET(PVR_FOG_DENSITY, 0x0000ff07); /* fog density */ + PVR_SET(PVR_UNK_0118, 0x00008040); /* M */ + + /* Initialize PVR DMA */ + // mutex_init((mutex_t *)&pvr_state.dma_lock, MUTEX_TYPE_NORMAL); + pvr_dma_init(); + + /* Setup our wait-ready semaphore */ + sem_init((semaphore_t *)&pvr_state.ready_sem, 1); + // TODO: This has been hacked to be 1 from 0 in kos + + /* Set us as valid and return success */ + pvr_state.valid = 1; + + /* Validate our memory pool */ + pvr_mem_reset(); + /* This doesn't work right now... */ + /*#ifndef NDEBUG + dbglog(DBG_KDEBUG, "pvr: free memory is %08lx bytes\n", + pvr_mem_available()); + #endif*//* !NDEBUG */ + + return 0; +} + +/* Shut down the PVR chip from ready status, leaving it in 2D mode as it + was before the init. */ +int pvr_shutdown(void) { + if(!pvr_state.valid) + return -1; + + /* Set us invalid */ + pvr_state.valid = 0; + + /* Stop anything that might be going on */ + PVR_SET(PVR_RESET, PVR_RESET_ALL); + PVR_SET(PVR_RESET, PVR_RESET_NONE); + + /* Unhook any int handlers */ + // vblank_handler_remove(pvr_state.vbl_handle); + // asic_evt_remove_handler(ASIC_EVT_PVR_OPAQUEDONE); + // asic_evt_disable(ASIC_EVT_PVR_OPAQUEDONE, ASIC_IRQ_DEFAULT); + // asic_evt_remove_handler(ASIC_EVT_PVR_OPAQUEMODDONE); + // asic_evt_disable(ASIC_EVT_PVR_OPAQUEMODDONE, ASIC_IRQ_DEFAULT); + // asic_evt_remove_handler(ASIC_EVT_PVR_TRANSDONE); + // asic_evt_disable(ASIC_EVT_PVR_TRANSDONE, ASIC_IRQ_DEFAULT); + // asic_evt_remove_handler(ASIC_EVT_PVR_TRANSMODDONE); + // asic_evt_disable(ASIC_EVT_PVR_TRANSMODDONE, ASIC_IRQ_DEFAULT); + // asic_evt_remove_handler(ASIC_EVT_PVR_PTDONE); + // asic_evt_disable(ASIC_EVT_PVR_PTDONE, ASIC_IRQ_DEFAULT); + // asic_evt_remove_handler(ASIC_EVT_PVR_RENDERDONE_TSP); + // asic_evt_disable(ASIC_EVT_PVR_RENDERDONE_TSP, ASIC_IRQ_DEFAULT); + + /* Shut down PVR DMA */ + pvr_dma_shutdown(); + + /* Invalidate our memory pool */ + pvr_mem_reset(); + + /* Destroy the semaphore */ + sem_destroy((semaphore_t *)&pvr_state.ready_sem); + // mutex_destroy((mutex_t *)&pvr_state.dma_lock); + + // /* Clear video memory */ + // vid_empty(); + memset(emu_vram, 0, sizeof(emu_vram)); + + // /* Reset the frame buffer offset */ + // vid_waitvbl(); + // vid_set_start(0); + + emu_term(); + /* Return success */ + return 0; +} diff --git a/vendor/koshle/hlepvr_irq.cpp b/vendor/koshle/hlepvr_irq.cpp new file mode 100644 index 00000000..491b1174 --- /dev/null +++ b/vendor/koshle/hlepvr_irq.cpp @@ -0,0 +1,244 @@ +/* KallistiOS ##version## + + pvr_irq.c + Copyright (C)2002,2004 Megan Potter + + */ + +#include +#include +#include +// #include +#include "pvr_internal.h" + +#ifdef PVR_RENDER_DBG +#include +#endif + +/* + PVR interrupt handler; the way things are setup, we're gonna get + one of these for each full vertical refresh and at the completion + of TA data acceptance. The timing here is pretty critical. We need + to flip pages during a vertical blank, and then signal to the program + that it's ok to start playing with TA registers again, or we waste + rendering time. +*/ + +#if 0 +// Find the next list to DMA out. If we have none left to do, then do +// nothing. Otherwise, start the DMA and chain back to us upon completion. +static void dma_next_list(void *data) { + int i, did = 0; + volatile pvr_dma_buffers_t * b; + + (void)data; + + // DBG(("dma_next_list\n")); + + for(i = 0; i < PVR_OPB_COUNT; i++) { + if((pvr_state.lists_enabled & (1 << i)) + && !(pvr_state.lists_dmaed & (1 << i))) { + // Get the buffers for this frame. + b = pvr_state.dma_buffers + (pvr_state.ram_target ^ 1); + + // Flush the last 32 bytes out of dcache, just in case. + // dcache_flush_range((ptr_t)(b->base[i] + b->ptr[i] - 32), 32); + dcache_flush_range((ptr_t)(b->base[i]), b->ptr[i] + 32); + //amt = b->ptr[i] > 16384 ? 16384 : b->ptr[i]; + //dcache_flush_range((ptr_t)(b->base[i] + b->ptr[i] - amt), amt); + + // Start the DMA transfer, chaining to ourselves. + //DBG(("dma_begin(buf %d, list %d, base %p, len %d)\n", + // pvr_state.ram_target ^ 1, i, + // b->base[i], b->ptr[i])); + pvr_dma_load_ta(b->base[i], b->ptr[i], 0, dma_next_list, 0); + + // Mark this list as done, and break out for now. + pvr_state.lists_dmaed |= 1 << i; + did++; + + break; + } + } + + // If that was the last one, then free up the DMA channel. + if(!did) { + //DBG(("dma_complete(buf %d)\n", pvr_state.ram_target ^ 1)); + + // Unlock + mutex_unlock((mutex_t *)&pvr_state.dma_lock); + pvr_state.lists_dmaed = 0; + + // Buffers are now empty again + pvr_state.dma_buffers[pvr_state.ram_target ^ 1].ready = 0; + + // Signal the client code to continue onwards. + sem_signal((semaphore_t *)&pvr_state.ready_sem); + thd_schedule(1, 0); + } +} + +#endif +void pvr_int_handler(uint32 code, void *data) { + int bufn = pvr_state.view_target; + + (void)data; + + // What kind of event did we get? + switch(code) { + case ASIC_EVT_PVR_OPAQUEDONE: + //DBG(("irq_opaquedone\n")); + pvr_state.lists_transferred |= 1 << PVR_OPB_OP; + break; + case ASIC_EVT_PVR_TRANSDONE: + //DBG(("irq_transdone\n")); + pvr_state.lists_transferred |= 1 << PVR_OPB_TP; + break; + case ASIC_EVT_PVR_OPAQUEMODDONE: + pvr_state.lists_transferred |= 1 << PVR_OPB_OM; + break; + case ASIC_EVT_PVR_TRANSMODDONE: + pvr_state.lists_transferred |= 1 << PVR_OPB_TM; + break; + case ASIC_EVT_PVR_PTDONE: + pvr_state.lists_transferred |= 1 << PVR_OPB_PT; + break; + case ASIC_EVT_PVR_RENDERDONE_TSP: + //DBG(("irq_renderdone\n")); + pvr_state.render_busy = 0; + pvr_state.render_completed = 1; + pvr_sync_stats(PVR_SYNC_RNDDONE); + break; + case ASIC_EVT_PVR_VBLANK_BEGIN: + pvr_sync_stats(PVR_SYNC_VBLANK); + break; + } + +#ifdef PVR_RENDER_DBG + /* Show register values on each interrupt */ + switch (code) { + case ASIC_EVT_PVR_ISP_OUTOFMEM: + DBG(("[ERROR]: ASIC_EVT_PVR_ISP_OUTOFMEM\n")); + break; + + case ASIC_EVT_PVR_STRIP_HALT: + DBG(("[ERROR]: ASIC_EVT_PVR_STRIP_HALT\n")); + break; + + case ASIC_EVT_PVR_OPB_OUTOFMEM: + DBG(("[ERROR]: ASIC_EVT_PVR_OPB_OUTOFMEM\n")); + DBG(("PVR_TA_OPB_START: %08lx\nPVR_TA_OPB_END: %08lx\nPVR_TA_OPB_POS: %08lx\n", + PVR_GET(PVR_TA_OPB_START), + PVR_GET(PVR_TA_OPB_END), + PVR_GET(PVR_TA_OPB_POS) << 2)); + break; + + case ASIC_EVT_PVR_TA_INPUT_ERR: + DBG(("[ERROR]: ASIC_EVT_PVR_TA_INPUT_ERR\n")); + break; + + case ASIC_EVT_PVR_TA_INPUT_OVERFLOW: + DBG(("[ERROR]: ASIC_EVT_PVR_TA_INPUT_OVERFLOW\n")); + break; + } +#endif + + /* Update our stats if we finished all registration */ + switch(code) { + case ASIC_EVT_PVR_OPAQUEDONE: + case ASIC_EVT_PVR_TRANSDONE: + case ASIC_EVT_PVR_OPAQUEMODDONE: + case ASIC_EVT_PVR_TRANSMODDONE: + case ASIC_EVT_PVR_PTDONE: + + if(pvr_state.lists_transferred == pvr_state.lists_enabled) { + pvr_sync_stats(PVR_SYNC_REGDONE); + } + + return; + } + + if(!pvr_state.to_texture[bufn]) { + // If it's not a vblank, ignore the rest of this for now. + if(code != ASIC_EVT_PVR_VBLANK_BEGIN) + return; + } + else { + // We don't need to wait for a vblank for rendering to a texture, but + // we really don't care about anything else unless we've actually gotten + // all the data submitted to the TA. + if(pvr_state.lists_transferred != pvr_state.lists_enabled && + !pvr_state.render_completed) + return; + } + + // If the render-done interrupt has fired then we are ready to flip to the + // new frame buffer. + if(pvr_state.render_completed) { + //DBG(("view(%d)\n", pvr_state.view_target ^ 1)); + + // Handle PVR stats + pvr_sync_stats(PVR_SYNC_PAGEFLIP); + + // Switch view address to the "good" buffer + pvr_state.view_target ^= 1; + + if(!pvr_state.to_texture[bufn]) + pvr_sync_view(); + + // Clear the render completed flag. + pvr_state.render_completed = 0; + } + + // If all lists are fully transferred and a render is not in progress, + // we are ready to start rendering. + if(!pvr_state.render_busy + && pvr_state.lists_transferred == pvr_state.lists_enabled) { + /* XXX Note: + For some reason, the render must be started _before_ we sync + to the new reg buffers. The only reasons I can think of for this + are that there may be something in the reg sync that messes up + the render in progress, or we are misusing some bits somewhere. */ + + // Begin rendering from the dirty TA buffer into the clean + // frame buffer. + //DBG(("start_render(%d -> %d)\n", pvr_state.ta_target, pvr_state.view_target ^ 1)); + pvr_state.ta_target ^= 1; + pvr_begin_queued_render(); + pvr_state.render_busy = 1; + pvr_sync_stats(PVR_SYNC_RNDSTART); + + // Clear the texture render flag if we had it set. + pvr_state.to_texture[bufn] = 0; + + // If we're not in DMA mode, then signal the client code + // to continue onwards. + if(!pvr_state.dma_mode) { + sem_signal((semaphore_t *)&pvr_state.ready_sem); + // thd_schedule(1, 0); + // What did that do? give up the thread? + } + + // Switch to the clean TA buffer. + pvr_state.lists_transferred = 0; + pvr_sync_reg_buffer(); + + // The TA is no longer busy. + pvr_state.ta_busy = 0; + } + + assert(!pvr_state.dma_mode); + // // If we're in DMA mode, the DMA source buffers are ready, and a DMA + // // is not in progress, then we are ready to start DMAing. + // if(pvr_state.dma_mode + // && !pvr_state.ta_busy + // && pvr_state.dma_buffers[pvr_state.ram_target ^ 1].ready + // && mutex_trylock((mutex_t *)&pvr_state.dma_lock) >= 0) { + // pvr_sync_stats(PVR_SYNC_REGSTART); + + // // Begin DMAing the first list. + // pvr_state.ta_busy = 1; + // // dma_next_list(0); + // assert(false && "dma mode has been hacked off"); + // } +} diff --git a/vendor/koshle/hlepvr_mem.cpp b/vendor/koshle/hlepvr_mem.cpp new file mode 100644 index 00000000..ddf1fe83 --- /dev/null +++ b/vendor/koshle/hlepvr_mem.cpp @@ -0,0 +1,229 @@ +/* KallistiOS ##version## + + pvr_mem.c + Copyright (C) 2002 Megan Potter + + */ + +#if 0 +#include +#include "dc/pvr.h" +#include "pvr_internal.h" +#include + +#if !defined(DC_TEXCONV) && !defined(MACOS64) && !defined(_WIN32) +#include /* For the struct mallinfo defs */ +#else +struct mallinfo { + int arena; /* non-mmapped space allocated from system */ + int ordblks; /* number of free chunks */ + int smblks; /* number of fastbin blocks */ + int hblks; /* number of mmapped regions */ + int hblkhd; /* space in mmapped regions */ + int usmblks; /* maximum total allocated space */ + int fsmblks; /* space available in freed fastbin blocks */ + int uordblks; /* total allocated space */ + int fordblks; /* total free space */ + int keepcost; /* top-most, releasable (via malloc_trim) space */ +}; +#endif + +#include + +#define assert_msg(cond, txt) assert(cond && txt) + +// #include + +/* + +This module basically serves as a KOS-friendly front end and support routines +for the pvr_mem_core module, which is a dlmalloc-derived malloc for use with +the PVR memory pool. + +I was originally going to make a totally separate thing that could be used +to generically manage any memory pool, but then I realized what a gruelling +and thankless task that would be when starting with dlmalloc, so we have this +instead. ^_^; + +*/ + +/* Bring in some prototypes from pvr_mem_core.c */ +/* We can't directly include its header because of name clashes with + the real malloc header */ +extern void * pvr_int_malloc(size_t bytes); +extern void pvr_int_free(void *ptr); +extern struct mallinfo pvr_int_mallinfo(); +extern void pvr_int_mem_reset(); +extern void pvr_int_malloc_stats(); + + +#ifdef PVR_KM_DBG + +#include +#include + +/* List of allocated memory blocks for leak checking */ +typedef struct memctl { + uint32 size; + tid_t thread; + uint32 addr; + pvr_ptr_t block; + LIST_ENTRY(memctl) list; +} memctl_t; + +static LIST_HEAD(memctl_list, memctl) block_list; + +#endif /* PVR_KM_DBG */ + + +/* PVR RAM base; NULL is considered invalid */ +static pvr_ptr_t pvr_mem_base = NULL; +#define CHECK_MEM_BASE \ + assert_msg(pvr_mem_base != NULL, \ + "pvr_mem_* used, but PVR hasn't been initialized yet") + +/* Used in pvr_mem_core.c */ +void * pvr_int_sbrk(size_t amt) { + ptr_t old, n; + + /* Are we valid? */ + CHECK_MEM_BASE; + + /* Try to increment it */ + old = (ptr_t)pvr_mem_base; + n = old + amt; + + /* Did we run over? */ + if(n > PVR_RAM_INT_TOP) + return (void *) - 1; + + /* Nope, everything's cool */ + pvr_mem_base = (pvr_ptr_t)n; + return (pvr_ptr_t)old; +} + +/* Allocate a chunk of memory from texture space; the returned value + will be relative to the base of texture memory (zero-based) */ +pvr_ptr_t pvr_mem_malloc(size_t size) { + ptr_t rv32; +#ifdef PVR_KM_DBG + uint32 ra = arch_get_ret_addr(); + memctl_t * ctl; +#endif /* PVR_KM_DBG */ + + CHECK_MEM_BASE; + + rv32 = (ptr_t)pvr_int_malloc(size); + assert_msg((rv32 & 0x1f) == 0, + "dlmalloc's alignment is broken; " + "please make a bug report"); + +#ifdef PVR_KM_DBG + ctl = malloc(sizeof(memctl_t)); + ctl->size = size; + ctl->thread = thd_current->tid; + ctl->addr = ra; + ctl->block = (pvr_ptr_t)rv32; + LIST_INSERT_HEAD(&block_list, ctl, list); +#endif /* PVR_KM_DBG */ + +#ifdef PVR_KM_DBG_VERBOSE + printf("Thread %d/%08lx allocated %lu bytes at %08lx\n", + ctl->thread, ctl->addr, ctl->size, rv32); +#endif + + return (pvr_ptr_t)rv32; +} + +/* Free a previously allocated chunk of memory */ +void pvr_mem_free(pvr_ptr_t chunk) { +#ifdef PVR_KM_DBG + uint32 ra = arch_get_ret_addr(); + memctl_t *ctl, *tmp; + int found; +#endif /* PVR_KM_DBG */ + + CHECK_MEM_BASE; + +#ifdef PVR_KM_DBG_VERBOSE + printf("Thread %d/%08lx freeing block @ %08lx\n", + thd_current->tid, ra, (uint32)chunk); +#endif + +#ifdef PVR_KM_DBG + found = 0; + + LIST_FOREACH_SAFE(ctl, &block_list, list, tmp) { + if(ctl->block == chunk) { + LIST_REMOVE(ctl, list); + free(ctl); + found = 1; + break; + } + } + + if(!found) { + dbglog(DBG_ERROR, + "pvr_mem_free: trying to free non-alloc'd block " + "%08lx (called from %d/%08lx\n", + (uint32)chunk, thd_current->tid, ra); + } + +#endif /* PVR_KM_DBG */ + + pvr_int_free((void *)chunk); +} + +/* Check the memory block list to see what's allocated */ +void pvr_mem_print_list(void) { +#ifdef PVR_KM_DBG + memctl_t * ctl; + + printf("pvr_mem_print_list block list:\n"); + LIST_FOREACH(ctl, &block_list, list) { + printf(" unfreed block at %08lx size %lu, " + "allocated by thread %d/%08lx\n", + (unsigned long)ctl->block, ctl->size, + ctl->thread, (unsigned long)ctl->addr); + } + printf("pvr_mem_print_list end block list\n"); +#endif /* PVR_KM_DBG */ +} + +/* Return the number of bytes available still in the memory pool */ +static uint32 pvr_mem_available_int(void) { + struct mallinfo mi = pvr_int_mallinfo(); + + /* This magic formula is modeled after mstats() */ + return mi.arena - mi.uordblks; +} + +uint32_t pvr_mem_available(void) { + CHECK_MEM_BASE; + + return pvr_mem_available_int() + + (PVR_RAM_INT_TOP - (ptr_t)pvr_mem_base); +} + +/* Reset the memory pool, equivalent to freeing all textures currently + residing in RAM. This _must_ be done on a mode change, configuration + change, etc. */ +void pvr_mem_reset(void) { + if(!pvr_state.valid) + pvr_mem_base = NULL; + else { + pvr_mem_base = (pvr_ptr_t)(PVR_RAM_INT_BASE + pvr_state.texture_base); + pvr_int_mem_reset(); + } +} + +/* Print some statistics (like mallocstats) */ +void pvr_mem_stats(void) { + printf("pvr_mem_stats():\n"); + pvr_int_malloc_stats(); + printf("max sbrk base: %08lx\n", (ptr_t)pvr_mem_base); +#ifdef PVR_KM_DBG + pvr_mem_print_list(); +#endif +} +#endif \ No newline at end of file diff --git a/vendor/koshle/hlepvr_mem_core.h b/vendor/koshle/hlepvr_mem_core.h new file mode 100644 index 00000000..c03ba3db --- /dev/null +++ b/vendor/koshle/hlepvr_mem_core.h @@ -0,0 +1,1402 @@ +/* KallistiOS ##version## + + pvr_mem_core.h + (c)2001 Megan Potter + + Imported from the public domain source by Doug Lea. + + */ + +#pragma once +#include + +#define dbglog(channel, ...) printf(__VA_ARGS__) + +#ifdef __MALLOC_H +#error Can not include in the same place as malloc.h +#endif + +#ifndef __PVR_MEM_CORE_H +#define __PVR_MEM_CORE_H + +// #include +// __BEGIN_DECLS + +/* + This is a version (aka dlmalloc) of malloc/free/realloc written by + Doug Lea and released to the public domain. Use, modify, and + redistribute this code without permission or acknowledgement in any + way you wish. Send questions, comments, complaints, performance + data, etc to dl@cs.oswego.edu + +* VERSION 2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) + + Note: There may be an updated version of this malloc obtainable at + ftp://gee.cs.oswego.edu/pub/misc/malloc.c + Check before installing! + +* Quickstart + + This library is all in one file to simplify the most common usage: + ftp it, compile it (-O), and link it into another program. All + of the compile-time options default to reasonable values for use on + most unix platforms. Compile -DWIN32 for reasonable defaults on windows. + You might later want to step through various compile-time and dynamic + tuning options. + + For convenience, an include file for code using this malloc is at: + ftp://gee.cs.oswego.edu/pub/misc/malloc-2.7.0.h + You don't really need this .h file unless you call functions not + defined in your system include files. The .h file contains only the + excerpts from this file needed for using this malloc on ANSI C/C++ + systems, so long as you haven't changed compile-time options about + naming and tuning parameters. If you do, then you can create your + own malloc.h that does include all settings by cutting at the point + indicated below. + +* Why use this malloc? + + This is not the fastest, most space-conserving, most portable, or + most tunable malloc ever written. However it is among the fastest + while also being among the most space-conserving, portable and tunable. + Consistent balance across these factors results in a good general-purpose + allocator for malloc-intensive programs. + + The main properties of the algorithms are: + * For large (>= 512 bytes) requests, it is a pure best-fit allocator, + with ties normally decided via FIFO (i.e. least recently used). + * For small (<= 64 bytes by default) requests, it is a caching + allocator, that maintains pools of quickly recycled chunks. + * In between, and for combinations of large and small requests, it does + the best it can trying to meet both goals at once. + * For very large requests (>= 128KB by default), it relies on system + memory mapping facilities, if supported. + + For a longer but slightly out of date high-level description, see + http://gee.cs.oswego.edu/dl/html/malloc.html + + You may already by default be using a C library containing a malloc + that is based on some version of this malloc (for example in + linux). You might still want to use the one in this file in order to + customize settings or to avoid overheads associated with library + versions. + +* Contents, described in more detail in "description of public routines" below. + + Standard (ANSI/SVID/...) functions: + malloc(size_t n); + calloc(size_t n_elements, size_t element_size); + free(Void_t* p); + realloc(Void_t* p, size_t n); + memalign(size_t alignment, size_t n); + valloc(size_t n); + mallinfo() + mallopt(int parameter_number, int parameter_value) + + Additional functions: + independent_calloc(size_t n_elements, size_t size, Void_t* chunks[]); + independent_comalloc(size_t n_elements, size_t sizes[], Void_t* chunks[]); + pvalloc(size_t n); + cfree(Void_t* p); + malloc_trim(size_t pad); + malloc_usable_size(Void_t* p); + malloc_stats(); + +* Vital statistics: + + Supported pointer representation: 4 or 8 bytes + Supported size_t representation: 4 or 8 bytes + Note that size_t is allowed to be 4 bytes even if pointers are 8. + You can adjust this by defining INTERNAL_SIZE_T + + Alignment: 2 * sizeof(size_t) (default) + (i.e., 8 byte alignment with 4byte size_t). This suffices for + nearly all current machines and C compilers. However, you can + define MALLOC_ALIGNMENT to be wider than this if necessary. + + Minimum overhead per allocated chunk: 4 or 8 bytes + Each malloced chunk has a hidden word of overhead holding size + and status information. + + Minimum allocated size: 4-byte ptrs: 16 bytes (including 4 overhead) + 8-byte ptrs: 24/32 bytes (including, 4/8 overhead) + + When a chunk is freed, 12 (for 4byte ptrs) or 20 (for 8 byte + ptrs but 4 byte size) or 24 (for 8/8) additional bytes are + needed; 4 (8) for a trailing size field and 8 (16) bytes for + free list pointers. Thus, the minimum allocatable size is + 16/24/32 bytes. + + Even a request for zero bytes (i.e., malloc(0)) returns a + pointer to something of the minimum allocatable size. + + The maximum overhead wastage (i.e., number of extra bytes + allocated than were requested in malloc) is less than or equal + to the minimum size, except for requests >= mmap_threshold that + are serviced via mmap(), where the worst case wastage is 2 * + sizeof(size_t) bytes plus the remainder from a system page (the + minimal mmap unit); typically 4096 or 8192 bytes. + + Maximum allocated size: 4-byte size_t: 2^32 minus about two pages + 8-byte size_t: 2^64 minus about two pages + + It is assumed that (possibly signed) size_t values suffice to + represent chunk sizes. `Possibly signed' is due to the fact + that `size_t' may be defined on a system as either a signed or + an unsigned type. The ISO C standard says that it must be + unsigned, but a few systems are known not to adhere to this. + Additionally, even when size_t is unsigned, sbrk (which is by + default used to obtain memory from system) accepts signed + arguments, and may not be able to handle size_t-wide arguments + with negative sign bit. Generally, values that would + appear as negative after accounting for overhead and alignment + are supported only via mmap(), which does not have this + limitation. + + Requests for sizes outside the allowed range will perform an optional + failure action and then return null. (Requests may also + also fail because a system is out of memory.) + + Thread-safety: NOT thread-safe unless USE_MALLOC_LOCK defined + + When USE_MALLOC_LOCK is defined, wrappers are created to + surround every public call with either a pthread mutex or + a win32 spinlock (depending on WIN32). This is not + especially fast, and can be a major bottleneck. + It is designed only to provide minimal protection + in concurrent environments, and to provide a basis for + extensions. If you are using malloc in a concurrent program, + you would be far better off obtaining ptmalloc, which is + derived from a version of this malloc, and is well-tuned for + concurrent programs. (See http://www.malloc.de) + + Compliance: I believe it is compliant with the 1997 Single Unix Specification + (See http://www.opennc.org). Also SVID/XPG, ANSI C, and probably + others as well. + +* Synopsis of compile-time options: + + People have reported using previous versions of this malloc on all + versions of Unix, sometimes by tweaking some of the defines + below. It has been tested most extensively on Solaris and + Linux. It is also reported to work on WIN32 platforms. + People also report using it in stand-alone embedded systems. + + The implementation is in straight, hand-tuned ANSI C. It is not + at all modular. (Sorry!) It uses a lot of macros. To be at all + usable, this code should be compiled using an optimizing compiler + (for example gcc -O3) that can simplify expressions and control + paths. (FAQ: some macros import variables as arguments rather than + declare locals because people reported that some debuggers + otherwise get confused.) + + OPTION DEFAULT VALUE + + Compilation Environment options: + + __STD_C derived from C compiler defines + WIN32 NOT defined + HAVE_MEMCPY defined + USE_MEMCPY 1 if HAVE_MEMCPY is defined + HAVE_MMAP defined as 1 + MMAP_CLEARS 1 + HAVE_MREMAP 0 unless linux defined + malloc_getpagesize derived from system #includes, or 4096 if not + HAVE_USR_INCLUDE_MALLOC_H NOT defined + LACKS_UNISTD_H NOT defined unless WIN32 + LACKS_SYS_PARAM_H NOT defined unless WIN32 + LACKS_SYS_MMAN_H NOT defined unless WIN32 + + Changing default word sizes: + + INTERNAL_SIZE_T size_t + MALLOC_ALIGNMENT 2 * sizeof(INTERNAL_SIZE_T) + + Configuration and functionality options: + + USE_DL_PREFIX NOT defined + USE_PUBLIC_MALLOC_WRAPPERS NOT defined + USE_MALLOC_LOCK NOT defined + DEBUG NOT defined + REALLOC_ZERO_BYTES_FREES NOT defined + MALLOC_FAILURE_ACTION errno = ENOMEM, if __STD_C defined, else no-op + TRIM_FASTBINS 0 + + Options for customizing MORECORE: + + MORECORE sbrk + MORECORE_CONTIGUOUS 1 + MORECORE_CANNOT_TRIM NOT defined + MMAP_AS_MORECORE_SIZE (1024 * 1024) + + Tuning options that are also dynamically changeable via mallopt: + + DEFAULT_MXFAST 64 + DEFAULT_TRIM_THRESHOLD 128 * 1024 + DEFAULT_TOP_PAD 0 + DEFAULT_MMAP_THRESHOLD 128 * 1024 + DEFAULT_MMAP_MAX 65536 + + There are several other #defined constants and macros that you + probably don't want to touch unless you are extending or adapting malloc. +*/ + +/* + Void_t* is the pointer type that malloc should say it returns +*/ + +#define __STD_C 1 + +#ifndef Void_t +#if (__STD_C || defined(WIN32)) +#define Void_t void +#else +#define Void_t char +#endif +#endif /*Void_t*/ + +#include /* for size_t */ + +#include + +/* define LACKS_SYS_PARAM_H if your system does not have a . */ + +#define LACKS_SYS_PARAM_H + + +#include /* needed for malloc_stats */ + + +/* + Debugging: + + Because freed chunks may be overwritten with bookkeeping fields, this + malloc will often die when freed memory is overwritten by user + programs. This can be very effective (albeit in an annoying way) + in helping track down dangling pointers. + + If you compile with -DDEBUG, a number of assertion checks are + enabled that will catch more memory errors. You probably won't be + able to make much sense of the actual assertion errors, but they + should help you locate incorrectly overwritten memory. The + checking is fairly extensive, and will slow down execution + noticeably. Calling malloc_stats or mallinfo with DEBUG set will + attempt to check every non-mmapped allocated and free chunk in the + course of computing the summaries. (By nature, mmapped regions + cannot be checked very much automatically.) + + Setting DEBUG may also be helpful if you are trying to modify + this code. The assertions in the check routines spell out in more + detail the assumptions and invariants underlying the algorithms. + + Setting DEBUG does NOT provide an automated mechanism for checking + that all accesses to malloced memory stay within their + bounds. However, there are several add-ons and adaptations of this + or other mallocs available that do this. +*/ + +#include + + +/* + INTERNAL_SIZE_T is the word-size used for internal bookkeeping + of chunk sizes. + + The default version is the same as size_t. + + While not strictly necessary, it is best to define this as an + unsigned type, even if size_t is a signed type. This may avoid some + artificial size limitations on some systems. + + On a 64-bit machine, you may be able to reduce malloc overhead by + defining INTERNAL_SIZE_T to be a 32 bit `unsigned int' at the + expense of not being able to handle more than 2^32 of malloced + space. If this limitation is acceptable, you are encouraged to set + this unless you are on a platform requiring 16byte alignments. In + this case the alignment requirements turn out to negate any + potential advantages of decreasing size_t word size. + + Implementers: Beware of the possible combinations of: + - INTERNAL_SIZE_T might be signed or unsigned, might be 32 or 64 bits, + and might be the same width as int or as long + - size_t might have different width and signedness as INTERNAL_SIZE_T + - int and long might be 32 or 64 bits, and might be the same width + To deal with this, most comparisons and difference computations + among INTERNAL_SIZE_Ts should cast them to unsigned long, being + aware of the fact that casting an unsigned int to a wider long does + not sign-extend. (This also makes checking for negative numbers + awkward.) Some of these casts result in harmless compiler warnings + on some systems. +*/ + +#ifndef INTERNAL_SIZE_T +#define INTERNAL_SIZE_T size_t +#endif + +/* The corresponding word size */ +#define SIZE_SZ (sizeof(INTERNAL_SIZE_T)) + + +/* + MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks. + It must be a power of two at least 2 * SIZE_SZ, even on machines + for which smaller alignments would suffice. It may be defined as + larger than this though. Note however that code and data structures + are optimized for the case of 8-byte alignment. +*/ + + +#define MALLOC_ALIGNMENT (32) + +/* The corresponding bit mask value */ +#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1) + + + +/* + REALLOC_ZERO_BYTES_FREES should be set if a call to + realloc with zero bytes should be the same as a call to free. + Some people think it should. Otherwise, since this malloc + returns a unique pointer for malloc(0), so does realloc(p, 0). +*/ + +/* #define REALLOC_ZERO_BYTES_FREES */ + +/* + TRIM_FASTBINS controls whether free() of a very small chunk can + immediately lead to trimming. Setting to true (1) can reduce memory + footprint, but will almost always slow down programs that use a lot + of small chunks. + + Define this only if you are willing to give up some speed to more + aggressively reduce system-level memory footprint when releasing + memory in programs that use many small chunks. You can get + essentially the same effect by setting MXFAST to 0, but this can + lead to even greater slowdowns in programs using many small chunks. + TRIM_FASTBINS is an in-between compile-time option, that disables + only those chunks bordering topmost memory from being placed in + fastbins. +*/ + +#define TRIM_FASTBINS 0 + + +/* + USE_DL_PREFIX will prefix all public routines with the string 'dl'. + This is necessary when you only want to use this malloc in one part + of a program, using your regular system malloc elsewhere. +*/ + +/* #define USE_DL_PREFIX */ + + +/* + USE_MALLOC_LOCK causes wrapper functions to surround each + callable routine with pthread mutex lock/unlock. + + USE_MALLOC_LOCK forces USE_PUBLIC_MALLOC_WRAPPERS to be defined +*/ + + +#define USE_MALLOC_LOCK + + +/* + If USE_PUBLIC_MALLOC_WRAPPERS is defined, every public routine is + actually a wrapper function that first calls MALLOC_PREACTION, then + calls the internal routine, and follows it with + MALLOC_POSTACTION. This is needed for locking, but you can also use + this, without USE_MALLOC_LOCK, for purposes of interception, + instrumentation, etc. It is a sad fact that using wrappers often + noticeably degrades performance of malloc-intensive programs. +*/ + +#ifdef USE_MALLOC_LOCK +#define USE_PUBLIC_MALLOC_WRAPPERS +#else +/* #define USE_PUBLIC_MALLOC_WRAPPERS */ +#endif + + +/* + Two-phase name translation. + All of the actual routines are given mangled names. + When wrappers are used, they become the public callable versions. + When DL_PREFIX is used, the callable names are prefixed. +*/ + +#ifndef USE_PUBLIC_MALLOC_WRAPPERS +#define cALLOc public_pvr_cALLOc +#define fREe public_pvr_fREe +#define cFREe public_pvr_cFREe +#define mALLOc public_pvr_mALLOc +#define mEMALIGn public_pvr_mEMALIGn +#define rEALLOc public_pvr_rEALLOc +#define vALLOc public_pvr_vALLOc +#define pVALLOc public_pvr_pVALLOc +#define mALLINFo public_pvr_mALLINFo +#define mALLOPt public_pvr_mALLOPt +#define mTRIm public_pvr_mTRIm +#define mSTATs public_pvr_mSTATs +#define mUSABLe public_pvr_mUSABLe +#define iCALLOc public_pvr_iCALLOc +#define iCOMALLOc public_pvr_iCOMALLOc +#endif + +#ifdef USE_DL_PREFIX +#define public_pvr_cALLOc dlcalloc +#define public_pvr_fREe dlfree +#define public_pvr_cFREe dlcfree +#define public_pvr_mALLOc dlmalloc +#define public_pvr_mEMALIGn dlmemalign +#define public_pvr_rEALLOc dlrealloc +#define public_pvr_vALLOc dlvalloc +#define public_pvr_pVALLOc dlpvalloc +#define public_pvr_mALLINFo dlmallinfo +#define public_pvr_mALLOPt dlmallopt +#define public_pvr_mTRIm dlmalloc_trim +#define public_pvr_mSTATs dlmalloc_stats +#define public_pvr_mUSABLe dlmalloc_usable_size +#define public_pvr_iCALLOc dlindependent_calloc +#define public_pvr_iCOMALLOc dlindependent_comalloc +#else /* USE_DL_PREFIX */ +#define public_pvr_cALLOc pvr_int_calloc +#define public_pvr_fREe pvr_int_free +#define public_pvr_cFREe pvr_int_cfree +#define public_pvr_mALLOc pvr_int_malloc +#define public_pvr_mEMALIGn pvr_int_memalign +#define public_pvr_rEALLOc pvr_int_realloc +#define public_pvr_vALLOc pvr_int_valloc +#define public_pvr_pVALLOc pvr_int_pvalloc +#define public_pvr_mALLINFo pvr_int_mallinfo +#define public_pvr_mALLOPt pvr_int_mallopt +#define public_pvr_mTRIm pvr_int_malloc_trim +#define public_pvr_mSTATs pvr_int_malloc_stats +#define public_pvr_mUSABLe pvr_int_malloc_usable_size +#define public_pvr_iCALLOc pvr_int_independent_calloc +#define public_pvr_iCOMALLOc pvr_int_independent_comalloc +#endif /* USE_DL_PREFIX */ + + +/* + HAVE_MEMCPY should be defined if you are not otherwise using + ANSI STD C, but still have memcpy and memset in your C library + and want to use them in calloc and realloc. Otherwise simple + macro versions are defined below. + + USE_MEMCPY should be defined as 1 if you actually want to + have memset and memcpy called. People report that the macro + versions are faster than libc versions on some systems. + + Even if USE_MEMCPY is set to 1, loops to copy/clear small chunks + (of <= 36 bytes) are manually unrolled in realloc and calloc. +*/ + +#define HAVE_MEMCPY + +#ifndef USE_MEMCPY +#ifdef HAVE_MEMCPY +#define USE_MEMCPY 1 +#else +#define USE_MEMCPY 0 +#endif +#endif + + +#if (__STD_C || defined(HAVE_MEMCPY)) + +#include + +#ifdef WIN32 +/* On Win32 memset and memcpy are already declared in windows.h */ +#else +/* #if __STD_C +void* memset(void*, int, size_t); +void* memcpy(void*, const void*, size_t); +#else +Void_t* memset(); +Void_t* memcpy(); +#endif */ +#endif +#endif + +/* + MALLOC_FAILURE_ACTION is the action to take before "return 0" when + malloc fails to be able to return memory, either because memory is + exhausted or because of illegal arguments. + + By default, sets errno if running on STD_C platform, else does nothing. +*/ + +/* #ifndef MALLOC_FAILURE_ACTION +#if __STD_C +#define MALLOC_FAILURE_ACTION \ + errno = ENOMEM; + +#else +#define MALLOC_FAILURE_ACTION +#endif +#endif */ + +#define MALLOC_FAILURE_ACTION \ + dbglog(DBG_ERROR, "pvr_mem: ERROR -- out of PVR memory; texture heap probably corrupted\n"); + +/* + MORECORE-related declarations. By default, rely on sbrk +*/ + + +#ifdef LACKS_UNISTD_H +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) +#if __STD_C +extern Void_t* sbrk(ptrdiff_t); +#else +extern Void_t* sbrk(); +#endif +#endif +#endif + +/* + MORECORE is the name of the routine to call to obtain more memory + from the system. See below for general guidance on writing + alternative MORECORE functions, as well as a version for WIN32 and a + sample version for pre-OSX macos. +*/ + +#define MORECORE pvr_int_sbrk + +/* + MORECORE_FAILURE is the value returned upon failure of MORECORE + as well as mmap. Since it cannot be an otherwise valid memory address, + and must reflect values of standard sys calls, you probably ought not + try to redefine it. +*/ + +#ifndef MORECORE_FAILURE +#define MORECORE_FAILURE (-1) +#endif + +/* + If MORECORE_CONTIGUOUS is true, take advantage of fact that + consecutive calls to MORECORE with positive arguments always return + contiguous increasing addresses. This is true of unix sbrk. Even + if not defined, when regions happen to be contiguous, malloc will + permit allocations spanning regions obtained from different + calls. But defining this when applicable enables some stronger + consistency checks and space efficiencies. +*/ + +#ifndef MORECORE_CONTIGUOUS +#define MORECORE_CONTIGUOUS 1 +#endif + +/* + Define MORECORE_CANNOT_TRIM if your version of MORECORE + cannot release space back to the system when given negative + arguments. This is generally necessary only if you are using + a hand-crafted MORECORE function that cannot handle negative arguments. +*/ + +/* #define MORECORE_CANNOT_TRIM */ + + +/* + Define HAVE_MMAP as true to optionally make malloc() use mmap() to + allocate very large blocks. These will be returned to the + operating system immediately after a free(). Also, if mmap + is available, it is used as a backup strategy in cases where + MORECORE fails to provide space from system. + + This malloc is best tuned to work with mmap for large requests. + If you do not have mmap, operations involving very large chunks (1MB + or so) may be slower than you'd like. +*/ + +#define HAVE_MMAP 0 + +#ifndef HAVE_MMAP +#define HAVE_MMAP 1 + +/* + Standard unix mmap using /dev/zero clears memory so calloc doesn't + need to. +*/ + +#ifndef MMAP_CLEARS +#define MMAP_CLEARS 1 +#endif + +#else /* no mmap */ +#ifndef MMAP_CLEARS +#define MMAP_CLEARS 0 +#endif +#endif + + +/* + MMAP_AS_MORECORE_SIZE is the minimum mmap size argument to use if + sbrk fails, and mmap is used as a backup (which is done only if + HAVE_MMAP). The value must be a multiple of page size. This + backup strategy generally applies only when systems have "holes" in + address space, so sbrk cannot perform contiguous expansion, but + there is still space available on system. On systems for which + this is known to be useful (i.e. most linux kernels), this occurs + only when programs allocate huge amounts of memory. Between this, + and the fact that mmap regions tend to be limited, the size should + be large, to avoid too many mmap calls and thus avoid running out + of kernel resources. +*/ + +#ifndef MMAP_AS_MORECORE_SIZE +#define MMAP_AS_MORECORE_SIZE (1024 * 1024) +#endif + +/* + Define HAVE_MREMAP to make realloc() use mremap() to re-allocate + large blocks. This is currently only possible on Linux with + kernel versions newer than 1.3.77. +*/ + +#ifndef HAVE_MREMAP +#ifdef linux +#define HAVE_MREMAP 1 +#else +#define HAVE_MREMAP 0 +#endif + +#endif /* HAVE_MMAP */ + + +/* + The system page size. To the extent possible, this malloc manages + memory from the system in page-size units. Note that this value is + cached during initialization into a field of malloc_state. So even + if malloc_getpagesize is a function, it is only called once. + + The following mechanics for getpagesize were adapted from bsd/gnu + getpagesize.h. If none of the system-probes here apply, a value of + 4096 is used, which should be OK: If they don't apply, then using + the actual value probably doesn't impact performance. +*/ + +#define malloc_getpagesize 4096 /* XXX: Should we make this 16384 +for cache considerations? */ + +#ifndef malloc_getpagesize + +#ifndef LACKS_UNISTD_H +# include +#endif + +# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ +# ifndef _SC_PAGE_SIZE +# define _SC_PAGE_SIZE _SC_PAGESIZE +# endif +# endif + +# ifdef _SC_PAGE_SIZE +# define malloc_getpagesize sysconf(_SC_PAGE_SIZE) +# else +# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) +extern size_t getpagesize(); +# define malloc_getpagesize getpagesize() +# else +# ifdef WIN32 /* use supplied emulation of getpagesize */ +# define malloc_getpagesize getpagesize() +# else +# ifndef LACKS_SYS_PARAM_H +# include +# endif +# ifdef EXEC_PAGESIZE +# define malloc_getpagesize EXEC_PAGESIZE +# else +# ifdef NBPG +# ifndef CLSIZE +# define malloc_getpagesize NBPG +# else +# define malloc_getpagesize (NBPG * CLSIZE) +# endif +# else +# ifdef NBPC +# define malloc_getpagesize NBPC +# else +# ifdef PAGESIZE +# define malloc_getpagesize PAGESIZE +# else /* just guess */ +# define malloc_getpagesize (4096) +# endif +# endif +# endif +# endif +# endif +# endif +# endif +#endif + +/* + This version of malloc supports the standard SVID/XPG mallinfo + routine that returns a struct containing usage properties and + statistics. It should work on any SVID/XPG compliant system that has + a /usr/include/malloc.h defining struct mallinfo. (If you'd like to + install such a thing yourself, cut out the preliminary declarations + as described above and below and save them in a malloc.h file. But + there's no compelling reason to bother to do this.) + + The main declaration needed is the mallinfo struct that is returned + (by-copy) by mallinfo(). The SVID/XPG malloinfo struct contains a + bunch of field that are not even meaningful in this version of + malloc. These fields are are instead filled by mallinfo() with + other numbers that might be of interest. + + HAVE_USR_INCLUDE_MALLOC_H should be set if you have a + /usr/include/malloc.h file that includes a declaration of struct + mallinfo. If so, it is included; else an SVID2/XPG2 compliant + version is declared below. These must be precisely the same for + mallinfo() to work. The original SVID version of this struct, + defined on most systems with mallinfo, declares all fields as + ints. But some others define as unsigned long. If your system + defines the fields using a type of different width than listed here, + you must #include your system version and #define + HAVE_USR_INCLUDE_MALLOC_H. +*/ + +#undef HAVE_USR_INCLUDE_MALLOC_H + +#ifdef HAVE_USR_INCLUDE_MALLOC_H +#include "/usr/include/malloc.h" +#else + +/* SVID2/XPG mallinfo structure */ + +struct mallinfo { + int arena; /* non-mmapped space allocated from system */ + int ordblks; /* number of free chunks */ + int smblks; /* number of fastbin blocks */ + int hblks; /* number of mmapped regions */ + int hblkhd; /* space in mmapped regions */ + int usmblks; /* maximum total allocated space */ + int fsmblks; /* space available in freed fastbin blocks */ + int uordblks; /* total allocated space */ + int fordblks; /* total free space */ + int keepcost; /* top-most, releasable (via malloc_trim) space */ +}; + +/* + SVID/XPG defines four standard parameter numbers for mallopt, + normally defined in malloc.h. Only one of these (M_MXFAST) is used + in this malloc. The others (M_NLBLKS, M_GRAIN, M_KEEP) don't apply, + so setting them has no effect. But this malloc also supports other + options in mallopt described below. +*/ +#endif + + +/* ---------- description of public routines ------------ */ + +/* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or null + if no space is available. Additionally, on failure, errno is + set to ENOMEM on ANSI C systems. + + If n is zero, malloc returns a minumum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 24 or 32 bytes on 64bit + systems.) On most systems, size_t is an unsigned type, so calls + with negative arguments are interpreted as requests for huge amounts + of space, which will often fail. The maximum supported value of n + differs across systems, but is in all cases less than the maximum + representable value of a size_t. +*/ +#if __STD_C +Void_t* public_pvr_mALLOc(size_t); +#else +Void_t* public_pvr_mALLOc(); +#endif + +/* + free(Void_t* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. It can have arbitrary (i.e., bad!) + effects if p has already been freed. + + Unless disabled (using mallopt), freeing very large spaces will + when possible, automatically trigger operations that give + back unused memory to the system, thus reducing program footprint. +*/ +#if __STD_C +void public_pvr_fREe(Void_t*); +#else +void public_pvr_fREe(); +#endif + +/* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. +*/ +#if __STD_C +Void_t* public_pvr_cALLOc(size_t, size_t); +#else +Void_t* public_pvr_cALLOc(); +#endif + +/* + realloc(Void_t* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p when possible, otherwise it employs the + equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. Unless the #define + REALLOC_ZERO_BYTES_FREES is set, realloc with a size argument of + zero (re)allocates a minimum-sized chunk. + + Large chunks that were internally obtained via mmap will always + be reallocated using malloc-copy-free sequences unless + the system supports MREMAP (currently only linux). + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. +*/ +#if __STD_C +Void_t* public_pvr_rEALLOc(Void_t*, size_t); +#else +Void_t* public_pvr_rEALLOc(); +#endif + +/* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. +*/ +#if __STD_C +Void_t* public_pvr_mEMALIGn(size_t, size_t); +#else +Void_t* public_pvr_mEMALIGn(); +#endif + +/* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. +*/ +#if __STD_C +Void_t* public_pvr_vALLOc(size_t); +#else +Void_t* public_pvr_vALLOc(); +#endif + + + +/* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. Only one of these (M_MXFAST) is used + in this malloc. The others (M_NLBLKS, M_GRAIN, M_KEEP) don't apply, + so setting them has no effect. But this malloc also supports four + other options in mallopt. See below for details. Briefly, supported + parameters are as follows (listed defaults are for "typical" + configurations). + + Symbol param # default allowed param values + M_MXFAST 1 64 0-80 (0 disables fastbins) + M_TRIM_THRESHOLD -1 128*1024 any (-1U disables trimming) + M_TOP_PAD -2 0 any + M_MMAP_THRESHOLD -3 128*1024 any (or 0 if no MMAP support) + M_MMAP_MAX -4 65536 any (0 disables use of mmap) +*/ +#if __STD_C +int public_pvr_mALLOPt(int, int); +#else +int public_pvr_mALLOPt(); +#endif + + +/* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: the number of fastbin blocks (i.e., small chunks that + have been freed but not use reused or consolidated) + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: total bytes held in fastbin blocks + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. +*/ +#if __STD_C +struct mallinfo public_pvr_mALLINFo(void); +#else +struct mallinfo public_pvr_mALLINFo(); +#endif + +/* + independent_calloc(size_t n_elements, size_t element_size, Void_t* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use regular calloc and assign pointers into this + space to represent elements. (In this case though, you cannot + independently free elements.) + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if(n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if(pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for(i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } +*/ +#if __STD_C +Void_t** public_pvr_iCALLOc(size_t, size_t, Void_t**); +#else +Void_t** public_pvr_iCALLOc(); +#endif + +/* + independent_comalloc(size_t n_elements, size_t sizes[], Void_t* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use a single regular malloc, and assign pointers at + particular offsets in the aggregate space. (In this case though, you + cannot independently free elements.) + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if(independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. +*/ +#if __STD_C +Void_t** public_pvr_iCOMALLOc(size_t, size_t*, Void_t**); +#else +Void_t** public_pvr_iCOMALLOc(); +#endif + + +/* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ +#if __STD_C +Void_t* public_pvr_pVALLOc(size_t); +#else +Void_t* public_pvr_pVALLOc(); +#endif + +/* + cfree(Void_t* p); + Equivalent to free(p). + + cfree is needed/defined on some systems that pair it with calloc, + for odd historical reasons (such as: cfree is used in example + code in the first edition of K&R). +*/ +#if __STD_C +void public_pvr_cFREe(Void_t*); +#else +void public_pvr_cFREe(); +#endif + +/* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative + arguments to sbrk) if there is unused memory at the `high' end of + the malloc pool. You can call this after freeing large blocks of + memory to potentially reduce the system-level memory requirements + of a program. However, it cannot guarantee to reduce memory. Under + some allocation patterns, some large free blocks of memory will be + locked between two used chunks, so they cannot be given back to + the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, + only the minimum amount of memory to maintain internal data + structures will be left (one page or less). Non-zero arguments + can be supplied to maintain enough trailing space to service + future expected allocations without having to re-obtain memory + from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. + On systems that do not support "negative sbrks", it will always + rreturn 0. +*/ +#if __STD_C +int public_pvr_mTRIm(size_t); +#else +int public_pvr_mTRIm(); +#endif + +/* + malloc_usable_size(Void_t* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); + +*/ +#if __STD_C +size_t public_pvr_mUSABLe(Void_t*); +#else +size_t public_pvr_mUSABLe(); +#endif + +/* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. + +*/ +#if __STD_C +void public_pvr_mSTATs(); +#else +void public_pvr_mSTATs(); +#endif + +/* mallopt tuning options */ + +/* + M_MXFAST is the maximum request size used for "fastbins", special bins + that hold returned chunks without consolidating their spaces. This + enables future requests for chunks of the same size to be handled + very quickly, but can increase fragmentation, and thus increase the + overall memory footprint of a program. + + This malloc manages fastbins very conservatively yet still + efficiently, so fragmentation is rarely a problem for values less + than or equal to the default. The maximum supported value of MXFAST + is 80. You wouldn't want it any higher than this anyway. Fastbins + are designed especially for use with many small structs, objects or + strings -- the default handles structs/objects/arrays with sizes up + to 8 4byte fields, or small strings representing words, tokens, + etc. Using fastbins for larger objects normally worsens + fragmentation without improving speed. + + M_MXFAST is set in REQUEST size units. It is internally used in + chunksize units, which adds padding and alignment. You can reduce + M_MXFAST to 0 to disable all use of fastbins. This causes the malloc + algorithm to be a closer approximation of fifo-best-fit in all cases, + not just for larger requests, but will generally cause it to be + slower. +*/ + + +/* M_MXFAST is a standard SVID/XPG tuning option, usually listed in malloc.h */ +#ifndef M_MXFAST +#define M_MXFAST 1 +#endif + +#ifndef DEFAULT_MXFAST +#define DEFAULT_MXFAST 64 +#endif + + +/* + M_TRIM_THRESHOLD is the maximum amount of unused top-most memory + to keep before releasing via malloc_trim in free(). + + Automatic trimming is mainly useful in long-lived programs. + Because trimming via sbrk can be slow on some systems, and can + sometimes be wasteful (in cases where programs immediately + afterward allocate more large chunks) the value should be high + enough so that your overall system performance would improve by + releasing this much memory. + + The trim threshold and the mmap control parameters (see below) + can be traded off with one another. Trimming and mmapping are + two different ways of releasing unused memory back to the + system. Between these two, it is often possible to keep + system-level demands of a long-lived program down to a bare + minimum. For example, in one test suite of sessions measuring + the XF86 X server on Linux, using a trim threshold of 128K and a + mmap threshold of 192K led to near-minimal long term resource + consumption. + + If you are using this malloc in a long-lived program, it should + pay to experiment with these values. As a rough guide, you + might set to a value close to the average size of a process + (program) running on your system. Releasing this much memory + would allow such a process to run in memory. Generally, it's + worth it to tune for trimming rather tham memory mapping when a + program undergoes phases where several large chunks are + allocated and released in ways that can reuse each other's + storage, perhaps mixed with phases where there are no such + chunks at all. And in well-behaved long-lived programs, + controlling release of large blocks via trimming versus mapping + is usually faster. + + However, in most programs, these parameters serve mainly as + protection against the system-level effects of carrying around + massive amounts of unneeded memory. Since frequent calls to + sbrk, mmap, and munmap otherwise degrade performance, the default + parameters are set to relatively high values that serve only as + safeguards. + + The trim value It must be greater than page size to have any useful + effect. To disable trimming completely, you can set to + (unsigned long)(-1) + + Trim settings interact with fastbin (MXFAST) settings: Unless + TRIM_FASTBINS is defined, automatic trimming never takes place upon + freeing a chunk with size less than or equal to MXFAST. Trimming is + instead delayed until subsequent freeing of larger chunks. However, + you can still force an attempted trim by calling malloc_trim. + + Also, trimming is not generally possible in cases where + the main arena is obtained via mmap. + + Note that the trick some people use of mallocing a huge space and + then freeing it at program startup, in an attempt to reserve system + memory, doesn't have the intended effect under automatic trimming, + since that memory will immediately be returned to the system. +*/ + +#define M_TRIM_THRESHOLD -1 + +#ifndef DEFAULT_TRIM_THRESHOLD +#define DEFAULT_TRIM_THRESHOLD (128 * 1024) +#endif + +/* + M_TOP_PAD is the amount of extra `padding' space to allocate or + retain whenever sbrk is called. It is used in two ways internally: + + * When sbrk is called to extend the top of the arena to satisfy + a new malloc request, this much padding is added to the sbrk + request. + + * When malloc_trim is called automatically from free(), + it is used as the `pad' argument. + + In both cases, the actual amount of padding is rounded + so that the end of the arena is always a system page boundary. + + The main reason for using padding is to avoid calling sbrk so + often. Having even a small pad greatly reduces the likelihood + that nearly every malloc request during program start-up (or + after trimming) will invoke sbrk, which needlessly wastes + time. + + Automatic rounding-up to page-size units is normally sufficient + to avoid measurable overhead, so the default is 0. However, in + systems where sbrk is relatively slow, it can pay to increase + this value, at the expense of carrying around more memory than + the program needs. +*/ + +#define M_TOP_PAD -2 + +#ifndef DEFAULT_TOP_PAD +#define DEFAULT_TOP_PAD (0) +#endif + +/* + M_MMAP_THRESHOLD is the request size threshold for using mmap() + to service a request. Requests of at least this size that cannot + be allocated using already-existing space will be serviced via mmap. + (If enough normal freed space already exists it is used instead.) + + Using mmap segregates relatively large chunks of memory so that + they can be individually obtained and released from the host + system. A request serviced through mmap is never reused by any + other request (at least not directly; the system may just so + happen to remap successive requests to the same locations). + + Segregating space in this way has the benefits that: + + 1. Mmapped space can ALWAYS be individually released back + to the system, which helps keep the system level memory + demands of a long-lived program low. + 2. Mapped memory can never become `locked' between + other chunks, as can happen with normally allocated chunks, which + means that even trimming via malloc_trim would not release them. + 3. On some systems with "holes" in address spaces, mmap can obtain + memory that sbrk cannot. + + However, it has the disadvantages that: + + 1. The space cannot be reclaimed, consolidated, and then + used to service later requests, as happens with normal chunks. + 2. It can lead to more wastage because of mmap page alignment + requirements + 3. It causes malloc performance to be more dependent on host + system memory management support routines which may vary in + implementation quality and may impose arbitrary + limitations. Generally, servicing a request via normal + malloc steps is faster than going through a system's mmap. + + The advantages of mmap nearly always outweigh disadvantages for + "large" chunks, but the value of "large" varies across systems. The + default is an empirically derived value that works well in most + systems. +*/ + +#define M_MMAP_THRESHOLD -3 + +#ifndef DEFAULT_MMAP_THRESHOLD +#define DEFAULT_MMAP_THRESHOLD (128 * 1024) +#endif + +/* + M_MMAP_MAX is the maximum number of requests to simultaneously + service using mmap. This parameter exists because +. Some systems have a limited number of internal tables for + use by mmap, and using more than a few of them may degrade + performance. + + The default is set to a value that serves only as a safeguard. + Setting to 0 disables use of mmap for servicing large requests. If + HAVE_MMAP is not set, the default value is 0, and attempts to set it + to non-zero values in mallopt will fail. +*/ + +#define M_MMAP_MAX -4 + +#ifndef DEFAULT_MMAP_MAX +#if HAVE_MMAP +#define DEFAULT_MMAP_MAX (65536) +#else +#define DEFAULT_MMAP_MAX (0) +#endif +#endif + +// __END_DECLS + +#endif /* __MALLOC_H */ + + diff --git a/vendor/koshle/hlepvr_misc.cpp b/vendor/koshle/hlepvr_misc.cpp new file mode 100644 index 00000000..4368a164 --- /dev/null +++ b/vendor/koshle/hlepvr_misc.cpp @@ -0,0 +1,286 @@ +/* KallistiOS ##version## + + pvr_misc.c + Copyright (C) 2002 Megan Potter + Copyright (C) 2014 Lawrence Sebald + + */ + +#include +#include +// #include +#include "dc/pvr.h" +// #include +#include "pvr_internal.h" +#include + +/* + These are miscellaneous parameters you can set which affect the + rendering process. +*/ + +/* Set the background plane color (the area of the screen not covered by + any other polygons) */ +void pvr_set_bg_color(float r, float g, float b) { + int ir, ig, ib; + + ir = (int)(255 * r); + ig = (int)(255 * g); + ib = (int)(255 * b); + + pvr_state.bg_color = (ir << 16) | (ig << 8) | (ib << 0); +} + +/* Enable/disable cheap shadow mode and set the cheap shadow scale register. */ +void pvr_set_shadow_scale(int enable, float scale_value) { + int s = (int)(scale_value * 255); + + PVR_SET(PVR_CHEAP_SHADOW, ((!!enable) << 8) | (s & 0xFF)); +} + +/* Set the Z-Clip value (that is to say the depth of the background layer). */ +void pvr_set_zclip(float zc) { + pvr_state.zclip = zc; +} + +/* Set a callback to be called before rendering starts */ +pvr_before_render_hook_t pvr_set_before_render_callback(pvr_before_render_hook_t callback) { + pvr_before_render_hook_t rv = pvr_state.isp_start_callback; + pvr_state.isp_start_callback = callback; + return rv; +} + +/* Return the current VBlank count */ +int pvr_get_vbl_count(void) { + return pvr_state.vbl_count; +} + +/* Fill in a statistics structure (above) from current data. This + is a super-set of frame count. */ +int pvr_get_stats(pvr_stats_t *stat) { + if(!pvr_state.valid) + return -1; + + assert(stat != NULL); + + stat->enabled_list_mask = pvr_state.lists_enabled; + stat->vbl_count = pvr_state.vbl_count; + stat->frame_last_time = pvr_state.frame_last_len; + stat->reg_last_time = pvr_state.reg_last_len; + stat->rnd_last_time = pvr_state.rnd_last_len; + + if(stat->frame_last_time != 0) + stat->frame_rate = 1000000000.0f / stat->frame_last_time; + else + stat->frame_rate = -1.0f; + + stat->vtx_buffer_used = pvr_state.vtx_buf_used; + stat->vtx_buffer_used_max = pvr_state.vtx_buf_used_max; + stat->buf_last_time = pvr_state.buf_last_len; + stat->frame_count = pvr_state.frame_count; + + return 0; +} + +int pvr_vertex_dma_enabled(void) { + return pvr_state.dma_mode; +} + +/******** INTERNAL STUFF ************************************************/ + +/* Update statistical counters */ +void pvr_sync_stats(int event) { + uint64_t t; + volatile pvr_ta_buffers_t *buf; + + if(event == PVR_SYNC_VBLANK) { + pvr_state.vbl_count++; + } + else { + /* Get the current time */ + t = 0;// timer_ns_gettime64(); + + switch(event) { + case PVR_SYNC_REGSTART: + pvr_state.reg_start_time = t; + break; + + case PVR_SYNC_REGDONE: + pvr_state.reg_last_len = t - pvr_state.reg_start_time; + + buf = pvr_state.ta_buffers + pvr_state.ta_target; + pvr_state.vtx_buf_used = PVR_GET(PVR_TA_VERTBUF_POS) - buf->vertex; + + if(pvr_state.vtx_buf_used > pvr_state.vtx_buf_used_max) + pvr_state.vtx_buf_used_max = pvr_state.vtx_buf_used; + + break; + + case PVR_SYNC_RNDSTART: + pvr_state.rnd_start_time = t; + break; + + case PVR_SYNC_RNDDONE: + pvr_state.rnd_last_len = t - pvr_state.rnd_start_time; + break; + + case PVR_SYNC_BUFSTART: + pvr_state.buf_start_time = t; + break; + + case PVR_SYNC_BUFDONE: + pvr_state.buf_last_len = t - pvr_state.buf_start_time; + break; + + case PVR_SYNC_PAGEFLIP: + pvr_state.frame_last_len = t - pvr_state.frame_last_time; + pvr_state.frame_last_time = t; + pvr_state.frame_count++; + break; + } + } +} + + +/* Synchronize the viewed page with what's in pvr_state */ +void pvr_sync_view(void) { + // TODO: present? + //vid_set_start(pvr_state.frame_buffers[pvr_state.view_target].frame); + PVR_SET(PVR_FB_ADDR, pvr_state.frame_buffers[pvr_state.view_target].frame); +} + +/* Synchronize the registration buffer with what's in pvr_state */ +void pvr_sync_reg_buffer(void) { + volatile pvr_ta_buffers_t *buf; + + buf = pvr_state.ta_buffers + pvr_state.ta_target; + + /* Reset TA */ + //PVR_SET(PVR_RESET, PVR_RESET_TA); + //PVR_SET(PVR_RESET, PVR_RESET_NONE); + + /* Set buffer pointers */ + PVR_SET(PVR_TA_OPB_START, buf->opb); + PVR_SET(PVR_TA_OPB_INIT, buf->opb + buf->opb_size); + PVR_SET(PVR_TA_OPB_END, buf->opb + buf->opb_size * (1 + buf->opb_overflow_count)); + PVR_SET(PVR_TA_VERTBUF_START, buf->vertex); + PVR_SET(PVR_TA_VERTBUF_END, buf->vertex + buf->vertex_size); + + /* Misc config parameters */ + PVR_SET(PVR_TILEMAT_CFG, pvr_state.tsize_const); /* Tile count: (H/32-1) << 16 | (W/32-1) */ + PVR_SET(PVR_OPB_CFG, pvr_state.list_reg_mask); /* List enables */ + PVR_SET(PVR_TA_INIT, PVR_TA_INIT_GO); /* Confirm settings */ + (void)PVR_GET(PVR_TA_INIT); + +#if 0 + printf("== SYNC REG BUFFER:\n"); + printf("TA_OL_BASE: %08lx\nTA_OL_LIMIT: %08lx\nTA_NEXT_OPB: %08lx\n", + PVR_GET(TA_OL_BASE), PVR_GET(TA_OL_LIMIT), PVR_GET(TA_NEXT_OPB) << 2); +#endif +} + +/* Begin a render operation that has been queued completely (i.e., the + opposite of ta_target) */ +void pvr_begin_queued_render(void) { + volatile pvr_ta_buffers_t * tbuf; + volatile pvr_frame_buffers_t * rbuf; + pvr_bkg_poly_t bkg; + b32_uint32 *vrl; + uint32 *bkgdata; + uint32 vert_end; + int i; + int bufn = pvr_state.view_target; + union { + float f; + uint32 i; + } zclip; + + /* Get the appropriate buffer */ + tbuf = pvr_state.ta_buffers + (pvr_state.ta_target ^ 1); + rbuf = pvr_state.frame_buffers + (bufn ^ 1); + + /* Calculate background value for below */ + /* Small side note: during setup, the value is originally + 0x01203000... I'm thinking that the upper word signifies + the length of the background plane list in dwords + shifted up by 4. */ + vert_end = 0x01000000 | ((PVR_GET(PVR_TA_VERTBUF_POS) - tbuf->vertex) << 1); + + /* Throw the background data on the end of the TA's list */ + bkg.flags1 = 0x90800000; /* These are from libdream.. ought to figure out */ + bkg.flags2 = 0x20800440; /* what they mean for sure... heh =) */ + bkg.dummy = 0; + bkg.x1 = 0.0f; + bkg.y1 = 480.0f; + bkg.z1 = 0.2f; + bkg.argb1 = pvr_state.bg_color; + bkg.x2 = 0.0f; + bkg.y2 = 0.0f; + bkg.z2 = 0.2f; + bkg.argb2 = pvr_state.bg_color; + bkg.x3 = 640.0f; + bkg.y3 = 480.0f; + bkg.z3 = 0.2f; + bkg.argb3 = pvr_state.bg_color; + bkgdata = (uint32 *)&bkg; + // vrl = (uint32*)(PVR_RAM_BASE | PVR_GET(PVR_TA_VERTBUF_POS)); + vrl = (b32_uint32*)(emu_vram + PVR_GET(PVR_TA_VERTBUF_POS)); + + for(i = 0; i < 15; i++) + vrl[i] = bkgdata[i]; + + /* Reset the ISP/TSP, just in case */ + //PVR_SET(PVR_RESET, PVR_RESET_ISPTSP); + //PVR_SET(PVR_RESET, PVR_RESET_NONE); + + /* Finish up rendering the current frame (into the other buffer) */ + PVR_SET(PVR_ISP_TILEMAT_ADDR, tbuf->tile_matrix); + PVR_SET(PVR_ISP_VERTBUF_ADDR, tbuf->vertex); + + if(!pvr_state.to_texture[bufn]) + PVR_SET(PVR_RENDER_ADDR, rbuf->frame); + else { + PVR_SET(PVR_RENDER_ADDR, pvr_state.to_txr_addr[bufn] | (1 << 24)); + PVR_SET(PVR_RENDER_ADDR_2, pvr_state.to_txr_addr[bufn] | (1 << 24)); + } + + PVR_SET(PVR_BGPLANE_CFG, vert_end); /* Bkg plane location */ + zclip.f = pvr_state.zclip; + PVR_SET(PVR_BGPLANE_Z, zclip.i); + PVR_SET(PVR_PCLIP_X, pvr_state.pclip_x); + PVR_SET(PVR_PCLIP_Y, pvr_state.pclip_y); + + if(!pvr_state.to_texture[bufn]) + PVR_SET(PVR_RENDER_MODULO, (pvr_state.w * 2) / 8); + else + PVR_SET(PVR_RENDER_MODULO, pvr_state.to_txr_rp[bufn]); + + // XXX Do we _really_ need this every time? + // SETREG(PVR_FB_CFG_2, 0x00000009); /* Alpha mode */ + + printf("VERBUF_FREE: %d, OPB_FREE: %d\n", PVR_GET(PVR_TA_VERTBUF_END) - PVR_GET(PVR_TA_VERTBUF_POS), PVR_GET(PVR_TA_OPB_END) - PVR_GET(PVR_TA_OPB_POS)*4); + if (!pvr_state.isp_start_callback || pvr_state.isp_start_callback()) + PVR_SET(PVR_ISP_START, PVR_ISP_START_GO); /* Start render */ +} + +void pvr_blank_polyhdr(int type) { + pvr_poly_hdr_t poly; + + // Make it. + pvr_blank_polyhdr_buf(type, &poly); + + // Submit it + pvr_prim(&poly, sizeof(poly)); +} + +void pvr_blank_polyhdr_buf(int type, pvr_poly_hdr_t * poly) { + /* Empty it out */ + memset(poly, 0, sizeof(pvr_poly_hdr_t)); + + /* Put in the list type */ + poly->cmd = (type << PVR_TA_CMD_TYPE_SHIFT) | 0x80840012; + + /* Fill in dummy values */ + poly->d1 = poly->d2 = poly->d3 = poly->d4 = 0xffffffff; + +} diff --git a/vendor/koshle/hlepvr_prim.cpp b/vendor/koshle/hlepvr_prim.cpp new file mode 100644 index 00000000..95dbc19f --- /dev/null +++ b/vendor/koshle/hlepvr_prim.cpp @@ -0,0 +1,807 @@ +/* KallistiOS ##version## + + pvr_prim.c + Copyright (C) 2002 Megan Potter + + */ + +#include +#include +#include "dc/pvr.h" +#include "pvr_internal.h" + +#define assert_msg(cond, txt) assert(cond && txt) + +/* + + Primitive handling + + These functions help you prepare primitives for loading into the + PVR for scene processing. + +*/ + +/* Compile a polygon context into a polygon header */ +void pvr_poly_compile(pvr_poly_hdr_t *dst, pvr_poly_cxt_t *src) { + int u, v; + uint32 txr_base; + + /* Basically we just take each parameter, clip it, shift it + into place, and OR it into the final result. */ + + /* The base values for CMD */ + dst->cmd = PVR_CMD_POLYHDR; + + if(src->txr.enable == PVR_TEXTURE_ENABLE) + dst->cmd |= 8; + + /* Or in the list type, shading type, color and UV formats */ + dst->cmd |= (src->list_type << PVR_TA_CMD_TYPE_SHIFT) & PVR_TA_CMD_TYPE_MASK; + dst->cmd |= (src->fmt.color << PVR_TA_CMD_CLRFMT_SHIFT) & PVR_TA_CMD_CLRFMT_MASK; + dst->cmd |= (src->gen.shading << PVR_TA_CMD_SHADE_SHIFT) & PVR_TA_CMD_SHADE_MASK; + dst->cmd |= (src->fmt.uv << PVR_TA_CMD_UVFMT_SHIFT) & PVR_TA_CMD_UVFMT_MASK; + dst->cmd |= (src->gen.clip_mode << PVR_TA_CMD_USERCLIP_SHIFT) & PVR_TA_CMD_USERCLIP_MASK; + dst->cmd |= (src->fmt.modifier << PVR_TA_CMD_MODIFIER_SHIFT) & PVR_TA_CMD_MODIFIER_MASK; + dst->cmd |= (src->gen.modifier_mode << PVR_TA_CMD_MODIFIERMODE_SHIFT) & PVR_TA_CMD_MODIFIERMODE_MASK; + dst->cmd |= (src->gen.specular << PVR_TA_CMD_SPECULAR_SHIFT) & PVR_TA_CMD_SPECULAR_MASK; + + /* Polygon mode 1 */ + dst->mode1 = (src->depth.comparison << PVR_TA_PM1_DEPTHCMP_SHIFT) & PVR_TA_PM1_DEPTHCMP_MASK; + dst->mode1 |= (src->gen.culling << PVR_TA_PM1_CULLING_SHIFT) & PVR_TA_PM1_CULLING_MASK; + dst->mode1 |= (src->depth.write << PVR_TA_PM1_DEPTHWRITE_SHIFT) & PVR_TA_PM1_DEPTHWRITE_MASK; + dst->mode1 |= (src->txr.enable << PVR_TA_PM1_TXRENABLE_SHIFT) & PVR_TA_PM1_TXRENABLE_MASK; + + /* Polygon mode 2 */ + dst->mode2 = (src->blend.src << PVR_TA_PM2_SRCBLEND_SHIFT) & PVR_TA_PM2_SRCBLEND_MASK; + dst->mode2 |= (src->blend.dst << PVR_TA_PM2_DSTBLEND_SHIFT) & PVR_TA_PM2_DSTBLEND_MASK; + dst->mode2 |= (src->blend.src_enable << PVR_TA_PM2_SRCENABLE_SHIFT) & PVR_TA_PM2_SRCENABLE_MASK; + dst->mode2 |= (src->blend.dst_enable << PVR_TA_PM2_DSTENABLE_SHIFT) & PVR_TA_PM2_DSTENABLE_MASK; + dst->mode2 |= (src->gen.fog_type << PVR_TA_PM2_FOG_SHIFT) & PVR_TA_PM2_FOG_MASK; + dst->mode2 |= (src->gen.color_clamp << PVR_TA_PM2_CLAMP_SHIFT) & PVR_TA_PM2_CLAMP_MASK; + dst->mode2 |= (src->gen.alpha << PVR_TA_PM2_ALPHA_SHIFT) & PVR_TA_PM2_ALPHA_MASK; + + if(src->txr.enable == PVR_TEXTURE_DISABLE) { + dst->mode3 = 0; + } + else { + dst->mode2 |= (src->txr.alpha << PVR_TA_PM2_TXRALPHA_SHIFT) & PVR_TA_PM2_TXRALPHA_MASK; + dst->mode2 |= (src->txr.uv_flip << PVR_TA_PM2_UVFLIP_SHIFT) & PVR_TA_PM2_UVFLIP_MASK; + dst->mode2 |= (src->txr.uv_clamp << PVR_TA_PM2_UVCLAMP_SHIFT) & PVR_TA_PM2_UVCLAMP_MASK; + dst->mode2 |= (src->txr.filter << PVR_TA_PM2_FILTER_SHIFT) & PVR_TA_PM2_FILTER_MASK; + dst->mode2 |= (src->txr.mipmap_bias << PVR_TA_PM2_MIPBIAS_SHIFT) & PVR_TA_PM2_MIPBIAS_MASK; + dst->mode2 |= (src->txr.env << PVR_TA_PM2_TXRENV_SHIFT) & PVR_TA_PM2_TXRENV_MASK; + + switch(src->txr.width) { + case 8: + u = 0; + break; + case 16: + u = 1; + break; + case 32: + u = 2; + break; + case 64: + u = 3; + break; + case 128: + u = 4; + break; + case 256: + u = 5; + break; + case 512: + u = 6; + break; + case 1024: + u = 7; + break; + default: + assert_msg(0, "Invalid texture U size"); + u = 0; + break; + } + + switch(src->txr.height) { + case 8: + v = 0; + break; + case 16: + v = 1; + break; + case 32: + v = 2; + break; + case 64: + v = 3; + break; + case 128: + v = 4; + break; + case 256: + v = 5; + break; + case 512: + v = 6; + break; + case 1024: + v = 7; + break; + default: + assert_msg(0, "Invalid texture V size"); + v = 0; + break; + } + + dst->mode2 |= (u << PVR_TA_PM2_USIZE_SHIFT) & PVR_TA_PM2_USIZE_MASK; + dst->mode2 |= (v << PVR_TA_PM2_VSIZE_SHIFT) & PVR_TA_PM2_VSIZE_MASK; + + /* Polygon mode 3 */ + dst->mode3 = (src->txr.mipmap << PVR_TA_PM3_MIPMAP_SHIFT) & PVR_TA_PM3_MIPMAP_MASK; + dst->mode3 |= (src->txr.format << PVR_TA_PM3_TXRFMT_SHIFT) & PVR_TA_PM3_TXRFMT_MASK; + + /* Convert the texture address */ + txr_base = (ptr_t)src->txr.base - (ptr_t)emu_vram; + txr_base = (txr_base & 0x00fffff8) >> 3; + dst->mode3 |= txr_base; + } + + if(src->fmt.modifier && src->gen.modifier_mode) { + /* If we're affected by a modifier volume, silently promote the header + to the one that is affected by a modifier volume. */ + dst->d1 = dst->mode2; + dst->d2 = dst->mode3; + } + else { + dst->d1 = dst->d2 = 0xffffffff; + } + + dst->d3 = dst->d4 = 0xffffffff; +} + +/* Create a colored polygon context with parameters similar to + the old "ta" function `ta_poly_hdr_col' */ +void pvr_poly_cxt_col(pvr_poly_cxt_t *dst, pvr_list_t list) { + int alpha; + + /* Start off blank */ + memset(dst, 0, sizeof(pvr_poly_cxt_t)); + + /* Fill in a few values */ + dst->list_type = list; + alpha = list > PVR_LIST_OP_MOD; + dst->fmt.color = PVR_CLRFMT_ARGBPACKED; + dst->fmt.uv = PVR_UVFMT_32BIT; + dst->gen.shading = PVR_SHADE_GOURAUD; + dst->depth.comparison = PVR_DEPTHCMP_GREATER; + dst->depth.write = PVR_DEPTHWRITE_ENABLE; + dst->gen.culling = PVR_CULLING_CCW; + dst->txr.enable = PVR_TEXTURE_DISABLE; + + if(!alpha) { + dst->gen.alpha = PVR_ALPHA_DISABLE; + dst->blend.src = PVR_BLEND_ONE; + dst->blend.dst = PVR_BLEND_ZERO; + } + else { + dst->gen.alpha = PVR_ALPHA_ENABLE; + dst->blend.src = PVR_BLEND_SRCALPHA; + dst->blend.dst = PVR_BLEND_INVSRCALPHA; + } + + dst->blend.src_enable = PVR_BLEND_DISABLE; + dst->blend.dst_enable = PVR_BLEND_DISABLE; + dst->gen.fog_type = PVR_FOG_DISABLE; + dst->gen.color_clamp = PVR_CLRCLAMP_DISABLE; +} + +/* Create a textured polygon context with parameters similar to + the old "ta" function `ta_poly_hdr_txr' */ +void pvr_poly_cxt_txr(pvr_poly_cxt_t *dst, pvr_list_t list, + int textureformat, int tw, int th, pvr_ptr_t textureaddr, + int filtering) { + int alpha; + + /* Start off blank */ + memset(dst, 0, sizeof(pvr_poly_cxt_t)); + + /* Fill in a few values */ + dst->list_type = list; + alpha = list > PVR_LIST_OP_MOD; + dst->fmt.color = PVR_CLRFMT_ARGBPACKED; + dst->fmt.uv = PVR_UVFMT_32BIT; + dst->gen.shading = PVR_SHADE_GOURAUD; + dst->depth.comparison = PVR_DEPTHCMP_GREATER; + dst->depth.write = PVR_DEPTHWRITE_ENABLE; + dst->gen.culling = PVR_CULLING_CCW; + dst->txr.enable = PVR_TEXTURE_ENABLE; + + if(!alpha) { + dst->gen.alpha = PVR_ALPHA_DISABLE; + dst->txr.alpha = PVR_TXRALPHA_ENABLE; + dst->blend.src = PVR_BLEND_ONE; + dst->blend.dst = PVR_BLEND_ZERO; + dst->txr.env = PVR_TXRENV_MODULATE; + } + else { + dst->gen.alpha = PVR_ALPHA_ENABLE; + dst->txr.alpha = PVR_TXRALPHA_ENABLE; + dst->blend.src = PVR_BLEND_SRCALPHA; + dst->blend.dst = PVR_BLEND_INVSRCALPHA; + dst->txr.env = PVR_TXRENV_MODULATEALPHA; + } + + dst->blend.src_enable = PVR_BLEND_DISABLE; + dst->blend.dst_enable = PVR_BLEND_DISABLE; + dst->gen.fog_type = PVR_FOG_DISABLE; + dst->gen.color_clamp = PVR_CLRCLAMP_DISABLE; + dst->txr.uv_flip = PVR_UVFLIP_NONE; + dst->txr.uv_clamp = PVR_UVCLAMP_NONE; + dst->txr.filter = filtering; + dst->txr.mipmap_bias = PVR_MIPBIAS_NORMAL; + dst->txr.width = tw; + dst->txr.height = th; + dst->txr.base = textureaddr; + dst->txr.format = textureformat; +} + +/* Create an untextured sprite context. */ +void pvr_sprite_cxt_col(pvr_sprite_cxt_t *dst, pvr_list_t list) { + int alpha; + + /* Start off blank */ + memset(dst, 0, sizeof(pvr_sprite_cxt_t)); + + /* Fill in a few values */ + dst->list_type = list; + alpha = list > PVR_LIST_OP_MOD; + dst->depth.comparison = PVR_DEPTHCMP_GREATER; + dst->depth.write = PVR_DEPTHWRITE_ENABLE; + dst->gen.culling = PVR_CULLING_CCW; + dst->txr.enable = PVR_TEXTURE_DISABLE; + + if(!alpha) { + dst->gen.alpha = PVR_ALPHA_DISABLE; + dst->blend.src = PVR_BLEND_ONE; + dst->blend.dst = PVR_BLEND_ZERO; + } + else { + dst->gen.alpha = PVR_ALPHA_ENABLE; + dst->blend.src = PVR_BLEND_SRCALPHA; + dst->blend.dst = PVR_BLEND_INVSRCALPHA; + } + + dst->blend.src_enable = PVR_BLEND_DISABLE; + dst->blend.dst_enable = PVR_BLEND_DISABLE; + dst->gen.fog_type = PVR_FOG_DISABLE; + dst->gen.color_clamp = PVR_CLRCLAMP_DISABLE; +} + +/* Create a textured sprite context. */ +void pvr_sprite_cxt_txr(pvr_sprite_cxt_t *dst, pvr_list_t list, + int textureformat, int tw, int th, pvr_ptr_t textureaddr, + int filtering) { + int alpha; + + /* Start off blank */ + memset(dst, 0, sizeof(pvr_sprite_cxt_t)); + + /* Fill in a few values */ + dst->list_type = list; + alpha = list > PVR_LIST_OP_MOD; + dst->depth.comparison = PVR_DEPTHCMP_GREATER; + dst->depth.write = PVR_DEPTHWRITE_ENABLE; + dst->gen.culling = PVR_CULLING_CCW; + + if(!alpha) { + dst->gen.alpha = PVR_ALPHA_DISABLE; + dst->txr.alpha = PVR_TXRALPHA_ENABLE; + dst->blend.src = PVR_BLEND_ONE; + dst->blend.dst = PVR_BLEND_ZERO; + dst->txr.env = PVR_TXRENV_MODULATE; + } + else { + dst->gen.alpha = PVR_ALPHA_ENABLE; + dst->txr.alpha = PVR_TXRALPHA_ENABLE; + dst->blend.src = PVR_BLEND_SRCALPHA; + dst->blend.dst = PVR_BLEND_INVSRCALPHA; + dst->txr.env = PVR_TXRENV_MODULATEALPHA; + } + + dst->blend.src_enable = PVR_BLEND_DISABLE; + dst->blend.dst_enable = PVR_BLEND_DISABLE; + dst->gen.fog_type = PVR_FOG_DISABLE; + dst->gen.color_clamp = PVR_CLRCLAMP_DISABLE; + dst->txr.enable = PVR_TEXTURE_ENABLE; + dst->txr.uv_flip = PVR_UVFLIP_NONE; + dst->txr.uv_clamp = PVR_UVCLAMP_NONE; + dst->txr.filter = filtering; + dst->txr.mipmap_bias = PVR_MIPBIAS_NORMAL; + dst->txr.width = tw; + dst->txr.height = th; + dst->txr.base = textureaddr; + dst->txr.format = textureformat; +} + +void pvr_sprite_compile(pvr_sprite_hdr_t *dst, pvr_sprite_cxt_t *src) { + int u, v; + uint32 txr_base; + + /* Basically we just take each parameter, clip it, shift it + into place, and OR it into the final result. */ + + /* The base values for CMD */ + dst->cmd = PVR_CMD_SPRITE; + + if(src->txr.enable == PVR_TEXTURE_ENABLE) + dst->cmd |= 8; + + /* Or in the list type, clipping mode, and UV formats */ + dst->cmd |= (src->list_type << PVR_TA_CMD_TYPE_SHIFT) & PVR_TA_CMD_TYPE_MASK; + dst->cmd |= (PVR_UVFMT_16BIT << PVR_TA_CMD_UVFMT_SHIFT) & PVR_TA_CMD_UVFMT_MASK; + dst->cmd |= (src->gen.clip_mode << PVR_TA_CMD_USERCLIP_SHIFT) & PVR_TA_CMD_USERCLIP_MASK; + dst->cmd |= (src->gen.specular << PVR_TA_CMD_SPECULAR_SHIFT) & PVR_TA_CMD_SPECULAR_MASK; + + /* Polygon mode 1 */ + dst->mode1 = (src->depth.comparison << PVR_TA_PM1_DEPTHCMP_SHIFT) & PVR_TA_PM1_DEPTHCMP_MASK; + dst->mode1 |= (src->gen.culling << PVR_TA_PM1_CULLING_SHIFT) & PVR_TA_PM1_CULLING_MASK; + dst->mode1 |= (src->depth.write << PVR_TA_PM1_DEPTHWRITE_SHIFT) & PVR_TA_PM1_DEPTHWRITE_MASK; + dst->mode1 |= (src->txr.enable << PVR_TA_PM1_TXRENABLE_SHIFT) & PVR_TA_PM1_TXRENABLE_MASK; + + /* Polygon mode 2 */ + dst->mode2 = (src->blend.src << PVR_TA_PM2_SRCBLEND_SHIFT) & PVR_TA_PM2_SRCBLEND_MASK; + dst->mode2 |= (src->blend.dst << PVR_TA_PM2_DSTBLEND_SHIFT) & PVR_TA_PM2_DSTBLEND_MASK; + dst->mode2 |= (src->blend.src_enable << PVR_TA_PM2_SRCENABLE_SHIFT) & PVR_TA_PM2_SRCENABLE_MASK; + dst->mode2 |= (src->blend.dst_enable << PVR_TA_PM2_DSTENABLE_SHIFT) & PVR_TA_PM2_DSTENABLE_MASK; + dst->mode2 |= (src->gen.fog_type << PVR_TA_PM2_FOG_SHIFT) & PVR_TA_PM2_FOG_MASK; + dst->mode2 |= (src->gen.color_clamp << PVR_TA_PM2_CLAMP_SHIFT) & PVR_TA_PM2_CLAMP_MASK; + dst->mode2 |= (src->gen.alpha << PVR_TA_PM2_ALPHA_SHIFT) & PVR_TA_PM2_ALPHA_MASK; + + if(src->txr.enable == PVR_TEXTURE_DISABLE) { + dst->mode3 = 0; + } + else { + dst->mode2 |= (src->txr.alpha << PVR_TA_PM2_TXRALPHA_SHIFT) & PVR_TA_PM2_TXRALPHA_MASK; + dst->mode2 |= (src->txr.uv_flip << PVR_TA_PM2_UVFLIP_SHIFT) & PVR_TA_PM2_UVFLIP_MASK; + dst->mode2 |= (src->txr.uv_clamp << PVR_TA_PM2_UVCLAMP_SHIFT) & PVR_TA_PM2_UVCLAMP_MASK; + dst->mode2 |= (src->txr.filter << PVR_TA_PM2_FILTER_SHIFT) & PVR_TA_PM2_FILTER_MASK; + dst->mode2 |= (src->txr.mipmap_bias << PVR_TA_PM2_MIPBIAS_SHIFT) & PVR_TA_PM2_MIPBIAS_MASK; + dst->mode2 |= (src->txr.env << PVR_TA_PM2_TXRENV_SHIFT) & PVR_TA_PM2_TXRENV_MASK; + + switch(src->txr.width) { + case 8: + u = 0; + break; + case 16: + u = 1; + break; + case 32: + u = 2; + break; + case 64: + u = 3; + break; + case 128: + u = 4; + break; + case 256: + u = 5; + break; + case 512: + u = 6; + break; + case 1024: + u = 7; + break; + default: + assert_msg(0, "Invalid texture U size"); + u = 0; + break; + } + + switch(src->txr.height) { + case 8: + v = 0; + break; + case 16: + v = 1; + break; + case 32: + v = 2; + break; + case 64: + v = 3; + break; + case 128: + v = 4; + break; + case 256: + v = 5; + break; + case 512: + v = 6; + break; + case 1024: + v = 7; + break; + default: + assert_msg(0, "Invalid texture V size"); + v = 0; + break; + } + + dst->mode2 |= (u << PVR_TA_PM2_USIZE_SHIFT) & PVR_TA_PM2_USIZE_MASK; + dst->mode2 |= (v << PVR_TA_PM2_VSIZE_SHIFT) & PVR_TA_PM2_VSIZE_MASK; + + /* Polygon mode 3 */ + dst->mode3 = (src->txr.mipmap << PVR_TA_PM3_MIPMAP_SHIFT) & PVR_TA_PM3_MIPMAP_MASK; + dst->mode3 |= (src->txr.format << PVR_TA_PM3_TXRFMT_SHIFT) & PVR_TA_PM3_TXRFMT_MASK; + + txr_base = (ptr_t)src->txr.base-(ptr_t)emu_vram; + txr_base = (txr_base & 0x00fffff8) >> 3; + dst->mode3 |= txr_base; + } + + dst->argb = 0xFFFFFFFF; + dst->oargb = 0x00000000; +} + +void pvr_mod_compile(pvr_mod_hdr_t *dst, pvr_list_t list, uint32 mode, + uint32 cull) { + dst->cmd = PVR_CMD_MODIFIER; + dst->cmd |= (list << PVR_TA_CMD_TYPE_SHIFT) & PVR_TA_CMD_TYPE_MASK; + + dst->mode1 = (mode << PVR_TA_PM1_MODIFIERINST_SHIFT) & PVR_TA_PM1_MODIFIERINST_MASK; + dst->mode1 |= (cull << PVR_TA_PM1_CULLING_SHIFT) & PVR_TA_PM1_CULLING_MASK; + + dst->d1 = dst->d2 = dst->d3 = dst->d4 = dst->d5 = dst->d6 = 0; +} + +/* Compile a polygon context into a polygon header that is affected by + modifier volumes */ +void pvr_poly_mod_compile(pvr_poly_mod_hdr_t *dst, pvr_poly_cxt_t *src) { + int u, v; + uint32 txr_base; + + /* Basically we just take each parameter, clip it, shift it + into place, and OR it into the final result. */ + + /* The base values for CMD */ + dst->cmd = PVR_CMD_POLYHDR; + + if(src->txr.enable == PVR_TEXTURE_ENABLE) + dst->cmd |= 8; + + /* Or in the list type, shading type, color and UV formats */ + dst->cmd |= (src->list_type << PVR_TA_CMD_TYPE_SHIFT) & PVR_TA_CMD_TYPE_MASK; + dst->cmd |= (src->fmt.color << PVR_TA_CMD_CLRFMT_SHIFT) & PVR_TA_CMD_CLRFMT_MASK; + dst->cmd |= (src->gen.shading << PVR_TA_CMD_SHADE_SHIFT) & PVR_TA_CMD_SHADE_MASK; + dst->cmd |= (src->fmt.uv << PVR_TA_CMD_UVFMT_SHIFT) & PVR_TA_CMD_UVFMT_MASK; + dst->cmd |= (src->gen.clip_mode << PVR_TA_CMD_USERCLIP_SHIFT) & PVR_TA_CMD_USERCLIP_MASK; + dst->cmd |= (src->fmt.modifier << PVR_TA_CMD_MODIFIER_SHIFT) & PVR_TA_CMD_MODIFIER_MASK; + dst->cmd |= (src->gen.modifier_mode << PVR_TA_CMD_MODIFIERMODE_SHIFT) & PVR_TA_CMD_MODIFIERMODE_MASK; + dst->cmd |= (src->gen.specular << PVR_TA_CMD_SPECULAR_SHIFT) & PVR_TA_CMD_SPECULAR_MASK; + + /* Polygon mode 1 */ + dst->mode1 = (src->depth.comparison << PVR_TA_PM1_DEPTHCMP_SHIFT) & PVR_TA_PM1_DEPTHCMP_MASK; + dst->mode1 |= (src->gen.culling << PVR_TA_PM1_CULLING_SHIFT) & PVR_TA_PM1_CULLING_MASK; + dst->mode1 |= (src->depth.write << PVR_TA_PM1_DEPTHWRITE_SHIFT) & PVR_TA_PM1_DEPTHWRITE_MASK; + dst->mode1 |= (src->txr.enable << PVR_TA_PM1_TXRENABLE_SHIFT) & PVR_TA_PM1_TXRENABLE_MASK; + + /* Polygon mode 2 (outside volume) */ + dst->mode2_0 = (src->blend.src << PVR_TA_PM2_SRCBLEND_SHIFT) & PVR_TA_PM2_SRCBLEND_MASK; + dst->mode2_0 |= (src->blend.dst << PVR_TA_PM2_DSTBLEND_SHIFT) & PVR_TA_PM2_DSTBLEND_MASK; + dst->mode2_0 |= (src->blend.src_enable << PVR_TA_PM2_SRCENABLE_SHIFT) & PVR_TA_PM2_SRCENABLE_MASK; + dst->mode2_0 |= (src->blend.dst_enable << PVR_TA_PM2_DSTENABLE_SHIFT) & PVR_TA_PM2_DSTENABLE_MASK; + dst->mode2_0 |= (src->gen.fog_type << PVR_TA_PM2_FOG_SHIFT) & PVR_TA_PM2_FOG_MASK; + dst->mode2_0 |= (src->gen.color_clamp << PVR_TA_PM2_CLAMP_SHIFT) & PVR_TA_PM2_CLAMP_MASK; + dst->mode2_0 |= (src->gen.alpha << PVR_TA_PM2_ALPHA_SHIFT) & PVR_TA_PM2_ALPHA_MASK; + + if(src->txr.enable == PVR_TEXTURE_DISABLE) { + dst->mode3_0 = 0; + } + else { + dst->mode2_0 |= (src->txr.alpha << PVR_TA_PM2_TXRALPHA_SHIFT) & PVR_TA_PM2_TXRALPHA_MASK; + dst->mode2_0 |= (src->txr.uv_flip << PVR_TA_PM2_UVFLIP_SHIFT) & PVR_TA_PM2_UVFLIP_MASK; + dst->mode2_0 |= (src->txr.uv_clamp << PVR_TA_PM2_UVCLAMP_SHIFT) & PVR_TA_PM2_UVCLAMP_MASK; + dst->mode2_0 |= (src->txr.filter << PVR_TA_PM2_FILTER_SHIFT) & PVR_TA_PM2_FILTER_MASK; + dst->mode2_0 |= (src->txr.mipmap_bias << PVR_TA_PM2_MIPBIAS_SHIFT) & PVR_TA_PM2_MIPBIAS_MASK; + dst->mode2_0 |= (src->txr.env << PVR_TA_PM2_TXRENV_SHIFT) & PVR_TA_PM2_TXRENV_MASK; + + switch(src->txr.width) { + case 8: + u = 0; + break; + case 16: + u = 1; + break; + case 32: + u = 2; + break; + case 64: + u = 3; + break; + case 128: + u = 4; + break; + case 256: + u = 5; + break; + case 512: + u = 6; + break; + case 1024: + u = 7; + break; + default: + assert_msg(0, "Invalid texture U size"); + u = 0; + break; + } + + switch(src->txr.height) { + case 8: + v = 0; + break; + case 16: + v = 1; + break; + case 32: + v = 2; + break; + case 64: + v = 3; + break; + case 128: + v = 4; + break; + case 256: + v = 5; + break; + case 512: + v = 6; + break; + case 1024: + v = 7; + break; + default: + assert_msg(0, "Invalid texture V size"); + v = 0; + break; + } + + dst->mode2_0 |= (u << PVR_TA_PM2_USIZE_SHIFT) & PVR_TA_PM2_USIZE_MASK; + dst->mode2_0 |= (v << PVR_TA_PM2_VSIZE_SHIFT) & PVR_TA_PM2_VSIZE_MASK; + + /* Polygon mode 3 (outside volume) */ + dst->mode3_0 = (src->txr.mipmap << PVR_TA_PM3_MIPMAP_SHIFT) & PVR_TA_PM3_MIPMAP_MASK; + dst->mode3_0 |= (src->txr.format << PVR_TA_PM3_TXRFMT_SHIFT) & PVR_TA_PM3_TXRFMT_MASK; + + /* Convert the texture address */ + txr_base = (ptr_t)src->txr.base-(ptr_t)emu_vram; + txr_base = (txr_base & 0x00fffff8) >> 3; + dst->mode3_0 |= txr_base; + } + + /* Polygon mode 2 (within volume) */ + dst->mode2_1 = (src->blend.src2 << PVR_TA_PM2_SRCBLEND_SHIFT) & PVR_TA_PM2_SRCBLEND_MASK; + dst->mode2_1 |= (src->blend.dst2 << PVR_TA_PM2_DSTBLEND_SHIFT) & PVR_TA_PM2_DSTBLEND_MASK; + dst->mode2_1 |= (src->blend.src_enable2 << PVR_TA_PM2_SRCENABLE_SHIFT) & PVR_TA_PM2_SRCENABLE_MASK; + dst->mode2_1 |= (src->blend.dst_enable2 << PVR_TA_PM2_DSTENABLE_SHIFT) & PVR_TA_PM2_DSTENABLE_MASK; + dst->mode2_1 |= (src->gen.fog_type2 << PVR_TA_PM2_FOG_SHIFT) & PVR_TA_PM2_FOG_MASK; + dst->mode2_1 |= (src->gen.color_clamp2 << PVR_TA_PM2_CLAMP_SHIFT) & PVR_TA_PM2_CLAMP_MASK; + dst->mode2_1 |= (src->gen.alpha2 << PVR_TA_PM2_ALPHA_SHIFT) & PVR_TA_PM2_ALPHA_MASK; + + if(src->txr2.enable == PVR_TEXTURE_DISABLE) { + dst->mode3_1 = 0; + } + else { + dst->mode2_1 |= (src->txr2.alpha << PVR_TA_PM2_TXRALPHA_SHIFT) & PVR_TA_PM2_TXRALPHA_MASK; + dst->mode2_1 |= (src->txr2.uv_flip << PVR_TA_PM2_UVFLIP_SHIFT) & PVR_TA_PM2_UVFLIP_MASK; + dst->mode2_1 |= (src->txr2.uv_clamp << PVR_TA_PM2_UVCLAMP_SHIFT) & PVR_TA_PM2_UVCLAMP_MASK; + dst->mode2_1 |= (src->txr2.filter << PVR_TA_PM2_FILTER_SHIFT) & PVR_TA_PM2_FILTER_MASK; + dst->mode2_1 |= (src->txr2.mipmap_bias << PVR_TA_PM2_MIPBIAS_SHIFT) & PVR_TA_PM2_MIPBIAS_MASK; + dst->mode2_1 |= (src->txr2.env << PVR_TA_PM2_TXRENV_SHIFT) & PVR_TA_PM2_TXRENV_MASK; + + switch(src->txr2.width) { + case 8: + u = 0; + break; + case 16: + u = 1; + break; + case 32: + u = 2; + break; + case 64: + u = 3; + break; + case 128: + u = 4; + break; + case 256: + u = 5; + break; + case 512: + u = 6; + break; + case 1024: + u = 7; + break; + default: + assert_msg(0, "Invalid texture U size"); + u = 0; + break; + } + + switch(src->txr2.height) { + case 8: + v = 0; + break; + case 16: + v = 1; + break; + case 32: + v = 2; + break; + case 64: + v = 3; + break; + case 128: + v = 4; + break; + case 256: + v = 5; + break; + case 512: + v = 6; + break; + case 1024: + v = 7; + break; + default: + assert_msg(0, "Invalid texture V size"); + v = 0; + break; + } + + dst->mode2_1 |= (u << PVR_TA_PM2_USIZE_SHIFT) & PVR_TA_PM2_USIZE_MASK; + dst->mode2_1 |= (v << PVR_TA_PM2_VSIZE_SHIFT) & PVR_TA_PM2_VSIZE_MASK; + + /* Polygon mode 3 (within volume) */ + dst->mode3_1 = (src->txr2.mipmap << PVR_TA_PM3_MIPMAP_SHIFT) & PVR_TA_PM3_MIPMAP_MASK; + dst->mode3_1 |= (src->txr2.format << PVR_TA_PM3_TXRFMT_SHIFT) & PVR_TA_PM3_TXRFMT_MASK; + + /* Convert the texture address */ + txr_base = (ptr_t)src->txr2.base-(ptr_t)emu_vram; + txr_base = (txr_base & 0x00fffff8) >> 3; + dst->mode3_1 |= txr_base; + } + + dst->d1 = dst->d2 = 0xffffffff; +} + +/* Create a colored polygon context for polygons affected by modifier volumes */ +void pvr_poly_cxt_col_mod(pvr_poly_cxt_t *dst, pvr_list_t list) { + int alpha; + + /* Start off blank */ + memset(dst, 0, sizeof(pvr_poly_cxt_t)); + + /* Fill in a few values */ + dst->list_type = list; + alpha = list > PVR_LIST_OP_MOD; + dst->fmt.color = PVR_CLRFMT_ARGBPACKED; + dst->fmt.uv = PVR_UVFMT_32BIT; + dst->gen.shading = PVR_SHADE_GOURAUD; + dst->depth.comparison = PVR_DEPTHCMP_GREATER; + dst->depth.write = PVR_DEPTHWRITE_ENABLE; + dst->gen.culling = PVR_CULLING_CCW; + dst->fmt.modifier = PVR_MODIFIER_ENABLE; + dst->gen.modifier_mode = PVR_MODIFIER_NORMAL; + dst->txr.enable = PVR_TEXTURE_DISABLE; + dst->txr2.enable = PVR_TEXTURE_DISABLE; + + if(!alpha) { + dst->gen.alpha = PVR_ALPHA_DISABLE; + dst->blend.src = PVR_BLEND_ONE; + dst->blend.dst = PVR_BLEND_ZERO; + dst->gen.alpha2 = PVR_ALPHA_DISABLE; + dst->blend.src2 = PVR_BLEND_ONE; + dst->blend.dst2 = PVR_BLEND_ZERO; + } + else { + dst->gen.alpha = PVR_ALPHA_ENABLE; + dst->blend.src = PVR_BLEND_SRCALPHA; + dst->blend.dst = PVR_BLEND_INVSRCALPHA; + dst->gen.alpha2 = PVR_ALPHA_ENABLE; + dst->blend.src2 = PVR_BLEND_SRCALPHA; + dst->blend.dst2 = PVR_BLEND_INVSRCALPHA; + } + + dst->blend.src_enable = PVR_BLEND_DISABLE; + dst->blend.dst_enable = PVR_BLEND_DISABLE; + dst->gen.fog_type = PVR_FOG_DISABLE; + dst->gen.color_clamp = PVR_CLRCLAMP_DISABLE; + dst->blend.src_enable2 = PVR_BLEND_DISABLE; + dst->blend.dst_enable2 = PVR_BLEND_DISABLE; + dst->gen.fog_type2 = PVR_FOG_DISABLE; + dst->gen.color_clamp2 = PVR_CLRCLAMP_DISABLE; +} + +/* Create a textured polygon context for polygons affected by modifier + volumes */ +void pvr_poly_cxt_txr_mod(pvr_poly_cxt_t *dst, pvr_list_t list, + int textureformat, int tw, int th, + pvr_ptr_t textureaddr, int filtering, + int textureformat2, int tw2, int th2, + pvr_ptr_t textureaddr2, int filtering2) { + int alpha; + + /* Start off blank */ + memset(dst, 0, sizeof(pvr_poly_cxt_t)); + + /* Fill in a few values */ + dst->list_type = list; + alpha = list > PVR_LIST_OP_MOD; + dst->fmt.color = PVR_CLRFMT_ARGBPACKED; + dst->fmt.uv = PVR_UVFMT_32BIT; + dst->gen.shading = PVR_SHADE_GOURAUD; + dst->depth.comparison = PVR_DEPTHCMP_GREATER; + dst->depth.write = PVR_DEPTHWRITE_ENABLE; + dst->gen.culling = PVR_CULLING_CCW; + dst->fmt.modifier = PVR_MODIFIER_ENABLE; + dst->gen.modifier_mode = PVR_MODIFIER_NORMAL; + dst->txr.enable = PVR_TEXTURE_ENABLE; + dst->txr2.enable = PVR_TEXTURE_ENABLE; + + if(!alpha) { + dst->gen.alpha = PVR_ALPHA_DISABLE; + dst->txr.alpha = PVR_TXRALPHA_ENABLE; + dst->blend.src = PVR_BLEND_ONE; + dst->blend.dst = PVR_BLEND_ZERO; + dst->txr.env = PVR_TXRENV_MODULATE; + dst->gen.alpha2 = PVR_ALPHA_DISABLE; + dst->txr2.alpha = PVR_TXRALPHA_ENABLE; + dst->blend.src2 = PVR_BLEND_ONE; + dst->blend.dst2 = PVR_BLEND_ZERO; + dst->txr2.env = PVR_TXRENV_MODULATE; + } + else { + dst->gen.alpha = PVR_ALPHA_ENABLE; + dst->txr.alpha = PVR_TXRALPHA_ENABLE; + dst->blend.src = PVR_BLEND_SRCALPHA; + dst->blend.dst = PVR_BLEND_INVSRCALPHA; + dst->txr.env = PVR_TXRENV_MODULATEALPHA; + dst->gen.alpha2 = PVR_ALPHA_ENABLE; + dst->txr2.alpha = PVR_TXRALPHA_ENABLE; + dst->blend.src2 = PVR_BLEND_SRCALPHA; + dst->blend.dst2 = PVR_BLEND_INVSRCALPHA; + dst->txr2.env = PVR_TXRENV_MODULATEALPHA; + } + + dst->blend.src_enable = PVR_BLEND_DISABLE; + dst->blend.dst_enable = PVR_BLEND_DISABLE; + dst->gen.fog_type = PVR_FOG_DISABLE; + dst->gen.color_clamp = PVR_CLRCLAMP_DISABLE; + dst->txr.uv_flip = PVR_UVFLIP_NONE; + dst->txr.uv_clamp = PVR_UVCLAMP_NONE; + dst->txr.filter = filtering; + dst->txr.mipmap_bias = PVR_MIPBIAS_NORMAL; + dst->txr.width = tw; + dst->txr.height = th; + dst->txr.base = textureaddr; + dst->txr.format = textureformat; + dst->blend.src_enable2 = PVR_BLEND_DISABLE; + dst->blend.dst_enable2 = PVR_BLEND_DISABLE; + dst->gen.fog_type2 = PVR_FOG_DISABLE; + dst->gen.color_clamp2 = PVR_CLRCLAMP_DISABLE; + dst->txr2.uv_flip = PVR_UVFLIP_NONE; + dst->txr2.uv_clamp = PVR_UVCLAMP_NONE; + dst->txr2.filter = filtering2; + dst->txr2.mipmap_bias = PVR_MIPBIAS_NORMAL; + dst->txr2.width = tw2; + dst->txr2.height = th2; + dst->txr2.base = textureaddr2; + dst->txr2.format = textureformat2; +} diff --git a/vendor/koshle/hlepvr_scene.cpp b/vendor/koshle/hlepvr_scene.cpp new file mode 100644 index 00000000..ca619f90 --- /dev/null +++ b/vendor/koshle/hlepvr_scene.cpp @@ -0,0 +1,376 @@ +/* KallistiOS ##version## + + pvr_scene.c + Copyright (C)2002,2004 Megan Potter + + */ + +#define dbglog(channel, ...) printf(__VA_ARGS__) +#define assert_msg(cond, txt) assert(cond && txt) +#include "emu/emu.h" + +#include +#include +#include +// #inclu'de +#include "dc/pvr.h" +#include "dc/sq.h" +#include "pvr_internal.h" +void pvr_ta_data(void*, int); + +/* + + Scene rendering + + Please see ../../include/dc/pvr.h for more info on this API! + +*/ + +void * pvr_set_vertbuf(pvr_list_t list, void * buffer, int len) { + void * oldbuf; + + // Make sure we have global DMA usage enabled. The DMA can still + // be used in other situations, but the user must take care of + // that themselves. + assert(pvr_state.dma_mode); + + // Make sure it's a valid list. + assert(list < PVR_OPB_COUNT); + + // Make sure it's an _enabled_ list. + assert(pvr_state.lists_enabled & (1 << list)); + + // Make sure the buffer parameters are valid. + assert(!(((ptr_t)buffer) & 31)); + assert(!(len & 63)); + + // Save the old value. + oldbuf = pvr_state.dma_buffers[0].base[list]; + + // Write new values. + pvr_state.dma_buffers[0].base[list] = (uint8 *)buffer; + pvr_state.dma_buffers[0].ptr[list] = 0; + pvr_state.dma_buffers[0].size[list] = len / 2; + pvr_state.dma_buffers[0].ready = 0; + pvr_state.dma_buffers[1].base[list] = ((uint8 *)buffer) + len / 2; + pvr_state.dma_buffers[1].ptr[list] = 0; + pvr_state.dma_buffers[1].size[list] = len / 2; + pvr_state.dma_buffers[1].ready = 0; + + return oldbuf; +} + +void * pvr_vertbuf_tail(pvr_list_t list) { + uint8 * bufbase; + + // Check the validity of the request. + assert(list < PVR_OPB_COUNT); + assert(pvr_state.dma_mode); + + // Get the buffer base. + bufbase = pvr_state.dma_buffers[pvr_state.ram_target].base[list]; + assert(bufbase); + + // Return the current end of the buffer. + return bufbase + pvr_state.dma_buffers[pvr_state.ram_target].ptr[list]; +} + +void pvr_vertbuf_written(pvr_list_t list, uint32 amt) { + uint32 val; + + // Check the validity of the request. + assert(list < PVR_OPB_COUNT); + assert(pvr_state.dma_mode); + + // Change the current end of the buffer. + val = pvr_state.dma_buffers[pvr_state.ram_target].ptr[list]; + val += amt; + assert(val < pvr_state.dma_buffers[pvr_state.ram_target].size[list]); + pvr_state.dma_buffers[pvr_state.ram_target].ptr[list] = val; +} + +/* Begin collecting data for a frame of 3D output to the off-screen + frame buffer */ +void pvr_scene_begin(void) { + emu_pump_events(); + + int i; + + // Get general stuff ready. + pvr_state.list_reg_open = -1; + + // Clear these out in case we're using DMA. + if(pvr_state.dma_mode) { + for(i = 0; i < PVR_OPB_COUNT; i++) { + pvr_state.dma_buffers[pvr_state.ram_target].ptr[i] = 0; + } + + pvr_sync_stats(PVR_SYNC_BUFSTART); + // DBG(("pvr_scene_begin(dma -> %d)\n", pvr_state.ram_target)); + } + else { + pvr_state.lists_closed = 0; + + // We assume registration is starting immediately + pvr_sync_stats(PVR_SYNC_REGSTART); + } +} + +/* Begin collecting data for a frame of 3D output to the specified texture; + pass in the size of the buffer in rx and ry, and the return values in + rx and ry will be the size actually used (if changed). Note that + currently this only supports screen-sized output! */ +/* Currently the resize functionality is not implemented, so make sure that + rx and ry are appropriate (i.e. *rx = 1024 and *ry = 512 for 640x480). + Also, note that this probably won't work with DMA mode for now... */ +void pvr_scene_begin_txr(pvr_ptr_t txr, uint32 *rx, uint32 *ry) { + int buf = pvr_state.view_target ^ 1; + (void)ry; + + /* For the most part, this isn't very much different than the normal render setup. + And, yes, if you remember KOS 1.1.6, this pretty much looks similar to what was + there. I'm quite uncreative with my variable naming ;) */ + // Mark us as rendering to a texture + pvr_state.to_texture[buf] = 1; + + // Set the render pitch up + pvr_state.to_txr_rp[buf] = (*rx) * 2 / 8; + + // Set the output address + pvr_state.to_txr_addr[buf] = (ptr_t)(txr) - PVR_RAM_INT_BASE; + + pvr_scene_begin(); +} + +/* Begin collecting data for the given list type. Lists do not have to be + submitted in any particular order, but all types of a list must be + submitted at once. If the given list has already been closed, then an + error (-1) is returned. */ +int pvr_list_begin(pvr_list_t list) { + /* Check to make sure we can do this */ +#ifndef NDEBUG + if(!pvr_state.dma_mode && pvr_state.lists_closed & (1 << list)) { + dbglog(DBG_WARNING, "pvr_list_begin: attempt to open already closed list\n"); + return -1; + } + +#endif /* !NDEBUG */ + + /* If we already had a list open, close it first */ + if(pvr_state.list_reg_open != -1 && pvr_state.list_reg_open != (int)list) + pvr_list_finish(); + + /* Ok, set the flag */ + pvr_state.list_reg_open = list; + + return 0; +} + +/* End collecting data for the current list type. Lists can never be opened + again within a single frame once they have been closed. Thus submitting + a primitive that belongs in a closed list is considered an error. Closing + a list that is already closed is also an error (-1). Note that if you open + a list but do not submit any primitives, this causes a hardware error. For + simplicity we just always submit a blank primitive. */ +int pvr_list_finish(void) { + /* Check to make sure we can do this */ +#ifndef NDEBUG + if(!pvr_state.dma_mode && pvr_state.list_reg_open == -1) { + dbglog(DBG_WARNING, "pvr_list_finish: attempt to close unopened list\n"); + return -1; + } + +#endif /* !NDEBUG */ + + if(!pvr_state.dma_mode) { + /* Release Store Queues if they are used */ + if(pvr_state.dr_used) { + pvr_dr_finish(); + } + + /* In case we haven't sent anything in this list, send a dummy */ + pvr_blank_polyhdr(pvr_state.list_reg_open); + + /* Set the flags */ + pvr_state.lists_closed |= (1 << pvr_state.list_reg_open); + + /* Send an EOL marker */ + // pvr_sq_set32((void *)0, 0, 32, PVR_DMA_TA); + uint8_t zeroes[32] = { 0 }; + pvr_ta_data(zeroes, 32); + } + + pvr_state.list_reg_open = -1; + + return 0; +} + +int pvr_prim(void * data, int size) { + /* Check to make sure we can do this */ +#ifndef NDEBUG + if(pvr_state.list_reg_open == -1) { + dbglog(DBG_WARNING, "pvr_prim: attempt to submit to unopened list\n"); + return -1; + } + +#endif /* !NDEBUG */ + + if(!pvr_state.dma_mode) { + /* Send the data */ + //pvr_sq_load((void *)0, data, size, PVR_DMA_TA); + pvr_ta_data(data, size); + } + else { + return pvr_list_prim(pvr_state.list_reg_open, data, size); + } + + return 0; +} + +int pvr_list_prim(pvr_list_t list, void * data, int size) { + volatile pvr_dma_buffers_t * b; + + b = pvr_state.dma_buffers + pvr_state.ram_target; + assert(b->base[list]); + + assert(!(size & 31)); + + memcpy(b->base[list] + b->ptr[list], data, size); + b->ptr[list] += size; + assert(b->ptr[list] <= b->size[list]); + + return 0; +} + +void pvr_dr_init(pvr_dr_state_t *vtx_buf_ptr) { + *vtx_buf_ptr = 0; + sq_lock((void *)PVR_TA_INPUT); + pvr_state.dr_used = 1; +} + +void pvr_dr_finish(void) { + pvr_state.dr_used = 0; + sq_unlock(); +} + +static uint8_t dr_target[32 * 1024 * 1024]; + +pvr_vertex_t *pvr_dr_target(pvr_dr_state_t &state) { + return reinterpret_cast(dr_target); +} + +void pvr_dr_commit(void* addr) { + pvr_ta_data(addr, 32); +} + + +int pvr_list_flush(pvr_list_t list) { + (void)list; + + assert_msg(0, "not implemented yet"); + return -1; +} + +/* Call this after you have finished submitting all data for a frame; once + this has been called, you can not submit any more data until one of the + pvr_scene_begin() functions is called again. An error (-1) is returned if + you have not started a scene already. */ +int pvr_scene_finish(void) { + int i, o; + volatile pvr_dma_buffers_t * b; + + /* Release Store Queues if they are used */ + if(pvr_state.dr_used) { + pvr_dr_finish(); + } + + // If we're in DMA mode, then this works a little differently... + if(pvr_state.dma_mode) { + assert_msg(false, "dma mode not supported"); + // DBG(("pvr_scene_finish(dma -> %d)\n", pvr_state.ram_target)); + // If any enabled lists are empty, fill them with a blank polyhdr. Also + // add a zero-marker to the end of each list. + b = pvr_state.dma_buffers + pvr_state.ram_target; + + for(i = 0; i < PVR_OPB_COUNT; i++) { + // Not enabled -> skip + if(!(pvr_state.lists_enabled & (1 << i))) + continue; + + // Make sure there's at least one primitive in each. + if(b->ptr[i] == 0) { + pvr_blank_polyhdr_buf(i, (pvr_poly_hdr_t*)(b->base[i])); + b->ptr[i] += 32; + } + + // Put a zero-marker on the end. + memset(b->base[i] + b->ptr[i], 0, 32); + b->ptr[i] += 32; + + // Verify that there is no overrun. + assert(b->ptr[i] <= b->size[i]); + } + + assert_msg(false, "this has been disabled"); + // // Flip buffers and mark them complete. + // o = irq_disable(); + // pvr_state.dma_buffers[pvr_state.ram_target].ready = 1; + // pvr_state.ram_target ^= 1; + // irq_restore(o); + + pvr_sync_stats(PVR_SYNC_BUFDONE); + } + else { + /* If a list was open, close it */ + if(pvr_state.list_reg_open != -1) + pvr_list_finish(); + + /* If any lists weren't submitted, then submit blank ones now */ + for(i = 0; i < PVR_OPB_COUNT; i++) { + if((pvr_state.lists_enabled & (1 << i)) + && (!(pvr_state.lists_closed & (1 << i)))) { + pvr_list_begin(i); + pvr_blank_polyhdr(i); + pvr_list_finish(); + } + } + } + + /* Ok, now it's just a matter of waiting for the interrupt... */ + return 0; +} + +int pvr_wait_ready(void) { + int t; + + assert(pvr_state.valid); + + t = sem_wait_timed((semaphore_t *)&pvr_state.ready_sem, 100); + + if(t < 0) { +#if 0 + dbglog(DBG_WARNING, "pvr_wait_ready: timed out\n"); + printf("VERTBUF_ADDR: %08lx\n", PVR_GET(PVR_ISP_VERTBUF_ADDR)); + printf("TILEMAT_ADDR: %08lx\n", PVR_GET(PVR_ISP_TILEMAT_ADDR)); + printf("OPB_START: %08lx\n", PVR_GET(PVR_TA_OPB_START)); + printf("OPB_END: %08lx\n", PVR_GET(PVR_TA_OPB_END)); + printf("OPB_POS: %08lx\n", PVR_GET(PVR_TA_OPB_POS)); + printf("OPB_INIT: %08lx\n", PVR_GET(PVR_TA_OPB_INIT)); + printf("VERTBUF_START: %08lx\n", PVR_GET(PVR_TA_VERTBUF_START)); + printf("VERTBUF_END: %08lx\n", PVR_GET(PVR_TA_VERTBUF_END)); + printf("VERTBUF_POS: %08lx\n", PVR_GET(PVR_TA_VERTBUF_POS)); +#endif + return -1; + } + + return 0; +} + +int pvr_check_ready(void) { + assert(pvr_state.valid); + + if(sem_count((semaphore_t *)&pvr_state.ready_sem) > 0) + return 0; + else + return -1; +} diff --git a/vendor/koshle/kos/dbglog.h b/vendor/koshle/kos/dbglog.h new file mode 100644 index 00000000..e8968c6a --- /dev/null +++ b/vendor/koshle/kos/dbglog.h @@ -0,0 +1,39 @@ +#pragma once + +/** \defgroup logging Logging + \brief KOS's Logging API + \ingroup debugging +*/ + +/** \brief Kernel debugging printf. + \ingroup logging + + This function is similar to printf(), but filters its output through a log + level check before being printed. This way, you can set the level of debug + info you want to see (or want your users to see). + + \param level The level of importance of this message. + \param fmt Message format string. + \param ... Format arguments + \see dbglog_levels +*/ +#define dbglog(level, fmt, ...) printf(fmt, ## __VA_ARGS__) + +/** \defgroup dbglog_levels Log Levels + \brief dbglog severity levels + \ingroup logging + + This is the list of levels that are allowed to be passed into the dbglog() + function, representing different levels of importance. + + @{ +*/ +#define DBG_DEAD 0 /**< \brief The system is dead */ +#define DBG_CRITICAL 1 /**< \brief A critical error message */ +#define DBG_ERROR 2 /**< \brief A normal error message */ +#define DBG_WARNING 3 /**< \brief Potential problem */ +#define DBG_NOTICE 4 /**< \brief Normal but significant */ +#define DBG_INFO 5 /**< \brief Informational messages */ +#define DBG_DEBUG 6 /**< \brief User debug messages */ +#define DBG_KDEBUG 7 /**< \brief Kernel debug messages */ +/** @} */ \ No newline at end of file diff --git a/vendor/koshle/kos/img.h b/vendor/koshle/kos/img.h new file mode 100644 index 00000000..9a2ba785 --- /dev/null +++ b/vendor/koshle/kos/img.h @@ -0,0 +1,183 @@ +/* KallistiOS ##version## + + kos/img.h + Copyright (C) 2002 Megan Potter + +*/ + +#pragma once + +/** \file kos/img.h + \brief Platform-independent image type. + \ingroup video_img + + This file provides a platform-independent image type that is designed to + hold any sort of textures or other image data. This type contains a very + basic description of the image data (width, height, pixel format), as well + as the image data itself. + + All of the image-loading libraries in kos-ports should provide a function + to load the image data into one of these types. + + \author Megan Potter +*/ + +#include "dc_hle_types.h" + +/** \defgroup video_img Images + \brief Platform-independent image representation + \ingroup video +*/ + +/** \brief Platform-indpendent image type. + \ingroup video_img + + You can use this type for textures or whatever you feel it's appropriate + for. "width" and "height" are as you would expect. "format" has a lower-half + which is platform-independent and used to basically describe the contained + data; the upper-half is platform-dependent and can hold anything (so AND it + off if you only want the bottom part). + + Note that in some of the more obscure formats (like the paletted formats) + the data interpretation may be platform dependent. Thus we also provide a + data length field. + + \headerfile kos/img.h +*/ +typedef struct kos_img { + void *data; /**< \brief Image data in the specified format. */ + uint32 w; /**< \brief Width of the image. */ + uint32 h; /**< \brief Height of the image. */ + uint32 fmt; /**< \brief Format of the image data. + \see kos_img_fmts + \see kos_img_fmt_macros */ + uint32 byte_count; /**< \brief Length of the image data, in bytes. */ +} kos_img_t; + +/** \defgroup video_img_fmt Format + \brief Video image formats + \ingroup video_img +*/ + +/** \defgroup kos_img_fmt_macros Accessors + \brief Macros for accessing kos_image_t::fmt + \ingroup video_img_fmt + + These macros provide easy access to the fmt field of a kos_img_t object. + + @{ +*/ +/** \brief Read the platform-independent half of the format. + + This macro masks the format of a kos_img_t to give you just the lower half + of the value, which contains the platform-independent half of the format. + + \param x An image format (fmt field of a kos_img_t). + + \return The platform-independent half of the format. +*/ +#define KOS_IMG_FMT_I(x) ((x) & 0xffff) + +/** \brief Read the platform-specific half of the format. + + This macro masks the format of a kos_img_t to give you just the upper half + of the value, which contains the platform-specific half of the format. + + \param x An image format (fmt field of a kos_img_t). + + \return The platform-specific half of the format. +*/ +#define KOS_IMG_FMT_D(x) (((x) >> 16) & 0xffff) + +/** \brief Build a format value from a platform-independent half and a + platform-specific half of the value. + + This macro combines the platform-independent and platform-specific portions + of an image format into a value suitable for storing as the fmt field of a + kos_img_t object. + + \param i The platform-independent half of the format. + \param d The platform-specific half of the format. This should + not be pre-shifted. + + \return A complete image format value, suitable for placing in + the fmt variable of a kos_img_t. +*/ +#define KOS_IMG_FMT(i, d) ( ((i) & 0xffff) | (((d) & 0xffff) << 16) ) + +/** @} */ + +/** \defgroup kos_img_fmts Types + \brief Video image format types + \ingroup video_img_fmt + + This is the list of platform-independent image types that can be used as the + lower-half of the fmt value for a kos_img_t. + + @{ +*/ +/** \brief Undefined or uninitialized format. */ +#define KOS_IMG_FMT_NONE 0x00 + +/** \brief 24-bpp interleaved R/G/B bytes. */ +#define KOS_IMG_FMT_RGB888 0x01 + +/** \brief 32-bpp interleaved A/R/G/B bytes. */ +#define KOS_IMG_FMT_ARGB8888 0x02 + +/** \brief 16-bpp interleaved R (5 bits), G (6 bits), B (5 bits). */ +#define KOS_IMG_FMT_RGB565 0x03 + +/** \brief 16-bpp interleaved A/R/G/B (4 bits each). */ +#define KOS_IMG_FMT_ARGB4444 0x04 + +/** \brief 16-bpp interleaved A (1 bit), R (5 bits), G (5 bits), B (5 bits). + \note This can also be used for RGB555 (with the top bit ignored). */ +#define KOS_IMG_FMT_ARGB1555 0x05 + +/** \brief Paletted, 4 bits per pixel (16 colors). */ +#define KOS_IMG_FMT_PAL4BPP 0x06 + +/** \brief Paletted, 8 bits per pixel (256 colors). */ +#define KOS_IMG_FMT_PAL8BPP 0x07 + +/** \brief 8-bit Y (4 bits), U (2 bits), V (2 bits). */ +#define KOS_IMG_FMT_YUV422 0x08 + +/** \brief 15-bpp interleaved B (5 bits), G (6 bits), R (5 bits). */ +#define KOS_IMG_FMT_BGR565 0x09 + +/** \brief 32-bpp interleaved R/G/B/A bytes. */ +#define KOS_IMG_FMT_RGBA8888 0x10 + +/** \brief Basic format mask (not an actual format value). */ +#define KOS_IMG_FMT_MASK 0xff + +/** \brief X axis of image data is inverted (stored right to left). */ +#define KOS_IMG_INVERTED_X 0x0100 + +/** \brief Y axis of image data is inverted (stored bottom to top). */ +#define KOS_IMG_INVERTED_Y 0x0200 + +/** \brief The image is not the owner of the image data buffer. + + This generally implies that the image data is stored in ROM and thus cannot + be freed. +*/ +#define KOS_IMG_NOT_OWNER 0x0400 + +/** @} */ + +/** \brief Free a kos_img_t object. + \ingroup video_img + + This function frees the data in a kos_img_t object, returning any memory to + the heap as appropriate. Optionally, this can also free the object itself, + if required. + + \param img The image object to free. + \param struct_also Set to non-zero to free the image object itself, + as well as any data contained therein. +*/ +void kos_img_free(kos_img_t *img, int struct_also); + diff --git a/vendor/koshle/kos/sem.h b/vendor/koshle/kos/sem.h new file mode 100644 index 00000000..b0785616 --- /dev/null +++ b/vendor/koshle/kos/sem.h @@ -0,0 +1,181 @@ +/* KallistiOS ##version## + + include/kos/sem.h + Copyright (C) 2001, 2003 Megan Potter + Copyright (C) 2012 Lawrence Sebald + +*/ + +/** \file kos/sem.h + \brief Semaphores. + \ingroup kthreads + + This file defines semaphores. A semaphore is a synchronization primitive + that allows a specified number of threads to be in its critical section at a + single point of time. Another way to think of it is that you have a + predetermined number of resources available, and the semaphore maintains the + resources. + + \author Megan Potter + \see kos/mutex.h +*/ + +#ifndef __KOS_SEM_H +#define __KOS_SEM_H + +#include + +/** \brief Semaphore type. + + This structure defines a semaphore. There are no public members of this + structure for you to actually do anything with in your code, so don't try. + + \headerfile kos/sem.h +*/ +typedef struct semaphore { + std::atomic initialized; /**< \brief Are we initialized? */ + std::atomic count; /**< \brief The semaphore count */ +} semaphore_t; + +/** \brief Initializer for a transient semaphore. + \param value The initial count of the semaphore. */ +#define SEM_INITIALIZER(value) { 1, value } + +/** \brief Allocate a new semaphore. + + This function allocates and initializes a new semaphore for use. + + \deprecated + This function is formally deprecated. Please update your code to use + sem_init() or static initialization with SEM_INITIALIZER instead. + + \param value The initial count of the semaphore (the number of + threads to allow in the critical section at a time) + + \return The created semaphore on success. NULL is returned + on failure and errno is set as appropriate. + + \par Error Conditions: + \em ENOMEM - out of memory \n + \em EINVAL - the semaphore's value is invalid (less than 0) +*/ +semaphore_t *sem_create(int value); + +/** \brief Initialize a semaphore for use. + + This function initializes the semaphore passed in with the starting count + value specified. + + \param sm The semaphore to initialize + \param count The initial count of the semaphore + \retval 0 On success + \retval -1 On error, errno will be set as appropriate + + \par Error Conditions: + \em EINVAL - the semaphore's value is invalid (less than 0) +*/ +int sem_init(semaphore_t *sm, int count); + +/** \brief Destroy a semaphore. + + This function frees a semaphore, releasing any memory associated with it. If + there are any threads currently waiting on the semaphore, they will be woken + with an ENOTRECOVERABLE error. + + \param sem The semaphore to destroy + \retval 0 On success (no error conditions currently defined) +*/ +int sem_destroy(semaphore_t *sem); + +/** \brief Wait on a semaphore. + + This function will decrement the semaphore's count and return, if resources + are available. Otherwise, the function will block until the resources become + available. + + This function does not protect you against doing things that will cause a + deadlock. This function is not safe to call in an interrupt. See + sem_trywait() for a safe function to call in an interrupt. + + \param sem The semaphore to wait on + \retval 0 On success + \retval -1 On error, sets errno as appropriate + + \par Error Conditions: + \em EPERM - called inside an interrupt \n + \em EINVAL - the semaphore was not initialized +*/ +int sem_wait(semaphore_t *sem); + +/** \brief Wait on a semaphore (with a timeout). + + This function will decrement the semaphore's count and return, if resources + are available. Otherwise, the function will block until the resources become + available or the timeout expires. + + This function does not protect you against doing things that will cause a + deadlock. This function is not safe to call in an interrupt. See + sem_trywait() for a safe function to call in an interrupt. + + \param sem The semaphore to wait on + \param timeout The maximum number of milliseconds to block (a value + of 0 here will block indefinitely) + \retval 0 On success + \retval -1 On error, sets errno as appropriate + + \par Error Conditions: + \em EPERM - called inside an interrupt \n + \em EINVAL - the semaphore was not initialized \n + \em EINVAL - the timeout value was invalid (less than 0) \n + \em ETIMEDOUT - timed out while blocking + */ +int sem_wait_timed(semaphore_t *sem, int timeout); + +/** \brief "Wait" on a semaphore without blocking. + + This function will decrement the semaphore's count and return, if resources + are available. Otherwise, it will return an error. + + This function does not protect you against doing things that will cause a + deadlock. This function, unlike the other waiting functions is safe to call + inside an interrupt. + + \param sem The semaphore to "wait" on + \retval 0 On success + \retval -1 On error, sets errno as appropriate + + \par Error Conditions: + \em EWOULDBLOCK - a call to sem_wait() would block \n + \em EINVAL - the semaphore was not initialized +*/ +int sem_trywait(semaphore_t *sem); + +/** \brief Signal a semaphore. + + This function will release resources associated with a semaphore, signalling + a waiting thread to continue on, if any are waiting. It is your + responsibility to make sure you only release resources you have. + + \param sem The semaphore to signal + \retval 0 On success + \retval -1 On error, sets errno as appropriate + + \par Error Conditions: + \em EINVAL - the semaphore was not initialized +*/ +int sem_signal(semaphore_t *sem); + +/** \brief Retrieve the number of available resources. + + This function will retrieve the count of available resources for a + semaphore. This is not a thread-safe way to make sure resources will be + available when you get around to waiting, so don't use it as such. + + \param sem The semaphore to check + \return The count of the semaphore (the number of resources + currently available) +*/ +int sem_count(semaphore_t *sem); + + +#endif /* __KOS_SEM_H */ diff --git a/vendor/koshle/pvr_fog_tables.h b/vendor/koshle/pvr_fog_tables.h new file mode 100644 index 00000000..1069cc75 --- /dev/null +++ b/vendor/koshle/pvr_fog_tables.h @@ -0,0 +1,192 @@ +/* Kallistios ##version## + + pvr_fog_tables.h + + (C)2002 Paul Boese + Portions (C)1999-2001, Brian Paul + + */ + +#ifndef __PVR_FOG_TABLES_H +#define __PVR_FOG_TABLES_H + +/* + This table is used by the pvr_fog.c module to scale values for the PVR's + fog table. It can be used directly with each value multiplied by 255 + to make perspectively correct table values for linear fog. The table does + not include the value of 1.0 which represents full visual occlusion. + */ +float inverse_w_depth[] = { + 0.94118, 0.88889, 0.84211, 0.80000, 0.76190, 0.72727, 0.69565, 0.66667, + 0.64000, 0.61538, 0.59259, 0.57143, 0.55172, 0.53333, 0.51613, 0.50000, + 0.47059, 0.44444, 0.42105, 0.40000, 0.38095, 0.36364, 0.34783, 0.33333, + 0.32000, 0.30769, 0.29630, 0.28571, 0.27586, 0.26667, 0.25806, 0.25000, + 0.23529, 0.22222, 0.21053, 0.20000, 0.19048, 0.18182, 0.17391, 0.16667, + 0.16000, 0.15385, 0.14815, 0.14286, 0.13793, 0.13333, 0.12903, 0.12500, + 0.11765, 0.11111, 0.10526, 0.10000, 0.09524, 0.09091, 0.08696, 0.08333, + 0.08000, 0.07692, 0.07407, 0.07143, 0.06897, 0.06667, 0.06452, 0.06250, + 0.05882, 0.05556, 0.05263, 0.05000, 0.04762, 0.04545, 0.04348, 0.04167, + 0.04000, 0.03846, 0.03704, 0.03571, 0.03448, 0.03333, 0.03226, 0.03125, + 0.02941, 0.02778, 0.02632, 0.02500, 0.02381, 0.02273, 0.02174, 0.02083, + 0.02000, 0.01923, 0.01852, 0.01786, 0.01724, 0.01667, 0.01613, 0.01562, + 0.01471, 0.01389, 0.01316, 0.01250, 0.01190, 0.01136, 0.01087, 0.01042, + 0.01000, 0.00962, 0.00926, 0.00893, 0.00862, 0.00833, 0.00806, 0.00781, + 0.00735, 0.00694, 0.00658, 0.00625, 0.00595, 0.00568, 0.00543, 0.00521, + 0.00500, 0.00481, 0.00463, 0.00446, 0.00431, 0.00417, 0.00403, 0.00391, +}; + +/* Code to generate the preceding inverse_w_depth[] table +#include +#include + +int main () { + int i, j; + float t; + + printf("float inverse_w_depth[] = {\n\t"); + for( i=1; i<=128; i++) { + j = i; + t = (pow(2.0, j>>4) * ((j&0xf)+16)/16.0f)/1.0f; + printf("%01.5f,%s", 1.0f/t, ((i)%8)?" ":"\n\t"); + } + printf("\n};\n"); + return 0; +} + */ + +/* + * This is essentially the same table as above except each value has been + * multiplied by 259.999999. This table is used by the EXP and EXP2 + * fog functions in pvr_fog.c. It just saves us one multiplication per table + * entry. + */ +float inverse_w_depth260[] = { + 244.706, 231.111, 218.947, 208.000, 198.095, 189.091, 180.870, 173.333, + 166.400, 160.000, 154.074, 148.571, 143.448, 138.667, 134.194, 130.000, + 122.353, 115.556, 109.474, 104.000, 99.048, 94.545, 90.435, 86.667, + 83.200, 80.000, 77.037, 74.286, 71.724, 69.333, 67.097, 65.000, + 61.176, 57.778, 54.737, 52.000, 49.524, 47.273, 45.217, 43.333, + 41.600, 40.000, 38.519, 37.143, 35.862, 34.667, 33.548, 32.500, + 30.588, 28.889, 27.368, 26.000, 24.762, 23.636, 22.609, 21.667, + 20.800, 20.000, 19.259, 18.571, 17.931, 17.333, 16.774, 16.250, + 15.294, 14.444, 13.684, 13.000, 12.381, 11.818, 11.304, 10.833, + 10.400, 10.000, 9.630, 9.286, 8.966, 8.667, 8.387, 8.125, + 7.647, 7.222, 6.842, 6.500, 6.190, 5.909, 5.652, 5.417, + 5.200, 5.000, 4.815, 4.643, 4.483, 4.333, 4.194, 4.062, + 3.824, 3.611, 3.421, 3.250, 3.095, 2.955, 2.826, 2.708, + 2.600, 2.500, 2.407, 2.321, 2.241, 2.167, 2.097, 2.031, + 1.912, 1.806, 1.711, 1.625, 1.548, 1.477, 1.413, 1.354, + 1.300, 1.250, 1.204, 1.161, 1.121, 1.083, 1.048, 1.016, +}; + +/* Code to generate the preceding inverse_w_depth260[] table +#include +#include + +int main () { + int i, j; + float t; + + printf("float inverse_w_depth260[] = {\n\t"); + for( i=1; i<=128; i++) { + j = i; + t = (pow(2.0, j>>4) * ((j&0xf)+16)/16.0f)/259.999999f; + printf("%03.3f,%s", 1.0f/t, ((i)%8)?" ":"\n\t"); + } + printf("\n};\n"); + return 0; +} + */ + +/* lookup table for the fast neg_exp function */ +static float exp_table[] = { + 1.00000000, 0.96169060, 0.92484879, 0.88941842, + 0.85534531, 0.82257754, 0.79106510, 0.76075989, + 0.73161560, 0.70358789, 0.67663383, 0.65071243, + 0.62578398, 0.60181057, 0.57875562, 0.55658382, + 0.53526145, 0.51475590, 0.49503589, 0.47607136, + 0.45783335, 0.44029403, 0.42342663, 0.40720543, + 0.39160562, 0.37660345, 0.36217600, 0.34830126, + 0.33495805, 0.32212600, 0.30978554, 0.29791784, + 0.28650481, 0.27552897, 0.26497361, 0.25482264, + 0.24506053, 0.23567241, 0.22664395, 0.21796136, + 0.20961139, 0.20158130, 0.19385885, 0.18643223, + 0.17929012, 0.17242162, 0.16581626, 0.15946393, + 0.15335497, 0.14748003, 0.14183016, 0.13639674, + 0.13117145, 0.12614636, 0.12131377, 0.11666631, + 0.11219689, 0.10789870, 0.10376516, 0.09978998, + 0.09596708, 0.09229065, 0.08875505, 0.08535489, + 0.08208500, 0.07894037, 0.07591622, 0.07300791, + 0.07021102, 0.06752128, 0.06493458, 0.06244697, + 0.06005467, 0.05775401, 0.05554149, 0.05341373, + 0.05136748, 0.04939962, 0.04750715, 0.04568718, + 0.04393693, 0.04225374, 0.04063502, 0.03907832, + 0.03758125, 0.03614154, 0.03475698, 0.03342546, + 0.03214495, 0.03091349, 0.02972922, 0.02859031, + 0.02749503, 0.02644171, 0.02542875, 0.02445459, + 0.02351775, 0.02261679, 0.02175036, 0.02091712, + 0.02011579, 0.01934517, 0.01860407, 0.01789136, + 0.01720595, 0.01654680, 0.01591290, 0.01530329, + 0.01471703, 0.01415323, 0.01361103, 0.01308960, + 0.01258814, 0.01210590, 0.01164213, 0.01119613, + 0.01076721, 0.01035472, 0.00995804, 0.00957655, + 0.00920968, 0.00885686, 0.00851756, 0.00819126, + 0.00787746, 0.00757568, 0.00728546, 0.00700636, + 0.00673795, 0.00647982, 0.00623158, 0.00599285, + 0.00576327, 0.00554248, 0.00533015, 0.00512596, + 0.00492959, 0.00474074, 0.00455912, 0.00438447, + 0.00421650, 0.00405497, 0.00389962, 0.00375023, + 0.00360656, 0.00346840, 0.00333553, 0.00320774, + 0.00308486, 0.00296668, 0.00285303, 0.00274373, + 0.00263862, 0.00253753, 0.00244032, 0.00234684, + 0.00225693, 0.00217047, 0.00208732, 0.00200735, + 0.00193045, 0.00185650, 0.00178538, 0.00171698, + 0.00165120, 0.00158795, 0.00152711, 0.00146861, + 0.00141235, 0.00135824, 0.00130621, 0.00125617, + 0.00120805, 0.00116177, 0.00111726, 0.00107446, + 0.00103330, 0.00099371, 0.00095564, 0.00091903, + 0.00088383, 0.00084997, 0.00081741, 0.00078609, + 0.00075598, 0.00072702, 0.00069916, 0.00067238, + 0.00064662, 0.00062185, 0.00059803, 0.00057512, + 0.00055308, 0.00053190, 0.00051152, 0.00049192, + 0.00047308, 0.00045495, 0.00043753, 0.00042076, + 0.00040465, 0.00038914, 0.00037424, 0.00035990, + 0.00034611, 0.00033285, 0.00032010, 0.00030784, + 0.00029604, 0.00028470, 0.00027380, 0.00026331, + 0.00025322, 0.00024352, 0.00023419, 0.00022522, + 0.00021659, 0.00020829, 0.00020031, 0.00019264, + 0.00018526, 0.00017816, 0.00017134, 0.00016477, + 0.00015846, 0.00015239, 0.00014655, 0.00014094, + 0.00013554, 0.00013035, 0.00012535, 0.00012055, + 0.00011593, 0.00011149, 0.00010722, 0.00010311, + 0.00009916, 0.00009536, 0.00009171, 0.00008820, + 0.00008482, 0.00008157, 0.00007844, 0.00007544, + 0.00007255, 0.00006977, 0.00006710, 0.00006453, + 0.00006205, 0.00005968, 0.00005739, 0.00005519, + 0.00005308, 0.00005104, 0.00004909, 0.00004721, +}; + +/* code to generate the preceding table + +#include +#include + +#define FOG_EXP_TABLE_SIZE 256 +#define FOG_MAX (10.0) +#define FOG_INCR (FOG_MAX/FOG_EXP_TABLE_SIZE) + +int main () { + float value, f = 0.0F; + int i = 0; + printf("static float exp_table[FOG_EXP_TABLE_SIZE] = {\n\t"); + for( ; i < FOG_EXP_TABLE_SIZE ; i++, f += FOG_INCR) { + value = (float) exp(-f); + printf("%01.8f, %s", value, ((i+1)%4)?" ":"\n\t"); + } + printf("\n};\n"); + return 0; +} + +*/ + +#endif diff --git a/vendor/koshle/pvr_internal.h b/vendor/koshle/pvr_internal.h new file mode 100644 index 00000000..a3d34985 --- /dev/null +++ b/vendor/koshle/pvr_internal.h @@ -0,0 +1,328 @@ +/* KallistiOS ##version## + + pvr_internal.h + Copyright (C) 2002, 2003, 2004 Megan Potter + + */ + +#pragma once + +#include +#include + +uint32_t pvr_map32(uint32_t offset32); + +struct b32_uint32 { + uint32_t data; + void operator=(uint32_t data) { + size_t offs = (uint8_t*)this - emu_vram; + assert(!(offs&3)); + assert(offs + +/**** State stuff ***************************************************/ + +/* The internal workings of the PVR2 are quite complex, and thank goodness + we have the TA to help us with this setup process for each frame, or + it'd be a LOT more work! + + Basically you have three different sets of buffers while registering + scene data: + + 1) Vertex buffer: this is a PVR RAM buffer that holds processed vertex + data as it is fed to the TA + 2) Object pointer buffer: this is essentially an array of lists which + holds data about which objects appear may appear in which tiles; for + some odd reason, it grows down (probably so you don't have to pre-size + vertex and OPB buffers, kinda like heap and stack) + 3) Tile matrix: this has a fixed-size entry for each tile on that will + be rendered to; each active list must have a pointer into the OPB, + or an "end of list" marker to mark it as have no OPB space + + As the TA collects data, the buffers may start to overflow if you have + a lot of polygons, and that's what the grow space is about. It is + initially using a fairly small amount of PVR RAM to hold the data + structures, but as it overflows the bins for each tile, it must + allocate a new block. + + 3D processing proceeds in a pipeline fashion. There are four functional + units we have to consider in this process: the main CPU, the tile + accelerator, the ISP/TSP, and the visual output. + + If vertex DMA is enabled, then the TA may optionally be fed by the CPU, + which will free it up from stalls that may happen with certain polygons + when feeding the TA, as well as enabling other benefits. + + So in an ideal situation with no DMA enabled, it looks like this: + + VBlanks SH4-to-TA ISP/TSP View + 0 ->T0 - - + 1 ->T1 T0->F0 - + 2 ->T0 T1->F1 F0 + 3 ->T1 T0->F0 F1 + ... + + When vertex DMA is enabled, we go into a naive 3-stage setup. This can + be improved later, but it's a start for now. + + In this mode, we augment the timing diagram above: + + VBlanks SH4-to-RAM DMA-to-TA ISP/TSP View + 0 ->R0 - - - + 1 ->R1 R0->T0 - - + 2 ->R0 R1->T1 T0->F0 - + 3 ->R1 R0->T0 T1->F1 F0 + 4 ->R0 R1->T1 T0->F0 F1 + ... + + In the current naive implementation, everything is timed off of vblank + interrupts. So the program can write vertices to the RAM buffers as long + as it wants. On the first vblank where the current RAM buffers are filled + up, DMA proceeds from the filled buffer to the TA. On the first vblank + where all the TA transfers have completed, ISP/TSP rendering is started. + On the first vblank where a frame has been completed, the view is switched + to the frame. Thus everything sort of cascades in natural order when it's + ready. This also solves the issue in previous versions where one would + write a single frame and it'd never show up unless you push through + several more frames. For example, a single frame written would look + like this: + + VBlanks SH4-to-RAM DMA-to-TA ISP/TSP View + 0 ->R0 - - - + 1 - R0->T0 - - + 2 - - T0->F0 - + 3 - - - F0 + + Another example, if the CPU spent more than 16msec generating data in the + SH4-to-RAM phase, it might look like this at 30fps: + + VBlanks SH4-to-RAM DMA-to-TA ISP/TSP View + 0 ->R0 - - - + 1 - R0->T0 - - + 2 ->R1 - T0->F0 - + 3 - R1->T1 - F0 + 4 ->R0 - T1->F1 F0 + 5 - R0->T0 - F1 + 6 ->R1 - T0->F0 F1 + ... + + Note that in the case where the potentially bigger frames cause the DMA-to-TA + or ISP/TSP phases to take longer than one frame, they are allowed to expand + into the next slot gracefully. + + */ + +/* Note that these must match the list types in pvr.h; these are here + mainly because they're easier to type =) */ +#define PVR_OPB_OP 0 /* Array indices for these structures */ +#define PVR_OPB_OM 1 +#define PVR_OPB_TP 2 +#define PVR_OPB_TM 3 +#define PVR_OPB_PT 4 +#define PVR_OPB_COUNT 5 + +// TA buffers structure: we have two sets of these +typedef struct { + uint32 vertex, vertex_size; /* Vertex buffer */ + uint32 opb, opb_size; /* Object pointer buffers, size */ + uint32 opb_addresses[PVR_OPB_COUNT]; /* Object pointer buffers (of each type) */ + uint32 tile_matrix, tile_matrix_size; /* Tile matrix, size */ + uint32 opb_overflow_count; /* Extra OPB space after opb_size for TA overflow */ +} pvr_ta_buffers_t; + +// DMA buffers structure: we have two sets of these +typedef struct { + uint8 * base[PVR_OPB_COUNT]; // DMA buffers, if assigned + uint32 ptr[PVR_OPB_COUNT]; // DMA buffer write pointer, if used + uint32 size[PVR_OPB_COUNT]; // DMA buffer sizes, or zero if none + int ready; // >0 if these buffers are ready to be DMAed +} pvr_dma_buffers_t; + +// Frame buffers structure: we have two sets of these +typedef struct { + uint32 frame, frame_size; // Output frame buffer, size +} pvr_frame_buffers_t; + +/* PVR status structure; not only will this hold status information, + but it will also server as the wait object for the frame-complete + genwaits. */ +typedef struct { + // If this is zero, then this state isn't valid + int valid; + + // General configuration + uint32 lists_enabled; // opb_completed's value when we're ready to render + uint32 list_reg_mask; // Active lists register mask + int dma_mode; // 1 if we are using DMA to transfer vertices + int opb_size[PVR_OPB_COUNT]; // opb size flags + + // Pipeline state + int ram_target; // RAM buffer we're writing into + // (^1 == RAM buffer we're DMAing from) + int ta_target; // TA buffer we're writing (or DMAing) into + // (^1 == TA buffer we're rendering from) + int view_target; // Frame buffer we're viewing + // (^1 == frame buffer we're rendering to) + + int list_reg_open; // Which list is open for registration, if any? (non-DMA only) + uint32 lists_closed; // (1 << idx) for each list which the SH4 has lost interest in + uint32 lists_transferred; // (1 << idx) for each list which has completely transferred to the TA + uint32 lists_dmaed; // (1 << idx) for each list which has been DMA'd (DMA mode only) + + // mutex_t dma_lock; // Locked if a DMA is in progress (vertex or texture) + int ta_busy; // >0 if a DMA is in progress and the TA hasn't signaled completion + int render_busy; // >0 if a render is in progress + int render_completed; // >1 if a render has recently finished + + // Memory pointers / buffers + pvr_dma_buffers_t dma_buffers[2]; // DMA buffers (if any) + pvr_ta_buffers_t ta_buffers[2]; // TA buffers + pvr_frame_buffers_t frame_buffers[2]; // Frame buffers + uint32 texture_base; // Start of texture RAM + + // Screen size / clipping constants + int w, h; // Screen width, height + int tw, th; // Screen tile width, height + uint32 tsize_const; // Screen tile size constant + float zclip; // Z clip plane + uint32 pclip_left, pclip_right; // X pixel clip constants + uint32 pclip_top, pclip_bottom; // Y pixel clip constants + uint32 pclip_x, pclip_y; // Composited clip constants + uint32 bg_color; // Background color in ARGB format + + /* Running statistics on the PVR system. All vars are in terms + of nanoseconds. */ + uint64_t frame_last_time; // When did the last frame completion occur? + uint64_t buf_start_time; // When did the last DMA buffer fill begin? + uint64_t reg_start_time; // When did the last registration begin? + uint64_t rnd_start_time; // When did the last render begin? + uint64_t frame_last_len; // VBlank-to-VBlank length for the last frame (1.0/FrameRate) + uint64_t buf_last_len; // Cumulative buffer fill time for the last frame + uint64_t reg_last_len; // Registration time for the last frame + uint64_t rnd_last_len; // Render time for the last frame + size_t vbl_count; // VBlank counter for animations and such + size_t frame_count; // Total number of viewed frames + size_t vtx_buf_used; // Vertex buffer used size for the last frame + size_t vtx_buf_used_max; // Maximum used vertex buffer size + + /* Wait-ready semaphore: this will be signaled whenever the pvr_wait_ready() + call should be ready to return. */ + semaphore_t ready_sem; + + // Handle for the vblank interrupt + int vbl_handle; + + // Non-zero if FSAA was enabled at init time. + int fsaa; + + // Non-zero if we are rendering to a texture + int to_texture[2]; + + // Render pitch for to-texture mode + int to_txr_rp[2]; + + // Output address for to-texture mode + uint32 to_txr_addr[2]; + + uint32 dr_used; + + // Callback to call before the start of rendering, may be NULL + pvr_before_render_hook_t isp_start_callback; +} pvr_state_t; + +/* There will be exactly one of these in KOS (in pvr_globals.c) */ +extern volatile pvr_state_t pvr_state; + +/* Background plane structure */ +typedef struct pvr_bkg_poly { + uint32 flags1, flags2; + uint32 dummy; + float x1, y1, z1; + uint32 argb1; + float x2, y2, z2; + uint32 argb2; + float x3, y3, z3; + uint32 argb3; +} pvr_bkg_poly_t; + +// Debug macro, for debugging IRQ wackiness +#define DBG(x) do { \ + int o = irq_disable(); \ + printf x; \ + irq_restore(o); \ + } while(0) + +/**** pvr_buffers.c ***************************************************/ + +/* Initialize buffers for TA/ISP/TSP usage */ +void pvr_allocate_buffers(pvr_init_params_t *params); + +/* Fill the tile matrices (after it's initialized) */ +void pvr_init_tile_matrices(int presort); + + +/**** pvr_misc.c ******************************************************/ + +/* What event is happening (for pvr_sync_stats)? */ +#define PVR_SYNC_VBLANK 1 /* VBlank IRQ */ +#define PVR_SYNC_BUFSTART 2 /* DMA buffer fill started */ +#define PVR_SYNC_BUFDONE 3 /* DMA buffer fill complete */ +#define PVR_SYNC_REGSTART 4 /* Registration started */ +#define PVR_SYNC_REGDONE 5 /* Registration complete */ +#define PVR_SYNC_RNDSTART 6 /* Render started */ +#define PVR_SYNC_RNDDONE 7 /* Render complete IRQ */ +#define PVR_SYNC_PAGEFLIP 8 /* View page was flipped */ + +/* Update statistical counters */ +void pvr_sync_stats(int event); + +/* Synchronize the viewed page with what's in pvr_state */ +void pvr_sync_view(void); + +/* Synchronize the registration buffer with what's in pvr_state */ +void pvr_sync_reg_buffer(void); + +/* Begin a render operation that has been queued completely */ +void pvr_begin_queued_render(void); + +/* Generate synthetic polygon headers for the given list type (to submit + blank lists that the user forgot) */ +void pvr_blank_polyhdr(int type); + +/* Same as above, but generates into a buffer instead of submitting. */ +void pvr_blank_polyhdr_buf(int type, pvr_poly_hdr_t * buf); + + +/**** pvr_irq.c *******************************************************/ + +/* Interrupt handler for PVR events */ +void pvr_int_handler(uint32 code, void *data); + + +#endif diff --git a/vendor/librw b/vendor/librw deleted file mode 160000 index 5501c4fd..00000000 --- a/vendor/librw +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5501c4fdc7425ff926be59369a13593bb6c81b54 diff --git a/vendor/librw/.appveyor.yml b/vendor/librw/.appveyor.yml new file mode 100644 index 00000000..f74fc38f --- /dev/null +++ b/vendor/librw/.appveyor.yml @@ -0,0 +1,32 @@ +image: Visual Studio 2017 +configuration: Release +environment: + GLFW_BASE: glfw-3.3.4.bin.WIN64 + GLFW_URL: https://github.com/glfw/glfw/releases/download/3.3.4/%GLFW_BASE%.zip + SDL2_BASE: SDL2-devel-2.0.14-VC + SDL2_URL: https://www.libsdl.org/release/%SDL2_BASE%.zip + SDL2_DIRAME: SDL2-2.0.14 + PREMAKE5_URL: https://github.com/premake/premake-core/releases/download/v5.0.0-alpha16/premake-5.0.0-alpha16-windows.zip + matrix: + - PLATFORM: win-amd64-null + - PLATFORM: win-amd64-gl3 + PREMAKE5_EXTRA_ARGS: --gfxlib=glfw + - PLATFORM: win-amd64-gl3 + PREMAKE5_EXTRA_ARGS: --gfxlib=sdl2 + - PLATFORM: win-amd64-d3d9 + +install: + - appveyor DownloadFile %GLFW_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE%.zip" + - appveyor DownloadFile %SDL2_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%SDL2_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%SDL2_BASE%.zip" + - appveyor DownloadFile %PREMAKE5_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/premake5.zip" + - mkdir "%APPVEYOR_BUILD_FOLDER%/bin" && cd "%APPVEYOR_BUILD_FOLDER%/bin" && 7z x "%APPVEYOR_BUILD_FOLDER%/premake5.zip" + - set PATH=%APPVEYOR_BUILD_FOLDER%/bin;%PATH% +before_build: + - mkdir "%APPVEYOR_BUILD_FOLDER%/build" + - cd "%APPVEYOR_BUILD_FOLDER%" + - premake5 vs2017 --glfwdir64=%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE% --sdl2dir=%APPVEYOR_BUILD_FOLDER%/%SDL2_DIRAME% %PREMAKE5_EXTRA_ARGS% +build: + project: c:\projects\librw\build\librw.sln + verbosity: minimal diff --git a/vendor/librw/.github/workflows/build-cmake-conan.yml b/vendor/librw/.github/workflows/build-cmake-conan.yml new file mode 100644 index 00000000..43f85a91 --- /dev/null +++ b/vendor/librw/.github/workflows/build-cmake-conan.yml @@ -0,0 +1,60 @@ +name: Conan +on: + pull_request: + push: + release: + types: published +jobs: + build-cmake: + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + platform: ['null', 'gl3', 'd3d9'] + gl3_gfxlib: ['glfw', 'sdl2'] + exclude: + - os: ubuntu-latest + platform: d3d9 + - os: macos-latest + platform: d3d9 + - platform: 'null' + gl3_gfxlib: sdl2 + - platform: d3d9 + gl3_gfxlib: sdl2 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: "Setup conan" + run: | + python -m pip install conan + conan config init + conan config set log.print_run_commands=True + conan config set general.revisions_enabled=1 + conan remote add bincrafters https://bincrafters.jfrog.io/artifactory/api/conan/public-conan + - name: "Create host profile" + shell: bash + run: | + cp ~/.conan/profiles/default host_profile + - name: "Download/build dependencies (conan install)" + run: | + conan install ${{ github.workspace }} librw/master@ -if build -o librw:platform=${{ matrix.platform }} -o librw:gl3_gfxlib=${{ matrix.gl3_gfxlib }} --build missing -pr:h ./host_profile -pr:b default + env: + CONAN_SYSREQUIRES_MODE: enabled + - name: "Build librw (conan build)" + run: | + conan build ${{ github.workspace }} -if build -bf build -pf package + - name: "Package librw (conan package)" + run: | + conan package ${{ github.workspace }} -if build -bf build -pf package + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "${{ matrix.os }}-${{ matrix.platform }}" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.github/workflows/build-ps2.yml b/vendor/librw/.github/workflows/build-ps2.yml new file mode 100644 index 00000000..bb0b0ece --- /dev/null +++ b/vendor/librw/.github/workflows/build-ps2.yml @@ -0,0 +1,29 @@ +name: Playstation 2 (by ps2dev) +on: + pull_request: + push: + release: + types: published +jobs: + build-playstation-2: + runs-on: ubuntu-latest + container: ps2dev/ps2dev + steps: + - uses: actions/checkout@v2 + - name: "Install dependencies" + run: | + apk add build-base cmake + - name: "Build files" + run: | + cmake -S. -Bbuild -DLIBRW_INSTALL=ON -DLIBRW_PLATFORM=PS2 -DCMAKE_TOOLCHAIN_FILE=cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake + cmake --build build --parallel + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "ps2" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.github/workflows/build-switch.yml b/vendor/librw/.github/workflows/build-switch.yml new file mode 100644 index 00000000..cfda93c9 --- /dev/null +++ b/vendor/librw/.github/workflows/build-switch.yml @@ -0,0 +1,26 @@ +name: Nintendo Switch (by devkitPro) +on: + pull_request: + push: + release: + types: published +jobs: + build-nintendo-switch: + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + steps: + - uses: actions/checkout@v2 + - name: "Build files" + run: | + /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -S. -Bbuild -DLIBRW_PLATFORM=GL3 -DLIBRW_GL3_GFXLIB=GLFW -DLIBRW_INSTALL=True + cmake --build build --parallel + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "switch-gl3" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.gitignore b/vendor/librw/.gitignore new file mode 100644 index 00000000..30d1cfbf --- /dev/null +++ b/vendor/librw/.gitignore @@ -0,0 +1,12 @@ +/bin +/build +/obj +/lib +/output +/.vs +librw.vcxproj.user +librw.VC.db +librw.VC.VC.opendb +imgui.ini +*.o +*.sim.o \ No newline at end of file diff --git a/vendor/librw/.travis.yml b/vendor/librw/.travis.yml new file mode 100644 index 00000000..91411e60 --- /dev/null +++ b/vendor/librw/.travis.yml @@ -0,0 +1,46 @@ +language: cpp + +matrix: + include: + - os: linux + env: TARGET=release_linux-amd64-null + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 gmake && cd /librw/build && make config=$TARGET verbose=1" + - os: linux + env: TARGET=release_linux-amd64-gl3 GFXLIB=glfw + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 --gfxlib=$GFXLIB gmake && cd /librw/build && make config=$TARGET verbose=1" + - os: linux + env: TARGET=release_linux-amd64-gl3 GFXLIB=sdl2 + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 --gfxlib=$GFXLIB gmake && cd /librw/build && make config=$TARGET verbose=1" + - name: "ps2" + os: linux + env: TARGET=release_ps2 + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 gmake && cd /librw/build && make config=$TARGET verbose=1" + allow_failures: + - name: "ps2" diff --git a/vendor/librw/ARCHITECTURE.MD b/vendor/librw/ARCHITECTURE.MD new file mode 100644 index 00000000..dad04fa1 --- /dev/null +++ b/vendor/librw/ARCHITECTURE.MD @@ -0,0 +1,174 @@ +This document gives an overview of the architecture of librw. + +Disclaimer: Some of these design decision were taken over from original RW, +some are my own. I only take partial responsibility. + +Differently from original RW, librw has no neat separation into modules. +Some things could be made optional, but in particular RW's RpWorld +plugin is integrated with everything else. + +# Plugins + +To extend structs with custom data, +RW (and librw) provides a plugin mechanism +for certain structs. +This can be used to tack more data onto a struct +and register custom streaming functions. +Plugins must be registered before any instance +of that struct is allocated. + +# Pipelines + +RW's pipeline architecture was designed for very flexible data flow. +Unfortunately for RW most of the rendering pipeline moved into the GPU +causing RW's pipeline architecture to be severely overengineered. + +librw's pipeline architecture is therefore much simplified +and only implements what is actually needed, +but the name *pipeline* is retained. + +Three pipelines are implemented in librw itself: +Default, Skin, MatFX (only env map so far). +Others can be implemented by applications using librw. + +# RW Objects + +## Frame + +A Frame is an orientation in space, arranged in a hierarchy. +Camera, Lights and Atomics can be attached to it. +It has two matrices: a (so called) model matrix, +which is relative to its parent, +and a local transformation matrix (LTM) which is relative to the world. +The LTM is updated automatically as needed whenever the hierarchy gets dirty. + +## Camera + +A Camera is attached to a Frame to position it in space +and has a framebuffer and a z-buffer attached to render to. +Rendering is started by `beginUpdate` and ended by `endUpdate`. +This sets up things like framebuffers and matrices +so that the Camera's raster can be rendered to. + +## Light + +Lights are attached to a Frame to position it in space. +They are used to light Atomics for rendering. +Different types of light are possible. + +## Geometry + +A Geometry contains the raw geometry data that can be rendered. +It has a list of materials that are applied to its triangles. +The latter are sorted by materials into meshes for easier instancing. + +## Atomic + +An Atomic is attached to a Frame to position it in space +and references a Geometry. +Atomics are the objects that are rendered by pipelines. + +## Clump + +A Clump is a container of Atomics, Lights and Cameras. +Clumps can be read from and written to DFF files. +Rendering a Clump will be render all of its Atomics. + +# Engine + +Due to the versatility of librw, +there are three levels of code: +Platform indpendent code, +platform specific code, +and render device specific code. + +The second category does not exist in original RW, +but because librw is supposed to be able to +convert between all sorts of platform specific files, +the code for that has to be available no matter +the render platform used. +The common interface for all device-independent +platform code is the `Driver` struct. +The `Engine` struct has an array with one for each platform. + +The render device specific code +is used for actually rendering something to the screen. +The common interface for the device-dependent +code is the `Device` struct and the `Engine` +struct only has a single one, as there can only be one render device +(i.e. you cannot select D3D or OpenGL at runtime). + +Thus when implementing a new backend +you have to implement the `Driver` and `Device` interfaces. +But do note that the `Driver` can be extended with plugins! + +# Driver + +The driver is mostly concerned with conversion +between platform independent data to platform dependent one, and vice versa. +This concerns the following two cases. + +## Raster, Images + +Images contain platform independent uncompressed pixel data. +Rasters contain platform dependent (and possibly compressed) pixel data. +A driver has to be able to convert an image to a raster for the purposes of loading textures +from files or having them converted from foreign rasters. +Converting from rasters to images is not absolutely necessary but it's needed e.g. for taking screenshots. +librw has a set of default raster formats that the platform is +expected to implement for the most part, however not all have to be supported necessarily. +A driver is also free to implement its own formats; +this is done for texture compression. + +Rasters have different types, +`TEXTURE` and `CAMERATEXTURE` rasters can be used as textures, +`CAMERA` and `CAMERATEXTURE` can be used as render targets, +`ZBUFFER` is used as a depth-buffer. + +## Pipelines + +A librw ObjPipeline implements essentially +an instance stage which converts platform independent geometry +to a format that can efficiently be rendered, +and a render stage that actually renders it. +(There is also an uninstance function, +but this is only used to convert platform specific geometry back to the generic format +and hence is not necessary.) + +# Device + +The device implements everything that is actually needed for rendering. + +This includes starting, handling and closing the rendering device, +setting render states, +allocating and destroying buffers and textures, +im2d and im3d rendering, +and the render functions of the pipelines. + +## System + +The `system` function implements certain device requests +from the engine (why these aren't separate functions I don't know, RW design). + +The `Engine` is started in different stages, at various points of which +the render device gets different requests. +At the end the device is initialized and ready for rendering. +A similar but reverse sequence happens on shutdown. + +Subsystems (screens) and video modes are queried through +the `system` by the application before the device is started. + +## Immediate mode + +Im2d and im3d are immediate-mode style rendering interface. + +## Pipelines + +For instancing the typical job is to allocate and fill +a struct to hold some data about an atomic, +an array of structs for all the meshes, +and vertex and index buffers to hold geometry for rendering. + +The render function will render the previously instanced +data by doing a per-object setup and then iterating over +and rendering all the meshes. diff --git a/vendor/librw/CMakeLists.txt b/vendor/librw/CMakeLists.txt new file mode 100644 index 00000000..1f4d3ce7 --- /dev/null +++ b/vendor/librw/CMakeLists.txt @@ -0,0 +1,160 @@ +set(librw_MAINPROJECT ON) +if(DEFINED PROJECT_NAME) + set(librw_MAINPROJECT OFF) +endif() + +cmake_minimum_required(VERSION 3.8) +project(librw + VERSION 0.0.1 + LANGUAGES C CXX +) +set(librw_AUTHOR aap) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +if(WIN32) + set(LIBRW_PLATFORMS "NULL" "GL3" "D3D9") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL ON) +elseif(NINTENDO_SWITCH) + set(LIBRW_PLATFORMS "NULL" "GL3") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL OFF) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/nx") + include(NXFunctions) +elseif(PS2) + set(LIBRW_PLATFORMS "PS2") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL OFF) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/ps2") + include(PS2Functions) +else() + set(LIBRW_PLATFORMS "NULL" "GL3") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL ON) +endif() +list(GET LIBRW_PLATFORMS 0 LIBRW_PLATFORM_DEFAULT) +set(LIBRW_PLATFORM "${LIBRW_PLATFORM_DEFAULT}" CACHE STRING "Platform") +set_property(CACHE LIBRW_PLATFORM PROPERTY STRINGS ${LIBRW_PLATFORMS}) +message(STATUS "LIBRW_PLATFORM = ${LIBRW_PLATFORM} (choices=${LIBRW_PLATFORMS})") +set("LIBRW_PLATFORM_${LIBRW_PLATFORM}" ON) +if(NOT LIBRW_PLATFORM IN_LIST LIBRW_PLATFORMS) + message(FATAL_ERROR "Illegal LIBRW_PLATFORM=${LIBRW_PLATFORM}") +endif() + +set(LIBRW_GL3_GFXLIBS "GLFW" "SDL2") +set(LIBRW_GL3_GFXLIB "GLFW" CACHE STRING "gfxlib for gl3") +set_property(CACHE LIBRW_GL3_GFXLIB PROPERTY STRINGS ${LIBRW_GL3_GFXLIBS}) +if(LIBRW_PLATFORM_GL3) + message(STATUS "LIBRW_GL3_GFXLIB = ${LIBRW_GL3_GFXLIB} (choices=${LIBRW_GL3_GFXLIBS})") +endif() +if(NOT LIBRW_GL3_GFXLIB IN_LIST LIBRW_GL3_GFXLIBS) + message(FATAL_ERROR "Illegal LIBRW_GL3_GFXLIB=${LIBRW_GL3_GFXLIB}") +endif() + +if(LIBRW_PLATFORM_PS2) + enable_language(DSM) +endif() + +if(NOT COMMAND librw_platform_target) + function(librw_platform_target) + endfunction() +endif() + +include(CMakeDependentOption) + +option(LIBRW_TOOLS "Build librw tools" ${librw_MAINPROJECT}) +option(LIBRW_INSTALL "Install librw files" ${librw_MAINPROJECT}) +cmake_dependent_option(LIBRW_EXAMPLES "Build librw examples" ON "LIBRW_TOOLS;NOT LIBRW_PLATFORM_NULL" OFF) + +if(LIBRW_INSTALL) + include(GNUInstallDirs) + set(LIBRW_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/librw") +endif() + +add_subdirectory(src) + +if(LIBRW_TOOLS AND NOT LIBRW_PLATFORM_PS2 AND NOT LIBRW_PLATFORM_NULL) + add_subdirectory(skeleton) +endif() + +add_subdirectory(tools) + +if(LIBRW_INSTALL) + include(CMakePackageConfigHelpers) + configure_package_config_file(cmake/librw-config.cmake.in librw-config.cmake + INSTALL_DESTINATION "${CMAKE_INSTALL_PREFIX}" + ) + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/librw-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + ) + install( + EXPORT librw-targets NAMESPACE librw:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + ) + + if(LIBRW_GL3_GFXLIB STREQUAL "SDL2") + install( + FILES "${CMAKE_CURRENT_LIST_DIR}/cmake/FindSDL2.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake" + ) + endif() + + string(REPLACE "." ";" cmake_c_compiler_version_list "${CMAKE_C_COMPILER_VERSION}") + list(GET cmake_c_compiler_version_list 0 cmake_c_compiler_version_major) + + string(TOLOWER "${LIBRW_PLATFORM}" librw_platform) + set(compiler) + set(os) + if(NOT LIBRW_PLATFORM STREQUAL "PS2") + if(MSVC) + set(compiler "-msvc${MSVC_VERSION}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(compiler "-gcc${cmake_c_compiler_version_major}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(compiler "-clang${cmake_c_compiler_version_major}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "AppleClang") + set(compiler "-appleclang${cmake_c_compiler_version_major}") + else() + set(compiler "-UNK") + message(WARNING "Unknown compiler. Created cpack package will be wrong. (override using cpack -P)") + endif() + endif() + if(LIBRW_PLATFORM_NULL) + set(platform "-null") + elseif(LIBRW_PLATFORM_PS2) + set(platform "-ps2") + elseif(LIBRW_PLATFORM_GL3) + if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") + set(platform "-gl3-glfw") + else() + set(platform "-gl3-sdl2") + endif() + elseif(LIBRW_PLATFORM_D3D9) + set(platform "-d3d9") + endif() + if(NOT LIBRW_PLATFORM_PS2) + if(WIN32) + set(os "-win") + elseif(NINTENDO_SWITCH) + set(os "-switch") + elseif(PS2) + set(os "-ps2") + elseif(APPLE) + set(os "-apple") + elseif(UNIX) + set(os "-linux") + else() + set(compiler "-UNK") + message(WARNING "Unknown os. Created cpack package will be wrong. (override using cpack -P)") + endif() + endif() + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}${platform}${os}${compiler}") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A (partial) re-implementation of RenderWare Graphics") + set(CPACK_PACKAGE_VENDOR "aap") + set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") + set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_GENERATOR "TXZ") + include(CPack) +endif() diff --git a/vendor/librw/Dockerfile b/vendor/librw/Dockerfile new file mode 100644 index 00000000..a6641738 --- /dev/null +++ b/vendor/librw/Dockerfile @@ -0,0 +1,60 @@ +FROM ubuntu:rolling + +ENV PS2DEV /ps2dev +ENV PS2SDK $PS2DEV/ps2sdk +ENV PATH $PATH:$PS2DEV/bin:$PS2DEV/ee/bin:$PS2DEV/iop/bin:$PS2DEV/dvp/bin:$PS2SDK/bin + +ENV DEBIAN_FRONTEND noninteractive + +ENV TOOLCHAIN_GIT_URL git://github.com/ps2dev/ps2toolchain.git +ENV TOOLCHAIN_GIT_BRANCH master + +ENV PREMAKE5_URL=https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-linux.tar.gz + +RUN mkdir -p "$PS2DEV" "$PS2SDK" \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y \ + build-essential \ + cmake \ + autoconf \ + bzip2 \ + gcc \ + git \ + libucl-dev \ + make \ + patch \ + vim \ + wget \ + zip \ + zlib1g-dev \ + libglfw3-dev \ + libsdl2-dev \ + && git clone -b $TOOLCHAIN_GIT_BRANCH $TOOLCHAIN_GIT_URL /toolchain \ + && cd /toolchain \ + && ./toolchain.sh \ + && git clone git://github.com/ps2dev/ps2eth.git /ps2dev/ps2eth \ + && make -C /ps2dev/ps2eth \ + && git clone git://github.com/ps2dev/ps2-packer.git /ps2-packer \ + && make install -C /ps2-packer \ + && rm -rf \ + /ps2-packer \ + /ps2dev/ps2eth/.git \ + /ps2dev/ps2sdk/test.tmp \ + /ps2dev/test.tmp \ + /toolchain \ + && rm -rf /var/lib/apt/lists/* \ + && wget "$PREMAKE5_URL" -O /tmp/premake5.tar.gz \ + && tar xf /tmp/premake5.tar.gz -C /usr/bin/ \ + && rm /tmp/premake5.tar.gz \ + && groupadd 1000 -g 1000 \ + && groupadd 1001 -g 1001 \ + && groupadd 2000 -g 2000 \ + && groupadd 999 -g 999 \ + && useradd -ms /bin/bash builder -g 1001 -G 1000,2000,999 \ + && printf "builder:builder" | chpasswd \ + && adduser builder sudo \ + && printf "builder ALL= NOPASSWD: ALL\\n" >> /etc/sudoers + +USER builder +WORKDIR /home/builder diff --git a/vendor/librw/LICENSE b/vendor/librw/LICENSE new file mode 100644 index 00000000..ef53cc2e --- /dev/null +++ b/vendor/librw/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 aap + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/librw/README.cmake b/vendor/librw/README.cmake new file mode 100644 index 00000000..59493b1d --- /dev/null +++ b/vendor/librw/README.cmake @@ -0,0 +1,9 @@ +Build with cmake +================ + +Linux + + mkdir build + cd build + cmake .. -DLIBRW_PLATFORM=GL3 -DLIBRW_GL3_GFXLIB=SDL2 + make diff --git a/vendor/librw/README.md b/vendor/librw/README.md new file mode 100644 index 00000000..77defd0e --- /dev/null +++ b/vendor/librw/README.md @@ -0,0 +1,26 @@ +librw +===== + +This library is supposed to be a re-implementation of RenderWare graphics, +or a good part of it anyway. + +It is intended to be cross-platform in two senses: +support rendering on different platforms similar to RW; +supporting all file formats for all platforms at all times and provide +way to convert to all other platforms. + +Supported file formats are DFF and TXD for PS2, D3D8, D3D9 and Xbox. +Not all pre-instanced PS2 DFFs are supported. +BSP is not supported at all. + +For rendering we have D3D9 and OpenGL (>=2.1, ES >= 2.0) backends. +Rendering some things on the PS2 is working as a test only. + +# Uses + +librw can be used for rendering [GTA](https://github.com/gtamodding/re3). + +# Building + +Get premake5. Generate a config, e.g. with ``premake5 gmake``, +and look in the build directory. diff --git a/vendor/librw/TODO b/vendor/librw/TODO new file mode 100644 index 00000000..5c2c2205 --- /dev/null +++ b/vendor/librw/TODO @@ -0,0 +1,24 @@ +TODO: +- tristrips +- examples + skeleton + interface + camtex + matfx1 (more new shaders) + hanim1 + fog + imlight? + patch? +- morphing +- Pipelines (PDS, Xbox, PC) +- bsp + +driver +- metrics + +ps2 +- rendering! +- ADC conversion + + +BUGS: + - fseek with negative offset on ps2 over ps2link messes up the current position diff --git a/vendor/librw/args.h b/vendor/librw/args.h new file mode 100644 index 00000000..0e666c1d --- /dev/null +++ b/vendor/librw/args.h @@ -0,0 +1,24 @@ +extern char *argv0; +#define USED(x) ((void)x) +#define SET(x) ((x)=0) + +#define ARGBEGIN for((argv0||(argv0=*argv)),argv++,argc--;\ + argv[0] && argv[0][0]=='-' && argv[0][1];\ + argc--, argv++) {\ + char *_args, *_argt;\ + char _argc;\ + _args = &argv[0][1];\ + if(_args[0]=='-' && _args[1]==0){\ + argc--; argv++; break;\ + }\ + _argc = 0;\ + while(*_args && (_argc = *_args++))\ + switch(_argc) +#define ARGEND SET(_argt);USED(_argt);USED(_argc);USED(_args);}USED(argv);USED(argc); +#define ARGF() (_argt=_args, _args=(char*)"",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): 0)) +#define EARGF(x) (_argt=_args, _args=(char*)"",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0))) + +#define ARGC() _argc + diff --git a/vendor/librw/cmake/FindSDL2.cmake b/vendor/librw/cmake/FindSDL2.cmake new file mode 100644 index 00000000..24288b4a --- /dev/null +++ b/vendor/librw/cmake/FindSDL2.cmake @@ -0,0 +1,38 @@ +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(SDL2 IMPORTED_TARGET "sdl2") + if(TARGET PkgConfig::SDL2 AND NOT TARGET SDL2::SDL2) + add_library(SDL2::SDL2 INTERFACE IMPORTED) + set_property(TARGET SDL2::SDL2 PROPERTY INTERFACE_LINK_LIBRARIES PkgConfig::SDL2) + endif() +endif() + +find_library(SDL2main_LIBRARY SDL2main) + +if(NOT SDL2_FOUND) + find_path(SDL2_INCLUDE_DIR sdl2.h) + find_library(SDL2_LIBRARY SDL2 SDL2d) + + find_library(SDL2main_LIBRARY SDL2main) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(libuv + REQUIRED_VARS SDL2_INCLUDE_DIR SDL2_LIBRARY + ) + + if(NOT TARGET SDL2::SDL2) + add_library(SDL2::SDL2 UNKNOWN IMPORTED) + set_target_properties(SDL2::SDL2 PROPERTIES + IMPORTED_LOCATION "${SDL2_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}" + ) + endif() +endif() + +if(SDL2main_LIBRARY AND NOT TARGET SDL2::SDL2main) + add_library(SDL2::SDL2main UNKNOWN IMPORTED) + set_target_properties(SDL2::SDL2main PROPERTIES + IMPORTED_LOCATION "${SDL2main_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}" + ) +endif() diff --git a/vendor/librw/cmake/librw-config.cmake.in b/vendor/librw/cmake/librw-config.cmake.in new file mode 100644 index 00000000..47b3487c --- /dev/null +++ b/vendor/librw/cmake/librw-config.cmake.in @@ -0,0 +1,22 @@ +include("${CMAKE_CURRENT_LIST_DIR}/librw-targets.cmake") + +set(LIBRW_PLATFORM "@LIBRW_PLATFORM@") +set(LIBRW_PLATFORMS "@LIBRW_PLATFORMS@") +set(LIBRW_PLATFORM_@LIBRW_PLATFORM@ ON) + +if(LIBRW_PLATFORM_GL3) + set(LIBRW_GL3_GFXLIB "@LIBRW_GL3_GFXLIB@") + set(LIBRW_GL3_GFXLIBS "@LIBRW_GL3_GFXLIBS@") + + set(OpenGL_GL_PREFERENCE GLVND) + find_package(OpenGL) + if(NOT TARGET OpenGL::OpenGL AND NOT TARGET OpenGL::EGL AND NOT TARGET OpenGL::GL) + message(FATAL_ERROR "find_package(OpenGL) failed: no target was created") + endif() + + if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") + find_package(glfw3 REQUIRED) + elseif(LIBRW_GL3_GFXLIB STREQUAL "SDL2") + find_package(SDL2 REQUIRED) + endif() +endif() diff --git a/vendor/librw/cmake/nx/NXFunctions.cmake b/vendor/librw/cmake/nx/NXFunctions.cmake new file mode 100644 index 00000000..bab3360c --- /dev/null +++ b/vendor/librw/cmake/nx/NXFunctions.cmake @@ -0,0 +1,37 @@ +if(NOT COMMAND nx_generate_nacp) + message(FATAL_ERROR "The `nx_generate_nacp` cmake command is not available. Please use an appropriate Nintendo Switch toolchain.") +endif() + +if(NOT COMMAND nx_create_nro) + message(FATAL_ERROR "The `nx_create_nro` cmake command is not available. Please use an appropriate Nintendo Switch toolchain.") +endif() + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +function(librw_platform_target TARGET) + cmake_parse_arguments(LPT "INSTALL" "" "" ${ARGN}) + + get_target_property(TARGET_TYPE "${TARGET}" TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + nx_generate_nacp(${TARGET}.nacp + NAME "${TARGET}" + AUTHOR "${librw_AUTHOR}" + VERSION "${librw_VERSION}" + ) + + nx_create_nro(${TARGET} + NACP ${TARGET}.nacp + ) + + if(LIBRW_INSTALL AND LPT_INSTALL) + get_target_property(TARGET_OUTPUT_NAME ${TARGET} OUTPUT_NAME) + if(NOT TARGET_OUTPUT_NAME) + set(TARGET_OUTPUT_NAME "${TARGET}") + endif() + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_OUTPUT_NAME}.nro" + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + endif() + endif() +endfunction() diff --git a/vendor/librw/cmake/ps2/PS2Functions.cmake b/vendor/librw/cmake/ps2/PS2Functions.cmake new file mode 100644 index 00000000..5cd81dd6 --- /dev/null +++ b/vendor/librw/cmake/ps2/PS2Functions.cmake @@ -0,0 +1,18 @@ +if(NOT COMMAND add_erl_executable) + message(FATAL_ERROR "The `add_erl_executable` cmake command is not available. Please use an appropriate Playstation 2 toolchain.") +endif() + +function(librw_platform_target TARGET) + cmake_parse_arguments(LPT "INSTALL" "" "" ${ARGN}) + + get_target_property(TARGET_TYPE "${TARGET}" TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + add_erl_executable(${TARGET} OUTPUT_VAR ERL_FILE) + + if(LIBRW_INSTALL AND LPT_INSTALL) + install(FILES "${ERL_FILE}" + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + endif() + endif() +endfunction() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in new file mode 100644 index 00000000..9a55d4cc --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in @@ -0,0 +1,16 @@ +set(CMAKE_DSM_COMPILER "@_CMAKE_DSM_COMPILER@") +set(CMAKE_DSM_COMPILER_ARG1 "@_CMAKE_DSM_COMPILER_ARG1@") +set(CMAKE_DSM_COMPILER_AR "@_CMAKE_DSM_COMPILER_AR@") +set(CMAKE_RANLIB "@CMAKE_RANLIB@") +set(CMAKE_DSM_COMPILER_RANLIB "@_CMAKE_DSM_COMPILER_RANLIB@") +set(CMAKE_LINKER "@CMAKE_LINKER@") +set(CMAKE_DSM_COMPILER_LOADED 1) +set(CMAKE_DSM_COMPILER_ID "@_CMAKE_DSM_COMPILER_ID@") +set(CMAKE_DSM_COMPILER_VERSION "@_CMAKE_DSM_COMPILER_VERSION@") +set(CMAKE_DSM_COMPILER_ENV_VAR "@_CMAKE_DSM_COMPILER_ENV_VAR@") +@_SET_CMAKE_DSM_COMPILER_ARCHITECTURE_ID@ + +set(CMAKE_DSM_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) +set(CMAKE_DSM_LINKER_PREFERENCE 0) + +@CMAKE_DSM_COMPILER_CUSTOM_CODE@ diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake new file mode 100644 index 00000000..27b56d62 --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake @@ -0,0 +1,79 @@ +if(UNIX) + set(CMAKE_DSM_OUTPUT_EXTENSION .o) +else() + set(CMAKE_DSM_OUTPUT_EXTENSION .obj) +endif() + +set(CMAKE_INCLUDE_FLAG_DSM "-I") + +set(CMAKE_DSM_FLAGS_INIT "$ENV{DSMFLAGS} ${CMAKE_DSM_FLAGS_INIT}") + +# replace for CMake >= 3.11 +foreach(c "" _DEBUG _RELEASE _MINSIZEREL _RELWITHDEBINFO) + string(STRIP "${CMAKE_DSM_FLAGS${c}_INIT}" CMAKE_DSM_FLAGS${c}_INIT) +endforeach() + +set (CMAKE_DSM_FLAGS "${CMAKE_DSM_FLAGS_INIT}" CACHE STRING + "Flags used by the assembler during all build types.") + +if(NOT CMAKE_NOT_USING_CONFIG_FLAGS) + get_property(_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + # default build type is none + if(NOT _GENERATOR_IS_MULTI_CONFIG AND NOT CMAKE_NO_BUILD_TYPE) + set (CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE_INIT} CACHE STRING + "Choose the type of build, options are: None, Debug Release RelWithDebInfo MinSizeRel.") + endif() + unset(_GENERATOR_IS_MULTI_CONFIG) + set (CMAKE_DSM_FLAGS_DEBUG "${CMAKE_DSM_FLAGS_DEBUG_INIT}" CACHE STRING + "Flags used by the assembler during debug builds.") + set (CMAKE_DSM_FLAGS_MINSIZEREL "${CMAKE_DSM_FLAGS_MINSIZEREL_INIT}" CACHE STRING + "Flags used by the assembler during release minsize builds.") + set (CMAKE_DSM_FLAGS_RELEASE "${CMAKE_DSM_FLAGS_RELEASE_INIT}" CACHE STRING + "Flags used by the assembler during release builds.") + set (CMAKE_DSM_FLAGS_RELWITHDEBINFO "${CMAKE_DSM_FLAGS_RELWITHDEBINFO_INIT}" CACHE STRING + "Flags used by the assembler during Release with Debug Info builds.") +endif() + +mark_as_advanced(CMAKE_DSM_FLAGS + CMAKE_DSM_FLAGS_DEBUG + CMAKE_DSM_FLAGS_MINSIZEREL + CMAKE_DSM_FLAGS_RELEASE + CMAKE_DSM_FLAGS_RELWITHDEBINFO + ) +# WITH: cmake_initialize_per_config_variable(CMAKE_DSM_FLAGS "Flags used by the DSM compiler") + +if(NOT CMAKE_DSM_COMPILE_OBJECT) + set(CMAKE_DSM_COMPILE_OBJECT " -o ") +endif() + +if(NOT CMAKE_DSM_CREATE_STATIC_LIBRARY) + set(CMAKE_DSM_CREATE_STATIC_LIBRARY + " cr " + " ") +endif() + +if(NOT CMAKE_DSM_LINK_EXECUTABLE) + set(CMAKE_DSM_LINK_EXECUTABLE + " -o ") +endif() + +if(NOT CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG) + set(CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG ${CMAKE_SHARED_LIBRARY_RUNTIME_DSM_FLAG}) +endif() + +if(NOT CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG_SEP) + set(CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG_SEP ${CMAKE_SHARED_LIBRARY_RUNTIME_DSM_FLAG_SEP}) +endif() + +if(NOT CMAKE_EXECUTABLE_RPATH_LINK_DSM_FLAG) + set(CMAKE_EXECUTABLE_RPATH_LINK_DSM_FLAG ${CMAKE_SHARED_LIBRARY_RPATH_LINK_DSM_FLAG}) +endif() + +# to be done +if(NOT CMAKE_DSM_CREATE_SHARED_LIBRARY) + set(CMAKE_DSM_CREATE_SHARED_LIBRARY) +endif() + +if(NOT CMAKE_DSM_CREATE_SHARED_MODULE) + set(CMAKE_DSM_CREATE_SHARED_MODULE) +endif() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake new file mode 100644 index 00000000..d3bda8b2 --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake @@ -0,0 +1,87 @@ +include(${CMAKE_ROOT}/Modules/CMakeDetermineCompiler.cmake) + +if (NOT CMAKE_DSM_COMPILER) + message(FATAL_ERROR "Need CMAKE_DSM_COMPILER set") +endif() + +_cmake_find_compiler_path(DSM) +mark_as_advanced(CMAKE_DSM_COMPILER) + +if (NOT CMAKE_DSM_COMPILER_ID) + # Table of per-vendor compiler id flags with expected output. + list(APPEND CMAKE_DSM_COMPILER_ID_VENDORS GNU ) + set(CMAKE_DSM_COMPILER_ID_VENDOR_FLAGS_GNU "--version") + set(CMAKE_DSM_COMPILER_ID_VENDOR_REGEX_GNU "(GNU assembler)|(GCC)|(Free Software Foundation)") + + include(CMakeDetermineCompilerId) + cmake_determine_compiler_id_vendor(DSM "") + +endif() + +if (NOT _CMAKE_TOOLCHAIN_LOCATION) + get_filename_component(_CMAKE_TOOLCHAIN_LOCATION "${CMAKE_DSM_COMPILER}" PATH) +endif() + +if (CMAKE_DSM_COMPILER_ID) + if (CMAKE_DSM_COMPILER_VERSION) + set(_version " ${CMAKE_DSM_COMPILER_VERSION}") + else() + set(_version "") + endif() + message(STATUS "The DSM compiler identification is ${CMAKE_DSM_COMPILER_ID}${_version}") + unset(_version) +else() + message(STATUS "The DSM compiler identification is unknown") +endif() + +if (NOT _CMAKE_TOOLCHAIN_PREFIX) + get_filename_component(COMPILER_BASENAME "${CMAKE_DSM_COMPILER}" NAME) + if (COMPILER_BASENAME MATCHES "^(.+1)g?as(-[0-9]+\\.[0-9]+\\.[0-9]+)?(\\.exe)?$") + set(_CMAKE_TOOLCHAIN_PREFIX ${CMAKE_MATCH_1}) + endif() + +endif() + +set(_CMAKE_PROCESSING_LANGUAGE "DSM") +find_program(CMAKE_DSM_COMPILER_AR NAMES ${_CMAKE_TOOLCHAIN_PREFIX}ar HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_RANLIB NAMES ${_CMAKE_TOOLCHAIN_PREFIX}ranlib HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_STRIP NAMES ${_CMAKE_TOOLCHAIN_PREFIX}strip HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_NM NAMES ${_CMAKE_TOOLCHAIN_PREFIX}nm HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_OBJDUMP NAMES ${_CMAKE_TOOLCHAIN_PREFIX}objdump HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_OBJCOPY NAMES ${_CMAKE_TOOLCHAIN_PREFIX}objcopy HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) + +unset(_CMAKE_PROCESSING_LANGUAGE) + +set(CMAKE_DSM_COMPILER_ENV_VAR "DSM") + +if (CMAKE_DSM_COMPILER) + message(STATUS "Found DSM assembler: ${CMAKE_DSM_COMPILER}") +else() + message(STATUS "Didn't find assembler") +endif() + +foreach(_var + COMPILER + COMPILER_ID + COMPILER_ARG1 + COMPILER_ENV_VAR + COMPILER_AR + COMPILER_RANLIB + COMPILER_VERSION + ) + set(_CMAKE_DSM_${_var} "${CMAKE_DSM_${_var}}") +endforeach() + +configure_file("${CMAKE_CURRENT_LIST_DIR}/CMakeDSMCompiler.cmake.in" + "${CMAKE_PLATFORM_INFO_DIR}/CMakeDSMCompiler.cmake" @ONLY) + +foreach(_var + COMPILER + COMPILER_ID + COMPILER_ARG1 + COMPILER_ENV_VAR + COMPILER_AR + COMPILER_VERSION + ) + unset(_CMAKE_DSM_${_var}) +endforeach() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake new file mode 100644 index 00000000..5c514ea2 --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake @@ -0,0 +1,7 @@ +set(_ASM_COMPILER_WORKS 0) + +if(CMAKE_DSM_COMPILER) + set(_DSM_COMPILER_WORKS) +endif() + +set(CMAKE_DSM_COMPILER_WORKS ${_DSM_COMPILER_WORKS} CACHE INTERNAL "") diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake new file mode 100644 index 00000000..bd2995e3 --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake @@ -0,0 +1 @@ +set(CMAKE_EXECUTABLE_SUFFIX ".elf") diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py b/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py new file mode 100644 index 00000000..4cc9d3ca --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py @@ -0,0 +1,24 @@ +from conans import ConanFile +import os +import shutil + + +class Ps2devCMakeToolchainConan(ConanFile): + name = "ps2dev-cmaketoolchain" + description = "CMake toolchain script for ps2dev" + topics = "ps2", "sdk", "library", "sony", "playstation", "ps2" + + def export_sources(self): + self.copy("*.cmake*", dst="cmake") + self.copy("Platform", dst="cmake") + + def package(self): + shutil.copytree(os.path.join(self.source_folder, "cmake"), + os.path.join(self.package_folder, "cmake")) + + def package_info(self): + self.user_info.cmake_dir = os.path.join(self.package_folder, "cmake").replace("\\", "/") + + cmake_toolchain_file = os.path.join(self.package_folder, "cmake", "toolchain_ps2_ee.cmake").replace("\\", "/") + self.user_info.cmake_toolchain_file = cmake_toolchain_file + self.cpp_info.CONAN_CMAKE_TOOLCHAIN_FILE = cmake_toolchain_file diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake new file mode 100644 index 00000000..da1efad2 --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.7) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + +set(CMAKE_SYSTEM_NAME "PlayStation2") +set(CMAKE_SYSTEM_PROCESSOR "mips64r5900el") +set(CMAKE_SYSTEM_VERSION 1) + +set(CMAKE_NO_SYSTEM_FROM_IMPORTED ON) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +if(NOT DEFINED ENV{PS2DEV}) + message(FATAL_ERROR "Need environment variable PS2DEV set") +endif() +if(NOT DEFINED ENV{PS2SDK}) + message(FATAL_ERROR "Need environment variable PS2SDK set") +endif() +if(NOT DEFINED ENV{GSKIT}) + message(FATAL_ERROR "Need environment variable PS2SDK set") +endif() + +set(PS2DEV "$ENV{PS2DEV}") +set(PS2SDK "$ENV{PS2SDK}") +set(GSKIT "$ENV{GSKIT}") + +if(NOT IS_DIRECTORY "${PS2DEV}") + message(FATAL_ERROR "PS2DEV must be a folder path (${PS2DEV})") +endif() + +if(NOT IS_DIRECTORY "${PS2SDK}") + message(FATAL_ERROR "PS2SDK must be a folder path (${PS2SDK})") +endif() + +if(NOT IS_DIRECTORY "${GSKIT}") + message(FATAL_ERROR "GSKIT must be a folder path (${GSKIT})") +endif() + +set(CMAKE_DSM_SOURCE_FILE_EXTENSIONS "dsm") + +set(CMAKE_C_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-gcc" CACHE FILEPATH "C compiler") +set(CMAKE_CXX_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-g++" CACHE FILEPATH "CXX compiler") +set(CMAKE_ASM_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-g++" CACHE FILEPATH "ASM assembler") +set(CMAKE_DSM_COMPILER "${PS2DEV}/dvp/bin/dvp-as" CACHE FILEPATH "DSM assembler") +set(CMAKE_AR "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ar" CACHE FILEPATH "archiver") +set(CMAKE_LINKER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ld" CACHE FILEPATH "Linker") +set(CMAKE_RANLIB "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ranlib" CACHE FILEPATH "ranlib") +set(CMAKE_STRIP "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-strip" CACHE FILEPATH "strip") + +set(CMAKE_ASM_FLAGS_INIT "-G0 -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_C_FLAGS_INIT "-G0 -fno-common -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_CXX_FLAGS_INIT "-G0 -fno-common -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-G0 -L\"${PS2SDK}/ee/lib\" -Wl,-r -Wl,-d") + +set(CMAKE_FIND_ROOT_PATH "${PS2DEV}/ee" "${PS2SDK}/ee" "${GSKIT}") +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +set(PS2 1) +set(EE 1) + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +function(add_erl_executable TARGET) + cmake_parse_arguments("AEE" "" "OUTPUT_VAR" "" ${ARGN}) + + get_target_property(output_dir "${TARGET}" RUNTIME_OUTPUT_DIRECTORY) + if(NOT output_dir) + set(output_dir ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + get_target_property(output_name ${TARGET} OUTPUT_NAME) + if(NOT output_name) + set(output_name ${TARGET}) + endif() + set(outfile "${output_dir}/${output_name}.erl") + + add_custom_command(OUTPUT "${outfile}" + COMMAND "${CMAKE_COMMAND}" -E copy "$" "${outfile}" + COMMAND "${CMAKE_STRIP}" --strip-unneeded -R .mdebug.eabi64 -R .reginfo -R .comment "${outfile}" + DEPENDS ${TARGET} + ) + add_custom_target("${TARGET}_erl" ALL + DEPENDS "${outfile}" + ) + + if(AEE_OUTPUT_VAR) + set("${AEE_OUTPUT_VAR}" "${outfile}" PARENT_SCOPE) + endif() +endfunction() diff --git a/vendor/librw/conan/playstation2 b/vendor/librw/conan/playstation2 new file mode 100644 index 00000000..ddad52b9 --- /dev/null +++ b/vendor/librw/conan/playstation2 @@ -0,0 +1,12 @@ +[settings] +os=Playstation2 +arch=mips +compiler=gcc +compiler.version=3.2 +compiler.libcxx=libstdc++ +build_type=Release +[options] +librw:platform=ps2 +[build_requires] +ps2dev-ps2toolchain/unknown@madebr/testing +[env] diff --git a/vendor/librw/conanfile.py b/vendor/librw/conanfile.py new file mode 100644 index 00000000..4cefb491 --- /dev/null +++ b/vendor/librw/conanfile.py @@ -0,0 +1,136 @@ +from conans import ConanFile, CMake, tools +from conans.errors import ConanException, ConanInvalidConfiguration +import os +import shutil +import textwrap + + +class LibrwConan(ConanFile): + name = "librw" + version = "master" + license = "MIT" + settings = "os", "arch", "compiler", "build_type" + generators = "cmake", "cmake_find_package" + options = { + "platform": ["null", "gl3", "d3d9", "ps2"], + "gl3_gfxlib": ["glfw", "sdl2"], + } + default_options = { + "platform": "gl3", + "gl3_gfxlib": "glfw", + "openal:with_external_libs": False, + "sdl2:vulkan": False, + "sdl2:opengl": True, + "sdl2:sdl2main": True, + } + no_copy_source = True + + @property + def _os_is_playstation2(self): + try: + return self.settings.os == "Playstation2" + except ConanException: + return False + + def config_options(self): + if self._os_is_playstation2: + self.options.platform = "ps2" + if self.settings.os == "Windows": + self.options.platform = "d3d9" + self.options["sdl2"].directx = False + + def configure(self): + if self.options.platform != "gl3": + del self.options.gl3_gfxlib + + def validate(self): + if self.options.platform == "d3d9" and self.settings.os != "Windows": + raise ConanInvalidConfiguration("platform=d3d9 can only be built for os=Windows") + if self._os_is_playstation2: + if self.options.platform not in ("null", "ps2"): + raise ConanInvalidConfiguration("os=Playstation2 only supports platform=(null,ps2)") + + def requirements(self): + if self.options.platform == "gl3": + if self.options.gl3_gfxlib == "glfw": + self.requires("glfw/3.3.2") + elif self.options.gl3_gfxlib == "sdl2": + self.requires("sdl2/2.0.12@bincrafters/stable") + elif self.options.platform == "ps2": + self.requires("ps2dev-ps2sdk/unknown@madebr/testing") + if self._os_is_playstation2: + self.requires("ps2dev-cmaketoolchain/{}".format(self.version)) + + def export_sources(self): + for d in ("cmake", "skeleton", "src", "tools"): + shutil.copytree(src=d, dst=os.path.join(self.export_sources_folder, d)) + self.copy("args.h") + self.copy("rw.h") + self.copy("CMakeLists.txt") + self.copy("LICENSE") + + @property + def _librw_platform(self): + return { + "null": "NULL", + "gl3": "GL3", + "d3d9": "D3D9", + "ps2": "PS2", + }[str(self.options.platform)] + + def build(self): + if self.source_folder == self.build_folder: + raise Exception("cannot build with source_folder == build_folder") + if self.options.platform == "gl3" and self.options.gl3_gfxlib == "glfw": + tools.save("Findglfw3.cmake", + textwrap.dedent( + """ + if(NOT TARGET glfw) + message(STATUS "Creating glfw TARGET") + add_library(glfw INTERFACE IMPORTED) + set_target_properties(glfw PROPERTIES + INTERFACE_LINK_LIBRARIES CONAN_PKG::glfw) + endif() + """), append=True) + tools.save("CMakeLists.txt", + textwrap.dedent( + """ + cmake_minimum_required(VERSION 3.0) + project(cmake_wrapper) + + include("{}/conanbuildinfo.cmake") + conan_basic_setup(TARGETS) + + add_subdirectory("{}" librw) + """).format(self.install_folder.replace("\\", "/"), + self.source_folder.replace("\\", "/"))) + cmake = CMake(self) + env = {} + cmake.definitions["LIBRW_PLATFORM"] = self._librw_platform + cmake.definitions["LIBRW_INSTALL"] = True + cmake.definitions["LIBRW_TOOLS"] = True + if self.options.platform == "gl3": + cmake.definitions["LIBRW_GL3_GFXLIB"] = str(self.options.gl3_gfxlib).upper() + if self._os_is_playstation2: + env["PS2SDK"] = self.deps_cpp_info["ps2dev-ps2sdk"].rootpath + with tools.environment_append(env): + cmake.configure(source_folder=self.build_folder) + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.includedirs.append(os.path.join("include", "librw")) + self.cpp_info.libs = ["librw" if self.settings.compiler == "Visual Studio" else "rw"] + if self.options.platform == "null": + self.cpp_info.defines.append("RW_NULL") + elif self.options.platform == "gl3": + self.cpp_info.defines.append("RW_GL3") + if self.options.gl3_gfxlib == "sdl2": + self.cpp_info.defines.append("LIBRW_SDL2") + elif self.options.platform == "d3d9": + self.cpp_info.defines.append("RW_D3D9") + elif self.options.platform == "ps2": + self.cpp_info.defines.append("RW_PS2") diff --git a/vendor/librw/docker_rebuild_ps2.sh b/vendor/librw/docker_rebuild_ps2.sh new file mode 100755 index 00000000..cf8148fb --- /dev/null +++ b/vendor/librw/docker_rebuild_ps2.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +TARGET=release_ps2 + +set -e + +LIBRWDIR=$(dirname "$(readlink -f "$0")") +echo "LIBRWDIR is $LIBRWDIR" + +cd "$LIBRWDIR" + +premake5 gmake + +docker rm librw_instance -f >/dev/null 2>&1 || true +docker stop librw_instance -t 0 >/dev/null 2>&1 || true + +docker pull librw/librw +docker run -v "$LIBRWDIR:/librw:rw,z" --name librw_instance -d librw/librw sleep infinity +docker exec -u builder librw_instance /bin/bash -c "cd /librw/build && make clean config=$TARGET && make config=$TARGET verbose=1" + +docker rm librw_instance -f >/dev/null 2>&1 || true +docker stop librw_instance -t 0 >/dev/null 2>&1 || true diff --git a/vendor/librw/premake-vs2019.cmd b/vendor/librw/premake-vs2019.cmd new file mode 100644 index 00000000..18298f5e --- /dev/null +++ b/vendor/librw/premake-vs2019.cmd @@ -0,0 +1 @@ +premake5 vs2019 \ No newline at end of file diff --git a/vendor/librw/premake5.exe b/vendor/librw/premake5.exe new file mode 100644 index 0000000000000000000000000000000000000000..9048d51e09da5bde02e6337dcfaa603a2cebf292 GIT binary patch literal 1362432 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