diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a524845f9..689b92ac6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -89,18 +89,47 @@ jobs: echo nofiles=ignore>> "%GITHUB_OUTPUT%" echo moredef=-DLOVE_EXTRA_DLLS=%CD%\angle\libEGL.dll;%CD%\angle\libGLESv2.dll>> "%GITHUB_OUTPUT%" exit /b 0 + - name: Download Windows SDK Setup 10.0.20348 + run: curl -Lo winsdksetup.exe https://go.microsoft.com/fwlink/?linkid=2164145 + - name: Install Debugging Tools for Windows + id: windbg + run: | + setlocal enabledelayedexpansion + start /WAIT %CD%\winsdksetup.exe /features OptionId.WindowsDesktopDebuggers /q /log %CD%\log.txt + echo ERRORLEVEL=!ERRORLEVEL! >> %GITHUB_OUTPUT% + - name: Print Debugging Tools Install Log + if: always() + run: | + type log.txt + exit /b ${{ steps.windbg.outputs.ERRORLEVEL }} + - name: Setup Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Download source_index.py + run: curl -Lo source_index.py https://gist.github.com/MikuAuahDark/d9c099f5714e09a765496471c2827a55/raw/df34956052035f3473c5f01861dfb53930d06843/source_index.py - name: Clone Megasource uses: actions/checkout@v3 with: path: megasource repository: love2d/megasource ref: 12.x + - id: megasource + name: Get Megasource Commit SHA + shell: python + run: | + import os + import subprocess + + result = subprocess.run("git -C megasource rev-parse HEAD".split(), check=True, capture_output=True, encoding="UTF-8") + commit = result.stdout.split()[0] + with open(os.environ["GITHUB_OUTPUT"], "w", encoding="UTF-8") as f: f.write(f"commit={commit}") - name: Checkout uses: actions/checkout@v3 with: path: megasource/libs/love - name: Download ANGLE - uses: robinraju/release-downloader@v1.5 + uses: robinraju/release-downloader@v1.7 if: steps.vars.outputs.angle == '1' with: repository: MikuAuahDark/angle-winbuild @@ -121,9 +150,27 @@ jobs: rmdir /s /q C:\Strawberry exit /b 0 - name: Configure - run: cmake -Bbuild -Hmegasource -T v142 -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=%CD%\install ${{ steps.vars.outputs.moredef }} + env: + CFLAGS: /Zi + CXXFLAGS: /Zi + LDFLAGS: /DEBUG:FULL /OPT:REF /OPT:ICF + run: cmake -Bbuild -Smegasource -T v142 -A ${{ matrix.platform }} --install-prefix %CD%\install -DCMAKE_PDB_OUTPUT_DIRECTORY=%CD%\pdb ${{ steps.vars.outputs.moredef }} - name: Install run: cmake --build build --target PACKAGE --config Release -j2 + - name: Copy LuaJIT lua51.pdb + run: | + copy /Y build\libs\LuaJIT\src\lua51.pdb pdb\Release\lua51.pdb + exit /b 0 + - name: Add srcsrv to PATH + run: | + echo C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\srcsrv>>%GITHUB_PATH% + - name: Embed Source Index into PDBs + run: | + python source_index.py ^ + --source %CD%\megasource\libs\love https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }} ^ + --source %CD%\megasource https://raw.githubusercontent.com/love2d/megasource/${{ steps.megasource.outputs.commit }} ^ + --source %CD%\build\libs\LuaJIT https://raw.githubusercontent.com/love2d/megasource/${{ steps.megasource.outputs.commit }}/libs/LuaJIT ^ + pdb\Release\*.pdb - name: Artifact uses: actions/upload-artifact@v3 with: @@ -138,6 +185,11 @@ jobs: with: name: love-windows-jitmodules path: build/libs/LuaJIT/src/jit/*.lua + - name: Artifact PDB + uses: actions/upload-artifact@v3 + with: + name: love-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-dbg + path: pdb/Release/*.pdb macOS: runs-on: macos-latest steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index dd63b7d45..f20c66fe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ if(MEGA) set(LOVE_LINK_LIBRARIES ${MEGA_FREETYPE} + ${MEGA_HARFBUZZ} ${MEGA_LIBOGG} ${MEGA_LIBVORBISFILE} ${MEGA_LIBVORBIS} @@ -173,6 +174,7 @@ Please see https://github.com/love2d/megasource endif() find_package(Freetype REQUIRED) + find_package(harfbuzz REQUIRED) find_package(ModPlug REQUIRED) find_package(OpenAL REQUIRED) find_package(OpenGL REQUIRED) @@ -200,6 +202,7 @@ Please see https://github.com/love2d/megasource ${OPENGL_gl_LIBRARY} ${SDL2_LIBRARY} ${FREETYPE_LIBRARY} + ${HARFBUZZ_LIBRARY} ${OPENAL_LIBRARY} ${MODPLUG_LIBRARY} ${THEORA_LIBRARY} @@ -477,12 +480,16 @@ set(LOVE_SRC_MODULE_FONT_ROOT src/modules/font/BMFontRasterizer.h src/modules/font/Font.cpp src/modules/font/Font.h + src/modules/font/GenericShaper.cpp + src/modules/font/GenericShaper.h src/modules/font/GlyphData.cpp src/modules/font/GlyphData.h src/modules/font/ImageRasterizer.cpp src/modules/font/ImageRasterizer.h src/modules/font/Rasterizer.cpp src/modules/font/Rasterizer.h + src/modules/font/TextShaper.cpp + src/modules/font/TextShaper.h src/modules/font/TrueTypeRasterizer.cpp src/modules/font/TrueTypeRasterizer.h src/modules/font/wrap_Font.cpp @@ -496,6 +503,8 @@ set(LOVE_SRC_MODULE_FONT_ROOT set(LOVE_SRC_MODULE_FONT_FREETYPE src/modules/font/freetype/Font.cpp src/modules/font/freetype/Font.h + src/modules/font/freetype/HarfbuzzShaper.cpp + src/modules/font/freetype/HarfbuzzShaper.h src/modules/font/freetype/TrueTypeRasterizer.cpp src/modules/font/freetype/TrueTypeRasterizer.h ) diff --git a/license.txt b/license.txt index 8cf1debae..a430dab12 100644 --- a/license.txt +++ b/license.txt @@ -99,7 +99,7 @@ This distribution contains code from the following projects (full license text b - dr_mp3 Website: https://mackron.github.io/dr_libs/ - Source download: https://github.com/mackron/dr_libs/blob/47fdc9d/dr_mp3.h + Source download: https://github.com/mackron/dr_libs/blob/dd762b8/dr_mp3.h License: MIT/Expat Copyright 2018 David Reid diff --git a/platform/unix/configure.ac b/platform/unix/configure.ac index fd639a91e..210d54d41 100644 --- a/platform/unix/configure.ac +++ b/platform/unix/configure.ac @@ -67,7 +67,10 @@ ACLOVE_DEP_PTHREAD # Conditional dependencies AS_VAR_IF([enable_module_audio], [yes], [ACLOVE_DEP_OPENAL], []) -AS_VAR_IF([enable_module_font], [yes], [ACLOVE_DEP_FREETYPE2], []) +AS_VAR_IF([enable_module_font], [yes], [ + ACLOVE_DEP_FREETYPE2 + ACLOVE_DEP_HARFBUZZ +], []) AS_VAR_IF([enable_module_sound], [yes], [ ACLOVE_DEP_LIBMODPLUG ACLOVE_DEP_VORBISFILE diff --git a/platform/unix/debian/control.in b/platform/unix/debian/control.in index e2dc757fc..e435df0ff 100644 --- a/platform/unix/debian/control.in +++ b/platform/unix/debian/control.in @@ -8,12 +8,13 @@ Build-Depends: debhelper (>= 9), libtool, g++ (>= 4.7.0), libfreetype6-dev, + libharfbuzz-dev, luajit, libluajit-5.1-dev, libmodplug-dev, libopenal-dev, libphysfs-dev, - libsdl2-dev (>= 2.0.1), + libsdl2-dev (>= 2.0.9), libogg-dev, libvorbis-dev, libtheora-dev, diff --git a/platform/unix/deps.m4 b/platform/unix/deps.m4 index e087a88e8..868bd0244 100644 --- a/platform/unix/deps.m4 +++ b/platform/unix/deps.m4 @@ -1,6 +1,9 @@ AC_DEFUN([ACLOVE_DEP_FREETYPE2], [ PKG_CHECK_MODULES([freetype2], [freetype2], [], [LOVE_MSG_ERROR([FreeType2])])]) +AC_DEFUN([ACLOVE_DEP_HARFBUZZ], [ + PKG_CHECK_MODULES([harfbuzz], [harfbuzz], [], [LOVE_MSG_ERROR([Harfbuzz])])]) + AC_DEFUN([ACLOVE_DEP_OPENAL], [ PKG_CHECK_MODULES([openal], [openal], [], [LOVE_MSG_ERROR([OpenAL])])]) diff --git a/platform/unix/genmodules b/platform/unix/genmodules index 78551842b..3d7cdca0d 100644 --- a/platform/unix/genmodules +++ b/platform/unix/genmodules @@ -107,7 +107,7 @@ inc_libraries="$inc_current/libraries" cat > src/Makefile.am << EOF AM_CPPFLAGS = -I$inc_current -I$inc_modules -I$inc_libraries -I$inc_libraries/enet/libenet/include -I$inc_libraries/box2d \$(LOVE_INCLUDES) \$(FILE_OFFSET)\ - \$(SDL_CFLAGS) \$(lua_CFLAGS) \$(freetype2_CFLAGS)\ + \$(SDL_CFLAGS) \$(lua_CFLAGS) \$(freetype2_CFLAGS) \$(harfbuzz_CFLAGS)\ \$(openal_CFLAGS) \$(zlib_CFLAGS) \$(libmodplug_CFLAGS)\ \$(vorbisfile_CFLAGS) \$(theora_CFLAGS) AUTOMAKE_OPTIONS = subdir-objects @@ -139,7 +139,7 @@ endif lib_LTLIBRARIES = liblove${love_suffix}.la liblove${love_amsuffix}_la_LDFLAGS = -module -export-dynamic \$(LDFLAGS) -release \$(PACKAGE_VERSION) liblove${love_amsuffix}_la_LIBADD = \ - \$(SDL_LIBS) \$(freetype2_LIBS) \$(lua_LIBS)\ + \$(SDL_LIBS) \$(freetype2_LIBS) \$(harfbuzz_LIBS) \$(lua_LIBS)\ \$(openal_LIBS) \$(zlib_LIBS) \$(libmodplug_LIBS)\ \$(vorbisfile_LIBS) \$(theora_LIBS) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 43bdc7230..eef3d95b9 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -50,6 +50,17 @@ 217DFC101D9F6D490055D849 /* url.lua.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD41D9F6D490055D849 /* url.lua.h */; }; 217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; }; 217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; }; + D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */; }; + D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */; }; + D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; }; + D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; }; + D9DAB9292961F10000C64820 /* GenericShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9252961F0FF00C64820 /* GenericShaper.h */; }; + D9DAB92A2961F10000C64820 /* GenericShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */; }; + D9DAB92B2961F10000C64820 /* GenericShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */; }; + D9DAB92C2961F10000C64820 /* TextShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9272961F0FF00C64820 /* TextShaper.h */; }; + D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; }; + D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; }; + D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9312963CD7500C64820 /* harfbuzz.framework */; }; FA0A3A5F23366CE9001C269E /* floattypes.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0A3A5D23366CE9001C269E /* floattypes.h */; }; FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; }; FA0A3A6123366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; }; @@ -1391,6 +1402,14 @@ 217DFBD41D9F6D490055D849 /* url.lua.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = url.lua.h; sourceTree = ""; }; 217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = ""; }; 217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = ""; }; + D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = harfbuzz.xcframework; path = ios/libraries/harfbuzz.xcframework; sourceTree = ""; }; + D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HarfbuzzShaper.h; sourceTree = ""; }; + D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HarfbuzzShaper.cpp; sourceTree = ""; }; + D9DAB9252961F0FF00C64820 /* GenericShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GenericShaper.h; sourceTree = ""; }; + D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GenericShaper.cpp; sourceTree = ""; }; + D9DAB9272961F0FF00C64820 /* TextShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextShaper.h; sourceTree = ""; }; + D9DAB9282961F10000C64820 /* TextShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextShaper.cpp; sourceTree = ""; }; + D9DAB9312963CD7500C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = ""; }; FA08F5AE16C7525600F007B5 /* liblove-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "liblove-macosx.plist"; path = "macosx/liblove-macosx.plist"; sourceTree = ""; }; FA0A3A5D23366CE9001C269E /* floattypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = floattypes.h; sourceTree = ""; }; FA0A3A5E23366CE9001C269E /* floattypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = floattypes.cpp; sourceTree = ""; }; @@ -2288,6 +2307,7 @@ buildActionMask = 2147483647; files = ( FACFB751276D7E3B0089F78D /* freetype.xcframework in Frameworks */, + D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */, FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */, FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */, FACFB753276D7F860089F78D /* Lua.xcframework in Frameworks */, @@ -2311,6 +2331,7 @@ FA577AC516C7513400860150 /* libmodplug.framework in Frameworks */, FADF4CC62663D0EC004F95C1 /* libz.tbd in Frameworks */, FA577AC816C7513C00860150 /* ogg.framework in Frameworks */, + D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */, FA577ACA16C7514100860150 /* OpenGL.framework in Frameworks */, FA577ACD16C7514C00860150 /* vorbis.framework in Frameworks */, ); @@ -2810,6 +2831,8 @@ FA0B7B731A95902C000E1D17 /* Font.cpp */, FA0B7B741A95902C000E1D17 /* Font.h */, FA0B7B751A95902C000E1D17 /* freetype */, + D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */, + D9DAB9252961F0FF00C64820 /* GenericShaper.h */, FA0B7B7A1A95902C000E1D17 /* GlyphData.cpp */, FA0B7B7B1A95902C000E1D17 /* GlyphData.h */, FA0B7B7C1A95902C000E1D17 /* ImageRasterizer.cpp */, @@ -2817,6 +2840,8 @@ FA522D5923FA5ED40059EE3C /* NotoSans-Regular.ttf.gzip.h */, FA0B7B7E1A95902C000E1D17 /* Rasterizer.cpp */, FA0B7B7F1A95902C000E1D17 /* Rasterizer.h */, + D9DAB9282961F10000C64820 /* TextShaper.cpp */, + D9DAB9272961F0FF00C64820 /* TextShaper.h */, FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */, FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */, FA0B7B811A95902C000E1D17 /* wrap_Font.cpp */, @@ -2834,6 +2859,8 @@ children = ( FA0B7B761A95902C000E1D17 /* Font.cpp */, FA0B7B771A95902C000E1D17 /* Font.h */, + D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */, + D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */, FA0B7B781A95902C000E1D17 /* TrueTypeRasterizer.cpp */, FA0B7B791A95902C000E1D17 /* TrueTypeRasterizer.h */, ); @@ -3366,6 +3393,7 @@ FA577A7916C71A1700860150 /* Cocoa.framework */, FAA627CD18E7E1560080752D /* CoreServices.framework */, FAD43ECB1FF312D800831BB8 /* freetype.framework */, + D9DAB9312963CD7500C64820 /* harfbuzz.framework */, FA577A8216C71A5300860150 /* libmodplug.framework */, FADF4CC52663D0EC004F95C1 /* libz.tbd */, FA577A6D16C719EA00860150 /* Lua.framework */, @@ -3533,6 +3561,7 @@ FA5D24A31A96D2C300C6FC8F /* ios */ = { isa = PBXGroup; children = ( + D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */, FACFB750276D7E2B0089F78D /* freetype.xcframework */, FACFB752276D7F6F0089F78D /* Lua.xcframework */, FA84DE79277D4C88002674C6 /* modplug.xcframework */, @@ -4100,6 +4129,7 @@ FABDA9B82552448300B5C523 /* b2_motor_joint.h in Headers */, FA0B7AC11A958EA3000E1D17 /* callbacks.h in Headers */, FA3C5E491F8D80CA0003C579 /* ShaderStage.h in Headers */, + D9DAB9292961F10000C64820 /* GenericShaper.h in Headers */, FA0B7D8F1A95902C000E1D17 /* ddsHandler.h in Headers */, FAB2D5AC1AABDD8A008224A4 /* TrueTypeRasterizer.h in Headers */, FABDAA042552448300B5C523 /* b2_edge_shape.h in Headers */, @@ -4137,6 +4167,7 @@ FA0B7EDD1A95902D000E1D17 /* Touch.h in Headers */, FA0B7EDE1A95902D000E1D17 /* Touch.h in Headers */, FAC7CD861FE35E95006A60C7 /* physfs.h in Headers */, + D9DAB92C2961F10000C64820 /* TextShaper.h in Headers */, FAF6C9E523C2DE2900D7B5BC /* spirv.hpp in Headers */, FA522D4F23F9FE380059EE3C /* MP3Decoder.h in Headers */, 217DFBEE1D9F6D490055D849 /* luasocket.h in Headers */, @@ -4181,6 +4212,7 @@ FAF6C9E823C2DE2900D7B5BC /* GLSL.ext.EXT.h in Headers */, FACA02F71F5E396B0084B28F /* wrap_DataModule.h in Headers */, FABDA9BE2552448300B5C523 /* Box2D.h in Headers */, + D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */, FA56AA3A1FAFF02000A43D5F /* memory.h in Headers */, FA0B7E441A95902C000E1D17 /* wrap_CircleShape.h in Headers */, FA0B7EB41A95902C000E1D17 /* System.h in Headers */, @@ -4660,6 +4692,7 @@ FADF540E1E3D7CDD00012CC0 /* wrap_Video.cpp in Sources */, FA0B7D4C1A95902C000E1D17 /* Shader.cpp in Sources */, FA0B792A1A958E3B000E1D17 /* Matrix.cpp in Sources */, + D9DAB92B2961F10000C64820 /* GenericShaper.cpp in Sources */, FAF140981E20934C00F898D2 /* PpTokens.cpp in Sources */, FAF140AA1E20934C00F898D2 /* SymbolTable.cpp in Sources */, FABDA9892552448300B5C523 /* b2_contact.cpp in Sources */, @@ -4778,6 +4811,7 @@ FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */, FA0B7E131A95902C000E1D17 /* GearJoint.cpp in Sources */, FABDA99B2552448300B5C523 /* b2_polygon_contact.cpp in Sources */, + D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */, FA0B7DC21A95902C000E1D17 /* wrap_Joystick.cpp in Sources */, FA0B7CD41A95902C000E1D17 /* Source.cpp in Sources */, FAA3A9AF1B7D465A00CED060 /* android.cpp in Sources */, @@ -4926,6 +4960,7 @@ FABDA9FE2552448300B5C523 /* b2_edge_shape.cpp in Sources */, FA0B7D7A1A95902C000E1D17 /* Quad.cpp in Sources */, FA620A3B1AA305F6005DB4C2 /* types.cpp in Sources */, + D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */, FA0B7DD41A95902C000E1D17 /* BezierCurve.cpp in Sources */, FA0B7E7C1A95902C000E1D17 /* wrap_World.cpp in Sources */, FAF6C9F923C2DE2900D7B5BC /* doc.cpp in Sources */, @@ -5087,6 +5122,7 @@ FA0B7EA31A95902C000E1D17 /* SoundData.cpp in Sources */, FA0B79291A958E3B000E1D17 /* Matrix.cpp in Sources */, FA8951A21AA2EDF300EC385A /* wrap_Event.cpp in Sources */, + D9DAB92A2961F10000C64820 /* GenericShaper.cpp in Sources */, FAF140691E20934C00F898D2 /* glslang_tab.cpp in Sources */, FA0B7ABF1A958EA3000E1D17 /* host.c in Sources */, FA0B7D4B1A95902C000E1D17 /* Shader.cpp in Sources */, @@ -5205,6 +5241,7 @@ FAF140551E20934C00F898D2 /* Link.cpp in Sources */, FABDA9792552448200B5C523 /* b2_joint.cpp in Sources */, FAF140841E20934C00F898D2 /* ParseHelper.cpp in Sources */, + D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */, FA0B7D7F1A95902C000E1D17 /* Volatile.cpp in Sources */, FA1BA0B11E16FD0800AA2803 /* Shader.cpp in Sources */, FABDA99A2552448300B5C523 /* b2_polygon_contact.cpp in Sources */, @@ -5353,6 +5390,7 @@ FA0B7E881A95902C000E1D17 /* Decoder.cpp in Sources */, FA0B7E3C1A95902C000E1D17 /* wrap_Body.cpp in Sources */, FA0B7D791A95902C000E1D17 /* Quad.cpp in Sources */, + D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */, FABDA9FD2552448300B5C523 /* b2_edge_shape.cpp in Sources */, FAC756F51E4F99B400B91289 /* Effect.cpp in Sources */, FA620A3A1AA305F6005DB4C2 /* types.cpp in Sources */, @@ -5716,6 +5754,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5751,6 +5790,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5787,6 +5827,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index e91c5bec8..0d7b4911a 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/platform/xcode/love.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ A93E6EED10420BA8007D418B /* love.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A93E6A3410420AC0007D418B /* love.cpp */; }; A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F16926109E7BAD00FC83D1 /* libmodplug.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; CE73F8001EEB64150052DAB3 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */; }; + D9DAB9372963CF6900C64820 /* harfbuzz.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9352963CF5F00C64820 /* harfbuzz.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; FA0797991BF480A200034B7C /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0797981BF480A200034B7C /* GameController.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; FA08F69616C766E000F007B5 /* love.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; }; FA08F69716C766E700F007B5 /* love.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -87,6 +88,7 @@ FAD4B1731C1F50A3004CF150 /* theora.framework in Copy Frameworks */, FAAFF04716CB120000CCDE45 /* OpenAL-Soft.framework in Copy Frameworks */, FAD43ED01FF3136500831BB8 /* freetype.framework in Copy Frameworks */, + D9DAB9372963CF6900C64820 /* harfbuzz.framework in Copy Frameworks */, A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */, A9255F58104324E100BA1496 /* ogg.framework in Copy Frameworks */, A9255E031043195A00BA1496 /* vorbis.framework in Copy Frameworks */, @@ -108,6 +110,7 @@ A97E3842132A9EDE00198A2F /* love-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "love-macosx.plist"; path = "macosx/love-macosx.plist"; sourceTree = ""; }; A9F16926109E7BAD00FC83D1 /* libmodplug.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libmodplug.framework; path = macosx/Frameworks/libmodplug.framework; sourceTree = ""; }; CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; }; + D9DAB9352963CF5F00C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = ""; }; FA0797981BF480A200034B7C /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; }; FA08F69116C765A200F007B5 /* love.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = love.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FA0B7F061A95AAF3000E1D17 /* love.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = love.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -233,6 +236,7 @@ children = ( 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, FAD43ECF1FF3133700831BB8 /* freetype.framework */, + D9DAB9352963CF5F00C64820 /* harfbuzz.framework */, A9F16926109E7BAD00FC83D1 /* libmodplug.framework */, FA08F69116C765A200F007B5 /* love.framework */, A93E6E5310420B57007D418B /* Lua.framework */, diff --git a/src/libraries/dr/dr_mp3.h b/src/libraries/dr/dr_mp3.h index 4fe386abe..59876c877 100644 --- a/src/libraries/dr/dr_mp3.h +++ b/src/libraries/dr/dr_mp3.h @@ -1,88 +1,50 @@ /* MP3 audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_mp3 - v0.5.6 - 2020-02-12 +dr_mp3 - v0.6.34 - 2022-09-17 David Reid - mackron@gmail.com -Based off minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for -differences between minimp3 and dr_mp3. +GitHub: https://github.com/mackron/dr_libs + +Based on minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for differences between minimp3 and dr_mp3. */ /* -RELEASE NOTES - v0.5.0 -======================= -Version 0.5.0 has breaking API changes. +RELEASE NOTES - VERSION 0.6 +=========================== +Version 0.6 includes breaking changes with the configuration of decoders. The ability to customize the number of output channels and the sample rate has been +removed. You must now use the channel count and sample rate reported by the MP3 stream itself, and all channel and sample rate conversion must be done +yourself. -Improved Client-Defined Memory Allocation ------------------------------------------ -The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The -existing system of DRMP3_MALLOC, DRMP3_REALLOC and DRMP3_FREE are still in place and will be used by default when no custom -allocation callbacks are specified. -To use the new system, you pass in a pointer to a drmp3_allocation_callbacks object to drmp3_init() and family, like this: +Changes to Initialization +------------------------- +Previously, `drmp3_init()`, etc. took a pointer to a `drmp3_config` object that allowed you to customize the output channels and sample rate. This has been +removed. If you need the old behaviour you will need to convert the data yourself or just not upgrade. The following APIs have changed. - void* my_malloc(size_t sz, void* pUserData) - { - return malloc(sz); - } - void* my_realloc(void* p, size_t sz, void* pUserData) - { - return realloc(p, sz); - } - void my_free(void* p, void* pUserData) - { - free(p); - } + `drmp3_init()` + `drmp3_init_memory()` + `drmp3_init_file()` - ... - drmp3_allocation_callbacks allocationCallbacks; - allocationCallbacks.pUserData = &myData; - allocationCallbacks.onMalloc = my_malloc; - allocationCallbacks.onRealloc = my_realloc; - allocationCallbacks.onFree = my_free; - drmp3_init_file(&mp3, "my_file.mp3", NULL, &allocationCallbacks); - -The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. - -Passing in null for the allocation callbacks object will cause dr_mp3 to use defaults which is the same as DRMP3_MALLOC, -DRMP3_REALLOC and DRMP3_FREE and the equivalent of how it worked in previous versions. - -Every API that opens a drmp3 object now takes this extra parameter. These include the following: - - drmp3_init() - drmp3_init_file() - drmp3_init_memory() - drmp3_open_and_read_pcm_frames_f32() - drmp3_open_and_read_pcm_frames_s16() - drmp3_open_memory_and_read_pcm_frames_f32() - drmp3_open_memory_and_read_pcm_frames_s16() - drmp3_open_file_and_read_pcm_frames_f32() - drmp3_open_file_and_read_pcm_frames_s16() - -Renamed APIs ------------- -The following APIs have been renamed for consistency with other dr_* libraries and to make it clear that they return PCM frame -counts rather than sample counts. - - drmp3_open_and_read_f32() -> drmp3_open_and_read_pcm_frames_f32() - drmp3_open_and_read_s16() -> drmp3_open_and_read_pcm_frames_s16() - drmp3_open_memory_and_read_f32() -> drmp3_open_memory_and_read_pcm_frames_f32() - drmp3_open_memory_and_read_s16() -> drmp3_open_memory_and_read_pcm_frames_s16() - drmp3_open_file_and_read_f32() -> drmp3_open_file_and_read_pcm_frames_f32() - drmp3_open_file_and_read_s16() -> drmp3_open_file_and_read_pcm_frames_s16() +Miscellaneous Changes +--------------------- +Support for loading a file from a `wchar_t` string has been added via the `drmp3_init_file_w()` API. */ /* -USAGE -===== -dr_mp3 is a single-file library. To use it, do something like the following in one .c file. +Introducation +============= +dr_mp3 is a single file library. To use it, do something like the following in one .c file. + + ```c #define DR_MP3_IMPLEMENTATION #include "dr_mp3.h" + ``` -You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, -do something like the following: +You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, do something like the following: + ```c drmp3 mp3; if (!drmp3_init_file(&mp3, "MySong.mp3", NULL)) { // Failed to open file @@ -91,28 +53,27 @@ do something like the following: ... drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToRead, pFrames); + ``` The drmp3 object is transparent so you can get access to the channel count and sample rate like so: + ``` drmp3_uint32 channels = mp3.channels; drmp3_uint32 sampleRate = mp3.sampleRate; + ``` -The third parameter of drmp3_init_file() in the example above allows you to control the output channel count and sample rate. It -is a pointer to a drmp3_config object. Setting any of the variables of this object to 0 will cause dr_mp3 to use defaults. +The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek callbacks with +`drmp3_init_memory()` and `drmp3_init()` respectively. -The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek -callbacks with drmp3_init_memory() and drmp3_init() respectively. +You do not need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request any number of PCM frames in each +call to `drmp3_read_pcm_frames_f32()` and it will return as many PCM frames as it can, up to the requested amount. -You do not need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request -any number of PCM frames in each call to drmp3_read_pcm_frames_f32() and it will return as many PCM frames as it can, up to the -requested amount. - -You can also decode an entire file in one go with drmp3_open_and_read_pcm_frames_f32(), drmp3_open_memory_and_read_pcm_frames_f32() and -drmp3_open_file_and_read_pcm_frames_f32(). +You can also decode an entire file in one go with `drmp3_open_and_read_pcm_frames_f32()`, `drmp3_open_memory_and_read_pcm_frames_f32()` and +`drmp3_open_file_and_read_pcm_frames_f32()`. -OPTIONS -======= +Build Options +============= #define these options before including this file. #define DR_MP3_NO_STDIO @@ -129,32 +90,136 @@ OPTIONS extern "C" { #endif -#include +#define DRMP3_STRINGIFY(x) #x +#define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) -#if defined(_MSC_VER) && _MSC_VER < 1600 -typedef signed char drmp3_int8; -typedef unsigned char drmp3_uint8; -typedef signed short drmp3_int16; -typedef unsigned short drmp3_uint16; -typedef signed int drmp3_int32; -typedef unsigned int drmp3_uint32; -typedef signed __int64 drmp3_int64; -typedef unsigned __int64 drmp3_uint64; +#define DRMP3_VERSION_MAJOR 0 +#define DRMP3_VERSION_MINOR 6 +#define DRMP3_VERSION_REVISION 34 +#define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized types. */ +typedef signed char drmp3_int8; +typedef unsigned char drmp3_uint8; +typedef signed short drmp3_int16; +typedef unsigned short drmp3_uint16; +typedef signed int drmp3_int32; +typedef unsigned int drmp3_uint32; +#if defined(_MSC_VER) && !defined(__clang__) + typedef signed __int64 drmp3_int64; + typedef unsigned __int64 drmp3_uint64; #else -#include -typedef int8_t drmp3_int8; -typedef uint8_t drmp3_uint8; -typedef int16_t drmp3_int16; -typedef uint16_t drmp3_uint16; -typedef int32_t drmp3_int32; -typedef uint32_t drmp3_uint32; -typedef int64_t drmp3_int64; -typedef uint64_t drmp3_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drmp3_int64; + typedef unsigned long long drmp3_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif #endif -typedef drmp3_uint8 drmp3_bool8; -typedef drmp3_uint32 drmp3_bool32; -#define DRMP3_TRUE 1 -#define DRMP3_FALSE 0 +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + typedef drmp3_uint64 drmp3_uintptr; +#else + typedef drmp3_uint32 drmp3_uintptr; +#endif +typedef drmp3_uint8 drmp3_bool8; +typedef drmp3_uint32 drmp3_bool32; +#define DRMP3_TRUE 1 +#define DRMP3_FALSE 0 + +#if !defined(DRMP3_API) + #if defined(DRMP3_DLL) + #if defined(_WIN32) + #define DRMP3_DLL_IMPORT __declspec(dllimport) + #define DRMP3_DLL_EXPORT __declspec(dllexport) + #define DRMP3_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRMP3_DLL_IMPORT __attribute__((visibility("default"))) + #define DRMP3_DLL_EXPORT __attribute__((visibility("default"))) + #define DRMP3_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRMP3_DLL_IMPORT + #define DRMP3_DLL_EXPORT + #define DRMP3_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) + #define DRMP3_API DRMP3_DLL_EXPORT + #else + #define DRMP3_API DRMP3_DLL_IMPORT + #endif + #define DRMP3_PRIVATE DRMP3_DLL_PRIVATE + #else + #define DRMP3_API extern + #define DRMP3_PRIVATE static + #endif +#endif + +typedef drmp3_int32 drmp3_result; +#define DRMP3_SUCCESS 0 +#define DRMP3_ERROR -1 /* A generic error. */ +#define DRMP3_INVALID_ARGS -2 +#define DRMP3_INVALID_OPERATION -3 +#define DRMP3_OUT_OF_MEMORY -4 +#define DRMP3_OUT_OF_RANGE -5 +#define DRMP3_ACCESS_DENIED -6 +#define DRMP3_DOES_NOT_EXIST -7 +#define DRMP3_ALREADY_EXISTS -8 +#define DRMP3_TOO_MANY_OPEN_FILES -9 +#define DRMP3_INVALID_FILE -10 +#define DRMP3_TOO_BIG -11 +#define DRMP3_PATH_TOO_LONG -12 +#define DRMP3_NAME_TOO_LONG -13 +#define DRMP3_NOT_DIRECTORY -14 +#define DRMP3_IS_DIRECTORY -15 +#define DRMP3_DIRECTORY_NOT_EMPTY -16 +#define DRMP3_END_OF_FILE -17 +#define DRMP3_NO_SPACE -18 +#define DRMP3_BUSY -19 +#define DRMP3_IO_ERROR -20 +#define DRMP3_INTERRUPT -21 +#define DRMP3_UNAVAILABLE -22 +#define DRMP3_ALREADY_IN_USE -23 +#define DRMP3_BAD_ADDRESS -24 +#define DRMP3_BAD_SEEK -25 +#define DRMP3_BAD_PIPE -26 +#define DRMP3_DEADLOCK -27 +#define DRMP3_TOO_MANY_LINKS -28 +#define DRMP3_NOT_IMPLEMENTED -29 +#define DRMP3_NO_MESSAGE -30 +#define DRMP3_BAD_MESSAGE -31 +#define DRMP3_NO_DATA_AVAILABLE -32 +#define DRMP3_INVALID_DATA -33 +#define DRMP3_TIMEOUT -34 +#define DRMP3_NO_NETWORK -35 +#define DRMP3_NOT_UNIQUE -36 +#define DRMP3_NOT_SOCKET -37 +#define DRMP3_NO_ADDRESS -38 +#define DRMP3_BAD_PROTOCOL -39 +#define DRMP3_PROTOCOL_UNAVAILABLE -40 +#define DRMP3_PROTOCOL_NOT_SUPPORTED -41 +#define DRMP3_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRMP3_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRMP3_SOCKET_NOT_SUPPORTED -44 +#define DRMP3_CONNECTION_RESET -45 +#define DRMP3_ALREADY_CONNECTED -46 +#define DRMP3_NOT_CONNECTED -47 +#define DRMP3_CONNECTION_REFUSED -48 +#define DRMP3_NO_HOST -49 +#define DRMP3_IN_PROGRESS -50 +#define DRMP3_CANCELLED -51 +#define DRMP3_MEMORY_ALREADY_MAPPED -52 +#define DRMP3_AT_END -53 + #define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 #define DRMP3_MAX_SAMPLES_PER_FRAME (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2) @@ -170,14 +235,27 @@ typedef drmp3_uint32 drmp3_bool32; I am using "__inline__" only when we're compiling in strict ANSI mode. */ #if defined(__STRICT_ANSI__) - #define DRMP3_INLINE __inline__ __attribute__((always_inline)) + #define DRMP3_GNUC_INLINE_HINT __inline__ #else - #define DRMP3_INLINE inline __attribute__((always_inline)) + #define DRMP3_GNUC_INLINE_HINT inline #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT + #endif +#elif defined(__WATCOMC__) + #define DRMP3_INLINE __inline #else #define DRMP3_INLINE #endif + +DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision); +DRMP3_API const char* drmp3_version_string(void); + + /* Low Level Push API ================== @@ -191,17 +269,17 @@ typedef struct { float mdct_overlap[2][9*32], qmf_state[15*2*32]; int reserv, free_format_bytes; - unsigned char header[4], reserv_buf[511]; + drmp3_uint8 header[4], reserv_buf[511]; } drmp3dec; /* Initializes a low level decoder. */ -void drmp3dec_init(drmp3dec *dec); +DRMP3_API void drmp3dec_init(drmp3dec *dec); /* Reads a frame from a low level decoder. */ -int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); +DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); /* Helper for converting between f32 and s16. */ -void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); +DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples); @@ -209,58 +287,6 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); Main API (Pull API) =================== */ -#ifndef DR_MP3_DEFAULT_CHANNELS -#define DR_MP3_DEFAULT_CHANNELS 2 -#endif -#ifndef DR_MP3_DEFAULT_SAMPLE_RATE -#define DR_MP3_DEFAULT_SAMPLE_RATE 44100 -#endif - -typedef struct drmp3_src drmp3_src; -typedef drmp3_uint64 (* drmp3_src_read_proc)(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData); /* Returns the number of frames that were read. */ - -typedef enum -{ - drmp3_src_algorithm_none, - drmp3_src_algorithm_linear -} drmp3_src_algorithm; - -#define DRMP3_SRC_CACHE_SIZE_IN_FRAMES 512 -typedef struct -{ - drmp3_src* pSRC; - float pCachedFrames[2 * DRMP3_SRC_CACHE_SIZE_IN_FRAMES]; - drmp3_uint32 cachedFrameCount; - drmp3_uint32 iNextFrame; -} drmp3_src_cache; - -typedef struct -{ - drmp3_uint32 sampleRateIn; - drmp3_uint32 sampleRateOut; - drmp3_uint32 channels; - drmp3_src_algorithm algorithm; - drmp3_uint32 cacheSizeInFrames; /* The number of frames to read from the client at a time. */ -} drmp3_src_config; - -struct drmp3_src -{ - drmp3_src_config config; - drmp3_src_read_proc onRead; - void* pUserData; - float bin[256]; - drmp3_src_cache cache; /* <-- For simplifying and optimizing client -> memory reading. */ - union - { - struct - { - double alpha; - drmp3_bool32 isPrevFramesLoaded : 1; - drmp3_bool32 isNextFramesLoaded : 1; - } linear; - } algo; -}; - typedef enum { drmp3_seek_origin_start, @@ -313,14 +339,13 @@ typedef struct typedef struct { - drmp3_uint32 outputChannels; - drmp3_uint32 outputSampleRate; + drmp3_uint32 channels; + drmp3_uint32 sampleRate; } drmp3_config; typedef struct { drmp3dec decoder; - drmp3dec_frame_info frameInfo; drmp3_uint32 channels; drmp3_uint32 sampleRate; drmp3_read_proc onRead; @@ -334,11 +359,11 @@ typedef struct drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; /* <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. */ drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. */ drmp3_uint64 streamCursor; /* The current byte the decoder is sitting on in the raw stream. */ - drmp3_src src; drmp3_seek_point* pSeekPoints; /* NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer. */ drmp3_uint32 seekPointCount; /* The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero. */ size_t dataSize; size_t dataCapacity; + size_t dataConsumed; drmp3_uint8* pData; drmp3_bool32 atEnd : 1; struct @@ -362,7 +387,7 @@ Close the loader with drmp3_uninit(). See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() */ -drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks); /* Initializes an MP3 decoder from a block of memory. @@ -372,7 +397,7 @@ the lifetime of the drmp3 object. The buffer should contain the contents of the entire MP3 file. */ -drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifndef DR_MP3_NO_STDIO /* @@ -382,46 +407,47 @@ This holds the internal FILE object until drmp3_uninit() is called. Keep this in objects because the operating system may restrict the number of file handles an application can have open at any given time. */ -drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif /* Uninitializes an MP3 decoder. */ -void drmp3_uninit(drmp3* pMP3); +DRMP3_API void drmp3_uninit(drmp3* pMP3); /* Reads PCM frames as interleaved 32-bit IEEE floating point PCM. Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. */ -drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); /* Reads PCM frames as interleaved signed 16-bit integer PCM. Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. */ -drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut); +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut); /* Seeks to a specific frame. Note that this is _not_ an MP3 frame, but rather a PCM frame. */ -drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); +DRMP3_API drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); /* Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet radio. Runs in linear time. Returns 0 on error. */ -drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); +DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); /* Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet radio. Runs in linear time. Returns 0 on error. */ -drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); +DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); /* Calculates the total number of MP3 and PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet @@ -429,7 +455,7 @@ radio. Runs in linear time. Returns 0 on error. This is equivalent to calling drmp3_get_mp3_frame_count() and drmp3_get_pcm_frame_count() except that it's more efficient. */ -drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount); +DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount); /* Calculates the seekpoints based on PCM frames. This is slow. @@ -440,7 +466,7 @@ seekpoints, in which case dr_mp3 will return a corrected count. Note that seektable seeking is not quite sample exact when the MP3 stream contains inconsistent sample rates. */ -drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints); +DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints); /* Binds a seek table to the decoder. @@ -450,31 +476,36 @@ remains valid while it is bound to the decoder. Use drmp3_calculate_seek_points() to calculate the seek points. */ -drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints); +DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints); /* Opens an decodes an entire MP3 stream as a single operation. -pConfig is both an input and output. On input it contains what you want. On output it contains what you got. +On output pConfig will receive the channel count and sample rate of the stream. Free the returned pointer with drmp3_free(). */ -float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); -drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); -float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); -drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifndef DR_MP3_NO_STDIO -float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); -drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif +/* +Allocates a block of memory on the heap. +*/ +DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks); + /* Frees any memory that was allocated by a public drmp3 API. */ -void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifdef __cplusplus } @@ -489,11 +520,34 @@ void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ -#ifdef DR_MP3_IMPLEMENTATION +#if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) +#ifndef dr_mp3_c +#define dr_mp3_c + #include #include #include /* For INT_MAX */ +DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision) +{ + if (pMajor) { + *pMajor = DRMP3_VERSION_MAJOR; + } + + if (pMinor) { + *pMinor = DRMP3_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRMP3_VERSION_REVISION; + } +} + +DRMP3_API const char* drmp3_version_string(void) +{ + return DRMP3_VERSION_STRING; +} + /* Disable SIMD when compiling with TCC for now. */ #if defined(__TINYC__) #define DR_MP3_NO_SIMD @@ -541,12 +595,12 @@ void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) #if !defined(DR_MP3_NO_SIMD) -#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(_M_ARM64) || defined(__x86_64__) || defined(__aarch64__)) +#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) /* x64 always have SSE2, arm64 always have neon, no need for generic code */ #define DR_MP3_ONLY_SIMD #endif -#if ((defined(_MSC_VER) && _MSC_VER >= 1400) && (defined(_M_IX86) || defined(_M_X64))) || ((defined(__i386__) || defined(__x86_64__)) && defined(__SSE2__)) +#if ((defined(_MSC_VER) && _MSC_VER >= 1400) && defined(_M_X64)) || ((defined(__i386) || defined(_M_IX86) || defined(__i386__) || defined(__x86_64__)) && ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__))) #if defined(_MSC_VER) #include #endif @@ -591,7 +645,7 @@ static __inline__ __attribute__((always_inline)) void drmp3_cpuid(int CPUInfo[], #endif } #endif -static int drmp3_have_simd() +static int drmp3_have_simd(void) { #ifdef DR_MP3_ONLY_SIMD return 1; @@ -632,7 +686,7 @@ end: #define DRMP3_VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s)) #define DRMP3_VREV(x) vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x))) typedef float32x4_t drmp3_f4; -static int drmp3_have_simd() +static int drmp3_have_simd(void) { /* TODO: detect neon for !DR_MP3_ONLY_SIMD */ return 1; } @@ -650,6 +704,44 @@ static int drmp3_have_simd() #endif +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) +#define DRMP3_HAVE_ARMV6 1 +static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_arm(drmp3_int32 a) +{ + drmp3_int32 x = 0; + __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); + return x; +} +#else +#define DRMP3_HAVE_ARMV6 0 +#endif + + +/* Standard library stuff. */ +#ifndef DRMP3_ASSERT +#include +#define DRMP3_ASSERT(expression) assert(expression) +#endif +#ifndef DRMP3_COPY_MEMORY +#define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRMP3_MOVE_MEMORY +#define DRMP3_MOVE_MEMORY(dst, src, sz) memmove((dst), (src), (sz)) +#endif +#ifndef DRMP3_ZERO_MEMORY +#define DRMP3_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#define DRMP3_ZERO_OBJECT(p) DRMP3_ZERO_MEMORY((p), sizeof(*(p))) +#ifndef DRMP3_MALLOC +#define DRMP3_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRMP3_REALLOC +#define DRMP3_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRMP3_FREE +#define DRMP3_FREE(p) free((p)) +#endif + typedef struct { const drmp3_uint8 *buf; @@ -916,7 +1008,7 @@ static int drmp3_L12_dequantize_granule(float *grbuf, drmp3_bs *bs, drmp3_L12_sc static void drmp3_L12_apply_scf_384(drmp3_L12_scale_info *sci, const float *scf, float *dst) { int i, k; - memcpy(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); + DRMP3_COPY_MEMORY(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); for (i = 0; i < sci->total_bands; i++, dst += 18, scf += 6) { for (k = 0; k < 12; k++) @@ -1061,14 +1153,14 @@ static void drmp3_L3_read_scalefactors(drmp3_uint8 *scf, drmp3_uint8 *ist_pos, c int cnt = scf_count[i]; if (scfsi & 8) { - memcpy(scf, ist_pos, cnt); + DRMP3_COPY_MEMORY(scf, ist_pos, cnt); } else { int bits = scf_size[i]; if (!bits) { - memset(scf, 0, cnt); - memset(ist_pos, 0, cnt); + DRMP3_ZERO_MEMORY(scf, cnt); + DRMP3_ZERO_MEMORY(ist_pos, cnt); } else { int max_scf = (scfsi < 0) ? (1 << bits) - 1 : -1; @@ -1139,16 +1231,16 @@ static void drmp3_L3_decode_scalefactors(const drmp3_uint8 *hdr, drmp3_uint8 *is int sh = 3 - scf_shift; for (i = 0; i < gr->n_short_sfb; i += 3) { - iscf[gr->n_long_sfb + i + 0] += gr->subblock_gain[0] << sh; - iscf[gr->n_long_sfb + i + 1] += gr->subblock_gain[1] << sh; - iscf[gr->n_long_sfb + i + 2] += gr->subblock_gain[2] << sh; + iscf[gr->n_long_sfb + i + 0] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 0] + (gr->subblock_gain[0] << sh)); + iscf[gr->n_long_sfb + i + 1] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 1] + (gr->subblock_gain[1] << sh)); + iscf[gr->n_long_sfb + i + 2] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 2] + (gr->subblock_gain[2] << sh)); } } else if (gr->preflag) { static const drmp3_uint8 g_preamp[10] = { 1,1,1,1,2,2,3,3,3,2 }; for (i = 0; i < 10; i++) { - iscf[11 + i] += g_preamp[i]; + iscf[11 + i] = (drmp3_uint8)(iscf[11 + i] + g_preamp[i]); } } @@ -1209,7 +1301,7 @@ static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *g static const drmp3_int16 tabindex[2*16] = { 0,32,64,98,0,132,180,218,292,364,426,538,648,746,0,1126,1460,1460,1460,1460,1460,1460,1460,1460,1842,1842,1842,1842,1842,1842,1842,1842 }; static const drmp3_uint8 g_linbits[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,6,8,10,13,4,5,6,7,8,9,11,13 }; -#define DRMP3_PEEK_BITS(n) (bs_cache >> (32 - n)) +#define DRMP3_PEEK_BITS(n) (bs_cache >> (32 - (n))) #define DRMP3_FLUSH_BITS(n) { bs_cache <<= (n); bs_sh += (n); } #define DRMP3_CHECK_BITS while (bs_sh >= 0) { bs_cache |= (drmp3_uint32)*bs_next_ptr++ << bs_sh; bs_sh -= 8; } #define DRMP3_BSPOS ((bs_next_ptr - bs->buf)*8 - 24 + bs_sh) @@ -1328,12 +1420,22 @@ static void drmp3_L3_midside_stereo(float *left, int n) int i = 0; float *right = left + 576; #if DRMP3_HAVE_SIMD - if (drmp3_have_simd()) for (; i < n - 3; i += 4) + if (drmp3_have_simd()) { - drmp3_f4 vl = DRMP3_VLD(left + i); - drmp3_f4 vr = DRMP3_VLD(right + i); - DRMP3_VSTORE(left + i, DRMP3_VADD(vl, vr)); - DRMP3_VSTORE(right + i, DRMP3_VSUB(vl, vr)); + for (; i < n - 3; i += 4) + { + drmp3_f4 vl = DRMP3_VLD(left + i); + drmp3_f4 vr = DRMP3_VLD(right + i); + DRMP3_VSTORE(left + i, DRMP3_VADD(vl, vr)); + DRMP3_VSTORE(right + i, DRMP3_VSUB(vl, vr)); + } +#ifdef __GNUC__ + /* Workaround for spurious -Waggressive-loop-optimizations warning from gcc. + * For more info see: https://github.com/lieff/minimp3/issues/88 + */ + if (__builtin_constant_p(n % 4 == 0) && n % 4 == 0) + return; +#endif } #endif for (; i < n; i++) @@ -1443,7 +1545,7 @@ static void drmp3_L3_reorder(float *grbuf, float *scratch, const drmp3_uint8 *sf *dst++ = src[2*len]; } } - memcpy(grbuf, scratch, (dst - scratch)*sizeof(float)); + DRMP3_COPY_MEMORY(grbuf, scratch, (dst - scratch)*sizeof(float)); } static void drmp3_L3_antialias(float *grbuf, int nbands) @@ -1612,8 +1714,8 @@ static void drmp3_L3_imdct_short(float *grbuf, float *overlap, int nbands) for (;nbands > 0; nbands--, overlap += 9, grbuf += 18) { float tmp[18]; - memcpy(tmp, grbuf, sizeof(tmp)); - memcpy(grbuf, overlap, 6*sizeof(float)); + DRMP3_COPY_MEMORY(tmp, grbuf, sizeof(tmp)); + DRMP3_COPY_MEMORY(grbuf, overlap, 6*sizeof(float)); drmp3_L3_imdct12(tmp, grbuf + 6, overlap + 6); drmp3_L3_imdct12(tmp + 1, grbuf + 12, overlap + 6); drmp3_L3_imdct12(tmp + 2, overlap, overlap + 6); @@ -1657,7 +1759,7 @@ static void drmp3_L3_save_reservoir(drmp3dec *h, drmp3dec_scratch *s) } if (remains > 0) { - memmove(h->reserv_buf, s->maindata + pos, remains); + DRMP3_MOVE_MEMORY(h->reserv_buf, s->maindata + pos, remains); } h->reserv = remains; } @@ -1666,8 +1768,8 @@ static int drmp3_L3_restore_reservoir(drmp3dec *h, drmp3_bs *bs, drmp3dec_scratc { int frame_bytes = (bs->limit - bs->pos)/8; int bytes_have = DRMP3_MIN(h->reserv, main_data_begin); - memcpy(s->maindata, h->reserv_buf + DRMP3_MAX(0, h->reserv - main_data_begin), DRMP3_MIN(h->reserv, main_data_begin)); - memcpy(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); + DRMP3_COPY_MEMORY(s->maindata, h->reserv_buf + DRMP3_MAX(0, h->reserv - main_data_begin), DRMP3_MIN(h->reserv, main_data_begin)); + DRMP3_COPY_MEMORY(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); drmp3_bs_init(&s->bs, s->maindata, bytes_have + frame_bytes); return h->reserv >= main_data_begin; } @@ -1767,7 +1869,7 @@ static void drmp3d_DCT_II(float *grbuf, int n) #if DRMP3_HAVE_SSE #define DRMP3_VSAVE2(i, v) _mm_storel_pi((__m64 *)(void*)&y[i*18], v) #else -#define DRMP3_VSAVE2(i, v) vst1_f32((float32_t *)&y[i*18], vget_low_f32(v)) +#define DRMP3_VSAVE2(i, v) vst1_f32((float32_t *)&y[(i)*18], vget_low_f32(v)) #endif for (i = 0; i < 7; i++, y += 4*18) { @@ -1783,7 +1885,7 @@ static void drmp3d_DCT_II(float *grbuf, int n) DRMP3_VSAVE2(3, t[3][7]); } else { -#define DRMP3_VSAVE4(i, v) DRMP3_VSTORE(&y[i*18], v) +#define DRMP3_VSAVE4(i, v) DRMP3_VSTORE(&y[(i)*18], v) for (i = 0; i < 7; i++, y += 4*18) { drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); @@ -1800,7 +1902,7 @@ static void drmp3d_DCT_II(float *grbuf, int n) } else #endif #ifdef DR_MP3_ONLY_SIMD - {} + {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ #else for (; k < n; k++) { @@ -1869,11 +1971,17 @@ typedef drmp3_int16 drmp3d_sample_t; static drmp3_int16 drmp3d_scale_pcm(float sample) { drmp3_int16 s; +#if DRMP3_HAVE_ARMV6 + drmp3_int32 s32 = (drmp3_int32)(sample + .5f); + s32 -= (s32 < 0); + s = (drmp3_int16)drmp3_clip_int16_arm(s32); +#else if (sample >= 32766.5) return (drmp3_int16) 32767; if (sample <= -32767.5) return (drmp3_int16)-32768; s = (drmp3_int16)(sample + .5f); s -= (s < 0); /* away from zero, to be compliant */ - return (drmp3_int16)s; +#endif + return s; } #else typedef float drmp3d_sample_t; @@ -2000,7 +2108,11 @@ static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); #endif #else + #if DRMP3_HAVE_SSE static const drmp3_f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; + #else + const drmp3_f4 g_scale = vdupq_n_f32(1.0f/32768.0f); + #endif a = DRMP3_VMUL(a, g_scale); b = DRMP3_VMUL(b, g_scale); #if DRMP3_HAVE_SSE @@ -2027,7 +2139,7 @@ static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) } else #endif #ifdef DR_MP3_ONLY_SIMD - {} + {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ #else for (i = 14; i >= 0; i--) { @@ -2068,7 +2180,7 @@ static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int drmp3d_DCT_II(grbuf + 576*i, nbands); } - memcpy(lins, qmf_state, sizeof(float)*15*64); + DRMP3_COPY_MEMORY(lins, qmf_state, sizeof(float)*15*64); for (i = 0; i < nbands; i += 2) { @@ -2084,7 +2196,7 @@ static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int } else #endif { - memcpy(qmf_state, lins + nbands*64, sizeof(float)*15*64); + DRMP3_COPY_MEMORY(qmf_state, lins + nbands*64, sizeof(float)*15*64); } } @@ -2140,12 +2252,12 @@ static int drmp3d_find_frame(const drmp3_uint8 *mp3, int mp3_bytes, int *free_fo return mp3_bytes; } -void drmp3dec_init(drmp3dec *dec) +DRMP3_API void drmp3dec_init(drmp3dec *dec) { dec->header[0] = 0; } -int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) +DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) { int i = 0, igr, frame_size = 0, success = 1; const drmp3_uint8 *hdr; @@ -2162,7 +2274,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes } if (!frame_size) { - memset(dec, 0, sizeof(drmp3dec)); + DRMP3_ZERO_MEMORY(dec, sizeof(drmp3dec)); i = drmp3d_find_frame(mp3, mp3_bytes, &dec->free_format_bytes, &frame_size); if (!frame_size || i + frame_size > mp3_bytes) { @@ -2172,7 +2284,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes } hdr = mp3 + i; - memcpy(dec->header, hdr, DRMP3_HDR_SIZE); + DRMP3_COPY_MEMORY(dec->header, hdr, DRMP3_HDR_SIZE); info->frame_bytes = i + frame_size; info->channels = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; info->hz = drmp3_hdr_sample_rate_hz(hdr); @@ -2198,7 +2310,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes { for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels)) { - memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); drmp3_L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); } @@ -2217,7 +2329,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes drmp3_L12_read_scale_info(hdr, bs_frame, sci); - memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); for (i = 0, igr = 0; igr < 3; igr++) { if (12 == (i += drmp3_L12_dequantize_granule(scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) @@ -2225,7 +2337,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes i = 0; drmp3_L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); - memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*384*info->channels); } if (bs_frame->pos > bs_frame->limit) @@ -2240,11 +2352,11 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes return success*drmp3_hdr_frame_samples(dec->header); } -void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) +DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples) { - int i = 0; + size_t i = 0; #if DRMP3_HAVE_SIMD - int aligned_count = num_samples & ~7; + size_t aligned_count = num_samples & ~7; for(; i < aligned_count; i+=8) { drmp3_f4 scale = DRMP3_VSET(32768.0f); @@ -2303,7 +2415,6 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) Main Public API ************************************************************************************************************************************************************/ - #if defined(SIZE_MAX) #define DRMP3_SIZE_MAX SIZE_MAX #else @@ -2319,46 +2430,52 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) #define DRMP3_SEEK_LEADING_MP3_FRAMES 2 #endif +#define DRMP3_MIN_DATA_CHUNK_SIZE 16384 -/* Standard library stuff. */ -#ifndef DRMP3_ASSERT -#include -#define DRMP3_ASSERT(expression) assert(expression) -#endif -#ifndef DRMP3_COPY_MEMORY -#define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) -#endif -#ifndef DRMP3_ZERO_MEMORY -#define DRMP3_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) -#endif -#define DRMP3_ZERO_OBJECT(p) DRMP3_ZERO_MEMORY((p), sizeof(*(p))) -#ifndef DRMP3_MALLOC -#define DRMP3_MALLOC(sz) malloc((sz)) -#endif -#ifndef DRMP3_REALLOC -#define DRMP3_REALLOC(p, sz) realloc((p), (sz)) -#endif -#ifndef DRMP3_FREE -#define DRMP3_FREE(p) free((p)) +/* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends at least 16K, but in an attempt to reduce data movement I'm making this slightly larger. */ +#ifndef DRMP3_DATA_CHUNK_SIZE +#define DRMP3_DATA_CHUNK_SIZE (DRMP3_MIN_DATA_CHUNK_SIZE*4) #endif -#define drmp3_countof(x) (sizeof(x) / sizeof(x[0])) -#define drmp3_max(x, y) (((x) > (y)) ? (x) : (y)) -#define drmp3_min(x, y) (((x) < (y)) ? (x) : (y)) -#define DRMP3_DATA_CHUNK_SIZE 16384 /* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends 16K. */ +#define DRMP3_COUNTOF(x) (sizeof(x) / sizeof(x[0])) +#define DRMP3_CLAMP(x, lo, hi) (DRMP3_MAX(lo, DRMP3_MIN(x, hi))) + +#ifndef DRMP3_PI_D +#define DRMP3_PI_D 3.14159265358979323846264 +#endif + +#define DRMP3_DEFAULT_RESAMPLER_LPF_ORDER 2 static DRMP3_INLINE float drmp3_mix_f32(float x, float y, float a) { return x*(1-a) + y*a; } - -static void drmp3_blend_f32(float* pOut, float* pInA, float* pInB, float factor, drmp3_uint32 channels) +static DRMP3_INLINE float drmp3_mix_f32_fast(float x, float y, float a) { - drmp3_uint32 i; - for (i = 0; i < channels; ++i) { - pOut[i] = drmp3_mix_f32(pInA[i], pInB[i], factor); + float r0 = (y - x); + float r1 = r0*a; + return x + r1; + /*return x + (y - x)*a;*/ +} + + +/* +Greatest common factor using Euclid's algorithm iteratively. +*/ +static DRMP3_INLINE drmp3_uint32 drmp3_gcf_u32(drmp3_uint32 a, drmp3_uint32 b) +{ + for (;;) { + if (b == 0) { + break; + } else { + drmp3_uint32 t = a; + a = b; + b = t % a; + } } + + return a; } @@ -2381,7 +2498,6 @@ static void drmp3__free_default(void* p, void* pUserData) } -#if 0 /* Unused, but leaving here in case I need to add it again later. */ static void* drmp3__malloc_from_callbacks(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { @@ -2399,7 +2515,6 @@ static void* drmp3__malloc_from_callbacks(size_t sz, const drmp3_allocation_call return NULL; } -#endif static void* drmp3__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drmp3_allocation_callbacks* pAllocationCallbacks) { @@ -2443,7 +2558,7 @@ static void drmp3__free_from_callbacks(void* p, const drmp3_allocation_callbacks } -drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(const drmp3_allocation_callbacks* pAllocationCallbacks) +static drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { /* Copy. */ @@ -2460,259 +2575,6 @@ drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(const drm } -void drmp3_src_cache_init(drmp3_src* pSRC, drmp3_src_cache* pCache) -{ - DRMP3_ASSERT(pSRC != NULL); - DRMP3_ASSERT(pCache != NULL); - - pCache->pSRC = pSRC; - pCache->cachedFrameCount = 0; - pCache->iNextFrame = 0; -} - -drmp3_uint64 drmp3_src_cache_read_frames(drmp3_src_cache* pCache, drmp3_uint64 frameCount, float* pFramesOut) -{ - drmp3_uint32 channels; - drmp3_uint64 totalFramesRead = 0; - - DRMP3_ASSERT(pCache != NULL); - DRMP3_ASSERT(pCache->pSRC != NULL); - DRMP3_ASSERT(pCache->pSRC->onRead != NULL); - DRMP3_ASSERT(frameCount > 0); - DRMP3_ASSERT(pFramesOut != NULL); - - channels = pCache->pSRC->config.channels; - - while (frameCount > 0) { - /* If there's anything in memory go ahead and copy that over first. */ - drmp3_uint32 framesToReadFromClient; - drmp3_uint64 framesRemainingInMemory = pCache->cachedFrameCount - pCache->iNextFrame; - drmp3_uint64 framesToReadFromMemory = frameCount; - if (framesToReadFromMemory > framesRemainingInMemory) { - framesToReadFromMemory = framesRemainingInMemory; - } - - DRMP3_COPY_MEMORY(pFramesOut, pCache->pCachedFrames + pCache->iNextFrame*channels, (drmp3_uint32)(framesToReadFromMemory * channels * sizeof(float))); - pCache->iNextFrame += (drmp3_uint32)framesToReadFromMemory; - - totalFramesRead += framesToReadFromMemory; - frameCount -= framesToReadFromMemory; - if (frameCount == 0) { - break; - } - - - /* At this point there are still more frames to read from the client, so we'll need to reload the cache with fresh data. */ - DRMP3_ASSERT(frameCount > 0); - pFramesOut += framesToReadFromMemory * channels; - - pCache->iNextFrame = 0; - pCache->cachedFrameCount = 0; - - framesToReadFromClient = drmp3_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; - if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { - framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; - } - - pCache->cachedFrameCount = (drmp3_uint32)pCache->pSRC->onRead(pCache->pSRC, framesToReadFromClient, pCache->pCachedFrames, pCache->pSRC->pUserData); - - - /* Get out of this loop if nothing was able to be retrieved. */ - if (pCache->cachedFrameCount == 0) { - break; - } - } - - return totalFramesRead; -} - - -drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); -drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); - -drmp3_bool32 drmp3_src_init(const drmp3_src_config* pConfig, drmp3_src_read_proc onRead, void* pUserData, drmp3_src* pSRC) -{ - if (pSRC == NULL) { - return DRMP3_FALSE; - } - - DRMP3_ZERO_OBJECT(pSRC); - - if (pConfig == NULL || onRead == NULL) { - return DRMP3_FALSE; - } - - if (pConfig->channels == 0 || pConfig->channels > 2) { - return DRMP3_FALSE; - } - - pSRC->config = *pConfig; - pSRC->onRead = onRead; - pSRC->pUserData = pUserData; - - if (pSRC->config.cacheSizeInFrames > DRMP3_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) { - pSRC->config.cacheSizeInFrames = DRMP3_SRC_CACHE_SIZE_IN_FRAMES; - } - - drmp3_src_cache_init(pSRC, &pSRC->cache); - return DRMP3_TRUE; -} - -drmp3_bool32 drmp3_src_set_input_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateIn) -{ - if (pSRC == NULL) { - return DRMP3_FALSE; - } - - /* Must have a sample rate of > 0. */ - if (sampleRateIn == 0) { - return DRMP3_FALSE; - } - - pSRC->config.sampleRateIn = sampleRateIn; - return DRMP3_TRUE; -} - -drmp3_bool32 drmp3_src_set_output_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateOut) -{ - if (pSRC == NULL) { - return DRMP3_FALSE; - } - - /* Must have a sample rate of > 0. */ - if (sampleRateOut == 0) { - return DRMP3_FALSE; - } - - pSRC->config.sampleRateOut = sampleRateOut; - return DRMP3_TRUE; -} - -drmp3_uint64 drmp3_src_read_frames_ex(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) -{ - drmp3_src_algorithm algorithm; - - if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) { - return 0; - } - - algorithm = pSRC->config.algorithm; - - /* Always use passthrough if the sample rates are the same. */ - if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) { - algorithm = drmp3_src_algorithm_none; - } - - /* Could just use a function pointer instead of a switch for this... */ - switch (algorithm) - { - case drmp3_src_algorithm_none: return drmp3_src_read_frames_passthrough(pSRC, frameCount, pFramesOut, flush); - case drmp3_src_algorithm_linear: return drmp3_src_read_frames_linear(pSRC, frameCount, pFramesOut, flush); - default: return 0; - } -} - -drmp3_uint64 drmp3_src_read_frames(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut) -{ - return drmp3_src_read_frames_ex(pSRC, frameCount, pFramesOut, DRMP3_FALSE); -} - -drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) -{ - DRMP3_ASSERT(pSRC != NULL); - DRMP3_ASSERT(frameCount > 0); - DRMP3_ASSERT(pFramesOut != NULL); - - (void)flush; /* Passthrough need not care about flushing. */ - return pSRC->onRead(pSRC, frameCount, pFramesOut, pSRC->pUserData); -} - -drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) -{ - double factor; - drmp3_uint64 totalFramesRead; - - DRMP3_ASSERT(pSRC != NULL); - DRMP3_ASSERT(frameCount > 0); - DRMP3_ASSERT(pFramesOut != NULL); - - /* For linear SRC, the bin is only 2 frames: 1 prior, 1 future. */ - - /* Load the bin if necessary. */ - if (!pSRC->algo.linear.isPrevFramesLoaded) { - drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin); - if (framesRead == 0) { - return 0; - } - pSRC->algo.linear.isPrevFramesLoaded = DRMP3_TRUE; - } - if (!pSRC->algo.linear.isNextFramesLoaded) { - drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin + pSRC->config.channels); - if (framesRead == 0) { - return 0; - } - pSRC->algo.linear.isNextFramesLoaded = DRMP3_TRUE; - } - - factor = (double)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; - - totalFramesRead = 0; - while (frameCount > 0) { - drmp3_uint32 i; - drmp3_uint32 framesToReadFromClient; - - /* The bin is where the previous and next frames are located. */ - float* pPrevFrame = pSRC->bin; - float* pNextFrame = pSRC->bin + pSRC->config.channels; - - drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, (float)pSRC->algo.linear.alpha, pSRC->config.channels); - - pSRC->algo.linear.alpha += factor; - - /* The new alpha value is how we determine whether or not we need to read fresh frames. */ - framesToReadFromClient = (drmp3_uint32)pSRC->algo.linear.alpha; - pSRC->algo.linear.alpha = pSRC->algo.linear.alpha - framesToReadFromClient; - - for (i = 0; i < framesToReadFromClient; ++i) { - drmp3_uint64 framesRead; - drmp3_uint32 j; - - for (j = 0; j < pSRC->config.channels; ++j) { - pPrevFrame[j] = pNextFrame[j]; - } - - framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); - if (framesRead == 0) { - drmp3_uint32 k; - for (k = 0; k < pSRC->config.channels; ++k) { - pNextFrame[k] = 0; - } - - if (pSRC->algo.linear.isNextFramesLoaded) { - pSRC->algo.linear.isNextFramesLoaded = DRMP3_FALSE; - } else { - if (flush) { - pSRC->algo.linear.isPrevFramesLoaded = DRMP3_FALSE; - } - } - - break; - } - } - - pFramesOut = (drmp3_uint8*)pFramesOut + (1 * pSRC->config.channels * sizeof(float)); - frameCount -= 1; - totalFramesRead += 1; - - /* If there's no frames available we need to get out of this loop. */ - if (!pSRC->algo.linear.isNextFramesLoaded && (!flush || !pSRC->algo.linear.isPrevFramesLoaded)) { - break; - } - } - - return totalFramesRead; -} - static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) { @@ -2768,112 +2630,8 @@ static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_se return DRMP3_TRUE; } -static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3_bool32 discard); -static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3); -static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData) -{ - drmp3* pMP3 = (drmp3*)pUserData; - float* pFramesOutF = (float*)pFramesOut; - drmp3_uint64 totalFramesRead = 0; - - DRMP3_ASSERT(pMP3 != NULL); - DRMP3_ASSERT(pMP3->onRead != NULL); - - while (frameCount > 0) { - /* Read from the in-memory buffer first. */ - while (pMP3->pcmFramesRemainingInMP3Frame > 0 && frameCount > 0) { - drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->pcmFrames; -#ifndef DR_MP3_FLOAT_OUTPUT - if (pMP3->mp3FrameChannels == 1) { - if (pMP3->channels == 1) { - /* Mono -> Mono. */ - pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; - } else { - /* Mono -> Stereo. */ - pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; - pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; - } - } else { - if (pMP3->channels == 1) { - /* Stereo -> Mono */ - float sample = 0; - sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; - sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; - pFramesOutF[0] = sample * 0.5f; - } else { - /* Stereo -> Stereo */ - pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; - pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; - } - } -#else - if (pMP3->mp3FrameChannels == 1) { - if (pMP3->channels == 1) { - /* Mono -> Mono. */ - pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; - } else { - /* Mono -> Stereo. */ - pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; - pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame]; - } - } else { - if (pMP3->channels == 1) { - /* Stereo -> Mono */ - float sample = 0; - sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; - sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; - pFramesOutF[0] = sample * 0.5f; - } else { - /* Stereo -> Stereo */ - pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; - pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; - } - } -#endif - - pMP3->pcmFramesConsumedInMP3Frame += 1; - pMP3->pcmFramesRemainingInMP3Frame -= 1; - totalFramesRead += 1; - frameCount -= 1; - pFramesOutF += pSRC->config.channels; - } - - if (frameCount == 0) { - break; - } - - DRMP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 0); - - /* - At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed - at this point which means we'll also need to update our sample rate conversion pipeline. - */ - if (drmp3_decode_next_frame(pMP3) == 0) { - break; - } - } - - return totalFramesRead; -} - -static drmp3_bool32 drmp3_init_src(drmp3* pMP3) -{ - drmp3_src_config srcConfig; - DRMP3_ZERO_OBJECT(&srcConfig); - srcConfig.sampleRateIn = DR_MP3_DEFAULT_SAMPLE_RATE; - srcConfig.sampleRateOut = pMP3->sampleRate; - srcConfig.channels = pMP3->channels; - srcConfig.algorithm = drmp3_src_algorithm_linear; - if (!drmp3_src_init(&srcConfig, drmp3_read_src, pMP3, &pMP3->src)) { - drmp3_uninit(pMP3); - return DRMP3_FALSE; - } - - return DRMP3_TRUE; -} - -static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3_bool32 discard) +static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) { drmp3_uint32 pcmFramesRead = 0; @@ -2884,14 +2642,20 @@ static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPC return 0; } - do { + for (;;) { drmp3dec_frame_info info; - size_t leftoverDataSize; - /* minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more. */ - if (pMP3->dataSize < DRMP3_DATA_CHUNK_SIZE) { + /* minimp3 recommends doing data submission in chunks of at least 16K. If we don't have at least 16K bytes available, get more. */ + if (pMP3->dataSize < DRMP3_MIN_DATA_CHUNK_SIZE) { size_t bytesRead; + /* First we need to move the data down. */ + if (pMP3->pData != NULL) { + DRMP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); + } + + pMP3->dataConsumed = 0; + if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { drmp3_uint8* pNewData; size_t newDataCap; @@ -2923,43 +2687,33 @@ static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPC return 0; /* File too big. */ } - pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ - + DRMP3_ASSERT(pMP3->pData != NULL); + DRMP3_ASSERT(pMP3->dataCapacity > 0); + + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ + /* Consume the data. */ - leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes); if (info.frame_bytes > 0) { - memmove(pMP3->pData, pMP3->pData + info.frame_bytes, leftoverDataSize); - pMP3->dataSize = leftoverDataSize; + pMP3->dataConsumed += (size_t)info.frame_bytes; + pMP3->dataSize -= (size_t)info.frame_bytes; } - /* - pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully - decoded the frame. A special case is if we are wanting to discard the frame, in which case we return successfully. - */ - if (pcmFramesRead > 0 || (info.frame_bytes > 0 && discard)) { + /* pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully decoded the frame. */ + if (pcmFramesRead > 0) { pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; pMP3->mp3FrameSampleRate = info.hz; - - /* We need to initialize the resampler if we don't yet have the channel count or sample rate. */ - if (pMP3->channels == 0 || pMP3->sampleRate == 0) { - if (pMP3->channels == 0) { - pMP3->channels = info.channels; - } - if (pMP3->sampleRate == 0) { - pMP3->sampleRate = info.hz; - } - drmp3_init_src(pMP3); - } - - drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate); break; } else if (info.frame_bytes == 0) { + /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ size_t bytesRead; - /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ + /* First we need to move the data down. */ + DRMP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); + pMP3->dataConsumed = 0; + if (pMP3->dataCapacity == pMP3->dataSize) { /* No room. Expand. */ drmp3_uint8* pNewData; @@ -2985,15 +2739,60 @@ static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPC pMP3->dataSize += bytesRead; } - } while (DRMP3_TRUE); + }; return pcmFramesRead; } +static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +{ + drmp3_uint32 pcmFramesRead = 0; + drmp3dec_frame_info info; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->memory.pData != NULL); + + if (pMP3->atEnd) { + return 0; + } + + for (;;) { + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->memory.pData + pMP3->memory.currentReadPos, (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos), pPCMFrames, &info); + if (pcmFramesRead > 0) { + pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; + pMP3->mp3FrameChannels = info.channels; + pMP3->mp3FrameSampleRate = info.hz; + break; + } else if (info.frame_bytes > 0) { + /* No frames were read, but it looks like we skipped past one. Read the next MP3 frame. */ + pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + } else { + /* Nothing at all was read. Abort. */ + break; + } + } + + /* Consume the data. */ + pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + + return pcmFramesRead; +} + +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +{ + if (pMP3->memory.pData != NULL && pMP3->memory.dataSize > 0) { + return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames); + } else { + return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames); + } +} + static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) { DRMP3_ASSERT(pMP3 != NULL); - return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, DRMP3_FALSE); + return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames); } #if 0 @@ -3017,32 +2816,14 @@ static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) } #endif -drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) +static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) { - drmp3_config config; - DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(onRead != NULL); /* This function assumes the output object has already been reset to 0. Do not do that here, otherwise things will break. */ drmp3dec_init(&pMP3->decoder); - /* The config can be null in which case we use defaults. */ - if (pConfig != NULL) { - config = *pConfig; - } else { - DRMP3_ZERO_OBJECT(&config); - } - - pMP3->channels = config.outputChannels; - - /* Cannot have more than 2 channels. */ - if (pMP3->channels > 2) { - pMP3->channels = 2; - } - - pMP3->sampleRate = config.outputSampleRate; - pMP3->onRead = onRead; pMP3->onSeek = onSeek; pMP3->pUserData = pUserData; @@ -3052,31 +2833,26 @@ drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek return DRMP3_FALSE; /* Invalid allocation callbacks. */ } - /* - We need a sample rate converter for converting the sample rate from the MP3 frames to the requested output sample rate. Note that if - we don't yet know the channel count or sample rate we defer this until the first frame is read. - */ - if (pMP3->channels != 0 && pMP3->sampleRate != 0) { - drmp3_init_src(pMP3); - } - /* Decode the first frame to confirm that it is indeed a valid MP3 stream. */ - if (!drmp3_decode_next_frame(pMP3)) { - drmp3_uninit(pMP3); + if (drmp3_decode_next_frame(pMP3) == 0) { + drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); /* The call above may have allocated memory. Need to make sure it's freed before aborting. */ return DRMP3_FALSE; /* Not a valid MP3 stream. */ } + pMP3->channels = pMP3->mp3FrameChannels; + pMP3->sampleRate = pMP3->mp3FrameSampleRate; + return DRMP3_TRUE; } -drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL || onRead == NULL) { return DRMP3_FALSE; } DRMP3_ZERO_OBJECT(pMP3); - return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pConfig, pAllocationCallbacks); + return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pAllocationCallbacks); } @@ -3131,7 +2907,7 @@ static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3 return DRMP3_TRUE; } -drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL) { return DRMP3_FALSE; @@ -3147,12 +2923,575 @@ drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, pMP3->memory.dataSize = dataSize; pMP3->memory.currentReadPos = 0; - return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pConfig, pAllocationCallbacks); + return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pAllocationCallbacks); } #ifndef DR_MP3_NO_STDIO #include +#include /* For wcslen(), wcsrtombs() */ + +/* drmp3_result_from_errno() is only used inside DR_MP3_NO_STDIO for now. Move this out if it's ever used elsewhere. */ +#include +static drmp3_result drmp3_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRMP3_SUCCESS; + #ifdef EPERM + case EPERM: return DRMP3_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRMP3_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRMP3_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRMP3_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRMP3_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRMP3_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRMP3_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRMP3_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRMP3_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRMP3_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRMP3_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRMP3_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRMP3_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRMP3_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRMP3_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRMP3_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRMP3_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRMP3_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRMP3_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRMP3_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRMP3_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRMP3_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRMP3_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRMP3_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRMP3_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRMP3_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRMP3_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRMP3_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRMP3_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRMP3_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRMP3_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRMP3_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DRMP3_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRMP3_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRMP3_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRMP3_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRMP3_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRMP3_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRMP3_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRMP3_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRMP3_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRMP3_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRMP3_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRMP3_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRMP3_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRMP3_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRMP3_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRMP3_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRMP3_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRMP3_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRMP3_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRMP3_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRMP3_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRMP3_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRMP3_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRMP3_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRMP3_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRMP3_ERROR; + #endif + #ifdef EADV + case EADV: return DRMP3_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRMP3_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRMP3_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRMP3_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRMP3_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRMP3_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRMP3_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRMP3_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRMP3_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRMP3_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRMP3_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRMP3_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRMP3_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRMP3_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRMP3_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRMP3_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRMP3_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRMP3_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRMP3_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRMP3_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRMP3_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRMP3_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRMP3_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRMP3_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRMP3_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRMP3_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRMP3_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRMP3_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRMP3_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRMP3_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRMP3_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRMP3_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRMP3_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRMP3_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRMP3_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRMP3_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRMP3_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRMP3_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRMP3_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRMP3_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRMP3_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRMP3_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRMP3_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRMP3_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRMP3_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRMP3_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRMP3_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRMP3_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRMP3_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRMP3_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRMP3_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRMP3_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRMP3_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRMP3_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRMP3_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRMP3_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRMP3_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRMP3_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRMP3_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRMP3_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRMP3_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRMP3_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRMP3_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRMP3_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRMP3_ERROR; + #endif + default: return DRMP3_ERROR; + } +} + +static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRMP3_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drmp3_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drmp3_result result = drmp3_result_from_errno(errno); + if (result == DRMP3_SUCCESS) { + result = DRMP3_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRMP3_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRMP3_HAS_WFOPEN + #endif +#endif + +static drmp3_result drmp3_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRMP3_INVALID_ARGS; + } + +#if defined(DRMP3_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drmp3_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drmp3_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because + fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note + that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler + error I'll look into improving compatibility. + */ + + /* + Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just + need to abort with an error. If you encounter a compiler lacking such support, add it to this list + and submit a bug report and it'll be added to the library upstream. + */ + #if defined(__DJGPP__) + { + /* Nothing to do here. This will fall through to the error check below. */ + } + #else + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRMP3_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drmp3_result_from_errno(errno); + } + + pFilePathMB = (char*)drmp3__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRMP3_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRMP3_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drmp3__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + #endif + + if (*ppFile == NULL) { + return DRMP3_ERROR; + } +#endif + + return DRMP3_SUCCESS; +} + + static size_t drmp3__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) { @@ -3164,25 +3503,44 @@ static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek return fseek((FILE*)pUserData, offset, (origin == drmp3_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } -drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) { + drmp3_bool32 result; FILE* pFile; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (fopen_s(&pFile, filePath, "rb") != 0) { - return DRMP3_FALSE; - } -#else - pFile = fopen(filePath, "rb"); - if (pFile == NULL) { - return DRMP3_FALSE; - } -#endif - return drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pConfig, pAllocationCallbacks); + if (drmp3_fopen(&pFile, pFilePath, "rb") != DRMP3_SUCCESS) { + return DRMP3_FALSE; + } + + result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRMP3_TRUE) { + fclose(pFile); + return result; + } + + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3_bool32 result; + FILE* pFile; + + if (drmp3_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks) != DRMP3_SUCCESS) { + return DRMP3_FALSE; + } + + result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRMP3_TRUE) { + fclose(pFile); + return result; + } + + return DRMP3_TRUE; } #endif -void drmp3_uninit(drmp3* pMP3) +DRMP3_API void drmp3_uninit(drmp3* pMP3) { if (pMP3 == NULL) { return; @@ -3190,82 +3548,198 @@ void drmp3_uninit(drmp3* pMP3) #ifndef DR_MP3_NO_STDIO if (pMP3->onRead == drmp3__on_read_stdio) { - fclose((FILE*)pMP3->pUserData); + FILE* pFile = (FILE*)pMP3->pUserData; + if (pFile != NULL) { + fclose(pFile); + pMP3->pUserData = NULL; /* Make sure the file handle is cleared to NULL to we don't attempt to close it a second time. */ + } } #endif drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); } -drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) +#if defined(DR_MP3_FLOAT_OUTPUT) +static void drmp3_f32_to_s16(drmp3_int16* dst, const float* src, drmp3_uint64 sampleCount) +{ + drmp3_uint64 i; + drmp3_uint64 i4; + drmp3_uint64 sampleCount4; + + /* Unrolled. */ + i = 0; + sampleCount4 = sampleCount >> 2; + for (i4 = 0; i4 < sampleCount4; i4 += 1) { + float x0 = src[i+0]; + float x1 = src[i+1]; + float x2 = src[i+2]; + float x3 = src[i+3]; + + x0 = ((x0 < -1) ? -1 : ((x0 > 1) ? 1 : x0)); + x1 = ((x1 < -1) ? -1 : ((x1 > 1) ? 1 : x1)); + x2 = ((x2 < -1) ? -1 : ((x2 > 1) ? 1 : x2)); + x3 = ((x3 < -1) ? -1 : ((x3 > 1) ? 1 : x3)); + + x0 = x0 * 32767.0f; + x1 = x1 * 32767.0f; + x2 = x2 * 32767.0f; + x3 = x3 * 32767.0f; + + dst[i+0] = (drmp3_int16)x0; + dst[i+1] = (drmp3_int16)x1; + dst[i+2] = (drmp3_int16)x2; + dst[i+3] = (drmp3_int16)x3; + + i += 4; + } + + /* Leftover. */ + for (; i < sampleCount; i += 1) { + float x = src[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + x = x * 32767.0f; /* -1..1 to -32767..32767 */ + + dst[i] = (drmp3_int16)x; + } +} +#endif + +#if !defined(DR_MP3_FLOAT_OUTPUT) +static void drmp3_s16_to_f32(float* dst, const drmp3_int16* src, drmp3_uint64 sampleCount) +{ + drmp3_uint64 i; + for (i = 0; i < sampleCount; i += 1) { + float x = (float)src[i]; + x = x * 0.000030517578125f; /* -32768..32767 to -1..0.999969482421875 */ + dst[i] = x; + } +} +#endif + + +static drmp3_uint64 drmp3_read_pcm_frames_raw(drmp3* pMP3, drmp3_uint64 framesToRead, void* pBufferOut) { drmp3_uint64 totalFramesRead = 0; - if (pMP3 == NULL || pMP3->onRead == NULL) { - return 0; - } + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onRead != NULL); - if (pBufferOut == NULL) { - float temp[4096]; - while (framesToRead > 0) { - drmp3_uint64 framesJustRead; - drmp3_uint64 framesToReadRightNow = sizeof(temp)/sizeof(temp[0]) / pMP3->channels; - if (framesToReadRightNow > framesToRead) { - framesToReadRightNow = framesToRead; - } - - framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); - if (framesJustRead == 0) { - break; - } - - framesToRead -= framesJustRead; - totalFramesRead += framesJustRead; + while (framesToRead > 0) { + drmp3_uint32 framesToConsume = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); + if (pBufferOut != NULL) { + #if defined(DR_MP3_FLOAT_OUTPUT) + /* f32 */ + float* pFramesOutF32 = (float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); + float* pFramesInF32 = (float*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + DRMP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); + #else + /* s16 */ + drmp3_int16* pFramesOutS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalFramesRead * pMP3->channels); + drmp3_int16* pFramesInS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(drmp3_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + DRMP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(drmp3_int16) * framesToConsume * pMP3->channels); + #endif + } + + pMP3->currentPCMFrame += framesToConsume; + pMP3->pcmFramesConsumedInMP3Frame += framesToConsume; + pMP3->pcmFramesRemainingInMP3Frame -= framesToConsume; + totalFramesRead += framesToConsume; + framesToRead -= framesToConsume; + + if (framesToRead == 0) { + break; + } + + DRMP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 0); + + /* + At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed + at this point which means we'll also need to update our sample rate conversion pipeline. + */ + if (drmp3_decode_next_frame(pMP3) == 0) { + break; } - } else { - totalFramesRead = drmp3_src_read_frames_ex(&pMP3->src, framesToRead, pBufferOut, DRMP3_TRUE); - pMP3->currentPCMFrame += totalFramesRead; } return totalFramesRead; } -drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut) -{ - float tempF32[4096]; - drmp3_uint64 pcmFramesJustRead; - drmp3_uint64 totalPCMFramesRead = 0; +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) +{ if (pMP3 == NULL || pMP3->onRead == NULL) { return 0; } - /* Naive implementation: read into a temp f32 buffer, then convert. */ - for (;;) { - drmp3_uint64 pcmFramesToReadThisIteration = (framesToRead - totalPCMFramesRead); - if (pcmFramesToReadThisIteration > drmp3_countof(tempF32)/pMP3->channels) { - pcmFramesToReadThisIteration = drmp3_countof(tempF32)/pMP3->channels; +#if defined(DR_MP3_FLOAT_OUTPUT) + /* Fast path. No conversion required. */ + return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); +#else + /* Slow path. Convert from s16 to f32. */ + { + drmp3_int16 pTempS16[8192]; + drmp3_uint64 totalPCMFramesRead = 0; + + while (totalPCMFramesRead < framesToRead) { + drmp3_uint64 framesJustRead; + drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; + drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempS16) / pMP3->channels; + if (framesToReadNow > framesRemaining) { + framesToReadNow = framesRemaining; + } + + framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempS16); + if (framesJustRead == 0) { + break; + } + + drmp3_s16_to_f32((float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalPCMFramesRead * pMP3->channels), pTempS16, framesJustRead * pMP3->channels); + totalPCMFramesRead += framesJustRead; } - pcmFramesJustRead = drmp3_read_pcm_frames_f32(pMP3, pcmFramesToReadThisIteration, tempF32); - if (pcmFramesJustRead == 0) { - break; - } - - drmp3dec_f32_to_s16(tempF32, pBufferOut, (int)(pcmFramesJustRead * pMP3->channels)); /* <-- Safe cast since pcmFramesJustRead will be clamped based on the size of tempF32 which is always small. */ - pBufferOut += pcmFramesJustRead * pMP3->channels; - - totalPCMFramesRead += pcmFramesJustRead; - - if (pcmFramesJustRead < pcmFramesToReadThisIteration) { - break; - } + return totalPCMFramesRead; } - - return totalPCMFramesRead; +#endif } -void drmp3_reset(drmp3* pMP3) +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut) +{ + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; + } + +#if !defined(DR_MP3_FLOAT_OUTPUT) + /* Fast path. No conversion required. */ + return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); +#else + /* Slow path. Convert from f32 to s16. */ + { + float pTempF32[4096]; + drmp3_uint64 totalPCMFramesRead = 0; + + while (totalPCMFramesRead < framesToRead) { + drmp3_uint64 framesJustRead; + drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; + drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempF32) / pMP3->channels; + if (framesToReadNow > framesRemaining) { + framesToReadNow = framesRemaining; + } + + framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempF32); + if (framesJustRead == 0) { + break; + } + + drmp3_f32_to_s16((drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalPCMFramesRead * pMP3->channels), pTempF32, framesJustRead * pMP3->channels); + totalPCMFramesRead += framesJustRead; + } + + return totalPCMFramesRead; + } +#endif +} + +static void drmp3_reset(drmp3* pMP3) { DRMP3_ASSERT(pMP3 != NULL); @@ -3274,19 +3748,10 @@ void drmp3_reset(drmp3* pMP3) pMP3->currentPCMFrame = 0; pMP3->dataSize = 0; pMP3->atEnd = DRMP3_FALSE; - pMP3->src.bin[0] = 0; - pMP3->src.bin[1] = 0; - pMP3->src.bin[2] = 0; - pMP3->src.bin[3] = 0; - pMP3->src.cache.cachedFrameCount = 0; - pMP3->src.cache.iNextFrame = 0; - pMP3->src.algo.linear.alpha = 0; - pMP3->src.algo.linear.isNextFramesLoaded = 0; - pMP3->src.algo.linear.isPrevFramesLoaded = 0; drmp3dec_init(&pMP3->decoder); } -drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) +static drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) { DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->onSeek != NULL); @@ -3301,78 +3766,29 @@ drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) return DRMP3_TRUE; } -float drmp3_get_cached_pcm_frame_count_from_src(drmp3* pMP3) -{ - return (pMP3->src.cache.cachedFrameCount - pMP3->src.cache.iNextFrame) + (float)pMP3->src.algo.linear.alpha; -} -float drmp3_get_pcm_frames_remaining_in_mp3_frame(drmp3* pMP3) -{ - float factor = (float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn; - float frameCountPreSRC = drmp3_get_cached_pcm_frame_count_from_src(pMP3) + pMP3->pcmFramesRemainingInMP3Frame; - return frameCountPreSRC * factor; -} - -/* -NOTE ON SEEKING -=============== -The seeking code below is a complete mess and is broken for cases when the sample rate changes. The problem -is with the resampling and the crappy resampler used by dr_mp3. What needs to happen is the following: - -1) The resampler needs to be replaced. -2) The resampler has state which needs to be updated whenever an MP3 frame is decoded outside of - drmp3_read_pcm_frames_f32(). The resampler needs an API to "flush" some imaginary input so that it's - state is updated accordingly. -*/ -drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset) +static drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset) { drmp3_uint64 framesRead; -#if 0 /* - MP3 is a bit annoying when it comes to seeking because of the bit reservoir. It basically means that an MP3 frame can possibly - depend on some of the data of prior frames. This means it's not as simple as seeking to the first byte of the MP3 frame that - contains the sample because that MP3 frame will need the data from the previous MP3 frame (which we just seeked past!). To - resolve this we seek past a number of MP3 frames up to a point, and then read-and-discard the remainder. + Just using a dumb read-and-discard for now. What would be nice is to parse only the header of the MP3 frame, and then skip over leading + frames without spending the time doing a full decode. I cannot see an easy way to do this in minimp3, however, so it may involve some + kind of manual processing. */ - drmp3_uint64 maxFramesToReadAndDiscard = (drmp3_uint64)(DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3 * ((float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn)); - - /* Now get rid of leading whole frames. */ - while (frameOffset > maxFramesToReadAndDiscard) { - float pcmFramesRemainingInCurrentMP3FrameF = drmp3_get_pcm_frames_remaining_in_mp3_frame(pMP3); - drmp3_uint32 pcmFramesRemainingInCurrentMP3Frame = (drmp3_uint32)pcmFramesRemainingInCurrentMP3FrameF; - if (frameOffset > pcmFramesRemainingInCurrentMP3Frame) { - frameOffset -= pcmFramesRemainingInCurrentMP3Frame; - pMP3->currentPCMFrame += pcmFramesRemainingInCurrentMP3Frame; - pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame; - pMP3->pcmFramesRemainingInMP3Frame = 0; - } else { - break; - } - - drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, pMP3->pcmFrames, DRMP3_FALSE); - if (pcmFrameCount == 0) { - break; - } - } - - /* The last step is to read-and-discard any remaining PCM frames to make it sample-exact. */ +#if defined(DR_MP3_FLOAT_OUTPUT) framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); - if (framesRead != frameOffset) { - return DRMP3_FALSE; - } #else - /* Just using a dumb read-and-discard for now pending updates to the resampler. */ - framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); + framesRead = drmp3_read_pcm_frames_s16(pMP3, frameOffset, NULL); +#endif if (framesRead != frameOffset) { return DRMP3_FALSE; } -#endif return DRMP3_TRUE; } -drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) +static drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) { DRMP3_ASSERT(pMP3 != NULL); @@ -3395,7 +3811,7 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 fram return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, (frameIndex - pMP3->currentPCMFrame)); } -drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex) +static drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex) { drmp3_uint32 iSeekPoint; @@ -3419,7 +3835,7 @@ drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, return DRMP3_TRUE; } -drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frameIndex) +static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frameIndex) { drmp3_seek_point seekPoint; drmp3_uint32 priorSeekPointIndex; @@ -3450,7 +3866,7 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frame /* Whole MP3 frames need to be discarded first. */ for (iMP3Frame = 0; iMP3Frame < seekPoint.mp3FramesToDiscard; ++iMP3Frame) { - drmp3_uint32 pcmFramesReadPreSRC; + drmp3_uint32 pcmFramesRead; drmp3d_sample_t* pPCMFrames; /* Pass in non-null for the last frame because we want to ensure the sample rate converter is preloaded correctly. */ @@ -3459,9 +3875,9 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frame pPCMFrames = (drmp3d_sample_t*)pMP3->pcmFrames; } - /* We first need to decode the next frame, and then we need to flush the resampler. */ - pcmFramesReadPreSRC = drmp3_decode_next_frame_ex(pMP3, pPCMFrames, DRMP3_TRUE); - if (pcmFramesReadPreSRC == 0) { + /* We first need to decode the next frame. */ + pcmFramesRead = drmp3_decode_next_frame_ex(pMP3, pPCMFrames); + if (pcmFramesRead == 0) { return DRMP3_FALSE; } } @@ -3469,17 +3885,6 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frame /* We seeked to an MP3 frame in the raw stream so we need to make sure the current PCM frame is set correctly. */ pMP3->currentPCMFrame = seekPoint.pcmFrameIndex - seekPoint.pcmFramesToDiscard; - /* - Update resampler. This is wrong. Need to instead update it on a per MP3 frame basis. Also broken for cases when - the sample rate is being reduced in my testing. Should work fine when the input and output sample rate is the same - or a clean multiple. - */ - pMP3->src.algo.linear.alpha = (drmp3_int64)pMP3->currentPCMFrame * ((double)pMP3->src.config.sampleRateIn / pMP3->src.config.sampleRateOut); /* <-- Cast to int64 is required for VC6. */ - pMP3->src.algo.linear.alpha = pMP3->src.algo.linear.alpha - (drmp3_uint32)(pMP3->src.algo.linear.alpha); - if (pMP3->src.algo.linear.alpha > 0) { - pMP3->src.algo.linear.isPrevFramesLoaded = 1; - } - /* Now at this point we can follow the same process as the brute force technique where we just skip over unnecessary MP3 frames and then read-and-discard at least 2 whole MP3 frames. @@ -3488,7 +3893,7 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frame return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, leftoverFrames); } -drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) +DRMP3_API drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) { if (pMP3 == NULL || pMP3->onSeek == NULL) { return DRMP3_FALSE; @@ -3506,12 +3911,11 @@ drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) } } -drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount) +DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount) { drmp3_uint64 currentPCMFrame; drmp3_uint64 totalPCMFrameCount; drmp3_uint64 totalMP3FrameCount; - float totalPCMFrameCountFractionalPart; if (pMP3 == NULL) { return DRMP3_FALSE; @@ -3537,25 +3941,15 @@ drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3Fr totalPCMFrameCount = 0; totalMP3FrameCount = 0; - totalPCMFrameCountFractionalPart = 0; /* <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate. */ for (;;) { - drmp3_uint32 pcmFramesInCurrentMP3FrameIn; - float srcRatio; - float pcmFramesInCurrentMP3FrameOutF; - drmp3_uint32 pcmFramesInCurrentMP3FrameOut; + drmp3_uint32 pcmFramesInCurrentMP3Frame; - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); - if (pcmFramesInCurrentMP3FrameIn == 0) { + pcmFramesInCurrentMP3Frame = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFramesInCurrentMP3Frame == 0) { break; } - srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; - DRMP3_ASSERT(srcRatio > 0); - - pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio); - pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF; - totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut; - totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut; + totalPCMFrameCount += pcmFramesInCurrentMP3Frame; totalMP3FrameCount += 1; } @@ -3578,7 +3972,7 @@ drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3Fr return DRMP3_TRUE; } -drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) +DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) { drmp3_uint64 totalPCMFrameCount; if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { @@ -3588,7 +3982,7 @@ drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) return totalPCMFrameCount; } -drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) +DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) { drmp3_uint64 totalMP3FrameCount; if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, NULL)) { @@ -3598,7 +3992,7 @@ drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) return totalMP3FrameCount; } -void drmp3__accumulate_running_pcm_frame_count(drmp3* pMP3, drmp3_uint32 pcmFrameCountIn, drmp3_uint64* pRunningPCMFrameCount, float* pRunningPCMFrameCountFractionalPart) +static void drmp3__accumulate_running_pcm_frame_count(drmp3* pMP3, drmp3_uint32 pcmFrameCountIn, drmp3_uint64* pRunningPCMFrameCount, float* pRunningPCMFrameCountFractionalPart) { float srcRatio; float pcmFrameCountOutF; @@ -3619,7 +4013,7 @@ typedef struct drmp3_uint64 pcmFrameIndex; /* <-- After sample rate conversion. */ } drmp3__seeking_mp3_frame_info; -drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints) +DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints) { drmp3_uint32 seekPointCount; drmp3_uint64 currentPCMFrame; @@ -3688,7 +4082,7 @@ drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCo mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount; /* We need to get information about this frame so we can know how many samples it contained. */ - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { return DRMP3_FALSE; /* This should never happen. */ } @@ -3720,19 +4114,19 @@ drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCo The next seek point is not in the current MP3 frame, so continue on to the next one. The first thing to do is cycle the cached MP3 frame info. */ - for (i = 0; i < drmp3_countof(mp3FrameInfo)-1; ++i) { + for (i = 0; i < DRMP3_COUNTOF(mp3FrameInfo)-1; ++i) { mp3FrameInfo[i] = mp3FrameInfo[i+1]; } /* Cache previous MP3 frame info. */ - mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; - mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; + mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; + mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; /* Go to the next MP3 frame. This shouldn't ever fail, but just in case it does we just set the seek point and break. If it happens, it should only ever do it for the last seek point. */ - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_TRUE); + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; @@ -3759,7 +4153,7 @@ drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCo return DRMP3_TRUE; } -drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints) +DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints) { if (pMP3 == NULL) { return DRMP3_FALSE; @@ -3779,7 +4173,7 @@ drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drm } -float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +static float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3_uint64 totalFramesRead = 0; drmp3_uint64 framesCapacity = 0; @@ -3789,7 +4183,7 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ DRMP3_ASSERT(pMP3 != NULL); for (;;) { - drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; + drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; @@ -3809,7 +4203,7 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(float); newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(float); - if (newFramesBufferSize > DRMP3_SIZE_MAX) { + if (newFramesBufferSize > (drmp3_uint64)DRMP3_SIZE_MAX) { break; } @@ -3833,8 +4227,8 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ } if (pConfig != NULL) { - pConfig->outputChannels = pMP3->channels; - pConfig->outputSampleRate = pMP3->sampleRate; + pConfig->channels = pMP3->channels; + pConfig->sampleRate = pMP3->sampleRate; } drmp3_uninit(pMP3); @@ -3846,7 +4240,7 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ return pFrames; } -drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3_uint64 totalFramesRead = 0; drmp3_uint64 framesCapacity = 0; @@ -3856,7 +4250,7 @@ drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, DRMP3_ASSERT(pMP3 != NULL); for (;;) { - drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; + drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_s16(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; @@ -3876,7 +4270,7 @@ drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(drmp3_int16); newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(drmp3_int16); - if (newFramesBufferSize > DRMP3_SIZE_MAX) { + if (newFramesBufferSize > (drmp3_uint64)DRMP3_SIZE_MAX) { break; } @@ -3900,8 +4294,8 @@ drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, } if (pConfig != NULL) { - pConfig->outputChannels = pMP3->channels; - pConfig->outputSampleRate = pMP3->sampleRate; + pConfig->channels = pMP3->channels; + pConfig->sampleRate = pMP3->sampleRate; } drmp3_uninit(pMP3); @@ -3914,20 +4308,20 @@ drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, } -float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig, pAllocationCallbacks)) { + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig, pAllocationCallbacks)) { + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { return NULL; } @@ -3935,20 +4329,20 @@ drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_se } -float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig, pAllocationCallbacks)) { + if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig, pAllocationCallbacks)) { + if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { return NULL; } @@ -3957,20 +4351,20 @@ drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t #ifndef DR_MP3_NO_STDIO -float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_file(&mp3, filePath, pConfig, pAllocationCallbacks)) { + if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_file(&mp3, filePath, pConfig, pAllocationCallbacks)) { + if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { return NULL; } @@ -3978,7 +4372,16 @@ drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3 } #endif -void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + return drmp3__malloc_from_callbacks(sz, pAllocationCallbacks); + } else { + return drmp3__malloc_default(sz, NULL); + } +} + +DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { drmp3__free_from_callbacks(p, pAllocationCallbacks); @@ -3987,7 +4390,8 @@ void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) } } -#endif /*DR_MP3_IMPLEMENTATION*/ +#endif /* dr_mp3_c */ +#endif /*DR_MP3_IMPLEMENTATION*/ /* DIFFERENCES BETWEEN minimp3 AND dr_mp3 @@ -4004,9 +4408,196 @@ DIFFERENCES BETWEEN minimp3 AND dr_mp3 using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. */ +/* +RELEASE NOTES - v0.5.0 +======================= +Version 0.5.0 has breaking API changes. + +Improved Client-Defined Memory Allocation +----------------------------------------- +The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The +existing system of DRMP3_MALLOC, DRMP3_REALLOC and DRMP3_FREE are still in place and will be used by default when no custom +allocation callbacks are specified. + +To use the new system, you pass in a pointer to a drmp3_allocation_callbacks object to drmp3_init() and family, like this: + + void* my_malloc(size_t sz, void* pUserData) + { + return malloc(sz); + } + void* my_realloc(void* p, size_t sz, void* pUserData) + { + return realloc(p, sz); + } + void my_free(void* p, void* pUserData) + { + free(p); + } + + ... + + drmp3_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = &myData; + allocationCallbacks.onMalloc = my_malloc; + allocationCallbacks.onRealloc = my_realloc; + allocationCallbacks.onFree = my_free; + drmp3_init_file(&mp3, "my_file.mp3", NULL, &allocationCallbacks); + +The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. + +Passing in null for the allocation callbacks object will cause dr_mp3 to use defaults which is the same as DRMP3_MALLOC, +DRMP3_REALLOC and DRMP3_FREE and the equivalent of how it worked in previous versions. + +Every API that opens a drmp3 object now takes this extra parameter. These include the following: + + drmp3_init() + drmp3_init_file() + drmp3_init_memory() + drmp3_open_and_read_pcm_frames_f32() + drmp3_open_and_read_pcm_frames_s16() + drmp3_open_memory_and_read_pcm_frames_f32() + drmp3_open_memory_and_read_pcm_frames_s16() + drmp3_open_file_and_read_pcm_frames_f32() + drmp3_open_file_and_read_pcm_frames_s16() + +Renamed APIs +------------ +The following APIs have been renamed for consistency with other dr_* libraries and to make it clear that they return PCM frame +counts rather than sample counts. + + drmp3_open_and_read_f32() -> drmp3_open_and_read_pcm_frames_f32() + drmp3_open_and_read_s16() -> drmp3_open_and_read_pcm_frames_s16() + drmp3_open_memory_and_read_f32() -> drmp3_open_memory_and_read_pcm_frames_f32() + drmp3_open_memory_and_read_s16() -> drmp3_open_memory_and_read_pcm_frames_s16() + drmp3_open_file_and_read_f32() -> drmp3_open_file_and_read_pcm_frames_f32() + drmp3_open_file_and_read_s16() -> drmp3_open_file_and_read_pcm_frames_s16() +*/ + /* REVISION HISTORY ================ +v0.6.34 - 2022-09-17 + - Fix compilation with DJGPP. + - Fix compilation when compiling with x86 with no SSE2. + - Remove an unnecessary variable from the drmp3 structure. + +v0.6.33 - 2022-04-10 + - Fix compilation error with the MSVC ARM64 build. + - Fix compilation error on older versions of GCC. + - Remove some unused functions. + +v0.6.32 - 2021-12-11 + - Fix a warning with Clang. + +v0.6.31 - 2021-08-22 + - Fix a bug when loading from memory. + +v0.6.30 - 2021-08-16 + - Silence some warnings. + - Replace memory operations with DRMP3_* macros. + +v0.6.29 - 2021-08-08 + - Bring up to date with minimp3. + +v0.6.28 - 2021-07-31 + - Fix platform detection for ARM64. + - Fix a compilation error with C89. + +v0.6.27 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + +v0.6.26 - 2021-01-31 + - Bring up to date with minimp3. + +v0.6.25 - 2020-12-26 + - Remove DRMP3_DEFAULT_CHANNELS and DRMP3_DEFAULT_SAMPLE_RATE which are leftovers from some removed APIs. + +v0.6.24 - 2020-12-07 + - Fix a typo in version date for 0.6.23. + +v0.6.23 - 2020-12-03 + - Fix an error where a file can be closed twice when initialization of the decoder fails. + +v0.6.22 - 2020-12-02 + - Fix an error where it's possible for a file handle to be left open when initialization of the decoder fails. + +v0.6.21 - 2020-11-28 + - Bring up to date with minimp3. + +v0.6.20 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.6.19 - 2020-11-13 + - Minor code clean up. + +v0.6.18 - 2020-11-01 + - Improve compiler support for older versions of GCC. + +v0.6.17 - 2020-09-28 + - Bring up to date with minimp3. + +v0.6.16 - 2020-08-02 + - Simplify sized types. + +v0.6.15 - 2020-07-25 + - Fix a compilation warning. + +v0.6.14 - 2020-07-23 + - Fix undefined behaviour with memmove(). + +v0.6.13 - 2020-07-06 + - Fix a bug when converting from s16 to f32 in drmp3_read_pcm_frames_f32(). + +v0.6.12 - 2020-06-23 + - Add include guard for the implementation section. + +v0.6.11 - 2020-05-26 + - Fix use of uninitialized variable error. + +v0.6.10 - 2020-05-16 + - Add compile-time and run-time version querying. + - DRMP3_VERSION_MINOR + - DRMP3_VERSION_MAJOR + - DRMP3_VERSION_REVISION + - DRMP3_VERSION_STRING + - drmp3_version() + - drmp3_version_string() + +v0.6.9 - 2020-04-30 + - Change the `pcm` parameter of drmp3dec_decode_frame() to a `const drmp3_uint8*` for consistency with internal APIs. + +v0.6.8 - 2020-04-26 + - Optimizations to decoding when initializing from memory. + +v0.6.7 - 2020-04-25 + - Fix a compilation error with DR_MP3_NO_STDIO + - Optimization to decoding by reducing some data movement. + +v0.6.6 - 2020-04-23 + - Fix a minor bug with the running PCM frame counter. + +v0.6.5 - 2020-04-19 + - Fix compilation error on ARM builds. + +v0.6.4 - 2020-04-19 + - Bring up to date with changes to minimp3. + +v0.6.3 - 2020-04-13 + - Fix some pedantic warnings. + +v0.6.2 - 2020-04-10 + - Fix a crash in drmp3_open_*_and_read_pcm_frames_*() if the output config object is NULL. + +v0.6.1 - 2020-04-05 + - Fix warnings. + +v0.6.0 - 2020-04-04 + - API CHANGE: Remove the pConfig parameter from the following APIs: + - drmp3_init() + - drmp3_init_memory() + - drmp3_init_file() + - Add drmp3_init_file_w() for opening a file from a wchar_t encoded path. + v0.5.6 - 2020-02-12 - Bring up to date with minimp3. @@ -4058,9 +4649,9 @@ v0.4.4 - 2019-05-06 - Fixes to the VC6 build. v0.4.3 - 2019-05-05 - - Use the channel count and/or sample rate of the first MP3 frame instead of DR_MP3_DEFAULT_CHANNELS and - DR_MP3_DEFAULT_SAMPLE_RATE when they are set to 0. To use the old behaviour, just set the relevant property to - DR_MP3_DEFAULT_CHANNELS or DR_MP3_DEFAULT_SAMPLE_RATE. + - Use the channel count and/or sample rate of the first MP3 frame instead of DRMP3_DEFAULT_CHANNELS and + DRMP3_DEFAULT_SAMPLE_RATE when they are set to 0. To use the old behaviour, just set the relevant property to + DRMP3_DEFAULT_CHANNELS or DRMP3_DEFAULT_SAMPLE_RATE. - Add s16 reading APIs - drmp3_read_pcm_frames_s16 - drmp3_open_memory_and_read_pcm_frames_s16 diff --git a/src/modules/font/BMFontRasterizer.cpp b/src/modules/font/BMFontRasterizer.cpp index 4d734a31c..660c4ce35 100644 --- a/src/modules/font/BMFontRasterizer.cpp +++ b/src/modules/font/BMFontRasterizer.cpp @@ -20,6 +20,7 @@ // LOVE #include "BMFontRasterizer.h" +#include "GenericShaper.h" #include "filesystem/Filesystem.h" #include "image/Image.h" @@ -164,6 +165,14 @@ BMFontRasterizer::~BMFontRasterizer() void BMFontRasterizer::parseConfig(const std::string &configtext) { + { + BMFontCharacter nullchar = {}; + nullchar.page = -1; + nullchar.glyph = 0; + characters.push_back(nullchar); + characterIndices[0] = (int)characters.size() - 1; + } + std::stringstream ss(configtext); std::string line; @@ -237,7 +246,10 @@ void BMFontRasterizer::parseConfig(const std::string &configtext) c.metrics.bearingY = -cline.getAttributeInt("yoffset"); c.metrics.advance = cline.getAttributeInt("xadvance"); - characters[id] = c; + c.glyph = id; + + characters.push_back(c); + characterIndices[id] = (int) characters.size() - 1; } else if (tag == "kerning") { @@ -257,13 +269,15 @@ void BMFontRasterizer::parseConfig(const std::string &configtext) bool guessheight = lineHeight == 0; // Verify the glyph character attributes. - for (const auto &cpair : characters) + for (const auto &c : characters) { - const BMFontCharacter &c = cpair.second; + if (c.glyph == 0) + continue; + int width = c.metrics.width; int height = c.metrics.height; - if (!unicode && cpair.first > 127) + if (!unicode && c.glyph > 127) throw love::Exception("Invalid BMFont character id (only unicode and ASCII are supported)"); if (c.page < 0 || images[c.page].get() == nullptr) @@ -272,13 +286,13 @@ void BMFontRasterizer::parseConfig(const std::string &configtext) const image::ImageData *id = images[c.page].get(); if (!id->inside(c.x, c.y)) - throw love::Exception("Invalid coordinates for BMFont character %u.", cpair.first); + throw love::Exception("Invalid coordinates for BMFont character %u.", c.glyph); if (width > 0 && !id->inside(c.x + width - 1, c.y)) - throw love::Exception("Invalid width %d for BMFont character %u.", width, cpair.first); + throw love::Exception("Invalid width %d for BMFont character %u.", width, c.glyph); if (height > 0 && !id->inside(c.x, c.y + height - 1)) - throw love::Exception("Invalid height %d for BMFont character %u.", height, cpair.first); + throw love::Exception("Invalid height %d for BMFont character %u.", height, c.glyph); if (guessheight) lineHeight = std::max(lineHeight, c.metrics.height); @@ -292,22 +306,37 @@ int BMFontRasterizer::getLineHeight() const return lineHeight; } -GlyphData *BMFontRasterizer::getGlyphData(uint32 glyph) const +int BMFontRasterizer::getGlyphSpacing(uint32 glyph) const { - auto it = characters.find(glyph); + auto it = characterIndices.find(glyph); + if (it == characterIndices.end()) + return 0; + return characters[it->second].metrics.advance; +} + +int BMFontRasterizer::getGlyphIndex(uint32 glyph) const +{ + auto it = characterIndices.find(glyph); + if (it == characterIndices.end()) + return 0; + return it->second; +} + +GlyphData *BMFontRasterizer::getGlyphDataForIndex(int index) const +{ // Return an empty GlyphData if we don't have the glyph character. - if (it == characters.end()) - return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); + if (index < 0 || index >= (int) characters.size()) + return new GlyphData(0, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); - const BMFontCharacter &c = it->second; + const BMFontCharacter& c = characters[index]; const auto &imagepair = images.find(c.page); if (imagepair == images.end()) - return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); + return new GlyphData(c.glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); image::ImageData *imagedata = imagepair->second.get(); - GlyphData *g = new GlyphData(glyph, c.metrics, PIXELFORMAT_RGBA8_UNORM); + GlyphData *g = new GlyphData(c.glyph, c.metrics, PIXELFORMAT_RGBA8_UNORM); size_t pixelsize = imagedata->getPixelSize(); @@ -333,7 +362,7 @@ int BMFontRasterizer::getGlyphCount() const bool BMFontRasterizer::hasGlyph(uint32 glyph) const { - return characters.find(glyph) != characters.end(); + return characterIndices.find(glyph) != characterIndices.end(); } float BMFontRasterizer::getKerning(uint32 leftglyph, uint32 rightglyph) const @@ -352,6 +381,11 @@ Rasterizer::DataType BMFontRasterizer::getDataType() const return DATA_IMAGE; } +TextShaper *BMFontRasterizer::newTextShaper() +{ + return new GenericShaper(this); +} + bool BMFontRasterizer::accepts(love::filesystem::FileData *fontdef) { const char *data = (const char *) fontdef->getData(); diff --git a/src/modules/font/BMFontRasterizer.h b/src/modules/font/BMFontRasterizer.h index 54335635f..3d4a452f8 100644 --- a/src/modules/font/BMFontRasterizer.h +++ b/src/modules/font/BMFontRasterizer.h @@ -47,11 +47,14 @@ public: // Implements Rasterizer. int getLineHeight() const override; - GlyphData *getGlyphData(uint32 glyph) const override; + int getGlyphSpacing(uint32 glyph) const override; + int getGlyphIndex(uint32 glyph) const override; + GlyphData *getGlyphDataForIndex(int index) const override; int getGlyphCount() const override; bool hasGlyph(uint32 glyph) const override; float getKerning(uint32 leftglyph, uint32 rightglyph) const override; DataType getDataType() const override; + TextShaper *newTextShaper() override; static bool accepts(love::filesystem::FileData *fontdef); @@ -63,6 +66,7 @@ private: int y; int page; GlyphMetrics metrics; + uint32 glyph; }; void parseConfig(const std::string &config); @@ -72,8 +76,10 @@ private: // Image pages, indexed by their page id. std::unordered_map> images; - // Glyph characters, indexed by their glyph id. - std::unordered_map characters; + std::vector characters; + + // Glyph character indices, indexed by their glyph id. + std::unordered_map characterIndices; // Kerning information, indexed by two (packed) characters. std::unordered_map kerning; diff --git a/src/modules/font/GenericShaper.cpp b/src/modules/font/GenericShaper.cpp new file mode 100644 index 000000000..ef5854602 --- /dev/null +++ b/src/modules/font/GenericShaper.cpp @@ -0,0 +1,203 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * 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. + **/ + +// LOVE +#include "GenericShaper.h" +#include "Rasterizer.h" +#include "common/Optional.h" + +namespace love +{ +namespace font +{ + +GenericShaper::GenericShaper(Rasterizer *rasterizer) + : TextShaper(rasterizer) +{ +} + +GenericShaper::~GenericShaper() +{ +} + +void GenericShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + if (rasterizers[0]->getDataType() == Rasterizer::DATA_TRUETYPE) + offset.y += getBaseline(); + + // Spacing counter and newline handling. + Vector2 curpos = offset; + + int maxwidth = 0; + uint32 prevglyph = 0; + + if (positions) + positions->reserve(range.getSize()); + + int colorindex = 0; + int ncolors = (int) codepoints.colors.size(); + Optional colorToAdd; + + // Make sure the right color is applied to the start of the glyph list, + // when the start isn't 0. + if (colors && range.getOffset() > 0 && !codepoints.colors.empty()) + { + for (; colorindex < ncolors; colorindex++) + { + if (codepoints.colors[colorindex].index >= (int) range.getOffset()) + break; + colorToAdd.set(codepoints.colors[colorindex].color); + } + } + + for (int i = (int) range.getMin(); i <= (int) range.getMax(); i++) + { + uint32 g = codepoints.cps[i]; + + // Do this before anything else so we don't miss colors corresponding + // to newlines. The actual add to the list happens after newline + // handling, to make sure the resulting index is valid in the positions + // array. + if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == i) + { + colorToAdd.set(codepoints.colors[colorindex].color); + colorindex++; + } + + if (g == '\n') + { + if (curpos.x > maxwidth) + maxwidth = (int)curpos.x; + + // Wrap newline, but do not output a position for it. + curpos.y += floorf(getHeight() * getLineHeight() + 0.5f); + curpos.x = offset.x; + prevglyph = 0; + continue; + } + + // Ignore carriage returns + if (g == '\r') + { + prevglyph = g; + continue; + } + + if (colorToAdd.hasValue && colors && positions) + { + IndexedColor c = {colorToAdd.value, (int) positions->size()}; + colors->push_back(c); + colorToAdd.clear(); + } + + // Add kerning to the current horizontal offset. + curpos.x += getKerning(prevglyph, g); + + GlyphIndex glyphindex; + int advance = getGlyphAdvance(g, &glyphindex); + + if (positions) + positions->push_back({ Vector2(curpos.x, curpos.y), glyphindex }); + + // Advance the x position for the next glyph. + curpos.x += advance; + + // Account for extra spacing given to space characters. + if (g == ' ' && extraspacing != 0.0f) + curpos.x = floorf(curpos.x + extraspacing); + + prevglyph = g; + } + + if (curpos.x > maxwidth) + maxwidth = (int)curpos.x; + + if (info != nullptr) + { + info->width = maxwidth - offset.x; + info->height = curpos.y - offset.y; + if (curpos.x > offset.x) + info->height += floorf(getHeight() * getLineHeight() + 0.5f); + } +} + +int GenericShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + uint32 prevglyph = 0; + + float w = 0.0f; + float outwidth = 0.0f; + float widthbeforelastspace = 0.0f; + int wrapindex = -1; + int lastspaceindex = -1; + + for (int i = (int)range.getMin(); i <= (int)range.getMax(); i++) + { + uint32 g = codepoints.cps[i]; + + if (g == '\r') + { + prevglyph = g; + continue; + } + + float newwidth = w + getKerning(prevglyph, g) + getGlyphAdvance(g); + + // Only wrap when there's a non-space character. + if (newwidth > wraplimit && !isWhitespace(g)) + { + // Rewind to the last seen space when wrapping. + if (lastspaceindex != -1) + { + wrapindex = lastspaceindex; + outwidth = widthbeforelastspace; + } + break; + } + + // Don't count trailing spaces in the output width. + if (isWhitespace(g)) + { + lastspaceindex = i; + if (!isWhitespace(prevglyph)) + widthbeforelastspace = w; + } + else + outwidth = newwidth; + + w = newwidth; + prevglyph = g; + wrapindex = i; + } + + if (width) + *width = outwidth; + + return wrapindex; +} + +} // font +} // love diff --git a/src/modules/font/GenericShaper.h b/src/modules/font/GenericShaper.h new file mode 100644 index 000000000..c77b5b309 --- /dev/null +++ b/src/modules/font/GenericShaper.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * 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. + **/ + +#pragma once + +// LOVE +#include "TextShaper.h" + +namespace love +{ +namespace font +{ + +class GenericShaper : public love::font::TextShaper +{ +public: + + GenericShaper(Rasterizer *rasterizer); + virtual ~GenericShaper(); + + void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) override; + int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override; + +private: + +}; // GenericShaper + +} // font +} // love diff --git a/src/modules/font/GlyphData.cpp b/src/modules/font/GlyphData.cpp index a5536bbf9..8b1d4c285 100644 --- a/src/modules/font/GlyphData.cpp +++ b/src/modules/font/GlyphData.cpp @@ -24,10 +24,6 @@ // UTF-8 #include "libraries/utf8/utf8.h" -// stdlib -#include -#include - namespace love { namespace font diff --git a/src/modules/font/ImageRasterizer.cpp b/src/modules/font/ImageRasterizer.cpp index 6c9a059fc..93b71b894 100644 --- a/src/modules/font/ImageRasterizer.cpp +++ b/src/modules/font/ImageRasterizer.cpp @@ -20,8 +20,9 @@ // LOVE #include "ImageRasterizer.h" - +#include "GenericShaper.h" #include "common/Exception.h" + #include namespace love @@ -31,10 +32,9 @@ namespace font static_assert(sizeof(Color32) == 4, "sizeof(Color32) must equal 4 bytes!"); -ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale) +ImageRasterizer::ImageRasterizer(love::image::ImageData *data, const uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale) : imageData(data) - , glyphs(glyphs) - , numglyphs(numglyphs) + , numglyphs(numglyphs + 1) // Always have a null glyph at the start of the array. , extraSpacing(extraspacing) { this->dpiScale = dpiscale; @@ -42,7 +42,7 @@ ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, i if (data->getFormat() != PIXELFORMAT_RGBA8_UNORM) throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!"); - load(); + load(glyphs, numglyphs); } ImageRasterizer::~ImageRasterizer() @@ -54,16 +54,33 @@ int ImageRasterizer::getLineHeight() const return getHeight(); } -GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const +int ImageRasterizer::getGlyphSpacing(uint32 glyph) const +{ + auto it = glyphIndices.find(glyph); + if (it == glyphIndices.end()) + return 0; + return imageGlyphs[it->second].width + extraSpacing; +} + +int ImageRasterizer::getGlyphIndex(uint32 glyph) const +{ + auto it = glyphIndices.find(glyph); + if (it == glyphIndices.end()) + return 0; + return it->second; +} + +GlyphData *ImageRasterizer::getGlyphDataForIndex(int index) const { GlyphMetrics gm = {}; + uint32 glyph = 0; // Set relevant glyph metrics if the glyph is in this ImageFont - std::map::const_iterator it = imageGlyphs.find(glyph); - if (it != imageGlyphs.end()) + if (index >= 0 && index < (int) imageGlyphs.size()) { - gm.width = it->second.width; - gm.advance = it->second.width + extraSpacing; + gm.width = imageGlyphs[index].width; + gm.advance = imageGlyphs[index].width + extraSpacing; + glyph = imageGlyphs[index].glyph; } gm.height = metrics.height; @@ -82,7 +99,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const // copy glyph pixels from imagedata to glyphdata for (int i = 0; i < g->getWidth() * g->getHeight(); i++) { - Color32 p = imagepixels[it->second.x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))]; + Color32 p = imagepixels[imageGlyphs[index].x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))]; // Use transparency instead of the spacer color if (p == spacer) @@ -94,7 +111,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const return g; } -void ImageRasterizer::load() +void ImageRasterizer::load(const uint32 *glyphs, int glyphcount) { auto pixels = (const Color32 *) imageData->getData(); @@ -113,7 +130,16 @@ void ImageRasterizer::load() int start = 0; int end = 0; - for (int i = 0; i < numglyphs; ++i) + { + ImageGlyphData nullglyph; + nullglyph.x = 0; + nullglyph.width = 0; + nullglyph.glyph = 0; + imageGlyphs.push_back(nullglyph); + glyphIndices[0] = (int) imageGlyphs.size() - 1; + } + + for (int i = 0; i < glyphcount; ++i) { start = end; @@ -133,8 +159,10 @@ void ImageRasterizer::load() ImageGlyphData imageGlyph; imageGlyph.x = start; imageGlyph.width = end - start; + imageGlyph.glyph = glyphs[i]; - imageGlyphs[glyphs[i]] = imageGlyph; + imageGlyphs.push_back(imageGlyph); + glyphIndices[glyphs[i]] = (int) imageGlyphs.size() - 1; } } @@ -145,7 +173,7 @@ int ImageRasterizer::getGlyphCount() const bool ImageRasterizer::hasGlyph(uint32 glyph) const { - return imageGlyphs.find(glyph) != imageGlyphs.end(); + return glyphIndices.find(glyph) != glyphIndices.end(); } Rasterizer::DataType ImageRasterizer::getDataType() const @@ -153,5 +181,10 @@ Rasterizer::DataType ImageRasterizer::getDataType() const return DATA_IMAGE; } +TextShaper *ImageRasterizer::newTextShaper() +{ + return new GenericShaper(this); +} + } // font } // love diff --git a/src/modules/font/ImageRasterizer.h b/src/modules/font/ImageRasterizer.h index 10a04da0d..538b6dbd5 100644 --- a/src/modules/font/ImageRasterizer.h +++ b/src/modules/font/ImageRasterizer.h @@ -39,15 +39,18 @@ namespace font class ImageRasterizer : public Rasterizer { public: - ImageRasterizer(love::image::ImageData *imageData, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale); + ImageRasterizer(love::image::ImageData *imageData, const uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale); virtual ~ImageRasterizer(); // Implement Rasterizer int getLineHeight() const override; - GlyphData *getGlyphData(uint32 glyph) const override; + int getGlyphSpacing(uint32 glyph) const override; + int getGlyphIndex(uint32 glyph) const override; + GlyphData *getGlyphDataForIndex(int index) const override; int getGlyphCount() const override; bool hasGlyph(uint32 glyph) const override; DataType getDataType() const override; + TextShaper *newTextShaper() override; private: @@ -57,23 +60,23 @@ private: { int x; int width; + uint32 glyph; }; // Load all the glyph positions into memory - void load(); + void load(const uint32 *glyphs, int glyphcount); // The image data StrongRef imageData; - // The glyphs in the font - uint32 *glyphs; - // Number of glyphs in the font int numglyphs; int extraSpacing; - std::map imageGlyphs; + + std::vector imageGlyphs; + std::map glyphIndices; // Color used to identify glyph separation in the source ImageData Color32 spacer; diff --git a/src/modules/font/Rasterizer.cpp b/src/modules/font/Rasterizer.cpp index a5dfcef8e..53fa2d7f3 100644 --- a/src/modules/font/Rasterizer.cpp +++ b/src/modules/font/Rasterizer.cpp @@ -55,6 +55,11 @@ int Rasterizer::getDescent() const return metrics.descent; } +GlyphData *Rasterizer::getGlyphData(uint32 glyph) const +{ + return getGlyphDataForIndex(getGlyphIndex(glyph)); +} + GlyphData *Rasterizer::getGlyphData(const std::string &text) const { uint32 codepoint = 0; diff --git a/src/modules/font/Rasterizer.h b/src/modules/font/Rasterizer.h index dee0a74be..67575edd6 100644 --- a/src/modules/font/Rasterizer.h +++ b/src/modules/font/Rasterizer.h @@ -31,6 +31,8 @@ namespace love namespace font { +class TextShaper; + /** * Holds the specific font metrics. **/ @@ -84,17 +86,32 @@ public: **/ virtual int getLineHeight() const = 0; + /** + * Gets the spacing of the given unicode glyph. + **/ + virtual int getGlyphSpacing(uint32 glyph) const = 0; + + /** + * Gets a rasterizer-specific index associated with the given glyph. + **/ + virtual int getGlyphIndex(uint32 glyph) const = 0; + /** * Gets a specific glyph. * @param glyph The (UNICODE) glyph codepoint to get data for. **/ - virtual GlyphData *getGlyphData(uint32 glyph) const = 0; + GlyphData *getGlyphData(uint32 glyph) const; /** * Gets a specific glyph. * @param text The (UNICODE) glyph character to get the data for. **/ - virtual GlyphData *getGlyphData(const std::string &text) const; + GlyphData *getGlyphData(const std::string &text) const; + + /** + * Gets a specific glyph for the given rasterizer glyph index. + **/ + virtual GlyphData *getGlyphDataForIndex(int index) const = 0; /** * Gets the number of glyphs the rasterizer has data for. @@ -120,6 +137,10 @@ public: virtual DataType getDataType() const = 0; + virtual ptrdiff_t getHandle() const { return 0; } + + virtual TextShaper *newTextShaper() = 0; + float getDPIScale() const; protected: diff --git a/src/modules/font/TextShaper.cpp b/src/modules/font/TextShaper.cpp new file mode 100644 index 000000000..43854e719 --- /dev/null +++ b/src/modules/font/TextShaper.cpp @@ -0,0 +1,381 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * 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. + **/ + +// LOVE +#include "TextShaper.h" +#include "Rasterizer.h" +#include "common/Exception.h" + +#include "libraries/utf8/utf8.h" + +namespace love +{ +namespace font +{ + +void getCodepointsFromString(const std::string &text, std::vector &codepoints) +{ + codepoints.reserve(text.size()); + + try + { + utf8::iterator i(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (i != end) + { + uint32 g = *i++; + codepoints.push_back(g); + } + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } +} + +void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints) +{ + if (strs.empty()) + return; + + codepoints.cps.reserve(strs[0].str.size()); + + for (const ColoredString &cstr : strs) + { + // No need to add the color if the string is empty anyway, and the code + // further on assumes no two colors share the same starting position. + if (cstr.str.size() == 0) + continue; + + IndexedColor c = { cstr.color, (int)codepoints.cps.size() }; + codepoints.colors.push_back(c); + + getCodepointsFromString(cstr.str, codepoints.cps); + } + + if (codepoints.colors.size() == 1) + { + IndexedColor c = codepoints.colors[0]; + + if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f)) + codepoints.colors.pop_back(); + } +} + +love::Type TextShaper::type("TextShaper", &Object::type); + +TextShaper::TextShaper(Rasterizer *rasterizer) + : rasterizers{rasterizer} + , dpiScales{rasterizer->getDPIScale()} + , height(floorf(rasterizer->getHeight() / rasterizer->getDPIScale() + 0.5f)) + , lineHeight(1) + , useSpacesForTab(false) +{ + if (!rasterizer->hasGlyph('\t')) + useSpacesForTab = true; +} + +TextShaper::~TextShaper() +{ +} + +float TextShaper::getHeight() const +{ + return height; +} + +void TextShaper::setLineHeight(float h) +{ + lineHeight = h; +} + +float TextShaper::getLineHeight() const +{ + return lineHeight; +} + +int TextShaper::getAscent() const +{ + return floorf(rasterizers[0]->getAscent() / rasterizers[0]->getDPIScale() + 0.5f); +} + +int TextShaper::getDescent() const +{ + return floorf(rasterizers[0]->getDescent() / rasterizers[0]->getDPIScale() + 0.5f); +} + +float TextShaper::getBaseline() const +{ + float ascent = getAscent(); + if (ascent != 0.0f) + return ascent; + else if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) + return floorf(getHeight() / 1.25f + 0.5f); // 1.25 is magic line height for true type fonts + else + return 0.0f; +} + +bool TextShaper::hasGlyph(uint32 glyph) const +{ + for (const StrongRef &r : rasterizers) + { + if (r->hasGlyph(glyph)) + return true; + } + + return false; +} + +bool TextShaper::hasGlyphs(const std::string &text) const +{ + if (text.size() == 0) + return false; + + try + { + utf8::iterator i(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (i != end) + { + uint32 codepoint = *i++; + + if (!hasGlyph(codepoint)) + return false; + } + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return true; +} + +float TextShaper::getKerning(uint32 leftglyph, uint32 rightglyph) +{ + uint64 packedglyphs = ((uint64)leftglyph << 32) | (uint64)rightglyph; + + const auto it = kerning.find(packedglyphs); + if (it != kerning.end()) + return it->second; + + float k = 0.0f; + bool found = false; + + for (const auto &r : rasterizers) + { + if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) + { + found = true; + k = floorf(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f); + break; + } + } + + if (!found) + k = floorf(rasterizers[0]->getKerning(leftglyph, rightglyph) / rasterizers[0]->getDPIScale() + 0.5f); + + kerning[packedglyphs] = k; + return k; +} + +float TextShaper::getKerning(const std::string &leftchar, const std::string &rightchar) +{ + uint32 left = 0; + uint32 right = 0; + + try + { + left = utf8::peek_next(leftchar.begin(), leftchar.end()); + right = utf8::peek_next(rightchar.begin(), rightchar.end()); + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return getKerning(left, right); +} + +int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex) +{ + const auto it = glyphAdvances.find(glyph); + if (it != glyphAdvances.end()) + { + if (glyphindex) + *glyphindex = it->second.second; + return it->second.first; + } + + int rasterizeri = 0; + uint32 realglyph = glyph; + + if (glyph == '\t' && isUsingSpacesForTab()) + realglyph = ' '; + + for (size_t i = 0; i < rasterizers.size(); i++) + { + if (rasterizers[i]->hasGlyph(realglyph)) + { + rasterizeri = (int) i; + break; + } + } + + const auto &r = rasterizers[rasterizeri]; + int advance = floorf(r->getGlyphSpacing(realglyph) / r->getDPIScale() + 0.5f); + + if (glyph == '\t' && realglyph == ' ') + advance *= SPACES_PER_TAB; + + GlyphIndex glyphi = {r->getGlyphIndex(realglyph), rasterizeri}; + + glyphAdvances[glyph] = std::make_pair(advance, glyphi); + if (glyphindex) + *glyphindex = glyphi; + return advance; +} + +int TextShaper::getWidth(const std::string &str) +{ + if (str.size() == 0) return 0; + + ColoredCodepoints codepoints; + getCodepointsFromString(str, codepoints.cps); + + TextInfo info; + computeGlyphPositions(codepoints, Range(), Vector2(0.0f, 0.0f), 0.0f, nullptr, nullptr, &info); + + return info.width; +} + +static size_t findNewline(const ColoredCodepoints &codepoints, size_t start) +{ + for (size_t i = start; i < codepoints.cps.size(); i++) + { + if (codepoints.cps[i] == '\n') + { + return i; + } + } + + return codepoints.cps.size(); +} + +void TextShaper::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths) +{ + size_t nextnewline = findNewline(codepoints, 0); + + for (size_t i = 0; i < codepoints.cps.size();) + { + if (nextnewline < i) + nextnewline = findNewline(codepoints, i); + + if (nextnewline == i) // Empty line. + { + lineranges.push_back(Range()); + if (linewidths) + linewidths->push_back(0); + i++; + } + else + { + Range r(i, nextnewline - i); + float width = 0.0f; + int wrapindex = computeWordWrapIndex(codepoints, r, wraplimit, &width); + + if (wrapindex >= (int) i) + { + r = Range(i, (size_t) wrapindex + 1 - i); + i = (size_t)wrapindex + 1; + } + else + { + r = Range(); + i++; + } + + // We've already handled this line, skip the newline character. + if (nextnewline == i) + i++; + + lineranges.push_back(r); + if (linewidths) + linewidths->push_back(width); + } + } +} + +void TextShaper::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) +{ + ColoredCodepoints cps; + getCodepointsFromString(text, cps); + + std::vector codepointranges; + getWrap(cps, wraplimit, codepointranges, linewidths); + + std::string line; + + for (const auto &range : codepointranges) + { + line.clear(); + + if (range.isValid()) + { + line.reserve(range.getSize()); + + for (size_t i = range.getMin(); i <= range.getMax(); i++) + { + char character[5] = { '\0' }; + char *end = utf8::unchecked::append(cps.cps[i], character); + line.append(character, end - character); + } + } + + lines.push_back(line); + } +} + +void TextShaper::setFallbacks(const std::vector &fallbacks) +{ + for (Rasterizer *r : fallbacks) + { + if (r->getDataType() != rasterizers[0]->getDataType()) + throw love::Exception("Font fallbacks must be of the same font type."); + } + + // Clear caches. + kerning.clear(); + glyphAdvances.clear(); + + rasterizers.resize(1); + dpiScales.resize(1); + + for (Rasterizer *r : fallbacks) + { + rasterizers.push_back(r); + dpiScales.push_back(r->getDPIScale()); + } +} + +} // font +} // love diff --git a/src/modules/font/TextShaper.h b/src/modules/font/TextShaper.h new file mode 100644 index 000000000..b5417514c --- /dev/null +++ b/src/modules/font/TextShaper.h @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * 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. + **/ + +#pragma once + +// LOVE +#include "common/Object.h" +#include "common/Vector.h" +#include "common/int.h" +#include "common/Color.h" +#include "common/Range.h" + +#include +#include +#include + +namespace love +{ +namespace font +{ + +class Rasterizer; + +struct ColoredString +{ + std::string str; + Colorf color; +}; + +struct IndexedColor +{ + Colorf color; + int index; +}; + +struct ColoredCodepoints +{ + std::vector cps; + std::vector colors; +}; + +void getCodepointsFromString(const std::string &str, std::vector &codepoints); +void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints); + +class TextShaper : public Object +{ +public: + + struct GlyphIndex + { + int index; + int rasterizerIndex; + }; + + struct GlyphPosition + { + Vector2 position; + GlyphIndex glyphIndex; + }; + + struct TextInfo + { + int width; + int height; + }; + + // This will be used if the Rasterizer doesn't have a tab character itself. + static const int SPACES_PER_TAB = 4; + + static love::Type type; + + virtual ~TextShaper(); + + const std::vector> &getRasterizers() const { return rasterizers; } + bool isUsingSpacesForTab() const { return useSpacesForTab; } + + float getHeight() const; + + /** + * Sets the line height (which should be a number to multiply the font size by, + * example: line height = 1.2 and size = 12 means that rendered line height = 12*1.2) + * @param height The new line height. + **/ + void setLineHeight(float height); + + /** + * Returns the line height. + **/ + float getLineHeight() const; + + // Extra font metrics + int getAscent() const; + int getDescent() const; + float getBaseline() const; + + bool hasGlyph(uint32 glyph) const; + bool hasGlyphs(const std::string &text) const; + + float getKerning(uint32 leftglyph, uint32 rightglyph); + float getKerning(const std::string &leftchar, const std::string &rightchar); + + int getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex = nullptr); + + int getWidth(const std::string &str); + + void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths = nullptr); + void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths = nullptr); + + virtual void setFallbacks(const std::vector &fallbacks); + + virtual void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) = 0; + virtual int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) = 0; + +protected: + + TextShaper(Rasterizer *rasterizer); + + static inline bool isWhitespace(uint32 codepoint) { return codepoint == ' ' || codepoint == '\t'; } + + std::vector> rasterizers; + std::vector dpiScales; + +private: + + int height; + float lineHeight; + + bool useSpacesForTab; + + // maps glyphs to advance and glyph+rasterizer index. + std::unordered_map> glyphAdvances; + + // map of left/right glyph pairs to horizontal kerning. + std::unordered_map kerning; + +}; // TextShaper + +} // font +} // love diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp new file mode 100644 index 000000000..8b6ad01c5 --- /dev/null +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -0,0 +1,409 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * 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. + **/ + +// LOVE +#include "HarfbuzzShaper.h" +#include "TrueTypeRasterizer.h" +#include "common/Optional.h" + +// harfbuzz +#include +#include + +namespace love +{ +namespace font +{ +namespace freetype +{ + +HarfbuzzShaper::HarfbuzzShaper(TrueTypeRasterizer *rasterizer) + : TextShaper(rasterizer) + , spaceGlyphIndex() + , tabSpacesAdvanceX(0) + , tabSpacesAdvanceY(0) +{ + hbFonts.push_back(hb_ft_font_create_referenced((FT_Face)rasterizer->getHandle())); + hbBuffers.push_back(hb_buffer_create()); + + if (hbFonts[0] == nullptr || hbFonts[0] == hb_font_get_empty()) + throw love::Exception("Could not create Harfbuzz font object."); + + if (hbBuffers[0] == nullptr || hbBuffers[0] == hb_buffer_get_empty()) + throw love::Exception("Could not create Harfbuzz buffer object."); + + updateSpacesForTabInfo(); +} + +HarfbuzzShaper::~HarfbuzzShaper() +{ + for (hb_buffer_t *buffer : hbBuffers) + hb_buffer_destroy(buffer); + for (hb_font_t *font : hbFonts) + hb_font_destroy(font); +} + +void HarfbuzzShaper::setFallbacks(const std::vector &fallbacks) +{ + for (size_t i = 1; i < rasterizers.size(); i++) + { + hb_buffer_destroy(hbBuffers[i]); + hb_font_destroy(hbFonts[i]); + } + + TextShaper::setFallbacks(fallbacks); + + hbFonts.resize(rasterizers.size()); + hbBuffers.resize(rasterizers.size()); + + for (size_t i = 1; i < rasterizers.size(); i++) + { + hbFonts[i] = hb_ft_font_create_referenced((FT_Face)rasterizers[i]->getHandle()); + hbBuffers[i] = hb_buffer_create(); + } + + updateSpacesForTabInfo(); +} + +void HarfbuzzShaper::updateSpacesForTabInfo() +{ + if (!isUsingSpacesForTab()) + return; + + hb_codepoint_t glyphid = 0; + for (size_t i = 0; i < hbFonts.size(); i++) + { + hb_font_t *hbfont = hbFonts[i]; + if (hb_font_get_glyph(hbfont, ' ', 0, &glyphid)) + { + spaceGlyphIndex.index = glyphid; + spaceGlyphIndex.rasterizerIndex = i; + tabSpacesAdvanceX = hb_font_get_glyph_h_advance(hbfont, glyphid) * SPACES_PER_TAB; + tabSpacesAdvanceY = hb_font_get_glyph_v_advance(hbfont, glyphid) * SPACES_PER_TAB; + break; + } + } +} + +bool HarfbuzzShaper::isValidGlyph(uint32 glyphindex, const std::vector &codepoints, uint32 codepointindex) +{ + if (glyphindex != 0) + return true; + + uint32 codepoint = codepoints[codepointindex]; + if (codepoint == '\n' || codepoint == '\r' || (codepoint == '\t' && isUsingSpacesForTab())) + return true; + + return false; +} + +void HarfbuzzShaper::computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector &bufferranges) +{ + bufferranges.clear(); + + // Less computation for the typical case (no fallback fonts). + if (rasterizers.size() == 1) + { + hb_buffer_reset(hbBuffers[0]); + hb_buffer_add_codepoints(hbBuffers[0], codepoints.cps.data(), codepoints.cps.size(), (unsigned int)range.getOffset(), (int)range.getSize()); + + // TODO: Expose APIs for direction and script? + hb_buffer_guess_segment_properties(hbBuffers[0]); + + hb_shape(hbFonts[0], hbBuffers[0], nullptr, 0); + + bufferranges.push_back({0, (int) range.first, Range(0, hb_buffer_get_length(hbBuffers[0]))}); + return; + } + + std::vector fallbackranges = { range }; + + // For each font, figure out the ranges of valid glyphs in the given string, + // and add the rest to a list to be shaped by the next fallback font. + // Harfbuzz doesn't have its own fallback API. + for (size_t rasti = 0; rasti < rasterizers.size(); rasti++) + { + hb_buffer_t *hbb = hbBuffers[rasti]; + hb_buffer_reset(hbb); + + for (Range r : fallbackranges) + hb_buffer_add_codepoints(hbb, codepoints.cps.data(), codepoints.cps.size(), (unsigned int)r.getOffset(), (int)r.getSize()); + + hb_buffer_guess_segment_properties(hbb); + + hb_shape(hbFonts[rasti], hbb, nullptr, 0); + + int glyphcount = (int)hb_buffer_get_length(hbb); + const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbb, nullptr); + + fallbackranges.clear(); + + for (int i = 0; i < glyphcount; i++) + { + if (isValidGlyph(glyphinfos[i].codepoint, codepoints.cps, glyphinfos[i].cluster)) + { + if (bufferranges.empty() || bufferranges.back().index != rasti || bufferranges.back().range.getMax() != i) + bufferranges.push_back({(int)rasti, (int)glyphinfos[i].cluster, Range(i, 1)}); + else + bufferranges.back().range.last++; + } + else if (rasti == rasterizers.size() - 1) + { + // Use the first font for remaining invalid glyphs when no + // fallback font supports them. + if (bufferranges.empty() || bufferranges.back().index != 0 || bufferranges.back().range.getMax() != i) + bufferranges.push_back({0, (int)glyphinfos[i].cluster, Range(i, 1)}); + else + bufferranges.back().range.last++; + } + else + { + if (fallbackranges.empty() || fallbackranges.back().getMax() != glyphinfos[i - 1].cluster) + fallbackranges.push_back(Range(glyphinfos[i].cluster, 1)); + else + fallbackranges.back().encapsulate(glyphinfos[i].cluster); + } + } + } + + std::sort(bufferranges.begin(), bufferranges.end(), [](const BufferRange &a, const BufferRange &b) + { + if (a.codepointStart != b.codepointStart) + return a.codepointStart < b.codepointStart; + if (a.index != b.index) + return a.index < b.index; + return a.range.first < b.range.first; + }); +} + +void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + offset.y += getBaseline(); + Vector2 curpos = offset; + + int colorindex = 0; + int ncolors = (int)codepoints.colors.size(); + Optional colorToAdd; + + // Make sure the right color is applied to the start of the glyph list, + // when the start isn't 0. + if (colors && range.getOffset() > 0 && !codepoints.colors.empty()) + { + for (; colorindex < ncolors; colorindex++) + { + if (codepoints.colors[colorindex].index >= (int) range.getOffset()) + break; + colorToAdd.set(codepoints.colors[colorindex].color); + } + } + + std::vector bufferranges; + computeBufferRanges(codepoints, range, bufferranges); + + int maxwidth = (int)curpos.x; + + for (const auto &bufferrange : bufferranges) + { + if (positions) + positions->reserve(positions->size() + bufferrange.range.getSize()); + + hb_buffer_t *hbbuffer = hbBuffers[bufferrange.index]; + + const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); + hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); + hb_direction_t direction = hb_buffer_get_direction(hbbuffer); + + for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++) + { + const hb_glyph_info_t &info = glyphinfos[i]; + hb_glyph_position_t &glyphpos = glyphpositions[i]; + + // TODO: this doesn't handle situations where the user inserted a color + // change in the middle of some characters that get combined into a single + // cluster. + if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == info.cluster) + { + colorToAdd.set(codepoints.colors[colorindex].color); + colorindex++; + } + + uint32 clustercodepoint = codepoints.cps[info.cluster]; + + // Harfbuzz doesn't handle newlines itself, but it does leave them in + // the glyph list so we can do it manually. + if (clustercodepoint == '\n') + { + if (curpos.x > maxwidth) + maxwidth = (int)curpos.x; + + // Wrap newline, but do not output a position for it. + curpos.y += floorf(getHeight() * getLineHeight() + 0.5f); + curpos.x = offset.x; + continue; + } + + // Ignore carriage returns + if (clustercodepoint == '\r') + continue; + + // This is a glyph index at this point, despite the name. + GlyphIndex gindex = { (int) info.codepoint, bufferrange.index }; + + if (clustercodepoint == '\t' && isUsingSpacesForTab()) + { + gindex = spaceGlyphIndex; + + // This should be safe to overwrite. + // TODO: RTL support? + glyphpos.x_offset = 0; + glyphpos.y_offset = 0; + glyphpos.x_advance = HB_DIRECTION_IS_HORIZONTAL(direction) ? tabSpacesAdvanceX : 0; + glyphpos.y_advance = HB_DIRECTION_IS_VERTICAL(direction) ? tabSpacesAdvanceY : 0; + } + + if (colorToAdd.hasValue && colors && positions) + { + IndexedColor c = {colorToAdd.value, (int) positions->size()}; + colors->push_back(c); + colorToAdd.clear(); + } + + if (positions) + { + GlyphPosition p = { curpos, gindex }; + + // Harfbuzz position coordinate systems are based on the given font. + // Freetype uses 26.6 fixed point coordinates, so harfbuzz does too. + p.position.x += floorf((glyphpos.x_offset >> 6) / dpiScales[0] + 0.5f); + p.position.y += floorf((glyphpos.y_offset >> 6) / dpiScales[0] + 0.5f); + + positions->push_back(p); + } + + curpos.x += floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f); + curpos.y += floorf((glyphpos.y_advance >> 6) / dpiScales[0] + 0.5f); + + // Account for extra spacing given to space characters. + if (clustercodepoint == ' ' && extraspacing != 0.0f) + curpos.x = floorf(curpos.x + extraspacing); + } + } + + if (curpos.x > maxwidth) + maxwidth = (int)curpos.x; + + if (info != nullptr) + { + info->width = maxwidth - offset.x; + info->height = curpos.y - offset.y; + if (curpos.x > offset.x) + info->height += floorf(getHeight() * getLineHeight() + 0.5f); + } +} + +int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + float w = 0.0f; + float outwidth = 0.0f; + float widthbeforelastspace = 0.0f; + int wrapindex = -1; + int lastspaceindex = -1; + + uint32 prevcodepoint = 0; + + std::vector bufferranges; + computeBufferRanges(codepoints, range, bufferranges); + + for (const auto &bufferrange : bufferranges) + { + hb_buffer_t *hbbuffer = hbBuffers[bufferrange.index]; + + const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); + hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); + hb_direction_t direction = hb_buffer_get_direction(hbbuffer); + + for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++) + { + const hb_glyph_info_t &info = glyphinfos[i]; + hb_glyph_position_t &glyphpos = glyphpositions[i]; + + uint32 clustercodepoint = codepoints.cps[info.cluster]; + + if (clustercodepoint == '\r') + { + prevcodepoint = clustercodepoint; + continue; + } + + if (clustercodepoint == '\t' && isUsingSpacesForTab()) + { + // This should be safe to overwrite. + // TODO: RTL support? + glyphpos.x_offset = 0; + glyphpos.y_offset = 0; + glyphpos.x_advance = HB_DIRECTION_IS_HORIZONTAL(direction) ? tabSpacesAdvanceX : 0; + glyphpos.y_advance = HB_DIRECTION_IS_VERTICAL(direction) ? tabSpacesAdvanceY : 0; + } + + float newwidth = w + floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f); + + // Only wrap when there's a non-space character. + if (newwidth > wraplimit && !isWhitespace(clustercodepoint)) + { + // Rewind to the last seen space when wrapping. + if (lastspaceindex != -1) + { + wrapindex = lastspaceindex; + outwidth = widthbeforelastspace; + } + break; + } + + // Don't count trailing spaces in the output width. + if (isWhitespace(clustercodepoint)) + { + lastspaceindex = info.cluster; + if (!isWhitespace(prevcodepoint)) + widthbeforelastspace = w; + } + else + outwidth = newwidth; + + w = newwidth; + prevcodepoint = clustercodepoint; + wrapindex = info.cluster; + } + } + + if (width) + *width = outwidth; + + return wrapindex; +} + +} // freetype +} // font +} // love diff --git a/src/modules/font/freetype/HarfbuzzShaper.h b/src/modules/font/freetype/HarfbuzzShaper.h new file mode 100644 index 000000000..3a71a5b8b --- /dev/null +++ b/src/modules/font/freetype/HarfbuzzShaper.h @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * 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. + **/ + +#pragma once + +// LOVE +#include "font/TextShaper.h" + +extern "C" +{ +typedef struct hb_font_t hb_font_t; +typedef struct hb_buffer_t hb_buffer_t; +} + +namespace love +{ +namespace font +{ +namespace freetype +{ + +class TrueTypeRasterizer; + +class HarfbuzzShaper : public love::font::TextShaper +{ +public: + + HarfbuzzShaper(TrueTypeRasterizer *rasterizer); + virtual ~HarfbuzzShaper(); + + void setFallbacks(const std::vector &fallbacks) override; + void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) override; + int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override; + +private: + + struct BufferRange + { + int index; + int codepointStart; + Range range; + }; + + void updateSpacesForTabInfo(); + bool isValidGlyph(uint32 glyphindex, const std::vector &codepoints, uint32 codepointindex); + void computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector &bufferranges); + + std::vector hbFonts; + std::vector hbBuffers; + + GlyphIndex spaceGlyphIndex; + int tabSpacesAdvanceX; + int tabSpacesAdvanceY; + +}; // HarfbuzzShaper + +} // freetype +} // font +} // love diff --git a/src/modules/font/freetype/TrueTypeRasterizer.cpp b/src/modules/font/freetype/TrueTypeRasterizer.cpp index 64dae54bd..b48f63cb2 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.cpp +++ b/src/modules/font/freetype/TrueTypeRasterizer.cpp @@ -20,6 +20,7 @@ // LOVE #include "TrueTypeRasterizer.h" +#include "HarfbuzzShaper.h" #include "common/Exception.h" // C @@ -75,7 +76,30 @@ int TrueTypeRasterizer::getLineHeight() const return (int)(getHeight() * 1.25); } -GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const +int TrueTypeRasterizer::getGlyphSpacing(uint32 glyph) const +{ + FT_Glyph ftglyph; + FT_Error err = FT_Err_Ok; + FT_UInt loadoption = hintingToLoadOption(hinting); + + // Initialize + err = FT_Load_Glyph(face, FT_Get_Char_Index(face, glyph), FT_LOAD_DEFAULT | loadoption); + if (err != FT_Err_Ok) + return 0; + + err = FT_Get_Glyph(face->glyph, &ftglyph); + if (err != FT_Err_Ok) + return 0; + + return (int)(ftglyph->advance.x >> 16); +} + +int TrueTypeRasterizer::getGlyphIndex(uint32 glyph) const +{ + return FT_Get_Char_Index(face, glyph); +} + +GlyphData *TrueTypeRasterizer::getGlyphDataForIndex(int index) const { love::font::GlyphMetrics glyphMetrics = {}; FT_Glyph ftglyph; @@ -84,7 +108,7 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const FT_UInt loadoption = hintingToLoadOption(hinting); // Initialize - err = FT_Load_Glyph(face, FT_Get_Char_Index(face, glyph), FT_LOAD_DEFAULT | loadoption); + err = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT | loadoption); if (err != FT_Err_Ok) throw love::Exception("TrueType Font glyph error: FT_Load_Glyph failed (0x%x)", err); @@ -104,7 +128,7 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const throw love::Exception("TrueType Font glyph error: FT_Glyph_To_Bitmap failed (0x%x)", err); FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) ftglyph; - FT_Bitmap &bitmap = bitmap_glyph->bitmap; //just to make things easier + const FT_Bitmap &bitmap = bitmap_glyph->bitmap; //just to make things easier // Get metrics glyphMetrics.bearingX = bitmap_glyph->left; @@ -113,7 +137,8 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const glyphMetrics.width = bitmap.width; glyphMetrics.advance = (int) (ftglyph->advance.x >> 16); - GlyphData *glyphData = new GlyphData(glyph, glyphMetrics, PIXELFORMAT_LA8_UNORM); + // TODO: https://stackoverflow.com/questions/60526004/how-to-get-glyph-unicode-using-freetype/69730502#69730502 + GlyphData *glyphData = new GlyphData(0, glyphMetrics, PIXELFORMAT_LA8_UNORM); const uint8 *pixels = bitmap.buffer; uint8 *dest = (uint8 *) glyphData->getData(); @@ -186,6 +211,11 @@ Rasterizer::DataType TrueTypeRasterizer::getDataType() const return DATA_TRUETYPE; } +TextShaper *TrueTypeRasterizer::newTextShaper() +{ + return new HarfbuzzShaper(this); +} + bool TrueTypeRasterizer::accepts(FT_Library library, love::Data *data) { const FT_Byte *fbase = (const FT_Byte *) data->getData(); diff --git a/src/modules/font/freetype/TrueTypeRasterizer.h b/src/modules/font/freetype/TrueTypeRasterizer.h index 6bcdf552d..e97e91967 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.h +++ b/src/modules/font/freetype/TrueTypeRasterizer.h @@ -49,11 +49,16 @@ public: // Implement Rasterizer int getLineHeight() const override; - GlyphData *getGlyphData(uint32 glyph) const override; + int getGlyphSpacing(uint32 glyph) const override; + int getGlyphIndex(uint32 glyph) const override; + GlyphData *getGlyphDataForIndex(int index) const override; int getGlyphCount() const override; bool hasGlyph(uint32 glyph) const override; float getKerning(uint32 leftglyph, uint32 rightglyph) const override; DataType getDataType() const override; + TextShaper *newTextShaper() override; + + ptrdiff_t getHandle() const override { return (ptrdiff_t) face; } static bool accepts(FT_Library library, love::Data *data); diff --git a/src/modules/graphics/Deprecations.cpp b/src/modules/graphics/Deprecations.cpp index aacc20f78..92a5d51c5 100644 --- a/src/modules/graphics/Deprecations.cpp +++ b/src/modules/graphics/Deprecations.cpp @@ -90,7 +90,7 @@ void Deprecations::draw(Graphics *gfx) int maxcount = 4; int remaining = std::max(0, total - maxcount); - std::vector strings; + std::vector strings; Colorf white(1, 1, 1, 1); // Grab the newest deprecation notices first. diff --git a/src/modules/graphics/Font.cpp b/src/modules/graphics/Font.cpp index 03b06d46d..a4e3485b0 100644 --- a/src/modules/graphics/Font.cpp +++ b/src/modules/graphics/Font.cpp @@ -21,8 +21,6 @@ #include "Font.h" #include "font/GlyphData.h" -#include "libraries/utf8/utf8.h" - #include "common/math.h" #include "common/Matrix.h" #include "Graphics.h" @@ -42,20 +40,27 @@ static inline uint16 normToUint16(double n) return (uint16) (n * LOVE_UINT16_MAX); } +static inline uint64 packGlyphIndex(love::font::TextShaper::GlyphIndex glyphindex) +{ + return ((uint64)glyphindex.rasterizerIndex << 32) | (uint64)glyphindex.index; +} + +static inline love::font::TextShaper::GlyphIndex unpackGlyphIndex(uint64 packedindex) +{ + return {(int) (packedindex & 0xFFFFFFFF), (int) (packedindex >> 32)}; +} + love::Type Font::type("Font", &Object::type); int Font::fontCount = 0; const CommonFormat Font::vertexFormat = CommonFormat::XYf_STus_RGBAub; Font::Font(love::font::Rasterizer *r, const SamplerState &s) - : rasterizers({r}) - , height(r->getHeight()) - , lineHeight(1) + : shaper(r->newTextShaper(), Acquire::NORETAIN) , textureWidth(128) , textureHeight(128) , samplerState() , dpiScale(r->getDPIScale()) - , useSpacesAsTab(false) , textureCacheID(0) { samplerState.minFilter = s.minFilter; @@ -66,7 +71,7 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s) // largest texture size if no rough match is found. while (true) { - if ((height * 0.8) * height * 30 <= textureWidth * textureHeight) + if ((shaper->getHeight() * 0.8) * shaper->getHeight() * 30 <= textureWidth * textureHeight) break; TextureSize nextsize = getNextTextureSize(); @@ -86,9 +91,6 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s) if (pixelFormat == PIXELFORMAT_LA8_UNORM && !gfx->isPixelFormatSupported(pixelFormat, PIXELFORMATUSAGEFLAGS_SAMPLE)) pixelFormat = PIXELFORMAT_RGBA8_UNORM; - if (!r->hasGlyph(9)) // No tab character in the Rasterizer. - useSpacesAsTab = true; - loadVolatile(); ++fontCount; } @@ -170,7 +172,7 @@ void Font::createTexture() // and transparent black otherwise. std::vector emptydata(datasize, 0); - if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) + if (shaper->getRasterizers()[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) { if (pixelFormat == PIXELFORMAT_LA8_UNORM) { @@ -204,15 +206,15 @@ void Font::createTexture() { textureCacheID++; - std::vector glyphstoadd; + std::vector glyphstoadd; for (const auto &glyphpair : glyphs) - glyphstoadd.push_back(glyphpair.first); + glyphstoadd.push_back(unpackGlyphIndex(glyphpair.first)); glyphs.clear(); - for (uint32 g : glyphstoadd) - addGlyph(g); + for (auto glyphindex : glyphstoadd) + addGlyph(glyphindex); } } @@ -222,42 +224,17 @@ void Font::unloadVolatile() textures.clear(); } -love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph, float &dpiscale) +love::font::GlyphData *Font::getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale) { - // Use spaces for the tab 'glyph'. - if (glyph == 9 && useSpacesAsTab) - { - love::font::GlyphData *spacegd = rasterizers[0]->getGlyphData(32); - PixelFormat fmt = spacegd->getFormat(); - - love::font::GlyphMetrics gm = {}; - gm.advance = spacegd->getAdvance() * SPACES_PER_TAB; - gm.bearingX = spacegd->getBearingX(); - gm.bearingY = spacegd->getBearingY(); - - spacegd->release(); - - dpiscale = rasterizers[0]->getDPIScale(); - return new love::font::GlyphData(glyph, gm, fmt); - } - - for (const StrongRef &r : rasterizers) - { - if (r->hasGlyph(glyph)) - { - dpiscale = r->getDPIScale(); - return r->getGlyphData(glyph); - } - } - - dpiscale = rasterizers[0]->getDPIScale(); - return rasterizers[0]->getGlyphData(glyph); + const auto &r = shaper->getRasterizers()[glyphindex.rasterizerIndex]; + dpiscale = r->getDPIScale(); + return r->getGlyphDataForIndex(glyphindex.index); } -const Font::Glyph &Font::addGlyph(uint32 glyph) +const Font::Glyph &Font::addGlyph(love::font::TextShaper::GlyphIndex glyphindex) { float glyphdpiscale = getDPIScale(); - StrongRef gd(getRasterizerGlyphData(glyph, glyphdpiscale), Acquire::NORETAIN); + StrongRef gd(getRasterizerGlyphData(glyphindex, glyphdpiscale), Acquire::NORETAIN); int w = gd->getWidth(); int h = gd->getHeight(); @@ -279,15 +256,13 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) // Makes sure the above code for checking if the glyph can fit at // the current position in the texture is run again for this glyph. - return addGlyph(glyph); + return addGlyph(glyphindex); } } Glyph g; - g.texture = 0; - g.spacing = floorf(gd->getAdvance() / glyphdpiscale + 0.5f); - + g.texture = nullptr; memset(g.vertices, 0, sizeof(GlyphVertex) * 4); // Don't waste space for empty glyphs. @@ -357,151 +332,77 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) rowHeight = std::max(rowHeight, h + TEXTURE_PADDING); } - glyphs[glyph] = g; - return glyphs[glyph]; + uint64 packedindex = packGlyphIndex(glyphindex); + glyphs[packedindex] = g; + return glyphs[packedindex]; } -const Font::Glyph &Font::findGlyph(uint32 glyph) +const Font::Glyph &Font::findGlyph(love::font::TextShaper::GlyphIndex glyphindex) { - const auto it = glyphs.find(glyph); + uint64 packedindex = packGlyphIndex(glyphindex); + const auto it = glyphs.find(packedindex); if (it != glyphs.end()) return it->second; - return addGlyph(glyph); + return addGlyph(glyphindex); } float Font::getKerning(uint32 leftglyph, uint32 rightglyph) { - uint64 packedglyphs = ((uint64) leftglyph << 32) | (uint64) rightglyph; - - const auto it = kerning.find(packedglyphs); - if (it != kerning.end()) - return it->second; - - float k = floorf(rasterizers[0]->getKerning(leftglyph, rightglyph) / dpiScale + 0.5f); - - for (const auto &r : rasterizers) - { - if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) - { - k = floorf(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f); - break; - } - } - - kerning[packedglyphs] = k; - return k; + return shaper->getKerning(leftglyph, rightglyph); } float Font::getKerning(const std::string &leftchar, const std::string &rightchar) { - uint32 left = 0; - uint32 right = 0; - - try - { - left = utf8::peek_next(leftchar.begin(), leftchar.end()); - right = utf8::peek_next(rightchar.begin(), rightchar.end()); - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - return getKerning(left, right); -} - -void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints) -{ - codepoints.reserve(text.size()); - - try - { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::iterator end(text.end(), text.begin(), text.end()); - - while (i != end) - { - uint32 g = *i++; - codepoints.push_back(g); - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } -} - -void Font::getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints) -{ - if (strs.empty()) - return; - - codepoints.cps.reserve(strs[0].str.size()); - - for (const ColoredString &cstr : strs) - { - // No need to add the color if the string is empty anyway, and the code - // further on assumes no two colors share the same starting position. - if (cstr.str.size() == 0) - continue; - - IndexedColor c = {cstr.color, (int) codepoints.cps.size()}; - codepoints.colors.push_back(c); - - getCodepointsFromString(cstr.str, codepoints.cps); - } - - if (codepoints.colors.size() == 1) - { - IndexedColor c = codepoints.colors[0]; - - if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f)) - codepoints.colors.pop_back(); - } + return shaper->getKerning(leftchar, rightchar); } float Font::getHeight() const { - return (float) floorf(height / dpiScale + 0.5f); + return shaper->getHeight(); } -std::vector Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector2 offset, TextInfo *info) +std::vector Font::generateVertices(const love::font::ColoredCodepoints &codepoints, Range range, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector2 offset, love::font::TextShaper::TextInfo *info) { - // Spacing counter and newline handling. - float dx = offset.x; - float dy = offset.y; + std::vector glyphpositions; + std::vector colors; + shaper->computeGlyphPositions(codepoints, range, offset, extra_spacing, &glyphpositions, &colors, info); - float heightoffset = 0.0f; + size_t vertstartsize = vertices.size(); + vertices.reserve(vertstartsize + glyphpositions.size() * 4); - if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) - heightoffset = getBaseline(); + Colorf linearconstantcolor = gammaCorrectColor(constantcolor); + Color32 curcolor = toColor32(constantcolor); - int maxwidth = 0; + int curcolori = 0; + int ncolors = (int)colors.size(); // Keeps track of when we need to switch textures in our vertex array. std::vector commands; - // Pre-allocate space for the maximum possible number of vertices. - size_t vertstartsize = vertices.size(); - vertices.reserve(vertstartsize + codepoints.cps.size() * 4); - - uint32 prevglyph = 0; - - Colorf linearconstantcolor = gammaCorrectColor(constantcolor); - - Color32 curcolor = toColor32(constantcolor); - int curcolori = -1; - int ncolors = (int) codepoints.colors.size(); - - for (int i = 0; i < (int) codepoints.cps.size(); i++) + for (int i = 0; i < (int) glyphpositions.size(); i++) { - uint32 g = codepoints.cps[i]; + const auto &info = glyphpositions[i]; - if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i) + uint32 cacheid = textureCacheID; + + const Glyph &glyph = findGlyph(info.glyphIndex); + + // If findGlyph invalidates the texture cache, restart the loop. + if (cacheid != textureCacheID) { - Colorf c = codepoints.colors[++curcolori].color; + i = -1; // The next iteration will increment this to 0. + commands.clear(); + vertices.resize(vertstartsize); + curcolori = 0; + curcolor = toColor32(constantcolor); + continue; + } + + if (curcolori < ncolors && colors[curcolori].index == i) + { + Colorf c = colors[curcolori].color; c.r = std::min(std::max(c.r, 0.0f), 1.0f); c.g = std::min(std::max(c.g, 0.0f), 1.0f); @@ -513,54 +414,17 @@ std::vector Font::generateVertices(const ColoredCodepoints &c unGammaCorrectColor(c); curcolor = toColor32(c); + curcolori++; } - if (g == '\n') - { - if (dx > maxwidth) - maxwidth = (int) dx; - - // Wrap newline, but do not print it. - dy += floorf(getHeight() * getLineHeight() + 0.5f); - dx = offset.x; - prevglyph = 0; - continue; - } - - // Ignore carriage returns - if (g == '\r') - continue; - - uint32 cacheid = textureCacheID; - - const Glyph &glyph = findGlyph(g); - - // If findGlyph invalidates the texture cache, re-start the loop. - if (cacheid != textureCacheID) - { - i = -1; // The next iteration will increment this to 0. - maxwidth = 0; - dx = offset.x; - dy = offset.y; - commands.clear(); - vertices.resize(vertstartsize); - prevglyph = 0; - curcolori = -1; - curcolor = toColor32(constantcolor); - continue; - } - - // Add kerning to the current horizontal offset. - dx += getKerning(prevglyph, g); - if (glyph.texture != nullptr) { // Copy the vertices and set their colors and relative positions. for (int j = 0; j < 4; j++) { vertices.push_back(glyph.vertices[j]); - vertices.back().x += dx; - vertices.back().y += dy + heightoffset; + vertices.back().x += info.position.x; + vertices.back().y += info.position.y; vertices.back().color = curcolor; } @@ -569,7 +433,7 @@ std::vector Font::generateVertices(const ColoredCodepoints &c { // Add a new draw command if the texture has changed. DrawCommand cmd; - cmd.startvertex = (int) vertices.size() - 4; + cmd.startvertex = (int)vertices.size() - 4; cmd.vertexcount = 0; cmd.texture = glyph.texture; commands.push_back(cmd); @@ -577,15 +441,6 @@ std::vector Font::generateVertices(const ColoredCodepoints &c commands.back().vertexcount += 4; } - - // Advance the x position for the next glyph. - dx += glyph.spacing; - - // Account for extra spacing given to space characters. - if (g == ' ' && extra_spacing != 0.0f) - dx = floorf(dx + extra_spacing); - - prevglyph = g; } const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool @@ -599,19 +454,10 @@ std::vector Font::generateVertices(const ColoredCodepoints &c std::sort(commands.begin(), commands.end(), drawsort); - if (dx > maxwidth) - maxwidth = (int) dx; - - if (info != nullptr) - { - info->width = maxwidth - offset.x; - info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y; - } - return commands; } -std::vector Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector &vertices, TextInfo *info) +std::vector Font::generateVerticesFormatted(const love::font::ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector &vertices, love::font::TextShaper::TextInfo *info) { wrap = std::max(wrap, 0.0f); @@ -620,17 +466,22 @@ std::vector Font::generateVerticesFormatted(const ColoredCode std::vector drawcommands; vertices.reserve(text.cps.size() * 4); + std::vector ranges; std::vector widths; - std::vector lines; - - getWrap(text, wrap, lines, &widths); + shaper->getWrap(text, wrap, ranges, &widths); float y = 0.0f; float maxwidth = 0.0f; - for (int i = 0; i < (int) lines.size(); i++) + for (int i = 0; i < (int)ranges.size(); i++) { - const auto &line = lines[i]; + const auto& range = ranges[i]; + + if (!range.isValid()) + { + y += getHeight() * getLineHeight(); + continue; + } float width = (float) widths[i]; love::Vector2 offset(0.0f, floorf(y)); @@ -648,7 +499,9 @@ std::vector Font::generateVerticesFormatted(const ColoredCode break; case ALIGN_JUSTIFY: { - float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' '); + auto start = text.cps.begin() + range.getOffset(); + auto end = start + range.getSize(); + float numspaces = std::count(start, end, ' '); if (width < wrap && numspaces >= 1) extraspacing = (wrap - width) / numspaces; else @@ -660,7 +513,7 @@ std::vector Font::generateVerticesFormatted(const ColoredCode break; } - std::vector newcommands = generateVertices(line, constantcolor, vertices, extraspacing, offset); + std::vector newcommands = generateVertices(text, range, constantcolor, vertices, extraspacing, offset); if (!newcommands.empty()) { @@ -724,21 +577,21 @@ void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector &text, const Matrix4 &m, const Colorf &constantcolor) +void Font::print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantcolor) { - ColoredCodepoints codepoints; - getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); std::vector vertices; - std::vector drawcommands = generateVertices(codepoints, constantcolor, vertices); + std::vector drawcommands = generateVertices(codepoints, Range(), constantcolor, vertices); printv(gfx, m, drawcommands, vertices); } -void Font::printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor) +void Font::printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor) { - ColoredCodepoints codepoints; - getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); std::vector vertices; std::vector drawcommands = generateVerticesFormatted(codepoints, constantcolor, wrap, align, vertices); @@ -748,241 +601,32 @@ void Font::printf(graphics::Graphics *gfx, const std::vector &tex int Font::getWidth(const std::string &str) { - if (str.size() == 0) return 0; - - std::istringstream iss(str); - std::string line; - int max_width = 0; - - while (getline(iss, line, '\n')) - { - int width = 0; - uint32 prevglyph = 0; - try - { - utf8::iterator i(line.begin(), line.begin(), line.end()); - utf8::iterator end(line.end(), line.begin(), line.end()); - - while (i != end) - { - uint32 c = *i++; - - // Ignore carriage returns - if (c == '\r') - continue; - - const Glyph &g = findGlyph(c); - width += g.spacing + getKerning(prevglyph, c); - - prevglyph = c; - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - max_width = std::max(max_width, width); - } - - return max_width; + return shaper->getWidth(str); } int Font::getWidth(uint32 glyph) { - const Glyph &g = findGlyph(glyph); - return g.spacing; + return shaper->getGlyphAdvance(glyph); } -void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *linewidths) +void Font::getWrap(const love::font::ColoredCodepoints &codepoints, float wraplimit, std::vector &ranges, std::vector *linewidths) { - // Per-line info. - float width = 0.0f; - float widthbeforelastspace = 0.0f; - float widthoftrailingspace = 0.0f; - uint32 prevglyph = 0; - - int lastspaceindex = -1; - - // Keeping the indexed colors "in sync" is a bit tricky, since we split - // things up and we might skip some glyphs but we don't want to skip any - // color which starts at those indices. - Colorf curcolor(1.0f, 1.0f, 1.0f, 1.0f); - bool addcurcolor = false; - int curcolori = -1; - int endcolori = (int) codepoints.colors.size() - 1; - - // A wrapped line of text. - ColoredCodepoints wline; - - int i = 0; - while (i < (int) codepoints.cps.size()) - { - uint32 c = codepoints.cps[i]; - - // Determine the current color before doing anything else, to make sure - // it's still applied to future glyphs even if this one is skipped. - if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i) - { - curcolor = codepoints.colors[curcolori + 1].color; - curcolori++; - addcurcolor = true; - } - - // Split text at newlines. - if (c == '\n') - { - lines.push_back(wline); - - // Ignore the width of any trailing spaces, for individual lines. - if (linewidths) - linewidths->push_back(width - widthoftrailingspace); - - // Make sure the new line keeps any color that was set previously. - addcurcolor = true; - - width = widthbeforelastspace = widthoftrailingspace = 0.0f; - prevglyph = 0; // Reset kerning information. - lastspaceindex = -1; - wline.cps.clear(); - wline.colors.clear(); - i++; - - continue; - } - - // Ignore carriage returns - if (c == '\r') - { - i++; - continue; - } - - const Glyph &g = findGlyph(c); - float charwidth = g.spacing + getKerning(prevglyph, c); - float newwidth = width + charwidth; - - // Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're - // processing a newline character, though. - if (c != ' ' && newwidth > wraplimit) - { - // If this is the first character in the line and it exceeds the - // limit, skip it completely. - if (wline.cps.empty()) - i++; - else if (lastspaceindex != -1) - { - // 'Rewind' to the last seen space, if the line has one. - // FIXME: This could be more efficient... - while (!wline.cps.empty() && wline.cps.back() != ' ') - wline.cps.pop_back(); - - while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size()) - wline.colors.pop_back(); - - // Also 'rewind' to the color that the last character is using. - for (int colori = curcolori; colori >= 0; colori--) - { - if (codepoints.colors[colori].index <= lastspaceindex) - { - curcolor = codepoints.colors[colori].color; - curcolori = colori; - break; - } - } - - // Ignore the width of trailing spaces in wrapped lines. - width = widthbeforelastspace; - - i = lastspaceindex; - i++; // Start the next line after the space. - } - - lines.push_back(wline); - - if (linewidths) - linewidths->push_back(width); - - addcurcolor = true; - - prevglyph = 0; - width = widthbeforelastspace = widthoftrailingspace = 0.0f; - wline.cps.clear(); - wline.colors.clear(); - lastspaceindex = -1; - - continue; - } - - if (prevglyph != ' ' && c == ' ') - widthbeforelastspace = width; - - width = newwidth; - prevglyph = c; - - if (addcurcolor) - { - wline.colors.push_back({curcolor, (int) wline.cps.size()}); - addcurcolor = false; - } - - wline.cps.push_back(c); - - // Keep track of the last seen space, so we can "rewind" to it when - // wrapping. - if (c == ' ') - { - lastspaceindex = i; - widthoftrailingspace += charwidth; - } - else if (c != '\n') - widthoftrailingspace = 0.0f; - - i++; - } - - // Push the last line. - lines.push_back(wline); - - // Ignore the width of any trailing spaces, for individual lines. - if (linewidths) - linewidths->push_back(width - widthoftrailingspace); + shaper->getWrap(codepoints, wraplimit, ranges, linewidths); } -void Font::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) +void Font::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) { - ColoredCodepoints cps; - getCodepointsFromString(text, cps); - - std::vector codepointlines; - getWrap(cps, wraplimit, codepointlines, linewidths); - - std::string line; - - for (const ColoredCodepoints &codepoints : codepointlines) - { - line.clear(); - line.reserve(codepoints.cps.size()); - - for (uint32 codepoint : codepoints.cps) - { - char character[5] = {'\0'}; - char *end = utf8::unchecked::append(codepoint, character); - line.append(character, end - character); - } - - lines.push_back(line); - } + shaper->getWrap(text, wraplimit, lines, linewidths); } void Font::setLineHeight(float height) { - lineHeight = height; + shaper->setLineHeight(height); } float Font::getLineHeight() const { - return lineHeight; + return shaper->getLineHeight(); } void Font::setSamplerState(const SamplerState &s) @@ -1002,75 +646,44 @@ const SamplerState &Font::getSamplerState() const int Font::getAscent() const { - return floorf(rasterizers[0]->getAscent() / dpiScale + 0.5f); + return shaper->getAscent(); } int Font::getDescent() const { - return floorf(rasterizers[0]->getDescent() / dpiScale + 0.5f); + return shaper->getDescent(); } float Font::getBaseline() const { - float ascent = getAscent(); - if (ascent != 0.0f) - return ascent; - else if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) - return floorf(getHeight() / 1.25f + 0.5f); // 1.25 is magic line height for true type fonts - else - return 0.0f; + return shaper->getBaseline(); } bool Font::hasGlyph(uint32 glyph) const { - for (const StrongRef &r : rasterizers) - { - if (r->hasGlyph(glyph)) - return true; - } - - return false; + return shaper->hasGlyph(glyph); } bool Font::hasGlyphs(const std::string &text) const { - if (text.size() == 0) - return false; - - try - { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::iterator end(text.end(), text.begin(), text.end()); - - while (i != end) - { - uint32 codepoint = *i++; - - if (!hasGlyph(codepoint)) - return false; - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - return true; + return shaper->hasGlyphs(text); } void Font::setFallbacks(const std::vector &fallbacks) { - for (const Font *f : fallbacks) - { - if (f->rasterizers[0]->getDataType() != this->rasterizers[0]->getDataType()) - throw love::Exception("Font fallbacks must be of the same font type."); - } + std::vector rasterizerfallbacks; + for (const Font* f : fallbacks) + rasterizerfallbacks.push_back(f->shaper->getRasterizers()[0]); - rasterizers.resize(1); + shaper->setFallbacks(rasterizerfallbacks); - // NOTE: this won't invalidate already-rasterized glyphs. - for (const Font *f : fallbacks) - rasterizers.push_back(f->rasterizers[0]); + // Invalidate existing textures. + textureCacheID++; + glyphs.clear(); + while (textures.size() > 1) + textures.pop_back(); + + rowHeight = textureX = textureY = TEXTURE_PADDING; } float Font::getDPIScale() const diff --git a/src/modules/graphics/Font.h b/src/modules/graphics/Font.h index 90d9bb920..873fcc696 100644 --- a/src/modules/graphics/Font.h +++ b/src/modules/graphics/Font.h @@ -33,6 +33,7 @@ #include "common/Vector.h" #include "font/Rasterizer.h" +#include "font/TextShaper.h" #include "Texture.h" #include "vertex.h" #include "Volatile.h" @@ -64,30 +65,6 @@ public: ALIGN_MAX_ENUM }; - struct ColoredString - { - std::string str; - Colorf color; - }; - - struct IndexedColor - { - Colorf color; - int index; - }; - - struct ColoredCodepoints - { - std::vector cps; - std::vector colors; - }; - - struct TextInfo - { - int width; - int height; - }; - // Used to determine when to change textures in the generated vertex array. struct DrawCommand { @@ -100,20 +77,17 @@ public: virtual ~Font(); - std::vector generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantColor, std::vector &vertices, - float extra_spacing = 0.0f, Vector2 offset = {}, TextInfo *info = nullptr); + std::vector generateVertices(const love::font::ColoredCodepoints &codepoints, Range range, const Colorf &constantColor, std::vector &vertices, + float extra_spacing = 0.0f, Vector2 offset = {}, love::font::TextShaper::TextInfo *info = nullptr); - std::vector generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align, - std::vector &vertices, TextInfo *info = nullptr); - - static void getCodepointsFromString(const std::string &str, Codepoints &codepoints); - static void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints); + std::vector generateVerticesFormatted(const love::font::ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align, + std::vector &vertices, love::font::TextShaper::TextInfo *info = nullptr); /** * Draws the specified text. **/ - void print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantColor); - void printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor); + void print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantColor); + void printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor); /** * Returns the height of the font. @@ -141,8 +115,8 @@ public: * @param max_width Optional output of the maximum width * Returns a vector with the lines. **/ - void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); - void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); + void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); + void getWrap(const love::font::ColoredCodepoints &codepoints, float wraplimit, std::vector &ranges, std::vector *line_widths = nullptr); /** * Sets the line height (which should be a number to multiply the font size by, @@ -191,7 +165,6 @@ private: struct Glyph { Texture *texture; - int spacing; GlyphVertex vertices[4]; }; @@ -204,26 +177,20 @@ private: void createTexture(); TextureSize getNextTextureSize() const; - love::font::GlyphData *getRasterizerGlyphData(uint32 glyph, float &dpiscale); - const Glyph &addGlyph(uint32 glyph); - const Glyph &findGlyph(uint32 glyph); + love::font::GlyphData *getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale); + const Glyph &addGlyph(love::font::TextShaper::GlyphIndex glyphindex); + const Glyph &findGlyph(love::font::TextShaper::GlyphIndex glyphindex); void printv(Graphics *gfx, const Matrix4 &t, const std::vector &drawcommands, const std::vector &vertices); - std::vector> rasterizers; - - int height; - float lineHeight; + StrongRef shaper; int textureWidth; int textureHeight; - std::vector> textures; + std::vector> textures; - // maps glyphs to glyph texture information - std::unordered_map glyphs; - - // map of left/right glyph pairs to horizontal kerning. - std::unordered_map kerning; + // maps packed glyph index values to glyph texture information + std::unordered_map glyphs; PixelFormat pixelFormat; @@ -234,8 +201,6 @@ private: int textureX, textureY; int rowHeight; - bool useSpacesAsTab; - // ID which is incremented when the texture cache is invalidated. uint32 textureCacheID; @@ -244,9 +209,6 @@ private: // use, for edge antialiasing. static const int TEXTURE_PADDING = 2; - // This will be used if the Rasterizer doesn't have a tab character itself. - static const int SPACES_PER_TAB = 4; - static StringMap::Entry alignModeEntries[]; static StringMap alignModes; diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 4be1b2351..85733e0be 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -439,7 +439,7 @@ Mesh *Graphics::newMesh(const std::vector &attributes, Pr return new Mesh(attributes, drawmode); } -love::graphics::TextBatch *Graphics::newTextBatch(graphics::Font *font, const std::vector &text) +love::graphics::TextBatch *Graphics::newTextBatch(graphics::Font *font, const std::vector &text) { return new TextBatch(font, text); } @@ -963,10 +963,16 @@ void Graphics::setRenderTargets(const RenderTargets &rts) resetProjection(); - // Invalidate temporary depth/stencil. This could be a clear, but if the - // user also clears a double-clear may be slow... + // Clear/reset the temporary depth/stencil buffers. + // TODO: make this deferred somehow to avoid double clearing if the user + // also calls love.graphics.clear after setCanvas. if (rts.depthStencil.texture == nullptr && rts.temporaryRTFlags != 0) - discard({}, true); + { + OptionalColorD clearcolor; + OptionalInt clearstencil(0); + OptionalDouble cleardepth(1.0); + clear(clearcolor, clearstencil, cleardepth); + } } void Graphics::setRenderTarget() @@ -1893,7 +1899,7 @@ void Graphics::drawShaderVertices(Buffer *indexbuffer, int indexcount, int insta draw(cmd); } -void Graphics::print(const std::vector &str, const Matrix4 &m) +void Graphics::print(const std::vector &str, const Matrix4 &m) { checkSetDefaultFont(); @@ -1901,12 +1907,12 @@ void Graphics::print(const std::vector &str, const Matrix4 print(str, states.back().font.get(), m); } -void Graphics::print(const std::vector &str, Font *font, const Matrix4 &m) +void Graphics::print(const std::vector &str, Font *font, const Matrix4 &m) { font->print(this, str, m, states.back().color); } -void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) +void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) { checkSetDefaultFont(); @@ -1914,7 +1920,7 @@ void Graphics::printf(const std::vector &str, float wrap, F printf(str, states.back().font.get(), wrap, align, m); } -void Graphics::printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m) +void Graphics::printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m) { font->printf(this, str, wrap, align, m, states.back().color); } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 1e11aab0a..328665ac9 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -460,7 +460,7 @@ public: Mesh *newMesh(const std::vector &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage); Mesh *newMesh(const std::vector &attributes, PrimitiveType drawmode); - TextBatch *newTextBatch(Font *font, const std::vector &text = {}); + TextBatch *newTextBatch(Font *font, const std::vector &text = {}); data::ByteData *readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset); GraphicsReadback *readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset); @@ -702,14 +702,14 @@ public: /** * Draws text at the specified coordinates **/ - void print(const std::vector &str, const Matrix4 &m); - void print(const std::vector &str, Font *font, const Matrix4 &m); + void print(const std::vector &str, const Matrix4 &m); + void print(const std::vector &str, Font *font, const Matrix4 &m); /** * Draws formatted text on screen at the specified coordinates. **/ - void printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m); - void printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m); + void printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m); + void printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m); /** * Draws a series of points at the specified positions. diff --git a/src/modules/graphics/Polyline.cpp b/src/modules/graphics/Polyline.cpp index b80cb78e2..686b0ae9d 100644 --- a/src/modules/graphics/Polyline.cpp +++ b/src/modules/graphics/Polyline.cpp @@ -49,26 +49,26 @@ void Polyline::render(const Vector2 *coords, size_t count, size_t size_hint, flo // compute sleeve bool is_looping = (coords[0] == coords[count - 1]); - Vector2 s; + Vector2 segment; if (!is_looping) // virtual starting point at second point mirrored on first point - s = coords[1] - coords[0]; + segment = coords[1] - coords[0]; else // virtual starting point at last vertex - s = coords[0] - coords[count - 2]; + segment = coords[0] - coords[count - 2]; - float len_s = s.getLength(); - Vector2 ns = s.getNormal(halfwidth / len_s); + float segmentLength = segment.getLength(); + Vector2 segmentNormal = segment.getNormal(halfwidth / segmentLength); - Vector2 q, r(coords[0]); + Vector2 pointA, pointB(coords[0]); for (size_t i = 0; i + 1 < count; i++) { - q = r; - r = coords[i + 1]; - renderEdge(anchors, normals, s, len_s, ns, q, r, halfwidth); + pointA = pointB; + pointB = coords[i + 1]; + renderEdge(anchors, normals, segment, segmentLength, segmentNormal, pointA, pointB, halfwidth); } - q = r; - r = is_looping ? coords[1] : r + s; - renderEdge(anchors, normals, s, len_s, ns, q, r, halfwidth); + pointA = pointB; + pointB = is_looping ? coords[1] : pointB + segment; + renderEdge(anchors, normals, segment, segmentLength, segmentNormal, pointA, pointB, halfwidth); vertex_count = normals.size(); @@ -108,8 +108,8 @@ void Polyline::render(const Vector2 *coords, size_t count, size_t size_hint, flo } void NoneJoinPolyline::renderEdge(std::vector &anchors, std::vector &normals, - Vector2 &s, float &len_s, Vector2 &ns, - const Vector2 &q, const Vector2 &r, float hw) + Vector2 &segment, float &segmentLength, Vector2 &segmentNormal, + const Vector2 &pointA, const Vector2 &pointB, float halfWidth) { // ns1------ns2 // | | @@ -117,19 +117,19 @@ void NoneJoinPolyline::renderEdge(std::vector &anchors, std::vector &anchors, std::vector &anchors, std::vector &normals, - Vector2 &s, float &len_s, Vector2 &ns, - const Vector2 &q, const Vector2 &r, float hw) + Vector2 &segment, float &segmentLength, Vector2 &segmentNormal, + const Vector2 &pointA, const Vector2 &pointB, float halfwidth) { - Vector2 t = (r - q); - float len_t = t.getLength(); - if (len_t == 0.0f) + Vector2 newSegment = (pointB - pointA); + float newSegmentLength = newSegment.getLength(); + if (newSegmentLength == 0.0f) { // degenerate segment, skip it return; } - Vector2 nt = t.getNormal(hw / len_t); + Vector2 newSegmentNormal = newSegment.getNormal(halfwidth / newSegmentLength); - anchors.push_back(q); - anchors.push_back(q); + anchors.push_back(pointA); + anchors.push_back(pointA); - float det = Vector2::cross(s, t); - if (fabs(det) / (len_s * len_t) < LINES_PARALLEL_EPS && Vector2::dot(s, t) > 0) + float det = Vector2::cross(segment, newSegment); + if (fabs(det) / (segmentLength * newSegmentLength) < LINES_PARALLEL_EPS && Vector2::dot(segment, newSegment) > 0) { // lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2 - normals.push_back(ns); - normals.push_back(-ns); + normals.push_back(segmentNormal); + normals.push_back(-segmentNormal); } else { // cramers rule - float lambda = Vector2::cross((nt - ns), t) / det; - Vector2 d = ns + s * lambda; + float lambda = Vector2::cross((newSegmentNormal - segmentNormal), newSegment) / det; + Vector2 d = segmentNormal + segment * lambda; normals.push_back(d); normals.push_back(-d); } - s = t; - ns = nt; - len_s = len_t; + segment = newSegment; + segmentNormal = newSegmentNormal; + segmentLength = newSegmentLength; } /** Calculate line boundary points. @@ -226,52 +226,53 @@ void MiterJoinPolyline::renderEdge(std::vector &anchors, std::vector &anchors, std::vector &normals, - Vector2 &s, float &len_s, Vector2 &ns, - const Vector2 &q, const Vector2 &r, float hw) + Vector2 &segment, float &segmentLength, Vector2 &segmentNormal, + const Vector2 &pointA, const Vector2 &pointB, float halfWidth) { - Vector2 t = (r - q); - float len_t = t.getLength(); + Vector2 newSegment = (pointB - pointA); + float newSegmentLength = newSegment.getLength(); - float det = Vector2::cross(s, t); - if (fabs(det) / (len_s * len_t) < LINES_PARALLEL_EPS && Vector2::dot(s, t) > 0) + float det = Vector2::cross(segment, newSegment); + if (fabs(det) / (segmentLength * newSegmentLength) < LINES_PARALLEL_EPS && Vector2::dot(segment, newSegment) > 0) { // lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2 - Vector2 n = t.getNormal(hw / len_t); - anchors.push_back(q); - anchors.push_back(q); - normals.push_back(n); - normals.push_back(-n); - s = t; - len_s = len_t; + Vector2 newSegmentNormal = newSegment.getNormal(halfWidth / newSegmentLength); + anchors.push_back(pointA); + anchors.push_back(pointA); + normals.push_back(newSegmentNormal); + normals.push_back(-newSegmentNormal); + segment = newSegment; + segmentLength = newSegmentLength; + segmentNormal = newSegmentNormal; return; // early out } // cramers rule - Vector2 nt = t.getNormal(hw / len_t); - float lambda = Vector2::cross((nt - ns), t) / det; - Vector2 d = ns + s * lambda; + Vector2 newSegmentNormal = newSegment.getNormal(halfWidth / newSegmentLength); + float lambda = Vector2::cross((newSegmentNormal - segmentNormal), newSegment) / det; + Vector2 d = segmentNormal + segment * lambda; - anchors.push_back(q); - anchors.push_back(q); - anchors.push_back(q); - anchors.push_back(q); + anchors.push_back(pointA); + anchors.push_back(pointA); + anchors.push_back(pointA); + anchors.push_back(pointA); if (det > 0) // 'left' turn -> intersection on the top { normals.push_back(d); - normals.push_back(-ns); + normals.push_back(-segmentNormal); normals.push_back(d); - normals.push_back(-nt); + normals.push_back(-newSegmentNormal); } else { - normals.push_back(ns); + normals.push_back(segmentNormal); normals.push_back(-d); - normals.push_back(nt); + normals.push_back(newSegmentNormal); normals.push_back(-d); } - s = t; - len_s = len_t; - ns = nt; + segment = newSegment; + segmentLength = newSegmentLength; + segmentNormal = newSegmentNormal; } void Polyline::calc_overdraw_vertex_count(bool is_looping) diff --git a/src/modules/graphics/Polyline.h b/src/modules/graphics/Polyline.h index 93e8f2d16..7685efd12 100644 --- a/src/modules/graphics/Polyline.h +++ b/src/modules/graphics/Polyline.h @@ -77,18 +77,18 @@ protected: /** Calculate line boundary points. * - * @param[out] anchors Anchor points defining the core line. - * @param[out] normals Normals defining the edge of the sleeve. - * @param[in,out] s Direction of segment pq (updated to the segment qr). - * @param[in,out] len_s Length of segment pq (updated to the segment qr). - * @param[in,out] ns Normal on the segment pq (updated to the segment qr). - * @param[in] q Current point on the line. - * @param[in] r Next point on the line. - * @param[in] hw Half line width (see Polyline.render()). + * @param[out] anchors Anchor points defining the core line. + * @param[out] normals Normals defining the edge of the sleeve. + * @param[in,out] segment Direction of segment pq (updated to the segment qr). + * @param[in,out] segmentLength Length of segment pq (updated to the segment qr). + * @param[in,out] segmentNormal Normal on the segment pq (updated to the segment qr). + * @param[in] pointA Current point on the line (q). + * @param[in] pointB Next point on the line (r). + * @param[in] halfWidth Half line width (see Polyline.render()). */ virtual void renderEdge(std::vector &anchors, std::vector &normals, - Vector2 &s, float &len_s, Vector2 &ns, - const Vector2 &q, const Vector2 &r, float hw) = 0; + Vector2 &segment, float &segmentLength, Vector2 &segmentNormal, + const Vector2 &pointA, const Vector2 &pointB, float halfWidth) = 0; Vector2 *vertices; Vector2 *overdraw; diff --git a/src/modules/graphics/TextBatch.cpp b/src/modules/graphics/TextBatch.cpp index 4c9e810a4..797e68bb1 100644 --- a/src/modules/graphics/TextBatch.cpp +++ b/src/modules/graphics/TextBatch.cpp @@ -30,7 +30,7 @@ namespace graphics love::Type TextBatch::type("TextBatch", &Drawable::type); -TextBatch::TextBatch(Font *font, const std::vector &text) +TextBatch::TextBatch(Font *font, const std::vector &text) : font(font) , vertexAttributes(Font::vertexFormat, 0) , vertexData(nullptr) @@ -112,13 +112,13 @@ void TextBatch::addTextData(const TextData &t) std::vector vertices; std::vector newcommands; - Font::TextInfo textinfo; + love::font::TextShaper::TextInfo textinfo; Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f); // We only have formatted text if the align mode is valid. if (t.align == Font::ALIGN_MAX_ENUM) - newcommands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo); + newcommands = font->generateVertices(t.codepoints, Range(), constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo); else newcommands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &textinfo); @@ -172,31 +172,31 @@ void TextBatch::addTextData(const TextData &t) regenerateVertices(); } -void TextBatch::set(const std::vector &text) +void TextBatch::set(const std::vector &text) { return set(text, -1.0f, Font::ALIGN_MAX_ENUM); } -void TextBatch::set(const std::vector &text, float wrap, Font::AlignMode align) +void TextBatch::set(const std::vector &text, float wrap, Font::AlignMode align) { if (text.empty() || (text.size() == 1 && text[0].str.empty())) return clear(); - Font::ColoredCodepoints codepoints; - Font::getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); addTextData({codepoints, wrap, align, {}, false, false, Matrix4()}); } -int TextBatch::add(const std::vector &text, const Matrix4 &m) +int TextBatch::add(const std::vector &text, const Matrix4 &m) { return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m); } -int TextBatch::addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m) +int TextBatch::addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m) { - Font::ColoredCodepoints codepoints; - Font::getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); addTextData({codepoints, wrap, align, {}, true, true, m}); diff --git a/src/modules/graphics/TextBatch.h b/src/modules/graphics/TextBatch.h index 7284af9d5..82763fc27 100644 --- a/src/modules/graphics/TextBatch.h +++ b/src/modules/graphics/TextBatch.h @@ -40,14 +40,14 @@ public: static love::Type type; - TextBatch(Font *font, const std::vector &text = {}); + TextBatch(Font *font, const std::vector &text = {}); virtual ~TextBatch(); - void set(const std::vector &text); - void set(const std::vector &text, float wrap, Font::AlignMode align); + void set(const std::vector &text); + void set(const std::vector &text, float wrap, Font::AlignMode align); - int add(const std::vector &text, const Matrix4 &m); - int addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m); + int add(const std::vector &text, const Matrix4 &m); + int addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m); void clear(); @@ -71,10 +71,10 @@ private: struct TextData { - Font::ColoredCodepoints codepoints; + love::font::ColoredCodepoints codepoints; float wrap; Font::AlignMode align; - Font::TextInfo textInfo; + love::font::TextShaper::TextInfo textInfo; bool useMatrix; bool appendVertices; Matrix4 matrix; diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index 8ca3e7601..9b7f30a04 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -207,7 +207,7 @@ private: void initCapabilities() override; void getAPIStats(int &shaderswitches) const override; - void endPass(); + void endPass(bool presenting); id getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil); void applyRenderState(id renderEncoder, const VertexAttributes &attributes); diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 916fc1b06..acbbe3d3b 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -983,7 +983,7 @@ bool Graphics::applyShaderUniforms(id encoder, love::g { Shader *s = (Shader *)shader; -#ifdef LOVE_MACOS +#if defined(LOVE_MACOS) || TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST size_t alignment = 256; #else size_t alignment = 16; @@ -1053,7 +1053,7 @@ void Graphics::applyShaderUniforms(id renderEncoder, lo { Shader *s = (Shader *)shader; -#ifdef LOVE_MACOS +#if defined(LOVE_MACOS) || TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST size_t alignment = 256; #else size_t alignment = 16; @@ -1358,7 +1358,7 @@ bool Graphics::dispatch(int x, int y, int z) void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int /*pixelw*/, int /*pixelh*/, bool /*hasSRGBtexture*/) { @autoreleasepool { - endPass(); + endPass(false); bool isbackbuffer = rts.getFirstTarget().texture == nullptr; @@ -1410,7 +1410,7 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int /*pixelw*/ dirtyRenderState = STATEBIT_ALL; }} -void Graphics::endPass() +void Graphics::endPass(bool presenting) { // Make sure the encoder gets set up, if nothing else has done it yet. useRenderEncoder(); @@ -1421,9 +1421,9 @@ void Graphics::endPass() love::graphics::Texture *depthstencil = rts.depthStencil.texture.get(); // Discard the depth/stencil buffer if we're using an internal cached one, - // or if this is the backbuffer. + // or if we're presenting the backbuffer to the display. if ((depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0) - || !rts.getFirstTarget().texture.get()) + || (presenting && !rts.getFirstTarget().texture.get())) { attachmentStoreActions.depth = MTLStoreActionDontCare; attachmentStoreActions.stencil = MTLStoreActionDontCare; @@ -1562,7 +1562,7 @@ void Graphics::present(void *screenshotCallbackData) // endPass calls useRenderEncoder, which makes sure activeDrawable is set // when possible. - endPass(); + endPass(true); id screenshotbuffer = nil; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 0090bfcf1..b37bcdca2 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -758,7 +758,7 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int pixelw, in OpenGL::TempDebugGroup debuggroup("setRenderTargets"); - endPass(); + endPass(false); bool iswindow = rts.getFirstTarget().texture == nullptr; Winding vertexwinding = state.winding; @@ -794,16 +794,18 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int pixelw, in } } -void Graphics::endPass() +void Graphics::endPass(bool presenting) { auto &rts = states.back().renderTargets; love::graphics::Texture *depthstencil = rts.depthStencil.texture.get(); - // Discard the depth/stencil buffer if we're using an internal cached one. - if (depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0) + // Discard the depth/stencil buffer if we're using an internal cached one, + // or if we're presenting the backbuffer to the display. + if ((depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0) + || (presenting && !rts.getFirstTarget().texture.get())) + { discard({}, true); - else if (!rts.getFirstTarget().texture.get()) - discard({}, true); // Backbuffer + } // Resolve MSAA buffers. MSAA is only supported for 2D render targets so we // don't have to worry about resolving to slices. @@ -1224,7 +1226,8 @@ void Graphics::present(void *screenshotCallbackData) deprecations.draw(this); flushBatchedDraws(); - endPass(); + + endPass(true); int w = getPixelWidth(); int h = getPixelHeight(); diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index cf9567bcb..f22137dad 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -149,7 +149,7 @@ private: void initCapabilities() override; void getAPIStats(int &shaderswitches) const override; - void endPass(); + void endPass(bool presenting); GLuint bindCachedFBO(const RenderTargets &targets); void discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index bc9ed8611..3bd3ed0d8 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -155,7 +155,7 @@ bool OpenGL::initContext() if (getVendor() == VENDOR_AMD) { bugs.clearRequiresDriverTextureStateUpdate = true; - if (!gl.isCoreProfile()) + if (!gl.isCoreProfile() && !GLAD_ES_VERSION_2_0) bugs.generateMipmapsRequiresTexture2DEnable = true; } #endif diff --git a/src/modules/graphics/wrap_Font.cpp b/src/modules/graphics/wrap_Font.cpp index 9814f0482..807feefbd 100644 --- a/src/modules/graphics/wrap_Font.cpp +++ b/src/modules/graphics/wrap_Font.cpp @@ -30,9 +30,9 @@ namespace love namespace graphics { -void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings) +void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings) { - Font::ColoredString coloredstr; + love::font::ColoredString coloredstr; coloredstr.color = Colorf(1.0f, 1.0f, 1.0f, 1.0f); if (lua_istable(L, idx)) @@ -103,7 +103,7 @@ int w_Font_getWrap(lua_State *L) { Font *t = luax_checkfont(L, 1); - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); float wrap = (float) luaL_checknumber(L, 3); diff --git a/src/modules/graphics/wrap_Font.h b/src/modules/graphics/wrap_Font.h index 39635d92b..69ff068d6 100644 --- a/src/modules/graphics/wrap_Font.h +++ b/src/modules/graphics/wrap_Font.h @@ -30,7 +30,7 @@ namespace graphics { Font *luax_checkfont(lua_State *L, int idx); -void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings); +void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings); extern "C" int luaopen_font(lua_State *L); } // graphics diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 176335352..d50c19d24 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -2112,7 +2112,7 @@ int w_newTextBatch(lua_State *L) luax_catchexcept(L, [&](){ t = instance()->newTextBatch(font); }); else { - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); luax_catchexcept(L, [&](){ t = instance()->newTextBatch(font, text); }); @@ -3132,7 +3132,7 @@ int w_drawShaderVertices(lua_State *L) int w_print(lua_State *L) { - std::vector str; + std::vector str; luax_checkcoloredstring(L, 1, str); if (luax_istype(L, 2, Font::type)) @@ -3157,7 +3157,7 @@ int w_print(lua_State *L) int w_printf(lua_State *L) { - std::vector str; + std::vector str; luax_checkcoloredstring(L, 1, str); Font *font = nullptr; diff --git a/src/modules/graphics/wrap_TextBatch.cpp b/src/modules/graphics/wrap_TextBatch.cpp index c69cb0650..6a21da769 100644 --- a/src/modules/graphics/wrap_TextBatch.cpp +++ b/src/modules/graphics/wrap_TextBatch.cpp @@ -36,7 +36,7 @@ int w_TextBatch_set(lua_State *L) { TextBatch *t = luax_checktextbatch(L, 1); - std::vector newtext; + std::vector newtext; luax_checkcoloredstring(L, 2, newtext); luax_catchexcept(L, [&](){ t->set(newtext); }); @@ -54,7 +54,7 @@ int w_TextBatch_setf(lua_State *L) if (!Font::getConstant(alignstr, align)) return luax_enumerror(L, "align mode", Font::getConstants(align), alignstr); - std::vector newtext; + std::vector newtext; luax_checkcoloredstring(L, 2, newtext); luax_catchexcept(L, [&](){ t->set(newtext, wraplimit, align); }); @@ -68,7 +68,7 @@ int w_TextBatch_add(lua_State *L) int index = 0; - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); if (luax_istype(L, 3, math::Transform::type)) @@ -102,7 +102,7 @@ int w_TextBatch_addf(lua_State *L) int index = 0; - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); float wrap = (float) luaL_checknumber(L, 3); diff --git a/src/modules/love/callbacks.lua b/src/modules/love/callbacks.lua index 080b63796..85541d397 100644 --- a/src/modules/love/callbacks.lua +++ b/src/modules/love/callbacks.lua @@ -238,8 +238,7 @@ function love.errhand(msg) if love.audio then love.audio.stop() end love.graphics.reset() - local font = love.graphics.newFont(14) - love.graphics.setFont(font) + love.graphics.setFont(love.graphics.newFont(15)) love.graphics.setColor(1, 1, 1) diff --git a/src/modules/physics/box2d/WheelJoint.cpp b/src/modules/physics/box2d/WheelJoint.cpp index 833c600de..cc42e4224 100644 --- a/src/modules/physics/box2d/WheelJoint.cpp +++ b/src/modules/physics/box2d/WheelJoint.cpp @@ -90,9 +90,10 @@ float WheelJoint::getMaxMotorTorque() const return Physics::scaleUp(Physics::scaleUp(joint->GetMaxMotorTorque())); } -float WheelJoint::getMotorTorque(float inv_dt) const +float WheelJoint::getMotorTorque(float dt) const { - return Physics::scaleUp(Physics::scaleUp(joint->GetMotorTorque(inv_dt))); + float invdt = 1.0f / dt; + return Physics::scaleUp(Physics::scaleUp(joint->GetMotorTorque(invdt))); } void WheelJoint::setStiffness(float k) diff --git a/src/modules/physics/box2d/WheelJoint.h b/src/modules/physics/box2d/WheelJoint.h index dc1696e75..83182a1bf 100644 --- a/src/modules/physics/box2d/WheelJoint.h +++ b/src/modules/physics/box2d/WheelJoint.h @@ -91,9 +91,9 @@ public: /** * Get the current motor torque, usually in N. - * @param inv_dt The inverse time step. + * @param dt The time step. **/ - float getMotorTorque(float inv_dt) const; + float getMotorTorque(float dt) const; /** * Sets the response speed. Dependent of mass diff --git a/src/modules/physics/box2d/wrap_WheelJoint.cpp b/src/modules/physics/box2d/wrap_WheelJoint.cpp index f796b5f12..fa116833e 100644 --- a/src/modules/physics/box2d/wrap_WheelJoint.cpp +++ b/src/modules/physics/box2d/wrap_WheelJoint.cpp @@ -97,8 +97,8 @@ int w_WheelJoint_getMaxMotorTorque(lua_State *L) int w_WheelJoint_getMotorTorque(lua_State *L) { WheelJoint *t = luax_checkwheeljoint(L, 1); - float inv_dt = (float)luaL_checknumber(L, 2); - lua_pushnumber(L, t->getMotorTorque(inv_dt)); + float dt = (float)luaL_checknumber(L, 2); + lua_pushnumber(L, t->getMotorTorque(dt)); return 1; } diff --git a/src/modules/sound/lullaby/MP3Decoder.cpp b/src/modules/sound/lullaby/MP3Decoder.cpp index f058de2ed..7557cdbb9 100644 --- a/src/modules/sound/lullaby/MP3Decoder.cpp +++ b/src/modules/sound/lullaby/MP3Decoder.cpp @@ -144,7 +144,7 @@ MP3Decoder::MP3Decoder(Stream *stream, int bufferSize) throw love::Exception("Could not find first valid mp3 header."); // initialize mp3 handle - if (!drmp3_init(&mp3, onRead, onSeek, this, nullptr, nullptr)) + if (!drmp3_init(&mp3, onRead, onSeek, this, nullptr)) throw love::Exception("Could not read mp3 data."); sampleRate = mp3.sampleRate;