diff --git a/.github/workflows/Nintendo 3DS.yml b/.github/workflows/Nintendo 3DS.yml index 880f5e5f7..b70820b2c 100644 --- a/.github/workflows/Nintendo 3DS.yml +++ b/.github/workflows/Nintendo 3DS.yml @@ -14,11 +14,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Configure - run: /opt/devkitpro/portlibs/3ds/bin/arm-none-eabi-cmake -S . -B build - - name: Build - run: make -C build + run: catnip -T 3DS - id: commit uses: prompt/actions-commit-hash@v3 @@ -27,8 +24,8 @@ jobs: with: name: Nintendo 3DS-${{ steps.commit.outputs.short }} path: | - build/*.elf - build/*.3dsx + build/**/*.elf + build/**/*.3dsx N3DS-Debug: runs-on: ubuntu-latest @@ -41,11 +38,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Configure - run: /opt/devkitpro/portlibs/3ds/bin/arm-none-eabi-cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build - - name: Build - run: make -C build + run: catnip -T 3DS debug - id: commit uses: prompt/actions-commit-hash@v3 @@ -54,5 +48,5 @@ jobs: with: name: Nintendo 3DS (Debug)-${{ steps.commit.outputs.short }} path: | - build/*.elf - build/*.3dsx + build/**/*.elf + build/**/*.3dsx diff --git a/.github/workflows/Nintendo Switch.yml b/.github/workflows/Nintendo Switch.yml index 153ca84b3..0bbd90dc3 100644 --- a/.github/workflows/Nintendo Switch.yml +++ b/.github/workflows/Nintendo Switch.yml @@ -14,11 +14,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Configure - run: /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -S . -B build - - name: Build - run: make -C build + run: catnip -T Switch - id: commit uses: prompt/actions-commit-hash@v3 @@ -27,8 +24,8 @@ jobs: with: name: Nintendo Switch-${{ steps.commit.outputs.short }} path: | - build/*.elf - build/*.nro + build/**/*.elf + build/**/*.nro Switch-Debug: runs-on: ubuntu-latest @@ -41,11 +38,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Configure - run: /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build - - name: Build - run: make -C build + run: catnip -T Switch debug - id: commit uses: prompt/actions-commit-hash@v3 @@ -54,5 +48,5 @@ jobs: with: name: Nintendo Switch (Debug)-${{ steps.commit.outputs.short }} path: | - build/*.elf - build/*.nro + build/**/*.elf + build/**/*.nro diff --git a/.github/workflows/Nintendo Wii U.yml b/.github/workflows/Nintendo Wii U.yml index d6741b52b..40167de11 100644 --- a/.github/workflows/Nintendo Wii U.yml +++ b/.github/workflows/Nintendo Wii U.yml @@ -14,11 +14,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Configure - run: /opt/devkitpro/portlibs/wiiu/bin/powerpc-eabi-cmake -S . -B build - - name: Build - run: make -C build + run: catnip -T WiiU - id: commit uses: prompt/actions-commit-hash@v3 @@ -27,8 +24,8 @@ jobs: with: name: Nintendo Wii U-${{ steps.commit.outputs.short }} path: | - build/*.elf - build/*.wuhb + build/**/*.elf + build/**/*.wuhb WiiU-Debug: runs-on: ubuntu-latest @@ -41,11 +38,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Configure - run: /opt/devkitpro/portlibs/wiiu/bin/powerpc-eabi-cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build - - name: Build - run: make -C build + run: catnip -T WiiU debug - id: commit uses: prompt/actions-commit-hash@v3 @@ -54,5 +48,5 @@ jobs: with: name: Nintendo Wii U (Debug)-${{ steps.commit.outputs.short }} path: | - build/*.elf - build/*.wuhb + build/**/*.elf + build/**/*.wuhb diff --git a/.gitignore b/.gitignore index e250eac9e..bd4ddc857 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ target/ *.3dsx *.nro *.wuhb +*.py +__pycache__/ +todo.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 947b7a022..63307b085 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,8 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE set(APP_TITLE "LÖVE Potion") set(APP_AUTHOR "LÖVEBrew Team") +file(COPY libraries/luasocket DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(APP_TITLE "LÖVE Potion (${COMMIT_HASH})") target_compile_definitions(${PROJECT_NAME} PRIVATE __DEBUG__=1) @@ -47,7 +49,6 @@ target_compile_options(${PROJECT_NAME} PRIVATE $<$:-fexceptions -fno-rtti> ) - if(NINTENDO_3DS) add_subdirectory(platform/ctr) @@ -76,6 +77,12 @@ if(NINTENDO_3DS) set(APP_LIBS citro3d) set(OUTPUT_FILENAME "${PROJECT_NAME}.3dsx") + + target_sources(${PROJECT_NAME} PRIVATE + source/modules/image/magpie/T3XHandler.cpp + ) + + execute_process(COMMAND patch -d ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket -N -i ${PROJECT_SOURCE_DIR}/platform/ctr/libraries/luasocket.patch) endif() if (NINTENDO_SWITCH) @@ -108,6 +115,29 @@ if (NINTENDO_SWITCH) ) set(OUTPUT_FILENAME "${PROJECT_NAME}.nro") + + target_sources(${PROJECT_NAME} PRIVATE + source/modules/image/magpie/ASTCHandler.cpp + source/modules/image/magpie/ddsHandler.cpp + source/modules/image/magpie/JPGHandler.cpp + source/modules/image/magpie/KTXHandler.cpp + source/modules/image/magpie/PKMHandler.cpp + source/modules/image/magpie/PNGHandler.cpp + source/modules/font/freetype/Font.cpp + source/modules/font/freetype/TrueTypeRasterizer.cpp + source/modules/graphics/freetype/Font.cpp + ) + + add_library(ddsparse + libraries/ddsparse/ddsparse.cpp + libraries/ddsparse/ddsparse.h + libraries/ddsparse/ddsinfo.h + ) + + find_package(Freetype REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE Freetype::Freetype bz2 png turbojpeg ddsparse) + + execute_process(COMMAND patch -d ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket -N -i ${PROJECT_SOURCE_DIR}/platform/hac/libraries/luasocket.patch) endif() if (NINTENDO_WIIU) @@ -135,6 +165,29 @@ if (NINTENDO_WIIU) ) set(OUTPUT_FILENAME "${PROJECT_NAME}.wuhb") + + target_sources(${PROJECT_NAME} PRIVATE + source/modules/image/magpie/ASTCHandler.cpp + source/modules/image/magpie/ddsHandler.cpp + source/modules/image/magpie/JPGHandler.cpp + source/modules/image/magpie/KTXHandler.cpp + source/modules/image/magpie/PKMHandler.cpp + source/modules/image/magpie/PNGHandler.cpp + source/modules/font/freetype/Font.cpp + source/modules/font/freetype/TrueTypeRasterizer.cpp + source/modules/graphics/freetype/Font.cpp + ) + + add_library(ddsparse + libraries/ddsparse/ddsparse.cpp + libraries/ddsparse/ddsparse.h + libraries/ddsparse/ddsinfo.h + ) + + find_package(Freetype REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE Freetype::Freetype bz2 png turbojpeg ddsparse) + + execute_process(COMMAND patch -d ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket -N -i ${PROJECT_SOURCE_DIR}/platform/cafe/libraries/luasocket.patch) endif() add_custom_target(test @@ -171,6 +224,47 @@ add_library(lua53 ) target_link_libraries(lua53 PRIVATE PkgConfig::lua51) +add_library(luasocket + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/luasocket.hpp + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/luasocket.cpp + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/auxiliar.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/auxiliar.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/buffer.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/buffer.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/compat.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/compat.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/except.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/except.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/inet.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/inet.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/io.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/io.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/ltn12.lua + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/luasocket.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/luasocket.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/mime.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/mime.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/options.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/options.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/pierror.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/select.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/select.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/smtp.lua + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/socket.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/tcp.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/tcp.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/timeout.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/timeout.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/udp.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/udp.h + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/usocket.c + ${CMAKE_CURRENT_BINARY_DIR}/luasocket/libluasocket/usocket.h +) +target_link_libraries(luasocket PRIVATE PkgConfig::lua51) +target_include_directories(luasocket PRIVATE + libraries/lua53 +) + FetchContent_Declare(lua-https GIT_REPOSITORY https://github.com/love2d/lua-https GIT_TAG main @@ -282,7 +376,7 @@ endif() # link everything else target_link_libraries(${PROJECT_NAME} PRIVATE - ${APP_LIBS} z luabit lua53 ogg modplug lua-https + ${APP_LIBS} z luabit lua53 ogg modplug lua-https luasocket ) @@ -292,25 +386,30 @@ include_directories( libraries/dr libraries/lua53 libraries/noise1234 + libraries/luasocket libraries/physfs libraries/wuff libraries/utf8 + libraries/ddsparse ) # find source -type f -name \*.cpp | clip target_sources(${PROJECT_NAME} PRIVATE +source/main.cpp source/common/b64.cpp source/common/Data.cpp +source/common/float.cpp source/common/luax.cpp source/common/Matrix.cpp source/common/Message.cpp source/common/Module.cpp source/common/Object.cpp source/common/pixelformat.cpp +source/common/Reference.cpp source/common/Stream.cpp source/common/types.cpp source/common/Variant.cpp -source/main.cpp +source/utility/guid.cpp source/modules/audio/Audio.cpp source/modules/audio/Pool.cpp source/modules/audio/Source.cpp @@ -321,8 +420,6 @@ source/modules/data/CompressedData.cpp source/modules/data/DataModule.cpp source/modules/data/DataStream.cpp source/modules/data/DataView.cpp -source/modules/data/misc/Compressor.cpp -source/modules/data/misc/HashFunction.cpp source/modules/data/wrap_ByteData.cpp source/modules/data/wrap_CompressedData.cpp source/modules/data/wrap_Data.cpp @@ -331,14 +428,45 @@ source/modules/data/wrap_DataView.cpp source/modules/event/Event.cpp source/modules/event/wrap_Event.cpp source/modules/filesystem/FileData.cpp -source/modules/filesystem/physfs/File.cpp -source/modules/filesystem/physfs/Filesystem.cpp source/modules/filesystem/wrap_File.cpp source/modules/filesystem/wrap_FileData.cpp source/modules/filesystem/wrap_Filesystem.cpp +source/modules/font/Font.cpp +source/modules/font/GlyphData.cpp +source/modules/font/Rasterizer.cpp +source/modules/font/TextShaper.cpp +source/modules/font/GenericShaper.cpp +source/modules/font/wrap_Font.cpp +source/modules/font/wrap_Rasterizer.cpp +source/modules/font/wrap_GlyphData.cpp +source/modules/graphics/Buffer.cpp +source/modules/graphics/Graphics.cpp +source/modules/graphics/FontBase.cpp +source/modules/graphics/Mesh.cpp +source/modules/graphics/Polyline.cpp +source/modules/graphics/wrap_Mesh.cpp +source/modules/graphics/TextBatch.cpp source/modules/graphics/renderstate.cpp +source/modules/graphics/samplerstate.cpp +source/modules/graphics/Shader.cpp +source/modules/graphics/Quad.cpp source/modules/graphics/Volatile.cpp +source/modules/graphics/vertex.cpp source/modules/graphics/wrap_Graphics.cpp +source/modules/graphics/wrap_Texture.cpp +source/modules/graphics/wrap_TextBatch.cpp +source/modules/graphics/wrap_Font.cpp +source/modules/graphics/wrap_Quad.cpp +source/modules/graphics/Texture.cpp +source/modules/image/CompressedImageData.cpp +source/modules/image/CompressedSlice.cpp +source/modules/image/FormatHandler.cpp +source/modules/image/Image.cpp +source/modules/image/ImageData.cpp +source/modules/image/ImageDataBase.cpp +source/modules/image/wrap_CompressedImageData.cpp +source/modules/image/wrap_Image.cpp +source/modules/image/wrap_ImageData.cpp source/modules/joystick/JoystickModule.cpp source/modules/joystick/wrap_Joystick.cpp source/modules/joystick/wrap_JoystickModule.cpp @@ -355,11 +483,6 @@ source/modules/math/wrap_Transform.cpp source/modules/sensor/Sensor.cpp source/modules/sensor/wrap_Sensor.cpp source/modules/sound/Decoder.cpp -source/modules/sound/lullaby/FLACDecoder.cpp -source/modules/sound/lullaby/ModPlugDecoder.cpp -source/modules/sound/lullaby/MP3Decoder.cpp -source/modules/sound/lullaby/VorbisDecoder.cpp -source/modules/sound/lullaby/WaveDecoder.cpp source/modules/sound/Sound.cpp source/modules/sound/SoundData.cpp source/modules/sound/wrap_Decoder.cpp @@ -378,5 +501,13 @@ source/modules/timer/wrap_Timer.cpp source/modules/touch/Touch.cpp source/modules/touch/wrap_Touch.cpp source/modules/window/wrap_Window.cpp -source/utility/guid.cpp +source/modules/data/misc/Compressor.cpp +source/modules/data/misc/HashFunction.cpp +source/modules/filesystem/physfs/File.cpp +source/modules/filesystem/physfs/Filesystem.cpp +source/modules/sound/lullaby/FLACDecoder.cpp +source/modules/sound/lullaby/ModPlugDecoder.cpp +source/modules/sound/lullaby/MP3Decoder.cpp +source/modules/sound/lullaby/VorbisDecoder.cpp +source/modules/sound/lullaby/WaveDecoder.cpp ) diff --git a/debug/debug.py b/debug/debug.py deleted file mode 100644 index 9524dbf7a..000000000 --- a/debug/debug.py +++ /dev/null @@ -1,75 +0,0 @@ -import sys -import argparse -import time -import os - -from io import TextIOWrapper -from socket import gaierror, socket, AF_INET, SOCK_STREAM -from pathlib import Path - - -def clear_console() -> None: - match os.name: - case "nt": - os.system("cls") - case _: - os.system("clear") - - -def main() -> None: - parser = argparse.ArgumentParser() - - parser.add_argument("host", help="the host to connect to") - parser.add_argument( - "-l", "--log", action="store_true", help="write data to log file" - ) - - args = parser.parse_args() - - clear_console() - - tcp_socket = socket(AF_INET, SOCK_STREAM, 0) - - try: - tcp_socket.connect((args.host, 8000)) - except (ConnectionRefusedError, TimeoutError): - print(f"Failed to connect to {args.host}:8000") - sys.exit(1) - except gaierror: - print(f"Invalid host: {args.host}") - sys.exit(1) - - log_file: TextIOWrapper = None - - if args.log: - Path("logs").mkdir(exist_ok=True) - timestamp = str(int(time.time())) - log_file = open(f"logs/{timestamp}.txt", "w") - - while True: - try: - raw_data = tcp_socket.recv(0x400) - - if not len(raw_data): - break - - data = raw_data.decode("utf-8") - - if args.log: - log_file.write(data) - - print(data, end="") - except ConnectionResetError: - print("Connection reset by peer") - break - except KeyboardInterrupt: - break - - tcp_socket.close() - - if args.log: - log_file.close() - - -if __name__ == "__main__": - main() diff --git a/debug/meta.py b/debug/meta.py deleted file mode 100644 index dd9dc76ad..000000000 --- a/debug/meta.py +++ /dev/null @@ -1,56 +0,0 @@ -import argparse -import re - -from pathlib import Path - - -def read_file(file_path) -> Path | None: - try: - return Path(file_path).read_text(encoding="utf-8") - except FileNotFoundError: - print(f"File '{file_path}' not found.") - - return None - - -MATCH_STRING = r"(love\.\w+\.\w+)\(\)\s+==>\s+\w+\s+-\s+(\d+\.\d+)" -TIME_REGEX = re.compile(MATCH_STRING, re.MULTILINE) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Read a file.") - parser.add_argument("file", type=str, help="Path to the file") - parser.add_argument( - "--top", "-t", action="store_true", help="Get top 3 slowest methods" - ) - - args = parser.parse_args() - - content = read_file(args.file) - - if not content: - print("No content found in the file.") - return - - times = re.findall(TIME_REGEX, content) - - if not times: - print("No times found in the file.") - return - - sorted_times = sorted(times, key=lambda x: float(x[1][:-1]), reverse=True) - - if args.top: - for time in sorted_times[:3]: - method, total_time = time - print(f"- {method}: {total_time}s") - - return - - for time in sorted_times: - method, total_time = time - print(f"- {method}: {total_time}s") - - -if __name__ == "__main__": - main() diff --git a/include/common/Color.hpp b/include/common/Color.hpp index fbd6f7279..07b1da36f 100644 --- a/include/common/Color.hpp +++ b/include/common/Color.hpp @@ -92,7 +92,50 @@ namespace love float r, g, b, a; + /* + ** For tex3ds-based textures + ** @param data: data from the tex3ds texture + ** @param width: power-of-two width of the data + ** @param position: Vector2 coordinate inside the image ([0-width-1], [0-height-1]) + */ + template + static T* fromTile(const void* data, const unsigned width, int x, int y) + { + return ((T*)data) + indexOfTile(width, x, y); + } + + /* + ** For tex3ds-based textures + ** @param texture: C3D_Tex* holding texture data + ** @param position: Vector2 coordinate inside the image ([0-width-1], [0-height-1]) + */ + template + static V* fromTile(const T* texture, int x, int y) + { + return Color::fromTile(texture->data, texture->width, x, y); + } + private: + static unsigned indexOfTile(const unsigned width, const unsigned x, const unsigned y) + { + const auto tileX = x / 8; + const auto tileY = y / 8; + + const auto subX = x % 8; + const auto subY = y % 8; + + if ((subY * 8 + subX) > coordsTable.size()) + throw love::Exception("Out of bounds tile position: {:d}x{:d}", subX, subY); + + return ((width / 8) * tileY + tileX) * 64 + coordsTable[subY * 8 + subX]; + } + + static const inline std::array coordsTable = { + 0, 1, 4, 5, 16, 17, 20, 21, 2, 3, 6, 7, 18, 19, 22, 23, 8, 9, 12, 13, 24, 25, + 28, 29, 10, 11, 14, 15, 26, 27, 30, 31, 32, 33, 36, 37, 48, 49, 52, 53, 34, 35, 38, 39, + 50, 51, 54, 55, 40, 41, 44, 45, 56, 57, 60, 61, 42, 43, 46, 47, 58, 59, 62, 63 + }; + static uint8_t as_uint8_t(const float& in) { return (255.0f * std::clamp(in, 0.0f, 1.0f) + 0.5f); @@ -115,4 +158,16 @@ namespace love uint8_t r, g, b, a; }; + + static bool gammaCorrect = false; + + inline void setGammaCorrect(bool enable) + { + gammaCorrect = enable; + } + + inline bool isGammaCorrect() + { + return gammaCorrect; + } } // namespace love diff --git a/include/common/Exception.hpp b/include/common/Exception.hpp index 7b526535e..ba931409f 100644 --- a/include/common/Exception.hpp +++ b/include/common/Exception.hpp @@ -27,4 +27,23 @@ namespace love private: std::string message; }; + +#if __DEBUG__ + #include + + // Macro to log to both stdout and a debug.log file + #define LOG(format, ...) \ + do \ + { \ + std::FILE* outFile = std::fopen("debug.log", "a"); \ + static const char* data = "%s %s:%d: " format "\n"; \ + \ + if (outFile) \ + { \ + std::fprintf(outFile, data, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + std::fclose(outFile); \ + } \ + std::printf(data, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#endif } // namespace love diff --git a/include/common/Map.hpp b/include/common/Map.hpp index be97fa8c2..66f56cf2c 100644 --- a/include/common/Map.hpp +++ b/include/common/Map.hpp @@ -101,43 +101,6 @@ class MapT return false; } - /** - * @brief Get the keys of the map - * @return std::span containing the keys - */ - constexpr std::span getKeys() const - { - return std::ranges::ref_view(this->items) | keys | std::views::take(N); - } - - /** - * @brief Get the names of the map (only available if K is std::string_view) - * @return std::span containing the keys - */ - constexpr std::span getNames() const - requires(std::is_same_v) - { - return this->getKeys(); - } - - /** - * @brief Get the values of the map - * @return std::span containing the values - */ - constexpr std::span getValues() const - { - return std::ranges::ref_view(this->items) | values | std::views::take(N); - } - - /** - * @brief Get the items of the map - * @return std::span containing the items - */ - constexpr std::span getItems() const - { - return this->items; - } - /** * Get the expected values of the map * @param type The type of the map (e.g. "Color") diff --git a/include/common/Matrix.hpp b/include/common/Matrix.hpp index 766b5f259..e4d419896 100644 --- a/include/common/Matrix.hpp +++ b/include/common/Matrix.hpp @@ -66,8 +66,7 @@ namespace love /** * Creates a new matrix set to a transformation. **/ - Matrix4(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, - float ky); + Matrix4(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); /** * Multiplies this Matrix with another Matrix, changing neither. @@ -93,6 +92,9 @@ namespace love void setColumn(int c, const Vector4& v); Vector4 getColumn(int c) const; + + float get(int row, int column) const; + /** * Resets this Matrix to the identity matrix. **/ @@ -160,8 +162,8 @@ namespace love * @param kx Shear along x-axis * @param ky Shear along y-axis **/ - void setTransformation(float x, float y, float angle, float sx, float sy, float ox, - float oy, float kx, float ky); + void setTransformation(float x, float y, float angle, float sx, float sy, float ox, float oy, + float kx, float ky); /** * Multiplies this Matrix with a translation. @@ -226,8 +228,7 @@ namespace love /** * Creates a new orthographic projection matrix. **/ - static Matrix4 ortho(float left, float right, float bottom, float top, float near, - float far); + static Matrix4 ortho(float left, float right, float bottom, float top, float near, float far); /** * Creates a new perspective projection matrix. @@ -258,8 +259,7 @@ namespace love /** * Creates a new matrix set to a transformation. **/ - Matrix3(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, - float ky); + Matrix3(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); ~Matrix3(); @@ -295,8 +295,8 @@ namespace love * @param kx Shear along x-axis * @param ky Shear along y-axis **/ - void setTransformation(float x, float y, float angle, float sx, float sy, float ox, - float oy, float kx, float ky); + void setTransformation(float x, float y, float angle, float sx, float sy, float ox, float oy, + float kx, float ky); /** * Transforms an array of vertices by this matrix. diff --git a/include/common/Reference.hpp b/include/common/Reference.hpp new file mode 100644 index 000000000..7d6267cf9 --- /dev/null +++ b/include/common/Reference.hpp @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2006-2024 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. + **/ + +#ifndef LOVE_REFERENCE_H +#define LOVE_REFERENCE_H + +struct lua_State; + +namespace love +{ + + /** + * This class wraps the reference functionality built into + * Lua, which allows C++ code to refer to Lua variables. + **/ + class Reference + { + public: + /** + * Creates the reference object, but does not create + * the actual reference. + **/ + Reference(); + + /** + * Creates the object and a reference to the value + * on the top of the stack. + **/ + Reference(lua_State* L); + + /** + * Deletes the reference, if any. + **/ + virtual ~Reference(); + + /** + * Creates a reference to the value on the + * top of the stack. + **/ + void ref(lua_State* L); + + /** + * Unrefs the reference, if any. + **/ + void unref(); + + /** + * Pushes the referred value onto the stack of the specified Lua coroutine. + * NOTE: The coroutine *must* belong to the same Lua state that was used for + * Reference::ref. + **/ + void push(lua_State* L); + + private: + // A pinned coroutine (probably the main thread) belonging to the Lua state + // in which the reference resides. + lua_State* pinnedL; + + // Index to the Lua reference. + int idx; + }; + +} // namespace love + +#endif // LOVE_REFERENCE_H diff --git a/include/common/Vector.hpp b/include/common/Vector.hpp index 93e35ea0c..5ebe7d500 100644 --- a/include/common/Vector.hpp +++ b/include/common/Vector.hpp @@ -38,8 +38,7 @@ namespace love Vector2(float x, float y) : x(x), y(y) {} - Vector2(const Vector2& v) : x(v.x), y(v.y) - {} + Vector2(const Vector2& v) = default; float getLength() const; float getLengthSquare() const; diff --git a/include/common/error.hpp b/include/common/error.hpp index 2423c8e76..db620a36f 100644 --- a/include/common/error.hpp +++ b/include/common/error.hpp @@ -1,16 +1,29 @@ namespace love { -// Startup +// #region Startup #define E_UNEXPECTED_ALIGNMENT \ "Cannot push love object to Lua: unexpected alignment\ (pointer is {} but alignment should be {:d})." - #define E_POINTER_TOO_LARGE "Cannot push love object to Lua: pointer value {} is too large!" +// #endregion -// General +// #region General #define E_OUT_OF_MEMORY "Out of memory." #define E_TITLE_TAKEOVER "Please run LÖVE Potion under Atmosphère title takeover." -// Filesystem +// #endregion + +// #region Audio +#define E_AUDIO_NOT_INITIALIZED "Failed to initialize ndsp" +#define E_CANNOT_CREATE_QUEUE_SOURCE \ + "Cannot create queueable sources using newSource. Use newQueueableSource instead." +#define E_INVALID_AUDIO_FORMAT "{:d}-channel Sources with {:d} bits per sample are not supported." +#define E_QUEUE_FORMAT_MISMATCH "Queued sound data must have same format as sound Source." +#define E_QUEUE_TYPE_MISMATCH "Only queueable Sources can be queued with sound data." +#define E_QUEUE_LENGTH_MALFORMED "Data length must be a multiple of sample size ({:d} bytes)." +#define E_QUEUE_CANNOT_BE_LOOPED "Queueable Sources can not be looped." +// #endregion + +// #region Filesystem #define E_PHYSFS_NOT_INITIALIZED "PHYSFS is not initialized." #define E_DATA_NOT_WRITTEN "Data could not be written." #define E_COULD_NOT_OPEN_FILE "Could not open file at path {}." @@ -19,17 +32,13 @@ namespace love #define E_INVALID_READ_SIZE "Invalid read size." #define E_INVALID_WRITE_SIZE "Invalid write size." #define E_NO_FILE_IN_LOVE_DIRS "\n\tno '{}' in LOVE game directories." -// Audio -#define E_AUDIO_NOT_INITIALIZED "Failed to initialize ndsp" -#define E_CANNOT_CREATE_QUEUE_SOURCE \ - "Cannot create queueable sources using newSource. Use newQueueableSource instead." -// Data -#define E_INVALID_SIZE_PARAMETER "Invalid size parameter (must be greater than 0)." -#define E_INVALID_COUNT_PARAMETER "Invalid count parameter (must be greater than 0)." -#define E_INVALID_OFFSET_AND_SIZE \ - "The given offset and size parameters don't fit within the Data's size." -#define E_DATAVIEW_OFFSET_AND_SIZE \ - "Offset and size of Data View must fit within the original Data's size." +// #endregion + +// #region Data +#define E_INVALID_SIZE_PARAMETER "Invalid size parameter (must be greater than 0)." +#define E_INVALID_COUNT_PARAMETER "Invalid count parameter (must be greater than 0)." +#define E_INVALID_OFFSET_AND_SIZE "The given offset and size parameters don't fit within the Data's size." +#define E_DATAVIEW_OFFSET_AND_SIZE " Offset and size of Data View must fit within the original Data's size." #define E_OFFSET_AND_SIZE_ARGS_FIT_WITHIN_DATA \ "Offset and size arguments must fit within the given Data's size." #define E_DATAVIEW_INVALID_SIZE "DataView size mn ust be greater than 0." @@ -41,6 +50,36 @@ namespace love "The given byte offset and pack format parameters do not fit within the ByteData's size." #define E_DATA_SIZE_MUST_BE_POSITIVE "Data size must be a positive number." #define E_SOUNDDATA_MISMATCH_CHANNEL_COUNT "Channel count mismatch: {:d} vs {:d}" +// #endregion + +// #region Graphics +#define E_BLEND_MIN_MAX_NOT_SUPPORTED "The 'min' and 'max' blend operations are not supported on this system." +// # endregion + +// #region Image +#define E_CANNOT_CREATE_TEXTURE "Cannot create texture: {:s} of {:d} is too large for this system." +#define E_CUSTOM_MIPMAP_RANGES_NOT_SUPPORTED \ + "Custom mipmap ranges for a texture are not supported on this system ({:d} mipmap levels are required " \ + "but only {:d} levels were provided.)" +#define E_COMPRESSED_TEXTURES_MISMATCHED_VIEW \ + "Compressed textures cannot use different pixel formats for texture views, aside from sRGB versus " \ + "linear variants of the same pixel format." +#define E_COLOR_FORMAT_TEXTURES_MISMATCHED_VIEW \ + "Color-format textures cannot use depth/stencil pixel formats and vice versa, in texture views." +#define E_DEPTH_STENCIL_TEXTURES_MISMATCHED_VIEW \ + "Using different pixel formats for texture views is not currently supported for depth or stencil " \ + "formats." +#define E_TEXTURE_VIEW_BITS_PER_PIXEL_MISMATCHED \ + "Texture view pixel formats must have the same bits per pixel as the base texture's pixel format." +#define E_TEXTURE_PIXELFORMAT_NOT_SUPPORTED "The {:s} {:s} pixel format is not supported {:s} on this system." +#define E_INVALID_RECT_DIMENSIONS \ + "Invalid rectangle dimensions (x: {:d}, y: {:d}, w: {:d}, h: {:d}) for {:d}x{:d} Texture." +#define E_AUTO_MIPMAPS_NOT_SUPPORTED \ + "Automatic mipmap generation is not supported for textures with the {:s} pixel format." +#define E_CANNOT_GENERATE_MIPMAPS_RT \ + "generateMipmaps cannot be called on this Texture while it's an active render target." +// #endregion + // Thread #define E_CHANNEL_VARIANT_UNKNOWN "boolean, number, string, love type, or table expected." #define E_THREAD_VARIANT_UNKNOWN "boolean, number, string, love type, or flat table expected." diff --git a/include/common/float.hpp b/include/common/float.hpp new file mode 100644 index 000000000..f7c737bac --- /dev/null +++ b/include/common/float.hpp @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2006-2024 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 + +#include "common/int.hpp" + +namespace love +{ + + typedef uint16_t float16_t; + typedef uint16_t float11_t; + typedef uint16_t float10_t; + + void float16Init(); + + float float16to32(float16_t f); + float16_t float32to16(float f); + + float float11to32(float11_t f); + float11_t float32to11(float f); + + float float10to32(float10_t f); + float10_t float32to10(float f); + +} // namespace love diff --git a/include/common/int.hpp b/include/common/int.hpp index 562e6b03f..f29af3681 100644 --- a/include/common/int.hpp +++ b/include/common/int.hpp @@ -13,19 +13,24 @@ static inline uint16_t swap_uint16(uint16_t x) { - return (x >> 8) | (x << 8); + return __builtin_bswap16(x); } static inline uint32_t swap_uint32(uint32_t x) { - return ((x & 0x000000FF) << 24) | ((x & 0x0000FF00) << 8) | ((x & 0x00FF0000) >> 8) | - ((x & 0xFF000000) >> 24); + return __builtin_bswap32(x); } static inline uint64_t swap_uint64(uint64_t x) { - return ((x << 56) & 0xFF00000000000000ULL) | ((x << 40) & 0x00FF000000000000ULL) | - ((x << 24) & 0x0000FF0000000000ULL) | ((x << 8) & 0x000000FF00000000ULL) | - ((x >> 8) & 0x00000000FF000000ULL) | ((x >> 24) & 0x0000000000FF0000ULL) | - ((x >> 40) & 0x000000000000FF00ULL) | ((x >> 56) & 0x00000000000000FFULL); + return __builtin_bswap64(x); +} + +static inline uint16_t swap16_big(uint16_t x) +{ +#if defined(LOVE_BIG_ENDIAN) + return x; +#else + return swap_uint16(x); +#endif } diff --git a/include/common/luax.hpp b/include/common/luax.hpp index 2c8e04cd6..bd3b6ad7f 100644 --- a/include/common/luax.hpp +++ b/include/common/luax.hpp @@ -96,14 +96,48 @@ namespace love bool luax_optboolean(lua_State* L, int index, bool default_value); + inline float luax_tofloat(lua_State* L, int idx) + { + return static_cast(lua_tonumber(L, idx)); + } + + inline float luax_checkfloat(lua_State* L, int index) + { + return static_cast(luaL_checknumber(L, index)); + } + + inline lua_Number luax_checknumberclamped(lua_State* L, int index, double minv, double maxv) + { + return std::min(std::max(luaL_checknumber(L, index), minv), maxv); + } + + inline lua_Number luax_optnumberclamped(lua_State* L, int index, double minv, double maxv, double def) + { + return std::min(std::max(luaL_optnumber(L, index, def), minv), maxv); + } + + inline lua_Number luax_checknumberclamped01(lua_State* L, int index) + { + return std::min(std::max(luaL_checknumber(L, index), 0.0), 1.0); + } + + inline lua_Number luax_optnumberclamped01(lua_State* L, int index, double def) + { + return std::min(std::max(luaL_optnumber(L, index, def), 0.0), 1.0); + } + std::string luax_tostring(lua_State* L, int index); std::string luax_checkstring(lua_State* L, int index); - bool luax_boolflag(lua_State* L, int index, const char* name, bool default_value); + int luax_checkintflag(lua_State* L, int tableIndex, const char* key); int luax_intflag(lua_State* L, int index, const char* name, int default_value); + bool luax_checkboolflag(lua_State* L, int tableIndex, const char* key); + + bool luax_boolflag(lua_State* L, int index, const char* name, bool default_value); + void luax_pushstring(lua_State* L, std::string_view string); void luax_pushpointerasstring(lua_State* L, const void* pointer); @@ -265,6 +299,8 @@ namespace love lua_Number luax_checknumberclamped01(lua_State* L, int index); + Reference* luax_refif(lua_State* L, int type); + // #endregion // #region Registry diff --git a/include/common/math.hpp b/include/common/math.hpp index 88914f8cc..7892b7272 100644 --- a/include/common/math.hpp +++ b/include/common/math.hpp @@ -1,6 +1,7 @@ #pragma once #include // for CHAR_BIT +#include #include // for rand() and RAND_MAX #include @@ -40,14 +41,14 @@ #define LOVE_TODEG(x) (float)(x * LOVE_M_TODEG) /* 3DS Texture Limits */ -#define LOVE_TEX3DS_MIN (size_t)0x08 -#define LOVE_TEX3DS_MAX (size_t)0x400 +#define LOVE_TEX3DS_MIN (size_t)8 +#define LOVE_TEX3DS_MAX (size_t)1024 namespace love { struct Rect { - static constexpr int EMPTY[4] = { 0, 0, 0, 0 }; + static constexpr int EMPTY[4] = { -1, -1, -1, -1 }; int x, y, w, h; diff --git a/include/common/memory.hpp b/include/common/memory.hpp new file mode 100644 index 000000000..1ef174720 --- /dev/null +++ b/include/common/memory.hpp @@ -0,0 +1,6 @@ +#include + +namespace love +{ + size_t alignUp(size_t size, size_t alignment); +} // namespace love diff --git a/include/common/pixelformat.hpp b/include/common/pixelformat.hpp index 47af5ae18..416f9c8fe 100644 --- a/include/common/pixelformat.hpp +++ b/include/common/pixelformat.hpp @@ -20,6 +20,8 @@ #pragma once +#include "common/Map.hpp" + #include #include @@ -49,6 +51,7 @@ namespace love PIXELFORMAT_RG8_UNORM, PIXELFORMAT_RG8_INT, PIXELFORMAT_RG8_UINT, + PIXELFORMAT_A4_UNORM, PIXELFORMAT_LA8_UNORM, // Same as RG8, but accessed as (L, L, L, A) PIXELFORMAT_RG16_UNORM, PIXELFORMAT_RG16_FLOAT, @@ -269,4 +272,130 @@ namespace love * Gets the number of color components in the given pixel format. **/ int getPixelFormatColorComponents(PixelFormat format); + + // clang-format off + STRINGMAP_DECLARE(PixelFormats, PixelFormat, + { "unknown", PIXELFORMAT_UNKNOWN }, + + { "normal", PIXELFORMAT_NORMAL }, + { "hdr", PIXELFORMAT_HDR }, + + { "r8", PIXELFORMAT_R8_UNORM }, + { "r8i", PIXELFORMAT_R8_INT }, + { "r8ui", PIXELFORMAT_R8_UINT }, + { "r16", PIXELFORMAT_R16_UNORM }, + { "r16f", PIXELFORMAT_R16_FLOAT }, + { "r16i", PIXELFORMAT_R16_INT }, + { "r16ui", PIXELFORMAT_R16_UINT }, + { "r32f", PIXELFORMAT_R32_FLOAT }, + { "r32i", PIXELFORMAT_R32_INT }, + { "r32ui", PIXELFORMAT_R32_UINT }, + + { "a4", PIXELFORMAT_A4_UNORM }, + + { "rg8", PIXELFORMAT_RG8_UNORM }, + { "rg8i", PIXELFORMAT_RG8_INT }, + { "rg8ui", PIXELFORMAT_RG8_UINT }, + { "la8", PIXELFORMAT_LA8_UNORM }, + { "rg16", PIXELFORMAT_RG16_UNORM }, + { "rg16f", PIXELFORMAT_RG16_FLOAT }, + { "rg16i", PIXELFORMAT_RG16_INT }, + { "rg16ui", PIXELFORMAT_RG16_UINT }, + { "rg32f", PIXELFORMAT_RG32_FLOAT }, + { "rg32i", PIXELFORMAT_RG32_INT }, + { "rg32ui", PIXELFORMAT_RG32_UINT }, + + { "rgba8", PIXELFORMAT_RGBA8_UNORM }, + { "srgba8", PIXELFORMAT_RGBA8_sRGB }, + { "bgra8", PIXELFORMAT_BGRA8_UNORM }, + { "bgra8srgb", PIXELFORMAT_BGRA8_sRGB }, + { "rgba8i", PIXELFORMAT_RGBA8_INT }, + { "rgba8ui", PIXELFORMAT_RGBA8_UINT }, + { "rgba16", PIXELFORMAT_RGBA16_UNORM }, + { "rgba16f", PIXELFORMAT_RGBA16_FLOAT }, + { "rgba16i", PIXELFORMAT_RGBA16_INT }, + { "rgba16ui", PIXELFORMAT_RGBA16_UINT }, + { "rgba32f", PIXELFORMAT_RGBA32_FLOAT }, + { "rgba32i", PIXELFORMAT_RGBA32_INT }, + { "rgba32ui", PIXELFORMAT_RGBA32_UINT }, + + { "rgba4", PIXELFORMAT_RGBA4_UNORM }, + { "rgb5a1", PIXELFORMAT_RGB5A1_UNORM }, + { "rgb565", PIXELFORMAT_RGB565_UNORM }, + { "rgb10a2", PIXELFORMAT_RGB10A2_UNORM }, + { "rg11b10f", PIXELFORMAT_RG11B10_FLOAT }, + + { "stencil8", PIXELFORMAT_STENCIL8 }, + { "depth16", PIXELFORMAT_DEPTH16_UNORM }, + { "depth24", PIXELFORMAT_DEPTH24_UNORM }, + { "depth32f", PIXELFORMAT_DEPTH32_FLOAT }, + { "depth24stencil8", PIXELFORMAT_DEPTH24_UNORM_STENCIL8 }, + { "depth32fstencil8", PIXELFORMAT_DEPTH32_FLOAT_STENCIL8 }, + + { "DXT1", PIXELFORMAT_DXT1_UNORM }, + { "DXT1srgb", PIXELFORMAT_DXT1_sRGB }, + { "DXT3", PIXELFORMAT_DXT3_UNORM }, + { "DXT3srgb", PIXELFORMAT_DXT3_sRGB }, + { "DXT5", PIXELFORMAT_DXT5_UNORM }, + { "DXT5srgb", PIXELFORMAT_DXT5_sRGB }, + { "BC4", PIXELFORMAT_BC4_UNORM }, + { "BC4s", PIXELFORMAT_BC4_SNORM }, + { "BC5", PIXELFORMAT_BC5_UNORM }, + { "BC5s", PIXELFORMAT_BC5_SNORM }, + { "BC6h", PIXELFORMAT_BC6H_UFLOAT }, + { "BC6hs", PIXELFORMAT_BC6H_FLOAT }, + { "BC7", PIXELFORMAT_BC7_UNORM }, + { "BC7srgb", PIXELFORMAT_BC7_sRGB }, + + { "PVR1rgb2", PIXELFORMAT_PVR1_RGB2_UNORM }, + { "PVR1rgb2srgb", PIXELFORMAT_PVR1_RGB2_sRGB }, + { "PVR1rgb4", PIXELFORMAT_PVR1_RGB4_UNORM }, + { "PVR1rgb4srgb", PIXELFORMAT_PVR1_RGB4_sRGB }, + { "PVR1rgba2", PIXELFORMAT_PVR1_RGBA2_UNORM }, + { "PVR1rgba2srgb", PIXELFORMAT_PVR1_RGBA2_sRGB }, + { "PVR1rgba4", PIXELFORMAT_PVR1_RGBA4_UNORM }, + { "PVR1rgba4srgb", PIXELFORMAT_PVR1_RGBA4_sRGB }, + + { "ETC1", PIXELFORMAT_ETC1_UNORM }, + { "ETC2rgb", PIXELFORMAT_ETC2_RGB_UNORM }, + { "ETC2srgb", PIXELFORMAT_ETC2_RGB_sRGB }, + { "ETC2rgba", PIXELFORMAT_ETC2_RGBA_UNORM }, + { "ETC2srgba", PIXELFORMAT_ETC2_RGBA_sRGB }, + { "ETC2rgba1", PIXELFORMAT_ETC2_RGBA1_UNORM }, + { "ETC2srgba1", PIXELFORMAT_ETC2_RGBA1_sRGB }, + { "EACr", PIXELFORMAT_EAC_R_UNORM }, + { "EACrs", PIXELFORMAT_EAC_R_SNORM }, + { "EACrg", PIXELFORMAT_EAC_RG_UNORM }, + { "EACrgs", PIXELFORMAT_EAC_RG_SNORM }, + + { "ASTC4x4", PIXELFORMAT_ASTC_4x4_UNORM }, + { "ASTC5x4", PIXELFORMAT_ASTC_5x4_UNORM }, + { "ASTC5x5", PIXELFORMAT_ASTC_5x5_UNORM }, + { "ASTC6x5", PIXELFORMAT_ASTC_6x5_UNORM }, + { "ASTC6x6", PIXELFORMAT_ASTC_6x6_UNORM }, + { "ASTC8x5", PIXELFORMAT_ASTC_8x5_UNORM }, + { "ASTC8x6", PIXELFORMAT_ASTC_8x6_UNORM }, + { "ASTC8x8", PIXELFORMAT_ASTC_8x8_UNORM }, + { "ASTC10x5", PIXELFORMAT_ASTC_10x5_UNORM }, + { "ASTC10x6", PIXELFORMAT_ASTC_10x6_UNORM }, + { "ASTC10x8", PIXELFORMAT_ASTC_10x8_UNORM }, + { "ASTC10x10", PIXELFORMAT_ASTC_10x10_UNORM }, + { "ASTC12x10", PIXELFORMAT_ASTC_12x10_UNORM }, + { "ASTC12x12", PIXELFORMAT_ASTC_12x12_UNORM }, + { "ASTC4x4srgb", PIXELFORMAT_ASTC_4x4_sRGB }, + { "ASTC5x4srgb", PIXELFORMAT_ASTC_5x4_sRGB }, + { "ASTC5x5srgb", PIXELFORMAT_ASTC_5x5_sRGB }, + { "ASTC6x5srgb", PIXELFORMAT_ASTC_6x5_sRGB }, + { "ASTC6x6srgb", PIXELFORMAT_ASTC_6x6_sRGB }, + { "ASTC8x5srgb", PIXELFORMAT_ASTC_8x5_sRGB }, + { "ASTC8x6srgb", PIXELFORMAT_ASTC_8x6_sRGB }, + { "ASTC8x8srgb", PIXELFORMAT_ASTC_8x8_sRGB }, + { "ASTC10x5srgb", PIXELFORMAT_ASTC_10x5_sRGB }, + { "ASTC10x6srgb", PIXELFORMAT_ASTC_10x6_sRGB }, + { "ASTC10x8srgb", PIXELFORMAT_ASTC_10x8_sRGB }, + { "ASTC10x10srgb", PIXELFORMAT_ASTC_10x10_sRGB }, + { "ASTC12x10srgb", PIXELFORMAT_ASTC_12x10_sRGB }, + { "ASTC12x12srgb", PIXELFORMAT_ASTC_12x12_sRGB }, + ); + // clang-format on } // namespace love diff --git a/include/common/screen.hpp b/include/common/screen.hpp index 4ae8e8c82..6e87adf3c 100644 --- a/include/common/screen.hpp +++ b/include/common/screen.hpp @@ -22,7 +22,7 @@ namespace love static constexpr inline Screen INVALID_SCREEN = (Screen)-1; static constexpr inline Screen DEFAULT_SCREEN = (Screen)0; - inline Screen currentScreen = INVALID_SCREEN; + inline Screen currentScreen = DEFAULT_SCREEN; std::span getScreenInfo(); @@ -35,7 +35,7 @@ namespace love return info[id]; } - const inline Screen getScreenId(std::string_view name) + inline Screen getScreenId(std::string_view name) { const auto& info = getScreenInfo(); @@ -47,4 +47,13 @@ namespace love return INVALID_SCREEN; } + + inline void setScreen(Screen id) + { + const auto& info = getScreenInfo(); + + id = (Screen)std::clamp(id, 0, info.size() - 1); + + currentScreen = id; + } } // namespace love diff --git a/include/driver/EventQueue.tcc b/include/driver/EventQueue.tcc index 062c19cc4..bf2d58856 100644 --- a/include/driver/EventQueue.tcc +++ b/include/driver/EventQueue.tcc @@ -3,7 +3,7 @@ #include "common/Singleton.tcc" #include "events.hpp" -#include "modules/joystick/Joystick.hpp" +#include "modules/joystick/Joystick.tcc" #include #include diff --git a/include/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index 472e66cd1..58d9581bc 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -2,23 +2,29 @@ #include "common/Singleton.tcc" +#include "modules/graphics/Graphics.tcc" +#include "modules/graphics/Shader.tcc" #include "modules/graphics/renderstate.hpp" +#include "modules/graphics/vertex.hpp" + #include +#include namespace love { - template - class RendererBase : public Singleton + class RendererBase { public: - enum RENDERER_INFO + RendererBase() + {} + + virtual void prepareDraw(GraphicsBase* graphics) = 0; + + bool isInFrame() const { - RENDERER_INFO_NAME, - RENDERER_INFO_VERSION, - RENDERER_INFO_VENDOR, - RENDERER_INFO_DEVICE - }; + return this->inFrame; + } protected: struct ContextBase diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp new file mode 100644 index 000000000..347f35b42 --- /dev/null +++ b/include/driver/graphics/DrawCommand.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include "common/Exception.hpp" + +#include "driver/graphics/StreamBuffer.hpp" + +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/vertex.hpp" + +namespace love +{ + struct BatchedDrawCommand + { + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + CommonFormat format = CommonFormat::NONE; + TriangleIndexMode indexMode = TRIANGLEINDEX_NONE; + int vertexCount = 0; + TextureBase* texture = nullptr; + ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; + + bool isFont = false; + bool pushTransform = true; + }; + + struct DrawIndexedCommand + { + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; + int indexCount = 0; + int instanceCount = 1; + + const VertexAttributes* attributes; + const BufferBindings* buffers; + + Resource* indexBuffer = nullptr; + + IndexDataType indexType = INDEX_UINT16; + size_t indexBufferOffset = 0; + + TextureBase* texture = nullptr; + CullMode cullMode = CULL_NONE; + + bool isFont = false; + + DrawIndexedCommand(const VertexAttributes* attributes, const BufferBindings* buffers, + Resource* indexBuffer) : + attributes(attributes), + buffers(buffers), + indexBuffer(indexBuffer) + {} + }; + + struct DrawCommand + { + DrawCommand(const BufferBindings* buffers) : buffers(buffers) + {} + + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; + + int vertexStart = 0; + int vertexCount = 0; + int instanceCount = 1; + + TextureBase* texture = nullptr; + CullMode cullMode = CULL_NONE; + const BufferBindings* buffers; + + bool isFont = false; + }; + + struct BatchedVertexData + { + void* stream; + }; + + struct BatchedDrawState + { + StreamBuffer* vertexBuffer = nullptr; + StreamBuffer* indexBuffer = nullptr; + + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + CommonFormat format = CommonFormat::NONE; + StrongRef texture; + ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; + + int vertexCount = 0; + int indexCount = 0; + + int lastVertexCount = 0; + int lastIndexCount = 0; + + MapInfo vertexBufferMap = MapInfo(); + MapInfo indexBufferMap = MapInfo(); + + bool flushing = false; + + bool isFont = false; + bool pushTransform = true; + }; + + constexpr size_t INIT_VERTEX_BUFFER_SIZE = sizeof(Vertex) * 4096 * 1; + constexpr size_t INIT_INDEX_BUFFER_SIZE = sizeof(uint16_t) * LOVE_UINT16_MAX * 1; + + inline StreamBuffer* newVertexBuffer(size_t size) + { + return new StreamBuffer(BUFFERUSAGE_VERTEX, size); + } + + inline StreamBuffer* newIndexBuffer(size_t size) + { + return new StreamBuffer(BUFFERUSAGE_INDEX, size); + } +} // namespace love diff --git a/include/driver/graphics/StreamBuffer.tcc b/include/driver/graphics/StreamBuffer.tcc new file mode 100644 index 000000000..7c4955bd6 --- /dev/null +++ b/include/driver/graphics/StreamBuffer.tcc @@ -0,0 +1,95 @@ +#pragma once + +#include "common/Object.hpp" + +#include "modules/graphics/Resource.hpp" +#include "modules/graphics/vertex.hpp" + +#include + +namespace love +{ + template + struct MapInfo + { + T* data = nullptr; + size_t size = 0; + + MapInfo() + {} + + MapInfo(T* data, size_t size) : data(data), size(size) + {} + }; + + static constexpr int BUFFER_FRAMES = 2; + + template + class StreamBufferBase : public Object, public Resource + { + public: + virtual ~StreamBufferBase() + {} + + size_t getSize() const + { + return this->bufferSize; + } + + BufferUsage getMode() const + { + return this->usage; + } + + size_t getUsableSize() const + { + return this->bufferSize - this->frameGPUReadOffset; + } + + size_t getGPUReadOffset() const + { + return (this->frameIndex * this->bufferSize) + this->frameGPUReadOffset; + } + + int getBufferIndex() const + { + return (this->frameIndex * this->bufferSize) + this->index; + } + + // MapInfo map(size_t) = 0; + + virtual size_t unmap(size_t) + { + return this->index; + } + + void markUsed(int count) + { + this->index += count; + this->frameGPUReadOffset += (count * sizeof(T)); + } + + void nextFrame() + { + this->index = 0; + this->frameGPUReadOffset = 0; + } + + protected: + StreamBufferBase(BufferUsage usage, size_t size) : + bufferSize(size), + frameGPUReadOffset(0), + frameIndex(0), + index(0), + usage(usage) + {} + + size_t bufferSize; + size_t frameGPUReadOffset; + size_t frameIndex; + + int index; + + BufferUsage usage; + }; +} // namespace love diff --git a/include/modules/data/misc/ZlibCompressor.hpp b/include/modules/data/misc/ZlibCompressor.hpp index bfc2b2320..774f23d20 100644 --- a/include/modules/data/misc/ZlibCompressor.hpp +++ b/include/modules/data/misc/ZlibCompressor.hpp @@ -5,6 +5,8 @@ #include "common/Exception.hpp" #include "common/int.hpp" +#include + #include namespace love @@ -22,8 +24,8 @@ namespace love return size; } - int zlibCompress(Format format, Bytef* destination, uLongf* destinationLength, - const Bytef* source, uLong sourceLength, int level) + int zlibCompress(Format format, Bytef* destination, uLongf* destinationLength, const Bytef* source, + uLong sourceLength, int level) { z_stream stream {}; @@ -56,8 +58,8 @@ namespace love return deflateEnd(&stream); } - int zlibDecompress(Format format, Bytef* destination, uLongf* destinationLength, - const Bytef* source, uLong sourceLength) + int zlibDecompress(Format format, Bytef* destination, uLongf* destinationLength, const Bytef* source, + uLong sourceLength) { z_stream stream {}; @@ -118,8 +120,8 @@ namespace love } uLongf dstLength = (uLongf)maxSize; - int status = zlibCompress(format, (Bytef*)compressedBytes, &dstLength, - (const Bytef*)data, (uLong)dataSize, level); + int status = zlibCompress(format, (Bytef*)compressedBytes, &dstLength, (const Bytef*)data, + (uLong)dataSize, level); if (status != Z_OK) { @@ -164,8 +166,8 @@ namespace love } uLongf dstLength = (uLongf)rawSize; - int status = zlibDecompress(format, (Bytef*)rawBytes, &dstLength, - (const Bytef*)data, (uLong)dataSize); + int status = + zlibDecompress(format, (Bytef*)rawBytes, &dstLength, (const Bytef*)data, (uLong)dataSize); if (status == Z_OK) { diff --git a/include/modules/font/Font.tcc b/include/modules/font/Font.tcc new file mode 100644 index 000000000..fdf38d7c6 --- /dev/null +++ b/include/modules/font/Font.tcc @@ -0,0 +1,73 @@ +#pragma once + +#include "common/Module.hpp" +#include "common/StrongRef.hpp" +#include "common/int.hpp" + +#include "modules/filesystem/FileData.hpp" +#include "modules/font/Rasterizer.hpp" +#include "modules/image/ImageData.hpp" + +#include +#include + +namespace love +{ + class SystemFont : public Data + { + public: + static inline Type type = Type("SystemFont", &Data::type); + + SystemFont(uint32_t dataType); + + virtual ~SystemFont(); + + SystemFont(const SystemFont& other) : data(other.data), size(other.size), dataType(other.dataType) + {} + + SystemFont* clone() const override + { + return new SystemFont(*this); + } + + void* getData() const override + { + return this->data; + } + + size_t getSize() const override + { + return this->size; + } + + private: + void* data; + size_t size; + + uint32_t dataType; + }; + + class FontModuleBase : public Module + { + public: + FontModuleBase(const char* name) : Module(M_FONT, name) + {} + + virtual ~FontModuleBase() + {} + + virtual Rasterizer* newRasterizer(FileData* data) const = 0; + + Rasterizer* newTrueTypeRasterizer(int size, const Rasterizer::Settings& settings) const; + + virtual Rasterizer* newTrueTypeRasterizer(Data* data, int size, + const Rasterizer::Settings& settings) const = 0; + + virtual GlyphData* newGlyphData(Rasterizer* rasterizer, const std::string& glyph) const; + + virtual GlyphData* newGlyphData(Rasterizer* rasterizer, uint32_t glyph) const; + + protected: + StrongRef defaultFontData; + }; +} // namespace love diff --git a/include/modules/font/GenericShaper.hpp b/include/modules/font/GenericShaper.hpp new file mode 100644 index 000000000..15842fe2c --- /dev/null +++ b/include/modules/font/GenericShaper.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "modules/font/TextShaper.hpp" + +namespace love +{ + class GenericShaper : public 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; + }; +} // namespace love diff --git a/include/modules/font/GlyphData.hpp b/include/modules/font/GlyphData.hpp new file mode 100644 index 000000000..6aaa1182e --- /dev/null +++ b/include/modules/font/GlyphData.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include "common/Data.hpp" +#include "common/Exception.hpp" +#include "common/Map.hpp" +#include "common/int.hpp" +#include "common/pixelformat.hpp" + +#include +#include + +namespace love +{ + struct GlyphMetrics + { + int height; + int width; + int advance; + int bearingX; + int bearingY; + }; + + struct GlyphSheet + { + int index; + + float left; + float top; + float right; + float bottom; + + uint16_t width; + uint16_t height; + void* data; + }; + + class GlyphData : public Data + { + public: + static Type type; + + GlyphData(uint32_t glyph, GlyphMetrics& metrics, PixelFormat format); + + GlyphData(uint32_t glyph, GlyphMetrics& metrics, PixelFormat format, GlyphSheet sheet); + + GlyphData(const GlyphData& other); + + ~GlyphData(); + + GlyphData* clone() const override; + + void* getData() const + { + return this->data; + } + + size_t getSize() const override; + + void* getData(int x, int y) const; + + size_t getPixelSize() const; + + virtual int getHeight() const + { + return this->metrics.height; + } + + virtual int getWidth() const + { + return this->metrics.width; + } + + uint32_t getGlyph() const + { + return this->glyph; + } + + std::string getGlyphString() const; + + int getAdvance() const + { + return this->metrics.advance; + } + + int getBearingX() const + { + return this->metrics.bearingX; + } + + int getBearingY() const + { + return this->metrics.bearingY; + } + + int getMinX() const + { + return this->getBearingX(); + } + + int getMaxX() const + { + return this->getBearingX() + this->getWidth(); + } + + int getMinY() const + { + return this->getBearingY(); + } + + int getMaxY() const + { + return this->getBearingY() + this->getHeight(); + } + + GlyphSheet getSheet() + { + return this->sheet; + } + + PixelFormat getFormat() const + { + return this->format; + } + + private: + uint32_t glyph; + GlyphMetrics metrics; + + uint8_t* data; + + PixelFormat format; + GlyphSheet sheet; + }; +} // namespace love diff --git a/include/modules/font/Rasterizer.hpp b/include/modules/font/Rasterizer.hpp new file mode 100644 index 000000000..76e7736a5 --- /dev/null +++ b/include/modules/font/Rasterizer.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "common/Object.hpp" +#include "common/Optional.hpp" +#include "common/StrongRef.hpp" +#include "common/int.hpp" + +#include "modules/font/GlyphData.hpp" + +namespace love +{ + class TextShaper; + + struct FontMetrics + { + float advance; + float ascent; + float descent; + float height; + }; + + class Rasterizer : public Object + { + public: + enum Hinting + { + HINTING_NORMAL, + HINTING_LIGHT, + HINTING_MONO, + HINTING_NONE, + HINTING_MAX_ENUM + }; + + struct Settings + { + Hinting hinting = HINTING_NORMAL; + OptionalFloat dpiScale; + bool sdf = false; + }; + + enum DataType + { + DATA_TRUETYPE, + DATA_IMAGE, + DATA_BCFNT + }; + + static Type type; + + virtual ~Rasterizer(); + + virtual int getHeight() const + { + return this->metrics.height; + } + + virtual int getAdvance() const + { + return this->metrics.advance; + } + + virtual int getAscent() const + { + return this->metrics.ascent; + } + + virtual int getDescent() const + { + return this->metrics.descent; + } + + virtual int getLineHeight() const = 0; + + virtual int getGlyphSpacing(uint32_t glyph) const = 0; + + virtual int getGlyphIndex(uint32_t glyph) const = 0; + + GlyphData* getGlyphData(uint32_t glyph) const; + + GlyphData* getGlyphData(const std::string& text) const; + + virtual GlyphData* getGlyphDataForIndex(int index) const = 0; + + virtual int getGlyphCount() const = 0; + + virtual bool hasGlyph(uint32_t glyph) const = 0; + + virtual bool hasGlyphs(const std::string& text) const; + + virtual float getKerning(uint32_t left, uint32_t right) const; + + virtual DataType getDataType() const = 0; + + virtual ptrdiff_t getHandle() const + { + return 0; + } + + virtual TextShaper* newTextShaper() = 0; + + float getDPIScale() const + { + return dpiScale; + } + + // clang-format off + STRINGMAP_DECLARE(Hintings, Hinting, + { "normal", HINTING_NORMAL }, + { "light", HINTING_LIGHT }, + { "mono", HINTING_MONO }, + { "none", HINTING_NONE } + ); + // clang-format on + + protected: + FontMetrics metrics; + float dpiScale; + + StrongRef data; + int size; + }; +} // namespace love diff --git a/include/modules/font/TextShaper.hpp b/include/modules/font/TextShaper.hpp new file mode 100644 index 000000000..748ba0c4b --- /dev/null +++ b/include/modules/font/TextShaper.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include "common/Color.hpp" +#include "common/Object.hpp" +#include "common/Range.hpp" +#include "common/StrongRef.hpp" +#include "common/Vector.hpp" +#include "common/int.hpp" + +#include +#include +#include + +namespace love +{ + class Rasterizer; + + struct ColoredString + { + std::string string; + Color color; + }; + + struct IndexedColor + { + Color color; + int index; + }; + + struct ColoredCodepoints + { + std::vector codepoints; + std::vector colors; + }; + + void getCodepointsFromString(const std::string& string, std::vector& codepoints); + + void getCodepointsFromString(const std::vector& strings, ColoredCodepoints& codepoints); + + using StrongRasterizers = std::vector>; + + class TextShaper : public Object + { + public: + struct GlyphIndex + { + int index; + int rasterizerIndex; + }; + + using GlyphAdvances = std::unordered_map>; + + struct GlyphPosition + { + Vector2 position; + GlyphIndex glyphIndex; + }; + + struct TextInfo + { + int width; + int height; + }; + + static constexpr int SPACES_PER_TAB = 4; + + static Type type; + + virtual ~TextShaper(); + + const StrongRasterizers& getRasterizers() const + { + return this->rasterizers; + } + + bool isUsingSpacesForTab() const + { + return this->useSpacesForTab; + } + + float getHeight() const + { + return this->height; + } + + void setLineHeight(float height) + { + this->lineHeight = height; + } + + float getLineHeight() const + { + return this->lineHeight; + } + + int getAscent() const; + + int getDescent() const; + + float getBaseline() const; + + bool hasGlyph(uint32_t codepoint) const; + + bool hasGlyphs(const std::string& text) const; + + float getKerning(uint32_t left, uint32_t right); + + float getKerning(const std::string& left, const std::string& right); + + float getGlyphAdvance(uint32_t codepoint, GlyphIndex* glyphIndex = nullptr); + + int getWidth(const std::string& text); + + void getWrap(const std::vector& text, float limit, std::vector& lines, + std::vector* lineWidths = nullptr); + + void getWrap(const ColoredCodepoints& codepoints, float limit, 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_t codepoint) + { + return codepoint == ' ' || codepoint == '\t'; + } + + StrongRasterizers rasterizers; + std::vector dpiScales; + + private: + int height; + float lineHeight; + bool useSpacesForTab; + + GlyphAdvances glyphAdvances; + std::unordered_map kernings; + }; +} // namespace love diff --git a/include/modules/font/freetype/Font.hpp b/include/modules/font/freetype/Font.hpp new file mode 100644 index 000000000..5582269b1 --- /dev/null +++ b/include/modules/font/freetype/Font.hpp @@ -0,0 +1,59 @@ +#include "common/Map.hpp" +#include "modules/font/Font.tcc" + +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H + +#if defined(__SWITCH__) + #include +using SystemFontType = PlSharedFontType; +#elif defined(__WIIU__) + #include +using SystemFontType = OSSharedDataType; +#else + #error "Unsupported platform for FreeType" +#endif + +namespace love +{ + class FontModule : public FontModuleBase + { + public: + FontModule(); + + virtual ~FontModule(); + + virtual Rasterizer* newRasterizer(FileData* data) const override; + + Rasterizer* newTrueTypeRasterizer(Data* data, int size, + const Rasterizer::Settings& settings) const override; + + using FontModuleBase::newTrueTypeRasterizer; + + static ByteData* loadSystemFontByType(SystemFontType type); + + // clang-format off +#if defined(__SWITCH__) + STRINGMAP_DECLARE(SystemFonts, PlSharedFontType, + { "standard", PlSharedFontType_Standard }, + { "simplified chinese", PlSharedFontType_ChineseSimplified }, + { "simplified chinese extended", PlSharedFontType_ExtChineseSimplified }, + { "traditional chinese", PlSharedFontType_ChineseTraditional }, + { "korean", PlSharedFontType_KO }, + { "nintendo extended", PlSharedFontType_NintendoExt } + ); +#elif defined(__WIIU__) + STRINGMAP_DECLARE(SystemFonts, OSSharedDataType, + { "standard", OS_SHAREDDATATYPE_FONT_STANDARD }, + { "chinese", OS_SHAREDDATATYPE_FONT_CHINESE }, + { "korean", OS_SHAREDDATATYPE_FONT_KOREAN }, + { "taiwanese", OS_SHAREDDATATYPE_FONT_TAIWANESE } + ); +#endif + // clang-format on + + private: + FT_Library library; + }; +} // namespace love diff --git a/include/modules/font/freetype/TrueTypeRasterizer.hpp b/include/modules/font/freetype/TrueTypeRasterizer.hpp new file mode 100644 index 000000000..8d6a12545 --- /dev/null +++ b/include/modules/font/freetype/TrueTypeRasterizer.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "common/Map.hpp" + +#include "modules/filesystem/FileData.hpp" +#include "modules/font/Rasterizer.hpp" + +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H + +namespace love +{ + class TrueTypeRasterizer : public Rasterizer + { + public: + TrueTypeRasterizer(FT_Library library, Data* data, int size, const Settings& settings, + float defaultDPIScale); + + virtual ~TrueTypeRasterizer(); + + int getLineHeight() const override; + + int getGlyphSpacing(uint32_t glyph) const override; + + int getGlyphIndex(uint32_t glyph) const override; + + GlyphData* getGlyphDataForIndex(int index) const override; + + int getGlyphCount() const override; + + bool hasGlyph(uint32_t glyph) const override; + + float getKerning(uint32_t left, uint32_t right) const override; + + DataType getDataType() const override; + + TextShaper* newTextShaper() override; + + ptrdiff_t getHandle() const override + { + return (ptrdiff_t)this->face; + } + + static bool accepts(FT_Library library, Data* data); + + // clang-format off + STRINGMAP_DECLARE(Hintings, Hinting, + { "normal", HINTING_NORMAL }, + { "light", HINTING_LIGHT }, + { "mono", HINTING_MONO }, + { "none", HINTING_NONE } + ); + // clang-format on + + private: + static FT_UInt hintingToLoadOption(Hinting hint); + + FT_Face face; + Hinting hinting; + bool sdf; + }; +} // namespace love diff --git a/include/modules/font/wrap_Font.hpp b/include/modules/font/wrap_Font.hpp new file mode 100644 index 000000000..5acc1d749 --- /dev/null +++ b/include/modules/font/wrap_Font.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "common/luax.hpp" + +namespace Wrap_FontModule +{ + int newRasterizer(lua_State* L); + + int newTrueTypeRasterizer(lua_State* L); + + int newBMFontRasterizer(lua_State* L); + + int newImageRasterizer(lua_State* L); + + int newGlyphData(lua_State* L); + + int open(lua_State* L); +} // namespace Wrap_FontModule diff --git a/include/modules/font/wrap_GlyphData.hpp b/include/modules/font/wrap_GlyphData.hpp new file mode 100644 index 000000000..77e91191b --- /dev/null +++ b/include/modules/font/wrap_GlyphData.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/data/wrap_Data.hpp" +#include "modules/font/GlyphData.hpp" + +namespace love +{ + GlyphData* luax_checkglyphdata(lua_State* L, int index); + + int open_glyphdata(lua_State* L); +} // namespace love + +namespace Wrap_GlyphData +{ + int clone(lua_State* L); + + int getWidth(lua_State* L); + + int getHeight(lua_State* L); + + int getDimensions(lua_State* L); + + int getGlyph(lua_State* L); + + int getGlyphString(lua_State* L); + + int getAdvance(lua_State* L); + + int getBearing(lua_State* L); + + int getBoundingBox(lua_State* L); + + int getFormat(lua_State* L); +} // namespace Wrap_GlyphData diff --git a/include/modules/font/wrap_Rasterizer.hpp b/include/modules/font/wrap_Rasterizer.hpp new file mode 100644 index 000000000..c20c8b620 --- /dev/null +++ b/include/modules/font/wrap_Rasterizer.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/font/Rasterizer.hpp" + +namespace love +{ + Rasterizer* luax_checkrasterizer(lua_State* L, int index); + + int open_rasterizer(lua_State* L); +} // namespace love + +namespace Wrap_Rasterizer +{ + int getHeight(lua_State* L); + + int getAdvance(lua_State* L); + + int getAscent(lua_State* L); + + int getDescent(lua_State* L); + + int getLineHeight(lua_State* L); + + int getGlyphData(lua_State* L); + + int getGlyphCount(lua_State* L); + + int hasGlyphs(lua_State* L); +} diff --git a/include/modules/graphics/Buffer.tcc b/include/modules/graphics/Buffer.tcc new file mode 100644 index 000000000..a47d3b92f --- /dev/null +++ b/include/modules/graphics/Buffer.tcc @@ -0,0 +1,234 @@ +/** + * Copyright (c) 2006-2024 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.hpp" +#include "common/Optional.hpp" +#include "common/config.hpp" +#include "common/int.hpp" +#include "modules/graphics/Resource.hpp" +#include "modules/graphics/vertex.hpp" + +// C +#include +#include +#include + +namespace love +{ + class GraphicsBase; + + /** + * A block of GPU-owned memory. + **/ + class BufferBase : public love::Object, public Resource + { + public: + static love::Type type; + + static int bufferCount; + static int64_t totalGraphicsMemory; + + static const size_t SHADER_STORAGE_BUFFER_MAX_STRIDE = 2048; + + enum MapType + { + MAP_WRITE_INVALIDATE, + MAP_READ_ONLY, + }; + + struct DataDeclaration + { + std::string name; + DataFormat format; + int arrayLength; + + DataDeclaration(const std::string& name, DataFormat format, int arrayLength = 0) : + name(name), + format(format), + arrayLength(arrayLength) + {} + }; + + struct DataMember + { + DataDeclaration decl; + DataFormatInfo info; + size_t offset; + size_t size; + + DataMember(const DataDeclaration& decl) : + decl(decl), + info(getDataFormatInfo(decl.format)), + offset(0), + size(0) + {} + }; + + struct Settings + { + BufferUsageFlags usageFlags; + BufferDataUsage dataUsage; + bool zeroInitialize; + std::string debugName; + + Settings(uint32_t usageflags, BufferDataUsage dataUsage) : + usageFlags((BufferUsageFlags)usageflags), + dataUsage(dataUsage), + zeroInitialize(false), + debugName() + {} + }; + + BufferBase(GraphicsBase* gfx, const Settings& settings, const std::vector& format, + size_t size, size_t arraylength); + + virtual ~BufferBase(); + + size_t getSize() const + { + return size; + } + BufferUsageFlags getUsageFlags() const + { + return usageFlags; + } + BufferDataUsage getDataUsage() const + { + return dataUsage; + } + bool isMapped() const + { + return mapped; + } + + size_t getArrayLength() const + { + return arrayLength; + } + size_t getArrayStride() const + { + return arrayStride; + } + const std::vector& getDataMembers() const + { + return dataMembers; + } + const DataMember& getDataMember(int index) const + { + return dataMembers[index]; + } + size_t getMemberOffset(int index) const + { + return dataMembers[index].offset; + } + int getDataMemberIndex(const std::string& name) const; + const std::string& getDebugName() const + { + return debugName; + } + + void setImmutable(bool immutable) + { + this->immutable = immutable; + }; + bool isImmutable() const + { + return immutable; + } + + /** + * Map a portion of the Buffer to client memory. + */ + virtual void* map(MapType map, size_t offset, size_t size) = 0; + + /** + * Unmap a previously mapped Buffer. The buffer must be unmapped when used + * to draw. + */ + virtual void unmap(size_t usedoffset, size_t usedsize) = 0; + + /** + * Fill a portion of the buffer with data. + */ + virtual bool fill(size_t offset, size_t size, const void* data) = 0; + + /** + * Reset the given portion of this buffer's data to 0. + */ + void clear(size_t offset, size_t size); + + /** + * Copy a portion of this Buffer's data to another buffer, using the GPU. + **/ + virtual void copyTo(BufferBase* dest, size_t sourceoffset, size_t destoffset, size_t size) = 0; + + /** + * Texel buffers may use an additional texture handle as well as a buffer + * handle. + **/ + virtual ptrdiff_t getTexelBufferHandle() const = 0; + + static std::vector getCommonFormatDeclaration(CommonFormat format); + + class Mapper + { + public: + Mapper(BufferBase& buffer, MapType maptype = MAP_WRITE_INVALIDATE) : buffer(buffer) + { + data = buffer.map(maptype, 0, buffer.getSize()); + } + + ~Mapper() + { + buffer.unmap(0, buffer.getSize()); + } + + BufferBase& buffer; + void* data; + + }; // Mapper + + protected: + virtual void clearInternal(size_t offset, size_t size) = 0; + + std::vector dataMembers; + size_t arrayLength; + size_t arrayStride; + + // The size of the buffer, in bytes. + size_t size; + + // Bit flags describing how the buffer can be used. + BufferUsageFlags usageFlags; + + // Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW. + BufferDataUsage dataUsage; + + std::string debugName; + + bool mapped; + MapType mappedType; + bool immutable; + + }; // Buffer +} // namespace love diff --git a/include/modules/graphics/Drawable.hpp b/include/modules/graphics/Drawable.hpp new file mode 100644 index 000000000..ff88b3875 --- /dev/null +++ b/include/modules/graphics/Drawable.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "common/Matrix.hpp" +#include "common/Object.hpp" + +namespace love +{ + class GraphicsBase; + + class Drawable : public Object + { + public: + static inline Type type = Type("Drawable", &Object::type); + + virtual ~Drawable() + {} + + virtual void draw(GraphicsBase* graphics, const Matrix4& transform) = 0; + }; +} // namespace love diff --git a/include/modules/graphics/Font.tcc b/include/modules/graphics/Font.tcc new file mode 100644 index 000000000..f1b9bbc1e --- /dev/null +++ b/include/modules/graphics/Font.tcc @@ -0,0 +1,182 @@ +#pragma once + +#include +#include +#include + +#include + +#include "common/Matrix.hpp" +#include "common/Object.hpp" +#include "common/StrongRef.hpp" +#include "common/Vector.hpp" + +#include "modules/font/Rasterizer.hpp" +#include "modules/font/TextShaper.hpp" + +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/Volatile.hpp" +#include "modules/graphics/vertex.hpp" + +namespace love +{ + class GraphicsBase; + + class FontBase : public Object, public Volatile + { + public: + static inline Type type = Type("Font", &Object::type); + + using Codepoints = std::vector; + using GlyphVertex = XYf_STf_RGBAf; + + enum AlignMode + { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT, + ALIGN_JUSTIFY, + ALIGN_MAX_ENUM + }; + + struct DrawCommand + { + TextureBase* texture; + int startVertex; + int vertexCount; + }; + + FontBase(Rasterizer* rasterizer, const SamplerState& samplerState); + + virtual ~FontBase(); + + std::vector generateVertices(const ColoredCodepoints& codepoints, Range range, + const Color& constantColor, + std::vector& vertices, + float extraSpacing = 0.0f, Vector2 offset = {}, + TextShaper::TextInfo* info = nullptr); + + std::vector generateVerticesFormatted(const ColoredCodepoints& text, + const Color& constantColor, float wrap, + AlignMode align, + std::vector& vertices, + TextShaper::TextInfo* info = nullptr); + + void print(GraphicsBase* graphics, const std::vector& text, const Matrix4& matrix, + const Color& color); + + void printf(GraphicsBase* gfx, const std::vector& text, float wrap, AlignMode align, + const Matrix4& m, const Color& constantColor); + + float getHeight() const; + + int getWidth(const std::string& text); + + int getWidth(uint32_t glyph); + + 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& ranges, + std::vector* line_widths = nullptr); + + void setLineHeight(float height); + + float getLineHeight() const; + + void setSamplerState(const SamplerState& state); + + const SamplerState& getSamplerState() const; + + int getAscent() const; + + int getDescent() const; + + int getBaseline() const; + + bool hasGlyph(uint32_t glyph) const; + + bool hasGlyphs(const std::string& text) const; + + float getKerning(uint32_t leftglyph, uint32_t rightglyph); + + float getKerning(const std::string& leftchar, const std::string& rightchar); + + void setFallbacks(const std::vector& fallbacks); + + float getDPIScale() const; + + uint32_t getTextureCacheID() const; + + void unloadVolatile() override; + + // clang-format off + STRINGMAP_DECLARE(AlignModes, AlignMode, + { "left", ALIGN_LEFT }, + { "center", ALIGN_CENTER }, + { "right", ALIGN_RIGHT }, + { "justify", ALIGN_JUSTIFY } + ); + // clang-format on + + struct Glyph + { + TextureBase* texture; + GlyphVertex vertices[4]; + }; + + static int fontCount; + + protected: + static inline uint64_t packGlyphIndex(TextShaper::GlyphIndex glyphindex) + { + return ((uint64_t)glyphindex.rasterizerIndex << 32) | (uint64_t)glyphindex.index; + } + + static inline TextShaper::GlyphIndex unpackGlyphIndex(uint64_t packed) + { + return { (int)(packed & 0xFFFFFFFF), (int)(packed >> 32) }; + } + + struct TextureSize + { + int width; + int height; + }; + + TextureSize getNextTextureSize() const; + + GlyphData* getRasterizerGlyphData(TextShaper::GlyphIndex glyphindex, float& dpiscale); + + const Glyph& findGlyph(TextShaper::GlyphIndex glyphindex); + + void printv(GraphicsBase* gfx, const Matrix4& t, const std::vector& drawcommands, + const std::vector& vertices); + + virtual const Glyph& addGlyph(TextShaper::GlyphIndex glyphindex); + + StrongRef shaper; + + int textureWidth; + int textureHeight; + + std::vector> textures; + std::unordered_map glyphs; + + PixelFormat pixelFormat; + SamplerState samplerState; + + float dpiScale; + + int textureX; + int textureY; + + int rowHeight; + + uint32_t textureCacheID; + + static constexpr int TEXTURE_PADDING = 2; + + virtual void createTexture() = 0; + }; +} // namespace love diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index bd0b88036..ceb470472 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -9,8 +9,19 @@ #include "common/int.hpp" #include "common/pixelformat.hpp" +#include "modules/font/Font.tcc" +#include "modules/math/MathModule.hpp" + +#include "modules/graphics/Font.tcc" +#include "modules/graphics/Mesh.hpp" +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/TextBatch.hpp" +#include "modules/graphics/Texture.tcc" #include "modules/graphics/Volatile.hpp" #include "modules/graphics/renderstate.hpp" +#include "modules/graphics/samplerstate.hpp" + +#include "driver/graphics/DrawCommand.hpp" #include #include @@ -25,27 +36,42 @@ namespace love using OptionalColor = Optional; - static bool gammaCorrect = false; - - inline void setGammaCorrect(bool enable) + inline void gammaCorrectColor(Color& color) { - gammaCorrect = enable; + if (love::isGammaCorrect()) + { + color.r = love::gammaToLinear(color.r); + color.g = love::gammaToLinear(color.g); + color.b = love::gammaToLinear(color.b); + } } - inline bool isGammaCorrect() + inline void unGammaCorrectColor(Color& color) { - return gammaCorrect; + if (love::isGammaCorrect()) + { + color.r = love::linearToGamma(color.r); + color.g = love::linearToGamma(color.g); + color.b = love::linearToGamma(color.b); + } } - void gammaCorrectColor(Color& color); + inline Color gammaCorrectColor(const Color& color) + { + Color result = color; + gammaCorrectColor(result); - void unGammaCorrectColor(Color& color); + return result; + } - Color gammaCorrectColor(const Color& color); + inline Color unGammaCorrectColor(const Color& color) + { + Color result = color; + unGammaCorrectColor(result); - Color unGammaCorrectColor(const Color& color); + return result; + } - template class GraphicsBase : public Module { public: @@ -81,6 +107,48 @@ namespace love LINE_JOIN_MAX_ENUM }; + enum Feature + { + FEATURE_MULTI_RENDER_TARGET_FORMATS, + FEATURE_CLAMP_ZERO, + FEATURE_CLAMP_ONE, + FEATURE_BLEND_MINMAX, + FEATURE_LIGHTEN, // Deprecated + FEATURE_FULL_NPOT, + FEATURE_PIXEL_SHADER_HIGHP, + FEATURE_SHADER_DERIVATIVES, + FEATURE_GLSL3, + FEATURE_GLSL4, + FEATURE_INSTANCING, + FEATURE_TEXEL_BUFFER, + FEATURE_INDEX_BUFFER_32BIT, + FEATURE_COPY_BUFFER, + FEATURE_COPY_BUFFER_TO_TEXTURE, + FEATURE_COPY_TEXTURE_TO_BUFFER, + FEATURE_COPY_RENDER_TARGET_TO_BUFFER, + FEATURE_MIPMAP_RANGE, + FEATURE_INDIRECT_DRAW, + FEATURE_MAX_ENUM + }; + + enum SystemLimit + { + LIMIT_POINT_SIZE, + LIMIT_TEXTURE_SIZE, + LIMIT_VOLUME_TEXTURE_SIZE, + LIMIT_CUBE_TEXTURE_SIZE, + LIMIT_TEXTURE_LAYERS, + LIMIT_TEXEL_BUFFER_SIZE, + LIMIT_SHADER_STORAGE_BUFFER_SIZE, + LIMIT_THREADGROUPS_X, + LIMIT_THREADGROUPS_Y, + LIMIT_THREADGROUPS_Z, + LIMIT_RENDER_TARGETS, + LIMIT_TEXTURE_MSAA, + LIMIT_ANISOTROPY, + LIMIT_MAX_ENUM + }; + enum StackType { STACK_ALL, @@ -94,6 +162,13 @@ namespace love TEMPORARY_RT_STENCIL = (1 << 1) }; + struct Capabilities + { + double limits[LIMIT_MAX_ENUM]; + bool features[FEATURE_MAX_ENUM]; + bool textureTypes[TEXTURE_MAX_ENUM]; + }; + struct RendererInfo { std::string name; @@ -113,172 +188,197 @@ namespace love int buffers; int64_t textureMemory; int64_t bufferMemory; + float cpuProcessingTime; + float gpuDrawingTime; }; - struct DisplayState + class TempTransform { - DisplayState() - {} - - Color color = Color::WHITE; - Color backgroundColor = Color::BLACK; - - BlendState blend = computeBlendState(BLEND_ALPHA, BLENDALPHA_MULTIPLY); + public: + TempTransform(GraphicsBase* graphics) : graphics(graphics) + { + graphics->pushTransform(); + } - float lineWidth = 1.0f; - LineStyle lineStyle = LINE_SMOOTH; - LineJoin lineJoin = LINE_JOIN_MITER; + TempTransform(GraphicsBase* graphics, const Matrix4& transform) : graphics(graphics) + { + graphics->pushTransform(); + graphics->transformStack.back() *= transform; + } - float pointSize = 1.0f; + ~TempTransform() + { + graphics->popTransform(); + } - bool scissor = false; - Rect scissorRect = Rect(); + private: + GraphicsBase* graphics; + }; - StencilState stencil; + struct ScreenshotInfo; + typedef void (*ScreenshotCallback)(const ScreenshotInfo* info, ImageData* i, void* ud); - CompareMode depthTest = COMPARE_ALWAYS; - bool depthWrite = false; + struct ScreenshotInfo + { + ScreenshotCallback callback = nullptr; + void* data = nullptr; + }; - CullMode meshCullMode = CULL_NONE; - Winding winding = WINDING_CCW; + struct RenderTargetStrongRef; - // StrongRef font; - // StrongRef shader; + struct RenderTarget + { + TextureBase* texture; + int slice; + int mipmap; - // RenderTargetsStrongRef renderTargets; + RenderTarget(TextureBase* texture, int slice = 0, int mipmap = 0) : + texture(texture), + slice(slice), + mipmap(mipmap) + {} - ColorChannelMask colorMask; + RenderTarget() : texture(nullptr), slice(0), mipmap(0) + {} - bool useCustomProjection = false; - Matrix4 customProjection; + bool operator!=(const RenderTarget& other) const + { + return this->texture != other.texture || this->slice != other.slice || + this->mipmap != other.mipmap; + } - // SamplerState defaultSampleState = SamplerState(); + bool operator!=(const RenderTargetStrongRef& other) const + { + return this->texture != other.texture.get() || this->slice != other.slice || + this->mipmap != other.mipmap; + } }; - GraphicsBase(const char* name) : - Module(M_GRAPHICS, name), - width(0), - height(0), - pixelWidth(0), - pixelHeight(0), - created(false), - active(true), - deviceProjectionMatrix() - { - this->transformStack.reserve(16); - this->transformStack.push_back(Matrix4()); + struct RenderTargetStrongRef + { + StrongRef texture; + int slice = 0; + int mipmap = 0; - this->pixelScaleStack.reserve(16); - this->pixelScaleStack.push_back(1.0); + RenderTargetStrongRef(TextureBase* texture, int slice = 0, int mipmap = 0) : + texture(texture), + slice(slice), + mipmap(mipmap) + {} - this->states.reserve(10); - this->states.push_back(DisplayState()); - } + bool operator!=(const RenderTargetStrongRef& other) const + { + return this->texture.get() != other.texture.get() || this->slice != other.slice || + this->mipmap != other.mipmap; + } + + bool operator!=(const RenderTarget& other) + { + return this->texture.get() != other.texture || this->slice != other.slice || + this->mipmap != other.mipmap; + } + }; - virtual ~GraphicsBase() + struct RenderTargetsStrongRef { - this->states.clear(); - } + std::vector colors; + RenderTargetStrongRef depthStencil; + uint32_t temporaryFlags; - void restoreState(const DisplayState& state) + RenderTargetsStrongRef() : depthStencil(nullptr), temporaryFlags(0) + {} + }; + + struct RenderTargets { - this->setColor(state.color); - this->setBackgroundColor(state.backgroundColor); + std::vector colors; + RenderTarget depthStencil; + uint32_t temporaryFlags; - this->setBlendState(state.blend); + RenderTargets() : depthStencil(nullptr), temporaryFlags(0) + {} - this->setLineWidth(state.lineWidth); - this->setLineStyle(state.lineStyle); - this->setLineJoin(state.lineJoin); + const RenderTarget& getFirstTarget() const + { + return this->colors.empty() ? this->depthStencil : this->colors[0]; + } - this->setPointSize(state.pointSize); + bool operator==(const RenderTargets& other) const + { + size_t numColors = this->colors.size(); + if (numColors != other.colors.size()) + return false; - if (state.scissor) - this->setScissor(state.scissorRect); - else - this->setScissor(); + for (size_t index = 0; index < numColors; index++) + { + if (this->colors[index] != other.colors[index]) + return false; + } - this->setMeshCullMode(state.meshCullMode); - this->setFrontFaceWinding(state.winding); + if (this->depthStencil != other.depthStencil || this->temporaryFlags != other.temporaryFlags) + return false; - // this->setFont(state.font.get()); - // this->setShader(state.shader.get()); - // this->setRenderTargets(state.renderTargets); + return true; + } + }; - // this->setStencilState(state.stencil); - // this->setDepthMode(state.depthTest, state.depthWrite); + struct DisplayState + { + DisplayState() + {} - this->setColorMask(state.colorMask); + Color color = Color(1.0f, 1.0f, 1.0f, 1.0f); + Color backgroundColor = Color(0.0f, 0.0f, 0.0f, 1.0f); - // this->setDefaultSamplerState(state.defaultSampleState); + BlendState blend = computeBlendState(BLEND_ALPHA, BLENDALPHA_MULTIPLY); - if (state.useCustomProjection) - this->updateDeviceProjection(state.customProjection); - else - this->resetProjection(); - } + float lineWidth = 1.0f; + LineStyle lineStyle = LINE_SMOOTH; + LineJoin lineJoin = LINE_JOIN_MITER; - void restoreStateChecked(const DisplayState& state) - { - const auto& current = this->states.back(); + float pointSize = 1.0f; - if (state.color != current.color) - this->setColor(state.color); + bool scissor = false; + Rect scissorRect = Rect(); - this->setBackgroundColor(state.backgroundColor); + StencilState stencil; - if (state.blend != current.blend) - this->setBlendState(state.blend); + CompareMode depthTest = COMPARE_ALWAYS; + bool depthWrite = false; - this->setLineWidth(state.lineWidth); - this->setLineStyle(state.lineStyle); - this->setLineJoin(state.lineJoin); + CullMode meshCullMode = CULL_NONE; + Winding winding = WINDING_CCW; - if (state.pointSize != current.pointSize) - this->setPointSize(state.pointSize); + StrongRef font; + StrongRef shader; - if (state.scissor != current.scissor || - (state.scissor && !(state.scissorRect != current.scissorRect))) - { - if (state.scissor) - this->setScissor(state.scissorRect); - else - this->setScissor(); - } + RenderTargetsStrongRef renderTargets; - this->setMeshCullMode(state.meshCullMode); + ColorChannelMask colorMask; - if (state.winding != current.winding) - this->setFrontFaceWinding(state.winding); + bool useCustomProjection = false; + Matrix4 customProjection; - // this->setFont(state.font.get()); - // this->setShader(state.shader.get()); + SamplerState defaultSamplerState = SamplerState(); + }; - // if (this->stencil != state.stencil) - // this->setStencilState(state.stencil); + GraphicsBase(const char* name); - // if (this->depthTest != state.depthTest || this->depthWrite != state.depthWrite) - // this->setDepthMode(state.depthTest, state.depthWrite); + virtual ~GraphicsBase(); - if (state.colorMask != current.colorMask) - this->setColorMask(state.colorMask); + void restoreState(const DisplayState& state); - // this->setDefaultSamplerState(state.defaultSampleState); + void restoreStateChecked(const DisplayState& state); - if (state.useCustomProjection) - this->updateDeviceProjection(state.customProjection); - else if (current.useCustomProjection) - this->resetProjection(); - } + bool isRenderTargetActive() const; - /* TODO: implement when they exist */ - bool isRenderTargetActive() const - { - return false; - } + bool isRenderTargetActive(TextureBase* texture) const; + + bool isRenderTargetActive(TextureBase* texture, int slice) const; void setActive(bool active) { + this->flushBatchedDraws(); this->active = active; } @@ -302,62 +402,43 @@ namespace love this->states.back().backgroundColor = color; } - void setScissor(const Rect& scissor) + Color getBackgroundColor() const { - auto& state = this->states.back(); - - Rect rect {}; - rect.x = scissor.x; - rect.y = scissor.y; - rect.w = std::max(0, scissor.w); - rect.h = std::max(0, scissor.h); - - static_cast(this)->setScissorImpl(scissor); + return this->states.back().backgroundColor; + } - state.scissorRect = rect; - state.scissor = true; + void setFont(FontBase* font) + { + this->states.back().font.set(font); } - void setScissor() + FontBase* getFont() { - this->states.back().scissor = false; - static_cast(this)->setScissorImpl(); + this->checkSetDefaultFont(); + return this->states.back().font.get(); } - void intersectScissor(const Rect& scissor) + const SamplerState& getDefaultSamplerState() const { - auto current = this->states.back().scissorRect; + return this->states.back().defaultSamplerState; + } - if (!this->states.back().scissor) - { - current.x = 0; - current.y = 0; + void setDefaultSamplerState(const SamplerState& state) + { + this->states.back().defaultSamplerState = state; + } - current.w = std::numeric_limits::max(); - current.h = std::numeric_limits::max(); - } + virtual void setScissor(const Rect& scissor) = 0; - int x1 = std::max(current.x, scissor.x); - int y1 = std::max(current.y, scissor.y); - int x2 = std::min(current.x + current.w, scissor.x + scissor.w); - int y2 = std::min(current.y + current.h, scissor.y + scissor.h); + virtual void setScissor() = 0; - Rect newScisssor = { x1, y1, std::max(0, x2 - x1), std::max(0, y2 - y1) }; - this->setScissor(newScisssor); - } + void setShader(); - bool getScissor(Rect& scissor) const - { - const auto& state = this->states.back(); + void setShader(ShaderBase* shader); - scissor = state.scissorRect; - return state.scissor; - } + void intersectScissor(const Rect& scissor); - Color getBackgroundColor() const - { - return this->states.back().backgroundColor; - } + bool getScissor(Rect& scissor) const; void setMeshCullMode(CullMode mode) { @@ -369,52 +450,55 @@ namespace love return this->states.back().meshCullMode; } - void setFrontFaceWinding(Winding winding) - { - static_cast(this)->setFrontFaceWindingImpl(winding); - this->states.back().winding = winding; - } + virtual void setFrontFaceWinding(Winding winding) = 0; - Winding getFrontFaceWinding() const - { - return this->states.back().winding; - } + Winding getFrontFaceWinding() const; - void setColorMask(ColorChannelMask mask) - { - static_cast(this)->setColorMaskImpl(mask); - this->states.back().colorMask = mask; - } + virtual void setColorMask(ColorChannelMask mask) = 0; - ColorChannelMask getColorMask() const - { - return this->states.back().colorMask; - } + ColorChannelMask getColorMask() const; - void setBlendMode(BlendMode mode, BlendAlpha alphaMode) - { - if (alphaMode == BLENDALPHA_MULTIPLY && !isAlphaMultiplyBlendSupported(mode)) - { - std::string_view modeName = "unknown"; - love::getConstant(mode, modeName); + void setBlendMode(BlendMode mode, BlendAlpha alphaMode); - throw love::Exception("The '{}' blend mode must be used with premultiplied alpha.", - modeName); - } + Quad* newQuad(Quad::Viewport viewport, double sourceWidth, double sourceHeight) const; - this->setBlendState(computeBlendState(mode, alphaMode)); - } + virtual TextureBase* newTexture(const TextureBase::Settings& settings, + const TextureBase::Slices* data = nullptr) = 0; + + virtual FontBase* newFont(Rasterizer* data) = 0; + + virtual FontBase* newDefaultFont(int size, const Rasterizer::Settings& settings) = 0; + + Mesh* newMesh(int vertexCount, PrimitiveType mode); + + TextBatch* newTextBatch(FontBase* font, const std::vector& text = {}); + + void checkSetDefaultFont(); + + void print(const std::vector& string, const Matrix4& matrix); + + void print(const std::vector& string, FontBase* font, const Matrix4& matrix); + + void printf(const std::vector& string, float wrap, FontBase::AlignMode align, + const Matrix4& matrix); + + void printf(const std::vector& string, FontBase* font, float wrap, + FontBase::AlignMode align, const Matrix4& matrix); + + virtual void initCapabilities() = 0; + + TextureBase* getDefaultTexture(TextureBase* texture); + + TextureBase* getDefaultTexture(TextureType type, DataBaseType dataType); BlendMode getBlendMode(BlendAlpha& alphaMode) const { return computeBlendMode(this->states.back().blend, alphaMode); } - void setBlendState(const BlendState& blend) - { - static_cast(this)->setBlendStateImpl(blend); - this->states.back().blend = blend; - } + virtual void setBlendState(const BlendState& blend) = 0; + + virtual void captureScreenshot(const ScreenshotInfo& info) = 0; const BlendState& getBlendState() const { @@ -451,95 +535,46 @@ namespace love return this->states.back().lineJoin; } - void setPointSize(float size) - { - this->states.back().pointSize = size; - } + virtual void setPointSize(float size) = 0; float getPointSize() const { return this->states.back().pointSize; } - PixelFormat getSizedFormat(PixelFormat format) + Capabilities getCapabilities() const { - switch (format) - { - case PIXELFORMAT_NORMAL: - if (isGammaCorrect()) - return PIXELFORMAT_RGBA8_sRGB; - else - return PIXELFORMAT_RGBA8_UNORM; - case PIXELFORMAT_HDR: - return PIXELFORMAT_RGBA16_FLOAT; - default: - return format; - } + return this->capabilities; } - // bool isPixelFormatSupported(PixelFormat format, uint32_t usage) - // { - // format = getSizedFormat(format); + PixelFormat getSizedFormat(PixelFormat format); - // bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; - // return (usage & pixelFormatUsage[format][readable ? 1 : 0]) == usage; - // } + RendererInfo getRendererInfo() const; - RendererInfo getRendererInfo() const - { + BatchedVertexData requestBatchedDraw(const BatchedDrawCommand& command); - RendererInfo info { .name = __RENDERER_NAME__, - .version = __RENDERER_VERSION__, - .vendor = __RENDERER_VENDOR__, - .device = __RENDERER_DEVICE__ }; + void flushBatchedDraws(); - return info; - } + static void flushBatchedDrawsGlobal(); - Stats getStats() const - { - Stats stats {}; + void advanceStreamBuffers(); - return stats; - } + static void advanceStreamBuffersGlobal(); - size_t getStackDepth() const - { - return this->stackTypeStack.size(); - } + virtual void draw(const DrawIndexedCommand& command) = 0; - void push(StackType type = STACK_TRANSFORM) - { - if (this->getStackDepth() == MAX_USER_STACK_DEPTH) - throw love::Exception("Maximum stack depth reached (more pushes than pops?)"); - - this->pushTransform(); - this->pixelScaleStack.push_back(this->pixelScaleStack.back()); - - if (type == STACK_ALL) - this->states.push_back(this->states.back()); + virtual void draw(const DrawCommand& command) = 0; - this->stackTypeStack.push_back(type); - } + Stats getStats() const; - void pop() + size_t getStackDepth() const { - if (this->getStackDepth() < 1) - throw love::Exception("Minimum stack depth reached (more pops than pushes?)"); - - this->popTransform(); - this->pixelScaleStack.pop_back(); + return this->stackTypeStack.size(); + } - if (this->stackTypeStack.back() == STACK_ALL) - { - DisplayState state = this->states[this->states.size() - 2]; - this->restoreStateChecked(state); + void push(StackType type = STACK_TRANSFORM); - this->states.pop_back(); - } - - this->stackTypeStack.pop_back(); - } + void pop(); const Matrix4& getTransform() const { @@ -588,40 +623,17 @@ namespace love this->pixelScaleStack.back() = 1.0; } - void applyTransform(const Matrix4& transform) - { - Matrix4& current = this->transformStack.back(); - current *= transform; - - float sx, sy; - current.getApproximateScale(sx, sy); - this->pixelScaleStack.back() *= (sx + sy) / 2.0; - } + int getWidth() const; - void replaceTransform(const Matrix4& transform) - { - this->transformStack.back() = transform; + int getHeight() const; - float sx, sy; - transform.getApproximateScale(sx, sy); - this->pixelScaleStack.back() = (sx + sy) / 2.0; - } + void applyTransform(const Matrix4& transform); - Vector2 transformPoint(Vector2 point) const - { - Vector2 result {}; - this->transformStack.back().transformXY(&result, &point, 1); + void replaceTransform(const Matrix4& transform); - return result; - } + Vector2 transformPoint(Vector2 point) const; - Vector2 inverseTransformPoint(Vector2 point) const - { - Vector2 result {}; - this->transformStack.back().inverse().transformXY(&result, &point, 1); - - return result; - } + Vector2 inverseTransformPoint(Vector2 point) const; void updateDeviceProjection(const Matrix4& projection) { @@ -633,76 +645,72 @@ namespace love return this->deviceProjectionMatrix; } - void resetProjection() - { - auto& state = this->states.back(); + void resetProjection(); - state.useCustomProjection = false; - this->updateDeviceProjection(Matrix4::ortho(0.0f, 0, 0, 0.0f, -10.0f, 10.0f)); - } + void reset(); - void reset() - { - DisplayState state {}; - this->restoreState(state); + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) = 0; - this->origin(); - } + virtual void clear(const std::vector& colors, OptionalInt stencil, + OptionalDouble depth) = 0; - void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) - { - static_cast(this)->clearImpl(color, stencil, depth); - } + virtual void present(void* screenshotCallbackData) = 0; - void clear(const std::vector& colors, OptionalInt stencil, - OptionalDouble depth) - { - if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) - return; + virtual bool setMode(int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, + bool backbufferdepth, int msaa) = 0; - const int numColors = (int)colors.size(); + virtual void unsetMode() = 0; - if (numColors <= 1) - { - this->clear(colors.size() > 0 ? colors[0] : OptionalColor(), stencil, depth); - return; - } - } + virtual void setActiveScreen() + {} - void present() - { - static_cast(this)->presentImpl(); - } + virtual void setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) = 0; - bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, - bool backBufferDepth, int msaa) - { - static_cast(this)->setModeImpl(width, height, pixelWidth, pixelHeight, - backBufferStencil, backBufferDepth, msaa); + virtual bool isPixelFormatSupported(PixelFormat format, uint32_t usage) = 0; - this->created = true; + double getCurrentDPIScale() const; - if (!Volatile::loadAll()) - std::printf("Failed to load all volatile objects.\n"); + double getScreenDPIScale() const; - this->restoreState(this->states.back()); + void polyline(std::span vertices); - return true; - } + void polygon(DrawMode mode, std::span vertices, bool skipLastFilledVertex = true); - void unsetMode() - { - static_cast(this)->unsetModeImpl(); - } + void rectangle(DrawMode mode, float x, float y, float w, float h); - double getCurrentDPIScale() - { - return 1.0; - } + void rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry, int points); + + void rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry); + + void circle(DrawMode mode, float x, float y, float radius, int points); + + void circle(DrawMode mode, float x, float y, float radius); + + void ellipse(DrawMode mode, float x, float y, float a, float b, int points); + + void ellipse(DrawMode mode, float x, float y, float a, float b); + + void arc(DrawMode mode, ArcMode arcMode, float x, float y, float radius, float angle1, float angle2, + int points); + + void arc(DrawMode mode, ArcMode arcMode, float x, float y, float radius, float angle1, float angle2); - double getScreenDPIScale() + void points(const Vector2* points, const Color* colors, int count); + + void draw(Drawable* drawable, const Matrix4& matrix); + + void draw(TextureBase* texture, Quad* quad, const Matrix4& matrix); + + template + T* getScratchBuffer(size_t count) { - return 1.0; + size_t bytes = count * sizeof(T); + + if (this->scratchBuffer.size() < bytes) + this->scratchBuffer.resize(bytes); + + return (T*)this->scratchBuffer.data(); } // clang-format off @@ -734,7 +742,12 @@ namespace love ); // clang-format on + private: + TextureBase* defaultTextures[TEXTURE_MAX_ENUM]; + protected: + int calculateEllipsePoints(float a, float b) const; + bool created; bool active; @@ -751,5 +764,20 @@ namespace love int pixelWidth; int pixelHeight; + + int drawCallsBatched; + int drawCalls; + + BatchedDrawState batchedDrawState; + std::vector scratchBuffer; + + float cpuProcessingTime; + float gpuDrawingTime; + + Capabilities capabilities; + + StrongRef defaultFont; + + std::vector pendingScreenshotCallbacks; }; } // namespace love diff --git a/include/modules/graphics/Mesh.hpp b/include/modules/graphics/Mesh.hpp new file mode 100644 index 000000000..a287e7f55 --- /dev/null +++ b/include/modules/graphics/Mesh.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "common/Range.hpp" +#include "common/StrongRef.hpp" +#include "common/int.hpp" + +#include "modules/graphics/Drawable.hpp" +#include "modules/graphics/Texture.tcc" + +#include + +namespace love +{ + class GraphicsBase; + + class Mesh : public Drawable + { + public: + static Type type; + + Mesh(GraphicsBase* graphics, const void* data, size_t size, PrimitiveType mode, + BufferDataUsage usage); + + Mesh(GraphicsBase* graphics, int vertexCount, PrimitiveType mode, BufferDataUsage usage); + + virtual ~Mesh(); + + void* checkVertexDataOffset(size_t index, size_t* byteOffset); + + size_t getVertexCount() const; + + size_t getVertexStride() const; + + Vertex* getVertexBuffer(); + + void* getVertexData(); + + void setVertexDataModified(size_t offset, size_t size); + + void flush(); + + void setVertexMap(const std::vector& map); + + void setVertexMap(IndexDataType type, const void* data, size_t size); + + void setVertexMap(); + + bool getVertexMap(std::vector& map) const; + + void setIndexBuffer(std::vector& buffer); + + uint16_t* getIndexBuffer(); + + size_t getIndexCount() const; + + void setTexture(TextureBase* texture); + + void setTexture(); + + TextureBase* getTexture() const; + + void setDrawMode(PrimitiveType mode); + + PrimitiveType getDrawMode() const; + + void setDrawRange(int start, int count); + + void setDrawRange(); + + bool getDrawRange(int& start, int& count) const; + + void draw(GraphicsBase* graphics, const Matrix4& matrix); + + void draw(GraphicsBase* graphics, const Matrix4& matrix, int instanceCount); + + // static std::vector getDefaultVertexFormat(); + + private: + friend class SpriteBatch; + + void drawInternal(GraphicsBase* graphics, const Matrix4& matrix, int instanceCount, int argsIndex); + + std::vector buffer; + Vertex* vertexBuffer = nullptr; + + std::vector indices; + uint16_t* indexBuffer = nullptr; + + Range modifiedVertexData = Range(); + + size_t vertexCount = 0; + size_t vertexStride = 0; + + bool indexDataModified = false; + bool useIndexBuffer = false; + size_t indexCount = 0; + IndexDataType indexDataType = INDEX_UINT16; + + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; + + Range drawRange = Range(); + StrongRef texture; + }; +} // namespace love diff --git a/include/modules/graphics/Polyline.hpp b/include/modules/graphics/Polyline.hpp new file mode 100644 index 000000000..adf6290df --- /dev/null +++ b/include/modules/graphics/Polyline.hpp @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2006-2024 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/Vector.hpp" +#include "modules/graphics/vertex.hpp" + +// C++ +#include +#include + +namespace love +{ + class GraphicsBase; + + /** + * Abstract base class for a chain of segments. + * @author Matthias Richter + **/ + class Polyline + { + public: + Polyline(TriangleIndexMode mode = TRIANGLEINDEX_STRIP) : + vertices(nullptr), + overdraw(nullptr), + vertex_count(0), + overdraw_vertex_count(0), + triangle_mode(mode), + overdraw_vertex_start(0) + {} + + virtual ~Polyline(); + + /** + * @param vertices Vertices defining the core line segments + * @param count Number of vertices + * @param size_hint Expected number of vertices of the rendering sleeve around the core line. + * @param halfwidth linewidth / 2. + * @param pixel_size Dimension of one pixel on the screen in world coordinates. + * @param draw_overdraw Fake antialias the line. + */ + void render(const Vector2* vertices, size_t count, size_t size_hint, float halfwidth, + float pixel_size, bool draw_overdraw); + + /** Draws the line on the screen + */ + void draw(GraphicsBase* gfx); + + protected: + virtual void calc_overdraw_vertex_count(bool is_looping); + virtual void render_overdraw(const std::vector& normals, float pixel_size, bool is_looping); + virtual void fill_color_array(Color constant_color, XYf_STf_RGBAf* attributes, int count); + + /** 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] 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& segment, float& segmentLength, Vector2& segmentNormal, + const Vector2& pointA, const Vector2& pointB, float halfWidth) = 0; + + Vector2* vertices; + Vector2* overdraw; + size_t vertex_count; + size_t overdraw_vertex_count; + TriangleIndexMode triangle_mode; + size_t overdraw_vertex_start; + + }; // Polyline + + /** + * A Polyline whose segments are not connected. + * @author Matthias Richter + */ + class NoneJoinPolyline : public Polyline + { + public: + NoneJoinPolyline() : Polyline(TRIANGLEINDEX_QUADS) + {} + + void render(const Vector2* vertices, size_t count, float halfwidth, float pixel_size, + bool draw_overdraw) + { + Polyline::render(vertices, count, 4 * count - 4, halfwidth, pixel_size, draw_overdraw); + + // discard the first and last two vertices. (these are redundant) + for (size_t i = 0; i < vertex_count - 4; ++i) + this->vertices[i] = this->vertices[i + 2]; + + // The last quad is now garbage, so zero it out to make sure it doesn't + // get rasterized. These vertices are in between the core line vertices + // and the overdraw vertices in the combined vertex array, so they still + // get "rendered" since we draw everything with one draw call. + memset(&this->vertices[vertex_count - 4], 0, sizeof(love::Vector2) * 4); + + vertex_count -= 4; + } + + protected: + void calc_overdraw_vertex_count(bool is_looping) override; + void render_overdraw(const std::vector& normals, float pixel_size, bool is_looping) override; + void fill_color_array(Color constant_color, XYf_STf_RGBAf* attributes, int count) override; + void renderEdge(std::vector& anchors, std::vector& normals, Vector2& s, + float& len_s, Vector2& ns, const Vector2& q, const Vector2& r, float hw) override; + + }; // NoneJoinPolyline + + /** + * A Polyline whose segments are connected by a sharp edge. + * @author Matthias Richter + */ + class MiterJoinPolyline : public Polyline + { + public: + void render(const Vector2* vertices, size_t count, float halfwidth, float pixel_size, + bool draw_overdraw) + { + Polyline::render(vertices, count, 2 * count, halfwidth, pixel_size, draw_overdraw); + } + + protected: + void renderEdge(std::vector& anchors, std::vector& normals, Vector2& s, + float& len_s, Vector2& ns, const Vector2& q, const Vector2& r, float hw) override; + + }; // MiterJoinPolyline + + /** + * A Polyline whose segments are connected by a flat edge. + * @author Matthias Richter + */ + class BevelJoinPolyline : public Polyline + { + public: + void render(const Vector2* vertices, size_t count, float halfwidth, float pixel_size, + bool draw_overdraw) + { + Polyline::render(vertices, count, 4 * count - 4, halfwidth, pixel_size, draw_overdraw); + } + + protected: + void renderEdge(std::vector& anchors, std::vector& normals, Vector2& s, + float& len_s, Vector2& ns, const Vector2& q, const Vector2& r, float hw) override; + + }; // BevelJoinPolyline +} // namespace love diff --git a/include/modules/graphics/Quad.hpp b/include/modules/graphics/Quad.hpp new file mode 100644 index 000000000..ca4e95c8b --- /dev/null +++ b/include/modules/graphics/Quad.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include "common/Object.hpp" +#include "common/Vector.hpp" +#include "common/math.hpp" + +namespace love +{ + class Quad : public Object + { + public: + static Type type; + + struct Viewport + { + double x, y; + double w, h; + }; + + Quad(const Viewport& viewport, double sourceWidth, double sourceHeight); + + virtual ~Quad(); + + void refresh(const Viewport& viewport, double sourceWidth, double sourceHeight); + + void setViewport(const Viewport& viewport) + { + this->refresh(viewport, this->sourceWidth, this->sourceHeight); + } + + void setTextureCoordinate(int index, const Vector2& coordinate) + { + this->textureCoordinates[index] = coordinate; + } + + Viewport getViewport() const + { + return this->viewport; + } + + void setLayer(int layer) + { + this->layer = layer; + } + + int getLayer() const + { + return this->layer; + } + + double getTextureWidth() const + { + return this->sourceWidth; + } + + double getTextureHeight() const + { + return this->sourceHeight; + } + + const Vector2* getVertexPositions() const + { + return this->vertexPositions; + } + + const Vector2* getTextureCoordinates() const + { + return this->textureCoordinates; + } + + private: + Vector2 vertexPositions[4]; + Vector2 textureCoordinates[4]; + + Viewport viewport; + double sourceWidth; + double sourceHeight; + + int layer; + }; +} // namespace love diff --git a/include/modules/graphics/Resource.hpp b/include/modules/graphics/Resource.hpp new file mode 100644 index 000000000..a42ee65eb --- /dev/null +++ b/include/modules/graphics/Resource.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace love +{ + class Resource + { + public: + virtual ~Resource() + {} + + virtual ptrdiff_t getHandle() const = 0; + }; +} // namespace love diff --git a/include/modules/graphics/Shader.tcc b/include/modules/graphics/Shader.tcc new file mode 100644 index 000000000..de2f850e1 --- /dev/null +++ b/include/modules/graphics/Shader.tcc @@ -0,0 +1,49 @@ +#pragma once + +#include "common/Map.hpp" +#include "common/Object.hpp" + +#include "modules/graphics/Resource.hpp" + +#include +#include +#include + +#include + +namespace love +{ + class ShaderBase : public Object, public Resource + { + public: + static Type type; + + static int shaderSwitches; + + enum StandardShader + { + STANDARD_DEFAULT, + STANDARD_TEXTURE, + STANDARD_VIDEO, + STANDARD_MAX_ENUM + }; + + static ShaderBase* current; + static ShaderBase* standardShaders[STANDARD_MAX_ENUM]; + + ShaderBase() + {} + + ShaderBase(StandardShader type); + + virtual ~ShaderBase(); + + virtual void attach() = 0; + + static void attachDefault(StandardShader type); + + static bool isDefaultActive(); + + StandardShader shaderType; + }; +} // namespace love diff --git a/include/modules/graphics/TextBatch.hpp b/include/modules/graphics/TextBatch.hpp new file mode 100644 index 000000000..afc4b7391 --- /dev/null +++ b/include/modules/graphics/TextBatch.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "common/Range.hpp" + +#include "modules/graphics/Buffer.tcc" +#include "modules/graphics/Drawable.hpp" +#include "modules/graphics/Font.tcc" + +namespace love +{ + class TextBatch : public Drawable + { + public: + static Type type; + + TextBatch(FontBase* font, const std::vector& text = {}); + + virtual ~TextBatch(); + + void set(const std::vector& text); + + void set(const std::vector& text, float wrap, FontBase::AlignMode align); + + int add(const std::vector& text, const Matrix4& matrix); + + int addf(const std::vector& text, float wrap, FontBase::AlignMode align, + const Matrix4& matrix); + + void clear(); + + void setFont(FontBase* font); + + FontBase* getFont() const; + + int getWidth(int index = 0) const; + + int getHeight(int index = 0) const; + + void draw(GraphicsBase* graphics, const Matrix4& matrix) override; + + private: + struct TextData + { + ColoredCodepoints codepoints; + float wrap; + FontBase::AlignMode align; + TextShaper::TextInfo textInfo; + bool useMatrix; + bool appendVertices; + Matrix4 matrix; + }; + + void uploadVertices(const std::vector& vertices, size_t offset); + + void regenerateVertices(); + + void addTextData(const TextData& data); + + StrongRef font; + + VertexAttributes vertexAttributes; + BufferBindings vertexBuffers; + + StrongRef vertexBuffer; + + std::vector buffer; + Range modifiedVertices; + + std::vector drawCommands; + std::vector textData; + + size_t vertexOffset; + uint32_t textureCacheID; + }; +} // namespace love diff --git a/include/modules/graphics/Texture.tcc b/include/modules/graphics/Texture.tcc new file mode 100644 index 000000000..42ea45377 --- /dev/null +++ b/include/modules/graphics/Texture.tcc @@ -0,0 +1,422 @@ +#pragma once + +#include "common/Exception.hpp" +#include "common/Map.hpp" +#include "common/Optional.hpp" +#include "common/int.hpp" +#include "common/math.hpp" +#include "common/pixelformat.hpp" + +#include "modules/graphics/Drawable.hpp" +#include "modules/graphics/Quad.hpp" +#include "modules/graphics/Resource.hpp" +#include "modules/graphics/renderstate.hpp" +#include "modules/graphics/samplerstate.hpp" +#include "modules/graphics/vertex.hpp" + +#include "modules/image/CompressedImageData.hpp" +#include "modules/image/Image.hpp" +#include "modules/image/ImageData.hpp" + +#include +#include + +namespace love +{ + class GraphicsBase; + + class BufferBase; + + enum TextureType + { + TEXTURE_2D, + TEXTURE_VOLUME, + TEXTURE_2D_ARRAY, + TEXTURE_CUBE, + TEXTURE_MAX_ENUM + }; + + enum PixelFormatUsage + { + PIXELFORMATUSAGE_SAMPLE, // Any sampling in shaders. + PIXELFORMATUSAGE_LINEAR, // Linear filtering. + PIXELFORMATUSAGE_RENDERTARGET, // Usable as a render target. + PIXELFORMATUSAGE_BLEND, // Blend support when used as a render target. + PIXELFORMATUSAGE_MSAA, // MSAA support when used as a render target. + PIXELFORMATUSAGE_COMPUTEWRITE, // Writable in compute shaders via imageStore. + PIXELFORMATUSAGE_MAX_ENUM + }; + + enum PixelFormatUsageFlags + { + PIXELFORMATUSAGEFLAGS_NONE = 0, + PIXELFORMATUSAGEFLAGS_SAMPLE = (1 << PIXELFORMATUSAGE_SAMPLE), + PIXELFORMATUSAGEFLAGS_LINEAR = (1 << PIXELFORMATUSAGE_LINEAR), + PIXELFORMATUSAGEFLAGS_RENDERTARGET = (1 << PIXELFORMATUSAGE_RENDERTARGET), + PIXELFORMATUSAGEFLAGS_BLEND = (1 << PIXELFORMATUSAGE_BLEND), + PIXELFORMATUSAGEFLAGS_MSAA = (1 << PIXELFORMATUSAGE_MSAA), + PIXELFORMATUSAGEFLAGS_COMPUTEWRITE = (1 << PIXELFORMATUSAGE_COMPUTEWRITE), + }; + + class TextureBase : public Drawable, public Resource + { + public: + static inline Type type = Type("Texture", &Drawable::type); + + static int textureCount; + + enum MipmapsMode + { + MIPMAPS_NONE, + MIPMAPS_AUTO, + MIPMAPS_MANUAL, + MIPMAPS_MAX_ENUM + }; + + enum SettingType + { + SETTING_WIDTH, + SETTING_HEIGHT, + SETTING_LAYERS, + SETTING_MIPMAPS, + SETTING_MIPMAP_COUNT, + SETTING_FORMAT, + SETTING_LINEAR, + SETTING_TYPE, + SETTING_DPI_SCALE, + SETTING_MSAA, + SETTING_RENDER_TARGET, + SETTING_COMPUTE_WRITE, + SETTING_VIEW_FORMATS, + SETTING_READABLE, + SETTING_DEBUGNAME, + SETTING_MAX_ENUM + }; + + struct Settings + { + int width = 1; + int height = 1; + int layers = 1; // depth for 3D textures + TextureType type = TEXTURE_2D; + MipmapsMode mipmaps = MIPMAPS_NONE; + int mipmapCount = 0; // only used when > 0. + PixelFormat format = PIXELFORMAT_NORMAL; + bool linear = false; + float dpiScale = 1.0f; + int msaa = 1; + bool renderTarget = false; + bool computeWrite = false; + std::vector viewFormats; + OptionalBool readable; + std::string debugName; + }; + + struct ViewSettings + { + Optional format; + Optional type; + OptionalInt mipmapStart; + OptionalInt mipmapCount; + OptionalInt layerStart; + OptionalInt layerCount; + std::string debugName; + }; + + struct Slices + { + public: + Slices(TextureType textype) : textureType(textype) + {} + + void clear() + { + this->data.clear(); + } + + void set(int slice, int mipmap, ImageDataBase* imageData); + + ImageDataBase* get(int slice, int mipmap) const; + + void add(CompressedImageData* compressedData, int startslice, int startmip, bool addallslices, + bool addallmips); + + int getSliceCount(int mip = 0) const; + + int getMipmapCount(int slice = 0) const; + + bool validate() const; + + TextureType getTextureType() const + { + return this->textureType; + } + + private: + TextureType textureType; + + // For 2D/Cube/2DArray texture types, each element in the data array has + // an array of mipmap levels. For 3D texture types, each mipmap level + // has an array of layers. + std::vector>> data; + + }; // Slices + + struct ViewInfo + { + TextureBase* texture; + int startMipmap; + int startLayer; + }; + + static int64_t totalGraphicsMemory; + + TextureType getTextureType() const + { + return this->textureType; + } + + PixelFormat getPixelFormat() const + { + return this->format; + } + + MipmapsMode getMipmapsMode() const + { + return this->mipmapsMode; + } + + bool isRenderTarget() const + { + return this->renderTarget; + } + + bool isComputeWritable() const + { + return this->computeWrite; + } + + bool isReadable() const + { + return this->readable; + } + + int getMSAA() const + { + return this->requestedMSAA; + } + + const std::vector& getViewFormats() const + { + return this->viewFormats; + } + + virtual void updateQuad(Quad* quad) + {} + + virtual void draw(GraphicsBase* graphics, const Matrix4& matrix) override; + + void draw(GraphicsBase* graphics, Quad* quad, const Matrix4& matrix); + + bool isCompressed() const + { + return love::isPixelFormatCompressed(this->format); + } + + /* TODO: make the wrapper also check isGammaCorrect */ + bool isFormatLinear() const + { + return !isPixelFormatSRGB(this->format); + } + + const ViewInfo& getRootViewInfo() const + { + return this->rootView; + } + + const ViewInfo& getParentViewInfo() const + { + return this->parentView; + } + + bool isValidSlice(int slice, int mip) const + { + return slice >= 0 && slice < this->getSliceCount(mip); + } + + int getSliceCount(int mip) const; + + void generateMipmaps(); + + const std::string& getDebugName() const + { + return this->debugName; + } + + int getWidth(int mipmap = 0) const + { + return std::max(this->width >> mipmap, 1); + } + + int getHeight(int mipmap = 0) const + { + return std::max(this->height >> mipmap, 1); + } + + int getDepth(int mipmap = 0) const + { + return std::max(this->depth >> mipmap, 1); + } + + int getLayerCount() const + { + return this->layers; + } + + int getMipmapCount() const + { + return this->mipmapCount; + } + + int getPixelWidth(int mipmap = 0) const + { + return std::max(this->pixelWidth >> mipmap, 1); + } + + int getPixelHeight(int mipmap = 0) const + { + return std::max(this->pixelHeight >> mipmap, 1); + } + + float getDPIScale() const + { + return (float)this->pixelHeight / (float)this->height; + } + + int getRequestedMSAA() const + { + return this->requestedMSAA; + } + + const SamplerState& getSamplerState() const + { + return this->samplerState; + } + + virtual ptrdiff_t getRenderTargetHandle() const = 0; + + virtual ptrdiff_t getSamplerHandle() const = 0; + + /* + * Sets the handle of the texture. + * This is ONLY used on 3DS due to memory constraints. + * The font textures are already on-device! + */ + virtual void setHandleData(ptrdiff_t data) = 0; + + virtual void setSamplerState(const SamplerState& state) = 0; + + void replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, bool reloadMipmaps); + + void replacePixels(const void* data, size_t size, int slice, int mipmap, const Rect& rect, + bool reloadMipmaps); + + SamplerState validateSamplerState(SamplerState state) const; + + static int getTotalMipmapCount(int width, int height) + { + return (int)std::log2(std::max(width, height)) + 1; + } + + static int getTotalMipmapCount(int width, int height, int depth) + { + return (int)std::log2(std::max(std::max(width, height), depth)) + 1; + } + + // clang-format off + STRINGMAP_DECLARE(TextureTypes, TextureType, + { "2d", TEXTURE_2D }, + { "volume", TEXTURE_VOLUME }, + { "2darray", TEXTURE_2D_ARRAY }, + { "cube", TEXTURE_CUBE } + ); + + STRINGMAP_DECLARE(MipmapsModes, MipmapsMode, + { "none", MIPMAPS_NONE }, + { "auto", MIPMAPS_AUTO }, + { "manual", MIPMAPS_MANUAL } + ); + + STRINGMAP_DECLARE(SettingTypes, SettingType, + { "width", SETTING_WIDTH }, + { "height", SETTING_HEIGHT }, + { "layers", SETTING_LAYERS }, + { "mipmaps", SETTING_MIPMAPS }, + { "mipmapcount", SETTING_MIPMAP_COUNT }, + { "format", SETTING_FORMAT }, + { "linear", SETTING_LINEAR }, + { "type", SETTING_TYPE }, + { "dpiscale", SETTING_DPI_SCALE }, + { "msaa", SETTING_MSAA }, + { "rendertarget", SETTING_RENDER_TARGET }, + { "computewrite", SETTING_COMPUTE_WRITE }, + { "viewformats", SETTING_VIEW_FORMATS }, + { "readable", SETTING_READABLE }, + { "debugname", SETTING_DEBUGNAME } + ); + // clang-format on + + protected: + TextureBase(GraphicsBase* graphics, const Settings& settings, const Slices* slices); + + virtual ~TextureBase(); + + void setGraphicsMemorySize(int64_t size); + + void uploadImageData(ImageDataBase* data, int level, int slice, int x, int y); + + virtual void uploadByteData(const void* data, size_t size, int level, int slice, + const Rect& rect) = 0; + + bool supportsGenerateMipmaps(const char*& outReason) const; + + virtual void generateMipmapsInternal() = 0; + + bool validateDimensions(bool throwException) const; + + void validatePixelFormat(GraphicsBase* graphics) const; + + TextureType textureType; + PixelFormat format; + bool renderTarget; + bool computeWrite; + bool readable; + + std::vector viewFormats; + MipmapsMode mipmapsMode; + + int width; + int height; + + int depth; + int layers; + int mipmapCount; + + int pixelWidth; + int pixelHeight; + + int requestedMSAA; + + SamplerState samplerState; + StrongRef quad; + + int64_t graphicsMemorySize; + std::string debugName; + + ViewInfo rootView; + ViewInfo parentView; + + private: + void validateViewFormats() const; + }; +} // namespace love diff --git a/include/modules/graphics/freetype/Font.hpp b/include/modules/graphics/freetype/Font.hpp new file mode 100644 index 000000000..1907299b6 --- /dev/null +++ b/include/modules/graphics/freetype/Font.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "modules/font/freetype/TrueTypeRasterizer.hpp" +#include "modules/graphics/Font.tcc" + +namespace love +{ + class Font : public FontBase + { + public: + Font(Rasterizer* rasterizer, const SamplerState& samplerState); + + virtual void createTexture() override; + + bool loadVolatile() override; + }; +} // namespace love diff --git a/include/modules/graphics/renderstate.hpp b/include/modules/graphics/renderstate.hpp index 0dc4aa8b1..e53f587f4 100644 --- a/include/modules/graphics/renderstate.hpp +++ b/include/modules/graphics/renderstate.hpp @@ -147,8 +147,8 @@ namespace love bool operator==(const StencilState& s) const { - return compare == s.compare && action == s.action && value == s.value && - readMask == s.readMask && writeMask == s.writeMask; + return compare == s.compare && action == s.action && value == s.value && readMask == s.readMask && + writeMask == s.writeMask; } }; @@ -202,12 +202,12 @@ namespace love { "screen", BLEND_SCREEN }, { "replace", BLEND_REPLACE }, { "none", BLEND_NONE }, - { "custom", BLEND_CUSTOM }, + { "custom", BLEND_CUSTOM } ); STRINGMAP_DECLARE(BlendAlphaModes, BlendAlpha, { "alphamultiply", BLENDALPHA_MULTIPLY }, - { "premultiplied", BLENDALPHA_PREMULTIPLIED }, + { "premultiplied", BLENDALPHA_PREMULTIPLIED } ); STRINGMAP_DECLARE(BlendFactors, BlendFactor, @@ -247,7 +247,7 @@ namespace love { "decrement", STENCIL_DECREMENT }, { "incrementwrap", STENCIL_INCREMENT_WRAP }, { "decrementwrap", STENCIL_DECREMENT_WRAP }, - { "invert", STENCIL_INVERT }, + { "invert", STENCIL_INVERT } ); STRINGMAP_DECLARE(CompareModes, CompareMode, diff --git a/include/modules/graphics/samplerstate.hpp b/include/modules/graphics/samplerstate.hpp new file mode 100644 index 000000000..b7152435f --- /dev/null +++ b/include/modules/graphics/samplerstate.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "common/Map.hpp" +#include "common/Optional.hpp" +#include "common/int.hpp" + +#include "modules/graphics/renderstate.hpp" + +namespace love +{ + struct SamplerState + { + enum WrapMode + { + WRAP_CLAMP, + WRAP_CLAMP_ZERO, + WRAP_CLAMP_ONE, + WRAP_REPEAT, + WRAP_MIRRORED_REPEAT, + WRAP_MAX_ENUM + }; + + enum FilterMode + { + FILTER_LINEAR, + FILTER_NEAREST, + FILTER_MAX_ENUM + }; + + enum MipmapFilterMode + { + MIPMAP_FILTER_NONE, + MIPMAP_FILTER_LINEAR, + MIPMAP_FILTER_NEAREST, + MIPMAP_FILTER_MAX_ENUM + }; + + FilterMode minFilter = FILTER_LINEAR; + FilterMode magFilter = FILTER_LINEAR; + MipmapFilterMode mipmapFilter = MIPMAP_FILTER_NONE; + + WrapMode wrapU = WRAP_CLAMP; + WrapMode wrapV = WRAP_CLAMP; + WrapMode wrapW = WRAP_CLAMP; + + float lodBias = 0.0f; + + uint8_t maxAnisotropy = 1; + + uint8_t minLod = 0; + uint8_t maxLod = LOVE_UINT8_MAX; + + Optional depthSampleMode; + + uint64_t toKey() const; + + static SamplerState fromKey(uint64_t key); + + static bool isClampZeroOrOne(WrapMode mode) + { + return mode == WRAP_CLAMP_ONE || mode == WRAP_CLAMP_ZERO; + } + + // clang-format off + STRINGMAP_DECLARE(FilterModes, FilterMode, + { "linear", FILTER_LINEAR }, + { "nearest", FILTER_NEAREST } + ); + + STRINGMAP_DECLARE(MipmapFilterModes, MipmapFilterMode, + { "none", MIPMAP_FILTER_NONE }, + { "linear", MIPMAP_FILTER_LINEAR }, + { "nearest", MIPMAP_FILTER_NEAREST } + ); + + STRINGMAP_DECLARE(WrapModes, WrapMode, + { "clamp", WRAP_CLAMP }, + { "clampzero", WRAP_CLAMP_ZERO }, + { "clampone", WRAP_CLAMP_ONE }, + { "repeat", WRAP_REPEAT }, + { "mirroredrepeat", WRAP_MIRRORED_REPEAT } + ); + // clang-format on + }; +} // namespace love diff --git a/include/modules/graphics/vertex.hpp b/include/modules/graphics/vertex.hpp index 23b2be538..719cfe9a1 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -6,8 +6,71 @@ #include "common/Vector.hpp" +#include + namespace love { + class Resource; + + enum BuiltinVertexAttribute + { + ATTRIB_POS = 0, + ATTRIB_TEXCOORD, + ATTRIB_COLOR, + ATTRIB_MAX_ENUM + }; + + enum BuiltinVertexAttributeFlags + { + ATTRIBFLAG_POS = 1 << ATTRIB_POS, + ATTRIBFLAG_TEXCOORD = 1 << ATTRIB_TEXCOORD, + ATTRIBFLAG_COLOR = 1 << ATTRIB_COLOR + }; + + enum BufferUsage + { + BUFFERUSAGE_VERTEX = 0, + BUFFERUSAGE_INDEX, + BUFFERUSAGE_TEXEL, + BUFFERUSAGE_UNIFORM, + BUFFERUSAGE_SHADER_STORAGE, + BUFFERUSAGE_INDIRECT_ARGUMENTS, + BUFFERUSAGE_MAX_ENUM + }; + + enum BufferUsageFlags + { + BUFFERUSAGEFLAG_NONE = 0, + BUFFERUSAGEFLAG_VERTEX = 1 << BUFFERUSAGE_VERTEX, + BUFFERUSAGEFLAG_INDEX = 1 << BUFFERUSAGE_INDEX, + BUFFERUSAGEFLAG_TEXEL = 1 << BUFFERUSAGE_TEXEL, + BUFFERUSAGEFLAG_SHADER_STORAGE = 1 << BUFFERUSAGE_SHADER_STORAGE, + BUFFERUSAGEFLAG_INDIRECT_ARGUMENTS = 1 << BUFFERUSAGE_INDIRECT_ARGUMENTS, + }; + + enum IndexDataType + { + INDEX_UINT16, + INDEX_UINT32, + INDEX_MAX_ENUM + }; + + enum PrimitiveType + { + PRIMITIVE_TRIANGLES, + PRIMITIVE_TRIANGLE_STRIP, + PRIMITIVE_TRIANGLE_FAN, + PRIMITIVE_POINTS, + PRIMITIVE_MAX_ENUM + }; + + enum AttributeStep + { + STEP_PER_VERTEX, + STEP_PER_INSTANCE, + STEP_MAX_ENUM + }; + enum CullMode { CULL_NONE, @@ -16,6 +79,84 @@ namespace love CULL_MAX_ENUM }; + // The expected usage pattern of buffer data. + enum BufferDataUsage + { + BUFFERDATAUSAGE_STREAM, + BUFFERDATAUSAGE_DYNAMIC, + BUFFERDATAUSAGE_STATIC, + BUFFERDATAUSAGE_READBACK, + BUFFERDATAUSAGE_MAX_ENUM + }; + + // Value types used when interfacing with the GPU (vertex and shader data). + // The order of this enum affects the dataFormatInfo array. + enum DataFormat + { + DATAFORMAT_FLOAT, + DATAFORMAT_FLOAT_VEC2, + DATAFORMAT_FLOAT_VEC3, + DATAFORMAT_FLOAT_VEC4, + + DATAFORMAT_FLOAT_MAT2X2, + DATAFORMAT_FLOAT_MAT2X3, + DATAFORMAT_FLOAT_MAT2X4, + + DATAFORMAT_FLOAT_MAT3X2, + DATAFORMAT_FLOAT_MAT3X3, + DATAFORMAT_FLOAT_MAT3X4, + + DATAFORMAT_FLOAT_MAT4X2, + DATAFORMAT_FLOAT_MAT4X3, + DATAFORMAT_FLOAT_MAT4X4, + + DATAFORMAT_INT32, + DATAFORMAT_INT32_VEC2, + DATAFORMAT_INT32_VEC3, + DATAFORMAT_INT32_VEC4, + + DATAFORMAT_UINT32, + DATAFORMAT_UINT32_VEC2, + DATAFORMAT_UINT32_VEC3, + DATAFORMAT_UINT32_VEC4, + + DATAFORMAT_SNORM8_VEC4, + DATAFORMAT_UNORM8_VEC4, + DATAFORMAT_INT8_VEC4, + DATAFORMAT_UINT8_VEC4, + + DATAFORMAT_SNORM16_VEC2, + DATAFORMAT_SNORM16_VEC4, + + DATAFORMAT_UNORM16_VEC2, + DATAFORMAT_UNORM16_VEC4, + + DATAFORMAT_INT16_VEC2, + DATAFORMAT_INT16_VEC4, + + DATAFORMAT_UINT16, + DATAFORMAT_UINT16_VEC2, + DATAFORMAT_UINT16_VEC4, + + DATAFORMAT_BOOL, + DATAFORMAT_BOOL_VEC2, + DATAFORMAT_BOOL_VEC3, + DATAFORMAT_BOOL_VEC4, + + DATAFORMAT_MAX_ENUM + }; + + enum DataBaseType + { + DATA_BASETYPE_FLOAT, + DATA_BASETYPE_INT, + DATA_BASETYPE_UINT, + DATA_BASETYPE_SNORM, + DATA_BASETYPE_UNORM, + DATA_BASETYPE_BOOL, + DATA_BASETYPE_MAX_ENUM + }; + enum Winding { WINDING_CW, @@ -31,39 +172,232 @@ namespace love TRIANGLEINDEX_QUADS }; - enum CommonFormat + enum class CommonFormat { - NONE, //< No format specified. - XYf, //< 2D vertex with floating-point position. - XYZf, //< 3D vertex with floating-point position. - RGBAf, //< 4-component color. - STf_RGBAf, //< 2D texture coordinate and 4-component color. - XYf_STf, //< 2D vertex with floating-point position and 2D texture coordinate. - XYf_STf_RGBAf //< 2D vertex with floating-point position, 2D texture coordinate and - // 4-component color. + NONE, //< No format. + XYf, //< 2D position. + XYZf, //< 3D position. + RGBAf, //< 32-bit floating point RGBA color. + STf_RGBAf, //< 2D texture coordinates and 32-bit floating point RGBA color. + STPf_RGBAf, //< 3D texture coordinates and 32-bit floating point RGBA color. + XYf_STf, //< 2D position and 2D texture coordinates. + XYf_STPf, //< 2D position and 3D texture coordinates. + XYf_STf_RGBAf, //< 2D position, 2D texture coordinates and 32-bit floating point RGBA color. + XYf_STus_RGBAf, //< 2D position, 2D unsigned short texture coordinates and 32-bit floating point RGBA + // color. + XYf_RGBAf, + XYf_STPf_RGBAf, //< 2D position, 3D texture coordinates and 32-bit floating point RGBA color. }; - enum PrimitiveType + struct STf_RGBAf { - PRIMITIVE_TRIANGLES, - PRIMITIVE_TRIANGLE_STRIP, - PRIMITIVE_TRIANGLE_FAN, - PRIMITIVE_POINTS, - PRIMITIVE_MAX_ENUM + float s, t; + Color color; }; - struct Vertex + struct STPf_RGBAf { - Vector3 position; - Vector2 texcoord; + float s, t, p; Color color; }; + struct XYf_STf + { + float x, y; + float s, t; + }; + + struct XYf_STPf + { + float x, y; + float s, t, p; + }; + + struct XYf_STf_RGBAf + { + float x, y; + float s, t; + Color color; + }; + + struct XYf_STus_RGBAf + { + float x, y; + uint16_t s, t; + Color color; + }; + + struct XYf_STPf_RGBAf + { + float x, y; + float s, t, p; + Color color; + }; + + using Vertex = XYf_STf_RGBAf; + + static constexpr size_t VERTEX_SIZE = sizeof(Vertex); + + static constexpr size_t POSITION_OFFSET = offsetof(Vertex, x); + static constexpr size_t TEXCOORD_OFFSET = offsetof(Vertex, s); + static constexpr size_t COLOR_OFFSET = offsetof(Vertex, color); + + void debugVertices(Vertex* vertices, size_t count); + + void debugIndices(uint16_t* indices, size_t count); + inline CommonFormat getSinglePositionFormat(bool is2D) { - return is2D ? XYf : XYZf; + return is2D ? CommonFormat::XYf : CommonFormat::XYZf; } + struct DataFormatInfo + { + DataBaseType baseType; + bool isMatrix; + int components; + int matrixRows; + int matrixColumns; + size_t componentSize; + size_t size; + }; + + const DataFormatInfo& getDataFormatInfo(DataFormat format); + + int getIndexCount(TriangleIndexMode mode, int vertexCount); + + size_t getFormatStride(CommonFormat format); + + void fillIndices(TriangleIndexMode mode, uint16_t vertexStart, uint16_t vertexCount, uint16_t* indices); + + void fillIndices(TriangleIndexMode mode, uint32_t vertexStart, uint32_t vertexCount, uint32_t* indices); + + struct BufferBindings + { + static const uint32_t MAX = 32; + + uint32_t useBits = 0; + + struct + { + Resource* buffer; + size_t offset; + int vertexCount; + } info[MAX]; + + void set(uint32_t index, Resource* r, size_t offset, int vertexCount) + { + useBits |= (1u << index); + info[index] = { r, offset, vertexCount }; + } + + void disable(uint32_t index) + { + useBits &= (1u << index); + } + + void clear() + { + useBits = 0; + } + }; + + struct VertexAttributeInfo + { + uint16_t offsetFromVertex; + uint8_t packedFormat; + uint8_t bufferIndex; + + void setFormat(DataFormat format) + { + packedFormat = (uint8_t)format; + } + + DataFormat getFormat() const + { + return (DataFormat)packedFormat; + } + }; + + struct VertexBufferLayout + { + // Attribute step rate is stored outside this struct as a bitmask. + uint16_t stride; + }; + + struct VertexAttributes + { + static constexpr uint32_t MAX = 32; + + uint32_t enableBits = 0; + uint32_t instanceBits = 0; + + VertexAttributeInfo attributes[MAX]; + VertexBufferLayout bufferLayouts[BufferBindings::MAX]; + + VertexAttributes() + { + std::memset(this, 0, sizeof(VertexAttributes)); + } + + VertexAttributes(CommonFormat format, uint8_t bufferIndex) + { + std::memset(this, 0, sizeof(VertexAttributes)); + this->setCommonFormat(format, bufferIndex); + } + + void set(uint32_t index, DataFormat format, uint16_t offsetFromVertex, uint8_t bufferIndex) + { + this->enableBits |= (1u << index); + + this->attributes[index].bufferIndex = bufferIndex; + this->attributes[index].setFormat(format); + this->attributes[index].offsetFromVertex = offsetFromVertex; + } + + void setBufferLayout(uint32_t bufferIndex, uint16_t stride, AttributeStep step = STEP_PER_VERTEX) + { + uint32_t bufferBit = (1u << bufferIndex); + + if (step == STEP_PER_INSTANCE) + this->instanceBits |= bufferBit; + else + this->instanceBits &= ~bufferBit; + + this->bufferLayouts[bufferIndex].stride = stride; + } + + void disable(uint32_t index) + { + this->enableBits &= ~(1u << index); + } + + void clear() + { + this->enableBits = 0; + } + + bool isEnabled(uint32_t index) const + { + return (this->enableBits & (1u << index)) != 0; + } + + AttributeStep getBufferStep(uint32_t index) const + { + return (this->instanceBits & (1u << index)) != 0 ? STEP_PER_INSTANCE : STEP_PER_VERTEX; + } + + void setCommonFormat(CommonFormat format, uint8_t bufferIndex); + + bool operator==(const VertexAttributes& other) const; + }; + + IndexDataType getIndexDataTypeFromMax(size_t maxValue); + + DataFormat getIndexDataFormat(IndexDataType type); + + size_t getIndexDataSize(IndexDataType type); + // clang-format off STRINGMAP_DECLARE(PrimitiveTypes, PrimitiveType, { "triangles", PRIMITIVE_TRIANGLES }, @@ -82,5 +416,23 @@ namespace love { "cw", WINDING_CW }, { "ccw", WINDING_CCW } ); + + STRINGMAP_DECLARE(BufferAttributes, BuiltinVertexAttribute, + { "VertexPosition", ATTRIB_POS }, + { "VertexTexCoord", ATTRIB_TEXCOORD }, + { "VertexColor", ATTRIB_COLOR } + ); + + STRINGMAP_DECLARE(IndexDataTypes, IndexDataType, + { "uint16", INDEX_UINT16 }, + { "uint32", INDEX_UINT32 } + ); + + STRINGMAP_DECLARE(BufferUsages, BufferDataUsage, + { "stream", BUFFERDATAUSAGE_STREAM }, + { "dynamic", BUFFERDATAUSAGE_DYNAMIC }, + { "static", BUFFERDATAUSAGE_STATIC }, + { "readback", BUFFERDATAUSAGE_READBACK } + ); // clang-format on } // namespace love diff --git a/include/modules/graphics/wrap_Font.hpp b/include/modules/graphics/wrap_Font.hpp new file mode 100644 index 000000000..8d6cc8b96 --- /dev/null +++ b/include/modules/graphics/wrap_Font.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/graphics/Font.tcc" + +namespace love +{ + FontBase* luax_checkfont(lua_State* L, int index); + + void luax_checkcoloredstring(lua_State* L, int index, std::vector& strings); + + int open_font(lua_State* L); +} // namespace love + +namespace Wrap_Font +{ + int getWidth(lua_State* L); + + int getHeight(lua_State* L); + + int getWrap(lua_State* L); + + int setLineHeight(lua_State* L); + + int getLineHeight(lua_State* L); + + int setFilter(lua_State* L); + + int getFilter(lua_State* L); + + int getAscent(lua_State* L); + + int getDescent(lua_State* L); + + int getBaseline(lua_State* L); + + int hasGlyphs(lua_State* L); + + int getKerning(lua_State* L); + + int setFallbacks(lua_State* L); + + int getDPIScale(lua_State* L); +} // namespace Wrap_Font diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index d0d5e27e0..8c351a09f 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -3,39 +3,51 @@ #include "common/luax.hpp" #include "modules/graphics/Graphics.hpp" -template -static void luax_checkstandardtransform(lua_State* L, int index, const T& func) -{ - /* TODO: check Transform type */ - - int nargs = lua_gettop(L); - - float x = luaL_optnumber(L, index + 0, 0.0); - float y = luaL_optnumber(L, index + 1, 0.0); - float a = luaL_optnumber(L, index + 2, 0.0); - float sx = luaL_optnumber(L, index + 3, 1.0); - float sy = luaL_optnumber(L, index + 4, sx); - - float ox = 0.0f; - float oy = 0.0f; - - if (nargs >= index + 5) - { - ox = luaL_optnumber(L, index + 5, 0.0); - oy = luaL_optnumber(L, index + 6, 0.0); - } - - float kx = 0.0f; - float ky = 0.0f; +#include "modules/math/Transform.hpp" - if (nargs >= index + 7) +namespace love +{ + template + static void luax_checkstandardtransform(lua_State* L, int index, const T& func) { - kx = luaL_optnumber(L, index + 7, 0.0); - ky = luaL_optnumber(L, index + 8, 0.0); + /* TODO: check Transform type */ + + auto* transform = luax_totype(L, index); + + if (transform != nullptr) + func(transform->getMatrix()); + else + { + int nargs = lua_gettop(L); + + float x = luaL_optnumber(L, index + 0, 0.0); + float y = luaL_optnumber(L, index + 1, 0.0); + float a = luaL_optnumber(L, index + 2, 0.0); + float sx = luaL_optnumber(L, index + 3, 1.0); + float sy = luaL_optnumber(L, index + 4, sx); + + float ox = 0.0f; + float oy = 0.0f; + + if (nargs >= index + 5) + { + ox = luaL_optnumber(L, index + 5, 0.0); + oy = luaL_optnumber(L, index + 6, 0.0); + } + + float kx = 0.0f; + float ky = 0.0f; + + if (nargs >= index + 7) + { + kx = luaL_optnumber(L, index + 7, 0.0); + ky = luaL_optnumber(L, index + 8, 0.0); + } + + func(love::Matrix4(x, y, a, sx, sy, ox, oy, kx, ky)); + } } - - func(love::Matrix4(x, y, a, sx, sy, ox, oy, kx, ky)); -} +} // namespace love namespace Wrap_Graphics { @@ -149,8 +161,48 @@ namespace Wrap_Graphics int inverseTransformPoint(lua_State* L); + int newTexture(lua_State* L); + + int newQuad(lua_State* L); + + int newImage(lua_State* L); + + int newCanvas(lua_State* L); + + int setFont(lua_State* L); + + int getFont(lua_State* L); + + int polygon(lua_State* L); + + int rectangle(lua_State* L); + + int circle(lua_State* L); + + int ellipse(lua_State* L); + + int arc(lua_State* L); + + int points(lua_State* L); + + int line(lua_State* L); + + int draw(lua_State* L); + + int newMesh(lua_State* L); + + int newTextBatch(lua_State* L); + + int newFont(lua_State* L); + + int print(lua_State* L); + + int printf(lua_State* L); + int getScreens(lua_State* L); + int getActiveScreen(lua_State* L); + int setActiveScreen(lua_State* L); int is3D(lua_State* L); @@ -163,5 +215,7 @@ namespace Wrap_Graphics int getDepth(lua_State* L); + int copyCurrentScanBuffer(lua_State* L); + int open(lua_State* L); } // namespace Wrap_Graphics diff --git a/include/modules/graphics/wrap_Mesh.hpp b/include/modules/graphics/wrap_Mesh.hpp new file mode 100644 index 000000000..4117df907 --- /dev/null +++ b/include/modules/graphics/wrap_Mesh.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/graphics/Mesh.hpp" + +namespace love +{ + Mesh* luax_checkmesh(lua_State* L, int index); + + int open_mesh(lua_State* L); +} // namespace love + +namespace Wrap_Mesh +{ + int setVertices(lua_State* L); + + int setVertex(lua_State* L); + + int getVertex(lua_State* L); + + int getVertexCount(lua_State* L); + + int flush(lua_State* L); + + int setVertexMap(lua_State* L); + + int getVertexMap(lua_State* L); + + int setIndexBuffer(lua_State* L); + + int getIndexBuffer(lua_State* L); + + int setTexture(lua_State* L); + + int getTexture(lua_State* L); + + int setDrawMode(lua_State* L); + + int getDrawMode(lua_State* L); + + int setDrawRange(lua_State* L); + + int getDrawRange(lua_State* L); + +} // namespace Wrap_Mesh diff --git a/include/modules/graphics/wrap_Quad.hpp b/include/modules/graphics/wrap_Quad.hpp new file mode 100644 index 000000000..cf5e134ba --- /dev/null +++ b/include/modules/graphics/wrap_Quad.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/graphics/Quad.hpp" + +namespace love +{ + Quad* luax_checkquad(lua_State* L, int index); + + int open_quad(lua_State* L); +} // namespace love + +namespace Wrap_Quad +{ + int setViewport(lua_State* L); + + int getViewport(lua_State* L); + + int getTextureDimensions(lua_State* L); + + int setLayer(lua_State* L); + + int getLayer(lua_State* L); +} // namespace Wrap_Quad diff --git a/include/modules/graphics/wrap_TextBatch.hpp b/include/modules/graphics/wrap_TextBatch.hpp new file mode 100644 index 000000000..9272a81a2 --- /dev/null +++ b/include/modules/graphics/wrap_TextBatch.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/graphics/TextBatch.hpp" + +namespace love +{ + TextBatch* luax_checktextbatch(lua_State* L, int index); + + int open_textbatch(lua_State* L); +} // namespace love + +namespace Wrap_TextBatch +{ + int set(lua_State* L); + + int setf(lua_State* L); + + int add(lua_State* L); + + int addf(lua_State* L); + + int clear(lua_State* L); + + int setFont(lua_State* L); + + int getFont(lua_State* L); + + int getWidth(lua_State* L); + + int getHeight(lua_State* L); + + int getDimensions(lua_State* L); +} // namespace Wrap_TextBatch diff --git a/include/modules/graphics/wrap_Texture.hpp b/include/modules/graphics/wrap_Texture.hpp new file mode 100644 index 000000000..8d6aaac2e --- /dev/null +++ b/include/modules/graphics/wrap_Texture.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/graphics/Texture.hpp" + +namespace love +{ + Texture* luax_checktexture(lua_State* L, int index); + + int open_texture(lua_State* L); +} // namespace love + +namespace Wrap_Texture +{ + int getTextureType(lua_State* L); + + int getWidth(lua_State* L); + + int getHeight(lua_State* L); + + int getDimensions(lua_State* L); + + int getDepth(lua_State* L); + + int getLayerCount(lua_State* L); + + int getMipmapCount(lua_State* L); + + int getPixelWidth(lua_State* L); + + int getPixelHeight(lua_State* L); + + int getPixelDimensions(lua_State* L); + + int getDPIScale(lua_State* L); + + int isFormatLinear(lua_State* L); + + int isCompressed(lua_State* L); + + int getMSAA(lua_State* L); + + int setFilter(lua_State* L); + + int getFilter(lua_State* L); + + int setMipmapFilter(lua_State* L); + + int getMipmapFilter(lua_State* L); + + int setWrap(lua_State* L); + + int getWrap(lua_State* L); + + int getFormat(lua_State* L); + + int isCanvas(lua_State* L); + + int isComputeWritable(lua_State* L); + + int isReadable(lua_State* L); + + int getViewFormats(lua_State* L); + + int getMipmapMode(lua_State* L); + + int getDepthSampleMode(lua_State* L); + + int setDepthSampleMode(lua_State* L); + + int generateMipmaps(lua_State* L); + + int replacePixels(lua_State* L); + + int renderTo(lua_State* L); + + int getDebugName(lua_State* L); +} // namespace Wrap_Texture diff --git a/include/modules/image/CompressedImageData.hpp b/include/modules/image/CompressedImageData.hpp new file mode 100644 index 000000000..a4dcf61a2 --- /dev/null +++ b/include/modules/image/CompressedImageData.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "common/Data.hpp" +#include "common/Map.hpp" +#include "common/int.hpp" +#include "common/pixelformat.hpp" + +#include "modules/image/CompressedSlice.hpp" +#include "modules/image/FormatHandler.hpp" + +#include +#include + +namespace love +{ + class CompressedImageData : public Data + { + public: + static Type type; + + CompressedImageData(const std::list& formats, Data* fileData); + + CompressedImageData(const CompressedImageData& other); + + virtual ~CompressedImageData(); + + CompressedImageData* clone() const override; + + void* getData() const override; + + size_t getSize() const override; + + int getMipmapCount() const; + + int getSliceCount(int mipmap = 0) const; + + size_t getSize(int mipmap) const; + + void* getData(int mipmap) const; + + int getWidth(int mipmap = 0) const; + + int getHeight(int mipmap = 0) const; + + PixelFormat getFormat() const; + + void setLinear(bool linear); + + bool isLinear() const; + + CompressedSlice* getSlice(int slice, int mipmap) const; + + protected: + PixelFormat format; + StrongRef memory; + CompressedSlices dataImages; + + void checkSliceExists(int slice, int mipmap) const; + }; +} // namespace love diff --git a/include/modules/image/CompressedSlice.hpp b/include/modules/image/CompressedSlice.hpp new file mode 100644 index 000000000..64ca6c45c --- /dev/null +++ b/include/modules/image/CompressedSlice.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "common/StrongRef.hpp" +#include "common/int.hpp" +#include "common/pixelformat.hpp" + +#include "modules/data/ByteData.hpp" +#include "modules/image/ImageDataBase.hpp" + +namespace love +{ + class CompressedSlice : public ImageDataBase + { + public: + CompressedSlice(PixelFormat format, int width, int height, ByteData* memory, size_t offset, + size_t size); + + CompressedSlice(const CompressedSlice&); + + virtual ~CompressedSlice(); + + CompressedSlice* clone() const override; + + void* getData() const override + { + return (uint8_t*)this->memory->getData() + this->offset; + } + + size_t getSize() const override + { + return this->size; + } + + size_t getOffset() const + { + return this->offset; + } + + private: + StrongRef memory; + size_t offset; + size_t size; + }; +} // namespace love diff --git a/include/modules/image/FormatHandler.hpp b/include/modules/image/FormatHandler.hpp new file mode 100644 index 000000000..88dee921b --- /dev/null +++ b/include/modules/image/FormatHandler.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "common/Data.hpp" +#include "common/Object.hpp" +#include "common/pixelformat.hpp" + +#include "modules/image/CompressedSlice.hpp" + +#include + +namespace love +{ + using CompressedSlices = std::vector>; + + class FormatHandler : public Object + { + public: + enum EncodedFormat + { + ENCODED_TGA, + ENCODED_PNG, + ENCODED_EXR, + ENCODED_MAX_ENUM + }; + + struct DecodedImage + { + PixelFormat format = PIXELFORMAT_RGBA8_UNORM; + int width = 0; + int height = 0; + size_t size = 0; + uint8_t* data = nullptr; + }; + + struct EncodedImage + { + size_t size; + uint8_t* data; + }; + + FormatHandler(); + + virtual ~FormatHandler(); + + virtual bool canDecode(Data* data) const; + + virtual bool canEncode(PixelFormat, EncodedFormat) const; + + virtual DecodedImage decode(Data*) const; + + virtual EncodedImage encode(const DecodedImage&, EncodedFormat) const; + + virtual bool canParseCompressed(Data*) const; + + virtual StrongRef parseCompressed(Data* filedata, CompressedSlices& images, + PixelFormat& format) const; + + virtual void freeRawPixels(uint8_t* memory); + + virtual void freeEncodedImage(uint8_t* memory); + }; +} // namespace love diff --git a/include/modules/image/Image.hpp b/include/modules/image/Image.hpp new file mode 100644 index 000000000..c1b6d4b0c --- /dev/null +++ b/include/modules/image/Image.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "common/Module.hpp" + +#include "modules/filesystem/physfs/Filesystem.hpp" +#include "modules/image/CompressedImageData.hpp" +#include "modules/image/ImageData.hpp" + +#include + +namespace love +{ + class Image : public Module + { + public: + Image(); + + virtual ~Image(); + + ImageData* newImageData(Data* data) const; + + ImageData* newImageData(int width, int height, PixelFormat format = PIXELFORMAT_RGBA8_UNORM) const; + + ImageData* newImageData(int width, int height, PixelFormat format, void* data, + bool own = false) const; + + CompressedImageData* newCompressedData(Data* data) const; + + bool isCompressed(Data* data) const; + + const std::list& getFormatHandlers() const; + + private: + ImageData* newPastedImageData(ImageData* source, int sx, int sy, int width, int height) const; + + std::list formatHandlers; + }; +} // namespace love diff --git a/include/modules/image/ImageData.hpp b/include/modules/image/ImageData.hpp new file mode 100644 index 000000000..24e5540a3 --- /dev/null +++ b/include/modules/image/ImageData.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include "common/Color.hpp" +#include "common/Data.hpp" +#include "common/Map.hpp" +#include "common/float.hpp" +#include "common/int.hpp" +#include "common/pixelformat.hpp" + +#include "modules/filesystem/FileData.hpp" + +#include + +#include "modules/image/FormatHandler.hpp" +#include "modules/image/ImageDataBase.hpp" + +namespace love +{ + class ImageData : public ImageDataBase + { + public: + union Pixel + { + uint8_t rgba8[4]; + uint16_t rgba16[4]; + float16_t rgba16f[4]; + float rgba32f[4]; + uint16_t packed16; + uint32_t packed32; + }; + + using PixelSetFunction = void (*)(const Color& color, Pixel* pixel); + using PixelGetFunction = void (*)(const Pixel* pixel, Color& color); + + static Type type; + + ImageData(Data* data); + + ImageData(int width, int height, PixelFormat format); + + ImageData(int width, int height, PixelFormat format, void* data, bool own); + + ImageData(const ImageData& other); + + virtual ~ImageData(); + + void paste(ImageData* source, int dx, int dy, int sx, int sy, int sw, int sh); + + bool inside(int x, int y) const; + + void setPixel(int x, int y, const Color& color); + + void getPixel(int x, int y, Color& color) const; + + Color getPixel(int x, int y) const; + + FileData* encode(FormatHandler::EncodedFormat format, const char* filename, bool writeFile) const; + + ImageData* clone() const override; + + void* getData() const override; + + size_t getSize() const override; + + size_t getPixelSize() const; + + PixelSetFunction getPixelSetFunction() const + { + return this->pixelSetFunction; + } + + PixelGetFunction getPixelGetFunction() const + { + return this->pixelGetFunction; + } + + static bool validPixelFormat(PixelFormat format); + + static PixelSetFunction getPixelSetFunction(PixelFormat format); + + static PixelGetFunction getPixelGetFunction(PixelFormat format); + + // clang-format off + STRINGMAP_DECLARE(EncodedFormats, FormatHandler::EncodedFormat, + { "png", FormatHandler::ENCODED_PNG }, + { "tga", FormatHandler::ENCODED_TGA }, + { "exr", FormatHandler::ENCODED_EXR } + ); + // clang-format on + + private: + void create(int width, int height, PixelFormat format, void* data = nullptr); + + void decode(Data* data); + + uint8_t* data = nullptr; + StrongRef decodeHandler; + + PixelSetFunction pixelSetFunction; + PixelGetFunction pixelGetFunction; + }; +} // namespace love diff --git a/include/modules/image/ImageDataBase.hpp b/include/modules/image/ImageDataBase.hpp new file mode 100644 index 000000000..729ce52d1 --- /dev/null +++ b/include/modules/image/ImageDataBase.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "common/Data.hpp" +#include "common/pixelformat.hpp" + +namespace love +{ + class ImageDataBase : public Data + { + public: + virtual ~ImageDataBase() + {} + + PixelFormat getFormat() const + { + return this->format; + } + + int getWidth() const + { + return this->width; + } + + int getHeight() const + { + return this->height; + } + + void setLinear(bool linear) + { + this->linear = linear; + } + + bool isLinear() const + { + return this->linear; + } + + protected: + ImageDataBase(PixelFormat format, int width, int height); + + PixelFormat format; + int width; + int height; + + bool linear; + }; +} // namespace love diff --git a/include/modules/image/magpie/ASTCHandler.hpp b/include/modules/image/magpie/ASTCHandler.hpp new file mode 100644 index 000000000..519084d96 --- /dev/null +++ b/include/modules/image/magpie/ASTCHandler.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "modules/image/FormatHandler.hpp" + +namespace love +{ + class ASTCHandler : public FormatHandler + { + public: + virtual ~ASTCHandler() + {} + + bool canParseCompressed(Data* data) const override; + + StrongRef parseCompressed(Data* data, CompressedSlices& images, + PixelFormat& format) const override; + }; +} // namespace love diff --git a/include/modules/image/magpie/JPGHandler.hpp b/include/modules/image/magpie/JPGHandler.hpp new file mode 100644 index 000000000..9e4a2700e --- /dev/null +++ b/include/modules/image/magpie/JPGHandler.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "modules/image/FormatHandler.hpp" + +namespace love +{ + class JPGHandler : public FormatHandler + { + public: + bool canDecode(Data* data) const override; + + DecodedImage decode(Data* data) const override; + }; +} // namespace love diff --git a/include/modules/image/magpie/KTXHandler.hpp b/include/modules/image/magpie/KTXHandler.hpp new file mode 100644 index 000000000..55cc7d719 --- /dev/null +++ b/include/modules/image/magpie/KTXHandler.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "modules/image/FormatHandler.hpp" + +namespace love +{ + class KTXHandler : public FormatHandler + { + public: + virtual ~KTXHandler() + {} + + // Implements FormatHandler. + bool canParseCompressed(Data* data) const override; + + StrongRef parseCompressed(Data* filedata, CompressedSlices& images, + PixelFormat& format) const override; + + }; // KTXHandler +} // namespace love diff --git a/include/modules/image/magpie/PKMHandler.hpp b/include/modules/image/magpie/PKMHandler.hpp new file mode 100644 index 000000000..281603c92 --- /dev/null +++ b/include/modules/image/magpie/PKMHandler.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "modules/image/FormatHandler.hpp" + +namespace love +{ + class PKMHandler : public FormatHandler + { + public: + virtual ~PKMHandler() + {} + + bool canParseCompressed(Data* data) const override; + + StrongRef parseCompressed(Data* data, CompressedSlices& images, + PixelFormat& format) const override; + }; +} diff --git a/include/modules/image/magpie/PNGHandler.hpp b/include/modules/image/magpie/PNGHandler.hpp new file mode 100644 index 000000000..0895e3e7c --- /dev/null +++ b/include/modules/image/magpie/PNGHandler.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "modules/image/FormatHandler.hpp" + +namespace love +{ + class PNGHandler : public FormatHandler + { + public: + virtual ~PNGHandler() + {} + + bool canDecode(Data* data) const override; + + bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat) const override; + + DecodedImage decode(Data* data) const override; + + EncodedImage encode(const DecodedImage& image, EncodedFormat format) const override; + }; +} // namespace love diff --git a/include/modules/image/magpie/T3XHandler.hpp b/include/modules/image/magpie/T3XHandler.hpp new file mode 100644 index 000000000..fe84df9ce --- /dev/null +++ b/include/modules/image/magpie/T3XHandler.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "common/math.hpp" +#include "modules/image/FormatHandler.hpp" + +namespace love +{ + class T3XHandler : public FormatHandler + { + public: + bool canDecode(Data* data) const override; + + DecodedImage decode(Data* data) const override; + + bool canParseCompressed(Data* data) const override; + + StrongRef parseCompressed(Data* filedata, CompressedSlices& images, + PixelFormat& format) const override; + }; +} // namespace love diff --git a/include/modules/image/magpie/ddsHandler.hpp b/include/modules/image/magpie/ddsHandler.hpp new file mode 100644 index 000000000..ac1d5aef4 --- /dev/null +++ b/include/modules/image/magpie/ddsHandler.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "modules/image/FormatHandler.hpp" + +namespace love +{ + class DDSHandler : public FormatHandler + { + public: + virtual ~DDSHandler() + {} + + bool canDecode(Data* data) const override; + + DecodedImage decode(Data* data) const override; + + bool canParseCompressed(Data* data) const override; + + StrongRef parseCompressed(Data* data, CompressedSlices& images, + PixelFormat& format) const override; + }; +} // namespace love diff --git a/include/modules/image/wrap_CompressedImageData.hpp b/include/modules/image/wrap_CompressedImageData.hpp new file mode 100644 index 000000000..5e2906f4f --- /dev/null +++ b/include/modules/image/wrap_CompressedImageData.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/image/CompressedImageData.hpp" + +namespace love +{ + CompressedImageData* luax_checkcompressedimagedata(lua_State* L, int index); + + int open_compressedimagedata(lua_State* L); +} // namespace love + +namespace Wrap_CompressedImageData +{ + int clone(lua_State* L); + + int getWidth(lua_State* L); + + int getHeight(lua_State* L); + + int getDimensions(lua_State* L); + + int getMipmapCount(lua_State* L); + + int getFormat(lua_State* L); + + int setLinear(lua_State* L); + + int isLinear(lua_State* L); +} // namespace Wrap_CompressedImageData diff --git a/include/modules/image/wrap_Image.hpp b/include/modules/image/wrap_Image.hpp new file mode 100644 index 000000000..0a4e7b319 --- /dev/null +++ b/include/modules/image/wrap_Image.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/image/Image.hpp" + +namespace Wrap_Image +{ + int newImageData(lua_State* L); + + int newCompressedData(lua_State* L); + + int isCompressed(lua_State* L); + + int open(lua_State* L); +} // namespace Wrap_Image diff --git a/include/modules/image/wrap_ImageData.hpp b/include/modules/image/wrap_ImageData.hpp new file mode 100644 index 000000000..1a406055f --- /dev/null +++ b/include/modules/image/wrap_ImageData.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "common/luax.hpp" +#include "modules/image/ImageData.hpp" + +namespace love +{ + ImageData* luax_checkimagedata(lua_State* L, int index); + + int open_imagedata(lua_State* L); +} // namespace love + +namespace Wrap_ImageData +{ + int clone(lua_State* L); + + int getFormat(lua_State* L); + + int setLinear(lua_State* L); + + int isLinear(lua_State* L); + + int getWidth(lua_State* L); + + int getHeight(lua_State* L); + + int getDimensions(lua_State* L); + + int getPixel(lua_State* L); + + int setPixel(lua_State* L); + + int paste(lua_State* L); + + int mapPixel(lua_State* L); + + int encode(lua_State* L); +} // namespace Wrap_ImageData diff --git a/include/modules/joystick/Joystick.tcc b/include/modules/joystick/Joystick.tcc index ae02ecaf5..aa7fa3078 100644 --- a/include/modules/joystick/Joystick.tcc +++ b/include/modules/joystick/Joystick.tcc @@ -53,6 +53,10 @@ namespace love GAMEPAD_BUTTON_DPAD_DOWN, GAMEPAD_BUTTON_DPAD_LEFT, GAMEPAD_BUTTON_DPAD_RIGHT, + GAMEPAD_BUTTON_MISC1, + GAMEPAD_BUTTON_MISC2, + GAMEPAD_BUTTON_Z, //< Nunchuk only + GAMEPAD_BUTTON_C, //< Nunchuk only GAMEPAD_BUTTON_MAX_ENUM }; @@ -188,21 +192,25 @@ namespace love // clang-format off STRINGMAP_DECLARE(GamepadButtons, GamepadButton, - { "a", GAMEPAD_BUTTON_A }, - { "b", GAMEPAD_BUTTON_B }, - { "x", GAMEPAD_BUTTON_X }, - { "y", GAMEPAD_BUTTON_Y }, - { "back", GAMEPAD_BUTTON_BACK }, - { "guide", GAMEPAD_BUTTON_GUIDE }, - { "start", GAMEPAD_BUTTON_START }, - { "leftstick", GAMEPAD_BUTTON_LEFTSTICK }, - { "rightstick", GAMEPAD_BUTTON_RIGHTSTICK }, - { "leftshoulder", GAMEPAD_BUTTON_LEFTSHOULDER }, - { "rightshoulder", GAMEPAD_BUTTON_RIGHTSHOULDER }, - { "dpup", GAMEPAD_BUTTON_DPAD_UP }, - { "dpdown", GAMEPAD_BUTTON_DPAD_DOWN }, - { "dpleft", GAMEPAD_BUTTON_DPAD_LEFT }, - { "dpright", GAMEPAD_BUTTON_DPAD_RIGHT } + { "a", GAMEPAD_BUTTON_A }, + { "b", GAMEPAD_BUTTON_B }, + { "x", GAMEPAD_BUTTON_X }, + { "y", GAMEPAD_BUTTON_Y }, + { "back", GAMEPAD_BUTTON_BACK }, + { "guide", GAMEPAD_BUTTON_GUIDE }, + { "start", GAMEPAD_BUTTON_START }, + { "leftstick", GAMEPAD_BUTTON_LEFTSTICK }, + { "rightstick", GAMEPAD_BUTTON_RIGHTSTICK }, + { "leftshoulder", GAMEPAD_BUTTON_LEFTSHOULDER }, + { "rightshoulder", GAMEPAD_BUTTON_RIGHTSHOULDER }, + { "dpup", GAMEPAD_BUTTON_DPAD_UP }, + { "dpdown", GAMEPAD_BUTTON_DPAD_DOWN }, + { "dpleft", GAMEPAD_BUTTON_DPAD_LEFT }, + { "dpright", GAMEPAD_BUTTON_DPAD_RIGHT }, + { "misc1", GAMEPAD_BUTTON_MISC1 }, + { "misc2", GAMEPAD_BUTTON_MISC2 }, + { "z", GAMEPAD_BUTTON_Z }, //< Nunchuk only + { "c", GAMEPAD_BUTTON_C } //< Nunchuk only ); STRINGMAP_DECLARE(GamepadAxes, GamepadAxis, @@ -215,29 +223,29 @@ namespace love ); STRINGMAP_DECLARE(InputTypes, InputType, - { "axis", INPUT_TYPE_AXIS }, - { "button", INPUT_TYPE_BUTTON }, - { "hat", INPUT_TYPE_HAT } + { "axis", INPUT_TYPE_AXIS }, + { "button", INPUT_TYPE_BUTTON }, + { "hat", INPUT_TYPE_HAT } ); STRINGMAP_DECLARE(GamepadTypes, GamepadType, - { "unknown", GAMEPAD_TYPE_UNKNOWN }, - { "3ds", GAMEPAD_TYPE_NINTENDO_3DS }, - { "3dsxl", GAMEPAD_TYPE_NINTENDO_3DS_XL }, - { "new3ds", GAMEPAD_TYPE_NEW_NINTENDO_3DS }, - { "new3dsxl", GAMEPAD_TYPE_NEW_NINTENDO_3DS_XL }, - { "2ds", GAMEPAD_TYPE_NINTENDO_2DS }, - { "new2dsxl", GAMEPAD_TYPE_NEW_NINTENDO_2DS_XL }, - { "wiiremote", GAMEPAD_TYPE_NINTENDO_WII_REMOTE }, - { "wiiremotenunchuck", GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK }, - { "wiiclassic", GAMEPAD_TYPE_NINTENDO_WII_CLASSIC }, - { "wiiugamepad", GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD }, - { "wiiupro", GAMEPAD_TYPE_NINTENDO_WII_U_PRO }, - { "switchhandheld", GAMEPAD_TYPE_NINTENDO_SWITCH_HANDHELD }, - { "switchpro", GAMEPAD_TYPE_NINTENDO_SWITCH_PRO }, - { "joyconleft", GAMEPAD_TYPE_JOYCON_LEFT }, - { "joyconright", GAMEPAD_TYPE_JOYCON_RIGHT }, - { "joyconpair", GAMEPAD_TYPE_JOYCON_PAIR } + { "unknown", GAMEPAD_TYPE_UNKNOWN }, + { "3ds", GAMEPAD_TYPE_NINTENDO_3DS }, + { "3dsxl", GAMEPAD_TYPE_NINTENDO_3DS_XL }, + { "new3ds", GAMEPAD_TYPE_NEW_NINTENDO_3DS }, + { "new3dsxl", GAMEPAD_TYPE_NEW_NINTENDO_3DS_XL }, + { "2ds", GAMEPAD_TYPE_NINTENDO_2DS }, + { "new2dsxl", GAMEPAD_TYPE_NEW_NINTENDO_2DS_XL }, + { "wiiremote", GAMEPAD_TYPE_NINTENDO_WII_REMOTE }, + { "wiiremotenunchuk", GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK }, + { "wiiclassic", GAMEPAD_TYPE_NINTENDO_WII_CLASSIC }, + { "wiiugamepad", GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD }, + { "wiiupro", GAMEPAD_TYPE_NINTENDO_WII_U_PRO }, + { "switchhandheld", GAMEPAD_TYPE_NINTENDO_SWITCH_HANDHELD }, + { "switchpro", GAMEPAD_TYPE_NINTENDO_SWITCH_PRO }, + { "joyconleft", GAMEPAD_TYPE_JOYCON_LEFT }, + { "joyconright", GAMEPAD_TYPE_JOYCON_RIGHT }, + { "joyconpair", GAMEPAD_TYPE_JOYCON_PAIR } ); STRINGMAP_DECLARE(JoystickTypes, JoystickType, @@ -260,11 +268,7 @@ namespace love return std::clamp(value / MAX_AXIS_VALUE, -1.0f, 1.0f); } - JoystickBase(int id) : - joystickType(JOYSTICK_TYPE_UNKNOWN), - instanceId(-1), - id(id), - sensors() + JoystickBase(int id) : joystickType(JOYSTICK_TYPE_UNKNOWN), instanceId(-1), id(id), sensors() { this->sensors[Sensor::SENSOR_ACCELEROMETER] = false; this->sensors[Sensor::SENSOR_GYROSCOPE] = false; diff --git a/include/modules/joystick/JoystickModule.hpp b/include/modules/joystick/JoystickModule.hpp index cb04b871b..373340e91 100644 --- a/include/modules/joystick/JoystickModule.hpp +++ b/include/modules/joystick/JoystickModule.hpp @@ -1,7 +1,7 @@ #pragma once #include "common/Module.hpp" -#include "modules/joystick/Joystick.hpp" +#include "modules/joystick/Joystick.tcc" #include #include diff --git a/include/modules/joystick/wrap_Joystick.hpp b/include/modules/joystick/wrap_Joystick.hpp index 61ac75a7b..fc9f7f560 100644 --- a/include/modules/joystick/wrap_Joystick.hpp +++ b/include/modules/joystick/wrap_Joystick.hpp @@ -1,7 +1,7 @@ #pragma once #include "common/luax.hpp" -#include "modules/joystick/Joystick.hpp" +#include "modules/joystick/Joystick.tcc" namespace love { @@ -67,4 +67,8 @@ namespace Wrap_Joystick int setSensorEnabled(lua_State* L); int getSensorData(lua_State* L); + + int getPosition(lua_State* L); + + int getAngle(lua_State* L); } // namespace Wrap_Joystick diff --git a/include/modules/keyboard/Keyboard.tcc b/include/modules/keyboard/Keyboard.tcc index 5494b3db5..7d02827be 100644 --- a/include/modules/keyboard/Keyboard.tcc +++ b/include/modules/keyboard/Keyboard.tcc @@ -5,12 +5,41 @@ #include "common/Result.hpp" #include +#include namespace love { class KeyboardBase : public Module { public: + enum KeyboardResult + { + RESULT_OK, //< Input accepted + RESULT_CANCEL, //< Input cancelled + RESULT_CONTINUE, //< Input validation callback should continue + RESULT_MAX_ENUM + }; + + struct KeyboardValidationInfo; + +#if defined(__3DS__) + using ValidationError = const char**; +#elif defined(__SWITCH__) + using ValidationError = char*; +#else + using ValidationError = void*; +#endif + + typedef KeyboardResult (*KeyboardValidationCallback)(const KeyboardValidationInfo* info, + const char* text, ValidationError error); + + struct KeyboardValidationInfo + { + KeyboardValidationCallback callback = nullptr; + void* data = nullptr; + void* luaState = nullptr; + }; + enum KeyboardType { TYPE_NORMAL, @@ -20,10 +49,11 @@ namespace love struct KeyboardOptions { - uint8_t type; - bool password; - std::string_view hint; - uint32_t maxLength; + uint8_t type; // KeyboardType + bool password; // Whether the input should be hidden + std::string_view hint; // Hint text + uint32_t maxLength; // Maximum length of the input + KeyboardValidationInfo callback; // Callback function }; enum KeyboardOption @@ -32,6 +62,7 @@ namespace love OPTION_PASSCODE, OPTION_HINT, OPTION_MAX_LENGTH, + OPTION_CALLBACK, OPTION_MAX_ENUM }; @@ -46,11 +77,7 @@ namespace love static constexpr uint32_t MULTIPLIER = 3; #endif - KeyboardBase() : - Module(M_KEYBOARD, "love.keyboard"), - keyRepeat(false), - showing(false), - text(nullptr) + KeyboardBase() : Module(M_KEYBOARD, "love.keyboard"), keyRepeat(false), showing(false), text(nullptr) {} virtual ~KeyboardBase() @@ -86,7 +113,8 @@ namespace love { "type", OPTION_TYPE }, { "password", OPTION_PASSCODE }, { "hint", OPTION_HINT }, - { "maxLength", OPTION_MAX_LENGTH } + { "maxLength", OPTION_MAX_LENGTH }, + { "callback", OPTION_CALLBACK } ); STRINGMAP_DECLARE(KeyboardTypes, KeyboardType, @@ -94,6 +122,12 @@ namespace love { "qwerty", TYPE_QWERTY }, { "numpad", TYPE_NUMPAD } ); + + STRINGMAP_DECLARE(KeyboardResults, KeyboardResult, + { "ok", RESULT_OK }, + { "cancel", RESULT_CANCEL }, + { "continue", RESULT_CONTINUE } + ); // clang-format on protected: diff --git a/include/modules/window/Window.tcc b/include/modules/window/Window.tcc index 9656c2ecf..88b4916c2 100644 --- a/include/modules/window/Window.tcc +++ b/include/modules/window/Window.tcc @@ -215,9 +215,9 @@ namespace love height = result.back().height; } - DisplayOrientation getDisplayOrientation(int displayIndex) const + DisplayOrientation getDisplayOrientation(int) const { - return ORIENTATION_LANDSCAPE; + return DisplayOrientation::ORIENTATION_LANDSCAPE; } double getDPIScale() const @@ -273,7 +273,7 @@ namespace love { "usedpiscale", SETTING_USE_DPISCALE }, { "refreshrate", SETTING_REFRESHRATE }, { "x", SETTING_X }, - { "y", SETTING_Y }, + { "y", SETTING_Y } ); STRINGMAP_DECLARE(FullscreenTypes, FullscreenType, @@ -297,7 +297,7 @@ namespace love // clang-format on protected: - void close(bool allowExceptions) + void close(bool) { this->open = false; } diff --git a/include/utility/guid.hpp b/include/utility/guid.hpp index 0165e4475..376d348b8 100644 --- a/include/utility/guid.hpp +++ b/include/utility/guid.hpp @@ -24,7 +24,7 @@ namespace love GAMEPAD_TYPE_NINTENDO_2DS, GAMEPAD_TYPE_NEW_NINTENDO_2DS_XL, GAMEPAD_TYPE_NINTENDO_WII_REMOTE, - GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK, + GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK, GAMEPAD_TYPE_NINTENDO_WII_CLASSIC, GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD, GAMEPAD_TYPE_NINTENDO_WII_U_PRO, diff --git a/include/utility/logfile.hpp b/include/utility/logfile.hpp deleted file mode 100644 index 8ccea9c8e..000000000 --- a/include/utility/logfile.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include -#include -#include - -class Log -{ - public: - static Log& getInstance() - { - static Log instance; - return instance; - } - - ~Log() - {} - - template - void write(std::source_location location, std::format_string format, Args&&... args) - { - auto output = std::format(format, std::forward(args)...); - - const auto path = std::string_view(location.file_name()); - const auto filename = path.substr(path.find_last_of('/') + 1); - - const auto line = (size_t)location.line(); - const auto column = (size_t)location.column(); - - auto buffer = std::format(BUFFER_FORMAT, filename, line, column, output); - - std::fwrite(buffer.c_str(), 1, buffer.size(), stdout); - std::fflush(stdout); - } - - private: - static constexpr const char* BUFFER_FORMAT = "{:s}({:d}:{:d}): {:s}\n"; -}; - -#if __DEBUG__ == 0 - #define LOG(...) -#else - #define LOG(format, ...) \ - Log::getInstance().write(std::source_location::current(), format, ##__VA_ARGS__); -#endif diff --git a/libraries/luasocket/libluasocket/auxiliar.c b/libraries/luasocket/libluasocket/auxiliar.c new file mode 100644 index 000000000..93a66a09f --- /dev/null +++ b/libraries/luasocket/libluasocket/auxiliar.c @@ -0,0 +1,154 @@ +/*=========================================================================*\ +* Auxiliar routines for class hierarchy manipulation +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "auxiliar.h" +#include +#include + +/*-------------------------------------------------------------------------*\ +* Initializes the module +\*-------------------------------------------------------------------------*/ +int auxiliar_open(lua_State *L) { + (void) L; + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Creates a new class with given methods +* Methods whose names start with __ are passed directly to the metatable. +\*-------------------------------------------------------------------------*/ +void auxiliar_newclass(lua_State *L, const char *classname, luaL_Reg *func) { + luaL_newmetatable(L, classname); /* mt */ + /* create __index table to place methods */ + lua_pushstring(L, "__index"); /* mt,"__index" */ + lua_newtable(L); /* mt,"__index",it */ + /* put class name into class metatable */ + lua_pushstring(L, "class"); /* mt,"__index",it,"class" */ + lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */ + lua_rawset(L, -3); /* mt,"__index",it */ + /* pass all methods that start with _ to the metatable, and all others + * to the index table */ + for (; func->name; func++) { /* mt,"__index",it */ + lua_pushstring(L, func->name); + lua_pushcfunction(L, func->func); + lua_rawset(L, func->name[0] == '_' ? -5: -3); + } + lua_rawset(L, -3); /* mt */ + lua_pop(L, 1); +} + +/*-------------------------------------------------------------------------*\ +* Prints the value of a class in a nice way +\*-------------------------------------------------------------------------*/ +int auxiliar_tostring(lua_State *L) { + char buf[32]; + if (!lua_getmetatable(L, 1)) goto error; + lua_pushstring(L, "__index"); + lua_gettable(L, -2); + if (!lua_istable(L, -1)) goto error; + lua_pushstring(L, "class"); + lua_gettable(L, -2); + if (!lua_isstring(L, -1)) goto error; + sprintf(buf, "%p", lua_touserdata(L, 1)); + lua_pushfstring(L, "%s: %s", lua_tostring(L, -1), buf); + return 1; +error: + lua_pushstring(L, "invalid object passed to 'auxiliar.c:__tostring'"); + lua_error(L); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Insert class into group +\*-------------------------------------------------------------------------*/ +void auxiliar_add2group(lua_State *L, const char *classname, const char *groupname) { + luaL_getmetatable(L, classname); + lua_pushstring(L, groupname); + lua_pushboolean(L, 1); + lua_rawset(L, -3); + lua_pop(L, 1); +} + +/*-------------------------------------------------------------------------*\ +* Make sure argument is a boolean +\*-------------------------------------------------------------------------*/ +int auxiliar_checkboolean(lua_State *L, int objidx) { + if (!lua_isboolean(L, objidx)) + auxiliar_typeerror(L, objidx, lua_typename(L, LUA_TBOOLEAN)); + return lua_toboolean(L, objidx); +} + +/*-------------------------------------------------------------------------*\ +* Return userdata pointer if object belongs to a given class, abort with +* error otherwise +\*-------------------------------------------------------------------------*/ +void *auxiliar_checkclass(lua_State *L, const char *classname, int objidx) { + void *data = auxiliar_getclassudata(L, classname, objidx); + if (!data) { + char msg[45]; + sprintf(msg, "%.35s expected", classname); + luaL_argerror(L, objidx, msg); + } + return data; +} + +/*-------------------------------------------------------------------------*\ +* Return userdata pointer if object belongs to a given group, abort with +* error otherwise +\*-------------------------------------------------------------------------*/ +void *auxiliar_checkgroup(lua_State *L, const char *groupname, int objidx) { + void *data = auxiliar_getgroupudata(L, groupname, objidx); + if (!data) { + char msg[45]; + sprintf(msg, "%.35s expected", groupname); + luaL_argerror(L, objidx, msg); + } + return data; +} + +/*-------------------------------------------------------------------------*\ +* Set object class +\*-------------------------------------------------------------------------*/ +void auxiliar_setclass(lua_State *L, const char *classname, int objidx) { + luaL_getmetatable(L, classname); + if (objidx < 0) objidx--; + lua_setmetatable(L, objidx); +} + +/*-------------------------------------------------------------------------*\ +* Get a userdata pointer if object belongs to a given group. Return NULL +* otherwise +\*-------------------------------------------------------------------------*/ +void *auxiliar_getgroupudata(lua_State *L, const char *groupname, int objidx) { + if (!lua_getmetatable(L, objidx)) + return NULL; + lua_pushstring(L, groupname); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); + return NULL; + } else { + lua_pop(L, 2); + return lua_touserdata(L, objidx); + } +} + +/*-------------------------------------------------------------------------*\ +* Get a userdata pointer if object belongs to a given class. Return NULL +* otherwise +\*-------------------------------------------------------------------------*/ +void *auxiliar_getclassudata(lua_State *L, const char *classname, int objidx) { + return luaL_testudata(L, objidx, classname); +} + +/*-------------------------------------------------------------------------*\ +* Throws error when argument does not have correct type. +* Used to be part of lauxlib in Lua 5.1, was dropped from 5.2. +\*-------------------------------------------------------------------------*/ +int auxiliar_typeerror (lua_State *L, int narg, const char *tname) { + const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, + luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} diff --git a/libraries/luasocket/libluasocket/auxiliar.h b/libraries/luasocket/libluasocket/auxiliar.h new file mode 100644 index 000000000..e8c3ead82 --- /dev/null +++ b/libraries/luasocket/libluasocket/auxiliar.h @@ -0,0 +1,54 @@ +#ifndef AUXILIAR_H +#define AUXILIAR_H +/*=========================================================================*\ +* Auxiliar routines for class hierarchy manipulation +* LuaSocket toolkit (but completely independent of other LuaSocket modules) +* +* A LuaSocket class is a name associated with Lua metatables. A LuaSocket +* group is a name associated with a class. A class can belong to any number +* of groups. This module provides the functionality to: +* +* - create new classes +* - add classes to groups +* - set the class of objects +* - check if an object belongs to a given class or group +* - get the userdata associated to objects +* - print objects in a pretty way +* +* LuaSocket class names follow the convention {}. Modules +* can define any number of classes and groups. The module tcp.c, for +* example, defines the classes tcp{master}, tcp{client} and tcp{server} and +* the groups tcp{client,server} and tcp{any}. Module functions can then +* perform type-checking on their arguments by either class or group. +* +* LuaSocket metatables define the __index metamethod as being a table. This +* table has one field for each method supported by the class, and a field +* "class" with the class name. +* +* The mapping from class name to the corresponding metatable and the +* reverse mapping are done using lauxlib. +\*=========================================================================*/ + +#include "luasocket.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int auxiliar_open(lua_State *L); +void auxiliar_newclass(lua_State *L, const char *classname, luaL_Reg *func); +int auxiliar_tostring(lua_State *L); +void auxiliar_add2group(lua_State *L, const char *classname, const char *group); +int auxiliar_checkboolean(lua_State *L, int objidx); +void *auxiliar_checkclass(lua_State *L, const char *classname, int objidx); +void *auxiliar_checkgroup(lua_State *L, const char *groupname, int objidx); +void auxiliar_setclass(lua_State *L, const char *classname, int objidx); +void *auxiliar_getgroupudata(lua_State *L, const char *groupname, int objidx); +void *auxiliar_getclassudata(lua_State *L, const char *groupname, int objidx); +int auxiliar_typeerror(lua_State *L, int narg, const char *tname); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* AUXILIAR_H */ diff --git a/libraries/luasocket/libluasocket/buffer.c b/libraries/luasocket/libluasocket/buffer.c new file mode 100644 index 000000000..7148be34f --- /dev/null +++ b/libraries/luasocket/libluasocket/buffer.c @@ -0,0 +1,273 @@ +/*=========================================================================*\ +* Input/Output interface for Lua programs +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "buffer.h" + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b); +static int recvline(p_buffer buf, luaL_Buffer *b); +static int recvall(p_buffer buf, luaL_Buffer *b); +static int buffer_get(p_buffer buf, const char **data, size_t *count); +static void buffer_skip(p_buffer buf, size_t count); +static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent); + +/* min and max macros */ +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) +#endif + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int buffer_open(lua_State *L) { + (void) L; + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Initializes C structure +\*-------------------------------------------------------------------------*/ +void buffer_init(p_buffer buf, p_io io, p_timeout tm) { + buf->first = buf->last = 0; + buf->io = io; + buf->tm = tm; + buf->received = buf->sent = 0; + buf->birthday = timeout_gettime(); +} + +/*-------------------------------------------------------------------------*\ +* object:getstats() interface +\*-------------------------------------------------------------------------*/ +int buffer_meth_getstats(lua_State *L, p_buffer buf) { + lua_pushnumber(L, (lua_Number) buf->received); + lua_pushnumber(L, (lua_Number) buf->sent); + lua_pushnumber(L, timeout_gettime() - buf->birthday); + return 3; +} + +/*-------------------------------------------------------------------------*\ +* object:setstats() interface +\*-------------------------------------------------------------------------*/ +int buffer_meth_setstats(lua_State *L, p_buffer buf) { + buf->received = (long) luaL_optnumber(L, 2, (lua_Number) buf->received); + buf->sent = (long) luaL_optnumber(L, 3, (lua_Number) buf->sent); + if (lua_isnumber(L, 4)) buf->birthday = timeout_gettime() - lua_tonumber(L, 4); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* object:send() interface +\*-------------------------------------------------------------------------*/ +int buffer_meth_send(lua_State *L, p_buffer buf) { + int top = lua_gettop(L); + int err = IO_DONE; + size_t size = 0, sent = 0; + const char *data = luaL_checklstring(L, 2, &size); + long start = (long) luaL_optnumber(L, 3, 1); + long end = (long) luaL_optnumber(L, 4, -1); + timeout_markstart(buf->tm); + if (start < 0) start = (long) (size+start+1); + if (end < 0) end = (long) (size+end+1); + if (start < 1) start = (long) 1; + if (end > (long) size) end = (long) size; + if (start <= end) err = sendraw(buf, data+start-1, end-start+1, &sent); + /* check if there was an error */ + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, buf->io->error(buf->io->ctx, err)); + lua_pushnumber(L, (lua_Number) (sent+start-1)); + } else { + lua_pushnumber(L, (lua_Number) (sent+start-1)); + lua_pushnil(L); + lua_pushnil(L); + } +#ifdef LUASOCKET_DEBUG + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, timeout_gettime() - timeout_getstart(buf->tm)); +#endif + return lua_gettop(L) - top; +} + +/*-------------------------------------------------------------------------*\ +* object:receive() interface +\*-------------------------------------------------------------------------*/ +int buffer_meth_receive(lua_State *L, p_buffer buf) { + int err = IO_DONE, top; + luaL_Buffer b; + size_t size; + const char *part = luaL_optlstring(L, 3, "", &size); + timeout_markstart(buf->tm); + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 3); + top = lua_gettop(L); + /* initialize buffer with optional extra prefix + * (useful for concatenating previous partial results) */ + luaL_buffinit(L, &b); + luaL_addlstring(&b, part, size); + /* receive new patterns */ + if (!lua_isnumber(L, 2)) { + const char *p= luaL_optstring(L, 2, "*l"); + if (p[0] == '*' && p[1] == 'l') err = recvline(buf, &b); + else if (p[0] == '*' && p[1] == 'a') err = recvall(buf, &b); + else luaL_argcheck(L, 0, 2, "invalid receive pattern"); + /* get a fixed number of bytes (minus what was already partially + * received) */ + } else { + double n = lua_tonumber(L, 2); + size_t wanted = (size_t) n; + luaL_argcheck(L, n >= 0, 2, "invalid receive pattern"); + if (size == 0 || wanted > size) + err = recvraw(buf, wanted-size, &b); + } + /* check if there was an error */ + if (err != IO_DONE) { + /* we can't push anyting in the stack before pushing the + * contents of the buffer. this is the reason for the complication */ + luaL_pushresult(&b); + lua_pushstring(L, buf->io->error(buf->io->ctx, err)); + lua_pushvalue(L, -2); + lua_pushnil(L); + lua_replace(L, -4); + } else { + luaL_pushresult(&b); + lua_pushnil(L); + lua_pushnil(L); + } +#ifdef LUASOCKET_DEBUG + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, timeout_gettime() - timeout_getstart(buf->tm)); +#endif + return lua_gettop(L) - top; +} + +/*-------------------------------------------------------------------------*\ +* Determines if there is any data in the read buffer +\*-------------------------------------------------------------------------*/ +int buffer_isempty(p_buffer buf) { + return buf->first >= buf->last; +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Sends a block of data (unbuffered) +\*-------------------------------------------------------------------------*/ +#define STEPSIZE 8192 +static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent) { + p_io io = buf->io; + p_timeout tm = buf->tm; + size_t total = 0; + int err = IO_DONE; + while (total < count && err == IO_DONE) { + size_t done = 0; + size_t step = (count-total <= STEPSIZE)? count-total: STEPSIZE; + err = io->send(io->ctx, data+total, step, &done, tm); + total += done; + } + *sent = total; + buf->sent += total; + return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads a fixed number of bytes (buffered) +\*-------------------------------------------------------------------------*/ +static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b) { + int err = IO_DONE; + size_t total = 0; + while (err == IO_DONE) { + size_t count; const char *data; + err = buffer_get(buf, &data, &count); + count = MIN(count, wanted - total); + luaL_addlstring(b, data, count); + buffer_skip(buf, count); + total += count; + if (total >= wanted) break; + } + return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads everything until the connection is closed (buffered) +\*-------------------------------------------------------------------------*/ +static int recvall(p_buffer buf, luaL_Buffer *b) { + int err = IO_DONE; + size_t total = 0; + while (err == IO_DONE) { + const char *data; size_t count; + err = buffer_get(buf, &data, &count); + total += count; + luaL_addlstring(b, data, count); + buffer_skip(buf, count); + } + if (err == IO_CLOSED) { + if (total > 0) return IO_DONE; + else return IO_CLOSED; + } else return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads a line terminated by a CR LF pair or just by a LF. The CR and LF +* are not returned by the function and are discarded from the buffer +\*-------------------------------------------------------------------------*/ +static int recvline(p_buffer buf, luaL_Buffer *b) { + int err = IO_DONE; + while (err == IO_DONE) { + size_t count, pos; const char *data; + err = buffer_get(buf, &data, &count); + pos = 0; + while (pos < count && data[pos] != '\n') { + /* we ignore all \r's */ + if (data[pos] != '\r') luaL_addchar(b, data[pos]); + pos++; + } + if (pos < count) { /* found '\n' */ + buffer_skip(buf, pos+1); /* skip '\n' too */ + break; /* we are done */ + } else /* reached the end of the buffer */ + buffer_skip(buf, pos); + } + return err; +} + +/*-------------------------------------------------------------------------*\ +* Skips a given number of bytes from read buffer. No data is read from the +* transport layer +\*-------------------------------------------------------------------------*/ +static void buffer_skip(p_buffer buf, size_t count) { + buf->received += count; + buf->first += count; + if (buffer_isempty(buf)) + buf->first = buf->last = 0; +} + +/*-------------------------------------------------------------------------*\ +* Return any data available in buffer, or get more data from transport layer +* if buffer is empty +\*-------------------------------------------------------------------------*/ +static int buffer_get(p_buffer buf, const char **data, size_t *count) { + int err = IO_DONE; + p_io io = buf->io; + p_timeout tm = buf->tm; + if (buffer_isempty(buf)) { + size_t got; + err = io->recv(io->ctx, buf->data, BUF_SIZE, &got, tm); + buf->first = 0; + buf->last = got; + } + *count = buf->last - buf->first; + *data = buf->data + buf->first; + return err; +} diff --git a/libraries/luasocket/libluasocket/buffer.h b/libraries/luasocket/libluasocket/buffer.h new file mode 100644 index 000000000..a0901fcc8 --- /dev/null +++ b/libraries/luasocket/libluasocket/buffer.h @@ -0,0 +1,52 @@ +#ifndef BUF_H +#define BUF_H +/*=========================================================================*\ +* Input/Output interface for Lua programs +* LuaSocket toolkit +* +* Line patterns require buffering. Reading one character at a time involves +* too many system calls and is very slow. This module implements the +* LuaSocket interface for input/output on connected objects, as seen by +* Lua programs. +* +* Input is buffered. Output is *not* buffered because there was no simple +* way of making sure the buffered output data would ever be sent. +* +* The module is built on top of the I/O abstraction defined in io.h and the +* timeout management is done with the timeout.h interface. +\*=========================================================================*/ +#include "luasocket.h" +#include "io.h" +#include "timeout.h" + +/* buffer size in bytes */ +#define BUF_SIZE 8192 + +/* buffer control structure */ +typedef struct t_buffer_ { + double birthday; /* throttle support info: creation time, */ + size_t sent, received; /* bytes sent, and bytes received */ + p_io io; /* IO driver used for this buffer */ + p_timeout tm; /* timeout management for this buffer */ + size_t first, last; /* index of first and last bytes of stored data */ + char data[BUF_SIZE]; /* storage space for buffer data */ +} t_buffer; +typedef t_buffer *p_buffer; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int buffer_open(lua_State *L); +void buffer_init(p_buffer buf, p_io io, p_timeout tm); +int buffer_meth_getstats(lua_State *L, p_buffer buf); +int buffer_meth_setstats(lua_State *L, p_buffer buf); +int buffer_meth_send(lua_State *L, p_buffer buf); +int buffer_meth_receive(lua_State *L, p_buffer buf); +int buffer_isempty(p_buffer buf); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* BUF_H */ diff --git a/libraries/luasocket/libluasocket/compat.c b/libraries/luasocket/libluasocket/compat.c new file mode 100644 index 000000000..34ffdaf71 --- /dev/null +++ b/libraries/luasocket/libluasocket/compat.c @@ -0,0 +1,39 @@ +#include "luasocket.h" +#include "compat.h" + +#if LUA_VERSION_NUM==501 + +/* +** Adapted from Lua 5.2 +*/ +void luasocket_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { + luaL_checkstack(L, nup+1, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ + int i; + lua_pushstring(L, l->name); + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -(nup+1)); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + lua_settable(L, -(nup + 3)); + } + lua_pop(L, nup); /* remove upvalues */ +} + +/* +** Duplicated from Lua 5.2 +*/ +void *luasocket_testudata (lua_State *L, int ud, const char *tname) { + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + luaL_getmetatable(L, tname); /* get correct metatable */ + if (!lua_rawequal(L, -1, -2)) /* not the same? */ + p = NULL; /* value is a userdata with wrong metatable */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + } + return NULL; /* value is not a userdata with a metatable */ +} + +#endif diff --git a/libraries/luasocket/libluasocket/compat.h b/libraries/luasocket/libluasocket/compat.h new file mode 100644 index 000000000..fa2d7d7c6 --- /dev/null +++ b/libraries/luasocket/libluasocket/compat.h @@ -0,0 +1,22 @@ +#ifndef COMPAT_H +#define COMPAT_H + +#if LUA_VERSION_NUM==501 + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +void luasocket_setfuncs (lua_State *L, const luaL_Reg *l, int nup); +void *luasocket_testudata ( lua_State *L, int arg, const char *tname); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#define luaL_setfuncs luasocket_setfuncs +#define luaL_testudata luasocket_testudata + +#endif + +#endif diff --git a/libraries/luasocket/libluasocket/except.c b/libraries/luasocket/libluasocket/except.c new file mode 100644 index 000000000..9c3317f26 --- /dev/null +++ b/libraries/luasocket/libluasocket/except.c @@ -0,0 +1,129 @@ +/*=========================================================================*\ +* Simple exception support +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "except.h" +#include + +#if LUA_VERSION_NUM < 502 +#define lua_pcallk(L, na, nr, err, ctx, cont) \ + (((void)ctx),((void)cont),lua_pcall(L, na, nr, err)) +#endif + +#if LUA_VERSION_NUM < 503 +typedef int lua_KContext; +#endif + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int global_protect(lua_State *L); +static int global_newtry(lua_State *L); +static int protected_(lua_State *L); +static int finalize(lua_State *L); +static int do_nothing(lua_State *L); + +/* except functions */ +static luaL_Reg func[] = { + {"newtry", global_newtry}, + {"protect", global_protect}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Try factory +\*-------------------------------------------------------------------------*/ +static void wrap(lua_State *L) { + lua_createtable(L, 1, 0); + lua_pushvalue(L, -2); + lua_rawseti(L, -2, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_setmetatable(L, -2); +} + +static int finalize(lua_State *L) { + if (!lua_toboolean(L, 1)) { + lua_pushvalue(L, lua_upvalueindex(2)); + lua_call(L, 0, 0); + lua_settop(L, 2); + wrap(L); + lua_error(L); + return 0; + } else return lua_gettop(L); +} + +static int do_nothing(lua_State *L) { + (void) L; + return 0; +} + +static int global_newtry(lua_State *L) { + lua_settop(L, 1); + if (lua_isnil(L, 1)) lua_pushcfunction(L, do_nothing); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, -2); + lua_pushcclosure(L, finalize, 2); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Protect factory +\*-------------------------------------------------------------------------*/ +static int unwrap(lua_State *L) { + if (lua_istable(L, -1) && lua_getmetatable(L, -1)) { + int r = lua_rawequal(L, -1, lua_upvalueindex(1)); + lua_pop(L, 1); + if (r) { + lua_pushnil(L); + lua_rawgeti(L, -2, 1); + return 1; + } + } + return 0; +} + +static int protected_finish(lua_State *L, int status, lua_KContext ctx) { + (void)ctx; + if (status != 0 && status != LUA_YIELD) { + if (unwrap(L)) return 2; + else return lua_error(L); + } else return lua_gettop(L); +} + +#if LUA_VERSION_NUM == 502 +static int protected_cont(lua_State *L) { + int ctx = 0; + int status = lua_getctx(L, &ctx); + return protected_finish(L, status, ctx); +} +#else +#define protected_cont protected_finish +#endif + +static int protected_(lua_State *L) { + int status; + lua_pushvalue(L, lua_upvalueindex(2)); + lua_insert(L, 1); + status = lua_pcallk(L, lua_gettop(L) - 1, LUA_MULTRET, 0, 0, protected_cont); + return protected_finish(L, status, 0); +} + +static int global_protect(lua_State *L) { + lua_settop(L, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + lua_pushcclosure(L, protected_, 2); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Init module +\*-------------------------------------------------------------------------*/ +int except_open(lua_State *L) { + lua_newtable(L); /* metatable for wrapped exceptions */ + lua_pushboolean(L, 0); + lua_setfield(L, -2, "__metatable"); + luaL_setfuncs(L, func, 1); + return 0; +} diff --git a/libraries/luasocket/libluasocket/except.h b/libraries/luasocket/libluasocket/except.h new file mode 100644 index 000000000..71c31fd4d --- /dev/null +++ b/libraries/luasocket/libluasocket/except.h @@ -0,0 +1,46 @@ +#ifndef EXCEPT_H +#define EXCEPT_H +/*=========================================================================*\ +* Exception control +* LuaSocket toolkit (but completely independent from other modules) +* +* This provides support for simple exceptions in Lua. During the +* development of the HTTP/FTP/SMTP support, it became aparent that +* error checking was taking a substantial amount of the coding. These +* function greatly simplify the task of checking errors. +* +* The main idea is that functions should return nil as their first return +* values when they find an error, and return an error message (or value) +* following nil. In case of success, as long as the first value is not nil, +* the other values don't matter. +* +* The idea is to nest function calls with the "try" function. This function +* checks the first value, and, if it's falsy, wraps the second value in a +* table with metatable and calls "error" on it. Otherwise, it returns all +* values it received. Basically, it works like the Lua "assert" function, +* but it creates errors targeted specifically at "protect". +* +* The "newtry" function is a factory for "try" functions that call a +* finalizer in protected mode before calling "error". +* +* The "protect" function returns a new function that behaves exactly like +* the function it receives, but the new function catches exceptions thrown +* by "try" functions and returns nil followed by the error message instead. +* +* With these three functions, it's easy to write functions that throw +* exceptions on error, but that don't interrupt the user script. +\*=========================================================================*/ + +#include "luasocket.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int except_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif diff --git a/libraries/luasocket/libluasocket/ftp.lua b/libraries/luasocket/libluasocket/ftp.lua new file mode 100644 index 000000000..a2f6563b8 --- /dev/null +++ b/libraries/luasocket/libluasocket/ftp.lua @@ -0,0 +1,332 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- FTP support for the Lua language +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local table = require("table") +local string = require("string") +local math = require("math") +local socket = require("socket") +local url = require("socket.url") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +socket.ftp = {} +local _M = socket.ftp +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in seconds before the program gives up on a connection +_M.TIMEOUT = 60 +-- default port for ftp service +local PORT = 21 +-- this is the default anonymous password. used when no password is +-- provided in url. should be changed to your e-mail. +_M.USER = "ftp" +_M.PASSWORD = "anonymous@anonymous.org" + +----------------------------------------------------------------------------- +-- Low level FTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) + local f = base.setmetatable({ tp = tp }, metat) + -- make sure everything gets closed in an exception + f.try = socket.newtry(function() f:close() end) + return f +end + +function metat.__index:portconnect() + self.try(self.server:settimeout(_M.TIMEOUT)) + self.data = self.try(self.server:accept()) + self.try(self.data:settimeout(_M.TIMEOUT)) +end + +function metat.__index:pasvconnect() + self.data = self.try(socket.tcp()) + self.try(self.data:settimeout(_M.TIMEOUT)) + self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) +end + +function metat.__index:login(user, password) + self.try(self.tp:command("user", user or _M.USER)) + local code, _ = self.try(self.tp:check{"2..", 331}) + if code == 331 then + self.try(self.tp:command("pass", password or _M.PASSWORD)) + self.try(self.tp:check("2..")) + end + return 1 +end + +function metat.__index:pasv() + self.try(self.tp:command("pasv")) + local _, reply = self.try(self.tp:check("2..")) + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) + self.try(a and b and c and d and p1 and p2, reply) + self.pasvt = { + address = string.format("%d.%d.%d.%d", a, b, c, d), + port = p1*256 + p2 + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + +function metat.__index:epsv() + self.try(self.tp:command("epsv")) + local _, reply = self.try(self.tp:check("229")) + local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" + local _, _, _, port = string.match(reply, pattern) + self.try(port, "invalid epsv response") + self.pasvt = { + address = self.tp:getpeername(), + port = port + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + + +function metat.__index:port(address, port) + self.pasvt = nil + if not address then + address = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local pl = math.mod(port, 256) + local ph = (port - pl)/256 + local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") + self.try(self.tp:command("port", arg)) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:eprt(family, address, port) + self.pasvt = nil + if not address then + address = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local arg = string.format("|%s|%s|%d|", family, address, port) + self.try(self.tp:command("eprt", arg)) + self.try(self.tp:check("2..")) + return 1 +end + + +function metat.__index:send(sendt) + self.try(self.pasvt or self.server, "need port or pasv first") + -- if there is a pasvt table, we already sent a PASV command + -- we just get the data connection into self.data + if self.pasvt then self:pasvconnect() end + -- get the transfer argument and command + local argument = sendt.argument or + url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = sendt.command or "stor" + -- send the transfer command and check the reply + self.try(self.tp:command(command, argument)) + local code, _ = self.try(self.tp:check{"2..", "1.."}) + -- if there is not a pasvt table, then there is a server + -- and we already sent a PORT command + if not self.pasvt then self:portconnect() end + -- get the sink, source and step for the transfer + local step = sendt.step or ltn12.pump.step + local readt = { self.tp } + local checkstep = function(src, snk) + -- check status in control connection while downloading + local readyt = socket.select(readt, nil, 0) + if readyt[tp] then code = self.try(self.tp:check("2..")) end + return step(src, snk) + end + local sink = socket.sink("close-when-done", self.data) + -- transfer all data and check error + self.try(ltn12.pump.all(sendt.source, sink, checkstep)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + -- done with data connection + self.data:close() + -- find out how many bytes were sent + local sent = socket.skip(1, self.data:getstats()) + self.data = nil + return sent +end + +function metat.__index:receive(recvt) + self.try(self.pasvt or self.server, "need port or pasv first") + if self.pasvt then self:pasvconnect() end + local argument = recvt.argument or + url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = recvt.command or "retr" + self.try(self.tp:command(command, argument)) + local code,reply = self.try(self.tp:check{"1..", "2.."}) + if (code >= 200) and (code <= 299) then + recvt.sink(reply) + return 1 + end + if not self.pasvt then self:portconnect() end + local source = socket.source("until-closed", self.data) + local step = recvt.step or ltn12.pump.step + self.try(ltn12.pump.all(source, recvt.sink, step)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + self.data:close() + self.data = nil + return 1 +end + +function metat.__index:cwd(dir) + self.try(self.tp:command("cwd", dir)) + self.try(self.tp:check(250)) + return 1 +end + +function metat.__index:type(type) + self.try(self.tp:command("type", type)) + self.try(self.tp:check(200)) + return 1 +end + +function metat.__index:greet() + local code = self.try(self.tp:check{"1..", "2.."}) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + return 1 +end + +function metat.__index:quit() + self.try(self.tp:command("quit")) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:close() + if self.data then self.data:close() end + if self.server then self.server:close() end + return self.tp:close() +end + +----------------------------------------------------------------------------- +-- High level FTP API +----------------------------------------------------------------------------- +local function override(t) + if t.url then + local u = url.parse(t.url) + for i,v in base.pairs(t) do + u[i] = v + end + return u + else return t end +end + +local function tput(putt) + putt = override(putt) + socket.try(putt.host, "missing hostname") + local f = _M.open(putt.host, putt.port, putt.create) + f:greet() + f:login(putt.user, putt.password) + if putt.type then f:type(putt.type) end + f:epsv() + local sent = f:send(putt) + f:quit() + f:close() + return sent +end + +local default = { + path = "/", + scheme = "ftp" +} + +local function genericform(u) + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t +end + +_M.genericform = genericform + +local function sput(u, body) + local putt = genericform(u) + putt.source = ltn12.source.string(body) + return tput(putt) +end + +_M.put = socket.protect(function(putt, body) + if base.type(putt) == "string" then return sput(putt, body) + else return tput(putt) end +end) + +local function tget(gett) + gett = override(gett) + socket.try(gett.host, "missing hostname") + local f = _M.open(gett.host, gett.port, gett.create) + f:greet() + f:login(gett.user, gett.password) + if gett.type then f:type(gett.type) end + f:epsv() + f:receive(gett) + f:quit() + return f:close() +end + +local function sget(u) + local gett = genericform(u) + local t = {} + gett.sink = ltn12.sink.table(t) + tget(gett) + return table.concat(t) +end + +_M.command = socket.protect(function(cmdt) + cmdt = override(cmdt) + socket.try(cmdt.host, "missing hostname") + socket.try(cmdt.command, "missing command") + local f = _M.open(cmdt.host, cmdt.port, cmdt.create) + f:greet() + f:login(cmdt.user, cmdt.password) + if type(cmdt.command) == "table" then + local argument = cmdt.argument or {} + local check = cmdt.check or {} + for i,cmd in ipairs(cmdt.command) do + f.try(f.tp:command(cmd, argument[i])) + if check[i] then f.try(f.tp:check(check[i])) end + end + else + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + end + f:quit() + return f:close() +end) + +_M.get = socket.protect(function(gett) + if base.type(gett) == "string" then return sget(gett) + else return tget(gett) end +end) + +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/headers.lua b/libraries/luasocket/libluasocket/headers.lua new file mode 100644 index 000000000..05818703f --- /dev/null +++ b/libraries/luasocket/libluasocket/headers.lua @@ -0,0 +1,107 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- Canonic header field capitalization +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- +local socket = require("socket") +socket.headers = {} +local _M = socket.headers + +_M.canonic = { + ["accept"] = "Accept", + ["accept-charset"] = "Accept-Charset", + ["accept-encoding"] = "Accept-Encoding", + ["accept-language"] = "Accept-Language", + ["accept-ranges"] = "Accept-Ranges", + ["action"] = "Action", + ["alternate-recipient"] = "Alternate-Recipient", + ["age"] = "Age", + ["allow"] = "Allow", + ["arrival-date"] = "Arrival-Date", + ["authorization"] = "Authorization", + ["bcc"] = "Bcc", + ["cache-control"] = "Cache-Control", + ["cc"] = "Cc", + ["comments"] = "Comments", + ["connection"] = "Connection", + ["content-description"] = "Content-Description", + ["content-disposition"] = "Content-Disposition", + ["content-encoding"] = "Content-Encoding", + ["content-id"] = "Content-ID", + ["content-language"] = "Content-Language", + ["content-length"] = "Content-Length", + ["content-location"] = "Content-Location", + ["content-md5"] = "Content-MD5", + ["content-range"] = "Content-Range", + ["content-transfer-encoding"] = "Content-Transfer-Encoding", + ["content-type"] = "Content-Type", + ["cookie"] = "Cookie", + ["date"] = "Date", + ["diagnostic-code"] = "Diagnostic-Code", + ["dsn-gateway"] = "DSN-Gateway", + ["etag"] = "ETag", + ["expect"] = "Expect", + ["expires"] = "Expires", + ["final-log-id"] = "Final-Log-ID", + ["final-recipient"] = "Final-Recipient", + ["from"] = "From", + ["host"] = "Host", + ["if-match"] = "If-Match", + ["if-modified-since"] = "If-Modified-Since", + ["if-none-match"] = "If-None-Match", + ["if-range"] = "If-Range", + ["if-unmodified-since"] = "If-Unmodified-Since", + ["in-reply-to"] = "In-Reply-To", + ["keywords"] = "Keywords", + ["last-attempt-date"] = "Last-Attempt-Date", + ["last-modified"] = "Last-Modified", + ["location"] = "Location", + ["max-forwards"] = "Max-Forwards", + ["message-id"] = "Message-ID", + ["mime-version"] = "MIME-Version", + ["original-envelope-id"] = "Original-Envelope-ID", + ["original-recipient"] = "Original-Recipient", + ["pragma"] = "Pragma", + ["proxy-authenticate"] = "Proxy-Authenticate", + ["proxy-authorization"] = "Proxy-Authorization", + ["range"] = "Range", + ["received"] = "Received", + ["received-from-mta"] = "Received-From-MTA", + ["references"] = "References", + ["referer"] = "Referer", + ["remote-mta"] = "Remote-MTA", + ["reply-to"] = "Reply-To", + ["reporting-mta"] = "Reporting-MTA", + ["resent-bcc"] = "Resent-Bcc", + ["resent-cc"] = "Resent-Cc", + ["resent-date"] = "Resent-Date", + ["resent-from"] = "Resent-From", + ["resent-message-id"] = "Resent-Message-ID", + ["resent-reply-to"] = "Resent-Reply-To", + ["resent-sender"] = "Resent-Sender", + ["resent-to"] = "Resent-To", + ["retry-after"] = "Retry-After", + ["return-path"] = "Return-Path", + ["sender"] = "Sender", + ["server"] = "Server", + ["smtp-remote-recipient"] = "SMTP-Remote-Recipient", + ["status"] = "Status", + ["subject"] = "Subject", + ["te"] = "TE", + ["to"] = "To", + ["trailer"] = "Trailer", + ["transfer-encoding"] = "Transfer-Encoding", + ["upgrade"] = "Upgrade", + ["user-agent"] = "User-Agent", + ["vary"] = "Vary", + ["via"] = "Via", + ["warning"] = "Warning", + ["will-retry-until"] = "Will-Retry-Until", + ["www-authenticate"] = "WWW-Authenticate", + ["x-mailer"] = "X-Mailer", +} + +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/http.lua b/libraries/luasocket/libluasocket/http.lua new file mode 100644 index 000000000..1d7672924 --- /dev/null +++ b/libraries/luasocket/libluasocket/http.lua @@ -0,0 +1,427 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- HTTP/1.1 client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +------------------------------------------------------------------------------- +local socket = require("socket") +local url = require("socket.url") +local ltn12 = require("ltn12") +local mime = require("mime") +local string = require("string") +local headers = require("socket.headers") +local base = _G +local table = require("table") +socket.http = {} +local _M = socket.http + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +_M.TIMEOUT = 60 +-- user agent field sent in request +_M.USERAGENT = socket._VERSION + +-- supported schemes and their particulars +local SCHEMES = { + http = { + port = 80 + , create = function(t) + return socket.tcp end } + , https = { + port = 443 + , create = function(t) + local https = assert( + require("ssl.https"), 'LuaSocket: LuaSec not found') + local tcp = assert( + https.tcp, 'LuaSocket: Function tcp() not available from LuaSec') + return tcp(t) end }} + +----------------------------------------------------------------------------- +-- Reads MIME headers from a connection, unfolding where needed +----------------------------------------------------------------------------- +local function receiveheaders(sock, headers) + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) + line, err = sock:receive() + if err then return nil, err end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line = sock:receive() + if err then return nil, err end + end + -- save pair in table + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + return headers +end + +----------------------------------------------------------------------------- +-- Extra sources and sinks +----------------------------------------------------------------------------- +socket.sourcet["http-chunked"] = function(sock, headers) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err, _ = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) +end + +socket.sinkt["http-chunked"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) +end + +----------------------------------------------------------------------------- +-- Low level HTTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(host, port, create) + -- create socket with user connect function, or with default + local c = socket.try(create()) + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + h.try(c:settimeout(_M.TIMEOUT)) + h.try(c:connect(host, port)) + -- here everything worked + return h +end + +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) +end + +function metat.__index:sendheaders(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f, v in base.pairs(tosend) do + h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) +end + +function metat.__index:receivestatusline() + local status,ec = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then + if ec == "timeout" then + return 408 + end + return nil, status + end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) +end + +function metat.__index:receiveheaders() + return self.try(receiveheaders(self.c)) +end + +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) +end + +function metat.__index:receive09body(status, sink, step) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) +end + +function metat.__index:close() + return self.c:close() +end + +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function adjusturi(reqt) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not _M.PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) +end + +local function adjustproxy(reqt) + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end +end + +local function adjustheaders(reqt) + -- default headers + local host = reqt.host + local port = tostring(reqt.port) + if port ~= tostring(SCHEMES[reqt.scheme].port) then + host = host .. ':' .. port end + local lower = { + ["user-agent"] = _M.USERAGENT, + ["host"] = host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. + url.unescape(reqt.password))) + end + -- if we have proxy authentication information, pass it along + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + if proxy.user and proxy.password then + lower["proxy-authorization"] = + "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password)) + end + end + -- override with user headers + for i,v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +-- default url parts +local default = { + path ="/" + , scheme = "http" +} + +local function adjustrequest(reqt) + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i,v in base.pairs(reqt) do nreqt[i] = v end + -- default to scheme particulars + local schemedefs, host, port, method + = SCHEMES[nreqt.scheme], nreqt.host, nreqt.port, nreqt.method + if not nreqt.create then nreqt.create = schemedefs.create(nreqt) end + if not (port and port ~= '') then nreqt.port = schemedefs.port end + if not (method and method ~= '') then nreqt.method = 'GET' end + if not (host and host ~= "") then + socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'") + end + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + if nreqt.source + and not nreqt.headers["content-length"] + and not nreqt.headers["transfer-encoding"] + then + nreqt.headers["transfer-encoding"] = "chunked" + end + + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + return nreqt +end + +local function shouldredirect(reqt, code, headers) + local location = headers.location + if not location then return false end + location = string.gsub(location, "%s", "") + if location == "" then return false end + local scheme = url.parse(location).scheme + if scheme and (not SCHEMES[scheme]) then return false end + -- avoid https downgrades + if ('https' == reqt.scheme) and ('https' ~= scheme) then return false end + return (reqt.redirect ~= false) and + (code == 301 or code == 302 or code == 303 or code == 307) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and ((false == reqt.maxredirects) + or ((reqt.nredirects or 0) + < (reqt.maxredirects or 5))) +end + +local function shouldreceivebody(reqt, code) + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +-- forward declarations +local trequest, tredirect + +--[[local]] function tredirect(reqt, location) + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + local newurl = url.absolute(reqt.url, location) + -- if switching schemes, reset port and create function + if url.parse(newurl).scheme ~= reqt.scheme then + reqt.port = nil + reqt.create = nil end + -- make new request + local result, code, headers, status = trequest { + url = newurl, + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + maxredirects = reqt.maxredirects, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status +end + +--[[local]] function trequest(reqt) + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = _M.open(nreqt.host, nreqt.port, nreqt.create) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + elseif code == 408 then + return 1, code + end + local headers + -- ignore any 100-continue messages + while code == 100 do + h:receiveheaders() + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then + h:close() + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status +end + +-- turns an url and a body into a generic request +local function genericform(u, b) + local t = {} + local reqt = { + url = u, + sink = ltn12.sink.table(t), + target = t + } + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" + } + reqt.method = "POST" + end + return reqt +end + +_M.genericform = genericform + +local function srequest(u, b) + local reqt = genericform(u, b) + local _, code, headers, status = trequest(reqt) + return table.concat(reqt.target), code, headers, status +end + +_M.request = socket.protect(function(reqt, body) + if base.type(reqt) == "string" then return srequest(reqt, body) + else return trequest(reqt) end +end) + +_M.schemes = SCHEMES +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/inet.c b/libraries/luasocket/libluasocket/inet.c new file mode 100644 index 000000000..138c9abe8 --- /dev/null +++ b/libraries/luasocket/libluasocket/inet.c @@ -0,0 +1,537 @@ +/*=========================================================================*\ +* Internet domain functions +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "inet.h" + +#include +#include +#include + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int inet_global_toip(lua_State *L); +static int inet_global_getaddrinfo(lua_State *L); +static int inet_global_tohostname(lua_State *L); +static int inet_global_getnameinfo(lua_State *L); +static void inet_pushresolved(lua_State *L, struct hostent *hp); +static int inet_global_gethostname(lua_State *L); + +/* DNS functions */ +static luaL_Reg func[] = { + { "toip", inet_global_toip}, + { "getaddrinfo", inet_global_getaddrinfo}, + { "tohostname", inet_global_tohostname}, + { "getnameinfo", inet_global_getnameinfo}, + { "gethostname", inet_global_gethostname}, + { NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int inet_open(lua_State *L) +{ + lua_pushstring(L, "dns"); + lua_newtable(L); + luaL_setfuncs(L, func, 0); + lua_settable(L, -3); + return 0; +} + +/*=========================================================================*\ +* Global Lua functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Returns all information provided by the resolver given a host name +* or ip address +\*-------------------------------------------------------------------------*/ +static int inet_gethost(const char *address, struct hostent **hp) { + struct in_addr addr; + if (inet_aton(address, &addr)) + return socket_gethostbyaddr((char *) &addr, sizeof(addr), hp); + else + return socket_gethostbyname(address, hp); +} + +/*-------------------------------------------------------------------------*\ +* Returns all information provided by the resolver given a host name +* or ip address +\*-------------------------------------------------------------------------*/ +static int inet_global_tohostname(lua_State *L) { + const char *address = luaL_checkstring(L, 1); + struct hostent *hp = NULL; + int err = inet_gethost(address, &hp); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_hoststrerror(err)); + return 2; + } + lua_pushstring(L, hp->h_name); + inet_pushresolved(L, hp); + return 2; +} + +static int inet_global_getnameinfo(lua_State *L) { + char hbuf[NI_MAXHOST]; + char sbuf[NI_MAXSERV]; + int i, ret; + struct addrinfo hints; + struct addrinfo *resolved, *iter; + const char *host = luaL_optstring(L, 1, NULL); + const char *serv = luaL_optstring(L, 2, NULL); + + if (!(host || serv)) + luaL_error(L, "host and serv cannot be both nil"); + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + + ret = getaddrinfo(host, serv, &hints, &resolved); + if (ret != 0) { + lua_pushnil(L); + lua_pushstring(L, socket_gaistrerror(ret)); + return 2; + } + + lua_newtable(L); + for (i = 1, iter = resolved; iter; i++, iter = iter->ai_next) { + getnameinfo(iter->ai_addr, (socklen_t) iter->ai_addrlen, + hbuf, host? (socklen_t) sizeof(hbuf): 0, + sbuf, serv? (socklen_t) sizeof(sbuf): 0, 0); + if (host) { + lua_pushnumber(L, i); + lua_pushstring(L, hbuf); + lua_settable(L, -3); + } + } + freeaddrinfo(resolved); + + if (serv) { + lua_pushstring(L, sbuf); + return 2; + } else { + return 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns all information provided by the resolver given a host name +* or ip address +\*-------------------------------------------------------------------------*/ +static int inet_global_toip(lua_State *L) +{ + const char *address = luaL_checkstring(L, 1); + struct hostent *hp = NULL; + int err = inet_gethost(address, &hp); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_hoststrerror(err)); + return 2; + } + lua_pushstring(L, inet_ntoa(*((struct in_addr *) hp->h_addr))); + inet_pushresolved(L, hp); + return 2; +} + +int inet_optfamily(lua_State* L, int narg, const char* def) +{ + static const char* optname[] = { "unspec", "inet", "inet6", NULL }; + static int optvalue[] = { AF_UNSPEC, AF_INET, AF_INET6, 0 }; + + return optvalue[luaL_checkoption(L, narg, def, optname)]; +} + +int inet_optsocktype(lua_State* L, int narg, const char* def) +{ + static const char* optname[] = { "stream", "dgram", NULL }; + static int optvalue[] = { SOCK_STREAM, SOCK_DGRAM, 0 }; + + return optvalue[luaL_checkoption(L, narg, def, optname)]; +} + +static int inet_global_getaddrinfo(lua_State *L) +{ + const char *hostname = luaL_checkstring(L, 1); + struct addrinfo *iterator = NULL, *resolved = NULL; + struct addrinfo hints; + int i = 1, ret = 0; + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + ret = getaddrinfo(hostname, NULL, &hints, &resolved); + if (ret != 0) { + lua_pushnil(L); + lua_pushstring(L, socket_gaistrerror(ret)); + return 2; + } + lua_newtable(L); + for (iterator = resolved; iterator; iterator = iterator->ai_next) { + char hbuf[NI_MAXHOST]; + ret = getnameinfo(iterator->ai_addr, (socklen_t) iterator->ai_addrlen, + hbuf, (socklen_t) sizeof(hbuf), NULL, 0, NI_NUMERICHOST); + if (ret){ + freeaddrinfo(resolved); + lua_pushnil(L); + lua_pushstring(L, socket_gaistrerror(ret)); + return 2; + } + lua_pushnumber(L, i); + lua_newtable(L); + switch (iterator->ai_family) { + case AF_INET: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "inet"); + lua_settable(L, -3); + break; + case AF_INET6: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "inet6"); + lua_settable(L, -3); + break; + case AF_UNSPEC: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "unspec"); + lua_settable(L, -3); + break; + default: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "unknown"); + lua_settable(L, -3); + break; + } + lua_pushliteral(L, "addr"); + lua_pushstring(L, hbuf); + lua_settable(L, -3); + lua_settable(L, -3); + i++; + } + freeaddrinfo(resolved); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Gets the host name +\*-------------------------------------------------------------------------*/ +static int inet_global_gethostname(lua_State *L) +{ + char name[257]; + name[256] = '\0'; + if (gethostname(name, 256) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } else { + lua_pushstring(L, name); + return 1; + } +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Retrieves socket peer name +\*-------------------------------------------------------------------------*/ +int inet_meth_getpeername(lua_State *L, p_socket ps, int family) +{ + int err; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); + char name[INET6_ADDRSTRLEN]; + char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ + if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + err = getnameinfo((struct sockaddr *) &peer, peer_len, + name, INET6_ADDRSTRLEN, + port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); + if (err) { + lua_pushnil(L); + lua_pushstring(L, LUA_GAI_STRERROR(err)); + return 2; + } + lua_pushstring(L, name); + lua_pushinteger(L, (int) strtol(port, (char **) NULL, 10)); + switch (family) { + case AF_INET: lua_pushliteral(L, "inet"); break; + case AF_INET6: lua_pushliteral(L, "inet6"); break; + case AF_UNSPEC: lua_pushliteral(L, "unspec"); break; + default: lua_pushliteral(L, "unknown"); break; + } + return 3; +} + +/*-------------------------------------------------------------------------*\ +* Retrieves socket local name +\*-------------------------------------------------------------------------*/ +int inet_meth_getsockname(lua_State *L, p_socket ps, int family) +{ + int err; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); + char name[INET6_ADDRSTRLEN]; + char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ + if (getsockname(*ps, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + err=getnameinfo((struct sockaddr *)&peer, peer_len, + name, INET6_ADDRSTRLEN, port, 6, NI_NUMERICHOST | NI_NUMERICSERV); + if (err) { + lua_pushnil(L); + lua_pushstring(L, LUA_GAI_STRERROR(err)); + return 2; + } + lua_pushstring(L, name); + lua_pushstring(L, port); + switch (family) { + case AF_INET: lua_pushliteral(L, "inet"); break; + case AF_INET6: lua_pushliteral(L, "inet6"); break; + case AF_UNSPEC: lua_pushliteral(L, "unspec"); break; + default: lua_pushliteral(L, "unknown"); break; + } + return 3; +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Passes all resolver information to Lua as a table +\*-------------------------------------------------------------------------*/ +static void inet_pushresolved(lua_State *L, struct hostent *hp) +{ + char **alias; + struct in_addr **addr; + int i, resolved; + lua_newtable(L); resolved = lua_gettop(L); + lua_pushstring(L, "name"); + lua_pushstring(L, hp->h_name); + lua_settable(L, resolved); + lua_pushstring(L, "ip"); + lua_pushstring(L, "alias"); + i = 1; + alias = hp->h_aliases; + lua_newtable(L); + if (alias) { + while (*alias) { + lua_pushnumber(L, i); + lua_pushstring(L, *alias); + lua_settable(L, -3); + i++; alias++; + } + } + lua_settable(L, resolved); + i = 1; + lua_newtable(L); + addr = (struct in_addr **) hp->h_addr_list; + if (addr) { + while (*addr) { + lua_pushnumber(L, i); + lua_pushstring(L, inet_ntoa(**addr)); + lua_settable(L, -3); + i++; addr++; + } + } + lua_settable(L, resolved); +} + +/*-------------------------------------------------------------------------*\ +* Tries to create a new inet socket +\*-------------------------------------------------------------------------*/ +const char *inet_trycreate(p_socket ps, int family, int type, int protocol) { + const char *err = socket_strerror(socket_create(ps, family, type, protocol)); + if (err == NULL && family == AF_INET6) { + int yes = 1; + setsockopt(*ps, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&yes, sizeof(yes)); + } + return err; +} + +/*-------------------------------------------------------------------------*\ +* "Disconnects" a DGRAM socket +\*-------------------------------------------------------------------------*/ +const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm) +{ + switch (family) { + case AF_INET: { + struct sockaddr_in sin; + memset((char *) &sin, 0, sizeof(sin)); + sin.sin_family = AF_UNSPEC; + sin.sin_addr.s_addr = INADDR_ANY; + return socket_strerror(socket_connect(ps, (SA *) &sin, + sizeof(sin), tm)); + } + case AF_INET6: { + struct sockaddr_in6 sin6; + struct in6_addr addrany = IN6ADDR_ANY_INIT; + memset((char *) &sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_UNSPEC; + sin6.sin6_addr = addrany; + return socket_strerror(socket_connect(ps, (SA *) &sin6, + sizeof(sin6), tm)); + } + } + return NULL; +} + +/*-------------------------------------------------------------------------*\ +* Tries to connect to remote address (address, port) +\*-------------------------------------------------------------------------*/ +const char *inet_tryconnect(p_socket ps, int *family, const char *address, + const char *serv, p_timeout tm, struct addrinfo *connecthints) +{ + struct addrinfo *iterator = NULL, *resolved = NULL; + const char *err = NULL; + int current_family = *family; + /* try resolving */ + err = socket_gaistrerror(getaddrinfo(address, serv, + connecthints, &resolved)); + if (err != NULL) { + if (resolved) freeaddrinfo(resolved); + return err; + } + for (iterator = resolved; iterator; iterator = iterator->ai_next) { + timeout_markstart(tm); + /* create new socket if necessary. if there was no + * bind, we need to create one for every new family + * that shows up while iterating. if there was a + * bind, all families will be the same and we will + * not enter this branch. */ + if (current_family != iterator->ai_family || *ps == SOCKET_INVALID) { + socket_destroy(ps); + err = inet_trycreate(ps, iterator->ai_family, + iterator->ai_socktype, iterator->ai_protocol); + if (err) continue; + current_family = iterator->ai_family; + /* set non-blocking before connect */ + socket_setnonblocking(ps); + } + /* try connecting to remote address */ + err = socket_strerror(socket_connect(ps, (SA *) iterator->ai_addr, + (socklen_t) iterator->ai_addrlen, tm)); + /* if success or timeout is zero, break out of loop */ + if (err == NULL || timeout_iszero(tm)) { + *family = current_family; + break; + } + } + freeaddrinfo(resolved); + /* here, if err is set, we failed */ + return err; +} + +/*-------------------------------------------------------------------------*\ +* Tries to accept a socket +\*-------------------------------------------------------------------------*/ +const char *inet_tryaccept(p_socket server, int family, p_socket client, + p_timeout tm) { + socklen_t len; + t_sockaddr_storage addr; + switch (family) { + case AF_INET6: len = sizeof(struct sockaddr_in6); break; + case AF_INET: len = sizeof(struct sockaddr_in); break; + default: len = sizeof(addr); break; + } + return socket_strerror(socket_accept(server, client, (SA *) &addr, + &len, tm)); +} + +/*-------------------------------------------------------------------------*\ +* Tries to bind socket to (address, port) +\*-------------------------------------------------------------------------*/ +const char *inet_trybind(p_socket ps, int *family, const char *address, + const char *serv, struct addrinfo *bindhints) { + struct addrinfo *iterator = NULL, *resolved = NULL; + const char *err = NULL; + int current_family = *family; + /* translate luasocket special values to C */ + if (strcmp(address, "*") == 0) address = NULL; + if (!serv) serv = "0"; + /* try resolving */ + err = socket_gaistrerror(getaddrinfo(address, serv, bindhints, &resolved)); + if (err) { + if (resolved) freeaddrinfo(resolved); + return err; + } + /* iterate over resolved addresses until one is good */ + for (iterator = resolved; iterator; iterator = iterator->ai_next) { + if (current_family != iterator->ai_family || *ps == SOCKET_INVALID) { + socket_destroy(ps); + err = inet_trycreate(ps, iterator->ai_family, + iterator->ai_socktype, iterator->ai_protocol); + if (err) continue; + current_family = iterator->ai_family; + } + /* try binding to local address */ + err = socket_strerror(socket_bind(ps, (SA *) iterator->ai_addr, + (socklen_t) iterator->ai_addrlen)); + /* keep trying unless bind succeeded */ + if (err == NULL) { + *family = current_family; + /* set to non-blocking after bind */ + socket_setnonblocking(ps); + break; + } + } + /* cleanup and return error */ + freeaddrinfo(resolved); + /* here, if err is set, we failed */ + return err; +} + +/*-------------------------------------------------------------------------*\ +* Some systems do not provide these so that we provide our own. +\*-------------------------------------------------------------------------*/ +#ifdef LUASOCKET_INET_ATON +int inet_aton(const char *cp, struct in_addr *inp) +{ + unsigned int a = 0, b = 0, c = 0, d = 0; + int n = 0, r; + unsigned long int addr = 0; + r = sscanf(cp, "%u.%u.%u.%u%n", &a, &b, &c, &d, &n); + if (r == 0 || n == 0) return 0; + cp += n; + if (*cp) return 0; + if (a > 255 || b > 255 || c > 255 || d > 255) return 0; + if (inp) { + addr += a; addr <<= 8; + addr += b; addr <<= 8; + addr += c; addr <<= 8; + addr += d; + inp->s_addr = htonl(addr); + } + return 1; +} +#endif + +#ifdef LUASOCKET_INET_PTON +int inet_pton(int af, const char *src, void *dst) +{ + struct addrinfo hints, *res; + int ret = 1; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = af; + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(src, NULL, &hints, &res) != 0) return -1; + if (af == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in *) res->ai_addr; + memcpy(dst, &in->sin_addr, sizeof(in->sin_addr)); + } else if (af == AF_INET6) { + struct sockaddr_in6 *in = (struct sockaddr_in6 *) res->ai_addr; + memcpy(dst, &in->sin6_addr, sizeof(in->sin6_addr)); + } else { + ret = -1; + } + freeaddrinfo(res); + return ret; +} + +#endif diff --git a/libraries/luasocket/libluasocket/inet.h b/libraries/luasocket/libluasocket/inet.h new file mode 100644 index 000000000..5618b61b3 --- /dev/null +++ b/libraries/luasocket/libluasocket/inet.h @@ -0,0 +1,56 @@ +#ifndef INET_H +#define INET_H +/*=========================================================================*\ +* Internet domain functions +* LuaSocket toolkit +* +* This module implements the creation and connection of internet domain +* sockets, on top of the socket.h interface, and the interface of with the +* resolver. +* +* The function inet_aton is provided for the platforms where it is not +* available. The module also implements the interface of the internet +* getpeername and getsockname functions as seen by Lua programs. +* +* The Lua functions toip and tohostname are also implemented here. +\*=========================================================================*/ +#include "luasocket.h" +#include "socket.h" +#include "timeout.h" + +#ifdef _WIN32 +#define LUASOCKET_INET_ATON +#endif + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int inet_open(lua_State *L); + +int inet_optfamily(lua_State* L, int narg, const char* def); +int inet_optsocktype(lua_State* L, int narg, const char* def); + +int inet_meth_getpeername(lua_State *L, p_socket ps, int family); +int inet_meth_getsockname(lua_State *L, p_socket ps, int family); + +const char *inet_trycreate(p_socket ps, int family, int type, int protocol); +const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm); +const char *inet_tryconnect(p_socket ps, int *family, const char *address, const char *serv, p_timeout tm, struct addrinfo *connecthints); +const char *inet_tryaccept(p_socket server, int family, p_socket client, p_timeout tm); +const char *inet_trybind(p_socket ps, int *family, const char *address, const char *serv, struct addrinfo *bindhints); + +#ifdef LUASOCKET_INET_ATON +int inet_aton(const char *cp, struct in_addr *inp); +#endif + +#ifdef LUASOCKET_INET_PTON +const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); +int inet_pton(int af, const char *src, void *dst); +#endif + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* INET_H */ diff --git a/libraries/luasocket/libluasocket/io.c b/libraries/luasocket/libluasocket/io.c new file mode 100644 index 000000000..5ad4b3afc --- /dev/null +++ b/libraries/luasocket/libluasocket/io.c @@ -0,0 +1,28 @@ +/*=========================================================================*\ +* Input/Output abstraction +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "io.h" + +/*-------------------------------------------------------------------------*\ +* Initializes C structure +\*-------------------------------------------------------------------------*/ +void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx) { + io->send = send; + io->recv = recv; + io->error = error; + io->ctx = ctx; +} + +/*-------------------------------------------------------------------------*\ +* I/O error strings +\*-------------------------------------------------------------------------*/ +const char *io_strerror(int err) { + switch (err) { + case IO_DONE: return NULL; + case IO_CLOSED: return "closed"; + case IO_TIMEOUT: return "timeout"; + default: return "unknown error"; + } +} diff --git a/libraries/luasocket/libluasocket/io.h b/libraries/luasocket/libluasocket/io.h new file mode 100644 index 000000000..b8a54df6e --- /dev/null +++ b/libraries/luasocket/libluasocket/io.h @@ -0,0 +1,70 @@ +#ifndef IO_H +#define IO_H +/*=========================================================================*\ +* Input/Output abstraction +* LuaSocket toolkit +* +* This module defines the interface that LuaSocket expects from the +* transport layer for streamed input/output. The idea is that if any +* transport implements this interface, then the buffer.c functions +* automatically work on it. +* +* The module socket.h implements this interface, and thus the module tcp.h +* is very simple. +\*=========================================================================*/ +#include "luasocket.h" +#include "timeout.h" + +/* IO error codes */ +enum { + IO_DONE = 0, /* operation completed successfully */ + IO_TIMEOUT = -1, /* operation timed out */ + IO_CLOSED = -2, /* the connection has been closed */ + IO_UNKNOWN = -3 +}; + +/* interface to error message function */ +typedef const char *(*p_error) ( + void *ctx, /* context needed by send */ + int err /* error code */ +); + +/* interface to send function */ +typedef int (*p_send) ( + void *ctx, /* context needed by send */ + const char *data, /* pointer to buffer with data to send */ + size_t count, /* number of bytes to send from buffer */ + size_t *sent, /* number of bytes sent uppon return */ + p_timeout tm /* timeout control */ +); + +/* interface to recv function */ +typedef int (*p_recv) ( + void *ctx, /* context needed by recv */ + char *data, /* pointer to buffer where data will be writen */ + size_t count, /* number of bytes to receive into buffer */ + size_t *got, /* number of bytes received uppon return */ + p_timeout tm /* timeout control */ +); + +/* IO driver definition */ +typedef struct t_io_ { + void *ctx; /* context needed by send/recv */ + p_send send; /* send function pointer */ + p_recv recv; /* receive function pointer */ + p_error error; /* strerror function */ +} t_io; +typedef t_io *p_io; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx); +const char *io_strerror(int err); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* IO_H */ diff --git a/libraries/luasocket/libluasocket/ltn12.lua b/libraries/luasocket/libluasocket/ltn12.lua new file mode 100644 index 000000000..e210b568a --- /dev/null +++ b/libraries/luasocket/libluasocket/ltn12.lua @@ -0,0 +1,321 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local unpack = unpack or table.unpack +local base = _G +local select = select + +local _M = {} +if module then -- heuristic for exporting a global package table + ltn12 = _M -- luacheck: ignore +end +local filter,source,sink,pump = {},{},{},{} + +_M.filter = filter +_M.source = source +_M.sink = sink +_M.pump = pump + +-- 2048 seems to be better in windows... +_M.BLOCKSIZE = 2048 +_M._VERSION = "LTN12 1.0.3" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- +-- returns a high level filter that cycles a low-level filter +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +-- chains a bunch of filters together +-- (thanks to Wim Couwenberg) +function filter.chain(...) + local arg = {...} + local n = select('#',...) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- +-- create an empty source +local function empty() + return nil +end + +function source.empty() + return empty +end + +-- returns a source that just outputs an error +function source.error(err) + return function() + return nil, err + end +end + +-- creates a file source +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(_M.BLOCKSIZE) + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +-- turns a fancy source into a simple source +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +-- creates string source +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+_M.BLOCKSIZE-1) + i = i + _M.BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +-- creates table source +function source.table(t) + base.assert('table' == type(t)) + local i = 0 + return function() + i = i + 1 + return t[i] + end +end + +-- creates rewindable source +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + table.insert(t, chunk) + end + end +end + +-- chains a source with one or several filter(s) +function source.chain(src, f, ...) + if ... then f=filter.chain(f, ...) end + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +-- creates a source that produces contents of several sources, one after the +-- other, as if they were concatenated +-- (thanks to Wim Couwenberg) +function source.cat(...) + local arg = {...} + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- +-- creates a sink that stores into a table +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then table.insert(t, chunk) end + return 1 + end + return f, t +end + +-- turns a fancy sink into a simple sink +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +-- creates a file sink +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +function sink.null() + return null +end + +-- creates a sink that just returns an error +function sink.error(err) + return function() + return nil, err + end +end + +-- chains a sink with one or several filter(s) +function sink.chain(f, snk, ...) + if ... then + local args = { f, snk, ... } + snk = table.remove(args, #args) + f = filter.chain(unpack(args)) + end + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- +-- pumps one chunk from the source to the sink +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +-- pumps all data from a source to a sink, using a step function +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/luasocket.c b/libraries/luasocket/libluasocket/luasocket.c new file mode 100644 index 000000000..0fd99f703 --- /dev/null +++ b/libraries/luasocket/libluasocket/luasocket.c @@ -0,0 +1,104 @@ +/*=========================================================================*\ +* LuaSocket toolkit +* Networking support for the Lua language +* Diego Nehab +* 26/11/1999 +* +* This library is part of an effort to progressively increase the network +* connectivity of the Lua language. The Lua interface to networking +* functions follows the Sockets API closely, trying to simplify all tasks +* involved in setting up both client and server connections. The provided +* IO routines, however, follow the Lua style, being very similar to the +* standard Lua read and write functions. +\*=========================================================================*/ + +#include "luasocket.h" +#include "auxiliar.h" +#include "except.h" +#include "timeout.h" +#include "buffer.h" +#include "inet.h" +#include "tcp.h" +#include "udp.h" +#include "select.h" + +/*-------------------------------------------------------------------------*\ +* Internal function prototypes +\*-------------------------------------------------------------------------*/ +static int global_skip(lua_State *L); +static int global_unload(lua_State *L); +static int base_open(lua_State *L); + +/*-------------------------------------------------------------------------*\ +* Modules and functions +\*-------------------------------------------------------------------------*/ +static const luaL_Reg mod[] = { + {"auxiliar", auxiliar_open}, + {"except", except_open}, + {"timeout", timeout_open}, + {"buffer", buffer_open}, + {"inet", inet_open}, + {"tcp", tcp_open}, + {"udp", udp_open}, + {"select", select_open}, + {NULL, NULL} +}; + +static luaL_Reg func[] = { + {"skip", global_skip}, + {"__unload", global_unload}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Skip a few arguments +\*-------------------------------------------------------------------------*/ +static int global_skip(lua_State *L) { + int amount = (int) luaL_checkinteger(L, 1); + int ret = lua_gettop(L) - amount - 1; + return ret >= 0 ? ret : 0; +} + +/*-------------------------------------------------------------------------*\ +* Unloads the library +\*-------------------------------------------------------------------------*/ +static int global_unload(lua_State *L) { + (void) L; + socket_close(); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Setup basic stuff. +\*-------------------------------------------------------------------------*/ +static int base_open(lua_State *L) { + if (socket_open()) { + /* export functions (and leave namespace table on top of stack) */ + lua_newtable(L); + luaL_setfuncs(L, func, 0); +#ifdef LUASOCKET_DEBUG + lua_pushstring(L, "_DEBUG"); + lua_pushboolean(L, 1); + lua_rawset(L, -3); +#endif + /* make version string available to scripts */ + lua_pushstring(L, "_VERSION"); + lua_pushstring(L, LUASOCKET_VERSION); + lua_rawset(L, -3); + return 1; + } else { + lua_pushstring(L, "unable to initialize library"); + lua_error(L); + return 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Initializes all library modules. +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_core(lua_State *L) { + int i; + base_open(L); + for (i = 0; mod[i].name; i++) mod[i].func(L); + return 1; +} diff --git a/libraries/luasocket/libluasocket/luasocket.h b/libraries/luasocket/libluasocket/luasocket.h new file mode 100644 index 000000000..1017fbaab --- /dev/null +++ b/libraries/luasocket/libluasocket/luasocket.h @@ -0,0 +1,36 @@ +#ifndef LUASOCKET_H +#define LUASOCKET_H +/*=========================================================================*\ +* LuaSocket toolkit +* Networking support for the Lua language +* Diego Nehab +* 9/11/1999 +\*=========================================================================*/ + +/*-------------------------------------------------------------------------* \ +* Current socket library version +\*-------------------------------------------------------------------------*/ +#define LUASOCKET_VERSION "LuaSocket 3.0.0" +#define LUASOCKET_COPYRIGHT "Copyright (C) 1999-2013 Diego Nehab" + +/*-------------------------------------------------------------------------*\ +* This macro prefixes all exported API functions +\*-------------------------------------------------------------------------*/ +#ifndef LUASOCKET_API +#ifdef _WIN32 +#define LUASOCKET_API __declspec(dllexport) +#else +#define LUASOCKET_API __attribute__ ((visibility ("default"))) +#endif +#endif + +#include "lua.h" +#include "lauxlib.h" +#include "compat.h" + +/*-------------------------------------------------------------------------*\ +* Initializes the library. +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_core(lua_State *L); + +#endif /* LUASOCKET_H */ diff --git a/libraries/luasocket/libluasocket/makefile b/libraries/luasocket/libluasocket/makefile new file mode 100644 index 000000000..06f4d1927 --- /dev/null +++ b/libraries/luasocket/libluasocket/makefile @@ -0,0 +1,461 @@ +# luasocket src/makefile +# +# Definitions in this section can be overriden on the command line or in the +# environment. +# +# These are equivalent: +# +# export PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw +# make +# +# and +# +# make PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw + +# PLAT: linux macosx win32 win64 mingw +# platform to build for +PLAT?=linux + +# LUAV: 5.1 5.2 5.3 5.4 +# lua version to build against +LUAV?=5.1 + +# MYCFLAGS: to be set by user if needed +MYCFLAGS?= + +# MYLDFLAGS: to be set by user if needed +MYLDFLAGS?= + +# DEBUG: NODEBUG DEBUG +# debug mode causes luasocket to collect and returns timing information useful +# for testing and debugging luasocket itself +DEBUG?=NODEBUG + +# where lua headers are found for macosx builds +# LUAINC_macosx: +# /opt/local/include +LUAINC_macosx_base?=/opt/local/include +LUAINC_macosx?=$(LUAINC_macosx_base)/lua/$(LUAV) $(LUAINC_macosx_base)/lua$(LUAV) $(LUAINC_macosx_base)/lua-$(LUAV) + +# FIXME default should this default to fink or to macports? +# What happens when more than one Lua version is installed? +LUAPREFIX_macosx?=/opt/local +CDIR_macosx?=lib/lua/$(LUAV) +LDIR_macosx?=share/lua/$(LUAV) + +# LUAINC_linux: +# /usr/include/lua$(LUAV) +# /usr/local/include +# /usr/local/include/lua$(LUAV) +# where lua headers are found for linux builds +LUAINC_linux_base?=/usr/include +LUAINC_linux?=$(LUAINC_linux_base)/lua/$(LUAV) $(LUAINC_linux_base)/lua$(LUAV) +LUAPREFIX_linux?=/usr/local +CDIR_linux?=lib/lua/$(LUAV) +LDIR_linux?=share/lua/$(LUAV) + +# LUAINC_freebsd: +# /usr/local/include/lua$(LUAV) +# where lua headers are found for freebsd builds +LUAINC_freebsd_base?=/usr/local/include/ +LUAINC_freebsd?=$(LUAINC_freebsd_base)/lua/$(LUAV) $(LUAINC_freebsd_base)/lua$(LUAV) +LUAPREFIX_freebsd?=/usr/local/ +CDIR_freebsd?=lib/lua/$(LUAV) +LDIR_freebsd?=share/lua/$(LUAV) + +# where lua headers are found for mingw builds +# LUAINC_mingw: +# /opt/local/include +LUAINC_mingw_base?=/usr/include +LUAINC_mingw?=$(LUAINC_mingw_base)/lua/$(LUAV) $(LUAINC_mingw_base)/lua$(LUAV) +LUALIB_mingw_base?=/usr/bin +LUALIB_mingw?=$(LUALIB_mingw_base)/lua/$(LUAV)/lua$(subst .,,$(LUAV)).dll +LUAPREFIX_mingw?=/usr +CDIR_mingw?=lua/$(LUAV) +LDIR_mingw?=lua/$(LUAV)/lua + + +# LUAINC_win32: +# LUALIB_win32: +# where lua headers and libraries are found for win32 builds +LUAPREFIX_win32?= +LUAINC_win32?=$(LUAPREFIX_win32)/include/lua/$(LUAV) $(LUAPREFIX_win32)/include/lua$(LUAV) +PLATFORM_win32?=Release +CDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32) +LDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32)/lua +LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUAV)/$(PLATFORM_win32) +LUALIBNAME_win32?=lua$(subst .,,$(LUAV)).lib + +# LUAINC_win64: +# LUALIB_win64: +# where lua headers and libraries are found for win64 builds +LUAPREFIX_win64?= +LUAINC_win64?=$(LUAPREFIX_win64)/include/lua/$(LUAV) $(LUAPREFIX_win64)/include/lua$(LUAV) +PLATFORM_win64?=x64/Release +CDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64) +LDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64)/lua +LUALIB_win64?=$(LUAPREFIX_win64)/lib/lua/$(LUAV)/$(PLATFORM_win64) +LUALIBNAME_win64?=lua$(subst .,,$(LUAV)).lib + + +# LUAINC_solaris: +LUAINC_solaris_base?=/usr/include +LUAINC_solaris?=$(LUAINC_solaris_base)/lua/$(LUAV) $(LUAINC_solaris_base)/lua$(LUAV) +LUAPREFIX_solaris?=/usr/local +CDIR_solaris?=lib/lua/$(LUAV) +LDIR_solaris?=share/lua/$(LUAV) + +# prefix: /usr/local /usr /opt/local /sw +# the top of the default install tree +prefix?=$(LUAPREFIX_$(PLAT)) + +CDIR?=$(CDIR_$(PLAT)) +LDIR?=$(LDIR_$(PLAT)) + +# DESTDIR: (no default) +# used by package managers to install into a temporary destination +DESTDIR?= + +#------ +# Definitions below can be overridden on the make command line, but +# shouldn't have to be. + + +#------ +# Install directories +# + +INSTALL_DIR=install -d +INSTALL_DATA=install -m644 +INSTALL_EXEC=install +INSTALL_TOP=$(DESTDIR)$(prefix) + +INSTALL_TOP_LDIR=$(INSTALL_TOP)/$(LDIR) +INSTALL_TOP_CDIR=$(INSTALL_TOP)/$(CDIR) + +INSTALL_SOCKET_LDIR=$(INSTALL_TOP_LDIR)/socket +INSTALL_SOCKET_CDIR=$(INSTALL_TOP_CDIR)/socket +INSTALL_MIME_LDIR=$(INSTALL_TOP_LDIR)/mime +INSTALL_MIME_CDIR=$(INSTALL_TOP_CDIR)/mime + +print: + @echo PLAT=$(PLAT) + @echo LUAV=$(LUAV) + @echo DEBUG=$(DEBUG) + @echo prefix=$(prefix) + @echo LUAINC_$(PLAT)=$(LUAINC_$(PLAT)) + @echo LUALIB_$(PLAT)=$(LUALIB_$(PLAT)) + @echo INSTALL_TOP_CDIR=$(INSTALL_TOP_CDIR) + @echo INSTALL_TOP_LDIR=$(INSTALL_TOP_LDIR) + @echo CFLAGS=$(CFLAGS) + @echo LDFLAGS=$(LDFLAGS) + +#------ +# Supported platforms +# +PLATS= macosx linux win32 win64 mingw solaris + +#------ +# Compiler and linker settings +# for Mac OS X +SO_macosx=so +O_macosx=o +CC_macosx=gcc +DEF_macosx= -DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +CFLAGS_macosx=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +LDFLAGS_macosx= -bundle -undefined dynamic_lookup -o +LD_macosx=gcc +SOCKET_macosx=usocket.o + +#------ +# Compiler and linker settings +# for Linux +SO_linux=so +O_linux=o +CC_linux=gcc +DEF_linux=-DLUASOCKET_$(DEBUG) +CFLAGS_linux=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_linux=-O -shared -fpic -o +LD_linux=gcc +SOCKET_linux=usocket.o + +#------ +# Compiler and linker settings +# for FreeBSD +SO_freebsd=so +O_freebsd=o +CC_freebsd=gcc +DEF_freebsd=-DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +CFLAGS_freebsd=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_freebsd=-O -shared -fpic -o +LD_freebsd=gcc +SOCKET_freebsd=usocket.o + +#------ +# Compiler and linker settings +# for Solaris +SO_solaris=so +O_solaris=o +CC_solaris=gcc +DEF_solaris=-DLUASOCKET_$(DEBUG) +CFLAGS_solaris=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_solaris=-lnsl -lsocket -lresolv -O -shared -fpic -o +LD_solaris=gcc +SOCKET_solaris=usocket.o + +#------ +# Compiler and linker settings +# for MingW +SO_mingw=dll +O_mingw=o +CC_mingw=gcc +DEF_mingw= -DLUASOCKET_$(DEBUG) \ + -DWINVER=0x0501 +CFLAGS_mingw=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +LDFLAGS_mingw= $(LUALIB) -shared -Wl,-s -lws2_32 -o +LD_mingw=gcc +SOCKET_mingw=wsocket.o + + +#------ +# Compiler and linker settings +# for Win32 +SO_win32=dll +O_win32=obj +CC_win32=cl +DEF_win32= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ + //D "_CRT_SECURE_NO_WARNINGS" \ + //D "_WINDLL" \ + //D "LUASOCKET_$(DEBUG)" +CFLAGS_win32=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +LDFLAGS_win32= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ + //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ + /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ + //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ + //MACHINE:X86 /LIBPATH:"$(LUALIB)" \ + $(LUALIBNAME_win32) ws2_32.lib //OUT: + +LD_win32=cl +SOCKET_win32=wsocket.obj + +#------ +# Compiler and linker settings +# for Win64 +SO_win64=dll +O_win64=obj +CC_win64=cl +DEF_win64= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ + //D "_CRT_SECURE_NO_WARNINGS" \ + //D "_WINDLL" \ + //D "LUASOCKET_$(DEBUG)" +CFLAGS_win64=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +LDFLAGS_win64= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ + //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ + /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ + //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ + /LIBPATH:"$(LUALIB)" \ + $(LUALIBNAME_win64) ws2_32.lib //OUT: + +LD_win64=cl +SOCKET_win64=wsocket.obj + +.SUFFIXES: .obj + +.c.obj: + $(CC) $(CFLAGS) //Fo"$@" //c $< + +#------ +# Output file names +# +SO=$(SO_$(PLAT)) +O=$(O_$(PLAT)) +SOCKET_V=3.0.0 +MIME_V=1.0.3 +SOCKET_SO=socket-$(SOCKET_V).$(SO) +MIME_SO=mime-$(MIME_V).$(SO) +UNIX_SO=unix.$(SO) +SERIAL_SO=serial.$(SO) +SOCKET=$(SOCKET_$(PLAT)) + +#------ +# Settings selected for platform +# +CC=$(CC_$(PLAT)) +DEF=$(DEF_$(PLAT)) +CFLAGS=$(MYCFLAGS) $(CFLAGS_$(PLAT)) +LDFLAGS=$(MYLDFLAGS) $(LDFLAGS_$(PLAT)) +LD=$(LD_$(PLAT)) +LUAINC= $(LUAINC_$(PLAT)) +LUALIB= $(LUALIB_$(PLAT)) + +#------ +# Modules belonging to socket-core +# +SOCKET_OBJS= \ + luasocket.$(O) \ + timeout.$(O) \ + buffer.$(O) \ + io.$(O) \ + auxiliar.$(O) \ + compat.$(O) \ + options.$(O) \ + inet.$(O) \ + $(SOCKET) \ + except.$(O) \ + select.$(O) \ + tcp.$(O) \ + udp.$(O) + +#------ +# Modules belonging mime-core +# +MIME_OBJS= \ + mime.$(O) \ + compat.$(O) + +#------ +# Modules belonging unix (local domain sockets) +# +UNIX_OBJS=\ + buffer.$(O) \ + auxiliar.$(O) \ + options.$(O) \ + timeout.$(O) \ + io.$(O) \ + usocket.$(O) \ + unixstream.$(O) \ + unixdgram.$(O) \ + compat.$(O) \ + unix.$(O) + +#------ +# Modules belonging to serial (device streams) +# +SERIAL_OBJS=\ + buffer.$(O) \ + compat.$(O) \ + auxiliar.$(O) \ + options.$(O) \ + timeout.$(O) \ + io.$(O) \ + usocket.$(O) \ + serial.$(O) + +#------ +# Files to install +# +TO_SOCKET_LDIR= \ + http.lua \ + url.lua \ + tp.lua \ + ftp.lua \ + headers.lua \ + smtp.lua + +TO_TOP_LDIR= \ + ltn12.lua \ + socket.lua \ + mime.lua + +#------ +# Targets +# +default: $(PLAT) + + +freebsd: + $(MAKE) all-unix PLAT=freebsd + +macosx: + $(MAKE) all-unix PLAT=macosx + +win32: + $(MAKE) all PLAT=win32 + +win64: + $(MAKE) all PLAT=win64 + +linux: + $(MAKE) all-unix PLAT=linux + +mingw: + $(MAKE) all PLAT=mingw + +solaris: + $(MAKE) all-unix PLAT=solaris + +none: + @echo "Please run" + @echo " make PLATFORM" + @echo "where PLATFORM is one of these:" + @echo " $(PLATS)" + +all: $(SOCKET_SO) $(MIME_SO) + +$(SOCKET_SO): $(SOCKET_OBJS) + $(LD) $(SOCKET_OBJS) $(LDFLAGS)$@ + +$(MIME_SO): $(MIME_OBJS) + $(LD) $(MIME_OBJS) $(LDFLAGS)$@ + +all-unix: all $(UNIX_SO) $(SERIAL_SO) + +$(UNIX_SO): $(UNIX_OBJS) + $(LD) $(UNIX_OBJS) $(LDFLAGS)$@ + +$(SERIAL_SO): $(SERIAL_OBJS) + $(LD) $(SERIAL_OBJS) $(LDFLAGS)$@ + +install: + $(INSTALL_DIR) $(INSTALL_TOP_LDIR) + $(INSTALL_DATA) $(TO_TOP_LDIR) $(INSTALL_TOP_LDIR) + $(INSTALL_DIR) $(INSTALL_SOCKET_LDIR) + $(INSTALL_DATA) $(TO_SOCKET_LDIR) $(INSTALL_SOCKET_LDIR) + $(INSTALL_DIR) $(INSTALL_SOCKET_CDIR) + $(INSTALL_EXEC) $(SOCKET_SO) $(INSTALL_SOCKET_CDIR)/core.$(SO) + $(INSTALL_DIR) $(INSTALL_MIME_CDIR) + $(INSTALL_EXEC) $(MIME_SO) $(INSTALL_MIME_CDIR)/core.$(SO) + +install-unix: install + $(INSTALL_EXEC) $(UNIX_SO) $(INSTALL_SOCKET_CDIR)/$(UNIX_SO) + $(INSTALL_EXEC) $(SERIAL_SO) $(INSTALL_SOCKET_CDIR)/$(SERIAL_SO) + +local: + $(MAKE) install INSTALL_TOP_CDIR=.. INSTALL_TOP_LDIR=.. + +clean: + rm -f $(SOCKET_SO) $(SOCKET_OBJS) $(SERIAL_OBJS) + rm -f $(MIME_SO) $(UNIX_SO) $(SERIAL_SO) $(MIME_OBJS) $(UNIX_OBJS) + +.PHONY: all $(PLATS) default clean echo none + +#------ +# List of dependencies +# +compat.$(O): compat.c compat.h +auxiliar.$(O): auxiliar.c auxiliar.h +buffer.$(O): buffer.c buffer.h io.h timeout.h +except.$(O): except.c except.h +inet.$(O): inet.c inet.h socket.h io.h timeout.h usocket.h +io.$(O): io.c io.h timeout.h +luasocket.$(O): luasocket.c luasocket.h auxiliar.h except.h \ + timeout.h buffer.h io.h inet.h socket.h usocket.h tcp.h \ + udp.h select.h +mime.$(O): mime.c mime.h +options.$(O): options.c auxiliar.h options.h socket.h io.h \ + timeout.h usocket.h inet.h +select.$(O): select.c socket.h io.h timeout.h usocket.h select.h +serial.$(O): serial.c auxiliar.h socket.h io.h timeout.h usocket.h \ + options.h unix.h buffer.h +tcp.$(O): tcp.c auxiliar.h socket.h io.h timeout.h usocket.h \ + inet.h options.h tcp.h buffer.h +timeout.$(O): timeout.c auxiliar.h timeout.h +udp.$(O): udp.c auxiliar.h socket.h io.h timeout.h usocket.h \ + inet.h options.h udp.h +unix.$(O): unix.c auxiliar.h socket.h io.h timeout.h usocket.h \ + options.h unix.h buffer.h +usocket.$(O): usocket.c socket.h io.h timeout.h usocket.h +wsocket.$(O): wsocket.c socket.h io.h timeout.h usocket.h diff --git a/libraries/luasocket/libluasocket/mbox.lua b/libraries/luasocket/libluasocket/mbox.lua new file mode 100644 index 000000000..f343d7168 --- /dev/null +++ b/libraries/luasocket/libluasocket/mbox.lua @@ -0,0 +1,96 @@ +R"luastring"--( +local _M = {} + +if module then + mbox = _M -- luacheck: ignore +end + +function _M.split_message(message_s) + local message = {} + message_s = string.gsub(message_s, "\r\n", "\n") + string.gsub(message_s, "^(.-\n)\n", function (h) message.headers = h end) + string.gsub(message_s, "^.-\n\n(.*)", function (b) message.body = b end) + if not message.body then + string.gsub(message_s, "^\n(.*)", function (b) message.body = b end) + end + if not message.headers and not message.body then + message.headers = message_s + end + return message.headers or "", message.body or "" +end + +function _M.split_headers(headers_s) + local headers = {} + headers_s = string.gsub(headers_s, "\r\n", "\n") + headers_s = string.gsub(headers_s, "\n[ ]+", " ") + string.gsub("\n" .. headers_s, "\n([^\n]+)", function (h) table.insert(headers, h) end) + return headers +end + +function _M.parse_header(header_s) + header_s = string.gsub(header_s, "\n[ ]+", " ") + header_s = string.gsub(header_s, "\n+", "") + local _, _, name, value = string.find(header_s, "([^%s:]-):%s*(.*)") + return name, value +end + +function _M.parse_headers(headers_s) + local headers_t = _M.split_headers(headers_s) + local headers = {} + for i = 1, #headers_t do + local name, value = _M.parse_header(headers_t[i]) + if name then + name = string.lower(name) + if headers[name] then + headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + end + return headers +end + +function _M.parse_from(from) + local _, _, name, address = string.find(from, "^%s*(.-)%s*%<(.-)%>") + if not address then + _, _, address = string.find(from, "%s*(.+)%s*") + end + name = name or "" + address = address or "" + if name == "" then name = address end + name = string.gsub(name, '"', "") + return name, address +end + +function _M.split_mbox(mbox_s) + local mbox = {} + mbox_s = string.gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n" + local nj, i + local j = 1 + while 1 do + i, nj = string.find(mbox_s, "\n\nFrom .-\n", j) + if not i then break end + local message = string.sub(mbox_s, j, i-1) + table.insert(mbox, message) + j = nj+1 + end + return mbox +end + +function _M.parse(mbox_s) + local mbox = _M.split_mbox(mbox_s) + for i = 1, #mbox do + mbox[i] = _M.parse_message(mbox[i]) + end + return mbox +end + +function _M.parse_message(message_s) + local message = {} + message.headers, message.body = _M.split_message(message_s) + message.headers = _M.parse_headers(message.headers) + return message +end + +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/mime.c b/libraries/luasocket/libluasocket/mime.c new file mode 100644 index 000000000..05602f566 --- /dev/null +++ b/libraries/luasocket/libluasocket/mime.c @@ -0,0 +1,852 @@ +/*=========================================================================*\ +* MIME support functions +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "mime.h" +#include +#include + +/*=========================================================================*\ +* Don't want to trust escape character constants +\*=========================================================================*/ +typedef unsigned char UC; +static const char CRLF[] = "\r\n"; +static const char EQCRLF[] = "=\r\n"; + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int mime_global_wrp(lua_State *L); +static int mime_global_b64(lua_State *L); +static int mime_global_unb64(lua_State *L); +static int mime_global_qp(lua_State *L); +static int mime_global_unqp(lua_State *L); +static int mime_global_qpwrp(lua_State *L); +static int mime_global_eol(lua_State *L); +static int mime_global_dot(lua_State *L); + +static size_t dot(int c, size_t state, luaL_Buffer *buffer); +/*static void b64setup(UC *base);*/ +static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer); +static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer); +static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer); + +/*static void qpsetup(UC *class, UC *unbase);*/ +static void qpquote(UC c, luaL_Buffer *buffer); +static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer); +static size_t qpencode(UC c, UC *input, size_t size, + const char *marker, luaL_Buffer *buffer); +static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer); + +/* code support functions */ +static luaL_Reg func[] = { + { "dot", mime_global_dot }, + { "b64", mime_global_b64 }, + { "eol", mime_global_eol }, + { "qp", mime_global_qp }, + { "qpwrp", mime_global_qpwrp }, + { "unb64", mime_global_unb64 }, + { "unqp", mime_global_unqp }, + { "wrp", mime_global_wrp }, + { NULL, NULL } +}; + +/*-------------------------------------------------------------------------*\ +* Quoted-printable globals +\*-------------------------------------------------------------------------*/ +enum {QP_PLAIN, QP_QUOTED, QP_CR, QP_IF_LAST}; + +static const UC qpclass[] = { + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_IF_LAST, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_CR, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_IF_LAST, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_QUOTED, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED +}; + +static const UC qpbase[] = "0123456789ABCDEF"; + +static const UC qpunbase[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, + 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255 +}; + +/*-------------------------------------------------------------------------*\ +* Base64 globals +\*-------------------------------------------------------------------------*/ +static const UC b64base[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const UC b64unbase[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, + 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}; + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_mime_core(lua_State *L) +{ + lua_newtable(L); + luaL_setfuncs(L, func, 0); + /* make version string available to scripts */ + lua_pushstring(L, "_VERSION"); + lua_pushstring(L, MIME_VERSION); + lua_rawset(L, -3); + /* initialize lookup tables */ + /*qpsetup(qpclass, qpunbase);*/ + /*b64setup(b64unbase);*/ + return 1; +} + +/*=========================================================================*\ +* Global Lua functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Incrementaly breaks a string into lines. The string can have CRLF breaks. +* A, n = wrp(l, B, length) +* A is a copy of B, broken into lines of at most 'length' bytes. +* 'l' is how many bytes are left for the first line of B. +* 'n' is the number of bytes left in the last line of A. +\*-------------------------------------------------------------------------*/ +static int mime_global_wrp(lua_State *L) +{ + size_t size = 0; + int left = (int) luaL_checknumber(L, 1); + const UC *input = (const UC *) luaL_optlstring(L, 2, NULL, &size); + const UC *last = input + size; + int length = (int) luaL_optnumber(L, 3, 76); + luaL_Buffer buffer; + /* end of input black-hole */ + if (!input) { + /* if last line has not been terminated, add a line break */ + if (left < length) lua_pushstring(L, CRLF); + /* otherwise, we are done */ + else lua_pushnil(L); + lua_pushnumber(L, length); + return 2; + } + luaL_buffinit(L, &buffer); + while (input < last) { + switch (*input) { + case '\r': + break; + case '\n': + luaL_addstring(&buffer, CRLF); + left = length; + break; + default: + if (left <= 0) { + left = length; + luaL_addstring(&buffer, CRLF); + } + luaL_addchar(&buffer, *input); + left--; + break; + } + input++; + } + luaL_pushresult(&buffer); + lua_pushnumber(L, left); + return 2; +} + +#if 0 +/*-------------------------------------------------------------------------*\ +* Fill base64 decode map. +\*-------------------------------------------------------------------------*/ +static void b64setup(UC *unbase) +{ + int i; + for (i = 0; i <= 255; i++) unbase[i] = (UC) 255; + for (i = 0; i < 64; i++) unbase[b64base[i]] = (UC) i; + unbase['='] = 0; + + printf("static const UC b64unbase[] = {\n"); + for (int i = 0; i < 256; i++) { + printf("%d, ", unbase[i]); + } + printf("\n}\n;"); +} +#endif + +/*-------------------------------------------------------------------------*\ +* Acumulates bytes in input buffer until 3 bytes are available. +* Translate the 3 bytes into Base64 form and append to buffer. +* Returns new number of bytes in buffer. +\*-------------------------------------------------------------------------*/ +static size_t b64encode(UC c, UC *input, size_t size, + luaL_Buffer *buffer) +{ + input[size++] = c; + if (size == 3) { + UC code[4]; + unsigned long value = 0; + value += input[0]; value <<= 8; + value += input[1]; value <<= 8; + value += input[2]; + code[3] = b64base[value & 0x3f]; value >>= 6; + code[2] = b64base[value & 0x3f]; value >>= 6; + code[1] = b64base[value & 0x3f]; value >>= 6; + code[0] = b64base[value]; + luaL_addlstring(buffer, (char *) code, 4); + size = 0; + } + return size; +} + +/*-------------------------------------------------------------------------*\ +* Encodes the Base64 last 1 or 2 bytes and adds padding '=' +* Result, if any, is appended to buffer. +* Returns 0. +\*-------------------------------------------------------------------------*/ +static size_t b64pad(const UC *input, size_t size, + luaL_Buffer *buffer) +{ + unsigned long value = 0; + UC code[4] = {'=', '=', '=', '='}; + switch (size) { + case 1: + value = input[0] << 4; + code[1] = b64base[value & 0x3f]; value >>= 6; + code[0] = b64base[value]; + luaL_addlstring(buffer, (char *) code, 4); + break; + case 2: + value = input[0]; value <<= 8; + value |= input[1]; value <<= 2; + code[2] = b64base[value & 0x3f]; value >>= 6; + code[1] = b64base[value & 0x3f]; value >>= 6; + code[0] = b64base[value]; + luaL_addlstring(buffer, (char *) code, 4); + break; + default: + break; + } + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Acumulates bytes in input buffer until 4 bytes are available. +* Translate the 4 bytes from Base64 form and append to buffer. +* Returns new number of bytes in buffer. +\*-------------------------------------------------------------------------*/ +static size_t b64decode(UC c, UC *input, size_t size, + luaL_Buffer *buffer) +{ + /* ignore invalid characters */ + if (b64unbase[c] > 64) return size; + input[size++] = c; + /* decode atom */ + if (size == 4) { + UC decoded[3]; + int valid, value = 0; + value = b64unbase[input[0]]; value <<= 6; + value |= b64unbase[input[1]]; value <<= 6; + value |= b64unbase[input[2]]; value <<= 6; + value |= b64unbase[input[3]]; + decoded[2] = (UC) (value & 0xff); value >>= 8; + decoded[1] = (UC) (value & 0xff); value >>= 8; + decoded[0] = (UC) value; + /* take care of paddding */ + valid = (input[2] == '=') ? 1 : (input[3] == '=') ? 2 : 3; + luaL_addlstring(buffer, (char *) decoded, valid); + return 0; + /* need more data */ + } else return size; +} + +/*-------------------------------------------------------------------------*\ +* Incrementally applies the Base64 transfer content encoding to a string +* A, B = b64(C, D) +* A is the encoded version of the largest prefix of C .. D that is +* divisible by 3. B has the remaining bytes of C .. D, *without* encoding. +* The easiest thing would be to concatenate the two strings and +* encode the result, but we can't afford that or Lua would dupplicate +* every chunk we received. +\*-------------------------------------------------------------------------*/ +static int mime_global_b64(lua_State *L) +{ + UC atom[3]; + size_t isize = 0, asize = 0; + const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); + const UC *last = input + isize; + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 2); + /* process first part of the input */ + luaL_buffinit(L, &buffer); + while (input < last) + asize = b64encode(*input++, atom, asize, &buffer); + input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); + /* if second part is nil, we are done */ + if (!input) { + size_t osize = 0; + asize = b64pad(atom, asize, &buffer); + luaL_pushresult(&buffer); + /* if the output is empty and the input is nil, return nil */ + lua_tolstring(L, -1, &osize); + if (osize == 0) lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* otherwise process the second part */ + last = input + isize; + while (input < last) + asize = b64encode(*input++, atom, asize, &buffer); + luaL_pushresult(&buffer); + lua_pushlstring(L, (char *) atom, asize); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Incrementally removes the Base64 transfer content encoding from a string +* A, B = b64(C, D) +* A is the encoded version of the largest prefix of C .. D that is +* divisible by 4. B has the remaining bytes of C .. D, *without* encoding. +\*-------------------------------------------------------------------------*/ +static int mime_global_unb64(lua_State *L) +{ + UC atom[4]; + size_t isize = 0, asize = 0; + const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); + const UC *last = input + isize; + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 2); + /* process first part of the input */ + luaL_buffinit(L, &buffer); + while (input < last) + asize = b64decode(*input++, atom, asize, &buffer); + input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); + /* if second is nil, we are done */ + if (!input) { + size_t osize = 0; + luaL_pushresult(&buffer); + /* if the output is empty and the input is nil, return nil */ + lua_tolstring(L, -1, &osize); + if (osize == 0) lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* otherwise, process the rest of the input */ + last = input + isize; + while (input < last) + asize = b64decode(*input++, atom, asize, &buffer); + luaL_pushresult(&buffer); + lua_pushlstring(L, (char *) atom, asize); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Quoted-printable encoding scheme +* all (except CRLF in text) can be =XX +* CLRL in not text must be =XX=XX +* 33 through 60 inclusive can be plain +* 62 through 126 inclusive can be plain +* 9 and 32 can be plain, unless in the end of a line, where must be =XX +* encoded lines must be no longer than 76 not counting CRLF +* soft line-break are =CRLF +* To encode one byte, we need to see the next two. +* Worst case is when we see a space, and wonder if a CRLF is comming +\*-------------------------------------------------------------------------*/ +#if 0 +/*-------------------------------------------------------------------------*\ +* Split quoted-printable characters into classes +* Precompute reverse map for encoding +\*-------------------------------------------------------------------------*/ +static void qpsetup(UC *cl, UC *unbase) +{ + + int i; + for (i = 0; i < 256; i++) cl[i] = QP_QUOTED; + for (i = 33; i <= 60; i++) cl[i] = QP_PLAIN; + for (i = 62; i <= 126; i++) cl[i] = QP_PLAIN; + cl['\t'] = QP_IF_LAST; + cl[' '] = QP_IF_LAST; + cl['\r'] = QP_CR; + for (i = 0; i < 256; i++) unbase[i] = 255; + unbase['0'] = 0; unbase['1'] = 1; unbase['2'] = 2; + unbase['3'] = 3; unbase['4'] = 4; unbase['5'] = 5; + unbase['6'] = 6; unbase['7'] = 7; unbase['8'] = 8; + unbase['9'] = 9; unbase['A'] = 10; unbase['a'] = 10; + unbase['B'] = 11; unbase['b'] = 11; unbase['C'] = 12; + unbase['c'] = 12; unbase['D'] = 13; unbase['d'] = 13; + unbase['E'] = 14; unbase['e'] = 14; unbase['F'] = 15; + unbase['f'] = 15; + +printf("static UC qpclass[] = {"); + for (int i = 0; i < 256; i++) { + if (i % 6 == 0) { + printf("\n "); + } + switch(cl[i]) { + case QP_QUOTED: + printf("QP_QUOTED, "); + break; + case QP_PLAIN: + printf("QP_PLAIN, "); + break; + case QP_CR: + printf("QP_CR, "); + break; + case QP_IF_LAST: + printf("QP_IF_LAST, "); + break; + } + } +printf("\n};\n"); + +printf("static const UC qpunbase[] = {"); + for (int i = 0; i < 256; i++) { + int c = qpunbase[i]; + printf("%d, ", c); + } +printf("\";\n"); +} +#endif + +/*-------------------------------------------------------------------------*\ +* Output one character in form =XX +\*-------------------------------------------------------------------------*/ +static void qpquote(UC c, luaL_Buffer *buffer) +{ + luaL_addchar(buffer, '='); + luaL_addchar(buffer, qpbase[c >> 4]); + luaL_addchar(buffer, qpbase[c & 0x0F]); +} + +/*-------------------------------------------------------------------------*\ +* Accumulate characters until we are sure about how to deal with them. +* Once we are sure, output to the buffer, in the correct form. +\*-------------------------------------------------------------------------*/ +static size_t qpencode(UC c, UC *input, size_t size, + const char *marker, luaL_Buffer *buffer) +{ + input[size++] = c; + /* deal with all characters we can have */ + while (size > 0) { + switch (qpclass[input[0]]) { + /* might be the CR of a CRLF sequence */ + case QP_CR: + if (size < 2) return size; + if (input[1] == '\n') { + luaL_addstring(buffer, marker); + return 0; + } else qpquote(input[0], buffer); + break; + /* might be a space and that has to be quoted if last in line */ + case QP_IF_LAST: + if (size < 3) return size; + /* if it is the last, quote it and we are done */ + if (input[1] == '\r' && input[2] == '\n') { + qpquote(input[0], buffer); + luaL_addstring(buffer, marker); + return 0; + } else luaL_addchar(buffer, input[0]); + break; + /* might have to be quoted always */ + case QP_QUOTED: + qpquote(input[0], buffer); + break; + /* might never have to be quoted */ + default: + luaL_addchar(buffer, input[0]); + break; + } + input[0] = input[1]; input[1] = input[2]; + size--; + } + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Deal with the final characters +\*-------------------------------------------------------------------------*/ +static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer) +{ + size_t i; + for (i = 0; i < size; i++) { + if (qpclass[input[i]] == QP_PLAIN) luaL_addchar(buffer, input[i]); + else qpquote(input[i], buffer); + } + if (size > 0) luaL_addstring(buffer, EQCRLF); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Incrementally converts a string to quoted-printable +* A, B = qp(C, D, marker) +* Marker is the text to be used to replace CRLF sequences found in A. +* A is the encoded version of the largest prefix of C .. D that +* can be encoded without doubts. +* B has the remaining bytes of C .. D, *without* encoding. +\*-------------------------------------------------------------------------*/ +static int mime_global_qp(lua_State *L) +{ + size_t asize = 0, isize = 0; + UC atom[3]; + const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); + const UC *last = input + isize; + const char *marker = luaL_optstring(L, 3, CRLF); + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 3); + /* process first part of input */ + luaL_buffinit(L, &buffer); + while (input < last) + asize = qpencode(*input++, atom, asize, marker, &buffer); + input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); + /* if second part is nil, we are done */ + if (!input) { + asize = qppad(atom, asize, &buffer); + luaL_pushresult(&buffer); + if (!(*lua_tostring(L, -1))) lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* otherwise process rest of input */ + last = input + isize; + while (input < last) + asize = qpencode(*input++, atom, asize, marker, &buffer); + luaL_pushresult(&buffer); + lua_pushlstring(L, (char *) atom, asize); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Accumulate characters until we are sure about how to deal with them. +* Once we are sure, output the to the buffer, in the correct form. +\*-------------------------------------------------------------------------*/ +static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer) { + int d; + input[size++] = c; + /* deal with all characters we can deal */ + switch (input[0]) { + /* if we have an escape character */ + case '=': + if (size < 3) return size; + /* eliminate soft line break */ + if (input[1] == '\r' && input[2] == '\n') return 0; + /* decode quoted representation */ + c = qpunbase[input[1]]; d = qpunbase[input[2]]; + /* if it is an invalid, do not decode */ + if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3); + else luaL_addchar(buffer, (char) ((c << 4) + d)); + return 0; + case '\r': + if (size < 2) return size; + if (input[1] == '\n') luaL_addlstring(buffer, (char *)input, 2); + return 0; + default: + if (input[0] == '\t' || (input[0] > 31 && input[0] < 127)) + luaL_addchar(buffer, input[0]); + return 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Incrementally decodes a string in quoted-printable +* A, B = qp(C, D) +* A is the decoded version of the largest prefix of C .. D that +* can be decoded without doubts. +* B has the remaining bytes of C .. D, *without* decoding. +\*-------------------------------------------------------------------------*/ +static int mime_global_unqp(lua_State *L) +{ + size_t asize = 0, isize = 0; + UC atom[3]; + const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); + const UC *last = input + isize; + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 2); + /* process first part of input */ + luaL_buffinit(L, &buffer); + while (input < last) + asize = qpdecode(*input++, atom, asize, &buffer); + input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); + /* if second part is nil, we are done */ + if (!input) { + luaL_pushresult(&buffer); + if (!(*lua_tostring(L, -1))) lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* otherwise process rest of input */ + last = input + isize; + while (input < last) + asize = qpdecode(*input++, atom, asize, &buffer); + luaL_pushresult(&buffer); + lua_pushlstring(L, (char *) atom, asize); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Incrementally breaks a quoted-printed string into lines +* A, n = qpwrp(l, B, length) +* A is a copy of B, broken into lines of at most 'length' bytes. +* 'l' is how many bytes are left for the first line of B. +* 'n' is the number of bytes left in the last line of A. +* There are two complications: lines can't be broken in the middle +* of an encoded =XX, and there might be line breaks already +\*-------------------------------------------------------------------------*/ +static int mime_global_qpwrp(lua_State *L) +{ + size_t size = 0; + int left = (int) luaL_checknumber(L, 1); + const UC *input = (const UC *) luaL_optlstring(L, 2, NULL, &size); + const UC *last = input + size; + int length = (int) luaL_optnumber(L, 3, 76); + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + if (left < length) lua_pushstring(L, EQCRLF); + else lua_pushnil(L); + lua_pushnumber(L, length); + return 2; + } + /* process all input */ + luaL_buffinit(L, &buffer); + while (input < last) { + switch (*input) { + case '\r': + break; + case '\n': + left = length; + luaL_addstring(&buffer, CRLF); + break; + case '=': + if (left <= 3) { + left = length; + luaL_addstring(&buffer, EQCRLF); + } + luaL_addchar(&buffer, *input); + left--; + break; + default: + if (left <= 1) { + left = length; + luaL_addstring(&buffer, EQCRLF); + } + luaL_addchar(&buffer, *input); + left--; + break; + } + input++; + } + luaL_pushresult(&buffer); + lua_pushnumber(L, left); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Here is what we do: \n, and \r are considered candidates for line +* break. We issue *one* new line marker if any of them is seen alone, or +* followed by a different one. That is, \n\n and \r\r will issue two +* end of line markers each, but \r\n, \n\r etc will only issue *one* +* marker. This covers Mac OS, Mac OS X, VMS, Unix and DOS, as well as +* probably other more obscure conventions. +* +* c is the current character being processed +* last is the previous character +\*-------------------------------------------------------------------------*/ +#define eolcandidate(c) (c == '\r' || c == '\n') +static int eolprocess(int c, int last, const char *marker, + luaL_Buffer *buffer) +{ + if (eolcandidate(c)) { + if (eolcandidate(last)) { + if (c == last) luaL_addstring(buffer, marker); + return 0; + } else { + luaL_addstring(buffer, marker); + return c; + } + } else { + luaL_addchar(buffer, (char) c); + return 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Converts a string to uniform EOL convention. +* A, n = eol(o, B, marker) +* A is the converted version of the largest prefix of B that can be +* converted unambiguously. 'o' is the context returned by the previous +* call. 'n' is the new context. +\*-------------------------------------------------------------------------*/ +static int mime_global_eol(lua_State *L) +{ + int ctx = (int) luaL_checkinteger(L, 1); + size_t isize = 0; + const char *input = luaL_optlstring(L, 2, NULL, &isize); + const char *last = input + isize; + const char *marker = luaL_optstring(L, 3, CRLF); + luaL_Buffer buffer; + luaL_buffinit(L, &buffer); + /* end of input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnumber(L, 0); + return 2; + } + /* process all input */ + while (input < last) + ctx = eolprocess(*input++, ctx, marker, &buffer); + luaL_pushresult(&buffer); + lua_pushnumber(L, ctx); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Takes one byte and stuff it if needed. +\*-------------------------------------------------------------------------*/ +static size_t dot(int c, size_t state, luaL_Buffer *buffer) +{ + luaL_addchar(buffer, (char) c); + switch (c) { + case '\r': + return 1; + case '\n': + return (state == 1)? 2: 0; + case '.': + if (state == 2) + luaL_addchar(buffer, '.'); + /* Falls through. */ + default: + return 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Incrementally applies smtp stuffing to a string +* A, n = dot(l, D) +\*-------------------------------------------------------------------------*/ +static int mime_global_dot(lua_State *L) +{ + size_t isize = 0, state = (size_t) luaL_checknumber(L, 1); + const char *input = luaL_optlstring(L, 2, NULL, &isize); + const char *last = input + isize; + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnumber(L, 2); + return 2; + } + /* process all input */ + luaL_buffinit(L, &buffer); + while (input < last) + state = dot(*input++, state, &buffer); + luaL_pushresult(&buffer); + lua_pushnumber(L, (lua_Number) state); + return 2; +} + diff --git a/libraries/luasocket/libluasocket/mime.h b/libraries/luasocket/libluasocket/mime.h new file mode 100644 index 000000000..4d938f46e --- /dev/null +++ b/libraries/luasocket/libluasocket/mime.h @@ -0,0 +1,22 @@ +#ifndef MIME_H +#define MIME_H +/*=========================================================================*\ +* Core MIME support +* LuaSocket toolkit +* +* This module provides functions to implement transfer content encodings +* and formatting conforming to RFC 2045. It is used by mime.lua, which +* provide a higher level interface to this functionality. +\*=========================================================================*/ +#include "luasocket.h" + +/*-------------------------------------------------------------------------*\ +* Current MIME library version +\*-------------------------------------------------------------------------*/ +#define MIME_VERSION "MIME 1.0.3" +#define MIME_COPYRIGHT "Copyright (C) 2004-2013 Diego Nehab" +#define MIME_AUTHORS "Diego Nehab" + +LUASOCKET_API int luaopen_mime_core(lua_State *L); + +#endif /* MIME_H */ diff --git a/libraries/luasocket/libluasocket/mime.lua b/libraries/luasocket/libluasocket/mime.lua new file mode 100644 index 000000000..4831ebc44 --- /dev/null +++ b/libraries/luasocket/libluasocket/mime.lua @@ -0,0 +1,84 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- MIME support for the Lua language. +-- Author: Diego Nehab +-- Conforming to RFCs 2045-2049 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local ltn12 = require("ltn12") +local mime = require("mime.core") +local _M = mime + +-- encode, decode and wrap algorithm tables +local encodet, decodet, wrapt = {},{},{} + +_M.encodet = encodet +_M.decodet = decodet +_M.wrapt = wrapt + +-- creates a function that chooses a filter by name from a given table +local function choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then + base.error("unknown key (" .. base.tostring(name) .. ")", 3) + else return f(opt1, opt2) end + end +end + +-- define the encoding filters +encodet['base64'] = function() + return ltn12.filter.cycle(_M.b64, "") +end + +encodet['quoted-printable'] = function(mode) + return ltn12.filter.cycle(_M.qp, "", + (mode == "binary") and "=0D=0A" or "\r\n") +end + +-- define the decoding filters +decodet['base64'] = function() + return ltn12.filter.cycle(_M.unb64, "") +end + +decodet['quoted-printable'] = function() + return ltn12.filter.cycle(_M.unqp, "") +end + +-- define the line-wrap filters +wrapt['text'] = function(length) + length = length or 76 + return ltn12.filter.cycle(_M.wrp, length, length) +end +wrapt['base64'] = wrapt['text'] +wrapt['default'] = wrapt['text'] + +wrapt['quoted-printable'] = function() + return ltn12.filter.cycle(_M.qpwrp, 76, 76) +end + +-- function that choose the encoding, decoding or wrap algorithm +_M.encode = choose(encodet) +_M.decode = choose(decodet) +_M.wrap = choose(wrapt) + +-- define the end-of-line normalization filter +function _M.normalize(marker) + return ltn12.filter.cycle(_M.eol, 0, marker) +end + +-- high level stuffing filter +function _M.stuff() + return ltn12.filter.cycle(_M.dot, 2) +end + +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/options.c b/libraries/luasocket/libluasocket/options.c new file mode 100644 index 000000000..3280c51d7 --- /dev/null +++ b/libraries/luasocket/libluasocket/options.c @@ -0,0 +1,480 @@ +/*=========================================================================*\ +* Common option interface +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "auxiliar.h" +#include "options.h" +#include "inet.h" +#include + +/*=========================================================================*\ +* Internal functions prototypes +\*=========================================================================*/ +static int opt_setmembership(lua_State *L, p_socket ps, int level, int name); +static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name); +static int opt_setboolean(lua_State *L, p_socket ps, int level, int name); +static int opt_getboolean(lua_State *L, p_socket ps, int level, int name); +static int opt_setint(lua_State *L, p_socket ps, int level, int name); +static int opt_getint(lua_State *L, p_socket ps, int level, int name); +static int opt_set(lua_State *L, p_socket ps, int level, int name, + void *val, int len); +static int opt_get(lua_State *L, p_socket ps, int level, int name, + void *val, int* len); + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Calls appropriate option handler +\*-------------------------------------------------------------------------*/ +int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps) +{ + const char *name = luaL_checkstring(L, 2); /* obj, name, ... */ + while (opt->name && strcmp(name, opt->name)) + opt++; + if (!opt->func) { + char msg[57]; + sprintf(msg, "unsupported option `%.35s'", name); + luaL_argerror(L, 2, msg); + } + return opt->func(L, ps); +} + +int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps) +{ + const char *name = luaL_checkstring(L, 2); /* obj, name, ... */ + while (opt->name && strcmp(name, opt->name)) + opt++; + if (!opt->func) { + char msg[57]; + sprintf(msg, "unsupported option `%.35s'", name); + luaL_argerror(L, 2, msg); + } + return opt->func(L, ps); +} + +/*------------------------------------------------------*/ +/* enables reuse of local address */ +int opt_set_reuseaddr(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); +} + +int opt_get_reuseaddr(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); +} + +/*------------------------------------------------------*/ +/* enables reuse of local port */ +int opt_set_reuseport(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); +} + +int opt_get_reuseport(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); +} + +/*------------------------------------------------------*/ +/* disables the Nagle algorithm */ +int opt_set_tcp_nodelay(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); +} + +int opt_get_tcp_nodelay(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); +} + +/*------------------------------------------------------*/ +#ifdef TCP_KEEPIDLE + +int opt_get_tcp_keepidle(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPIDLE); +} + +int opt_set_tcp_keepidle(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPIDLE); +} + +#endif + +/*------------------------------------------------------*/ +#ifdef TCP_KEEPCNT + +int opt_get_tcp_keepcnt(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPCNT); +} + +int opt_set_tcp_keepcnt(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPCNT); +} + +#endif + +/*------------------------------------------------------*/ +#ifdef TCP_KEEPINTVL + +int opt_get_tcp_keepintvl(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPINTVL); +} + +int opt_set_tcp_keepintvl(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPINTVL); +} + +#endif + +/*------------------------------------------------------*/ +int opt_set_keepalive(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); +} + +int opt_get_keepalive(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); +} + +/*------------------------------------------------------*/ +int opt_set_dontroute(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); +} + +int opt_get_dontroute(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); +} + +/*------------------------------------------------------*/ +int opt_set_broadcast(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_BROADCAST); +} + +int opt_get_broadcast(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_BROADCAST); +} + +/*------------------------------------------------------*/ +int opt_set_recv_buf_size(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, SOL_SOCKET, SO_RCVBUF); +} + +int opt_get_recv_buf_size(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, SOL_SOCKET, SO_RCVBUF); +} + +/*------------------------------------------------------*/ +int opt_get_send_buf_size(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, SOL_SOCKET, SO_SNDBUF); +} + +int opt_set_send_buf_size(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, SOL_SOCKET, SO_SNDBUF); +} + +// /*------------------------------------------------------*/ + +#ifdef TCP_FASTOPEN +int opt_set_tcp_fastopen(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_FASTOPEN); +} +#endif + +#ifdef TCP_FASTOPEN_CONNECT +int opt_set_tcp_fastopen_connect(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_FASTOPEN_CONNECT); +} +#endif + +/*------------------------------------------------------*/ + +#ifdef TCP_DEFER_ACCEPT +int opt_set_tcp_defer_accept(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_DEFER_ACCEPT); +} +#endif + +/*------------------------------------------------------*/ +int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); +} + +int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); +} + +/*------------------------------------------------------*/ +int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); +} + +int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); +} + +/*------------------------------------------------------*/ +int opt_set_ip_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); +} + +int opt_get_ip_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); +} + +/*------------------------------------------------------*/ +int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); +} + +int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); +} + +/*------------------------------------------------------*/ +int opt_set_linger(lua_State *L, p_socket ps) +{ + struct linger li; /* obj, name, table */ + if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); + lua_pushstring(L, "on"); + lua_gettable(L, 3); + if (!lua_isboolean(L, -1)) + luaL_argerror(L, 3, "boolean 'on' field expected"); + li.l_onoff = (u_short) lua_toboolean(L, -1); + lua_pushstring(L, "timeout"); + lua_gettable(L, 3); + if (!lua_isnumber(L, -1)) + luaL_argerror(L, 3, "number 'timeout' field expected"); + li.l_linger = (u_short) lua_tonumber(L, -1); + return opt_set(L, ps, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof(li)); +} + +int opt_get_linger(lua_State *L, p_socket ps) +{ + struct linger li; /* obj, name */ + int len = sizeof(li); + int err = opt_get(L, ps, SOL_SOCKET, SO_LINGER, (char *) &li, &len); + if (err) + return err; + lua_newtable(L); + lua_pushboolean(L, li.l_onoff); + lua_setfield(L, -2, "on"); + lua_pushinteger(L, li.l_linger); + lua_setfield(L, -2, "timeout"); + return 1; +} + +/*------------------------------------------------------*/ +int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_IP, IP_MULTICAST_TTL); +} + +/*------------------------------------------------------*/ +int opt_set_ip_multicast_if(lua_State *L, p_socket ps) +{ + const char *address = luaL_checkstring(L, 3); /* obj, name, ip */ + struct in_addr val; + val.s_addr = htonl(INADDR_ANY); + if (strcmp(address, "*") && !inet_aton(address, &val)) + luaL_argerror(L, 3, "ip expected"); + return opt_set(L, ps, IPPROTO_IP, IP_MULTICAST_IF, + (char *) &val, sizeof(val)); +} + +int opt_get_ip_multicast_if(lua_State *L, p_socket ps) +{ + struct in_addr val; + socklen_t len = sizeof(val); + if (getsockopt(*ps, IPPROTO_IP, IP_MULTICAST_IF, (char *) &val, &len) < 0) { + lua_pushnil(L); + lua_pushstring(L, "getsockopt failed"); + return 2; + } + lua_pushstring(L, inet_ntoa(val)); + return 1; +} + +/*------------------------------------------------------*/ +int opt_set_ip_add_membership(lua_State *L, p_socket ps) +{ + return opt_setmembership(L, ps, IPPROTO_IP, IP_ADD_MEMBERSHIP); +} + +int opt_set_ip_drop_membersip(lua_State *L, p_socket ps) +{ + return opt_setmembership(L, ps, IPPROTO_IP, IP_DROP_MEMBERSHIP); +} + +/*------------------------------------------------------*/ +int opt_set_ip6_add_membership(lua_State *L, p_socket ps) +{ + return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP); +} + +int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps) +{ + return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP); +} + +/*------------------------------------------------------*/ +int opt_get_ip6_v6only(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); +} + +int opt_set_ip6_v6only(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); +} + +/*------------------------------------------------------*/ +int opt_get_error(lua_State *L, p_socket ps) +{ + int val = 0; + socklen_t len = sizeof(val); + if (getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *) &val, &len) < 0) { + lua_pushnil(L); + lua_pushstring(L, "getsockopt failed"); + return 2; + } + lua_pushstring(L, socket_strerror(val)); + return 1; +} + +/*=========================================================================*\ +* Auxiliar functions +\*=========================================================================*/ +static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) +{ + struct ip_mreq val; /* obj, name, table */ + if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); + lua_pushstring(L, "multiaddr"); + lua_gettable(L, 3); + if (!lua_isstring(L, -1)) + luaL_argerror(L, 3, "string 'multiaddr' field expected"); + if (!inet_aton(lua_tostring(L, -1), &val.imr_multiaddr)) + luaL_argerror(L, 3, "invalid 'multiaddr' ip address"); + lua_pushstring(L, "interface"); + lua_gettable(L, 3); + if (!lua_isstring(L, -1)) + luaL_argerror(L, 3, "string 'interface' field expected"); + val.imr_interface.s_addr = htonl(INADDR_ANY); + if (strcmp(lua_tostring(L, -1), "*") && + !inet_aton(lua_tostring(L, -1), &val.imr_interface)) + luaL_argerror(L, 3, "invalid 'interface' ip address"); + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} + +static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) +{ + struct ipv6_mreq val; /* obj, opt-name, table */ + memset(&val, 0, sizeof(val)); + if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); + lua_pushstring(L, "multiaddr"); + lua_gettable(L, 3); + if (!lua_isstring(L, -1)) + luaL_argerror(L, 3, "string 'multiaddr' field expected"); + if (!inet_pton(AF_INET6, lua_tostring(L, -1), &val.ipv6mr_multiaddr)) + luaL_argerror(L, 3, "invalid 'multiaddr' ip address"); + lua_pushstring(L, "interface"); + lua_gettable(L, 3); + /* By default we listen to interface on default route + * (sigh). However, interface= can override it. We should + * support either number, or name for it. Waiting for + * windows port of if_nametoindex */ + if (!lua_isnil(L, -1)) { + if (lua_isnumber(L, -1)) { + val.ipv6mr_interface = (unsigned int) lua_tonumber(L, -1); + } else + luaL_argerror(L, -1, "number 'interface' field expected"); + } + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} + +static +int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len) +{ + socklen_t socklen = *len; + if (getsockopt(*ps, level, name, (char *) val, &socklen) < 0) { + lua_pushnil(L); + lua_pushstring(L, "getsockopt failed"); + return 2; + } + *len = socklen; + return 0; +} + +static +int opt_set(lua_State *L, p_socket ps, int level, int name, void *val, int len) +{ + if (setsockopt(*ps, level, name, (char *) val, len) < 0) { + lua_pushnil(L); + lua_pushstring(L, "setsockopt failed"); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int opt_getboolean(lua_State *L, p_socket ps, int level, int name) +{ + int val = 0; + int len = sizeof(val); + int err = opt_get(L, ps, level, name, (char *) &val, &len); + if (err) + return err; + lua_pushboolean(L, val); + return 1; +} + +static int opt_setboolean(lua_State *L, p_socket ps, int level, int name) +{ + int val = auxiliar_checkboolean(L, 3); /* obj, name, bool */ + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} + +static int opt_getint(lua_State *L, p_socket ps, int level, int name) +{ + int val = 0; + int len = sizeof(val); + int err = opt_get(L, ps, level, name, (char *) &val, &len); + if (err) + return err; + lua_pushnumber(L, val); + return 1; +} + +static int opt_setint(lua_State *L, p_socket ps, int level, int name) +{ + int val = (int) lua_tonumber(L, 3); /* obj, name, int */ + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} diff --git a/libraries/luasocket/libluasocket/options.h b/libraries/luasocket/libluasocket/options.h new file mode 100644 index 000000000..456eeb5f4 --- /dev/null +++ b/libraries/luasocket/libluasocket/options.h @@ -0,0 +1,113 @@ +#ifndef OPTIONS_H +#define OPTIONS_H +/*=========================================================================*\ +* Common option interface +* LuaSocket toolkit +* +* This module provides a common interface to socket options, used mainly by +* modules UDP and TCP. +\*=========================================================================*/ + +#include "luasocket.h" +#include "socket.h" + +/* option registry */ +typedef struct t_opt { + const char *name; + int (*func)(lua_State *L, p_socket ps); +} t_opt; +typedef t_opt *p_opt; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps); +int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps); + +int opt_set_reuseaddr(lua_State *L, p_socket ps); +int opt_get_reuseaddr(lua_State *L, p_socket ps); + +int opt_set_reuseport(lua_State *L, p_socket ps); +int opt_get_reuseport(lua_State *L, p_socket ps); + +int opt_set_tcp_nodelay(lua_State *L, p_socket ps); +int opt_get_tcp_nodelay(lua_State *L, p_socket ps); + +#ifdef TCP_KEEPIDLE +int opt_set_tcp_keepidle(lua_State *L, p_socket ps); +int opt_get_tcp_keepidle(lua_State *L, p_socket ps); +#endif + +#ifdef TCP_KEEPCNT +int opt_set_tcp_keepcnt(lua_State *L, p_socket ps); +int opt_get_tcp_keepcnt(lua_State *L, p_socket ps); +#endif + +#ifdef TCP_KEEPINTVL +int opt_set_tcp_keepintvl(lua_State *L, p_socket ps); +int opt_get_tcp_keepintvl(lua_State *L, p_socket ps); +#endif + +#ifdef TCP_DEFER_ACCEPT +int opt_set_tcp_defer_accept(lua_State *L, p_socket ps); +#endif + +int opt_set_keepalive(lua_State *L, p_socket ps); +int opt_get_keepalive(lua_State *L, p_socket ps); + +int opt_set_dontroute(lua_State *L, p_socket ps); +int opt_get_dontroute(lua_State *L, p_socket ps); + +int opt_set_broadcast(lua_State *L, p_socket ps); +int opt_get_broadcast(lua_State *L, p_socket ps); + +int opt_set_recv_buf_size(lua_State *L, p_socket ps); +int opt_get_recv_buf_size(lua_State *L, p_socket ps); + +int opt_set_send_buf_size(lua_State *L, p_socket ps); +int opt_get_send_buf_size(lua_State *L, p_socket ps); + +#ifdef TCP_FASTOPEN +int opt_set_tcp_fastopen(lua_State *L, p_socket ps); +#endif +#ifdef TCP_FASTOPEN_CONNECT +int opt_set_tcp_fastopen_connect(lua_State *L, p_socket ps); +#endif + +int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps); +int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps); + +int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps); +int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_loop(lua_State *L, p_socket ps); +int opt_get_ip_multicast_loop(lua_State *L, p_socket ps); + +int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps); +int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps); + +int opt_set_linger(lua_State *L, p_socket ps); +int opt_get_linger(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_if(lua_State *L, p_socket ps); +int opt_get_ip_multicast_if(lua_State *L, p_socket ps); + +int opt_set_ip_add_membership(lua_State *L, p_socket ps); +int opt_set_ip_drop_membersip(lua_State *L, p_socket ps); + +int opt_set_ip6_add_membership(lua_State *L, p_socket ps); +int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps); + +int opt_set_ip6_v6only(lua_State *L, p_socket ps); +int opt_get_ip6_v6only(lua_State *L, p_socket ps); + +int opt_get_error(lua_State *L, p_socket ps); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif diff --git a/libraries/luasocket/libluasocket/pierror.h b/libraries/luasocket/libluasocket/pierror.h new file mode 100644 index 000000000..cb773ab7f --- /dev/null +++ b/libraries/luasocket/libluasocket/pierror.h @@ -0,0 +1,28 @@ +#ifndef PIERROR_H +#define PIERROR_H +/*=========================================================================*\ +* Error messages +* Defines platform independent error messages +\*=========================================================================*/ + +#define PIE_HOST_NOT_FOUND "host not found" +#define PIE_ADDRINUSE "address already in use" +#define PIE_ISCONN "already connected" +#define PIE_ACCESS "permission denied" +#define PIE_CONNREFUSED "connection refused" +#define PIE_CONNABORTED "closed" +#define PIE_CONNRESET "closed" +#define PIE_TIMEDOUT "timeout" +#define PIE_AGAIN "temporary failure in name resolution" +#define PIE_BADFLAGS "invalid value for ai_flags" +#define PIE_BADHINTS "invalid value for hints" +#define PIE_FAIL "non-recoverable failure in name resolution" +#define PIE_FAMILY "ai_family not supported" +#define PIE_MEMORY "memory allocation failure" +#define PIE_NONAME "host or service not provided, or not known" +#define PIE_OVERFLOW "argument buffer overflow" +#define PIE_PROTOCOL "resolved protocol is unknown" +#define PIE_SERVICE "service not supported for socket type" +#define PIE_SOCKTYPE "ai_socktype not supported" + +#endif diff --git a/libraries/luasocket/libluasocket/select.c b/libraries/luasocket/libluasocket/select.c new file mode 100644 index 000000000..bb47c4592 --- /dev/null +++ b/libraries/luasocket/libluasocket/select.c @@ -0,0 +1,214 @@ +/*=========================================================================*\ +* Select implementation +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "socket.h" +#include "timeout.h" +#include "select.h" + +#include + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static t_socket getfd(lua_State *L); +static int dirty(lua_State *L); +static void collect_fd(lua_State *L, int tab, int itab, + fd_set *set, t_socket *max_fd); +static int check_dirty(lua_State *L, int tab, int dtab, fd_set *set); +static void return_fd(lua_State *L, fd_set *set, t_socket max_fd, + int itab, int tab, int start); +static void make_assoc(lua_State *L, int tab); +static int global_select(lua_State *L); + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"select", global_select}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int select_open(lua_State *L) { + lua_pushstring(L, "_SETSIZE"); + lua_pushinteger(L, FD_SETSIZE); + lua_rawset(L, -3); + lua_pushstring(L, "_SOCKETINVALID"); + lua_pushinteger(L, SOCKET_INVALID); + lua_rawset(L, -3); + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Global Lua functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Waits for a set of sockets until a condition is met or timeout. +\*-------------------------------------------------------------------------*/ +static int global_select(lua_State *L) { + int rtab, wtab, itab, ret, ndirty; + t_socket max_fd = SOCKET_INVALID; + fd_set rset, wset; + t_timeout tm; + double t = luaL_optnumber(L, 3, -1); + FD_ZERO(&rset); FD_ZERO(&wset); + lua_settop(L, 3); + lua_newtable(L); itab = lua_gettop(L); + lua_newtable(L); rtab = lua_gettop(L); + lua_newtable(L); wtab = lua_gettop(L); + collect_fd(L, 1, itab, &rset, &max_fd); + collect_fd(L, 2, itab, &wset, &max_fd); + ndirty = check_dirty(L, 1, rtab, &rset); + t = ndirty > 0? 0.0: t; + timeout_init(&tm, t, -1); + timeout_markstart(&tm); + ret = socket_select(max_fd+1, &rset, &wset, NULL, &tm); + if (ret > 0 || ndirty > 0) { + return_fd(L, &rset, max_fd+1, itab, rtab, ndirty); + return_fd(L, &wset, max_fd+1, itab, wtab, 0); + make_assoc(L, rtab); + make_assoc(L, wtab); + return 2; + } else if (ret == 0) { + lua_pushstring(L, "timeout"); + return 3; + } else { + luaL_error(L, "select failed"); + return 3; + } +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +static t_socket getfd(lua_State *L) { + t_socket fd = SOCKET_INVALID; + lua_pushstring(L, "getfd"); + lua_gettable(L, -2); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, -2); + lua_call(L, 1, 1); + if (lua_isnumber(L, -1)) { + double numfd = lua_tonumber(L, -1); + fd = (numfd >= 0.0)? (t_socket) numfd: SOCKET_INVALID; + } + } + lua_pop(L, 1); + return fd; +} + +static int dirty(lua_State *L) { + int is = 0; + lua_pushstring(L, "dirty"); + lua_gettable(L, -2); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, -2); + lua_call(L, 1, 1); + is = lua_toboolean(L, -1); + } + lua_pop(L, 1); + return is; +} + +static void collect_fd(lua_State *L, int tab, int itab, + fd_set *set, t_socket *max_fd) { + int i = 1, n = 0; + /* nil is the same as an empty table */ + if (lua_isnil(L, tab)) return; + /* otherwise we need it to be a table */ + luaL_checktype(L, tab, LUA_TTABLE); + for ( ;; ) { + t_socket fd; + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + break; + } + /* getfd figures out if this is a socket */ + fd = getfd(L); + if (fd != SOCKET_INVALID) { + /* make sure we don't overflow the fd_set */ +#ifdef _WIN32 + if (n >= FD_SETSIZE) + luaL_argerror(L, tab, "too many sockets"); +#else + if (fd >= FD_SETSIZE) + luaL_argerror(L, tab, "descriptor too large for set size"); +#endif + FD_SET(fd, set); + n++; + /* keep track of the largest descriptor so far */ + if (*max_fd == SOCKET_INVALID || *max_fd < fd) + *max_fd = fd; + /* make sure we can map back from descriptor to the object */ + lua_pushnumber(L, (lua_Number) fd); + lua_pushvalue(L, -2); + lua_settable(L, itab); + } + lua_pop(L, 1); + i = i + 1; + } +} + +static int check_dirty(lua_State *L, int tab, int dtab, fd_set *set) { + int ndirty = 0, i = 1; + if (lua_isnil(L, tab)) + return 0; + for ( ;; ) { + t_socket fd; + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + break; + } + fd = getfd(L); + if (fd != SOCKET_INVALID && dirty(L)) { + lua_pushnumber(L, ++ndirty); + lua_pushvalue(L, -2); + lua_settable(L, dtab); + FD_CLR(fd, set); + } + lua_pop(L, 1); + i = i + 1; + } + return ndirty; +} + +static void return_fd(lua_State *L, fd_set *set, t_socket max_fd, + int itab, int tab, int start) { + t_socket fd; + for (fd = 0; fd < max_fd; fd++) { + if (FD_ISSET(fd, set)) { + lua_pushnumber(L, ++start); + lua_pushnumber(L, (lua_Number) fd); + lua_gettable(L, itab); + lua_settable(L, tab); + } + } +} + +static void make_assoc(lua_State *L, int tab) { + int i = 1, atab; + lua_newtable(L); atab = lua_gettop(L); + for ( ;; ) { + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (!lua_isnil(L, -1)) { + lua_pushnumber(L, i); + lua_pushvalue(L, -2); + lua_settable(L, atab); + lua_pushnumber(L, i); + lua_settable(L, atab); + } else { + lua_pop(L, 1); + break; + } + i = i+1; + } +} diff --git a/libraries/luasocket/libluasocket/select.h b/libraries/luasocket/libluasocket/select.h new file mode 100644 index 000000000..5d45fe753 --- /dev/null +++ b/libraries/luasocket/libluasocket/select.h @@ -0,0 +1,23 @@ +#ifndef SELECT_H +#define SELECT_H +/*=========================================================================*\ +* Select implementation +* LuaSocket toolkit +* +* Each object that can be passed to the select function has to export +* method getfd() which returns the descriptor to be passed to the +* underlying select function. Another method, dirty(), should return +* true if there is data ready for reading (required for buffered input). +\*=========================================================================*/ + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int select_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* SELECT_H */ diff --git a/libraries/luasocket/libluasocket/serial.c b/libraries/luasocket/libluasocket/serial.c new file mode 100644 index 000000000..21485d3e2 --- /dev/null +++ b/libraries/luasocket/libluasocket/serial.c @@ -0,0 +1,171 @@ +/*=========================================================================*\ +* Serial stream +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unix.h" + +#include +#include + +/* +Reuses userdata definition from unix.h, since it is useful for all +stream-like objects. + +If we stored the serial path for use in error messages or userdata +printing, we might need our own userdata definition. + +Group usage is semi-inherited from unix.c, but unnecessary since we +have only one object type. +*/ + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_send(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_close(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_getstats(lua_State *L); +static int meth_setstats(lua_State *L); + +/* serial object methods */ +static luaL_Reg serial_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"close", meth_close}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"getstats", meth_getstats}, + {"setstats", meth_setstats}, + {"receive", meth_receive}, + {"send", meth_send}, + {"setfd", meth_setfd}, + {"settimeout", meth_settimeout}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_serial(lua_State *L) { + /* create classes */ + auxiliar_newclass(L, "serial{client}", serial_methods); + /* create class groups */ + auxiliar_add2group(L, "serial{client}", "serial{any}"); + lua_pushcfunction(L, global_create); + return 1; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_send(L, &un->buf); +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_receive(L, &un->buf); +} + +static int meth_getstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_getstats(L, &un->buf); +} + +static int meth_setstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_setstats(L, &un->buf); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + lua_pushboolean(L, !buffer_isempty(&un->buf)); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ + + +/*-------------------------------------------------------------------------*\ +* Creates a serial object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) { + const char* path = luaL_checkstring(L, 1); + + /* allocate unix object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + + /* open serial device */ + t_socket sock = open(path, O_NOCTTY|O_RDWR); + + /*printf("open %s on %d\n", path, sock);*/ + + if (sock < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + lua_pushnumber(L, errno); + return 3; + } + /* set its type as client object */ + auxiliar_setclass(L, "serial{client}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_write, (p_recv) socket_read, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; +} diff --git a/libraries/luasocket/libluasocket/smtp.lua b/libraries/luasocket/libluasocket/smtp.lua new file mode 100644 index 000000000..8a8bf4de1 --- /dev/null +++ b/libraries/luasocket/libluasocket/smtp.lua @@ -0,0 +1,259 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- SMTP client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local coroutine = require("coroutine") +local string = require("string") +local math = require("math") +local os = require("os") +local socket = require("socket") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +local headers = require("socket.headers") +local mime = require("mime") + +socket.smtp = {} +local _M = socket.smtp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout for connection +_M.TIMEOUT = 60 +-- default server used to send e-mails +_M.SERVER = "localhost" +-- default port +_M.PORT = 25 +-- domain used in HELO command and default sendmail +-- If we are under a CGI, try to get from environment +_M.DOMAIN = os.getenv("SERVER_NAME") or "localhost" +-- default time zone (means we don't know) +_M.ZONE = "-0000" + +--------------------------------------------------------------------------- +-- Low level SMTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function metat.__index:greet(domain) + self.try(self.tp:check("2..")) + self.try(self.tp:command("EHLO", domain or _M.DOMAIN)) + return socket.skip(1, self.try(self.tp:check("2.."))) +end + +function metat.__index:mail(from) + self.try(self.tp:command("MAIL", "FROM:" .. from)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:rcpt(to) + self.try(self.tp:command("RCPT", "TO:" .. to)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:data(src, step) + self.try(self.tp:command("DATA")) + self.try(self.tp:check("3..")) + self.try(self.tp:source(src, step)) + self.try(self.tp:send("\r\n.\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:quit() + self.try(self.tp:command("QUIT")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:close() + return self.tp:close() +end + +function metat.__index:login(user, password) + self.try(self.tp:command("AUTH", "LOGIN")) + self.try(self.tp:check("3..")) + self.try(self.tp:send(mime.b64(user) .. "\r\n")) + self.try(self.tp:check("3..")) + self.try(self.tp:send(mime.b64(password) .. "\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:plain(user, password) + local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) + self.try(self.tp:command("AUTH", auth)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:auth(user, password, ext) + if not user or not password then return 1 end + if string.find(ext, "AUTH[^\n]+LOGIN") then + return self:login(user, password) + elseif string.find(ext, "AUTH[^\n]+PLAIN") then + return self:plain(user, password) + else + self.try(nil, "authentication not supported") + end +end + +-- send message or throw an exception +function metat.__index:send(mailt) + self:mail(mailt.from) + if base.type(mailt.rcpt) == "table" then + for i,v in base.ipairs(mailt.rcpt) do + self:rcpt(v) + end + else + self:rcpt(mailt.rcpt) + end + self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) +end + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server or _M.SERVER, port or _M.PORT, + _M.TIMEOUT, create)) + local s = base.setmetatable({tp = tp}, metat) + -- make sure tp is closed if we get an exception + s.try = socket.newtry(function() + s:close() + end) + return s +end + +-- convert headers to lowercase +local function lower_headers(headers) + local lower = {} + for i,v in base.pairs(headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +--------------------------------------------------------------------------- +-- Multipart message source +----------------------------------------------------------------------------- +-- returns a hopefully unique mime boundary +local seqno = 0 +local function newboundary() + seqno = seqno + 1 + return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), + math.random(0, 99999), seqno) +end + +-- send_message forward declaration +local send_message + +-- yield the headers all at once, it's faster +local function send_headers(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f,v in base.pairs(tosend) do + h = (canonic[f] or f) .. ': ' .. v .. "\r\n" .. h + end + coroutine.yield(h) +end + +-- yield multipart message body from a multipart message table +local function send_multipart(mesgt) + -- make sure we have our boundary and send headers + local bd = newboundary() + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or 'multipart/mixed' + headers['content-type'] = headers['content-type'] .. + '; boundary="' .. bd .. '"' + send_headers(headers) + -- send preamble + if mesgt.body.preamble then + coroutine.yield(mesgt.body.preamble) + coroutine.yield("\r\n") + end + -- send each part separated by a boundary + for i, m in base.ipairs(mesgt.body) do + coroutine.yield("\r\n--" .. bd .. "\r\n") + send_message(m) + end + -- send last boundary + coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") + -- send epilogue + if mesgt.body.epilogue then + coroutine.yield(mesgt.body.epilogue) + coroutine.yield("\r\n") + end +end + +-- yield message body from a source +local function send_source(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from source + while true do + local chunk, err = mesgt.body() + if err then coroutine.yield(nil, err) + elseif chunk then coroutine.yield(chunk) + else break end + end +end + +-- yield message body from a string +local function send_string(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from string + coroutine.yield(mesgt.body) +end + +-- message source +function send_message(mesgt) + if base.type(mesgt.body) == "table" then send_multipart(mesgt) + elseif base.type(mesgt.body) == "function" then send_source(mesgt) + else send_string(mesgt) end +end + +-- set defaul headers +local function adjust_headers(mesgt) + local lower = lower_headers(mesgt.headers) + lower["date"] = lower["date"] or + os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or _M.ZONE) + lower["x-mailer"] = lower["x-mailer"] or socket._VERSION + -- this can't be overriden + lower["mime-version"] = "1.0" + return lower +end + +function _M.message(mesgt) + mesgt.headers = adjust_headers(mesgt) + -- create and return message source + local co = coroutine.create(function() send_message(mesgt) end) + return function() + local ret, a, b = coroutine.resume(co) + if ret then return a, b + else return nil, a end + end +end + +--------------------------------------------------------------------------- +-- High level SMTP API +----------------------------------------------------------------------------- +_M.send = socket.protect(function(mailt) + local s = _M.open(mailt.server, mailt.port, mailt.create) + local ext = s:greet(mailt.domain) + s:auth(mailt.user, mailt.password, ext) + s:send(mailt) + s:quit() + return s:close() +end) + +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/socket.h b/libraries/luasocket/libluasocket/socket.h new file mode 100644 index 000000000..2555bab64 --- /dev/null +++ b/libraries/luasocket/libluasocket/socket.h @@ -0,0 +1,75 @@ +#ifndef SOCKET_H +#define SOCKET_H +/*=========================================================================*\ +* Socket compatibilization module +* LuaSocket toolkit +* +* BSD Sockets and WinSock are similar, but there are a few irritating +* differences. Also, not all *nix platforms behave the same. This module +* (and the associated usocket.h and wsocket.h) factor these differences and +* creates a interface compatible with the io.h module. +\*=========================================================================*/ +#include "io.h" + +/*=========================================================================*\ +* Platform specific compatibilization +\*=========================================================================*/ +#ifdef _WIN32 +#include "wsocket.h" +#define LUA_GAI_STRERROR gai_strerrorA +#else +#include "usocket.h" +#define LUA_GAI_STRERROR gai_strerror +#endif + +/*=========================================================================*\ +* The connect and accept functions accept a timeout and their +* implementations are somewhat complicated. We chose to move +* the timeout control into this module for these functions in +* order to simplify the modules that use them. +\*=========================================================================*/ +#include "timeout.h" + +/* convenient shorthand */ +typedef struct sockaddr SA; + +/*=========================================================================*\ +* Functions bellow implement a comfortable platform independent +* interface to sockets +\*=========================================================================*/ + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int socket_waitfd(p_socket ps, int sw, p_timeout tm); +int socket_open(void); +int socket_close(void); +void socket_destroy(p_socket ps); +int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, p_timeout tm); +int socket_create(p_socket ps, int domain, int type, int protocol); +int socket_bind(p_socket ps, SA *addr, socklen_t addr_len); +int socket_listen(p_socket ps, int backlog); +void socket_shutdown(p_socket ps, int how); +int socket_connect(p_socket ps, SA *addr, socklen_t addr_len, p_timeout tm); +int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *addr_len, p_timeout tm); +int socket_send(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); +int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t addr_len, p_timeout tm); +int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); +int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *addr_len, p_timeout tm); +int socket_write(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); +int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); +void socket_setblocking(p_socket ps); +void socket_setnonblocking(p_socket ps); +int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp); +int socket_gethostbyname(const char *addr, struct hostent **hp); +const char *socket_hoststrerror(int err); +const char *socket_strerror(int err); +const char *socket_ioerror(p_socket ps, int err); +const char *socket_gaistrerror(int err); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* SOCKET_H */ diff --git a/libraries/luasocket/libluasocket/socket.lua b/libraries/luasocket/libluasocket/socket.lua new file mode 100644 index 000000000..f70f3e1e4 --- /dev/null +++ b/libraries/luasocket/libluasocket/socket.lua @@ -0,0 +1,152 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- LuaSocket helper module +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local math = require("math") +local socket = require("socket.core") + +local _M = socket + +----------------------------------------------------------------------------- +-- Exported auxiliar functions +----------------------------------------------------------------------------- +function _M.connect4(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet") +end + +function _M.connect6(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet6") +end + +function _M.bind(host, port, backlog) + if host == "*" then host = "0.0.0.0" end + local addrinfo, err = socket.dns.getaddrinfo(host); + if not addrinfo then return nil, err end + local sock, res + err = "no info on address" + for i, alt in base.ipairs(addrinfo) do + if alt.family == "inet" then + sock, err = socket.tcp4() + else + sock, err = socket.tcp6() + end + if not sock then return nil, err end + sock:setoption("reuseaddr", true) + res, err = sock:bind(alt.addr, port) + if not res then + sock:close() + else + res, err = sock:listen(backlog) + if not res then + sock:close() + else + return sock + end + end + end + return nil, err +end + +_M.try = _M.newtry() + +function _M.choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) + else return f(opt1, opt2) end + end +end + +----------------------------------------------------------------------------- +-- Socket sources and sinks, conforming to LTN12 +----------------------------------------------------------------------------- +-- create namespaces inside LuaSocket namespace +local sourcet, sinkt = {}, {} +_M.sourcet = sourcet +_M.sinkt = sinkt + +_M.BLOCKSIZE = 2048 + +sinkt["close-when-done"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else return sock:send(chunk) end + end + }) +end + +sinkt["keep-open"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then return sock:send(chunk) + else return 1 end + end + }) +end + +sinkt["default"] = sinkt["keep-open"] + +_M.sink = _M.choose(sinkt) + +sourcet["by-length"] = function(sock, length) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) +end + +sourcet["until-closed"] = function(sock) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else return nil, err end + end + }) +end + + +sourcet["default"] = sourcet["until-closed"] + +_M.source = _M.choose(sourcet) + +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/tcp.c b/libraries/luasocket/libluasocket/tcp.c new file mode 100644 index 000000000..e84db8454 --- /dev/null +++ b/libraries/luasocket/libluasocket/tcp.c @@ -0,0 +1,480 @@ +/*=========================================================================*\ +* TCP object +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "inet.h" +#include "options.h" +#include "tcp.h" + +#include + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int global_create4(lua_State *L); +static int global_create6(lua_State *L); +static int global_connect(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_listen(lua_State *L); +static int meth_getfamily(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_getstats(lua_State *L); +static int meth_setstats(lua_State *L); +static int meth_getsockname(lua_State *L); +static int meth_getpeername(lua_State *L); +static int meth_shutdown(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_accept(lua_State *L); +static int meth_close(lua_State *L); +static int meth_getoption(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_gettimeout(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); + +/* tcp object methods */ +static luaL_Reg tcp_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"accept", meth_accept}, + {"bind", meth_bind}, + {"close", meth_close}, + {"connect", meth_connect}, + {"dirty", meth_dirty}, + {"getfamily", meth_getfamily}, + {"getfd", meth_getfd}, + {"getoption", meth_getoption}, + {"getpeername", meth_getpeername}, + {"getsockname", meth_getsockname}, + {"getstats", meth_getstats}, + {"setstats", meth_setstats}, + {"listen", meth_listen}, + {"receive", meth_receive}, + {"send", meth_send}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, + {"shutdown", meth_shutdown}, + {NULL, NULL} +}; + +/* socket option handlers */ +static t_opt optget[] = { + {"keepalive", opt_get_keepalive}, + {"reuseaddr", opt_get_reuseaddr}, + {"reuseport", opt_get_reuseport}, + {"tcp-nodelay", opt_get_tcp_nodelay}, +#ifdef TCP_KEEPIDLE + {"tcp-keepidle", opt_get_tcp_keepidle}, +#endif +#ifdef TCP_KEEPCNT + {"tcp-keepcnt", opt_get_tcp_keepcnt}, +#endif +#ifdef TCP_KEEPINTVL + {"tcp-keepintvl", opt_get_tcp_keepintvl}, +#endif + {"linger", opt_get_linger}, + {"error", opt_get_error}, + {"recv-buffer-size", opt_get_recv_buf_size}, + {"send-buffer-size", opt_get_send_buf_size}, + {NULL, NULL} +}; + +static t_opt optset[] = { + {"keepalive", opt_set_keepalive}, + {"reuseaddr", opt_set_reuseaddr}, + {"reuseport", opt_set_reuseport}, + {"tcp-nodelay", opt_set_tcp_nodelay}, +#ifdef TCP_KEEPIDLE + {"tcp-keepidle", opt_set_tcp_keepidle}, +#endif +#ifdef TCP_KEEPCNT + {"tcp-keepcnt", opt_set_tcp_keepcnt}, +#endif +#ifdef TCP_KEEPINTVL + {"tcp-keepintvl", opt_set_tcp_keepintvl}, +#endif + {"ipv6-v6only", opt_set_ip6_v6only}, + {"linger", opt_set_linger}, + {"recv-buffer-size", opt_set_recv_buf_size}, + {"send-buffer-size", opt_set_send_buf_size}, +#ifdef TCP_DEFER_ACCEPT + {"tcp-defer-accept", opt_set_tcp_defer_accept}, +#endif +#ifdef TCP_FASTOPEN + {"tcp-fastopen", opt_set_tcp_fastopen}, +#endif +#ifdef TCP_FASTOPEN_CONNECT + {"tcp-fastopen-connect", opt_set_tcp_fastopen_connect}, +#endif + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"tcp", global_create}, + {"tcp4", global_create4}, + {"tcp6", global_create6}, + {"connect", global_connect}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int tcp_open(lua_State *L) +{ + /* create classes */ + auxiliar_newclass(L, "tcp{master}", tcp_methods); + auxiliar_newclass(L, "tcp{client}", tcp_methods); + auxiliar_newclass(L, "tcp{server}", tcp_methods); + /* create class groups */ + auxiliar_add2group(L, "tcp{master}", "tcp{any}"); + auxiliar_add2group(L, "tcp{client}", "tcp{any}"); + auxiliar_add2group(L, "tcp{server}", "tcp{any}"); + /* define library functions */ + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + return buffer_meth_send(L, &tcp->buf); +} + +static int meth_receive(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + return buffer_meth_receive(L, &tcp->buf); +} + +static int meth_getstats(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + return buffer_meth_getstats(L, &tcp->buf); +} + +static int meth_setstats(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + return buffer_meth_setstats(L, &tcp->buf); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_getoption(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return opt_meth_getoption(L, optget, &tcp->sock); +} + +static int meth_setoption(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return opt_meth_setoption(L, optset, &tcp->sock); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + lua_pushnumber(L, (int) tcp->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + tcp->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + lua_pushboolean(L, !buffer_isempty(&tcp->buf)); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Waits for and returns a client object attempting connection to the +* server object +\*-------------------------------------------------------------------------*/ +static int meth_accept(lua_State *L) +{ + p_tcp server = (p_tcp) auxiliar_checkclass(L, "tcp{server}", 1); + p_timeout tm = timeout_markstart(&server->tm); + t_socket sock; + const char *err = inet_tryaccept(&server->sock, server->family, &sock, tm); + /* if successful, push client socket */ + if (err == NULL) { + p_tcp clnt = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); + auxiliar_setclass(L, "tcp{client}", -1); + /* initialize structure fields */ + memset(clnt, 0, sizeof(t_tcp)); + socket_setnonblocking(&sock); + clnt->sock = sock; + io_init(&clnt->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &clnt->sock); + timeout_init(&clnt->tm, -1, -1); + buffer_init(&clnt->buf, &clnt->io, &clnt->tm); + clnt->family = server->family; + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } +} + +/*-------------------------------------------------------------------------*\ +* Binds an object to an address +\*-------------------------------------------------------------------------*/ +static int meth_bind(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{master}", 1); + const char *address = luaL_checkstring(L, 2); + const char *port = luaL_checkstring(L, 3); + const char *err; + struct addrinfo bindhints; + memset(&bindhints, 0, sizeof(bindhints)); + bindhints.ai_socktype = SOCK_STREAM; + bindhints.ai_family = tcp->family; + bindhints.ai_flags = AI_PASSIVE; + err = inet_trybind(&tcp->sock, &tcp->family, address, port, &bindhints); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master tcp object into a client object. +\*-------------------------------------------------------------------------*/ +static int meth_connect(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + const char *address = luaL_checkstring(L, 2); + const char *port = luaL_checkstring(L, 3); + struct addrinfo connecthints; + const char *err; + memset(&connecthints, 0, sizeof(connecthints)); + connecthints.ai_socktype = SOCK_STREAM; + /* make sure we try to connect only to the same family */ + connecthints.ai_family = tcp->family; + timeout_markstart(&tcp->tm); + err = inet_tryconnect(&tcp->sock, &tcp->family, address, port, + &tcp->tm, &connecthints); + /* have to set the class even if it failed due to non-blocking connects */ + auxiliar_setclass(L, "tcp{client}", 1); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + socket_destroy(&tcp->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Returns family as string +\*-------------------------------------------------------------------------*/ +static int meth_getfamily(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + if (tcp->family == AF_INET6) { + lua_pushliteral(L, "inet6"); + return 1; + } else if (tcp->family == AF_INET) { + lua_pushliteral(L, "inet4"); + return 1; + } else { + lua_pushliteral(L, "inet4"); + return 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Puts the sockt in listen mode +\*-------------------------------------------------------------------------*/ +static int meth_listen(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{master}", 1); + int backlog = (int) luaL_optnumber(L, 2, 32); + int err = socket_listen(&tcp->sock, backlog); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } + /* turn master object into a server object */ + auxiliar_setclass(L, "tcp{server}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Shuts the connection down partially +\*-------------------------------------------------------------------------*/ +static int meth_shutdown(lua_State *L) +{ + /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ + static const char* methods[] = { "receive", "send", "both", NULL }; + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + int how = luaL_checkoption(L, 2, "both", methods); + socket_shutdown(&tcp->sock, how); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call inet methods +\*-------------------------------------------------------------------------*/ +static int meth_getpeername(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return inet_meth_getpeername(L, &tcp->sock, tcp->family); +} + +static int meth_getsockname(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return inet_meth_getsockname(L, &tcp->sock, tcp->family); +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return timeout_meth_settimeout(L, &tcp->tm); +} + +static int meth_gettimeout(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return timeout_meth_gettimeout(L, &tcp->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master tcp object +\*-------------------------------------------------------------------------*/ +static int tcp_create(lua_State *L, int family) { + p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); + memset(tcp, 0, sizeof(t_tcp)); + /* set its type as master object */ + auxiliar_setclass(L, "tcp{master}", -1); + /* if family is AF_UNSPEC, we leave the socket invalid and + * store AF_UNSPEC into family. This will allow it to later be + * replaced with an AF_INET6 or AF_INET socket upon first use. */ + tcp->sock = SOCKET_INVALID; + tcp->family = family; + io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &tcp->sock); + timeout_init(&tcp->tm, -1, -1); + buffer_init(&tcp->buf, &tcp->io, &tcp->tm); + if (family != AF_UNSPEC) { + const char *err = inet_trycreate(&tcp->sock, family, SOCK_STREAM, 0); + if (err != NULL) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + socket_setnonblocking(&tcp->sock); + } + return 1; +} + +static int global_create(lua_State *L) { + return tcp_create(L, AF_UNSPEC); +} + +static int global_create4(lua_State *L) { + return tcp_create(L, AF_INET); +} + +static int global_create6(lua_State *L) { + return tcp_create(L, AF_INET6); +} + +static int global_connect(lua_State *L) { + const char *remoteaddr = luaL_checkstring(L, 1); + const char *remoteserv = luaL_checkstring(L, 2); + const char *localaddr = luaL_optstring(L, 3, NULL); + const char *localserv = luaL_optstring(L, 4, "0"); + int family = inet_optfamily(L, 5, "unspec"); + p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); + struct addrinfo bindhints, connecthints; + const char *err = NULL; + /* initialize tcp structure */ + memset(tcp, 0, sizeof(t_tcp)); + io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &tcp->sock); + timeout_init(&tcp->tm, -1, -1); + buffer_init(&tcp->buf, &tcp->io, &tcp->tm); + tcp->sock = SOCKET_INVALID; + tcp->family = AF_UNSPEC; + /* allow user to pick local address and port */ + memset(&bindhints, 0, sizeof(bindhints)); + bindhints.ai_socktype = SOCK_STREAM; + bindhints.ai_family = family; + bindhints.ai_flags = AI_PASSIVE; + if (localaddr) { + err = inet_trybind(&tcp->sock, &tcp->family, localaddr, + localserv, &bindhints); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + } + /* try to connect to remote address and port */ + memset(&connecthints, 0, sizeof(connecthints)); + connecthints.ai_socktype = SOCK_STREAM; + /* make sure we try to connect only to the same family */ + connecthints.ai_family = tcp->family; + err = inet_tryconnect(&tcp->sock, &tcp->family, remoteaddr, remoteserv, + &tcp->tm, &connecthints); + if (err) { + socket_destroy(&tcp->sock); + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + auxiliar_setclass(L, "tcp{client}", -1); + return 1; +} diff --git a/libraries/luasocket/libluasocket/tcp.h b/libraries/luasocket/libluasocket/tcp.h new file mode 100644 index 000000000..9b282efeb --- /dev/null +++ b/libraries/luasocket/libluasocket/tcp.h @@ -0,0 +1,43 @@ +#ifndef TCP_H +#define TCP_H +/*=========================================================================*\ +* TCP object +* LuaSocket toolkit +* +* The tcp.h module is basicly a glue that puts together modules buffer.h, +* timeout.h socket.h and inet.h to provide the LuaSocket TCP (AF_INET, +* SOCK_STREAM) support. +* +* Three classes are defined: master, client and server. The master class is +* a newly created tcp object, that has not been bound or connected. Server +* objects are tcp objects bound to some local address. Client objects are +* tcp objects either connected to some address or returned by the accept +* method of a server object. +\*=========================================================================*/ +#include "luasocket.h" + +#include "buffer.h" +#include "timeout.h" +#include "socket.h" + +typedef struct t_tcp_ { + t_socket sock; + t_io io; + t_buffer buf; + t_timeout tm; + int family; +} t_tcp; + +typedef t_tcp *p_tcp; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int tcp_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* TCP_H */ diff --git a/libraries/luasocket/libluasocket/timeout.c b/libraries/luasocket/libluasocket/timeout.c new file mode 100644 index 000000000..2bdc0698c --- /dev/null +++ b/libraries/luasocket/libluasocket/timeout.c @@ -0,0 +1,226 @@ +/*=========================================================================*\ +* Timeout management functions +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "timeout.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +/* min and max macros */ +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) +#endif + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int timeout_lua_gettime(lua_State *L); +static int timeout_lua_sleep(lua_State *L); + +static luaL_Reg func[] = { + { "gettime", timeout_lua_gettime }, + { "sleep", timeout_lua_sleep }, + { NULL, NULL } +}; + +/*=========================================================================*\ +* Exported functions. +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initialize structure +\*-------------------------------------------------------------------------*/ +void timeout_init(p_timeout tm, double block, double total) { + tm->block = block; + tm->total = total; +} + +/*-------------------------------------------------------------------------*\ +* Determines how much time we have left for the next system call, +* if the previous call was successful +* Input +* tm: timeout control structure +* Returns +* the number of ms left or -1 if there is no time limit +\*-------------------------------------------------------------------------*/ +double timeout_get(p_timeout tm) { + if (tm->block < 0.0 && tm->total < 0.0) { + return -1; + } else if (tm->block < 0.0) { + double t = tm->total - timeout_gettime() + tm->start; + return MAX(t, 0.0); + } else if (tm->total < 0.0) { + return tm->block; + } else { + double t = tm->total - timeout_gettime() + tm->start; + return MIN(tm->block, MAX(t, 0.0)); + } +} + +/*-------------------------------------------------------------------------*\ +* Returns time since start of operation +* Input +* tm: timeout control structure +* Returns +* start field of structure +\*-------------------------------------------------------------------------*/ +double timeout_getstart(p_timeout tm) { + return tm->start; +} + +/*-------------------------------------------------------------------------*\ +* Determines how much time we have left for the next system call, +* if the previous call was a failure +* Input +* tm: timeout control structure +* Returns +* the number of ms left or -1 if there is no time limit +\*-------------------------------------------------------------------------*/ +double timeout_getretry(p_timeout tm) { + if (tm->block < 0.0 && tm->total < 0.0) { + return -1; + } else if (tm->block < 0.0) { + double t = tm->total - timeout_gettime() + tm->start; + return MAX(t, 0.0); + } else if (tm->total < 0.0) { + double t = tm->block - timeout_gettime() + tm->start; + return MAX(t, 0.0); + } else { + double t = tm->total - timeout_gettime() + tm->start; + return MIN(tm->block, MAX(t, 0.0)); + } +} + +/*-------------------------------------------------------------------------*\ +* Marks the operation start time in structure +* Input +* tm: timeout control structure +\*-------------------------------------------------------------------------*/ +p_timeout timeout_markstart(p_timeout tm) { + tm->start = timeout_gettime(); + return tm; +} + +/*-------------------------------------------------------------------------*\ +* Gets time in s, relative to January 1, 1970 (UTC) +* Returns +* time in s. +\*-------------------------------------------------------------------------*/ +#ifdef _WIN32 +double timeout_gettime(void) { + FILETIME ft; + double t; + GetSystemTimeAsFileTime(&ft); + /* Windows file time (time since January 1, 1601 (UTC)) */ + t = ft.dwLowDateTime/1.0e7 + ft.dwHighDateTime*(4294967296.0/1.0e7); + /* convert to Unix Epoch time (time since January 1, 1970 (UTC)) */ + return (t - 11644473600.0); +} +#else +double timeout_gettime(void) { + struct timeval v; + gettimeofday(&v, (struct timezone *) NULL); + /* Unix Epoch time (time since January 1, 1970 (UTC)) */ + return v.tv_sec + v.tv_usec/1.0e6; +} +#endif + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int timeout_open(lua_State *L) { + luaL_setfuncs(L, func, 0); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Sets timeout values for IO operations +* Lua Input: base, time [, mode] +* time: time out value in seconds +* mode: "b" for block timeout, "t" for total timeout. (default: b) +\*-------------------------------------------------------------------------*/ +int timeout_meth_settimeout(lua_State *L, p_timeout tm) { + double t = luaL_optnumber(L, 2, -1); + const char *mode = luaL_optstring(L, 3, "b"); + switch (*mode) { + case 'b': + tm->block = t; + break; + case 'r': case 't': + tm->total = t; + break; + default: + luaL_argcheck(L, 0, 3, "invalid timeout mode"); + break; + } + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Gets timeout values for IO operations +* Lua Output: block, total +\*-------------------------------------------------------------------------*/ +int timeout_meth_gettimeout(lua_State *L, p_timeout tm) { + lua_pushnumber(L, tm->block); + lua_pushnumber(L, tm->total); + return 2; +} + +/*=========================================================================*\ +* Test support functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Returns the time the system has been up, in secconds. +\*-------------------------------------------------------------------------*/ +static int timeout_lua_gettime(lua_State *L) +{ + lua_pushnumber(L, timeout_gettime()); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Sleep for n seconds. +\*-------------------------------------------------------------------------*/ +#ifdef _WIN32 +int timeout_lua_sleep(lua_State *L) +{ + double n = luaL_checknumber(L, 1); + if (n < 0.0) n = 0.0; + if (n < DBL_MAX/1000.0) n *= 1000.0; + if (n > INT_MAX) n = INT_MAX; + Sleep((int)n); + return 0; +} +#else +int timeout_lua_sleep(lua_State *L) +{ + double n = luaL_checknumber(L, 1); + struct timespec t, r; + if (n < 0.0) n = 0.0; + if (n > INT_MAX) n = INT_MAX; + t.tv_sec = (int) n; + n -= t.tv_sec; + t.tv_nsec = (int) (n * 1000000000); + if (t.tv_nsec >= 1000000000) t.tv_nsec = 999999999; + while (nanosleep(&t, &r) != 0) { + t.tv_sec = r.tv_sec; + t.tv_nsec = r.tv_nsec; + } + return 0; +} +#endif diff --git a/libraries/luasocket/libluasocket/timeout.h b/libraries/luasocket/libluasocket/timeout.h new file mode 100644 index 000000000..9e5250d33 --- /dev/null +++ b/libraries/luasocket/libluasocket/timeout.h @@ -0,0 +1,40 @@ +#ifndef TIMEOUT_H +#define TIMEOUT_H +/*=========================================================================*\ +* Timeout management functions +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +/* timeout control structure */ +typedef struct t_timeout_ { + double block; /* maximum time for blocking calls */ + double total; /* total number of miliseconds for operation */ + double start; /* time of start of operation */ +} t_timeout; +typedef t_timeout *p_timeout; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +void timeout_init(p_timeout tm, double block, double total); +double timeout_get(p_timeout tm); +double timeout_getstart(p_timeout tm); +double timeout_getretry(p_timeout tm); +p_timeout timeout_markstart(p_timeout tm); + +double timeout_gettime(void); + +int timeout_open(lua_State *L); + +int timeout_meth_settimeout(lua_State *L, p_timeout tm); +int timeout_meth_gettimeout(lua_State *L, p_timeout tm); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#define timeout_iszero(tm) ((tm)->block == 0.0) + +#endif /* TIMEOUT_H */ diff --git a/libraries/luasocket/libluasocket/tp.lua b/libraries/luasocket/libluasocket/tp.lua new file mode 100644 index 000000000..03a5344d7 --- /dev/null +++ b/libraries/luasocket/libluasocket/tp.lua @@ -0,0 +1,137 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- Unified SMTP/FTP subsystem +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local socket = require("socket") +local ltn12 = require("ltn12") + +socket.tp = {} +local _M = socket.tp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +_M.TIMEOUT = 60 + +----------------------------------------------------------------------------- +-- Implementation +----------------------------------------------------------------------------- +-- gets server reply (works for SMTP and FTP) +local function get_reply(c) + local code, current, sep + local line, err = c:receive() + local reply = line + if err then return nil, err end + code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + if not code then return nil, "invalid server reply" end + if sep == "-" then -- reply is multiline + repeat + line, err = c:receive() + if err then return nil, err end + current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + reply = reply .. "\n" .. line + -- reply ends with same code + until code == current and sep == " " + end + return code, reply +end + +-- metatable for sock object +local metat = { __index = {} } + +function metat.__index:getpeername() + return self.c:getpeername() +end + +function metat.__index:getsockname() + return self.c:getpeername() +end + +function metat.__index:check(ok) + local code, reply = get_reply(self.c) + if not code then return nil, reply end + if base.type(ok) ~= "function" then + if base.type(ok) == "table" then + for i, v in base.ipairs(ok) do + if string.find(code, v) then + return base.tonumber(code), reply + end + end + return nil, reply + else + if string.find(code, ok) then return base.tonumber(code), reply + else return nil, reply end + end + else return ok(base.tonumber(code), reply) end +end + +function metat.__index:command(cmd, arg) + cmd = string.upper(cmd) + if arg then + return self.c:send(cmd .. " " .. arg.. "\r\n") + else + return self.c:send(cmd .. "\r\n") + end +end + +function metat.__index:sink(snk, pat) + local chunk, err = self.c:receive(pat) + return snk(chunk, err) +end + +function metat.__index:send(data) + return self.c:send(data) +end + +function metat.__index:receive(pat) + return self.c:receive(pat) +end + +function metat.__index:getfd() + return self.c:getfd() +end + +function metat.__index:dirty() + return self.c:dirty() +end + +function metat.__index:getcontrol() + return self.c +end + +function metat.__index:source(source, step) + local sink = socket.sink("keep-open", self.c) + local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) + return ret, err +end + +-- closes the underlying c +function metat.__index:close() + self.c:close() + return 1 +end + +-- connect with server and return c object +function _M.connect(host, port, timeout, create) + local c, e = (create or socket.tcp)() + if not c then return nil, e end + c:settimeout(timeout or _M.TIMEOUT) + local r, e = c:connect(host, port) + if not r then + c:close() + return nil, e + end + return base.setmetatable({c = c}, metat) +end + +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/udp.c b/libraries/luasocket/libluasocket/udp.c new file mode 100644 index 000000000..712ad50fe --- /dev/null +++ b/libraries/luasocket/libluasocket/udp.c @@ -0,0 +1,488 @@ +/*=========================================================================*\ +* UDP object +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "inet.h" +#include "options.h" +#include "udp.h" + +#include +#include + +/* min and max macros */ +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) +#endif + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int global_create4(lua_State *L); +static int global_create6(lua_State *L); +static int meth_send(lua_State *L); +static int meth_sendto(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_receivefrom(lua_State *L); +static int meth_getfamily(lua_State *L); +static int meth_getsockname(lua_State *L); +static int meth_getpeername(lua_State *L); +static int meth_gettimeout(lua_State *L); +static int meth_setsockname(lua_State *L); +static int meth_setpeername(lua_State *L); +static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_getoption(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); + +/* udp object methods */ +static luaL_Reg udp_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"close", meth_close}, + {"dirty", meth_dirty}, + {"getfamily", meth_getfamily}, + {"getfd", meth_getfd}, + {"getpeername", meth_getpeername}, + {"getsockname", meth_getsockname}, + {"receive", meth_receive}, + {"receivefrom", meth_receivefrom}, + {"send", meth_send}, + {"sendto", meth_sendto}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"getoption", meth_getoption}, + {"setpeername", meth_setpeername}, + {"setsockname", meth_setsockname}, + {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, + {NULL, NULL} +}; + +/* socket options for setoption */ +static t_opt optset[] = { + {"dontroute", opt_set_dontroute}, + {"broadcast", opt_set_broadcast}, + {"reuseaddr", opt_set_reuseaddr}, + {"reuseport", opt_set_reuseport}, + {"ip-multicast-if", opt_set_ip_multicast_if}, + {"ip-multicast-ttl", opt_set_ip_multicast_ttl}, + {"ip-multicast-loop", opt_set_ip_multicast_loop}, + {"ip-add-membership", opt_set_ip_add_membership}, + {"ip-drop-membership", opt_set_ip_drop_membersip}, + {"ipv6-unicast-hops", opt_set_ip6_unicast_hops}, + {"ipv6-multicast-hops", opt_set_ip6_unicast_hops}, + {"ipv6-multicast-loop", opt_set_ip6_multicast_loop}, + {"ipv6-add-membership", opt_set_ip6_add_membership}, + {"ipv6-drop-membership", opt_set_ip6_drop_membersip}, + {"ipv6-v6only", opt_set_ip6_v6only}, + {"recv-buffer-size", opt_set_recv_buf_size}, + {"send-buffer-size", opt_set_send_buf_size}, + {NULL, NULL} +}; + +/* socket options for getoption */ +static t_opt optget[] = { + {"dontroute", opt_get_dontroute}, + {"broadcast", opt_get_broadcast}, + {"reuseaddr", opt_get_reuseaddr}, + {"reuseport", opt_get_reuseport}, + {"ip-multicast-if", opt_get_ip_multicast_if}, + {"ip-multicast-loop", opt_get_ip_multicast_loop}, + {"error", opt_get_error}, + {"ipv6-unicast-hops", opt_get_ip6_unicast_hops}, + {"ipv6-multicast-hops", opt_get_ip6_unicast_hops}, + {"ipv6-multicast-loop", opt_get_ip6_multicast_loop}, + {"ipv6-v6only", opt_get_ip6_v6only}, + {"recv-buffer-size", opt_get_recv_buf_size}, + {"send-buffer-size", opt_get_send_buf_size}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"udp", global_create}, + {"udp4", global_create4}, + {"udp6", global_create6}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int udp_open(lua_State *L) { + /* create classes */ + auxiliar_newclass(L, "udp{connected}", udp_methods); + auxiliar_newclass(L, "udp{unconnected}", udp_methods); + /* create class groups */ + auxiliar_add2group(L, "udp{connected}", "udp{any}"); + auxiliar_add2group(L, "udp{unconnected}", "udp{any}"); + auxiliar_add2group(L, "udp{connected}", "select{able}"); + auxiliar_add2group(L, "udp{unconnected}", "select{able}"); + /* define library functions */ + luaL_setfuncs(L, func, 0); + /* export default UDP size */ + lua_pushliteral(L, "_DATAGRAMSIZE"); + lua_pushinteger(L, UDP_DATAGRAMSIZE); + lua_rawset(L, -3); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +static const char *udp_strerror(int err) { + /* a 'closed' error on an unconnected means the target address was not + * accepted by the transport layer */ + if (err == IO_CLOSED) return "refused"; + else return socket_strerror(err); +} + +/*-------------------------------------------------------------------------*\ +* Send data through connected udp socket +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{connected}", 1); + p_timeout tm = &udp->tm; + size_t count, sent = 0; + int err; + const char *data = luaL_checklstring(L, 2, &count); + timeout_markstart(tm); + err = socket_send(&udp->sock, data, count, &sent, tm); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Send data through unconnected udp socket +\*-------------------------------------------------------------------------*/ +static int meth_sendto(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); + size_t count, sent = 0; + const char *data = luaL_checklstring(L, 2, &count); + const char *ip = luaL_checkstring(L, 3); + const char *port = luaL_checkstring(L, 4); + p_timeout tm = &udp->tm; + int err; + struct addrinfo aihint; + struct addrinfo *ai; + memset(&aihint, 0, sizeof(aihint)); + aihint.ai_family = udp->family; + aihint.ai_socktype = SOCK_DGRAM; + aihint.ai_flags = AI_NUMERICHOST; +#ifdef AI_NUMERICSERV + aihint.ai_flags |= AI_NUMERICSERV; +#endif + err = getaddrinfo(ip, port, &aihint, &ai); + if (err) { + lua_pushnil(L); + lua_pushstring(L, LUA_GAI_STRERROR(err)); + return 2; + } + + /* create socket if on first sendto if AF_UNSPEC was set */ + if (udp->family == AF_UNSPEC && udp->sock == SOCKET_INVALID) { + struct addrinfo *ap; + const char *errstr = NULL; + for (ap = ai; ap != NULL; ap = ap->ai_next) { + errstr = inet_trycreate(&udp->sock, ap->ai_family, SOCK_DGRAM, 0); + if (errstr == NULL) { + socket_setnonblocking(&udp->sock); + udp->family = ap->ai_family; + break; + } + } + if (errstr != NULL) { + lua_pushnil(L); + lua_pushstring(L, errstr); + freeaddrinfo(ai); + return 2; + } + } + + timeout_markstart(tm); + err = socket_sendto(&udp->sock, data, count, &sent, ai->ai_addr, + (socklen_t) ai->ai_addrlen, tm); + freeaddrinfo(ai); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Receives data from a UDP socket +\*-------------------------------------------------------------------------*/ +static int meth_receive(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + char buf[UDP_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + int err; + p_timeout tm = &udp->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + err = socket_recv(&udp->sock, dgram, wanted, &got, tm); + /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + lua_pushlstring(L, dgram, got); + if (wanted > sizeof(buf)) free(dgram); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Receives data and sender from a UDP socket +\*-------------------------------------------------------------------------*/ +static int meth_receivefrom(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); + char buf[UDP_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + int err; + p_timeout tm = &udp->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + err = socket_recvfrom(&udp->sock, dgram, wanted, &got, (SA *) &addr, + &addr_len, tm); + /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + err = getnameinfo((struct sockaddr *)&addr, addr_len, addrstr, + INET6_ADDRSTRLEN, portstr, 6, NI_NUMERICHOST | NI_NUMERICSERV); + if (err) { + lua_pushnil(L); + lua_pushstring(L, LUA_GAI_STRERROR(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + lua_pushlstring(L, dgram, got); + lua_pushstring(L, addrstr); + lua_pushinteger(L, (int) strtol(portstr, (char **) NULL, 10)); + if (wanted > sizeof(buf)) free(dgram); + return 3; +} + +/*-------------------------------------------------------------------------*\ +* Returns family as string +\*-------------------------------------------------------------------------*/ +static int meth_getfamily(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + if (udp->family == AF_INET6) { + lua_pushliteral(L, "inet6"); + return 1; + } else { + lua_pushliteral(L, "inet4"); + return 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + lua_pushnumber(L, (int) udp->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + udp->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + (void) udp; + lua_pushboolean(L, 0); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call inet methods +\*-------------------------------------------------------------------------*/ +static int meth_getpeername(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{connected}", 1); + return inet_meth_getpeername(L, &udp->sock, udp->family); +} + +static int meth_getsockname(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return inet_meth_getsockname(L, &udp->sock, udp->family); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return opt_meth_setoption(L, optset, &udp->sock); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_getoption(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return opt_meth_getoption(L, optget, &udp->sock); +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return timeout_meth_settimeout(L, &udp->tm); +} + +static int meth_gettimeout(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return timeout_meth_gettimeout(L, &udp->tm); +} + +/*-------------------------------------------------------------------------*\ +* Turns a master udp object into a client object. +\*-------------------------------------------------------------------------*/ +static int meth_setpeername(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + p_timeout tm = &udp->tm; + const char *address = luaL_checkstring(L, 2); + int connecting = strcmp(address, "*"); + const char *port = connecting? luaL_checkstring(L, 3): "0"; + struct addrinfo connecthints; + const char *err; + memset(&connecthints, 0, sizeof(connecthints)); + connecthints.ai_socktype = SOCK_DGRAM; + /* make sure we try to connect only to the same family */ + connecthints.ai_family = udp->family; + if (connecting) { + err = inet_tryconnect(&udp->sock, &udp->family, address, + port, tm, &connecthints); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + auxiliar_setclass(L, "udp{connected}", 1); + } else { + /* we ignore possible errors because Mac OS X always + * returns EAFNOSUPPORT */ + inet_trydisconnect(&udp->sock, udp->family, tm); + auxiliar_setclass(L, "udp{unconnected}", 1); + } + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + socket_destroy(&udp->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master object into a server object +\*-------------------------------------------------------------------------*/ +static int meth_setsockname(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); + const char *address = luaL_checkstring(L, 2); + const char *port = luaL_checkstring(L, 3); + const char *err; + struct addrinfo bindhints; + memset(&bindhints, 0, sizeof(bindhints)); + bindhints.ai_socktype = SOCK_DGRAM; + bindhints.ai_family = udp->family; + bindhints.ai_flags = AI_PASSIVE; + err = inet_trybind(&udp->sock, &udp->family, address, port, &bindhints); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master udp object +\*-------------------------------------------------------------------------*/ +static int udp_create(lua_State *L, int family) { + /* allocate udp object */ + p_udp udp = (p_udp) lua_newuserdata(L, sizeof(t_udp)); + auxiliar_setclass(L, "udp{unconnected}", -1); + /* if family is AF_UNSPEC, we leave the socket invalid and + * store AF_UNSPEC into family. This will allow it to later be + * replaced with an AF_INET6 or AF_INET socket upon first use. */ + udp->sock = SOCKET_INVALID; + timeout_init(&udp->tm, -1, -1); + udp->family = family; + if (family != AF_UNSPEC) { + const char *err = inet_trycreate(&udp->sock, family, SOCK_DGRAM, 0); + if (err != NULL) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + socket_setnonblocking(&udp->sock); + } + return 1; +} + +static int global_create(lua_State *L) { + return udp_create(L, AF_UNSPEC); +} + +static int global_create4(lua_State *L) { + return udp_create(L, AF_INET); +} + +static int global_create6(lua_State *L) { + return udp_create(L, AF_INET6); +} diff --git a/libraries/luasocket/libluasocket/udp.h b/libraries/luasocket/libluasocket/udp.h new file mode 100644 index 000000000..07d5247fc --- /dev/null +++ b/libraries/luasocket/libluasocket/udp.h @@ -0,0 +1,39 @@ +#ifndef UDP_H +#define UDP_H +/*=========================================================================*\ +* UDP object +* LuaSocket toolkit +* +* The udp.h module provides LuaSocket with support for UDP protocol +* (AF_INET, SOCK_DGRAM). +* +* Two classes are defined: connected and unconnected. UDP objects are +* originally unconnected. They can be "connected" to a given address +* with a call to the setpeername function. The same function can be used to +* break the connection. +\*=========================================================================*/ +#include "luasocket.h" + +#include "timeout.h" +#include "socket.h" + +#define UDP_DATAGRAMSIZE 8192 + +typedef struct t_udp_ { + t_socket sock; + t_timeout tm; + int family; +} t_udp; +typedef t_udp *p_udp; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int udp_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* UDP_H */ diff --git a/libraries/luasocket/libluasocket/unix.c b/libraries/luasocket/libluasocket/unix.c new file mode 100644 index 000000000..268d8b212 --- /dev/null +++ b/libraries/luasocket/libluasocket/unix.c @@ -0,0 +1,69 @@ +/*=========================================================================*\ +* Unix domain socket +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "unixstream.h" +#include "unixdgram.h" + +/*-------------------------------------------------------------------------*\ +* Modules and functions +\*-------------------------------------------------------------------------*/ +static const luaL_Reg mod[] = { + {"stream", unixstream_open}, + {"dgram", unixdgram_open}, + {NULL, NULL} +}; + +static void add_alias(lua_State *L, int index, const char *name, const char *target) +{ + lua_getfield(L, index, target); + lua_setfield(L, index, name); +} + +static int compat_socket_unix_call(lua_State *L) +{ + /* Look up socket.unix.stream in the socket.unix table (which is the first + * argument). */ + lua_getfield(L, 1, "stream"); + + /* Replace the stack entry for the socket.unix table with the + * socket.unix.stream function. */ + lua_replace(L, 1); + + /* Call socket.unix.stream, passing along any arguments. */ + int n = lua_gettop(L); + lua_call(L, n-1, LUA_MULTRET); + + /* Pass along the return values from socket.unix.stream. */ + n = lua_gettop(L); + return n; +} + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_unix(lua_State *L) +{ + int i; + lua_newtable(L); + int socket_unix_table = lua_gettop(L); + + for (i = 0; mod[i].name; i++) + mod[i].func(L); + + /* Add backwards compatibility aliases "tcp" and "udp" for the "stream" and + * "dgram" functions. */ + add_alias(L, socket_unix_table, "tcp", "stream"); + add_alias(L, socket_unix_table, "udp", "dgram"); + + /* Add a backwards compatibility function and a metatable setup to call it + * for the old socket.unix() interface. */ + lua_pushcfunction(L, compat_socket_unix_call); + lua_setfield(L, socket_unix_table, "__call"); + lua_pushvalue(L, socket_unix_table); + lua_setmetatable(L, socket_unix_table); + + return 1; +} diff --git a/libraries/luasocket/libluasocket/unix.h b/libraries/luasocket/libluasocket/unix.h new file mode 100644 index 000000000..c20356189 --- /dev/null +++ b/libraries/luasocket/libluasocket/unix.h @@ -0,0 +1,26 @@ +#ifndef UNIX_H +#define UNIX_H +/*=========================================================================*\ +* Unix domain object +* LuaSocket toolkit +* +* This module is just an example of how to extend LuaSocket with a new +* domain. +\*=========================================================================*/ +#include "luasocket.h" + +#include "buffer.h" +#include "timeout.h" +#include "socket.h" + +typedef struct t_unix_ { + t_socket sock; + t_io io; + t_buffer buf; + t_timeout tm; +} t_unix; +typedef t_unix *p_unix; + +LUASOCKET_API int luaopen_socket_unix(lua_State *L); + +#endif /* UNIX_H */ diff --git a/libraries/luasocket/libluasocket/unixdgram.c b/libraries/luasocket/libluasocket/unixdgram.c new file mode 100644 index 000000000..69093d734 --- /dev/null +++ b/libraries/luasocket/libluasocket/unixdgram.c @@ -0,0 +1,405 @@ +/*=========================================================================*\ +* Unix domain socket dgram submodule +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unix.h" + +#include +#include + +#include + +#define UNIXDGRAM_DATAGRAMSIZE 8192 + +/* provide a SUN_LEN macro if sys/un.h doesn't (e.g. Android) */ +#ifndef SUN_LEN +#define SUN_LEN(ptr) \ + ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) +#endif + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_gettimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_receivefrom(lua_State *L); +static int meth_sendto(lua_State *L); +static int meth_getsockname(lua_State *L); + +static const char *unixdgram_tryconnect(p_unix un, const char *path); +static const char *unixdgram_trybind(p_unix un, const char *path); + +/* unixdgram object methods */ +static luaL_Reg unixdgram_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"bind", meth_bind}, + {"close", meth_close}, + {"connect", meth_connect}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"send", meth_send}, + {"sendto", meth_sendto}, + {"receive", meth_receive}, + {"receivefrom", meth_receivefrom}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"getsockname", meth_getsockname}, + {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, + {NULL, NULL} +}; + +/* socket option handlers */ +static t_opt optset[] = { + {"reuseaddr", opt_set_reuseaddr}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"dgram", global_create}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int unixdgram_open(lua_State *L) +{ + /* create classes */ + auxiliar_newclass(L, "unixdgram{connected}", unixdgram_methods); + auxiliar_newclass(L, "unixdgram{unconnected}", unixdgram_methods); + /* create class groups */ + auxiliar_add2group(L, "unixdgram{connected}", "unixdgram{any}"); + auxiliar_add2group(L, "unixdgram{unconnected}", "unixdgram{any}"); + auxiliar_add2group(L, "unixdgram{connected}", "select{able}"); + auxiliar_add2group(L, "unixdgram{unconnected}", "select{able}"); + + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +static const char *unixdgram_strerror(int err) +{ + /* a 'closed' error on an unconnected means the target address was not + * accepted by the transport layer */ + if (err == IO_CLOSED) return "refused"; + else return socket_strerror(err); +} + +static int meth_send(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{connected}", 1); + p_timeout tm = &un->tm; + size_t count, sent = 0; + int err; + const char *data = luaL_checklstring(L, 2, &count); + timeout_markstart(tm); + err = socket_send(&un->sock, data, count, &sent, tm); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Send data through unconnected unixdgram socket +\*-------------------------------------------------------------------------*/ +static int meth_sendto(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + size_t count, sent = 0; + const char *data = luaL_checklstring(L, 2, &count); + const char *path = luaL_checkstring(L, 3); + p_timeout tm = &un->tm; + int err; + struct sockaddr_un remote; + size_t len = strlen(path); + + if (len >= sizeof(remote.sun_path)) { + lua_pushnil(L); + lua_pushstring(L, "path too long"); + return 2; + } + + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(tm); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) + + len + 1; + err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, remote.sun_len, tm); +#else + err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, + sizeof(remote.sun_family) + len, tm); +#endif + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + char buf[UNIXDGRAM_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + int err; + p_timeout tm = &un->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + err = socket_recv(&un->sock, dgram, wanted, &got, tm); + /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + lua_pushlstring(L, dgram, got); + if (wanted > sizeof(buf)) free(dgram); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Receives data and sender from a DGRAM socket +\*-------------------------------------------------------------------------*/ +static int meth_receivefrom(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + char buf[UNIXDGRAM_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + int err; + p_timeout tm = &un->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + addr.sun_path[0] = '\0'; + err = socket_recvfrom(&un->sock, dgram, wanted, &got, (SA *) &addr, + &addr_len, tm); + /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + + lua_pushlstring(L, dgram, got); + /* the path may be empty, when client send without bind */ + lua_pushstring(L, addr.sun_path); + if (wanted > sizeof(buf)) free(dgram); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return opt_meth_setoption(L, optset, &un->sock); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + (void) un; + lua_pushboolean(L, 0); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Binds an object to an address +\*-------------------------------------------------------------------------*/ +static const char *unixdgram_trybind(p_unix un, const char *path) { + struct sockaddr_un local; + size_t len = strlen(path); + if (len >= sizeof(local.sun_path)) return "path too long"; + memset(&local, 0, sizeof(local)); + strcpy(local.sun_path, path); + local.sun_family = AF_UNIX; + size_t addrlen = SUN_LEN(&local); +#ifdef UNIX_HAS_SUN_LEN + local.sun_len = addrlen + 1; +#endif + int err = socket_bind(&un->sock, (SA *) &local, addrlen); + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_bind(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixdgram_trybind(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int meth_getsockname(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + struct sockaddr_un peer = {0}; + socklen_t peer_len = sizeof(peer); + + if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + + lua_pushstring(L, peer.sun_path); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master unixdgram object into a client object. +\*-------------------------------------------------------------------------*/ +static const char *unixdgram_tryconnect(p_unix un, const char *path) +{ + struct sockaddr_un remote; + size_t len = strlen(path); + if (len >= sizeof(remote.sun_path)) return "path too long"; + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(&un->tm); + size_t addrlen = SUN_LEN(&remote); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = addrlen + 1; +#endif + int err = socket_connect(&un->sock, (SA *) &remote, addrlen, &un->tm); + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_connect(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixdgram_tryconnect(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn unconnected object into a connected object */ + auxiliar_setclass(L, "unixdgram{connected}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +static int meth_gettimeout(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return timeout_meth_gettimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master unixdgram object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) +{ + t_socket sock; + int err = socket_create(&sock, AF_UNIX, SOCK_DGRAM, 0); + /* try to allocate a system socket */ + if (err == IO_DONE) { + /* allocate unixdgram object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + /* set its type as master object */ + auxiliar_setclass(L, "unixdgram{unconnected}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} diff --git a/libraries/luasocket/libluasocket/unixdgram.h b/libraries/luasocket/libluasocket/unixdgram.h new file mode 100644 index 000000000..a1a0166bd --- /dev/null +++ b/libraries/luasocket/libluasocket/unixdgram.h @@ -0,0 +1,28 @@ +#ifndef UNIXDGRAM_H +#define UNIXDGRAM_H +/*=========================================================================*\ +* DGRAM object +* LuaSocket toolkit +* +* The dgram.h module provides LuaSocket with support for DGRAM protocol +* (AF_INET, SOCK_DGRAM). +* +* Two classes are defined: connected and unconnected. DGRAM objects are +* originally unconnected. They can be "connected" to a given address +* with a call to the setpeername function. The same function can be used to +* break the connection. +\*=========================================================================*/ + +#include "unix.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int unixdgram_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* UNIXDGRAM_H */ diff --git a/libraries/luasocket/libluasocket/unixstream.c b/libraries/luasocket/libluasocket/unixstream.c new file mode 100644 index 000000000..02aced9c8 --- /dev/null +++ b/libraries/luasocket/libluasocket/unixstream.c @@ -0,0 +1,355 @@ +/*=========================================================================*\ +* Unix domain socket stream sub module +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unixstream.h" + +#include +#include + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_listen(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_shutdown(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_accept(lua_State *L); +static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_getstats(lua_State *L); +static int meth_setstats(lua_State *L); +static int meth_getsockname(lua_State *L); + +static const char *unixstream_tryconnect(p_unix un, const char *path); +static const char *unixstream_trybind(p_unix un, const char *path); + +/* unixstream object methods */ +static luaL_Reg unixstream_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"accept", meth_accept}, + {"bind", meth_bind}, + {"close", meth_close}, + {"connect", meth_connect}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"getstats", meth_getstats}, + {"setstats", meth_setstats}, + {"listen", meth_listen}, + {"receive", meth_receive}, + {"send", meth_send}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"getsockname", meth_getsockname}, + {"settimeout", meth_settimeout}, + {"shutdown", meth_shutdown}, + {NULL, NULL} +}; + +/* socket option handlers */ +static t_opt optset[] = { + {"keepalive", opt_set_keepalive}, + {"reuseaddr", opt_set_reuseaddr}, + {"linger", opt_set_linger}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"stream", global_create}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int unixstream_open(lua_State *L) +{ + /* create classes */ + auxiliar_newclass(L, "unixstream{master}", unixstream_methods); + auxiliar_newclass(L, "unixstream{client}", unixstream_methods); + auxiliar_newclass(L, "unixstream{server}", unixstream_methods); + + /* create class groups */ + auxiliar_add2group(L, "unixstream{master}", "unixstream{any}"); + auxiliar_add2group(L, "unixstream{client}", "unixstream{any}"); + auxiliar_add2group(L, "unixstream{server}", "unixstream{any}"); + + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_send(L, &un->buf); +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_receive(L, &un->buf); +} + +static int meth_getstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_getstats(L, &un->buf); +} + +static int meth_setstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_setstats(L, &un->buf); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + return opt_meth_setoption(L, optset, &un->sock); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + lua_pushboolean(L, !buffer_isempty(&un->buf)); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Waits for and returns a client object attempting connection to the +* server object +\*-------------------------------------------------------------------------*/ +static int meth_accept(lua_State *L) { + p_unix server = (p_unix) auxiliar_checkclass(L, "unixstream{server}", 1); + p_timeout tm = timeout_markstart(&server->tm); + t_socket sock; + int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); + /* if successful, push client socket */ + if (err == IO_DONE) { + p_unix clnt = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + auxiliar_setclass(L, "unixstream{client}", -1); + /* initialize structure fields */ + socket_setnonblocking(&sock); + clnt->sock = sock; + io_init(&clnt->io, (p_send)socket_send, (p_recv)socket_recv, + (p_error) socket_ioerror, &clnt->sock); + timeout_init(&clnt->tm, -1, -1); + buffer_init(&clnt->buf, &clnt->io, &clnt->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} + +/*-------------------------------------------------------------------------*\ +* Binds an object to an address +\*-------------------------------------------------------------------------*/ +static const char *unixstream_trybind(p_unix un, const char *path) { + struct sockaddr_un local; + size_t len = strlen(path); + int err; + if (len >= sizeof(local.sun_path)) return "path too long"; + memset(&local, 0, sizeof(local)); + strcpy(local.sun_path, path); + local.sun_family = AF_UNIX; +#ifdef UNIX_HAS_SUN_LEN + local.sun_len = sizeof(local.sun_family) + sizeof(local.sun_len) + + len + 1; + err = socket_bind(&un->sock, (SA *) &local, local.sun_len); + +#else + err = socket_bind(&un->sock, (SA *) &local, + sizeof(local.sun_family) + len); +#endif + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_bind(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixstream_trybind(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int meth_getsockname(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + struct sockaddr_un peer = {0}; + socklen_t peer_len = sizeof(peer); + + if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + + lua_pushstring(L, peer.sun_path); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master unixstream object into a client object. +\*-------------------------------------------------------------------------*/ +static const char *unixstream_tryconnect(p_unix un, const char *path) +{ + struct sockaddr_un remote; + int err; + size_t len = strlen(path); + if (len >= sizeof(remote.sun_path)) return "path too long"; + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(&un->tm); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) + + len + 1; + err = socket_connect(&un->sock, (SA *) &remote, remote.sun_len, &un->tm); +#else + err = socket_connect(&un->sock, (SA *) &remote, + sizeof(remote.sun_family) + len, &un->tm); +#endif + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_connect(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixstream_tryconnect(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn master object into a client object */ + auxiliar_setclass(L, "unixstream{client}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Puts the sockt in listen mode +\*-------------------------------------------------------------------------*/ +static int meth_listen(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + int backlog = (int) luaL_optnumber(L, 2, 32); + int err = socket_listen(&un->sock, backlog); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } + /* turn master object into a server object */ + auxiliar_setclass(L, "unixstream{server}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Shuts the connection down partially +\*-------------------------------------------------------------------------*/ +static int meth_shutdown(lua_State *L) +{ + /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ + static const char* methods[] = { "receive", "send", "both", NULL }; + p_unix stream = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + int how = luaL_checkoption(L, 2, "both", methods); + socket_shutdown(&stream->sock, how); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master unixstream object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) { + t_socket sock; + int err = socket_create(&sock, AF_UNIX, SOCK_STREAM, 0); + /* try to allocate a system socket */ + if (err == IO_DONE) { + /* allocate unixstream object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + /* set its type as master object */ + auxiliar_setclass(L, "unixstream{master}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} diff --git a/libraries/luasocket/libluasocket/unixstream.h b/libraries/luasocket/libluasocket/unixstream.h new file mode 100644 index 000000000..7916affa7 --- /dev/null +++ b/libraries/luasocket/libluasocket/unixstream.h @@ -0,0 +1,29 @@ +#ifndef UNIXSTREAM_H +#define UNIXSTREAM_H +/*=========================================================================*\ +* UNIX STREAM object +* LuaSocket toolkit +* +* The unixstream.h module is basicly a glue that puts together modules buffer.h, +* timeout.h socket.h and inet.h to provide the LuaSocket UNIX STREAM (AF_UNIX, +* SOCK_STREAM) support. +* +* Three classes are defined: master, client and server. The master class is +* a newly created unixstream object, that has not been bound or connected. Server +* objects are unixstream objects bound to some local address. Client objects are +* unixstream objects either connected to some address or returned by the accept +* method of a server object. +\*=========================================================================*/ +#include "unix.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int unixstream_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* UNIXSTREAM_H */ diff --git a/libraries/luasocket/libluasocket/url.lua b/libraries/luasocket/libluasocket/url.lua new file mode 100644 index 000000000..3bf1c17e4 --- /dev/null +++ b/libraries/luasocket/libluasocket/url.lua @@ -0,0 +1,333 @@ +R"luastring"--( +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local base = _G +local table = require("table") +local socket = require("socket") + +socket.url = {} +local _M = socket.url + +----------------------------------------------------------------------------- +-- Module version +----------------------------------------------------------------------------- +_M._VERSION = "URL 1.0.3" + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function _M.escape(s) + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end)) +end + +----------------------------------------------------------------------------- +-- Protects a path segment, to prevent it from interfering with the +-- url parsing. +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +local function make_set(t) + local s = {} + for i,v in base.ipairs(t) do + s[t[i]] = 1 + end + return s +end + +-- these are allowed within a path segment, along with alphanum +-- other characters must be escaped +local segment_set = make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +local function protect_segment(s) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02X", string.byte(c)) end + end) +end + +----------------------------------------------------------------------------- +-- Unencodes a escaped hexadecimal string into its binary representation +-- Input +-- s: escaped hexadecimal string to be unencoded +-- Returns +-- unescaped binary representation of escaped hexadecimal binary +----------------------------------------------------------------------------- +function _M.unescape(s) + return (string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end)) +end + +----------------------------------------------------------------------------- +-- Removes '..' and '.' components appropriately from a path. +-- Input +-- path +-- Returns +-- dot-normalized path +local function remove_dot_components(path) + local marker = string.char(1) + repeat + local was = path + path = path:gsub('//', '/'..marker..'/', 1) + until path == was + repeat + local was = path + path = path:gsub('/%./', '/', 1) + until path == was + repeat + local was = path + path = path:gsub('[^/]+/%.%./([^/]+)', '%1', 1) + until path == was + path = path:gsub('[^/]+/%.%./*$', '') + path = path:gsub('/%.%.$', '/') + path = path:gsub('/%.$', '/') + path = path:gsub('^/%.%./', '/') + path = path:gsub(marker, '') + return path +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +local function absolute_path(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then + return remove_dot_components(relative_path) end + base_path = base_path:gsub("[^/]*$", "") + if not base_path:find'/$' then base_path = base_path .. '/' end + local path = base_path .. relative_path + path = remove_dot_components(path) + return path +end + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- ::= :///;?# +-- ::= @: +-- ::= [:] +-- :: = {/} +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/} is considered part of +----------------------------------------------------------------------------- +function _M.parse(url, default) + -- initialize default parameters + local parsed = {} + for i,v in base.pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = s; return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get query string + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([^:%]]*)$", + function(p) parsed.port = p; return "" end) + if authority ~= "" then + -- IPv6? + parsed.host = string.match(authority, "^%[(.+)%]$") or authority + end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by parse +-- Returns +-- a stringing with the corresponding URL +----------------------------------------------------------------------------- +function _M.build(parsed) + --local ppath = _M.parse_path(parsed.path or "") + --local url = _M.build_path(ppath) + local url = parsed.path or "" + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if string.find(authority, ":") then -- IPv6? + authority = "[" .. authority .. "]" + end + if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function _M.absolute(base_url, relative_url) + local base_parsed + if base.type(base_url) == "table" then + base_parsed = base_url + base_url = _M.build(base_parsed) + else + base_parsed = _M.parse(base_url) + end + local result + local relative_parsed = _M.parse(relative_url) + if not base_parsed then + result = relative_url + elseif not relative_parsed then + result = base_url + elseif relative_parsed.scheme then + result = relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end + end + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end + end + result = _M.build(relative_parsed) + end + return remove_dot_components(result) +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function _M.parse_path(path) + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i = 1, #parsed do + parsed[i] = _M.unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- unsafe: if true, segments are not protected before path is built +-- Returns +-- path: corresponding path stringing +----------------------------------------------------------------------------- +function _M.build_path(parsed, unsafe) + local path = "" + local n = #parsed + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path +end +return _M +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--" diff --git a/libraries/luasocket/libluasocket/usocket.c b/libraries/luasocket/libluasocket/usocket.c new file mode 100644 index 000000000..69635daa6 --- /dev/null +++ b/libraries/luasocket/libluasocket/usocket.c @@ -0,0 +1,454 @@ +/*=========================================================================*\ +* Socket compatibilization module for Unix +* LuaSocket toolkit +* +* The code is now interrupt-safe. +* The penalty of calling select to avoid busy-wait is only paid when +* the I/O call fail in the first place. +\*=========================================================================*/ +#include "luasocket.h" + +#include "socket.h" +#include "pierror.h" + +#include +#include + +/*-------------------------------------------------------------------------*\ +* Wait for readable/writable/connected socket with timeout +\*-------------------------------------------------------------------------*/ +#ifndef SOCKET_SELECT +#include + +#define WAITFD_R POLLIN +#define WAITFD_W POLLOUT +#define WAITFD_C (POLLIN|POLLOUT) +int socket_waitfd(p_socket ps, int sw, p_timeout tm) { + int ret; + struct pollfd pfd; + pfd.fd = *ps; + pfd.events = sw; + pfd.revents = 0; + if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ + do { + int t = (int)(timeout_getretry(tm)*1e3); + ret = poll(&pfd, 1, t >= 0? t: -1); + } while (ret == -1 && errno == EINTR); + if (ret == -1) return errno; + if (ret == 0) return IO_TIMEOUT; + if (sw == WAITFD_C && (pfd.revents & (POLLIN|POLLERR))) return IO_CLOSED; + return IO_DONE; +} +#else + +#define WAITFD_R 1 +#define WAITFD_W 2 +#define WAITFD_C (WAITFD_R|WAITFD_W) + +int socket_waitfd(p_socket ps, int sw, p_timeout tm) { + int ret; + fd_set rfds, wfds, *rp, *wp; + struct timeval tv, *tp; + double t; + if (*ps >= FD_SETSIZE) return EINVAL; + if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ + do { + /* must set bits within loop, because select may have modifed them */ + rp = wp = NULL; + if (sw & WAITFD_R) { FD_ZERO(&rfds); FD_SET(*ps, &rfds); rp = &rfds; } + if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } + t = timeout_getretry(tm); + tp = NULL; + if (t >= 0.0) { + tv.tv_sec = (int)t; + tv.tv_usec = (int)((t-tv.tv_sec)*1.0e6); + tp = &tv; + } + ret = select(*ps+1, rp, wp, NULL, tp); + } while (ret == -1 && errno == EINTR); + if (ret == -1) return errno; + if (ret == 0) return IO_TIMEOUT; + if (sw == WAITFD_C && FD_ISSET(*ps, &rfds)) return IO_CLOSED; + return IO_DONE; +} +#endif + + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int socket_open(void) { + /* installs a handler to ignore sigpipe or it will crash us */ + signal(SIGPIPE, SIG_IGN); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Close module +\*-------------------------------------------------------------------------*/ +int socket_close(void) { + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Close and inutilize socket +\*-------------------------------------------------------------------------*/ +void socket_destroy(p_socket ps) { + if (*ps != SOCKET_INVALID) { + close(*ps); + *ps = SOCKET_INVALID; + } +} + +/*-------------------------------------------------------------------------*\ +* Select with timeout control +\*-------------------------------------------------------------------------*/ +int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, + p_timeout tm) { + int ret; + do { + struct timeval tv; + double t = timeout_getretry(tm); + tv.tv_sec = (int) t; + tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); + /* timeout = 0 means no wait */ + ret = select(n, rfds, wfds, efds, t >= 0.0 ? &tv: NULL); + } while (ret < 0 && errno == EINTR); + return ret; +} + +/*-------------------------------------------------------------------------*\ +* Creates and sets up a socket +\*-------------------------------------------------------------------------*/ +int socket_create(p_socket ps, int domain, int type, int protocol) { + *ps = socket(domain, type, protocol); + if (*ps != SOCKET_INVALID) return IO_DONE; + else return errno; +} + +/*-------------------------------------------------------------------------*\ +* Binds or returns error message +\*-------------------------------------------------------------------------*/ +int socket_bind(p_socket ps, SA *addr, socklen_t len) { + int err = IO_DONE; + socket_setblocking(ps); + if (bind(*ps, addr, len) < 0) err = errno; + socket_setnonblocking(ps); + return err; +} + +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ +int socket_listen(p_socket ps, int backlog) { + int err = IO_DONE; + if (listen(*ps, backlog)) err = errno; + return err; +} + +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ +void socket_shutdown(p_socket ps, int how) { + shutdown(*ps, how); +} + +/*-------------------------------------------------------------------------*\ +* Connects or returns error message +\*-------------------------------------------------------------------------*/ +int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { + int err; + /* avoid calling on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* call connect until done or failed without being interrupted */ + do if (connect(*ps, addr, len) == 0) return IO_DONE; + while ((err = errno) == EINTR); + /* if connection failed immediately, return error code */ + if (err != EINPROGRESS && err != EAGAIN) return err; + /* zero timeout case optimization */ + if (timeout_iszero(tm)) return IO_TIMEOUT; + /* wait until we have the result of the connection attempt or timeout */ + err = socket_waitfd(ps, WAITFD_C, tm); + if (err == IO_CLOSED) { + if (recv(*ps, (char *) &err, 0, 0) == 0) return IO_DONE; + else return errno; + } else return err; +} + +/*-------------------------------------------------------------------------*\ +* Accept with timeout +\*-------------------------------------------------------------------------*/ +int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, p_timeout tm) { + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int err; + if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; + err = errno; + if (err == EINTR) continue; + if (err != EAGAIN && err != ECONNABORTED) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + /* can't reach here */ + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Send with timeout +\*-------------------------------------------------------------------------*/ +int socket_send(p_socket ps, const char *data, size_t count, + size_t *sent, p_timeout tm) +{ + int err; + *sent = 0; + /* avoid making system calls on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* loop until we send something or we give up on error */ + for ( ;; ) { + long put = (long) send(*ps, data, count, 0); + /* if we sent anything, we are done */ + if (put >= 0) { + *sent = put; + return IO_DONE; + } + err = errno; + /* EPIPE means the connection was closed */ + if (err == EPIPE) return IO_CLOSED; + /* EPROTOTYPE means the connection is being closed (on Yosemite!)*/ + if (err == EPROTOTYPE) continue; + /* we call was interrupted, just try again */ + if (err == EINTR) continue; + /* if failed fatal reason, report error */ + if (err != EAGAIN) return err; + /* wait until we can send something or we timeout */ + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } + /* can't reach here */ + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Sendto with timeout +\*-------------------------------------------------------------------------*/ +int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, + SA *addr, socklen_t len, p_timeout tm) +{ + int err; + *sent = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long put = (long) sendto(*ps, data, count, 0, addr, len); + if (put >= 0) { + *sent = put; + return IO_DONE; + } + err = errno; + if (err == EPIPE) return IO_CLOSED; + if (err == EPROTOTYPE) continue; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Receive with timeout +\*-------------------------------------------------------------------------*/ +int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) { + int err; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long taken = (long) recv(*ps, data, count, 0); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + err = errno; + if (taken == 0) return IO_CLOSED; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Recvfrom with timeout +\*-------------------------------------------------------------------------*/ +int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, + SA *addr, socklen_t *len, p_timeout tm) { + int err; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long taken = (long) recvfrom(*ps, data, count, 0, addr, len); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + err = errno; + if (taken == 0) return IO_CLOSED; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + + +/*-------------------------------------------------------------------------*\ +* Write with timeout +* +* socket_read and socket_write are cut-n-paste of socket_send and socket_recv, +* with send/recv replaced with write/read. We can't just use write/read +* in the socket version, because behaviour when size is zero is different. +\*-------------------------------------------------------------------------*/ +int socket_write(p_socket ps, const char *data, size_t count, + size_t *sent, p_timeout tm) +{ + int err; + *sent = 0; + /* avoid making system calls on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* loop until we send something or we give up on error */ + for ( ;; ) { + long put = (long) write(*ps, data, count); + /* if we sent anything, we are done */ + if (put >= 0) { + *sent = put; + return IO_DONE; + } + err = errno; + /* EPIPE means the connection was closed */ + if (err == EPIPE) return IO_CLOSED; + /* EPROTOTYPE means the connection is being closed (on Yosemite!)*/ + if (err == EPROTOTYPE) continue; + /* we call was interrupted, just try again */ + if (err == EINTR) continue; + /* if failed fatal reason, report error */ + if (err != EAGAIN) return err; + /* wait until we can send something or we timeout */ + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } + /* can't reach here */ + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Read with timeout +* See note for socket_write +\*-------------------------------------------------------------------------*/ +int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) { + int err; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long taken = (long) read(*ps, data, count); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + err = errno; + if (taken == 0) return IO_CLOSED; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode +\*-------------------------------------------------------------------------*/ +void socket_setblocking(p_socket ps) { + int flags = fcntl(*ps, F_GETFL, 0); + flags &= (~(O_NONBLOCK)); + fcntl(*ps, F_SETFL, flags); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode +\*-------------------------------------------------------------------------*/ +void socket_setnonblocking(p_socket ps) { + int flags = fcntl(*ps, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(*ps, F_SETFL, flags); +} + +/*-------------------------------------------------------------------------*\ +* DNS helpers +\*-------------------------------------------------------------------------*/ +int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { + *hp = gethostbyaddr(addr, len, AF_INET); + if (*hp) return IO_DONE; + else if (h_errno) return h_errno; + else if (errno) return errno; + else return IO_UNKNOWN; +} + +int socket_gethostbyname(const char *addr, struct hostent **hp) { + *hp = gethostbyname(addr); + if (*hp) return IO_DONE; + else if (h_errno) return h_errno; + else if (errno) return errno; + else return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Error translation functions +* Make sure important error messages are standard +\*-------------------------------------------------------------------------*/ +const char *socket_hoststrerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case HOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; + default: return hstrerror(err); + } +} + +const char *socket_strerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case EADDRINUSE: return PIE_ADDRINUSE; + case EISCONN: return PIE_ISCONN; + case EACCES: return PIE_ACCESS; + case ECONNREFUSED: return PIE_CONNREFUSED; + case ECONNABORTED: return PIE_CONNABORTED; + case ECONNRESET: return PIE_CONNRESET; + case ETIMEDOUT: return PIE_TIMEDOUT; + default: { + return strerror(err); + } + } +} + +const char *socket_ioerror(p_socket ps, int err) { + (void) ps; + return socket_strerror(err); +} + +const char *socket_gaistrerror(int err) { + if (err == 0) return NULL; + switch (err) { + case EAI_AGAIN: return PIE_AGAIN; + case EAI_BADFLAGS: return PIE_BADFLAGS; +#ifdef EAI_BADHINTS + case EAI_BADHINTS: return PIE_BADHINTS; +#endif + case EAI_FAIL: return PIE_FAIL; + case EAI_FAMILY: return PIE_FAMILY; + case EAI_MEMORY: return PIE_MEMORY; + case EAI_NONAME: return PIE_NONAME; +#ifdef EAI_OVERFLOW + case EAI_OVERFLOW: return PIE_OVERFLOW; +#endif +#ifdef EAI_PROTOCOL + case EAI_PROTOCOL: return PIE_PROTOCOL; +#endif + case EAI_SERVICE: return PIE_SERVICE; + case EAI_SOCKTYPE: return PIE_SOCKTYPE; + case EAI_SYSTEM: return strerror(errno); + default: return LUA_GAI_STRERROR(err); + } +} diff --git a/libraries/luasocket/libluasocket/usocket.h b/libraries/luasocket/libluasocket/usocket.h new file mode 100644 index 000000000..45f2f99f7 --- /dev/null +++ b/libraries/luasocket/libluasocket/usocket.h @@ -0,0 +1,59 @@ +#ifndef USOCKET_H +#define USOCKET_H +/*=========================================================================*\ +* Socket compatibilization module for Unix +* LuaSocket toolkit +\*=========================================================================*/ + +/*=========================================================================*\ +* BSD include files +\*=========================================================================*/ +/* error codes */ +#include +/* close function */ +#include +/* fnctnl function and associated constants */ +#include +/* struct sockaddr */ +#include +/* socket function */ +#include +/* struct timeval */ +#include +/* gethostbyname and gethostbyaddr functions */ +#include +/* sigpipe handling */ +#include +/* IP stuff*/ +#include +#include +/* TCP options (nagle algorithm disable) */ +#include +#include + +#ifndef SO_REUSEPORT +#define SO_REUSEPORT SO_REUSEADDR +#endif + +/* Some platforms use IPV6_JOIN_GROUP instead if + * IPV6_ADD_MEMBERSHIP. The semantics are same, though. */ +#ifndef IPV6_ADD_MEMBERSHIP +#ifdef IPV6_JOIN_GROUP +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#endif /* IPV6_JOIN_GROUP */ +#endif /* !IPV6_ADD_MEMBERSHIP */ + +/* Same with IPV6_DROP_MEMBERSHIP / IPV6_LEAVE_GROUP. */ +#ifndef IPV6_DROP_MEMBERSHIP +#ifdef IPV6_LEAVE_GROUP +#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif /* IPV6_LEAVE_GROUP */ +#endif /* !IPV6_DROP_MEMBERSHIP */ + +typedef int t_socket; +typedef t_socket *p_socket; +typedef struct sockaddr_storage t_sockaddr_storage; + +#define SOCKET_INVALID (-1) + +#endif /* USOCKET_H */ diff --git a/libraries/luasocket/libluasocket/wsocket.c b/libraries/luasocket/libluasocket/wsocket.c new file mode 100644 index 000000000..6cb1e415c --- /dev/null +++ b/libraries/luasocket/libluasocket/wsocket.c @@ -0,0 +1,434 @@ +/*=========================================================================*\ +* Socket compatibilization module for Win32 +* LuaSocket toolkit +* +* The penalty of calling select to avoid busy-wait is only paid when +* the I/O call fail in the first place. +\*=========================================================================*/ +#include "luasocket.h" + +#include + +#include "socket.h" +#include "pierror.h" + +/* WinSock doesn't have a strerror... */ +static const char *wstrerror(int err); + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int socket_open(void) { + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 0); + int err = WSAStartup(wVersionRequested, &wsaData ); + if (err != 0) return 0; + if ((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) && + (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)) { + WSACleanup(); + return 0; + } + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Close module +\*-------------------------------------------------------------------------*/ +int socket_close(void) { + WSACleanup(); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Wait for readable/writable/connected socket with timeout +\*-------------------------------------------------------------------------*/ +#define WAITFD_R 1 +#define WAITFD_W 2 +#define WAITFD_E 4 +#define WAITFD_C (WAITFD_E|WAITFD_W) + +int socket_waitfd(p_socket ps, int sw, p_timeout tm) { + int ret; + fd_set rfds, wfds, efds, *rp = NULL, *wp = NULL, *ep = NULL; + struct timeval tv, *tp = NULL; + double t; + if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ + if (sw & WAITFD_R) { + FD_ZERO(&rfds); + FD_SET(*ps, &rfds); + rp = &rfds; + } + if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } + if (sw & WAITFD_C) { FD_ZERO(&efds); FD_SET(*ps, &efds); ep = &efds; } + if ((t = timeout_get(tm)) >= 0.0) { + tv.tv_sec = (int) t; + tv.tv_usec = (int) ((t-tv.tv_sec)*1.0e6); + tp = &tv; + } + ret = select(0, rp, wp, ep, tp); + if (ret == -1) return WSAGetLastError(); + if (ret == 0) return IO_TIMEOUT; + if (sw == WAITFD_C && FD_ISSET(*ps, &efds)) return IO_CLOSED; + return IO_DONE; +} + +/*-------------------------------------------------------------------------*\ +* Select with int timeout in ms +\*-------------------------------------------------------------------------*/ +int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, + p_timeout tm) { + struct timeval tv; + double t = timeout_get(tm); + tv.tv_sec = (int) t; + tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); + if (n <= 0) { + Sleep((DWORD) (1000*t)); + return 0; + } else return select(0, rfds, wfds, efds, t >= 0.0? &tv: NULL); +} + +/*-------------------------------------------------------------------------*\ +* Close and inutilize socket +\*-------------------------------------------------------------------------*/ +void socket_destroy(p_socket ps) { + if (*ps != SOCKET_INVALID) { + socket_setblocking(ps); /* close can take a long time on WIN32 */ + closesocket(*ps); + *ps = SOCKET_INVALID; + } +} + +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ +void socket_shutdown(p_socket ps, int how) { + socket_setblocking(ps); + shutdown(*ps, how); + socket_setnonblocking(ps); +} + +/*-------------------------------------------------------------------------*\ +* Creates and sets up a socket +\*-------------------------------------------------------------------------*/ +int socket_create(p_socket ps, int domain, int type, int protocol) { + *ps = socket(domain, type, protocol); + if (*ps != SOCKET_INVALID) return IO_DONE; + else return WSAGetLastError(); +} + +/*-------------------------------------------------------------------------*\ +* Connects or returns error message +\*-------------------------------------------------------------------------*/ +int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { + int err; + /* don't call on closed socket */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* ask system to connect */ + if (connect(*ps, addr, len) == 0) return IO_DONE; + /* make sure the system is trying to connect */ + err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return err; + /* zero timeout case optimization */ + if (timeout_iszero(tm)) return IO_TIMEOUT; + /* we wait until something happens */ + err = socket_waitfd(ps, WAITFD_C, tm); + if (err == IO_CLOSED) { + int elen = sizeof(err); + /* give windows time to set the error (yes, disgusting) */ + Sleep(10); + /* find out why we failed */ + getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &elen); + /* we KNOW there was an error. if 'why' is 0, we will return + * "unknown error", but it's not really our fault */ + return err > 0? err: IO_UNKNOWN; + } else return err; + +} + +/*-------------------------------------------------------------------------*\ +* Binds or returns error message +\*-------------------------------------------------------------------------*/ +int socket_bind(p_socket ps, SA *addr, socklen_t len) { + int err = IO_DONE; + socket_setblocking(ps); + if (bind(*ps, addr, len) < 0) err = WSAGetLastError(); + socket_setnonblocking(ps); + return err; +} + +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ +int socket_listen(p_socket ps, int backlog) { + int err = IO_DONE; + socket_setblocking(ps); + if (listen(*ps, backlog) < 0) err = WSAGetLastError(); + socket_setnonblocking(ps); + return err; +} + +/*-------------------------------------------------------------------------*\ +* Accept with timeout +\*-------------------------------------------------------------------------*/ +int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, + p_timeout tm) { + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int err; + /* try to get client socket */ + if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; + /* find out why we failed */ + err = WSAGetLastError(); + /* if we failed because there was no connectoin, keep trying */ + if (err != WSAEWOULDBLOCK && err != WSAECONNABORTED) return err; + /* call select to avoid busy wait */ + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Send with timeout +* On windows, if you try to send 10MB, the OS will buffer EVERYTHING +* this can take an awful lot of time and we will end up blocked. +* Therefore, whoever calls this function should not pass a huge buffer. +\*-------------------------------------------------------------------------*/ +int socket_send(p_socket ps, const char *data, size_t count, + size_t *sent, p_timeout tm) +{ + int err; + *sent = 0; + /* avoid making system calls on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* loop until we send something or we give up on error */ + for ( ;; ) { + /* try to send something */ + int put = send(*ps, data, (int) count, 0); + /* if we sent something, we are done */ + if (put > 0) { + *sent = put; + return IO_DONE; + } + /* deal with failure */ + err = WSAGetLastError(); + /* we can only proceed if there was no serious error */ + if (err != WSAEWOULDBLOCK) return err; + /* avoid busy wait */ + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Sendto with timeout +\*-------------------------------------------------------------------------*/ +int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, + SA *addr, socklen_t len, p_timeout tm) +{ + int err; + *sent = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int put = sendto(*ps, data, (int) count, 0, addr, len); + if (put > 0) { + *sent = put; + return IO_DONE; + } + err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) return err; + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Receive with timeout +\*-------------------------------------------------------------------------*/ +int socket_recv(p_socket ps, char *data, size_t count, size_t *got, + p_timeout tm) +{ + int err, prev = IO_DONE; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int taken = recv(*ps, data, (int) count, 0); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + if (taken == 0) return IO_CLOSED; + err = WSAGetLastError(); + /* On UDP, a connreset simply means the previous send failed. + * So we try again. + * On TCP, it means our socket is now useless, so the error passes. + * (We will loop again, exiting because the same error will happen) */ + if (err != WSAEWOULDBLOCK) { + if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; + prev = err; + } + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Recvfrom with timeout +\*-------------------------------------------------------------------------*/ +int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, + SA *addr, socklen_t *len, p_timeout tm) +{ + int err, prev = IO_DONE; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int taken = recvfrom(*ps, data, (int) count, 0, addr, len); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + if (taken == 0) return IO_CLOSED; + err = WSAGetLastError(); + /* On UDP, a connreset simply means the previous send failed. + * So we try again. + * On TCP, it means our socket is now useless, so the error passes. + * (We will loop again, exiting because the same error will happen) */ + if (err != WSAEWOULDBLOCK) { + if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; + prev = err; + } + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode +\*-------------------------------------------------------------------------*/ +void socket_setblocking(p_socket ps) { + u_long argp = 0; + ioctlsocket(*ps, FIONBIO, &argp); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode +\*-------------------------------------------------------------------------*/ +void socket_setnonblocking(p_socket ps) { + u_long argp = 1; + ioctlsocket(*ps, FIONBIO, &argp); +} + +/*-------------------------------------------------------------------------*\ +* DNS helpers +\*-------------------------------------------------------------------------*/ +int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { + *hp = gethostbyaddr(addr, len, AF_INET); + if (*hp) return IO_DONE; + else return WSAGetLastError(); +} + +int socket_gethostbyname(const char *addr, struct hostent **hp) { + *hp = gethostbyname(addr); + if (*hp) return IO_DONE; + else return WSAGetLastError(); +} + +/*-------------------------------------------------------------------------*\ +* Error translation functions +\*-------------------------------------------------------------------------*/ +const char *socket_hoststrerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; + default: return wstrerror(err); + } +} + +const char *socket_strerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case WSAEADDRINUSE: return PIE_ADDRINUSE; + case WSAECONNREFUSED : return PIE_CONNREFUSED; + case WSAEISCONN: return PIE_ISCONN; + case WSAEACCES: return PIE_ACCESS; + case WSAECONNABORTED: return PIE_CONNABORTED; + case WSAECONNRESET: return PIE_CONNRESET; + case WSAETIMEDOUT: return PIE_TIMEDOUT; + default: return wstrerror(err); + } +} + +const char *socket_ioerror(p_socket ps, int err) { + (void) ps; + return socket_strerror(err); +} + +static const char *wstrerror(int err) { + switch (err) { + case WSAEINTR: return "Interrupted function call"; + case WSAEACCES: return PIE_ACCESS; /* "Permission denied"; */ + case WSAEFAULT: return "Bad address"; + case WSAEINVAL: return "Invalid argument"; + case WSAEMFILE: return "Too many open files"; + case WSAEWOULDBLOCK: return "Resource temporarily unavailable"; + case WSAEINPROGRESS: return "Operation now in progress"; + case WSAEALREADY: return "Operation already in progress"; + case WSAENOTSOCK: return "Socket operation on nonsocket"; + case WSAEDESTADDRREQ: return "Destination address required"; + case WSAEMSGSIZE: return "Message too long"; + case WSAEPROTOTYPE: return "Protocol wrong type for socket"; + case WSAENOPROTOOPT: return "Bad protocol option"; + case WSAEPROTONOSUPPORT: return "Protocol not supported"; + case WSAESOCKTNOSUPPORT: return PIE_SOCKTYPE; /* "Socket type not supported"; */ + case WSAEOPNOTSUPP: return "Operation not supported"; + case WSAEPFNOSUPPORT: return "Protocol family not supported"; + case WSAEAFNOSUPPORT: return PIE_FAMILY; /* "Address family not supported by protocol family"; */ + case WSAEADDRINUSE: return PIE_ADDRINUSE; /* "Address already in use"; */ + case WSAEADDRNOTAVAIL: return "Cannot assign requested address"; + case WSAENETDOWN: return "Network is down"; + case WSAENETUNREACH: return "Network is unreachable"; + case WSAENETRESET: return "Network dropped connection on reset"; + case WSAECONNABORTED: return "Software caused connection abort"; + case WSAECONNRESET: return PIE_CONNRESET; /* "Connection reset by peer"; */ + case WSAENOBUFS: return "No buffer space available"; + case WSAEISCONN: return PIE_ISCONN; /* "Socket is already connected"; */ + case WSAENOTCONN: return "Socket is not connected"; + case WSAESHUTDOWN: return "Cannot send after socket shutdown"; + case WSAETIMEDOUT: return PIE_TIMEDOUT; /* "Connection timed out"; */ + case WSAECONNREFUSED: return PIE_CONNREFUSED; /* "Connection refused"; */ + case WSAEHOSTDOWN: return "Host is down"; + case WSAEHOSTUNREACH: return "No route to host"; + case WSAEPROCLIM: return "Too many processes"; + case WSASYSNOTREADY: return "Network subsystem is unavailable"; + case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range"; + case WSANOTINITIALISED: + return "Successful WSAStartup not yet performed"; + case WSAEDISCON: return "Graceful shutdown in progress"; + case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; /* "Host not found"; */ + case WSATRY_AGAIN: return "Nonauthoritative host not found"; + case WSANO_RECOVERY: return PIE_FAIL; /* "Nonrecoverable name lookup error"; */ + case WSANO_DATA: return "Valid name, no data record of requested type"; + default: return "Unknown error"; + } +} + +const char *socket_gaistrerror(int err) { + if (err == 0) return NULL; + switch (err) { + case EAI_AGAIN: return PIE_AGAIN; + case EAI_BADFLAGS: return PIE_BADFLAGS; +#ifdef EAI_BADHINTS + case EAI_BADHINTS: return PIE_BADHINTS; +#endif + case EAI_FAIL: return PIE_FAIL; + case EAI_FAMILY: return PIE_FAMILY; + case EAI_MEMORY: return PIE_MEMORY; + case EAI_NONAME: return PIE_NONAME; +#ifdef EAI_OVERFLOW + case EAI_OVERFLOW: return PIE_OVERFLOW; +#endif +#ifdef EAI_PROTOCOL + case EAI_PROTOCOL: return PIE_PROTOCOL; +#endif + case EAI_SERVICE: return PIE_SERVICE; + case EAI_SOCKTYPE: return PIE_SOCKTYPE; +#ifdef EAI_SYSTEM + case EAI_SYSTEM: return strerror(errno); +#endif + default: return LUA_GAI_STRERROR(err); + } +} diff --git a/libraries/luasocket/libluasocket/wsocket.h b/libraries/luasocket/libluasocket/wsocket.h new file mode 100644 index 000000000..398664026 --- /dev/null +++ b/libraries/luasocket/libluasocket/wsocket.h @@ -0,0 +1,33 @@ +#ifndef WSOCKET_H +#define WSOCKET_H +/*=========================================================================*\ +* Socket compatibilization module for Win32 +* LuaSocket toolkit +\*=========================================================================*/ + +/*=========================================================================*\ +* WinSock include files +\*=========================================================================*/ +#include +#include + +typedef int socklen_t; +typedef SOCKADDR_STORAGE t_sockaddr_storage; +typedef SOCKET t_socket; +typedef t_socket *p_socket; + +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 27 +#endif + +#define SOCKET_INVALID (INVALID_SOCKET) + +#ifndef SO_REUSEPORT +#define SO_REUSEPORT SO_REUSEADDR +#endif + +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV (0) +#endif + +#endif /* WSOCKET_H */ diff --git a/libraries/luasocket/luasocket.cpp b/libraries/luasocket/luasocket.cpp new file mode 100644 index 000000000..9c973a1fb --- /dev/null +++ b/libraries/luasocket/luasocket.cpp @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2006-2022 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. + **/ + +#include "luasocket.hpp" + +// LuaSocket +extern "C" +{ +#include "libluasocket/luasocket.h" +#include "libluasocket/mime.h" +} + +// Lua files +static constexpr char ftp_lua[] = { +#include "libluasocket/ftp.lua" +}; + +static constexpr char headers_lua[] = { +#include "libluasocket/headers.lua" +}; + +static constexpr char http_lua[] = { +#include "libluasocket/http.lua" +}; + +static constexpr char ltn12_lua[] = { +#include "libluasocket/ltn12.lua" +}; + +static constexpr char mbox_lua[] = { +#include "libluasocket/mbox.lua" +}; + +static constexpr char mime_lua[] = { +#include "libluasocket/mime.lua" +}; + +static constexpr char smtp_lua[] = { +#include "libluasocket/smtp.lua" +}; + +static constexpr char socket_lua[] = { +#include "libluasocket/socket.lua" +}; + +static constexpr char tp_lua[] = { +#include "libluasocket/tp.lua" +}; + +static constexpr char url_lua[] = { +#include "libluasocket/url.lua" +}; + +static void preload(lua_State* L, const char* name, lua_CFunction func) +{ + lua_getglobal(L, "package"); + lua_getfield(L, -1, "preload"); + lua_pushcfunction(L, func); + lua_setfield(L, -2, name); + lua_pop(L, 2); +} + +static void preload(lua_State* L, const char* name, const char* chunkname, const void* lua, + size_t size) +{ + if (luaL_loadbuffer(L, (const char*)lua, size, name) != 0) + { + luaL_loadstring(L, "local name, msg = ... return function() error(name..\": \"..msg) end"); + lua_pushstring(L, name); + lua_pushvalue(L, -3); + // Before: + // -1: error message + // -2: module name + // -3: loadstring function + // -4: error message + lua_call(L, 2, 1); + // After: + // -1: function + // -2: error message + lua_remove(L, -2); + } + + lua_getglobal(L, "package"); + lua_getfield(L, -1, "preload"); + lua_pushvalue(L, -3); + lua_setfield(L, -2, name); + lua_pop(L, 3); +} + +namespace love::luasocket +{ + int preload(lua_State* L) + { + // Preload code from LuaSocket. + preload(L, "socket.core", luaopen_socket_core); + preload(L, "mime.core", luaopen_mime_core); + + preload(L, "socket", "=[socket \"socket.lua\"]", socket_lua, sizeof(socket_lua)); + preload(L, "socket.ftp", "=[socket \"ftp.lua\"]", ftp_lua, sizeof(ftp_lua)); + preload(L, "socket.http", "=[socket \"http.lua\"]", http_lua, sizeof(http_lua)); + preload(L, "ltn12", "=[socket \"ltn12.lua\"]", ltn12_lua, sizeof(ltn12_lua)); + preload(L, "mime", "=[socket \"mime.lua\"]", mime_lua, sizeof(mime_lua)); + preload(L, "socket.smtp", "=[socket \"smtp.lua\"]", smtp_lua, sizeof(smtp_lua)); + preload(L, "socket.tp", "=[socket \"tp.lua\"]", tp_lua, sizeof(tp_lua)); + preload(L, "socket.url", "=[socket \"url.lua\"]", url_lua, sizeof(url_lua)); + preload(L, "socket.headers", "=[socket \"headers.lua\"]", headers_lua, sizeof(headers_lua)); + preload(L, "mbox", "=[socket \"mbox.lua\"]", mbox_lua, sizeof(mbox_lua)); + + // No need to register garbage collector function. + + return 0; + } +} // namespace love::luasocket diff --git a/libraries/luasocket/luasocket.hpp b/libraries/luasocket/luasocket.hpp new file mode 100644 index 000000000..4ef5879ca --- /dev/null +++ b/libraries/luasocket/luasocket.hpp @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2006-2022 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. + **/ + +#ifndef LOVE_LUASOCKET_LUASOCKET_H +#define LOVE_LUASOCKET_LUASOCKET_H + +// LOVE + +extern "C" +{ +#include +#include +#include +} + +namespace love +{ + namespace luasocket + { + int preload(lua_State* L); + } // namespace luasocket +} // namespace love + +#endif // LOVE_LUASOCKET_LUASOCKET_H diff --git a/platform/cafe/CMakeLists.txt b/platform/cafe/CMakeLists.txt index 7a9188c41..02e5d2806 100644 --- a/platform/cafe/CMakeLists.txt +++ b/platform/cafe/CMakeLists.txt @@ -26,11 +26,15 @@ source/driver/audio/AudioBuffer.cpp source/driver/audio/DigitalSound.cpp source/driver/audio/SoundChannel.cpp source/driver/display/Framebuffer.cpp -source/driver/display/Renderer.cpp +source/driver/display/GX2.cpp +source/driver/display/Uniform.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp source/modules/graphics/Graphics.cpp -source/modules/joystick/Joystick.cpp +source/modules/graphics/Shader.cpp +source/modules/graphics/Texture.cpp +source/modules/joystick/vpad/Joystick.cpp +source/modules/joystick/kpad/Joystick.cpp source/modules/joystick/JoystickModule.cpp source/modules/keyboard/Keyboard.cpp source/modules/system/System.cpp diff --git a/platform/cafe/content/shaders/video.gsh b/platform/cafe/content/shaders/video.gsh new file mode 100644 index 000000000..4f61a2e7a Binary files /dev/null and b/platform/cafe/content/shaders/video.gsh differ diff --git a/platform/cafe/include/driver/EventQueue.hpp b/platform/cafe/include/driver/EventQueue.hpp index 4c5524e69..c183fc3ef 100644 --- a/platform/cafe/include/driver/EventQueue.hpp +++ b/platform/cafe/include/driver/EventQueue.hpp @@ -1,7 +1,9 @@ #pragma once #include "driver/EventQueue.tcc" -#include "modules/joystick/Joystick.hpp" + +#include "modules/joystick/kpad/Joystick.hpp" +#include "modules/joystick/vpad/Joystick.hpp" #include @@ -17,7 +19,7 @@ namespace love void pollInternal() override; private: - Joystick* gamepad; + vpad::Joystick* gamepad; VPADTouchData previousTouch; }; } // namespace love diff --git a/platform/cafe/include/driver/display/Framebuffer.hpp b/platform/cafe/include/driver/display/Framebuffer.hpp index f5eef220c..a8c19b4a9 100644 --- a/platform/cafe/include/driver/display/Framebuffer.hpp +++ b/platform/cafe/include/driver/display/Framebuffer.hpp @@ -3,6 +3,7 @@ #include "common/screen.hpp" #include "driver/display/Framebuffer.tcc" +#include "driver/display/Uniform.hpp" #include @@ -10,8 +11,6 @@ #include #include -#include - namespace love { class Framebuffer : public FramebufferBase @@ -37,14 +36,9 @@ namespace love return this->depth; } - glm::mat4& getModelView() + Uniform* getUniform() { - return this->modelView; - } - - glm::mat4& getProjection() - { - return this->projection; + return this->uniform; } bool allocateScanBuffer(MEMHeapHandle handle); @@ -64,11 +58,13 @@ namespace love GX2ColorBuffer target; GX2DepthBuffer depth; - uint8_t mode; - uint8_t id; + uint8_t renderMode; + GX2ScanTarget id; + + Uniform* uniform = nullptr; - glm::mat4 modelView; - glm::mat4 projection; + glm::mat4 tmpModel; + glm::highp_mat4 ortho; void* scanBuffer; uint32_t scanBufferSize; diff --git a/platform/cafe/include/driver/display/GX2.hpp b/platform/cafe/include/driver/display/GX2.hpp new file mode 100644 index 000000000..19479701f --- /dev/null +++ b/platform/cafe/include/driver/display/GX2.hpp @@ -0,0 +1,197 @@ +#pragma once + +#include "common/Map.hpp" +#include "common/pixelformat.hpp" + +#include "driver/display/Framebuffer.hpp" +#include "driver/display/Renderer.tcc" +#include "driver/display/Uniform.hpp" + +#include "modules/graphics/vertex.hpp" +#include +#include +#include + +/* Enforces GLSL std140/std430 alignment rules for glm types */ +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES +/* Enables usage of SIMD CPU instructions (requiring the above as well) */ +#define GLM_FORCE_INTRINSICS +#include +#include +#include +#include +#include +#include + +namespace love +{ + class GX2 : public RendererBase + { + public: + GX2(); + + void initialize(); + + ~GX2(); + + int onForegroundAcquired(); + + int onForegroundReleased(); + + void clear(const Color& color); + + void clearDepthStencil(int depth, uint8_t mask, double stencil); + + GX2ColorBuffer& getInternalBackbuffer(); + + GX2DepthBuffer& getInternalDepthbuffer(); + + void bindFramebuffer(GX2ColorBuffer* target); + + void present(); + + void setBlendState(const BlendState& state); + + void setViewport(const Rect& viewport); + + void setScissor(const Rect& scissor); + + void setCullMode(CullMode mode); + + void setColorMask(ColorChannelMask mask); + + void setVertexWinding(Winding winding); + + void setSamplerState(TextureBase* texture, const SamplerState& state); + + void ensureInFrame(); + + void prepareDraw(GraphicsBase* graphics); + + void bindTextureToUnit(TextureBase* texture, int unit); + + void bindTextureToUnit(GX2Texture* texture, GX2Sampler* sampler, int unit); + + void setMode(int width, int height); + + void copyCurrentScanBuffer(); + + // clang-format off + ENUMMAP_DECLARE(PixelFormats, PixelFormat, GX2SurfaceFormat, + { PIXELFORMAT_R8_UNORM, GX2_SURFACE_FORMAT_UNORM_R8 }, + { PIXELFORMAT_R16_UNORM, GX2_SURFACE_FORMAT_UNORM_R16 }, + { PIXELFORMAT_RG8_UNORM, GX2_SURFACE_FORMAT_UNORM_R8_G8 }, + { PIXELFORMAT_RGBA8_UNORM, GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8 }, + { PIXELFORMAT_RGB565_UNORM, GX2_SURFACE_FORMAT_UNORM_R5_G6_B5 }, + { PIXELFORMAT_RGBA8_sRGB, GX2_SURFACE_FORMAT_SRGB_R8_G8_B8_A8 }, + { PIXELFORMAT_DXT1_UNORM, GX2_SURFACE_FORMAT_UNORM_BC1 }, + { PIXELFORMAT_DXT3_UNORM, GX2_SURFACE_FORMAT_UNORM_BC2 }, + { PIXELFORMAT_DXT5_UNORM, GX2_SURFACE_FORMAT_UNORM_BC3 }, + { PIXELFORMAT_BC4_UNORM, GX2_SURFACE_FORMAT_UNORM_BC4 }, + { PIXELFORMAT_BC5_UNORM, GX2_SURFACE_FORMAT_UNORM_BC5 } + ); + + ENUMMAP_DECLARE(BlendOperations, BlendOperation, GX2BlendCombineMode, + { BLENDOP_ADD, GX2_BLEND_COMBINE_MODE_ADD }, + { BLENDOP_SUBTRACT, GX2_BLEND_COMBINE_MODE_SUB }, + { BLENDOP_REVERSE_SUBTRACT, GX2_BLEND_COMBINE_MODE_REV_SUB }, + { BLENDOP_MIN, GX2_BLEND_COMBINE_MODE_MIN }, + { BLENDOP_MAX, GX2_BLEND_COMBINE_MODE_MAX } + ); + + ENUMMAP_DECLARE(BlendFactors, BlendFactor, GX2BlendMode, + { BLENDFACTOR_ZERO, GX2_BLEND_MODE_ZERO }, + { BLENDFACTOR_ONE, GX2_BLEND_MODE_ONE }, + { BLENDFACTOR_SRC_COLOR, GX2_BLEND_MODE_SRC_COLOR }, + { BLENDFACTOR_ONE_MINUS_SRC_COLOR, GX2_BLEND_MODE_INV_SRC_COLOR }, + { BLENDFACTOR_SRC_ALPHA, GX2_BLEND_MODE_SRC_ALPHA }, + { BLENDFACTOR_ONE_MINUS_SRC_ALPHA, GX2_BLEND_MODE_INV_SRC_ALPHA }, + { BLENDFACTOR_DST_COLOR, GX2_BLEND_MODE_DST_COLOR }, + { BLENDFACTOR_ONE_MINUS_DST_COLOR, GX2_BLEND_MODE_INV_DST_COLOR }, + { BLENDFACTOR_DST_ALPHA, GX2_BLEND_MODE_DST_ALPHA }, + { BLENDFACTOR_ONE_MINUS_DST_ALPHA, GX2_BLEND_MODE_INV_DST_ALPHA }, + { BLENDFACTOR_SRC_ALPHA_SATURATED, GX2_BLEND_MODE_SRC_ALPHA_SAT } + ); + + ENUMMAP_DECLARE(WindingModes, Winding, GX2FrontFace, + { WINDING_CW, GX2_FRONT_FACE_CW }, + { WINDING_CCW, GX2_FRONT_FACE_CCW } + ); + + ENUMMAP_DECLARE(ClampModes, SamplerState::WrapMode, GX2TexClampMode, + { SamplerState::WRAP_CLAMP, GX2_TEX_CLAMP_MODE_CLAMP }, + { SamplerState::WRAP_CLAMP_ZERO, GX2_TEX_CLAMP_MODE_CLAMP_BORDER }, + { SamplerState::WRAP_REPEAT, GX2_TEX_CLAMP_MODE_MIRROR }, + { SamplerState::WRAP_MIRRORED_REPEAT, GX2_TEX_CLAMP_MODE_MIRROR } + ); + + ENUMMAP_DECLARE(FilterModes, SamplerState::FilterMode, GX2TexXYFilterMode, + { SamplerState::FILTER_NEAREST, GX2_TEX_XY_FILTER_MODE_POINT }, + { SamplerState::FILTER_LINEAR, GX2_TEX_XY_FILTER_MODE_LINEAR } + ); + + static GX2PrimitiveMode getPrimitiveType(PrimitiveType type) + { + switch (type) + { + case PRIMITIVE_TRIANGLES: + return GX2_PRIMITIVE_MODE_TRIANGLES; + case PRIMITIVE_POINTS: + return GX2_PRIMITIVE_MODE_POINTS; + case PRIMITIVE_TRIANGLE_STRIP: + return GX2_PRIMITIVE_MODE_TRIANGLE_STRIP; + case PRIMITIVE_TRIANGLE_FAN: + return GX2_PRIMITIVE_MODE_TRIANGLE_FAN; + default: + throw love::Exception("Invalid primitive type"); + } + } + + static GX2IndexType getIndexType(IndexDataType type) + { + switch (type) + { + default: + case INDEX_UINT16: + return GX2_INDEX_TYPE_U16; + case INDEX_UINT32: + return GX2_INDEX_TYPE_U32; + } + } + // clang-format on + + private: + std::array targets; + + void createFramebuffers(); + + void destroyFramebuffers(); + + GX2ColorBuffer* getFramebuffer(); + + struct Context : public ContextBase + { + bool cullBack; + bool cullFront; + + GX2FrontFace winding; + bool depthWrite; + bool depthTest; + GX2CompareFunction compareMode; + + uint32_t writeMask; + + GX2ColorBuffer* boundFramebuffer = nullptr; + } context; + + Uniform* uniform; + + bool inForeground; + + void* commandBuffer; + GX2ContextState* state; + bool dirtyProjection; + }; + + extern GX2 gx2; +} // namespace love diff --git a/platform/cafe/include/driver/display/Renderer.hpp b/platform/cafe/include/driver/display/Renderer.hpp deleted file mode 100644 index 84e959c36..000000000 --- a/platform/cafe/include/driver/display/Renderer.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -#include "common/Map.hpp" -#include "common/pixelformat.hpp" - -#include "driver/display/Framebuffer.hpp" -#include "driver/display/Renderer.tcc" - -#include "modules/graphics/vertex.hpp" - -#include - -namespace love -{ - class Renderer : public RendererBase - { - public: - Renderer(); - - void initialize(); - - ~Renderer(); - - int onForegroundAcquired(); - - int onForegroundReleased(); - - void clear(const Color& color); - - void clearDepthStencil(int depth, uint8_t mask, double stencil); - - void bindFramebuffer(); - - void present(); - - void setBlendState(const BlendState& state); - - void setViewport(const Rect& viewport); - - void setScissor(const Rect& scissor); - - void setCullMode(CullMode mode); - - void setColorMask(ColorChannelMask mask); - - void setVertexWinding(Winding winding); - - // clang-format off - ENUMMAP_DECLARE(BlendOperations, BlendOperation, GX2BlendCombineMode, - { BLENDOP_ADD, GX2_BLEND_COMBINE_MODE_ADD }, - { BLENDOP_SUBTRACT, GX2_BLEND_COMBINE_MODE_SUB }, - { BLENDOP_REVERSE_SUBTRACT, GX2_BLEND_COMBINE_MODE_REV_SUB }, - { BLENDOP_MIN, GX2_BLEND_COMBINE_MODE_MIN }, - { BLENDOP_MAX, GX2_BLEND_COMBINE_MODE_MAX } - ); - - ENUMMAP_DECLARE(BlendFactors, BlendFactor, GX2BlendMode, - { BLENDFACTOR_ZERO, GX2_BLEND_MODE_ZERO }, - { BLENDFACTOR_ONE, GX2_BLEND_MODE_ONE }, - { BLENDFACTOR_SRC_COLOR, GX2_BLEND_MODE_SRC_COLOR, }, - { BLENDFACTOR_ONE_MINUS_SRC_COLOR, GX2_BLEND_MODE_INV_SRC_COLOR }, - { BLENDFACTOR_SRC_ALPHA, GX2_BLEND_MODE_SRC_ALPHA }, - { BLENDFACTOR_ONE_MINUS_SRC_ALPHA, GX2_BLEND_MODE_INV_SRC_ALPHA }, - { BLENDFACTOR_DST_COLOR, GX2_BLEND_MODE_DST_COLOR }, - { BLENDFACTOR_ONE_MINUS_DST_COLOR, GX2_BLEND_MODE_INV_DST_COLOR }, - { BLENDFACTOR_DST_ALPHA, GX2_BLEND_MODE_DST_ALPHA }, - { BLENDFACTOR_ONE_MINUS_DST_ALPHA, GX2_BLEND_MODE_INV_DST_ALPHA }, - { BLENDFACTOR_SRC_ALPHA_SATURATED, GX2_BLEND_MODE_SRC_ALPHA_SAT } - ); - - ENUMMAP_DECLARE(WindingModes, Winding, GX2FrontFace, - { WINDING_CW, GX2_FRONT_FACE_CW }, - { WINDING_CCW, GX2_FRONT_FACE_CCW } - ); - // clang-format on - - private: - std::array targets; - - void ensureInFrame(); - - void createFramebuffers(); - - void destroyFramebuffers(); - - struct Context : public ContextBase - { - glm::mat4 modelView; - glm::mat4 projection; - - bool cullBack; - bool cullFront; - - GX2FrontFace winding; - bool depthWrite; - bool depthTest; - GX2CompareFunction compareMode; - - uint32_t writeMask; - - Framebuffer target; - } context; - - bool inForeground; - - void* commandBuffer; - GX2ContextState* state; - }; -} // namespace love diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp new file mode 100644 index 000000000..40b57bfce --- /dev/null +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "common/Exception.hpp" +#include "common/Matrix.hpp" + +#include +#include + +namespace love +{ + struct Uniform + { + glm::mat4 modelView; + glm::mat4 projection; + }; + + void debugUniform(Uniform* uniform); + + static constexpr auto UNIFORM_SIZE = sizeof(Uniform); +} // namespace love diff --git a/platform/cafe/include/driver/display/utility.hpp b/platform/cafe/include/driver/display/utility.hpp index 264431af8..afdb0a7c8 100644 --- a/platform/cafe/include/driver/display/utility.hpp +++ b/platform/cafe/include/driver/display/utility.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp new file mode 100644 index 000000000..4130480d9 --- /dev/null +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "driver/graphics/StreamBuffer.tcc" +#include "modules/graphics/Volatile.hpp" + +#include +#include +#include + +namespace love +{ + static GX2RResourceFlags getBufferUsage(BufferUsage usage) + { + if (usage == BUFFERUSAGE_VERTEX) + return GX2R_RESOURCE_BIND_VERTEX_BUFFER; + + return GX2R_RESOURCE_BIND_INDEX_BUFFER; + } + + template + class StreamBuffer final : public StreamBufferBase + { + public: + StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size), buffer {} + { + const auto flags = getBufferUsage(usage); + + this->buffer.elemCount = size; + this->buffer.elemSize = sizeof(T); + this->buffer.flags = flags | BUFFER_CREATE_FLAGS; + + if (!GX2RCreateBuffer(&this->buffer)) + throw love::Exception("Failed to create StreamBuffer"); + } + + StreamBuffer(StreamBuffer&&) = delete; + + StreamBuffer& operator=(const StreamBuffer&) = delete; + + ~StreamBuffer() + { + GX2RDestroyBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); + } + + MapInfo map(size_t) + { + MapInfo info {}; + + auto* data = (T*)GX2RLockBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); + + info.data = &data[this->index]; + info.size = this->bufferSize - this->frameGPUReadOffset; + + return info; + } + + size_t unmap(size_t) + { + GX2RUnlockBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); + + if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) + GX2RSetAttributeBuffer(&this->buffer, 0, this->buffer.elemSize, 0); + + return this->index; + } + + ptrdiff_t getHandle() const override + { + return (ptrdiff_t)std::addressof(this->buffer); + } + + private: + static constexpr auto BUFFER_CREATE_FLAGS = + GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ; + + GX2RBuffer buffer; + }; +} // namespace love diff --git a/platform/cafe/include/modules/graphics/Graphics.hpp b/platform/cafe/include/modules/graphics/Graphics.hpp index 4ead2d97c..047cd80a4 100644 --- a/platform/cafe/include/modules/graphics/Graphics.hpp +++ b/platform/cafe/include/modules/graphics/Graphics.hpp @@ -2,48 +2,72 @@ #include "modules/graphics/Graphics.tcc" +#include + namespace love { - class Graphics : public GraphicsBase + class Graphics : public GraphicsBase { public: Graphics(); - void clearImpl(OptionalColor color, OptionalInt depth, OptionalDouble stencil); + ~Graphics(); - using GraphicsBase::clear; + virtual void initCapabilities() override; - void presentImpl(); + virtual void captureScreenshot(const ScreenshotInfo& info) override; - void setScissorImpl(const Rect& scissor); + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) override; - void setScissorImpl(); + virtual void clear(const std::vector& colors, OptionalInt stencil, + OptionalDouble depth) override; - void setFrontFaceWindingImpl(Winding winding); + virtual void present(void* screenshotCallbackData) override; - void setColorMaskImpl(ColorChannelMask mask); + virtual void setScissor(const Rect& scissor) override; - void setBlendStateImpl(const BlendState& state); + virtual void setScissor() override; - void getRendererInfoImpl(RendererInfo& info) const; + virtual void setFrontFaceWinding(Winding winding) override; - bool setModeImpl(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa); + virtual void setColorMask(ColorChannelMask mask) override; - bool isActive() const; + virtual void setBlendState(const BlendState& state) override; - void unsetModeImpl(); + virtual void setPointSize(float size) override; - void setViewport(int x, int y, int width, int height); + virtual FontBase* newFont(Rasterizer* data) override; + + virtual FontBase* newDefaultFont(int size, const Rasterizer::Settings& settings) override; + + virtual bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa) override; + + virtual void setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) override; + + virtual bool isPixelFormatSupported(PixelFormat format, uint32_t usage) override; - bool is3D() const; + void draw(const DrawIndexedCommand& command) override; - void set3D(bool enable); + void draw(const DrawCommand& command) override; + + using GraphicsBase::draw; + + bool isActive() const; + + virtual void unsetMode() override; + + void setActiveScreen() override; + + void setViewport(int x, int y, int width, int height); - bool isWide() const; + GX2ColorBuffer getInternalBackbuffer() const; - void setWide(bool enable); + void copyCurrentScanBuffer(); - float getDepth() const; + // clang-format off + virtual TextureBase* newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data = nullptr) override; + // clang-format on }; } // namespace love diff --git a/platform/cafe/include/modules/graphics/Shader.hpp b/platform/cafe/include/modules/graphics/Shader.hpp new file mode 100644 index 000000000..ba11250d9 --- /dev/null +++ b/platform/cafe/include/modules/graphics/Shader.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "modules/graphics/Graphics.hpp" +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/Volatile.hpp" + +#include + +#include "driver/display/Uniform.hpp" + +namespace love +{ + class Shader final : public ShaderBase, public Volatile + { + public: + struct UniformInfo + { + uint32_t location; + std::string name; + }; + + Shader(StandardShader shader); + + virtual ~Shader(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + void attach() override; + + ptrdiff_t getHandle() const override; + + const UniformInfo getUniform(const std::string& name) const; + + bool hasUniform(const std::string& name) const; + + void updateBuiltinUniforms(GraphicsBase* graphics, Uniform* uniform); + + uint32_t getPixelSamplerLocation(int index); + + private: + static constexpr auto INVALIDATE_UNIFORM_BLOCK = + GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_UNIFORM_BLOCK; + + bool validate(const char* filepath, std::string& error); + WHBGfxShaderGroup program; + + UniformInfo uniform; + }; +} // namespace love diff --git a/platform/cafe/include/modules/graphics/Texture.hpp b/platform/cafe/include/modules/graphics/Texture.hpp new file mode 100644 index 000000000..73f85113e --- /dev/null +++ b/platform/cafe/include/modules/graphics/Texture.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/Volatile.hpp" + +#include +#include + +namespace love +{ + class Texture final : public TextureBase, public Volatile + { + public: + Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data); + + virtual ~Texture(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + ptrdiff_t getHandle() const override; + + ptrdiff_t getRenderTargetHandle() const override; + + ptrdiff_t getSamplerHandle() const override; + + void setSamplerState(const SamplerState& state) override; + + void uploadByteData(const void* data, size_t size, int slice, int mipmap, const Rect& rect) override; + + void generateMipmapsInternal() override; + + void setHandleData(ptrdiff_t data) override + {} + + private: + void createTexture(); + + Slices slices; + + GX2Texture* texture = nullptr; + GX2ColorBuffer* target = nullptr; + GX2Sampler sampler; + }; +} // namespace love diff --git a/platform/cafe/include/modules/joystick/Joystick.hpp b/platform/cafe/include/modules/joystick/Joystick.hpp deleted file mode 100644 index 935515d28..000000000 --- a/platform/cafe/include/modules/joystick/Joystick.hpp +++ /dev/null @@ -1,168 +0,0 @@ -#pragma once - -#include "modules/joystick/Joystick.tcc" - -#include -#include - -namespace love -{ - class Joystick : public JoystickBase - { - public: - Joystick(int id); - - Joystick(int id, int index); - - ~Joystick(); - - virtual void update(); - - bool isDRCGamepad() const - { - return this->gamepadType == GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD; - } - - VPADStatus& getVPADStatus() - { - return this->vpadStatus; - } - - virtual bool open(int64_t deviceId) override; - - virtual void close() override; - - virtual bool isConnected() const override; - - virtual float getAxis(GamepadAxis axis) const override; - - virtual std::vector getAxes() const override; - - virtual bool isDown(std::span buttons) const override; - - virtual bool isUp(std::span buttons) const override; - - virtual bool isAxisChanged(GamepadAxis axis) const override; - - virtual void setPlayerIndex(int index) override; - - virtual int getPlayerIndex() const override; - - virtual JoystickInput getGamepadMapping(const GamepadInput& input) const override; - - virtual std::string getGamepadMappingString() const override; - - virtual bool isVibrationSupported() const override; - - virtual bool setVibration(float left, float right, float duration = -1.0f) override; - - virtual bool setVibration() override; - - virtual void getVibration(float& left, float& right) const override; - - virtual bool hasSensor(Sensor::SensorType type) const override; - - virtual bool isSensorEnabled(Sensor::SensorType type) const override; - - virtual void setSensorEnabled(Sensor::SensorType type, bool enable) override; - - virtual std::vector getSensorData(Sensor::SensorType type) const override; - - using JoystickBase::getConstant; - - enum VPADAxis - { - VPADAXIS_LEFTX = VPAD_STICK_L_EMULATION_LEFT | VPAD_STICK_L_EMULATION_RIGHT, - VPADAXIS_LEFTY = VPAD_STICK_L_EMULATION_UP | VPAD_STICK_L_EMULATION_DOWN, - VPADAXIS_RIGHTX = VPAD_STICK_R_EMULATION_LEFT | VPAD_STICK_R_EMULATION_RIGHT, - VPADAXIS_RIGHTY = VPAD_STICK_R_EMULATION_UP | VPAD_STICK_R_EMULATION_DOWN, - VPADAXIS_TRIGGERLEFT = VPAD_BUTTON_ZL, - VPADAXIS_TRIGGERRIGHT = VPAD_BUTTON_ZR - }; - - enum WPADProAxis - { - WPADPROAXIS_LEFTX = WPAD_PRO_STICK_L_EMULATION_LEFT | WPAD_PRO_STICK_L_EMULATION_RIGHT, - WPADPROAXIS_LEFTY = WPAD_PRO_STICK_L_EMULATION_UP | WPAD_PRO_STICK_L_EMULATION_DOWN, - WPADPROAXIS_RIGHTX = WPAD_PRO_STICK_R_EMULATION_LEFT | WPAD_PRO_STICK_R_EMULATION_RIGHT, - WPADPROAXIS_RIGHTY = WPAD_PRO_STICK_R_EMULATION_UP | WPAD_PRO_STICK_R_EMULATION_DOWN - }; - - // clang-format off - ENUMMAP_DECLARE(WpadProButtons, GamepadButton, WPADProButton, - { GAMEPAD_BUTTON_A, WPAD_PRO_BUTTON_A }, - { GAMEPAD_BUTTON_B, WPAD_PRO_BUTTON_B }, - { GAMEPAD_BUTTON_X, WPAD_PRO_BUTTON_X }, - { GAMEPAD_BUTTON_Y, WPAD_PRO_BUTTON_Y }, - - { GAMEPAD_BUTTON_DPAD_LEFT, WPAD_PRO_BUTTON_LEFT }, - { GAMEPAD_BUTTON_DPAD_UP, WPAD_PRO_BUTTON_UP }, - { GAMEPAD_BUTTON_DPAD_RIGHT, WPAD_PRO_BUTTON_RIGHT }, - { GAMEPAD_BUTTON_DPAD_DOWN, WPAD_PRO_BUTTON_DOWN }, - - { GAMEPAD_BUTTON_START, WPAD_PRO_BUTTON_PLUS }, - { GAMEPAD_BUTTON_BACK, WPAD_PRO_BUTTON_MINUS }, - { GAMEPAD_BUTTON_LEFTSHOULDER, WPAD_PRO_TRIGGER_L }, - { GAMEPAD_BUTTON_RIGHTSHOULDER, WPAD_PRO_TRIGGER_R }, - - { GAMEPAD_BUTTON_LEFTSTICK, WPAD_PRO_BUTTON_STICK_L }, - { GAMEPAD_BUTTON_RIGHTSTICK, WPAD_PRO_BUTTON_STICK_R } - ); - - ENUMMAP_DECLARE(VpadAxes, GamepadAxis, VPADAxis, - { GAMEPAD_AXIS_LEFTX, VPADAXIS_LEFTX }, - { GAMEPAD_AXIS_LEFTY, VPADAXIS_LEFTY }, - { GAMEPAD_AXIS_RIGHTX, VPADAXIS_RIGHTX }, - { GAMEPAD_AXIS_RIGHTY, VPADAXIS_RIGHTY }, - { GAMEPAD_AXIS_TRIGGERLEFT, VPADAXIS_TRIGGERLEFT }, - { GAMEPAD_AXIS_TRIGGERRIGHT, VPADAXIS_TRIGGERRIGHT } - ); - - ENUMMAP_DECLARE(VpadButtons, GamepadButton, VPADButtons, - { GAMEPAD_BUTTON_A, VPAD_BUTTON_A }, - { GAMEPAD_BUTTON_B, VPAD_BUTTON_B }, - { GAMEPAD_BUTTON_X, VPAD_BUTTON_X }, - { GAMEPAD_BUTTON_Y, VPAD_BUTTON_Y }, - - { GAMEPAD_BUTTON_DPAD_LEFT, VPAD_BUTTON_LEFT }, - { GAMEPAD_BUTTON_DPAD_UP, VPAD_BUTTON_UP }, - { GAMEPAD_BUTTON_DPAD_RIGHT, VPAD_BUTTON_RIGHT }, - { GAMEPAD_BUTTON_DPAD_DOWN, VPAD_BUTTON_DOWN }, - - { GAMEPAD_BUTTON_START, VPAD_BUTTON_PLUS }, - { GAMEPAD_BUTTON_BACK, VPAD_BUTTON_MINUS }, - { GAMEPAD_BUTTON_LEFTSHOULDER, VPAD_BUTTON_L }, - { GAMEPAD_BUTTON_RIGHTSHOULDER, VPAD_BUTTON_R }, - - { GAMEPAD_BUTTON_LEFTSTICK, VPAD_BUTTON_STICK_L }, - { GAMEPAD_BUTTON_RIGHTSTICK, VPAD_BUTTON_STICK_R } - ); - - ENUMMAP_DECLARE(WpadTypes, GamepadType, WPADExtensionType, - { GAMEPAD_TYPE_NINTENDO_WII_U_PRO, WPAD_EXT_PRO_CONTROLLER } - ); - // clang-format on - - private: - VPADStatus vpadStatus; - VPADReadError vpadError; - - KPADStatus kpadStatus; - KPADError kpadError; - - mutable struct State - { - uint32_t pressed; - uint32_t released; - uint32_t held; - } state; - - float getVPADAxis(GamepadAxis axis) const; - - bool isVPADButtonDown(std::span buttons) const; - - float getKPADAxis(GamepadAxis axis) const; - - bool isKPADButtonDown(std::span buttons) const; - }; -} // namespace love diff --git a/platform/cafe/include/modules/joystick/kpad/Joystick.hpp b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp new file mode 100644 index 000000000..35b36615c --- /dev/null +++ b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp @@ -0,0 +1,210 @@ +#pragma once + +#include "common/Vector.hpp" + +#include "modules/joystick/Joystick.tcc" + +#include + +namespace love +{ + namespace kpad + { + class Joystick : public JoystickBase + { + public: + Joystick(int id); + + Joystick(int id, int index); + + ~Joystick(); + + virtual void update(); + + virtual bool open(int64_t deviceId) override; + + virtual void close() override; + + virtual bool isConnected() const override; + + virtual float getAxis(GamepadAxis axis) const override; + + virtual std::vector getAxes() const override; + + virtual bool isDown(std::span buttons) const override; + + virtual bool isUp(std::span buttons) const override; + + virtual bool isAxisChanged(GamepadAxis axis) const override; + + virtual void setPlayerIndex(int index) override; + + virtual int getPlayerIndex() const override; + + virtual JoystickInput getGamepadMapping(const GamepadInput& input) const override; + + virtual std::string getGamepadMappingString() const override; + + virtual bool isVibrationSupported() const override; + + virtual bool setVibration(float left, float right, float duration = -1.0f) override; + + virtual bool setVibration() override; + + virtual void getVibration(float& left, float& right) const override; + + virtual bool hasSensor(Sensor::SensorType type) const override; + + virtual bool isSensorEnabled(Sensor::SensorType type) const override; + + virtual void setSensorEnabled(Sensor::SensorType type, bool enable) override; + + virtual std::vector getSensorData(Sensor::SensorType type) const override; + + Vector2 getPosition() const; + + Vector2 getAngle() const; + + using JoystickBase::getConstant; + + // #region Wii Remote + // clang-format off + ENUMMAP_DECLARE(CoreButtons, GamepadButton, WPADButton, + { GAMEPAD_BUTTON_A, WPAD_BUTTON_A }, + { GAMEPAD_BUTTON_B, WPAD_BUTTON_B }, + + { GAMEPAD_BUTTON_DPAD_LEFT, WPAD_BUTTON_LEFT }, + { GAMEPAD_BUTTON_DPAD_UP, WPAD_BUTTON_UP }, + { GAMEPAD_BUTTON_DPAD_RIGHT, WPAD_BUTTON_RIGHT }, + { GAMEPAD_BUTTON_DPAD_DOWN, WPAD_BUTTON_DOWN }, + + { GAMEPAD_BUTTON_START, WPAD_BUTTON_PLUS }, + { GAMEPAD_BUTTON_BACK, WPAD_BUTTON_MINUS }, + + { GAMEPAD_BUTTON_MISC1, WPAD_BUTTON_1 }, + { GAMEPAD_BUTTON_MISC2, WPAD_BUTTON_2 } + ) + // clang-format on + // #endregion + + // #region WiimoteNunchuck + enum NunchuckAxis + { + NUNCHUCK_AXIS_X = WPAD_NUNCHUK_STICK_EMULATION_LEFT | WPAD_NUNCHUK_STICK_EMULATION_RIGHT, + NUNCHUCK_AXIS_Y = WPAD_NUNCHUK_STICK_EMULATION_UP | WPAD_NUNCHUK_STICK_EMULATION_DOWN + }; + + // clang-format off + ENUMMAP_DECLARE(NunchuckButtons, GamepadButton, WPADNunchukButton, + { GAMEPAD_BUTTON_C, WPAD_NUNCHUK_BUTTON_C }, + { GAMEPAD_BUTTON_Z, WPAD_NUNCHUK_BUTTON_Z } + ); + + ENUMMAP_DECLARE(NunchuckAxes, GamepadAxis, NunchuckAxis, + { GAMEPAD_AXIS_LEFTX, NUNCHUCK_AXIS_X }, + { GAMEPAD_AXIS_LEFTY, NUNCHUCK_AXIS_Y }, + ); + // clang-format on + // #endregion + + // #region Classic + enum ClassicAxis + { + CLASSIC_AXIS_LEFTX = + WPAD_CLASSIC_STICK_L_EMULATION_LEFT | WPAD_CLASSIC_STICK_R_EMULATION_RIGHT, + CLASSIC_AXIS_LEFTY = WPAD_CLASSIC_STICK_L_EMULATION_UP | WPAD_CLASSIC_STICK_L_EMULATION_DOWN, + CLASSIC_AXIS_RIGHTX = + WPAD_CLASSIC_STICK_R_EMULATION_LEFT | WPAD_CLASSIC_STICK_R_EMULATION_RIGHT, + CLASSIC_AXIS_RIGHTY = WPAD_CLASSIC_STICK_R_EMULATION_UP | WPAD_CLASSIC_STICK_R_EMULATION_DOWN + }; + + // clang-format off + ENUMMAP_DECLARE(ClassicButtons, GamepadButton, WPADClassicButton, + { GAMEPAD_BUTTON_A, WPAD_CLASSIC_BUTTON_A }, + { GAMEPAD_BUTTON_B, WPAD_CLASSIC_BUTTON_B }, + { GAMEPAD_BUTTON_X, WPAD_CLASSIC_BUTTON_X }, + { GAMEPAD_BUTTON_Y, WPAD_CLASSIC_BUTTON_Y }, + + { GAMEPAD_BUTTON_DPAD_LEFT, WPAD_CLASSIC_BUTTON_LEFT }, + { GAMEPAD_BUTTON_DPAD_UP, WPAD_CLASSIC_BUTTON_UP }, + { GAMEPAD_BUTTON_DPAD_RIGHT, WPAD_CLASSIC_BUTTON_RIGHT }, + { GAMEPAD_BUTTON_DPAD_DOWN, WPAD_CLASSIC_BUTTON_DOWN }, + + { GAMEPAD_BUTTON_START, WPAD_CLASSIC_BUTTON_PLUS }, + { GAMEPAD_BUTTON_BACK, WPAD_CLASSIC_BUTTON_MINUS }, + + { GAMEPAD_BUTTON_LEFTSHOULDER, WPAD_CLASSIC_BUTTON_L }, + { GAMEPAD_BUTTON_RIGHTSHOULDER, WPAD_CLASSIC_BUTTON_R } + ); + + ENUMMAP_DECLARE(ClassicAxes, GamepadAxis, ClassicAxis, + { GAMEPAD_AXIS_LEFTX, CLASSIC_AXIS_LEFTX }, + { GAMEPAD_AXIS_LEFTY, CLASSIC_AXIS_LEFTY }, + { GAMEPAD_AXIS_RIGHTX, CLASSIC_AXIS_RIGHTX }, + { GAMEPAD_AXIS_RIGHTY, CLASSIC_AXIS_RIGHTY } + ); + // clang-format on + // #endregion + + // #region Pro + enum ProAxis + { + PRO_AXIS_LEFTX = WPAD_PRO_STICK_L_EMULATION_LEFT | WPAD_PRO_STICK_L_EMULATION_RIGHT, + PRO_AXIS_LEFTY = WPAD_PRO_STICK_L_EMULATION_UP | WPAD_PRO_STICK_L_EMULATION_DOWN, + PRO_AXIS_RIGHTX = WPAD_PRO_STICK_R_EMULATION_LEFT | WPAD_PRO_STICK_R_EMULATION_RIGHT, + PRO_AXIS_RIGHTY = WPAD_PRO_STICK_R_EMULATION_UP | WPAD_PRO_STICK_R_EMULATION_DOWN + }; + + // clang-format off + ENUMMAP_DECLARE(ProButtons, GamepadButton, WPADProButton, + { GAMEPAD_BUTTON_A, WPAD_PRO_BUTTON_A }, + { GAMEPAD_BUTTON_B, WPAD_PRO_BUTTON_B }, + { GAMEPAD_BUTTON_X, WPAD_PRO_BUTTON_X }, + { GAMEPAD_BUTTON_Y, WPAD_PRO_BUTTON_Y }, + + { GAMEPAD_BUTTON_DPAD_LEFT, WPAD_PRO_BUTTON_LEFT }, + { GAMEPAD_BUTTON_DPAD_UP, WPAD_PRO_BUTTON_UP }, + { GAMEPAD_BUTTON_DPAD_RIGHT, WPAD_PRO_BUTTON_RIGHT }, + { GAMEPAD_BUTTON_DPAD_DOWN, WPAD_PRO_BUTTON_DOWN }, + + { GAMEPAD_BUTTON_START, WPAD_PRO_BUTTON_PLUS }, + { GAMEPAD_BUTTON_BACK, WPAD_PRO_BUTTON_MINUS }, + + { GAMEPAD_BUTTON_LEFTSHOULDER, WPAD_PRO_TRIGGER_L }, + { GAMEPAD_BUTTON_RIGHTSHOULDER, WPAD_PRO_TRIGGER_R }, + + { GAMEPAD_BUTTON_LEFTSTICK, WPAD_PRO_BUTTON_STICK_L }, + { GAMEPAD_BUTTON_RIGHTSTICK, WPAD_PRO_BUTTON_STICK_R } + ); + + ENUMMAP_DECLARE(ProAxes, GamepadAxis, ProAxis, + { GAMEPAD_AXIS_LEFTX, PRO_AXIS_LEFTX }, + { GAMEPAD_AXIS_LEFTY, PRO_AXIS_LEFTY }, + { GAMEPAD_AXIS_RIGHTX, PRO_AXIS_RIGHTX }, + { GAMEPAD_AXIS_RIGHTY, PRO_AXIS_RIGHTY } + ); + // clang-format on + // #endregion + + private: + template + bool isButtonDown(std::span buttons) const; + + template + bool isButtonUp(std::span buttons) const; + + template + bool isAxisValueChanged(GamepadAxis axis) const; + + KPADStatus status; + KPADError error; + + mutable struct State + { + uint32_t pressed; + uint32_t released; + uint32_t held; + } state; + }; + } // namespace kpad +} // namespace love diff --git a/platform/cafe/include/modules/joystick/vpad/Joystick.hpp b/platform/cafe/include/modules/joystick/vpad/Joystick.hpp new file mode 100644 index 000000000..a3fa4420e --- /dev/null +++ b/platform/cafe/include/modules/joystick/vpad/Joystick.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "modules/joystick/Joystick.tcc" + +#include + +namespace love +{ + namespace vpad + { + class Joystick : public JoystickBase + { + public: + Joystick(int id); + + Joystick(int id, int index); + + ~Joystick(); + + virtual void update(); + + VPADStatus& getVPADStatus() + { + return this->status; + } + + virtual bool open(int64_t deviceId) override; + + virtual void close() override; + + virtual bool isConnected() const override; + + virtual float getAxis(GamepadAxis axis) const override; + + virtual std::vector getAxes() const override; + + virtual bool isDown(std::span buttons) const override; + + virtual bool isUp(std::span buttons) const override; + + virtual bool isAxisChanged(GamepadAxis axis) const override; + + virtual void setPlayerIndex(int index) override; + + virtual int getPlayerIndex() const override; + + virtual JoystickInput getGamepadMapping(const GamepadInput& input) const override; + + virtual std::string getGamepadMappingString() const override; + + virtual bool isVibrationSupported() const override; + + virtual bool setVibration(float left, float right, float duration = -1.0f) override; + + virtual bool setVibration() override; + + virtual void getVibration(float& left, float& right) const override; + + virtual bool hasSensor(Sensor::SensorType type) const override; + + virtual bool isSensorEnabled(Sensor::SensorType type) const override; + + virtual void setSensorEnabled(Sensor::SensorType type, bool enable) override; + + virtual std::vector getSensorData(Sensor::SensorType type) const override; + + using JoystickBase::getConstant; + + enum VPADAxis + { + VPADAXIS_LEFTX = VPAD_STICK_L_EMULATION_LEFT | VPAD_STICK_L_EMULATION_RIGHT, + VPADAXIS_LEFTY = VPAD_STICK_L_EMULATION_UP | VPAD_STICK_L_EMULATION_DOWN, + VPADAXIS_RIGHTX = VPAD_STICK_R_EMULATION_LEFT | VPAD_STICK_R_EMULATION_RIGHT, + VPADAXIS_RIGHTY = VPAD_STICK_R_EMULATION_UP | VPAD_STICK_R_EMULATION_DOWN, + VPADAXIS_TRIGGERLEFT = VPAD_BUTTON_ZL, + VPADAXIS_TRIGGERRIGHT = VPAD_BUTTON_ZR + }; + + // clang-format off + ENUMMAP_DECLARE(VpadAxes, GamepadAxis, VPADAxis, + { GAMEPAD_AXIS_LEFTX, VPADAXIS_LEFTX }, + { GAMEPAD_AXIS_LEFTY, VPADAXIS_LEFTY }, + { GAMEPAD_AXIS_RIGHTX, VPADAXIS_RIGHTX }, + { GAMEPAD_AXIS_RIGHTY, VPADAXIS_RIGHTY }, + { GAMEPAD_AXIS_TRIGGERLEFT, VPADAXIS_TRIGGERLEFT }, + { GAMEPAD_AXIS_TRIGGERRIGHT, VPADAXIS_TRIGGERRIGHT } + ); + + ENUMMAP_DECLARE(VpadButtons, GamepadButton, VPADButtons, + { GAMEPAD_BUTTON_A, VPAD_BUTTON_A }, + { GAMEPAD_BUTTON_B, VPAD_BUTTON_B }, + { GAMEPAD_BUTTON_X, VPAD_BUTTON_X }, + { GAMEPAD_BUTTON_Y, VPAD_BUTTON_Y }, + + { GAMEPAD_BUTTON_DPAD_LEFT, VPAD_BUTTON_LEFT }, + { GAMEPAD_BUTTON_DPAD_UP, VPAD_BUTTON_UP }, + { GAMEPAD_BUTTON_DPAD_RIGHT, VPAD_BUTTON_RIGHT }, + { GAMEPAD_BUTTON_DPAD_DOWN, VPAD_BUTTON_DOWN }, + + { GAMEPAD_BUTTON_START, VPAD_BUTTON_PLUS }, + { GAMEPAD_BUTTON_BACK, VPAD_BUTTON_MINUS }, + { GAMEPAD_BUTTON_LEFTSHOULDER, VPAD_BUTTON_L }, + { GAMEPAD_BUTTON_RIGHTSHOULDER, VPAD_BUTTON_R }, + + { GAMEPAD_BUTTON_LEFTSTICK, VPAD_BUTTON_STICK_L }, + { GAMEPAD_BUTTON_RIGHTSTICK, VPAD_BUTTON_STICK_R } + ); + // clang-format on + + private: + VPADStatus status; + VPADReadError error; + + mutable struct State + { + uint32_t pressed; + uint32_t released; + uint32_t held; + } state; + }; + } // namespace vpad +} // namespace love diff --git a/platform/cafe/include/modules/system/System.hpp b/platform/cafe/include/modules/system/System.hpp index cd50c8d81..9414f4e4a 100644 --- a/platform/cafe/include/modules/system/System.hpp +++ b/platform/cafe/include/modules/system/System.hpp @@ -14,7 +14,7 @@ extern "C" { - typedef enum USCLanguage + enum USCLanguage { USCLanguage_JA = 0, USCLanguage_EN = 1, diff --git a/platform/cafe/libraries/luasocket.patch b/platform/cafe/libraries/luasocket.patch new file mode 100644 index 000000000..8d2a9fb65 --- /dev/null +++ b/platform/cafe/libraries/luasocket.patch @@ -0,0 +1,2109 @@ +diff --git a/inet.c b/inet.c +index 138c9ab..35b8131 100755 +--- a/inet.c ++++ b/inet.c +@@ -139,9 +139,13 @@ static int inet_global_toip(lua_State *L) + + int inet_optfamily(lua_State* L, int narg, const char* def) + { ++ #if !defined(__WIIU__) + static const char* optname[] = { "unspec", "inet", "inet6", NULL }; + static int optvalue[] = { AF_UNSPEC, AF_INET, AF_INET6, 0 }; +- ++ #else ++ static const char* optname[] = { "unspec", "inet", NULL }; ++ static int optvalue[] = { AF_UNSPEC, AF_INET, 0 }; ++ #endif + return optvalue[luaL_checkoption(L, narg, def, optname)]; + } + +@@ -187,11 +191,13 @@ static int inet_global_getaddrinfo(lua_State *L) + lua_pushliteral(L, "inet"); + lua_settable(L, -3); + break; ++ #if !defined(__WIIU__) + case AF_INET6: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "inet6"); + lua_settable(L, -3); + break; ++ #endif + case AF_UNSPEC: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "unspec"); +@@ -241,16 +247,26 @@ int inet_meth_getpeername(lua_State *L, p_socket ps, int family) + int err; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); ++ #if !defined(__WIIU__) + char name[INET6_ADDRSTRLEN]; ++ #else ++ char name[INET_ADDRSTRLEN]; ++ #endif + char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ + if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } ++ #if !defined(__WIIU__) + err = getnameinfo((struct sockaddr *) &peer, peer_len, + name, INET6_ADDRSTRLEN, + port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); ++ #else ++ err = getnameinfo((struct sockaddr *) &peer, peer_len, ++ name, INET_ADDRSTRLEN, ++ port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); ++ #endif + if (err) { + lua_pushnil(L); + lua_pushstring(L, LUA_GAI_STRERROR(err)); +@@ -260,7 +276,9 @@ int inet_meth_getpeername(lua_State *L, p_socket ps, int family) + lua_pushinteger(L, (int) strtol(port, (char **) NULL, 10)); + switch (family) { + case AF_INET: lua_pushliteral(L, "inet"); break; ++ #if !defined(__WIIU__) + case AF_INET6: lua_pushliteral(L, "inet6"); break; ++ #endif + case AF_UNSPEC: lua_pushliteral(L, "unspec"); break; + default: lua_pushliteral(L, "unknown"); break; + } +@@ -275,15 +293,24 @@ int inet_meth_getsockname(lua_State *L, p_socket ps, int family) + int err; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); ++ #if !defined(__WIIU__) + char name[INET6_ADDRSTRLEN]; ++ #else ++ char name[INET_ADDRSTRLEN]; ++ #endif + char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ + if (getsockname(*ps, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } ++ #if !defined(__WIIU__) + err=getnameinfo((struct sockaddr *)&peer, peer_len, + name, INET6_ADDRSTRLEN, port, 6, NI_NUMERICHOST | NI_NUMERICSERV); ++ #else ++ err=getnameinfo((struct sockaddr *)&peer, peer_len, ++ name, INET_ADDRSTRLEN, port, 6, NI_NUMERICHOST | NI_NUMERICSERV); ++ #endif + if (err) { + lua_pushnil(L); + lua_pushstring(L, LUA_GAI_STRERROR(err)); +@@ -293,7 +320,9 @@ int inet_meth_getsockname(lua_State *L, p_socket ps, int family) + lua_pushstring(L, port); + switch (family) { + case AF_INET: lua_pushliteral(L, "inet"); break; ++ #if !defined(__WIIU__) + case AF_INET6: lua_pushliteral(L, "inet6"); break; ++ #endif + case AF_UNSPEC: lua_pushliteral(L, "unspec"); break; + default: lua_pushliteral(L, "unknown"); break; + } +@@ -348,10 +377,12 @@ static void inet_pushresolved(lua_State *L, struct hostent *hp) + \*-------------------------------------------------------------------------*/ + const char *inet_trycreate(p_socket ps, int family, int type, int protocol) { + const char *err = socket_strerror(socket_create(ps, family, type, protocol)); ++ #if !defined(__WIIU__) + if (err == NULL && family == AF_INET6) { + int yes = 1; + setsockopt(*ps, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&yes, sizeof(yes)); + } ++ #endif + return err; + } + +@@ -369,6 +400,7 @@ const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm) + return socket_strerror(socket_connect(ps, (SA *) &sin, + sizeof(sin), tm)); + } ++ #if !defined(__WIIU__) + case AF_INET6: { + struct sockaddr_in6 sin6; + struct in6_addr addrany = IN6ADDR_ANY_INIT; +@@ -378,6 +410,7 @@ const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm) + return socket_strerror(socket_connect(ps, (SA *) &sin6, + sizeof(sin6), tm)); + } ++ #endif + } + return NULL; + } +@@ -436,7 +469,9 @@ const char *inet_tryaccept(p_socket server, int family, p_socket client, + socklen_t len; + t_sockaddr_storage addr; + switch (family) { ++ #if !defined(__WIIU__) + case AF_INET6: len = sizeof(struct sockaddr_in6); break; ++ #endif + case AF_INET: len = sizeof(struct sockaddr_in); break; + default: len = sizeof(addr); break; + } +diff --git a/makefile b/makefile +deleted file mode 100755 +index 06f4d19..0000000 +--- a/makefile ++++ /dev/null +@@ -1,461 +0,0 @@ +-# luasocket src/makefile +-# +-# Definitions in this section can be overriden on the command line or in the +-# environment. +-# +-# These are equivalent: +-# +-# export PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw +-# make +-# +-# and +-# +-# make PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw +- +-# PLAT: linux macosx win32 win64 mingw +-# platform to build for +-PLAT?=linux +- +-# LUAV: 5.1 5.2 5.3 5.4 +-# lua version to build against +-LUAV?=5.1 +- +-# MYCFLAGS: to be set by user if needed +-MYCFLAGS?= +- +-# MYLDFLAGS: to be set by user if needed +-MYLDFLAGS?= +- +-# DEBUG: NODEBUG DEBUG +-# debug mode causes luasocket to collect and returns timing information useful +-# for testing and debugging luasocket itself +-DEBUG?=NODEBUG +- +-# where lua headers are found for macosx builds +-# LUAINC_macosx: +-# /opt/local/include +-LUAINC_macosx_base?=/opt/local/include +-LUAINC_macosx?=$(LUAINC_macosx_base)/lua/$(LUAV) $(LUAINC_macosx_base)/lua$(LUAV) $(LUAINC_macosx_base)/lua-$(LUAV) +- +-# FIXME default should this default to fink or to macports? +-# What happens when more than one Lua version is installed? +-LUAPREFIX_macosx?=/opt/local +-CDIR_macosx?=lib/lua/$(LUAV) +-LDIR_macosx?=share/lua/$(LUAV) +- +-# LUAINC_linux: +-# /usr/include/lua$(LUAV) +-# /usr/local/include +-# /usr/local/include/lua$(LUAV) +-# where lua headers are found for linux builds +-LUAINC_linux_base?=/usr/include +-LUAINC_linux?=$(LUAINC_linux_base)/lua/$(LUAV) $(LUAINC_linux_base)/lua$(LUAV) +-LUAPREFIX_linux?=/usr/local +-CDIR_linux?=lib/lua/$(LUAV) +-LDIR_linux?=share/lua/$(LUAV) +- +-# LUAINC_freebsd: +-# /usr/local/include/lua$(LUAV) +-# where lua headers are found for freebsd builds +-LUAINC_freebsd_base?=/usr/local/include/ +-LUAINC_freebsd?=$(LUAINC_freebsd_base)/lua/$(LUAV) $(LUAINC_freebsd_base)/lua$(LUAV) +-LUAPREFIX_freebsd?=/usr/local/ +-CDIR_freebsd?=lib/lua/$(LUAV) +-LDIR_freebsd?=share/lua/$(LUAV) +- +-# where lua headers are found for mingw builds +-# LUAINC_mingw: +-# /opt/local/include +-LUAINC_mingw_base?=/usr/include +-LUAINC_mingw?=$(LUAINC_mingw_base)/lua/$(LUAV) $(LUAINC_mingw_base)/lua$(LUAV) +-LUALIB_mingw_base?=/usr/bin +-LUALIB_mingw?=$(LUALIB_mingw_base)/lua/$(LUAV)/lua$(subst .,,$(LUAV)).dll +-LUAPREFIX_mingw?=/usr +-CDIR_mingw?=lua/$(LUAV) +-LDIR_mingw?=lua/$(LUAV)/lua +- +- +-# LUAINC_win32: +-# LUALIB_win32: +-# where lua headers and libraries are found for win32 builds +-LUAPREFIX_win32?= +-LUAINC_win32?=$(LUAPREFIX_win32)/include/lua/$(LUAV) $(LUAPREFIX_win32)/include/lua$(LUAV) +-PLATFORM_win32?=Release +-CDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32) +-LDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32)/lua +-LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUAV)/$(PLATFORM_win32) +-LUALIBNAME_win32?=lua$(subst .,,$(LUAV)).lib +- +-# LUAINC_win64: +-# LUALIB_win64: +-# where lua headers and libraries are found for win64 builds +-LUAPREFIX_win64?= +-LUAINC_win64?=$(LUAPREFIX_win64)/include/lua/$(LUAV) $(LUAPREFIX_win64)/include/lua$(LUAV) +-PLATFORM_win64?=x64/Release +-CDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64) +-LDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64)/lua +-LUALIB_win64?=$(LUAPREFIX_win64)/lib/lua/$(LUAV)/$(PLATFORM_win64) +-LUALIBNAME_win64?=lua$(subst .,,$(LUAV)).lib +- +- +-# LUAINC_solaris: +-LUAINC_solaris_base?=/usr/include +-LUAINC_solaris?=$(LUAINC_solaris_base)/lua/$(LUAV) $(LUAINC_solaris_base)/lua$(LUAV) +-LUAPREFIX_solaris?=/usr/local +-CDIR_solaris?=lib/lua/$(LUAV) +-LDIR_solaris?=share/lua/$(LUAV) +- +-# prefix: /usr/local /usr /opt/local /sw +-# the top of the default install tree +-prefix?=$(LUAPREFIX_$(PLAT)) +- +-CDIR?=$(CDIR_$(PLAT)) +-LDIR?=$(LDIR_$(PLAT)) +- +-# DESTDIR: (no default) +-# used by package managers to install into a temporary destination +-DESTDIR?= +- +-#------ +-# Definitions below can be overridden on the make command line, but +-# shouldn't have to be. +- +- +-#------ +-# Install directories +-# +- +-INSTALL_DIR=install -d +-INSTALL_DATA=install -m644 +-INSTALL_EXEC=install +-INSTALL_TOP=$(DESTDIR)$(prefix) +- +-INSTALL_TOP_LDIR=$(INSTALL_TOP)/$(LDIR) +-INSTALL_TOP_CDIR=$(INSTALL_TOP)/$(CDIR) +- +-INSTALL_SOCKET_LDIR=$(INSTALL_TOP_LDIR)/socket +-INSTALL_SOCKET_CDIR=$(INSTALL_TOP_CDIR)/socket +-INSTALL_MIME_LDIR=$(INSTALL_TOP_LDIR)/mime +-INSTALL_MIME_CDIR=$(INSTALL_TOP_CDIR)/mime +- +-print: +- @echo PLAT=$(PLAT) +- @echo LUAV=$(LUAV) +- @echo DEBUG=$(DEBUG) +- @echo prefix=$(prefix) +- @echo LUAINC_$(PLAT)=$(LUAINC_$(PLAT)) +- @echo LUALIB_$(PLAT)=$(LUALIB_$(PLAT)) +- @echo INSTALL_TOP_CDIR=$(INSTALL_TOP_CDIR) +- @echo INSTALL_TOP_LDIR=$(INSTALL_TOP_LDIR) +- @echo CFLAGS=$(CFLAGS) +- @echo LDFLAGS=$(LDFLAGS) +- +-#------ +-# Supported platforms +-# +-PLATS= macosx linux win32 win64 mingw solaris +- +-#------ +-# Compiler and linker settings +-# for Mac OS X +-SO_macosx=so +-O_macosx=o +-CC_macosx=gcc +-DEF_macosx= -DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +-CFLAGS_macosx=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +-LDFLAGS_macosx= -bundle -undefined dynamic_lookup -o +-LD_macosx=gcc +-SOCKET_macosx=usocket.o +- +-#------ +-# Compiler and linker settings +-# for Linux +-SO_linux=so +-O_linux=o +-CC_linux=gcc +-DEF_linux=-DLUASOCKET_$(DEBUG) +-CFLAGS_linux=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_linux=-O -shared -fpic -o +-LD_linux=gcc +-SOCKET_linux=usocket.o +- +-#------ +-# Compiler and linker settings +-# for FreeBSD +-SO_freebsd=so +-O_freebsd=o +-CC_freebsd=gcc +-DEF_freebsd=-DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +-CFLAGS_freebsd=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_freebsd=-O -shared -fpic -o +-LD_freebsd=gcc +-SOCKET_freebsd=usocket.o +- +-#------ +-# Compiler and linker settings +-# for Solaris +-SO_solaris=so +-O_solaris=o +-CC_solaris=gcc +-DEF_solaris=-DLUASOCKET_$(DEBUG) +-CFLAGS_solaris=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_solaris=-lnsl -lsocket -lresolv -O -shared -fpic -o +-LD_solaris=gcc +-SOCKET_solaris=usocket.o +- +-#------ +-# Compiler and linker settings +-# for MingW +-SO_mingw=dll +-O_mingw=o +-CC_mingw=gcc +-DEF_mingw= -DLUASOCKET_$(DEBUG) \ +- -DWINVER=0x0501 +-CFLAGS_mingw=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +-LDFLAGS_mingw= $(LUALIB) -shared -Wl,-s -lws2_32 -o +-LD_mingw=gcc +-SOCKET_mingw=wsocket.o +- +- +-#------ +-# Compiler and linker settings +-# for Win32 +-SO_win32=dll +-O_win32=obj +-CC_win32=cl +-DEF_win32= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ +- //D "_CRT_SECURE_NO_WARNINGS" \ +- //D "_WINDLL" \ +- //D "LUASOCKET_$(DEBUG)" +-CFLAGS_win32=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +-LDFLAGS_win32= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ +- //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ +- /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ +- //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ +- //MACHINE:X86 /LIBPATH:"$(LUALIB)" \ +- $(LUALIBNAME_win32) ws2_32.lib //OUT: +- +-LD_win32=cl +-SOCKET_win32=wsocket.obj +- +-#------ +-# Compiler and linker settings +-# for Win64 +-SO_win64=dll +-O_win64=obj +-CC_win64=cl +-DEF_win64= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ +- //D "_CRT_SECURE_NO_WARNINGS" \ +- //D "_WINDLL" \ +- //D "LUASOCKET_$(DEBUG)" +-CFLAGS_win64=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +-LDFLAGS_win64= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ +- //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ +- /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ +- //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ +- /LIBPATH:"$(LUALIB)" \ +- $(LUALIBNAME_win64) ws2_32.lib //OUT: +- +-LD_win64=cl +-SOCKET_win64=wsocket.obj +- +-.SUFFIXES: .obj +- +-.c.obj: +- $(CC) $(CFLAGS) //Fo"$@" //c $< +- +-#------ +-# Output file names +-# +-SO=$(SO_$(PLAT)) +-O=$(O_$(PLAT)) +-SOCKET_V=3.0.0 +-MIME_V=1.0.3 +-SOCKET_SO=socket-$(SOCKET_V).$(SO) +-MIME_SO=mime-$(MIME_V).$(SO) +-UNIX_SO=unix.$(SO) +-SERIAL_SO=serial.$(SO) +-SOCKET=$(SOCKET_$(PLAT)) +- +-#------ +-# Settings selected for platform +-# +-CC=$(CC_$(PLAT)) +-DEF=$(DEF_$(PLAT)) +-CFLAGS=$(MYCFLAGS) $(CFLAGS_$(PLAT)) +-LDFLAGS=$(MYLDFLAGS) $(LDFLAGS_$(PLAT)) +-LD=$(LD_$(PLAT)) +-LUAINC= $(LUAINC_$(PLAT)) +-LUALIB= $(LUALIB_$(PLAT)) +- +-#------ +-# Modules belonging to socket-core +-# +-SOCKET_OBJS= \ +- luasocket.$(O) \ +- timeout.$(O) \ +- buffer.$(O) \ +- io.$(O) \ +- auxiliar.$(O) \ +- compat.$(O) \ +- options.$(O) \ +- inet.$(O) \ +- $(SOCKET) \ +- except.$(O) \ +- select.$(O) \ +- tcp.$(O) \ +- udp.$(O) +- +-#------ +-# Modules belonging mime-core +-# +-MIME_OBJS= \ +- mime.$(O) \ +- compat.$(O) +- +-#------ +-# Modules belonging unix (local domain sockets) +-# +-UNIX_OBJS=\ +- buffer.$(O) \ +- auxiliar.$(O) \ +- options.$(O) \ +- timeout.$(O) \ +- io.$(O) \ +- usocket.$(O) \ +- unixstream.$(O) \ +- unixdgram.$(O) \ +- compat.$(O) \ +- unix.$(O) +- +-#------ +-# Modules belonging to serial (device streams) +-# +-SERIAL_OBJS=\ +- buffer.$(O) \ +- compat.$(O) \ +- auxiliar.$(O) \ +- options.$(O) \ +- timeout.$(O) \ +- io.$(O) \ +- usocket.$(O) \ +- serial.$(O) +- +-#------ +-# Files to install +-# +-TO_SOCKET_LDIR= \ +- http.lua \ +- url.lua \ +- tp.lua \ +- ftp.lua \ +- headers.lua \ +- smtp.lua +- +-TO_TOP_LDIR= \ +- ltn12.lua \ +- socket.lua \ +- mime.lua +- +-#------ +-# Targets +-# +-default: $(PLAT) +- +- +-freebsd: +- $(MAKE) all-unix PLAT=freebsd +- +-macosx: +- $(MAKE) all-unix PLAT=macosx +- +-win32: +- $(MAKE) all PLAT=win32 +- +-win64: +- $(MAKE) all PLAT=win64 +- +-linux: +- $(MAKE) all-unix PLAT=linux +- +-mingw: +- $(MAKE) all PLAT=mingw +- +-solaris: +- $(MAKE) all-unix PLAT=solaris +- +-none: +- @echo "Please run" +- @echo " make PLATFORM" +- @echo "where PLATFORM is one of these:" +- @echo " $(PLATS)" +- +-all: $(SOCKET_SO) $(MIME_SO) +- +-$(SOCKET_SO): $(SOCKET_OBJS) +- $(LD) $(SOCKET_OBJS) $(LDFLAGS)$@ +- +-$(MIME_SO): $(MIME_OBJS) +- $(LD) $(MIME_OBJS) $(LDFLAGS)$@ +- +-all-unix: all $(UNIX_SO) $(SERIAL_SO) +- +-$(UNIX_SO): $(UNIX_OBJS) +- $(LD) $(UNIX_OBJS) $(LDFLAGS)$@ +- +-$(SERIAL_SO): $(SERIAL_OBJS) +- $(LD) $(SERIAL_OBJS) $(LDFLAGS)$@ +- +-install: +- $(INSTALL_DIR) $(INSTALL_TOP_LDIR) +- $(INSTALL_DATA) $(TO_TOP_LDIR) $(INSTALL_TOP_LDIR) +- $(INSTALL_DIR) $(INSTALL_SOCKET_LDIR) +- $(INSTALL_DATA) $(TO_SOCKET_LDIR) $(INSTALL_SOCKET_LDIR) +- $(INSTALL_DIR) $(INSTALL_SOCKET_CDIR) +- $(INSTALL_EXEC) $(SOCKET_SO) $(INSTALL_SOCKET_CDIR)/core.$(SO) +- $(INSTALL_DIR) $(INSTALL_MIME_CDIR) +- $(INSTALL_EXEC) $(MIME_SO) $(INSTALL_MIME_CDIR)/core.$(SO) +- +-install-unix: install +- $(INSTALL_EXEC) $(UNIX_SO) $(INSTALL_SOCKET_CDIR)/$(UNIX_SO) +- $(INSTALL_EXEC) $(SERIAL_SO) $(INSTALL_SOCKET_CDIR)/$(SERIAL_SO) +- +-local: +- $(MAKE) install INSTALL_TOP_CDIR=.. INSTALL_TOP_LDIR=.. +- +-clean: +- rm -f $(SOCKET_SO) $(SOCKET_OBJS) $(SERIAL_OBJS) +- rm -f $(MIME_SO) $(UNIX_SO) $(SERIAL_SO) $(MIME_OBJS) $(UNIX_OBJS) +- +-.PHONY: all $(PLATS) default clean echo none +- +-#------ +-# List of dependencies +-# +-compat.$(O): compat.c compat.h +-auxiliar.$(O): auxiliar.c auxiliar.h +-buffer.$(O): buffer.c buffer.h io.h timeout.h +-except.$(O): except.c except.h +-inet.$(O): inet.c inet.h socket.h io.h timeout.h usocket.h +-io.$(O): io.c io.h timeout.h +-luasocket.$(O): luasocket.c luasocket.h auxiliar.h except.h \ +- timeout.h buffer.h io.h inet.h socket.h usocket.h tcp.h \ +- udp.h select.h +-mime.$(O): mime.c mime.h +-options.$(O): options.c auxiliar.h options.h socket.h io.h \ +- timeout.h usocket.h inet.h +-select.$(O): select.c socket.h io.h timeout.h usocket.h select.h +-serial.$(O): serial.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- options.h unix.h buffer.h +-tcp.$(O): tcp.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- inet.h options.h tcp.h buffer.h +-timeout.$(O): timeout.c auxiliar.h timeout.h +-udp.$(O): udp.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- inet.h options.h udp.h +-unix.$(O): unix.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- options.h unix.h buffer.h +-usocket.$(O): usocket.c socket.h io.h timeout.h usocket.h +-wsocket.$(O): wsocket.c socket.h io.h timeout.h usocket.h +diff --git a/options.c b/options.c +index 3280c51..ce8fcb4 100644 +--- a/options.c ++++ b/options.c +@@ -22,6 +22,22 @@ static int opt_set(lua_State *L, p_socket ps, int level, int name, + static int opt_get(lua_State *L, p_socket ps, int level, int name, + void *val, int* len); + ++static int set_opt_error(lua_State* L) ++{ ++ lua_pushnil(L); ++ lua_pushstring(L, "setsockopt failed: not supported"); ++ ++ return 2; ++} ++ ++static int get_opt_error(lua_State* L) ++{ ++ lua_pushnil(L); ++ lua_pushstring(L, "getsockopt failed: not supported"); ++ ++ return 2; ++} ++ + /*=========================================================================*\ + * Exported functions + \*=========================================================================*/ +@@ -147,6 +163,7 @@ int opt_get_keepalive(lua_State *L, p_socket ps) + } + + /*------------------------------------------------------*/ ++#if !defined(__WIIU__) + int opt_set_dontroute(lua_State *L, p_socket ps) + { + return opt_setboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); +@@ -156,6 +173,10 @@ int opt_get_dontroute(lua_State *L, p_socket ps) + { + return opt_getboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); + } ++#else ++int opt_set_dontroute(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_dontroute(lua_State *L, p_socket ps) { return get_opt_error(L); } ++#endif + + /*------------------------------------------------------*/ + int opt_set_broadcast(lua_State *L, p_socket ps) +@@ -216,6 +237,7 @@ int opt_set_tcp_defer_accept(lua_State *L, p_socket ps) + #endif + + /*------------------------------------------------------*/ ++#if !defined(__WIIU__) + int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) + { + return opt_setint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); +@@ -236,7 +258,6 @@ int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) + { + return opt_getint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); + } +- + /*------------------------------------------------------*/ + int opt_set_ip_multicast_loop(lua_State *L, p_socket ps) + { +@@ -247,8 +268,19 @@ int opt_get_ip_multicast_loop(lua_State *L, p_socket ps) + { + return opt_getboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); + } ++#else ++int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps) { return get_opt_error(L); } ++ ++int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) { return get_opt_error(L); } ++ ++int opt_set_ip_multicast_loop(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip_multicast_loop(lua_State *L, p_socket ps) { return get_opt_error(L); } ++#endif + + /*------------------------------------------------------*/ ++#if !defined(__WIIU__) + int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) + { + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); +@@ -258,7 +290,10 @@ int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) + { + return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); + } +- ++#else ++int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) { return get_opt_error(L); } ++#endif + /*------------------------------------------------------*/ + int opt_set_linger(lua_State *L, p_socket ps) + { +@@ -293,6 +328,7 @@ int opt_get_linger(lua_State *L, p_socket ps) + } + + /*------------------------------------------------------*/ ++#if !defined(__WIIU__) + int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) + { + return opt_setint(L, ps, IPPROTO_IP, IP_MULTICAST_TTL); +@@ -355,6 +391,21 @@ int opt_set_ip6_v6only(lua_State *L, p_socket ps) + { + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); + } ++#else ++int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) { return set_opt_error(L); } ++ ++int opt_set_ip_multicast_if(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip_multicast_if(lua_State *L, p_socket ps) { return get_opt_error(L); } ++ ++int opt_set_ip_add_membership(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_set_ip_drop_membersip(lua_State *L, p_socket ps) { return set_opt_error(L); } ++ ++int opt_set_ip6_add_membership(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps) { return set_opt_error(L); } ++ ++int opt_get_ip6_v6only(lua_State *L, p_socket ps) { return get_opt_error(L); } ++int opt_set_ip6_v6only(lua_State *L, p_socket ps) { return set_opt_error(L); } ++#endif + + /*------------------------------------------------------*/ + int opt_get_error(lua_State *L, p_socket ps) +@@ -373,6 +424,7 @@ int opt_get_error(lua_State *L, p_socket ps) + /*=========================================================================*\ + * Auxiliar functions + \*=========================================================================*/ ++#if !defined(__WIIU__) + static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) + { + struct ip_mreq val; /* obj, name, table */ +@@ -419,6 +471,10 @@ static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) + } + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); + } ++#else ++static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) { return set_opt_error(L); } ++static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) { return set_opt_error(L); } ++#endif + + static + int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len) +diff --git a/serial.c b/serial.c +deleted file mode 100644 +index 21485d3..0000000 +--- a/serial.c ++++ /dev/null +@@ -1,171 +0,0 @@ +-/*=========================================================================*\ +-* Serial stream +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "auxiliar.h" +-#include "socket.h" +-#include "options.h" +-#include "unix.h" +- +-#include +-#include +- +-/* +-Reuses userdata definition from unix.h, since it is useful for all +-stream-like objects. +- +-If we stored the serial path for use in error messages or userdata +-printing, we might need our own userdata definition. +- +-Group usage is semi-inherited from unix.c, but unnecessary since we +-have only one object type. +-*/ +- +-/*=========================================================================*\ +-* Internal function prototypes +-\*=========================================================================*/ +-static int global_create(lua_State *L); +-static int meth_send(lua_State *L); +-static int meth_receive(lua_State *L); +-static int meth_close(lua_State *L); +-static int meth_settimeout(lua_State *L); +-static int meth_getfd(lua_State *L); +-static int meth_setfd(lua_State *L); +-static int meth_dirty(lua_State *L); +-static int meth_getstats(lua_State *L); +-static int meth_setstats(lua_State *L); +- +-/* serial object methods */ +-static luaL_Reg serial_methods[] = { +- {"__gc", meth_close}, +- {"__tostring", auxiliar_tostring}, +- {"close", meth_close}, +- {"dirty", meth_dirty}, +- {"getfd", meth_getfd}, +- {"getstats", meth_getstats}, +- {"setstats", meth_setstats}, +- {"receive", meth_receive}, +- {"send", meth_send}, +- {"setfd", meth_setfd}, +- {"settimeout", meth_settimeout}, +- {NULL, NULL} +-}; +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-LUASOCKET_API int luaopen_socket_serial(lua_State *L) { +- /* create classes */ +- auxiliar_newclass(L, "serial{client}", serial_methods); +- /* create class groups */ +- auxiliar_add2group(L, "serial{client}", "serial{any}"); +- lua_pushcfunction(L, global_create); +- return 1; +-} +- +-/*=========================================================================*\ +-* Lua methods +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Just call buffered IO methods +-\*-------------------------------------------------------------------------*/ +-static int meth_send(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_send(L, &un->buf); +-} +- +-static int meth_receive(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_receive(L, &un->buf); +-} +- +-static int meth_getstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_getstats(L, &un->buf); +-} +- +-static int meth_setstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_setstats(L, &un->buf); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select support methods +-\*-------------------------------------------------------------------------*/ +-static int meth_getfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- lua_pushnumber(L, (int) un->sock); +- return 1; +-} +- +-/* this is very dangerous, but can be handy for those that are brave enough */ +-static int meth_setfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- un->sock = (t_socket) luaL_checknumber(L, 2); +- return 0; +-} +- +-static int meth_dirty(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- lua_pushboolean(L, !buffer_isempty(&un->buf)); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Closes socket used by object +-\*-------------------------------------------------------------------------*/ +-static int meth_close(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- socket_destroy(&un->sock); +- lua_pushnumber(L, 1); +- return 1; +-} +- +- +-/*-------------------------------------------------------------------------*\ +-* Just call tm methods +-\*-------------------------------------------------------------------------*/ +-static int meth_settimeout(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- return timeout_meth_settimeout(L, &un->tm); +-} +- +-/*=========================================================================*\ +-* Library functions +-\*=========================================================================*/ +- +- +-/*-------------------------------------------------------------------------*\ +-* Creates a serial object +-\*-------------------------------------------------------------------------*/ +-static int global_create(lua_State *L) { +- const char* path = luaL_checkstring(L, 1); +- +- /* allocate unix object */ +- p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- +- /* open serial device */ +- t_socket sock = open(path, O_NOCTTY|O_RDWR); +- +- /*printf("open %s on %d\n", path, sock);*/ +- +- if (sock < 0) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(errno)); +- lua_pushnumber(L, errno); +- return 3; +- } +- /* set its type as client object */ +- auxiliar_setclass(L, "serial{client}", -1); +- /* initialize remaining structure fields */ +- socket_setnonblocking(&sock); +- un->sock = sock; +- io_init(&un->io, (p_send) socket_write, (p_recv) socket_read, +- (p_error) socket_ioerror, &un->sock); +- timeout_init(&un->tm, -1, -1); +- buffer_init(&un->buf, &un->io, &un->tm); +- return 1; +-} +diff --git a/tcp.c b/tcp.c +index e84db84..d718a85 100644 +--- a/tcp.c ++++ b/tcp.c +@@ -312,6 +312,7 @@ static int meth_close(lua_State *L) + static int meth_getfamily(lua_State *L) + { + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); ++ #if !defined(__WIIU__) + if (tcp->family == AF_INET6) { + lua_pushliteral(L, "inet6"); + return 1; +@@ -322,6 +323,15 @@ static int meth_getfamily(lua_State *L) + lua_pushliteral(L, "inet4"); + return 1; + } ++ #else ++ if (tcp->family == AF_INET) { ++ lua_pushliteral(L, "inet4"); ++ return 1; ++ } else { ++ lua_pushliteral(L, "inet4"); ++ return 1; ++ } ++ #endif + } + + /*-------------------------------------------------------------------------*\ +@@ -427,9 +437,18 @@ static int global_create4(lua_State *L) { + return tcp_create(L, AF_INET); + } + ++#if !defined(__WIIU__) + static int global_create6(lua_State *L) { + return tcp_create(L, AF_INET6); + } ++#else ++static int global_create6(lua_State *L) { ++ lua_pushnil(L); ++ lua_pushstring(L, "Setting local interface error: not supported"); ++ ++ return 2; ++} ++#endif + + static int global_connect(lua_State *L) { + const char *remoteaddr = luaL_checkstring(L, 1); +diff --git a/udp.c b/udp.c +index 712ad50..7262c8e 100755 +--- a/udp.c ++++ b/udp.c +@@ -267,7 +267,11 @@ static int meth_receivefrom(lua_State *L) { + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); ++ #if !defined(__WIIU__) + char addrstr[INET6_ADDRSTRLEN]; ++ #else ++ char addrstr[INET_ADDRSTRLEN]; ++ #endif + char portstr[6]; + int err; + p_timeout tm = &udp->tm; +@@ -286,8 +290,13 @@ static int meth_receivefrom(lua_State *L) { + if (wanted > sizeof(buf)) free(dgram); + return 2; + } ++ #if !defined(__WIIU__) + err = getnameinfo((struct sockaddr *)&addr, addr_len, addrstr, + INET6_ADDRSTRLEN, portstr, 6, NI_NUMERICHOST | NI_NUMERICSERV); ++ #else ++ err = getnameinfo((struct sockaddr *)&addr, addr_len, addrstr, ++ INET_ADDRSTRLEN, portstr, 6, NI_NUMERICHOST | NI_NUMERICSERV); ++ #endif + if (err) { + lua_pushnil(L); + lua_pushstring(L, LUA_GAI_STRERROR(err)); +@@ -306,6 +315,7 @@ static int meth_receivefrom(lua_State *L) { + \*-------------------------------------------------------------------------*/ + static int meth_getfamily(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); ++ #if !defined(__WIIU__) + if (udp->family == AF_INET6) { + lua_pushliteral(L, "inet6"); + return 1; +@@ -313,6 +323,10 @@ static int meth_getfamily(lua_State *L) { + lua_pushliteral(L, "inet4"); + return 1; + } ++ #else ++ lua_pushliteral(L, "inet4"); ++ return 1; ++ #endif + } + + /*-------------------------------------------------------------------------*\ +@@ -483,6 +497,15 @@ static int global_create4(lua_State *L) { + return udp_create(L, AF_INET); + } + ++#if !defined(__WIIU__) + static int global_create6(lua_State *L) { + return udp_create(L, AF_INET6); + } ++#else ++static int global_create6(lua_State *L) { ++ lua_pushnil(L); ++ lua_pushstring(L, "Setting local interface error: not supported"); ++ ++ return 2; ++} ++#endif +diff --git a/unix.c b/unix.c +deleted file mode 100644 +index 268d8b2..0000000 +--- a/unix.c ++++ /dev/null +@@ -1,69 +0,0 @@ +-/*=========================================================================*\ +-* Unix domain socket +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "unixstream.h" +-#include "unixdgram.h" +- +-/*-------------------------------------------------------------------------*\ +-* Modules and functions +-\*-------------------------------------------------------------------------*/ +-static const luaL_Reg mod[] = { +- {"stream", unixstream_open}, +- {"dgram", unixdgram_open}, +- {NULL, NULL} +-}; +- +-static void add_alias(lua_State *L, int index, const char *name, const char *target) +-{ +- lua_getfield(L, index, target); +- lua_setfield(L, index, name); +-} +- +-static int compat_socket_unix_call(lua_State *L) +-{ +- /* Look up socket.unix.stream in the socket.unix table (which is the first +- * argument). */ +- lua_getfield(L, 1, "stream"); +- +- /* Replace the stack entry for the socket.unix table with the +- * socket.unix.stream function. */ +- lua_replace(L, 1); +- +- /* Call socket.unix.stream, passing along any arguments. */ +- int n = lua_gettop(L); +- lua_call(L, n-1, LUA_MULTRET); +- +- /* Pass along the return values from socket.unix.stream. */ +- n = lua_gettop(L); +- return n; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-LUASOCKET_API int luaopen_socket_unix(lua_State *L) +-{ +- int i; +- lua_newtable(L); +- int socket_unix_table = lua_gettop(L); +- +- for (i = 0; mod[i].name; i++) +- mod[i].func(L); +- +- /* Add backwards compatibility aliases "tcp" and "udp" for the "stream" and +- * "dgram" functions. */ +- add_alias(L, socket_unix_table, "tcp", "stream"); +- add_alias(L, socket_unix_table, "udp", "dgram"); +- +- /* Add a backwards compatibility function and a metatable setup to call it +- * for the old socket.unix() interface. */ +- lua_pushcfunction(L, compat_socket_unix_call); +- lua_setfield(L, socket_unix_table, "__call"); +- lua_pushvalue(L, socket_unix_table); +- lua_setmetatable(L, socket_unix_table); +- +- return 1; +-} +diff --git a/unix.h b/unix.h +deleted file mode 100644 +index c203561..0000000 +--- a/unix.h ++++ /dev/null +@@ -1,26 +0,0 @@ +-#ifndef UNIX_H +-#define UNIX_H +-/*=========================================================================*\ +-* Unix domain object +-* LuaSocket toolkit +-* +-* This module is just an example of how to extend LuaSocket with a new +-* domain. +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "buffer.h" +-#include "timeout.h" +-#include "socket.h" +- +-typedef struct t_unix_ { +- t_socket sock; +- t_io io; +- t_buffer buf; +- t_timeout tm; +-} t_unix; +-typedef t_unix *p_unix; +- +-LUASOCKET_API int luaopen_socket_unix(lua_State *L); +- +-#endif /* UNIX_H */ +diff --git a/unixdgram.h b/unixdgram.h +deleted file mode 100644 +index a1a0166..0000000 +--- a/unixdgram.h ++++ /dev/null +@@ -1,28 +0,0 @@ +-#ifndef UNIXDGRAM_H +-#define UNIXDGRAM_H +-/*=========================================================================*\ +-* DGRAM object +-* LuaSocket toolkit +-* +-* The dgram.h module provides LuaSocket with support for DGRAM protocol +-* (AF_INET, SOCK_DGRAM). +-* +-* Two classes are defined: connected and unconnected. DGRAM objects are +-* originally unconnected. They can be "connected" to a given address +-* with a call to the setpeername function. The same function can be used to +-* break the connection. +-\*=========================================================================*/ +- +-#include "unix.h" +- +-#ifndef _WIN32 +-#pragma GCC visibility push(hidden) +-#endif +- +-int unixdgram_open(lua_State *L); +- +-#ifndef _WIN32 +-#pragma GCC visibility pop +-#endif +- +-#endif /* UNIXDGRAM_H */ +diff --git a/unixstream.c b/unixstream.c +deleted file mode 100644 +index 02aced9..0000000 +--- a/unixstream.c ++++ /dev/null +@@ -1,355 +0,0 @@ +-/*=========================================================================*\ +-* Unix domain socket stream sub module +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "auxiliar.h" +-#include "socket.h" +-#include "options.h" +-#include "unixstream.h" +- +-#include +-#include +- +-/*=========================================================================*\ +-* Internal function prototypes +-\*=========================================================================*/ +-static int global_create(lua_State *L); +-static int meth_connect(lua_State *L); +-static int meth_listen(lua_State *L); +-static int meth_bind(lua_State *L); +-static int meth_send(lua_State *L); +-static int meth_shutdown(lua_State *L); +-static int meth_receive(lua_State *L); +-static int meth_accept(lua_State *L); +-static int meth_close(lua_State *L); +-static int meth_setoption(lua_State *L); +-static int meth_settimeout(lua_State *L); +-static int meth_getfd(lua_State *L); +-static int meth_setfd(lua_State *L); +-static int meth_dirty(lua_State *L); +-static int meth_getstats(lua_State *L); +-static int meth_setstats(lua_State *L); +-static int meth_getsockname(lua_State *L); +- +-static const char *unixstream_tryconnect(p_unix un, const char *path); +-static const char *unixstream_trybind(p_unix un, const char *path); +- +-/* unixstream object methods */ +-static luaL_Reg unixstream_methods[] = { +- {"__gc", meth_close}, +- {"__tostring", auxiliar_tostring}, +- {"accept", meth_accept}, +- {"bind", meth_bind}, +- {"close", meth_close}, +- {"connect", meth_connect}, +- {"dirty", meth_dirty}, +- {"getfd", meth_getfd}, +- {"getstats", meth_getstats}, +- {"setstats", meth_setstats}, +- {"listen", meth_listen}, +- {"receive", meth_receive}, +- {"send", meth_send}, +- {"setfd", meth_setfd}, +- {"setoption", meth_setoption}, +- {"setpeername", meth_connect}, +- {"setsockname", meth_bind}, +- {"getsockname", meth_getsockname}, +- {"settimeout", meth_settimeout}, +- {"shutdown", meth_shutdown}, +- {NULL, NULL} +-}; +- +-/* socket option handlers */ +-static t_opt optset[] = { +- {"keepalive", opt_set_keepalive}, +- {"reuseaddr", opt_set_reuseaddr}, +- {"linger", opt_set_linger}, +- {NULL, NULL} +-}; +- +-/* functions in library namespace */ +-static luaL_Reg func[] = { +- {"stream", global_create}, +- {NULL, NULL} +-}; +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-int unixstream_open(lua_State *L) +-{ +- /* create classes */ +- auxiliar_newclass(L, "unixstream{master}", unixstream_methods); +- auxiliar_newclass(L, "unixstream{client}", unixstream_methods); +- auxiliar_newclass(L, "unixstream{server}", unixstream_methods); +- +- /* create class groups */ +- auxiliar_add2group(L, "unixstream{master}", "unixstream{any}"); +- auxiliar_add2group(L, "unixstream{client}", "unixstream{any}"); +- auxiliar_add2group(L, "unixstream{server}", "unixstream{any}"); +- +- luaL_setfuncs(L, func, 0); +- return 0; +-} +- +-/*=========================================================================*\ +-* Lua methods +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Just call buffered IO methods +-\*-------------------------------------------------------------------------*/ +-static int meth_send(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_send(L, &un->buf); +-} +- +-static int meth_receive(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_receive(L, &un->buf); +-} +- +-static int meth_getstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_getstats(L, &un->buf); +-} +- +-static int meth_setstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_setstats(L, &un->buf); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call option handler +-\*-------------------------------------------------------------------------*/ +-static int meth_setoption(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- return opt_meth_setoption(L, optset, &un->sock); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select support methods +-\*-------------------------------------------------------------------------*/ +-static int meth_getfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- lua_pushnumber(L, (int) un->sock); +- return 1; +-} +- +-/* this is very dangerous, but can be handy for those that are brave enough */ +-static int meth_setfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- un->sock = (t_socket) luaL_checknumber(L, 2); +- return 0; +-} +- +-static int meth_dirty(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- lua_pushboolean(L, !buffer_isempty(&un->buf)); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Waits for and returns a client object attempting connection to the +-* server object +-\*-------------------------------------------------------------------------*/ +-static int meth_accept(lua_State *L) { +- p_unix server = (p_unix) auxiliar_checkclass(L, "unixstream{server}", 1); +- p_timeout tm = timeout_markstart(&server->tm); +- t_socket sock; +- int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); +- /* if successful, push client socket */ +- if (err == IO_DONE) { +- p_unix clnt = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- auxiliar_setclass(L, "unixstream{client}", -1); +- /* initialize structure fields */ +- socket_setnonblocking(&sock); +- clnt->sock = sock; +- io_init(&clnt->io, (p_send)socket_send, (p_recv)socket_recv, +- (p_error) socket_ioerror, &clnt->sock); +- timeout_init(&clnt->tm, -1, -1); +- buffer_init(&clnt->buf, &clnt->io, &clnt->tm); +- return 1; +- } else { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Binds an object to an address +-\*-------------------------------------------------------------------------*/ +-static const char *unixstream_trybind(p_unix un, const char *path) { +- struct sockaddr_un local; +- size_t len = strlen(path); +- int err; +- if (len >= sizeof(local.sun_path)) return "path too long"; +- memset(&local, 0, sizeof(local)); +- strcpy(local.sun_path, path); +- local.sun_family = AF_UNIX; +-#ifdef UNIX_HAS_SUN_LEN +- local.sun_len = sizeof(local.sun_family) + sizeof(local.sun_len) +- + len + 1; +- err = socket_bind(&un->sock, (SA *) &local, local.sun_len); +- +-#else +- err = socket_bind(&un->sock, (SA *) &local, +- sizeof(local.sun_family) + len); +-#endif +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_bind(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixstream_trybind(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- lua_pushnumber(L, 1); +- return 1; +-} +- +-static int meth_getsockname(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- struct sockaddr_un peer = {0}; +- socklen_t peer_len = sizeof(peer); +- +- if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(errno)); +- return 2; +- } +- +- lua_pushstring(L, peer.sun_path); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Turns a master unixstream object into a client object. +-\*-------------------------------------------------------------------------*/ +-static const char *unixstream_tryconnect(p_unix un, const char *path) +-{ +- struct sockaddr_un remote; +- int err; +- size_t len = strlen(path); +- if (len >= sizeof(remote.sun_path)) return "path too long"; +- memset(&remote, 0, sizeof(remote)); +- strcpy(remote.sun_path, path); +- remote.sun_family = AF_UNIX; +- timeout_markstart(&un->tm); +-#ifdef UNIX_HAS_SUN_LEN +- remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) +- + len + 1; +- err = socket_connect(&un->sock, (SA *) &remote, remote.sun_len, &un->tm); +-#else +- err = socket_connect(&un->sock, (SA *) &remote, +- sizeof(remote.sun_family) + len, &un->tm); +-#endif +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_connect(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixstream_tryconnect(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- /* turn master object into a client object */ +- auxiliar_setclass(L, "unixstream{client}", 1); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Closes socket used by object +-\*-------------------------------------------------------------------------*/ +-static int meth_close(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- socket_destroy(&un->sock); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Puts the sockt in listen mode +-\*-------------------------------------------------------------------------*/ +-static int meth_listen(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- int backlog = (int) luaL_optnumber(L, 2, 32); +- int err = socket_listen(&un->sock, backlog); +- if (err != IO_DONE) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +- /* turn master object into a server object */ +- auxiliar_setclass(L, "unixstream{server}", 1); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Shuts the connection down partially +-\*-------------------------------------------------------------------------*/ +-static int meth_shutdown(lua_State *L) +-{ +- /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ +- static const char* methods[] = { "receive", "send", "both", NULL }; +- p_unix stream = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- int how = luaL_checkoption(L, 2, "both", methods); +- socket_shutdown(&stream->sock, how); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call tm methods +-\*-------------------------------------------------------------------------*/ +-static int meth_settimeout(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- return timeout_meth_settimeout(L, &un->tm); +-} +- +-/*=========================================================================*\ +-* Library functions +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Creates a master unixstream object +-\*-------------------------------------------------------------------------*/ +-static int global_create(lua_State *L) { +- t_socket sock; +- int err = socket_create(&sock, AF_UNIX, SOCK_STREAM, 0); +- /* try to allocate a system socket */ +- if (err == IO_DONE) { +- /* allocate unixstream object */ +- p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- /* set its type as master object */ +- auxiliar_setclass(L, "unixstream{master}", -1); +- /* initialize remaining structure fields */ +- socket_setnonblocking(&sock); +- un->sock = sock; +- io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, +- (p_error) socket_ioerror, &un->sock); +- timeout_init(&un->tm, -1, -1); +- buffer_init(&un->buf, &un->io, &un->tm); +- return 1; +- } else { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +-} +diff --git a/unixstream.h b/unixstream.h +deleted file mode 100644 +index 7916aff..0000000 +--- a/unixstream.h ++++ /dev/null +@@ -1,29 +0,0 @@ +-#ifndef UNIXSTREAM_H +-#define UNIXSTREAM_H +-/*=========================================================================*\ +-* UNIX STREAM object +-* LuaSocket toolkit +-* +-* The unixstream.h module is basicly a glue that puts together modules buffer.h, +-* timeout.h socket.h and inet.h to provide the LuaSocket UNIX STREAM (AF_UNIX, +-* SOCK_STREAM) support. +-* +-* Three classes are defined: master, client and server. The master class is +-* a newly created unixstream object, that has not been bound or connected. Server +-* objects are unixstream objects bound to some local address. Client objects are +-* unixstream objects either connected to some address or returned by the accept +-* method of a server object. +-\*=========================================================================*/ +-#include "unix.h" +- +-#ifndef _WIN32 +-#pragma GCC visibility push(hidden) +-#endif +- +-int unixstream_open(lua_State *L); +- +-#ifndef _WIN32 +-#pragma GCC visibility pop +-#endif +- +-#endif /* UNIXSTREAM_H */ +diff --git a/usocket.c b/usocket.c +index 69635da..ecf6d2a 100644 +--- a/usocket.c ++++ b/usocket.c +@@ -18,7 +18,7 @@ + * Wait for readable/writable/connected socket with timeout + \*-------------------------------------------------------------------------*/ + #ifndef SOCKET_SELECT +-#include ++#include + + #define WAITFD_R POLLIN + #define WAITFD_W POLLOUT +@@ -236,7 +236,7 @@ int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, + *sent = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { +- long put = (long) sendto(*ps, data, count, 0, addr, len); ++ long put = (long) sendto(*ps, data, count, 0, addr, len); + if (put >= 0) { + *sent = put; + return IO_DONE; +@@ -403,7 +403,15 @@ const char *socket_hoststrerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case HOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; ++ #if !defined(__WIIU__) + default: return hstrerror(err); ++ #else ++ case TRY_AGAIN: return "Host name lookup failure"; ++ case NO_RECOVERY: return "Unknown server error"; ++ case NO_ADDRESS: ++ return "No address associated with name"; ++ ++ #endif + } + } + +diff --git a/usocket.h b/usocket.h +index 45f2f99..12c25ca 100644 +--- a/usocket.h ++++ b/usocket.h +@@ -29,7 +29,6 @@ + #include + /* TCP options (nagle algorithm disable) */ + #include +-#include + + #ifndef SO_REUSEPORT + #define SO_REUSEPORT SO_REUSEADDR +diff --git a/wsocket.c b/wsocket.c +deleted file mode 100755 +index 6cb1e41..0000000 +--- a/wsocket.c ++++ /dev/null +@@ -1,434 +0,0 @@ +-/*=========================================================================*\ +-* Socket compatibilization module for Win32 +-* LuaSocket toolkit +-* +-* The penalty of calling select to avoid busy-wait is only paid when +-* the I/O call fail in the first place. +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include +- +-#include "socket.h" +-#include "pierror.h" +- +-/* WinSock doesn't have a strerror... */ +-static const char *wstrerror(int err); +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-int socket_open(void) { +- WSADATA wsaData; +- WORD wVersionRequested = MAKEWORD(2, 0); +- int err = WSAStartup(wVersionRequested, &wsaData ); +- if (err != 0) return 0; +- if ((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) && +- (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)) { +- WSACleanup(); +- return 0; +- } +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Close module +-\*-------------------------------------------------------------------------*/ +-int socket_close(void) { +- WSACleanup(); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Wait for readable/writable/connected socket with timeout +-\*-------------------------------------------------------------------------*/ +-#define WAITFD_R 1 +-#define WAITFD_W 2 +-#define WAITFD_E 4 +-#define WAITFD_C (WAITFD_E|WAITFD_W) +- +-int socket_waitfd(p_socket ps, int sw, p_timeout tm) { +- int ret; +- fd_set rfds, wfds, efds, *rp = NULL, *wp = NULL, *ep = NULL; +- struct timeval tv, *tp = NULL; +- double t; +- if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ +- if (sw & WAITFD_R) { +- FD_ZERO(&rfds); +- FD_SET(*ps, &rfds); +- rp = &rfds; +- } +- if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } +- if (sw & WAITFD_C) { FD_ZERO(&efds); FD_SET(*ps, &efds); ep = &efds; } +- if ((t = timeout_get(tm)) >= 0.0) { +- tv.tv_sec = (int) t; +- tv.tv_usec = (int) ((t-tv.tv_sec)*1.0e6); +- tp = &tv; +- } +- ret = select(0, rp, wp, ep, tp); +- if (ret == -1) return WSAGetLastError(); +- if (ret == 0) return IO_TIMEOUT; +- if (sw == WAITFD_C && FD_ISSET(*ps, &efds)) return IO_CLOSED; +- return IO_DONE; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select with int timeout in ms +-\*-------------------------------------------------------------------------*/ +-int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, +- p_timeout tm) { +- struct timeval tv; +- double t = timeout_get(tm); +- tv.tv_sec = (int) t; +- tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); +- if (n <= 0) { +- Sleep((DWORD) (1000*t)); +- return 0; +- } else return select(0, rfds, wfds, efds, t >= 0.0? &tv: NULL); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Close and inutilize socket +-\*-------------------------------------------------------------------------*/ +-void socket_destroy(p_socket ps) { +- if (*ps != SOCKET_INVALID) { +- socket_setblocking(ps); /* close can take a long time on WIN32 */ +- closesocket(*ps); +- *ps = SOCKET_INVALID; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* +-\*-------------------------------------------------------------------------*/ +-void socket_shutdown(p_socket ps, int how) { +- socket_setblocking(ps); +- shutdown(*ps, how); +- socket_setnonblocking(ps); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Creates and sets up a socket +-\*-------------------------------------------------------------------------*/ +-int socket_create(p_socket ps, int domain, int type, int protocol) { +- *ps = socket(domain, type, protocol); +- if (*ps != SOCKET_INVALID) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Connects or returns error message +-\*-------------------------------------------------------------------------*/ +-int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { +- int err; +- /* don't call on closed socket */ +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- /* ask system to connect */ +- if (connect(*ps, addr, len) == 0) return IO_DONE; +- /* make sure the system is trying to connect */ +- err = WSAGetLastError(); +- if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return err; +- /* zero timeout case optimization */ +- if (timeout_iszero(tm)) return IO_TIMEOUT; +- /* we wait until something happens */ +- err = socket_waitfd(ps, WAITFD_C, tm); +- if (err == IO_CLOSED) { +- int elen = sizeof(err); +- /* give windows time to set the error (yes, disgusting) */ +- Sleep(10); +- /* find out why we failed */ +- getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &elen); +- /* we KNOW there was an error. if 'why' is 0, we will return +- * "unknown error", but it's not really our fault */ +- return err > 0? err: IO_UNKNOWN; +- } else return err; +- +-} +- +-/*-------------------------------------------------------------------------*\ +-* Binds or returns error message +-\*-------------------------------------------------------------------------*/ +-int socket_bind(p_socket ps, SA *addr, socklen_t len) { +- int err = IO_DONE; +- socket_setblocking(ps); +- if (bind(*ps, addr, len) < 0) err = WSAGetLastError(); +- socket_setnonblocking(ps); +- return err; +-} +- +-/*-------------------------------------------------------------------------*\ +-* +-\*-------------------------------------------------------------------------*/ +-int socket_listen(p_socket ps, int backlog) { +- int err = IO_DONE; +- socket_setblocking(ps); +- if (listen(*ps, backlog) < 0) err = WSAGetLastError(); +- socket_setnonblocking(ps); +- return err; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Accept with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, +- p_timeout tm) { +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int err; +- /* try to get client socket */ +- if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; +- /* find out why we failed */ +- err = WSAGetLastError(); +- /* if we failed because there was no connectoin, keep trying */ +- if (err != WSAEWOULDBLOCK && err != WSAECONNABORTED) return err; +- /* call select to avoid busy wait */ +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Send with timeout +-* On windows, if you try to send 10MB, the OS will buffer EVERYTHING +-* this can take an awful lot of time and we will end up blocked. +-* Therefore, whoever calls this function should not pass a huge buffer. +-\*-------------------------------------------------------------------------*/ +-int socket_send(p_socket ps, const char *data, size_t count, +- size_t *sent, p_timeout tm) +-{ +- int err; +- *sent = 0; +- /* avoid making system calls on closed sockets */ +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- /* loop until we send something or we give up on error */ +- for ( ;; ) { +- /* try to send something */ +- int put = send(*ps, data, (int) count, 0); +- /* if we sent something, we are done */ +- if (put > 0) { +- *sent = put; +- return IO_DONE; +- } +- /* deal with failure */ +- err = WSAGetLastError(); +- /* we can only proceed if there was no serious error */ +- if (err != WSAEWOULDBLOCK) return err; +- /* avoid busy wait */ +- if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Sendto with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, +- SA *addr, socklen_t len, p_timeout tm) +-{ +- int err; +- *sent = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int put = sendto(*ps, data, (int) count, 0, addr, len); +- if (put > 0) { +- *sent = put; +- return IO_DONE; +- } +- err = WSAGetLastError(); +- if (err != WSAEWOULDBLOCK) return err; +- if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Receive with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_recv(p_socket ps, char *data, size_t count, size_t *got, +- p_timeout tm) +-{ +- int err, prev = IO_DONE; +- *got = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int taken = recv(*ps, data, (int) count, 0); +- if (taken > 0) { +- *got = taken; +- return IO_DONE; +- } +- if (taken == 0) return IO_CLOSED; +- err = WSAGetLastError(); +- /* On UDP, a connreset simply means the previous send failed. +- * So we try again. +- * On TCP, it means our socket is now useless, so the error passes. +- * (We will loop again, exiting because the same error will happen) */ +- if (err != WSAEWOULDBLOCK) { +- if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; +- prev = err; +- } +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Recvfrom with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, +- SA *addr, socklen_t *len, p_timeout tm) +-{ +- int err, prev = IO_DONE; +- *got = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int taken = recvfrom(*ps, data, (int) count, 0, addr, len); +- if (taken > 0) { +- *got = taken; +- return IO_DONE; +- } +- if (taken == 0) return IO_CLOSED; +- err = WSAGetLastError(); +- /* On UDP, a connreset simply means the previous send failed. +- * So we try again. +- * On TCP, it means our socket is now useless, so the error passes. +- * (We will loop again, exiting because the same error will happen) */ +- if (err != WSAEWOULDBLOCK) { +- if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; +- prev = err; +- } +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Put socket into blocking mode +-\*-------------------------------------------------------------------------*/ +-void socket_setblocking(p_socket ps) { +- u_long argp = 0; +- ioctlsocket(*ps, FIONBIO, &argp); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Put socket into non-blocking mode +-\*-------------------------------------------------------------------------*/ +-void socket_setnonblocking(p_socket ps) { +- u_long argp = 1; +- ioctlsocket(*ps, FIONBIO, &argp); +-} +- +-/*-------------------------------------------------------------------------*\ +-* DNS helpers +-\*-------------------------------------------------------------------------*/ +-int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { +- *hp = gethostbyaddr(addr, len, AF_INET); +- if (*hp) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-int socket_gethostbyname(const char *addr, struct hostent **hp) { +- *hp = gethostbyname(addr); +- if (*hp) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Error translation functions +-\*-------------------------------------------------------------------------*/ +-const char *socket_hoststrerror(int err) { +- if (err <= 0) return io_strerror(err); +- switch (err) { +- case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; +- default: return wstrerror(err); +- } +-} +- +-const char *socket_strerror(int err) { +- if (err <= 0) return io_strerror(err); +- switch (err) { +- case WSAEADDRINUSE: return PIE_ADDRINUSE; +- case WSAECONNREFUSED : return PIE_CONNREFUSED; +- case WSAEISCONN: return PIE_ISCONN; +- case WSAEACCES: return PIE_ACCESS; +- case WSAECONNABORTED: return PIE_CONNABORTED; +- case WSAECONNRESET: return PIE_CONNRESET; +- case WSAETIMEDOUT: return PIE_TIMEDOUT; +- default: return wstrerror(err); +- } +-} +- +-const char *socket_ioerror(p_socket ps, int err) { +- (void) ps; +- return socket_strerror(err); +-} +- +-static const char *wstrerror(int err) { +- switch (err) { +- case WSAEINTR: return "Interrupted function call"; +- case WSAEACCES: return PIE_ACCESS; /* "Permission denied"; */ +- case WSAEFAULT: return "Bad address"; +- case WSAEINVAL: return "Invalid argument"; +- case WSAEMFILE: return "Too many open files"; +- case WSAEWOULDBLOCK: return "Resource temporarily unavailable"; +- case WSAEINPROGRESS: return "Operation now in progress"; +- case WSAEALREADY: return "Operation already in progress"; +- case WSAENOTSOCK: return "Socket operation on nonsocket"; +- case WSAEDESTADDRREQ: return "Destination address required"; +- case WSAEMSGSIZE: return "Message too long"; +- case WSAEPROTOTYPE: return "Protocol wrong type for socket"; +- case WSAENOPROTOOPT: return "Bad protocol option"; +- case WSAEPROTONOSUPPORT: return "Protocol not supported"; +- case WSAESOCKTNOSUPPORT: return PIE_SOCKTYPE; /* "Socket type not supported"; */ +- case WSAEOPNOTSUPP: return "Operation not supported"; +- case WSAEPFNOSUPPORT: return "Protocol family not supported"; +- case WSAEAFNOSUPPORT: return PIE_FAMILY; /* "Address family not supported by protocol family"; */ +- case WSAEADDRINUSE: return PIE_ADDRINUSE; /* "Address already in use"; */ +- case WSAEADDRNOTAVAIL: return "Cannot assign requested address"; +- case WSAENETDOWN: return "Network is down"; +- case WSAENETUNREACH: return "Network is unreachable"; +- case WSAENETRESET: return "Network dropped connection on reset"; +- case WSAECONNABORTED: return "Software caused connection abort"; +- case WSAECONNRESET: return PIE_CONNRESET; /* "Connection reset by peer"; */ +- case WSAENOBUFS: return "No buffer space available"; +- case WSAEISCONN: return PIE_ISCONN; /* "Socket is already connected"; */ +- case WSAENOTCONN: return "Socket is not connected"; +- case WSAESHUTDOWN: return "Cannot send after socket shutdown"; +- case WSAETIMEDOUT: return PIE_TIMEDOUT; /* "Connection timed out"; */ +- case WSAECONNREFUSED: return PIE_CONNREFUSED; /* "Connection refused"; */ +- case WSAEHOSTDOWN: return "Host is down"; +- case WSAEHOSTUNREACH: return "No route to host"; +- case WSAEPROCLIM: return "Too many processes"; +- case WSASYSNOTREADY: return "Network subsystem is unavailable"; +- case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range"; +- case WSANOTINITIALISED: +- return "Successful WSAStartup not yet performed"; +- case WSAEDISCON: return "Graceful shutdown in progress"; +- case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; /* "Host not found"; */ +- case WSATRY_AGAIN: return "Nonauthoritative host not found"; +- case WSANO_RECOVERY: return PIE_FAIL; /* "Nonrecoverable name lookup error"; */ +- case WSANO_DATA: return "Valid name, no data record of requested type"; +- default: return "Unknown error"; +- } +-} +- +-const char *socket_gaistrerror(int err) { +- if (err == 0) return NULL; +- switch (err) { +- case EAI_AGAIN: return PIE_AGAIN; +- case EAI_BADFLAGS: return PIE_BADFLAGS; +-#ifdef EAI_BADHINTS +- case EAI_BADHINTS: return PIE_BADHINTS; +-#endif +- case EAI_FAIL: return PIE_FAIL; +- case EAI_FAMILY: return PIE_FAMILY; +- case EAI_MEMORY: return PIE_MEMORY; +- case EAI_NONAME: return PIE_NONAME; +-#ifdef EAI_OVERFLOW +- case EAI_OVERFLOW: return PIE_OVERFLOW; +-#endif +-#ifdef EAI_PROTOCOL +- case EAI_PROTOCOL: return PIE_PROTOCOL; +-#endif +- case EAI_SERVICE: return PIE_SERVICE; +- case EAI_SOCKTYPE: return PIE_SOCKTYPE; +-#ifdef EAI_SYSTEM +- case EAI_SYSTEM: return strerror(errno); +-#endif +- default: return LUA_GAI_STRERROR(err); +- } +-} +diff --git a/wsocket.h b/wsocket.h +deleted file mode 100644 +index 3986640..0000000 +--- a/wsocket.h ++++ /dev/null +@@ -1,33 +0,0 @@ +-#ifndef WSOCKET_H +-#define WSOCKET_H +-/*=========================================================================*\ +-* Socket compatibilization module for Win32 +-* LuaSocket toolkit +-\*=========================================================================*/ +- +-/*=========================================================================*\ +-* WinSock include files +-\*=========================================================================*/ +-#include +-#include +- +-typedef int socklen_t; +-typedef SOCKADDR_STORAGE t_sockaddr_storage; +-typedef SOCKET t_socket; +-typedef t_socket *p_socket; +- +-#ifndef IPV6_V6ONLY +-#define IPV6_V6ONLY 27 +-#endif +- +-#define SOCKET_INVALID (INVALID_SOCKET) +- +-#ifndef SO_REUSEPORT +-#define SO_REUSEPORT SO_REUSEADDR +-#endif +- +-#ifndef AI_NUMERICSERV +-#define AI_NUMERICSERV (0) +-#endif +- +-#endif /* WSOCKET_H */ diff --git a/platform/cafe/source/boot.cpp b/platform/cafe/source/boot.cpp index 9e723f2a6..c456293c8 100644 --- a/platform/cafe/source/boot.cpp +++ b/platform/cafe/source/boot.cpp @@ -20,29 +20,29 @@ #include -#include "utility/logfile.hpp" - namespace love { // clang-format off - static constexpr std::array services = + static constexpr std::array services = {{ { "procUI", BIND(ProcUIInit, OSSavesDone_ReadyToRelease), &ProcUIShutdown }, { "vpad", BIND(VPADInit), &VPADShutdown }, { "kpad", BIND(KPADInit), &KPADShutdown }, { "ac", BIND(ACInitialize), &ACFinalize }, - { "fs", BIND(FSInit), &FSShutdown }, - { "bsp", BIND(bspInitializeShimInterface), []() { } } + { "fs", BIND(FSInit), &FSShutdown } + // { "bsp", BIND(bspInitializeShimInterface), []() { } } }}; // clang-format on uint32_t Console::mainCoreId = 0; bool Console::mainCoreIdSet = false; + static constexpr const char* DEFAULT_PATH = "fs:/vol/external01/lovepotion.wuhb"; + std::string getApplicationPath(const std::string& argv0) { if (argv0 == "embedded boot.lua") - return "fs:/vol/external01/lovepotion.wuhb"; + return DEFAULT_PATH; OSDynLoad_Module module; const auto type = OS_DYNLOAD_EXPORT_FUNC; @@ -62,14 +62,14 @@ namespace love } } - return std::string {}; + return DEFAULT_PATH; } int preInit() { /* we aren't running Aroma */ - if (getApplicationPath().empty()) - return -1; + // if (getApplicationPath().empty()) + // return -1; for (const auto& service : services) { @@ -77,6 +77,9 @@ namespace love return -1; } + WPADEnableWiiRemote(true); + WPADEnableURCC(true); + Console::setMainCoreId(OSGetCoreId()); return 0; diff --git a/platform/cafe/source/driver/EventQueue.cpp b/platform/cafe/source/driver/EventQueue.cpp index b274d5a16..3905e5c51 100644 --- a/platform/cafe/source/driver/EventQueue.cpp +++ b/platform/cafe/source/driver/EventQueue.cpp @@ -60,27 +60,27 @@ namespace love continue; if (joystick->getGamepadType() == GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD) - this->gamepad = (Joystick*)joystick; + this->gamepad = (vpad::Joystick*)joystick; joystick->update(); + const auto which = joystick->getInstanceID(); - for (int input = 0; input < Joystick::GAMEPAD_BUTTON_MAX_ENUM; input++) + for (int input = 0; input < JoystickBase::GAMEPAD_BUTTON_MAX_ENUM; input++) { - std::vector inputs = { Joystick::GamepadButton(input) }; - + std::vector inputs = { JoystickBase::GamepadButton(input) }; if (joystick->isDown(inputs)) - this->sendGamepadButtonEvent(SUBTYPE_GAMEPADDOWN, 0, input); + this->sendGamepadButtonEvent(SUBTYPE_GAMEPADDOWN, which, input); if (joystick->isUp(inputs)) - this->sendGamepadButtonEvent(SUBTYPE_GAMEPADUP, 0, input); + this->sendGamepadButtonEvent(SUBTYPE_GAMEPADUP, which, input); } - for (int input = 0; input < Joystick::GAMEPAD_AXIS_MAX_ENUM; input++) + for (int input = 0; input < JoystickBase::GAMEPAD_AXIS_MAX_ENUM; input++) { - if (joystick->isAxisChanged(Joystick::GamepadAxis(input))) + if (joystick->isAxisChanged(JoystickBase::GamepadAxis(input))) { - float value = joystick->getAxis(Joystick::GamepadAxis(input)); - this->sendGamepadAxisEvent(0, input, value); + float value = joystick->getAxis(JoystickBase::GamepadAxis(input)); + this->sendGamepadAxisEvent(which, input, value); } } @@ -90,7 +90,7 @@ namespace love continue; auto data = joystick->getSensorData(Sensor::SensorType(input)); - this->sendGamepadSensorEvent(0, input, data); + this->sendGamepadSensorEvent(which, input, data); } } diff --git a/platform/cafe/source/driver/audio/SoundChannel.cpp b/platform/cafe/source/driver/audio/SoundChannel.cpp index 3ec745c82..678e20b0e 100644 --- a/platform/cafe/source/driver/audio/SoundChannel.cpp +++ b/platform/cafe/source/driver/audio/SoundChannel.cpp @@ -4,8 +4,6 @@ #include "driver/audio/DigitalSound.hpp" #include "driver/audio/DigitalSoundMix.hpp" -#include "utility/logfile.hpp" - namespace love { SoundChannel::SoundChannel() : diff --git a/platform/cafe/source/driver/display/Framebuffer.cpp b/platform/cafe/source/driver/display/Framebuffer.cpp index de61dc4f7..c5eb39da7 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -3,19 +3,25 @@ #include +#include + namespace love { - Framebuffer::Framebuffer() : - target {}, - depth {}, - modelView(1.0f), - projection(1.0f), - scanBuffer(nullptr), - scanBufferSize(0) - {} + Framebuffer::Framebuffer() : target {}, depth {}, scanBuffer(nullptr), scanBufferSize(0) + { + this->uniform = (Uniform*)memalign(GX2_UNIFORM_BLOCK_ALIGNMENT, sizeof(Uniform)); + + this->uniform->modelView = glm::mat4(1.0f); + this->uniform->projection = glm::mat4(1.0f); + + this->tmpModel = glm::mat4(1.0f); + } void Framebuffer::destroy() - {} + { + std::free(this->uniform); + this->uniform = nullptr; + } bool Framebuffer::allocateScanBuffer(MEMHeapHandle handle) { @@ -29,12 +35,12 @@ namespace love if (this->id == GX2_SCAN_TARGET_TV) { - const auto mode = (GX2TVRenderMode)this->mode; + const auto mode = (GX2TVRenderMode)this->renderMode; GX2SetTVBuffer(this->scanBuffer, this->scanBufferSize, mode, FORMAT, BUFFER_MODE); } else { - const auto mode = (GX2DrcRenderMode)this->mode; + const auto mode = (GX2DrcRenderMode)this->renderMode; GX2SetDRCBuffer(this->scanBuffer, this->scanBufferSize, mode, FORMAT, BUFFER_MODE); } @@ -73,7 +79,7 @@ namespace love void Framebuffer::copyScanBuffer() { - GX2CopyColorBufferToScanBuffer(&this->target, (GX2ScanTarget)this->mode); + GX2CopyColorBufferToScanBuffer(&this->target, this->id); } void Framebuffer::create(const ScreenInfo& info) @@ -95,8 +101,7 @@ namespace love GX2CalcTVSize(mode, FORMAT, BUFFER_MODE, &this->scanBufferSize, &unknown); GX2SetTVScale(info.width, info.height); - - this->mode = mode; + this->renderMode = mode; } else { @@ -105,16 +110,31 @@ namespace love GX2CalcDRCSize(mode, FORMAT, BUFFER_MODE, &this->scanBufferSize, &unknown); GX2SetDRCScale(info.width, info.height); - - this->mode = mode; + this->renderMode = mode; } + this->id = (GX2ScanTarget)info.id; this->width = info.width; this->height = info.height; - this->id = info.id; this->viewport = { 0, 0, info.width, info.height }; this->scissor = { 0, 0, info.width, info.height }; + + this->ortho = glm::ortho(0.0f, (float)info.width, (float)info.height, 0.0f, Z_NEAR, Z_FAR); + + /* glm::value_ptr lets us access the data linearly rather than an XxY matrix */ + uint32_t* dstModel = (uint32_t*)glm::value_ptr(this->uniform->modelView); + uint32_t* dstProj = (uint32_t*)glm::value_ptr(this->uniform->projection); + + const size_t count = sizeof(glm::mat4) / sizeof(uint32_t); + + uint32_t* model = (uint32_t*)glm::value_ptr(this->tmpModel); + for (size_t index = 0; index < count; index++) + dstModel[index] = __builtin_bswap32(model[index]); + + uint32_t* projection = (uint32_t*)glm::value_ptr(this->ortho); + for (size_t index = 0; index < count; index++) + dstProj[index] = __builtin_bswap32(projection[index]); } void Framebuffer::setScissor(const Rect& scissor) @@ -134,7 +154,6 @@ namespace love else this->viewport = viewport; - GX2SetViewport(this->viewport.x, this->viewport.y, this->viewport.w, this->viewport.h, - Z_NEAR, Z_FAR); + GX2SetViewport(this->viewport.x, this->viewport.y, this->viewport.w, this->viewport.h, Z_NEAR, Z_FAR); } } // namespace love diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp new file mode 100644 index 000000000..9074ea811 --- /dev/null +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -0,0 +1,410 @@ +#include "driver/display/GX2.hpp" +#include "driver/display/Uniform.hpp" + +/* keyboard needs GX2 inited first */ +#include "modules/graphics/Shader.hpp" +#include "modules/keyboard/Keyboard.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace love +{ +#define Keyboard() (Module::getInstance(Module::M_KEYBOARD)) + + GX2::GX2() : + targets {}, + context {}, + inForeground(false), + commandBuffer(nullptr), + state(nullptr), + dirtyProjection(false) + {} + + GX2::~GX2() + { + if (this->inForeground) + this->onForegroundReleased(); + + GX2Shutdown(); + + delete this->uniform; + + free(this->state); + this->state = nullptr; + + free(this->commandBuffer); + this->commandBuffer = nullptr; + } + + int GX2::onForegroundAcquired() + { + this->inForeground = true; + + auto foregroundHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_FG); + auto memOneHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1); + + for (auto& target : this->targets) + { + if (!target.allocateScanBuffer(foregroundHeap)) + return -1; + + if (!target.invalidateColorBuffer(memOneHeap)) + return -2; + + if (!target.invalidateDepthBuffer(memOneHeap)) + return -3; + } + + return 0; + } + + static uint32_t ProcUIAcquired(void*) + { + return gx2.onForegroundAcquired(); + } + + int GX2::onForegroundReleased() + { + GX2DrawDone(); + + auto foregroundHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_FG); + auto memOneHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1); + + MEMFreeToFrmHeap(foregroundHeap, MEM_FRM_HEAP_FREE_ALL); + MEMFreeToFrmHeap(memOneHeap, MEM_FRM_HEAP_FREE_ALL); + + this->inForeground = false; + + return 0; + } + + static uint32_t ProcUIReleased(void*) + { + return gx2.onForegroundReleased(); + } + + void GX2::initialize() + { + if (this->initialized) + return; + + this->commandBuffer = memalign(GX2_COMMAND_BUFFER_ALIGNMENT, GX2_COMMAND_BUFFER_SIZE); + + if (!this->commandBuffer) + throw love::Exception("Failed to allocate GX2 command buffer."); + + // clang-format off + uint32_t attributes[9] = + { + GX2_INIT_CMD_BUF_BASE, (uintptr_t)this->commandBuffer, + GX2_INIT_CMD_BUF_POOL_SIZE, GX2_COMMAND_BUFFER_SIZE, + GX2_INIT_ARGC, 0, GX2_INIT_ARGV, 0, + GX2_INIT_END + }; + // clang-format on + + GX2Init(attributes); + + this->state = (GX2ContextState*)memalign(GX2_CONTEXT_STATE_ALIGNMENT, sizeof(GX2ContextState)); + + if (!this->state) + throw love::Exception("Failed to allocate GX2 context state."); + + GX2SetupContextStateEx(this->state, false); + GX2SetContextState(this->state); + + this->createFramebuffers(); + + GX2SetDepthOnlyControl(false, false, GX2_COMPARE_FUNC_ALWAYS); + // GX2SetAlphaTest(false, GX2_COMPARE_FUNC_ALWAYS, 0.0f); + + GX2SetColorControl(GX2_LOGIC_OP_COPY, 0xFF, false, true); + GX2SetSwapInterval(1); + + ProcUIRegisterCallback(PROCUI_CALLBACK_ACQUIRE, ProcUIAcquired, nullptr, 100); + ProcUIRegisterCallback(PROCUI_CALLBACK_RELEASE, ProcUIReleased, nullptr, 100); + + if (auto result = this->onForegroundAcquired(); result != 0) + throw love::Exception("Failed to acquire foreground: {:d}", result); + + if (Keyboard()) + Keyboard()->initialize(); + + this->context.winding = GX2_FRONT_FACE_CCW; + this->context.cullBack = false; + this->context.cullFront = false; + this->context.depthTest = false; + this->context.depthWrite = true; + this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; + + this->uniform = (Uniform*)memalign(GX2_UNIFORM_BLOCK_ALIGNMENT, sizeof(Uniform)); + this->uniform->modelView = glm::mat4(1.0f); + this->uniform->projection = glm::mat4(1.0f); + + this->bindFramebuffer(&this->targets[0].get()); + + this->initialized = true; + } + + void GX2::createFramebuffers() + { + const auto info = love::getScreenInfo(); + + for (size_t index = 0; index < info.size(); ++index) + this->targets[index].create(info[index]); + } + + GX2ColorBuffer& GX2::getInternalBackbuffer() + { + return this->targets[love::currentScreen].get(); + } + + GX2DepthBuffer& GX2::getInternalDepthbuffer() + { + return this->targets[love::currentScreen].getDepth(); + } + + GX2ColorBuffer* GX2::getFramebuffer() + { + return this->context.boundFramebuffer; + } + + void GX2::destroyFramebuffers() + { + for (auto& target : this->targets) + target.destroy(); + } + + void GX2::ensureInFrame() + { + GX2SetContextState(this->state); + + if (!this->inFrame) + this->inFrame = true; + } + + void GX2::copyCurrentScanBuffer() + { + Graphics::flushBatchedDrawsGlobal(); + Graphics::advanceStreamBuffersGlobal(); + + this->targets[love::currentScreen].copyScanBuffer(); + + GX2Flush(); + GX2WaitForFlip(); + } + + void GX2::clear(const Color& color) + { + if (!this->inFrame) + return; + + GX2ClearColor(this->getFramebuffer(), color.r, color.g, color.b, color.a); + GX2SetContextState(this->state); + } + + void GX2::clearDepthStencil(int depth, uint8_t mask, double stencil) + { + // GX2ClearDepthStencilEx(&this->getInternalDepthbuffer(), depth, stencil, GX2_CLEAR_FLAGS_BOTH); + // GX2SetContextState(this->state); + } + + void GX2::bindFramebuffer(GX2ColorBuffer* target) + { + bool bindingModified = false; + + if (this->context.boundFramebuffer != target) + { + bindingModified = true; + this->context.boundFramebuffer = target; + } + + if (bindingModified) + { + GX2SetColorBuffer(target, GX2_RENDER_TARGET_0); + this->setMode(target->surface.width, target->surface.height); + } + } + + void GX2::setMode(int width, int height) + { + this->setViewport({ 0, 0, width, height }); + this->setScissor({ 0, 0, width, height }); + + auto* newUniform = this->targets[love::currentScreen].getUniform(); + std::memcpy(this->uniform, newUniform, sizeof(Uniform)); + } + + void GX2::setSamplerState(TextureBase* texture, const SamplerState& state) + { + auto* sampler = (GX2Sampler*)texture->getSamplerHandle(); + GX2InitSampler(sampler, GX2_TEX_CLAMP_MODE_WRAP, GX2_TEX_XY_FILTER_MODE_LINEAR); + + GX2TexXYFilterMode minFilter; + + if (!GX2::getConstant(state.minFilter, minFilter)) + return; + + GX2TexXYFilterMode magFilter; + + if (!GX2::getConstant(state.magFilter, magFilter)) + return; + + GX2InitSamplerXYFilter(sampler, magFilter, minFilter, GX2_TEX_ANISO_RATIO_NONE); + + GX2TexClampMode wrapU; + + if (!GX2::getConstant(state.wrapU, wrapU)) + return; + + GX2TexClampMode wrapV; + + if (!GX2::getConstant(state.wrapV, wrapV)) + return; + + GX2TexClampMode wrapW; + + if (!GX2::getConstant(state.wrapW, wrapW)) + return; + + GX2InitSamplerClamping(sampler, wrapU, wrapV, wrapW); + GX2InitSamplerLOD(sampler, state.minLod, state.maxLod, state.lodBias); + } + + void GX2::prepareDraw(GraphicsBase* graphics) + { + if (Shader::current != nullptr) + { + auto* shader = (Shader*)ShaderBase::current; + shader->updateBuiltinUniforms(graphics, this->uniform); + } + } + + void GX2::bindTextureToUnit(TextureBase* texture, int unit) + { + if (texture == nullptr) + return; + + auto* handle = (GX2Texture*)texture->getHandle(); + + if (handle == nullptr) + return; + + auto* sampler = (GX2Sampler*)texture->getSamplerHandle(); + + if (sampler == nullptr) + return; + + this->bindTextureToUnit(handle, sampler, unit); + } + + void GX2::bindTextureToUnit(GX2Texture* texture, GX2Sampler* sampler, int unit) + { + auto* shader = (Shader*)ShaderBase::current; + auto location = shader->getPixelSamplerLocation(0); + + GX2SetPixelTexture(texture, unit); + GX2SetPixelSampler(sampler, location); + } + + void GX2::present() + { + this->inFrame = false; + GX2SwapScanBuffers(); + } + + void GX2::setViewport(const Rect& rect) + { + Rect view = rect; + if (rect == Rect::EMPTY) + view = this->targets[love::currentScreen].getViewport(); + + GX2SetViewport(view.x, view.y, view.w, view.h, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); + this->context.viewport = view; + } + + void GX2::setScissor(const Rect& rect) + { + Rect scissor = rect; + if (rect == Rect::EMPTY) + scissor = this->targets[love::currentScreen].getScissor(); + + GX2SetScissor(scissor.x, scissor.y, scissor.w, scissor.h); + this->context.scissor = scissor; + } + + void GX2::setCullMode(CullMode mode) + { + const auto enabled = mode != CullMode::CULL_NONE; + + this->context.cullBack = (enabled && mode == CullMode::CULL_BACK); + this->context.cullFront = (enabled && mode == CullMode::CULL_FRONT); + + GX2SetCullOnlyControl(this->context.winding, this->context.cullBack, this->context.cullFront); + } + + void GX2::setVertexWinding(Winding winding) + { + GX2FrontFace windingMode; + + if (!GX2::getConstant(winding, windingMode)) + return; + + GX2SetCullOnlyControl(windingMode, this->context.cullBack, this->context.cullFront); + this->context.winding = windingMode; + } + + void GX2::setColorMask(ColorChannelMask mask) + { + const auto red = (GX2_CHANNEL_MASK_R * mask.r); + const auto green = (GX2_CHANNEL_MASK_G * mask.g); + const auto blue = (GX2_CHANNEL_MASK_B * mask.b); + const auto alpha = (GX2_CHANNEL_MASK_A * mask.a); + + const auto value = GX2ChannelMask(red + green + blue + alpha); + const auto NONE = GX2ChannelMask(0); + + GX2SetTargetChannelMasks(value, NONE, NONE, NONE, NONE, NONE, NONE, NONE); + } + + void GX2::setBlendState(const BlendState& state) + { + GX2BlendCombineMode operationRGB; + if (!GX2::getConstant(state.operationRGB, operationRGB)) + return; + + GX2BlendCombineMode operationA; + if (!GX2::getConstant(state.operationA, operationA)) + return; + + GX2BlendMode sourceColor; + if (!GX2::getConstant(state.srcFactorRGB, sourceColor)) + return; + + GX2BlendMode destColor; + if (!GX2::getConstant(state.dstFactorRGB, destColor)) + return; + + GX2BlendMode sourceAlpha; + if (!GX2::getConstant(state.srcFactorA, sourceAlpha)) + return; + + GX2BlendMode destAlpha; + if (!GX2::getConstant(state.dstFactorA, destAlpha)) + return; + + GX2SetBlendControl(GX2_RENDER_TARGET_0, sourceColor, destColor, operationRGB, true, sourceAlpha, + destAlpha, operationA); + } + + GX2 gx2; +} // namespace love diff --git a/platform/cafe/source/driver/display/Renderer.cpp b/platform/cafe/source/driver/display/Renderer.cpp deleted file mode 100644 index 25a232634..000000000 --- a/platform/cafe/source/driver/display/Renderer.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include "driver/display/Renderer.hpp" - -/* keyboard needs GX2 inited first */ -#include "modules/keyboard/Keyboard.hpp" - -#include -#include -#include -#include -#include -#include - -#include - -#include - -namespace love -{ -#define Keyboard() (Module::getInstance(Module::M_KEYBOARD)) - - Renderer::Renderer() : - context {}, - inForeground(false), - commandBuffer(nullptr), - state(nullptr), - targets {} - {} - - Renderer::~Renderer() - { - if (this->inForeground) - this->onForegroundReleased(); - - GX2Shutdown(); - - free(this->state); - this->state = nullptr; - - free(this->commandBuffer); - this->commandBuffer = nullptr; - } - - int Renderer::onForegroundAcquired() - { - this->inForeground = true; - - auto foregroundHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_FG); - auto memOneHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1); - - for (auto& target : this->targets) - { - if (!target.allocateScanBuffer(foregroundHeap)) - return -1; - - if (!target.invalidateColorBuffer(memOneHeap)) - return -2; - - if (!target.invalidateDepthBuffer(memOneHeap)) - return -3; - } - - return 0; - } - - static uint32_t ProcUIAcquired(void*) - { - return Renderer::getInstance().onForegroundAcquired(); - } - - int Renderer::onForegroundReleased() - { - auto foregroundHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_FG); - auto memOneHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1); - - MEMFreeToFrmHeap(foregroundHeap, MEM_FRM_HEAP_FREE_ALL); - MEMFreeToFrmHeap(memOneHeap, MEM_FRM_HEAP_FREE_ALL); - - this->inForeground = false; - - return 0; - } - - static uint32_t ProcUIReleased(void*) - { - return Renderer::getInstance().onForegroundReleased(); - } - - void Renderer::initialize() - { - if (this->initialized) - return; - - this->commandBuffer = memalign(GX2_COMMAND_BUFFER_ALIGNMENT, GX2_COMMAND_BUFFER_SIZE); - - if (!this->commandBuffer) - throw love::Exception("Failed to allocate GX2 command buffer."); - - // clang-format off - uint32_t attributes[9] = - { - GX2_INIT_CMD_BUF_BASE, (uintptr_t)this->commandBuffer, - GX2_INIT_CMD_BUF_POOL_SIZE, GX2_COMMAND_BUFFER_SIZE, - GX2_INIT_ARGC, 0, GX2_INIT_ARGV, 0, - GX2_INIT_END - }; - // clang-format on - - GX2Init(attributes); - - this->createFramebuffers(); - - this->state = - (GX2ContextState*)memalign(GX2_CONTEXT_STATE_ALIGNMENT, sizeof(GX2ContextState)); - - if (!this->state) - throw love::Exception("Failed to allocate GX2 context state."); - - GX2SetupContextStateEx(this->state, false); - GX2SetContextState(this->state); - - GX2SetDepthOnlyControl(false, false, GX2_COMPARE_FUNC_ALWAYS); - GX2SetAlphaTest(false, GX2_COMPARE_FUNC_ALWAYS, 0.0f); - - GX2SetColorControl(GX2_LOGIC_OP_COPY, 0xFF, false, true); - GX2SetSwapInterval(1); - - ProcUIRegisterCallback(PROCUI_CALLBACK_ACQUIRE, ProcUIAcquired, nullptr, 100); - ProcUIRegisterCallback(PROCUI_CALLBACK_RELEASE, ProcUIReleased, nullptr, 100); - - if (auto result = this->onForegroundAcquired(); result != 0) - throw love::Exception("Failed to acquire foreground: {:d}", result); - - if (Keyboard()) - Keyboard()->initialize(); - - this->context.winding = GX2_FRONT_FACE_CCW; - this->context.cullBack = true; - this->context.depthTest = false; - this->context.depthWrite = false; - this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; - - this->initialized = true; - } - - void Renderer::createFramebuffers() - { - const auto& info = love::getScreenInfo(); - - for (size_t index = 0; index < info.size(); ++index) - { - Framebuffer framebuffer {}; - framebuffer.create(info[index]); - - this->targets[index] = std::move(framebuffer); - } - } - - void Renderer::destroyFramebuffers() - { - for (auto& target : this->targets) - target.destroy(); - } - - void Renderer::ensureInFrame() - { - if (!this->inFrame) - { - GX2SetContextState(this->state); - this->inFrame = true; - } - } - - void Renderer::clear(const Color& color) - { - GX2ClearColor(&this->context.target.get(), color.r, color.g, color.b, color.a); - } - - void Renderer::clearDepthStencil(int depth, uint8_t mask, double stencil) - { - GX2ClearDepthStencilEx(&this->context.target.getDepth(), depth, stencil, - GX2_CLEAR_FLAGS_BOTH); - } - - void Renderer::bindFramebuffer() - { - this->ensureInFrame(); - - this->context.target = this->targets[love::currentScreen]; - auto viewport = this->context.target.getViewport(); - - GX2SetColorBuffer(&this->context.target.get(), GX2_RENDER_TARGET_0); - this->setViewport(viewport); - } - - void Renderer::present() - { - if (this->inFrame) - { - // GX2DrawDone(); - this->inFrame = false; - } - - if (Keyboard()->hasTextInput()) - { - nn::swkbd::DrawDRC(); - GX2SetContextState(this->state); - } - - for (auto& target : this->targets) - target.copyScanBuffer(); - - GX2SwapScanBuffers(); - GX2Flush(); - GX2WaitForFlip(); - } - - void Renderer::setViewport(const Rect& viewport) - { - this->context.target.setViewport(viewport); - } - - void Renderer::setScissor(const Rect& scissor) - { - this->context.target.setScissor(scissor); - } - - void Renderer::setCullMode(CullMode mode) - { - const auto enabled = mode != CullMode::CULL_NONE; - - this->context.cullBack = (enabled && mode == CullMode::CULL_BACK); - this->context.cullFront = (enabled && mode == CullMode::CULL_FRONT); - - GX2SetCullOnlyControl(this->context.winding, this->context.cullBack, - this->context.cullFront); - } - - void Renderer::setVertexWinding(Winding winding) - { - GX2FrontFace windingMode; - - if (!Renderer::getConstant(winding, windingMode)) - return; - - GX2SetCullOnlyControl(this->context.winding, this->context.cullBack, - this->context.cullFront); - - this->context.winding = windingMode; - } - - void Renderer::setColorMask(ColorChannelMask mask) - { - const auto red = (GX2_CHANNEL_MASK_R * mask.r); - const auto green = (GX2_CHANNEL_MASK_G * mask.g); - const auto blue = (GX2_CHANNEL_MASK_B * mask.b); - const auto alpha = (GX2_CHANNEL_MASK_A * mask.a); - - const auto value = GX2ChannelMask(red + green + blue + alpha); - const auto NONE = GX2ChannelMask(0); - - GX2SetTargetChannelMasks(value, NONE, NONE, NONE, NONE, NONE, NONE, NONE); - } - - void Renderer::setBlendState(const BlendState& state) - { - GX2BlendCombineMode operationRGB; - if (!Renderer::getConstant(state.operationRGB, operationRGB)) - return; - - GX2BlendCombineMode operationA; - if (!Renderer::getConstant(state.operationA, operationA)) - return; - - GX2BlendMode sourceColor; - if (!Renderer::getConstant(state.srcFactorRGB, sourceColor)) - return; - - GX2BlendMode destColor; - if (!Renderer::getConstant(state.dstFactorRGB, destColor)) - return; - - GX2BlendMode sourceAlpha; - if (!Renderer::getConstant(state.srcFactorA, sourceAlpha)) - return; - - GX2BlendMode destAlpha; - if (!Renderer::getConstant(state.dstFactorA, destAlpha)) - return; - - GX2SetBlendControl(GX2_RENDER_TARGET_0, sourceColor, destColor, operationRGB, true, - sourceAlpha, destAlpha, operationA); - } -} // namespace love diff --git a/platform/cafe/source/driver/display/Uniform.cpp b/platform/cafe/source/driver/display/Uniform.cpp new file mode 100644 index 000000000..be4b68ddf --- /dev/null +++ b/platform/cafe/source/driver/display/Uniform.cpp @@ -0,0 +1,17 @@ +#include "driver/display/Uniform.hpp" + +namespace love +{ + void debugUniform(Uniform* uniform) + { + const auto count = sizeof(glm::mat4) / sizeof(uint32_t); + + uint32_t* model = (uint32_t*)glm::value_ptr(uniform->modelView); + for (size_t index = 0; index < count; index++) + std::printf("modelView[%zu] = %u\n", index, model[index]); + + uint32_t* projection = (uint32_t*)glm::value_ptr(uniform->projection); + for (size_t index = 0; index < count; index++) + std::printf("projection[%zu] = %u\n", index, projection[index]); + } +} // namespace love diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index 950206b02..fe4c9c189 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -1,11 +1,20 @@ -#include "driver/display/Renderer.hpp" +#include "driver/display/GX2.hpp" #include "modules/graphics/Graphics.hpp" #include "modules/window/Window.hpp" +#include "modules/graphics/Shader.hpp" +#include "modules/graphics/Texture.hpp" +#include "modules/graphics/freetype/Font.hpp" + +#include +#include +#include +#include + namespace love { - Graphics::Graphics() : GraphicsBase("love.graphics.citro3d") + Graphics::Graphics() : GraphicsBase("love.graphics.gx2") { auto* window = Module::getInstance(M_WINDOW); @@ -24,60 +33,345 @@ namespace love } } - void Graphics::clearImpl(OptionalColor color, OptionalInt depth, OptionalDouble stencil) + Graphics::~Graphics() + {} + + void Graphics::captureScreenshot(const ScreenshotInfo& info) + {} + + void Graphics::initCapabilities() + { + // clang-format off + this->capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS] = false; + this->capabilities.features[FEATURE_CLAMP_ZERO] = true; + this->capabilities.features[FEATURE_CLAMP_ONE] = true; + this->capabilities.features[FEATURE_BLEND_MINMAX] = true; + this->capabilities.features[FEATURE_LIGHTEN] = true; + this->capabilities.features[FEATURE_FULL_NPOT] = true; + this->capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = false; + this->capabilities.features[FEATURE_SHADER_DERIVATIVES] = false; + this->capabilities.features[FEATURE_GLSL3] = false; + this->capabilities.features[FEATURE_GLSL4] = false; + this->capabilities.features[FEATURE_INSTANCING] = false; + this->capabilities.features[FEATURE_TEXEL_BUFFER] = false; + this->capabilities.features[FEATURE_INDEX_BUFFER_32BIT] = false; + this->capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = false; //< might be possible + this->capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = false; //< might be possible + this->capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = false; //< might be possible + this->capabilities.features[FEATURE_MIPMAP_RANGE] = false; + this->capabilities.features[FEATURE_INDIRECT_DRAW] = false; + static_assert(FEATURE_MAX_ENUM == 19, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + + this->capabilities.limits[LIMIT_POINT_SIZE] = 8.0f; + this->capabilities.limits[LIMIT_TEXTURE_SIZE] = 4096; + this->capabilities.limits[LIMIT_TEXTURE_LAYERS] = 1; + this->capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = 4096; + this->capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = 4096; + this->capabilities.limits[LIMIT_TEXEL_BUFFER_SIZE] = 0; + this->capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_X] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_Y] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_Z] = 0; + this->capabilities.limits[LIMIT_RENDER_TARGETS] = 1; //< max simultaneous render targets + this->capabilities.limits[LIMIT_TEXTURE_MSAA] = 0; + this->capabilities.limits[LIMIT_ANISOTROPY] = 0; + static_assert(LIMIT_MAX_ENUM == 13, "Graphics::initCapabilities must be updated when adding a new system limit!"); + // clang-format on + + this->capabilities.textureTypes[TEXTURE_2D] = true; + this->capabilities.textureTypes[TEXTURE_VOLUME] = false; + this->capabilities.textureTypes[TEXTURE_CUBE] = true; + this->capabilities.textureTypes[TEXTURE_2D_ARRAY] = false; + } + + void Graphics::setActiveScreen() + { + gx2.ensureInFrame(); + } + + void Graphics::clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) { - Renderer::getInstance().bindFramebuffer(); + if (color.hasValue) + { + bool hasIntegerFormat = false; + const auto& targets = this->states.back().renderTargets; + + for (const auto& target : targets.colors) + { + if (target.texture.get() && love::isPixelFormatInteger(target.texture->getPixelFormat())) + hasIntegerFormat = true; + + if (hasIntegerFormat) + { + std::vector colors(targets.colors.size()); + + for (size_t index = 0; index < colors.size(); index++) + colors[index] = color; + + this->clear(colors, stencil, depth); + return; + } + } + } + + if (color.hasValue || stencil.hasValue || depth.hasValue) + this->flushBatchedDraws(); if (color.hasValue) { - // gammaCorrectColor(color.value); - Renderer::getInstance().clear(color.value); + gammaCorrectColor(color.value); + gx2.clear(color.value); + } + + gx2.bindFramebuffer(&gx2.getInternalBackbuffer()); + } + + GX2ColorBuffer Graphics::getInternalBackbuffer() const + { + return gx2.getInternalBackbuffer(); + } + + void Graphics::clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) + { + if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) + return; + + int numColors = (int)colors.size(); + + const auto& targets = this->states.back().renderTargets.colors; + const int numColorTargets = targets.size(); + + if (numColors <= 1 && + (numColorTargets == 0 || (numColorTargets == 1 && targets[0].texture.get() != nullptr && + !isPixelFormatInteger(targets[0].texture->getPixelFormat())))) + { + this->clear(colors.size() > 0 ? colors[0] : OptionalColor(), stencil, depth); + return; + } + + this->flushBatchedDraws(); + + numColors = std::min(numColors, numColorTargets); + + for (int index = 0; index < numColors; index++) + { + OptionalColor current = colors[index]; + + if (!current.hasValue) + continue; + + Color value(current.value.r, current.value.g, current.value.b, current.value.a); + + gammaCorrectColor(value); + gx2.clear(value); } - if (stencil.hasValue && depth.hasValue) - Renderer::getInstance().clearDepthStencil(stencil.value, 0xFF, depth.value); + gx2.bindFramebuffer(&gx2.getInternalBackbuffer()); } - void Graphics::presentImpl() + void Graphics::copyCurrentScanBuffer() { - Renderer::getInstance().present(); + gx2.copyCurrentScanBuffer(); + } + + void Graphics::present(void* screenshotCallbackData) + { + if (!this->isActive()) + return; + + if (this->isRenderTargetActive()) + throw love::Exception("present cannot be called while a render target is active."); + + gx2.present(); + + this->drawCalls = 0; + this->drawCallsBatched = 0; + Shader::shaderSwitches = 0; } - void Graphics::setScissorImpl(const Rect& scissor) + void Graphics::setScissor(const Rect& scissor) { - Renderer::getInstance().setScissor(scissor); + this->flushBatchedDraws(); + + auto& state = this->states.back(); + double dpiscale = this->getCurrentDPIScale(); + + Rect rectangle {}; + rectangle.x = scissor.x * dpiscale; + rectangle.y = scissor.y * dpiscale; + rectangle.w = scissor.w * dpiscale; + rectangle.h = scissor.h * dpiscale; + + gx2.setScissor(rectangle); + + state.scissor = true; + state.scissorRect = scissor; } - void Graphics::setScissorImpl() + void Graphics::setPointSize(float size) { - Renderer::getInstance().setScissor(Rect::EMPTY); + if (size != this->states.back().pointSize) + this->flushBatchedDraws(); + + this->states.back().pointSize = size; } - void Graphics::setFrontFaceWindingImpl(Winding winding) + void Graphics::setScissor() { - Renderer::getInstance().setVertexWinding(winding); + if (this->states.back().scissor) + this->flushBatchedDraws(); + + this->states.back().scissor = false; + gx2.setScissor(Rect::EMPTY); } - void Graphics::setColorMaskImpl(ColorChannelMask mask) + void Graphics::setFrontFaceWinding(Winding winding) { - Renderer::getInstance().setColorMask(mask); + auto& state = this->states.back(); + + if (state.winding != winding) + this->flushBatchedDraws(); + + state.winding = winding; + + if (this->isRenderTargetActive()) + winding = (winding == WINDING_CW) ? WINDING_CCW : WINDING_CW; // ??? + + gx2.setVertexWinding(winding); } - void Graphics::setBlendStateImpl(const BlendState& state) + void Graphics::setColorMask(ColorChannelMask mask) { - Renderer::getInstance().setBlendState(state); + this->flushBatchedDraws(); + + gx2.setColorMask(mask); + this->states.back().colorMask = mask; } - bool Graphics::setModeImpl(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa) + void Graphics::setBlendState(const BlendState& state) { - Renderer::getInstance().initialize(); + if (!(state == this->states.back().blend)) + this->flushBatchedDraws(); + + if (state.operationRGB == BLENDOP_MAX || state.operationA == BLENDOP_MAX || + state.operationRGB == BLENDOP_MIN || state.operationA == BLENDOP_MIN) + { + if (!capabilities.features[FEATURE_BLEND_MINMAX]) + throw love::Exception(E_BLEND_MIN_MAX_NOT_SUPPORTED); + } + + if (state.enable) + gx2.setBlendState(state); + + this->states.back().blend = state; + } + + bool Graphics::isPixelFormatSupported(PixelFormat format, uint32_t usage) + { + format = this->getSizedFormat(format); + bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; + + GX2SurfaceFormat color; + bool supported = GX2::getConstant(format, color); + + return readable && supported; + } + + void Graphics::setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) + { + const auto& state = this->states.back(); + + bool isWindow = targets.getFirstTarget().texture == nullptr; + + if (isWindow) + gx2.bindFramebuffer(&gx2.getInternalBackbuffer()); + else + gx2.bindFramebuffer((GX2ColorBuffer*)targets.getFirstTarget().texture->getRenderTargetHandle()); + + gx2.setViewport({ 0, 0, pixelWidth, pixelHeight }); + + if (state.scissor) + gx2.setScissor(state.scissorRect); + } + + TextureBase* Graphics::newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data) + { + return new Texture(this, settings, data); + } + + FontBase* Graphics::newFont(Rasterizer* data) + { + return new Font(data, this->states.back().defaultSamplerState); + } + + FontBase* Graphics::newDefaultFont(int size, const Rasterizer::Settings& settings) + { + auto* module = Module::getInstance(Module::M_FONT); + + if (module == nullptr) + throw love::Exception("Font module has not been loaded."); + + StrongRef rasterizer = module->newTrueTypeRasterizer(size, settings); + + return this->newFont(rasterizer.get()); + } + + bool Graphics::setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa) + { + gx2.initialize(); + + this->created = true; + this->initCapabilities(); + + // gx2.setupContext(); + + try + { + if (this->batchedDrawState.vertexBuffer == nullptr) + { + this->batchedDrawState.indexBuffer = newIndexBuffer(INIT_INDEX_BUFFER_SIZE); + this->batchedDrawState.vertexBuffer = newVertexBuffer(INIT_VERTEX_BUFFER_SIZE); + } + } + catch (love::Exception&) + { + throw; + } + + if (!Volatile::loadAll()) + std::printf("Failed to load all volatile objects.\n"); + + this->restoreState(this->states.back()); + + for (int index = 0; index < ShaderBase::STANDARD_MAX_ENUM; index++) + { + auto type = (Shader::StandardShader)index; + + try + { + Shader::standardShaders[type] = new Shader(type); + } + catch (const std::exception& e) + { + throw; + } + } + + if (!Shader::current) + Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach(); return true; } - void Graphics::unsetModeImpl() - {} + void Graphics::unsetMode() + { + if (!this->isCreated()) + return; + + this->flushBatchedDraws(); + } bool Graphics::isActive() const { @@ -87,6 +381,35 @@ namespace love void Graphics::setViewport(int x, int y, int width, int height) { - Renderer::getInstance().setViewport({ x, y, width, height }); + gx2.setViewport({ x, y, width, height }); + } + + void Graphics::draw(const DrawIndexedCommand& command) + { + gx2.prepareDraw(this); + gx2.bindTextureToUnit(command.texture, 0); + // gx2.setCullMode(command.cullMode); + + const auto mode = GX2::getPrimitiveType(command.primitiveType); + auto* buffer = (GX2RBuffer*)command.indexBuffer->getHandle(); + const auto indexType = GX2::getIndexType(command.indexType); + const uint32_t count = (uint32_t)command.indexCount; + const uint32_t offset = (uint32_t)command.indexBufferOffset; + const uint32_t instanceCount = (uint32_t)command.instanceCount; + + GX2RDrawIndexed(mode, buffer, indexType, count, offset, 0, instanceCount); + ++this->drawCalls; + } + + void Graphics::draw(const DrawCommand& command) + { + gx2.prepareDraw(this); + + const auto mode = GX2::getPrimitiveType(command.primitiveType); + const auto vertexCount = command.vertexCount; + const auto vertexStart = command.vertexStart; + + GX2DrawEx(mode, vertexCount, vertexStart, 1); + ++this->drawCalls; } } // namespace love diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp new file mode 100644 index 000000000..9471678cb --- /dev/null +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -0,0 +1,189 @@ +#include "modules/graphics/Shader.hpp" + +#include "common/screen.hpp" + +#include +#include +#include + +#include + +#define SHADERS_DIR "/vol/content/shaders/" + +#define DEFAULT_PRIMITIVE_SHADER (SHADERS_DIR "color.gsh") +#define DEFAULT_TEXTURE_SHADER (SHADERS_DIR "texture.gsh") +#define DEFAULT_VIDEO_SHADER (SHADERS_DIR "video.gsh") + +namespace love +{ + Shader::Shader(StandardShader type) : ShaderBase(type), uniform {} + { + std::string error; + + switch (type) + { + default: + case STANDARD_DEFAULT: + { + if (!this->validate(DEFAULT_PRIMITIVE_SHADER, error)) + throw love::Exception("Failed to load primitive shader: {:s}", error); + + break; + } + case STANDARD_TEXTURE: + { + if (!this->validate(DEFAULT_TEXTURE_SHADER, error)) + throw love::Exception("Failed to load texture shader: {:s}", error); + + break; + } + case STANDARD_VIDEO: + { + if (!this->validate(DEFAULT_VIDEO_SHADER, error)) + throw love::Exception("Failed to load video shader: {:s}", error); + + break; + } + } + + // clang-format off + WHBGfxInitShaderAttribute(&this->program, "inPos", 0, POSITION_OFFSET, GX2_ATTRIB_FORMAT_FLOAT_32_32); + WHBGfxInitShaderAttribute(&this->program, "inTexCoord", 0, TEXCOORD_OFFSET, GX2_ATTRIB_FORMAT_FLOAT_32_32); + WHBGfxInitShaderAttribute(&this->program, "inColor", 0, COLOR_OFFSET, GX2_ATTRIB_FORMAT_FLOAT_32_32_32_32); + // clang-format on + + this->uniform = this->getUniform("Transformation"); + + if (!WHBGfxInitFetchShader(&this->program)) + throw love::Exception("Failed to initialize Fetch Shader"); + } + + Shader::~Shader() + { + this->unloadVolatile(); + } + + bool Shader::loadVolatile() + { + return true; + } + + void Shader::unloadVolatile() + { + WHBGfxFreeShaderGroup(&this->program); + } + + uint32_t Shader::getPixelSamplerLocation(int index) + { + if (this->shaderType != Shader::STANDARD_TEXTURE) + throw love::Exception("Invalid shader set!"); + + size_t count = this->program.pixelShader->samplerVarCount; + + if (index > count) + throw love::Exception("Invalid sampler index"); + + return this->program.pixelShader->samplerVars[index].location; + } + + const Shader::UniformInfo Shader::getUniform(const std::string& name) const + { + const auto count = this->program.vertexShader->uniformBlockCount; + + for (size_t index = 0; index < count; index++) + { + const auto uniform = this->program.vertexShader->uniformBlocks[index]; + + if (uniform.name == name) + return UniformInfo { uniform.offset, name }; + } + + return {}; + } + + bool Shader::hasUniform(const std::string& name) const + { + return this->uniform.name == name; + } + + void Shader::updateBuiltinUniforms(GraphicsBase* graphics, Uniform* uniform) + { + if (current != this) + return; + + auto& transform = graphics->getTransform(); + // uniform->update(transform); + + GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, uniform, UNIFORM_SIZE); + GX2SetVertexUniformBlock(this->uniform.location, UNIFORM_SIZE, uniform); + } + + ptrdiff_t Shader::getHandle() const + { + return 0; + } + + void Shader::attach() + { + if (current != this) + { + Graphics::flushBatchedDrawsGlobal(); + + GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); + + GX2SetFetchShader(&this->program.fetchShader); + GX2SetVertexShader(this->program.vertexShader); + GX2SetPixelShader(this->program.pixelShader); + + current = this; + shaderSwitches++; + } + } + + bool Shader::validate(const char* filepath, std::string& error) + { + FILE* file = std::fopen(filepath, "rb"); + + if (!file) + { + error = "File does not exist."; + std::fclose(file); + return false; + } + + std::unique_ptr data; + + std::fseek(file, 0, SEEK_END); + long size = std::ftell(file); + std::rewind(file); + + try + { + data = std::make_unique(size); + } + catch (std::bad_alloc&) + { + error = "Not enough memory."; + return false; + } + + long readSize = (long)std::fread(data.get(), 1, size, file); + + if (readSize != size) + { + error = "Failed to read whole file."; + std::fclose(file); + return false; + } + + std::fclose(file); + + if (!WHBGfxLoadGFDShaderGroup(&this->program, 0, data.get())) + { + error = "Failed to load Shader Group"; + return false; + } + + return true; + } +} // namespace love diff --git a/platform/cafe/source/modules/graphics/Texture.cpp b/platform/cafe/source/modules/graphics/Texture.cpp new file mode 100644 index 000000000..640f7f784 --- /dev/null +++ b/platform/cafe/source/modules/graphics/Texture.cpp @@ -0,0 +1,219 @@ +#include "driver/display/GX2.hpp" + +#include "modules/graphics/Texture.hpp" + +#include +#include + +#include + +namespace love +{ + static void createTextureObject(GX2Texture*& texture, PixelFormat format, int width, int height) + { + texture = new GX2Texture(); + + if (!texture) + throw love::Exception("Failed to create GX2Texture."); + + std::memset(&texture->surface, 0, sizeof(GX2Surface)); + + texture->surface.use = GX2_SURFACE_USE_TEXTURE; + texture->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D; + texture->surface.width = width; + texture->surface.height = height; + + texture->surface.depth = 1; + texture->surface.mipLevels = 1; + + GX2SurfaceFormat gpuFormat; + if (!GX2::getConstant(format, gpuFormat)) + throw love::Exception("Invalid pixel format {:s}.", love::getConstant(format)); + + texture->surface.format = gpuFormat; + texture->surface.aa = GX2_AA_MODE1X; + texture->surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED; + texture->viewFirstMip = 0; + texture->viewNumMips = 0; + texture->viewFirstSlice = 0; + texture->viewNumSlices = 1; + texture->compMap = GX2_COMP_MAP(GX2_SQ_SEL_R, GX2_SQ_SEL_G, GX2_SQ_SEL_B, GX2_SQ_SEL_A); + + GX2CalcSurfaceSizeAndAlignment(&texture->surface); + GX2InitTextureRegs(texture); + + texture->surface.image = memalign(texture->surface.alignment, texture->surface.imageSize); + + if (!texture->surface.image) + throw love::Exception("Failed to allocate texture memory."); + + std::memset(texture->surface.image, 0, texture->surface.imageSize); + GX2Invalidate(GX2_INVALIDATE_MODE_CPU_TEXTURE, texture->surface.image, texture->surface.imageSize); + } + + Texture::Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data) : + TextureBase(graphics, settings, data), + slices(settings.type), + sampler {} + { + if (data != nullptr) + slices = *data; + + if (!this->loadVolatile()) + throw love::Exception("Failed to create texture."); + + slices.clear(); + } + + Texture::~Texture() + { + this->unloadVolatile(); + } + + bool Texture::loadVolatile() + { + if (this->texture != nullptr || this->target != nullptr) + return true; + + if (this->parentView.texture != this) + { + Texture* baseTexture = (Texture*)this->parentView.texture; + baseTexture->loadVolatile(); + } + + if (this->isReadable()) + this->createTexture(); + + int64_t memorySize = 0; + + for (int mip = 0; mip < this->getMipmapCount(); mip++) + { + int width = this->getPixelWidth(mip); + int height = this->getPixelHeight(mip); + + const auto faces = (this->textureType == TEXTURE_CUBE) ? 6 : 1; + int slices = this->getDepth(mip) * this->layers * faces; + + memorySize += getPixelFormatSliceSize(this->format, width, height, false) * slices; + } + + this->setGraphicsMemorySize(memorySize); + + return true; + } + + void Texture::unloadVolatile() + { + if (this->texture != nullptr) + delete this->texture; + + if (this->target != nullptr) + delete this->target; + + this->setGraphicsMemorySize(0); + } + + void Texture::createTexture() + { + if (!this->isRenderTarget()) + { + try + { + createTextureObject(this->texture, this->format, this->pixelWidth, this->pixelHeight); + } + catch (love::Exception&) + { + throw; + } + + int mipCount = this->getMipmapCount(); + int sliceCount = 1; + + if (this->textureType == TEXTURE_VOLUME) + sliceCount = this->getDepth(); + else if (this->textureType == TEXTURE_2D_ARRAY) + sliceCount = this->layers; + else if (this->textureType == TEXTURE_CUBE) + sliceCount = 6; + + for (int mipmap = 0; mipmap < mipCount; mipmap++) + { + for (int slice = 0; slice < sliceCount; slice++) + { + auto* data = this->slices.get(slice, mipmap); + + if (data != nullptr) + this->uploadImageData(data, mipmap, slice, 0, 0); + } + } + } + + bool hasData = this->slices.get(0, 0) != nullptr; + int clearMips = 1; + + if (isPixelFormatDepthStencil(this->format)) + clearMips = mipmapCount; + + if (this->isRenderTarget()) + { + } + else if (!hasData) + { + for (int mipmap = 0; mipmap < clearMips; mipmap++) + { + } + } + + this->setSamplerState(this->samplerState); + + if (this->slices.getMipmapCount() <= 1 && this->getMipmapsMode() != MIPMAPS_NONE) + this->generateMipmaps(); + } + + void Texture::setSamplerState(const SamplerState& state) + { + this->samplerState = this->validateSamplerState(state); + gx2.setSamplerState(this, this->samplerState); + } + + void Texture::uploadByteData(const void* data, size_t size, int level, int slice, const Rect& rect) + { + const auto pitch = this->texture->surface.pitch; + + uint8_t* destination = (uint8_t*)this->texture->surface.image; + uint8_t* source = (uint8_t*)data; + + size_t pixelSize = getPixelFormatBlockSize(this->format); + + for (uint32_t y = 0; y < (uint32_t)rect.h; y++) + { + const auto srcRow = (y * rect.w * pixelSize); + const auto destRow = (rect.x + (y + rect.y) * pitch) * pixelSize; + + std::memcpy(destination + destRow, source + srcRow, rect.w * pixelSize); + } + + const auto imageSize = this->texture->surface.imageSize; + + GX2Invalidate(GX2_INVALIDATE_MODE_CPU_TEXTURE, this->texture->surface.image, imageSize); + GX2Flush(); + } + + void Texture::generateMipmapsInternal() + {} + + ptrdiff_t Texture::getHandle() const + { + return (ptrdiff_t)this->texture; + } + + ptrdiff_t Texture::getRenderTargetHandle() const + { + return (ptrdiff_t)this->target; + } + + ptrdiff_t Texture::getSamplerHandle() const + { + return (ptrdiff_t)std::addressof(this->sampler); + } +} // namespace love diff --git a/platform/cafe/source/modules/joystick/Joystick.cpp b/platform/cafe/source/modules/joystick/Joystick.cpp deleted file mode 100644 index 86c602ec4..000000000 --- a/platform/cafe/source/modules/joystick/Joystick.cpp +++ /dev/null @@ -1,424 +0,0 @@ -#include "modules/joystick/Joystick.hpp" - -#include "utility/logfile.hpp" - -namespace love -{ - Joystick::Joystick(int id) : JoystickBase(id) - {} - - Joystick::Joystick(int id, int index) : JoystickBase(id, index) - { - this->open(index); - } - - Joystick::~Joystick() - { - this->close(); - } - - void Joystick::update() - { - if (this->isDRCGamepad()) - { - VPADRead(VPAD_CHAN_0, &this->vpadStatus, 1, &this->vpadError); - - const auto& status = this->vpadStatus; - if (this->vpadError != VPAD_READ_NO_SAMPLES) - this->state = { status.trigger, status.release, status.hold }; - } - else - { - KPADReadEx(KPADChan(this->id - 1), &this->kpadStatus, 1, &this->kpadError); - - const auto& status = this->kpadStatus; - if (this->kpadError != KPAD_ERROR_NO_SAMPLES) - this->state = { status.trigger, status.release, status.hold }; - } - } - - static GamepadType getSystemGamepadType(int id) - { - if (id == 0) - return GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD; - - KPADStatus status {}; - KPADError error = KPAD_ERROR_OK; - - KPADReadEx(KPADChan(id - 1), &status, 1, &error); - - GamepadType type = GAMEPAD_TYPE_UNKNOWN; - Joystick::getConstant((WPADExtensionType)status.extensionType, type); - - return type; - } - - bool Joystick::open(int64_t deviceId) - { - int index = (int)deviceId; - this->close(); - - this->instanceId = 0; - - this->gamepadType = getSystemGamepadType(this->id); - this->guid = getGamepadGUID(this->gamepadType); - - if (!Joystick::getConstant(this->gamepadType, this->name)) - this->name = "Unknown"; - - this->joystickType = JOYSTICK_TYPE_GAMEPAD; - this->update(); - - return this->isConnected(); - } - - void Joystick::close() - { - this->instanceId = -1; - } - - bool Joystick::isConnected() const - { - if (this->isDRCGamepad()) - { - switch (this->vpadError) - { - case VPAD_READ_INVALID_CONTROLLER: - case VPAD_READ_UNINITIALIZED: - return false; - default: - return true; - } - } - - switch (this->kpadError) - { - case KPAD_ERROR_INVALID_CONTROLLER: - case KPAD_ERROR_UNINITIALIZED: - return false; - default: - return true; - } - - return false; - } - - float Joystick::getVPADAxis(GamepadAxis axis) const - { - const auto& status = this->vpadStatus; - - switch (axis) - { - case GAMEPAD_AXIS_LEFTX: - return clamp(status.leftStick.x); - case GAMEPAD_AXIS_LEFTY: - return clamp(status.leftStick.y); - case GAMEPAD_AXIS_RIGHTX: - return clamp(status.rightStick.x); - case GAMEPAD_AXIS_RIGHTY: - return clamp(status.rightStick.y); - case GAMEPAD_AXIS_TRIGGERLEFT: - return status.hold & VPAD_BUTTON_ZL ? 1.0f : 0.0f; - case GAMEPAD_AXIS_TRIGGERRIGHT: - return status.hold & VPAD_BUTTON_ZR ? 1.0f : 0.0f; - default: - return 0.0f; - } - } - - float Joystick::getKPADAxis(GamepadAxis axis) const - { - const auto& status = this->kpadStatus; - - switch (this->gamepadType) - { - case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: - { - switch (axis) - { - case GAMEPAD_AXIS_LEFTX: - return clamp(status.pro.leftStick.x); - case GAMEPAD_AXIS_LEFTY: - return clamp(status.pro.leftStick.y); - case GAMEPAD_AXIS_RIGHTX: - return clamp(status.pro.rightStick.x); - case GAMEPAD_AXIS_RIGHTY: - return clamp(status.pro.rightStick.y); - case GAMEPAD_AXIS_TRIGGERLEFT: - return status.hold & WPAD_PRO_TRIGGER_ZL ? 1.0f : 0.0f; - case GAMEPAD_AXIS_TRIGGERRIGHT: - return status.hold & WPAD_PRO_TRIGGER_ZR ? 1.0f : 0.0f; - default: - break; - } - break; - } - default: - return 0.0f; - } - - return 0.0f; - } - - float Joystick::getAxis(GamepadAxis axis) const - { - switch (this->gamepadType) - { - case GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD: - return this->getVPADAxis(axis); - case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: - return this->getKPADAxis(axis); - default: - return 0.0f; - } - } - - std::vector Joystick::getAxes() const - { - std::vector axes; - int count = this->getAxisCount(); - - if (!this->isConnected() || count <= 0) - return axes; - - axes.reserve(count); - - for (int i = 0; i < count; i++) - axes.push_back(this->getAxis((GamepadAxis)i)); - - return axes; - } - - bool Joystick::isVPADButtonDown(std::span buttons) const - { - VPADButtons result; - - for (const auto& button : buttons) - { - if (!Joystick::getConstant(button, result)) - continue; - - if (this->state.pressed & result) - { - this->state.pressed ^= result; - return true; - } - } - - return false; - } - - static bool isProControllerDown(const KPADStatus& status, - std::span buttons) - { - WPADProButton result; - - for (const auto& button : buttons) - { - if (!Joystick::getConstant(button, result)) - continue; - - if (status.pro.trigger & result) - return true; - } - - return false; - } - - bool Joystick::isKPADButtonDown(std::span buttons) const - { - switch (this->gamepadType) - { - case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: - return isProControllerDown(this->kpadStatus, buttons); - default: - break; - } - - return false; - } - - bool Joystick::isDown(std::span buttons) const - { - if (!this->isConnected()) - return false; - - if (this->isDRCGamepad()) - return this->isVPADButtonDown(buttons); - else - return this->isKPADButtonDown(buttons); - - return false; - } - - bool Joystick::isUp(std::span buttons) const - { - if (!this->isConnected()) - return false; - - VPADButtons result; - - for (GamepadButton button : buttons) - { - if (!Joystick::getConstant(button, result)) - continue; - - if (this->state.released & result) - { - this->state.released ^= result; - return true; - } - } - - return false; - } - - bool Joystick::isAxisChanged(GamepadAxis axis) const - { - if (!this->isConnected()) - return false; - - VPADAxis result; - - if (!Joystick::getConstant(axis, result)) - return false; - - if ((this->state.held & result) || (this->state.released & result)) - { - this->state.held ^= result; - return true; - } - - return false; - } - - void Joystick::setPlayerIndex(int) - {} - - int Joystick::getPlayerIndex() const - { - return this->id; - } - - Joystick::JoystickInput Joystick::getGamepadMapping(const GamepadInput& input) const - { - JoystickInput result {}; - - return result; - } - - std::string Joystick::getGamepadMappingString() const - { - return std::string(); - } - - bool Joystick::isVibrationSupported() const - { - return false; - } - - bool Joystick::setVibration(float, float, float) - { - return false; - } - - bool Joystick::setVibration() - { - return false; - } - - void Joystick::getVibration(float&, float&) const - {} - - bool Joystick::hasSensor(Sensor::SensorType type) const - { - switch (type) - { - case Sensor::SENSOR_ACCELEROMETER: - return getGamepadHasAccelerometer(this->gamepadType); - case Sensor::SENSOR_GYROSCOPE: - return getGamepadHasGyroscope(this->gamepadType); - default: - break; - } - - return false; - } - - bool Joystick::isSensorEnabled(Sensor::SensorType type) const - { - return this->sensors.at(type) == true; - } - - void Joystick::setSensorEnabled(Sensor::SensorType type, bool enable) - { - if (!this->hasSensor(type)) - { - std::string_view name = "Unknown"; - Sensor::getConstant(type, name); - - throw love::Exception("\"{}\" gamepad sensor is not supported", name); - } - - switch (type) - { - case Sensor::SENSOR_ACCELEROMETER: - case Sensor::SENSOR_GYROSCOPE: - this->sensors[type] = enable; - break; - default: - break; - } - } - - std::vector Joystick::getSensorData(Sensor::SensorType type) const - { - std::vector data {}; - - if (!this->hasSensor(type)) - { - std::string_view name = "Unknown"; - Sensor::getConstant(type, name); - - throw love::Exception("\"{}\" gamepad sensor is not enabled.", name); - } - - if (!this->isSensorEnabled(type)) - { - std::string_view name = "Unknown"; - Sensor::getConstant(type, name); - - throw love::Exception("\"{}\" gamepad sensor is not enabled.", name); - } - - data.reserve(3); - - switch (type) - { - case Sensor::SENSOR_ACCELEROMETER: - { - const auto& accelerometer = this->vpadStatus.accelorometer; - - data.push_back(accelerometer.acc.x); - data.push_back(accelerometer.acc.y); - data.push_back(accelerometer.acc.z); - - return data; - } - case Sensor::SENSOR_GYROSCOPE: - { - const auto& gyroscope = this->vpadStatus.gyro; - - data.push_back(gyroscope.x); - data.push_back(gyroscope.y); - data.push_back(gyroscope.z); - - return data; - } - default: - break; - } - - return data; - } -} // namespace love diff --git a/platform/cafe/source/modules/joystick/JoystickModule.cpp b/platform/cafe/source/modules/joystick/JoystickModule.cpp index 5cd6b14df..8fa8a608b 100644 --- a/platform/cafe/source/modules/joystick/JoystickModule.cpp +++ b/platform/cafe/source/modules/joystick/JoystickModule.cpp @@ -3,7 +3,8 @@ #include #include -#include "utility/logfile.hpp" +#include "modules/joystick/kpad/Joystick.hpp" +#include "modules/joystick/vpad/Joystick.hpp" namespace love::joystick { @@ -11,34 +12,34 @@ namespace love::joystick { int count = 0; - VPADStatus status {}; + VPADStatus vpadStatus {}; VPADReadError error = VPAD_READ_SUCCESS; - VPADRead(VPAD_CHAN_0, &status, 1, &error); + VPADRead(VPAD_CHAN_0, &vpadStatus, 1, &error); if (error == VPAD_READ_SUCCESS || error == VPAD_READ_NO_SAMPLES) count++; for (int channel = 0; channel < 4; channel++) { - KPADStatus status {}; + KPADStatus kpadStatus {}; KPADError error = KPAD_ERROR_OK; - KPADReadEx((KPADChan)channel, &status, 1, &error); + KPADReadEx((KPADChan)channel, &kpadStatus, 1, &error); bool success = error == KPAD_ERROR_OK || error == KPAD_ERROR_NO_SAMPLES; - if (success && status.extensionType != 0xFF) + if (success && kpadStatus.extensionType != 0xFF) count++; } return count; } - /* - ** TODO: open either vpad::Joystick or kpad::Joystick based on what opens first. - */ JoystickBase* openJoystick(int index) { - return nullptr; + if (index == 0) + return new vpad::Joystick(index); + + return new kpad::Joystick(index); } } // namespace love::joystick diff --git a/platform/cafe/source/modules/joystick/kpad/Joystick.cpp b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp new file mode 100644 index 000000000..9280c83d0 --- /dev/null +++ b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp @@ -0,0 +1,497 @@ +#include "common/screen.hpp" + +#include "modules/joystick/kpad/Joystick.hpp" + +namespace love +{ + namespace kpad + { + Joystick::Joystick(int id) : JoystickBase(id) + {} + + Joystick::Joystick(int id, int index) : JoystickBase(id, index) + { + this->open(index); + } + + Joystick::~Joystick() + { + this->close(); + } + + void Joystick::update() + { + KPADReadEx(WPAD_CHAN_0, &this->status, 1, &this->error); + + if (this->error != KPAD_ERROR_NO_SAMPLES) + { + switch (this->getGamepadType()) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + { + this->state = { this->status.trigger, this->status.release, this->status.hold }; + break; + } + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + { + this->state = { this->status.classic.trigger, this->status.classic.release, + this->status.classic.hold }; + break; + } + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + { + this->state = { this->status.pro.trigger, this->status.pro.release, + this->status.pro.hold }; + break; + } + default: + break; + } + } + } + + bool Joystick::open(int64_t deviceId) + { + if (deviceId > WPAD_CHAN_4) + return false; + + int index = (int)deviceId; + this->close(); + + this->instanceId = index; + + WPADExtensionType extension; + if (WPADProbe(WPADChan(index - 1), &extension) < 0) + return false; + + switch (extension) + { + case WPAD_EXT_CORE: + { + this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_REMOTE; + break; + } + case WPAD_EXT_CLASSIC: + { + this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_CLASSIC; + break; + } + case WPAD_EXT_NUNCHUK: + { + this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK; + break; + } + case WPAD_EXT_PRO_CONTROLLER: + { + this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_U_PRO; + break; + } + default: + { + this->gamepadType = GAMEPAD_TYPE_UNKNOWN; + break; + } + } + + this->guid = love::getGamepadGUID(this->gamepadType); + if (!Joystick::getConstant(this->gamepadType, this->name)) + this->name = "Unknown"; + + this->joystickType = JOYSTICK_TYPE_GAMEPAD; + this->update(); + + return this->isConnected(); + } + + void Joystick::close() + { + this->instanceId = -1; + } + + bool Joystick::isConnected() const + { + switch (this->error) + { + case KPAD_ERROR_INVALID_CONTROLLER: + case KPAD_ERROR_UNINITIALIZED: + return false; + default: + return true; + } + + return false; + } + + float Joystick::getAxis(GamepadAxis axis) const + { + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + return 0.0f; + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + { + if (axis == GAMEPAD_AXIS_LEFTX) + return clamp(this->status.classic.leftStick.x); + else if (axis == GAMEPAD_AXIS_LEFTY) + return clamp(this->status.classic.leftStick.y); + else if (axis == GAMEPAD_AXIS_RIGHTX) + return clamp(this->status.classic.rightStick.x); + else if (axis == GAMEPAD_AXIS_RIGHTY) + return clamp(this->status.classic.rightStick.y); + else if (axis == GAMEPAD_AXIS_TRIGGERLEFT) + return this->status.classic.hold & WPAD_CLASSIC_BUTTON_ZL ? 1.0f : 0.0f; + else if (axis == GAMEPAD_AXIS_TRIGGERRIGHT) + return this->status.classic.hold & WPAD_CLASSIC_BUTTON_ZR ? 1.0f : 0.0f; + + break; + } + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: + { + if (axis == GAMEPAD_AXIS_LEFTX) + return clamp(this->status.nunchuk.stick.x); + else if (axis == GAMEPAD_AXIS_LEFTY) + return clamp(this->status.nunchuk.stick.y); + + break; + } + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + { + if (axis == GAMEPAD_AXIS_LEFTX) + return clamp(this->status.pro.leftStick.x); + else if (axis == GAMEPAD_AXIS_LEFTY) + return clamp(this->status.pro.leftStick.y); + else if (axis == GAMEPAD_AXIS_RIGHTX) + return clamp(this->status.pro.rightStick.x); + else if (axis == GAMEPAD_AXIS_RIGHTY) + return clamp(this->status.pro.rightStick.y); + else if (axis == GAMEPAD_AXIS_TRIGGERLEFT) + return this->status.pro.hold & WPAD_PRO_TRIGGER_ZL ? 1.0f : 0.0f; + else if (axis == GAMEPAD_AXIS_TRIGGERRIGHT) + return this->status.pro.hold & WPAD_PRO_TRIGGER_ZR ? 1.0f : 0.0f; + + break; + } + default: + break; + } + + return 0.0f; + } + + std::vector Joystick::getAxes() const + { + std::vector axes; + int count = this->getAxisCount(); + + if (!this->isConnected() || count <= 0) + return axes; + + axes.reserve(count); + + for (int i = 0; i < count; i++) + axes.push_back(this->getAxis((GamepadAxis)i)); + + return axes; + } + + template + bool Joystick::isButtonDown(std::span buttons) const + { + T result; + + for (const auto& button : buttons) + { + if (!Joystick::getConstant(button, result)) + continue; + + if (this->state.pressed & result) + { + this->state.pressed ^= result; + return true; + } + } + + return false; + } + + template + bool Joystick::isButtonUp(std::span buttons) const + { + T result; + + for (const auto& button : buttons) + { + if (!Joystick::getConstant(button, result)) + continue; + + if (this->state.released & result) + { + this->state.released ^= result; + return true; + } + } + + return false; + } + + bool Joystick::isDown(std::span buttons) const + { + if (!this->isConnected()) + return false; + + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + return isButtonDown(buttons); + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: + return isButtonDown(buttons) || isButtonDown(buttons); + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + return isButtonDown(buttons); + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + return isButtonDown(buttons); + default: + break; + } + + return false; + } + + bool Joystick::isUp(std::span buttons) const + { + if (!this->isConnected()) + return false; + + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + return isButtonUp(buttons); + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: + return isButtonUp(buttons) || isButtonUp(buttons); + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + return isButtonUp(buttons); + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + return isButtonUp(buttons); + default: + break; + } + + return false; + } + + template + bool Joystick::isAxisValueChanged(GamepadAxis axis) const + { + T result; + + if (!Joystick::getConstant(axis, result)) + return false; + + if ((this->state.held & result) || (this->state.released & result)) + { + this->state.held ^= result; + return true; + } + + return false; + } + + bool Joystick::isAxisChanged(GamepadAxis axis) const + { + if (!this->isConnected()) + return false; + + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + break; + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: + return isAxisValueChanged(axis); + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + return isAxisValueChanged(axis); + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + return isAxisValueChanged(axis); + default: + break; + } + + return false; + } + + void Joystick::setPlayerIndex(int) + {} + + int Joystick::getPlayerIndex() const + { + return this->id; + } + + Joystick::JoystickInput Joystick::getGamepadMapping(const GamepadInput& input) const + { + JoystickInput result {}; + + return result; + } + + std::string Joystick::getGamepadMappingString() const + { + return std::string(); + } + + bool Joystick::isVibrationSupported() const + { + return false; + } + + bool Joystick::setVibration(float, float, float) + { + return false; + } + + bool Joystick::setVibration() + { + return false; + } + + void Joystick::getVibration(float&, float&) const + {} + + bool Joystick::hasSensor(Sensor::SensorType type) const + { + switch (type) + { + case Sensor::SENSOR_ACCELEROMETER: + return getGamepadHasAccelerometer(this->gamepadType); + case Sensor::SENSOR_GYROSCOPE: + return getGamepadHasGyroscope(this->gamepadType); + default: + break; + } + + return false; + } + + bool Joystick::isSensorEnabled(Sensor::SensorType type) const + { + return this->sensors.at(type) == true; + } + + void Joystick::setSensorEnabled(Sensor::SensorType type, bool enable) + { + if (!this->hasSensor(type)) + { + std::string_view name = "Unknown"; + Sensor::getConstant(type, name); + + throw love::Exception("\"{}\" gamepad sensor is not supported", name); + } + + switch (type) + { + case Sensor::SENSOR_ACCELEROMETER: + case Sensor::SENSOR_GYROSCOPE: + this->sensors[type] = enable; + break; + default: + break; + } + } + + std::vector Joystick::getSensorData(Sensor::SensorType type) const + { + std::vector data {}; + + if (!this->hasSensor(type)) + { + std::string_view name = "Unknown"; + Sensor::getConstant(type, name); + + throw love::Exception("\"{}\" gamepad sensor is not enabled.", name); + } + + data.reserve(3); + + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + { + data.push_back(this->status.acc.x); + data.push_back(this->status.acc.y); + data.push_back(this->status.acc.z); + break; + } + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: + data.push_back(this->status.acc.x); + data.push_back(this->status.acc.y); + data.push_back(this->status.acc.z); + + data.push_back(this->status.nunchuk.acc.x); + data.push_back(this->status.nunchuk.acc.y); + data.push_back(this->status.nunchuk.acc.z); + break; + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + default: + break; + } + + return data; + } + + static Vector2 ndcToScreen(const KPADVec2D& input) + { + Vector2 result {}; + const auto& info = love::getScreenInfo((Screen)0); + + result.x = ((input.x) / 2) * info.width; + result.y = ((input.y + 1.0f) / 2) * info.height; + + return result; + } + + Vector2 Joystick::getPosition() const + { + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: + { + if (this->status.posValid) + return ndcToScreen(this->status.pos); + + break; + } + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + default: + break; + } + + return Vector2 {}; + } + + Vector2 Joystick::getAngle() const + { + Vector2 result {}; + + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: + { + if (!this->status.posValid) + break; + + result.x = this->status.angle.x; + result.y = this->status.angle.y; + break; + } + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + default: + break; + } + + return result; + } + } // namespace kpad +} // namespace love diff --git a/platform/cafe/source/modules/joystick/vpad/Joystick.cpp b/platform/cafe/source/modules/joystick/vpad/Joystick.cpp new file mode 100644 index 000000000..eb44b4d2f --- /dev/null +++ b/platform/cafe/source/modules/joystick/vpad/Joystick.cpp @@ -0,0 +1,286 @@ +#include "modules/joystick/vpad/Joystick.hpp" + +namespace love +{ + namespace vpad + { + Joystick::Joystick(int id) : JoystickBase(id) + {} + + Joystick::Joystick(int id, int index) : JoystickBase(id, index) + { + this->open(index); + } + + Joystick::~Joystick() + { + this->close(); + } + + void Joystick::update() + { + VPADRead(VPAD_CHAN_0, &this->status, 1, &this->error); + + const auto& status = this->status; + if (this->error != VPAD_READ_NO_SAMPLES) + this->state = { status.trigger, status.release, status.hold }; + } + + bool Joystick::open(int64_t deviceId) + { + int index = (int)deviceId; + this->close(); + + this->instanceId = 0; + + this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD; + this->guid = love::getGamepadGUID(this->gamepadType); + + if (!Joystick::getConstant(this->gamepadType, this->name)) + this->name = "Unknown"; + + this->joystickType = JOYSTICK_TYPE_GAMEPAD; + this->update(); + + return this->isConnected(); + } + + void Joystick::close() + { + this->instanceId = -1; + } + + bool Joystick::isConnected() const + { + switch (this->error) + { + case VPAD_READ_INVALID_CONTROLLER: + case VPAD_READ_UNINITIALIZED: + return false; + default: + return true; + } + + return false; + } + + float Joystick::getAxis(GamepadAxis axis) const + { + const auto status = this->status; + + switch (axis) + { + case GAMEPAD_AXIS_LEFTX: + return clamp(status.leftStick.x); + case GAMEPAD_AXIS_LEFTY: + return clamp(status.leftStick.y); + case GAMEPAD_AXIS_RIGHTX: + return clamp(status.rightStick.x); + case GAMEPAD_AXIS_RIGHTY: + return clamp(status.rightStick.y); + case GAMEPAD_AXIS_TRIGGERLEFT: + return status.hold & VPAD_BUTTON_ZL ? 1.0f : 0.0f; + case GAMEPAD_AXIS_TRIGGERRIGHT: + return status.hold & VPAD_BUTTON_ZR ? 1.0f : 0.0f; + default: + return 0.0f; + } + + return 0.0f; + } + + std::vector Joystick::getAxes() const + { + std::vector axes; + int count = this->getAxisCount(); + + if (!this->isConnected() || count <= 0) + return axes; + + axes.reserve(count); + + for (int i = 0; i < count; i++) + axes.push_back(this->getAxis((GamepadAxis)i)); + + return axes; + } + + bool Joystick::isDown(std::span buttons) const + { + VPADButtons result; + + for (const auto& button : buttons) + { + if (!Joystick::getConstant(button, result)) + continue; + + if (this->state.pressed & result) + { + this->state.pressed ^= result; + return true; + } + } + + return false; + } + + bool Joystick::isUp(std::span buttons) const + { + if (!this->isConnected()) + return false; + + VPADButtons result; + + for (GamepadButton button : buttons) + { + if (!Joystick::getConstant(button, result)) + continue; + + if (this->state.released & result) + { + this->state.released ^= result; + return true; + } + } + + return false; + } + + bool Joystick::isAxisChanged(GamepadAxis axis) const + { + if (!this->isConnected()) + return false; + + VPADAxis result; + + if (!Joystick::getConstant(axis, result)) + return false; + + if ((this->state.held & result) || (this->state.released & result)) + { + this->state.held ^= result; + return true; + } + + return false; + } + + void Joystick::setPlayerIndex(int) + {} + + int Joystick::getPlayerIndex() const + { + return this->id; + } + + Joystick::JoystickInput Joystick::getGamepadMapping(const GamepadInput& input) const + { + JoystickInput result {}; + + return result; + } + + std::string Joystick::getGamepadMappingString() const + { + return std::string(); + } + + bool Joystick::isVibrationSupported() const + { + return false; + } + + bool Joystick::setVibration(float, float, float) + { + return false; + } + + bool Joystick::setVibration() + { + return false; + } + + void Joystick::getVibration(float&, float&) const + {} + + bool Joystick::hasSensor(Sensor::SensorType type) const + { + switch (type) + { + case Sensor::SENSOR_ACCELEROMETER: + return getGamepadHasAccelerometer(this->gamepadType); + case Sensor::SENSOR_GYROSCOPE: + return getGamepadHasGyroscope(this->gamepadType); + default: + break; + } + + return false; + } + + bool Joystick::isSensorEnabled(Sensor::SensorType type) const + { + return this->sensors.at(type) == true; + } + + void Joystick::setSensorEnabled(Sensor::SensorType type, bool enable) + { + if (!this->hasSensor(type)) + { + std::string_view name = "Unknown"; + Sensor::getConstant(type, name); + + throw love::Exception("\"{}\" gamepad sensor is not supported", name); + } + + switch (type) + { + case Sensor::SENSOR_ACCELEROMETER: + case Sensor::SENSOR_GYROSCOPE: + this->sensors[type] = enable; + break; + default: + break; + } + } + + std::vector Joystick::getSensorData(Sensor::SensorType type) const + { + std::vector data {}; + + if (!this->hasSensor(type)) + { + std::string_view name = "Unknown"; + Sensor::getConstant(type, name); + + throw love::Exception("\"{}\" gamepad sensor is not enabled.", name); + } + + data.reserve(3); + + switch (type) + { + case Sensor::SENSOR_ACCELEROMETER: + { + data.push_back(this->status.accelorometer.acc.x); + data.push_back(this->status.accelorometer.acc.y); + data.push_back(this->status.accelorometer.acc.z); + + return data; + } + case Sensor::SENSOR_GYROSCOPE: + { + data.push_back(this->status.gyro.x); + data.push_back(this->status.gyro.y); + data.push_back(this->status.gyro.z); + + return data; + } + default: + break; + } + + return data; + } + } // namespace vpad +} // namespace love diff --git a/platform/cafe/source/modules/window/Window.cpp b/platform/cafe/source/modules/window/Window.cpp index 4fcec1c95..5ac0316d9 100644 --- a/platform/cafe/source/modules/window/Window.cpp +++ b/platform/cafe/source/modules/window/Window.cpp @@ -4,7 +4,7 @@ namespace love { - Window::Window() : WindowBase("love.window.citro3d") + Window::Window() : WindowBase("love.window.gx2") { this->setDisplaySleepEnabled(false); } diff --git a/platform/ctr/CMakeLists.txt b/platform/ctr/CMakeLists.txt index 83a76dc48..7ed60e1dc 100644 --- a/platform/ctr/CMakeLists.txt +++ b/platform/ctr/CMakeLists.txt @@ -39,10 +39,15 @@ source/boot.cpp source/common/screen.cpp source/driver/audio/DigitalSound.cpp source/driver/display/Framebuffer.cpp -source/driver/display/Renderer.cpp +source/driver/display/citro3d.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp +source/modules/font/BCFNTRasterizer.cpp +source/modules/font/Font.cpp source/modules/graphics/Graphics.cpp +source/modules/graphics/Shader.cpp +source/modules/graphics/Texture.cpp +source/modules/graphics/Font.cpp source/modules/joystick/Joystick.cpp source/modules/joystick/JoystickModule.cpp source/modules/keyboard/Keyboard.cpp diff --git a/platform/ctr/assets/shaders/main.v.pica b/platform/ctr/assets/shaders/main.v.pica index 898152510..dc1920125 100644 --- a/platform/ctr/assets/shaders/main.v.pica +++ b/platform/ctr/assets/shaders/main.v.pica @@ -10,19 +10,20 @@ ; Inputs .in inPosition v0 -.in inColor v1 -.in inTexCoord v2 +.in inTexCoord v1 +.in inColor v2 ; Outputs .out outPosition position -.out outColor color .out outTexCoord0 texcoord0 +.out outColor color ; void main() .proc main ; r0 = vec4(inPosition, 1.0) - mov r0.xyz, inPosition - mov r0.w, ones + mov r0.xy, inPosition + mov r0.z, zeros + mov r0.w, ones ; pos = mdlvMtx * inPosition; dp4 r1.x, mdlvMtx[0], r0 diff --git a/platform/ctr/include/boot.hpp b/platform/ctr/include/boot.hpp index 64904c5de..d1c901f69 100644 --- a/platform/ctr/include/boot.hpp +++ b/platform/ctr/include/boot.hpp @@ -20,8 +20,6 @@ namespace love std::string getApplicationPath(const std::string& argv0); - static bool mcuHwcInited = false; - int preInit(); bool mainLoop(lua_State* L, int argc, int* nres); diff --git a/platform/ctr/include/driver/display/Framebuffer.hpp b/platform/ctr/include/driver/display/Framebuffer.hpp index 2ccf670cd..368374c21 100644 --- a/platform/ctr/include/driver/display/Framebuffer.hpp +++ b/platform/ctr/include/driver/display/Framebuffer.hpp @@ -18,8 +18,6 @@ namespace love void destroy(); - void setViewport(const Rect& viewport); - void setScissor(const Rect& scissor); C3D_RenderTarget* get() const @@ -37,13 +35,15 @@ namespace love return this->projection; } + static void calculateBounds(const Rect& bounds, Rect& out, const int width, const int height); + private: - static constexpr auto FORMAT = GPU_RB_DEPTH16; + static constexpr auto DEPTH_FORMAT = GPU_RB_DEPTH16; + static constexpr auto COLOR_FORMAT = GPU_RB_RGBA8; static constexpr auto DISPLAY_TRANSFER_FLAGS = GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | - GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | - GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO); C3D_RenderTarget* target; diff --git a/platform/ctr/include/driver/display/Renderer.hpp b/platform/ctr/include/driver/display/citro3d.hpp similarity index 59% rename from platform/ctr/include/driver/display/Renderer.hpp rename to platform/ctr/include/driver/display/citro3d.hpp index dff5e823e..461f21bb3 100644 --- a/platform/ctr/include/driver/display/Renderer.hpp +++ b/platform/ctr/include/driver/display/citro3d.hpp @@ -8,30 +8,48 @@ #include "driver/display/Framebuffer.hpp" #include "driver/display/Renderer.tcc" -#include "modules/graphics/vertex.hpp" +#include "modules/graphics/renderstate.hpp" +#include "modules/graphics/samplerstate.hpp" + +#include "driver/graphics/DrawCommand.hpp" + +using C3D_IndexType = decltype(C3D_UNSIGNED_BYTE); namespace love { - class Renderer : public RendererBase + + class citro3d : public RendererBase { public: - Renderer(); + enum TexEnvMode + { + TEXENV_MODE_PRIMITIVE, + TEXENV_MODE_TEXTURE, + TEXENV_MODE_FONT, + TEXENV_MODE_MAX_ENUM + }; + + citro3d(); void initialize(); - ~Renderer(); + void setupContext(); + + ~citro3d(); void clear(const Color& color); void clearDepthStencil(int depth, uint8_t mask, double stencil); - void bindFramebuffer(); + C3D_RenderTarget* getFramebuffer(); + + void bindFramebuffer(C3D_RenderTarget* framebuffer); void present(); void setBlendState(const BlendState& state); - void setViewport(const Rect& viewport, bool tilt); + void setViewport(int width, int height, bool tilt); void setScissor(const Rect& scissor); @@ -41,6 +59,18 @@ namespace love void setVertexWinding(Winding winding); + void setSamplerState(C3D_Tex* texture, SamplerState state); + + virtual void prepareDraw(GraphicsBase* graphics) override; + + void setVertexAttributes(const VertexAttributes& attributes, const BufferBindings& buffers); + + void bindTextureToUnit(TextureType target, C3D_Tex* texture, int unit, bool isFont = false); + + void bindTextureToUnit(TextureBase* texture, int unit, bool isFont = false); + + C3D_RenderTarget* getInternalBackbuffer() const; + void setWideMode(bool wide) { this->modeChanged([this, wide]() { gfxSetWide(wide); }); @@ -61,18 +91,24 @@ namespace love return gfxIs3D(); } - // clang-format off - ENUMMAP_DECLARE(PrimitiveModes, PrimitiveType, GPU_Primitive_t, - { PRIMITIVE_TRIANGLES, GPU_TRIANGLES }, - { PRIMITIVE_TRIANGLE_FAN, GPU_TRIANGLE_FAN }, - { PRIMITIVE_TRIANGLE_STRIP, GPU_TRIANGLE_STRIP } - ); + void ensureInFrame(); + + void copyCurrentScanBuffer() + {} + + static GPU_TEXTURE_MODE_PARAM getTextureType(TextureType type); + static GPU_Primitive_t getPrimitiveType(PrimitiveType type); + + // clang-format off ENUMMAP_DECLARE(PixelFormats, PixelFormat, GPU_TEXCOLOR, - { PIXELFORMAT_RGBA8_UNORM, GPU_RGBA8 }, - { PIXELFORMAT_RGBA8_UNORM, GPU_RGB8 }, - { PIXELFORMAT_RGBA4_UNORM, GPU_RGBA4 }, - { PIXELFORMAT_RGB565_UNORM, GPU_RGB565 }, + { PIXELFORMAT_RGBA8_UNORM, GPU_RGBA8 }, + { PIXELFORMAT_RGBA4_UNORM, GPU_RGBA4 }, + { PIXELFORMAT_RGB565_UNORM, GPU_RGB565 }, + { PIXELFORMAT_RGB5A1_UNORM, GPU_RGBA5551 }, + { PIXELFORMAT_ETC1_UNORM, GPU_ETC1 }, + { PIXELFORMAT_LA8_UNORM, GPU_LA8 }, + { PIXELFORMAT_A4_UNORM, GPU_A4 } ); ENUMMAP_DECLARE(CullModes, CullMode, GPU_CULLMODE, @@ -105,7 +141,23 @@ namespace love // clang-format on private: - void ensureInFrame(); + static GPU_TEXTURE_WRAP_PARAM getWrapMode(SamplerState::WrapMode mode); + + static int getTextureUnit(GPU_TEXUNIT unit); + + struct Context : public ContextBase + { + C3D_Mtx modelView; + C3D_Mtx projection; + + C3D_RenderTarget* boundFramebuffer = nullptr; + + int currentTextureUnit = 0; + std::vector boundTextures[TEXTURE_MAX_ENUM + 1]; + TexEnvMode texEnvMode = TEXENV_MODE_MAX_ENUM; + } context; + + bool isDefaultFramebufferActive() const; void createFramebuffers(); @@ -119,13 +171,10 @@ namespace love this->createFramebuffers(); } - struct Context : public ContextBase - { - C3D_Mtx modelView; - C3D_Mtx projection; - Framebuffer target; - } context; - std::vector targets; + + void updateTexEnvMode(TexEnvMode mode); }; + + extern citro3d c3d; } // namespace love diff --git a/platform/ctr/include/driver/graphics/StreamBuffer.hpp b/platform/ctr/include/driver/graphics/StreamBuffer.hpp new file mode 100644 index 000000000..ee92a7a9c --- /dev/null +++ b/platform/ctr/include/driver/graphics/StreamBuffer.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "driver/graphics/StreamBuffer.tcc" +#include "modules/graphics/Volatile.hpp" + +#include <3ds.h> +#include + +namespace love +{ + template + class StreamBuffer final : public StreamBufferBase + { + public: + StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size), buffer {} + { + this->data = (T*)linearAlloc(this->bufferSize); + + if (this->data == nullptr) + throw love::Exception(E_OUT_OF_MEMORY); + + if (usage != BUFFERUSAGE_VERTEX) + return; + + BufInfo_Init(&this->buffer); + BufInfo_Add(&this->buffer, this->data, sizeof(Vertex), 3, 0x210); + + C3D_SetBufInfo(&this->buffer); + } + + MapInfo map(size_t) + { + MapInfo info {}; + info.data = &this->data[this->index]; + info.size = this->bufferSize - this->frameGPUReadOffset; + + return info; + } + + ~StreamBuffer() + { + linearFree(this->data); + } + + ptrdiff_t getHandle() const override + { + return (ptrdiff_t)this->data; + } + + private: + T* data; + C3D_BufInfo buffer; + }; +} // namespace love diff --git a/platform/ctr/include/modules/font/BCFNTRasterizer.hpp b/platform/ctr/include/modules/font/BCFNTRasterizer.hpp new file mode 100644 index 000000000..ff4032ba5 --- /dev/null +++ b/platform/ctr/include/modules/font/BCFNTRasterizer.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "modules/font/Rasterizer.hpp" + +#include <3ds.h> + +#include + +namespace love +{ + class BCFNTRasterizer : public Rasterizer + { + public: + static bool accepts(Data* data); + + static Type type; + + BCFNTRasterizer(Data* data, int size); + + ~BCFNTRasterizer(); + + TextShaper* newTextShaper() override; + + int getLineHeight() const + { + return 1; + } + + bool hasGlyph(uint32_t) const override; + + int getGlyphSpacing(uint32_t glyph) const override; + + int getGlyphIndex(uint32_t) const override; + + GlyphData* getGlyphDataForIndex(int index) const override; + + int getGlyphCount() const override; + + float getKerning(uint32_t, uint32_t) const override + { + return 0.0f; + } + + DataType getDataType() const override + { + return DATA_BCFNT; + } + + ptrdiff_t getHandle() const override; + + private: + CFNT_s* getUserdata(Data* data) const; + + int glyphCount; + float scale; + + CFNT_s* userdata; + }; +} // namespace love diff --git a/platform/ctr/include/modules/font/Font.hpp b/platform/ctr/include/modules/font/Font.hpp new file mode 100644 index 000000000..0c9d1624f --- /dev/null +++ b/platform/ctr/include/modules/font/Font.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "modules/font/Font.tcc" + +#include <3ds.h> + +using SystemFontType = CFG_Region; + +namespace love +{ + class FontModule : public FontModuleBase + { + public: + FontModule(); + + static SystemFont* loadSystemFontByType(CFG_Region region); + + static CFNT_s* loadSystemFont(CFG_Region region, size_t& size); + + virtual Rasterizer* newRasterizer(FileData* data) const override; + + Rasterizer* newTrueTypeRasterizer(Data* data, int size, + const Rasterizer::Settings& settings) const override; + + using FontModuleBase::newTrueTypeRasterizer; + + // clang-format off + STRINGMAP_DECLARE(SystemFonts, CFG_Region, + { "standard", CFG_REGION_USA }, + { "chinese", CFG_REGION_CHN }, + { "korean", CFG_REGION_KOR }, + { "taiwanese", CFG_REGION_TWN } + ); + // clang-format on + + private: + static constexpr auto FONT_ARCHIVE_TITLE = 0x0004009B00014002ULL; + + static inline std::array fontPaths = { + "font:/cbf_std.bcfnt.lz", + "font:/cbf_zh-Hans-CN.bcfnt.lz", + "font:/cbf_ko-Hang-KR.bcfnt.lz", + "font:/cbf_zh-Hant-TW.bcfnt.lz", + }; + }; +} // namespace love diff --git a/platform/ctr/include/modules/graphics/Buffer.hpp b/platform/ctr/include/modules/graphics/Buffer.hpp new file mode 100644 index 000000000..ea53d3dce --- /dev/null +++ b/platform/ctr/include/modules/graphics/Buffer.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "common/Range.hpp" + +#include "modules/graphics/Buffer.tcc" +#include "modules/graphics/Volatile.hpp" + +#include + +namespace love +{ + class Buffer : public BufferBase, public Volatile + { + public: + Buffer::Buffer(GraphicsBase* graphics, const Settings& settings, + const std::vector& format, const void* data, size_t size, + size_t arraylength); + + virtual ~Buffer(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + void* map(MapType map, size_t offset, size_t size) override; + + void unmap(size_t usedoffset, size_t usedsize) override; + + bool fill(size_t offset, size_t size, const void* data) override; + + void copyTo(BufferBase* dest, size_t sourceoffset, size_t destoffset, size_t size) override; + + ptrdiff_t getHandle() const override + { + return 0; + }; + + BufferUsage getMapUsage() const + { + return mapUsage; + } + + private: + bool load(const void* data); + + void clearInternal(size_t offset, size_t size) override; + + BufferUsage mapUsage = BUFFERUSAGE_VERTEX; + + char* memoryMap = nullptr; + bool ownsMemoryMap = false; + + Range mappedRange; + + C3D_BufInfo* buffer; + C3D_Tex* texture; + }; +} // namespace love diff --git a/platform/ctr/include/modules/graphics/Font.hpp b/platform/ctr/include/modules/graphics/Font.hpp new file mode 100644 index 000000000..e29fa15e4 --- /dev/null +++ b/platform/ctr/include/modules/graphics/Font.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "modules/font/BCFNTRasterizer.hpp" +#include "modules/graphics/Font.tcc" + +namespace love +{ + class Font : public FontBase + { + public: + Font(Rasterizer* rasterizer, const SamplerState& samplerState); + + virtual void createTexture() override; + + bool loadVolatile() override; + + private: + virtual const FontBase::Glyph& addGlyph(TextShaper::GlyphIndex glyphIndex) override; + }; +} // namespace love diff --git a/platform/ctr/include/modules/graphics/Graphics.hpp b/platform/ctr/include/modules/graphics/Graphics.hpp index 4ead2d97c..dcd7e2f62 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -1,41 +1,80 @@ #pragma once #include "modules/graphics/Graphics.tcc" +#include "modules/graphics/Texture.hpp" namespace love { - class Graphics : public GraphicsBase + class Graphics : public GraphicsBase { public: Graphics(); - void clearImpl(OptionalColor color, OptionalInt depth, OptionalDouble stencil); + ~Graphics(); - using GraphicsBase::clear; + virtual void initCapabilities() override; - void presentImpl(); + virtual void captureScreenshot(const ScreenshotInfo& info) override; - void setScissorImpl(const Rect& scissor); + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) override; - void setScissorImpl(); + virtual void clear(const std::vector& colors, OptionalInt stencil, + OptionalDouble depth) override; - void setFrontFaceWindingImpl(Winding winding); + virtual void present(void* screenshotCallbackData) override; - void setColorMaskImpl(ColorChannelMask mask); + virtual void setScissor(const Rect& scissor) override; - void setBlendStateImpl(const BlendState& state); + virtual void setScissor() override; - void getRendererInfoImpl(RendererInfo& info) const; + virtual void setFrontFaceWinding(Winding winding) override; - bool setModeImpl(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa); + virtual void setColorMask(ColorChannelMask mask) override; + + virtual void setPointSize(float size) override + { + if (size != this->states.back().pointSize) + this->flushBatchedDraws(); + + this->states.back().pointSize = size; + } + + virtual void setBlendState(const BlendState& state) override; + + virtual FontBase* newFont(Rasterizer* data) override; + + virtual FontBase* newDefaultFont(int size, const Rasterizer::Settings& settings) override; + + virtual bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa) override; + + virtual void setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) override; + + virtual bool isPixelFormatSupported(PixelFormat format, uint32_t usage) override; + + void draw(const DrawIndexedCommand& command) override; + + void draw(const DrawCommand& command) override; + + using GraphicsBase::draw; + + void points(Vector2* positions, const Color* colors, int count); bool isActive() const; - void unsetModeImpl(); + virtual void unsetMode() override; + + void setActiveScreen() override; void setViewport(int x, int y, int width, int height); + C3D_RenderTarget* getInternalBackbuffer() const; + + // clang-format off + virtual TextureBase* newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data = nullptr) override; + // clang-format on + bool is3D() const; void set3D(bool enable); diff --git a/platform/ctr/include/modules/graphics/Shader.hpp b/platform/ctr/include/modules/graphics/Shader.hpp new file mode 100644 index 000000000..2e8c81261 --- /dev/null +++ b/platform/ctr/include/modules/graphics/Shader.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "modules/graphics/Graphics.hpp" +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/Volatile.hpp" + +#include <3ds.h> + +namespace love +{ + class Shader final : public ShaderBase, public Volatile + { + public: + struct UniformInfo + { + int8_t location; + std::string name; + }; + + Shader(); + + virtual ~Shader(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + void attach() override; + + ptrdiff_t getHandle() const override; + + const UniformInfo getUniform(const std::string& name) const; + + bool hasUniform(const std::string& name) const; + + void updateBuiltinUniforms(GraphicsBase* graphics, C3D_Mtx mdlvMtx, const C3D_Mtx& projMtx); + + private: + bool validate(const char* filepath, std::string& error); + + DVLB_s* dvlb = nullptr; + shaderProgram_s program; + + std::vector data; + UniformInfo uniforms[0x02]; + }; +} // namespace love diff --git a/platform/ctr/include/modules/graphics/Texture.hpp b/platform/ctr/include/modules/graphics/Texture.hpp new file mode 100644 index 000000000..c5d60edc2 --- /dev/null +++ b/platform/ctr/include/modules/graphics/Texture.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/Volatile.hpp" + +#include + +namespace love +{ + class Texture final : public TextureBase, public Volatile + { + public: + Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data); + + virtual ~Texture(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + ptrdiff_t getHandle() const override; + + ptrdiff_t getRenderTargetHandle() const override; + + ptrdiff_t getSamplerHandle() const override; + + void updateQuad(Quad* quad); + + void setSamplerState(const SamplerState& state) override; + + void uploadByteData(const void* data, size_t size, int slice, int mipmap, const Rect& rect) override; + + void generateMipmapsInternal() override; + + void setHandleData(ptrdiff_t data) override + { + this->texture->data = (void*)data; + } + + // bool validateDimensions(bool throwException) const; + + // void validatePixelFormat(Graphics& graphics) const; + + private: + void createTexture(); + + Slices slices; + + C3D_Tex* texture = nullptr; + C3D_RenderTarget* target = nullptr; + }; +} // namespace love diff --git a/platform/ctr/include/modules/keyboard/Keyboard.hpp b/platform/ctr/include/modules/keyboard/Keyboard.hpp index d1f3b91a6..6b4095561 100644 --- a/platform/ctr/include/modules/keyboard/Keyboard.hpp +++ b/platform/ctr/include/modules/keyboard/Keyboard.hpp @@ -24,6 +24,12 @@ namespace love { TYPE_QWERTY, SWKBD_TYPE_QWERTY }, { TYPE_NUMPAD, SWKBD_TYPE_NUMPAD } ); + + ENUMMAP_DECLARE(CtrKeyboardResults, KeyboardResult, SwkbdCallbackResult, + { RESULT_OK, SWKBD_CALLBACK_OK }, + { RESULT_CANCEL, SWKBD_CALLBACK_CLOSE }, + { RESULT_CONTINUE, SWKBD_CALLBACK_CONTINUE } + ); // clang-format on private: diff --git a/platform/ctr/libraries/luasocket.patch b/platform/ctr/libraries/luasocket.patch new file mode 100644 index 000000000..01bbcb478 --- /dev/null +++ b/platform/ctr/libraries/luasocket.patch @@ -0,0 +1,2397 @@ +diff --git a/inet.c b/inet.c +index 138c9ab..c262cf9 100755 +--- a/inet.c ++++ b/inet.c +@@ -139,8 +139,13 @@ static int inet_global_toip(lua_State *L) + + int inet_optfamily(lua_State* L, int narg, const char* def) + { ++ #if !defined(__3DS__) + static const char* optname[] = { "unspec", "inet", "inet6", NULL }; + static int optvalue[] = { AF_UNSPEC, AF_INET, AF_INET6, 0 }; ++ #else ++ static const char* optname[] = { "unspec", "inet", NULL }; ++ static int optvalue[] = { AF_UNSPEC, AF_INET, 0 }; ++ #endif + + return optvalue[luaL_checkoption(L, narg, def, optname)]; + } +@@ -348,10 +353,12 @@ static void inet_pushresolved(lua_State *L, struct hostent *hp) + \*-------------------------------------------------------------------------*/ + const char *inet_trycreate(p_socket ps, int family, int type, int protocol) { + const char *err = socket_strerror(socket_create(ps, family, type, protocol)); ++ #if !defined(__3DS__) + if (err == NULL && family == AF_INET6) { + int yes = 1; + setsockopt(*ps, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&yes, sizeof(yes)); + } ++ #endif + return err; + } + +@@ -369,6 +376,7 @@ const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm) + return socket_strerror(socket_connect(ps, (SA *) &sin, + sizeof(sin), tm)); + } ++ #if !defined(__3DS__) + case AF_INET6: { + struct sockaddr_in6 sin6; + struct in6_addr addrany = IN6ADDR_ANY_INIT; +@@ -378,6 +386,7 @@ const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm) + return socket_strerror(socket_connect(ps, (SA *) &sin6, + sizeof(sin6), tm)); + } ++ #endif + } + return NULL; + } +@@ -436,7 +445,9 @@ const char *inet_tryaccept(p_socket server, int family, p_socket client, + socklen_t len; + t_sockaddr_storage addr; + switch (family) { ++ #if !defined(__3DS__) + case AF_INET6: len = sizeof(struct sockaddr_in6); break; ++ #endif + case AF_INET: len = sizeof(struct sockaddr_in); break; + default: len = sizeof(addr); break; + } +diff --git a/inet.h b/inet.h +index 5618b61..f11d5ed 100644 +--- a/inet.h ++++ b/inet.h +@@ -22,6 +22,10 @@ + #define LUASOCKET_INET_ATON + #endif + ++#ifndef INET6_ADDRSTRLEN ++#define INET6_ADDRSTRLEN INET_ADDRSTRLEN ++#endif ++ + #ifndef _WIN32 + #pragma GCC visibility push(hidden) + #endif +diff --git a/makefile b/makefile +deleted file mode 100755 +index 06f4d19..0000000 +--- a/makefile ++++ /dev/null +@@ -1,461 +0,0 @@ +-# luasocket src/makefile +-# +-# Definitions in this section can be overriden on the command line or in the +-# environment. +-# +-# These are equivalent: +-# +-# export PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw +-# make +-# +-# and +-# +-# make PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw +- +-# PLAT: linux macosx win32 win64 mingw +-# platform to build for +-PLAT?=linux +- +-# LUAV: 5.1 5.2 5.3 5.4 +-# lua version to build against +-LUAV?=5.1 +- +-# MYCFLAGS: to be set by user if needed +-MYCFLAGS?= +- +-# MYLDFLAGS: to be set by user if needed +-MYLDFLAGS?= +- +-# DEBUG: NODEBUG DEBUG +-# debug mode causes luasocket to collect and returns timing information useful +-# for testing and debugging luasocket itself +-DEBUG?=NODEBUG +- +-# where lua headers are found for macosx builds +-# LUAINC_macosx: +-# /opt/local/include +-LUAINC_macosx_base?=/opt/local/include +-LUAINC_macosx?=$(LUAINC_macosx_base)/lua/$(LUAV) $(LUAINC_macosx_base)/lua$(LUAV) $(LUAINC_macosx_base)/lua-$(LUAV) +- +-# FIXME default should this default to fink or to macports? +-# What happens when more than one Lua version is installed? +-LUAPREFIX_macosx?=/opt/local +-CDIR_macosx?=lib/lua/$(LUAV) +-LDIR_macosx?=share/lua/$(LUAV) +- +-# LUAINC_linux: +-# /usr/include/lua$(LUAV) +-# /usr/local/include +-# /usr/local/include/lua$(LUAV) +-# where lua headers are found for linux builds +-LUAINC_linux_base?=/usr/include +-LUAINC_linux?=$(LUAINC_linux_base)/lua/$(LUAV) $(LUAINC_linux_base)/lua$(LUAV) +-LUAPREFIX_linux?=/usr/local +-CDIR_linux?=lib/lua/$(LUAV) +-LDIR_linux?=share/lua/$(LUAV) +- +-# LUAINC_freebsd: +-# /usr/local/include/lua$(LUAV) +-# where lua headers are found for freebsd builds +-LUAINC_freebsd_base?=/usr/local/include/ +-LUAINC_freebsd?=$(LUAINC_freebsd_base)/lua/$(LUAV) $(LUAINC_freebsd_base)/lua$(LUAV) +-LUAPREFIX_freebsd?=/usr/local/ +-CDIR_freebsd?=lib/lua/$(LUAV) +-LDIR_freebsd?=share/lua/$(LUAV) +- +-# where lua headers are found for mingw builds +-# LUAINC_mingw: +-# /opt/local/include +-LUAINC_mingw_base?=/usr/include +-LUAINC_mingw?=$(LUAINC_mingw_base)/lua/$(LUAV) $(LUAINC_mingw_base)/lua$(LUAV) +-LUALIB_mingw_base?=/usr/bin +-LUALIB_mingw?=$(LUALIB_mingw_base)/lua/$(LUAV)/lua$(subst .,,$(LUAV)).dll +-LUAPREFIX_mingw?=/usr +-CDIR_mingw?=lua/$(LUAV) +-LDIR_mingw?=lua/$(LUAV)/lua +- +- +-# LUAINC_win32: +-# LUALIB_win32: +-# where lua headers and libraries are found for win32 builds +-LUAPREFIX_win32?= +-LUAINC_win32?=$(LUAPREFIX_win32)/include/lua/$(LUAV) $(LUAPREFIX_win32)/include/lua$(LUAV) +-PLATFORM_win32?=Release +-CDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32) +-LDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32)/lua +-LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUAV)/$(PLATFORM_win32) +-LUALIBNAME_win32?=lua$(subst .,,$(LUAV)).lib +- +-# LUAINC_win64: +-# LUALIB_win64: +-# where lua headers and libraries are found for win64 builds +-LUAPREFIX_win64?= +-LUAINC_win64?=$(LUAPREFIX_win64)/include/lua/$(LUAV) $(LUAPREFIX_win64)/include/lua$(LUAV) +-PLATFORM_win64?=x64/Release +-CDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64) +-LDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64)/lua +-LUALIB_win64?=$(LUAPREFIX_win64)/lib/lua/$(LUAV)/$(PLATFORM_win64) +-LUALIBNAME_win64?=lua$(subst .,,$(LUAV)).lib +- +- +-# LUAINC_solaris: +-LUAINC_solaris_base?=/usr/include +-LUAINC_solaris?=$(LUAINC_solaris_base)/lua/$(LUAV) $(LUAINC_solaris_base)/lua$(LUAV) +-LUAPREFIX_solaris?=/usr/local +-CDIR_solaris?=lib/lua/$(LUAV) +-LDIR_solaris?=share/lua/$(LUAV) +- +-# prefix: /usr/local /usr /opt/local /sw +-# the top of the default install tree +-prefix?=$(LUAPREFIX_$(PLAT)) +- +-CDIR?=$(CDIR_$(PLAT)) +-LDIR?=$(LDIR_$(PLAT)) +- +-# DESTDIR: (no default) +-# used by package managers to install into a temporary destination +-DESTDIR?= +- +-#------ +-# Definitions below can be overridden on the make command line, but +-# shouldn't have to be. +- +- +-#------ +-# Install directories +-# +- +-INSTALL_DIR=install -d +-INSTALL_DATA=install -m644 +-INSTALL_EXEC=install +-INSTALL_TOP=$(DESTDIR)$(prefix) +- +-INSTALL_TOP_LDIR=$(INSTALL_TOP)/$(LDIR) +-INSTALL_TOP_CDIR=$(INSTALL_TOP)/$(CDIR) +- +-INSTALL_SOCKET_LDIR=$(INSTALL_TOP_LDIR)/socket +-INSTALL_SOCKET_CDIR=$(INSTALL_TOP_CDIR)/socket +-INSTALL_MIME_LDIR=$(INSTALL_TOP_LDIR)/mime +-INSTALL_MIME_CDIR=$(INSTALL_TOP_CDIR)/mime +- +-print: +- @echo PLAT=$(PLAT) +- @echo LUAV=$(LUAV) +- @echo DEBUG=$(DEBUG) +- @echo prefix=$(prefix) +- @echo LUAINC_$(PLAT)=$(LUAINC_$(PLAT)) +- @echo LUALIB_$(PLAT)=$(LUALIB_$(PLAT)) +- @echo INSTALL_TOP_CDIR=$(INSTALL_TOP_CDIR) +- @echo INSTALL_TOP_LDIR=$(INSTALL_TOP_LDIR) +- @echo CFLAGS=$(CFLAGS) +- @echo LDFLAGS=$(LDFLAGS) +- +-#------ +-# Supported platforms +-# +-PLATS= macosx linux win32 win64 mingw solaris +- +-#------ +-# Compiler and linker settings +-# for Mac OS X +-SO_macosx=so +-O_macosx=o +-CC_macosx=gcc +-DEF_macosx= -DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +-CFLAGS_macosx=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +-LDFLAGS_macosx= -bundle -undefined dynamic_lookup -o +-LD_macosx=gcc +-SOCKET_macosx=usocket.o +- +-#------ +-# Compiler and linker settings +-# for Linux +-SO_linux=so +-O_linux=o +-CC_linux=gcc +-DEF_linux=-DLUASOCKET_$(DEBUG) +-CFLAGS_linux=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_linux=-O -shared -fpic -o +-LD_linux=gcc +-SOCKET_linux=usocket.o +- +-#------ +-# Compiler and linker settings +-# for FreeBSD +-SO_freebsd=so +-O_freebsd=o +-CC_freebsd=gcc +-DEF_freebsd=-DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +-CFLAGS_freebsd=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_freebsd=-O -shared -fpic -o +-LD_freebsd=gcc +-SOCKET_freebsd=usocket.o +- +-#------ +-# Compiler and linker settings +-# for Solaris +-SO_solaris=so +-O_solaris=o +-CC_solaris=gcc +-DEF_solaris=-DLUASOCKET_$(DEBUG) +-CFLAGS_solaris=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_solaris=-lnsl -lsocket -lresolv -O -shared -fpic -o +-LD_solaris=gcc +-SOCKET_solaris=usocket.o +- +-#------ +-# Compiler and linker settings +-# for MingW +-SO_mingw=dll +-O_mingw=o +-CC_mingw=gcc +-DEF_mingw= -DLUASOCKET_$(DEBUG) \ +- -DWINVER=0x0501 +-CFLAGS_mingw=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +-LDFLAGS_mingw= $(LUALIB) -shared -Wl,-s -lws2_32 -o +-LD_mingw=gcc +-SOCKET_mingw=wsocket.o +- +- +-#------ +-# Compiler and linker settings +-# for Win32 +-SO_win32=dll +-O_win32=obj +-CC_win32=cl +-DEF_win32= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ +- //D "_CRT_SECURE_NO_WARNINGS" \ +- //D "_WINDLL" \ +- //D "LUASOCKET_$(DEBUG)" +-CFLAGS_win32=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +-LDFLAGS_win32= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ +- //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ +- /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ +- //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ +- //MACHINE:X86 /LIBPATH:"$(LUALIB)" \ +- $(LUALIBNAME_win32) ws2_32.lib //OUT: +- +-LD_win32=cl +-SOCKET_win32=wsocket.obj +- +-#------ +-# Compiler and linker settings +-# for Win64 +-SO_win64=dll +-O_win64=obj +-CC_win64=cl +-DEF_win64= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ +- //D "_CRT_SECURE_NO_WARNINGS" \ +- //D "_WINDLL" \ +- //D "LUASOCKET_$(DEBUG)" +-CFLAGS_win64=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +-LDFLAGS_win64= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ +- //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ +- /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ +- //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ +- /LIBPATH:"$(LUALIB)" \ +- $(LUALIBNAME_win64) ws2_32.lib //OUT: +- +-LD_win64=cl +-SOCKET_win64=wsocket.obj +- +-.SUFFIXES: .obj +- +-.c.obj: +- $(CC) $(CFLAGS) //Fo"$@" //c $< +- +-#------ +-# Output file names +-# +-SO=$(SO_$(PLAT)) +-O=$(O_$(PLAT)) +-SOCKET_V=3.0.0 +-MIME_V=1.0.3 +-SOCKET_SO=socket-$(SOCKET_V).$(SO) +-MIME_SO=mime-$(MIME_V).$(SO) +-UNIX_SO=unix.$(SO) +-SERIAL_SO=serial.$(SO) +-SOCKET=$(SOCKET_$(PLAT)) +- +-#------ +-# Settings selected for platform +-# +-CC=$(CC_$(PLAT)) +-DEF=$(DEF_$(PLAT)) +-CFLAGS=$(MYCFLAGS) $(CFLAGS_$(PLAT)) +-LDFLAGS=$(MYLDFLAGS) $(LDFLAGS_$(PLAT)) +-LD=$(LD_$(PLAT)) +-LUAINC= $(LUAINC_$(PLAT)) +-LUALIB= $(LUALIB_$(PLAT)) +- +-#------ +-# Modules belonging to socket-core +-# +-SOCKET_OBJS= \ +- luasocket.$(O) \ +- timeout.$(O) \ +- buffer.$(O) \ +- io.$(O) \ +- auxiliar.$(O) \ +- compat.$(O) \ +- options.$(O) \ +- inet.$(O) \ +- $(SOCKET) \ +- except.$(O) \ +- select.$(O) \ +- tcp.$(O) \ +- udp.$(O) +- +-#------ +-# Modules belonging mime-core +-# +-MIME_OBJS= \ +- mime.$(O) \ +- compat.$(O) +- +-#------ +-# Modules belonging unix (local domain sockets) +-# +-UNIX_OBJS=\ +- buffer.$(O) \ +- auxiliar.$(O) \ +- options.$(O) \ +- timeout.$(O) \ +- io.$(O) \ +- usocket.$(O) \ +- unixstream.$(O) \ +- unixdgram.$(O) \ +- compat.$(O) \ +- unix.$(O) +- +-#------ +-# Modules belonging to serial (device streams) +-# +-SERIAL_OBJS=\ +- buffer.$(O) \ +- compat.$(O) \ +- auxiliar.$(O) \ +- options.$(O) \ +- timeout.$(O) \ +- io.$(O) \ +- usocket.$(O) \ +- serial.$(O) +- +-#------ +-# Files to install +-# +-TO_SOCKET_LDIR= \ +- http.lua \ +- url.lua \ +- tp.lua \ +- ftp.lua \ +- headers.lua \ +- smtp.lua +- +-TO_TOP_LDIR= \ +- ltn12.lua \ +- socket.lua \ +- mime.lua +- +-#------ +-# Targets +-# +-default: $(PLAT) +- +- +-freebsd: +- $(MAKE) all-unix PLAT=freebsd +- +-macosx: +- $(MAKE) all-unix PLAT=macosx +- +-win32: +- $(MAKE) all PLAT=win32 +- +-win64: +- $(MAKE) all PLAT=win64 +- +-linux: +- $(MAKE) all-unix PLAT=linux +- +-mingw: +- $(MAKE) all PLAT=mingw +- +-solaris: +- $(MAKE) all-unix PLAT=solaris +- +-none: +- @echo "Please run" +- @echo " make PLATFORM" +- @echo "where PLATFORM is one of these:" +- @echo " $(PLATS)" +- +-all: $(SOCKET_SO) $(MIME_SO) +- +-$(SOCKET_SO): $(SOCKET_OBJS) +- $(LD) $(SOCKET_OBJS) $(LDFLAGS)$@ +- +-$(MIME_SO): $(MIME_OBJS) +- $(LD) $(MIME_OBJS) $(LDFLAGS)$@ +- +-all-unix: all $(UNIX_SO) $(SERIAL_SO) +- +-$(UNIX_SO): $(UNIX_OBJS) +- $(LD) $(UNIX_OBJS) $(LDFLAGS)$@ +- +-$(SERIAL_SO): $(SERIAL_OBJS) +- $(LD) $(SERIAL_OBJS) $(LDFLAGS)$@ +- +-install: +- $(INSTALL_DIR) $(INSTALL_TOP_LDIR) +- $(INSTALL_DATA) $(TO_TOP_LDIR) $(INSTALL_TOP_LDIR) +- $(INSTALL_DIR) $(INSTALL_SOCKET_LDIR) +- $(INSTALL_DATA) $(TO_SOCKET_LDIR) $(INSTALL_SOCKET_LDIR) +- $(INSTALL_DIR) $(INSTALL_SOCKET_CDIR) +- $(INSTALL_EXEC) $(SOCKET_SO) $(INSTALL_SOCKET_CDIR)/core.$(SO) +- $(INSTALL_DIR) $(INSTALL_MIME_CDIR) +- $(INSTALL_EXEC) $(MIME_SO) $(INSTALL_MIME_CDIR)/core.$(SO) +- +-install-unix: install +- $(INSTALL_EXEC) $(UNIX_SO) $(INSTALL_SOCKET_CDIR)/$(UNIX_SO) +- $(INSTALL_EXEC) $(SERIAL_SO) $(INSTALL_SOCKET_CDIR)/$(SERIAL_SO) +- +-local: +- $(MAKE) install INSTALL_TOP_CDIR=.. INSTALL_TOP_LDIR=.. +- +-clean: +- rm -f $(SOCKET_SO) $(SOCKET_OBJS) $(SERIAL_OBJS) +- rm -f $(MIME_SO) $(UNIX_SO) $(SERIAL_SO) $(MIME_OBJS) $(UNIX_OBJS) +- +-.PHONY: all $(PLATS) default clean echo none +- +-#------ +-# List of dependencies +-# +-compat.$(O): compat.c compat.h +-auxiliar.$(O): auxiliar.c auxiliar.h +-buffer.$(O): buffer.c buffer.h io.h timeout.h +-except.$(O): except.c except.h +-inet.$(O): inet.c inet.h socket.h io.h timeout.h usocket.h +-io.$(O): io.c io.h timeout.h +-luasocket.$(O): luasocket.c luasocket.h auxiliar.h except.h \ +- timeout.h buffer.h io.h inet.h socket.h usocket.h tcp.h \ +- udp.h select.h +-mime.$(O): mime.c mime.h +-options.$(O): options.c auxiliar.h options.h socket.h io.h \ +- timeout.h usocket.h inet.h +-select.$(O): select.c socket.h io.h timeout.h usocket.h select.h +-serial.$(O): serial.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- options.h unix.h buffer.h +-tcp.$(O): tcp.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- inet.h options.h tcp.h buffer.h +-timeout.$(O): timeout.c auxiliar.h timeout.h +-udp.$(O): udp.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- inet.h options.h udp.h +-unix.$(O): unix.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- options.h unix.h buffer.h +-usocket.$(O): usocket.c socket.h io.h timeout.h usocket.h +-wsocket.$(O): wsocket.c socket.h io.h timeout.h usocket.h +diff --git a/options.c b/options.c +index 3280c51..9a45e6f 100644 +--- a/options.c ++++ b/options.c +@@ -22,6 +22,22 @@ static int opt_set(lua_State *L, p_socket ps, int level, int name, + static int opt_get(lua_State *L, p_socket ps, int level, int name, + void *val, int* len); + ++static int set_opt_error(lua_State* L) ++{ ++ lua_pushnil(L); ++ lua_pushstring(L, "setsockopt failed: not supported"); ++ ++ return 2; ++} ++ ++static int get_opt_error(lua_State* L) ++{ ++ lua_pushnil(L); ++ lua_pushstring(L, "getsockopt failed: not supported"); ++ ++ return 2; ++} ++ + /*=========================================================================*\ + * Exported functions + \*=========================================================================*/ +@@ -136,6 +152,7 @@ int opt_set_tcp_keepintvl(lua_State *L, p_socket ps) + #endif + + /*------------------------------------------------------*/ ++#if !defined(__3DS__) + int opt_set_keepalive(lua_State *L, p_socket ps) + { + return opt_setboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); +@@ -167,6 +184,16 @@ int opt_get_broadcast(lua_State *L, p_socket ps) + { + return opt_getboolean(L, ps, SOL_SOCKET, SO_BROADCAST); + } ++#else ++int opt_set_keepalive(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_keepalive(lua_State *L, p_socket ps) { return get_opt_error(L); } ++ ++int opt_set_dontroute(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_dontroute(lua_State *L, p_socket ps) { return get_opt_error(L); } ++ ++int opt_set_broadcast(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_broadcast(lua_State *L, p_socket ps) { return get_opt_error(L); } ++#endif + + /*------------------------------------------------------*/ + int opt_set_recv_buf_size(lua_State *L, p_socket ps) +@@ -216,6 +243,7 @@ int opt_set_tcp_defer_accept(lua_State *L, p_socket ps) + #endif + + /*------------------------------------------------------*/ ++#if !defined(__3DS__) + int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) + { + return opt_setint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); +@@ -236,6 +264,13 @@ int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) + { + return opt_getint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); + } ++#else ++int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps) { return get_opt_error(L); } ++ ++int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) { return get_opt_error(L); } ++#endif + + /*------------------------------------------------------*/ + int opt_set_ip_multicast_loop(lua_State *L, p_socket ps) +@@ -249,6 +284,7 @@ int opt_get_ip_multicast_loop(lua_State *L, p_socket ps) + } + + /*------------------------------------------------------*/ ++#if !defined(__3DS__) + int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) + { + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); +@@ -258,6 +294,10 @@ int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) + { + return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); + } ++#else ++int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) { return get_opt_error(L); } ++#endif + + /*------------------------------------------------------*/ + int opt_set_linger(lua_State *L, p_socket ps) +@@ -299,6 +339,7 @@ int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) + } + + /*------------------------------------------------------*/ ++#if !defined(__3DS__) + int opt_set_ip_multicast_if(lua_State *L, p_socket ps) + { + const char *address = luaL_checkstring(L, 3); /* obj, name, ip */ +@@ -322,6 +363,10 @@ int opt_get_ip_multicast_if(lua_State *L, p_socket ps) + lua_pushstring(L, inet_ntoa(val)); + return 1; + } ++#else ++int opt_set_ip_multicast_if(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_get_ip_multicast_if(lua_State *L, p_socket ps) { return get_opt_error(L); } ++#endif + + /*------------------------------------------------------*/ + int opt_set_ip_add_membership(lua_State *L, p_socket ps) +@@ -335,6 +380,7 @@ int opt_set_ip_drop_membersip(lua_State *L, p_socket ps) + } + + /*------------------------------------------------------*/ ++#if !defined(__3DS__) + int opt_set_ip6_add_membership(lua_State *L, p_socket ps) + { + return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP); +@@ -355,7 +401,13 @@ int opt_set_ip6_v6only(lua_State *L, p_socket ps) + { + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); + } ++#else ++int opt_set_ip6_add_membership(lua_State *L, p_socket ps) { return set_opt_error(L); } ++int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps) { return set_opt_error(L); } + ++int opt_get_ip6_v6only(lua_State *L, p_socket ps) { return get_opt_error(L); } ++int opt_set_ip6_v6only(lua_State *L, p_socket ps) { return set_opt_error(L); } ++#endif + /*------------------------------------------------------*/ + int opt_get_error(lua_State *L, p_socket ps) + { +@@ -394,6 +446,7 @@ static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); + } + ++#if !defined(__3DS__) + static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) + { + struct ipv6_mreq val; /* obj, opt-name, table */ +@@ -419,6 +472,12 @@ static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) + } + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); + } ++#else ++static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) ++{ ++ return set_opt_error(L); ++} ++#endif + + static + int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len) +diff --git a/serial.c b/serial.c +deleted file mode 100644 +index 21485d3..0000000 +--- a/serial.c ++++ /dev/null +@@ -1,171 +0,0 @@ +-/*=========================================================================*\ +-* Serial stream +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "auxiliar.h" +-#include "socket.h" +-#include "options.h" +-#include "unix.h" +- +-#include +-#include +- +-/* +-Reuses userdata definition from unix.h, since it is useful for all +-stream-like objects. +- +-If we stored the serial path for use in error messages or userdata +-printing, we might need our own userdata definition. +- +-Group usage is semi-inherited from unix.c, but unnecessary since we +-have only one object type. +-*/ +- +-/*=========================================================================*\ +-* Internal function prototypes +-\*=========================================================================*/ +-static int global_create(lua_State *L); +-static int meth_send(lua_State *L); +-static int meth_receive(lua_State *L); +-static int meth_close(lua_State *L); +-static int meth_settimeout(lua_State *L); +-static int meth_getfd(lua_State *L); +-static int meth_setfd(lua_State *L); +-static int meth_dirty(lua_State *L); +-static int meth_getstats(lua_State *L); +-static int meth_setstats(lua_State *L); +- +-/* serial object methods */ +-static luaL_Reg serial_methods[] = { +- {"__gc", meth_close}, +- {"__tostring", auxiliar_tostring}, +- {"close", meth_close}, +- {"dirty", meth_dirty}, +- {"getfd", meth_getfd}, +- {"getstats", meth_getstats}, +- {"setstats", meth_setstats}, +- {"receive", meth_receive}, +- {"send", meth_send}, +- {"setfd", meth_setfd}, +- {"settimeout", meth_settimeout}, +- {NULL, NULL} +-}; +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-LUASOCKET_API int luaopen_socket_serial(lua_State *L) { +- /* create classes */ +- auxiliar_newclass(L, "serial{client}", serial_methods); +- /* create class groups */ +- auxiliar_add2group(L, "serial{client}", "serial{any}"); +- lua_pushcfunction(L, global_create); +- return 1; +-} +- +-/*=========================================================================*\ +-* Lua methods +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Just call buffered IO methods +-\*-------------------------------------------------------------------------*/ +-static int meth_send(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_send(L, &un->buf); +-} +- +-static int meth_receive(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_receive(L, &un->buf); +-} +- +-static int meth_getstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_getstats(L, &un->buf); +-} +- +-static int meth_setstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_setstats(L, &un->buf); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select support methods +-\*-------------------------------------------------------------------------*/ +-static int meth_getfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- lua_pushnumber(L, (int) un->sock); +- return 1; +-} +- +-/* this is very dangerous, but can be handy for those that are brave enough */ +-static int meth_setfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- un->sock = (t_socket) luaL_checknumber(L, 2); +- return 0; +-} +- +-static int meth_dirty(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- lua_pushboolean(L, !buffer_isempty(&un->buf)); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Closes socket used by object +-\*-------------------------------------------------------------------------*/ +-static int meth_close(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- socket_destroy(&un->sock); +- lua_pushnumber(L, 1); +- return 1; +-} +- +- +-/*-------------------------------------------------------------------------*\ +-* Just call tm methods +-\*-------------------------------------------------------------------------*/ +-static int meth_settimeout(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- return timeout_meth_settimeout(L, &un->tm); +-} +- +-/*=========================================================================*\ +-* Library functions +-\*=========================================================================*/ +- +- +-/*-------------------------------------------------------------------------*\ +-* Creates a serial object +-\*-------------------------------------------------------------------------*/ +-static int global_create(lua_State *L) { +- const char* path = luaL_checkstring(L, 1); +- +- /* allocate unix object */ +- p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- +- /* open serial device */ +- t_socket sock = open(path, O_NOCTTY|O_RDWR); +- +- /*printf("open %s on %d\n", path, sock);*/ +- +- if (sock < 0) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(errno)); +- lua_pushnumber(L, errno); +- return 3; +- } +- /* set its type as client object */ +- auxiliar_setclass(L, "serial{client}", -1); +- /* initialize remaining structure fields */ +- socket_setnonblocking(&sock); +- un->sock = sock; +- io_init(&un->io, (p_send) socket_write, (p_recv) socket_read, +- (p_error) socket_ioerror, &un->sock); +- timeout_init(&un->tm, -1, -1); +- buffer_init(&un->buf, &un->io, &un->tm); +- return 1; +-} +diff --git a/tcp.c b/tcp.c +index e84db84..9dfa27e 100644 +--- a/tcp.c ++++ b/tcp.c +@@ -427,9 +427,18 @@ static int global_create4(lua_State *L) { + return tcp_create(L, AF_INET); + } + ++#if !defined(__3DS__) + static int global_create6(lua_State *L) { + return tcp_create(L, AF_INET6); + } ++#else ++static int global_create6(lua_State* L) { ++ lua_pushnil(L); ++ lua_pushstring(L, "Setting local interface error: not supported"); ++ ++ return 2; ++} ++#endif + + static int global_connect(lua_State *L) { + const char *remoteaddr = luaL_checkstring(L, 1); +diff --git a/udp.c b/udp.c +index 712ad50..a3c7a5f 100755 +--- a/udp.c ++++ b/udp.c +@@ -483,6 +483,15 @@ static int global_create4(lua_State *L) { + return udp_create(L, AF_INET); + } + ++#if !defined(__3DS__) + static int global_create6(lua_State *L) { + return udp_create(L, AF_INET6); + } ++#else ++static int global_create6(lua_State *L) { ++ lua_pushnil(L); ++ lua_pushstring(L, "Setting local interface error: not supported"); ++ ++ return 2; ++} ++#endif +diff --git a/unix.c b/unix.c +deleted file mode 100644 +index 268d8b2..0000000 +--- a/unix.c ++++ /dev/null +@@ -1,69 +0,0 @@ +-/*=========================================================================*\ +-* Unix domain socket +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "unixstream.h" +-#include "unixdgram.h" +- +-/*-------------------------------------------------------------------------*\ +-* Modules and functions +-\*-------------------------------------------------------------------------*/ +-static const luaL_Reg mod[] = { +- {"stream", unixstream_open}, +- {"dgram", unixdgram_open}, +- {NULL, NULL} +-}; +- +-static void add_alias(lua_State *L, int index, const char *name, const char *target) +-{ +- lua_getfield(L, index, target); +- lua_setfield(L, index, name); +-} +- +-static int compat_socket_unix_call(lua_State *L) +-{ +- /* Look up socket.unix.stream in the socket.unix table (which is the first +- * argument). */ +- lua_getfield(L, 1, "stream"); +- +- /* Replace the stack entry for the socket.unix table with the +- * socket.unix.stream function. */ +- lua_replace(L, 1); +- +- /* Call socket.unix.stream, passing along any arguments. */ +- int n = lua_gettop(L); +- lua_call(L, n-1, LUA_MULTRET); +- +- /* Pass along the return values from socket.unix.stream. */ +- n = lua_gettop(L); +- return n; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-LUASOCKET_API int luaopen_socket_unix(lua_State *L) +-{ +- int i; +- lua_newtable(L); +- int socket_unix_table = lua_gettop(L); +- +- for (i = 0; mod[i].name; i++) +- mod[i].func(L); +- +- /* Add backwards compatibility aliases "tcp" and "udp" for the "stream" and +- * "dgram" functions. */ +- add_alias(L, socket_unix_table, "tcp", "stream"); +- add_alias(L, socket_unix_table, "udp", "dgram"); +- +- /* Add a backwards compatibility function and a metatable setup to call it +- * for the old socket.unix() interface. */ +- lua_pushcfunction(L, compat_socket_unix_call); +- lua_setfield(L, socket_unix_table, "__call"); +- lua_pushvalue(L, socket_unix_table); +- lua_setmetatable(L, socket_unix_table); +- +- return 1; +-} +diff --git a/unix.h b/unix.h +deleted file mode 100644 +index c203561..0000000 +--- a/unix.h ++++ /dev/null +@@ -1,26 +0,0 @@ +-#ifndef UNIX_H +-#define UNIX_H +-/*=========================================================================*\ +-* Unix domain object +-* LuaSocket toolkit +-* +-* This module is just an example of how to extend LuaSocket with a new +-* domain. +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "buffer.h" +-#include "timeout.h" +-#include "socket.h" +- +-typedef struct t_unix_ { +- t_socket sock; +- t_io io; +- t_buffer buf; +- t_timeout tm; +-} t_unix; +-typedef t_unix *p_unix; +- +-LUASOCKET_API int luaopen_socket_unix(lua_State *L); +- +-#endif /* UNIX_H */ +diff --git a/unixdgram.c b/unixdgram.c +deleted file mode 100644 +index 69093d7..0000000 +--- a/unixdgram.c ++++ /dev/null +@@ -1,405 +0,0 @@ +-/*=========================================================================*\ +-* Unix domain socket dgram submodule +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "auxiliar.h" +-#include "socket.h" +-#include "options.h" +-#include "unix.h" +- +-#include +-#include +- +-#include +- +-#define UNIXDGRAM_DATAGRAMSIZE 8192 +- +-/* provide a SUN_LEN macro if sys/un.h doesn't (e.g. Android) */ +-#ifndef SUN_LEN +-#define SUN_LEN(ptr) \ +- ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ +- + strlen ((ptr)->sun_path)) +-#endif +- +-/*=========================================================================*\ +-* Internal function prototypes +-\*=========================================================================*/ +-static int global_create(lua_State *L); +-static int meth_connect(lua_State *L); +-static int meth_bind(lua_State *L); +-static int meth_send(lua_State *L); +-static int meth_receive(lua_State *L); +-static int meth_close(lua_State *L); +-static int meth_setoption(lua_State *L); +-static int meth_settimeout(lua_State *L); +-static int meth_gettimeout(lua_State *L); +-static int meth_getfd(lua_State *L); +-static int meth_setfd(lua_State *L); +-static int meth_dirty(lua_State *L); +-static int meth_receivefrom(lua_State *L); +-static int meth_sendto(lua_State *L); +-static int meth_getsockname(lua_State *L); +- +-static const char *unixdgram_tryconnect(p_unix un, const char *path); +-static const char *unixdgram_trybind(p_unix un, const char *path); +- +-/* unixdgram object methods */ +-static luaL_Reg unixdgram_methods[] = { +- {"__gc", meth_close}, +- {"__tostring", auxiliar_tostring}, +- {"bind", meth_bind}, +- {"close", meth_close}, +- {"connect", meth_connect}, +- {"dirty", meth_dirty}, +- {"getfd", meth_getfd}, +- {"send", meth_send}, +- {"sendto", meth_sendto}, +- {"receive", meth_receive}, +- {"receivefrom", meth_receivefrom}, +- {"setfd", meth_setfd}, +- {"setoption", meth_setoption}, +- {"setpeername", meth_connect}, +- {"setsockname", meth_bind}, +- {"getsockname", meth_getsockname}, +- {"settimeout", meth_settimeout}, +- {"gettimeout", meth_gettimeout}, +- {NULL, NULL} +-}; +- +-/* socket option handlers */ +-static t_opt optset[] = { +- {"reuseaddr", opt_set_reuseaddr}, +- {NULL, NULL} +-}; +- +-/* functions in library namespace */ +-static luaL_Reg func[] = { +- {"dgram", global_create}, +- {NULL, NULL} +-}; +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-int unixdgram_open(lua_State *L) +-{ +- /* create classes */ +- auxiliar_newclass(L, "unixdgram{connected}", unixdgram_methods); +- auxiliar_newclass(L, "unixdgram{unconnected}", unixdgram_methods); +- /* create class groups */ +- auxiliar_add2group(L, "unixdgram{connected}", "unixdgram{any}"); +- auxiliar_add2group(L, "unixdgram{unconnected}", "unixdgram{any}"); +- auxiliar_add2group(L, "unixdgram{connected}", "select{able}"); +- auxiliar_add2group(L, "unixdgram{unconnected}", "select{able}"); +- +- luaL_setfuncs(L, func, 0); +- return 0; +-} +- +-/*=========================================================================*\ +-* Lua methods +-\*=========================================================================*/ +-static const char *unixdgram_strerror(int err) +-{ +- /* a 'closed' error on an unconnected means the target address was not +- * accepted by the transport layer */ +- if (err == IO_CLOSED) return "refused"; +- else return socket_strerror(err); +-} +- +-static int meth_send(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{connected}", 1); +- p_timeout tm = &un->tm; +- size_t count, sent = 0; +- int err; +- const char *data = luaL_checklstring(L, 2, &count); +- timeout_markstart(tm); +- err = socket_send(&un->sock, data, count, &sent, tm); +- if (err != IO_DONE) { +- lua_pushnil(L); +- lua_pushstring(L, unixdgram_strerror(err)); +- return 2; +- } +- lua_pushnumber(L, (lua_Number) sent); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Send data through unconnected unixdgram socket +-\*-------------------------------------------------------------------------*/ +-static int meth_sendto(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); +- size_t count, sent = 0; +- const char *data = luaL_checklstring(L, 2, &count); +- const char *path = luaL_checkstring(L, 3); +- p_timeout tm = &un->tm; +- int err; +- struct sockaddr_un remote; +- size_t len = strlen(path); +- +- if (len >= sizeof(remote.sun_path)) { +- lua_pushnil(L); +- lua_pushstring(L, "path too long"); +- return 2; +- } +- +- memset(&remote, 0, sizeof(remote)); +- strcpy(remote.sun_path, path); +- remote.sun_family = AF_UNIX; +- timeout_markstart(tm); +-#ifdef UNIX_HAS_SUN_LEN +- remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) +- + len + 1; +- err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, remote.sun_len, tm); +-#else +- err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, +- sizeof(remote.sun_family) + len, tm); +-#endif +- if (err != IO_DONE) { +- lua_pushnil(L); +- lua_pushstring(L, unixdgram_strerror(err)); +- return 2; +- } +- lua_pushnumber(L, (lua_Number) sent); +- return 1; +-} +- +-static int meth_receive(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- char buf[UNIXDGRAM_DATAGRAMSIZE]; +- size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); +- char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; +- int err; +- p_timeout tm = &un->tm; +- timeout_markstart(tm); +- if (!dgram) { +- lua_pushnil(L); +- lua_pushliteral(L, "out of memory"); +- return 2; +- } +- err = socket_recv(&un->sock, dgram, wanted, &got, tm); +- /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ +- if (err != IO_DONE && err != IO_CLOSED) { +- lua_pushnil(L); +- lua_pushstring(L, unixdgram_strerror(err)); +- if (wanted > sizeof(buf)) free(dgram); +- return 2; +- } +- lua_pushlstring(L, dgram, got); +- if (wanted > sizeof(buf)) free(dgram); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Receives data and sender from a DGRAM socket +-\*-------------------------------------------------------------------------*/ +-static int meth_receivefrom(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); +- char buf[UNIXDGRAM_DATAGRAMSIZE]; +- size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); +- char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; +- struct sockaddr_un addr; +- socklen_t addr_len = sizeof(addr); +- int err; +- p_timeout tm = &un->tm; +- timeout_markstart(tm); +- if (!dgram) { +- lua_pushnil(L); +- lua_pushliteral(L, "out of memory"); +- return 2; +- } +- addr.sun_path[0] = '\0'; +- err = socket_recvfrom(&un->sock, dgram, wanted, &got, (SA *) &addr, +- &addr_len, tm); +- /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ +- if (err != IO_DONE && err != IO_CLOSED) { +- lua_pushnil(L); +- lua_pushstring(L, unixdgram_strerror(err)); +- if (wanted > sizeof(buf)) free(dgram); +- return 2; +- } +- +- lua_pushlstring(L, dgram, got); +- /* the path may be empty, when client send without bind */ +- lua_pushstring(L, addr.sun_path); +- if (wanted > sizeof(buf)) free(dgram); +- return 2; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call option handler +-\*-------------------------------------------------------------------------*/ +-static int meth_setoption(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- return opt_meth_setoption(L, optset, &un->sock); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select support methods +-\*-------------------------------------------------------------------------*/ +-static int meth_getfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- lua_pushnumber(L, (int) un->sock); +- return 1; +-} +- +-/* this is very dangerous, but can be handy for those that are brave enough */ +-static int meth_setfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- un->sock = (t_socket) luaL_checknumber(L, 2); +- return 0; +-} +- +-static int meth_dirty(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- (void) un; +- lua_pushboolean(L, 0); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Binds an object to an address +-\*-------------------------------------------------------------------------*/ +-static const char *unixdgram_trybind(p_unix un, const char *path) { +- struct sockaddr_un local; +- size_t len = strlen(path); +- if (len >= sizeof(local.sun_path)) return "path too long"; +- memset(&local, 0, sizeof(local)); +- strcpy(local.sun_path, path); +- local.sun_family = AF_UNIX; +- size_t addrlen = SUN_LEN(&local); +-#ifdef UNIX_HAS_SUN_LEN +- local.sun_len = addrlen + 1; +-#endif +- int err = socket_bind(&un->sock, (SA *) &local, addrlen); +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_bind(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixdgram_trybind(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- lua_pushnumber(L, 1); +- return 1; +-} +- +-static int meth_getsockname(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- struct sockaddr_un peer = {0}; +- socklen_t peer_len = sizeof(peer); +- +- if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(errno)); +- return 2; +- } +- +- lua_pushstring(L, peer.sun_path); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Turns a master unixdgram object into a client object. +-\*-------------------------------------------------------------------------*/ +-static const char *unixdgram_tryconnect(p_unix un, const char *path) +-{ +- struct sockaddr_un remote; +- size_t len = strlen(path); +- if (len >= sizeof(remote.sun_path)) return "path too long"; +- memset(&remote, 0, sizeof(remote)); +- strcpy(remote.sun_path, path); +- remote.sun_family = AF_UNIX; +- timeout_markstart(&un->tm); +- size_t addrlen = SUN_LEN(&remote); +-#ifdef UNIX_HAS_SUN_LEN +- remote.sun_len = addrlen + 1; +-#endif +- int err = socket_connect(&un->sock, (SA *) &remote, addrlen, &un->tm); +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_connect(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixdgram_tryconnect(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- /* turn unconnected object into a connected object */ +- auxiliar_setclass(L, "unixdgram{connected}", 1); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Closes socket used by object +-\*-------------------------------------------------------------------------*/ +-static int meth_close(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- socket_destroy(&un->sock); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call tm methods +-\*-------------------------------------------------------------------------*/ +-static int meth_settimeout(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- return timeout_meth_settimeout(L, &un->tm); +-} +- +-static int meth_gettimeout(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- return timeout_meth_gettimeout(L, &un->tm); +-} +- +-/*=========================================================================*\ +-* Library functions +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Creates a master unixdgram object +-\*-------------------------------------------------------------------------*/ +-static int global_create(lua_State *L) +-{ +- t_socket sock; +- int err = socket_create(&sock, AF_UNIX, SOCK_DGRAM, 0); +- /* try to allocate a system socket */ +- if (err == IO_DONE) { +- /* allocate unixdgram object */ +- p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- /* set its type as master object */ +- auxiliar_setclass(L, "unixdgram{unconnected}", -1); +- /* initialize remaining structure fields */ +- socket_setnonblocking(&sock); +- un->sock = sock; +- io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, +- (p_error) socket_ioerror, &un->sock); +- timeout_init(&un->tm, -1, -1); +- buffer_init(&un->buf, &un->io, &un->tm); +- return 1; +- } else { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +-} +diff --git a/unixdgram.h b/unixdgram.h +deleted file mode 100644 +index a1a0166..0000000 +--- a/unixdgram.h ++++ /dev/null +@@ -1,28 +0,0 @@ +-#ifndef UNIXDGRAM_H +-#define UNIXDGRAM_H +-/*=========================================================================*\ +-* DGRAM object +-* LuaSocket toolkit +-* +-* The dgram.h module provides LuaSocket with support for DGRAM protocol +-* (AF_INET, SOCK_DGRAM). +-* +-* Two classes are defined: connected and unconnected. DGRAM objects are +-* originally unconnected. They can be "connected" to a given address +-* with a call to the setpeername function. The same function can be used to +-* break the connection. +-\*=========================================================================*/ +- +-#include "unix.h" +- +-#ifndef _WIN32 +-#pragma GCC visibility push(hidden) +-#endif +- +-int unixdgram_open(lua_State *L); +- +-#ifndef _WIN32 +-#pragma GCC visibility pop +-#endif +- +-#endif /* UNIXDGRAM_H */ +diff --git a/unixstream.c b/unixstream.c +deleted file mode 100644 +index 02aced9..0000000 +--- a/unixstream.c ++++ /dev/null +@@ -1,355 +0,0 @@ +-/*=========================================================================*\ +-* Unix domain socket stream sub module +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "auxiliar.h" +-#include "socket.h" +-#include "options.h" +-#include "unixstream.h" +- +-#include +-#include +- +-/*=========================================================================*\ +-* Internal function prototypes +-\*=========================================================================*/ +-static int global_create(lua_State *L); +-static int meth_connect(lua_State *L); +-static int meth_listen(lua_State *L); +-static int meth_bind(lua_State *L); +-static int meth_send(lua_State *L); +-static int meth_shutdown(lua_State *L); +-static int meth_receive(lua_State *L); +-static int meth_accept(lua_State *L); +-static int meth_close(lua_State *L); +-static int meth_setoption(lua_State *L); +-static int meth_settimeout(lua_State *L); +-static int meth_getfd(lua_State *L); +-static int meth_setfd(lua_State *L); +-static int meth_dirty(lua_State *L); +-static int meth_getstats(lua_State *L); +-static int meth_setstats(lua_State *L); +-static int meth_getsockname(lua_State *L); +- +-static const char *unixstream_tryconnect(p_unix un, const char *path); +-static const char *unixstream_trybind(p_unix un, const char *path); +- +-/* unixstream object methods */ +-static luaL_Reg unixstream_methods[] = { +- {"__gc", meth_close}, +- {"__tostring", auxiliar_tostring}, +- {"accept", meth_accept}, +- {"bind", meth_bind}, +- {"close", meth_close}, +- {"connect", meth_connect}, +- {"dirty", meth_dirty}, +- {"getfd", meth_getfd}, +- {"getstats", meth_getstats}, +- {"setstats", meth_setstats}, +- {"listen", meth_listen}, +- {"receive", meth_receive}, +- {"send", meth_send}, +- {"setfd", meth_setfd}, +- {"setoption", meth_setoption}, +- {"setpeername", meth_connect}, +- {"setsockname", meth_bind}, +- {"getsockname", meth_getsockname}, +- {"settimeout", meth_settimeout}, +- {"shutdown", meth_shutdown}, +- {NULL, NULL} +-}; +- +-/* socket option handlers */ +-static t_opt optset[] = { +- {"keepalive", opt_set_keepalive}, +- {"reuseaddr", opt_set_reuseaddr}, +- {"linger", opt_set_linger}, +- {NULL, NULL} +-}; +- +-/* functions in library namespace */ +-static luaL_Reg func[] = { +- {"stream", global_create}, +- {NULL, NULL} +-}; +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-int unixstream_open(lua_State *L) +-{ +- /* create classes */ +- auxiliar_newclass(L, "unixstream{master}", unixstream_methods); +- auxiliar_newclass(L, "unixstream{client}", unixstream_methods); +- auxiliar_newclass(L, "unixstream{server}", unixstream_methods); +- +- /* create class groups */ +- auxiliar_add2group(L, "unixstream{master}", "unixstream{any}"); +- auxiliar_add2group(L, "unixstream{client}", "unixstream{any}"); +- auxiliar_add2group(L, "unixstream{server}", "unixstream{any}"); +- +- luaL_setfuncs(L, func, 0); +- return 0; +-} +- +-/*=========================================================================*\ +-* Lua methods +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Just call buffered IO methods +-\*-------------------------------------------------------------------------*/ +-static int meth_send(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_send(L, &un->buf); +-} +- +-static int meth_receive(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_receive(L, &un->buf); +-} +- +-static int meth_getstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_getstats(L, &un->buf); +-} +- +-static int meth_setstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_setstats(L, &un->buf); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call option handler +-\*-------------------------------------------------------------------------*/ +-static int meth_setoption(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- return opt_meth_setoption(L, optset, &un->sock); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select support methods +-\*-------------------------------------------------------------------------*/ +-static int meth_getfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- lua_pushnumber(L, (int) un->sock); +- return 1; +-} +- +-/* this is very dangerous, but can be handy for those that are brave enough */ +-static int meth_setfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- un->sock = (t_socket) luaL_checknumber(L, 2); +- return 0; +-} +- +-static int meth_dirty(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- lua_pushboolean(L, !buffer_isempty(&un->buf)); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Waits for and returns a client object attempting connection to the +-* server object +-\*-------------------------------------------------------------------------*/ +-static int meth_accept(lua_State *L) { +- p_unix server = (p_unix) auxiliar_checkclass(L, "unixstream{server}", 1); +- p_timeout tm = timeout_markstart(&server->tm); +- t_socket sock; +- int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); +- /* if successful, push client socket */ +- if (err == IO_DONE) { +- p_unix clnt = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- auxiliar_setclass(L, "unixstream{client}", -1); +- /* initialize structure fields */ +- socket_setnonblocking(&sock); +- clnt->sock = sock; +- io_init(&clnt->io, (p_send)socket_send, (p_recv)socket_recv, +- (p_error) socket_ioerror, &clnt->sock); +- timeout_init(&clnt->tm, -1, -1); +- buffer_init(&clnt->buf, &clnt->io, &clnt->tm); +- return 1; +- } else { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Binds an object to an address +-\*-------------------------------------------------------------------------*/ +-static const char *unixstream_trybind(p_unix un, const char *path) { +- struct sockaddr_un local; +- size_t len = strlen(path); +- int err; +- if (len >= sizeof(local.sun_path)) return "path too long"; +- memset(&local, 0, sizeof(local)); +- strcpy(local.sun_path, path); +- local.sun_family = AF_UNIX; +-#ifdef UNIX_HAS_SUN_LEN +- local.sun_len = sizeof(local.sun_family) + sizeof(local.sun_len) +- + len + 1; +- err = socket_bind(&un->sock, (SA *) &local, local.sun_len); +- +-#else +- err = socket_bind(&un->sock, (SA *) &local, +- sizeof(local.sun_family) + len); +-#endif +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_bind(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixstream_trybind(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- lua_pushnumber(L, 1); +- return 1; +-} +- +-static int meth_getsockname(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- struct sockaddr_un peer = {0}; +- socklen_t peer_len = sizeof(peer); +- +- if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(errno)); +- return 2; +- } +- +- lua_pushstring(L, peer.sun_path); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Turns a master unixstream object into a client object. +-\*-------------------------------------------------------------------------*/ +-static const char *unixstream_tryconnect(p_unix un, const char *path) +-{ +- struct sockaddr_un remote; +- int err; +- size_t len = strlen(path); +- if (len >= sizeof(remote.sun_path)) return "path too long"; +- memset(&remote, 0, sizeof(remote)); +- strcpy(remote.sun_path, path); +- remote.sun_family = AF_UNIX; +- timeout_markstart(&un->tm); +-#ifdef UNIX_HAS_SUN_LEN +- remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) +- + len + 1; +- err = socket_connect(&un->sock, (SA *) &remote, remote.sun_len, &un->tm); +-#else +- err = socket_connect(&un->sock, (SA *) &remote, +- sizeof(remote.sun_family) + len, &un->tm); +-#endif +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_connect(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixstream_tryconnect(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- /* turn master object into a client object */ +- auxiliar_setclass(L, "unixstream{client}", 1); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Closes socket used by object +-\*-------------------------------------------------------------------------*/ +-static int meth_close(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- socket_destroy(&un->sock); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Puts the sockt in listen mode +-\*-------------------------------------------------------------------------*/ +-static int meth_listen(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- int backlog = (int) luaL_optnumber(L, 2, 32); +- int err = socket_listen(&un->sock, backlog); +- if (err != IO_DONE) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +- /* turn master object into a server object */ +- auxiliar_setclass(L, "unixstream{server}", 1); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Shuts the connection down partially +-\*-------------------------------------------------------------------------*/ +-static int meth_shutdown(lua_State *L) +-{ +- /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ +- static const char* methods[] = { "receive", "send", "both", NULL }; +- p_unix stream = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- int how = luaL_checkoption(L, 2, "both", methods); +- socket_shutdown(&stream->sock, how); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call tm methods +-\*-------------------------------------------------------------------------*/ +-static int meth_settimeout(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- return timeout_meth_settimeout(L, &un->tm); +-} +- +-/*=========================================================================*\ +-* Library functions +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Creates a master unixstream object +-\*-------------------------------------------------------------------------*/ +-static int global_create(lua_State *L) { +- t_socket sock; +- int err = socket_create(&sock, AF_UNIX, SOCK_STREAM, 0); +- /* try to allocate a system socket */ +- if (err == IO_DONE) { +- /* allocate unixstream object */ +- p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- /* set its type as master object */ +- auxiliar_setclass(L, "unixstream{master}", -1); +- /* initialize remaining structure fields */ +- socket_setnonblocking(&sock); +- un->sock = sock; +- io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, +- (p_error) socket_ioerror, &un->sock); +- timeout_init(&un->tm, -1, -1); +- buffer_init(&un->buf, &un->io, &un->tm); +- return 1; +- } else { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +-} +diff --git a/unixstream.h b/unixstream.h +deleted file mode 100644 +index 7916aff..0000000 +--- a/unixstream.h ++++ /dev/null +@@ -1,29 +0,0 @@ +-#ifndef UNIXSTREAM_H +-#define UNIXSTREAM_H +-/*=========================================================================*\ +-* UNIX STREAM object +-* LuaSocket toolkit +-* +-* The unixstream.h module is basicly a glue that puts together modules buffer.h, +-* timeout.h socket.h and inet.h to provide the LuaSocket UNIX STREAM (AF_UNIX, +-* SOCK_STREAM) support. +-* +-* Three classes are defined: master, client and server. The master class is +-* a newly created unixstream object, that has not been bound or connected. Server +-* objects are unixstream objects bound to some local address. Client objects are +-* unixstream objects either connected to some address or returned by the accept +-* method of a server object. +-\*=========================================================================*/ +-#include "unix.h" +- +-#ifndef _WIN32 +-#pragma GCC visibility push(hidden) +-#endif +- +-int unixstream_open(lua_State *L); +- +-#ifndef _WIN32 +-#pragma GCC visibility pop +-#endif +- +-#endif /* UNIXSTREAM_H */ +diff --git a/usocket.c b/usocket.c +index 69635da..3cd8cde 100644 +--- a/usocket.c ++++ b/usocket.c +@@ -18,7 +18,7 @@ + * Wait for readable/writable/connected socket with timeout + \*-------------------------------------------------------------------------*/ + #ifndef SOCKET_SELECT +-#include ++#include + + #define WAITFD_R POLLIN + #define WAITFD_W POLLOUT +@@ -431,12 +431,16 @@ const char *socket_ioerror(p_socket ps, int err) { + const char *socket_gaistrerror(int err) { + if (err == 0) return NULL; + switch (err) { ++#if !defined(__3DS__) + case EAI_AGAIN: return PIE_AGAIN; + case EAI_BADFLAGS: return PIE_BADFLAGS; ++#endif + #ifdef EAI_BADHINTS + case EAI_BADHINTS: return PIE_BADHINTS; + #endif ++#if !defined(__3DS__) + case EAI_FAIL: return PIE_FAIL; ++#endif + case EAI_FAMILY: return PIE_FAMILY; + case EAI_MEMORY: return PIE_MEMORY; + case EAI_NONAME: return PIE_NONAME; +@@ -446,9 +450,13 @@ const char *socket_gaistrerror(int err) { + #ifdef EAI_PROTOCOL + case EAI_PROTOCOL: return PIE_PROTOCOL; + #endif ++#if !defined(__3DS__) + case EAI_SERVICE: return PIE_SERVICE; ++#endif + case EAI_SOCKTYPE: return PIE_SOCKTYPE; ++#if !defined(__3DS__) + case EAI_SYSTEM: return strerror(errno); ++#endif + default: return LUA_GAI_STRERROR(err); + } + } +diff --git a/usocket.h b/usocket.h +index 45f2f99..12c25ca 100644 +--- a/usocket.h ++++ b/usocket.h +@@ -29,7 +29,6 @@ + #include + /* TCP options (nagle algorithm disable) */ + #include +-#include + + #ifndef SO_REUSEPORT + #define SO_REUSEPORT SO_REUSEADDR +diff --git a/wsocket.c b/wsocket.c +deleted file mode 100755 +index 6cb1e41..0000000 +--- a/wsocket.c ++++ /dev/null +@@ -1,434 +0,0 @@ +-/*=========================================================================*\ +-* Socket compatibilization module for Win32 +-* LuaSocket toolkit +-* +-* The penalty of calling select to avoid busy-wait is only paid when +-* the I/O call fail in the first place. +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include +- +-#include "socket.h" +-#include "pierror.h" +- +-/* WinSock doesn't have a strerror... */ +-static const char *wstrerror(int err); +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-int socket_open(void) { +- WSADATA wsaData; +- WORD wVersionRequested = MAKEWORD(2, 0); +- int err = WSAStartup(wVersionRequested, &wsaData ); +- if (err != 0) return 0; +- if ((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) && +- (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)) { +- WSACleanup(); +- return 0; +- } +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Close module +-\*-------------------------------------------------------------------------*/ +-int socket_close(void) { +- WSACleanup(); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Wait for readable/writable/connected socket with timeout +-\*-------------------------------------------------------------------------*/ +-#define WAITFD_R 1 +-#define WAITFD_W 2 +-#define WAITFD_E 4 +-#define WAITFD_C (WAITFD_E|WAITFD_W) +- +-int socket_waitfd(p_socket ps, int sw, p_timeout tm) { +- int ret; +- fd_set rfds, wfds, efds, *rp = NULL, *wp = NULL, *ep = NULL; +- struct timeval tv, *tp = NULL; +- double t; +- if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ +- if (sw & WAITFD_R) { +- FD_ZERO(&rfds); +- FD_SET(*ps, &rfds); +- rp = &rfds; +- } +- if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } +- if (sw & WAITFD_C) { FD_ZERO(&efds); FD_SET(*ps, &efds); ep = &efds; } +- if ((t = timeout_get(tm)) >= 0.0) { +- tv.tv_sec = (int) t; +- tv.tv_usec = (int) ((t-tv.tv_sec)*1.0e6); +- tp = &tv; +- } +- ret = select(0, rp, wp, ep, tp); +- if (ret == -1) return WSAGetLastError(); +- if (ret == 0) return IO_TIMEOUT; +- if (sw == WAITFD_C && FD_ISSET(*ps, &efds)) return IO_CLOSED; +- return IO_DONE; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select with int timeout in ms +-\*-------------------------------------------------------------------------*/ +-int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, +- p_timeout tm) { +- struct timeval tv; +- double t = timeout_get(tm); +- tv.tv_sec = (int) t; +- tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); +- if (n <= 0) { +- Sleep((DWORD) (1000*t)); +- return 0; +- } else return select(0, rfds, wfds, efds, t >= 0.0? &tv: NULL); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Close and inutilize socket +-\*-------------------------------------------------------------------------*/ +-void socket_destroy(p_socket ps) { +- if (*ps != SOCKET_INVALID) { +- socket_setblocking(ps); /* close can take a long time on WIN32 */ +- closesocket(*ps); +- *ps = SOCKET_INVALID; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* +-\*-------------------------------------------------------------------------*/ +-void socket_shutdown(p_socket ps, int how) { +- socket_setblocking(ps); +- shutdown(*ps, how); +- socket_setnonblocking(ps); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Creates and sets up a socket +-\*-------------------------------------------------------------------------*/ +-int socket_create(p_socket ps, int domain, int type, int protocol) { +- *ps = socket(domain, type, protocol); +- if (*ps != SOCKET_INVALID) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Connects or returns error message +-\*-------------------------------------------------------------------------*/ +-int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { +- int err; +- /* don't call on closed socket */ +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- /* ask system to connect */ +- if (connect(*ps, addr, len) == 0) return IO_DONE; +- /* make sure the system is trying to connect */ +- err = WSAGetLastError(); +- if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return err; +- /* zero timeout case optimization */ +- if (timeout_iszero(tm)) return IO_TIMEOUT; +- /* we wait until something happens */ +- err = socket_waitfd(ps, WAITFD_C, tm); +- if (err == IO_CLOSED) { +- int elen = sizeof(err); +- /* give windows time to set the error (yes, disgusting) */ +- Sleep(10); +- /* find out why we failed */ +- getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &elen); +- /* we KNOW there was an error. if 'why' is 0, we will return +- * "unknown error", but it's not really our fault */ +- return err > 0? err: IO_UNKNOWN; +- } else return err; +- +-} +- +-/*-------------------------------------------------------------------------*\ +-* Binds or returns error message +-\*-------------------------------------------------------------------------*/ +-int socket_bind(p_socket ps, SA *addr, socklen_t len) { +- int err = IO_DONE; +- socket_setblocking(ps); +- if (bind(*ps, addr, len) < 0) err = WSAGetLastError(); +- socket_setnonblocking(ps); +- return err; +-} +- +-/*-------------------------------------------------------------------------*\ +-* +-\*-------------------------------------------------------------------------*/ +-int socket_listen(p_socket ps, int backlog) { +- int err = IO_DONE; +- socket_setblocking(ps); +- if (listen(*ps, backlog) < 0) err = WSAGetLastError(); +- socket_setnonblocking(ps); +- return err; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Accept with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, +- p_timeout tm) { +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int err; +- /* try to get client socket */ +- if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; +- /* find out why we failed */ +- err = WSAGetLastError(); +- /* if we failed because there was no connectoin, keep trying */ +- if (err != WSAEWOULDBLOCK && err != WSAECONNABORTED) return err; +- /* call select to avoid busy wait */ +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Send with timeout +-* On windows, if you try to send 10MB, the OS will buffer EVERYTHING +-* this can take an awful lot of time and we will end up blocked. +-* Therefore, whoever calls this function should not pass a huge buffer. +-\*-------------------------------------------------------------------------*/ +-int socket_send(p_socket ps, const char *data, size_t count, +- size_t *sent, p_timeout tm) +-{ +- int err; +- *sent = 0; +- /* avoid making system calls on closed sockets */ +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- /* loop until we send something or we give up on error */ +- for ( ;; ) { +- /* try to send something */ +- int put = send(*ps, data, (int) count, 0); +- /* if we sent something, we are done */ +- if (put > 0) { +- *sent = put; +- return IO_DONE; +- } +- /* deal with failure */ +- err = WSAGetLastError(); +- /* we can only proceed if there was no serious error */ +- if (err != WSAEWOULDBLOCK) return err; +- /* avoid busy wait */ +- if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Sendto with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, +- SA *addr, socklen_t len, p_timeout tm) +-{ +- int err; +- *sent = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int put = sendto(*ps, data, (int) count, 0, addr, len); +- if (put > 0) { +- *sent = put; +- return IO_DONE; +- } +- err = WSAGetLastError(); +- if (err != WSAEWOULDBLOCK) return err; +- if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Receive with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_recv(p_socket ps, char *data, size_t count, size_t *got, +- p_timeout tm) +-{ +- int err, prev = IO_DONE; +- *got = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int taken = recv(*ps, data, (int) count, 0); +- if (taken > 0) { +- *got = taken; +- return IO_DONE; +- } +- if (taken == 0) return IO_CLOSED; +- err = WSAGetLastError(); +- /* On UDP, a connreset simply means the previous send failed. +- * So we try again. +- * On TCP, it means our socket is now useless, so the error passes. +- * (We will loop again, exiting because the same error will happen) */ +- if (err != WSAEWOULDBLOCK) { +- if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; +- prev = err; +- } +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Recvfrom with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, +- SA *addr, socklen_t *len, p_timeout tm) +-{ +- int err, prev = IO_DONE; +- *got = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int taken = recvfrom(*ps, data, (int) count, 0, addr, len); +- if (taken > 0) { +- *got = taken; +- return IO_DONE; +- } +- if (taken == 0) return IO_CLOSED; +- err = WSAGetLastError(); +- /* On UDP, a connreset simply means the previous send failed. +- * So we try again. +- * On TCP, it means our socket is now useless, so the error passes. +- * (We will loop again, exiting because the same error will happen) */ +- if (err != WSAEWOULDBLOCK) { +- if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; +- prev = err; +- } +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Put socket into blocking mode +-\*-------------------------------------------------------------------------*/ +-void socket_setblocking(p_socket ps) { +- u_long argp = 0; +- ioctlsocket(*ps, FIONBIO, &argp); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Put socket into non-blocking mode +-\*-------------------------------------------------------------------------*/ +-void socket_setnonblocking(p_socket ps) { +- u_long argp = 1; +- ioctlsocket(*ps, FIONBIO, &argp); +-} +- +-/*-------------------------------------------------------------------------*\ +-* DNS helpers +-\*-------------------------------------------------------------------------*/ +-int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { +- *hp = gethostbyaddr(addr, len, AF_INET); +- if (*hp) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-int socket_gethostbyname(const char *addr, struct hostent **hp) { +- *hp = gethostbyname(addr); +- if (*hp) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Error translation functions +-\*-------------------------------------------------------------------------*/ +-const char *socket_hoststrerror(int err) { +- if (err <= 0) return io_strerror(err); +- switch (err) { +- case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; +- default: return wstrerror(err); +- } +-} +- +-const char *socket_strerror(int err) { +- if (err <= 0) return io_strerror(err); +- switch (err) { +- case WSAEADDRINUSE: return PIE_ADDRINUSE; +- case WSAECONNREFUSED : return PIE_CONNREFUSED; +- case WSAEISCONN: return PIE_ISCONN; +- case WSAEACCES: return PIE_ACCESS; +- case WSAECONNABORTED: return PIE_CONNABORTED; +- case WSAECONNRESET: return PIE_CONNRESET; +- case WSAETIMEDOUT: return PIE_TIMEDOUT; +- default: return wstrerror(err); +- } +-} +- +-const char *socket_ioerror(p_socket ps, int err) { +- (void) ps; +- return socket_strerror(err); +-} +- +-static const char *wstrerror(int err) { +- switch (err) { +- case WSAEINTR: return "Interrupted function call"; +- case WSAEACCES: return PIE_ACCESS; /* "Permission denied"; */ +- case WSAEFAULT: return "Bad address"; +- case WSAEINVAL: return "Invalid argument"; +- case WSAEMFILE: return "Too many open files"; +- case WSAEWOULDBLOCK: return "Resource temporarily unavailable"; +- case WSAEINPROGRESS: return "Operation now in progress"; +- case WSAEALREADY: return "Operation already in progress"; +- case WSAENOTSOCK: return "Socket operation on nonsocket"; +- case WSAEDESTADDRREQ: return "Destination address required"; +- case WSAEMSGSIZE: return "Message too long"; +- case WSAEPROTOTYPE: return "Protocol wrong type for socket"; +- case WSAENOPROTOOPT: return "Bad protocol option"; +- case WSAEPROTONOSUPPORT: return "Protocol not supported"; +- case WSAESOCKTNOSUPPORT: return PIE_SOCKTYPE; /* "Socket type not supported"; */ +- case WSAEOPNOTSUPP: return "Operation not supported"; +- case WSAEPFNOSUPPORT: return "Protocol family not supported"; +- case WSAEAFNOSUPPORT: return PIE_FAMILY; /* "Address family not supported by protocol family"; */ +- case WSAEADDRINUSE: return PIE_ADDRINUSE; /* "Address already in use"; */ +- case WSAEADDRNOTAVAIL: return "Cannot assign requested address"; +- case WSAENETDOWN: return "Network is down"; +- case WSAENETUNREACH: return "Network is unreachable"; +- case WSAENETRESET: return "Network dropped connection on reset"; +- case WSAECONNABORTED: return "Software caused connection abort"; +- case WSAECONNRESET: return PIE_CONNRESET; /* "Connection reset by peer"; */ +- case WSAENOBUFS: return "No buffer space available"; +- case WSAEISCONN: return PIE_ISCONN; /* "Socket is already connected"; */ +- case WSAENOTCONN: return "Socket is not connected"; +- case WSAESHUTDOWN: return "Cannot send after socket shutdown"; +- case WSAETIMEDOUT: return PIE_TIMEDOUT; /* "Connection timed out"; */ +- case WSAECONNREFUSED: return PIE_CONNREFUSED; /* "Connection refused"; */ +- case WSAEHOSTDOWN: return "Host is down"; +- case WSAEHOSTUNREACH: return "No route to host"; +- case WSAEPROCLIM: return "Too many processes"; +- case WSASYSNOTREADY: return "Network subsystem is unavailable"; +- case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range"; +- case WSANOTINITIALISED: +- return "Successful WSAStartup not yet performed"; +- case WSAEDISCON: return "Graceful shutdown in progress"; +- case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; /* "Host not found"; */ +- case WSATRY_AGAIN: return "Nonauthoritative host not found"; +- case WSANO_RECOVERY: return PIE_FAIL; /* "Nonrecoverable name lookup error"; */ +- case WSANO_DATA: return "Valid name, no data record of requested type"; +- default: return "Unknown error"; +- } +-} +- +-const char *socket_gaistrerror(int err) { +- if (err == 0) return NULL; +- switch (err) { +- case EAI_AGAIN: return PIE_AGAIN; +- case EAI_BADFLAGS: return PIE_BADFLAGS; +-#ifdef EAI_BADHINTS +- case EAI_BADHINTS: return PIE_BADHINTS; +-#endif +- case EAI_FAIL: return PIE_FAIL; +- case EAI_FAMILY: return PIE_FAMILY; +- case EAI_MEMORY: return PIE_MEMORY; +- case EAI_NONAME: return PIE_NONAME; +-#ifdef EAI_OVERFLOW +- case EAI_OVERFLOW: return PIE_OVERFLOW; +-#endif +-#ifdef EAI_PROTOCOL +- case EAI_PROTOCOL: return PIE_PROTOCOL; +-#endif +- case EAI_SERVICE: return PIE_SERVICE; +- case EAI_SOCKTYPE: return PIE_SOCKTYPE; +-#ifdef EAI_SYSTEM +- case EAI_SYSTEM: return strerror(errno); +-#endif +- default: return LUA_GAI_STRERROR(err); +- } +-} +diff --git a/wsocket.h b/wsocket.h +deleted file mode 100644 +index 3986640..0000000 +--- a/wsocket.h ++++ /dev/null +@@ -1,33 +0,0 @@ +-#ifndef WSOCKET_H +-#define WSOCKET_H +-/*=========================================================================*\ +-* Socket compatibilization module for Win32 +-* LuaSocket toolkit +-\*=========================================================================*/ +- +-/*=========================================================================*\ +-* WinSock include files +-\*=========================================================================*/ +-#include +-#include +- +-typedef int socklen_t; +-typedef SOCKADDR_STORAGE t_sockaddr_storage; +-typedef SOCKET t_socket; +-typedef t_socket *p_socket; +- +-#ifndef IPV6_V6ONLY +-#define IPV6_V6ONLY 27 +-#endif +- +-#define SOCKET_INVALID (INVALID_SOCKET) +- +-#ifndef SO_REUSEPORT +-#define SO_REUSEPORT SO_REUSEADDR +-#endif +- +-#ifndef AI_NUMERICSERV +-#define AI_NUMERICSERV (0) +-#endif +- +-#endif /* WSOCKET_H */ diff --git a/platform/ctr/romfs/shaders/main_v_pica.shbin b/platform/ctr/romfs/shaders/main_v_pica.shbin index 0a1c13868..2fbf8cde7 100644 Binary files a/platform/ctr/romfs/shaders/main_v_pica.shbin and b/platform/ctr/romfs/shaders/main_v_pica.shbin differ diff --git a/platform/ctr/source/boot.cpp b/platform/ctr/source/boot.cpp index 5740c053a..598ecb535 100644 --- a/platform/ctr/source/boot.cpp +++ b/platform/ctr/source/boot.cpp @@ -10,8 +10,6 @@ #include #include -#include "driver/display/Renderer.hpp" - namespace love { // clang-format off diff --git a/platform/ctr/source/common/screen.cpp b/platform/ctr/source/common/screen.cpp index efe65aa36..bd5faa915 100644 --- a/platform/ctr/source/common/screen.cpp +++ b/platform/ctr/source/common/screen.cpp @@ -9,21 +9,21 @@ namespace love // clang-format off inline constinit ScreenInfo GFX_3D[0x03] = { - { GFX_TOP, 0, "left", 400, 240 }, - { GFX_TOP, 1, "right", 400, 240 }, - { GFX_BOTTOM, 2, "bottom", 320, 240 } + { GFX_TOP, 0, "left", GSP_SCREEN_HEIGHT_TOP, GSP_SCREEN_WIDTH }, + { GFX_TOP, 1, "right", GSP_SCREEN_HEIGHT_TOP, GSP_SCREEN_WIDTH }, + { GFX_BOTTOM, 2, "bottom", GSP_SCREEN_HEIGHT_BOTTOM, GSP_SCREEN_WIDTH } }; inline constinit ScreenInfo GFX_2D[0x02] = { - { GFX_TOP, 0, "top", 400, 240 }, - { GFX_BOTTOM, 1, "bottom", 320, 240 } + { GFX_TOP, 0, "top", GSP_SCREEN_HEIGHT_TOP, GSP_SCREEN_WIDTH }, + { GFX_BOTTOM, 1, "bottom", GSP_SCREEN_HEIGHT_BOTTOM, GSP_SCREEN_WIDTH } }; inline constinit ScreenInfo GFX_WIDE[0x02] = { - { GFX_TOP, 0, "top", 800, 240 }, - { GFX_BOTTOM, 1, "bottom", 320, 240 } + { GFX_TOP, 0, "top", GSP_SCREEN_HEIGHT_TOP_2X, GSP_SCREEN_WIDTH }, + { GFX_BOTTOM, 1, "bottom", GSP_SCREEN_HEIGHT_BOTTOM, GSP_SCREEN_WIDTH } }; // clang-format on diff --git a/platform/ctr/source/driver/EventQueue.cpp b/platform/ctr/source/driver/EventQueue.cpp index 60412919c..573887160 100644 --- a/platform/ctr/source/driver/EventQueue.cpp +++ b/platform/ctr/source/driver/EventQueue.cpp @@ -107,6 +107,12 @@ namespace love float value = joystick->getAxis(Joystick::GamepadAxis(input)); this->sendGamepadAxisEvent(0, input, value); } + + Joystick::HidAxisType value; + Joystick::getConstant(Joystick::GamepadAxis(input), value); + + if (hidKeysUp() & value) + this->sendGamepadAxisEvent(0, input, 0.0f); } for (int input = 0; input < Sensor::SENSOR_MAX_ENUM; input++) diff --git a/platform/ctr/source/driver/display/Framebuffer.cpp b/platform/ctr/source/driver/display/Framebuffer.cpp index c17610714..a502e843d 100644 --- a/platform/ctr/source/driver/display/Framebuffer.cpp +++ b/platform/ctr/source/driver/display/Framebuffer.cpp @@ -13,16 +13,23 @@ namespace love { const auto side = (gfxIs3D() && info.name == "right") ? GFX_RIGHT : GFX_LEFT; - this->target = C3D_RenderTargetCreate(info.width, info.height, GPU_RB_RGBA8, FORMAT); + this->target = C3D_RenderTargetCreate(info.height, info.width, COLOR_FORMAT, DEPTH_FORMAT); if (!this->target) - throw love::Exception("Failed to create render target."); + throw love::Exception("Failed to create render target '{:s}'.", info.name); const auto screen = (gfxScreen_t)info.id; - C3D_RenderTargetSetOutput(this->target, screen, side, Framebuffer::DISPLAY_TRANSFER_FLAGS); + C3D_RenderTargetSetOutput(this->target, screen, side, DISPLAY_TRANSFER_FLAGS); this->width = info.width; this->height = info.height; + + // clang-format off + Mtx_OrthoTilt(&this->projection, 0.0f, (float)this->width, (float)this->height, 0.0f, Z_NEAR, Z_FAR, true); + // clang-format on + + this->viewport = { 0, 0, this->width, this->height }; + this->scissor = { 0, 0, this->width, this->height }; } void Framebuffer::destroy() @@ -34,7 +41,7 @@ namespace love } } - static const Rect calculateBounds(const Rect& bounds, int width, int height) + void Framebuffer::calculateBounds(const Rect& bounds, Rect& out, const int width, const int height) { // clang-format off const uint32_t left = height > (bounds.y + bounds.h) ? height - (bounds.y + bounds.h) : 0; @@ -43,20 +50,7 @@ namespace love const uint32_t bottom = width - bounds.x; // clang-format on - return { (int)left, (int)top, (int)right, (int)bottom }; - } - - void Framebuffer::setViewport(const Rect& viewport) - { - auto result = calculateBounds(viewport, this->width, this->height); - - if (result == Rect::EMPTY) - result = { 0, 0, this->width, this->height }; - - Mtx_OrthoTilt(&this->projection, (float)result.x, (float)this->width, (float)this->height, - (float)result.h, Framebuffer::Z_NEAR, Framebuffer::Z_FAR, true); - - this->viewport = result; + out = { (int)left, (int)top, (int)right, (int)bottom }; } void Framebuffer::setScissor(const Rect& scissor) @@ -64,13 +58,10 @@ namespace love const bool enabled = scissor != Rect::EMPTY; GPU_SCISSORMODE mode = enabled ? GPU_SCISSOR_NORMAL : GPU_SCISSOR_DISABLE; - auto result = calculateBounds(scissor, this->width, this->height); - - if (result == Rect::EMPTY) - result = { 0, 0, this->width, this->height }; - - C3D_SetScissor(mode, result.x, result.y, result.w, result.h); + Rect result {}; + calculateBounds(scissor, result, this->width, this->height); this->scissor = result; + C3D_SetScissor(mode, result.y, result.x, result.h, result.w); } } // namespace love diff --git a/platform/ctr/source/driver/display/Renderer.cpp b/platform/ctr/source/driver/display/Renderer.cpp deleted file mode 100644 index 0681fa243..000000000 --- a/platform/ctr/source/driver/display/Renderer.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#include "common/screen.hpp" - -#include "driver/display/Renderer.hpp" - -namespace love -{ - Renderer::Renderer() : context {} - { - this->targets.reserve(3); - } - - void Renderer::initialize() - { - if (this->initialized) - return; - - gfxInitDefault(); - - if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) - throw love::Exception("Failed to initialize citro3d."); - - C3D_CullFace(GPU_CULL_NONE); - C3D_DepthTest(true, GPU_GEQUAL, GPU_WRITE_ALL); - - Mtx_Identity(&this->context.modelView); - Mtx_Identity(&this->context.projection); - - this->set3DMode(true); - - this->initialized = true; - } - - Renderer::~Renderer() - { - this->destroyFramebuffers(); - - C3D_Fini(); - gfxExit(); - } - - void Renderer::createFramebuffers() - { - const auto& info = getScreenInfo(); - - for (size_t index = 0; index < info.size(); ++index) - { - Framebuffer target {}; - target.create(info[index]); - - this->targets.push_back(std::move(target)); - } - } - - void Renderer::destroyFramebuffers() - { - for (auto& target : this->targets) - target.destroy(); - } - - void Renderer::ensureInFrame() - { - if (!this->inFrame) - { - C3D_FrameBegin(C3D_FRAME_SYNCDRAW); - this->inFrame = true; - } - } - - void Renderer::clear(const Color& color) - { - C3D_RenderTargetClear(this->context.target.get(), C3D_CLEAR_ALL, color.abgr(), 0); - } - - void Renderer::clearDepthStencil(int depth, uint8_t mask, double stencil) - { - /// ??? - } - - void Renderer::bindFramebuffer() - { - this->ensureInFrame(); - - this->context.target = this->targets[currentScreen]; - auto viewport = this->context.target.getViewport(); - - C3D_FrameDrawOn(this->context.target.get()); - this->setViewport(viewport, this->context.target.get()->linked); - } - - void Renderer::present() - { - if (this->inFrame) - { - C3D_FrameEnd(0); - this->inFrame = false; - } - } - - void Renderer::setViewport(const Rect& viewport, bool tilt) - { - if (this->context.viewport == viewport) - return; - - this->context.viewport = viewport; - - if (viewport.h == GSP_SCREEN_WIDTH && tilt) - { - if (viewport.w == GSP_SCREEN_HEIGHT_TOP || viewport.w == GSP_SCREEN_HEIGHT_TOP_2X) - { - Mtx_Copy(&this->context.projection, &this->targets[0].getProjection()); - this->context.dirtyProjection = true; - return; - } - else if (viewport.w == GSP_SCREEN_HEIGHT_BOTTOM) - { - const auto index = gfxIs3D() ? 2 : 1; - - Mtx_Copy(&this->context.projection, &this->targets[index].getProjection()); - this->context.dirtyProjection = true; - return; - } - } - - auto* ortho = tilt ? Mtx_OrthoTilt : Mtx_Ortho; - ortho(&this->context.projection, 0.0f, viewport.w, viewport.h, 0.0f, Framebuffer::Z_NEAR, - Framebuffer::Z_FAR, true); - - this->context.dirtyProjection = true; - C3D_SetViewport(0, 0, (uint32_t)viewport.w, (uint32_t)viewport.h); - } - - void Renderer::setScissor(const Rect& scissor) - { - this->context.target.setScissor(scissor); - } - - void Renderer::setCullMode(CullMode mode) - { - GPU_CULLMODE cullMode; - if (!Renderer::getConstant(mode, cullMode)) - return; - - if (this->context.cullMode == mode) - return; - - C3D_CullFace(cullMode); - this->context.cullMode = mode; - } - - void Renderer::setVertexWinding(Winding winding) - {} - - void Renderer::setColorMask(ColorChannelMask mask) - { - uint8_t writeMask = GPU_WRITE_DEPTH; - writeMask |= mask.get(); - - if (this->context.colorMask == mask) - return; - - this->context.colorMask = mask; - C3D_DepthTest(true, GPU_GEQUAL, (GPU_WRITEMASK)writeMask); - } - - void Renderer::setBlendState(const BlendState& state) - { - if (this->context.blendState == state) - return; - - GPU_BLENDEQUATION operationRGB; - if (!Renderer::getConstant(state.operationRGB, operationRGB)) - return; - - GPU_BLENDEQUATION operationA; - if (!Renderer::getConstant(state.operationA, operationA)) - return; - - GPU_BLENDFACTOR sourceColor; - if (!Renderer::getConstant(state.srcFactorRGB, sourceColor)) - return; - - GPU_BLENDFACTOR destColor; - if (!Renderer::getConstant(state.dstFactorRGB, destColor)) - return; - - GPU_BLENDFACTOR sourceAlpha; - if (!Renderer::getConstant(state.srcFactorA, sourceAlpha)) - return; - - GPU_BLENDFACTOR destAlpha; - if (!Renderer::getConstant(state.dstFactorA, destAlpha)) - return; - - C3D_AlphaBlend(operationRGB, operationA, sourceColor, destColor, sourceAlpha, destAlpha); - this->context.blendState = state; - } -} // namespace love diff --git a/platform/ctr/source/driver/display/citro3d.cpp b/platform/ctr/source/driver/display/citro3d.cpp new file mode 100644 index 000000000..c054f1c69 --- /dev/null +++ b/platform/ctr/source/driver/display/citro3d.cpp @@ -0,0 +1,404 @@ +#include "common/screen.hpp" + +#include "driver/display/citro3d.hpp" +#include "modules/graphics/Shader.hpp" + +namespace love +{ + citro3d::citro3d() : context {} + { + this->targets.reserve(3); + } + + void citro3d::initialize() + { + if (this->initialized) + return; + + gfxInitDefault(); + + if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) + throw love::Exception("Failed to initialize citro3d."); + + C3D_CullFace(GPU_CULL_NONE); + C3D_DepthTest(true, GPU_GEQUAL, GPU_WRITE_ALL); + + C3D_AttrInfo* attributes = C3D_GetAttrInfo(); + AttrInfo_Init(attributes); + + AttrInfo_AddLoader(attributes, 0, GPU_FLOAT, 2); //< position + AttrInfo_AddLoader(attributes, 1, GPU_FLOAT, 2); //< texcoord + AttrInfo_AddLoader(attributes, 2, GPU_FLOAT, 4); //< color + + Mtx_Identity(&this->context.modelView); + Mtx_Identity(&this->context.projection); + + this->set3DMode(true); + + this->initialized = true; + } + + void citro3d::updateTexEnvMode(TexEnvMode mode) + { + if (mode == this->context.texEnvMode || mode == TEXENV_MODE_MAX_ENUM) + return; + + this->context.texEnvMode = mode; + + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + switch (mode) + { + default: + case TEXENV_MODE_PRIMITIVE: + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + break; + case TEXENV_MODE_TEXTURE: + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + break; + case TEXENV_MODE_FONT: + { + C3D_TexEnvSrc(env, C3D_RGB, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); + + C3D_TexEnvSrc(env, C3D_Alpha, GPU_PRIMARY_COLOR, GPU_TEXTURE0, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Alpha, GPU_MODULATE); + break; + } + } + } + + void citro3d::setupContext() + { + for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++) + { + this->context.boundTextures[i].clear(); + this->context.boundTextures[i].resize(3, nullptr); + } + + this->bindFramebuffer(this->targets[0].get()); + } + + citro3d::~citro3d() + { + this->destroyFramebuffers(); + + C3D_Fini(); + gfxExit(); + } + + void citro3d::createFramebuffers() + { + const auto& info = getScreenInfo(); + const size_t numFramebuffers = info.size(); + + for (size_t index = 0; index < numFramebuffers; ++index) + { + Framebuffer target {}; + target.create(info[index]); + + this->targets.push_back(std::move(target)); + } + } + + void citro3d::destroyFramebuffers() + { + for (auto& target : this->targets) + target.destroy(); + } + + void citro3d::ensureInFrame() + { + if (!this->inFrame) + { + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + this->inFrame = true; + } + } + + bool citro3d::isDefaultFramebufferActive() const + { + for (size_t index = 0; index < this->targets.size(); index++) + { + if (this->targets[index].get() == this->context.boundFramebuffer) + return true; + } + + return false; + } + + C3D_RenderTarget* citro3d::getInternalBackbuffer() const + { + return this->targets[love::currentScreen].get(); + } + + void citro3d::clear(const Color& color) + { + if (!this->inFrame) + return; + + C3D_RenderTargetClear(this->getFramebuffer(), C3D_CLEAR_ALL, color.abgr(), 0); + } + + void citro3d::clearDepthStencil(int depth, uint8_t mask, double stencil) + { + /// ??? + } + + C3D_RenderTarget* citro3d::getFramebuffer() + { + return this->context.boundFramebuffer; + } + + void citro3d::bindFramebuffer(C3D_RenderTarget* framebuffer) + { + bool bindingModified = false; + + if (this->context.boundFramebuffer != framebuffer) + { + bindingModified = true; + this->context.boundFramebuffer = framebuffer; + } + + if (bindingModified) + { + C3D_FrameDrawOn(framebuffer); + this->setViewport(framebuffer->frameBuf.height, framebuffer->frameBuf.width, framebuffer->linked); + } + } + + void citro3d::present() + { + if (this->inFrame) + { + Graphics::flushBatchedDrawsGlobal(); + + C3D_FrameEnd(0); + + Graphics::advanceStreamBuffersGlobal(); + this->inFrame = false; + } + } + + void citro3d::setViewport(int width, int height, bool tilt) + { + this->context.dirtyProjection = true; + + if (height == GSP_SCREEN_WIDTH && tilt) + { + if (width == GSP_SCREEN_HEIGHT_TOP || width == GSP_SCREEN_HEIGHT_TOP_2X) + { + Mtx_Copy(&this->context.projection, &this->targets[0].getProjection()); + return; + } + else if (width == GSP_SCREEN_HEIGHT_BOTTOM) + { + const auto index = gfxIs3D() ? 2 : 1; + Mtx_Copy(&this->context.projection, &this->targets[index].getProjection()); + return; + } + } + + // clang-format off + auto* ortho = tilt ? Mtx_OrthoTilt : Mtx_Ortho; + ortho(&this->context.projection, 0.0f, width, height, 0.0f, Framebuffer::Z_NEAR, Framebuffer::Z_FAR, true); + // clang-format on + + C3D_SetViewport(0, 0, (uint32_t)width, (uint32_t)height); + } + + void citro3d::setScissor(const Rect& scissor) + { + const int width = this->context.boundFramebuffer->frameBuf.height; + const int height = this->context.boundFramebuffer->frameBuf.width; + + Framebuffer::calculateBounds(scissor, this->context.scissor, width, height); + + // clang-format off + GPU_SCISSORMODE mode = (scissor != Rect::EMPTY) ? GPU_SCISSOR_NORMAL : GPU_SCISSOR_DISABLE; + C3D_SetScissor(mode, this->context.scissor.y, this->context.scissor.x, this->context.scissor.h, this->context.scissor.w); + // clang-format on + } + + void citro3d::setCullMode(CullMode mode) + { + GPU_CULLMODE cullMode; + if (!citro3d::getConstant(mode, cullMode)) + return; + + if (this->context.cullMode == mode) + return; + + C3D_CullFace(cullMode); + this->context.cullMode = mode; + } + + void citro3d::setVertexWinding(Winding) + {} + + void citro3d::setColorMask(ColorChannelMask mask) + { + uint8_t writeMask = GPU_WRITE_DEPTH; + writeMask |= mask.get(); + + if (this->context.colorMask == mask) + return; + + this->context.colorMask = mask; + C3D_DepthTest(true, GPU_GEQUAL, (GPU_WRITEMASK)writeMask); + } + + void citro3d::setBlendState(const BlendState& state) + { + if (this->context.blendState == state) + return; + + GPU_BLENDEQUATION operationRGB; + if (!citro3d::getConstant(state.operationRGB, operationRGB)) + return; + + GPU_BLENDEQUATION operationA; + if (!citro3d::getConstant(state.operationA, operationA)) + return; + + GPU_BLENDFACTOR sourceColor; + if (!citro3d::getConstant(state.srcFactorRGB, sourceColor)) + return; + + GPU_BLENDFACTOR destColor; + if (!citro3d::getConstant(state.dstFactorRGB, destColor)) + return; + + GPU_BLENDFACTOR sourceAlpha; + if (!citro3d::getConstant(state.srcFactorA, sourceAlpha)) + return; + + GPU_BLENDFACTOR destAlpha; + if (!citro3d::getConstant(state.dstFactorA, destAlpha)) + return; + + this->context.blendState = state; + C3D_AlphaBlend(operationRGB, operationA, sourceColor, destColor, sourceAlpha, destAlpha); + } + + void citro3d::setSamplerState(C3D_Tex* texture, SamplerState state) + { + auto magFilter = (state.minFilter == SamplerState::FILTER_NEAREST) ? GPU_NEAREST : GPU_LINEAR; + auto minFilter = (state.magFilter == SamplerState::FILTER_NEAREST) ? GPU_NEAREST : GPU_LINEAR; + + C3D_TexSetFilter(texture, magFilter, minFilter); + + auto wrapU = citro3d::getWrapMode(state.wrapU); + auto wrapV = citro3d::getWrapMode(state.wrapV); + + C3D_TexSetWrap(texture, wrapU, wrapV); + + float maxBias = 0xFFF; + + if (maxBias > 0.01f) + maxBias = 0.01f; + + state.lodBias = std::clamp(state.lodBias, -maxBias, maxBias); + C3D_TexSetLodBias(texture, state.lodBias); + } + + void citro3d::prepareDraw(GraphicsBase* graphics) + { + // clang-format off + if (Shader::current != nullptr && this->context.dirtyProjection) + { + ((Shader*)Shader::current)->updateBuiltinUniforms(graphics, this->context.modelView, this->context.projection); + this->context.dirtyProjection = false; + } + // clang-format on + } + + void citro3d::bindTextureToUnit(TextureType target, C3D_Tex* texture, int unit, bool isFont) + { + if (texture == nullptr) + return this->updateTexEnvMode(TEXENV_MODE_PRIMITIVE); + + isFont ? this->updateTexEnvMode(TEXENV_MODE_FONT) : this->updateTexEnvMode(TEXENV_MODE_TEXTURE); + + C3D_TexBind(0, texture); + } + + void citro3d::bindTextureToUnit(TextureBase* texture, int unit, bool isFont) + { + if (texture == nullptr) + return this->updateTexEnvMode(TEXENV_MODE_PRIMITIVE); + + auto textureType = texture->getTextureType(); + auto* handle = (C3D_Tex*)texture->getHandle(); + + this->bindTextureToUnit(textureType, handle, unit, isFont); + } + + void citro3d::setVertexAttributes(const VertexAttributes& attributes, const BufferBindings& buffers) + {} + + GPU_TEXTURE_MODE_PARAM citro3d::getTextureType(TextureType type) + { + switch (type) + { + case TEXTURE_2D: + default: + return GPU_TEX_2D; + case TEXTURE_CUBE: + return GPU_TEX_CUBE_MAP; + } + } + + GPU_TEXTURE_WRAP_PARAM citro3d::getWrapMode(SamplerState::WrapMode mode) + { + switch (mode) + { + case SamplerState::WRAP_CLAMP: + return GPU_CLAMP_TO_EDGE; + case SamplerState::WRAP_REPEAT: + return GPU_REPEAT; + case SamplerState::WRAP_MIRRORED_REPEAT: + return GPU_MIRRORED_REPEAT; + default: + return GPU_CLAMP_TO_EDGE; + } + } + + GPU_Primitive_t citro3d::getPrimitiveType(PrimitiveType type) + { + switch (type) + { + case PRIMITIVE_TRIANGLES: + return GPU_TRIANGLES; + case PRIMITIVE_TRIANGLE_FAN: + return GPU_TRIANGLE_FAN; + case PRIMITIVE_TRIANGLE_STRIP: + return GPU_TRIANGLE_STRIP; + default: + break; + } + + throw love::Exception("Invalid primitive type: {:d}.", (int)type); + } + + int citro3d::getTextureUnit(GPU_TEXUNIT unit) + { + switch (unit) + { + case GPU_TEXUNIT0: + default: + return 0; + case GPU_TEXUNIT1: + return 1; + case GPU_TEXUNIT2: + return 2; + } + + throw love::Exception("Invalid texture unit: {:d}.", (int)unit); + } + + citro3d c3d; +} // namespace love diff --git a/platform/ctr/source/modules/font/BCFNTRasterizer.cpp b/platform/ctr/source/modules/font/BCFNTRasterizer.cpp new file mode 100644 index 000000000..6fe30faa6 --- /dev/null +++ b/platform/ctr/source/modules/font/BCFNTRasterizer.cpp @@ -0,0 +1,195 @@ +#include "modules/font/BCFNTRasterizer.hpp" +#include "modules/font/GenericShaper.hpp" + +#include "driver/display/citro3d.hpp" + +#include +#include + +namespace love +{ + Type BCFNTRasterizer::type("BCFNTRasterizer", &Rasterizer::type); + + static float scaleMetric(uint8_t metric, float scale) + { + return metric * scale; + } + + bool BCFNTRasterizer::accepts(Data* data) + { + return (!std::memcmp(data->getData(), "CFNT", 4) || !std::memcmp(data->getData(), "CFNU", 4)); + } + + static int fontGlyphIndexFromCodePoint(CFNT_s* font, uint32_t codepoint) + { + int result = 0xFFFF; + + if (codepoint < 0x10000) + { + for (CMAP_s* cmap = font->finf.cmap; cmap; cmap = cmap->next) + { + if (codepoint < cmap->codeBegin || codepoint > cmap->codeEnd) + continue; + + if (cmap->mappingMethod == CMAP_TYPE_DIRECT) + { + result = cmap->indexOffset + (codepoint - cmap->codeBegin); + break; + } + + if (cmap->mappingMethod == CMAP_TYPE_TABLE) + { + result = cmap->indexTable[codepoint - cmap->codeBegin]; + break; + } + + int l = -1; + int r = cmap->nScanEntries; + + while (r - l > 1) + { + int middle = l + (r - l) / 2; + if (cmap->scanEntries[middle].code > codepoint) + r = middle; + else + l = middle; + } + + if (l >= 0 && cmap->scanEntries[l].code == codepoint) + { + result = cmap->scanEntries[l].glyphIndex; + break; + } + } + } + + if (result == 0xFFFF) // Bogus CMAP entry. Probably exist to save space by using TABLE mappings? + { + if (font->finf.alterCharIndex == 0xFFFF) + return -1; + else + return font->finf.alterCharIndex; + } + + return result; + } + + CFNT_s* BCFNTRasterizer::getUserdata(Data* data) const + { + return (this->userdata == nullptr) ? (CFNT_s*)data->getData() : this->userdata; + } + + BCFNTRasterizer::BCFNTRasterizer(Data* data, int size) + { + this->dpiScale = 1.0f; + this->size = std::floor(size * this->dpiScale + 0.5f); + + if (this->size == 0) + throw love::Exception("Invalid font size: {:d}", this->size); + + if (linearGetSize(data->getData()) == 0) + { + this->userdata = (CFNT_s*)linearAlloc(data->getSize()); + + if (this->userdata == nullptr) + throw love::Exception(E_OUT_OF_MEMORY); + + std::memcpy(this->userdata, data->getData(), data->getSize()); + } + + /* if we already have this data loaded, fixing this (again) is a bad time™ */ + if ((uintptr_t)fontGetInfo(this->getUserdata(data))->tglp < (uintptr_t)this->getUserdata(data)) + fontFixPointers(this->getUserdata(data)); + + auto* fontInfo = fontGetInfo(this->getUserdata(data)); + auto* sheetInfo = fontInfo->tglp; + + this->scale = std::floor(this->size * this->dpiScale + 0.5f) / sheetInfo->cellHeight; + + this->metrics.advance = scaleMetric(sheetInfo->maxCharWidth, this->scale); + this->metrics.ascent = scaleMetric(fontInfo->ascent, this->scale); + this->metrics.descent = scaleMetric((fontInfo->height - fontInfo->ascent), this->scale); + this->metrics.height = scaleMetric(sheetInfo->cellHeight, this->scale); + + /* todo: more accuracy? */ + for (auto map = fontInfo->cmap; map != nullptr; map = map->next) + this->glyphCount += (map->codeEnd - map->codeBegin) + 1; + + this->data = data; + } + + BCFNTRasterizer::~BCFNTRasterizer() + { + if (this->userdata) + linearFree(this->userdata); + } + + TextShaper* BCFNTRasterizer::newTextShaper() + { + return new GenericShaper(this); + } + + bool BCFNTRasterizer::hasGlyph(uint32_t codepoint) const + { + const int index = this->getGlyphIndex(codepoint); + const auto* info = fontGetInfo(this->getUserdata(this->data)); + + return index != info->alterCharIndex && codepoint != '\t'; + } + + int BCFNTRasterizer::getGlyphSpacing(uint32_t codepoint) const + { + const int index = this->getGlyphIndex(codepoint); + const auto flag = GLYPH_POS_CALC_VTXCOORD; + + fontGlyphPos_s result {}; + fontCalcGlyphPos(&result, this->getUserdata(this->data), index, flag, this->scale, this->scale); + + return result.xAdvance; + } + + GlyphData* BCFNTRasterizer::getGlyphDataForIndex(int index) const + { + fontGlyphPos_s result {}; + const auto flag = GLYPH_POS_CALC_VTXCOORD; + + fontCalcGlyphPos(&result, this->getUserdata(this->data), index, flag, this->scale, this->scale); + + GlyphMetrics metrics {}; + metrics.height = this->metrics.height; + metrics.width = result.width; + metrics.advance = result.xAdvance; + metrics.bearingX = result.xOffset; + metrics.bearingY = this->metrics.ascent; + + GlyphSheet sheet {}; + sheet.index = result.sheetIndex; + sheet.top = result.texcoord.top; + sheet.left = result.texcoord.left; + sheet.right = result.texcoord.right; + sheet.bottom = result.texcoord.bottom; + + const auto* info = fontGetGlyphInfo(this->getUserdata(this->data)); + + PixelFormat format; + if (!citro3d::getConstant((GPU_TEXCOLOR)info->sheetFmt, format)) + throw love::Exception("Invalid texture format: {:u}", info->sheetFmt); + + return new GlyphData(0, metrics, format, sheet); + } + + int BCFNTRasterizer::getGlyphIndex(uint32_t codepoint) const + { + return love::fontGlyphIndexFromCodePoint(this->getUserdata(this->data), codepoint); + } + + int BCFNTRasterizer::getGlyphCount() const + { + return this->glyphCount; + } + + ptrdiff_t BCFNTRasterizer::getHandle() const + { + return (ptrdiff_t)this->getUserdata(this->data); + } +} // namespace love diff --git a/platform/ctr/source/modules/font/Font.cpp b/platform/ctr/source/modules/font/Font.cpp new file mode 100644 index 000000000..682be80b5 --- /dev/null +++ b/platform/ctr/source/modules/font/Font.cpp @@ -0,0 +1,126 @@ +#include "modules/font/Font.hpp" +#include "common/Result.hpp" + +#include "modules/font/BCFNTRasterizer.hpp" + +#include + +namespace love +{ + // #region System Font + SystemFont::SystemFont(uint32_t dataType) : data(nullptr), size(0), dataType(dataType) + { + this->data = FontModule::loadSystemFont(CFG_REGION_USA, this->size); + } + + SystemFont::~SystemFont() + { + linearFree(this->data); + } + // #endregion + + SystemFont* FontModule::loadSystemFontByType(SystemFontType type) + { + return new SystemFont(type); + } + + static CFNT_s* loadFromArchive(uint64_t title, const char* path, size_t& outSize) + { + std::unique_ptr data; + long size = 0; + + CFNT_s* font = nullptr; + + if (auto result = Result(romfsMountFromTitle(title, MEDIATYPE_NAND, "font")); result.failed()) + { + throw love::Exception("Failed to mount 'font:/' from NAND: {:X}", result.get()); + return nullptr; + } + + std::FILE* file = std::fopen(path, "rb"); + + if (!file) + { + std::fclose(file); + romfsUnmount("font"); + + throw love::Exception("Failed to open font file: {:s}", path); + return nullptr; + } + + std::fseek(file, 0, SEEK_END); + size = std::ftell(file); + std::rewind(file); + + try + { + data = std::make_unique(size); + } + catch (std::bad_alloc&) + { + throw love::Exception(E_OUT_OF_MEMORY); + } + + std::fread(data.get(), 1, size, file); + std::fclose(file); + + romfsUnmount("font"); + + uint32_t fontSize = *(uint32_t*)data.get() >> 0x08; + font = (CFNT_s*)linearAlloc(fontSize); + + if (font == nullptr) + throw love::Exception(E_OUT_OF_MEMORY); + + if (font && !decompress_LZ11(font, fontSize, nullptr, data.get() + 0x04, size - 0x04)) + { + linearFree(font); + throw love::Exception("Failed to decompress font file: {:s}", path); + } + + outSize = fontSize; + return font; + } + + static size_t getFontIndex(CFG_Region region) + { + switch (region) + { + default: + case CFG_REGION_JPN ... CFG_REGION_AUS: + return 0; + case CFG_REGION_CHN: + return 1; + case CFG_REGION_KOR: + return 2; + case CFG_REGION_TWN: + return 3; + } + } + + CFNT_s* FontModule::loadSystemFont(CFG_Region region, size_t& size) + { + const auto index = getFontIndex(region); + const auto archive = FontModule::FONT_ARCHIVE_TITLE | (index << 8); + + return loadFromArchive(archive, FontModule::fontPaths[index], size); + } + + FontModule::FontModule() : FontModuleBase("love.font.ctr") + { + this->defaultFontData.set(new SystemFont(CFG_REGION_USA), Acquire::NO_RETAIN); + } + + Rasterizer* FontModule::newRasterizer(FileData* data) const + { + if (BCFNTRasterizer::accepts(data)) + return new BCFNTRasterizer(data, 12); + + throw love::Exception("Invalid font file: {:s}", data->getFilename()); + } + + Rasterizer* FontModule::newTrueTypeRasterizer(Data* data, int size, const Rasterizer::Settings&) const + { + return new BCFNTRasterizer(data, size); + } +} // namespace love diff --git a/platform/ctr/source/modules/graphics/Buffer.cpp b/platform/ctr/source/modules/graphics/Buffer.cpp new file mode 100644 index 000000000..37ab8c598 --- /dev/null +++ b/platform/ctr/source/modules/graphics/Buffer.cpp @@ -0,0 +1,131 @@ +#include "modules/graphics/Buffer.hpp" + +#include <3ds.h> + +namespace love +{ + Buffer::Buffer(GraphicsBase* graphics, const Settings& settings, + const std::vector& format, const void* data, size_t size, + size_t arraylength) : + BufferBase(graphics, settings, format, size, arraylength), + { + this->size = this->getSize(); + arraylength = this->getArrayLength(); + + if (usageFlags & BUFFERUSAGEFLAG_VERTEX) + { + mapUsage = BUFFERUSAGE_VERTEX; + this->buffer = new C3D_BufInfo(); + } + else if (usageFlags & BUFFERUSAGEFLAG_INDEX) + mapUsage = BUFFERUSAGE_INDEX; + + if (dataUsage == BUFFERDATAUSAGE_STREAM) + this->ownsMemoryMap = true; + + std::vector empty {}; + + try + { + empty.resize(this->getSize()); + data = empty.data(); + } + catch (std::exception&) + { + data = nullptr; + } + + if (!this->load(data)) + { + this->unloadVolatile(); + throw love::Exception("Could not create buffer with %d bytes (out of VRAM?)", size); + } + } + + Buffer::~Buffer() + { + this->unloadVolatile(); + if (this->memoryMap != nullptr && this->ownsMemoryMap) + linearFree(this->memoryMap); + } + + bool Buffer::loadVolatile() + { + if (this->buffer != nullptr) + return true; + + return this->load(nullptr); + } + + void Buffer::unloadVolatile() + { + this->mapped = false; + + if (this->buffer != nullptr) + delete this->buffer; + + this->buffer = nullptr; + + if (this->texture != nullptr) + delete this->texture; + + this->texture = nullptr; + } + + bool Buffer::load(const void* data) + { + if (this->mapUsage != BUFFERUSAGE_VERTEX) + return true; + + BufInfo_Init(this->buffer); + + if (data != nullptr) + BufInfo_Add(this->buffer, data, this->getArrayStride(), 3, 0x210); + } + + void* Buffer::map(MapType map, size_t offset, size_t size) + { + if (size == 0) + return nullptr; + + const bool isReadback = this->dataUsage == BUFFERDATAUSAGE_READBACK; + if (map == MAP_WRITE_INVALIDATE && (this->isImmutable() || isReadback)) + return nullptr; + + if (map == MAP_READ_ONLY && this->dataUsage != BUFFERDATAUSAGE_READBACK) + return nullptr; + + Range range(offset, size); + + if (!Range(0, this->getSize()).contains(range)) + return nullptr; + + char* data = nullptr; + + if (map == MAP_READ_ONLY) + { + } + else if (this->ownsMemoryMap) + { + if (this->memoryMap == nullptr) + this->memoryMap = (char*)linearAlloc(this->getSize()); + + data = this->memoryMap; + } + else + { + } + + if (data != nullptr) + { + this->mapped = true; + this->mappedType = map; + this->mappedRange = range; + + if (!this->ownsMemoryMap) + this->memoryMap = data; + } + + return data; + } +} // namespace love diff --git a/platform/ctr/source/modules/graphics/Font.cpp b/platform/ctr/source/modules/graphics/Font.cpp new file mode 100644 index 000000000..3285c0f20 --- /dev/null +++ b/platform/ctr/source/modules/graphics/Font.cpp @@ -0,0 +1,94 @@ +#include "modules/graphics/Font.hpp" +#include "modules/graphics/Graphics.tcc" + +namespace love +{ + Font::Font(Rasterizer* rasterizer, const SamplerState& samplerState) : FontBase(rasterizer, samplerState) + { + this->loadVolatile(); + } + + bool Font::loadVolatile() + { + this->textureCacheID++; + this->glyphs.clear(); + this->textures.clear(); + this->createTexture(); + + return true; + } + + void Font::createTexture() + { + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + graphics->flushBatchedDraws(); + + CFNT_s* font = (CFNT_s*)this->shaper->getRasterizers()[0]->getHandle(); + const auto* info = fontGetGlyphInfo(font); + + this->textures.reserve(info->nSheets); + + /* the texture and font data are the same size */ + for (size_t index = 0; index < info->nSheets; index++) + { + TextureBase::Settings settings {}; + settings.format = this->pixelFormat; + settings.width = info->sheetWidth; + settings.height = info->sheetHeight; + + auto* texture = graphics->newTexture(settings, nullptr); + auto* data = fontGetGlyphSheetTex(font, index); + + texture->setHandleData((ptrdiff_t)data); + texture->setSamplerState(this->samplerState); + + this->textures.emplace_back(texture, Acquire::NO_RETAIN); + } + } + + const Font::Glyph& Font::addGlyph(TextShaper::GlyphIndex glyphIndex) + { + float glyphDPIScale = this->getDPIScale(); + StrongRef gd(this->getRasterizerGlyphData(glyphIndex, glyphDPIScale), Acquire::NO_RETAIN); + + int width = gd->getWidth(); + int height = gd->getHeight(); + + Glyph glyph {}; + glyph.texture = nullptr; + + std::fill_n(glyph.vertices, 4, GlyphVertex {}); + + if (width > 0 && height > 0) + { + auto sheet = gd->getSheet(); + + TextureBase* texture = this->textures[sheet.index]; + glyph.texture = texture; + + Color color(1.0f, 1.0f, 1.0f, 1.0f); + + // clang-format off + std::array vertices = + { + Vertex { 0.0f, 0.0f, sheet.left, sheet.top, color }, + Vertex { 0.0f, height, sheet.left, sheet.bottom, color }, + Vertex { width, 0.0f, sheet.right, sheet.top, color }, + Vertex { width, height, sheet.right, sheet.bottom, color } + }; + // clang-format on + + for (int index = 0; index < 4; index++) + { + glyph.vertices[index] = vertices[index]; + glyph.vertices[index].x += gd->getBearingX() / dpiScale; + glyph.vertices[index].y += gd->getBearingY() / dpiScale; + } + } + + const auto index = packGlyphIndex(glyphIndex); + this->glyphs[index] = glyph; + + return this->glyphs[index]; + } +} // namespace love diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index 2692c0e40..c912fabc1 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -1,8 +1,11 @@ -#include "driver/display/Renderer.hpp" +#include "driver/display/citro3d.hpp" #include "modules/graphics/Graphics.hpp" +#include "modules/graphics/Shader.hpp" #include "modules/window/Window.hpp" +#include "modules/graphics/Font.hpp" + namespace love { Graphics::Graphics() : GraphicsBase("love.graphics.citro3d") @@ -24,60 +27,319 @@ namespace love } } - void Graphics::clearImpl(OptionalColor color, OptionalInt depth, OptionalDouble stencil) + Graphics::~Graphics() + {} + + void Graphics::initCapabilities() + { + // clang-format off + this->capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS] = false; + this->capabilities.features[FEATURE_CLAMP_ZERO] = true; + this->capabilities.features[FEATURE_CLAMP_ONE] = true; + this->capabilities.features[FEATURE_BLEND_MINMAX] = true; + this->capabilities.features[FEATURE_LIGHTEN] = true; + this->capabilities.features[FEATURE_FULL_NPOT] = true; + this->capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = false; + this->capabilities.features[FEATURE_SHADER_DERIVATIVES] = false; + this->capabilities.features[FEATURE_GLSL3] = false; + this->capabilities.features[FEATURE_GLSL4] = false; + this->capabilities.features[FEATURE_INSTANCING] = false; + this->capabilities.features[FEATURE_TEXEL_BUFFER] = false; + this->capabilities.features[FEATURE_INDEX_BUFFER_32BIT] = false; + this->capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = false; //< might be possible + this->capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = false; //< might be possible + this->capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = false; //< might be possible + this->capabilities.features[FEATURE_MIPMAP_RANGE] = false; + this->capabilities.features[FEATURE_INDIRECT_DRAW] = false; + static_assert(FEATURE_MAX_ENUM == 19, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + + this->capabilities.limits[LIMIT_POINT_SIZE] = 8.0f; + this->capabilities.limits[LIMIT_TEXTURE_SIZE] = LOVE_TEX3DS_MAX; + this->capabilities.limits[LIMIT_TEXTURE_LAYERS] = 1; + this->capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = LOVE_TEX3DS_MAX; + this->capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = LOVE_TEX3DS_MAX; + this->capabilities.limits[LIMIT_TEXEL_BUFFER_SIZE] = 0; + this->capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_X] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_Y] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_Z] = 0; + this->capabilities.limits[LIMIT_RENDER_TARGETS] = 1; //< max simultaneous render targets + this->capabilities.limits[LIMIT_TEXTURE_MSAA] = 0; + this->capabilities.limits[LIMIT_ANISOTROPY] = 0; + static_assert(LIMIT_MAX_ENUM == 13, "Graphics::initCapabilities must be updated when adding a new system limit!"); + // clang-format on + + this->capabilities.textureTypes[TEXTURE_2D] = true; + this->capabilities.textureTypes[TEXTURE_VOLUME] = false; + this->capabilities.textureTypes[TEXTURE_CUBE] = true; + this->capabilities.textureTypes[TEXTURE_2D_ARRAY] = false; + } + + void Graphics::captureScreenshot(const ScreenshotInfo& info) + {} + + void Graphics::setActiveScreen() { - Renderer::getInstance().bindFramebuffer(); + c3d.ensureInFrame(); + } + void Graphics::clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) + { if (color.hasValue) { - // gammaCorrectColor(color.value); - Renderer::getInstance().clear(color.value); + bool hasIntegerFormat = false; + const auto& targets = this->states.back().renderTargets; + + for (const auto& target : targets.colors) + { + if (target.texture.get() && love::isPixelFormatInteger(target.texture->getPixelFormat())) + hasIntegerFormat = true; + + if (hasIntegerFormat) + { + std::vector colors(targets.colors.size()); + + for (size_t index = 0; index < colors.size(); index++) + colors[index] = color; + + this->clear(colors, stencil, depth); + return; + } + } + } + + if (color.hasValue || stencil.hasValue || depth.hasValue) + this->flushBatchedDraws(); + + if (color.hasValue) + { + gammaCorrectColor(color.value); + c3d.clear(color.value); + } + + c3d.bindFramebuffer(c3d.getInternalBackbuffer()); + } + + C3D_RenderTarget* Graphics::getInternalBackbuffer() const + { + return c3d.getInternalBackbuffer(); + } + + void Graphics::clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) + { + if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) + return; + + int numColors = (int)colors.size(); + + const auto& targets = this->states.back().renderTargets.colors; + const int numColorTargets = targets.size(); + + if (numColors <= 1 && + (numColorTargets == 0 || (numColorTargets == 1 && targets[0].texture.get() != nullptr && + !isPixelFormatInteger(targets[0].texture->getPixelFormat())))) + { + this->clear(colors.size() > 0 ? colors[0] : OptionalColor(), stencil, depth); + return; } - if (stencil.hasValue && depth.hasValue) - Renderer::getInstance().clearDepthStencil(stencil.value, 0xFF, depth.value); + this->flushBatchedDraws(); + + numColors = std::min(numColors, numColorTargets); + + for (int index = 0; index < numColors; index++) + { + OptionalColor current = colors[index]; + + if (!current.hasValue) + continue; + + Color value(current.value.r, current.value.g, current.value.b, current.value.a); + + gammaCorrectColor(value); + c3d.clear(value); + } + + c3d.bindFramebuffer(c3d.getInternalBackbuffer()); } - void Graphics::presentImpl() + void Graphics::present(void* screenshotCallbackData) { - Renderer::getInstance().present(); + if (!this->isActive()) + return; + + if (this->isRenderTargetActive()) + throw love::Exception("present cannot be called while a render target is active."); + + c3d.present(); + + this->drawCalls = 0; + this->drawCallsBatched = 0; + + this->cpuProcessingTime = C3D_GetProcessingTime(); + this->gpuDrawingTime = C3D_GetDrawingTime(); } - void Graphics::setScissorImpl(const Rect& scissor) + void Graphics::setScissor(const Rect& scissor) { - Renderer::getInstance().setScissor(scissor); + this->flushBatchedDraws(); + + auto& state = this->states.back(); + double dpiscale = this->getCurrentDPIScale(); + + Rect rectangle {}; + rectangle.x = scissor.x * dpiscale; + rectangle.y = scissor.y * dpiscale; + rectangle.w = scissor.w * dpiscale; + rectangle.h = scissor.h * dpiscale; + + c3d.setScissor(rectangle); + + state.scissor = true; + state.scissorRect = scissor; } - void Graphics::setScissorImpl() + void Graphics::setScissor() { - Renderer::getInstance().setScissor(Rect::EMPTY); + if (this->states.back().scissor) + this->flushBatchedDraws(); + + this->states.back().scissor = false; + c3d.setScissor(Rect::EMPTY); } - void Graphics::setFrontFaceWindingImpl(Winding winding) + void Graphics::setFrontFaceWinding(Winding winding) { - Renderer::getInstance().setVertexWinding(winding); + auto& state = this->states.back(); + + if (state.winding != winding) + this->flushBatchedDraws(); + + state.winding = winding; + + if (this->isRenderTargetActive()) + winding = (winding == WINDING_CW) ? WINDING_CCW : WINDING_CW; // ??? + + c3d.setVertexWinding(winding); } - void Graphics::setColorMaskImpl(ColorChannelMask mask) + void Graphics::setColorMask(ColorChannelMask mask) { - Renderer::getInstance().setColorMask(mask); + this->flushBatchedDraws(); + + c3d.setColorMask(mask); + this->states.back().colorMask = mask; } - void Graphics::setBlendStateImpl(const BlendState& state) + void Graphics::setBlendState(const BlendState& state) { - Renderer::getInstance().setBlendState(state); + if (!(state == this->states.back().blend)) + this->flushBatchedDraws(); + + if (state.operationRGB == BLENDOP_MAX || state.operationA == BLENDOP_MAX || + state.operationRGB == BLENDOP_MIN || state.operationA == BLENDOP_MIN) + { + if (!capabilities.features[FEATURE_BLEND_MINMAX]) + throw love::Exception(E_BLEND_MIN_MAX_NOT_SUPPORTED); + } + + if (state.enable) + c3d.setBlendState(state); + + this->states.back().blend = state; } - bool Graphics::setModeImpl(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa) + FontBase* Graphics::newFont(Rasterizer* data) { - Renderer::getInstance().initialize(); + return new Font(data, this->states.back().defaultSamplerState); + } + + FontBase* Graphics::newDefaultFont(int size, const Rasterizer::Settings& settings) + { + auto* module = Module::getInstance(Module::M_FONT); + + if (module == nullptr) + throw love::Exception("Font module has not been loaded."); + + StrongRef rasterizer = module->newTrueTypeRasterizer(size, settings); + + return this->newFont(rasterizer.get()); + } + + bool Graphics::setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa) + { + c3d.initialize(); + + this->created = true; + this->initCapabilities(); + + c3d.setupContext(); + + try + { + if (this->batchedDrawState.vertexBuffer == nullptr) + { + this->batchedDrawState.indexBuffer = newIndexBuffer(INIT_INDEX_BUFFER_SIZE); + this->batchedDrawState.vertexBuffer = newVertexBuffer(INIT_VERTEX_BUFFER_SIZE); + } + } + catch (love::Exception&) + { + throw; + } + + if (!Volatile::loadAll()) + std::printf("Failed to load all volatile objects.\n"); + + this->restoreState(this->states.back()); + + for (int index = 0; index < 1; index++) + { + auto type = (Shader::StandardShader)index; + + try + { + Shader::standardShaders[type] = new Shader(); + } + catch (const std::exception& e) + { + throw; + } + } + + if (!Shader::current) + Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach(); return true; } - void Graphics::unsetModeImpl() - {} + void Graphics::unsetMode() + { + if (!this->isCreated()) + return; + + this->flushBatchedDraws(); + } + + void Graphics::setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) + { + const auto& state = this->states.back(); + + bool isWindow = targets.getFirstTarget().texture == nullptr; + + if (isWindow) + c3d.bindFramebuffer(c3d.getInternalBackbuffer()); + else + c3d.bindFramebuffer((C3D_RenderTarget*)targets.getFirstTarget().texture->getRenderTargetHandle()); + + bool tilt = isWindow ? true : false; + c3d.setViewport(pixelWidth, pixelHeight, tilt); + + if (state.scissor) + c3d.setScissor(state.scissorRect); + } bool Graphics::isActive() const { @@ -87,7 +349,87 @@ namespace love void Graphics::setViewport(int x, int y, int width, int height) { - Renderer::getInstance().setViewport({ x, y, width, height }, true); + c3d.setViewport(width, height, true); + } + + TextureBase* Graphics::newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data) + { + return new Texture(this, settings, data); + } + + void Graphics::points(Vector2* positions, const Color* colors, int count) + { + const auto pointSize = this->states.back().pointSize; + + for (int index = 0; index < count; index++) + { + const auto& position = positions[index]; + + if (!colors) + { + this->circle(DRAW_FILL, position.x, position.y, pointSize); + return; + } + + auto& color = colors[index]; + + gammaCorrectColor(this->getColor()); + + if (isGammaCorrect()) + { + Color current = colors[index]; + + gammaCorrectColor(current); + current *= color; + unGammaCorrectColor(current); + + this->setColor(current); + } + else + this->setColor(color); + + this->circle(DRAW_FILL, position.x, position.y, pointSize); + } + } + + bool Graphics::isPixelFormatSupported(PixelFormat format, uint32_t usage) + { + format = this->getSizedFormat(format); + bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; + + GPU_TEXCOLOR color; + bool supported = citro3d::getConstant(format, color); + + return readable && supported; + } + + void Graphics::draw(const DrawIndexedCommand& command) + { + c3d.prepareDraw(this); + // c3d.setVertexAttributes(*command.attributes, *command.buffers); + c3d.bindTextureToUnit(command.texture, 0, command.isFont); + + const auto* indices = (const uint16_t*)command.indexBuffer->getHandle(); + const size_t offset = command.indexBufferOffset; + + const auto primitiveType = citro3d::getPrimitiveType(command.primitiveType); + const auto dataType = C3D_UNSIGNED_SHORT; + + C3D_DrawElements(primitiveType, command.indexCount, dataType, &indices[offset]); + + ++this->drawCalls; + } + + void Graphics::draw(const DrawCommand& command) + { + c3d.prepareDraw(this); + // c3d.setVertexAttributes(*command.attributes, *command.buffers); + c3d.bindTextureToUnit(command.texture, 0, command.isFont); + + const auto primitiveType = citro3d::getPrimitiveType(command.primitiveType); + + C3D_DrawArrays(primitiveType, command.vertexStart, command.vertexCount); + ++this->drawCalls; } bool Graphics::is3D() const @@ -97,7 +439,7 @@ namespace love void Graphics::set3D(bool enable) { - Renderer::getInstance().set3DMode(enable); + c3d.set3DMode(enable); } bool Graphics::isWide() const @@ -107,7 +449,7 @@ namespace love void Graphics::setWide(bool enable) { - Renderer::getInstance().setWideMode(enable); + c3d.setWideMode(enable); } float Graphics::getDepth() const diff --git a/platform/ctr/source/modules/graphics/Shader.cpp b/platform/ctr/source/modules/graphics/Shader.cpp new file mode 100644 index 000000000..40d390026 --- /dev/null +++ b/platform/ctr/source/modules/graphics/Shader.cpp @@ -0,0 +1,149 @@ +#include "modules/graphics/Shader.hpp" +#include "driver/display/citro3d.hpp" + +#define SHADERS_DIR "romfs:/shaders/" + +#define DEFAULT_SHADER (SHADERS_DIR "main_v_pica.shbin") + +namespace love +{ + Shader::Shader() : ShaderBase(STANDARD_DEFAULT), uniforms {} + { + this->loadVolatile(); + } + + Shader::~Shader() + { + this->unloadVolatile(); + } + + bool Shader::loadVolatile() + { + if (this->dvlb != nullptr) + return true; + + std::string error; + + if (!this->validate(DEFAULT_SHADER, error)) + throw love::Exception("Failed to load default shader: {:s}", error); + + shaderProgramInit(&this->program); + shaderProgramSetVsh(&this->program, &this->dvlb->DVLE[0]); + + this->uniforms[0] = this->getUniform("mdlvMtx"); + this->uniforms[1] = this->getUniform("projMtx"); + + return true; + } + + void Shader::unloadVolatile() + { + shaderProgramFree(&this->program); + DVLB_Free(this->dvlb); + } + + const Shader::UniformInfo Shader::getUniform(const std::string& name) const + { + int8_t location = 0; + if ((location = shaderInstanceGetUniformLocation(this->program.vertexShader, name.c_str())) < 0) + throw love::Exception("Failed to get uniform location: {:s}", name); + + return { location, name }; + } + + bool Shader::hasUniform(const std::string& name) const + { + for (int index = 0; index < 2; index++) + { + if (this->uniforms[index].name == name) + return true; + } + + return false; + } + + static void updateTransform(C3D_Mtx& matrix, const Matrix4& transform) + { + for (int row = 0; row < 4; row++) + { + for (int column = 0; column < 4; column++) + matrix.m[row * 4 + (3 - column)] = transform.get(row, column); + } + } + + void Shader::updateBuiltinUniforms(GraphicsBase* graphics, C3D_Mtx mdlvMtx, const C3D_Mtx& projMtx) + { + if (this->hasUniform("mdlvMtx")) + { + updateTransform(mdlvMtx, graphics->getTransform()); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->uniforms[0].location, &mdlvMtx); + } + + if (this->hasUniform("projMtx")) + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->uniforms[1].location, &projMtx); + } + + void Shader::attach() + { + if (current != this) + { + Graphics::flushBatchedDrawsGlobal(); + + C3D_BindProgram(&this->program); + current = this; + } + } + + ptrdiff_t Shader::getHandle() const + { + return 0; + } + + bool Shader::validate(const char* filepath, std::string& error) + { + std::FILE* file = std::fopen(filepath, "rb"); + + if (file == nullptr) + { + error = "Failed to open file."; + std::fclose(file); + return false; + } + + std::fseek(file, 0, SEEK_END); + long size = std::ftell(file); + std::rewind(file); + + try + { + this->data.resize(size / sizeof(uint32_t)); + } + catch (std::bad_alloc&) + { + error = E_OUT_OF_MEMORY; + std::fclose(file); + return false; + } + + long read = std::fread(this->data.data(), 1, size, file); + + if (read != size) + { + error = "Failed to read file."; + std::fclose(file); + return false; + } + + std::fclose(file); + this->dvlb = DVLB_ParseFile(this->data.data(), size); + + if (this->dvlb == nullptr) + { + error = "Failed to parse DVLB."; + std::fclose(file); + return false; + } + + return true; + } +} // namespace love diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp new file mode 100644 index 000000000..b0496af73 --- /dev/null +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -0,0 +1,328 @@ +#include "modules/graphics/Texture.hpp" +#include "modules/graphics/Graphics.hpp" + +#include "driver/display/citro3d.hpp" +#include "driver/graphics/DrawCommand.hpp" + +namespace love +{ + static void createFramebufferObject(C3D_RenderTarget*& target, C3D_Tex* texture, uint16_t width, + uint16_t height, bool clear) + { + texture = new C3D_Tex(); + + if (!C3D_TexInitVRAM(texture, width, height, GPU_RGBA8)) + throw love::Exception("Failed to create framebuffer texture!"); + + auto* previousFramebuffer = c3d.getFramebuffer(); + + target = C3D_RenderTargetCreateFromTex(texture, GPU_TEXFACE_2D, 0, GPU_RB_DEPTH16); + + c3d.bindFramebuffer(target); + + if (clear) + c3d.clear({ 0, 0, 0, 0 }); + + c3d.bindFramebuffer(previousFramebuffer); + } + + static void createTextureObject(C3D_Tex*& texture, PixelFormat format, uint16_t width, uint16_t height) + { + GPU_TEXCOLOR gpuFormat; + if (!citro3d::getConstant(format, gpuFormat)) + throw love::Exception("Invalid GPU texture format: {:s}.", love::getConstant(format)); + + texture = new C3D_Tex(); + + if (!C3D_TexInit(texture, width, height, gpuFormat)) + throw love::Exception("Failed to create texture object!"); + } + + Texture::Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data) : + TextureBase(graphics, settings, data), + slices(settings.type) + { + if (data != nullptr) + slices = *data; + + if (!this->loadVolatile()) + throw love::Exception("Failed to create texture."); + + slices.clear(); + } + + Texture::~Texture() + { + this->unloadVolatile(); + } + + bool Texture::loadVolatile() + { + if (this->texture != nullptr || this->target != nullptr) + return true; + + if (this->parentView.texture != this) + { + Texture* baseTexture = (Texture*)this->parentView.texture; + baseTexture->loadVolatile(); + } + + if (this->isReadable()) + this->createTexture(); + + int64_t memorySize = 0; + + for (int mip = 0; mip < this->getMipmapCount(); mip++) + { + int width = this->getPixelWidth(mip); + int height = this->getPixelHeight(mip); + + const auto faces = (this->textureType == TEXTURE_CUBE) ? 6 : 1; + int slices = this->getDepth(mip) * this->layers * faces; + + memorySize += getPixelFormatSliceSize(this->format, width, height) * slices; + } + + this->setGraphicsMemorySize(memorySize); + + return true; + } + + void Texture::unloadVolatile() + { + if (this->texture != nullptr) + { + C3D_TexDelete(this->texture); + delete this->texture; + } + + if (this->target != nullptr) + { + C3D_RenderTargetDelete(this->target); + C3D_TexDelete(this->texture); + + delete this->texture; + } + + this->setGraphicsMemorySize(0); + } + + static void* getTextureFace(C3D_Tex* texture, GPU_TEXFACE face, int mipmap, uint32_t* size = nullptr) + { + if (C3D_TexGetType(texture) == GPU_TEX_CUBE_MAP) + return C3D_TexCubeGetImagePtr(texture, face, mipmap, size); + + return C3D_Tex2DGetImagePtr(texture, mipmap, size); + } + + void Texture::createTexture() + { + const auto powTwoWidth = NextPo2(this->pixelWidth); + const auto powTwoHeight = NextPo2(this->pixelHeight); + + /* + ** Textures cannot be initialized in VRAM unless they are render targets. + */ + if (!this->isRenderTarget()) + { + try + { + createTextureObject(this->texture, this->format, powTwoWidth, powTwoHeight); + } + catch (love::Exception&) + { + throw; + } + + int mipCount = this->getMipmapCount(); + int sliceCount = 1; + + if (this->textureType == TEXTURE_VOLUME) + sliceCount = getDepth(); + else if (this->textureType == TEXTURE_2D_ARRAY) + sliceCount = getLayerCount(); + else if (this->textureType == TEXTURE_CUBE) + sliceCount = 6; + + for (int mipmap = 0; mipmap < mipCount; mipmap++) + { + for (int slice = 0; slice < sliceCount; slice++) + { + auto* data = this->slices.get(slice, mipmap); + + if (data != nullptr) + this->uploadImageData(data, mipmap, slice, 0, 0); + } + } + } + + bool hasData = this->slices.get(0, 0) != nullptr; + + int clearMips = 1; + if (isPixelFormatDepthStencil(this->format)) + clearMips = mipmapCount; + + if (this->isRenderTarget()) + { + bool clear = !hasData; + + try + { + createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight, clear); + } + catch (love::Exception&) + { + throw; + } + } + else if (!hasData) + { + for (int mipmap = 0; mipmap < clearMips; mipmap++) + { + uint32_t mipmapSize = 0; + + GPU_TEXFACE face = GPU_TEXFACE_2D; + if (this->textureType == TEXTURE_CUBE) + face = GPU_TEXFACE(GPU_POSITIVE_X + mipmap); + + auto* data = getTextureFace(this->texture, face, mipmap, &mipmapSize); + + std::memset(data, 0, mipmapSize); + } + } + + this->setSamplerState(this->samplerState); + + if (this->slices.getMipmapCount() <= 1 && this->getMipmapsMode() != MIPMAPS_NONE) + this->generateMipmaps(); + } + + static Vector2 getVertex(const float x, const float y, const Vector2& virtualDim, + const Vector2& physicalDim) + { + const auto u = x / physicalDim.x; + const auto v = (virtualDim.y - y) / physicalDim.y; + + return Vector2(u, v); + } + + static void refreshQuad(Quad* quad, const Quad::Viewport& viewport, const Vector2& virtualSize, + const Vector2& realSize, bool isRenderTarget) + { + quad->refresh(viewport, realSize.x, realSize.y); + const auto* textureCoords = quad->getTextureCoordinates(); + + if (isRenderTarget) + { + quad->setTextureCoordinate(0, getVertex(0.0f, 0.0f, virtualSize, realSize)); + quad->setTextureCoordinate(1, getVertex(0.0f, virtualSize.y, virtualSize, realSize)); + quad->setTextureCoordinate(2, getVertex(virtualSize.x, virtualSize.y, virtualSize, realSize)); + quad->setTextureCoordinate(3, getVertex(virtualSize.x, 0.0f, virtualSize, realSize)); + + return; + } + + for (size_t index = 0; index < 4; index++) + quad->setTextureCoordinate(index, { textureCoords[index].x, 1.0f - textureCoords[index].y }); + } + + void Texture::updateQuad(Quad* quad) + { + const auto& viewport = quad->getViewport(); + + Vector2 physicalDim { (float)this->texture->width, (float)this->texture->height }; + Vector2 virtualDim { (float)this->pixelWidth, (float)this->pixelHeight }; + + refreshQuad(quad, viewport, virtualDim, physicalDim, this->renderTarget); + } + + void Texture::setSamplerState(const SamplerState& state) + { + this->samplerState = this->validateSamplerState(state); + c3d.setSamplerState(this->texture, this->samplerState); + } + + template + void replaceTiledPixels(const void* source, void* texture, const Rect& rect, const int width, + const int height) + { + const int sourcePowerTwo = NextPo2(rect.w); + const int destPowerTwo = NextPo2(width); + + for (int y = 0; y < std::min(rect.h, height - rect.y); y++) + { + for (int x = 0; x < std::min(rect.w, width - rect.x); x++) + { + const auto* sourcePixel = Color::fromTile(source, sourcePowerTwo, x, y); + auto* destPixel = Color::fromTile(texture, destPowerTwo, rect.x + x, rect.y + y); + + *destPixel = *sourcePixel; + } + } + } + + void Texture::uploadByteData(const void* data, size_t size, int level, int slice, const Rect& rect) + { + const auto face = GPU_TEXFACE(slice); + void* textureData = getTextureFace(this->texture, face, level); + + if (textureData == nullptr) + throw love::Exception("Failed to get texture data pointer (not initialized?)."); + + const auto mipWidth = this->getPixelWidth(level); + const auto mipHeight = this->getPixelHeight(level); + + // copy it directly if the size is the whole thing + if (rect == Rect(0, 0, mipWidth, mipHeight)) + std::memcpy(textureData, data, size); + else + { + switch (this->getPixelFormat()) + { + case PIXELFORMAT_RGB565_UNORM: + case PIXELFORMAT_RGB5A1_UNORM: + replaceTiledPixels(data, textureData, rect, this->width, this->height); + break; + case PIXELFORMAT_RGBA4_UNORM: + replaceTiledPixels(data, textureData, rect, this->width, this->height); + break; + case PIXELFORMAT_LA8_UNORM: + replaceTiledPixels(data, textureData, rect, this->width, this->height); + break; + case PIXELFORMAT_RGBA8_UNORM: + default: + replaceTiledPixels(data, textureData, rect, this->width, this->height); + break; + } + } + + C3D_TexFlush(this->texture); + } + + void Texture::generateMipmapsInternal() + { + if (C3D_TexGetType(this->texture) == GPU_TEX_2D) + { + C3D_TexGenerateMipmap(this->texture, GPU_TEXFACE_2D); + return; + } + + for (int face = 0; face < 6; face++) + C3D_TexGenerateMipmap(this->texture, GPU_TEXFACE(face)); + } + + ptrdiff_t Texture::getHandle() const + { + return (ptrdiff_t)this->texture; + } + + ptrdiff_t Texture::getRenderTargetHandle() const + { + return (ptrdiff_t)this->target; + } + + ptrdiff_t Texture::getSamplerHandle() const + { + return 0; + } +} // namespace love diff --git a/platform/ctr/source/modules/joystick/Joystick.cpp b/platform/ctr/source/modules/joystick/Joystick.cpp index ad8749159..2c3a9481c 100644 --- a/platform/ctr/source/modules/joystick/Joystick.cpp +++ b/platform/ctr/source/modules/joystick/Joystick.cpp @@ -1,8 +1,6 @@ #include "modules/joystick/Joystick.hpp" #include "utility/guid.hpp" -#include "utility/logfile.hpp" - namespace love { Joystick::Joystick(int id) : JoystickBase(id) diff --git a/platform/ctr/source/modules/joystick/JoystickModule.cpp b/platform/ctr/source/modules/joystick/JoystickModule.cpp index 9d0cf10fb..3d248921c 100644 --- a/platform/ctr/source/modules/joystick/JoystickModule.cpp +++ b/platform/ctr/source/modules/joystick/JoystickModule.cpp @@ -1,5 +1,7 @@ #include "modules/joystick/JoystickModule.hpp" +#include "modules/joystick/Joystick.hpp" + namespace love::joystick { int getJoystickCount() diff --git a/platform/ctr/source/modules/keyboard/Keyboard.cpp b/platform/ctr/source/modules/keyboard/Keyboard.cpp index 25e3c929a..5c5b6f016 100644 --- a/platform/ctr/source/modules/keyboard/Keyboard.cpp +++ b/platform/ctr/source/modules/keyboard/Keyboard.cpp @@ -3,6 +3,17 @@ namespace love { + static SwkbdCallbackResult inputCallback(void* udata, const char** msg, const char* text, size_t length) + { + auto luaCallback = (KeyboardBase::KeyboardValidationInfo*)udata; + auto result = luaCallback->callback(luaCallback, text, msg); + + SwkbdCallbackResult out; + Keyboard::getConstant(result, out); + + return out; + } + Keyboard::Keyboard() : KeyboardBase(), state {} {} @@ -24,6 +35,7 @@ namespace love swkbdInit(&this->state, type, 2, length); swkbdSetInitialText(&this->state, this->text.get()); swkbdSetHintText(&this->state, options.hint.data()); + swkbdSetFilterCallback(&this->state, inputCallback, (void*)&options.callback); if (options.password) swkbdSetPasswordMode(&this->state, SWKBD_PASSWORD_HIDE_DELAY); diff --git a/platform/hac/CMakeLists.txt b/platform/hac/CMakeLists.txt index 4901221d4..be610d7a0 100644 --- a/platform/hac/CMakeLists.txt +++ b/platform/hac/CMakeLists.txt @@ -34,10 +34,13 @@ source/driver/audio/MemoryPool.cpp source/driver/display/deko3d/CIntrusiveTree.cpp source/driver/display/deko3d/CMemPool.cpp source/driver/display/Framebuffer.cpp -source/driver/display/Renderer.cpp +source/driver/display/deko.cpp +source/driver/graphics/StreamBuffer.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp source/modules/graphics/Graphics.cpp +source/modules/graphics/Shader.cpp +source/modules/graphics/Texture.cpp source/modules/joystick/Joystick.cpp source/modules/joystick/JoystickModule.cpp source/modules/keyboard/Keyboard.cpp diff --git a/platform/hac/include/driver/display/Framebuffer.hpp b/platform/hac/include/driver/display/Framebuffer.hpp index 92d32e0e4..6fb4e9cd7 100644 --- a/platform/hac/include/driver/display/Framebuffer.hpp +++ b/platform/hac/include/driver/display/Framebuffer.hpp @@ -20,9 +20,9 @@ namespace love void destroy(); - dk::Image& getImage() + dk::Image* getImage() { - return this->image; + return std::addressof(this->image); } private: diff --git a/platform/hac/include/driver/display/Renderer.hpp b/platform/hac/include/driver/display/Renderer.hpp deleted file mode 100644 index 43bb4a387..000000000 --- a/platform/hac/include/driver/display/Renderer.hpp +++ /dev/null @@ -1,196 +0,0 @@ -#pragma once - -#include "common/Map.hpp" -#include "common/pixelformat.hpp" - -#include "driver/display/Framebuffer.hpp" -#include "driver/display/Renderer.tcc" - -#include "driver/display/deko3d/CCmdMemRing.h" -#include "driver/display/deko3d/CCmdVtxRing.h" -#include "driver/display/deko3d/CDescriptorSet.h" - -#include "modules/graphics/vertex.hpp" - -/* Enforces GLSL std140/std430 alignment rules for glm types */ -#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES -/* Enables usage of SIMD CPU instructions (requiring the above as well) */ -#define GLM_FORCE_INTRINSICS -#include -#include -#include -#include - -namespace love -{ - class Renderer : public RendererBase - { - public: - enum MemoryPool - { - MEMORYPOOL_IMAGE, - MEMORYPOOL_CODE, - MEMORYPOOL_DATA - }; - - enum QueueType - { - QUEUE_TYPE_MAIN, - QUEUE_TYPE_IMAGES - }; - - Renderer(); - - void initialize(); - - ~Renderer(); - - void clear(const Color& color); - - void clearDepthStencil(int stencil, uint8_t mask, double depth); - - void bindFramebuffer(); - - void present(); - - void setBlendState(const BlendState& state); - - void setViewport(const Rect& viewport); - - void setScissor(const Rect& scissor); - - void setCullMode(CullMode mode); - - void setColorMask(ColorChannelMask mask); - - void setVertexWinding(Winding winding); - - void onModeChanged() - { - this->destroyFramebuffers(); - this->createFramebuffers(); - } - - CMemPool& getMemoryPool(MemoryPool pool) - { - switch (pool) - { - default: - case MEMORYPOOL_IMAGE: - return images; - case MEMORYPOOL_CODE: - return code; - case MEMORYPOOL_DATA: - return data; - } - } - - dk::Queue& getQueue(QueueType type) - { - switch (type) - { - default: - case QUEUE_TYPE_MAIN: - return mainQueue; - case QUEUE_TYPE_IMAGES: - return textureQueue; - } - } - - // clang-format off - ENUMMAP_DECLARE(BlendOperations, BlendOperation, DkBlendOp, - { BLENDOP_ADD, DkBlendOp_Add }, - { BLENDOP_SUBTRACT, DkBlendOp_Sub }, - { BLENDOP_REVERSE_SUBTRACT, DkBlendOp_RevSub }, - { BLENDOP_MIN, DkBlendOp_Min }, - { BLENDOP_MAX, DkBlendOp_Max } - ); - - ENUMMAP_DECLARE(BlendFactors, BlendFactor, DkBlendFactor, - { BLENDFACTOR_ZERO, DkBlendFactor_Zero }, - { BLENDFACTOR_ONE, DkBlendFactor_One }, - { BLENDFACTOR_SRC_COLOR, DkBlendFactor_SrcColor }, - { BLENDFACTOR_ONE_MINUS_SRC_COLOR, DkBlendFactor_InvSrcColor }, - { BLENDFACTOR_SRC_ALPHA, DkBlendFactor_SrcAlpha }, - { BLENDFACTOR_ONE_MINUS_SRC_ALPHA, DkBlendFactor_InvSrcAlpha }, - { BLENDFACTOR_DST_COLOR, DkBlendFactor_DstColor }, - { BLENDFACTOR_ONE_MINUS_DST_COLOR, DkBlendFactor_InvDstColor }, - { BLENDFACTOR_DST_ALPHA, DkBlendFactor_DstAlpha }, - { BLENDFACTOR_ONE_MINUS_DST_ALPHA, DkBlendFactor_InvDstAlpha }, - { BLENDFACTOR_SRC_ALPHA_SATURATED, DkBlendFactor_SrcAlphaSaturate } - ); - - ENUMMAP_DECLARE(CullModes, CullMode, DkFace, - { CULL_NONE, DkFace_None }, - { CULL_FRONT, DkFace_Front }, - { CULL_BACK, DkFace_Back } - ); - - ENUMMAP_DECLARE(WindingModes, Winding, DkFrontFace, - { WINDING_CCW, DkFrontFace_CCW }, - { WINDING_CW, DkFrontFace_CW } - ); - // clang-format on - - private: - static constexpr int GPU_POOL_SIZE = 0x4000000; - static constexpr int GPU_USE_FLAGS = (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image); - - static constexpr int CPU_POOL_SIZE = 0x100000; - static constexpr int CPU_USE_FLAGS = - (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached); - - static constexpr int SHADER_POOL_SIZE = 0x20000; - static constexpr int SHADER_USE_FLAGS = - DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code; - - static constexpr int RENDERTARGET_USE_FLAGS = - DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression; - - static constexpr int COMMAND_SIZE = 0x100000; - static constexpr int VERTEX_COMMAND_SIZE = 0x100000; - - void ensureInFrame(); - - void createFramebuffers(); - - void destroyFramebuffers(); - - struct Context : public ContextBase - { - dk::RasterizerState rasterizer; - dk::ColorWriteState colorWrite; - dk::BlendState blend; - } context; - - struct Transform - { - glm::mat4 projection; - glm::mat4 modelView; - } transform; - - static constexpr auto TRANSFORM_SIZE = sizeof(Transform); - - CMemPool::Handle unformBuffer; - - dk::UniqueDevice device; - - dk::UniqueQueue mainQueue; - dk::UniqueQueue textureQueue; - - dk::UniqueCmdBuf commandBuffer; - dk::UniqueSwapchain swapchain; - - CMemPool images; - CMemPool data; - CMemPool code; - - int framebufferSlot; - - Framebuffer framebuffers[0x02]; - Framebuffer depthbuffer; - - CCmdMemRing<2> commands; - std::array targets; - }; -} // namespace love diff --git a/platform/hac/include/driver/display/deko.hpp b/platform/hac/include/driver/display/deko.hpp new file mode 100644 index 000000000..94375dac3 --- /dev/null +++ b/platform/hac/include/driver/display/deko.hpp @@ -0,0 +1,297 @@ +#pragma once + +#include "common/Map.hpp" +#include "common/pixelformat.hpp" + +#include "driver/display/Framebuffer.hpp" +#include "driver/display/Renderer.tcc" + +#include "driver/graphics/BitAlloc.hpp" + +#include "driver/display/deko3d/CCmdMemRing.h" +#include "driver/display/deko3d/CDescriptorSet.h" + +#include "modules/graphics/vertex.hpp" + +/* Enforces GLSL std140/std430 alignment rules for glm types */ +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES +/* Enables usage of SIMD CPU instructions (requiring the above as well) */ +#define GLM_FORCE_INTRINSICS +#include +#include +#include +#include + +#define MAX_OBJECTS 0x400 +#define MAX_RENDERTARGETS 2 + +namespace love +{ + class deko3d : public RendererBase + { + public: + enum MemoryPool + { + MEMORYPOOL_IMAGE, + MEMORYPOOL_CODE, + MEMORYPOOL_DATA + }; + + enum QueueType + { + QUEUE_TYPE_MAIN, + QUEUE_TYPE_IMAGES + }; + + deko3d(); + + void initialize(); + + ~deko3d(); + + void clear(const Color& color); + + void clearDepthStencil(int stencil, uint8_t mask, double depth); + + dk::Image& getInternalBackbuffer(); + + void bindFramebuffer(dk::Image* target = nullptr); + + void present(); + + void setBlendState(const BlendState& state); + + void setViewport(const Rect& viewport); + + void setScissor(const Rect& scissor); + + void setCullMode(CullMode mode); + + void setColorMask(ColorChannelMask mask); + + void setVertexWinding(Winding winding); + + void setPointSize(float size); + + void setSamplerState(TextureBase* texture, const SamplerState& state); + + void prepareDraw(GraphicsBase* graphics); + + void useProgram(const dk::Shader& vertex, const dk::Shader& fragment); + + void bindBuffer(BufferUsage usage, CMemPool::Handle& memory); + + void onModeChanged() + { + this->destroyFramebuffers(); + this->createFramebuffers(); + } + + CMemPool& getMemoryPool(MemoryPool pool) + { + switch (pool) + { + default: + case MEMORYPOOL_IMAGE: + return images; + case MEMORYPOOL_CODE: + return code; + case MEMORYPOOL_DATA: + return data; + } + } + + dk::Device getDevice() + { + return this->device; + } + + dk::Queue getQueue(QueueType type) + { + switch (type) + { + default: + case QUEUE_TYPE_MAIN: + return mainQueue; + case QUEUE_TYPE_IMAGES: + return textureQueue; + } + } + + void registerTexture(TextureBase* texture, bool registering); + + void ensureInFrame(); + + void setVertexAttributes(bool isTexture); + + void bindTextureToUnit(TextureBase* texture, int unit); + + void bindTextureToUnit(DkResHandle texture, int unit); + + void drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, + uint32_t instanceCount); + + void draw(DkPrimitive primitive, uint32_t vertexCount, uint32_t firstVertex); + + // clang-format off + ENUMMAP_DECLARE(BlendOperations, BlendOperation, DkBlendOp, + { BLENDOP_ADD, DkBlendOp_Add }, + { BLENDOP_SUBTRACT, DkBlendOp_Sub }, + { BLENDOP_REVERSE_SUBTRACT, DkBlendOp_RevSub }, + { BLENDOP_MIN, DkBlendOp_Min }, + { BLENDOP_MAX, DkBlendOp_Max } + ); + + ENUMMAP_DECLARE(BlendFactors, BlendFactor, DkBlendFactor, + { BLENDFACTOR_ZERO, DkBlendFactor_Zero }, + { BLENDFACTOR_ONE, DkBlendFactor_One }, + { BLENDFACTOR_SRC_COLOR, DkBlendFactor_SrcColor }, + { BLENDFACTOR_ONE_MINUS_SRC_COLOR, DkBlendFactor_InvSrcColor }, + { BLENDFACTOR_SRC_ALPHA, DkBlendFactor_SrcAlpha }, + { BLENDFACTOR_ONE_MINUS_SRC_ALPHA, DkBlendFactor_InvSrcAlpha }, + { BLENDFACTOR_DST_COLOR, DkBlendFactor_DstColor }, + { BLENDFACTOR_ONE_MINUS_DST_COLOR, DkBlendFactor_InvDstColor }, + { BLENDFACTOR_DST_ALPHA, DkBlendFactor_DstAlpha }, + { BLENDFACTOR_ONE_MINUS_DST_ALPHA, DkBlendFactor_InvDstAlpha }, + { BLENDFACTOR_SRC_ALPHA_SATURATED, DkBlendFactor_SrcAlphaSaturate } + ); + + ENUMMAP_DECLARE(CullModes, CullMode, DkFace, + { CULL_NONE, DkFace_None }, + { CULL_FRONT, DkFace_Front }, + { CULL_BACK, DkFace_Back } + ); + + ENUMMAP_DECLARE(WindingModes, Winding, DkFrontFace, + { WINDING_CCW, DkFrontFace_CCW }, + { WINDING_CW, DkFrontFace_CW } + ); + + ENUMMAP_DECLARE(FilterModes, SamplerState::FilterMode, DkFilter, + { SamplerState::FILTER_LINEAR, DkFilter_Linear }, + { SamplerState::FILTER_NEAREST, DkFilter_Nearest } + ); + + ENUMMAP_DECLARE(MipFilterModes, SamplerState::MipmapFilterMode, DkMipFilter, + { SamplerState::MIPMAP_FILTER_LINEAR, DkMipFilter_Linear }, + { SamplerState::MIPMAP_FILTER_NEAREST, DkMipFilter_Nearest }, + { SamplerState::MIPMAP_FILTER_NONE, DkMipFilter_None } + ); + + ENUMMAP_DECLARE(WrapModes, SamplerState::WrapMode, DkWrapMode, + { SamplerState::WRAP_CLAMP, DkWrapMode_Clamp }, + { SamplerState::WRAP_CLAMP_ZERO, DkWrapMode_ClampToBorder }, + { SamplerState::WRAP_REPEAT, DkWrapMode_Repeat }, + { SamplerState::WRAP_MIRRORED_REPEAT, DkWrapMode_MirroredRepeat } + ); + + ENUMMAP_DECLARE(PixelFormats, PixelFormat, DkImageFormat, + { PIXELFORMAT_R8_UNORM, DkImageFormat_R8_Unorm }, + { PIXELFORMAT_R16_UNORM, DkImageFormat_R16_Unorm }, + { PIXELFORMAT_RG8_UNORM, DkImageFormat_RG8_Unorm }, + { PIXELFORMAT_RGBA8_UNORM, DkImageFormat_RGBA8_Unorm }, + { PIXELFORMAT_RGB565_UNORM, DkImageFormat_RGB565_Unorm }, + { PIXELFORMAT_RGBA8_sRGB, DkImageFormat_RGBA8_Unorm_sRGB }, + { PIXELFORMAT_DXT1_UNORM, DkImageFormat_RGBA_BC1 }, + { PIXELFORMAT_DXT3_UNORM, DkImageFormat_RGBA_BC2 }, + { PIXELFORMAT_DXT5_UNORM, DkImageFormat_RGBA_BC3 }, + { PIXELFORMAT_ETC1_UNORM, DkImageFormat_RGB_ETC2 }, + { PIXELFORMAT_ETC2_RGB_UNORM, DkImageFormat_RGB_ETC2 }, + { PIXELFORMAT_ETC2_RGBA1_UNORM, DkImageFormat_RGBA_ETC2 }, + { PIXELFORMAT_ETC2_RGBA_UNORM, DkImageFormat_RGBA_ETC2 }, + { PIXELFORMAT_BC4_UNORM, DkImageFormat_R_BC4_Unorm }, + { PIXELFORMAT_BC5_UNORM, DkImageFormat_RG_BC5_Unorm }, + { PIXELFORMAT_BC7_UNORM, DkImageFormat_RGBA_BC7_Unorm }, + { PIXELFORMAT_BC7_sRGB, DkImageFormat_RGBA_BC7_Unorm_sRGB }, + { PIXELFORMAT_ASTC_4x4_UNORM, DkImageFormat_RGBA_ASTC_4x4 }, + { PIXELFORMAT_ASTC_5x4_UNORM, DkImageFormat_RGBA_ASTC_5x4 }, + { PIXELFORMAT_ASTC_6x5_UNORM, DkImageFormat_RGBA_ASTC_6x5 }, + { PIXELFORMAT_ASTC_6x6_UNORM, DkImageFormat_RGBA_ASTC_6x6 }, + { PIXELFORMAT_ASTC_8x5_UNORM, DkImageFormat_RGBA_ASTC_8x5 }, + { PIXELFORMAT_ASTC_8x6_UNORM, DkImageFormat_RGBA_ASTC_8x6 }, + { PIXELFORMAT_ASTC_8x8_UNORM, DkImageFormat_RGBA_ASTC_8x8 }, + { PIXELFORMAT_ASTC_10x5_UNORM, DkImageFormat_RGBA_ASTC_10x5 }, + { PIXELFORMAT_ASTC_10x6_UNORM, DkImageFormat_RGBA_ASTC_10x6 }, + { PIXELFORMAT_ASTC_10x8_UNORM, DkImageFormat_RGBA_ASTC_10x8 }, + { PIXELFORMAT_ASTC_10x10_UNORM, DkImageFormat_RGBA_ASTC_10x10 }, + { PIXELFORMAT_ASTC_12x10_UNORM, DkImageFormat_RGBA_ASTC_12x10 }, + { PIXELFORMAT_ASTC_12x12_UNORM, DkImageFormat_RGBA_ASTC_12x12 } + ); + + ENUMMAP_DECLARE(PrimitiveTypes, PrimitiveType, DkPrimitive, + { PRIMITIVE_TRIANGLES, DkPrimitive_Triangles }, + { PRIMITIVE_POINTS, DkPrimitive_Points }, + { PRIMITIVE_TRIANGLE_STRIP, DkPrimitive_TriangleStrip }, + { PRIMITIVE_TRIANGLE_FAN, DkPrimitive_TriangleFan } + ); + // clang-format on + + private: + static constexpr int GPU_POOL_SIZE = 0x4000000; + static constexpr int GPU_USE_FLAGS = (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image); + + static constexpr int CPU_POOL_SIZE = 0x100000; + static constexpr int CPU_USE_FLAGS = (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached); + + static constexpr int SHADER_POOL_SIZE = 0x20000; + static constexpr int SHADER_USE_FLAGS = + DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code; + + static constexpr int RENDERTARGET_USE_FLAGS = + DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression; + + static constexpr int COMMAND_SIZE = 0x100000; + + void createFramebuffers(); + + void destroyFramebuffers(); + + struct Context : public ContextBase + { + dk::RasterizerState rasterizer; + dk::ColorWriteState colorWrite; + dk::BlendState blend; + dk::ColorState color; + dk::DepthStencilState depthStencil; + + dk::Image* boundFramebuffer; + bool descriptorsDirty; + } context; + + struct Transform + { + glm::mat4 modelView; + glm::mat4 projection; + } transform; + + static constexpr auto TRANSFORM_SIZE = sizeof(Transform); + + CMemPool::Handle uniform; + + dk::UniqueDevice device; + + dk::UniqueQueue mainQueue; + dk::UniqueQueue textureQueue; + + dk::UniqueCmdBuf commandBuffer; + dk::UniqueSwapchain swapchain; + + CMemPool images; + CMemPool data; + CMemPool code; + + int framebufferSlot; + + Framebuffer framebuffers[MAX_RENDERTARGETS]; + Framebuffer depthbuffer; + + CCmdMemRing commands; + std::array targets; + + BitwiseAlloc textureHandles; + CDescriptorSet imageSet; + CDescriptorSet samplerSet; + }; + + extern deko3d d3d; +} // namespace love diff --git a/platform/hac/include/driver/display/deko3d/CCmdVtxRing.h b/platform/hac/include/driver/display/deko3d/CCmdVtxRing.h index a1a17067e..b372acf27 100644 --- a/platform/hac/include/driver/display/deko3d/CCmdVtxRing.h +++ b/platform/hac/include/driver/display/deko3d/CCmdVtxRing.h @@ -37,8 +37,7 @@ class CCmdVtxRing return m_mem; } - /* Return current buffer's size */ - const uint32_t getSize() + uint32_t getSize() const { return m_mem.getSize() / NumSlices; } @@ -47,8 +46,7 @@ class CCmdVtxRing std::pair begin() { const auto offset = m_curSlice * m_sliceSize; - return std::make_pair((void*)((char*)m_mem.getCpuAddr() + offset), - m_mem.getGpuAddr() + offset); + return std::make_pair((void*)((char*)m_mem.getCpuAddr() + offset), m_mem.getGpuAddr() + offset); } /* diff --git a/platform/hac/include/driver/display/deko3d/bitalloc.hpp b/platform/hac/include/driver/display/deko3d/bitalloc.hpp deleted file mode 100644 index e18532296..000000000 --- a/platform/hac/include/driver/display/deko3d/bitalloc.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include - -template -class BitwiseAlloc : std::bitset -{ - public: - size_t allocate() - { - size_t index = 0; - while (index < this->size() && this->test(index)) - ++index; - - this->set(index); - return index; - } - - void reset(uint32_t handle) - { - auto index = 0; - - if (this->Find(handle, index)) - this->reset(index); - } - - bool find(uint32_t handle, int& out) - { - size_t index = (handle & ((1U << 20) - 1)); - - if (this->test(index)) - { - out = index; - return true; - } - - out = -1; - return false; - } -}; diff --git a/platform/hac/include/driver/graphics/Attributes.hpp b/platform/hac/include/driver/graphics/Attributes.hpp new file mode 100644 index 000000000..887fdbb54 --- /dev/null +++ b/platform/hac/include/driver/graphics/Attributes.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "common/Color.hpp" + +#include "modules/graphics/vertex.hpp" + +#include +#include +#define DK_HPP_SUPPORT_VECTOR +#include + +namespace love +{ + namespace vertex + { + struct Attributes + { + dk::detail::ArrayProxy attributeState; + dk::detail::ArrayProxy bufferState; + }; + + // clang-format off + /* Primitives */ + constexpr std::array PrimitiveBufferState = { + DkVtxBufferState { sizeof(Vertex), 0 }, + }; + + constexpr std::array PrimitiveAttribState = { + DkVtxAttribState { 0, 0, POSITION_OFFSET, DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState { 0, 0, COLOR_OFFSET, DkVtxAttribSize_4x32, DkVtxAttribType_Float, 0 } + }; + + /* Textures*/ + constexpr std::array TextureBufferState = { + DkVtxBufferState { sizeof(Vertex), 0 }, + }; + + constexpr std::array TextureAttribState = { + DkVtxAttribState { 0, 0, POSITION_OFFSET, DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState { 0, 0, COLOR_OFFSET, DkVtxAttribSize_4x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState { 0, 0, TEXCOORD_OFFSET, DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0 } + }; + // clang-format on + + static void getAttributes(bool isTexture, Attributes& out) + { + if (!isTexture) + { + out.attributeState = PrimitiveAttribState; + out.bufferState = PrimitiveBufferState; + return; + } + + out.attributeState = TextureAttribState; + out.bufferState = TextureBufferState; + } + } // namespace vertex +} // namespace love diff --git a/platform/hac/include/driver/graphics/BitAlloc.hpp b/platform/hac/include/driver/graphics/BitAlloc.hpp new file mode 100644 index 000000000..f879d08fa --- /dev/null +++ b/platform/hac/include/driver/graphics/BitAlloc.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace love +{ + template + class BitwiseAlloc : std::bitset + { + public: + size_t allocate() + { + size_t index = 0; + while (index < this->size() && this->test(index)) + ++index; + + this->set(index); + return index; + } + + void deallocate(uint32_t handle) + { + auto index = 0; + + if (this->find(handle, index)) + this->reset(index); + } + + bool find(uint32_t handle, int& out) + { + size_t index = (handle & ((1U << 20) - 1)); + + if (this->test(index)) + { + out = index; + return true; + } + + out = -1; + return false; + } + }; +} // namespace love diff --git a/platform/hac/include/driver/graphics/StreamBuffer.hpp b/platform/hac/include/driver/graphics/StreamBuffer.hpp new file mode 100644 index 000000000..0b52d77bc --- /dev/null +++ b/platform/hac/include/driver/graphics/StreamBuffer.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "driver/display/deko.hpp" + +#include "driver/graphics/StreamBuffer.tcc" +#include "modules/graphics/Volatile.hpp" + +namespace love +{ + template + class StreamBuffer final : public StreamBufferBase + { + public: + StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size) + { + this->sliceSize = (size + DK_CMDMEM_ALIGNMENT - 1) & ~(DK_CMDMEM_ALIGNMENT - 1); + } + + bool allocate(CMemPool& pool) + { + this->memory = pool.allocate(this->sliceSize, alignof(T)); + return this->memory; + } + + StreamBuffer(const StreamBuffer&) = delete; + + StreamBuffer& operator=(const StreamBuffer&) = delete; + + ~StreamBuffer() + { + this->memory.destroy(); + } + + MapInfo map(size_t) + { + MapInfo info {}; + info.data = &((T*)this->memory.getCpuAddr())[this->index]; + info.size = this->bufferSize - this->frameGPUReadOffset; + + return info; + } + + size_t unmap(size_t); + + void* getData() const + { + return this->memory.getCpuAddr(); + } + + ptrdiff_t getHandle() const + { + return (ptrdiff_t)this->memory.getGpuAddr(); + } + + private: + CMemPool::Handle memory; + uint32_t sliceSize; + }; +} // namespace love diff --git a/platform/hac/include/modules/graphics/Graphics.hpp b/platform/hac/include/modules/graphics/Graphics.hpp index e9d847b0f..db5d6c60e 100644 --- a/platform/hac/include/modules/graphics/Graphics.hpp +++ b/platform/hac/include/modules/graphics/Graphics.hpp @@ -4,45 +4,72 @@ namespace love { - class Graphics : public GraphicsBase + class Graphics : public GraphicsBase { public: Graphics(); - void backbufferChanged(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa); + void backbufferChanged(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa); void backbufferChanged(int width, int height, int pixelWidth, int pixelHeight) { - this->backbufferChanged(width, height, pixelWidth, pixelHeight, - this->backBufferHasStencil, this->backBufferHasDepth, - this->requestedBackbufferMSAA); + this->backbufferChanged(width, height, pixelWidth, pixelHeight, this->backBufferHasStencil, + this->backBufferHasDepth, this->requestedBackbufferMSAA); } - void clearImpl(OptionalColor color, OptionalInt depth, OptionalDouble stencil); + virtual void initCapabilities() override; - using GraphicsBase::clear; + virtual void captureScreenshot(const ScreenshotInfo& info) override; - void presentImpl(); + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) override; - void setScissorImpl(const Rect& scissor); + virtual void clear(const std::vector& colors, OptionalInt stencil, + OptionalDouble depth) override; - void setScissorImpl(); + virtual void present(void* screenshotCallbackData) override; - void setFrontFaceWindingImpl(Winding winding); + virtual void setScissor(const Rect& scissor) override; - void setColorMaskImpl(ColorChannelMask mask); + virtual void setScissor() override; - void setBlendStateImpl(const BlendState& state); + virtual void setFrontFaceWinding(Winding winding) override; - void getRendererInfoImpl(RendererInfo& info) const; + virtual void setColorMask(ColorChannelMask mask) override; - bool setModeImpl(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa); + virtual void setBlendState(const BlendState& state) override; + + virtual void setPointSize(float size) override; + + virtual FontBase* newFont(Rasterizer* data) override; + + virtual FontBase* newDefaultFont(int size, const Rasterizer::Settings& settings) override; + + // clang-format off + virtual TextureBase* newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data = nullptr) override; + // clang-format on + + virtual bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa) override; + + virtual void setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) override; + + virtual bool isPixelFormatSupported(PixelFormat format, uint32_t usage) override; + + void draw(const DrawIndexedCommand& command) override; + + void draw(const DrawCommand& command) override; + + using GraphicsBase::draw; bool isActive() const; - void unsetModeImpl(); + virtual void unsetMode() override; + + void setActiveScreen() override; + + void setViewport(int x, int y, int width, int height); private: bool backBufferHasStencil; diff --git a/platform/hac/include/modules/graphics/Shader.hpp b/platform/hac/include/modules/graphics/Shader.hpp new file mode 100644 index 000000000..2b5825293 --- /dev/null +++ b/platform/hac/include/modules/graphics/Shader.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "driver/display/deko3d/CMemPool.h" +#include "modules/graphics/Graphics.hpp" +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/Volatile.hpp" + +#include + +namespace love +{ + class Shader : public ShaderBase, public Volatile + { + public: + struct UniformInfo + { + int8_t location; + std::string name; + }; + + struct Stage + { + dk::Shader shader; + CMemPool::Handle memory; + }; + + struct Program + { + Stage vertex; + Stage fragment; + } program; + + Shader(); + + Shader(StandardShader shader); + + virtual ~Shader(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + void attach() override; + + ptrdiff_t getHandle() const override; + + const UniformInfo getUniform(const std::string& name) const; + + bool hasUniform(const std::string& name) const; + + void updateBuiltinUniforms(GraphicsBase* graphics); + + private: + bool validate(Stage& stage, const char* filepath, std::string& error); + }; +} // namespace love diff --git a/platform/hac/include/modules/graphics/Texture.hpp b/platform/hac/include/modules/graphics/Texture.hpp new file mode 100644 index 000000000..3880e8fdc --- /dev/null +++ b/platform/hac/include/modules/graphics/Texture.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/Volatile.hpp" + +#include "driver/display/deko3d/CMemPool.h" +#include + +namespace love +{ + class Texture final : public TextureBase, public Volatile + { + public: + Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data); + + virtual ~Texture(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + ptrdiff_t getHandle() const override; + + ptrdiff_t getRenderTargetHandle() const override; + + ptrdiff_t getSamplerHandle() const override; + + dk::ImageDescriptor& getDescriptorHandle() + { + return this->descriptor; + } + + dk::SamplerDescriptor& getSamplerDescriptor() + { + return this->samplerDescriptor; + } + + void setSamplerState(const SamplerState& state) override; + + void uploadByteData(const void* data, size_t size, int slice, int mipmap, const Rect& rect) override; + + void generateMipmapsInternal() override; + + void setHandleData(ptrdiff_t data) override + { + this->handle = (DkResHandle)data; + } + + private: + void createTexture(); + + Slices slices; + + dk::Image image; + dk::ImageDescriptor descriptor; + dk::Sampler sampler; + dk::SamplerDescriptor samplerDescriptor; + + DkResHandle handle; + CMemPool::Handle memory; + }; +} // namespace love diff --git a/platform/hac/libraries/luasocket.patch b/platform/hac/libraries/luasocket.patch new file mode 100644 index 000000000..6ba9fa36a --- /dev/null +++ b/platform/hac/libraries/luasocket.patch @@ -0,0 +1,2133 @@ +diff --git a/inet.c b/inet.c +index 138c9ab..1cb1cda 100755 +--- a/inet.c ++++ b/inet.c +@@ -371,7 +371,11 @@ const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm) + } + case AF_INET6: { + struct sockaddr_in6 sin6; ++ #if !defined(__SWITCH__) + struct in6_addr addrany = IN6ADDR_ANY_INIT; ++ #else ++ struct in6_addr addrany = in6addr_any; ++ #endif + memset((char *) &sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_UNSPEC; + sin6.sin6_addr = addrany; +diff --git a/makefile b/makefile +deleted file mode 100755 +index 06f4d19..0000000 +--- a/makefile ++++ /dev/null +@@ -1,461 +0,0 @@ +-# luasocket src/makefile +-# +-# Definitions in this section can be overriden on the command line or in the +-# environment. +-# +-# These are equivalent: +-# +-# export PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw +-# make +-# +-# and +-# +-# make PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw +- +-# PLAT: linux macosx win32 win64 mingw +-# platform to build for +-PLAT?=linux +- +-# LUAV: 5.1 5.2 5.3 5.4 +-# lua version to build against +-LUAV?=5.1 +- +-# MYCFLAGS: to be set by user if needed +-MYCFLAGS?= +- +-# MYLDFLAGS: to be set by user if needed +-MYLDFLAGS?= +- +-# DEBUG: NODEBUG DEBUG +-# debug mode causes luasocket to collect and returns timing information useful +-# for testing and debugging luasocket itself +-DEBUG?=NODEBUG +- +-# where lua headers are found for macosx builds +-# LUAINC_macosx: +-# /opt/local/include +-LUAINC_macosx_base?=/opt/local/include +-LUAINC_macosx?=$(LUAINC_macosx_base)/lua/$(LUAV) $(LUAINC_macosx_base)/lua$(LUAV) $(LUAINC_macosx_base)/lua-$(LUAV) +- +-# FIXME default should this default to fink or to macports? +-# What happens when more than one Lua version is installed? +-LUAPREFIX_macosx?=/opt/local +-CDIR_macosx?=lib/lua/$(LUAV) +-LDIR_macosx?=share/lua/$(LUAV) +- +-# LUAINC_linux: +-# /usr/include/lua$(LUAV) +-# /usr/local/include +-# /usr/local/include/lua$(LUAV) +-# where lua headers are found for linux builds +-LUAINC_linux_base?=/usr/include +-LUAINC_linux?=$(LUAINC_linux_base)/lua/$(LUAV) $(LUAINC_linux_base)/lua$(LUAV) +-LUAPREFIX_linux?=/usr/local +-CDIR_linux?=lib/lua/$(LUAV) +-LDIR_linux?=share/lua/$(LUAV) +- +-# LUAINC_freebsd: +-# /usr/local/include/lua$(LUAV) +-# where lua headers are found for freebsd builds +-LUAINC_freebsd_base?=/usr/local/include/ +-LUAINC_freebsd?=$(LUAINC_freebsd_base)/lua/$(LUAV) $(LUAINC_freebsd_base)/lua$(LUAV) +-LUAPREFIX_freebsd?=/usr/local/ +-CDIR_freebsd?=lib/lua/$(LUAV) +-LDIR_freebsd?=share/lua/$(LUAV) +- +-# where lua headers are found for mingw builds +-# LUAINC_mingw: +-# /opt/local/include +-LUAINC_mingw_base?=/usr/include +-LUAINC_mingw?=$(LUAINC_mingw_base)/lua/$(LUAV) $(LUAINC_mingw_base)/lua$(LUAV) +-LUALIB_mingw_base?=/usr/bin +-LUALIB_mingw?=$(LUALIB_mingw_base)/lua/$(LUAV)/lua$(subst .,,$(LUAV)).dll +-LUAPREFIX_mingw?=/usr +-CDIR_mingw?=lua/$(LUAV) +-LDIR_mingw?=lua/$(LUAV)/lua +- +- +-# LUAINC_win32: +-# LUALIB_win32: +-# where lua headers and libraries are found for win32 builds +-LUAPREFIX_win32?= +-LUAINC_win32?=$(LUAPREFIX_win32)/include/lua/$(LUAV) $(LUAPREFIX_win32)/include/lua$(LUAV) +-PLATFORM_win32?=Release +-CDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32) +-LDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32)/lua +-LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUAV)/$(PLATFORM_win32) +-LUALIBNAME_win32?=lua$(subst .,,$(LUAV)).lib +- +-# LUAINC_win64: +-# LUALIB_win64: +-# where lua headers and libraries are found for win64 builds +-LUAPREFIX_win64?= +-LUAINC_win64?=$(LUAPREFIX_win64)/include/lua/$(LUAV) $(LUAPREFIX_win64)/include/lua$(LUAV) +-PLATFORM_win64?=x64/Release +-CDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64) +-LDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64)/lua +-LUALIB_win64?=$(LUAPREFIX_win64)/lib/lua/$(LUAV)/$(PLATFORM_win64) +-LUALIBNAME_win64?=lua$(subst .,,$(LUAV)).lib +- +- +-# LUAINC_solaris: +-LUAINC_solaris_base?=/usr/include +-LUAINC_solaris?=$(LUAINC_solaris_base)/lua/$(LUAV) $(LUAINC_solaris_base)/lua$(LUAV) +-LUAPREFIX_solaris?=/usr/local +-CDIR_solaris?=lib/lua/$(LUAV) +-LDIR_solaris?=share/lua/$(LUAV) +- +-# prefix: /usr/local /usr /opt/local /sw +-# the top of the default install tree +-prefix?=$(LUAPREFIX_$(PLAT)) +- +-CDIR?=$(CDIR_$(PLAT)) +-LDIR?=$(LDIR_$(PLAT)) +- +-# DESTDIR: (no default) +-# used by package managers to install into a temporary destination +-DESTDIR?= +- +-#------ +-# Definitions below can be overridden on the make command line, but +-# shouldn't have to be. +- +- +-#------ +-# Install directories +-# +- +-INSTALL_DIR=install -d +-INSTALL_DATA=install -m644 +-INSTALL_EXEC=install +-INSTALL_TOP=$(DESTDIR)$(prefix) +- +-INSTALL_TOP_LDIR=$(INSTALL_TOP)/$(LDIR) +-INSTALL_TOP_CDIR=$(INSTALL_TOP)/$(CDIR) +- +-INSTALL_SOCKET_LDIR=$(INSTALL_TOP_LDIR)/socket +-INSTALL_SOCKET_CDIR=$(INSTALL_TOP_CDIR)/socket +-INSTALL_MIME_LDIR=$(INSTALL_TOP_LDIR)/mime +-INSTALL_MIME_CDIR=$(INSTALL_TOP_CDIR)/mime +- +-print: +- @echo PLAT=$(PLAT) +- @echo LUAV=$(LUAV) +- @echo DEBUG=$(DEBUG) +- @echo prefix=$(prefix) +- @echo LUAINC_$(PLAT)=$(LUAINC_$(PLAT)) +- @echo LUALIB_$(PLAT)=$(LUALIB_$(PLAT)) +- @echo INSTALL_TOP_CDIR=$(INSTALL_TOP_CDIR) +- @echo INSTALL_TOP_LDIR=$(INSTALL_TOP_LDIR) +- @echo CFLAGS=$(CFLAGS) +- @echo LDFLAGS=$(LDFLAGS) +- +-#------ +-# Supported platforms +-# +-PLATS= macosx linux win32 win64 mingw solaris +- +-#------ +-# Compiler and linker settings +-# for Mac OS X +-SO_macosx=so +-O_macosx=o +-CC_macosx=gcc +-DEF_macosx= -DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +-CFLAGS_macosx=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +-LDFLAGS_macosx= -bundle -undefined dynamic_lookup -o +-LD_macosx=gcc +-SOCKET_macosx=usocket.o +- +-#------ +-# Compiler and linker settings +-# for Linux +-SO_linux=so +-O_linux=o +-CC_linux=gcc +-DEF_linux=-DLUASOCKET_$(DEBUG) +-CFLAGS_linux=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_linux=-O -shared -fpic -o +-LD_linux=gcc +-SOCKET_linux=usocket.o +- +-#------ +-# Compiler and linker settings +-# for FreeBSD +-SO_freebsd=so +-O_freebsd=o +-CC_freebsd=gcc +-DEF_freebsd=-DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +-CFLAGS_freebsd=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_freebsd=-O -shared -fpic -o +-LD_freebsd=gcc +-SOCKET_freebsd=usocket.o +- +-#------ +-# Compiler and linker settings +-# for Solaris +-SO_solaris=so +-O_solaris=o +-CC_solaris=gcc +-DEF_solaris=-DLUASOCKET_$(DEBUG) +-CFLAGS_solaris=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ +- -Wimplicit -O2 -ggdb3 -fpic +-LDFLAGS_solaris=-lnsl -lsocket -lresolv -O -shared -fpic -o +-LD_solaris=gcc +-SOCKET_solaris=usocket.o +- +-#------ +-# Compiler and linker settings +-# for MingW +-SO_mingw=dll +-O_mingw=o +-CC_mingw=gcc +-DEF_mingw= -DLUASOCKET_$(DEBUG) \ +- -DWINVER=0x0501 +-CFLAGS_mingw=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +-LDFLAGS_mingw= $(LUALIB) -shared -Wl,-s -lws2_32 -o +-LD_mingw=gcc +-SOCKET_mingw=wsocket.o +- +- +-#------ +-# Compiler and linker settings +-# for Win32 +-SO_win32=dll +-O_win32=obj +-CC_win32=cl +-DEF_win32= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ +- //D "_CRT_SECURE_NO_WARNINGS" \ +- //D "_WINDLL" \ +- //D "LUASOCKET_$(DEBUG)" +-CFLAGS_win32=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +-LDFLAGS_win32= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ +- //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ +- /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ +- //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ +- //MACHINE:X86 /LIBPATH:"$(LUALIB)" \ +- $(LUALIBNAME_win32) ws2_32.lib //OUT: +- +-LD_win32=cl +-SOCKET_win32=wsocket.obj +- +-#------ +-# Compiler and linker settings +-# for Win64 +-SO_win64=dll +-O_win64=obj +-CC_win64=cl +-DEF_win64= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ +- //D "_CRT_SECURE_NO_WARNINGS" \ +- //D "_WINDLL" \ +- //D "LUASOCKET_$(DEBUG)" +-CFLAGS_win64=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +-LDFLAGS_win64= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ +- //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ +- /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ +- //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ +- /LIBPATH:"$(LUALIB)" \ +- $(LUALIBNAME_win64) ws2_32.lib //OUT: +- +-LD_win64=cl +-SOCKET_win64=wsocket.obj +- +-.SUFFIXES: .obj +- +-.c.obj: +- $(CC) $(CFLAGS) //Fo"$@" //c $< +- +-#------ +-# Output file names +-# +-SO=$(SO_$(PLAT)) +-O=$(O_$(PLAT)) +-SOCKET_V=3.0.0 +-MIME_V=1.0.3 +-SOCKET_SO=socket-$(SOCKET_V).$(SO) +-MIME_SO=mime-$(MIME_V).$(SO) +-UNIX_SO=unix.$(SO) +-SERIAL_SO=serial.$(SO) +-SOCKET=$(SOCKET_$(PLAT)) +- +-#------ +-# Settings selected for platform +-# +-CC=$(CC_$(PLAT)) +-DEF=$(DEF_$(PLAT)) +-CFLAGS=$(MYCFLAGS) $(CFLAGS_$(PLAT)) +-LDFLAGS=$(MYLDFLAGS) $(LDFLAGS_$(PLAT)) +-LD=$(LD_$(PLAT)) +-LUAINC= $(LUAINC_$(PLAT)) +-LUALIB= $(LUALIB_$(PLAT)) +- +-#------ +-# Modules belonging to socket-core +-# +-SOCKET_OBJS= \ +- luasocket.$(O) \ +- timeout.$(O) \ +- buffer.$(O) \ +- io.$(O) \ +- auxiliar.$(O) \ +- compat.$(O) \ +- options.$(O) \ +- inet.$(O) \ +- $(SOCKET) \ +- except.$(O) \ +- select.$(O) \ +- tcp.$(O) \ +- udp.$(O) +- +-#------ +-# Modules belonging mime-core +-# +-MIME_OBJS= \ +- mime.$(O) \ +- compat.$(O) +- +-#------ +-# Modules belonging unix (local domain sockets) +-# +-UNIX_OBJS=\ +- buffer.$(O) \ +- auxiliar.$(O) \ +- options.$(O) \ +- timeout.$(O) \ +- io.$(O) \ +- usocket.$(O) \ +- unixstream.$(O) \ +- unixdgram.$(O) \ +- compat.$(O) \ +- unix.$(O) +- +-#------ +-# Modules belonging to serial (device streams) +-# +-SERIAL_OBJS=\ +- buffer.$(O) \ +- compat.$(O) \ +- auxiliar.$(O) \ +- options.$(O) \ +- timeout.$(O) \ +- io.$(O) \ +- usocket.$(O) \ +- serial.$(O) +- +-#------ +-# Files to install +-# +-TO_SOCKET_LDIR= \ +- http.lua \ +- url.lua \ +- tp.lua \ +- ftp.lua \ +- headers.lua \ +- smtp.lua +- +-TO_TOP_LDIR= \ +- ltn12.lua \ +- socket.lua \ +- mime.lua +- +-#------ +-# Targets +-# +-default: $(PLAT) +- +- +-freebsd: +- $(MAKE) all-unix PLAT=freebsd +- +-macosx: +- $(MAKE) all-unix PLAT=macosx +- +-win32: +- $(MAKE) all PLAT=win32 +- +-win64: +- $(MAKE) all PLAT=win64 +- +-linux: +- $(MAKE) all-unix PLAT=linux +- +-mingw: +- $(MAKE) all PLAT=mingw +- +-solaris: +- $(MAKE) all-unix PLAT=solaris +- +-none: +- @echo "Please run" +- @echo " make PLATFORM" +- @echo "where PLATFORM is one of these:" +- @echo " $(PLATS)" +- +-all: $(SOCKET_SO) $(MIME_SO) +- +-$(SOCKET_SO): $(SOCKET_OBJS) +- $(LD) $(SOCKET_OBJS) $(LDFLAGS)$@ +- +-$(MIME_SO): $(MIME_OBJS) +- $(LD) $(MIME_OBJS) $(LDFLAGS)$@ +- +-all-unix: all $(UNIX_SO) $(SERIAL_SO) +- +-$(UNIX_SO): $(UNIX_OBJS) +- $(LD) $(UNIX_OBJS) $(LDFLAGS)$@ +- +-$(SERIAL_SO): $(SERIAL_OBJS) +- $(LD) $(SERIAL_OBJS) $(LDFLAGS)$@ +- +-install: +- $(INSTALL_DIR) $(INSTALL_TOP_LDIR) +- $(INSTALL_DATA) $(TO_TOP_LDIR) $(INSTALL_TOP_LDIR) +- $(INSTALL_DIR) $(INSTALL_SOCKET_LDIR) +- $(INSTALL_DATA) $(TO_SOCKET_LDIR) $(INSTALL_SOCKET_LDIR) +- $(INSTALL_DIR) $(INSTALL_SOCKET_CDIR) +- $(INSTALL_EXEC) $(SOCKET_SO) $(INSTALL_SOCKET_CDIR)/core.$(SO) +- $(INSTALL_DIR) $(INSTALL_MIME_CDIR) +- $(INSTALL_EXEC) $(MIME_SO) $(INSTALL_MIME_CDIR)/core.$(SO) +- +-install-unix: install +- $(INSTALL_EXEC) $(UNIX_SO) $(INSTALL_SOCKET_CDIR)/$(UNIX_SO) +- $(INSTALL_EXEC) $(SERIAL_SO) $(INSTALL_SOCKET_CDIR)/$(SERIAL_SO) +- +-local: +- $(MAKE) install INSTALL_TOP_CDIR=.. INSTALL_TOP_LDIR=.. +- +-clean: +- rm -f $(SOCKET_SO) $(SOCKET_OBJS) $(SERIAL_OBJS) +- rm -f $(MIME_SO) $(UNIX_SO) $(SERIAL_SO) $(MIME_OBJS) $(UNIX_OBJS) +- +-.PHONY: all $(PLATS) default clean echo none +- +-#------ +-# List of dependencies +-# +-compat.$(O): compat.c compat.h +-auxiliar.$(O): auxiliar.c auxiliar.h +-buffer.$(O): buffer.c buffer.h io.h timeout.h +-except.$(O): except.c except.h +-inet.$(O): inet.c inet.h socket.h io.h timeout.h usocket.h +-io.$(O): io.c io.h timeout.h +-luasocket.$(O): luasocket.c luasocket.h auxiliar.h except.h \ +- timeout.h buffer.h io.h inet.h socket.h usocket.h tcp.h \ +- udp.h select.h +-mime.$(O): mime.c mime.h +-options.$(O): options.c auxiliar.h options.h socket.h io.h \ +- timeout.h usocket.h inet.h +-select.$(O): select.c socket.h io.h timeout.h usocket.h select.h +-serial.$(O): serial.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- options.h unix.h buffer.h +-tcp.$(O): tcp.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- inet.h options.h tcp.h buffer.h +-timeout.$(O): timeout.c auxiliar.h timeout.h +-udp.$(O): udp.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- inet.h options.h udp.h +-unix.$(O): unix.c auxiliar.h socket.h io.h timeout.h usocket.h \ +- options.h unix.h buffer.h +-usocket.$(O): usocket.c socket.h io.h timeout.h usocket.h +-wsocket.$(O): wsocket.c socket.h io.h timeout.h usocket.h +diff --git a/options.c b/options.c +index 3280c51..7c88d01 100644 +--- a/options.c ++++ b/options.c +@@ -22,6 +22,22 @@ static int opt_set(lua_State *L, p_socket ps, int level, int name, + static int opt_get(lua_State *L, p_socket ps, int level, int name, + void *val, int* len); + ++static int set_opt_error(lua_State* L) ++{ ++ lua_pushnil(L); ++ lua_pushstring(L, "setsockopt failed: not supported"); ++ ++ return 2; ++} ++ ++static int get_opt_error(lua_State* L) ++{ ++ lua_pushnil(L); ++ lua_pushstring(L, "getsockopt failed: not supported"); ++ ++ return 2; ++} ++ + /*=========================================================================*\ + * Exported functions + \*=========================================================================*/ +@@ -393,7 +409,7 @@ static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) + luaL_argerror(L, 3, "invalid 'interface' ip address"); + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); + } +- ++#if !defined(__SWITCH__) + static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) + { + struct ipv6_mreq val; /* obj, opt-name, table */ +@@ -419,6 +435,9 @@ static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) + } + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); + } ++#else ++static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) { return set_opt_error(L); } ++#endif + + static + int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len) +diff --git a/serial.c b/serial.c +deleted file mode 100644 +index 21485d3..0000000 +--- a/serial.c ++++ /dev/null +@@ -1,171 +0,0 @@ +-/*=========================================================================*\ +-* Serial stream +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "auxiliar.h" +-#include "socket.h" +-#include "options.h" +-#include "unix.h" +- +-#include +-#include +- +-/* +-Reuses userdata definition from unix.h, since it is useful for all +-stream-like objects. +- +-If we stored the serial path for use in error messages or userdata +-printing, we might need our own userdata definition. +- +-Group usage is semi-inherited from unix.c, but unnecessary since we +-have only one object type. +-*/ +- +-/*=========================================================================*\ +-* Internal function prototypes +-\*=========================================================================*/ +-static int global_create(lua_State *L); +-static int meth_send(lua_State *L); +-static int meth_receive(lua_State *L); +-static int meth_close(lua_State *L); +-static int meth_settimeout(lua_State *L); +-static int meth_getfd(lua_State *L); +-static int meth_setfd(lua_State *L); +-static int meth_dirty(lua_State *L); +-static int meth_getstats(lua_State *L); +-static int meth_setstats(lua_State *L); +- +-/* serial object methods */ +-static luaL_Reg serial_methods[] = { +- {"__gc", meth_close}, +- {"__tostring", auxiliar_tostring}, +- {"close", meth_close}, +- {"dirty", meth_dirty}, +- {"getfd", meth_getfd}, +- {"getstats", meth_getstats}, +- {"setstats", meth_setstats}, +- {"receive", meth_receive}, +- {"send", meth_send}, +- {"setfd", meth_setfd}, +- {"settimeout", meth_settimeout}, +- {NULL, NULL} +-}; +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-LUASOCKET_API int luaopen_socket_serial(lua_State *L) { +- /* create classes */ +- auxiliar_newclass(L, "serial{client}", serial_methods); +- /* create class groups */ +- auxiliar_add2group(L, "serial{client}", "serial{any}"); +- lua_pushcfunction(L, global_create); +- return 1; +-} +- +-/*=========================================================================*\ +-* Lua methods +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Just call buffered IO methods +-\*-------------------------------------------------------------------------*/ +-static int meth_send(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_send(L, &un->buf); +-} +- +-static int meth_receive(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_receive(L, &un->buf); +-} +- +-static int meth_getstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_getstats(L, &un->buf); +-} +- +-static int meth_setstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); +- return buffer_meth_setstats(L, &un->buf); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select support methods +-\*-------------------------------------------------------------------------*/ +-static int meth_getfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- lua_pushnumber(L, (int) un->sock); +- return 1; +-} +- +-/* this is very dangerous, but can be handy for those that are brave enough */ +-static int meth_setfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- un->sock = (t_socket) luaL_checknumber(L, 2); +- return 0; +-} +- +-static int meth_dirty(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- lua_pushboolean(L, !buffer_isempty(&un->buf)); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Closes socket used by object +-\*-------------------------------------------------------------------------*/ +-static int meth_close(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- socket_destroy(&un->sock); +- lua_pushnumber(L, 1); +- return 1; +-} +- +- +-/*-------------------------------------------------------------------------*\ +-* Just call tm methods +-\*-------------------------------------------------------------------------*/ +-static int meth_settimeout(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); +- return timeout_meth_settimeout(L, &un->tm); +-} +- +-/*=========================================================================*\ +-* Library functions +-\*=========================================================================*/ +- +- +-/*-------------------------------------------------------------------------*\ +-* Creates a serial object +-\*-------------------------------------------------------------------------*/ +-static int global_create(lua_State *L) { +- const char* path = luaL_checkstring(L, 1); +- +- /* allocate unix object */ +- p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- +- /* open serial device */ +- t_socket sock = open(path, O_NOCTTY|O_RDWR); +- +- /*printf("open %s on %d\n", path, sock);*/ +- +- if (sock < 0) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(errno)); +- lua_pushnumber(L, errno); +- return 3; +- } +- /* set its type as client object */ +- auxiliar_setclass(L, "serial{client}", -1); +- /* initialize remaining structure fields */ +- socket_setnonblocking(&sock); +- un->sock = sock; +- io_init(&un->io, (p_send) socket_write, (p_recv) socket_read, +- (p_error) socket_ioerror, &un->sock); +- timeout_init(&un->tm, -1, -1); +- buffer_init(&un->buf, &un->io, &un->tm); +- return 1; +-} +diff --git a/unix.c b/unix.c +deleted file mode 100644 +index 268d8b2..0000000 +--- a/unix.c ++++ /dev/null +@@ -1,69 +0,0 @@ +-/*=========================================================================*\ +-* Unix domain socket +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "unixstream.h" +-#include "unixdgram.h" +- +-/*-------------------------------------------------------------------------*\ +-* Modules and functions +-\*-------------------------------------------------------------------------*/ +-static const luaL_Reg mod[] = { +- {"stream", unixstream_open}, +- {"dgram", unixdgram_open}, +- {NULL, NULL} +-}; +- +-static void add_alias(lua_State *L, int index, const char *name, const char *target) +-{ +- lua_getfield(L, index, target); +- lua_setfield(L, index, name); +-} +- +-static int compat_socket_unix_call(lua_State *L) +-{ +- /* Look up socket.unix.stream in the socket.unix table (which is the first +- * argument). */ +- lua_getfield(L, 1, "stream"); +- +- /* Replace the stack entry for the socket.unix table with the +- * socket.unix.stream function. */ +- lua_replace(L, 1); +- +- /* Call socket.unix.stream, passing along any arguments. */ +- int n = lua_gettop(L); +- lua_call(L, n-1, LUA_MULTRET); +- +- /* Pass along the return values from socket.unix.stream. */ +- n = lua_gettop(L); +- return n; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-LUASOCKET_API int luaopen_socket_unix(lua_State *L) +-{ +- int i; +- lua_newtable(L); +- int socket_unix_table = lua_gettop(L); +- +- for (i = 0; mod[i].name; i++) +- mod[i].func(L); +- +- /* Add backwards compatibility aliases "tcp" and "udp" for the "stream" and +- * "dgram" functions. */ +- add_alias(L, socket_unix_table, "tcp", "stream"); +- add_alias(L, socket_unix_table, "udp", "dgram"); +- +- /* Add a backwards compatibility function and a metatable setup to call it +- * for the old socket.unix() interface. */ +- lua_pushcfunction(L, compat_socket_unix_call); +- lua_setfield(L, socket_unix_table, "__call"); +- lua_pushvalue(L, socket_unix_table); +- lua_setmetatable(L, socket_unix_table); +- +- return 1; +-} +diff --git a/unix.h b/unix.h +deleted file mode 100644 +index c203561..0000000 +--- a/unix.h ++++ /dev/null +@@ -1,26 +0,0 @@ +-#ifndef UNIX_H +-#define UNIX_H +-/*=========================================================================*\ +-* Unix domain object +-* LuaSocket toolkit +-* +-* This module is just an example of how to extend LuaSocket with a new +-* domain. +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "buffer.h" +-#include "timeout.h" +-#include "socket.h" +- +-typedef struct t_unix_ { +- t_socket sock; +- t_io io; +- t_buffer buf; +- t_timeout tm; +-} t_unix; +-typedef t_unix *p_unix; +- +-LUASOCKET_API int luaopen_socket_unix(lua_State *L); +- +-#endif /* UNIX_H */ +diff --git a/unixdgram.c b/unixdgram.c +deleted file mode 100644 +index 69093d7..0000000 +--- a/unixdgram.c ++++ /dev/null +@@ -1,405 +0,0 @@ +-/*=========================================================================*\ +-* Unix domain socket dgram submodule +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "auxiliar.h" +-#include "socket.h" +-#include "options.h" +-#include "unix.h" +- +-#include +-#include +- +-#include +- +-#define UNIXDGRAM_DATAGRAMSIZE 8192 +- +-/* provide a SUN_LEN macro if sys/un.h doesn't (e.g. Android) */ +-#ifndef SUN_LEN +-#define SUN_LEN(ptr) \ +- ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ +- + strlen ((ptr)->sun_path)) +-#endif +- +-/*=========================================================================*\ +-* Internal function prototypes +-\*=========================================================================*/ +-static int global_create(lua_State *L); +-static int meth_connect(lua_State *L); +-static int meth_bind(lua_State *L); +-static int meth_send(lua_State *L); +-static int meth_receive(lua_State *L); +-static int meth_close(lua_State *L); +-static int meth_setoption(lua_State *L); +-static int meth_settimeout(lua_State *L); +-static int meth_gettimeout(lua_State *L); +-static int meth_getfd(lua_State *L); +-static int meth_setfd(lua_State *L); +-static int meth_dirty(lua_State *L); +-static int meth_receivefrom(lua_State *L); +-static int meth_sendto(lua_State *L); +-static int meth_getsockname(lua_State *L); +- +-static const char *unixdgram_tryconnect(p_unix un, const char *path); +-static const char *unixdgram_trybind(p_unix un, const char *path); +- +-/* unixdgram object methods */ +-static luaL_Reg unixdgram_methods[] = { +- {"__gc", meth_close}, +- {"__tostring", auxiliar_tostring}, +- {"bind", meth_bind}, +- {"close", meth_close}, +- {"connect", meth_connect}, +- {"dirty", meth_dirty}, +- {"getfd", meth_getfd}, +- {"send", meth_send}, +- {"sendto", meth_sendto}, +- {"receive", meth_receive}, +- {"receivefrom", meth_receivefrom}, +- {"setfd", meth_setfd}, +- {"setoption", meth_setoption}, +- {"setpeername", meth_connect}, +- {"setsockname", meth_bind}, +- {"getsockname", meth_getsockname}, +- {"settimeout", meth_settimeout}, +- {"gettimeout", meth_gettimeout}, +- {NULL, NULL} +-}; +- +-/* socket option handlers */ +-static t_opt optset[] = { +- {"reuseaddr", opt_set_reuseaddr}, +- {NULL, NULL} +-}; +- +-/* functions in library namespace */ +-static luaL_Reg func[] = { +- {"dgram", global_create}, +- {NULL, NULL} +-}; +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-int unixdgram_open(lua_State *L) +-{ +- /* create classes */ +- auxiliar_newclass(L, "unixdgram{connected}", unixdgram_methods); +- auxiliar_newclass(L, "unixdgram{unconnected}", unixdgram_methods); +- /* create class groups */ +- auxiliar_add2group(L, "unixdgram{connected}", "unixdgram{any}"); +- auxiliar_add2group(L, "unixdgram{unconnected}", "unixdgram{any}"); +- auxiliar_add2group(L, "unixdgram{connected}", "select{able}"); +- auxiliar_add2group(L, "unixdgram{unconnected}", "select{able}"); +- +- luaL_setfuncs(L, func, 0); +- return 0; +-} +- +-/*=========================================================================*\ +-* Lua methods +-\*=========================================================================*/ +-static const char *unixdgram_strerror(int err) +-{ +- /* a 'closed' error on an unconnected means the target address was not +- * accepted by the transport layer */ +- if (err == IO_CLOSED) return "refused"; +- else return socket_strerror(err); +-} +- +-static int meth_send(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{connected}", 1); +- p_timeout tm = &un->tm; +- size_t count, sent = 0; +- int err; +- const char *data = luaL_checklstring(L, 2, &count); +- timeout_markstart(tm); +- err = socket_send(&un->sock, data, count, &sent, tm); +- if (err != IO_DONE) { +- lua_pushnil(L); +- lua_pushstring(L, unixdgram_strerror(err)); +- return 2; +- } +- lua_pushnumber(L, (lua_Number) sent); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Send data through unconnected unixdgram socket +-\*-------------------------------------------------------------------------*/ +-static int meth_sendto(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); +- size_t count, sent = 0; +- const char *data = luaL_checklstring(L, 2, &count); +- const char *path = luaL_checkstring(L, 3); +- p_timeout tm = &un->tm; +- int err; +- struct sockaddr_un remote; +- size_t len = strlen(path); +- +- if (len >= sizeof(remote.sun_path)) { +- lua_pushnil(L); +- lua_pushstring(L, "path too long"); +- return 2; +- } +- +- memset(&remote, 0, sizeof(remote)); +- strcpy(remote.sun_path, path); +- remote.sun_family = AF_UNIX; +- timeout_markstart(tm); +-#ifdef UNIX_HAS_SUN_LEN +- remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) +- + len + 1; +- err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, remote.sun_len, tm); +-#else +- err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, +- sizeof(remote.sun_family) + len, tm); +-#endif +- if (err != IO_DONE) { +- lua_pushnil(L); +- lua_pushstring(L, unixdgram_strerror(err)); +- return 2; +- } +- lua_pushnumber(L, (lua_Number) sent); +- return 1; +-} +- +-static int meth_receive(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- char buf[UNIXDGRAM_DATAGRAMSIZE]; +- size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); +- char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; +- int err; +- p_timeout tm = &un->tm; +- timeout_markstart(tm); +- if (!dgram) { +- lua_pushnil(L); +- lua_pushliteral(L, "out of memory"); +- return 2; +- } +- err = socket_recv(&un->sock, dgram, wanted, &got, tm); +- /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ +- if (err != IO_DONE && err != IO_CLOSED) { +- lua_pushnil(L); +- lua_pushstring(L, unixdgram_strerror(err)); +- if (wanted > sizeof(buf)) free(dgram); +- return 2; +- } +- lua_pushlstring(L, dgram, got); +- if (wanted > sizeof(buf)) free(dgram); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Receives data and sender from a DGRAM socket +-\*-------------------------------------------------------------------------*/ +-static int meth_receivefrom(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); +- char buf[UNIXDGRAM_DATAGRAMSIZE]; +- size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); +- char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; +- struct sockaddr_un addr; +- socklen_t addr_len = sizeof(addr); +- int err; +- p_timeout tm = &un->tm; +- timeout_markstart(tm); +- if (!dgram) { +- lua_pushnil(L); +- lua_pushliteral(L, "out of memory"); +- return 2; +- } +- addr.sun_path[0] = '\0'; +- err = socket_recvfrom(&un->sock, dgram, wanted, &got, (SA *) &addr, +- &addr_len, tm); +- /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ +- if (err != IO_DONE && err != IO_CLOSED) { +- lua_pushnil(L); +- lua_pushstring(L, unixdgram_strerror(err)); +- if (wanted > sizeof(buf)) free(dgram); +- return 2; +- } +- +- lua_pushlstring(L, dgram, got); +- /* the path may be empty, when client send without bind */ +- lua_pushstring(L, addr.sun_path); +- if (wanted > sizeof(buf)) free(dgram); +- return 2; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call option handler +-\*-------------------------------------------------------------------------*/ +-static int meth_setoption(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- return opt_meth_setoption(L, optset, &un->sock); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select support methods +-\*-------------------------------------------------------------------------*/ +-static int meth_getfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- lua_pushnumber(L, (int) un->sock); +- return 1; +-} +- +-/* this is very dangerous, but can be handy for those that are brave enough */ +-static int meth_setfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- un->sock = (t_socket) luaL_checknumber(L, 2); +- return 0; +-} +- +-static int meth_dirty(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- (void) un; +- lua_pushboolean(L, 0); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Binds an object to an address +-\*-------------------------------------------------------------------------*/ +-static const char *unixdgram_trybind(p_unix un, const char *path) { +- struct sockaddr_un local; +- size_t len = strlen(path); +- if (len >= sizeof(local.sun_path)) return "path too long"; +- memset(&local, 0, sizeof(local)); +- strcpy(local.sun_path, path); +- local.sun_family = AF_UNIX; +- size_t addrlen = SUN_LEN(&local); +-#ifdef UNIX_HAS_SUN_LEN +- local.sun_len = addrlen + 1; +-#endif +- int err = socket_bind(&un->sock, (SA *) &local, addrlen); +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_bind(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixdgram_trybind(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- lua_pushnumber(L, 1); +- return 1; +-} +- +-static int meth_getsockname(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- struct sockaddr_un peer = {0}; +- socklen_t peer_len = sizeof(peer); +- +- if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(errno)); +- return 2; +- } +- +- lua_pushstring(L, peer.sun_path); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Turns a master unixdgram object into a client object. +-\*-------------------------------------------------------------------------*/ +-static const char *unixdgram_tryconnect(p_unix un, const char *path) +-{ +- struct sockaddr_un remote; +- size_t len = strlen(path); +- if (len >= sizeof(remote.sun_path)) return "path too long"; +- memset(&remote, 0, sizeof(remote)); +- strcpy(remote.sun_path, path); +- remote.sun_family = AF_UNIX; +- timeout_markstart(&un->tm); +- size_t addrlen = SUN_LEN(&remote); +-#ifdef UNIX_HAS_SUN_LEN +- remote.sun_len = addrlen + 1; +-#endif +- int err = socket_connect(&un->sock, (SA *) &remote, addrlen, &un->tm); +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_connect(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixdgram_tryconnect(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- /* turn unconnected object into a connected object */ +- auxiliar_setclass(L, "unixdgram{connected}", 1); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Closes socket used by object +-\*-------------------------------------------------------------------------*/ +-static int meth_close(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- socket_destroy(&un->sock); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call tm methods +-\*-------------------------------------------------------------------------*/ +-static int meth_settimeout(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- return timeout_meth_settimeout(L, &un->tm); +-} +- +-static int meth_gettimeout(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); +- return timeout_meth_gettimeout(L, &un->tm); +-} +- +-/*=========================================================================*\ +-* Library functions +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Creates a master unixdgram object +-\*-------------------------------------------------------------------------*/ +-static int global_create(lua_State *L) +-{ +- t_socket sock; +- int err = socket_create(&sock, AF_UNIX, SOCK_DGRAM, 0); +- /* try to allocate a system socket */ +- if (err == IO_DONE) { +- /* allocate unixdgram object */ +- p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- /* set its type as master object */ +- auxiliar_setclass(L, "unixdgram{unconnected}", -1); +- /* initialize remaining structure fields */ +- socket_setnonblocking(&sock); +- un->sock = sock; +- io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, +- (p_error) socket_ioerror, &un->sock); +- timeout_init(&un->tm, -1, -1); +- buffer_init(&un->buf, &un->io, &un->tm); +- return 1; +- } else { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +-} +diff --git a/unixdgram.h b/unixdgram.h +deleted file mode 100644 +index a1a0166..0000000 +--- a/unixdgram.h ++++ /dev/null +@@ -1,28 +0,0 @@ +-#ifndef UNIXDGRAM_H +-#define UNIXDGRAM_H +-/*=========================================================================*\ +-* DGRAM object +-* LuaSocket toolkit +-* +-* The dgram.h module provides LuaSocket with support for DGRAM protocol +-* (AF_INET, SOCK_DGRAM). +-* +-* Two classes are defined: connected and unconnected. DGRAM objects are +-* originally unconnected. They can be "connected" to a given address +-* with a call to the setpeername function. The same function can be used to +-* break the connection. +-\*=========================================================================*/ +- +-#include "unix.h" +- +-#ifndef _WIN32 +-#pragma GCC visibility push(hidden) +-#endif +- +-int unixdgram_open(lua_State *L); +- +-#ifndef _WIN32 +-#pragma GCC visibility pop +-#endif +- +-#endif /* UNIXDGRAM_H */ +diff --git a/unixstream.c b/unixstream.c +deleted file mode 100644 +index 02aced9..0000000 +--- a/unixstream.c ++++ /dev/null +@@ -1,355 +0,0 @@ +-/*=========================================================================*\ +-* Unix domain socket stream sub module +-* LuaSocket toolkit +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include "auxiliar.h" +-#include "socket.h" +-#include "options.h" +-#include "unixstream.h" +- +-#include +-#include +- +-/*=========================================================================*\ +-* Internal function prototypes +-\*=========================================================================*/ +-static int global_create(lua_State *L); +-static int meth_connect(lua_State *L); +-static int meth_listen(lua_State *L); +-static int meth_bind(lua_State *L); +-static int meth_send(lua_State *L); +-static int meth_shutdown(lua_State *L); +-static int meth_receive(lua_State *L); +-static int meth_accept(lua_State *L); +-static int meth_close(lua_State *L); +-static int meth_setoption(lua_State *L); +-static int meth_settimeout(lua_State *L); +-static int meth_getfd(lua_State *L); +-static int meth_setfd(lua_State *L); +-static int meth_dirty(lua_State *L); +-static int meth_getstats(lua_State *L); +-static int meth_setstats(lua_State *L); +-static int meth_getsockname(lua_State *L); +- +-static const char *unixstream_tryconnect(p_unix un, const char *path); +-static const char *unixstream_trybind(p_unix un, const char *path); +- +-/* unixstream object methods */ +-static luaL_Reg unixstream_methods[] = { +- {"__gc", meth_close}, +- {"__tostring", auxiliar_tostring}, +- {"accept", meth_accept}, +- {"bind", meth_bind}, +- {"close", meth_close}, +- {"connect", meth_connect}, +- {"dirty", meth_dirty}, +- {"getfd", meth_getfd}, +- {"getstats", meth_getstats}, +- {"setstats", meth_setstats}, +- {"listen", meth_listen}, +- {"receive", meth_receive}, +- {"send", meth_send}, +- {"setfd", meth_setfd}, +- {"setoption", meth_setoption}, +- {"setpeername", meth_connect}, +- {"setsockname", meth_bind}, +- {"getsockname", meth_getsockname}, +- {"settimeout", meth_settimeout}, +- {"shutdown", meth_shutdown}, +- {NULL, NULL} +-}; +- +-/* socket option handlers */ +-static t_opt optset[] = { +- {"keepalive", opt_set_keepalive}, +- {"reuseaddr", opt_set_reuseaddr}, +- {"linger", opt_set_linger}, +- {NULL, NULL} +-}; +- +-/* functions in library namespace */ +-static luaL_Reg func[] = { +- {"stream", global_create}, +- {NULL, NULL} +-}; +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-int unixstream_open(lua_State *L) +-{ +- /* create classes */ +- auxiliar_newclass(L, "unixstream{master}", unixstream_methods); +- auxiliar_newclass(L, "unixstream{client}", unixstream_methods); +- auxiliar_newclass(L, "unixstream{server}", unixstream_methods); +- +- /* create class groups */ +- auxiliar_add2group(L, "unixstream{master}", "unixstream{any}"); +- auxiliar_add2group(L, "unixstream{client}", "unixstream{any}"); +- auxiliar_add2group(L, "unixstream{server}", "unixstream{any}"); +- +- luaL_setfuncs(L, func, 0); +- return 0; +-} +- +-/*=========================================================================*\ +-* Lua methods +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Just call buffered IO methods +-\*-------------------------------------------------------------------------*/ +-static int meth_send(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_send(L, &un->buf); +-} +- +-static int meth_receive(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_receive(L, &un->buf); +-} +- +-static int meth_getstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_getstats(L, &un->buf); +-} +- +-static int meth_setstats(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- return buffer_meth_setstats(L, &un->buf); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call option handler +-\*-------------------------------------------------------------------------*/ +-static int meth_setoption(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- return opt_meth_setoption(L, optset, &un->sock); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select support methods +-\*-------------------------------------------------------------------------*/ +-static int meth_getfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- lua_pushnumber(L, (int) un->sock); +- return 1; +-} +- +-/* this is very dangerous, but can be handy for those that are brave enough */ +-static int meth_setfd(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- un->sock = (t_socket) luaL_checknumber(L, 2); +- return 0; +-} +- +-static int meth_dirty(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- lua_pushboolean(L, !buffer_isempty(&un->buf)); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Waits for and returns a client object attempting connection to the +-* server object +-\*-------------------------------------------------------------------------*/ +-static int meth_accept(lua_State *L) { +- p_unix server = (p_unix) auxiliar_checkclass(L, "unixstream{server}", 1); +- p_timeout tm = timeout_markstart(&server->tm); +- t_socket sock; +- int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); +- /* if successful, push client socket */ +- if (err == IO_DONE) { +- p_unix clnt = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- auxiliar_setclass(L, "unixstream{client}", -1); +- /* initialize structure fields */ +- socket_setnonblocking(&sock); +- clnt->sock = sock; +- io_init(&clnt->io, (p_send)socket_send, (p_recv)socket_recv, +- (p_error) socket_ioerror, &clnt->sock); +- timeout_init(&clnt->tm, -1, -1); +- buffer_init(&clnt->buf, &clnt->io, &clnt->tm); +- return 1; +- } else { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Binds an object to an address +-\*-------------------------------------------------------------------------*/ +-static const char *unixstream_trybind(p_unix un, const char *path) { +- struct sockaddr_un local; +- size_t len = strlen(path); +- int err; +- if (len >= sizeof(local.sun_path)) return "path too long"; +- memset(&local, 0, sizeof(local)); +- strcpy(local.sun_path, path); +- local.sun_family = AF_UNIX; +-#ifdef UNIX_HAS_SUN_LEN +- local.sun_len = sizeof(local.sun_family) + sizeof(local.sun_len) +- + len + 1; +- err = socket_bind(&un->sock, (SA *) &local, local.sun_len); +- +-#else +- err = socket_bind(&un->sock, (SA *) &local, +- sizeof(local.sun_family) + len); +-#endif +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_bind(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixstream_trybind(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- lua_pushnumber(L, 1); +- return 1; +-} +- +-static int meth_getsockname(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- struct sockaddr_un peer = {0}; +- socklen_t peer_len = sizeof(peer); +- +- if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(errno)); +- return 2; +- } +- +- lua_pushstring(L, peer.sun_path); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Turns a master unixstream object into a client object. +-\*-------------------------------------------------------------------------*/ +-static const char *unixstream_tryconnect(p_unix un, const char *path) +-{ +- struct sockaddr_un remote; +- int err; +- size_t len = strlen(path); +- if (len >= sizeof(remote.sun_path)) return "path too long"; +- memset(&remote, 0, sizeof(remote)); +- strcpy(remote.sun_path, path); +- remote.sun_family = AF_UNIX; +- timeout_markstart(&un->tm); +-#ifdef UNIX_HAS_SUN_LEN +- remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) +- + len + 1; +- err = socket_connect(&un->sock, (SA *) &remote, remote.sun_len, &un->tm); +-#else +- err = socket_connect(&un->sock, (SA *) &remote, +- sizeof(remote.sun_family) + len, &un->tm); +-#endif +- if (err != IO_DONE) socket_destroy(&un->sock); +- return socket_strerror(err); +-} +- +-static int meth_connect(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- const char *path = luaL_checkstring(L, 2); +- const char *err = unixstream_tryconnect(un, path); +- if (err) { +- lua_pushnil(L); +- lua_pushstring(L, err); +- return 2; +- } +- /* turn master object into a client object */ +- auxiliar_setclass(L, "unixstream{client}", 1); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Closes socket used by object +-\*-------------------------------------------------------------------------*/ +-static int meth_close(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- socket_destroy(&un->sock); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Puts the sockt in listen mode +-\*-------------------------------------------------------------------------*/ +-static int meth_listen(lua_State *L) +-{ +- p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); +- int backlog = (int) luaL_optnumber(L, 2, 32); +- int err = socket_listen(&un->sock, backlog); +- if (err != IO_DONE) { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +- /* turn master object into a server object */ +- auxiliar_setclass(L, "unixstream{server}", 1); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Shuts the connection down partially +-\*-------------------------------------------------------------------------*/ +-static int meth_shutdown(lua_State *L) +-{ +- /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ +- static const char* methods[] = { "receive", "send", "both", NULL }; +- p_unix stream = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); +- int how = luaL_checkoption(L, 2, "both", methods); +- socket_shutdown(&stream->sock, how); +- lua_pushnumber(L, 1); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Just call tm methods +-\*-------------------------------------------------------------------------*/ +-static int meth_settimeout(lua_State *L) { +- p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); +- return timeout_meth_settimeout(L, &un->tm); +-} +- +-/*=========================================================================*\ +-* Library functions +-\*=========================================================================*/ +-/*-------------------------------------------------------------------------*\ +-* Creates a master unixstream object +-\*-------------------------------------------------------------------------*/ +-static int global_create(lua_State *L) { +- t_socket sock; +- int err = socket_create(&sock, AF_UNIX, SOCK_STREAM, 0); +- /* try to allocate a system socket */ +- if (err == IO_DONE) { +- /* allocate unixstream object */ +- p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); +- /* set its type as master object */ +- auxiliar_setclass(L, "unixstream{master}", -1); +- /* initialize remaining structure fields */ +- socket_setnonblocking(&sock); +- un->sock = sock; +- io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, +- (p_error) socket_ioerror, &un->sock); +- timeout_init(&un->tm, -1, -1); +- buffer_init(&un->buf, &un->io, &un->tm); +- return 1; +- } else { +- lua_pushnil(L); +- lua_pushstring(L, socket_strerror(err)); +- return 2; +- } +-} +diff --git a/unixstream.h b/unixstream.h +deleted file mode 100644 +index 7916aff..0000000 +--- a/unixstream.h ++++ /dev/null +@@ -1,29 +0,0 @@ +-#ifndef UNIXSTREAM_H +-#define UNIXSTREAM_H +-/*=========================================================================*\ +-* UNIX STREAM object +-* LuaSocket toolkit +-* +-* The unixstream.h module is basicly a glue that puts together modules buffer.h, +-* timeout.h socket.h and inet.h to provide the LuaSocket UNIX STREAM (AF_UNIX, +-* SOCK_STREAM) support. +-* +-* Three classes are defined: master, client and server. The master class is +-* a newly created unixstream object, that has not been bound or connected. Server +-* objects are unixstream objects bound to some local address. Client objects are +-* unixstream objects either connected to some address or returned by the accept +-* method of a server object. +-\*=========================================================================*/ +-#include "unix.h" +- +-#ifndef _WIN32 +-#pragma GCC visibility push(hidden) +-#endif +- +-int unixstream_open(lua_State *L); +- +-#ifndef _WIN32 +-#pragma GCC visibility pop +-#endif +- +-#endif /* UNIXSTREAM_H */ +diff --git a/wsocket.c b/wsocket.c +deleted file mode 100755 +index 6cb1e41..0000000 +--- a/wsocket.c ++++ /dev/null +@@ -1,434 +0,0 @@ +-/*=========================================================================*\ +-* Socket compatibilization module for Win32 +-* LuaSocket toolkit +-* +-* The penalty of calling select to avoid busy-wait is only paid when +-* the I/O call fail in the first place. +-\*=========================================================================*/ +-#include "luasocket.h" +- +-#include +- +-#include "socket.h" +-#include "pierror.h" +- +-/* WinSock doesn't have a strerror... */ +-static const char *wstrerror(int err); +- +-/*-------------------------------------------------------------------------*\ +-* Initializes module +-\*-------------------------------------------------------------------------*/ +-int socket_open(void) { +- WSADATA wsaData; +- WORD wVersionRequested = MAKEWORD(2, 0); +- int err = WSAStartup(wVersionRequested, &wsaData ); +- if (err != 0) return 0; +- if ((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) && +- (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)) { +- WSACleanup(); +- return 0; +- } +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Close module +-\*-------------------------------------------------------------------------*/ +-int socket_close(void) { +- WSACleanup(); +- return 1; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Wait for readable/writable/connected socket with timeout +-\*-------------------------------------------------------------------------*/ +-#define WAITFD_R 1 +-#define WAITFD_W 2 +-#define WAITFD_E 4 +-#define WAITFD_C (WAITFD_E|WAITFD_W) +- +-int socket_waitfd(p_socket ps, int sw, p_timeout tm) { +- int ret; +- fd_set rfds, wfds, efds, *rp = NULL, *wp = NULL, *ep = NULL; +- struct timeval tv, *tp = NULL; +- double t; +- if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ +- if (sw & WAITFD_R) { +- FD_ZERO(&rfds); +- FD_SET(*ps, &rfds); +- rp = &rfds; +- } +- if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } +- if (sw & WAITFD_C) { FD_ZERO(&efds); FD_SET(*ps, &efds); ep = &efds; } +- if ((t = timeout_get(tm)) >= 0.0) { +- tv.tv_sec = (int) t; +- tv.tv_usec = (int) ((t-tv.tv_sec)*1.0e6); +- tp = &tv; +- } +- ret = select(0, rp, wp, ep, tp); +- if (ret == -1) return WSAGetLastError(); +- if (ret == 0) return IO_TIMEOUT; +- if (sw == WAITFD_C && FD_ISSET(*ps, &efds)) return IO_CLOSED; +- return IO_DONE; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Select with int timeout in ms +-\*-------------------------------------------------------------------------*/ +-int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, +- p_timeout tm) { +- struct timeval tv; +- double t = timeout_get(tm); +- tv.tv_sec = (int) t; +- tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); +- if (n <= 0) { +- Sleep((DWORD) (1000*t)); +- return 0; +- } else return select(0, rfds, wfds, efds, t >= 0.0? &tv: NULL); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Close and inutilize socket +-\*-------------------------------------------------------------------------*/ +-void socket_destroy(p_socket ps) { +- if (*ps != SOCKET_INVALID) { +- socket_setblocking(ps); /* close can take a long time on WIN32 */ +- closesocket(*ps); +- *ps = SOCKET_INVALID; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* +-\*-------------------------------------------------------------------------*/ +-void socket_shutdown(p_socket ps, int how) { +- socket_setblocking(ps); +- shutdown(*ps, how); +- socket_setnonblocking(ps); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Creates and sets up a socket +-\*-------------------------------------------------------------------------*/ +-int socket_create(p_socket ps, int domain, int type, int protocol) { +- *ps = socket(domain, type, protocol); +- if (*ps != SOCKET_INVALID) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Connects or returns error message +-\*-------------------------------------------------------------------------*/ +-int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { +- int err; +- /* don't call on closed socket */ +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- /* ask system to connect */ +- if (connect(*ps, addr, len) == 0) return IO_DONE; +- /* make sure the system is trying to connect */ +- err = WSAGetLastError(); +- if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return err; +- /* zero timeout case optimization */ +- if (timeout_iszero(tm)) return IO_TIMEOUT; +- /* we wait until something happens */ +- err = socket_waitfd(ps, WAITFD_C, tm); +- if (err == IO_CLOSED) { +- int elen = sizeof(err); +- /* give windows time to set the error (yes, disgusting) */ +- Sleep(10); +- /* find out why we failed */ +- getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &elen); +- /* we KNOW there was an error. if 'why' is 0, we will return +- * "unknown error", but it's not really our fault */ +- return err > 0? err: IO_UNKNOWN; +- } else return err; +- +-} +- +-/*-------------------------------------------------------------------------*\ +-* Binds or returns error message +-\*-------------------------------------------------------------------------*/ +-int socket_bind(p_socket ps, SA *addr, socklen_t len) { +- int err = IO_DONE; +- socket_setblocking(ps); +- if (bind(*ps, addr, len) < 0) err = WSAGetLastError(); +- socket_setnonblocking(ps); +- return err; +-} +- +-/*-------------------------------------------------------------------------*\ +-* +-\*-------------------------------------------------------------------------*/ +-int socket_listen(p_socket ps, int backlog) { +- int err = IO_DONE; +- socket_setblocking(ps); +- if (listen(*ps, backlog) < 0) err = WSAGetLastError(); +- socket_setnonblocking(ps); +- return err; +-} +- +-/*-------------------------------------------------------------------------*\ +-* Accept with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, +- p_timeout tm) { +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int err; +- /* try to get client socket */ +- if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; +- /* find out why we failed */ +- err = WSAGetLastError(); +- /* if we failed because there was no connectoin, keep trying */ +- if (err != WSAEWOULDBLOCK && err != WSAECONNABORTED) return err; +- /* call select to avoid busy wait */ +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Send with timeout +-* On windows, if you try to send 10MB, the OS will buffer EVERYTHING +-* this can take an awful lot of time and we will end up blocked. +-* Therefore, whoever calls this function should not pass a huge buffer. +-\*-------------------------------------------------------------------------*/ +-int socket_send(p_socket ps, const char *data, size_t count, +- size_t *sent, p_timeout tm) +-{ +- int err; +- *sent = 0; +- /* avoid making system calls on closed sockets */ +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- /* loop until we send something or we give up on error */ +- for ( ;; ) { +- /* try to send something */ +- int put = send(*ps, data, (int) count, 0); +- /* if we sent something, we are done */ +- if (put > 0) { +- *sent = put; +- return IO_DONE; +- } +- /* deal with failure */ +- err = WSAGetLastError(); +- /* we can only proceed if there was no serious error */ +- if (err != WSAEWOULDBLOCK) return err; +- /* avoid busy wait */ +- if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Sendto with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, +- SA *addr, socklen_t len, p_timeout tm) +-{ +- int err; +- *sent = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int put = sendto(*ps, data, (int) count, 0, addr, len); +- if (put > 0) { +- *sent = put; +- return IO_DONE; +- } +- err = WSAGetLastError(); +- if (err != WSAEWOULDBLOCK) return err; +- if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Receive with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_recv(p_socket ps, char *data, size_t count, size_t *got, +- p_timeout tm) +-{ +- int err, prev = IO_DONE; +- *got = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int taken = recv(*ps, data, (int) count, 0); +- if (taken > 0) { +- *got = taken; +- return IO_DONE; +- } +- if (taken == 0) return IO_CLOSED; +- err = WSAGetLastError(); +- /* On UDP, a connreset simply means the previous send failed. +- * So we try again. +- * On TCP, it means our socket is now useless, so the error passes. +- * (We will loop again, exiting because the same error will happen) */ +- if (err != WSAEWOULDBLOCK) { +- if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; +- prev = err; +- } +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Recvfrom with timeout +-\*-------------------------------------------------------------------------*/ +-int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, +- SA *addr, socklen_t *len, p_timeout tm) +-{ +- int err, prev = IO_DONE; +- *got = 0; +- if (*ps == SOCKET_INVALID) return IO_CLOSED; +- for ( ;; ) { +- int taken = recvfrom(*ps, data, (int) count, 0, addr, len); +- if (taken > 0) { +- *got = taken; +- return IO_DONE; +- } +- if (taken == 0) return IO_CLOSED; +- err = WSAGetLastError(); +- /* On UDP, a connreset simply means the previous send failed. +- * So we try again. +- * On TCP, it means our socket is now useless, so the error passes. +- * (We will loop again, exiting because the same error will happen) */ +- if (err != WSAEWOULDBLOCK) { +- if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; +- prev = err; +- } +- if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; +- } +-} +- +-/*-------------------------------------------------------------------------*\ +-* Put socket into blocking mode +-\*-------------------------------------------------------------------------*/ +-void socket_setblocking(p_socket ps) { +- u_long argp = 0; +- ioctlsocket(*ps, FIONBIO, &argp); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Put socket into non-blocking mode +-\*-------------------------------------------------------------------------*/ +-void socket_setnonblocking(p_socket ps) { +- u_long argp = 1; +- ioctlsocket(*ps, FIONBIO, &argp); +-} +- +-/*-------------------------------------------------------------------------*\ +-* DNS helpers +-\*-------------------------------------------------------------------------*/ +-int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { +- *hp = gethostbyaddr(addr, len, AF_INET); +- if (*hp) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-int socket_gethostbyname(const char *addr, struct hostent **hp) { +- *hp = gethostbyname(addr); +- if (*hp) return IO_DONE; +- else return WSAGetLastError(); +-} +- +-/*-------------------------------------------------------------------------*\ +-* Error translation functions +-\*-------------------------------------------------------------------------*/ +-const char *socket_hoststrerror(int err) { +- if (err <= 0) return io_strerror(err); +- switch (err) { +- case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; +- default: return wstrerror(err); +- } +-} +- +-const char *socket_strerror(int err) { +- if (err <= 0) return io_strerror(err); +- switch (err) { +- case WSAEADDRINUSE: return PIE_ADDRINUSE; +- case WSAECONNREFUSED : return PIE_CONNREFUSED; +- case WSAEISCONN: return PIE_ISCONN; +- case WSAEACCES: return PIE_ACCESS; +- case WSAECONNABORTED: return PIE_CONNABORTED; +- case WSAECONNRESET: return PIE_CONNRESET; +- case WSAETIMEDOUT: return PIE_TIMEDOUT; +- default: return wstrerror(err); +- } +-} +- +-const char *socket_ioerror(p_socket ps, int err) { +- (void) ps; +- return socket_strerror(err); +-} +- +-static const char *wstrerror(int err) { +- switch (err) { +- case WSAEINTR: return "Interrupted function call"; +- case WSAEACCES: return PIE_ACCESS; /* "Permission denied"; */ +- case WSAEFAULT: return "Bad address"; +- case WSAEINVAL: return "Invalid argument"; +- case WSAEMFILE: return "Too many open files"; +- case WSAEWOULDBLOCK: return "Resource temporarily unavailable"; +- case WSAEINPROGRESS: return "Operation now in progress"; +- case WSAEALREADY: return "Operation already in progress"; +- case WSAENOTSOCK: return "Socket operation on nonsocket"; +- case WSAEDESTADDRREQ: return "Destination address required"; +- case WSAEMSGSIZE: return "Message too long"; +- case WSAEPROTOTYPE: return "Protocol wrong type for socket"; +- case WSAENOPROTOOPT: return "Bad protocol option"; +- case WSAEPROTONOSUPPORT: return "Protocol not supported"; +- case WSAESOCKTNOSUPPORT: return PIE_SOCKTYPE; /* "Socket type not supported"; */ +- case WSAEOPNOTSUPP: return "Operation not supported"; +- case WSAEPFNOSUPPORT: return "Protocol family not supported"; +- case WSAEAFNOSUPPORT: return PIE_FAMILY; /* "Address family not supported by protocol family"; */ +- case WSAEADDRINUSE: return PIE_ADDRINUSE; /* "Address already in use"; */ +- case WSAEADDRNOTAVAIL: return "Cannot assign requested address"; +- case WSAENETDOWN: return "Network is down"; +- case WSAENETUNREACH: return "Network is unreachable"; +- case WSAENETRESET: return "Network dropped connection on reset"; +- case WSAECONNABORTED: return "Software caused connection abort"; +- case WSAECONNRESET: return PIE_CONNRESET; /* "Connection reset by peer"; */ +- case WSAENOBUFS: return "No buffer space available"; +- case WSAEISCONN: return PIE_ISCONN; /* "Socket is already connected"; */ +- case WSAENOTCONN: return "Socket is not connected"; +- case WSAESHUTDOWN: return "Cannot send after socket shutdown"; +- case WSAETIMEDOUT: return PIE_TIMEDOUT; /* "Connection timed out"; */ +- case WSAECONNREFUSED: return PIE_CONNREFUSED; /* "Connection refused"; */ +- case WSAEHOSTDOWN: return "Host is down"; +- case WSAEHOSTUNREACH: return "No route to host"; +- case WSAEPROCLIM: return "Too many processes"; +- case WSASYSNOTREADY: return "Network subsystem is unavailable"; +- case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range"; +- case WSANOTINITIALISED: +- return "Successful WSAStartup not yet performed"; +- case WSAEDISCON: return "Graceful shutdown in progress"; +- case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; /* "Host not found"; */ +- case WSATRY_AGAIN: return "Nonauthoritative host not found"; +- case WSANO_RECOVERY: return PIE_FAIL; /* "Nonrecoverable name lookup error"; */ +- case WSANO_DATA: return "Valid name, no data record of requested type"; +- default: return "Unknown error"; +- } +-} +- +-const char *socket_gaistrerror(int err) { +- if (err == 0) return NULL; +- switch (err) { +- case EAI_AGAIN: return PIE_AGAIN; +- case EAI_BADFLAGS: return PIE_BADFLAGS; +-#ifdef EAI_BADHINTS +- case EAI_BADHINTS: return PIE_BADHINTS; +-#endif +- case EAI_FAIL: return PIE_FAIL; +- case EAI_FAMILY: return PIE_FAMILY; +- case EAI_MEMORY: return PIE_MEMORY; +- case EAI_NONAME: return PIE_NONAME; +-#ifdef EAI_OVERFLOW +- case EAI_OVERFLOW: return PIE_OVERFLOW; +-#endif +-#ifdef EAI_PROTOCOL +- case EAI_PROTOCOL: return PIE_PROTOCOL; +-#endif +- case EAI_SERVICE: return PIE_SERVICE; +- case EAI_SOCKTYPE: return PIE_SOCKTYPE; +-#ifdef EAI_SYSTEM +- case EAI_SYSTEM: return strerror(errno); +-#endif +- default: return LUA_GAI_STRERROR(err); +- } +-} +diff --git a/wsocket.h b/wsocket.h +deleted file mode 100644 +index 3986640..0000000 +--- a/wsocket.h ++++ /dev/null +@@ -1,33 +0,0 @@ +-#ifndef WSOCKET_H +-#define WSOCKET_H +-/*=========================================================================*\ +-* Socket compatibilization module for Win32 +-* LuaSocket toolkit +-\*=========================================================================*/ +- +-/*=========================================================================*\ +-* WinSock include files +-\*=========================================================================*/ +-#include +-#include +- +-typedef int socklen_t; +-typedef SOCKADDR_STORAGE t_sockaddr_storage; +-typedef SOCKET t_socket; +-typedef t_socket *p_socket; +- +-#ifndef IPV6_V6ONLY +-#define IPV6_V6ONLY 27 +-#endif +- +-#define SOCKET_INVALID (INVALID_SOCKET) +- +-#ifndef SO_REUSEPORT +-#define SO_REUSEPORT SO_REUSEADDR +-#endif +- +-#ifndef AI_NUMERICSERV +-#define AI_NUMERICSERV (0) +-#endif +- +-#endif /* WSOCKET_H */ diff --git a/platform/hac/romfs/shaders/transform_vsh.dksh b/platform/hac/romfs/shaders/transform_vsh.dksh index 394efc117..7e5ba6048 100644 Binary files a/platform/hac/romfs/shaders/transform_vsh.dksh and b/platform/hac/romfs/shaders/transform_vsh.dksh differ diff --git a/platform/hac/shaders/transform_vsh.glsl b/platform/hac/shaders/transform_vsh.glsl index 360c720eb..da5bb586d 100644 --- a/platform/hac/shaders/transform_vsh.glsl +++ b/platform/hac/shaders/transform_vsh.glsl @@ -1,6 +1,6 @@ #version 460 -layout (location = 0) in vec3 inPos; +layout (location = 0) in vec2 inPos; layout (location = 1) in vec4 inColor; layout (location = 2) in vec2 inTexCoord; @@ -15,7 +15,7 @@ layout (std140, binding = 0) uniform Transformation void main() { - vec4 pos = u.mdlvMtx * vec4(inPos, 1.0); + vec4 pos = u.mdlvMtx * vec4(inPos, 0.0, 1.0); gl_Position = u.projMtx * pos; outColor = inColor; diff --git a/platform/hac/source/boot.cpp b/platform/hac/source/boot.cpp index 066be8498..7f60ef81d 100644 --- a/platform/hac/source/boot.cpp +++ b/platform/hac/source/boot.cpp @@ -15,14 +15,14 @@ namespace love // clang-format off static constexpr std::array services = {{ - { "bsd", BIND(socketInitializeDefault), &socketExit }, - { "pl:u", BIND(plInitialize, PlServiceType_User), &plExit }, - { "acc:a", BIND(accountInitialize, AccountServiceType_Application), &accountExit }, - { "set", BIND(setInitialize), &setExit }, - { "set:sys", BIND(setsysInitialize), &setsysExit }, - { "psm", BIND(psmInitialize), &psmExit }, - { "friend:u", BIND(friendsInitialize, FriendsServiceType_User), &friendsExit }, - { "nifm:u", BIND(nifmInitialize, NifmServiceType_User), &nifmExit } + { "bsd", BIND(socketInitializeDefault), &socketExit }, + { "pl:u", BIND(plInitialize, PlServiceType_User), &plExit }, + { "acc:a", BIND(accountInitialize, AccountServiceType_Application), &accountExit }, + { "set", BIND(setInitialize), &setExit }, + { "set:sys", BIND(setsysInitialize), &setsysExit }, + { "psm", BIND(psmInitialize), &psmExit }, + { "friend:u", BIND(friendsInitialize, FriendsServiceType_User), &friendsExit }, + { "nifm:u", BIND(nifmInitialize, NifmServiceType_User), &nifmExit } }}; // clang-format on @@ -65,6 +65,7 @@ namespace love } padConfigureInput(8, HidNpadStyleSet_NpadStandard); + hidInitializeTouchScreen(); romfsInit(); diff --git a/platform/hac/source/driver/EventQueue.cpp b/platform/hac/source/driver/EventQueue.cpp index 246c7b28a..d7f375a82 100644 --- a/platform/hac/source/driver/EventQueue.cpp +++ b/platform/hac/source/driver/EventQueue.cpp @@ -1,3 +1,5 @@ +#include "common/screen.hpp" + #include "driver/EventQueue.hpp" #include "modules/joystick/JoystickModule.hpp" @@ -8,17 +10,17 @@ namespace love { EventQueue::EventQueue() : EventQueueBase() { - for (int index = 0; index < 0x08; index++) - { - auto player = HidNpadIdType(HidNpadIdType_No1 + index); - hidAcquireNpadStyleSetUpdateEventHandle(player, &this->padStyleUpdates[index], true); - } + // for (int index = 0; index < 0x08; index++) + // { + // auto player = HidNpadIdType(HidNpadIdType_No1 + index); + // hidAcquireNpadStyleSetUpdateEventHandle(player, &this->padStyleUpdates[index], true); + // } } EventQueue::~EventQueue() { - for (int index = 0; index < 0x08; index++) - eventClose(&this->padStyleUpdates[index]); + // for (int index = 0; index < 0x08; index++) + // eventClose(&this->padStyleUpdates[index]); } static void checkFocus() @@ -60,7 +62,13 @@ namespace love } case AppletMessage_OperationModeChanged: { + auto info = love::getScreenInfo(Screen(0)); + EventQueue::getInstance().sendResize(info.width, info.height); + + break; } + default: + break; } } } @@ -74,16 +82,16 @@ namespace love int current = JOYSTICK_MODULE()->getJoystickCount(); - for (int index = 0; index < 0x08; index++) - { - if (R_SUCCEEDED(eventWait(&this->padStyleUpdates[index], 0))) - { - int newCount = JOYSTICK_MODULE()->getJoystickCount(); + // for (int index = 0; index < 0x08; index++) + // { + // if (R_SUCCEEDED(eventWait(&this->padStyleUpdates[index], 0))) + // { + // int newCount = JOYSTICK_MODULE()->getJoystickCount(); - if (newCount != current) - this->sendJoystickStatus(newCount > current, index); - } - } + // if (newCount != current) + // this->sendJoystickStatus(newCount > current, index); + // } + // } for (int index = 0; index < current; index++) { @@ -92,25 +100,26 @@ namespace love if (joystick == nullptr) continue; + const auto id = joystick->getID(); joystick->update(); - for (int input = 0; input < Joystick::GAMEPAD_BUTTON_MAX_ENUM; input++) + for (int input = 0; input < JoystickBase::GAMEPAD_BUTTON_MAX_ENUM; input++) { - std::vector inputs = { Joystick::GamepadButton(input) }; + std::vector inputs = { JoystickBase::GamepadButton(input) }; if (joystick->isDown(inputs)) - this->sendGamepadButtonEvent(SUBTYPE_GAMEPADDOWN, 0, input); + this->sendGamepadButtonEvent(SUBTYPE_GAMEPADDOWN, id, input); if (joystick->isUp(inputs)) - this->sendGamepadButtonEvent(SUBTYPE_GAMEPADUP, 0, input); + this->sendGamepadButtonEvent(SUBTYPE_GAMEPADUP, id, input); } - for (int input = 0; input < Joystick::GAMEPAD_AXIS_MAX_ENUM; input++) + for (int input = 0; input < JoystickBase::GAMEPAD_AXIS_MAX_ENUM; input++) { - if (joystick->isAxisChanged(Joystick::GamepadAxis(input))) + if (joystick->isAxisChanged(JoystickBase::GamepadAxis(input))) { - float value = joystick->getAxis(Joystick::GamepadAxis(input)); - this->sendGamepadAxisEvent(0, input, value); + float value = joystick->getAxis(JoystickBase::GamepadAxis(input)); + this->sendGamepadAxisEvent(id, input, value); } } @@ -120,7 +129,7 @@ namespace love continue; auto data = joystick->getSensorData(Sensor::SensorType(input)); - this->sendGamepadSensorEvent(0, input, data); + this->sendGamepadSensorEvent(id, input, data); } } } diff --git a/platform/hac/source/driver/display/Framebuffer.cpp b/platform/hac/source/driver/display/Framebuffer.cpp index 452b43d8a..97ba99286 100644 --- a/platform/hac/source/driver/display/Framebuffer.cpp +++ b/platform/hac/source/driver/display/Framebuffer.cpp @@ -8,8 +8,7 @@ namespace love Framebuffer::~Framebuffer() {} - void Framebuffer::create(const ScreenInfo& info, dk::Device& device, CMemPool& images, - bool depth) + void Framebuffer::create(const ScreenInfo& info, dk::Device& device, CMemPool& images, bool depth) { const auto flags = depth ? BASE_FLAGS : MAIN_FLAGS; const auto format = depth ? DkImageFormat_Z24S8 : DkImageFormat_RGBA8_Unorm; @@ -33,6 +32,7 @@ namespace love void Framebuffer::destroy() { - this->memory.destroy(); + if (this->memory) + this->memory.destroy(); } } // namespace love diff --git a/platform/hac/source/driver/display/Renderer.cpp b/platform/hac/source/driver/display/Renderer.cpp deleted file mode 100644 index 4afd9da96..000000000 --- a/platform/hac/source/driver/display/Renderer.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#include "driver/display/Renderer.hpp" - -namespace love -{ - Renderer::Renderer() : - transform {}, - device(dk::DeviceMaker {}.setFlags(DkDeviceFlags_DepthMinusOneToOne).create()), - mainQueue(dk::QueueMaker { this->device }.setFlags(DkQueueFlags_Graphics).create()), - textureQueue(dk::QueueMaker { this->device }.setFlags(DkQueueFlags_Graphics).create()), - commandBuffer(dk::CmdBufMaker { this->device }.create()), - swapchain {}, - images(CMemPool(this->device, GPU_USE_FLAGS, GPU_POOL_SIZE)), - data(CMemPool(this->device, CPU_USE_FLAGS, CPU_POOL_SIZE)), - code(CMemPool(this->device, SHADER_USE_FLAGS, SHADER_POOL_SIZE)), - framebufferSlot(-1) - {} - - void Renderer::initialize() - { - if (this->initialized) - return; - - this->unformBuffer = this->data.allocate(TRANSFORM_SIZE, DK_UNIFORM_BUF_ALIGNMENT); - this->transform.modelView = glm::mat4(1.0f); - - this->commands.allocate(this->data, COMMAND_SIZE); - this->createFramebuffers(); - - this->initialized = true; - } - - Renderer::~Renderer() - { - this->destroyFramebuffers(); - this->unformBuffer.destroy(); - } - - void Renderer::createFramebuffers() - { - const auto& info = getScreenInfo()[0]; - - this->depthbuffer.create(info, this->device, this->images, true); - - for (size_t index = 0; index < this->targets.size(); ++index) - { - this->framebuffers[index].create(info, this->device, this->images, false); - this->targets[index] = &this->framebuffers[index].getImage(); - } - - this->swapchain = - dk::SwapchainMaker { this->device, nwindowGetDefault(), this->targets }.create(); - - this->context.viewport = Rect { 0, 0, info.width, info.height }; - this->context.scissor = Rect { 0, 0, info.width, info.height }; - - this->setViewport(this->context.viewport); - this->setScissor(this->context.scissor); - } - - void Renderer::destroyFramebuffers() - { - if (!this->swapchain) - return; - - this->mainQueue.waitIdle(); - this->textureQueue.waitIdle(); - - this->commandBuffer.clear(); - this->swapchain.destroy(); - - for (auto& framebuffer : this->framebuffers) - framebuffer.destroy(); - - this->depthbuffer.destroy(); - } - - void Renderer::ensureInFrame() - { - if (!this->inFrame) - { - this->commands.begin(this->commandBuffer); - this->inFrame = true; - } - } - - void Renderer::clear(const Color& color) - { - this->commandBuffer.clearColor(0, DkColorMask_RGBA, color.r, color.g, color.b, color.a); - } - - void Renderer::clearDepthStencil(int depth, uint8_t mask, double stencil) - { - this->commandBuffer.clearDepthStencil(true, depth, mask, stencil); - } - - void Renderer::bindFramebuffer() - { - if (!this->swapchain) - return; - - this->ensureInFrame(); - - if (this->framebufferSlot < 0) - this->framebufferSlot = this->mainQueue.acquireImage(this->swapchain); - - dk::ImageView target { this->framebuffers[this->framebufferSlot].getImage() }; - dk::ImageView depth { this->depthbuffer.getImage() }; - - this->commandBuffer.bindRenderTargets(&target, &depth); - } - - void Renderer::present() - { - if (!this->swapchain) - return; - - if (this->inFrame) - { - this->mainQueue.submitCommands(this->commands.end(this->commandBuffer)); - this->mainQueue.presentImage(this->swapchain, this->framebufferSlot); - - this->inFrame = false; - } - - this->framebufferSlot = -1; - } - - void Renderer::setVertexWinding(Winding winding) - { - DkFrontFace face; - if (!Renderer::getConstant(winding, face)) - return; - - this->context.rasterizer.setFrontFace(face); - } - - void Renderer::setCullMode(CullMode mode) - { - DkFace cullMode; - if (!Renderer::getConstant(mode, cullMode)) - return; - - this->context.rasterizer.setCullMode(cullMode); - } - - void Renderer::setColorMask(ColorChannelMask mask) - { - const auto red = (DkColorMask_R * mask.r); - const auto green = (DkColorMask_G * mask.g); - const auto blue = (DkColorMask_B * mask.b); - const auto alpha = (DkColorMask_A * mask.a); - - this->context.colorWrite.setMask(0, (red + green + blue + alpha)); - } - - void Renderer::setBlendState(const BlendState& state) - { - if (this->context.blendState == state) - return; - - DkBlendOp operationRGB; - if (!Renderer::getConstant(state.operationRGB, operationRGB)) - return; - - DkBlendOp operationA; - if (!Renderer::getConstant(state.operationA, operationA)) - return; - - DkBlendFactor sourceColor; - if (!Renderer::getConstant(state.srcFactorRGB, sourceColor)) - return; - - DkBlendFactor destColor; - if (!Renderer::getConstant(state.dstFactorRGB, destColor)) - return; - - DkBlendFactor sourceAlpha; - if (!Renderer::getConstant(state.srcFactorA, sourceAlpha)) - return; - - DkBlendFactor destAlpha; - if (!Renderer::getConstant(state.dstFactorA, destAlpha)) - return; - - this->context.blend.setColorBlendOp(operationRGB); - this->context.blend.setAlphaBlendOp(operationA); - - // Blend factors - this->context.blend.setSrcColorBlendFactor(sourceColor); - this->context.blend.setSrcAlphaBlendFactor(sourceAlpha); - - this->context.blend.setDstColorBlendFactor(destColor); - this->context.blend.setDstAlphaBlendFactor(destAlpha); - } - - static DkScissor dkScissorFromRect(const Rect& rect) - { - DkScissor scissor {}; - - scissor.x = (uint32_t)rect.x; - scissor.y = (uint32_t)rect.y; - scissor.width = (uint32_t)rect.w; - scissor.height = (uint32_t)rect.h; - - return scissor; - } - - void Renderer::setScissor(const Rect& scissor) - { - this->ensureInFrame(); - DkScissor _scissor {}; - - if (scissor == Rect::EMPTY) - _scissor = dkScissorFromRect(this->context.scissor); - else - _scissor = dkScissorFromRect(scissor); - - this->commandBuffer.setScissors(0, { _scissor }); - } - - static DkViewport dkViewportFromRect(const Rect& rect) - { - DkViewport viewport {}; - - viewport.x = (float)rect.x; - viewport.y = (float)rect.y; - viewport.width = (float)rect.w; - viewport.height = (float)rect.h; - viewport.near = Framebuffer::Z_NEAR; - viewport.far = Framebuffer::Z_FAR; - - return viewport; - } - - void Renderer::setViewport(const Rect& viewport) - { - this->ensureInFrame(); - DkViewport _viewport {}; - - if (viewport == Rect::EMPTY) - _viewport = dkViewportFromRect(this->context.viewport); - else - _viewport = dkViewportFromRect(viewport); - - this->commandBuffer.setViewports(0, { _viewport }); - } -} // namespace love diff --git a/platform/hac/source/driver/display/deko.cpp b/platform/hac/source/driver/display/deko.cpp new file mode 100644 index 000000000..d8dfc0631 --- /dev/null +++ b/platform/hac/source/driver/display/deko.cpp @@ -0,0 +1,419 @@ +#include "driver/display/deko.hpp" +#include "driver/graphics/Attributes.hpp" + +#include "modules/graphics/Texture.hpp" + +namespace love +{ + deko3d::deko3d() : + context {}, + transform {}, + device(dk::DeviceMaker {}.setFlags(DkDeviceFlags_DepthMinusOneToOne).create()), + mainQueue(dk::QueueMaker { this->device }.setFlags(DkQueueFlags_Graphics).create()), + textureQueue(dk::QueueMaker { this->device }.setFlags(DkQueueFlags_Graphics).create()), + swapchain {}, + images(CMemPool(this->device, GPU_USE_FLAGS, GPU_POOL_SIZE)), + data(CMemPool(this->device, CPU_USE_FLAGS, CPU_POOL_SIZE)), + code(CMemPool(this->device, SHADER_USE_FLAGS, SHADER_POOL_SIZE)), + framebufferSlot(-1) + {} + + void deko3d::initialize() + { + if (this->initialized) + return; + + this->uniform = this->data.allocate(TRANSFORM_SIZE, DK_UNIFORM_BUF_ALIGNMENT); + this->transform.modelView = glm::mat4(1.0f); + + this->commands.allocate(this->data, COMMAND_SIZE); + this->commandBuffer = dk::CmdBufMaker { this->device }.create(); + + this->createFramebuffers(); + + this->context.rasterizer.setCullMode(DkFace_None); + + this->context.depthStencil.setDepthTestEnable(true); + this->context.depthStencil.setDepthWriteEnable(true); + this->context.depthStencil.setDepthCompareOp(DkCompareOp_Always); + + this->context.color.setBlendEnable(0, true); + + this->imageSet.allocate(this->data); + this->samplerSet.allocate(this->data); + + this->ensureInFrame(); + + this->imageSet.bindForImages(this->commandBuffer); + this->samplerSet.bindForSamplers(this->commandBuffer); + + this->initialized = true; + } + + deko3d::~deko3d() + { + this->destroyFramebuffers(); + this->uniform.destroy(); + } + + void deko3d::createFramebuffers() + { + const auto info = getScreenInfo()[0]; + + this->depthbuffer.create(info, this->device, this->images, true); + + for (size_t index = 0; index < this->targets.size(); ++index) + { + this->framebuffers[index].create(info, this->device, this->images, false); + this->targets[index] = this->framebuffers[index].getImage(); + } + + this->swapchain = dk::SwapchainMaker { this->device, nwindowGetDefault(), this->targets }.create(); + + this->context.viewport = Rect { 0, 0, info.width, info.height }; + this->context.scissor = Rect { 0, 0, info.width, info.height }; + + this->setViewport(this->context.viewport); + this->setScissor(this->context.scissor); + } + + void deko3d::destroyFramebuffers() + { + if (!this->swapchain) + return; + + this->mainQueue.waitIdle(); + this->textureQueue.waitIdle(); + + this->commandBuffer.clear(); + this->swapchain.destroy(); + + this->context.boundFramebuffer = nullptr; + for (auto& framebuffer : this->framebuffers) + framebuffer.destroy(); + + this->depthbuffer.destroy(); + } + + void deko3d::ensureInFrame() + { + if (!this->inFrame) + { + this->context.descriptorsDirty = false; + this->commands.begin(this->commandBuffer); + this->inFrame = true; + } + } + + void deko3d::clear(const Color& color) + { + if (!this->inFrame) + return; + + this->commandBuffer.clearColor(0, DkColorMask_RGBA, color.r, color.g, color.b, color.a); + } + + void deko3d::clearDepthStencil(int depth, uint8_t mask, double stencil) + { + if (!this->inFrame) + return; + + this->commandBuffer.clearDepthStencil(true, depth, mask, stencil); + } + + // dk::Image& deko3d::getInternalBackbuffer() + // { + // this->ensureInFrame(); + // return this->framebuffers[this->framebufferSlot].getImage(); + // } + + void deko3d::useProgram(const dk::Shader& vertex, const dk::Shader& fragment) + { + this->ensureInFrame(); + + // clang-format off + this->commandBuffer.bindShaders(DkStageFlag_GraphicsMask, { &vertex, &fragment }); + this->commandBuffer.bindUniformBuffer(DkStage_Vertex, 0, this->uniform.getGpuAddr(), this->uniform.getSize()); + // clang-format on + } + + void deko3d::registerTexture(TextureBase* texture, bool registering) + { + if (registering) + { + const auto index = this->textureHandles.allocate(); + texture->setHandleData(dkMakeTextureHandle(index, index)); + return; + } + + this->textureHandles.deallocate((uint32_t)texture->getHandle()); + } + + void deko3d::bindFramebuffer(dk::Image* framebuffer) + { + if (!this->swapchain) + return; + + if (this->framebufferSlot < 0) + this->framebufferSlot = this->mainQueue.acquireImage(this->swapchain); + + if (!framebuffer) + framebuffer = this->framebuffers[this->framebufferSlot].getImage(); + + bool bindingModified = false; + + if (this->context.boundFramebuffer != framebuffer) + { + bindingModified = true; + this->context.boundFramebuffer = framebuffer; + } + + if (bindingModified) + { + dk::ImageView depth { *this->depthbuffer.getImage() }; + dk::ImageView target { *framebuffer }; + + this->commandBuffer.bindRenderTargets(&target, &depth); + } + } + + void deko3d::bindBuffer(BufferUsage usage, CMemPool::Handle& handle) + { + if (usage == BUFFERUSAGE_VERTEX) + this->commandBuffer.bindVtxBuffer(0, handle.getGpuAddr(), handle.getSize()); + else if (usage == BUFFERUSAGE_INDEX) + this->commandBuffer.bindIdxBuffer(DkIdxFormat_Uint16, handle.getGpuAddr()); + } + + void deko3d::setVertexAttributes(bool isTexture) + { + vertex::Attributes attributes {}; + vertex::getAttributes(isTexture, attributes); + + this->commandBuffer.bindVtxAttribState(attributes.attributeState); + this->commandBuffer.bindVtxBufferState(attributes.bufferState); + } + + void deko3d::bindTextureToUnit(TextureBase* texture, int unit) + { + if (!texture) + return; + + const auto handle = (DkResHandle)texture->getHandle(); + this->bindTextureToUnit(handle, unit); + } + + void deko3d::bindTextureToUnit(DkResHandle texture, int unit) + { + if (this->context.descriptorsDirty) + { + this->commandBuffer.barrier(DkBarrier_Primitives, DkInvalidateFlags_Descriptors); + this->context.descriptorsDirty = false; + } + + this->commandBuffer.bindTextures(DkStage_Fragment, 0, texture); + } + + void deko3d::drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, + uint32_t instanceCount) + { + this->commandBuffer.drawIndexed(primitive, indexCount, instanceCount, indexOffset, 0, 0); + } + + void deko3d::draw(DkPrimitive primitive, uint32_t vertexCount, uint32_t firstVertex) + { + this->commandBuffer.draw(primitive, vertexCount, 1, firstVertex, 0); + } + + void deko3d::prepareDraw(GraphicsBase* graphics) + { + this->commandBuffer.bindRasterizerState(this->context.rasterizer); + this->commandBuffer.bindColorState(this->context.color); + this->commandBuffer.bindColorWriteState(this->context.colorWrite); + this->commandBuffer.bindBlendStates(0, this->context.blend); + + if (!this->uniform) + return; + + this->commandBuffer.pushConstants(this->uniform.getGpuAddr(), this->uniform.getSize(), 0, + TRANSFORM_SIZE, &this->transform); + } + + void deko3d::present() + { + if (!this->swapchain) + return; + + if (this->inFrame) + { + GraphicsBase::flushBatchedDrawsGlobal(); + GraphicsBase::advanceStreamBuffersGlobal(); + + this->mainQueue.submitCommands(this->commands.end(this->commandBuffer)); + this->mainQueue.presentImage(this->swapchain, this->framebufferSlot); + + this->inFrame = false; + } + + this->framebufferSlot = -1; + } + + void deko3d::setVertexWinding(Winding winding) + { + DkFrontFace face; + if (!deko3d::getConstant(winding, face)) + return; + + this->context.rasterizer.setFrontFace(face); + } + + void deko3d::setPointSize(float size) + { + this->commandBuffer.setPointSize(size); + } + + void deko3d::setCullMode(CullMode mode) + { + DkFace cullMode; + if (!deko3d::getConstant(mode, cullMode)) + return; + + this->context.rasterizer.setCullMode(cullMode); + } + + void deko3d::setColorMask(ColorChannelMask mask) + { + const auto red = (DkColorMask_R * mask.r); + const auto green = (DkColorMask_G * mask.g); + const auto blue = (DkColorMask_B * mask.b); + const auto alpha = (DkColorMask_A * mask.a); + + this->context.colorWrite.setMask(0, uint32_t(red + green + blue + alpha)); + } + + void deko3d::setBlendState(const BlendState& state) + { + if (this->context.blendState == state) + return; + + DkBlendOp operationRGB; + if (!deko3d::getConstant(state.operationRGB, operationRGB)) + return; + + DkBlendOp operationA; + if (!deko3d::getConstant(state.operationA, operationA)) + return; + + DkBlendFactor sourceColor; + if (!deko3d::getConstant(state.srcFactorRGB, sourceColor)) + return; + + DkBlendFactor destColor; + if (!deko3d::getConstant(state.dstFactorRGB, destColor)) + return; + + DkBlendFactor sourceAlpha; + if (!deko3d::getConstant(state.srcFactorA, sourceAlpha)) + return; + + DkBlendFactor destAlpha; + if (!deko3d::getConstant(state.dstFactorA, destAlpha)) + return; + + this->context.blend.setColorBlendOp(operationRGB); + this->context.blend.setAlphaBlendOp(operationA); + + this->context.blend.setSrcColorBlendFactor(sourceColor); + this->context.blend.setSrcAlphaBlendFactor(sourceAlpha); + + this->context.blend.setDstColorBlendFactor(destColor); + this->context.blend.setDstAlphaBlendFactor(destAlpha); + } + + void deko3d::setSamplerState(TextureBase* texture, const SamplerState& state) + { + this->ensureInFrame(); + + auto* sampler = (dk::Sampler*)texture->getSamplerHandle(); + auto& imageDescriptor = ((Texture*)texture)->getDescriptorHandle(); + // auto& samplerDescriptor = ((Texture*)texture)->getSamplerDescriptor(); + + auto index = -1; + + if (!this->textureHandles.find((uint32_t)texture->getHandle(), index)) + index = this->textureHandles.allocate(); + + DkFilter magFilter; + if (!deko3d::getConstant(state.magFilter, magFilter)) + return; + + DkFilter minFilter; + if (!deko3d::getConstant(state.minFilter, minFilter)) + return; + + DkMipFilter mipFilter; + if (!deko3d::getConstant(state.mipmapFilter, mipFilter)) + return; + + sampler->setFilter(minFilter, magFilter, mipFilter); + + DkWrapMode wrapU; + if (!deko3d::getConstant(state.wrapU, wrapU)) + return; + + DkWrapMode wrapV; + if (!deko3d::getConstant(state.wrapV, wrapV)) + return; + + DkWrapMode wrapW; + if (!deko3d::getConstant(state.wrapW, wrapW)) + return; + + sampler->setWrapMode(wrapU, wrapV, wrapW); + + dk::SamplerDescriptor samplerDescriptor {}; + samplerDescriptor.initialize(*sampler); + + this->imageSet.update(this->commandBuffer, index, imageDescriptor); + this->samplerSet.update(this->commandBuffer, index, samplerDescriptor); + + this->context.descriptorsDirty = true; + } + + template + T dkRectFromRect(const Rect& rect) + { + T value {}; + value.x = rect.x; + value.y = rect.y; + value.width = rect.w; + value.height = rect.h; + + return value; + } + + void deko3d::setScissor(const Rect& rect) + { + this->ensureInFrame(); + + const auto scissor = rect == Rect::EMPTY ? this->context.scissor : rect; + DkScissor dkScissor = dkRectFromRect(scissor); + + this->commandBuffer.setScissors(0, dkScissor); + } + + void deko3d::setViewport(const Rect& rect) + { + this->ensureInFrame(); + + const auto viewport = rect == Rect::EMPTY ? this->context.viewport : rect; + DkViewport dkViewport = dkRectFromRect(viewport); + + this->commandBuffer.setViewports(0, dkViewport); + + this->transform.projection = + glm::ortho(0.0f, (float)viewport.w, (float)viewport.h, 0.0f, -10.0f, 10.0f); + } + + deko3d d3d; +} // namespace love diff --git a/platform/hac/source/driver/graphics/StreamBuffer.cpp b/platform/hac/source/driver/graphics/StreamBuffer.cpp new file mode 100644 index 000000000..23e82828c --- /dev/null +++ b/platform/hac/source/driver/graphics/StreamBuffer.cpp @@ -0,0 +1,20 @@ +#include "driver/display/deko.hpp" + +#include "driver/graphics/StreamBuffer.hpp" + +namespace love +{ + template<> + size_t StreamBuffer::unmap(size_t) + { + d3d.bindBuffer(this->usage, this->memory); + return this->index; + } + + template<> + size_t StreamBuffer::unmap(size_t) + { + d3d.bindBuffer(this->usage, this->memory); + return this->index; + } +} // namespace love diff --git a/platform/hac/source/modules/graphics/Graphics.cpp b/platform/hac/source/modules/graphics/Graphics.cpp index 706bf89b6..80930b00a 100644 --- a/platform/hac/source/modules/graphics/Graphics.cpp +++ b/platform/hac/source/modules/graphics/Graphics.cpp @@ -1,8 +1,12 @@ -#include "driver/display/Renderer.hpp" +#include "driver/display/deko.hpp" #include "modules/graphics/Graphics.hpp" #include "modules/window/Window.hpp" +#include "modules/graphics/Shader.hpp" +#include "modules/graphics/Texture.hpp" +#include "modules/graphics/freetype/Font.hpp" + namespace love { Graphics::Graphics() : @@ -28,11 +32,64 @@ namespace love } } - void Graphics::backbufferChanged(int width, int height, int pixelWidth, int pixelHeight, - bool stencil, bool depth, int msaa) + void Graphics::captureScreenshot(const ScreenshotInfo& info) + {} + + void Graphics::initCapabilities() { - bool changed = width != this->width || height != this->height || - pixelWidth != this->pixelWidth || pixelHeight != this->pixelHeight; + // clang-format off + this->capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS] = false; + this->capabilities.features[FEATURE_CLAMP_ZERO] = true; + this->capabilities.features[FEATURE_CLAMP_ONE] = true; + this->capabilities.features[FEATURE_BLEND_MINMAX] = true; + this->capabilities.features[FEATURE_LIGHTEN] = true; + this->capabilities.features[FEATURE_FULL_NPOT] = true; + this->capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = false; + this->capabilities.features[FEATURE_SHADER_DERIVATIVES] = false; + this->capabilities.features[FEATURE_GLSL3] = false; + this->capabilities.features[FEATURE_GLSL4] = false; + this->capabilities.features[FEATURE_INSTANCING] = false; + this->capabilities.features[FEATURE_TEXEL_BUFFER] = false; + this->capabilities.features[FEATURE_INDEX_BUFFER_32BIT] = false; + this->capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = false; //< might be possible + this->capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = false; //< might be possible + this->capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = false; //< might be possible + this->capabilities.features[FEATURE_MIPMAP_RANGE] = false; + this->capabilities.features[FEATURE_INDIRECT_DRAW] = false; + static_assert(FEATURE_MAX_ENUM == 19, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + + this->capabilities.limits[LIMIT_POINT_SIZE] = 8.0f; + this->capabilities.limits[LIMIT_TEXTURE_SIZE] = 4096; + this->capabilities.limits[LIMIT_TEXTURE_LAYERS] = 1; + this->capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = 4096; + this->capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = 4096; + this->capabilities.limits[LIMIT_TEXEL_BUFFER_SIZE] = 0; + this->capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_X] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_Y] = 0; + this->capabilities.limits[LIMIT_THREADGROUPS_Z] = 0; + this->capabilities.limits[LIMIT_RENDER_TARGETS] = 1; //< max simultaneous render targets + this->capabilities.limits[LIMIT_TEXTURE_MSAA] = 0; + this->capabilities.limits[LIMIT_ANISOTROPY] = 0; + static_assert(LIMIT_MAX_ENUM == 13, "Graphics::initCapabilities must be updated when adding a new system limit!"); + // clang-format on + + this->capabilities.textureTypes[TEXTURE_2D] = true; + this->capabilities.textureTypes[TEXTURE_VOLUME] = false; + this->capabilities.textureTypes[TEXTURE_CUBE] = true; + this->capabilities.textureTypes[TEXTURE_2D_ARRAY] = false; + } + + void Graphics::setActiveScreen() + { + d3d.ensureInFrame(); + } + + void Graphics::backbufferChanged(int width, int height, int pixelWidth, int pixelHeight, bool stencil, + bool depth, int msaa) + { + bool changed = width != this->width || height != this->height || pixelWidth != this->pixelWidth || + pixelHeight != this->pixelHeight; changed |= stencil != this->backBufferHasStencil || depth != this->backBufferHasDepth; changed |= msaa != this->requestedBackbufferMSAA; @@ -49,7 +106,7 @@ namespace love if (!this->isRenderTargetActive()) { - Renderer::getInstance().setViewport({ 0, 0, pixelWidth, pixelHeight }); + d3d.setViewport({ 0, 0, pixelWidth, pixelHeight }); if (this->states.back().scissor) this->setScissor(this->states.back().scissorRect); @@ -60,65 +117,279 @@ namespace love if (!changed) return; - Renderer::getInstance().onModeChanged(); + d3d.onModeChanged(); } - void Graphics::clearImpl(OptionalColor color, OptionalInt depth, OptionalDouble stencil) + void Graphics::clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) { - Renderer::getInstance().bindFramebuffer(); + if (color.hasValue) + { + bool hasIntegerFormat = false; + const auto& targets = this->states.back().renderTargets; + + for (const auto& target : targets.colors) + { + if (target.texture.get() && love::isPixelFormatInteger(target.texture->getPixelFormat())) + hasIntegerFormat = true; + + if (hasIntegerFormat) + { + std::vector colors(targets.colors.size()); + + for (size_t index = 0; index < colors.size(); index++) + colors[index] = color; + + this->clear(colors, stencil, depth); + return; + } + } + } + + d3d.bindFramebuffer(); + + if (color.hasValue || stencil.hasValue || depth.hasValue) + this->flushBatchedDraws(); if (color.hasValue) { - // gammaCorrectColor(color.value); - Renderer::getInstance().clear(color.value); + gammaCorrectColor(color.value); + d3d.clear(color.value); } + } + + void Graphics::clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) + { + if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) + return; + + int numColors = (int)colors.size(); + + const auto& targets = this->states.back().renderTargets.colors; + const int numColorTargets = targets.size(); + + if (numColors <= 1 && + (numColorTargets == 0 || (numColorTargets == 1 && targets[0].texture.get() != nullptr && + !isPixelFormatInteger(targets[0].texture->getPixelFormat())))) + { + this->clear(colors.size() > 0 ? colors[0] : OptionalColor(), stencil, depth); + return; + } + + d3d.bindFramebuffer(); + + this->flushBatchedDraws(); + + numColors = std::min(numColors, numColorTargets); + + for (int index = 0; index < numColors; index++) + { + OptionalColor current = colors[index]; + + if (!current.hasValue) + continue; + + Color value(current.value.r, current.value.g, current.value.b, current.value.a); + + gammaCorrectColor(value); + d3d.clear(value); + } + } + + void Graphics::present(void* screenshotCallback) + { + if (!this->isActive()) + return; + + if (this->isRenderTargetActive()) + throw love::Exception("present cannot be called while a render target is active."); + + d3d.present(); + + this->drawCalls = 0; + this->drawCallsBatched = 0; + } + + void Graphics::setScissor(const Rect& scissor) + { + this->flushBatchedDraws(); + + auto& state = this->states.back(); + double dpiscale = this->getCurrentDPIScale(); + + Rect rectangle {}; + rectangle.x = scissor.x * dpiscale; + rectangle.y = scissor.y * dpiscale; + rectangle.w = scissor.w * dpiscale; + rectangle.h = scissor.h * dpiscale; + + d3d.setScissor(rectangle); + + state.scissor = true; + state.scissorRect = scissor; + } + + void Graphics::setScissor() + { + if (this->states.back().scissor) + this->flushBatchedDraws(); + + this->states.back().scissor = false; + d3d.setScissor(Rect::EMPTY); + } + + void Graphics::setPointSize(float size) + { + if (size != this->states.back().pointSize) + this->flushBatchedDraws(); - if (stencil.hasValue && depth.hasValue) - Renderer::getInstance().clearDepthStencil(stencil.value, 0xFF, depth.value); + this->states.back().pointSize = size; + d3d.setPointSize(size); } - void Graphics::presentImpl() + void Graphics::setFrontFaceWinding(Winding winding) { - Renderer::getInstance().present(); + auto& state = this->states.back(); + + if (state.winding != winding) + this->flushBatchedDraws(); + + state.winding = winding; + + if (this->isRenderTargetActive()) + winding = (winding == WINDING_CW) ? WINDING_CCW : WINDING_CW; // ??? + + d3d.setVertexWinding(winding); + } + + void Graphics::setColorMask(ColorChannelMask mask) + { + this->flushBatchedDraws(); + + d3d.setColorMask(mask); + this->states.back().colorMask = mask; + } + + void Graphics::setBlendState(const BlendState& state) + { + if (!(state == this->states.back().blend)) + this->flushBatchedDraws(); + + if (state.operationRGB == BLENDOP_MAX || state.operationA == BLENDOP_MAX || + state.operationRGB == BLENDOP_MIN || state.operationA == BLENDOP_MIN) + { + if (!capabilities.features[FEATURE_BLEND_MINMAX]) + throw love::Exception(E_BLEND_MIN_MAX_NOT_SUPPORTED); + } + + if (state.enable) + d3d.setBlendState(state); + + this->states.back().blend = state; } - void Graphics::setScissorImpl(const Rect& scissor) + bool Graphics::isPixelFormatSupported(PixelFormat format, uint32_t usage) { - Renderer::getInstance().setScissor(scissor); + format = this->getSizedFormat(format); + bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; + + DkImageFormat color; + bool supported = deko3d::getConstant(format, color); + + return readable && supported; } - void Graphics::setScissorImpl() + void Graphics::setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) { - Renderer::getInstance().setScissor(Rect::EMPTY); + const auto& state = this->states.back(); + + bool isWindow = targets.getFirstTarget().texture == nullptr; + + if (isWindow) + d3d.bindFramebuffer(); + else + d3d.bindFramebuffer((dk::Image*)targets.getFirstTarget().texture->getRenderTargetHandle()); + + d3d.setViewport({ 0, 0, pixelWidth, pixelHeight }); + + if (state.scissor) + d3d.setScissor(state.scissorRect); } - void Graphics::setFrontFaceWindingImpl(Winding winding) + TextureBase* Graphics::newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data) { - Renderer::getInstance().setVertexWinding(winding); + return new Texture(this, settings, data); } - void Graphics::setColorMaskImpl(ColorChannelMask mask) + FontBase* Graphics::newFont(Rasterizer* data) { - Renderer::getInstance().setColorMask(mask); + return new Font(data, this->states.back().defaultSamplerState); } - void Graphics::setBlendStateImpl(const BlendState& state) + FontBase* Graphics::newDefaultFont(int size, const Rasterizer::Settings& settings) { - Renderer::getInstance().setBlendState(state); + auto* module = Module::getInstance(Module::M_FONT); + + if (module == nullptr) + throw love::Exception("Font module has not been loaded."); + + StrongRef rasterizer = module->newTrueTypeRasterizer(size, settings); + + return this->newFont(rasterizer.get()); } - bool Graphics::setModeImpl(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa) + bool Graphics::setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa) { - Renderer::getInstance().initialize(); + d3d.initialize(); + + this->created = true; + this->initCapabilities(); - this->backbufferChanged(width, height, pixelWidth, pixelHeight, backBufferStencil, - backBufferDepth, msaa); + // d3d.setupContext(); + + try + { + if (this->batchedDrawState.vertexBuffer == nullptr) + { + this->batchedDrawState.indexBuffer = newIndexBuffer(INIT_INDEX_BUFFER_SIZE); + this->batchedDrawState.indexBuffer->allocate(d3d.getMemoryPool(deko3d::MEMORYPOOL_DATA)); + + this->batchedDrawState.vertexBuffer = newVertexBuffer(INIT_VERTEX_BUFFER_SIZE); + this->batchedDrawState.vertexBuffer->allocate(d3d.getMemoryPool(deko3d::MEMORYPOOL_DATA)); + } + } + catch (love::Exception&) + { + throw; + } + + if (!Volatile::loadAll()) + std::printf("Failed to load all volatile objects.\n"); + + this->restoreState(this->states.back()); + + for (int index = 0; index < Shader::STANDARD_MAX_ENUM; index++) + { + auto type = (Shader::StandardShader)index; + + try + { + Shader::standardShaders[type] = new Shader(type); + } + catch (const std::exception& e) + { + throw; + } + } + + if (!Shader::current) + Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach(); return true; } - void Graphics::unsetModeImpl() + void Graphics::unsetMode() {} bool Graphics::isActive() const @@ -126,4 +397,36 @@ namespace love auto* window = Module::getInstance(M_WINDOW); return this->active && this->created && window != nullptr && window->isOpen(); } + + void Graphics::draw(const DrawIndexedCommand& command) + { + d3d.prepareDraw(this); + d3d.setVertexAttributes(command.texture != nullptr); + d3d.bindTextureToUnit(command.texture, 0); + d3d.setCullMode(command.cullMode); + + DkPrimitive primitive; + deko3d::getConstant(command.primitiveType, primitive); + + const auto indexCount = command.indexCount; + const auto offset = command.indexBufferOffset; + const auto instanceCount = command.instanceCount; + + d3d.drawIndexed(primitive, indexCount, offset, instanceCount); + ++drawCalls; + } + + void Graphics::draw(const DrawCommand& command) + { + d3d.prepareDraw(this); + d3d.setVertexAttributes(command.texture != nullptr); + d3d.bindTextureToUnit(command.texture, 0); + d3d.setCullMode(command.cullMode); + + DkPrimitive primitive; + deko3d::getConstant(command.primitiveType, primitive); + + d3d.draw(primitive, command.vertexCount, command.vertexStart); + ++drawCalls; + } } // namespace love diff --git a/platform/hac/source/modules/graphics/Shader.cpp b/platform/hac/source/modules/graphics/Shader.cpp new file mode 100644 index 000000000..bce1184b7 --- /dev/null +++ b/platform/hac/source/modules/graphics/Shader.cpp @@ -0,0 +1,207 @@ +#include "modules/graphics/Shader.hpp" +#include "driver/display/deko.hpp" + +#include + +#define SHADERS_DIR "romfs:/shaders/" + +#define DEFAULT_VERTEX_SHADER (SHADERS_DIR "transform_vsh.dksh") +#define DEFAULT_FRAGMENT_SHADER (SHADERS_DIR "color_fsh.dksh") +#define DEFAULT_TEXTURE_SHADER (SHADERS_DIR "texture_fsh.dksh") +#define DEFAULT_VIDEO_SHADER (SHADERS_DIR "video_fsh.dksh") + +namespace love +{ + struct DkshHeader + { + uint32_t magic; + uint32_t header_sz; + uint32_t control_sz; + uint32_t code_sz; + uint32_t programs_off; + uint32_t num_programs; + }; + + Shader::Shader() + {} + + Shader::Shader(StandardShader shader) : ShaderBase(shader) + { + std::string error; + const char* fragmentStage = nullptr; + + switch (shader) + { + case STANDARD_DEFAULT: + { + fragmentStage = DEFAULT_FRAGMENT_SHADER; + break; + } + case STANDARD_TEXTURE: + { + fragmentStage = DEFAULT_TEXTURE_SHADER; + break; + } + case STANDARD_VIDEO: + { + fragmentStage = DEFAULT_VIDEO_SHADER; + break; + } + default: + break; + } + + if (!this->validate(this->program.vertex, DEFAULT_VERTEX_SHADER, error)) + throw love::Exception("Invalid vertex shader: {:s}", error); + + error.clear(); + + if (!this->validate(this->program.fragment, fragmentStage, error)) + throw love::Exception("Invalid fragment shader: {:s}", error); + } + + Shader::~Shader() + { + this->unloadVolatile(); + } + + void Shader::attach() + { + if (Shader::current != this) + { + Graphics::flushBatchedDrawsGlobal(); + + d3d.useProgram(this->program.vertex.shader, this->program.fragment.shader); + ++shaderSwitches; + + Shader::current = this; + } + } + + bool Shader::loadVolatile() + { + return true; + } + + void Shader::unloadVolatile() + { + this->program.vertex.memory.destroy(); + this->program.fragment.memory.destroy(); + } + + void Shader::updateBuiltinUniforms(GraphicsBase* graphics) + {} + + ptrdiff_t Shader::getHandle() const + { + return 0; + } + + static bool loadFile(Shader::Stage& stage, const uint8_t* buffer, size_t size, std::string& error) + { + DkshHeader header {}; + std::unique_ptr control = nullptr; + + stage.memory.destroy(); + + if (!buffer || size == 0) + { + error = "Buffer size is zero."; + return false; + } + + const auto headerSize = sizeof(DkshHeader); + std::memcpy(&header, buffer, headerSize); + + if (header.header_sz != headerSize) + { + error = "Invalid header size."; + return false; + } + + try + { + control = std::make_unique(header.control_sz); + } + catch (std::bad_alloc&) + { + error = E_OUT_OF_MEMORY; + return false; + } + + std::memcpy(control.get(), buffer, header.control_sz); + + auto& pool = d3d.getMemoryPool(deko3d::MEMORYPOOL_CODE); + stage.memory = pool.allocate(header.code_sz, DK_SHADER_CODE_ALIGNMENT); + + if (!stage.memory) + { + error = "Failed to allocate memory."; + return false; + } + + std::memcpy(stage.memory.getCpuAddr(), buffer + header.control_sz, header.code_sz); + + dk::ShaderMaker { stage.memory.getMemBlock(), stage.memory.getOffset() } + .setControl(control.get()) + .setProgramId(0) + .initialize(stage.shader); + + if (!stage.shader.isValid()) + { + error = "Failed to initialize shader."; + stage.memory.destroy(); + return false; + } + + return true; + } + + bool Shader::validate(Stage& stage, const char* filepath, std::string& error) + { + if (!filepath) + { + error = "No filepath provided."; + return false; + } + + FILE* file = fopen(filepath, "rb"); + + if (!file) + { + error = "File '" + std::string(filepath) + "' does not exist."; + std::fclose(file); + return false; + } + + std::fseek(file, 0, SEEK_END); + size_t size = std::ftell(file); + + if (size == 0) + { + error = "File '" + std::string(filepath) + "' is empty."; + std::fclose(file); + return false; + } + + std::rewind(file); + + std::unique_ptr buffer = nullptr; + + try + { + buffer = std::make_unique(size); + } + catch (std::bad_alloc&) + { + error = E_OUT_OF_MEMORY; + std::fclose(file); + return false; + } + + std::fread(buffer.get(), size, 1, file); + std::fclose(file); + + return loadFile(stage, buffer.get(), size, error); + } +} // namespace love diff --git a/platform/hac/source/modules/graphics/Texture.cpp b/platform/hac/source/modules/graphics/Texture.cpp new file mode 100644 index 000000000..9ecc66d73 --- /dev/null +++ b/platform/hac/source/modules/graphics/Texture.cpp @@ -0,0 +1,220 @@ +#include "driver/display/deko.hpp" + +#include "modules/graphics/Texture.hpp" +#include + +namespace love +{ + static void createTextureObject(dk::Image& image, CMemPool::Handle& memory, + dk::ImageDescriptor& descriptor, uint32_t width, uint32_t height, + PixelFormat format) + { + DkImageFormat gpuFormat; + if (!deko3d::getConstant(format, gpuFormat)) + throw love::Exception("Invalid image format."); + + auto device = d3d.getDevice(); + + dk::ImageLayout layout {}; + dk::ImageLayoutMaker { device } + .setFlags(0) + .setFormat(gpuFormat) + .setDimensions(width, height) + .initialize(layout); + + auto& pool = d3d.getMemoryPool(deko3d::MEMORYPOOL_IMAGE); + + memory = pool.allocate(layout.getSize(), layout.getAlignment()); + + if (!memory) + throw love::Exception("Failed to allocate CMemPool::Handle!"); + + image.initialize(layout, memory.getMemBlock(), memory.getOffset()); + descriptor.initialize(image); + } + + static void createFramebufferObject() + {} + + Texture::Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data) : + TextureBase(graphics, settings, data), + slices(settings.type), + image {}, + descriptor {}, + sampler {}, + samplerDescriptor {}, + handle(0), + memory {} + { + if (data != nullptr) + this->slices = *data; + + if (!this->loadVolatile()) + throw love::Exception("Failed to create texture."); + + slices.clear(); + } + + Texture::~Texture() + { + this->unloadVolatile(); + } + + bool Texture::loadVolatile() + { + // const auto& layout = this->image.getLayout(); + // if (layout.getSize() != 0) + // return true; + + if (this->parentView.texture != this) + { + Texture* baseTexture = (Texture*)this->parentView.texture; + baseTexture->loadVolatile(); + } + + if (this->isReadable()) + this->createTexture(); + + int64_t memorySize = 0; + + for (int mip = 0; mip < this->getMipmapCount(); mip++) + { + int width = this->getPixelWidth(mip); + int height = this->getPixelHeight(mip); + + const auto faces = (this->textureType == TEXTURE_CUBE) ? 6 : 1; + int slices = this->getDepth(mip) * this->layers * faces; + + memorySize += getPixelFormatSliceSize(this->format, width, height, false) * slices; + } + + this->setGraphicsMemorySize(memorySize); + + return true; + } + + void Texture::unloadVolatile() + { + d3d.registerTexture(this, false); + + this->memory.destroy(); + this->setGraphicsMemorySize(0); + } + + void Texture::createTexture() + { + if (!this->isRenderTarget()) + { + try + { + createTextureObject(this->image, this->memory, this->descriptor, this->width, this->height, + this->format); + } + catch (love::Exception&) + { + throw; + } + + int mipCount = this->getMipmapCount(); + int sliceCount = 1; + + if (this->textureType == TEXTURE_VOLUME) + sliceCount = this->getDepth(); + else if (this->textureType == TEXTURE_2D_ARRAY) + sliceCount = this->layers; + else if (this->textureType == TEXTURE_CUBE) + sliceCount = 6; + + for (int mipmap = 0; mipmap < mipCount; mipmap++) + { + for (int slice = 0; slice < sliceCount; slice++) + { + auto* data = this->slices.get(slice, mipmap); + + if (data != nullptr) + this->uploadImageData(data, mipmap, slice, 0, 0); + } + } + } + + bool hasData = this->slices.get(0, 0) != nullptr; + int clearMips = 1; + + if (isPixelFormatDepthStencil(this->format)) + clearMips = mipmapCount; + + if (this->isRenderTarget()) + { + } + else if (!hasData) + { + for (int mipmap = 0; mipmap < clearMips; mipmap++) + { + } + } + + d3d.registerTexture(this, true); + this->setSamplerState(this->samplerState); + + if (this->slices.getMipmapCount() <= 1 && this->getMipmapsMode() != MIPMAPS_NONE) + this->generateMipmaps(); + } + + void Texture::setSamplerState(const SamplerState& state) + { + this->samplerState = this->validateSamplerState(state); + d3d.setSamplerState(this, this->samplerState); + } + + void Texture::uploadByteData(const void* data, size_t size, int level, int slice, const Rect& rect) + { + auto& scratchPool = d3d.getMemoryPool(deko3d::MEMORYPOOL_DATA); + auto tempImageMemory = scratchPool.allocate(size, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); + + if (!tempImageMemory) + throw love::Exception("Failed to allocate temporary memory."); + + std::memcpy(tempImageMemory.getCpuAddr(), data, size); + + auto device = d3d.getDevice(); + auto tempCmdBuff = dk::CmdBufMaker { device }.create(); + + auto tempCmdMem = scratchPool.allocate(DK_MEMBLOCK_ALIGNMENT); + + if (!tempCmdMem) + throw love::Exception("Failed to allocate temporary command memory."); + + tempCmdBuff.addMemory(tempCmdMem.getMemBlock(), tempCmdMem.getOffset(), tempCmdMem.getSize()); + + dk::ImageView view { this->image }; + DkImageRect dkRect { rect.x, rect.y, 0u, rect.w, rect.h, 1u }; + + tempCmdBuff.copyBufferToImage({ tempImageMemory.getGpuAddr() }, view, dkRect); + + auto transferQueue = d3d.getQueue(deko3d::QUEUE_TYPE_IMAGES); + + transferQueue.submitCommands(tempCmdBuff.finishList()); + transferQueue.waitIdle(); + + tempCmdMem.destroy(); + tempImageMemory.destroy(); + } + + void Texture::generateMipmapsInternal() + {} + + ptrdiff_t Texture::getHandle() const + { + return (ptrdiff_t)this->handle; + } + + ptrdiff_t Texture::getRenderTargetHandle() const + { + return (ptrdiff_t)this->handle; + } + + ptrdiff_t Texture::getSamplerHandle() const + { + return (ptrdiff_t)std::addressof(this->sampler); + } +} // namespace love diff --git a/platform/hac/source/modules/joystick/JoystickModule.cpp b/platform/hac/source/modules/joystick/JoystickModule.cpp index fd85f0754..dee71c124 100644 --- a/platform/hac/source/modules/joystick/JoystickModule.cpp +++ b/platform/hac/source/modules/joystick/JoystickModule.cpp @@ -1,4 +1,5 @@ #include "modules/joystick/JoystickModule.hpp" +#include "modules/joystick/Joystick.hpp" #include diff --git a/source/common/Matrix.cpp b/source/common/Matrix.cpp index 600cb4646..c0485691e 100644 --- a/source/common/Matrix.cpp +++ b/source/common/Matrix.cpp @@ -156,8 +156,8 @@ namespace love multiply(a, b, e); } - Matrix4::Matrix4(float x, float y, float angle, float sx, float sy, float ox, float oy, - float kx, float ky) + Matrix4::Matrix4(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, + float ky) { setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); } @@ -205,6 +205,11 @@ namespace love return Vector4(e[c * 4 + 0], e[c * 4 + 1], e[c * 4 + 2], e[c * 4 + 3]); } + float Matrix4::get(int row, int column) const + { + return e[column * 4 + row]; + } + void Matrix4::setIdentity() { memset(e, 0, sizeof(float) * 16); @@ -260,8 +265,8 @@ namespace love e[13] = y; } - void Matrix4::setTransformation(float x, float y, float angle, float sx, float sy, float ox, - float oy, float kx, float ky) + void Matrix4::setTransformation(float x, float y, float angle, float sx, float sy, float ox, float oy, + float kx, float ky) { memset(e, 0, sizeof(float) * 16); // zero out matrix float c = cosf(angle), s = sinf(angle); @@ -318,53 +323,53 @@ namespace love { Matrix4 inv; - inv.e[0] = e[5] * e[10] * e[15] - e[5] * e[11] * e[14] - e[9] * e[6] * e[15] + - e[9] * e[7] * e[14] + e[13] * e[6] * e[11] - e[13] * e[7] * e[10]; + inv.e[0] = e[5] * e[10] * e[15] - e[5] * e[11] * e[14] - e[9] * e[6] * e[15] + e[9] * e[7] * e[14] + + e[13] * e[6] * e[11] - e[13] * e[7] * e[10]; - inv.e[4] = -e[4] * e[10] * e[15] + e[4] * e[11] * e[14] + e[8] * e[6] * e[15] - - e[8] * e[7] * e[14] - e[12] * e[6] * e[11] + e[12] * e[7] * e[10]; + inv.e[4] = -e[4] * e[10] * e[15] + e[4] * e[11] * e[14] + e[8] * e[6] * e[15] - e[8] * e[7] * e[14] - + e[12] * e[6] * e[11] + e[12] * e[7] * e[10]; - inv.e[8] = e[4] * e[9] * e[15] - e[4] * e[11] * e[13] - e[8] * e[5] * e[15] + - e[8] * e[7] * e[13] + e[12] * e[5] * e[11] - e[12] * e[7] * e[9]; + inv.e[8] = e[4] * e[9] * e[15] - e[4] * e[11] * e[13] - e[8] * e[5] * e[15] + e[8] * e[7] * e[13] + + e[12] * e[5] * e[11] - e[12] * e[7] * e[9]; - inv.e[12] = -e[4] * e[9] * e[14] + e[4] * e[10] * e[13] + e[8] * e[5] * e[14] - - e[8] * e[6] * e[13] - e[12] * e[5] * e[10] + e[12] * e[6] * e[9]; + inv.e[12] = -e[4] * e[9] * e[14] + e[4] * e[10] * e[13] + e[8] * e[5] * e[14] - e[8] * e[6] * e[13] - + e[12] * e[5] * e[10] + e[12] * e[6] * e[9]; - inv.e[1] = -e[1] * e[10] * e[15] + e[1] * e[11] * e[14] + e[9] * e[2] * e[15] - - e[9] * e[3] * e[14] - e[13] * e[2] * e[11] + e[13] * e[3] * e[10]; + inv.e[1] = -e[1] * e[10] * e[15] + e[1] * e[11] * e[14] + e[9] * e[2] * e[15] - e[9] * e[3] * e[14] - + e[13] * e[2] * e[11] + e[13] * e[3] * e[10]; - inv.e[5] = e[0] * e[10] * e[15] - e[0] * e[11] * e[14] - e[8] * e[2] * e[15] + - e[8] * e[3] * e[14] + e[12] * e[2] * e[11] - e[12] * e[3] * e[10]; + inv.e[5] = e[0] * e[10] * e[15] - e[0] * e[11] * e[14] - e[8] * e[2] * e[15] + e[8] * e[3] * e[14] + + e[12] * e[2] * e[11] - e[12] * e[3] * e[10]; - inv.e[9] = -e[0] * e[9] * e[15] + e[0] * e[11] * e[13] + e[8] * e[1] * e[15] - - e[8] * e[3] * e[13] - e[12] * e[1] * e[11] + e[12] * e[3] * e[9]; + inv.e[9] = -e[0] * e[9] * e[15] + e[0] * e[11] * e[13] + e[8] * e[1] * e[15] - e[8] * e[3] * e[13] - + e[12] * e[1] * e[11] + e[12] * e[3] * e[9]; - inv.e[13] = e[0] * e[9] * e[14] - e[0] * e[10] * e[13] - e[8] * e[1] * e[14] + - e[8] * e[2] * e[13] + e[12] * e[1] * e[10] - e[12] * e[2] * e[9]; + inv.e[13] = e[0] * e[9] * e[14] - e[0] * e[10] * e[13] - e[8] * e[1] * e[14] + e[8] * e[2] * e[13] + + e[12] * e[1] * e[10] - e[12] * e[2] * e[9]; - inv.e[2] = e[1] * e[6] * e[15] - e[1] * e[7] * e[14] - e[5] * e[2] * e[15] + - e[5] * e[3] * e[14] + e[13] * e[2] * e[7] - e[13] * e[3] * e[6]; + inv.e[2] = e[1] * e[6] * e[15] - e[1] * e[7] * e[14] - e[5] * e[2] * e[15] + e[5] * e[3] * e[14] + + e[13] * e[2] * e[7] - e[13] * e[3] * e[6]; - inv.e[6] = -e[0] * e[6] * e[15] + e[0] * e[7] * e[14] + e[4] * e[2] * e[15] - - e[4] * e[3] * e[14] - e[12] * e[2] * e[7] + e[12] * e[3] * e[6]; + inv.e[6] = -e[0] * e[6] * e[15] + e[0] * e[7] * e[14] + e[4] * e[2] * e[15] - e[4] * e[3] * e[14] - + e[12] * e[2] * e[7] + e[12] * e[3] * e[6]; - inv.e[10] = e[0] * e[5] * e[15] - e[0] * e[7] * e[13] - e[4] * e[1] * e[15] + - e[4] * e[3] * e[13] + e[12] * e[1] * e[7] - e[12] * e[3] * e[5]; + inv.e[10] = e[0] * e[5] * e[15] - e[0] * e[7] * e[13] - e[4] * e[1] * e[15] + e[4] * e[3] * e[13] + + e[12] * e[1] * e[7] - e[12] * e[3] * e[5]; - inv.e[14] = -e[0] * e[5] * e[14] + e[0] * e[6] * e[13] + e[4] * e[1] * e[14] - - e[4] * e[2] * e[13] - e[12] * e[1] * e[6] + e[12] * e[2] * e[5]; + inv.e[14] = -e[0] * e[5] * e[14] + e[0] * e[6] * e[13] + e[4] * e[1] * e[14] - e[4] * e[2] * e[13] - + e[12] * e[1] * e[6] + e[12] * e[2] * e[5]; - inv.e[3] = -e[1] * e[6] * e[11] + e[1] * e[7] * e[10] + e[5] * e[2] * e[11] - - e[5] * e[3] * e[10] - e[9] * e[2] * e[7] + e[9] * e[3] * e[6]; + inv.e[3] = -e[1] * e[6] * e[11] + e[1] * e[7] * e[10] + e[5] * e[2] * e[11] - e[5] * e[3] * e[10] - + e[9] * e[2] * e[7] + e[9] * e[3] * e[6]; - inv.e[7] = e[0] * e[6] * e[11] - e[0] * e[7] * e[10] - e[4] * e[2] * e[11] + - e[4] * e[3] * e[10] + e[8] * e[2] * e[7] - e[8] * e[3] * e[6]; + inv.e[7] = e[0] * e[6] * e[11] - e[0] * e[7] * e[10] - e[4] * e[2] * e[11] + e[4] * e[3] * e[10] + + e[8] * e[2] * e[7] - e[8] * e[3] * e[6]; - inv.e[11] = -e[0] * e[5] * e[11] + e[0] * e[7] * e[9] + e[4] * e[1] * e[11] - - e[4] * e[3] * e[9] - e[8] * e[1] * e[7] + e[8] * e[3] * e[5]; + inv.e[11] = -e[0] * e[5] * e[11] + e[0] * e[7] * e[9] + e[4] * e[1] * e[11] - e[4] * e[3] * e[9] - + e[8] * e[1] * e[7] + e[8] * e[3] * e[5]; - inv.e[15] = e[0] * e[5] * e[10] - e[0] * e[6] * e[9] - e[4] * e[1] * e[10] + - e[4] * e[2] * e[9] + e[8] * e[1] * e[6] - e[8] * e[2] * e[5]; + inv.e[15] = e[0] * e[5] * e[10] - e[0] * e[6] * e[9] - e[4] * e[1] * e[10] + e[4] * e[2] * e[9] + + e[8] * e[1] * e[6] - e[8] * e[2] * e[5]; float det = e[0] * inv.e[0] + e[1] * inv.e[4] + e[2] * inv.e[8] + e[3] * inv.e[12]; @@ -438,8 +443,8 @@ namespace love e[8] = mat4elems[10]; } - Matrix3::Matrix3(float x, float y, float angle, float sx, float sy, float ox, float oy, - float kx, float ky) + Matrix3::Matrix3(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, + float ky) { setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); } @@ -509,8 +514,8 @@ namespace love return m; } - void Matrix3::setTransformation(float x, float y, float angle, float sx, float sy, float ox, - float oy, float kx, float ky) + void Matrix3::setTransformation(float x, float y, float angle, float sx, float sy, float ox, float oy, + float kx, float ky) { float c = cosf(angle), s = sinf(angle); // matrix multiplication carried out on paper: diff --git a/source/common/Reference.cpp b/source/common/Reference.cpp new file mode 100644 index 000000000..99e20facd --- /dev/null +++ b/source/common/Reference.cpp @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2006-2024 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. + **/ + +#include "common/Reference.hpp" +#include "common/luax.hpp" + +namespace love +{ + + const char REFERENCE_TABLE_NAME[] = "love-references"; + + Reference::Reference() : pinnedL(nullptr), idx(LUA_REFNIL) + {} + + Reference::Reference(lua_State* L) : pinnedL(nullptr), idx(LUA_REFNIL) + { + ref(L); + } + + Reference::~Reference() + { + unref(); + } + + void Reference::ref(lua_State* L) + { + unref(); // Previously created reference needs to be cleared + pinnedL = luax_getpinnedthread(L); + luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME); + lua_insert(L, -2); // Move reference table behind value. + idx = luaL_ref(L, -2); + lua_pop(L, 1); + } + + void Reference::unref() + { + if (idx != LUA_REFNIL) + { + // We use a pinned thread/coroutine for the Lua state because we know it + // hasn't been garbage collected and is valid, as long as the whole lua + // state is still open. + luax_insist(pinnedL, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME); + luaL_unref(pinnedL, -1, idx); + lua_pop(pinnedL, 1); + idx = LUA_REFNIL; + } + } + + void Reference::push(lua_State* L) + { + if (idx != LUA_REFNIL) + { + luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME); + lua_rawgeti(L, -1, idx); + lua_remove(L, -2); + } + else + lua_pushnil(L); + } + +} // namespace love diff --git a/source/common/float.cpp b/source/common/float.cpp new file mode 100644 index 000000000..9b07078fa --- /dev/null +++ b/source/common/float.cpp @@ -0,0 +1,262 @@ +/** + * Copyright (c) 2006-2024 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. + **/ + +#include "common/float.hpp" + +#include +#include + +namespace love +{ + + // Code from ftp://www.fox-toolkit.org/pub/fasthalffloatconversion.pdf + + static bool halfInitialized = false; + + // tables for half -> float conversions + static uint32_t mantissatable[2048]; + static uint16_t offsettable[64]; + static uint32_t exponenttable[64]; + + // tables for float -> half conversions + static uint16_t basetable[512]; + static uint8_t shifttable[512]; + + static uint32_t convertMantissa(uint32_t i) + { + uint32_t m = i << 13; // Zero pad mantissa bits + uint32_t e = 0; // Zero exponent + + while (!(m & 0x00800000)) // While not normalized + { + e -= 0x00800000; // Decrement exponent (1<<23) + m <<= 1; // Shift mantissa + } + + m &= ~(0x00800000); // Clear leading 1 bit + e += 0x38800000; // Adjust bias ((127-14)<<23) + + return m | e; // Return combined number + } + + void float16Init() + { + if (halfInitialized) + return; + + halfInitialized = true; + + // tables for float16 -> float32 conversions. + + mantissatable[0] = 0; + + for (uint32_t i = 1; i < 1024; i++) + mantissatable[i] = convertMantissa(i); + + for (uint32_t i = 1024; i < 2048; i++) + mantissatable[i] = 0x38000000 + ((i - 1024) << 13); + + exponenttable[0] = 0; + exponenttable[32] = 0x80000000; + + for (uint32_t i = 0; i < 31; i++) + exponenttable[i] = i << 23; + + for (uint32_t i = 33; i < 63; i++) + exponenttable[i] = 0x80000000 + ((i - 32) << 23); + + exponenttable[31] = 0x47800000; + exponenttable[63] = 0xC7800000; + + for (int i = 0; i < 64; i++) + { + if (i == 0 || i == 32) + offsettable[i] = 0; + else + offsettable[i] = 1024; + } + + // tables for float32 -> float16 conversions. + + for (uint32_t i = 0; i < 256; i++) + { + int e = (int)i - 127; + + if (e < -24) // Very small numbers map to zero + { + basetable[i | 0x000] = 0x0000; + basetable[i | 0x100] = 0x8000; + shifttable[i | 0x000] = 24; + shifttable[i | 0x100] = 24; + } + else if (e < -14) // Small numbers map to denorms + { + basetable[i | 0x000] = (0x0400 >> (-e - 14)); + basetable[i | 0x100] = (0x0400 >> (-e - 14)) | 0x8000; + shifttable[i | 0x000] = -e - 1; + shifttable[i | 0x100] = -e - 1; + } + else if (e <= 15) // Normal numbers just lose precision + { + basetable[i | 0x000] = ((e + 15) << 10); + basetable[i | 0x100] = ((e + 15) << 10) | 0x8000; + shifttable[i | 0x000] = 13; + shifttable[i | 0x100] = 13; + } + else if (e < 128) // Large numbers map to Infinity + { + basetable[i | 0x000] = 0x7C00; + basetable[i | 0x100] = 0xFC00; + shifttable[i | 0x000] = 24; + shifttable[i | 0x100] = 24; + } + else // Infinity and NaN's stay Infinity and NaN's + { + basetable[i | 0x000] = 0x7C00; + basetable[i | 0x100] = 0xFC00; + shifttable[i | 0x000] = 13; + shifttable[i | 0x100] = 13; + } + } + } + + static inline uint32_t asuint32(float f) + { + union + { + float f; + uint32_t u; + } conv; + conv.f = f; + return conv.u; + } + + static inline float asfloat32(uint32_t u) + { + union + { + float f; + uint32_t u; + } conv; + conv.u = u; + return conv.f; + } + + float float16to32(float16_t f) + { + return asfloat32(mantissatable[offsettable[f >> 10] + (f & 0x3FF)] + exponenttable[f >> 10]); + } + + float16_t float32to16(float f) + { + uint32_t u = asuint32(f); + return basetable[(u >> 23) & 0x1FF] + ((u & 0x007FFFFF) >> shifttable[(u >> 23) & 0x1FF]); + } + + // Adapted from + // https://stackoverflow.com/questions/41532085/how-to-pack-unpack-11-and-10-bit-floats-in-javascript-for-webgl2 + + float float11to32(float11_t f) + { + uint32_t exponent = f >> 6; + uint32_t mantissa = f & 0x3F; + + if (exponent == 0) + return mantissa == 0 ? 0 : powf(2.0f, -14.0f) * (mantissa / 64.0f); + + if (exponent < 31) + return powf(2.0f, exponent - 15) * (1.0f + mantissa / 64.0f); + + return mantissa == 0 ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + + float11_t float32to11(float f) + { + const uint16_t EXPONENT_BITS = 0x1F; + const uint16_t EXPONENT_SHIFT = 6; + const uint16_t EXPONENT_BIAS = 15; + const uint16_t MANTISSA_BITS = 0x3F; + const uint16_t MANTISSA_SHIFT = (23 - EXPONENT_SHIFT); + const uint16_t MAX_EXPONENT = (EXPONENT_BITS << EXPONENT_SHIFT); + + uint32_t u = asuint32(f); + + if (u & 0x80000000) + return 0; // Negative values go to 0. + + // Map exponent to the range [-127,128] + int32_t exponent = (int32_t)((u >> 23) & 0xFF) - 127; + uint32_t mantissa = u & 0x007FFFFF; + + if (exponent > 15) // Infinity or NaN + return MAX_EXPONENT | (exponent == 128 ? (mantissa & MANTISSA_BITS) : 0); + else if (exponent <= -15) + return 0; + + exponent += EXPONENT_BIAS; + + return ((uint16_t)exponent << EXPONENT_SHIFT) | (mantissa >> MANTISSA_SHIFT); + } + + float float10to32(float10_t f) + { + uint16_t exponent = f >> 5; + uint16_t mantissa = f & 0x1F; + + if (exponent == 0) + return mantissa == 0 ? 0 : powf(2.0f, -14.0f) * (mantissa / 32.0f); + + if (exponent < 31) + return powf(2.0f, exponent - 15) * (1.0f + mantissa / 32.0f); + + return mantissa == 0 ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + + float10_t float32to10(float f) + { + const uint16_t EXPONENT_BITS = 0x1F; + const uint16_t EXPONENT_SHIFT = 5; + const uint16_t EXPONENT_BIAS = 15; + const uint16_t MANTISSA_BITS = 0x1F; + const uint16_t MANTISSA_SHIFT = (23 - EXPONENT_SHIFT); + const uint16_t MAX_EXPONENT = (EXPONENT_BITS << EXPONENT_SHIFT); + + uint32_t u = asuint32(f); + + if (u & 0x80000000) + return 0; // Negative values go to 0. + + // Map exponent to the range [-127,128] + int32_t exponent = (int32_t)((u >> 23) & 0xFF) - 127; + uint32_t mantissa = u & 0x007FFFFF; + + if (exponent > 15) // Infinity or NaN + return MAX_EXPONENT | (exponent == 128 ? (mantissa & MANTISSA_BITS) : 0); + else if (exponent <= -15) + return 0; + + exponent += EXPONENT_BIAS; + + return ((uint16_t)exponent << EXPONENT_SHIFT) | (mantissa >> MANTISSA_SHIFT); + } + +} // namespace love diff --git a/source/common/luax.cpp b/source/common/luax.cpp index cc8245a37..45a5a8fb0 100644 --- a/source/common/luax.cpp +++ b/source/common/luax.cpp @@ -3,6 +3,7 @@ #include "common/Module.hpp" #include "common/Object.hpp" +#include "common/Reference.hpp" #include #include @@ -617,6 +618,25 @@ namespace love lua_pushlstring(L, string, sizeof(void*)); } + bool luax_checkboolflag(lua_State* L, int tableIndex, const char* key) + { + lua_getfield(L, tableIndex, key); + + bool result = false; + + if (lua_type(L, -1) != LUA_TBOOLEAN) + { + auto error = std::format("expected boolean field '{:s}' in table", key); + luaL_argerror(L, tableIndex, error.c_str()); + } + else + result = luax_toboolean(L, -1); + + lua_pop(L, 1); + + return result; + } + bool luax_boolflag(lua_State* L, int index, const char* name, bool default_value) { lua_getfield(L, index, name); @@ -633,6 +653,25 @@ namespace love return result; } + int luax_checkintflag(lua_State* L, int tableIndex, const char* key) + { + lua_getfield(L, tableIndex, key); + + int result = 0; + + if (!lua_isnumber(L, -1)) + { + auto error = std::format("expected integer field '{:s}' in table", key); + luaL_argerror(L, tableIndex, error.c_str()); + } + else + result = luaL_checkinteger(L, -1); + + lua_pop(L, 1); + + return result; + } + int luax_intflag(lua_State* L, int index, const char* name, int default_value) { lua_getfield(L, index, name); @@ -816,11 +855,6 @@ namespace love lua_pop(L, 1); } - lua_Number luax_checknumberclamped01(lua_State* L, int index) - { - return std::clamp((float)luaL_checknumber(L, index), 0.0f, 1.0f); - } - // #endregion // #region Registry @@ -956,5 +990,17 @@ namespace love return 1; } + Reference* luax_refif(lua_State* L, int type) + { + Reference* r = nullptr; + + // Create a reference only if the test succeeds. + if (lua_type(L, -1) == type) + r = new Reference(L); + else // Pop the value manually if it fails (done by Reference if it succeeds). + lua_pop(L, 1); + + return r; + } // #endregion } // namespace love diff --git a/source/common/pixelformat.cpp b/source/common/pixelformat.cpp index 5af2e6ed1..ffab5155b 100644 --- a/source/common/pixelformat.cpp +++ b/source/common/pixelformat.cpp @@ -30,6 +30,7 @@ namespace love { 2, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_RG8_UNORM { 2, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_SINT }, // PIXELFORMAT_RG8_INT { 2, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UINT }, // PIXELFORMAT_RG8_UINT + { 2, 1, 1, 1, true, false, false, false, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_A4_UNORM { 2, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_LA8_UNORM { 2, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_RG16_UNORM { 2, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RG16_FLOAT @@ -136,129 +137,6 @@ namespace love static constexpr size_t FORMAT_STRUCT_SIZE = sizeof(PixelFormatInfo); static_assert(FORMATS_SIZE / FORMAT_STRUCT_SIZE == PIXELFORMAT_MAX_ENUM, "Update the formatInfo array when adding or removing a PixelFormat"); - - STRINGMAP_DECLARE(PixelFormats, PixelFormat, - { "unknown", PIXELFORMAT_UNKNOWN }, - - { "normal", PIXELFORMAT_NORMAL }, - { "hdr", PIXELFORMAT_HDR }, - - { "r8", PIXELFORMAT_R8_UNORM }, - { "r8i", PIXELFORMAT_R8_INT }, - { "r8ui", PIXELFORMAT_R8_UINT }, - { "r16", PIXELFORMAT_R16_UNORM }, - { "r16f", PIXELFORMAT_R16_FLOAT }, - { "r16i", PIXELFORMAT_R16_INT }, - { "r16ui", PIXELFORMAT_R16_UINT }, - { "r32f", PIXELFORMAT_R32_FLOAT }, - { "r32i", PIXELFORMAT_R32_INT }, - { "r32ui", PIXELFORMAT_R32_UINT }, - - { "rg8", PIXELFORMAT_RG8_UNORM }, - { "rg8i", PIXELFORMAT_RG8_INT }, - { "rg8ui", PIXELFORMAT_RG8_UINT }, - { "la8", PIXELFORMAT_LA8_UNORM }, - { "rg16", PIXELFORMAT_RG16_UNORM }, - { "rg16f", PIXELFORMAT_RG16_FLOAT }, - { "rg16i", PIXELFORMAT_RG16_INT }, - { "rg16ui", PIXELFORMAT_RG16_UINT }, - { "rg32f", PIXELFORMAT_RG32_FLOAT }, - { "rg32i", PIXELFORMAT_RG32_INT }, - { "rg32ui", PIXELFORMAT_RG32_UINT }, - - { "rgba8", PIXELFORMAT_RGBA8_UNORM }, - { "srgba8", PIXELFORMAT_RGBA8_sRGB }, - { "bgra8", PIXELFORMAT_BGRA8_UNORM }, - { "bgra8srgb", PIXELFORMAT_BGRA8_sRGB }, - { "rgba8i", PIXELFORMAT_RGBA8_INT }, - { "rgba8ui", PIXELFORMAT_RGBA8_UINT }, - { "rgba16", PIXELFORMAT_RGBA16_UNORM }, - { "rgba16f", PIXELFORMAT_RGBA16_FLOAT }, - { "rgba16i", PIXELFORMAT_RGBA16_INT }, - { "rgba16ui", PIXELFORMAT_RGBA16_UINT }, - { "rgba32f", PIXELFORMAT_RGBA32_FLOAT }, - { "rgba32i", PIXELFORMAT_RGBA32_INT }, - { "rgba32ui", PIXELFORMAT_RGBA32_UINT }, - - { "rgba4", PIXELFORMAT_RGBA4_UNORM }, - { "rgb5a1", PIXELFORMAT_RGB5A1_UNORM }, - { "rgb565", PIXELFORMAT_RGB565_UNORM }, - { "rgb10a2", PIXELFORMAT_RGB10A2_UNORM }, - { "rg11b10f", PIXELFORMAT_RG11B10_FLOAT }, - - { "stencil8", PIXELFORMAT_STENCIL8 }, - { "depth16", PIXELFORMAT_DEPTH16_UNORM }, - { "depth24", PIXELFORMAT_DEPTH24_UNORM }, - { "depth32f", PIXELFORMAT_DEPTH32_FLOAT }, - { "depth24stencil8", PIXELFORMAT_DEPTH24_UNORM_STENCIL8 }, - { "depth32fstencil8", PIXELFORMAT_DEPTH32_FLOAT_STENCIL8 }, - - { "DXT1", PIXELFORMAT_DXT1_UNORM }, - { "DXT1srgb", PIXELFORMAT_DXT1_sRGB }, - { "DXT3", PIXELFORMAT_DXT3_UNORM }, - { "DXT3srgb", PIXELFORMAT_DXT3_sRGB }, - { "DXT5", PIXELFORMAT_DXT5_UNORM }, - { "DXT5srgb", PIXELFORMAT_DXT5_sRGB }, - { "BC4", PIXELFORMAT_BC4_UNORM }, - { "BC4s", PIXELFORMAT_BC4_SNORM }, - { "BC5", PIXELFORMAT_BC5_UNORM }, - { "BC5s", PIXELFORMAT_BC5_SNORM }, - { "BC6h", PIXELFORMAT_BC6H_UFLOAT }, - { "BC6hs", PIXELFORMAT_BC6H_FLOAT }, - { "BC7", PIXELFORMAT_BC7_UNORM }, - { "BC7srgb", PIXELFORMAT_BC7_sRGB }, - - { "PVR1rgb2", PIXELFORMAT_PVR1_RGB2_UNORM }, - { "PVR1rgb2srgb", PIXELFORMAT_PVR1_RGB2_sRGB }, - { "PVR1rgb4", PIXELFORMAT_PVR1_RGB4_UNORM }, - { "PVR1rgb4srgb", PIXELFORMAT_PVR1_RGB4_sRGB }, - { "PVR1rgba2", PIXELFORMAT_PVR1_RGBA2_UNORM }, - { "PVR1rgba2srgb", PIXELFORMAT_PVR1_RGBA2_sRGB }, - { "PVR1rgba4", PIXELFORMAT_PVR1_RGBA4_UNORM }, - { "PVR1rgba4srgb", PIXELFORMAT_PVR1_RGBA4_sRGB }, - - { "ETC1", PIXELFORMAT_ETC1_UNORM }, - { "ETC2rgb", PIXELFORMAT_ETC2_RGB_UNORM }, - { "ETC2srgb", PIXELFORMAT_ETC2_RGB_sRGB }, - { "ETC2rgba", PIXELFORMAT_ETC2_RGBA_UNORM }, - { "ETC2srgba", PIXELFORMAT_ETC2_RGBA_sRGB }, - { "ETC2rgba1", PIXELFORMAT_ETC2_RGBA1_UNORM }, - { "ETC2srgba1", PIXELFORMAT_ETC2_RGBA1_sRGB }, - { "EACr", PIXELFORMAT_EAC_R_UNORM }, - { "EACrs", PIXELFORMAT_EAC_R_SNORM }, - { "EACrg", PIXELFORMAT_EAC_RG_UNORM }, - { "EACrgs", PIXELFORMAT_EAC_RG_SNORM }, - - { "ASTC4x4", PIXELFORMAT_ASTC_4x4_UNORM }, - { "ASTC5x4", PIXELFORMAT_ASTC_5x4_UNORM }, - { "ASTC5x5", PIXELFORMAT_ASTC_5x5_UNORM }, - { "ASTC6x5", PIXELFORMAT_ASTC_6x5_UNORM }, - { "ASTC6x6", PIXELFORMAT_ASTC_6x6_UNORM }, - { "ASTC8x5", PIXELFORMAT_ASTC_8x5_UNORM }, - { "ASTC8x6", PIXELFORMAT_ASTC_8x6_UNORM }, - { "ASTC8x8", PIXELFORMAT_ASTC_8x8_UNORM }, - { "ASTC10x5", PIXELFORMAT_ASTC_10x5_UNORM }, - { "ASTC10x6", PIXELFORMAT_ASTC_10x6_UNORM }, - { "ASTC10x8", PIXELFORMAT_ASTC_10x8_UNORM }, - { "ASTC10x10", PIXELFORMAT_ASTC_10x10_UNORM }, - { "ASTC12x10", PIXELFORMAT_ASTC_12x10_UNORM }, - { "ASTC12x12", PIXELFORMAT_ASTC_12x12_UNORM }, - { "ASTC4x4srgb", PIXELFORMAT_ASTC_4x4_sRGB }, - { "ASTC5x4srgb", PIXELFORMAT_ASTC_5x4_sRGB }, - { "ASTC5x5srgb", PIXELFORMAT_ASTC_5x5_sRGB }, - { "ASTC6x5srgb", PIXELFORMAT_ASTC_6x5_sRGB }, - { "ASTC6x6srgb", PIXELFORMAT_ASTC_6x6_sRGB }, - { "ASTC8x5srgb", PIXELFORMAT_ASTC_8x5_sRGB }, - { "ASTC8x6srgb", PIXELFORMAT_ASTC_8x6_sRGB }, - { "ASTC8x8srgb", PIXELFORMAT_ASTC_8x8_sRGB }, - { "ASTC10x5srgb", PIXELFORMAT_ASTC_10x5_sRGB }, - { "ASTC10x6srgb", PIXELFORMAT_ASTC_10x6_sRGB }, - { "ASTC10x8srgb", PIXELFORMAT_ASTC_10x8_sRGB }, - { "ASTC10x10srgb", PIXELFORMAT_ASTC_10x10_sRGB }, - { "ASTC12x10srgb", PIXELFORMAT_ASTC_12x10_sRGB }, - { "ASTC12x12srgb", PIXELFORMAT_ASTC_12x12_sRGB }, - ); - static_assert(PixelFormats.size() == (size_t)PIXELFORMAT_MAX_ENUM, "Pixel format string map is missing entries!"); // clang-format on diff --git a/source/main.cpp b/source/main.cpp index 88cddb2ac..b03153f7b 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -98,7 +98,8 @@ static DoneAction runLove(char** argv, int argc, int& result, love::Variant& res { if (lua_type(L, index) == LUA_TSTRING && strcmp(lua_tostring(L, index), "restart") == 0) action = DONE_RESTART; - else if (lua_isnumber(L, index)) + + if (lua_isnumber(L, index)) result = lua_tonumber(L, index); if (index < lua_gettop(L)) diff --git a/source/modules/audio/Source.cpp b/source/modules/audio/Source.cpp index f67a25cc9..5445181f8 100644 --- a/source/modules/audio/Source.cpp +++ b/source/modules/audio/Source.cpp @@ -15,39 +15,35 @@ namespace love { public: InvalidFormatException(int channels, int bitdepth) : - Exception("{:d}-channel Sources with {:d} bits per sample are not supported.", channels, - bitdepth) + Exception(E_INVALID_AUDIO_FORMAT, channels, bitdepth) {} }; class QueueFormatMismatchException : public love::Exception { public: - QueueFormatMismatchException() : - Exception("Queued sound data must have same format as sound Source.") + QueueFormatMismatchException() : Exception(E_QUEUE_FORMAT_MISMATCH) {} }; class QueueTypeMismatchException : public love::Exception { public: - QueueTypeMismatchException() : - Exception("Only queueable Sources can be queued with sound data.") + QueueTypeMismatchException() : Exception(E_QUEUE_TYPE_MISMATCH) {} }; class QueueMalformedLengthException : public love::Exception { public: - QueueMalformedLengthException(int bytes) : - Exception("Data length must be a multiple of sample size ({:d} bytes).", bytes) + QueueMalformedLengthException(int bytes) : Exception(E_QUEUE_LENGTH_MALFORMED, bytes) {} }; class QueueLoopingException : public love::Exception { public: - QueueLoopingException() : Exception("Queueable Sources can not be looped.") + QueueLoopingException() : Exception(E_QUEUE_CANNOT_BE_LOOPED) {} }; @@ -78,10 +74,7 @@ namespace love // clang-format off for (int index = 0; index < this->buffers; index++) - { - std::printf("Creating buffer %d\n", index); this->streamBuffers.push_back(DigitalSound::getInstance().createBuffer(decoder->getSize(), this->channels)); - } // clang-format on } @@ -105,6 +98,7 @@ namespace love } Source::Source(const Source& other) : + Object(other), sourceType(other.sourceType), pool(other.pool), valid(false), @@ -492,9 +486,9 @@ namespace love { case TYPE_STATIC: { - const auto size = this->staticBuffer->getSize(); - const auto nsamples = ((size / this->channels) / (this->bitDepth / 8)) - - (this->offsetSamples / this->channels); + const auto size = this->staticBuffer->getSize(); + const auto nsamples = + ((size / this->channels) / (this->bitDepth / 8)) - (this->offsetSamples / this->channels); return (unit == UNIT_SAMPLES) ? nsamples : (nsamples / this->sampleRate); } @@ -540,7 +534,7 @@ namespace love void Source::setLooping(bool looping) { - if (this->sourceType == TYPE_STREAM) + if (this->sourceType == TYPE_QUEUE) throw QueueLoopingException(); if (this->valid && this->sourceType == TYPE_STATIC) @@ -620,7 +614,7 @@ namespace love if (decoded > 0) { - DigitalSound::getInstance().setLooping(buffer, this->looping); + // DigitalSound::getInstance().setLooping(buffer, this->looping); const int samples = int((decoded / this->channels) / (this->bitDepth / 8)); DigitalSound::getInstance().prepare(buffer, decoder->getBuffer(), decoded, samples); diff --git a/source/modules/audio/wrap_Source.cpp b/source/modules/audio/wrap_Source.cpp index 62d372eb7..e3619bd9d 100644 --- a/source/modules/audio/wrap_Source.cpp +++ b/source/modules/audio/wrap_Source.cpp @@ -140,7 +140,7 @@ int Wrap_Source::setLooping(lua_State* L) auto* self = luax_checksource(L, 1); bool looping = luax_checkboolean(L, 2); - self->setLooping(looping); + luax_catchexcept(L, [&]() { self->setLooping(looping); }); return 0; } diff --git a/source/modules/data/ByteData.cpp b/source/modules/data/ByteData.cpp index 86a01e363..bf974998b 100644 --- a/source/modules/data/ByteData.cpp +++ b/source/modules/data/ByteData.cpp @@ -52,7 +52,7 @@ namespace love void ByteData::create() { if (this->size == 0) - throw love::Exception("ByteData size must be greater than 0."); + throw love::Exception(E_DATA_SIZE_MUST_BE_POSITIVE); try { diff --git a/source/modules/data/DataModule.cpp b/source/modules/data/DataModule.cpp index 419686e64..2a74aab6a 100644 --- a/source/modules/data/DataModule.cpp +++ b/source/modules/data/DataModule.cpp @@ -94,8 +94,7 @@ namespace love { namespace data { - CompressedData* compress(Compressor::Format format, const char* bytes, size_t size, - int level) + CompressedData* compress(Compressor::Format format, const char* bytes, size_t size, int level) { auto* compressor = Compressor::getCompressor(format); @@ -103,8 +102,7 @@ namespace love throw love::Exception("Invalid compression format."); size_t compressedSize = 0; - auto* compressedBytes = - compressor->compress(format, bytes, size, level, compressedSize); + auto* compressedBytes = compressor->compress(format, bytes, size, level, compressedSize); CompressedData* data = nullptr; @@ -135,15 +133,15 @@ namespace love { size_t rawSize = data->getDecompressedSize(); - auto* bytes = decompress(data->getFormat(), (const char*)data->getData(), - data->getSize(), rawSize); + auto* bytes = + decompress(data->getFormat(), (const char*)data->getData(), data->getSize(), rawSize); decompressedSize = rawSize; return bytes; } - char* encode(EncodeFormat format, const void* source, size_t size, - size_t& destinationLength, size_t lineLength) + char* encode(EncodeFormat format, const void* source, size_t size, size_t& destinationLength, + size_t lineLength) { switch (format) { @@ -155,8 +153,7 @@ namespace love } } - char* decode(EncodeFormat format, const char* source, size_t size, - size_t& destinationLength) + char* decode(EncodeFormat format, const char* source, size_t size, size_t& destinationLength) { switch (format) { diff --git a/source/modules/data/DataView.cpp b/source/modules/data/DataView.cpp index 5c5ad121f..bed691bb7 100644 --- a/source/modules/data/DataView.cpp +++ b/source/modules/data/DataView.cpp @@ -7,10 +7,7 @@ namespace love { Type DataView::type("DataView", &Data::type); - DataView::DataView(Data* data, size_t offset, size_t size) : - data(data), - offset(offset), - size(size) + DataView::DataView(Data* data, size_t offset, size_t size) : data(data), offset(offset), size(size) { if (offset >= data->getSize() || size > data->getSize() || offset > data->getSize() - size) throw love::Exception(E_DATAVIEW_OFFSET_AND_SIZE); @@ -19,10 +16,7 @@ namespace love throw love::Exception(E_DATAVIEW_INVALID_SIZE); } - DataView::DataView(const DataView& other) : - data(other.data), - offset(other.offset), - size(other.size) + DataView::DataView(const DataView& other) : data(other.data), offset(other.offset), size(other.size) {} DataView::~DataView() diff --git a/source/modules/event/Event.cpp b/source/modules/event/Event.cpp index c48b19f8d..33dd2d742 100644 --- a/source/modules/event/Event.cpp +++ b/source/modules/event/Event.cpp @@ -53,6 +53,10 @@ namespace love result = new Message("focus", args); break; } + case SUBTYPE_RESIZE: + args.emplace_back((double)event.resize.width); + args.emplace_back((double)event.resize.height); + result = new Message("resize", args); default: break; } @@ -175,7 +179,7 @@ namespace love } case SUBTYPE_GAMEPADAXIS: { - if (!Joystick::getConstant((Joystick::GamepadAxis)event.gamepadAxis.axis, name)) + if (!JoystickBase::getConstant((JoystickBase::GamepadAxis)event.gamepadAxis.axis, name)) break; joystick = module->getJoystickFromID(event.gamepadAxis.which); @@ -194,7 +198,7 @@ namespace love case SUBTYPE_GAMEPADUP: { // clang-format off - if (!Joystick::getConstant((Joystick::GamepadButton)event.gamepadButton.button, name)) + if (!JoystickBase::getConstant((JoystickBase::GamepadButton)event.gamepadButton.button, name)) break; // clang-format on @@ -256,6 +260,9 @@ namespace love case TYPE_GAMEPAD: message = convertJoystickEvent(event, args); break; + case TYPE_WINDOW: + message = convertWindowEvent(event, args); + break; default: break; } diff --git a/source/modules/filesystem/physfs/File.cpp b/source/modules/filesystem/physfs/File.cpp index cff8fd85f..c243c44c0 100644 --- a/source/modules/filesystem/physfs/File.cpp +++ b/source/modules/filesystem/physfs/File.cpp @@ -6,8 +6,6 @@ #include #include -#include - namespace love { static bool setupWriteDirectory() diff --git a/source/modules/filesystem/wrap_File.cpp b/source/modules/filesystem/wrap_File.cpp index ae04587ed..0fc60368a 100644 --- a/source/modules/filesystem/wrap_File.cpp +++ b/source/modules/filesystem/wrap_File.cpp @@ -6,8 +6,6 @@ #include "modules/data/wrap_DataModule.hpp" -#include - using namespace love; int Wrap_File::getSize(lua_State* L) diff --git a/source/modules/filesystem/wrap_Filesystem.cpp b/source/modules/filesystem/wrap_Filesystem.cpp index 9e76fc759..553077bad 100644 --- a/source/modules/filesystem/wrap_Filesystem.cpp +++ b/source/modules/filesystem/wrap_Filesystem.cpp @@ -41,6 +41,8 @@ static std::filesystem::path translatePath(const std::filesystem::path& input) return path.replace_extension(".t3x"); else if (std::find(fonts.begin(), fonts.end(), input.extension()) != fonts.end()) return path.replace_extension(".bcfnt"); + + return path; } int Wrap_Filesystem::init(lua_State* L) @@ -332,8 +334,8 @@ int Wrap_Filesystem::createDirectory(lua_State* L) int Wrap_Filesystem::remove(lua_State* L) { - const char* path = luaL_checkstring(L, 1); - luax_pushboolean(L, instance()->remove(path)); + auto path = translatePath(luaL_checkstring(L, 1)); + luax_pushboolean(L, instance()->remove(path.c_str())); return 1; } @@ -349,17 +351,17 @@ int Wrap_Filesystem::read(lua_State* L) start = 2; } - const char* filename = luaL_checkstring(L, start + 0); - int64_t length = luaL_optinteger(L, start + 1, -1); + auto filename = translatePath(luaL_checkstring(L, start + 0)); + int64_t length = luaL_optinteger(L, start + 1, -1); FileData* data = nullptr; try { if (length >= 0) - data = instance()->read(filename, length); + data = instance()->read(filename.c_str(), length); else - data = instance()->read(filename); + data = instance()->read(filename.c_str()); } catch (love::Exception& e) { @@ -469,8 +471,8 @@ int Wrap_Filesystem::lines(lua_State* L) int Wrap_Filesystem::exists(lua_State* L) { - const char* filename = luaL_checkstring(L, 1); - luax_pushboolean(L, instance()->exists(filename)); + auto filename = translatePath(luaL_checkstring(L, 1)); + luax_pushboolean(L, instance()->exists(filename.c_str())); return 1; } @@ -532,7 +534,7 @@ int Wrap_Filesystem::load(lua_State* L) int Wrap_Filesystem::getInfo(lua_State* L) { - const char* filepath = luaL_checkstring(L, 1); + auto filepath = translatePath(luaL_checkstring(L, 1)); Filesystem::Info info {}; int start = 2; @@ -547,7 +549,7 @@ int Wrap_Filesystem::getInfo(lua_State* L) start++; } - if (instance()->getInfo(filepath, info)) + if (instance()->getInfo(filepath.c_str(), info)) { if (filterType != Filesystem::FILETYPE_MAX_ENUM && info.type != filterType) { @@ -768,11 +770,11 @@ namespace love if (lua_isstring(L, index)) { - const char* filename = luaL_checkstring(L, index); + auto filename = translatePath(luaL_checkstring(L, index)); try { - result = instance()->openFile(filename, File::MODE_CLOSED); + result = instance()->openFile(filename.c_str(), File::MODE_CLOSED); } catch (love::Exception& e) { diff --git a/source/modules/font/Font.cpp b/source/modules/font/Font.cpp new file mode 100644 index 000000000..f70b53205 --- /dev/null +++ b/source/modules/font/Font.cpp @@ -0,0 +1,32 @@ +#include "modules/font/Font.tcc" + +#include "utf8.h" + +namespace love +{ + Rasterizer* FontModuleBase::newTrueTypeRasterizer(int size, const Rasterizer::Settings& settings) const + { + return this->newTrueTypeRasterizer(this->defaultFontData.get(), size, settings); + } + + GlyphData* FontModuleBase::newGlyphData(Rasterizer* rasterizer, const std::string& text) const + { + uint32_t codepoint = 0; + + try + { + codepoint = utf8::peek_next(text.begin(), text.end()); + } + catch (utf8::exception& e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return rasterizer->getGlyphData(codepoint); + } + + GlyphData* FontModuleBase::newGlyphData(Rasterizer* rasterizer, uint32_t codepoint) const + { + return rasterizer->getGlyphData(codepoint); + } +} // namespace love diff --git a/source/modules/font/GenericShaper.cpp b/source/modules/font/GenericShaper.cpp new file mode 100644 index 000000000..4cea57dbf --- /dev/null +++ b/source/modules/font/GenericShaper.cpp @@ -0,0 +1,197 @@ +#include "common/Optional.hpp" + +#include "modules/font/GenericShaper.hpp" +#include "modules/font/Rasterizer.hpp" + +namespace love +{ + GenericShaper::GenericShaper(Rasterizer* rasterizer) : TextShaper(rasterizer) + {} + + 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.codepoints.size()); + + const auto dataType = this->rasterizers[0]->getDataType(); + + switch (dataType) + { + default: + case Rasterizer::DATA_TRUETYPE: + offset.y += this->getBaseline(); + break; + case Rasterizer::DATA_BCFNT: + offset.y += -this->getBaseline(); + break; + } + + // Spacing counter and newline handling. + Vector2 curpos = offset; + + int maxwidth = 0; + uint32_t 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_t g = codepoints.codepoints[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.codepoints.size()); + + uint32_t prevglyph = 0; + + float w = 0.0f; + float outwidth = 0.0f; + float widthbeforelastspace = 0.0f; + int firstindexafterspace = -1; + + for (int i = (int)range.getMin(); i <= (int)range.getMax(); i++) + { + uint32_t g = codepoints.codepoints[i]; + + if (g == '\r') + { + prevglyph = g; + continue; + } + + float newwidth = w + getKerning(prevglyph, g) + getGlyphAdvance(g); + + // Don't count trailing spaces in the output width. + if (isWhitespace(g)) + { + if (!isWhitespace(prevglyph)) + widthbeforelastspace = w; + } + else + { + if (isWhitespace(prevglyph)) + firstindexafterspace = i; + + // Only wrap when there's a non-space character. + if (newwidth > wraplimit) + { + // If this is the first character, wrap from the next one instead of this one. + int wrapindex = i > (int)range.first ? i : (int)range.first + 1; + + // Rewind to after the last seen space when wrapping. + if (firstindexafterspace != -1) + { + wrapindex = firstindexafterspace; + outwidth = widthbeforelastspace; + } + + if (width) + *width = outwidth; + + return wrapindex; + } + + outwidth = newwidth; + } + + w = newwidth; + prevglyph = g; + } + + if (width) + *width = outwidth; + + // There wasn't any wrap in the middle of the range. + return range.last + 1; + } +} // namespace love diff --git a/source/modules/font/GlyphData.cpp b/source/modules/font/GlyphData.cpp new file mode 100644 index 000000000..efcd123cc --- /dev/null +++ b/source/modules/font/GlyphData.cpp @@ -0,0 +1,95 @@ +#include "modules/font/GlyphData.hpp" +#include "common/Console.hpp" + +#include "utf8.h" + +namespace love +{ + Type GlyphData::type("GlyphData", &Data::type); + + GlyphData::GlyphData(uint32_t glyph, GlyphMetrics& metrics, PixelFormat format) : + glyph(glyph), + metrics(metrics), + data(nullptr), + format(format) + { + if (this->format != PIXELFORMAT_LA8_UNORM && this->format != PIXELFORMAT_RGBA8_UNORM) + throw love::Exception("Invalid GlyphData pixel format."); + + if constexpr (!Console::is(Console::CTR)) + { + if (this->metrics.width > 0 && this->metrics.height > 0) + this->data = new uint8_t[metrics.width * metrics.height * getPixelSize()]; + } + } + + GlyphData::GlyphData(uint32_t glyph, GlyphMetrics& metrics, PixelFormat format, GlyphSheet sheet) : + glyph(glyph), + metrics(metrics), + data(nullptr), + format(format), + sheet(sheet) + {} + + GlyphData::GlyphData(const GlyphData& other) : + glyph(other.glyph), + metrics(other.metrics), + data(nullptr), + format(other.format) + { + if constexpr (!Console::is(Console::CTR)) + { + this->data = new uint8_t[metrics.width * metrics.height * getPixelSize()]; + std::memcpy(this->data, other.data, other.getSize()); + } + } + + GlyphData::~GlyphData() + { + delete[] this->data; + } + + GlyphData* GlyphData::clone() const + { + return new GlyphData(*this); + } + + size_t GlyphData::getPixelSize() const + { + return getPixelFormatBlockSize(this->format); + } + + void* GlyphData::getData(int x, int y) const + { + if (this->data == nullptr) + return nullptr; + + return this->data + (y * this->getWidth() + x) * getPixelSize(); + } + + size_t GlyphData::getSize() const + { + return size_t(this->getWidth() * this->getHeight()) * getPixelSize(); + } + + std::string GlyphData::getGlyphString() const + { + char buffer[5] { 0 }; + ptrdiff_t length = 0; + + try + { + char* end = utf8::append(glyph, buffer); + length = end - buffer; + } + catch (utf8::exception& e) + { + throw love::Exception("UTF-8 decoding error: {:s}", e.what()); + } + + if (length < 0) + return std::string {}; + + return std::string(buffer, length); + } +} // namespace love diff --git a/source/modules/font/Rasterizer.cpp b/source/modules/font/Rasterizer.cpp new file mode 100644 index 000000000..6ab974a1e --- /dev/null +++ b/source/modules/font/Rasterizer.cpp @@ -0,0 +1,63 @@ +#include "modules/font/Rasterizer.hpp" + +#include "utf8.h" + +namespace love +{ + Type Rasterizer::type("Rasterizer", &Object::type); + + Rasterizer::~Rasterizer() + {} + + GlyphData* Rasterizer::getGlyphData(uint32_t glyph) const + { + return this->getGlyphDataForIndex(this->getGlyphIndex(glyph)); + } + + GlyphData* Rasterizer::getGlyphData(const std::string& text) const + { + uint32_t codepoint = 0; + + try + { + codepoint = utf8::peek_next(text.begin(), text.end()); + } + catch (utf8::exception& e) + { + throw love::Exception("UTF-8 decoding error: {:s}", e.what()); + } + + return this->getGlyphData(codepoint); + } + + bool Rasterizer::hasGlyphs(const std::string& text) const + { + if (text.size() == 0) + return false; + + try + { + utf8::iterator it(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (it != end) + { + uint32_t codepoint = *it++; + + if (!this->hasGlyph(codepoint)) + return false; + } + } + catch (utf8::exception& e) + { + throw love::Exception("UTF-8 decoding error: {:s}", e.what()); + } + + return true; + } + + float Rasterizer::getKerning(uint32_t, uint32_t) const + { + return 0.0f; + } +} // namespace love diff --git a/source/modules/font/TextShaper.cpp b/source/modules/font/TextShaper.cpp new file mode 100644 index 000000000..5ba56cb46 --- /dev/null +++ b/source/modules/font/TextShaper.cpp @@ -0,0 +1,351 @@ +#include "common/Exception.hpp" + +#include "modules/font/Rasterizer.hpp" +#include "modules/font/TextShaper.hpp" + +#include "utf8.h" + +namespace love +{ + 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_t 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.codepoints.reserve(strs[0].string.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.string.size() == 0) + continue; + + IndexedColor c = { cstr.color, (int)codepoints.codepoints.size() }; + codepoints.colors.push_back(c); + + getCodepointsFromString(cstr.string, codepoints.codepoints); + } + + if (codepoints.colors.size() == 1) + { + IndexedColor c = codepoints.colors[0]; + + if (c.index == 0 && c.color == Color(1.0f, 1.0f, 1.0f, 1.0f)) + codepoints.colors.pop_back(); + } + } + + Type TextShaper::type("TextShaper", &Object::type); + + TextShaper::TextShaper(Rasterizer* rasterizer) : + rasterizers { rasterizer }, + dpiScales { rasterizer->getDPIScale() }, + height(std::floor(rasterizer->getHeight() / rasterizer->getDPIScale() + 0.5f)), + lineHeight(1), + useSpacesForTab(false) + { + if (!rasterizer->hasGlyph('\t')) + this->useSpacesForTab = true; + } + + TextShaper::~TextShaper() + {} + + int TextShaper::getAscent() const + { + return std::floor(this->rasterizers[0]->getAscent() / rasterizers[0]->getDPIScale() + 0.5f); + } + + int TextShaper::getDescent() const + { + return std::floor(this->rasterizers[0]->getDescent() / rasterizers[0]->getDPIScale() + 0.5f); + } + + float TextShaper::getBaseline() const + { + float ascent = this->getAscent(); + + if (ascent != 0.0f) + return ascent; + else if (rasterizers[0]->getDataType() == Rasterizer::DATA_TRUETYPE) + return std::floor(this->getHeight() / 1.25f + 0.5f); + else + return 0.0f; + } + + bool TextShaper::hasGlyph(uint32_t glyph) const + { + for (const auto& rasterizer : this->rasterizers) + { + if (rasterizer->hasGlyph(glyph)) + return true; + } + + return false; + } + + bool TextShaper::hasGlyphs(const std::string& text) const + { + if (text.size() == 0) + return false; + + try + { + utf8::iterator it(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (it != end) + { + uint32_t glyph = *it++; + + if (!this->hasGlyph(glyph)) + return false; + } + } + catch (utf8::exception& e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return true; + } + + float TextShaper::getKerning(uint32_t leftglyph, uint32_t rightglyph) + { + if (this->rasterizers[0]->getDataType() == Rasterizer::DATA_BCFNT) + return 0.0f; + + uint64_t packedglyphs = ((uint64_t)leftglyph << 32) | (uint64_t)rightglyph; + + const auto it = this->kernings.find(packedglyphs); + if (it != this->kernings.end()) + return it->second; + + float result = 0.0f; + bool found = false; + + for (const auto& r : rasterizers) + { + if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) + { + found = true; + result = std::floor(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f); + break; + } + } + + if (!found) + { + auto kerning = rasterizers[0]->getKerning(leftglyph, rightglyph); + result = std::floor(kerning / this->rasterizers[0]->getDPIScale() + 0.5f); + } + + this->kernings[packedglyphs] = result; + return result; + } + + float TextShaper::getKerning(const std::string& leftchar, const std::string& rightchar) + { + uint32_t left = 0; + uint32_t 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 this->getKerning(left, right); + } + + float TextShaper::getGlyphAdvance(uint32_t glyph, GlyphIndex* glyphindex) + { + const auto it = this->glyphAdvances.find(glyph); + if (it != this->glyphAdvances.end()) + { + if (glyphindex) + *glyphindex = it->second.second; + + return it->second.first; + } + + int rasterizerIndex = 0; + uint32_t realGlyph = glyph; + + if (glyph == '\t' && isUsingSpacesForTab()) + realGlyph = ' '; + + for (size_t i = 0; i < this->rasterizers.size(); i++) + { + if (this->rasterizers[i]->hasGlyph(realGlyph)) + { + rasterizerIndex = (int)i; + break; + } + } + + const auto& r = this->rasterizers[rasterizerIndex]; + int advance = floorf(r->getGlyphSpacing(realGlyph) / r->getDPIScale() + 0.5f); + + if (glyph == '\t' && realGlyph == ' ') + advance *= SPACES_PER_TAB; + + GlyphIndex glyphi = { r->getGlyphIndex(realGlyph), rasterizerIndex }; + + this->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.codepoints); + + TextInfo info {}; + this->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.codepoints.size(); i++) + { + if (codepoints.codepoints[i] == '\n') + return i; + } + + return codepoints.codepoints.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.codepoints.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 = this->computeWordWrapIndex(codepoints, r, wraplimit, &width); + + if (wrapindex > (int)i) + { + r = Range(i, (size_t)wrapindex - i); + i = (size_t)wrapindex; + } + 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 codepoints; + getCodepointsFromString(text, codepoints); + + std::vector codepointranges; + getWrap(codepoints, 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(codepoints.codepoints[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. + this->kernings.clear(); + this->glyphAdvances.clear(); + + this->rasterizers.resize(1); + this->dpiScales.resize(1); + + for (Rasterizer* r : fallbacks) + { + this->rasterizers.push_back(r); + this->dpiScales.push_back(r->getDPIScale()); + } + } +} // namespace love diff --git a/source/modules/font/freetype/Font.cpp b/source/modules/font/freetype/Font.cpp new file mode 100644 index 000000000..029a14d79 --- /dev/null +++ b/source/modules/font/freetype/Font.cpp @@ -0,0 +1,76 @@ +#include "modules/font/freetype/Font.hpp" + +#include "modules/font/freetype/TrueTypeRasterizer.hpp" +#include "modules/window/Window.hpp" + +#include + +namespace love +{ +#if defined(__SWITCH__) + ByteData* FontModule::loadSystemFontByType(SystemFontType type = PlSharedFontType_Standard) + { + PlFontData data {}; + if (R_FAILED(plGetSharedFontByType(&data, type))) + throw love::Exception("Failed to load Shared Font {:d}", (int)type); + + std::string_view name {}; + FontModule::getConstant(type, name); + + if (data.address == nullptr) + throw love::Exception("Error loading system font '{:s}'", name); + + // create a copy of the data + return new ByteData(data.address, data.size, false); + } +#elif defined(__WIIU__) + ByteData* FontModule::loadSystemFontByType(SystemFontType type = OS_SHAREDDATATYPE_FONT_STANDARD) + { + void* data = nullptr; + size_t size = 0; + + std::string_view name {}; + FontModule::getConstant(type, name); + + if (!OSGetSharedData(type, 0, &data, &size)) + throw love::Exception("Error loading system font '{:s}'", name); + + return new ByteData(data, size, false); + } +#else + #error "Unsupported platform for FreeType" +#endif + + FontModule::FontModule() : FontModuleBase("love.font.freetype") + { + if (FT_Init_FreeType(&this->library) != 0) + throw love::Exception("Error initializing FreeType library."); + + this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); + } + + FontModule::~FontModule() + { + FT_Done_FreeType(this->library); + } + + Rasterizer* FontModule::newRasterizer(FileData* data) const + { + if (TrueTypeRasterizer::accepts(this->library, data)) + return newTrueTypeRasterizer(data, 12, Rasterizer::Settings()); + + throw love::Exception("Invalid font file: {:s}", data->getFilename()); + } + + Rasterizer* FontModule::newTrueTypeRasterizer(Data* data, int size, + const Rasterizer::Settings& settings) const + { + float dpiScale = 1.0f; + auto window = Module::getInstance(Module::M_WINDOW); + + if (window != nullptr) + dpiScale = window->getDPIScale(); + + return new TrueTypeRasterizer(this->library, data, size, settings, dpiScale); + } +} // namespace love diff --git a/source/modules/font/freetype/TrueTypeRasterizer.cpp b/source/modules/font/freetype/TrueTypeRasterizer.cpp new file mode 100644 index 000000000..0071f3e47 --- /dev/null +++ b/source/modules/font/freetype/TrueTypeRasterizer.cpp @@ -0,0 +1,233 @@ +#include "modules/font/freetype/TrueTypeRasterizer.hpp" + +#include "modules/font/GenericShaper.hpp" + +#include + +#define E_TTF_LOAD_FAILED "TrueType Font loading error: FT_New_Face failed: {:s} (problem with font file?)" +#define E_TTF_SIZE_FAILED "TrueType Font loading error: FT_Set_Pixel_Sizes failed: {:s} (invalid size?)" + +#define E_GLYPH_LOAD_FAILED "TrueType Font glyph error: FT_Load_Glyph failed: {:s}" +#define E_GLYPH_GET_FAILED "TrueType Font glyph error: FT_Get_Glyph failed: {:s}" + +#define E_GLYPH_RENDER_FAILED "TrueType Font glyph error: FT_Glyph_To_Bitmap failed: {:s}" + +namespace love +{ + TrueTypeRasterizer::TrueTypeRasterizer(FT_Library library, Data* data, int size, const Settings& settings, + float defaultDPIScale) : + hinting(settings.hinting) + { + this->data.set(data); + this->dpiScale = settings.dpiScale.get(defaultDPIScale); + + this->size = std::floor(size * dpiScale + 0.5f); + this->sdf = settings.sdf; + + if (size <= 0) + throw love::Exception("Invalid TrueType font size: %d", size); + + FT_Error err = FT_Err_Ok; + err = FT_New_Memory_Face(library, (const FT_Byte*)data->getData(), /* first byte in memory */ + data->getSize(), /* size in bytes */ + 0, /* face_index */ + &face); + + if (err != FT_Err_Ok) + throw love::Exception(E_TTF_LOAD_FAILED, FT_Error_String(err)); + + err = FT_Set_Pixel_Sizes(face, size, size); + + if (err != FT_Err_Ok) + throw love::Exception(E_TTF_SIZE_FAILED, FT_Error_String(err)); + + // Set global metrics + FT_Size_Metrics& s = face->size->metrics; + metrics.advance = (int)(s.max_advance >> 6); + metrics.ascent = (int)(s.ascender >> 6); + metrics.descent = (int)(s.descender >> 6); + metrics.height = (int)(s.height >> 6); + } + + TrueTypeRasterizer::~TrueTypeRasterizer() + { + FT_Done_Face(this->face); + } + + int TrueTypeRasterizer::getLineHeight() const + { + return (int)(this->getHeight() * 1.25); + } + + FT_UInt TrueTypeRasterizer::hintingToLoadOption(Hinting hint) + { + switch (hint) + { + default: + case HINTING_NORMAL: + return FT_LOAD_DEFAULT; + case HINTING_LIGHT: + return FT_LOAD_TARGET_LIGHT; + case HINTING_MONO: + return FT_LOAD_TARGET_MONO; + case HINTING_NONE: + return FT_LOAD_NO_HINTING; + } + } + + int TrueTypeRasterizer::getGlyphSpacing(uint32_t glyph) const + { + FT_Glyph ftGlyph; + FT_Error error = FT_Err_Ok; + FT_UInt loadOption = hintingToLoadOption(hinting); + + const auto index = FT_Get_Char_Index(this->face, glyph); + error = FT_Load_Glyph(this->face, index, FT_LOAD_DEFAULT | loadOption); + + if (error != FT_Err_Ok) + return 0; + + error = FT_Get_Glyph(this->face->glyph, &ftGlyph); + + if (error != FT_Err_Ok) + return 0; + + return (int)(ftGlyph->advance.x >> 16); + } + + int TrueTypeRasterizer::getGlyphIndex(uint32_t glyph) const + { + return FT_Get_Char_Index(this->face, glyph); + } + + GlyphData* TrueTypeRasterizer::getGlyphDataForIndex(int index) const + { + GlyphMetrics metrics = {}; + FT_Glyph ftGlyph; + + FT_Error error = FT_Err_Ok; + FT_UInt loadOption = hintingToLoadOption(hinting); + + error = FT_Load_Glyph(this->face, index, FT_LOAD_DEFAULT | loadOption); + + if (error != FT_Err_Ok) + throw love::Exception(E_GLYPH_LOAD_FAILED, FT_Error_String(error)); + + error = FT_Get_Glyph(this->face->glyph, &ftGlyph); + + if (error != FT_Err_Ok) + throw love::Exception(E_GLYPH_GET_FAILED, FT_Error_String(error)); + + FT_Render_Mode mode = FT_RENDER_MODE_NORMAL; + if (this->sdf) + mode = FT_RENDER_MODE_SDF; + else if (this->hinting == HINTING_MONO) + mode = FT_RENDER_MODE_MONO; + + error = FT_Glyph_To_Bitmap(&ftGlyph, mode, 0, 1); + + if (error != FT_Err_Ok) + { + if (mode == FT_RENDER_MODE_SDF) + { + error = FT_Glyph_To_Bitmap(&ftGlyph, FT_RENDER_MODE_NORMAL, 0, 1); + + if (error != FT_Err_Ok) + throw love::Exception(E_GLYPH_RENDER_FAILED, FT_Error_String(error)); + } + else + throw love::Exception(E_GLYPH_RENDER_FAILED, FT_Error_String(error)); + } + + FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)ftGlyph; + const auto& bitmap = bitmapGlyph->bitmap; + + metrics.bearingX = bitmapGlyph->left; + metrics.bearingY = bitmapGlyph->top; + metrics.width = bitmap.width; + metrics.height = bitmap.rows; + metrics.advance = (int)(bitmapGlyph->root.advance.x >> 16); + + GlyphData* data = new GlyphData(0, metrics, PIXELFORMAT_LA8_UNORM); + + const uint8_t* pixels = bitmap.buffer; + uint8_t* destination = (uint8_t*)data->getData(); + + if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) + { + for (int y = 0; y < (int)bitmap.rows; y++) + { + for (int x = 0; x < (int)bitmap.width; x++) + { + uint8_t value = ((pixels[x / 8]) & (1 << (7 - (x % 8)))) ? 255 : 0; + destination[2 * (y * bitmap.width + x) + 0] = 255; + destination[2 * (y * bitmap.width + x) + 1] = value; + } + + pixels += bitmap.pitch; + } + } + else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) + { + for (int y = 0; y < (int)bitmap.rows; y++) + { + for (int x = 0; x < (int)bitmap.width; x++) + { + destination[2 * (y * bitmap.width + x) + 0] = 255; + destination[2 * (y * bitmap.width + x) + 1] = pixels[x]; + } + + pixels += bitmap.pitch; + } + } + else + { + delete data; + FT_Done_Glyph(ftGlyph); + throw love::Exception("Unknown TrueType glyph pixel mode."); + } + + FT_Done_Glyph(ftGlyph); + return data; + } + + int TrueTypeRasterizer::getGlyphCount() const + { + return face->num_glyphs; + } + + bool TrueTypeRasterizer::hasGlyph(uint32_t glyph) const + { + return FT_Get_Char_Index(this->face, glyph) != 0; + } + + float TrueTypeRasterizer::getKerning(uint32_t left, uint32_t right) const + { + FT_Vector kerning; + + const auto leftIndex = FT_Get_Char_Index(face, left); + const auto rightIndex = FT_Get_Char_Index(face, right); + + FT_Get_Kerning(face, leftIndex, rightIndex, FT_KERNING_DEFAULT, &kerning); + + return (float)(kerning.x >> 6); + } + + Rasterizer::DataType TrueTypeRasterizer::getDataType() const + { + return Rasterizer::DataType::DATA_TRUETYPE; + } + + TextShaper* TrueTypeRasterizer::newTextShaper() + { + return new GenericShaper(this); + } + + bool TrueTypeRasterizer::accepts(FT_Library library, Data* data) + { + const FT_Byte* base = (const FT_Byte*)data->getData(); + FT_Long size = (FT_Long)data->getSize(); + + return FT_New_Memory_Face(library, base, size, -1, nullptr) == 0; + } +} // namespace love diff --git a/source/modules/font/wrap_Font.cpp b/source/modules/font/wrap_Font.cpp new file mode 100644 index 000000000..baec05bc7 --- /dev/null +++ b/source/modules/font/wrap_Font.cpp @@ -0,0 +1,203 @@ +#include "modules/font/wrap_Font.hpp" + +#if defined(__3DS__) + #include "modules/font/Font.hpp" +#else + #include "modules/font/freetype/Font.hpp" +#endif + +#include "modules/font/wrap_GlyphData.hpp" +#include "modules/font/wrap_Rasterizer.hpp" + +#include "modules/filesystem/wrap_Filesystem.hpp" + +using namespace love; + +#define instance() (Module::getInstance(Module::M_FONT)) + +int Wrap_FontModule::newRasterizer(lua_State* L) +{ + if (lua_type(L, 1) == LUA_TNUMBER || lua_type(L, 2) == LUA_TNUMBER || lua_isnone(L, 1)) + return newTrueTypeRasterizer(L); + else if (lua_isnoneornil(L, 2)) + { + Rasterizer* rasterizer = nullptr; + auto* fileData = luax_getfiledata(L, 1); + + // clang-format off + luax_catchexcept(L, + [&]() { rasterizer = instance()->newRasterizer(fileData); }, + [&](bool) { fileData->release(); } + ); + // clang-format on + + luax_pushtype(L, rasterizer); + rasterizer->release(); + + return 1; + } + else + return newBMFontRasterizer(L); +} + +static Rasterizer::Settings luax_checktruetypesettings(lua_State* L, int index) +{ + Rasterizer::Settings settings {}; + + if (lua_type(L, index)) + { + const char* hinting = lua_isnoneornil(L, index) ? nullptr : luaL_checkstring(L, index); + if (hinting && !Rasterizer::getConstant(hinting, settings.hinting)) + luax_enumerror(L, "Font hinting mode", Rasterizer::Hintings, hinting); + + if (!lua_isnoneornil(L, index + 1)) + settings.dpiScale.set((float)luaL_checknumber(L, index + 1)); + } + else + { + luaL_checktype(L, index, LUA_TTABLE); + + lua_getfield(L, index, "hinting"); + if (!lua_isnoneornil(L, -1)) + { + const char* hinting = luaL_checkstring(L, -1); + if (!Rasterizer::getConstant(hinting, settings.hinting)) + luax_enumerror(L, "Font hinting mode", Rasterizer::Hintings, hinting); + } + lua_pop(L, 1); + + lua_getfield(L, index, "dpiscale"); + if (!lua_isnoneornil(L, -1)) + settings.dpiScale.set((float)luaL_checknumber(L, -1)); + lua_pop(L, 1); + } + + return settings; +} + +/* +** This is a simplified version(?) from 3.0 to make it suck less. +** We check the system font type *first*, but if it fails, we just get the file data via filename. +** Getting the data by filename will automatically propagate the error if the file doesn't exist. +** +** On Nintendo 3DS, we return a Data object wrapped through SystemFont because of linear memory. +*/ +static Data* luax_checkfilename(lua_State* L, int index) +{ + // CFG_REGION_USA, PlSharedFontType_Standard, OS_SHAREDDATATYPE_FONT_STANDARD + auto systemFontType = SystemFontType(0); + const char* name = luaL_checkstring(L, index); + + if (!FontModule::getConstant(name, systemFontType)) + return luax_getfiledata(L, index); + + return FontModule::loadSystemFontByType(systemFontType); +} + +int Wrap_FontModule::newTrueTypeRasterizer(lua_State* L) +{ + Rasterizer* rasterizer = nullptr; + + if (lua_type(L, 1) == LUA_TNUMBER || lua_isnone(L, 1)) + { + int size = luaL_optinteger(L, 1, 13); + Rasterizer::Settings settings {}; + + if (!lua_isnoneornil(L, 2)) + settings = luax_checktruetypesettings(L, 2); + + luax_catchexcept(L, [&] { rasterizer = instance()->newTrueTypeRasterizer(size, settings); }); + } + else + { + int size = luaL_optinteger(L, 2, 12); + + Rasterizer::Settings settings {}; + if (!lua_isnoneornil(L, 3)) + settings = luax_checktruetypesettings(L, 3); + + Data* data = nullptr; + + if (luax_istype(L, 1, Data::type)) + { + data = luax_checkdata(L, 1); + data->retain(); + } + else + data = luax_checkfilename(L, 1); + + // clang-format off + luax_catchexcept( + L, [&] { rasterizer = instance()->newTrueTypeRasterizer(data, size, settings); }, + [&](bool) { data->release(); } + ); + // clang-format on + } + + luax_pushtype(L, rasterizer); + rasterizer->release(); + + return 1; +} + +int Wrap_FontModule::newBMFontRasterizer(lua_State* L) +{ + return 0; +} + +int Wrap_FontModule::newGlyphData(lua_State* L) +{ + auto* rasterizer = luax_checkrasterizer(L, 1); + GlyphData* data = nullptr; + + if (lua_type(L, 1) == LUA_TSTRING) + { + auto glyph = luax_checkstring(L, 2); + luax_catchexcept(L, [&] { data = rasterizer->getGlyphData(glyph); }); + } + else + { + auto glyph = (uint32_t)luaL_checknumber(L, 2); + data = instance()->newGlyphData(rasterizer, glyph); + } + + luax_pushtype(L, data); + data->release(); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "newRasterizer", Wrap_FontModule::newRasterizer }, + { "newGlyphData", Wrap_FontModule::newGlyphData }, + { "newTrueTypeRasterizer", Wrap_FontModule::newTrueTypeRasterizer }, + { "newBMFontRasterizer", Wrap_FontModule::newBMFontRasterizer } +}; + +static constexpr lua_CFunction types[] = +{ + love::open_glyphdata, + love::open_rasterizer +}; +// clang-format on + +int Wrap_FontModule::open(lua_State* L) +{ + auto* instance = instance(); + + if (instance == nullptr) + luax_catchexcept(L, [&]() { instance = new FontModule(); }); + else + instance->retain(); + + WrappedModule module {}; + module.instance = instance; + module.name = "font"; + module.type = &Module::type; + module.functions = functions; + module.types = types; + + return luax_register_module(L, module); +} diff --git a/source/modules/font/wrap_GlyphData.cpp b/source/modules/font/wrap_GlyphData.cpp new file mode 100644 index 000000000..296ef1ccb --- /dev/null +++ b/source/modules/font/wrap_GlyphData.cpp @@ -0,0 +1,142 @@ +#include "modules/font/wrap_GlyphData.hpp" + +using namespace love; + +int Wrap_GlyphData::clone(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + GlyphData* clone = nullptr; + + luax_catchexcept(L, [&] { clone = self->clone(); }); + luax_pushtype(L, clone); + clone->release(); + + return 1; +} + +int Wrap_GlyphData::getWidth(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + lua_pushinteger(L, self->getWidth()); + + return 1; +} + +int Wrap_GlyphData::getHeight(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + lua_pushinteger(L, self->getHeight()); + + return 1; +} + +int Wrap_GlyphData::getDimensions(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + lua_pushinteger(L, self->getWidth()); + lua_pushinteger(L, self->getHeight()); + + return 2; +} + +int Wrap_GlyphData::getGlyph(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + lua_pushinteger(L, (lua_Number)self->getGlyph()); + + return 1; +} + +int Wrap_GlyphData::getGlyphString(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + luax_catchexcept(L, [&] { luax_pushstring(L, self->getGlyphString()); }); + + return 1; +} + +int Wrap_GlyphData::getAdvance(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + lua_pushinteger(L, self->getAdvance()); + + return 1; +} + +int Wrap_GlyphData::getBearing(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + lua_pushinteger(L, self->getBearingX()); + lua_pushinteger(L, self->getBearingY()); + + return 2; +} + +int Wrap_GlyphData::getBoundingBox(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + int minX = self->getMinX(); + int minY = self->getMinY(); + int maxX = self->getMaxX(); + int maxY = self->getMaxY(); + + int width = maxX - minX; + int height = maxY - minY; + + lua_pushinteger(L, minX); + lua_pushinteger(L, minY); + lua_pushinteger(L, width); + lua_pushinteger(L, height); + + return 4; +} + +int Wrap_GlyphData::getFormat(lua_State* L) +{ + auto* self = luax_checkglyphdata(L, 1); + + std::string_view format {}; + if (!getConstant(self->getFormat(), format)) + return luax_enumerror(L, "pixel format", format.data()); + + luax_pushstring(L, format); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "clone", Wrap_GlyphData::clone }, + { "getWidth", Wrap_GlyphData::getWidth }, + { "getHeight", Wrap_GlyphData::getHeight }, + { "getDimensions", Wrap_GlyphData::getDimensions }, + { "getGlyph", Wrap_GlyphData::getGlyph }, + { "getGlyphString", Wrap_GlyphData::getGlyphString }, + { "getAdvance", Wrap_GlyphData::getAdvance }, + { "getBearing", Wrap_GlyphData::getBearing }, + { "getBoundingBox", Wrap_GlyphData::getBoundingBox }, + { "getFormat", Wrap_GlyphData::getFormat } +}; +// clang-format on + +namespace love +{ + GlyphData* luax_checkglyphdata(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_glyphdata(lua_State* L) + { + return luax_register_type(L, &GlyphData::type, functions); + } +} // namespace love diff --git a/source/modules/font/wrap_Rasterizer.cpp b/source/modules/font/wrap_Rasterizer.cpp new file mode 100644 index 000000000..9911fed2b --- /dev/null +++ b/source/modules/font/wrap_Rasterizer.cpp @@ -0,0 +1,135 @@ +#include "modules/font/wrap_Rasterizer.hpp" + +using namespace love; + +int Wrap_Rasterizer::getHeight(lua_State* L) +{ + auto* self = luax_checkrasterizer(L, 1); + + lua_pushinteger(L, self->getHeight()); + + return 1; +} + +int Wrap_Rasterizer::getAdvance(lua_State* L) +{ + auto* self = luax_checkrasterizer(L, 1); + + lua_pushinteger(L, self->getAdvance()); + + return 1; +} + +int Wrap_Rasterizer::getAscent(lua_State* L) +{ + auto* self = luax_checkrasterizer(L, 1); + + lua_pushinteger(L, self->getAscent()); + + return 1; +} + +int Wrap_Rasterizer::getDescent(lua_State* L) +{ + auto* self = luax_checkrasterizer(L, 1); + + lua_pushinteger(L, self->getDescent()); + + return 1; +} + +int Wrap_Rasterizer::getLineHeight(lua_State* L) +{ + auto* self = luax_checkrasterizer(L, 1); + + lua_pushinteger(L, self->getLineHeight()); + + return 1; +} + +int Wrap_Rasterizer::getGlyphData(lua_State* L) +{ + auto* self = luax_checkrasterizer(L, 1); + GlyphData* data = nullptr; + + luax_catchexcept(L, [&] { + if (lua_type(L, 2) == LUA_TSTRING) + { + auto glyph = luax_checkstring(L, 2); + data = self->getGlyphData(glyph); + } + else + { + auto codepoint = (uint32_t)luaL_checknumber(L, 2); + data = self->getGlyphData(codepoint); + } + }); + + luax_pushtype(L, data); + data->release(); + + return 1; +} + +int Wrap_Rasterizer::getGlyphCount(lua_State* L) +{ + auto* self = luax_checkrasterizer(L, 1); + + lua_pushinteger(L, self->getGlyphCount()); + + return 1; +} + +int Wrap_Rasterizer::hasGlyphs(lua_State* L) +{ + auto* self = luax_checkrasterizer(L, 1); + + bool hasGlyph = false; + + int count = lua_gettop(L) - 1; + count = count < 1 ? 1 : count; + + luax_catchexcept(L, [&]() { + for (int index = 2; index < count + 2; index++) + { + if (lua_type(L, index) == LUA_TSTRING) + hasGlyph = self->hasGlyphs(luax_checkstring(L, index)); + else + hasGlyph = self->hasGlyph((uint32_t)luaL_checknumber(L, index)); + + if (!hasGlyph) + break; + } + }); + + luax_pushboolean(L, hasGlyph); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "getHeight", Wrap_Rasterizer::getHeight }, + { "getAdvance", Wrap_Rasterizer::getAdvance }, + { "getAscent", Wrap_Rasterizer::getAscent }, + { "getDescent", Wrap_Rasterizer::getDescent }, + { "getLineHeight", Wrap_Rasterizer::getLineHeight }, + { "getGlyphData", Wrap_Rasterizer::getGlyphData }, + { "getGlyphCount", Wrap_Rasterizer::getGlyphCount }, + { "hasGlyphs", Wrap_Rasterizer::hasGlyphs } +}; +// clang-format on + +namespace love +{ + Rasterizer* luax_checkrasterizer(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_rasterizer(lua_State* L) + { + return luax_register_type(L, &Rasterizer::type, functions); + } +} // namespace love diff --git a/source/modules/graphics/Buffer.cpp b/source/modules/graphics/Buffer.cpp new file mode 100644 index 000000000..b42d32349 --- /dev/null +++ b/source/modules/graphics/Buffer.cpp @@ -0,0 +1,192 @@ +#include "common/memory.hpp" + +#include "modules/graphics/Buffer.tcc" +#include "modules/graphics/Graphics.tcc" + +#include "modules/graphics/vertex.hpp" + +namespace love +{ + BufferBase::BufferBase(GraphicsBase* graphics, const Settings& settings, + const std::vector& bufferFormat, size_t size, size_t length) : + arrayLength(0), + arrayStride(0), + size(size), + usageFlags(settings.usageFlags), + dataUsage(settings.dataUsage), + debugName(settings.debugName), + mapped(false), + mappedType(MAP_WRITE_INVALIDATE), + immutable(false) + { + if (size == 0 && arrayLength == 0) + throw love::Exception("Size or array length must be specified."); + + if (bufferFormat.size() == 0) + throw love::Exception("Data format must contain values."); + + const auto& caps = graphics->getCapabilities(); + + bool indexBuffer = usageFlags & BUFFERUSAGEFLAG_INDEX; + bool vertexBuffer = usageFlags & BUFFERUSAGEFLAG_VERTEX; + + size_t offset = 0; + size_t stride = 0; + size_t structureAlignment = 1; + + for (const DataDeclaration& declaration : bufferFormat) + { + DataMember member(declaration); + + DataFormat format = member.decl.format; + const DataFormatInfo& info = member.info; + + if (indexBuffer) + { + if (!caps.features[GraphicsBase::FEATURE_INDEX_BUFFER_32BIT] && format == DATAFORMAT_UINT32) + throw love::Exception("32-bit index buffers are not supported by this system."); + + if (format != DATAFORMAT_UINT16 && format != DATAFORMAT_UINT32) + throw love::Exception("Index buffers only support uint16 and uint32 data types."); + + if (bufferFormat.size() > 1) + throw love::Exception("Index buffers only support a single value per element."); + + if (declaration.arrayLength > 0) + throw love::Exception("Arrays are not supported in index buffers."); + } + + if (vertexBuffer) + { + if (declaration.arrayLength > 0) + throw love::Exception("Arrays are not supported in vertex buffers."); + + if (info.isMatrix) + throw love::Exception("Matrix data types are not supported in vertex buffers."); + + if (info.baseType == DATA_BASETYPE_BOOL) + throw love::Exception("Boolean data types are not supported in vertex buffers."); + + if (declaration.name.empty()) + throw love::Exception("Vertex buffer data members must have a name."); + } + + member.offset = offset; + member.size = member.info.size; + + offset = member.offset + member.size; + this->dataMembers.push_back(member); + } + + // stride = alignUp(offset, structureAlignment); + + if (size != 0) + { + size_t remainder = size % stride; + + if (remainder > 0) + size += stride - remainder; + + arrayLength = size / stride; + } + else + size = arrayLength * stride; + + this->arrayStride = stride; + this->arrayLength = arrayLength; + this->size = size; + + ++bufferCount; + totalGraphicsMemory += size; + } + + BufferBase::~BufferBase() + { + totalGraphicsMemory -= size; + --bufferCount; + } + + int BufferBase::getDataMemberIndex(const std::string& name) const + { + for (size_t index = 0; index < this->dataMembers.size(); index++) + { + if (this->dataMembers[index].decl.name == name) + return (int)index; + } + + return -1; + } + + void BufferBase::clear(size_t offset, size_t size) + { + if (this->isImmutable()) + throw love::Exception("Cannot clear an immutable Buffer."); + else if (this->isMapped()) + throw love::Exception("Cannot clear a mapped Buffer."); + else if (offset + size > this->getSize()) + throw love::Exception( + "The given offset and size parameters to clear() are not within the Buffer's size."); + else if (offset % 4 != 0 || size % 4 != 0) + throw love::Exception( + "clear() must be used with offset and size parameters that are multiples of 4 bytes."); + + this->clearInternal(offset, size); + } + + std::vector BufferBase::getCommonFormatDeclaration(CommonFormat format) + { + switch (format) + { + case CommonFormat::NONE: + return {}; + case CommonFormat::XYf: + return { { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 } }; + case CommonFormat::XYZf: + return { { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC3 } }; + case CommonFormat::RGBAf: + return { { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 } }; + case CommonFormat::STf_RGBAf: + return { + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + }; + case CommonFormat::STPf_RGBAf: + return { + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + }; + case CommonFormat::XYf_STf: + return { + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, + }; + case CommonFormat::XYf_STPf: + return { + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 }, + }; + case CommonFormat::XYf_STf_RGBAf: + return { + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + }; + case CommonFormat::XYf_STus_RGBAf: + return { + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_UNORM16_VEC2 }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + }; + case CommonFormat::XYf_STPf_RGBAf: + return { + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + }; + default: + break; + } + + return {}; + } +} // namespace love diff --git a/source/modules/graphics/FontBase.cpp b/source/modules/graphics/FontBase.cpp new file mode 100644 index 000000000..9447d605d --- /dev/null +++ b/source/modules/graphics/FontBase.cpp @@ -0,0 +1,586 @@ +#include "modules/graphics/Font.tcc" +#include "modules/graphics/Graphics.tcc" + +#include "modules/font/GlyphData.hpp" + +#include "common/Console.hpp" +#include "common/Matrix.hpp" +#include "common/math.hpp" + +#include +#include +#include + +namespace love +{ + int FontBase::fontCount = 0; + + FontBase::FontBase(Rasterizer* rasterizer, const SamplerState& samplerState) : + shaper(rasterizer->newTextShaper(), Acquire::NO_RETAIN), + textureWidth(128), + textureHeight(128), + samplerState(), + dpiScale(rasterizer->getDPIScale()), + textureCacheID(0) + { + this->samplerState.minFilter = samplerState.minFilter; + this->samplerState.magFilter = samplerState.magFilter; + this->samplerState.maxAnisotropy = samplerState.maxAnisotropy; + + if constexpr (!Console::is(Console::CTR)) + { + while (true) + { + if ((shaper->getHeight() * 0.8) * shaper->getHeight() * 30 <= textureWidth * textureHeight) + break; + + TextureSize nextsize = getNextTextureSize(); + + if (nextsize.width <= textureWidth && nextsize.height <= textureHeight) + break; + + textureWidth = nextsize.width; + textureHeight = nextsize.height; + } + } + + auto* glyphData = rasterizer->getGlyphData(32); + this->pixelFormat = glyphData->getFormat(); + glyphData->release(); + + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + + auto supported = graphics->isPixelFormatSupported(this->pixelFormat, PIXELFORMATUSAGEFLAGS_SAMPLE); + if (this->pixelFormat == PIXELFORMAT_LA8_UNORM && !supported) + this->pixelFormat = PIXELFORMAT_RGBA8_UNORM; + + ++fontCount; + } + + FontBase::~FontBase() + { + --fontCount; + } + + FontBase::TextureSize FontBase::getNextTextureSize() const + { + TextureSize size = { textureWidth, textureHeight }; + + int maxsize = 2048; + auto gfx = Module::getInstance(Module::M_GRAPHICS); + + if (gfx != nullptr) + { + const auto& caps = gfx->getCapabilities(); + maxsize = (int)caps.limits[GraphicsBase::LIMIT_TEXTURE_SIZE]; + } + + int maxwidth = std::min(8192, maxsize); + int maxheight = std::min(4096, maxsize); + + if (size.width * 2 <= maxwidth || size.height * 2 <= maxheight) + { + // {128, 128} -> {256, 128} -> {256, 256} -> {512, 256} -> etc. + if (size.width == size.height) + size.width *= 2; + else + size.height *= 2; + } + + return size; + } + + void FontBase::unloadVolatile() + { + this->glyphs.clear(); + this->textures.clear(); + } + + GlyphData* FontBase::getRasterizerGlyphData(TextShaper::GlyphIndex glyphindex, float& dpiscale) + { + const auto& rasterizer = shaper->getRasterizers()[glyphindex.rasterizerIndex]; + dpiScale = rasterizer->getDPIScale(); + + return rasterizer->getGlyphDataForIndex(glyphindex.index); + } + + const FontBase::Glyph& FontBase::findGlyph(TextShaper::GlyphIndex glyphindex) + { + uint64_t packed = packGlyphIndex(glyphindex); + const auto it = glyphs.find(packed); + + if (it != glyphs.end()) + return it->second; + + return this->addGlyph(glyphindex); + } + + const FontBase::Glyph& FontBase::addGlyph(TextShaper::GlyphIndex glyphIndex) + { + float dpiScale = this->getDPIScale(); + StrongRef gd(this->getRasterizerGlyphData(glyphIndex, dpiScale), Acquire::NO_RETAIN); + + int width = gd->getWidth(); + int height = gd->getHeight(); + + if (width + TEXTURE_PADDING * 2 < textureWidth && height + TEXTURE_PADDING * 2 < textureHeight) + { + if (this->textureX + width + TEXTURE_PADDING > textureWidth) + { + this->textureX = TEXTURE_PADDING; + this->textureY += this->rowHeight; + this->rowHeight = TEXTURE_PADDING; + } + + if (this->textureY + height + TEXTURE_PADDING > textureHeight) + { + this->createTexture(); + return this->addGlyph(glyphIndex); + } + } + + Glyph glyph {}; + glyph.texture = nullptr; + std::fill_n(glyph.vertices, 4, GlyphVertex {}); + + if (width > 0 && height > 0) + { + TextureBase* texture = this->textures.back(); + glyph.texture = texture; + + Rect rect = { this->textureX, this->textureY, gd->getWidth(), gd->getHeight() }; + + if (this->pixelFormat != gd->getFormat()) + { + const auto supportedFormat = + this->pixelFormat == PIXELFORMAT_RGBA8_UNORM && gd->getFormat() == PIXELFORMAT_LA8_UNORM; + + if (!supportedFormat) + throw love::Exception( + "Cannot upload font glyphs to texture atlas: unexpected format conversion."); + + const uint8_t* source = (const uint8_t*)gd->getData(); + size_t destSize = getPixelFormatSliceSize(this->pixelFormat, width, height); + std::vector destination(destSize, 0); + uint8_t* destData = destination.data(); + + for (int pixel = 0; pixel < width * height; pixel++) + { + destData[pixel * 4 + 0] = source[pixel * 2 + 0]; + destData[pixel * 4 + 1] = source[pixel * 2 + 0]; + destData[pixel * 4 + 2] = source[pixel * 2 + 0]; + destData[pixel * 4 + 3] = source[pixel * 2 + 1]; + } + + texture->replacePixels(destData, destSize, 0, 0, rect, false); + } + else + texture->replacePixels(gd->getData(), gd->getSize(), 0, 0, rect, false); + + double tX = (double)this->textureX, tY = (double)this->textureY; + double tW = (double)textureWidth, tH = (double)textureHeight; + + Color color(1.0f, 1.0f, 1.0f, 1.0f); + int offset = 1; + + // clang-format off + const GlyphVertex vertices[4] = + { + { float(-offset), float(-offset), (tX - offset) / tW, (tY - offset) / tH, color }, + { float(-offset), height + offset, (tX - offset) / tW, (tY + height + offset) / tH, color }, + { width + offset, float(-offset), (tX + width + offset) / tW, (tY - offset) / tH, color }, + { width + offset, height + offset, (tX + width + offset) / tW, (tY + height + offset) / tH, color } + }; + // clang-format on + + for (int i = 0; i < 4; i++) + { + glyph.vertices[i] = vertices[i]; + glyph.vertices[i].x += gd->getBearingX(); + glyph.vertices[i].y -= gd->getBearingY(); + } + + this->textureX += width + TEXTURE_PADDING; + this->rowHeight = std::max(this->rowHeight, height + TEXTURE_PADDING); + } + + uint64_t packed = packGlyphIndex(glyphIndex); + this->glyphs[packed] = glyph; + + return this->glyphs[packed]; + } + + float FontBase::getKerning(uint32_t leftglyph, uint32_t rightglyph) + { + return this->shaper->getKerning(leftglyph, rightglyph); + } + + float FontBase::getKerning(const std::string& leftchar, const std::string& rightchar) + { + return this->shaper->getKerning(leftchar, rightchar); + } + + float FontBase::getHeight() const + { + return this->shaper->getHeight(); + } + + void FontBase::print(GraphicsBase* graphics, const std::vector& text, + const Matrix4& matrix, const Color& constantcolor) + { + ColoredCodepoints codepoints; + getCodepointsFromString(text, codepoints); + + std::vector vertices {}; + auto drawcommands = this->generateVertices(codepoints, Range(), constantcolor, vertices); + + this->printv(graphics, matrix, drawcommands, vertices); + } + + void FontBase::printf(GraphicsBase* graphics, const std::vector& text, float wrap, + AlignMode align, const Matrix4& matrix, const Color& constantcolor) + { + ColoredCodepoints codepoints {}; + getCodepointsFromString(text, codepoints); + + std::vector vertices; + auto drawcommands = this->generateVerticesFormatted(codepoints, constantcolor, wrap, align, vertices); + + this->printv(graphics, matrix, drawcommands, vertices); + } + + static bool sortGlyphs(const FontBase::DrawCommand& left, const FontBase::DrawCommand& right) + { + if constexpr (Console::is(Console::CTR)) + { + const auto result = left.texture - right.texture; + return result < 0; + } + + return left.texture < right.texture; + } + + std::vector FontBase::generateVertices(const ColoredCodepoints& codepoints, + Range range, const Color& constantColor, + std::vector& vertices, + float extra_spacing, Vector2 offset, + TextShaper::TextInfo* info) + { + std::vector glyphPositions {}; + std::vector colors; + this->shaper->computeGlyphPositions(codepoints, range, offset, extra_spacing, &glyphPositions, + &colors, info); + + size_t vertexStartSize = vertices.size(); + vertices.reserve(vertexStartSize + glyphPositions.size() * 4); + + Color linearConstantColor = gammaCorrectColor(constantColor); + Color currentColor = constantColor; + + int currentColorIndex = 0; + int numColors = (int)colors.size(); + + // Keeps track of when we need to switch textures in our vertex array. + std::vector commands {}; + + for (int i = 0; i < (int)glyphPositions.size(); i++) + { + const auto& info = glyphPositions[i]; + + uint32_t cacheid = textureCacheID; + const Glyph& glyph = findGlyph(info.glyphIndex); + + // If findGlyph invalidates the texture cache, restart the loop. + if (cacheid != textureCacheID) + { + i = -1; // The next iteration will increment this to 0. + commands.clear(); + vertices.resize(vertexStartSize); + currentColorIndex = 0; + currentColor = constantColor; + continue; + } + + if (currentColorIndex < numColors && colors[currentColorIndex].index == i) + { + Color c = colors[currentColorIndex].color; + + c.r = std::clamp(c.r, 0.0f, 1.0f); + c.g = std::clamp(c.g, 0.0f, 1.0f); + c.b = std::clamp(c.b, 0.0f, 1.0f); + c.a = std::clamp(c.a, 0.0f, 1.0f); + + gammaCorrectColor(c); + c *= linearConstantColor; + unGammaCorrectColor(c); + + currentColor = c; + currentColorIndex++; + } + + 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 += info.position.x; + vertices.back().y += info.position.y; + vertices.back().color = currentColor; + } + + // Check if glyph texture has changed since the last iteration. + if (commands.empty() || commands.back().texture != glyph.texture) + { + // Add a new draw command if the texture has changed. + DrawCommand cmd; + cmd.startVertex = (int)vertices.size() - 4; + cmd.vertexCount = 0; + cmd.texture = glyph.texture; + commands.push_back(cmd); + } + + commands.back().vertexCount += 4; + } + } + + std::sort(commands.begin(), commands.end(), sortGlyphs); + + return commands; + } + + std::vector FontBase::generateVerticesFormatted(const ColoredCodepoints& text, + const Color& constantcolor, + float wrap, AlignMode align, + std::vector& vertices, + TextShaper::TextInfo* info) + { + wrap = std::max(wrap, 0.0f); + + uint32_t cacheid = textureCacheID; + + std::vector drawcommands; + vertices.reserve(text.codepoints.size() * 4); + + std::vector ranges; + std::vector widths; + shaper->getWrap(text, wrap, ranges, &widths); + + float y = 0.0f; + float maxwidth = 0.0f; + + for (int i = 0; i < (int)ranges.size(); 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)); + float extraspacing = 0.0f; + + maxwidth = std::max(width, maxwidth); + + switch (align) + { + case ALIGN_RIGHT: + offset.x = floorf(wrap - width); + break; + case ALIGN_CENTER: + offset.x = floorf((wrap - width) / 2.0f); + break; + case ALIGN_JUSTIFY: + { + auto start = text.codepoints.begin() + range.getOffset(); + auto end = start + range.getSize(); + float numspaces = std::count(start, end, ' '); + + if (width < wrap && numspaces >= 1) + extraspacing = (wrap - width) / numspaces; + else + extraspacing = 0.0f; + + break; + } + case ALIGN_LEFT: + default: + break; + } + + auto newcommands = generateVertices(text, range, constantcolor, vertices, extraspacing, offset); + + if (!newcommands.empty()) + { + auto firstcmd = newcommands.begin(); + + // If the first draw command in the new list has the same texture + // as the last one in the existing list we're building and its + // vertices are in-order, we can combine them (saving a draw call.) + if (!drawcommands.empty()) + { + auto prevcmd = drawcommands.back(); + if (prevcmd.texture == firstcmd->texture && + (prevcmd.startVertex + prevcmd.vertexCount) == firstcmd->startVertex) + { + drawcommands.back().vertexCount += firstcmd->vertexCount; + ++firstcmd; + } + } + + // Append the new draw commands to the list we're building. + drawcommands.insert(drawcommands.end(), firstcmd, newcommands.end()); + } + + y += getHeight() * getLineHeight(); + } + + if (info != nullptr) + { + info->width = (int)maxwidth; + info->height = (int)y; + } + + if (cacheid != textureCacheID) + { + vertices.clear(); + drawcommands = generateVerticesFormatted(text, constantcolor, wrap, align, vertices); + } + + return drawcommands; + } + + static constexpr auto shaderType = + (Console::is(Console::CTR)) ? ShaderBase::STANDARD_DEFAULT : ShaderBase::STANDARD_TEXTURE; + + void FontBase::printv(GraphicsBase* graphics, const Matrix4& matrix, + const std::vector& drawcommands, + const std::vector& vertices) + { + if (vertices.empty() || drawcommands.empty()) + return; + + Matrix4 m(graphics->getTransform(), matrix); + + for (const DrawCommand& cmd : drawcommands) + { + BatchedDrawCommand command {}; + command.format = CommonFormat::XYf_STf_RGBAf; + command.indexMode = TRIANGLEINDEX_QUADS; + command.vertexCount = cmd.vertexCount; + command.texture = cmd.texture; + command.isFont = true; + command.shaderType = shaderType; + + auto data = graphics->requestBatchedDraw(command); + GlyphVertex* vertexdata = (GlyphVertex*)data.stream; + + std::copy_n(&vertices[cmd.startVertex], cmd.vertexCount, vertexdata); + m.transformXY(vertexdata, &vertices[cmd.startVertex], cmd.vertexCount); + } + } + + int FontBase::getWidth(const std::string& str) + { + return this->shaper->getWidth(str); + } + + int FontBase::getWidth(uint32_t glyph) + { + return this->shaper->getGlyphAdvance(glyph); + } + + void FontBase::getWrap(const ColoredCodepoints& codepoints, float wraplimit, std::vector& ranges, + std::vector* linewidths) + { + this->shaper->getWrap(codepoints, wraplimit, ranges, linewidths); + } + + void FontBase::getWrap(const std::vector& text, float wraplimit, + std::vector& lines, std::vector* linewidths) + { + this->shaper->getWrap(text, wraplimit, lines, linewidths); + } + + void FontBase::setLineHeight(float height) + { + this->shaper->setLineHeight(height); + } + + float FontBase::getLineHeight() const + { + return this->shaper->getLineHeight(); + } + + const SamplerState& FontBase::getSamplerState() const + { + return this->samplerState; + } + + int FontBase::getAscent() const + { + return this->shaper->getAscent(); + } + + int FontBase::getDescent() const + { + return this->shaper->getDescent(); + } + + int FontBase::getBaseline() const + { + return this->shaper->getBaseline(); + } + + bool FontBase::hasGlyph(uint32_t glyph) const + { + return this->shaper->hasGlyph(glyph); + } + + bool FontBase::hasGlyphs(const std::string& text) const + { + return this->shaper->hasGlyphs(text); + } + + float FontBase::getDPIScale() const + { + return this->dpiScale; + } + + uint32_t FontBase::getTextureCacheID() const + { + return this->textureCacheID; + } + + void FontBase::setFallbacks(const std::vector& fallbacks) + { + std::vector rasterizers {}; + + for (const FontBase* font : fallbacks) + rasterizers.push_back(font->shaper->getRasterizers()[0]); + + this->shaper->setFallbacks(rasterizers); + this->glyphs.clear(); + + if constexpr (!Console::is(Console::CTR)) + { + this->textureCacheID++; + + while (this->textures.size() > 1) + this->textures.pop_back(); + } + } + + void FontBase::setSamplerState(const SamplerState& state) + { + this->samplerState.minFilter = state.minFilter; + this->samplerState.magFilter = state.magFilter; + this->samplerState.maxAnisotropy = state.maxAnisotropy; + + for (const auto& texture : this->textures) + texture->setSamplerState(state); + } +} // namespace love diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp new file mode 100644 index 000000000..87f3561df --- /dev/null +++ b/source/modules/graphics/Graphics.cpp @@ -0,0 +1,1164 @@ +#include "modules/graphics/Graphics.tcc" +#include "modules/graphics/Polyline.hpp" + +#include "common/Console.hpp" +#include "common/screen.hpp" + +#include + +namespace love +{ + GraphicsBase::GraphicsBase(const char* name) : + Module(M_GRAPHICS, name), + defaultTextures(), + created(false), + active(true), + deviceProjectionMatrix(), + width(0), + height(0), + pixelWidth(0), + pixelHeight(0), + drawCallsBatched(0), + drawCalls(0), + batchedDrawState(), + cpuProcessingTime(0.0f), + gpuDrawingTime(0.0f), + capabilities() + { + this->transformStack.reserve(16); + this->transformStack.push_back(Matrix4()); + + this->pixelScaleStack.reserve(16); + this->pixelScaleStack.push_back(1.0); + + this->states.reserve(10); + this->states.push_back(DisplayState()); + } + + GraphicsBase::~GraphicsBase() + { + for (int index = 0; index < ShaderBase::STANDARD_MAX_ENUM; index++) + { + if (ShaderBase::standardShaders[index]) + { + ShaderBase::standardShaders[index]->release(); + ShaderBase::standardShaders[index] = nullptr; + } + } + + this->states.clear(); + this->defaultFont.set(nullptr); + + if (this->batchedDrawState.vertexBuffer) + this->batchedDrawState.vertexBuffer->release(); + + if (this->batchedDrawState.indexBuffer) + this->batchedDrawState.indexBuffer->release(); + + Volatile::unloadAll(); + } + + void GraphicsBase::resetProjection() + { + this->flushBatchedDraws(); + + auto& state = this->states.back(); + + state.useCustomProjection = false; + this->updateDeviceProjection(Matrix4::ortho(0.0f, 0, 0, 0.0f, -10.0f, 10.0f)); + } + + void GraphicsBase::reset() + { + DisplayState state {}; + this->restoreState(state); + + this->origin(); + } + + double GraphicsBase::getCurrentDPIScale() const + { + return 1.0; + } + + double GraphicsBase::getScreenDPIScale() const + { + return 1.0; + } + + void GraphicsBase::restoreState(const DisplayState& state) + { + this->setColor(state.color); + this->setBackgroundColor(state.backgroundColor); + + this->setBlendState(state.blend); + + this->setLineWidth(state.lineWidth); + this->setLineStyle(state.lineStyle); + this->setLineJoin(state.lineJoin); + + this->setPointSize(state.pointSize); + + if (state.scissor) + this->setScissor(state.scissorRect); + else + this->setScissor(); + + this->setMeshCullMode(state.meshCullMode); + this->setFrontFaceWinding(state.winding); + + this->setFont(state.font.get()); + this->setShader(state.shader.get()); + // this->setRenderTargets(state.renderTargets); + + // this->setStencilState(state.stencil); + // this->setDepthMode(state.depthTest, state.depthWrite); + + this->setColorMask(state.colorMask); + + this->setDefaultSamplerState(state.defaultSamplerState); + + if (state.useCustomProjection) + this->updateDeviceProjection(state.customProjection); + else + this->resetProjection(); + } + + void GraphicsBase::restoreStateChecked(const DisplayState& state) + { + const auto& current = this->states.back(); + + if (state.color != current.color) + this->setColor(state.color); + + this->setBackgroundColor(state.backgroundColor); + + if (state.blend != current.blend) + this->setBlendState(state.blend); + + this->setLineWidth(state.lineWidth); + this->setLineStyle(state.lineStyle); + this->setLineJoin(state.lineJoin); + + if (state.pointSize != current.pointSize) + this->setPointSize(state.pointSize); + + if (state.scissor != current.scissor || + (state.scissor && !(state.scissorRect != current.scissorRect))) + { + if (state.scissor) + this->setScissor(state.scissorRect); + else + this->setScissor(); + } + + this->setMeshCullMode(state.meshCullMode); + + if (state.winding != current.winding) + this->setFrontFaceWinding(state.winding); + + this->setFont(state.font.get()); + this->setShader(state.shader.get()); + + // if (this->stencil != state.stencil) + // this->setStencilState(state.stencil); + + // if (this->depthTest != state.depthTest || this->depthWrite != state.depthWrite) + // this->setDepthMode(state.depthTest, state.depthWrite); + + if (state.colorMask != current.colorMask) + this->setColorMask(state.colorMask); + + this->setDefaultSamplerState(state.defaultSamplerState); + + if (state.useCustomProjection) + this->updateDeviceProjection(state.customProjection); + else if (current.useCustomProjection) + this->resetProjection(); + } + + void GraphicsBase::setScissor(const Rect& scissor) + { + this->flushBatchedDraws(); + + auto& state = this->states.back(); + + Rect rect {}; + rect.x = scissor.x; + rect.y = scissor.y; + rect.w = std::max(0, scissor.w); + rect.h = std::max(0, scissor.h); + + state.scissorRect = rect; + state.scissor = true; + } + + void GraphicsBase::setScissor() + { + if (this->states.back().scissor) + this->flushBatchedDraws(); + + this->states.back().scissor = false; + } + + void GraphicsBase::setShader(ShaderBase* shader) + { + if (shader == nullptr) + return this->setShader(); + + shader->attach(); + this->states.back().shader.set(shader); + } + + void GraphicsBase::setShader() + { + ShaderBase::attachDefault(ShaderBase::STANDARD_DEFAULT); + this->states.back().shader.set(nullptr); + } + + GraphicsBase::Stats GraphicsBase::getStats() const + { + Stats stats {}; + + stats.drawCalls = this->drawCalls; + if (this->batchedDrawState.vertexCount > 0) + stats.drawCalls++; + + stats.drawCallsBatched = this->drawCallsBatched; + stats.textures = TextureBase::textureCount; + stats.textureMemory = TextureBase::totalGraphicsMemory; + stats.shaderSwitches = ShaderBase::shaderSwitches; + stats.cpuProcessingTime = GraphicsBase::cpuProcessingTime; + stats.gpuDrawingTime = GraphicsBase::gpuDrawingTime; + + return stats; + } + + void GraphicsBase::setFrontFaceWinding(Winding winding) + { + if (this->states.back().winding != winding) + this->flushBatchedDraws(); + + this->states.back().winding = winding; + } + + Winding GraphicsBase::getFrontFaceWinding() const + { + return this->states.back().winding; + } + + void GraphicsBase::setColorMask(ColorChannelMask mask) + { + this->flushBatchedDraws(); + this->states.back().colorMask = mask; + } + + ColorChannelMask GraphicsBase::getColorMask() const + { + return this->states.back().colorMask; + } + + void GraphicsBase::setBlendMode(BlendMode mode, BlendAlpha alphaMode) + { + if (alphaMode == BLENDALPHA_MULTIPLY && !isAlphaMultiplyBlendSupported(mode)) + { + std::string_view modeName = "unknown"; + love::getConstant(mode, modeName); + + throw love::Exception("The '{}' blend mode must be used with premultiplied alpha.", modeName); + } + + this->setBlendState(computeBlendState(mode, alphaMode)); + } + + void GraphicsBase::setBlendState(const BlendState& state) + { + if (!(state == this->states.back().blend)) + this->flushBatchedDraws(); + + this->states.back().blend = state; + } + + BatchedVertexData GraphicsBase::requestBatchedDraw(const BatchedDrawCommand& command) + { + BatchedDrawState& state = this->batchedDrawState; + + bool shouldFlush = false; + bool shouldResize = false; + + // clang-format off + if (command.primitiveMode != state.primitiveMode + || command.format != state.format + || ((command.indexMode != TRIANGLEINDEX_NONE) != (state.indexCount > 0)) + || command.texture != state.texture + || command.shaderType != state.shaderType) + { + shouldFlush = true; + } + // clang-format on + + int totalVertices = state.vertexCount + command.vertexCount; + + if (totalVertices > LOVE_UINT16_MAX && command.indexMode != TRIANGLEINDEX_NONE) + shouldFlush = true; + + int requestedIndexCount = getIndexCount(command.indexMode, command.vertexCount); + size_t requestedIndexSize = requestedIndexCount * sizeof(uint16_t); + + size_t newDataSize = 0; + size_t bufferSizes[2] = { 0, 0 }; + + if (command.format != CommonFormat::NONE) + { + size_t stride = getFormatStride(command.format); + size_t dataSize = stride * totalVertices; + + if (state.vertexBufferMap.data != nullptr && dataSize > state.vertexBufferMap.size) + shouldFlush = true; + + if (dataSize > state.vertexBuffer->getUsableSize()) + { + bufferSizes[0] = std::max(dataSize, state.vertexBuffer->getSize() * 2); + shouldResize = true; + } + + newDataSize = stride * command.vertexCount; + } + + if (command.indexMode != TRIANGLEINDEX_NONE) + { + size_t dataSize = (state.indexCount + requestedIndexCount) * sizeof(uint16_t); + + if (state.indexBufferMap.data != nullptr && dataSize > state.indexBufferMap.size) + shouldFlush = true; + + if (dataSize > state.indexBuffer->getUsableSize()) + { + bufferSizes[1] = std::max(dataSize, state.indexBuffer->getSize() * 2); + shouldResize = true; + } + } + + if (shouldFlush || shouldResize) + { + flushBatchedDraws(); + + state.primitiveMode = command.primitiveMode; + state.format = command.format; + state.texture = command.texture; + state.shaderType = command.shaderType; + state.isFont = command.isFont; + state.pushTransform = command.pushTransform; + } + + if (state.lastVertexCount == 0) + { + if (ShaderBase::isDefaultActive()) + ShaderBase::attachDefault(state.shaderType); + } + + if (shouldResize) + { + if (state.vertexBuffer->getSize() < bufferSizes[0]) + { + state.vertexBuffer->release(); + state.vertexBuffer = newVertexBuffer(bufferSizes[0]); + } + + if (state.indexBuffer->getSize() < bufferSizes[1]) + { + state.indexBuffer->release(); + state.indexBuffer = newIndexBuffer(bufferSizes[1]); + } + } + + if (command.indexMode != TRIANGLEINDEX_NONE) + { + if (state.indexBufferMap.data == nullptr) + state.indexBufferMap = state.indexBuffer->map(requestedIndexSize); + + auto* indices = state.indexBufferMap.data; + fillIndices(command.indexMode, state.vertexCount, command.vertexCount, indices); + + state.indexBufferMap.data += requestedIndexCount; + } + + BatchedVertexData data {}; + + if (newDataSize > 0) + { + if (state.vertexBufferMap.data == nullptr) + state.vertexBufferMap = state.vertexBuffer->map(newDataSize); + + data.stream = state.vertexBufferMap.data; + state.vertexBufferMap.data += command.vertexCount; + } + + if (state.vertexCount > 0) + this->drawCallsBatched++; + + state.lastVertexCount += command.vertexCount; + state.lastIndexCount += requestedIndexCount; + + state.vertexCount += command.vertexCount; + state.indexCount += requestedIndexCount; + + return data; + } + + void GraphicsBase::flushBatchedDraws() + { + BatchedDrawState& state = this->batchedDrawState; + + if ((state.lastIndexCount == 0 && state.lastVertexCount == 0) || state.flushing) + return; + + VertexAttributes attributes {}; + BufferBindings buffers {}; + + size_t usedSizes[2] = { 0, 0 }; + + if (state.format != CommonFormat::NONE) + { + attributes.setCommonFormat(state.format, (uint8_t)0); + + usedSizes[0] = state.lastVertexCount; + + size_t offset = state.vertexBuffer->unmap(usedSizes[0]); + buffers.set(0, state.vertexBuffer, offset, state.vertexCount); + + state.vertexBufferMap = MapInfo(); + } + + if (attributes.enableBits == 0) + return; + + state.flushing = true; + + // auto originalColor = this->getColor(); + // if (attributes.isEnabled(ATTRIB_COLOR)) + // this->setColor(Color::WHITE); + + if (state.pushTransform) + this->pushIdentityTransform(); + + if (state.lastIndexCount > 0) + { + usedSizes[1] = state.lastIndexCount; + + DrawIndexedCommand command(&attributes, &buffers, state.indexBuffer); + command.primitiveType = state.primitiveMode; + command.indexCount = state.lastIndexCount; + command.indexType = INDEX_UINT16; + command.indexBufferOffset = state.indexBuffer->unmap(usedSizes[1]); + command.texture = state.texture; + command.isFont = state.isFont; + + this->draw(command); + + state.indexBufferMap = MapInfo(); + } + else + { + DrawCommand command(&buffers); + command.primitiveType = state.primitiveMode; + command.vertexStart = 0; + command.vertexCount = state.lastVertexCount; + command.texture = state.texture; + + this->draw(command); + } + + if (usedSizes[0] > 0) + state.vertexBuffer->markUsed(usedSizes[0]); + + if (usedSizes[1] > 0) + state.indexBuffer->markUsed(usedSizes[1]); + + if (state.pushTransform) + this->popTransform(); + + // if (attributes.isEnabled(ATTRIB_COLOR)) + // this->setColor(originalColor); + + state.lastVertexCount = 0; + state.lastIndexCount = 0; + state.flushing = false; + } + + Quad* GraphicsBase::newQuad(Quad::Viewport viewport, double sourceWidth, double sourceHeight) const + { + return new Quad(viewport, sourceWidth, sourceHeight); + } + + TextBatch* GraphicsBase::newTextBatch(FontBase* font, const std::vector& text) + { + return new TextBatch(font, text); + } + + void GraphicsBase::checkSetDefaultFont() + { + if (this->states.back().font.get() != nullptr) + return; + + if (!this->defaultFont.get()) + { + Rasterizer::Settings settings {}; + this->defaultFont.set(this->newDefaultFont(13, settings), Acquire::NO_RETAIN); + } + + this->states.back().font.set(this->defaultFont.get()); + } + + void GraphicsBase::print(const std::vector& string, const Matrix4& matrix) + { + this->checkSetDefaultFont(); + + if (this->states.back().font.get() != nullptr) + this->print(string, this->states.back().font.get(), matrix); + } + + void GraphicsBase::print(const std::vector& string, FontBase* font, const Matrix4& matrix) + { + font->print(this, string, matrix, this->states.back().color); + } + + void GraphicsBase::printf(const std::vector& string, float wrap, FontBase::AlignMode align, + const Matrix4& matrix) + { + this->checkSetDefaultFont(); + + if (this->states.back().font.get() != nullptr) + this->printf(string, this->states.back().font.get(), wrap, align, matrix); + } + + void GraphicsBase::printf(const std::vector& string, FontBase* font, float wrap, + FontBase::AlignMode align, const Matrix4& matrix) + { + font->printf(this, string, wrap, align, matrix, this->states.back().color); + } + + void GraphicsBase::flushBatchedDrawsGlobal() + { + auto* instance = Module::getInstance(Module::M_GRAPHICS); + + if (instance != nullptr) + instance->flushBatchedDraws(); + } + + void GraphicsBase::advanceStreamBuffers() + { + if (this->batchedDrawState.vertexBuffer) + this->batchedDrawState.vertexBuffer->nextFrame(); + + if (this->batchedDrawState.indexBuffer) + this->batchedDrawState.indexBuffer->nextFrame(); + + this->batchedDrawState.vertexCount = 0; + this->batchedDrawState.indexCount = 0; + } + + void GraphicsBase::advanceStreamBuffersGlobal() + { + auto* instance = Module::getInstance(Module::M_GRAPHICS); + + if (instance != nullptr) + instance->advanceStreamBuffers(); + } + + TextureBase* GraphicsBase::getDefaultTexture(TextureBase* texture) + { + if (texture != nullptr) + return texture; + + return getDefaultTexture(TEXTURE_2D, DATA_BASETYPE_FLOAT); + } + + void GraphicsBase::push(StackType type) + { + if (this->getStackDepth() == MAX_USER_STACK_DEPTH) + throw love::Exception("Maximum stack depth reached (more pushes than pops?)"); + + this->pushTransform(); + this->pixelScaleStack.push_back(this->pixelScaleStack.back()); + + if (type == STACK_ALL) + this->states.push_back(this->states.back()); + + this->stackTypeStack.push_back(type); + } + + void GraphicsBase::pop() + { + if (this->getStackDepth() < 1) + throw love::Exception("Minimum stack depth reached (more pops than pushes?)"); + + this->popTransform(); + this->pixelScaleStack.pop_back(); + + if (this->stackTypeStack.back() == STACK_ALL) + { + DisplayState state = this->states[this->states.size() - 2]; + this->restoreStateChecked(state); + + this->states.pop_back(); + } + + this->stackTypeStack.pop_back(); + } + + void GraphicsBase::applyTransform(const Matrix4& transform) + { + Matrix4& current = this->transformStack.back(); + current *= transform; + + float sx, sy; + current.getApproximateScale(sx, sy); + this->pixelScaleStack.back() *= (sx + sy) / 2.0; + } + + void GraphicsBase::replaceTransform(const Matrix4& transform) + { + this->transformStack.back() = transform; + + float sx, sy; + transform.getApproximateScale(sx, sy); + this->pixelScaleStack.back() = (sx + sy) / 2.0; + } + + Vector2 GraphicsBase::transformPoint(Vector2 point) const + { + Vector2 result {}; + this->transformStack.back().transformXY(&result, &point, 1); + + return result; + } + + Vector2 GraphicsBase::inverseTransformPoint(Vector2 point) const + { + Vector2 result {}; + this->transformStack.back().inverse().transformXY(&result, &point, 1); + + return result; + } + + PixelFormat GraphicsBase::getSizedFormat(PixelFormat format) + { + switch (format) + { + case PIXELFORMAT_NORMAL: + if (isGammaCorrect()) + return PIXELFORMAT_RGBA8_sRGB; + else + return PIXELFORMAT_RGBA8_UNORM; + case PIXELFORMAT_HDR: + return PIXELFORMAT_RGBA16_FLOAT; + default: + return format; + } + } + + int GraphicsBase::getWidth() const + { + auto& info = love::getScreenInfo(currentScreen); + return info.width; + } + + int GraphicsBase::getHeight() const + { + auto& info = love::getScreenInfo(currentScreen); + return info.height; + } + + GraphicsBase::RendererInfo GraphicsBase::getRendererInfo() const + + { + RendererInfo info {}; + info.name = __RENDERER_NAME__; + info.version = __RENDERER_VERSION__; + info.vendor = __RENDERER_VENDOR__; + info.device = __RENDERER_DEVICE__; + + return info; + } + + void GraphicsBase::intersectScissor(const Rect& scissor) + { + auto current = this->states.back().scissorRect; + + if (!this->states.back().scissor) + { + current.x = 0; + current.y = 0; + + current.w = std::numeric_limits::max(); + current.h = std::numeric_limits::max(); + } + + int x1 = std::max(current.x, scissor.x); + int y1 = std::max(current.y, scissor.y); + int x2 = std::min(current.x + current.w, scissor.x + scissor.w); + int y2 = std::min(current.y + current.h, scissor.y + scissor.h); + + Rect newScisssor = { x1, y1, std::max(0, x2 - x1), std::max(0, y2 - y1) }; + this->setScissor(newScisssor); + } + + bool GraphicsBase::getScissor(Rect& scissor) const + { + const auto& state = this->states.back(); + + scissor = state.scissorRect; + return state.scissor; + } + + bool GraphicsBase::isRenderTargetActive() const + { + const auto& targets = this->states.back().renderTargets; + return !targets.colors.empty() || targets.depthStencil.texture != nullptr; + } + + bool GraphicsBase::isRenderTargetActive(TextureBase* texture) const + { + auto* root = texture->getRootViewInfo().texture; + const auto& targets = this->states.back().renderTargets; + + for (const auto& target : targets.colors) + { + if (target.texture.get() && target.texture->getRootViewInfo().texture == root) + return true; + } + + // clang-format off + if (targets.depthStencil.texture.get() && targets.depthStencil.texture->getRootViewInfo().texture == root) + return true; + // clang-format on + + return false; + } + + bool GraphicsBase::isRenderTargetActive(TextureBase* texture, int slice) const + { + const auto& rootInfo = texture->getRootViewInfo(); + slice += rootInfo.startLayer; + + const auto& targets = this->states.back().renderTargets; + + for (const auto& target : targets.colors) + { + const auto& info = target.texture->getRootViewInfo(); + if (rootInfo.texture == info.texture && target.slice + info.startLayer == slice) + return true; + } + + if (targets.depthStencil.texture.get()) + { + const auto& info = targets.depthStencil.texture->getRootViewInfo(); + if (rootInfo.texture == info.texture && targets.depthStencil.slice + info.startLayer == slice) + return true; + } + + return false; + } + + TextureBase* GraphicsBase::getDefaultTexture(TextureType type, DataBaseType dataType) + { + TextureBase* texture = this->defaultTextures[type]; + + if (texture != nullptr) + return texture; + + TextureBase::Settings settings {}; + settings.type = type; + settings.width = 1; + settings.height = 1; + + switch (dataType) + { + case DATA_BASETYPE_INT: + settings.format = PIXELFORMAT_RGBA8_INT; + break; + case DATA_BASETYPE_UINT: + settings.format = PIXELFORMAT_RGBA8_UINT; + break; + case DATA_BASETYPE_FLOAT: + default: + settings.format = PIXELFORMAT_RGBA8_UNORM; + break; + } + + if constexpr (Console::is(Console::CTR)) + { + settings.width = 5; + settings.height = 5; + } + + texture = this->newTexture(settings); + + SamplerState state {}; + state.minFilter = state.magFilter = SamplerState::FILTER_NEAREST; + state.wrapU = state.wrapV = state.wrapW = SamplerState::WRAP_CLAMP; + + texture->setSamplerState(state); + + uint8_t pixel[4] = { 255, 255, 255, 255 }; + if (isPixelFormatInteger(settings.format)) + pixel[0] = pixel[1] = pixel[2] = pixel[3] = 1; + + // clang-format off + for (int slice = 0; slice < (type == TEXTURE_CUBE ? 6 : 1); slice++) + texture->replacePixels(pixel, sizeof(pixel), slice, 0, { 0, 0, settings.width, settings.height }, false); + // clang-format on + + this->defaultTextures[type] = texture; + + return texture; + } + + void GraphicsBase::polyline(std::span vertices) + { + float halfWidth = this->getLineWidth() * 0.5f; + LineJoin lineJoin = this->getLineJoin(); + LineStyle lineStyle = this->getLineStyle(); + + float pixelSize = 1.0f / std::max((float)this->pixelScaleStack.back(), 0.000001f); + + if (lineJoin == LINE_JOIN_NONE) + { + NoneJoinPolyline line; + line.render(vertices.data(), vertices.size(), halfWidth, pixelSize, lineStyle == LINE_SMOOTH); + line.draw(this); + } + else if (lineJoin == LINE_JOIN_BEVEL) + { + BevelJoinPolyline line; + line.render(vertices.data(), vertices.size(), halfWidth, pixelSize, lineStyle == LINE_SMOOTH); + line.draw(this); + } + else if (lineJoin == LINE_JOIN_MITER) + { + MiterJoinPolyline line; + line.render(vertices.data(), vertices.size(), halfWidth, pixelSize, lineStyle == LINE_SMOOTH); + line.draw(this); + } + } + + void GraphicsBase::polygon(DrawMode mode, std::span vertices, bool skipLastFilledVertex) + { + if (mode == DRAW_LINE) + this->polyline(vertices); + else + { + const auto& transform = this->getTransform(); + bool is2D = transform.isAffine2DTransform(); + + BatchedDrawCommand command {}; + command.format = CommonFormat::XYf_STf_RGBAf; + command.indexMode = TRIANGLEINDEX_FAN; + command.vertexCount = (int)vertices.size() - (skipLastFilledVertex ? 1 : 0); + + BatchedVertexData data = this->requestBatchedDraw(command); + + // constexpr float inf = std::numeric_limits::infinity(); + // Vector2 minimum(inf, inf); + // Vector2 maximum(-inf, -inf); + + // for (int index = 0; index < command.vertexCount; index++) + // { + // Vector2 vector = vertices[index]; + // minimum.x = std::min(minimum.x, vector.x); + // minimum.y = std::min(minimum.y, vector.y); + // maximum.x = std::max(maximum.x, vector.x); + // maximum.y = std::max(maximum.y, vector.y); + // } + + // Vector2 invSize(1.0f / (maximum.x - minimum.x), 1.0f / (maximum.y - minimum.y)); + // Vector2 start(minimum.x * invSize.x, minimum.y * invSize.y); + + XYf_STf_RGBAf* stream = (XYf_STf_RGBAf*)data.stream; + + // for (int index = 0; index < command.vertexCount; index++) + // { + // stream[index].s = vertices[index].x * invSize.x - start.x; + // stream[index].t = vertices[index].y * invSize.y - start.y; + // stream[index].color = this->getColor(); + // } + + Color color = this->getColor(); + + for (int index = 0; index < command.vertexCount; index++) + { + stream[index].s = 0.0f; + stream[index].t = 0.0f; + stream[index].color = color; + } + + if (is2D) + transform.transformXY(stream, vertices.data(), command.vertexCount); + } + } + + int GraphicsBase::calculateEllipsePoints(float a, float b) const + { + auto points = (int)std::sqrt(((a + b) / 2.0f) * 20.0f * (float)this->pixelScaleStack.back()); + + return std::max(points, 8); + } + + void GraphicsBase::rectangle(DrawMode mode, float x, float y, float w, float h) + { + Vector2 coords[] = { Vector2(x, y), Vector2(x, y + h), Vector2(x + w, y + h), Vector2(x + w, y), + Vector2(x, y) }; + + this->polygon(mode, coords); + } + + void GraphicsBase::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry, + int points) + { + if (rx <= 0 || ry <= 0) + return this->rectangle(mode, x, y, w, h); + + if (w >= 0.02f) + rx = std::min(rx, w / 2.0f - 0.01f); + + if (h >= 0.02f) + ry = std::min(ry, h / 2.0f - 0.01f); + + points = std::max(points / 4, 1); + + const float halfPi = float(LOVE_M_PI / 2); + float shift = halfPi / ((float)points + 1.0f); + + size_t numCoords = (points + 2) * 4; + auto* coords = this->getScratchBuffer(numCoords + 1); + + float phi = 0.0f; + + for (int i = 0; i <= points + 2; ++i, phi += shift) + { + coords[i].x = x + rx * (1 - cosf(phi)); + coords[i].y = y + ry * (1 - sinf(phi)); + } + + phi = halfPi; + + for (int i = points + 2; i <= 2 * (points + 2); ++i, phi += shift) + { + coords[i].x = x + w - rx * (1 + cosf(phi)); + coords[i].y = y + ry * (1 - sinf(phi)); + } + + phi = 2 * halfPi; + + for (int i = 2 * (points + 2); i <= 3 * (points + 2); ++i, phi += shift) + { + coords[i].x = x + w - rx * (1 + cosf(phi)); + coords[i].y = y + h - ry * (1 + sinf(phi)); + } + + phi = 3 * halfPi; + + for (int i = 3 * (points + 2); i <= 4 * (points + 2); ++i, phi += shift) + { + coords[i].x = x + rx * (1 - cosf(phi)); + coords[i].y = y + h - ry * (1 + sinf(phi)); + } + + coords[numCoords] = coords[0]; + this->polygon(mode, std::span(coords, numCoords + 1)); + } + + void GraphicsBase::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry) + { + const float a = std::min(rx, std::abs(w / 2)); + const float b = std::min(ry, std::abs(h / 2)); + + int points = this->calculateEllipsePoints(a, b); + this->rectangle(mode, x, y, w, h, rx, ry, points); + } + + void GraphicsBase::ellipse(DrawMode mode, float x, float y, float a, float b, int points) + { + float twoPi = float(LOVE_M_PI * 2); + + if (points <= 0) + points = 1; + + float shift = twoPi / points; + float phi = 0.0f; + + int extraPoints = 1 + (mode == DRAW_FILL ? 1 : 0); + + auto* polygonCoords = this->getScratchBuffer(points + extraPoints); + auto* coords = polygonCoords; + + if (mode == DRAW_FILL) + { + coords[0].x = x; + coords[0].y = y; + coords++; + } + + for (int index = 0; index < points; ++index, phi += shift) + { + coords[index].x = x + a * cosf(phi); + coords[index].y = y + b * sinf(phi); + } + + coords[points] = coords[0]; + this->polygon(mode, std::span(polygonCoords, points + extraPoints), false); + } + + void GraphicsBase::ellipse(DrawMode mode, float x, float y, float a, float b) + { + int points = this->calculateEllipsePoints(a, b); + this->ellipse(mode, x, y, a, b, points); + } + + void GraphicsBase::circle(DrawMode mode, float x, float y, float radius, int points) + { + this->ellipse(mode, x, y, radius, radius, points); + } + + void GraphicsBase::circle(DrawMode mode, float x, float y, float radius) + { + this->ellipse(mode, x, y, radius, radius); + } + + void GraphicsBase::arc(DrawMode mode, ArcMode arcMode, float x, float y, float radius, float angle1, + float angle2, int points) + { + if (points <= 0 || angle1 == angle2) + return; + + if (std::abs(angle1 - angle2) >= 2.0f * (float)LOVE_M_PI) + { + this->circle(mode, x, y, radius, points); + return; + } + + float shift = (angle2 - angle1) / points; + + if (shift == 0.0) + return; + + if (mode == DRAW_LINE && arcMode == ARC_CLOSED && std::abs(angle1 - angle2) < LOVE_TORAD(4)) + arcMode = ARC_OPEN; + + if (mode == DRAW_FILL && arcMode == ARC_OPEN) + arcMode = ARC_CLOSED; + + float phi = angle1; + + Vector2* coords = nullptr; + int numCoords = 0; + + const auto createPoints = [&](Vector2* coordinates) { + for (int i = 0; i <= points; ++i, phi += shift) + { + coordinates[i].x = x + radius * cosf(phi); + coordinates[i].y = y + radius * sinf(phi); + } + }; + + if (arcMode == ARC_PIE) + { + numCoords = points + 3; + + coords = this->getScratchBuffer(numCoords); + coords[0] = coords[numCoords - 1] = Vector2(x, y); + + createPoints(coords + 1); + } + else if (arcMode == ARC_OPEN) + { + numCoords = points + 1; + coords = this->getScratchBuffer(numCoords); + + createPoints(coords); + } + else + { + numCoords = points + 2; + coords = this->getScratchBuffer(numCoords); + + createPoints(coords); + coords[numCoords - 1] = coords[0]; + } + + this->polygon(mode, std::span(coords, numCoords)); + } + + void GraphicsBase::arc(DrawMode mode, ArcMode arcMode, float x, float y, float radius, float angle1, + float angle2) + { + float points = (float)this->calculateEllipsePoints(radius, radius); + float angle = std::abs(angle1 - angle2); + + if (angle < 2.0f * (float)LOVE_M_PI) + points *= angle / (2.0f * (float)LOVE_M_PI); + + this->arc(mode, arcMode, x, y, radius, angle1, angle2, int(points + 0.5f)); + } + + void GraphicsBase::points(const Vector2* positions, const Color* colors, int count) + { + const Matrix4& transform = this->getTransform(); + bool is2D = transform.isAffine2DTransform(); + + BatchedDrawCommand command {}; + command.primitiveMode = PRIMITIVE_POINTS; + command.format = CommonFormat::XYf_STf_RGBAf; + command.vertexCount = count; + + BatchedVertexData data = this->requestBatchedDraw(command); + + XYf_STf_RGBAf* stream = (XYf_STf_RGBAf*)data.stream; + + if (is2D) + transform.transformXY(stream, positions, command.vertexCount); + + if (!colors) + { + Color color = this->getColor(); + + for (int index = 0; index < command.vertexCount; index++) + stream[index].color = color; + + return; + } + + Color color = this->getColor(); + gammaCorrectColor(color); + + if (isGammaCorrect()) + { + for (int index = 0; index < command.vertexCount; index++) + { + Color current = colors[index]; + + gammaCorrectColor(current); + current *= color; + unGammaCorrectColor(current); + + stream[index].color = current; + } + } + else + { + for (int index = 0; index < command.vertexCount; index++) + stream[index].color = colors[index]; + } + } + + void GraphicsBase::draw(Drawable* drawable, const Matrix4& matrix) + { + drawable->draw(this, matrix); + } + + void GraphicsBase::draw(TextureBase* texture, Quad* q, const Matrix4& matrix) + { + texture->draw(this, q, matrix); + } +} // namespace love diff --git a/source/modules/graphics/Mesh.cpp b/source/modules/graphics/Mesh.cpp new file mode 100644 index 000000000..f86ee96e5 --- /dev/null +++ b/source/modules/graphics/Mesh.cpp @@ -0,0 +1,355 @@ +#include "common/Exception.hpp" +#include "common/Matrix.hpp" + +#include "modules/graphics/Mesh.hpp" + +#include "modules/graphics/Graphics.tcc" +#include "modules/graphics/Shader.tcc" + +#include +#include + +namespace love +{ + // clang-format off + static_assert(offsetof(Vertex, x) == sizeof(float) * 0, "Incorrect position offset in Vertex struct"); + static_assert(offsetof(Vertex, s) == sizeof(float) * 2, "Incorrect texture coordinate offset in Vertex struct"); + static_assert(offsetof(Vertex, color.r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct"); + // clang-format on + + Type Mesh::type("Mesh", &Drawable::type); + + Mesh::Mesh(GraphicsBase* graphics, const void* data, size_t size, PrimitiveType mode, + BufferDataUsage usage) : + primitiveType(mode) + { + if (size == 0) + throw love::Exception("Mesh data size cannot be zero."); + + size_t vertexCount = size / sizeof(Vertex); + this->buffer.resize(vertexCount); + + this->indexDataType = getIndexDataTypeFromMax(vertexCount); + } + + Mesh::Mesh(GraphicsBase* graphics, int vertexCount, PrimitiveType mode, BufferDataUsage usage) : + vertexCount((size_t)vertexCount), + indexDataType(getIndexDataTypeFromMax(vertexCount)), + primitiveType(mode) + { + if (vertexCount <= 0) + throw love::Exception("Mesh vertex count cannot be zero."); + + this->buffer.resize(vertexCount); + this->vertexStride = sizeof(Vertex); + } + + Mesh::~Mesh() + {} + + void* Mesh::checkVertexDataOffset(size_t vertexIndex, size_t* byteOffset) + { + if (vertexIndex >= this->vertexCount) + throw love::Exception("Invalid vertex index: {:d}", vertexIndex + 1); + + if (this->buffer.empty()) + throw love::Exception("Mesh must own its own vertex buffer."); + + size_t offset = vertexIndex * sizeof(Vertex); + + if (byteOffset != nullptr) + *byteOffset = offset; + + return this->buffer.data() + offset; + } + + size_t Mesh::getVertexCount() const + { + return this->vertexCount; + } + + size_t Mesh::getVertexStride() const + { + return sizeof(Vertex); + } + + Vertex* Mesh::getVertexBuffer() + { + return this->buffer.data(); + } + + void* Mesh::getVertexData() + { + return (void*)this->buffer.data(); + } + + void Mesh::setVertexDataModified(size_t offset, size_t size) + { + if (!this->buffer.empty()) + this->modifiedVertexData.encapsulate(offset, size); + } + + void Mesh::flush() + { + if (this->buffer.data() != nullptr && this->modifiedVertexData.isValid()) + { + size_t offset = this->modifiedVertexData.getOffset(); + size_t size = this->modifiedVertexData.getSize(); + + this->modifiedVertexData.invalidate(); + } + } + + template + static void copyToIndexBuffer(const std::vector& indices, void* data, size_t maxValue) + { + T* elements = (T*)data; + + for (size_t index = 0; index < indices.size(); index++) + { + if (indices[index] > maxValue) + throw love::Exception("Invalid vertex map value: {:d}", indices[index] + 1); + + elements[index] = (T)indices[index]; + } + } + + void Mesh::setVertexMap(const std::vector& map) + { + if (map.empty()) + throw love::Exception("Vertex map cannot be empty."); + + size_t maxValue = this->getVertexCount(); + IndexDataType dataType = getIndexDataTypeFromMax(maxValue); + DataFormat dataFormat = getIndexDataFormat(dataType); + + bool recreate = this->indices.empty() || map.size() > this->indices.size(); + + if (recreate) + this->indices.resize(map.size()); + + this->indexCount = map.size(); + this->useIndexBuffer = true; + this->indexDataType = dataType; + + if (this->indexCount == 0) + return; + + switch (dataType) + { + case INDEX_UINT16: + copyToIndexBuffer(map, this->indices.data(), maxValue); + break; + case INDEX_UINT32: + default: + copyToIndexBuffer(map, this->indices.data(), maxValue); + break; + } + + this->indexDataModified = true; + } + + void Mesh::setVertexMap(IndexDataType type, const void* data, size_t dataSize) + { + auto dataFormat = getIndexDataFormat(type); + bool recreate = this->indices.empty() || dataSize > (this->indices.size() * sizeof(uint16_t)); + + if (recreate) + this->indices.resize(dataSize / getIndexDataSize(type)); + + this->indexCount = dataSize / getIndexDataSize(type); + this->useIndexBuffer = true; + this->indexDataType = type; + + if (this->indexCount == 0) + return; + + std::memcpy(this->indices.data(), data, dataSize); + this->indexDataModified = true; + } + + void Mesh::setVertexMap() + { + this->useIndexBuffer = false; + } + + template + static void copyFromIndexBuffer(const void* buffer, size_t count, std::vector& indices) + { + const T* elements = (const T*)buffer; + + for (size_t i = 0; i < count; i++) + indices.push_back((uint32_t)elements[i]); + } + + bool Mesh::getVertexMap(std::vector& map) const + { + if (!this->useIndexBuffer) + return false; + + map.clear(); + + if (this->indices.data() == nullptr || this->indexCount == 0) + return false; + + map.reserve(this->indexCount); + + switch (this->indexDataType) + { + case INDEX_UINT16: + copyFromIndexBuffer(this->indices.data(), this->indexCount, map); + break; + case INDEX_UINT32: + default: + copyFromIndexBuffer(this->indices.data(), this->indexCount, map); + break; + } + + return true; + } + + void Mesh::setIndexBuffer(std::vector& buffer) + { + if (this->buffer.data() == nullptr) + throw love::Exception("Mesh must own its own vertex buffer."); + + this->indices = buffer; + this->useIndexBuffer = buffer.data() != nullptr; + this->indexCount = buffer.data() != nullptr ? buffer.size() : 0; + + if (buffer.data() != nullptr) + this->indexDataType = INDEX_UINT16; + } + + uint16_t* Mesh::getIndexBuffer() + { + return this->indices.data(); + } + + size_t Mesh::getIndexCount() const + { + return this->indexCount; + } + + void Mesh::setTexture(TextureBase* texture) + { + this->texture.set(texture); + } + + void Mesh::setTexture() + { + this->texture.set(nullptr); + } + + TextureBase* Mesh::getTexture() const + { + return this->texture.get(); + } + + void Mesh::setDrawMode(PrimitiveType mode) + { + this->primitiveType = mode; + } + + PrimitiveType Mesh::getDrawMode() const + { + return this->primitiveType; + } + + void Mesh::setDrawRange(int start, int count) + { + if (start < 0 || count <= 0) + throw love::Exception("Invalid draw range: start = {:d}, count = {:d}", start, count); + + this->drawRange = Range(start, count); + } + + void Mesh::setDrawRange() + { + this->drawRange.invalidate(); + } + + bool Mesh::getDrawRange(int& start, int& count) const + { + if (!this->drawRange.isValid()) + return false; + + start = this->drawRange.getOffset(); + count = this->drawRange.getSize(); + + return true; + } + + // std::vector Mesh::getDefaultVertexFormat() + // { + // return BufferBase::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAf); + // } + + void Mesh::draw(GraphicsBase* graphics, const Matrix4& matrix) + { + this->drawInternal(graphics, matrix, 1, 0); + } + + void Mesh::drawInternal(GraphicsBase* graphics, const Matrix4& matrix, int instanceCount, int argsIndex) + { + if (this->vertexCount <= 0) + return; + + if (instanceCount > 1 && !graphics->getCapabilities().features[GraphicsBase::FEATURE_INSTANCING]) + throw love::Exception("Instanced drawing is not supported by the current graphics hardware."); + + // clang-format off + if (this->primitiveType == PRIMITIVE_TRIANGLE_FAN && this->useIndexBuffer && this->indices.data() != nullptr) + throw love::Exception("The 'fan' Mesh draw mode cannot be used with an index buffer / vertex map."); + // clang-format on + + graphics->flushBatchedDraws(); + this->flush(); + + if (ShaderBase::isDefaultActive()) + ShaderBase::attachDefault(ShaderBase::STANDARD_DEFAULT); + + GraphicsBase::TempTransform transform(graphics, matrix); + + int indexCount = this->indexCount; + Range range = this->drawRange; + + if (this->indices.data() != nullptr && this->indexCount > 0) + { + Range r(0, this->indexCount); + + if (range.isValid()) + r.intersect(range); + + DrawIndexedCommand command(nullptr, nullptr, nullptr); + command.primitiveType = this->primitiveType; + command.indexType = this->indexDataType; + command.instanceCount = instanceCount; + command.texture = this->texture; + command.cullMode = graphics->getMeshCullMode(); + command.indexBufferOffset = r.getOffset(); + command.indexCount = (int)r.getSize(); + + if (command.indexCount > 0) + graphics->draw(command); + } + else + { + Range r(0, this->vertexCount); + + if (range.isValid()) + r.intersect(range); + + // DrawCommand command {}; + // command.primitiveType = this->primitiveType; + // command.vertexStart = (int)r.getOffset(); + // command.vertexCount = (int)r.getSize(); + // command.instanceCount = instanceCount; + // command.texture = this->texture; + // command.cullMode = graphics->getMeshCullMode(); + + // if (command.vertexCount > 0) + // graphics->draw(command); + } + } +} // namespace love diff --git a/source/modules/graphics/Polyline.cpp b/source/modules/graphics/Polyline.cpp new file mode 100644 index 000000000..4c0f40d81 --- /dev/null +++ b/source/modules/graphics/Polyline.cpp @@ -0,0 +1,497 @@ +/** + * Copyright (c) 2006-2024 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 "modules/graphics/Polyline.hpp" +#include "modules/graphics/Graphics.hpp" + +// C++ +#include + +// treat adjacent segments with angles between their directions <5 degree as straight +static const float LINES_PARALLEL_EPS = 0.05f; + +namespace love +{ + void Polyline::render(const Vector2* coords, size_t count, size_t size_hint, float halfwidth, + float pixel_size, bool draw_overdraw) + { + static std::vector anchors; + anchors.clear(); + anchors.reserve(size_hint); + + static std::vector normals; + normals.clear(); + normals.reserve(size_hint); + + // prepare vertex arrays + if (draw_overdraw) + halfwidth -= pixel_size * 0.3f; + + // compute sleeve + bool is_looping = (coords[0] == coords[count - 1]); + Vector2 segment; + if (!is_looping) // virtual starting point at second point mirrored on first point + segment = coords[1] - coords[0]; + else // virtual starting point at last vertex + segment = coords[0] - coords[count - 2]; + + float segmentLength = segment.getLength(); + Vector2 segmentNormal = segment.getNormal(halfwidth / segmentLength); + + Vector2 pointA, pointB(coords[0]); + for (size_t i = 0; i + 1 < count; i++) + { + pointA = pointB; + pointB = coords[i + 1]; + renderEdge(anchors, normals, segment, segmentLength, segmentNormal, pointA, pointB, halfwidth); + } + + pointA = pointB; + pointB = is_looping ? coords[1] : pointB + segment; + renderEdge(anchors, normals, segment, segmentLength, segmentNormal, pointA, pointB, halfwidth); + + vertex_count = normals.size(); + + size_t extra_vertices = 0; + + if (draw_overdraw) + { + calc_overdraw_vertex_count(is_looping); + + // When drawing overdraw lines using triangle strips, we want to add an + // extra degenerate triangle in between the core line and the overdraw + // line in order to break up the strip into two. This will let us draw + // everything in one draw call. + if (triangle_mode == TRIANGLEINDEX_STRIP) + extra_vertices = 2; + } + + // Use a single linear array for both the regular and overdraw vertices. + vertices = new Vector2[vertex_count + extra_vertices + overdraw_vertex_count]; + + for (size_t i = 0; i < vertex_count; ++i) + vertices[i] = anchors[i] + normals[i]; + + if (draw_overdraw) + { + overdraw = vertices + vertex_count + extra_vertices; + overdraw_vertex_start = vertex_count + extra_vertices; + render_overdraw(normals, pixel_size, is_looping); + } + + // Add the degenerate triangle strip. + if (extra_vertices) + { + vertices[vertex_count + 0] = vertices[vertex_count - 1]; + vertices[vertex_count + 1] = vertices[overdraw_vertex_start]; + } + } + + void NoneJoinPolyline::renderEdge(std::vector& anchors, std::vector& normals, + Vector2& segment, float& segmentLength, Vector2& segmentNormal, + const Vector2& pointA, const Vector2& pointB, float halfWidth) + { + // ns1------ns2 + // | | + // q ------ r + // | | + // (-ns1)----(-ns2) + + anchors.push_back(pointA); + anchors.push_back(pointA); + normals.push_back(segmentNormal); + normals.push_back(-segmentNormal); + + segment = (pointB - pointA); + segmentLength = segment.getLength(); + segmentNormal = segment.getNormal(halfWidth / segmentLength); + + anchors.push_back(pointA); + anchors.push_back(pointA); + normals.push_back(segmentNormal); + normals.push_back(-segmentNormal); + } + + /** Calculate line boundary points. + * + * Sketch: + * + * u1 + * -------------+---...___ + * | ```'''-- --- + * p- - - - - - q- - . _ _ | w/2 + * | ` ' ' r + + * -------------+---...___ | w/2 + * u2 ```'''-- --- + * + * u1 and u2 depend on four things: + * - the half line width w/2 + * - the previous line vertex p + * - the current line vertex q + * - the next line vertex r + * + * u1/u2 are the intersection points of the parallel lines to p-q and q-r, + * i.e. the point where + * + * (q + w/2 * ns) + lambda * (q - p) = (q + w/2 * nt) + mu * (r - q) (u1) + * (q - w/2 * ns) + lambda * (q - p) = (q - w/2 * nt) + mu * (r - q) (u2) + * + * with ns,nt being the normals on the segments s = p-q and t = q-r, + * + * ns = perp(s) / |s| + * nt = perp(t) / |t|. + * + * Using the linear equation system (similar for u2) + * + * q + w/2 * ns + lambda * s - (q + w/2 * nt + mu * t) = 0 (u1) + * <=> q-q + lambda * s - mu * t = (nt - ns) * w/2 + * <=> lambda * s - mu * t = (nt - ns) * w/2 + * + * the intersection points can be efficiently calculated using Cramer's rule. + */ + void MiterJoinPolyline::renderEdge(std::vector& anchors, std::vector& normals, + Vector2& segment, float& segmentLength, Vector2& segmentNormal, + const Vector2& pointA, const Vector2& pointB, float halfwidth) + { + Vector2 newSegment = (pointB - pointA); + float newSegmentLength = newSegment.getLength(); + if (newSegmentLength == 0.0f) + { + // degenerate segment, skip it + return; + } + + Vector2 newSegmentNormal = newSegment.getNormal(halfwidth / newSegmentLength); + + anchors.push_back(pointA); + anchors.push_back(pointA); + + float det = Vector2::cross(segment, newSegment); + if (fabs(det) / (segmentLength * newSegmentLength) < LINES_PARALLEL_EPS) + { + // lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2 + normals.push_back(segmentNormal); + normals.push_back(-segmentNormal); + + if (Vector2::dot(segment, newSegment) < 0) + { + // line reverses direction; because the normal flips, the + // triangle strip would twist here, so insert a zero-size + // quad to contain the twist + // ____.___.____ + // | |\ /| | + // p q X q r + // |____|/ \|____| + anchors.push_back(pointA); + anchors.push_back(pointA); + normals.push_back(-segmentNormal); + normals.push_back(segmentNormal); + } + } + else + { + // cramers rule + float lambda = Vector2::cross((newSegmentNormal - segmentNormal), newSegment) / det; + Vector2 d = segmentNormal + segment * lambda; + normals.push_back(d); + normals.push_back(-d); + } + + segment = newSegment; + segmentNormal = newSegmentNormal; + segmentLength = newSegmentLength; + } + + /** Calculate line boundary points. + * + * Sketch: + * + * uh1___uh2 + * .' '. + * .' q '. + * .' ' ' '. + *.' ' .'. ' '. + * ' .' ul'. ' + * p .' '. r + * + * + * ul can be found as above, uh1 and uh2 are much simpler: + * + * uh1 = q + ns * w/2, uh2 = q + nt * w/2 + */ + void BevelJoinPolyline::renderEdge(std::vector& anchors, std::vector& normals, + Vector2& segment, float& segmentLength, Vector2& segmentNormal, + const Vector2& pointA, const Vector2& pointB, float halfWidth) + { + Vector2 newSegment = (pointB - pointA); + float newSegmentLength = newSegment.getLength(); + + float det = Vector2::cross(segment, newSegment); + if (fabs(det) / (segmentLength * newSegmentLength) < LINES_PARALLEL_EPS) + { + // lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2 + Vector2 newSegmentNormal = newSegment.getNormal(halfWidth / newSegmentLength); + anchors.push_back(pointA); + anchors.push_back(pointA); + normals.push_back(segmentNormal); + normals.push_back(-segmentNormal); + + if (Vector2::dot(segment, newSegment) < 0) + { + // line reverses direction; same as for miter + anchors.push_back(pointA); + anchors.push_back(pointA); + normals.push_back(-segmentNormal); + normals.push_back(segmentNormal); + } + + segment = newSegment; + segmentLength = newSegmentLength; + segmentNormal = newSegmentNormal; + return; // early out + } + + // cramers rule + Vector2 newSegmentNormal = newSegment.getNormal(halfWidth / newSegmentLength); + float lambda = Vector2::cross((newSegmentNormal - segmentNormal), newSegment) / det; + Vector2 d = segmentNormal + segment * lambda; + + 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(-segmentNormal); + normals.push_back(d); + normals.push_back(-newSegmentNormal); + } + else + { + normals.push_back(segmentNormal); + normals.push_back(-d); + normals.push_back(newSegmentNormal); + normals.push_back(-d); + } + segment = newSegment; + segmentLength = newSegmentLength; + segmentNormal = newSegmentNormal; + } + + void Polyline::calc_overdraw_vertex_count(bool is_looping) + { + overdraw_vertex_count = 2 * vertex_count + (is_looping ? 0 : 2); + } + + void Polyline::render_overdraw(const std::vector& normals, float pixel_size, bool is_looping) + { + // upper segment + for (size_t i = 0; i + 1 < vertex_count; i += 2) + { + overdraw[i] = vertices[i]; + overdraw[i + 1] = vertices[i] + normals[i] * (pixel_size / normals[i].getLength()); + } + // lower segment + for (size_t i = 0; i + 1 < vertex_count; i += 2) + { + size_t k = vertex_count - i - 1; + overdraw[vertex_count + i] = vertices[k]; + overdraw[vertex_count + i + 1] = vertices[k] + normals[k] * (pixel_size / normals[k].getLength()); + } + + // if not looping, the outer overdraw vertices need to be displaced + // to cover the line endings, i.e.: + // +- - - - //- - + +- - - - - //- - - + + // +-------//-----+ : +-------//-----+ : + // | core // line | --> : | core // line | : + // +-----//-------+ : +-----//-------+ : + // +- - //- - - - + +- - - //- - - - - + + if (!is_looping) + { + // left edge + Vector2 spacer = (overdraw[1] - overdraw[3]); + spacer.normalize(pixel_size); + overdraw[1] += spacer; + overdraw[overdraw_vertex_count - 3] += spacer; + + // right edge + spacer = (overdraw[vertex_count - 1] - overdraw[vertex_count - 3]); + spacer.normalize(pixel_size); + overdraw[vertex_count - 1] += spacer; + overdraw[vertex_count + 1] += spacer; + + // we need to draw two more triangles to close the + // overdraw at the line start. + overdraw[overdraw_vertex_count - 2] = overdraw[0]; + overdraw[overdraw_vertex_count - 1] = overdraw[1]; + } + } + + void NoneJoinPolyline::calc_overdraw_vertex_count(bool /*is_looping*/) + { + overdraw_vertex_count = 4 * (vertex_count - 2); // less than ideal + } + + void NoneJoinPolyline::render_overdraw(const std::vector& /*normals*/, float pixel_size, + bool /*is_looping*/) + { + for (size_t i = 2; i + 3 < vertex_count; i += 4) + { + // v0-v2 + // | / | <- main quad line + // v1-v3 + + Vector2 s = vertices[i + 0] - vertices[i + 2]; + Vector2 t = vertices[i + 0] - vertices[i + 1]; + s.normalize(pixel_size); + t.normalize(pixel_size); + + const size_t k = 4 * (i - 2); + + overdraw[k + 0] = vertices[i + 0]; + overdraw[k + 1] = vertices[i + 1]; + overdraw[k + 2] = vertices[i + 0] + s + t; + overdraw[k + 3] = vertices[i + 1] + s - t; + + overdraw[k + 4] = vertices[i + 1]; + overdraw[k + 5] = vertices[i + 3]; + overdraw[k + 6] = vertices[i + 1] + s - t; + overdraw[k + 7] = vertices[i + 3] - s - t; + + overdraw[k + 8] = vertices[i + 3]; + overdraw[k + 9] = vertices[i + 2]; + overdraw[k + 10] = vertices[i + 3] - s - t; + overdraw[k + 11] = vertices[i + 2] - s + t; + + overdraw[k + 12] = vertices[i + 2]; + overdraw[k + 13] = vertices[i + 0]; + overdraw[k + 14] = vertices[i + 2] - s + t; + overdraw[k + 15] = vertices[i + 0] + s + t; + } + } + + Polyline::~Polyline() + { + if (vertices) + delete[] vertices; + } + + void Polyline::draw(GraphicsBase* gfx) + { + const Matrix4& t = gfx->getTransform(); + bool is2D = t.isAffine2DTransform(); + Color curcolor = gfx->getColor(); + + int overdraw_start = (int)overdraw_vertex_start; + int overdraw_count = (int)overdraw_vertex_count; + + int total_vertex_count = (int)vertex_count; + if (overdraw) + total_vertex_count = overdraw_start + overdraw_count; + + // love's automatic batching can only deal with < 65k vertices per draw. + // uint16_max - 3 is evenly divisible by 6 (needed for quads mode). + int maxvertices = LOVE_UINT16_MAX - 3; + + int advance = maxvertices; + if (triangle_mode == TRIANGLEINDEX_STRIP) + advance -= 2; + + for (int vertex_start = 0; vertex_start < total_vertex_count; vertex_start += advance) + { + const Vector2* verts = vertices + vertex_start; + + BatchedDrawCommand cmd {}; + cmd.format = CommonFormat::XYf_STf_RGBAf; + cmd.indexMode = triangle_mode; + cmd.vertexCount = std::min(maxvertices, total_vertex_count - vertex_start); + + BatchedVertexData data = gfx->requestBatchedDraw(cmd); + + if (is2D) + t.transformXY((XYf_STf_RGBAf*)data.stream, verts, cmd.vertexCount); + + XYf_STf_RGBAf* attributes = (XYf_STf_RGBAf*)data.stream; + int draw_rough_count = std::min(cmd.vertexCount, (int)vertex_count - vertex_start); + + // Constant vertex color up to the overdraw vertices. + // Texture coordinates are a constant value, we only have them to keep auto-batching + // when drawing filled and line polygons together. + for (int i = 0; i < draw_rough_count; i++) + { + attributes[i].s = 0.0f; + attributes[i].t = 0.0f; + attributes[i].color = curcolor; + } + + if (overdraw) + { + int draw_remaining_count = cmd.vertexCount - draw_rough_count; + + int draw_overdraw_begin = overdraw_start - vertex_start; + int draw_overdraw_end = draw_overdraw_begin + overdraw_count; + + draw_overdraw_begin = std::max(0, draw_overdraw_begin); + + int draw_overdraw_count = + std::min(draw_remaining_count, draw_overdraw_end - draw_overdraw_begin); + + if (draw_overdraw_count > 0) + { + XYf_STf_RGBAf* c = attributes + draw_overdraw_begin; + fill_color_array(curcolor, c, draw_overdraw_count); + } + } + } + } + + void Polyline::fill_color_array(Color constant_color, XYf_STf_RGBAf* attributes, int count) + { + // Note: assigning each element individually seems to be needed to avoid + // performance issues in OpenGL + Windows. VS' compiler is likely doing + // something that doesn't play nice with write-combined memory from the + // graphics driver, when assigning a whole struct after modifying it, or + // when using memcpy. + for (int i = 0; i < count; ++i) + { + attributes[i].s = 0.0f; + attributes[i].t = 0.0f; + attributes[i].color.r = constant_color.r; + attributes[i].color.g = constant_color.g; + attributes[i].color.b = constant_color.b; + attributes[i].color.a = constant_color.a * ((i + 1) % 2); + } + } + + void NoneJoinPolyline::fill_color_array(Color constant_color, XYf_STf_RGBAf* attributes, int count) + { + for (int i = 0; i < count; ++i) + { + attributes[i].s = 0.0f; + attributes[i].t = 0.0f; + attributes[i].color.r = constant_color.r; + attributes[i].color.g = constant_color.g; + attributes[i].color.b = constant_color.b; + attributes[i].color.a = constant_color.a * ((i & 3) < 2); + } + } +} // namespace love diff --git a/source/modules/graphics/Quad.cpp b/source/modules/graphics/Quad.cpp new file mode 100644 index 000000000..1cab82db9 --- /dev/null +++ b/source/modules/graphics/Quad.cpp @@ -0,0 +1,37 @@ +#include "modules/graphics/Quad.hpp" + +#include + +namespace love +{ + Type Quad::type("Quad", &Object::type); + + Quad::Quad(const Viewport& viewport, double sourceWidth, double sourceHeight) : + sourceWidth(sourceWidth), + sourceHeight(sourceHeight) + { + this->refresh(viewport, sourceWidth, sourceHeight); + } + + Quad::~Quad() + {} + + void Quad::refresh(const Viewport& v, double sourceWidth, double sourceHeight) + { + this->viewport = v; + this->sourceWidth = sourceWidth; + this->sourceHeight = sourceHeight; + + this->vertexPositions[0] = Vector2(0.0f, 0.0f); + this->vertexPositions[1] = Vector2(0.0f, (float)v.h); + this->vertexPositions[2] = Vector2((float)v.w, 0.0f); + this->vertexPositions[3] = Vector2((float)v.w, (float)v.h); + + // clang-format off + this->textureCoordinates[0] = Vector2(float(v.x / sourceWidth), float(v.y / sourceHeight)); + this->textureCoordinates[1] = Vector2(float(v.x / sourceWidth), float((v.y + v.h) / sourceHeight)); + this->textureCoordinates[2] = Vector2(float((v.x + v.w) / sourceWidth), float(v.y / sourceHeight)); + this->textureCoordinates[3] = Vector2(float((v.x + v.w) / sourceWidth), float((v.y + v.h) / sourceHeight)); + // clang-format on + } +} // namespace love diff --git a/source/modules/graphics/Shader.cpp b/source/modules/graphics/Shader.cpp new file mode 100644 index 000000000..71a96efb0 --- /dev/null +++ b/source/modules/graphics/Shader.cpp @@ -0,0 +1,51 @@ +#include "common/Exception.hpp" + +#include "modules/graphics/Shader.tcc" + +namespace love +{ + ShaderBase* ShaderBase::current = nullptr; + ShaderBase* ShaderBase::standardShaders[STANDARD_MAX_ENUM] = { nullptr }; + + int ShaderBase::shaderSwitches = 0; + + ShaderBase::ShaderBase(StandardShader type) : shaderType(type) + {} + + ShaderBase::~ShaderBase() + { + for (int index = 0; index < STANDARD_MAX_ENUM; index++) + { + if (this == standardShaders[index]) + standardShaders[index] = nullptr; + } + + if (current == this) + this->attachDefault(STANDARD_DEFAULT); + } + + void ShaderBase::attachDefault(StandardShader type) + { + auto* defaultShader = standardShaders[type]; + + if (defaultShader == nullptr) + { + current = nullptr; + return; + } + + if (current != defaultShader) + defaultShader->attach(); + } + + bool ShaderBase::isDefaultActive() + { + for (int index = 0; index < STANDARD_MAX_ENUM; index++) + { + if (current == standardShaders[index]) + return true; + } + + return false; + } +} // namespace love diff --git a/source/modules/graphics/TextBatch.cpp b/source/modules/graphics/TextBatch.cpp new file mode 100644 index 000000000..6d3cd623d --- /dev/null +++ b/source/modules/graphics/TextBatch.cpp @@ -0,0 +1,243 @@ +#include "common/Console.hpp" + +#include "modules/graphics/Graphics.tcc" +#include "modules/graphics/TextBatch.hpp" + +#include + +namespace love +{ + Type TextBatch::type("TextBatch", &Drawable::type); + + TextBatch::TextBatch(FontBase* font, const std::vector& text) : + font(font), + vertexAttributes(CommonFormat::XYf_STf_RGBAf, 0), + buffer {}, + modifiedVertices(), + vertexOffset(0), + textureCacheID((uint32_t)-1) + { + this->set(text); + } + + TextBatch::~TextBatch() + {} + + void TextBatch::uploadVertices(const std::vector& vertices, size_t vertexOffset) + { + size_t offset = vertexOffset * sizeof(FontBase::GlyphVertex); + size_t dataSize = vertices.size() * sizeof(FontBase::GlyphVertex); + + const auto currentByteSize = this->buffer.size() * sizeof(FontBase::GlyphVertex); + + if (dataSize > 0 && (!this->vertexBuffer || (offset + dataSize) > currentByteSize)) + { + size_t newSize = (size_t(offset + dataSize * 1.5)) / sizeof(FontBase::GlyphVertex); + + if (!this->buffer.empty()) + newSize = std::max(size_t(this->buffer.size() * 1.5), newSize); + + this->buffer.resize(newSize); + } + + if (!this->buffer.empty() && dataSize > 0) + { + std::memcpy(&this->buffer[vertexOffset], &vertices[0], dataSize); + this->modifiedVertices = Range(offset, dataSize); + } + } + + void TextBatch::regenerateVertices() + { + if (this->font->getTextureCacheID() != this->textureCacheID) + { + std::vector textData = this->textData; + this->clear(); + + for (const TextData& data : textData) + this->addTextData(data); + + this->textureCacheID = this->font->getTextureCacheID(); + } + } + + void TextBatch::addTextData(const TextData& data) + { + std::vector vertices {}; + std::vector newCommands {}; + + TextShaper::TextInfo textInfo {}; + Color constantColor = Color(1.0f, 1.0f, 1.0f, 1.0f); + + // clang-format off + if (data.align == FontBase::ALIGN_MAX_ENUM) + newCommands = this->font->generateVertices(data.codepoints, Range(), constantColor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textInfo); + else + newCommands = this->font->generateVerticesFormatted(data.codepoints, constantColor, data.wrap, data.align, vertices, &textInfo); + // clang-format on + + size_t vertexOffset = this->vertexOffset; + + if (!data.appendVertices) + { + vertexOffset = 0; + this->vertexOffset = 0; + this->drawCommands.clear(); + this->textData.clear(); + } + + if (data.useMatrix && !vertices.empty()) + data.matrix.transformXY(vertices.data(), vertices.data(), (int)vertices.size()); + + this->uploadVertices(vertices, vertexOffset); + + if (!newCommands.empty()) + { + for (auto& command : newCommands) + command.startVertex += vertexOffset; + + auto firstCommand = newCommands.begin(); + + if (!this->drawCommands.empty()) + { + auto previous = this->drawCommands.back(); + bool sameRange = (previous.startVertex + previous.vertexCount) == firstCommand->startVertex; + + if (previous.texture == firstCommand->texture && sameRange) + { + previous.vertexCount += firstCommand->vertexCount; + ++firstCommand; + } + } + + this->drawCommands.insert(this->drawCommands.end(), firstCommand, newCommands.end()); + } + + this->vertexOffset += vertices.size(); + this->textData.push_back(data); + + if (this->font->getTextureCacheID() != this->textureCacheID) + this->regenerateVertices(); + } + + void TextBatch::set(const std::vector& text) + { + return this->set(text, -1.0f, FontBase::ALIGN_MAX_ENUM); + } + + void TextBatch::set(const std::vector& text, float wrap, FontBase::AlignMode align) + { + if (text.empty() || (text.size() == 1 && text[0].string.empty())) + return this->clear(); + + ColoredCodepoints codepoints {}; + getCodepointsFromString(text, codepoints); + + this->addTextData({ codepoints, wrap, align, {}, false, false, Matrix4() }); + } + + int TextBatch::add(const std::vector& text, const Matrix4& matrix) + { + return this->addf(text, -1.0f, FontBase::ALIGN_MAX_ENUM, matrix); + } + + int TextBatch::addf(const std::vector& text, float wrap, FontBase::AlignMode align, + const Matrix4& matrix) + { + ColoredCodepoints codepoints {}; + getCodepointsFromString(text, codepoints); + + this->addTextData({ codepoints, wrap, align, {}, true, true, matrix }); + + return (int)this->textData.size() - 1; + } + + void TextBatch::clear() + { + this->textData.clear(); + this->drawCommands.clear(); + this->textureCacheID = this->font->getTextureCacheID(); + this->vertexOffset = 0; + } + + void TextBatch::setFont(FontBase* font) + { + this->font.set(font); + this->textureCacheID = (uint32_t)-1; + this->regenerateVertices(); + } + + FontBase* TextBatch::getFont() const + { + return this->font.get(); + } + + int TextBatch::getWidth(int index) const + { + if (index < 0) + index = std::max((int)this->textData.size() - 1, 0); + + if (index >= (int)this->textData.size()) + return 0; + + return this->textData[index].textInfo.width; + } + + int TextBatch::getHeight(int index) const + { + if (index < 0) + index = std::max((int)this->textData.size() - 1, 0); + + if (index >= (int)this->textData.size()) + return 0; + + return this->textData[index].textInfo.height; + } + + static constexpr auto shaderType = + Console::is(Console::CTR) ? ShaderBase::STANDARD_DEFAULT : ShaderBase::STANDARD_TEXTURE; + + void TextBatch::draw(GraphicsBase* graphics, const Matrix4& matrix) + { + if (this->buffer.empty() || this->drawCommands.empty()) + return; + + graphics->flushBatchedDraws(); + + if (this->font->getTextureCacheID() != this->textureCacheID) + this->regenerateVertices(); + + if (ShaderBase::isDefaultActive()) + ShaderBase::attachDefault(ShaderBase::STANDARD_DEFAULT); + + TextureBase* firstTexture = nullptr; + if (!this->drawCommands.empty()) + firstTexture = this->drawCommands[0].texture; + + int totalVertices = 0; + for (const auto& command : this->drawCommands) + totalVertices = std::max(command.startVertex + command.vertexCount, totalVertices); + + if (this->modifiedVertices.isValid()) + this->modifiedVertices.invalidate(); + + GraphicsBase::TempTransform transform(graphics, matrix); + + for (const FontBase::DrawCommand& cmd : this->drawCommands) + { + BatchedDrawCommand command {}; + command.format = CommonFormat::XYf_STf_RGBAf; + command.indexMode = TRIANGLEINDEX_QUADS; + command.vertexCount = cmd.vertexCount; + command.texture = cmd.texture; + command.shaderType = shaderType; + command.isFont = true; + command.pushTransform = false; + + auto data = graphics->requestBatchedDraw(command); + FontBase::GlyphVertex* vertexdata = (FontBase::GlyphVertex*)data.stream; + + std::copy_n(&this->buffer[cmd.startVertex], cmd.vertexCount, vertexdata); + } + } +} // namespace love diff --git a/source/modules/graphics/Texture.cpp b/source/modules/graphics/Texture.cpp new file mode 100644 index 000000000..f876ac547 --- /dev/null +++ b/source/modules/graphics/Texture.cpp @@ -0,0 +1,679 @@ +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/Graphics.tcc" + +#include "common/Console.hpp" + +using namespace love; + +int TextureBase::textureCount = 0; +int64_t TextureBase::totalGraphicsMemory = 0; + +#define E_INVALID_LAYER_COUNT \ + "Invalid number of image data layers in mipmap level {:d} (expected {:d}, got {:d})" +#define E_MISSING_DATA "Missing image data (slice {:d}, mipmap level {:d})" +#define E_SIDE_INVALID \ + "{:s} of image data (slice {:d}, mipmap level {:d}) is incorrect (expected {:d}, got {:d})" +#define E_ALL_SLICES_AND_MIPMAPS_TYPE "All texture slices and mipmaps must have the same {:s}" + +namespace love +{ + // #region Slices + + void TextureBase::Slices::set(int slice, int mipmap, ImageDataBase* imageData) + { + if (this->textureType == TEXTURE_VOLUME) + { + if (mipmap >= (int)this->data.size()) + this->data.resize(mipmap + 1); + + if (slice >= (int)this->data[mipmap].size()) + this->data[mipmap].resize(slice + 1); + + this->data[mipmap][slice].set(imageData); + } + else + { + if (slice >= (int)this->data.size()) + this->data.resize(slice + 1); + + if (mipmap >= (int)this->data[slice].size()) + this->data[slice].resize(mipmap + 1); + + this->data[slice][mipmap].set(imageData); + } + } + + ImageDataBase* TextureBase::Slices::get(int slice, int mipmap) const + { + if (slice < 0 || slice >= this->getSliceCount(mipmap)) + return nullptr; + + if (mipmap < 0 || mipmap >= this->getMipmapCount(slice)) + return nullptr; + + if (this->textureType == TEXTURE_VOLUME) + return this->data[mipmap][slice].get(); + else + return this->data[slice][mipmap].get(); + } + + void TextureBase::Slices::add(CompressedImageData* compressedData, int startSlice, int startMipmap, + bool addAllSlices, bool addAllMipmaps) + + { + int sliceCount = addAllSlices ? compressedData->getSliceCount() : 1; + int mipCount = addAllMipmaps ? compressedData->getMipmapCount() : 1; + + for (int mip = 0; mip < mipCount; mip++) + { + for (int slice = 0; slice < sliceCount; slice++) + this->set(startSlice + slice, startMipmap + mip, compressedData->getSlice(slice, mip)); + } + } + + int TextureBase::Slices::getSliceCount(int mipmap) const + { + if (this->textureType == TEXTURE_VOLUME) + { + if (mipmap < 0 || mipmap >= (int)this->data.size()) + return 0; + + return (int)this->data[mipmap].size(); + } + else + return (int)this->data.size(); + } + + int TextureBase::Slices::getMipmapCount(int slice) const + { + if (this->textureType == TEXTURE_VOLUME) + return (int)this->data.size(); + else + { + if (slice < 0 || slice >= (int)this->data.size()) + return 0; + + return this->data[slice].size(); + } + } + + bool TextureBase::Slices::validate() const + { + int sliceCount = this->getSliceCount(); + int mipCount = this->getMipmapCount(0); + + if (sliceCount == 0 || mipCount == 0) + throw love::Exception("At least one ImageData or CompressedImageData is required."); + + if (this->textureType == TEXTURE_CUBE && sliceCount != 6) + throw love::Exception("Cube textures must have exactly 6 sides."); + + auto* firstData = this->get(0, 0); + + int width = firstData->getWidth(); + int height = firstData->getHeight(); + auto format = firstData->getFormat(); + bool linear = firstData->isLinear(); + + if (this->textureType == TEXTURE_CUBE && width != height) + throw love::Exception("Cube textures must be square."); + + int mipW = width; + int mipH = height; + int mipSlices = sliceCount; + + for (int mip = 0; mip < mipCount; mip++) + { + if (this->textureType == TEXTURE_VOLUME) + { + sliceCount = this->getSliceCount(mip); + + if (sliceCount != mipSlices) + throw love::Exception(E_INVALID_LAYER_COUNT, mip + 1, mipSlices, sliceCount); + } + + for (int slice = 0; slice < sliceCount; slice++) + { + auto* slicedata = get(slice, mip); + + if (slicedata == nullptr) + throw love::Exception(E_MISSING_DATA, slice + 1, mip + 1); + + int realW = slicedata->getWidth(); + int realH = slicedata->getHeight(); + + if (getMipmapCount(slice) != mipCount) + throw love::Exception("All texture layers must have the same mipmap count."); + + if (mipW != realW) + throw love::Exception(E_SIDE_INVALID, "width", slice + 1, mip + 1, mipW, realW); + + if (mipH != realH) + throw love::Exception(E_SIDE_INVALID, "height", slice + 1, mip + 1, mipH, realH); + + if (format != slicedata->getFormat()) + throw love::Exception(E_ALL_SLICES_AND_MIPMAPS_TYPE, "pixel format."); + + if (linear != slicedata->isLinear()) + throw love::Exception(E_ALL_SLICES_AND_MIPMAPS_TYPE, "linear setting."); + } + } + + mipW = std::max(mipW / 2, 1); + mipH = std::max(mipH / 2, 1); + + if (this->textureType == TEXTURE_VOLUME) + mipSlices = std::max(mipSlices / 2, 1); + + return true; + } + + // #endregion + + TextureBase::TextureBase(GraphicsBase* graphics, const Settings& settings, const Slices* slices) : + textureType(settings.type), + format(settings.format), + renderTarget(settings.renderTarget), + computeWrite(settings.computeWrite), + readable(true), + viewFormats(settings.viewFormats), + mipmapsMode(settings.mipmaps), + width(settings.width), + height(settings.height), + depth(settings.type == TEXTURE_VOLUME ? settings.layers : 1), + layers(settings.type == TEXTURE_2D_ARRAY ? settings.layers : 1), + mipmapCount(1), + pixelWidth(0), + pixelHeight(0), + requestedMSAA(settings.msaa > 1 ? settings.msaa : 1), + samplerState(), + graphicsMemorySize(0), + debugName(settings.debugName), + rootView { this, 0, 0 }, + parentView { this, 0, 0 } + { + const auto& capabilities = graphics->getCapabilities(); + int requestedMipmapCount = settings.mipmapCount; + + if (slices != nullptr && slices->getMipmapCount() > 0 && slices->getSliceCount() > 0) + { + this->textureType = slices->getTextureType(); + + if (requestedMSAA > 1) + throw love::Exception("MSAA textures cannot be created from image data."); + + int dataMipmaps = 1; + if (slices->validate() && slices->getMipmapCount() > 1) + { + dataMipmaps = slices->getMipmapCount(); + + if (requestedMipmapCount > 0) + dataMipmaps = std::min(requestedMipmapCount, dataMipmaps); + else + requestedMipmapCount = dataMipmaps; + } + + auto* slice = slices->get(0, 0); //< ImageDataBase* + + this->format = slice->getFormat(); + if (love::isGammaCorrect() && !slice->isLinear()) + this->format = love::getLinearPixelFormat(this->format); + + this->pixelWidth = slice->getWidth(); + this->pixelHeight = slice->getHeight(); + + if (this->textureType == TEXTURE_2D_ARRAY) + this->layers = slices->getSliceCount(); + else if (this->textureType == TEXTURE_VOLUME) + this->depth = slices->getSliceCount(); + + this->width = (int)(this->pixelWidth / settings.dpiScale + 0.5); + this->height = (int)(this->pixelHeight / settings.dpiScale + 0.5); + + if (this->isCompressed() && dataMipmaps <= 1) + this->mipmapsMode = MIPMAPS_NONE; + } + else + { + if (this->isCompressed()) + throw love::Exception("Compressed textures must be created with initial data."); + + this->pixelWidth = ((int)(width * settings.dpiScale) + 0.5); + this->pixelHeight = ((int)(height * settings.dpiScale) + 0.5); + } + + if (settings.readable.hasValue) + this->readable = settings.readable.value; + else + this->readable = !this->renderTarget || !love::isPixelFormatDepthStencil(this->format); + + this->format = graphics->getSizedFormat(this->format); + if (!love::isGammaCorrect() || settings.linear) + format = love::getLinearPixelFormat(format); + + if (this->mipmapsMode == MIPMAPS_AUTO && this->isCompressed()) + this->mipmapsMode = MIPMAPS_MANUAL; + + if (this->mipmapsMode != MIPMAPS_NONE) + { + int totalMipmapCount = getTotalMipmapCount(this->width, this->height, this->depth); + + if (requestedMipmapCount > 0) + this->mipmapCount = std::min(totalMipmapCount, requestedMipmapCount); + else + this->mipmapCount = totalMipmapCount; + + // clang-format off + if (this->mipmapCount != totalMipmapCount && !capabilities.features[GraphicsBase::FEATURE_MIPMAP_RANGE]) + throw love::Exception(E_CUSTOM_MIPMAP_RANGES_NOT_SUPPORTED, totalMipmapCount, this->mipmapCount); + // clang-format on + } + + const char* mipmapError = nullptr; + if (this->mipmapsMode == MIPMAPS_AUTO && !this->supportsGenerateMipmaps(mipmapError)) + { + std::string_view name = "unknown"; + love::getConstant(this->format, name); + throw love::Exception(E_AUTO_MIPMAPS_NOT_SUPPORTED, name); + } + + if (this->pixelWidth <= 0 || this->pixelHeight <= 0 || this->layers <= 0 || this->depth <= 0) + throw love::Exception("Texture dimensions must be greater than 0."); + + if (this->textureType != TEXTURE_2D && requestedMSAA > 1) + throw love::Exception("MSAA is only supported for textures with the 2D texture type."); + + if (!this->renderTarget && requestedMSAA > 1) + throw love::Exception("MSAA is only supported for render target textures."); + + if (this->readable && love::isPixelFormatDepthStencil(this->format) && settings.msaa > 1) + throw love::Exception("Readable depth/stencil textures with MSAA are not currently supported."); + + if ((!this->readable || settings.msaa > 1) && this->mipmapsMode != MIPMAPS_NONE) + throw love::Exception("Non-readable and MSAA textures cannot have mipmaps."); + + if (!this->readable && this->textureType != TEXTURE_2D) + throw love::Exception("Non-readable pixel formats are only supported for 2D texture types."); + + if (this->isCompressed() && this->renderTarget) + throw love::Exception("Compressed textures cannot be render targets."); + + for (auto viewFormat : this->viewFormats) + { + if (getLinearPixelFormat(viewFormat) == getLinearPixelFormat(this->format)) + continue; + + if (isPixelFormatCompressed(this->format) || isPixelFormatCompressed(viewFormat)) + throw love::Exception(E_COMPRESSED_TEXTURES_MISMATCHED_VIEW); + + if (isPixelFormatColor(this->format) != isPixelFormatColor(viewFormat)) + throw love::Exception(E_COLOR_FORMAT_TEXTURES_MISMATCHED_VIEW); + + if (isPixelFormatDepthStencil(viewFormat)) + throw love::Exception(E_DEPTH_STENCIL_TEXTURES_MISMATCHED_VIEW); + + size_t viewBytes = getPixelFormatBlockSize(viewFormat); + size_t baseBytes = getPixelFormatBlockSize(this->format); + + if (viewBytes != baseBytes) + throw love::Exception(E_TEXTURE_VIEW_BITS_PER_PIXEL_MISMATCHED); + } + + this->validatePixelFormat(graphics); + + if (!capabilities.textureTypes[this->textureType]) + { + std::string_view name = "unknown"; + TextureBase::getConstant(this->textureType, name); + throw love::Exception("{:s} textures are not supported on this system.", name); + } + + this->validateDimensions(true); + + if (this->getMipmapCount() == 1) + this->samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; + + Quad::Viewport viewport = { 0, 0, (double)this->width, (double)this->height }; + this->quad.set(new Quad(viewport, this->width, this->height), Acquire::NO_RETAIN); + + ++textureCount; + } + + TextureBase::~TextureBase() + { + this->setGraphicsMemorySize(0); + + if (this == rootView.texture) + --textureCount; + + if (rootView.texture != this && rootView.texture != nullptr) + rootView.texture->release(); + + if (parentView.texture != this && parentView.texture != nullptr) + parentView.texture->release(); + } + + void TextureBase::setGraphicsMemorySize(int64_t size) + { + totalGraphicsMemory = std::max(totalGraphicsMemory - this->graphicsMemorySize, (int64_t)0); + + size = std::max(size, (int64_t)0); + this->graphicsMemorySize = size; + totalGraphicsMemory += size; + } + + void TextureBase::draw(GraphicsBase* graphics, const Matrix4& matrix) + { + this->draw(graphics, this->quad, matrix); + } + + static constexpr ShaderBase::StandardShader shader = + (Console::is(Console::CTR) ? ShaderBase::STANDARD_DEFAULT : ShaderBase::STANDARD_TEXTURE); + + void TextureBase::draw(GraphicsBase* graphics, Quad* quad, const Matrix4& matrix) + { + if (!this->readable) + throw love::Exception("Textures with non-readable formats cannot be drawn."); + + if (this->renderTarget && graphics->isRenderTargetActive(this)) + throw love::Exception("Cannot render a Texture to itself."); + + const auto& transform = graphics->getTransform(); + bool is2D = transform.isAffine2DTransform(); + + BatchedDrawCommand command {}; + command.format = CommonFormat::XYf_STf_RGBAf; + command.indexMode = TRIANGLEINDEX_QUADS; + command.vertexCount = 4; + command.texture = this; + command.shaderType = shader; + + BatchedVertexData data = graphics->requestBatchedDraw(command); + + Matrix4 translated(transform, matrix); + + XYf_STf_RGBAf* stream = (XYf_STf_RGBAf*)data.stream; + + if (is2D) + translated.transformXY(stream, quad->getVertexPositions(), 4); + + if constexpr (Console::is(Console::CTR)) + this->updateQuad(quad); + + const auto* texCoords = quad->getTextureCoordinates(); + Color color = graphics->getColor(); + + for (int index = 0; index < 4; index++) + { + stream[index].s = texCoords[index].x; + stream[index].t = texCoords[index].y; + stream[index].color = color; + } + } + + void TextureBase::uploadImageData(ImageDataBase* data, int level, int slice, int x, int y) + { + Rect rectangle = { x, y, data->getWidth(), data->getHeight() }; + this->uploadByteData(data->getData(), data->getSize(), level, slice, rectangle); + } + + void TextureBase::replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, + bool reloadMipmaps) + { + if (!this->isReadable()) + throw love::Exception("replacePixels can only be called on a readable Texture."); + + if (this->getMSAA() > 1) + throw love::Exception("replacePixels cannot be called on a multisampled Texture."); + + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + if (graphics != nullptr && graphics->isRenderTargetActive(this)) + throw love::Exception("Cannot replace pixels of a Texture that is currently being rendered to."); + + if (this->getHandle() == 0) + return; + + if (getLinearPixelFormat(data->getFormat()) != getLinearPixelFormat(this->getPixelFormat())) + throw love::Exception("Pixel formats must match."); + + if (mipmap < 0 || mipmap >= this->getMipmapCount()) + throw love::Exception("Invalid texture mipmap index {:d}.", mipmap); + + if (slice < 0 || (this->textureType == TEXTURE_CUBE && slice >= 6) || + (this->textureType == TEXTURE_VOLUME && slice >= this->getDepth(mipmap)) || + (this->textureType == TEXTURE_2D_ARRAY && slice >= this->getLayerCount())) + { + throw love::Exception("Invalid slice index {:d}.", slice); + } + + Rect rect = { x, y, data->getWidth(), data->getHeight() }; + + int mipW = this->getPixelWidth(mipmap); + int mipH = this->getPixelHeight(mipmap); + + const bool rectSizeInvalid = rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0; + if (rectSizeInvalid || (rect.x + rect.w) > mipW || (rect.y + rect.h) > mipH) + throw love::Exception(E_INVALID_RECT_DIMENSIONS, rect.x, rect.y, rect.w, rect.h, mipW, mipH); + + GraphicsBase::flushBatchedDrawsGlobal(); + + this->uploadImageData(data, mipmap, slice, x, y); + + if (reloadMipmaps && mipmap == 0 && this->getMipmapCount() > 1) + this->generateMipmaps(); + } + + void TextureBase::replacePixels(const void* data, size_t size, int slice, int mipmap, const Rect& rect, + bool reloadMipmaps) + { + if (!this->isReadable() || this->getMSAA() > 1) + return; + + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + + if (graphics != nullptr && graphics->isRenderTargetActive(this)) + return; + + GraphicsBase::flushBatchedDrawsGlobal(); + + this->uploadByteData(data, size, mipmap, slice, rect); + + if (reloadMipmaps && mipmap == 0 && this->getMipmapCount() > 1) + this->generateMipmaps(); + } + + SamplerState TextureBase::validateSamplerState(SamplerState state) const + { + if (this->readable) + return state; + + if (state.depthSampleMode.hasValue && !love::isPixelFormatDepth(this->format)) + throw love::Exception("Only depth textures can have a depth sample compare mode."); + + if (state.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE && this->getMipmapCount() == 1) + state.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; + + if (this->textureType == TEXTURE_CUBE) + state.wrapU = state.wrapV = state.wrapW = SamplerState::WRAP_CLAMP; + + if (state.minFilter == SamplerState::FILTER_LINEAR || + state.magFilter == SamplerState::FILTER_LINEAR || + state.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR) + { + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + if (!graphics->isPixelFormatSupported(this->format, PIXELFORMATUSAGE_LINEAR)) + { + state.minFilter = state.magFilter = SamplerState::FILTER_NEAREST; + if (state.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR) + state.mipmapFilter = SamplerState::MIPMAP_FILTER_NEAREST; + } + } + + GraphicsBase::flushBatchedDrawsGlobal(); + + return state; + } + + bool TextureBase::supportsGenerateMipmaps(const char*& outReason) const + { + // clang-format off + if (this->getMipmapsMode() == MIPMAPS_NONE) + { + outReason = "generateMipmaps can only be called on a Texture which was created with mipmaps enabled."; + return false; + } + + if (isPixelFormatCompressed(this->format)) + { + outReason = "generateMipmaps cannot be called on a compressed Texture."; + return false; + } + + if (isPixelFormatDepthStencil(this->format)) + { + outReason = "generateMipmaps cannot be called on a depth/stencil Texture."; + return false; + } + + if (isPixelFormatInteger(this->format)) + { + outReason = "generateMipmaps cannot be called on an integer Texture."; + return false; + } + + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + if (graphics != nullptr && !graphics->isPixelFormatSupported(format, PIXELFORMATUSAGE_LINEAR)) + { + outReason = "generateMipmaps cannot be called on textures with formats that don't support linear filtering on this system."; + return false; + } + // clang-format on + + return true; + } + + void TextureBase::generateMipmaps() + { + const char* error = nullptr; + if (!this->supportsGenerateMipmaps(error)) + throw love::Exception("{:s}", error); + + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + if (graphics != nullptr && graphics->isRenderTargetActive(this)) + throw love::Exception(E_CANNOT_GENERATE_MIPMAPS_RT); + + this->generateMipmapsInternal(); + } + + bool TextureBase::validateDimensions(bool throwException) const + { + bool success = true; + + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + + if (graphics == nullptr) + return false; + + const auto& capabilities = graphics->getCapabilities(); + + int max2DSize = capabilities.limits[GraphicsBase::LIMIT_TEXTURE_SIZE]; + int max3DSize = capabilities.limits[GraphicsBase::LIMIT_VOLUME_TEXTURE_SIZE]; + int maxCubeSize = capabilities.limits[GraphicsBase::LIMIT_CUBE_TEXTURE_SIZE]; + int maxLayers = capabilities.limits[GraphicsBase::LIMIT_TEXTURE_LAYERS]; + + int largestDimension = 0; + const char* largestName = nullptr; + + if ((this->textureType == TEXTURE_2D || this->textureType == TEXTURE_2D_ARRAY) && + (this->pixelWidth > max2DSize || pixelHeight > max2DSize)) + { + success = false; + largestDimension = std::max(this->pixelWidth, this->pixelHeight); + largestName = this->pixelWidth > this->pixelHeight ? "pixel width" : "pixel height"; + } + else if (this->textureType == TEXTURE_2D_ARRAY && layers > maxLayers) + { + success = false; + largestDimension = layers; + largestName = "array layer count"; + } + else if (this->textureType == TEXTURE_CUBE && + (this->pixelWidth > maxCubeSize || this->pixelWidth != this->pixelHeight)) + { + success = false; + largestDimension = std::max(this->pixelWidth, this->pixelHeight); + largestName = this->pixelWidth > this->pixelHeight ? "pixel width" : "pixel height"; + + if (throwException && this->pixelWidth != this->pixelHeight) + throw love::Exception("Cubemap textures must have equal width and height."); + } + else if (this->textureType == TEXTURE_VOLUME && + (this->pixelWidth > max3DSize || this->pixelHeight > max3DSize || this->depth > max3DSize)) + { + success = false; + largestDimension = std::max(this->pixelWidth, std::max(this->pixelHeight, this->depth)); + if (largestDimension == this->pixelWidth) + largestName = "pixel width"; + else if (largestDimension == this->pixelHeight) + largestName = "pixel height"; + else + largestName = "pixel depth"; + } + + if (throwException && largestName != nullptr) + throw love::Exception(E_CANNOT_CREATE_TEXTURE, largestName, largestDimension); + + return success; + } + + void TextureBase::validatePixelFormat(GraphicsBase* graphics) const + { + uint32_t usage = PIXELFORMATUSAGEFLAGS_NONE; + + if (this->renderTarget) + usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; + + if (this->readable) + usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; + + if (this->computeWrite) + usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; + + if (!graphics->isPixelFormatSupported(this->format, usage)) + { + std::string_view name = "unknown"; + love::getConstant(this->format, name); + + std::string_view readableString = ""; + if (this->readable != !isPixelFormatDepthStencil(this->format)) + readableString = this->readable ? "readable" : "non-readable"; + + std::string_view rtargetStr = ""; + if (this->computeWrite) + rtargetStr = "as a compute shader-writable texture"; + else if (this->renderTarget) + rtargetStr = "as a render target"; + + throw love::Exception(E_TEXTURE_PIXELFORMAT_NOT_SUPPORTED, name, readableString, rtargetStr); + } + } + + int TextureBase::getSliceCount(int mipmap) const + { + switch (this->textureType) + { + case TEXTURE_2D: + return 1; + case TEXTURE_VOLUME: + return this->getDepth(mipmap); + case TEXTURE_2D_ARRAY: + return this->layers; + case TEXTURE_CUBE: + return 6; + default: + break; + } + + return 1; + } +} // namespace love diff --git a/source/modules/graphics/freetype/Font.cpp b/source/modules/graphics/freetype/Font.cpp new file mode 100644 index 000000000..a2a70d0ac --- /dev/null +++ b/source/modules/graphics/freetype/Font.cpp @@ -0,0 +1,95 @@ +#include "modules/graphics/freetype/Font.hpp" +#include "modules/graphics/Graphics.tcc" + +namespace love +{ + Font::Font(Rasterizer* rasterizer, const SamplerState& samplerState) : FontBase(rasterizer, samplerState) + { + this->loadVolatile(); + } + + bool Font::loadVolatile() + { + textureCacheID++; + glyphs.clear(); + textures.clear(); + createTexture(); + return true; + } + + void Font::createTexture() + { + auto* module = Module::getInstance(Module::M_GRAPHICS); + module->flushBatchedDraws(); + + TextureBase* texture = nullptr; + TextureSize size = { this->textureWidth, this->textureHeight }; + TextureSize nextSize = this->getNextTextureSize(); + bool recreateTexture = false; + + const bool biggerWidth = nextSize.width > size.width; + const bool biggerHeight = nextSize.height > size.height; + + if ((biggerWidth || biggerHeight) && !this->textures.empty()) + { + recreateTexture = true; + size = nextSize; + this->textures.pop_back(); + } + + TextureBase::Settings settings {}; + settings.format = this->pixelFormat; + settings.width = size.width; + settings.height = size.height; + + texture = module->newTexture(settings, nullptr); + texture->setSamplerState(this->samplerState); + + { + size_t dataSize = getPixelFormatSliceSize(this->pixelFormat, size.width, size.height); + size_t pixelCount = size.width * size.height; + + std::vector empty(dataSize, 0); + if (shaper->getRasterizers()[0]->getDataType() == Rasterizer::DATA_TRUETYPE) + { + if (this->pixelFormat == PIXELFORMAT_LA8_UNORM) + { + for (size_t index = 0; index < pixelCount; index++) + empty[index * 2 + 0] = 255; + } + else if (this->pixelFormat == PIXELFORMAT_RGBA8_UNORM) + { + for (size_t index = 0; index < pixelCount; index++) + { + empty[index * 4 + 0] = 255; + empty[index * 4 + 1] = 255; + empty[index * 4 + 2] = 255; + } + } + } + + Rect rectangle = { 0, 0, size.width, size.height }; + texture->replacePixels(empty.data(), empty.size(), 0, 0, rectangle, false); + } + + this->textures.emplace_back(texture, Acquire::NO_RETAIN); + + this->textureWidth = size.width; + this->textureHeight = size.height; + this->rowHeight = this->textureX = this->textureY = TEXTURE_PADDING; + + if (recreateTexture) + { + this->textureCacheID++; + std::vector glyphsToAdd; + + for (const auto& glyphPair : this->glyphs) + glyphsToAdd.push_back(unpackGlyphIndex(glyphPair.first)); + + this->glyphs.clear(); + + for (auto glyphIndex : glyphsToAdd) + this->addGlyph(glyphIndex); + } + } +} // namespace love diff --git a/source/modules/graphics/memory.cpp b/source/modules/graphics/memory.cpp new file mode 100644 index 000000000..23647ecfa --- /dev/null +++ b/source/modules/graphics/memory.cpp @@ -0,0 +1,9 @@ +#include "common/memory.hpp" + +namespace love +{ + size_t alignUp(size_t size, size_t alignment) + { + return (size + alignment - 1) & ~(alignment - 1); + } +} // namespace love diff --git a/source/modules/graphics/samplerstate.cpp b/source/modules/graphics/samplerstate.cpp new file mode 100644 index 000000000..09a2d0258 --- /dev/null +++ b/source/modules/graphics/samplerstate.cpp @@ -0,0 +1,60 @@ +#include "modules/graphics/samplerstate.hpp" + +namespace love +{ + // #region SamplerState + + uint64_t SamplerState::toKey() const + { + union + { + float f; + uint32_t i; + } conv; + + conv.f = lodBias; + + const uint32_t BITS_4 = 0xF; + + return (minFilter << 0) | (magFilter << 1) | (mipmapFilter << 2) | (wrapU << 4) | (wrapV << 7) | + (wrapW << 10) | (maxAnisotropy << 13) | ((BITS_4 & minLod) << 17) | ((BITS_4 & maxLod) << 21) | + (depthSampleMode.hasValue << 25) | (depthSampleMode.value << 26) | ((uint64_t)conv.i << 32); + } + + SamplerState SamplerState::fromKey(uint64_t key) + { + const uint32_t BITS_1 = 0x1; + const uint32_t BITS_2 = 0x3; + const uint32_t BITS_3 = 0x7; + const uint32_t BITS_4 = 0xF; + + SamplerState s; + + s.minFilter = (FilterMode)((key >> 0) & BITS_1); + s.magFilter = (FilterMode)((key >> 1) & BITS_1); + s.mipmapFilter = (MipmapFilterMode)((key >> 2) & BITS_2); + + s.wrapU = (WrapMode)((key >> 4) & BITS_3); + s.wrapV = (WrapMode)((key >> 7) & BITS_3); + s.wrapW = (WrapMode)((key >> 10) & BITS_3); + + s.maxAnisotropy = (key >> 13) & BITS_4; + + s.minLod = (key >> 17) & BITS_4; + s.maxLod = (key >> 21) & BITS_4; + + s.depthSampleMode.hasValue = ((key >> 25) & BITS_1) != 0; + s.depthSampleMode.value = (CompareMode)((key >> 26) & BITS_4); + + union + { + float f; + uint32_t i; + } conv; + + conv.i = (uint32_t)(key >> 32); + s.lodBias = conv.f; + + return s; + } +} // namespace love diff --git a/source/modules/graphics/vertex.cpp b/source/modules/graphics/vertex.cpp new file mode 100644 index 000000000..37680e87b --- /dev/null +++ b/source/modules/graphics/vertex.cpp @@ -0,0 +1,307 @@ +#include "modules/graphics/vertex.hpp" + +#include + +namespace love +{ + void debugVertices(Vertex* vertices, size_t count) + { + for (size_t i = 0; i < count; i++) + { + const auto& v = vertices[i]; + + std::printf("Vertex %zu:\n", i); + std::printf(" Position: %.2f, %.2f\n", v.x, v.y); + std::printf(" Texture Coordinates: %f, %f\n", v.s, v.t); + std::printf(" Color: %.2f, %.2f, %.2f, %.2f\n", v.color.r, v.color.g, v.color.b, v.color.a); + } + } + + void debugIndices(uint16_t* indices, size_t count) + { + std::printf("Indices (%zu): ", count); + for (size_t index = 0; index < count; index++) + std::printf("%hu ", indices[index]); + + std::printf("\n"); + } + + int getIndexCount(TriangleIndexMode mode, int vertexCount) + { + switch (mode) + { + case TRIANGLEINDEX_NONE: + return 0; + case TRIANGLEINDEX_STRIP: + case TRIANGLEINDEX_FAN: + return 3 * (vertexCount - 2); + case TRIANGLEINDEX_QUADS: + return vertexCount * 6 / 4; + } + + return 0; + } + + size_t getFormatStride(CommonFormat format) + { + switch (format) + { + case CommonFormat::NONE: + return 0; + case CommonFormat::XYf: + return sizeof(float) * 2; + case CommonFormat::XYZf: + return sizeof(float) * 3; + case CommonFormat::RGBAf: + return sizeof(float) * 4; + case CommonFormat::STf_RGBAf: + return sizeof(STf_RGBAf); + case CommonFormat::STPf_RGBAf: + return sizeof(STPf_RGBAf); + case CommonFormat::XYf_STf: + return sizeof(XYf_STf); + case CommonFormat::XYf_STPf: + return sizeof(XYf_STPf); + case CommonFormat::XYf_STf_RGBAf: + return sizeof(XYf_STf_RGBAf); + case CommonFormat::XYf_STus_RGBAf: + return sizeof(XYf_STus_RGBAf); + case CommonFormat::XYf_STPf_RGBAf: + return sizeof(XYf_STPf_RGBAf); + } + + return 0; + } + + template + static void fillIndicesT(TriangleIndexMode mode, T vertexStart, T vertexCount, T* indices) + { + switch (mode) + { + case TRIANGLEINDEX_NONE: + break; + case TRIANGLEINDEX_STRIP: + { + int i = 0; + for (T index = 0; index < vertexCount - 2; index++) + { + indices[i++] = vertexStart + index; + indices[i++] = vertexStart + index + 1 + (index & 1); + indices[i++] = vertexStart + index + 2 - (index & 1); + } + + break; + } + case TRIANGLEINDEX_FAN: + { + int i = 0; + for (T index = 2; index < vertexCount; index++) + { + indices[i++] = vertexStart; + indices[i++] = vertexStart + index - 1; + indices[i++] = vertexStart + index; + } + + break; + } + case TRIANGLEINDEX_QUADS: + { + // 0---2 + // | / | + // 1---3 + int count = vertexCount / 4; + for (int i = 0; i < count; i++) + { + int ii = i * 6; + T vi = T(vertexStart + i * 4); + + indices[ii + 0] = vi + 0; + indices[ii + 1] = vi + 1; + indices[ii + 2] = vi + 2; + + indices[ii + 3] = vi + 2; + indices[ii + 4] = vi + 1; + indices[ii + 5] = vi + 3; + } + + break; + } + } + } + + void fillIndices(TriangleIndexMode mode, uint16_t vertexStart, uint16_t vertexCount, uint16_t* indices) + { + fillIndicesT(mode, vertexStart, vertexCount, indices); + } + + void fillIndices(TriangleIndexMode mode, uint32_t vertexStart, uint32_t vertexCount, uint32_t* indices) + { + fillIndicesT(mode, vertexStart, vertexCount, indices); + } + + void VertexAttributes::setCommonFormat(CommonFormat format, uint8_t bufferindex) + { + setBufferLayout(bufferindex, (uint16_t)getFormatStride(format)); + + switch (format) + { + case CommonFormat::NONE: + break; + case CommonFormat::XYf: + set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex); + break; + case CommonFormat::XYZf: + set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC3, 0, bufferindex); + break; + case CommonFormat::RGBAf: + set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, 0, bufferindex); + break; + case CommonFormat::STf_RGBAf: + set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, 0, bufferindex); + set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, uint16_t(sizeof(float) * 2), bufferindex); + break; + case CommonFormat::STPf_RGBAf: + set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, 0, bufferindex); + set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, uint16_t(sizeof(float) * 3), bufferindex); + break; + case CommonFormat::XYf_STf: + set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex); + set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, uint16_t(sizeof(float) * 2), bufferindex); + break; + case CommonFormat::XYf_STPf: + set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex); + set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, uint16_t(sizeof(float) * 2), bufferindex); + break; + case CommonFormat::XYf_STf_RGBAf: + set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex); + set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, uint16_t(sizeof(float) * 2), bufferindex); + set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, uint16_t(sizeof(float) * 4), bufferindex); + break; + case CommonFormat::XYf_STus_RGBAf: + set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex); + set(ATTRIB_TEXCOORD, DATAFORMAT_UNORM16_VEC2, uint16_t(sizeof(float) * 2), bufferindex); + set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, uint16_t(sizeof(float) * 2 + sizeof(uint16_t) * 2), + bufferindex); + break; + case CommonFormat::XYf_STPf_RGBAf: + set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex); + set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, uint16_t(sizeof(float) * 2), bufferindex); + set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, uint16_t(sizeof(float) * 5), bufferindex); + break; + } + } + + bool VertexAttributes::operator==(const VertexAttributes& other) const + { + if (enableBits != other.enableBits || instanceBits != other.instanceBits) + return false; + + uint32_t allbits = enableBits; + uint32_t i = 0; + + while (allbits) + { + if (isEnabled(i)) + { + const auto& a = this->attributes[i]; + const auto& b = other.attributes[i]; + if (a.bufferIndex != b.bufferIndex || a.packedFormat != b.packedFormat || + a.offsetFromVertex != b.offsetFromVertex) + return false; + + if (bufferLayouts[a.bufferIndex].stride != other.bufferLayouts[a.bufferIndex].stride) + return false; + } + + i++; + allbits >>= 1; + } + + return true; + } + + // Order here relies on order of DataFormat enum. + static const DataFormatInfo dataFormatInfo[] { + // baseType, isMatrix, components, rows, columns, componentSize, size + { DATA_BASETYPE_FLOAT, false, 1, 0, 0, 4, 4 }, // DATAFORMAT_FLOAT + { DATA_BASETYPE_FLOAT, false, 2, 0, 0, 4, 8 }, // DATAFORMAT_FLOAT_VEC2 + { DATA_BASETYPE_FLOAT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_FLOAT_VEC3 + { DATA_BASETYPE_FLOAT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_FLOAT_VEC4 + + { DATA_BASETYPE_FLOAT, true, 4, 2, 2, 4, 16 }, // DATAFORMAT_FLOAT_MAT2X2 + { DATA_BASETYPE_FLOAT, true, 6, 2, 3, 4, 24 }, // DATAFORMAT_FLOAT_MAT2X3 + { DATA_BASETYPE_FLOAT, true, 8, 2, 4, 4, 32 }, // DATAFORMAT_FLOAT_MAT2X4 + + { DATA_BASETYPE_FLOAT, true, 6, 3, 2, 4, 24 }, // DATAFORMAT_FLOAT_MAT3X2 + { DATA_BASETYPE_FLOAT, true, 9, 3, 3, 4, 36 }, // DATAFORMAT_FLOAT_MAT3X3 + { DATA_BASETYPE_FLOAT, true, 12, 3, 4, 4, 48 }, // DATAFORMAT_FLOAT_MAT3X4 + + { DATA_BASETYPE_FLOAT, true, 8, 4, 2, 4, 32 }, // DATAFORMAT_FLOAT_MAT4X2 + { DATA_BASETYPE_FLOAT, true, 12, 4, 3, 4, 48 }, // DATAFORMAT_FLOAT_MAT4X3 + { DATA_BASETYPE_FLOAT, true, 16, 4, 4, 4, 64 }, // DATAFORMAT_FLOAT_MAT4X4 + + { DATA_BASETYPE_INT, false, 1, 0, 0, 4, 4 }, // DATAFORMAT_INT32 + { DATA_BASETYPE_INT, false, 2, 0, 0, 4, 8 }, // DATAFORMAT_INT32_VEC2 + { DATA_BASETYPE_INT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_INT32_VEC3 + { DATA_BASETYPE_INT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_INT32_VEC4 + + { DATA_BASETYPE_UINT, false, 1, 0, 0, 4, 4 }, // DATAFORMAT_UINT32 + { DATA_BASETYPE_UINT, false, 2, 0, 0, 4, 8 }, // DATAFORMAT_UINT32_VEC2 + { DATA_BASETYPE_UINT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_UINT32_VEC3 + { DATA_BASETYPE_UINT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_UINT32_VEC4 + + { DATA_BASETYPE_SNORM, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_SNORM8_VEC4 + { DATA_BASETYPE_UNORM, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_UNORM8_VEC4 + { DATA_BASETYPE_INT, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_INT8_VEC4 + { DATA_BASETYPE_UINT, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_UINT8_VEC4 + + { DATA_BASETYPE_SNORM, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_SNORM16_VEC2 + { DATA_BASETYPE_SNORM, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_SNORM16_VEC4 + + { DATA_BASETYPE_UNORM, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_UNORM16_VEC2 + { DATA_BASETYPE_UNORM, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_UNORM16_VEC4 + + { DATA_BASETYPE_INT, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_INT16_VEC2 + { DATA_BASETYPE_INT, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_INT16_VEC4 + + { DATA_BASETYPE_UINT, false, 1, 0, 0, 2, 2 }, // DATAFORMAT_UINT16 + { DATA_BASETYPE_UINT, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_UINT16_VEC2 + { DATA_BASETYPE_UINT, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_UINT16_VEC4 + + { DATA_BASETYPE_BOOL, false, 1, 0, 0, 4, 4 }, // DATAFORMAT_BOOL + { DATA_BASETYPE_BOOL, false, 2, 0, 0, 4, 8 }, // DATAFORMAT_BOOL_VEC2 + { DATA_BASETYPE_BOOL, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_BOOL_VEC3 + { DATA_BASETYPE_BOOL, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_BOOL_VEC4 + }; + + static_assert((sizeof(dataFormatInfo) / sizeof(DataFormatInfo)) == DATAFORMAT_MAX_ENUM, + "dataFormatInfo array size must match number of DataFormat enum values."); + + const DataFormatInfo& getDataFormatInfo(DataFormat format) + { + return dataFormatInfo[format]; + } + + IndexDataType getIndexDataTypeFromMax(size_t maxValue) + { + return maxValue > LOVE_UINT16_MAX ? INDEX_UINT32 : INDEX_UINT16; + } + + DataFormat getIndexDataFormat(IndexDataType type) + { + return type == INDEX_UINT32 ? DATAFORMAT_UINT32 : DATAFORMAT_UINT16; + } + + size_t getIndexDataSize(IndexDataType type) + { + switch (type) + { + case INDEX_UINT16: + return sizeof(uint16_t); + case INDEX_UINT32: + return sizeof(uint32_t); + default: + return 0; + } + } +} // namespace love diff --git a/source/modules/graphics/wrap_Font.cpp b/source/modules/graphics/wrap_Font.cpp new file mode 100644 index 000000000..bfe57bc4e --- /dev/null +++ b/source/modules/graphics/wrap_Font.cpp @@ -0,0 +1,293 @@ +#include "modules/graphics/wrap_Font.hpp" + +#include + +using namespace love; + +int Wrap_Font::getWidth(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + if (lua_type(L, 2) == LUA_TSTRING) + { + auto* string = luaL_checkstring(L, 2); + luax_catchexcept(L, [&] { lua_pushnumber(L, self->getWidth(string)); }); + } + else + { + auto glyph = (uint32_t)luaL_checknumber(L, 2); + luax_catchexcept(L, [&] { lua_pushnumber(L, self->getWidth(glyph)); }); + } + + return 1; +} + +int Wrap_Font::getHeight(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + lua_pushnumber(L, self->getHeight()); + + return 1; +} + +int Wrap_Font::getWrap(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + std::vector text {}; + luax_checkcoloredstring(L, 2, text); + + float wrap = (float)luaL_checknumber(L, 3); + + int maxWidth = 0; + std::vector lines {}; + std::vector widths {}; + + luax_catchexcept(L, [&]() { self->getWrap(text, wrap, lines, &widths); }); + + for (int width : widths) + maxWidth = std::max(maxWidth, width); + + lua_pushinteger(L, maxWidth); + lua_createtable(L, (int)lines.size(), 0); + + for (int i = 0; i < (int)lines.size(); i++) + { + lua_pushstring(L, lines[i].c_str()); + lua_rawseti(L, -2, i + 1); + } + + return 2; +} + +int Wrap_Font::setLineHeight(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + float height = (float)luaL_checknumber(L, 2); + + self->setLineHeight(height); + + return 0; +} + +int Wrap_Font::getLineHeight(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + lua_pushnumber(L, self->getLineHeight()); + + return 1; +} + +int Wrap_Font::setFilter(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + auto samplerState = self->getSamplerState(); + + const char* minString = luaL_checkstring(L, 2); + const char* magString = luaL_optstring(L, 3, minString); + + if (!SamplerState::getConstant(minString, samplerState.minFilter)) + return luax_enumerror(L, "filter mode", SamplerState::FilterModes, minString); + + if (!SamplerState::getConstant(magString, samplerState.magFilter)) + return luax_enumerror(L, "filter mode", SamplerState::FilterModes, magString); + + samplerState.maxAnisotropy = std::min(std::max(1, (int)luaL_optnumber(L, 4, 1.0)), LOVE_UINT8_MAX); + + luax_catchexcept(L, [&]() { self->setSamplerState(samplerState); }); + + return 0; +} + +int Wrap_Font::getFilter(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + auto samplerState = self->getSamplerState(); + + std::string_view minString {}; + std::string_view magString {}; + + SamplerState::getConstant(samplerState.minFilter, minString); + SamplerState::getConstant(samplerState.magFilter, magString); + + luax_pushstring(L, minString); + luax_pushstring(L, magString); + lua_pushnumber(L, samplerState.maxAnisotropy); + + return 3; +} + +int Wrap_Font::getAscent(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + lua_pushnumber(L, self->getAscent()); + + return 1; +} + +int Wrap_Font::getDescent(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + lua_pushnumber(L, self->getDescent()); + + return 1; +} + +int Wrap_Font::getBaseline(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + lua_pushnumber(L, self->getBaseline()); + + return 1; +} + +int Wrap_Font::hasGlyphs(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + bool hasGlyph = false; + int count = std::max(lua_gettop(L) - 1, 1); + + luax_catchexcept(L, [&]() { + for (int index = 2; index < count; index++) + { + if (lua_type(L, index) == LUA_TSTRING) + hasGlyph = self->hasGlyphs(luax_checkstring(L, index)); + else + hasGlyph = self->hasGlyph((uint32_t)luaL_checknumber(L, index)); + + if (!hasGlyph) + break; + } + }); + + luax_pushboolean(L, hasGlyph); + + return 1; +} + +int Wrap_Font::getKerning(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + float kerning = 0.0f; + + luax_catchexcept(L, [&]() { + if (lua_type(L, 2) == LUA_TSTRING) + { + auto left = luax_checkstring(L, 2); + auto right = luax_checkstring(L, 3); + + kerning = self->getKerning(left, right); + } + else + { + auto left = (uint32_t)luaL_checknumber(L, 2); + auto right = (uint32_t)luaL_checknumber(L, 3); + + kerning = self->getKerning(left, right); + } + }); + + lua_pushnumber(L, kerning); + + return 1; +} + +int Wrap_Font::setFallbacks(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + std::vector fallbacks {}; + + for (int index = 2; index <= lua_gettop(L); index++) + fallbacks.push_back(luax_checkfont(L, index)); + + luax_catchexcept(L, [&]() { self->setFallbacks(fallbacks); }); + + return 0; +} + +int Wrap_Font::getDPIScale(lua_State* L) +{ + auto* self = luax_checkfont(L, 1); + + lua_pushnumber(L, self->getDPIScale()); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "getWidth", Wrap_Font::getWidth }, + { "getHeight", Wrap_Font::getHeight }, + { "getWrap", Wrap_Font::getWrap }, + { "setLineHeight", Wrap_Font::setLineHeight }, + { "getLineHeight", Wrap_Font::getLineHeight }, + { "setFilter", Wrap_Font::setFilter }, + { "getFilter", Wrap_Font::getFilter }, + { "getAscent", Wrap_Font::getAscent }, + { "getDescent", Wrap_Font::getDescent }, + { "getBaseline", Wrap_Font::getBaseline }, + { "hasGlyphs", Wrap_Font::hasGlyphs }, + { "setFallbacks", Wrap_Font::setFallbacks }, + { "getDPIScale", Wrap_Font::getDPIScale } +}; +// clang-format on + +namespace love +{ + FontBase* luax_checkfont(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + void luax_checkcoloredstring(lua_State* L, int index, std::vector& strings) + { + ColoredString coloredString {}; + coloredString.color = Color(1.0f, 1.0f, 1.0f, 1.0f); + + if (lua_istable(L, index)) + { + const int length = luax_objlen(L, index); + + for (int i = 0; i <= length; i++) + { + lua_rawgeti(L, index, i + 1); + + if (lua_istable(L, -1)) + { + for (int j = 1; j <= 4; j++) + lua_rawgeti(L, -j, j); + + coloredString.color.r = (float)luaL_checknumber(L, -4); + coloredString.color.g = (float)luaL_checknumber(L, -3); + coloredString.color.b = (float)luaL_checknumber(L, -2); + coloredString.color.a = (float)luaL_optnumber(L, -1, 1.0f); + + lua_pop(L, 4); + } + else + { + coloredString.string = luaL_checkstring(L, -1); + strings.push_back(coloredString); + } + } + } + else + { + coloredString.string = luaL_checkstring(L, index); + strings.push_back(coloredString); + } + } + + int open_font(lua_State* L) + { + return luax_register_type(L, &FontBase::type, functions); + } +} // namespace love diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 584e6a3cc..730f769fb 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -2,6 +2,19 @@ #include "modules/graphics/wrap_Graphics.hpp" +#include "modules/filesystem/wrap_Filesystem.hpp" + +#include "modules/graphics/wrap_Font.hpp" +#include "modules/graphics/wrap_Quad.hpp" +#include "modules/graphics/wrap_TextBatch.hpp" +#include "modules/graphics/wrap_Texture.hpp" + +#include "modules/image/Image.hpp" +#include "modules/image/ImageData.hpp" +#include "modules/image/wrap_CompressedImageData.hpp" +#include "modules/image/wrap_Image.hpp" +#include "modules/image/wrap_ImageData.hpp" + using namespace love; #define instance() (Module::getInstance(Module::M_GRAPHICS)) @@ -14,7 +27,7 @@ static int luax_checkgraphicscreated(lua_State* L) return 0; } -int Wrap_Graphics::reset(lua_State* L) +int Wrap_Graphics::reset(lua_State*) { instance()->reset(); @@ -112,7 +125,7 @@ int Wrap_Graphics::clear(lua_State* L) int Wrap_Graphics::present(lua_State* L) { - luax_catchexcept(L, [&]() { instance()->present(); }); + luax_catchexcept(L, [&]() { instance()->present(L); }); return 0; } @@ -126,19 +139,19 @@ int Wrap_Graphics::setColor(lua_State* L) for (int index = 1; index <= 4; index++) lua_rawgeti(L, 1, index); - color.r = luaL_checknumber(L, -4); - color.g = luaL_checknumber(L, -3); - color.b = luaL_checknumber(L, -2); - color.a = luaL_optnumber(L, -1, 1.0); + color.r = luax_checknumberclamped01(L, -4); + color.g = luax_checknumberclamped01(L, -3); + color.b = luax_checknumberclamped01(L, -2); + color.a = luax_optnumberclamped01(L, -1, 1.0); lua_pop(L, 4); } else { - color.r = luaL_checknumber(L, 1); - color.g = luaL_checknumber(L, 2); - color.b = luaL_checknumber(L, 3); - color.a = luaL_optnumber(L, 4, 1.0); + color.r = luax_checknumberclamped01(L, 1); + color.g = luax_checknumberclamped01(L, 2); + color.b = luax_checknumberclamped01(L, 3); + color.a = luax_optnumberclamped01(L, 4, 1.0); } instance()->setColor(color); @@ -547,8 +560,7 @@ int Wrap_Graphics::setScissor(lua_State* L) { int argc = lua_gettop(L); - if (argc == 0 || - (argc == 4 && lua_isnil(L, 1) && lua_isnil(L, 2) && lua_isnil(L, 3) && lua_isnil(L, 4))) + if (argc == 0 || (argc == 4 && lua_isnil(L, 1) && lua_isnil(L, 2) && lua_isnil(L, 3) && lua_isnil(L, 4))) { instance()->setScissor(); return 0; @@ -665,7 +677,7 @@ int Wrap_Graphics::shear(lua_State* L) return 0; } -int Wrap_Graphics::origin(lua_State* L) +int Wrap_Graphics::origin(lua_State*) { instance()->origin(); @@ -722,6 +734,972 @@ int Wrap_Graphics::inverseTransformPoint(lua_State* L) return 2; } +static void parseDPIScale(Data*, float*) +{} + +// clang-format off +static std::pair, StrongRef> +getImageData(lua_State* L, int index, bool allowCompressed, float* dpiScale) +{ + StrongRef imageData; + StrongRef compressedImageData; + + if (luax_istype(L, index, ImageData::type)) + imageData.set(luax_checkimagedata(L, index)); + else if (luax_istype(L, index, CompressedImageData::type)) + compressedImageData.set(luax_checkcompressedimagedata(L, index)); + else if (luax_cangetdata(L, index)) + { + auto* module = Module::getInstance(Module::M_IMAGE); + if (module == nullptr) + luaL_error(L, "Cannot load images without the love.image module."); + + StrongRef fileData(luax_getdata(L, index), Acquire::NO_RETAIN); + + if (dpiScale != nullptr) + parseDPIScale(fileData, dpiScale); + + if (allowCompressed && module->isCompressed(fileData)) + luax_catchexcept(L, [&]() { compressedImageData.set(module->newCompressedData(fileData), Acquire::NO_RETAIN); }); + else + luax_catchexcept(L, [&]() { imageData.set(module->newImageData(fileData), Acquire::NO_RETAIN); }); + } + else + imageData.set(luax_checkimagedata(L, index)); + + return { imageData, compressedImageData }; +} +// clang-format on + +static void luax_checktexturesettings(lua_State* L, int index, bool optional, bool checkType, + bool checkDimensions, OptionalBool forceRenderTarget, + Texture::Settings& settings, bool& setDPIScale) +{ + setDPIScale = false; + + if (forceRenderTarget.hasValue) + settings.renderTarget = forceRenderTarget.value; + + if (optional && lua_isnoneornil(L, index)) + return; + + luax_checktablefields(L, index, "texture setting name", Texture::getConstant); + + if (!forceRenderTarget.hasValue) + { + const char* name = Texture::getConstant(Texture::SETTING_RENDER_TARGET); + settings.renderTarget = luax_boolflag(L, index, name, settings.renderTarget); + } + + lua_getfield(L, index, Texture::getConstant(Texture::SETTING_DEBUGNAME)); + if (!lua_isnoneornil(L, -1)) + settings.debugName = luaL_checkstring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, index, Texture::getConstant(Texture::SETTING_FORMAT)); + if (!lua_isnoneornil(L, -1)) + { + const char* formatName = luaL_checkstring(L, -1); + if (!love::getConstant(formatName, settings.format)) + luax_enumerror(L, "pixel format", formatName); + } + lua_pop(L, 1); + + if (checkType) + { + lua_getfield(L, index, Texture::getConstant(Texture::SETTING_TYPE)); + if (!lua_isnoneornil(L, -1)) + { + const char* typeName = luaL_checkstring(L, -1); + if (!Texture::getConstant(typeName, settings.type)) + luax_enumerror(L, "texture type", Texture::TextureTypes, typeName); + } + lua_pop(L, 1); + } + + // clang-format off + if (checkDimensions) + { + settings.width = luax_checkintflag(L, index, Texture::getConstant(Texture::SETTING_WIDTH)); + settings.height = luax_checkintflag(L, index, Texture::getConstant(Texture::SETTING_HEIGHT)); + + if (settings.type == TEXTURE_2D_ARRAY || settings.type == TEXTURE_VOLUME) + settings.layers = luax_checkintflag(L, index, Texture::getConstant(Texture::SETTING_LAYERS)); + } + else + { + settings.width = luax_intflag(L, index, Texture::getConstant(Texture::SETTING_WIDTH), settings.width); + settings.height = luax_intflag(L, index, Texture::getConstant(Texture::SETTING_HEIGHT), settings.height); + + if (settings.type == TEXTURE_2D_ARRAY || settings.type == TEXTURE_VOLUME) + settings.layers = luax_intflag(L, index, Texture::getConstant(Texture::SETTING_LAYERS), settings.layers); + } + // clang-format on + + lua_getfield(L, index, Texture::getConstant(Texture::SETTING_MIPMAPS)); + if (!lua_isnoneornil(L, -1)) + { + if (lua_type(L, -1) == LUA_TBOOLEAN) + settings.mipmaps = luax_toboolean(L, -1) ? Texture::MIPMAPS_MANUAL : Texture::MIPMAPS_NONE; + else + { + const char* name = luaL_checkstring(L, -1); + if (!Texture::getConstant(name, settings.mipmaps)) + luax_enumerror(L, "mipmap mode", Texture::MipmapsModes, name); + } + } + lua_pop(L, 1); + + lua_getfield(L, index, Texture::getConstant(Texture::SETTING_MIPMAP_COUNT)); + if (!lua_isnoneornil(L, -1)) + settings.mipmapCount = luaL_checkinteger(L, -1); + lua_pop(L, 1); + + settings.linear = luax_boolflag(L, index, Texture::getConstant(Texture::SETTING_LINEAR), settings.linear); + settings.msaa = luax_intflag(L, index, Texture::getConstant(Texture::SETTING_MSAA), settings.msaa); + + settings.computeWrite = + luax_boolflag(L, index, Texture::getConstant(Texture::SETTING_COMPUTE_WRITE), settings.computeWrite); + + lua_getfield(L, index, Texture::getConstant(Texture::SETTING_VIEW_FORMATS)); + if (!lua_isnoneornil(L, -1)) + { + if (lua_type(L, -1) != LUA_TTABLE) + luaL_argerror(L, index, "expected field 'viewformats' to be a table type"); + + for (int index = 1; index <= (int)luax_objlen(L, -1); index++) + { + lua_rawgeti(L, -1, index); + const char* formatName = luaL_checkstring(L, -1); + + PixelFormat format = PIXELFORMAT_UNKNOWN; + if (!love::getConstant(formatName, format)) + luax_enumerror(L, "pixel format", formatName); + + settings.viewFormats.push_back(format); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + lua_getfield(L, index, Texture::getConstant(Texture::SETTING_READABLE)); + if (!lua_isnoneornil(L, -1)) + settings.readable.set(luax_checkboolean(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, index, Texture::getConstant(Texture::SETTING_DPI_SCALE)); + if (lua_isnumber(L, -1)) + { + settings.dpiScale = luaL_checknumber(L, -1); + setDPIScale = true; + } + lua_pop(L, 1); +} + +static int pushNewTexture(lua_State* L, TextureBase::Slices* slices, const Texture::Settings& settings) +{ + StrongRef texture; + + // clang-format off + luax_catchexcept(L, + [&]() { texture.set(instance()->newTexture(settings, slices), Acquire::NO_RETAIN); }, + [&](bool) { if (slices) slices->clear(); } + ); + // clang-format on + + luax_pushtype(L, texture); + return 1; +} + +int Wrap_Graphics::newTexture(lua_State* L) +{ + luax_checkgraphicscreated(L); + + Texture::Slices slices(TEXTURE_2D); + Texture::Slices* slicesRef = &slices; + + Texture::Settings settings {}; + settings.type = TEXTURE_2D; + bool dpiScaleSet = false; + + if (lua_type(L, 1) == LUA_TNUMBER) + { + slicesRef = nullptr; + + settings.width = luaL_checkinteger(L, 1); + settings.height = luaL_checkinteger(L, 2); + + int start = 3; + + if (lua_type(L, 3) == LUA_TNUMBER) + { + settings.layers = luaL_checkinteger(L, 3); + settings.type = TEXTURE_2D_ARRAY; + start = 4; + } + + luax_checktexturesettings(L, start, true, true, false, OptionalBool(), settings, dpiScaleSet); + } + else + { + luax_checktexturesettings(L, 2, true, false, false, OptionalBool(), settings, dpiScaleSet); + float* autoDpiScale = dpiScaleSet ? nullptr : &settings.dpiScale; + + if (lua_istable(L, 1)) + { + const int length = luax_objlen(L, 1); + + for (int index = 0; index < length; index++) + { + lua_rawgeti(L, 1, index + 1); + + auto data = getImageData(L, -1, true, index == 0 ? autoDpiScale : nullptr); + + if (data.first.get()) + slices.set(0, index, data.first); + else + slices.set(0, index, data.second->getSlice(0, 0)); + } + + lua_pop(L, length); + } + else + { + auto data = getImageData(L, 1, true, autoDpiScale); + + if (data.first.get()) + slices.set(0, 0, data.first); + else + slices.add(data.second, 0, 0, false, settings.mipmaps != Texture::MIPMAPS_NONE); + } + } + + return pushNewTexture(L, slicesRef, settings); +} + +int Wrap_Graphics::newQuad(lua_State* L) +{ + luax_checkgraphicscreated(L); + + Quad::Viewport viewport {}; + viewport.x = luaL_checknumber(L, 1); + viewport.y = luaL_checknumber(L, 2); + viewport.w = luaL_checknumber(L, 3); + viewport.h = luaL_checknumber(L, 4); + + double sourceWidth = 0.0f; + double sourceHeight = 0.0f; + + int layer = 0; + + if (luax_istype(L, 5, TextureBase::type)) + { + TextureBase* texture = luax_checktexture(L, 5); + sourceWidth = texture->getWidth(); + sourceHeight = texture->getHeight(); + } + else if (luax_istype(L, 6, TextureBase::type)) + { + layer = (int)luaL_checkinteger(L, 5) - 1; + TextureBase* texture = luax_checktexture(L, 6); + sourceWidth = texture->getWidth(); + sourceHeight = texture->getHeight(); + } + else if (!lua_isnoneornil(L, 7)) + { + layer = (int)luaL_checkinteger(L, 5) - 1; + sourceWidth = luaL_checknumber(L, 6); + sourceHeight = luaL_checknumber(L, 7); + } + else + { + sourceWidth = luaL_checknumber(L, 5); + sourceHeight = luaL_checknumber(L, 6); + } + + Quad* quad = instance()->newQuad(viewport, sourceWidth, sourceHeight); + quad->setLayer(layer); + + luax_pushtype(L, quad); + quad->release(); + + return 1; +} + +int Wrap_Graphics::newImage(lua_State* L) +{ + return newTexture(L); +} + +int Wrap_Graphics::draw(lua_State* L) +{ + Drawable* drawable = nullptr; + TextureBase* texture = nullptr; + + Quad* quad = luax_totype(L, 2); + int start = 2; + + if (quad != nullptr) + { + texture = luax_checktexture(L, 1); + start = 3; + } + else if (lua_isnil(L, 2) && !lua_isnoneornil(L, 3)) + return luax_typeerror(L, 2, "Quad"); + else + { + drawable = luax_checktype(L, 1); + start = 2; + } + + luax_checkstandardtransform(L, start, [&](const Matrix4& transform) { + if (texture && quad) + instance()->draw(texture, quad, transform); + else + instance()->draw(drawable, transform); + }); + + return 0; +} + +int Wrap_Graphics::setFont(lua_State* L) +{ + auto* font = luax_checktype(L, 1); + instance()->setFont(font); + + return 0; +} + +int Wrap_Graphics::getFont(lua_State* L) +{ + FontBase* font = nullptr; + luax_catchexcept(L, [&]() { font = instance()->getFont(); }); + + luax_pushtype(L, font); + + return 1; +} + +static BufferDataUsage luax_optdatausage(lua_State* L, int idx, BufferDataUsage def) +{ + const char* usagestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx); + + if (usagestr && !getConstant(usagestr, def)) + luax_enumerror(L, "usage hint", BufferUsages, usagestr); + + return def; +} + +static PrimitiveType luax_checkmeshdrawmode(lua_State* L, int idx) +{ + const char* modestr = luaL_checkstring(L, idx); + + PrimitiveType mode = PRIMITIVE_TRIANGLES; + if (!getConstant(modestr, mode)) + luax_enumerror(L, "mesh draw mode", PrimitiveTypes, modestr); + + return mode; +} + +static Mesh* newStandardMesh(lua_State* L) +{ + Mesh* mesh = nullptr; + + PrimitiveType drawMode = luax_checkmeshdrawmode(L, 2); + BufferDataUsage usage = luax_optdatausage(L, 3, BUFFERDATAUSAGE_DYNAMIC); + + // std::vector format = Mesh::getDefaultVertexFormat(); + + if (lua_istable(L, 1)) + { + size_t vertexCount = luax_objlen(L, 1); + + std::vector vertices {}; + vertices.reserve(vertexCount); + + for (size_t index = 0; index <= vertexCount; index++) + { + lua_rawgeti(L, 1, (int)index + 1); + + if (lua_type(L, -1) != LUA_TTABLE) + { + luax_typeerror(L, 1, "table of tables"); + return nullptr; + } + + for (int j = 1; j <= 8; j++) + lua_rawgeti(L, -j, j); + + Vertex vertex {}; + vertex.x = luaL_checknumber(L, -8); + vertex.y = luaL_checknumber(L, -7); + vertex.s = luaL_optnumber(L, -6, 0.0f); + vertex.t = luaL_optnumber(L, -5, 0.0f); + + vertex.color.r = luax_optnumberclamped01(L, -4, 1.0f); + vertex.color.g = luax_optnumberclamped01(L, -3, 1.0f); + vertex.color.b = luax_optnumberclamped01(L, -2, 1.0f); + vertex.color.a = luax_optnumberclamped01(L, -1, 1.0f); + + lua_pop(L, 9); + + vertices.push_back(vertex); + } + + // luax_catchexcept(L, [&]() { mesh = instance()->newMesh(vertices, drawMode, usage); }); + } + else + { + int count = (int)luaL_checkinteger(L, 1); + // luax_catchexcept(L, [&]() { mesh = instance()->newMesh(format, count, drawMode, usage); }); + } + + return mesh; +} + +int Wrap_Graphics::newMesh(lua_State* L) +{ + luax_checkgraphicscreated(L); + + // int firstArgType = lua_type(L, 1); + // if (firstArgType != LUA_TTABLE && firstArgType != LUA_TNUMBER) + // return luax_typeerror(L, 1, "table or number expected."); + + // Mesh* mesh = nullptr; + + // int secondArgType = lua_type(L, 2); + + // if (firstArgType == LUA_TTABLE && + // (secondArgType == LUA_TTABLE || secondArgType == LUA_TNUMBER || secondArgType == LUA_TUSERDATA)) + // { + // } + // else + // mesh = newStandardMesh(L); + + // luax_pushtype(L, mesh); + // mesh->release(); + + return 0; +} + +int Wrap_Graphics::newTextBatch(lua_State* L) +{ + luax_checkgraphicscreated(L); + + auto* font = luax_checkfont(L, 1); + TextBatch* batch = nullptr; + + if (lua_isnoneornil(L, 2)) + luax_catchexcept(L, [&]() { batch = instance()->newTextBatch(font); }); + else + { + std::vector text {}; + luax_checkcoloredstring(L, 2, text); + + luax_catchexcept(L, [&]() { batch = instance()->newTextBatch(font, text); }); + } + + luax_pushtype(L, batch); + batch->release(); + + return 1; +} + +int Wrap_Graphics::newFont(lua_State* L) +{ + luax_checkgraphicscreated(L); + + FontBase* font = nullptr; + + if (!luax_istype(L, 1, Rasterizer::type)) + { + std::vector indices {}; + + for (int index = 0; index < lua_gettop(L); index++) + indices.push_back(index + 1); + + luax_convobj(L, indices, "font", "newRasterizer"); + } + + auto* rasterizer = luax_checktype(L, 1); + luax_catchexcept(L, [&]() { font = instance()->newFont(rasterizer); }); + + luax_pushtype(L, font); + font->release(); + + return 1; +} + +int Wrap_Graphics::print(lua_State* L) +{ + std::vector strings {}; + luax_checkcoloredstring(L, 1, strings); + + // clang-format off + if (luax_istype(L, 2, FontBase::type)) + { + auto* font = luax_checkfont(L, 2); + luax_checkstandardtransform(L, 3, [&](const Matrix4& transform) { + instance()->print(strings, font, transform); + }); + } + else + { + luax_checkstandardtransform(L, 2, [&](const Matrix4& transform) { + instance()->print(strings, transform); + }); + } + // clang-format on + + return 0; +} + +int Wrap_Graphics::printf(lua_State* L) +{ + std::vector strings {}; + luax_checkcoloredstring(L, 1, strings); + + FontBase* font = nullptr; + int start = 2; + + if (luax_istype(L, 2, FontBase::type)) + { + font = luax_checkfont(L, start); + start++; + } + + auto align = FontBase::ALIGN_LEFT; + Matrix4 matrix; + + int formatIndex = start + 2; + + if (luax_istype(L, start, Transform::type)) + { + auto* transform = luax_totype(L, start); + matrix = transform->getMatrix(); + + formatIndex = start + 1; + } + else + { + float x = luaL_checknumber(L, start + 0); + float y = luaL_checknumber(L, start + 1); + + float angle = luaL_optnumber(L, start + 4, 0.0f); + float sx = luaL_optnumber(L, start + 5, 1.0f); + float sy = luaL_optnumber(L, start + 6, sx); + float ox = luaL_optnumber(L, start + 7, 0.0f); + float oy = luaL_optnumber(L, start + 8, 0.0f); + float kx = luaL_optnumber(L, start + 9, 0.0f); + float ky = luaL_optnumber(L, start + 10, 0.0f); + + matrix = Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky); + } + + float wrap = luaL_checknumber(L, formatIndex); + + auto* alignment = lua_isnoneornil(L, formatIndex + 1) ? nullptr : luaL_checkstring(L, formatIndex + 1); + if (alignment != nullptr && !FontBase::getConstant(alignment, align)) + return luax_enumerror(L, "alignment", FontBase::AlignModes, alignment); + + if (font != nullptr) + luax_catchexcept(L, [&]() { instance()->printf(strings, font, wrap, align, matrix); }); + else + luax_catchexcept(L, [&]() { instance()->printf(strings, wrap, align, matrix); }); + + return 0; +} + +int Wrap_Graphics::getStats(lua_State* L) +{ + auto stats = instance()->getStats(); + + if (lua_istable(L, 1)) + lua_pushvalue(L, 1); + else + lua_createtable(L, 0, 7); + + lua_pushinteger(L, stats.drawCalls); + lua_setfield(L, -2, "drawcalls"); + + lua_pushinteger(L, stats.drawCallsBatched); + lua_setfield(L, -2, "drawcallsbatched"); + + lua_pushinteger(L, stats.shaderSwitches); + lua_setfield(L, -2, "shaderswitches"); + + lua_pushinteger(L, stats.textures); + lua_setfield(L, -2, "textures"); + + lua_pushnumber(L, (lua_Number)stats.textureMemory); + lua_setfield(L, -2, "texturememory"); + + lua_pushnumber(L, (lua_Number)stats.cpuProcessingTime); + lua_setfield(L, -2, "cpuprocessingtime"); + + lua_pushnumber(L, (lua_Number)stats.gpuDrawingTime); + lua_setfield(L, -2, "gpudrawingtime"); + + return 1; +} + +int Wrap_Graphics::polygon(lua_State* L) +{ + int argc = lua_gettop(L) - 1; + + Graphics::DrawMode mode; + const char* name = luaL_checkstring(L, 1); + + if (!Graphics::getConstant(name, mode)) + return luax_enumerror(L, "draw mode", Graphics::DrawModes, name); + + bool isTable = false; + + if (argc == 1 && lua_istable(L, 2)) + { + argc = (int)luax_objlen(L, 2); + isTable = true; + } + + if (argc % 2 != 0) + return luaL_error(L, "Number of vertex components must be a multiple of two."); + else if (argc < 6) + return luaL_error(L, "Need at least three vertices to draw a polygon."); + + int numVertices = argc / 2; + auto* coords = instance()->getScratchBuffer(numVertices + 1); + + if (isTable) + { + for (int index = 0; index < numVertices; index++) + { + lua_rawgeti(L, 2, (index * 2) + 1); + lua_rawgeti(L, 2, (index * 2) + 2); + + coords[index].x = luax_checkfloat(L, -2); + coords[index].y = luax_checkfloat(L, -1); + + lua_pop(L, 2); + } + } + else + { + for (int index = 0; index < numVertices; ++index) + { + coords[index].x = luax_checkfloat(L, (index * 2) + 2); + coords[index].y = luax_checkfloat(L, (index * 2) + 3); + } + } + + coords[numVertices] = coords[0]; + + luax_catchexcept(L, [&]() { instance()->polygon(mode, std::span(coords, numVertices + 1)); }); + + return 0; +} + +int Wrap_Graphics::rectangle(lua_State* L) +{ + Graphics::DrawMode mode; + const char* name = luaL_checkstring(L, 1); + + if (!Graphics::getConstant(name, mode)) + return luax_enumerror(L, "draw mode", Graphics::DrawModes, name); + + float x = luaL_checknumber(L, 2); + float y = luaL_checknumber(L, 3); + float w = luaL_checknumber(L, 4); + float h = luaL_checknumber(L, 5); + + if (lua_isnoneornil(L, 6)) + { + luax_catchexcept(L, [&]() { instance()->rectangle(mode, x, y, w, h); }); + return 0; + } + + float rx = luaL_optnumber(L, 6, 0.0f); + float ry = luaL_optnumber(L, 7, rx); + + if (lua_isnoneornil(L, 8)) + luax_catchexcept(L, [&]() { instance()->rectangle(mode, x, y, w, h, rx, ry); }); + else + { + int points = luaL_checkinteger(L, 8); + luax_catchexcept(L, [&]() { instance()->rectangle(mode, x, y, w, h, rx, ry, points); }); + } + + return 0; +} + +int Wrap_Graphics::circle(lua_State* L) +{ + Graphics::DrawMode mode; + const char* name = luaL_checkstring(L, 1); + + if (!Graphics::getConstant(name, mode)) + return luax_enumerror(L, "draw mode", Graphics::DrawModes, name); + + float x = luaL_checknumber(L, 2); + float y = luaL_checknumber(L, 3); + float radius = luaL_checknumber(L, 4); + + if (lua_isnoneornil(L, 5)) + luax_catchexcept(L, [&]() { instance()->circle(mode, x, y, radius); }); + else + { + int points = luaL_checkinteger(L, 5); + luax_catchexcept(L, [&]() { instance()->circle(mode, x, y, radius, points); }); + } + + return 0; +} + +int Wrap_Graphics::ellipse(lua_State* L) +{ + Graphics::DrawMode mode; + const char* name = luaL_checkstring(L, 1); + + if (!Graphics::getConstant(name, mode)) + return luax_enumerror(L, "draw mode", Graphics::DrawModes, name); + + float x = luaL_checknumber(L, 2); + float y = luaL_checknumber(L, 3); + float a = luaL_checknumber(L, 4); + float b = luaL_optnumber(L, 5, a); + + if (lua_isnoneornil(L, 6)) + luax_catchexcept(L, [&]() { instance()->ellipse(mode, x, y, a, b); }); + else + { + int points = luaL_checkinteger(L, 6); + luax_catchexcept(L, [&]() { instance()->ellipse(mode, x, y, a, b, points); }); + } + + return 0; +} + +int Wrap_Graphics::arc(lua_State* L) +{ + Graphics::DrawMode mode; + const char* name = luaL_checkstring(L, 1); + + if (!Graphics::getConstant(name, mode)) + return luax_enumerror(L, "draw mode", Graphics::DrawModes, name); + + int start = 2; + + Graphics::ArcMode arcMode = Graphics::ARC_PIE; + + if (lua_type(L, 2) == LUA_TSTRING) + { + const char* arcName = luaL_checkstring(L, 2); + + if (!Graphics::getConstant(arcName, arcMode)) + return luax_enumerror(L, "arc mode", Graphics::ArcModes, arcName); + + start = 3; + } + + float x = luaL_checknumber(L, start + 0); + float y = luaL_checknumber(L, start + 1); + float radius = luaL_checknumber(L, start + 2); + float angle1 = luaL_checknumber(L, start + 3); + float angle2 = luaL_checknumber(L, start + 4); + + if (lua_isnoneornil(L, start + 5)) + luax_catchexcept(L, [&]() { instance()->arc(mode, arcMode, x, y, radius, angle1, angle2); }); + else + { + int points = luaL_checkinteger(L, start + 5); + luax_catchexcept(L, [&]() { instance()->arc(mode, arcMode, x, y, radius, angle1, angle2, points); }); + } + + return 0; +} + +int Wrap_Graphics::points(lua_State* L) +{ + int argc = lua_gettop(L); + + bool isTable = false; + bool isTableOfTables = false; + + if (argc == 1 && lua_istable(L, 1)) + { + isTable = true; + argc = (int)luax_objlen(L, 1); + + lua_rawgeti(L, 1, 1); + isTableOfTables = lua_istable(L, -1); + lua_pop(L, 1); + } + + if (argc % 2 != 0 && !isTableOfTables) + return luaL_error(L, "Number of vertex components must be a multiple of two."); + + int numPositions = argc / 2; + + if (isTableOfTables) + numPositions = argc; + + Vector2* positions = nullptr; + Color* colors = nullptr; + + if (isTableOfTables) + { + size_t size = (sizeof(Vector2) + sizeof(Color)) * numPositions; + uint8_t* data = instance()->getScratchBuffer(size); + + positions = (Vector2*)data; + colors = (Color*)(data + sizeof(Vector2) * numPositions); + } + else + positions = instance()->getScratchBuffer(numPositions); + + if (isTable) + { + if (isTableOfTables) + { + for (int index = 0; index < argc; index++) + { + lua_rawgeti(L, 1, index + 1); + + for (int j = 1; j <= 6; j++) + lua_rawgeti(L, -j, j); + + positions[index].x = luax_checkfloat(L, -6); + positions[index].y = luax_checkfloat(L, -5); + + colors[index].r = (float)luax_optnumberclamped01(L, -4, 1.0); + colors[index].g = (float)luax_optnumberclamped01(L, -3, 1.0); + colors[index].b = (float)luax_optnumberclamped01(L, -2, 1.0); + colors[index].a = (float)luax_optnumberclamped01(L, -1, 1.0); + + lua_pop(L, 7); + } + } + else + { + for (int index = 0; index < numPositions; index++) + { + lua_rawgeti(L, 1, index * 2 + 1); + lua_rawgeti(L, 1, index * 2 + 2); + + positions[index].x = luax_checkfloat(L, -2); + positions[index].y = luax_checkfloat(L, -1); + + lua_pop(L, 2); + } + } + } + else + { + for (int index = 0; index < numPositions; index++) + { + positions[index].x = luax_checkfloat(L, index * 2 + 1); + positions[index].y = luax_checkfloat(L, index * 2 + 2); + } + } + + luax_catchexcept(L, [&]() { instance()->points(positions, colors, numPositions); }); + + return 0; +} + +int Wrap_Graphics::line(lua_State* L) +{ + int argc = lua_gettop(L); + int arg1Type = lua_type(L, 1); + bool isTable = false; + + if (argc == 1 && arg1Type == LUA_TTABLE) + { + argc = (int)luax_objlen(L, 1); + isTable = true; + } + + if (arg1Type != LUA_TTABLE && arg1Type != LUA_TNUMBER) + return luax_typeerror(L, 1, "table or number"); + else if (argc % 2 != 0) + return luaL_error(L, "Number of vertex components must be a multiple of two."); + else if (argc < 4) + return luaL_error(L, "Need at least two vertices to draw a line."); + + int numVertices = argc / 2; + + auto* coords = instance()->getScratchBuffer(numVertices); + + if (isTable) + { + for (int index = 0; index < numVertices; ++index) + { + lua_rawgeti(L, 1, (index * 2) + 1); + lua_rawgeti(L, 1, (index * 2) + 2); + + coords[index].x = luax_checkfloat(L, -2); + coords[index].y = luax_checkfloat(L, -1); + + lua_pop(L, 2); + } + } + else + { + for (int index = 0; index < numVertices; ++index) + { + coords[index].x = luax_checkfloat(L, (index * 2) + 1); + coords[index].y = luax_checkfloat(L, (index * 2) + 2); + } + } + + luax_catchexcept(L, [&]() { instance()->polyline(std::span(coords, numVertices)); }); + + return 0; +} + +int Wrap_Graphics::getWidth(lua_State* L) +{ + lua_pushinteger(L, instance()->getWidth()); + + return 1; +} + +int Wrap_Graphics::getHeight(lua_State* L) +{ + lua_pushinteger(L, instance()->getHeight()); + + return 1; +} + +int Wrap_Graphics::getDimensions(lua_State* L) +{ + lua_pushinteger(L, instance()->getWidth()); + lua_pushinteger(L, instance()->getHeight()); + + return 2; +} + +int Wrap_Graphics::getPixelWidth(lua_State* L) +{ + lua_pushinteger(L, instance()->getWidth()); + + return 1; +} + +int Wrap_Graphics::getPixelHeight(lua_State* L) +{ + lua_pushinteger(L, instance()->getHeight()); + + return 1; +} + +int Wrap_Graphics::getPixelDimensions(lua_State* L) +{ + lua_pushinteger(L, instance()->getWidth()); + lua_pushinteger(L, instance()->getHeight()); + + return 2; +} + // Homebrew Stuff™ int Wrap_Graphics::getScreens(lua_State* L) @@ -738,15 +1716,34 @@ int Wrap_Graphics::getScreens(lua_State* L) return 1; } +int Wrap_Graphics::getActiveScreen(lua_State* L) +{ + auto& info = love::getScreenInfo(love::currentScreen); + luax_pushstring(L, info.name); + + return 1; +} + int Wrap_Graphics::setActiveScreen(lua_State* L) { std::string name = luax_checkstring(L, 1); - Screen screen = love::getScreenId(name); + auto value = love::getScreenId(name); + + if (value == INVALID_SCREEN) + return luaL_error(L, "Invalid screen '%s'", name.c_str()); + + love::currentScreen = value; + instance()->setActiveScreen(); - love::currentScreen = screen; + return 0; +} +#if !defined(__WIIU__) +int Wrap_Graphics::copyCurrentScanBuffer(lua_State* L) +{ return 0; } +#endif // clang-format off #if defined(__3DS__) @@ -795,7 +1792,16 @@ static constexpr std::array platformFunctions = { "setWide", Wrap_Graphics::setWide }, { "getDepth", Wrap_Graphics::getDepth } }}; -#else +#elif defined(__WIIU__) +int Wrap_Graphics::copyCurrentScanBuffer(lua_State* L) +{ + instance()->copyCurrentScanBuffer(); + + return 0; +} +#endif + +#if !defined(__3DS__) static constexpr std::span platformFunctions = {}; #endif @@ -846,9 +1852,57 @@ static constexpr luaL_Reg functions[] = { "replaceTransform", Wrap_Graphics::replaceTransform }, { "transformPoint", Wrap_Graphics::transformPoint }, { "inverseTransformPoint", Wrap_Graphics::inverseTransformPoint }, + { "getStats", Wrap_Graphics::getStats }, + + { "getWidth", Wrap_Graphics::getWidth }, + { "getHeight", Wrap_Graphics::getHeight }, + { "getDimensions", Wrap_Graphics::getDimensions }, + { "getPixelWidth", Wrap_Graphics::getPixelWidth }, + { "getPixelHeight", Wrap_Graphics::getPixelHeight }, + { "getPixelDimensions", Wrap_Graphics::getPixelDimensions }, + + { "draw", Wrap_Graphics::draw }, + + { "polygon", Wrap_Graphics::polygon }, + { "rectangle", Wrap_Graphics::rectangle }, + { "circle", Wrap_Graphics::circle }, + { "ellipse", Wrap_Graphics::ellipse }, + { "arc", Wrap_Graphics::arc }, + { "points", Wrap_Graphics::points }, + { "line", Wrap_Graphics::line }, + + { "newTexture", Wrap_Graphics::newTexture }, + { "newQuad", Wrap_Graphics::newQuad }, + { "newImage", Wrap_Graphics::newImage }, + + // { "newMesh", Wrap_Graphics::newMesh }, + + { "newTextBatch", Wrap_Graphics::newTextBatch }, + + { "newFont", Wrap_Graphics::newFont }, + { "setFont", Wrap_Graphics::setFont }, + { "getFont", Wrap_Graphics::getFont }, + { "print", Wrap_Graphics::print }, + { "printf", Wrap_Graphics::printf }, + + { "getScreens", Wrap_Graphics::getScreens }, + { "getActiveScreen", Wrap_Graphics::getActiveScreen }, + { "setActiveScreen", Wrap_Graphics::setActiveScreen }, + { "copyCurrentScanBuffer", Wrap_Graphics::copyCurrentScanBuffer } +}; - { "setActiveScreen", Wrap_Graphics::setActiveScreen }, - { "getScreens", Wrap_Graphics::getScreens }, +static int open_drawable(lua_State* L) +{ + return luax_register_type(L, &Drawable::type); +} + +static constexpr lua_CFunction types[] = +{ + open_drawable, + love::open_texture, + love::open_quad, + love::open_font, + love::open_textbatch }; // clang-format on @@ -866,6 +1920,7 @@ int Wrap_Graphics::open(lua_State* L) module.type = &Module::type; module.functions = functions; module.platformFunctions = platformFunctions; + module.types = types; return luax_register_module(L, module); } diff --git a/source/modules/graphics/wrap_Mesh.cpp b/source/modules/graphics/wrap_Mesh.cpp new file mode 100644 index 000000000..037b6ce3a --- /dev/null +++ b/source/modules/graphics/wrap_Mesh.cpp @@ -0,0 +1,228 @@ +#include "modules/graphics/wrap_Mesh.hpp" + +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/wrap_Texture.hpp" + +#include + +using namespace love; + +int Wrap_Mesh::getVertexCount(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + lua_pushinteger(L, self->getVertexCount()); + + return 1; +} + +int Wrap_Mesh::flush(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + + self->flush(); + + return 0; +} + +int Wrap_Mesh::setVertexMap(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + + if (lua_isnoneornil(L, 2)) + { + luax_catchexcept(L, [&] { self->setVertexMap(); }); + return 0; + } + + if (luax_istype(L, 2, Data::type)) + { + auto* data = luax_totype(L, 2, Data::type); + + const char* indexTypeString = luaL_checkstring(L, 3); + IndexDataType indexType; + + if (!getConstant(indexTypeString, indexType)) + return luax_enumerror(L, "index data type", IndexDataTypes, indexTypeString); + + size_t dataTypeSize = getIndexDataSize(indexType); + int indexCount = luaL_optinteger(L, 4, data->getSize() / dataTypeSize); + + if (indexCount < 1 || indexCount * dataTypeSize > data->getSize()) + return luaL_error(L, "Invalid index count: %d.", indexCount); + + luax_catchexcept(L, [&] { self->setVertexMap(indexType, data->getData(), indexCount); }); + + return 0; + } + + bool isTable = lua_istable(L, 2); + int argc = isTable ? luax_objlen(L, 2) : lua_gettop(L) - 1; + + std::vector vertexMap {}; + vertexMap.reserve(argc); + + if (isTable) + { + for (int index = 0; index < argc; index++) + { + lua_rawgeti(L, 2, index + 1); + vertexMap.push_back(uint32_t(luaL_checkinteger(L, -1) - 1)); + lua_pop(L, 1); + } + } + else + { + for (int index = 0; index < argc; index++) + vertexMap.push_back(uint32_t(luaL_checkinteger(L, index + 2) - 1)); + } + + luax_catchexcept(L, [&] { self->setVertexMap(vertexMap); }); + + return 0; +} + +int Wrap_Mesh::getVertexMap(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + + std::vector vertexMap {}; + bool hasVertexMap = false; + + luax_catchexcept(L, [&] { hasVertexMap = self->getVertexMap(vertexMap); }); + + if (!hasVertexMap) + { + lua_pushnil(L); + return 1; + } + + int elementCount = (int)vertexMap.size(); + lua_createtable(L, elementCount, 0); + + for (int index = 0; index < elementCount; index++) + { + lua_pushinteger(L, lua_Integer(vertexMap[index]) + 1); + lua_rawseti(L, -2, index + 1); + } + + return 1; +} + +int Wrap_Mesh::setTexture(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + + if (lua_isnoneornil(L, 2)) + self->setTexture(); + else + { + auto* texture = luax_checktexture(L, 2); + self->setTexture(texture); + } + + return 0; +} + +int Wrap_Mesh::getTexture(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + auto* texture = self->getTexture(); + + if (texture == nullptr) + return 0; + + luax_pushtype(L, texture); + + return 1; +} + +int Wrap_Mesh::setDrawMode(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + + const char* modeString = luaL_checkstring(L, 2); + PrimitiveType mode; + + if (!getConstant(modeString, mode)) + return luax_enumerror(L, "mesh draw mode", PrimitiveTypes, modeString); + + self->setDrawMode(mode); + + return 0; +} + +int Wrap_Mesh::getDrawMode(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + auto mode = self->getDrawMode(); + + std::string_view name {}; + if (!getConstant(mode, name)) + return luaL_error(L, "Unknown mesh draw mode."); + + luax_pushstring(L, name); + + return 1; +} + +int Wrap_Mesh::setDrawRange(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + + if (lua_isnoneornil(L, 2)) + self->setDrawRange(); + else + { + int start = luaL_checkinteger(L, 2) - 1; + int count = luaL_checkinteger(L, 3); + + luax_catchexcept(L, [&] { self->setDrawRange(start, count); }); + } + + return 0; +} + +int Wrap_Mesh::getDrawRange(lua_State* L) +{ + auto* self = luax_checkmesh(L, 1); + + int start = 0; + int count = 1; + + if (!self->getDrawRange(start, count)) + return 0; + + lua_pushinteger(L, start + 1); + lua_pushinteger(L, count); + + return 2; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "getVertexCount", Wrap_Mesh::getVertexCount }, + { "flush", Wrap_Mesh::flush }, + { "setVertexMap", Wrap_Mesh::setVertexMap }, + { "getVertexMap", Wrap_Mesh::getVertexMap }, + { "setTexture", Wrap_Mesh::setTexture }, + { "getTexture", Wrap_Mesh::getTexture }, + { "setDrawMode", Wrap_Mesh::setDrawMode }, + { "getDrawMode", Wrap_Mesh::getDrawMode }, + { "setDrawRange", Wrap_Mesh::setDrawRange }, + { "getDrawRange", Wrap_Mesh::getDrawRange } +}; +// clang-format on + +namespace love +{ + Mesh* luax_checkmesh(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_mesh(lua_State* L) + { + return luax_register_type(L, &Mesh::type, functions); + } +} // namespace love diff --git a/source/modules/graphics/wrap_Quad.cpp b/source/modules/graphics/wrap_Quad.cpp new file mode 100644 index 000000000..3e2c0726a --- /dev/null +++ b/source/modules/graphics/wrap_Quad.cpp @@ -0,0 +1,96 @@ +#include "modules/graphics/wrap_Quad.hpp" + +using namespace love; + +int Wrap_Quad::setViewport(lua_State* L) +{ + auto* self = luax_checkquad(L, 1); + + Quad::Viewport viewport {}; + viewport.x = luaL_checknumber(L, 2); + viewport.y = luaL_checknumber(L, 3); + viewport.w = luaL_checknumber(L, 4); + viewport.h = luaL_checknumber(L, 5); + + if (lua_isnoneornil(L, 6)) + self->setViewport(viewport); + else + { + double sw = luaL_checknumber(L, 6); + double sh = luaL_checknumber(L, 7); + + self->refresh(viewport, sw, sh); + } + + return 0; +} + +int Wrap_Quad::getViewport(lua_State* L) +{ + auto* self = luax_checkquad(L, 1); + + auto viewport = self->getViewport(); + + lua_pushnumber(L, viewport.x); + lua_pushnumber(L, viewport.y); + lua_pushnumber(L, viewport.w); + lua_pushnumber(L, viewport.h); + + return 4; +} + +int Wrap_Quad::getTextureDimensions(lua_State* L) +{ + auto* self = luax_checkquad(L, 1); + + int width = self->getTextureWidth(); + int height = self->getTextureHeight(); + + lua_pushnumber(L, width); + lua_pushnumber(L, height); + + return 2; +} + +int Wrap_Quad::setLayer(lua_State* L) +{ + auto* self = luax_checkquad(L, 1); + int layer = luaL_checkinteger(L, 2) - 1; + + self->setLayer(layer); + + return 0; +} + +int Wrap_Quad::getLayer(lua_State* L) +{ + auto* self = luax_checkquad(L, 1); + + lua_pushinteger(L, self->getLayer() + 1); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "setViewport", Wrap_Quad::setViewport }, + { "getViewport", Wrap_Quad::getViewport }, + { "getTextureDimensions", Wrap_Quad::getTextureDimensions }, + { "setLayer", Wrap_Quad::setLayer }, + { "getLayer", Wrap_Quad::getLayer } +}; +// clang-format on + +namespace love +{ + Quad* luax_checkquad(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_quad(lua_State* L) + { + return luax_register_type(L, &Quad::type, functions); + } +} // namespace love diff --git a/source/modules/graphics/wrap_TextBatch.cpp b/source/modules/graphics/wrap_TextBatch.cpp new file mode 100644 index 000000000..cd7c3291e --- /dev/null +++ b/source/modules/graphics/wrap_TextBatch.cpp @@ -0,0 +1,208 @@ +#include "modules/graphics/wrap_TextBatch.hpp" + +#include "modules/graphics/wrap_Font.hpp" +#include "modules/math/wrap_Transform.hpp" + +using namespace love; + +int Wrap_TextBatch::set(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + std::vector newText {}; + luax_checkcoloredstring(L, 2, newText); + + luax_catchexcept(L, [&]() { self->set(newText); }); + + return 0; +} + +int Wrap_TextBatch::setf(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + float limit = luaL_checknumber(L, 3); + + FontBase::AlignMode align; + const char* name = luaL_checkstring(L, 4); + + if (!FontBase::getConstant(name, align)) + return luax_enumerror(L, "align mode", FontBase::AlignModes, name); + + std::vector newText {}; + luax_checkcoloredstring(L, 2, newText); + + luax_catchexcept(L, [&]() { self->set(newText, limit, align); }); + + return 0; +} + +int Wrap_TextBatch::add(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + int index = 0; + + std::vector text {}; + luax_checkcoloredstring(L, 2, text); + + if (luax_istype(L, 3, Transform::type)) + { + auto* transform = luax_totype(L, 3); + luax_catchexcept(L, [&]() { index = self->add(text, transform->getMatrix()); }); + } + else + { + float x = luaL_optnumber(L, 3, 0.0f); + float y = luaL_optnumber(L, 4, 0.0f); + float a = luaL_optnumber(L, 5, 0.0f); + + float sx = luaL_optnumber(L, 6, 1.0f); + float sy = luaL_optnumber(L, 7, sx); + + float ox = luaL_optnumber(L, 8, 0.0f); + float oy = luaL_optnumber(L, 9, 0.0f); + + float kx = luaL_optnumber(L, 10, 0.0f); + float ky = luaL_optnumber(L, 11, 0.0f); + + Matrix4 matrix(x, y, a, sx, sy, ox, oy, kx, ky); + luax_catchexcept(L, [&]() { index = self->add(text, matrix); }); + } + + lua_pushinteger(L, index + 1); + + return 1; +} + +int Wrap_TextBatch::addf(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + int index = 0; + + std::vector newText {}; + luax_checkcoloredstring(L, 2, newText); + + float wrap = luaL_checknumber(L, 3); + + auto align = FontBase::ALIGN_MAX_ENUM; + const char* name = luaL_checkstring(L, 4); + + if (!FontBase::getConstant(name, align)) + return luax_enumerror(L, "align mode", FontBase::AlignModes, name); + + if (luax_istype(L, 5, Transform::type)) + { + auto* transform = luax_totype(L, 5); + luax_catchexcept(L, [&]() { index = self->addf(newText, wrap, align, transform->getMatrix()); }); + } + else + { + float x = luaL_optnumber(L, 5, 0.0f); + float y = luaL_optnumber(L, 6, 0.0f); + float a = luaL_optnumber(L, 7, 0.0f); + float sx = luaL_optnumber(L, 8, 1.0f); + float sy = luaL_optnumber(L, 9, sx); + float ox = luaL_optnumber(L, 10, 0.0f); + float oy = luaL_optnumber(L, 11, 0.0f); + float kx = luaL_optnumber(L, 12, 0.0f); + float ky = luaL_optnumber(L, 13, 0.0f); + + Matrix4 matrix(x, y, a, sx, sy, ox, oy, kx, ky); + luax_catchexcept(L, [&]() { index = self->addf(newText, wrap, align, matrix); }); + } + + lua_pushinteger(L, index + 1); + + return 1; +} + +int Wrap_TextBatch::clear(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + luax_catchexcept(L, [&]() { self->clear(); }); + + return 0; +} + +int Wrap_TextBatch::setFont(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + auto* font = luax_checktype(L, 2); + + luax_catchexcept(L, [&]() { self->setFont(font); }); + + return 0; +} + +int Wrap_TextBatch::getFont(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + auto* font = self->getFont(); + luax_pushtype(L, font); + + return 1; +} + +int Wrap_TextBatch::getWidth(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + int index = luaL_optinteger(L, 2, 0) - 1; + lua_pushnumber(L, self->getWidth(index)); + + return 1; +} + +int Wrap_TextBatch::getHeight(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + int index = luaL_optinteger(L, 2, 0) - 1; + lua_pushnumber(L, self->getHeight(index)); + + return 1; +} + +int Wrap_TextBatch::getDimensions(lua_State* L) +{ + auto* self = luax_checktextbatch(L, 1); + + int index = luaL_optinteger(L, 2, 0) - 1; + lua_pushnumber(L, self->getWidth(index)); + lua_pushnumber(L, self->getHeight(index)); + + return 2; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "set", Wrap_TextBatch::set }, + { "setf", Wrap_TextBatch::setf }, + { "add", Wrap_TextBatch::add }, + { "addf", Wrap_TextBatch::addf }, + { "clear", Wrap_TextBatch::clear }, + { "setFont", Wrap_TextBatch::setFont }, + { "getFont", Wrap_TextBatch::getFont }, + { "getWidth", Wrap_TextBatch::getWidth }, + { "getHeight", Wrap_TextBatch::getHeight }, + { "getDimensions", Wrap_TextBatch::getDimensions } +}; +// clang-format on + +namespace love +{ + TextBatch* luax_checktextbatch(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_textbatch(lua_State* L) + { + return luax_register_type(L, &TextBatch::type, functions); + } +} // namespace love diff --git a/source/modules/graphics/wrap_Texture.cpp b/source/modules/graphics/wrap_Texture.cpp new file mode 100644 index 000000000..6c2b587dd --- /dev/null +++ b/source/modules/graphics/wrap_Texture.cpp @@ -0,0 +1,492 @@ +#include "modules/graphics/wrap_Texture.hpp" +#include "modules/graphics/Graphics.hpp" + +using namespace love; + +int Wrap_Texture::getTextureType(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + std::string_view type; + if (!Texture::getConstant(self->getTextureType(), type)) + return luax_enumerror(L, "texture type", Texture::TextureTypes, type); + + luax_pushstring(L, type); + return 1; +} + +static int optMipmap(lua_State* L, Texture* texture, int index) +{ + int mipmap = 0; + + if (!lua_isnoneornil(L, index)) + { + mipmap = luaL_checkinteger(L, index) - 1; + + if (mipmap < 0 || mipmap >= texture->getMipmapCount()) + luaL_error(L, "Invalid mipmap index: %d", mipmap + 1); + } + + return mipmap; +} + +int Wrap_Texture::getWidth(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + const int mipmap = optMipmap(L, self, 2); + + lua_pushnumber(L, self->getWidth(mipmap)); + + return 1; +} + +int Wrap_Texture::getHeight(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + const int mipmap = optMipmap(L, self, 2); + + lua_pushnumber(L, self->getHeight(mipmap)); + + return 1; +} + +int Wrap_Texture::getDimensions(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + const int mipmap = optMipmap(L, self, 2); + + lua_pushnumber(L, self->getWidth(mipmap)); + lua_pushnumber(L, self->getHeight(mipmap)); + + return 2; +} + +int Wrap_Texture::getDepth(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushnumber(L, self->getDepth()); + + return 1; +} + +int Wrap_Texture::getLayerCount(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushnumber(L, self->getLayerCount()); + + return 1; +} + +int Wrap_Texture::getMipmapCount(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushnumber(L, self->getMipmapCount()); + + return 1; +} + +int Wrap_Texture::getPixelWidth(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + const int mipmap = optMipmap(L, self, 2); + + lua_pushnumber(L, self->getPixelWidth(mipmap)); + + return 1; +} + +int Wrap_Texture::getPixelHeight(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + const int mipmap = optMipmap(L, self, 2); + + lua_pushnumber(L, self->getPixelHeight(mipmap)); + + return 1; +} + +int Wrap_Texture::getPixelDimensions(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + const int mipmap = optMipmap(L, self, 2); + + lua_pushnumber(L, self->getPixelWidth(mipmap)); + lua_pushnumber(L, self->getPixelHeight(mipmap)); + + return 2; +} + +int Wrap_Texture::getDPIScale(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushnumber(L, self->getDPIScale()); + + return 1; +} + +int Wrap_Texture::isFormatLinear(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushboolean(L, self->isFormatLinear()); + + return 1; +} + +int Wrap_Texture::isCompressed(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushboolean(L, self->isCompressed()); + + return 1; +} + +int Wrap_Texture::getMSAA(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushnumber(L, self->getMSAA()); + + return 1; +} + +int Wrap_Texture::setFilter(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + SamplerState state = self->getSamplerState(); + + const char* minFilter = luaL_checkstring(L, 2); + const char* magFilter = luaL_checkstring(L, 3); + + if (!SamplerState::getConstant(minFilter, state.minFilter)) + return luax_enumerror(L, "minification filter", SamplerState::FilterModes, minFilter); + + if (!SamplerState::getConstant(magFilter, state.magFilter)) + return luax_enumerror(L, "magnification filter", SamplerState::FilterModes, magFilter); + + state.maxAnisotropy = std::clamp((int)luaL_optnumber(L, 4, 1.0), 1, LOVE_UINT8_MAX); + + luax_catchexcept(L, [&]() { self->setSamplerState(state); }); + + return 0; +} + +int Wrap_Texture::getFilter(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + const auto& state = self->getSamplerState(); + + std::string_view minFilter; + std::string_view magFilter; + + if (!SamplerState::getConstant(state.minFilter, minFilter)) + return luax_enumerror(L, "minification filter", SamplerState::FilterModes, minFilter); + + if (!SamplerState::getConstant(state.magFilter, magFilter)) + return luax_enumerror(L, "magnification filter", SamplerState::FilterModes, magFilter); + + luax_pushstring(L, minFilter); + luax_pushstring(L, magFilter); + lua_pushnumber(L, state.maxAnisotropy); + + return 3; +} + +int Wrap_Texture::setMipmapFilter(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + auto state = self->getSamplerState(); + + if (lua_isnoneornil(L, 2)) + state.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE; + else + { + const char* mipmapType = luaL_checkstring(L, 2); + if (!SamplerState::getConstant(mipmapType, state.mipmapFilter)) + return luax_enumerror(L, "mipmap filter", SamplerState::MipmapFilterModes, mipmapType); + } + + state.lodBias = -((float)luaL_optnumber(L, 3, 0.0)); + + luax_catchexcept(L, [&]() { self->setSamplerState(state); }); + + return 0; +} + +int Wrap_Texture::getMipmapFilter(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + const auto& state = self->getSamplerState(); + + std::string_view mipmapFilter; + + if (SamplerState::getConstant(state.mipmapFilter, mipmapFilter)) + luax_pushstring(L, mipmapFilter); + else + lua_pushnil(L); + + lua_pushnumber(L, -state.lodBias); + + return 2; +} + +int Wrap_Texture::setWrap(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + auto state = self->getSamplerState(); + + const char* wrapS = luaL_checkstring(L, 2); + const char* wrapT = luaL_optstring(L, 3, wrapS); + const char* wrapR = luaL_optstring(L, 4, wrapS); + + if (!SamplerState::getConstant(wrapS, state.wrapU)) + return luax_enumerror(L, "wrap mode", SamplerState::WrapModes, wrapS); + + if (!SamplerState::getConstant(wrapT, state.wrapV)) + return luax_enumerror(L, "wrap mode", SamplerState::WrapModes, wrapT); + + if (!SamplerState::getConstant(wrapR, state.wrapW)) + return luax_enumerror(L, "wrap mode", SamplerState::WrapModes, wrapR); + + luax_catchexcept(L, [&]() { self->setSamplerState(state); }); + + return 0; +} + +int Wrap_Texture::getWrap(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + const auto& state = self->getSamplerState(); + + std::string_view wrapS; + std::string_view wrapT; + std::string_view wrapR; + + if (!SamplerState::getConstant(state.wrapU, wrapS)) + return luaL_error(L, "Unknown wrap mode."); + + if (!SamplerState::getConstant(state.wrapV, wrapT)) + return luaL_error(L, "Unknown wrap mode."); + + if (!SamplerState::getConstant(state.wrapW, wrapR)) + return luaL_error(L, "Unknown wrap mode."); + + luax_pushstring(L, wrapS); + luax_pushstring(L, wrapT); + luax_pushstring(L, wrapR); + + return 3; +} + +int Wrap_Texture::getFormat(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + auto pixelFormat = self->getPixelFormat(); + std::string_view format; + + if (!love::getConstant(pixelFormat, format)) + return luaL_error(L, "Unknown pixel format."); + + luax_pushstring(L, format); + return 1; +} + +int Wrap_Texture::isCanvas(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushboolean(L, self->isRenderTarget()); + + return 1; +} + +int Wrap_Texture::isComputeWritable(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushboolean(L, self->isComputeWritable()); + + return 1; +} + +int Wrap_Texture::isReadable(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + lua_pushboolean(L, self->isReadable()); + + return 1; +} + +int Wrap_Texture::getViewFormats(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + const auto& formats = self->getViewFormats(); + + lua_createtable(L, (int)formats.size(), 0); + + for (int index = 0; index < (int)formats.size(); index++) + { + std::string_view name; + if (!love::getConstant(formats[index], name)) + return luaL_error(L, "Unknown pixel format."); + + luax_pushstring(L, name); + lua_rawseti(L, -2, index + 1); + } + + return 1; +} + +int Wrap_Texture::setDepthSampleMode(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + auto state = self->getSamplerState(); + + state.depthSampleMode.hasValue = false; + if (!lua_isnoneornil(L, 2)) + { + const char* name = luaL_checkstring(L, 2); + + state.depthSampleMode.hasValue = true; + if (!love::getConstant(name, state.depthSampleMode.value)) + return luax_enumerror(L, "depth sample mode", love::CompareModes, name); + } + + luax_catchexcept(L, [&]() { self->setSamplerState(state); }); + + return 0; +} + +int Wrap_Texture::getDepthSampleMode(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + const auto& state = self->getSamplerState(); + + if (state.depthSampleMode.hasValue) + { + std::string_view name; + if (!love::getConstant(state.depthSampleMode.value, name)) + return luaL_error(L, "Unknown compare mode."); + + luax_pushstring(L, name); + } + else + lua_pushnil(L); + + return 1; +} + +int Wrap_Texture::getMipmapMode(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + std::string_view name; + if (!Texture::getConstant(self->getMipmapsMode(), name)) + return luax_enumerror(L, "mipmap mode", Texture::MipmapsModes, name); + + luax_pushstring(L, name); + + return 1; +} + +int Wrap_Texture::generateMipmaps(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + luax_catchexcept(L, [&]() { self->generateMipmaps(); }); + + return 0; +} + +// TODO: Implement this +int Wrap_Texture::replacePixels(lua_State* L) +{ + return 0; +} + +// TODO: Implement this +int Wrap_Texture::renderTo(lua_State* L) +{ + return 0; +} + +int Wrap_Texture::getDebugName(lua_State* L) +{ + auto* self = luax_checktexture(L, 1); + + const auto& name = self->getDebugName(); + + if (name.empty()) + lua_pushnil(L); + else + luax_pushstring(L, name); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "getTextureType", Wrap_Texture::getTextureType }, + { "getWidth", Wrap_Texture::getWidth }, + { "getHeight", Wrap_Texture::getHeight }, + { "getDimensions", Wrap_Texture::getDimensions }, + { "getDepth", Wrap_Texture::getDepth }, + { "getLayerCount", Wrap_Texture::getLayerCount }, + { "getMipmapCount", Wrap_Texture::getMipmapCount }, + { "getPixelWidth", Wrap_Texture::getPixelWidth }, + { "getPixelHeight", Wrap_Texture::getPixelHeight }, + { "getPixelDimensions", Wrap_Texture::getPixelDimensions }, + { "getDPIScale", Wrap_Texture::getDPIScale }, + { "isFormatLinear", Wrap_Texture::isFormatLinear }, + { "isCompressed", Wrap_Texture::isCompressed }, + { "getMSAA", Wrap_Texture::getMSAA }, + { "setFilter", Wrap_Texture::setFilter }, + { "getFilter", Wrap_Texture::getFilter }, + { "setMipmapFilter", Wrap_Texture::setMipmapFilter }, + { "getMipmapFilter", Wrap_Texture::getMipmapFilter }, + { "setWrap", Wrap_Texture::setWrap }, + { "getWrap", Wrap_Texture::getWrap }, + { "getFormat", Wrap_Texture::getFormat }, + { "isCanvas", Wrap_Texture::isCanvas }, + { "isComputeWritable", Wrap_Texture::isComputeWritable }, + { "isReadable", Wrap_Texture::isReadable }, + { "getViewFormats", Wrap_Texture::getViewFormats }, + { "getMipmapMode", Wrap_Texture::getMipmapMode }, + { "getDepthSampleMode", Wrap_Texture::getDepthSampleMode }, + { "setDepthSampleMode", Wrap_Texture::setDepthSampleMode }, + { "generateMipmaps", Wrap_Texture::generateMipmaps }, + { "replacePixels", Wrap_Texture::replacePixels }, + { "renderTo", Wrap_Texture::renderTo }, + { "getDebugName", Wrap_Texture::getDebugName } +}; +// clang-format on + +namespace love +{ + Texture* luax_checktexture(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_texture(lua_State* L) + { + return luax_register_type(L, &Texture::type, functions); + } +} // namespace love diff --git a/source/modules/image/CompressedImageData.cpp b/source/modules/image/CompressedImageData.cpp new file mode 100644 index 000000000..af282c301 --- /dev/null +++ b/source/modules/image/CompressedImageData.cpp @@ -0,0 +1,135 @@ +#include "common/Exception.hpp" + +#include "modules/image/CompressedImageData.hpp" + +#define E_COULD_NOT_PARSE_COMPRESSED_IMAGE_DATA "Could not parse compressed data: {:s}" + +namespace love +{ + Type CompressedImageData::type("CompressedImageData", &Data::type); + + CompressedImageData::CompressedImageData(const std::list& formats, Data* fileData) : + format(PIXELFORMAT_UNKNOWN) + { + FormatHandler* parser = nullptr; + + for (auto* handler : formats) + { + if (handler->canParseCompressed(fileData)) + { + parser = handler; + break; + } + } + + if (parser == nullptr) + throw love::Exception(E_COULD_NOT_PARSE_COMPRESSED_IMAGE_DATA, "Unknown format."); + + memory = parser->parseCompressed(fileData, this->dataImages, this->format); + + if (memory == nullptr) + throw love::Exception(E_COULD_NOT_PARSE_COMPRESSED_IMAGE_DATA, "Memory allocation failed."); + + if (this->dataImages.size() == 0 || memory->getSize() == 0) + throw love::Exception(E_COULD_NOT_PARSE_COMPRESSED_IMAGE_DATA, "No valid data?"); + + this->format = love::getLinearPixelFormat(this->format); + } + + CompressedImageData::CompressedImageData(const CompressedImageData& other) : format(other.format) + { + this->memory.set(other.memory->clone(), Acquire::NO_RETAIN); + + // clang-format off + for (const auto& image : other.dataImages) + { + auto* slice = new CompressedSlice(image->getFormat(), image->getWidth(), image->getHeight(), memory, image->getOffset(), image->getSize()); + this->dataImages.push_back(slice); + slice->release(); + } + // clang-format on + } + + CompressedImageData::~CompressedImageData() + {} + + CompressedImageData* CompressedImageData::clone() const + { + return new CompressedImageData(*this); + } + + size_t CompressedImageData::getSize() const + { + return this->memory->getSize(); + } + + void* CompressedImageData::getData() const + { + return this->memory->getData(); + } + + int CompressedImageData::getMipmapCount() const + { + return this->dataImages.size(); + } + + int CompressedImageData::getSliceCount(int) const + { + return 1; + } + + size_t CompressedImageData::getSize(int mipmap) const + { + this->checkSliceExists(0, mipmap); + return this->dataImages[mipmap]->getSize(); + } + + void* CompressedImageData::getData(int mipmap) const + { + this->checkSliceExists(0, mipmap); + return this->dataImages[mipmap]->getData(); + } + + int CompressedImageData::getWidth(int mipmap) const + { + this->checkSliceExists(0, mipmap); + return this->dataImages[mipmap]->getWidth(); + } + + int CompressedImageData::getHeight(int mipmap) const + { + this->checkSliceExists(0, mipmap); + return this->dataImages[mipmap]->getHeight(); + } + + PixelFormat CompressedImageData::getFormat() const + { + return this->format; + } + + bool CompressedImageData::isLinear() const + { + return this->dataImages.empty() ? false : this->dataImages[0]->isLinear(); + } + + void CompressedImageData::setLinear(bool linear) + { + for (auto& slice : this->dataImages) + slice->setLinear(linear); + } + + CompressedSlice* CompressedImageData::getSlice(int slice, int mipmap) const + { + this->checkSliceExists(slice, mipmap); + return this->dataImages[mipmap].get(); + } + + void CompressedImageData::checkSliceExists(int slice, int mipmap) const + { + if (slice != 0) + throw love::Exception("Slice index {:d} does not exist.", slice + 1); + + if (mipmap < 0 || mipmap >= (int)this->dataImages.size()) + throw love::Exception("Mipmap level {:d} does not exist.", mipmap + 1); + } +} // namespace love diff --git a/source/modules/image/CompressedSlice.cpp b/source/modules/image/CompressedSlice.cpp new file mode 100644 index 000000000..27a7c7fc8 --- /dev/null +++ b/source/modules/image/CompressedSlice.cpp @@ -0,0 +1,29 @@ +#include "common/Exception.hpp" + +#include "modules/image/CompressedSlice.hpp" + +namespace love +{ + CompressedSlice::CompressedSlice(PixelFormat format, int width, int height, ByteData* memory, + size_t offset, size_t size) : + ImageDataBase(format, width, height), + memory(memory), + offset(offset), + size(size) + {} + + CompressedSlice::CompressedSlice(const CompressedSlice& other) : + ImageDataBase(other.getFormat(), other.getWidth(), other.getHeight()), + memory(other.memory), + offset(other.offset), + size(other.size) + {} + + CompressedSlice::~CompressedSlice() + {} + + CompressedSlice* CompressedSlice::clone() const + { + return new CompressedSlice(*this); + } +} // namespace love diff --git a/source/modules/image/FormatHandler.cpp b/source/modules/image/FormatHandler.cpp new file mode 100644 index 000000000..773afbde6 --- /dev/null +++ b/source/modules/image/FormatHandler.cpp @@ -0,0 +1,52 @@ +#include "common/Exception.hpp" + +#include "modules/image/FormatHandler.hpp" + +namespace love +{ + FormatHandler::FormatHandler() + {} + + FormatHandler::~FormatHandler() + {} + + bool FormatHandler::canDecode(Data*) const + { + return false; + } + + bool FormatHandler::canEncode(PixelFormat, EncodedFormat) const + { + return false; + } + + FormatHandler::DecodedImage FormatHandler::decode(Data*) const + { + throw love::Exception("Image decoding is not implemented for this format."); + } + + FormatHandler::EncodedImage FormatHandler::encode(const DecodedImage&, EncodedFormat) const + { + throw love::Exception("Image encoding is not implemented for this format."); + } + + bool FormatHandler::canParseCompressed(Data*) const + { + return false; + } + + StrongRef FormatHandler::parseCompressed(Data*, CompressedSlices&, PixelFormat&) const + { + throw love::Exception("Compressed image parsing is not implemented for this format."); + } + + void FormatHandler::freeRawPixels(uint8_t* memory) + { + delete[] memory; + } + + void FormatHandler::freeEncodedImage(uint8_t* memory) + { + delete[] memory; + } +} // namespace love diff --git a/source/modules/image/Image.cpp b/source/modules/image/Image.cpp new file mode 100644 index 000000000..0ea9aa7be --- /dev/null +++ b/source/modules/image/Image.cpp @@ -0,0 +1,90 @@ +#include "modules/image/Image.hpp" + +#if !defined(__3DS__) + #include "modules/image/magpie/ASTCHandler.hpp" + #include "modules/image/magpie/JPGHandler.hpp" + #include "modules/image/magpie/KTXHandler.hpp" + #include "modules/image/magpie/PNGHandler.hpp" + #include "modules/image/magpie/ddsHandler.hpp" +#else + #include "modules/image/magpie/T3XHandler.hpp" +#endif + +namespace love +{ + Image::Image() : Module(M_IMAGE, "love.image") + { + float16Init(); + + this->formatHandlers = { +#if defined(__3DS__) + new T3XHandler +#else + new ASTCHandler, + new JPGHandler, + new KTXHandler, + new PNGHandler, + new DDSHandler +#endif + }; + } + + Image::~Image() + { + for (FormatHandler* handler : this->formatHandlers) + handler->release(); + } + + ImageData* Image::newImageData(Data* data) const + { + return new ImageData(data); + } + + ImageData* Image::newImageData(int width, int height, PixelFormat format) const + { + return new ImageData(width, height, format); + } + + ImageData* Image::newImageData(int width, int height, PixelFormat format, void* data, bool own) const + { + return new ImageData(width, height, format, data, own); + } + + CompressedImageData* Image::newCompressedData(Data* data) const + { + return new CompressedImageData(this->formatHandlers, data); + } + + bool Image::isCompressed(Data* data) const + { + for (FormatHandler* handler : this->formatHandlers) + { + if (handler->canParseCompressed(data)) + return true; + } + + return false; + } + + const std::list& Image::getFormatHandlers() const + { + return this->formatHandlers; + } + + ImageData* Image::newPastedImageData(ImageData* source, int x, int y, int width, int height) const + { + auto* result = this->newImageData(width, height, source->getFormat()); + + try + { + result->paste(source, 0, 0, x, y, width, height); + } + catch (love::Exception&) + { + result->release(); + throw; + } + + return result; + } +} // namespace love diff --git a/source/modules/image/ImageData.cpp b/source/modules/image/ImageData.cpp new file mode 100644 index 000000000..950a2bbee --- /dev/null +++ b/source/modules/image/ImageData.cpp @@ -0,0 +1,900 @@ +#include "common/Console.hpp" +#include "common/math.hpp" + +#include "modules/filesystem/physfs/Filesystem.hpp" +#include "modules/image/Image.hpp" +#include "modules/image/ImageData.hpp" + +#define E_PIXELFORMAT_NOT_SUPPORTED "ImageData does not support the {:s} pixel format." +#define E_IMAGEDATA_NOT_MULTIPLE_OF_8 "ImageData only supports sizes that are multiples of 8." +#define E_LOVE_IMAGE_NOT_LOADED "love.image must be loaded in order to {:s} an ImageData." +#define E_LOVE_FILESYSTEM_NOT_LOADED \ + "love.filesystem must be loaded in order to write an encoded ImageData to a file." +#define E_CANNOT_CONVERT_FROM_FORMAT \ + "ImageData:paste does not currently support converting from the {:s} pixel format." +#define E_CANNOT_CONVERT_TO_FORMAT \ + "ImageData:paste does not currently support converting to the {:s} pixel format." +#define E_OUT_OF_RANGE_PIXEL "Attempt to set out-of-range pixel at ({:d}, {:d})." + +namespace love +{ + Type ImageData::type("ImageData", &Data::type); + + ImageData::ImageData(Data* data) : ImageDataBase(PIXELFORMAT_UNKNOWN, 0, 0) + { + this->decode(data); + } + + ImageData::ImageData(int width, int height, PixelFormat format) : ImageDataBase(format, width, height) + { + if (!ImageData::validPixelFormat(format)) + { + const char* name = love::getConstant(format); + throw love::Exception(E_PIXELFORMAT_NOT_SUPPORTED, name); + } + + this->create(width, height, format); + std::memset(this->data, 0, this->getSize()); + } + + ImageData::ImageData(int width, int height, PixelFormat format, void* data, bool own) : + ImageDataBase(format, width, height) + { + if (!ImageData::validPixelFormat(format)) + { + const char* name = love::getConstant(format); + throw love::Exception(E_PIXELFORMAT_NOT_SUPPORTED, name); + } + + if (own) + this->data = (uint8_t*)data; + else + this->create(width, height, format, data); + } + + ImageData::ImageData(const ImageData& other) : ImageDataBase(other.format, other.width, other.height) + { + this->create(other.width, other.height, other.format, other.data); + } + + ImageData::~ImageData() + { + if (this->decodeHandler.get()) + this->decodeHandler->freeRawPixels(this->data); + else + delete[] this->data; + } + + ImageData* ImageData::clone() const + { + return new ImageData(*this); + } + + template + void copyBytesTiled(const void* data, const void* buffer, const int width, const int height) + { + if (width % 8 != 0 && height % 8 != 0) + throw love::Exception(E_IMAGEDATA_NOT_MULTIPLE_OF_8); + + const auto nextWidth = NextPo2(width); + + auto* destination = (T*)data; + const T* source = (const T*)buffer; + + for (int j = 0; j < height; j += 8) + { + std::memcpy(destination, source, width * 8 * sizeof(T)); + + source += width * 8; + destination += nextWidth * 8; + } + } + + static constexpr bool needsPowTwo = (Console::is(Console::CTR) ? true : false); + + void ImageData::create(int width, int height, PixelFormat format, void* data) + { + const auto dataSize = love::getPixelFormatSliceSize(format, width, height, needsPowTwo); + + try + { + this->data = new uint8_t[dataSize]; + } + catch (std::bad_alloc&) + { + throw love::Exception(E_OUT_OF_MEMORY); + } + + if (data) + { + if constexpr (Console::is(Console::CTR)) + { + if (format == PIXELFORMAT_RGB565_UNORM) + copyBytesTiled(this->data, data, width, height); + else + copyBytesTiled(this->data, data, width, height); + } + else + std::memcpy(this->data, data, dataSize); + } + + this->decodeHandler = nullptr; + this->format = format; + + this->pixelSetFunction = ImageData::getPixelSetFunction(format); + this->pixelGetFunction = ImageData::getPixelGetFunction(format); + } + + void ImageData::decode(Data* data) + { + FormatHandler* decoder = nullptr; + FormatHandler::DecodedImage image {}; + + auto* module = Module::getInstance(Module::M_IMAGE); + + if (module == nullptr) + throw love::Exception(E_LOVE_IMAGE_NOT_LOADED, "decode"); + + for (auto* handler : module->getFormatHandlers()) + { + if (handler->canDecode(data)) + { + decoder = handler; + break; + } + } + + if (decoder) + image = decoder->decode(data); + + if (image.data == nullptr) + throw love::Exception("Could not decode to ImageData: unsupported format"); + + if (image.size != love::getPixelFormatSliceSize(image.format, image.width, image.height, false)) + { + decoder->freeRawPixels(image.data); + throw love::Exception("Could not convert image!"); + } + + if (this->decodeHandler) + decodeHandler->freeRawPixels(this->data); + else + delete[] this->data; + + image.format = getLinearPixelFormat(image.format); + + this->width = image.width; + this->height = image.height; + this->data = image.data; + this->format = image.format; + + this->decodeHandler = decoder; + + this->pixelSetFunction = ImageData::getPixelSetFunction(this->format); + this->pixelGetFunction = ImageData::getPixelGetFunction(this->format); + } + + FileData* ImageData::encode(FormatHandler::EncodedFormat encodedFormat, const char* filename, + bool writeFile) const + { + FormatHandler* encoder = nullptr; + + FormatHandler::EncodedImage image {}; + FormatHandler::DecodedImage rawImage {}; + + rawImage.width = this->width; + rawImage.height = this->height; + rawImage.size = this->getSize(); + rawImage.data = this->data; + rawImage.format = this->format; + + auto* module = Module::getInstance(Module::M_IMAGE); + + if (module == nullptr) + throw love::Exception(E_LOVE_IMAGE_NOT_LOADED, "encode"); + + for (auto* handler : module->getFormatHandlers()) + { + if (handler->canEncode(this->format, encodedFormat)) + { + encoder = handler; + break; + } + } + + if (encoder != nullptr) + image = encoder->encode(rawImage, encodedFormat); + + if (encoder == nullptr || image.data == nullptr) + { + const char* formatName = love::getConstant(this->format); + throw love::Exception("No suitable image encoder for the {:s} pixel format.", formatName); + } + + FileData* fileData = nullptr; + + try + { + fileData = new FileData(image.size, filename); + } + catch (love::Exception&) + { + encoder->freeEncodedImage(image.data); + throw; + } + + std::memcpy(fileData->getData(), image.data, image.size); + encoder->freeEncodedImage(image.data); + + if (writeFile) + { + auto* filesystem = Module::getInstance(Module::M_FILESYSTEM); + + if (filesystem == nullptr) + throw love::Exception(E_LOVE_FILESYSTEM_NOT_LOADED); + + try + { + filesystem->write(filename, fileData->getData(), fileData->getSize()); + } + catch (love::Exception&) + { + fileData->release(); + throw; + } + } + + return fileData; + } + + size_t ImageData::getSize() const + { + return size_t(this->getWidth() * this->getHeight()) * this->getPixelSize(); + } + + void* ImageData::getData() const + { + return this->data; + } + + bool ImageData::inside(int x, int y) const + { + return x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight(); + } + + static void setPixelR8(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba8[0] = (uint8_t)(std::clamp(color.r, 0.0f, 1.0f) * 255.0f + 0.5f); + } + + static void setPixelRG8(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba8[0] = (uint8_t)(std::clamp(color.r, 0.0f, 1.0f) * 255.0f + 0.5f); + pixel->rgba8[1] = (uint8_t)(std::clamp(color.g, 0.0f, 1.0f) * 255.0f + 0.5f); + } + + static void setPixelRGBA8(const Color& color, ImageData::Pixel* pixel) + { + if (Console::is(Console::CTR)) + { + pixel->packed32 = color.abgr(); + return; + } + + pixel->rgba8[0] = (uint8_t)(std::clamp(color.r, 0.0f, 1.0f) * 255.0f + 0.5f); + pixel->rgba8[1] = (uint8_t)(std::clamp(color.g, 0.0f, 1.0f) * 255.0f + 0.5f); + pixel->rgba8[2] = (uint8_t)(std::clamp(color.b, 0.0f, 1.0f) * 255.0f + 0.5f); + pixel->rgba8[3] = (uint8_t)(std::clamp(color.a, 0.0f, 1.0f) * 255.0f + 0.5f); + } + + static void setPixelR16(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba16[0] = (uint16_t)(std::clamp(color.r, 0.0f, 1.0f) * 65535.0f + 0.5f); + } + + static void setPixelRG16(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba16[0] = (uint16_t)(std::clamp(color.r, 0.0f, 1.0f) * 65535.0f + 0.5f); + pixel->rgba16[1] = (uint16_t)(std::clamp(color.g, 0.0f, 1.0f) * 65535.0f + 0.5f); + } + + static void setPixelRGBA16(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba16[0] = (uint16_t)(std::clamp(color.r, 0.0f, 1.0f) * 65535.0f + 0.5f); + pixel->rgba16[1] = (uint16_t)(std::clamp(color.g, 0.0f, 1.0f) * 65535.0f + 0.5f); + pixel->rgba16[2] = (uint16_t)(std::clamp(color.b, 0.0f, 1.0f) * 65535.0f + 0.5f); + pixel->rgba16[3] = (uint16_t)(std::clamp(color.a, 0.0f, 1.0f) * 65535.0f + 0.5f); + } + + static void setPixelR16F(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba16f[0] = float32to16(color.r); + } + + static void setPixelRG16F(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba16f[0] = float32to16(color.r); + pixel->rgba16f[1] = float32to16(color.g); + } + + static void setPixelRGBA16F(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba16f[0] = float32to16(color.r); + pixel->rgba16f[1] = float32to16(color.g); + pixel->rgba16f[2] = float32to16(color.b); + pixel->rgba16f[3] = float32to16(color.a); + } + + static void setPixelR32F(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba32f[0] = color.r; + } + + static void setPixelRG32F(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba32f[0] = color.r; + pixel->rgba32f[1] = color.g; + } + + static void setPixelRGBA32F(const Color& color, ImageData::Pixel* pixel) + { + pixel->rgba32f[0] = color.r; + pixel->rgba32f[1] = color.g; + pixel->rgba32f[2] = color.b; + pixel->rgba32f[3] = color.a; + } + + static void setPixelRGBA4(const Color& color, ImageData::Pixel* pixel) + { + uint16_t red = (uint16_t)(std::clamp(color.r, 0.0f, 1.0f) * 0xF + 0.5f); + uint16_t green = (uint16_t)(std::clamp(color.g, 0.0f, 1.0f) * 0xF + 0.5f); + uint16_t blue = (uint16_t)(std::clamp(color.b, 0.0f, 1.0f) * 0xF + 0.5f); + uint16_t alpha = (uint16_t)(std::clamp(color.a, 0.0f, 1.0f) * 0xF + 0.5f); + + pixel->packed16 = (red << 12) | (green << 8) | (blue << 4) | (alpha << 0); + } + + static void setPixelRGB5A1(const Color& color, ImageData::Pixel* pixel) + { + uint16_t red = (uint16_t)(std::clamp(color.r, 0.0f, 1.0f) * 0x1F + 0.5f); + uint16_t green = (uint16_t)(std::clamp(color.g, 0.0f, 1.0f) * 0x1F + 0.5f); + uint16_t blue = (uint16_t)(std::clamp(color.b, 0.0f, 1.0f) * 0x1F + 0.5f); + uint16_t alpha = (uint16_t)(std::clamp(color.a, 0.0f, 1.0f) * 0x1 + 0.5f); + + pixel->packed16 = (red << 11) | (green << 6) | (blue << 1) | (alpha << 0); + } + + static void setPixelRGB565(const Color& color, ImageData::Pixel* pixel) + { + uint16_t red = (uint16_t)(std::clamp(color.r, 0.0f, 1.0f) * 0x1F + 0.5f); + uint16_t green = (uint16_t)(std::clamp(color.g, 0.0f, 1.0f) * 0x3F + 0.5f); + uint16_t blue = (uint16_t)(std::clamp(color.b, 0.0f, 1.0f) * 0x1F + 0.5f); + + pixel->packed16 = (red << 11) | (green << 5) | (blue << 0); + } + + static void setPixelRGB10A2(const Color& color, ImageData::Pixel* pixel) + { + uint32_t red = (uint32_t)(std::clamp(color.r, 0.0f, 1.0f) * 0x3FF + 0.5f); + uint32_t green = (uint32_t)(std::clamp(color.g, 0.0f, 1.0f) * 0x3FF + 0.5f); + uint32_t blue = (uint32_t)(std::clamp(color.b, 0.0f, 1.0f) * 0x3FF + 0.5f); + uint32_t alpha = (uint32_t)(std::clamp(color.a, 0.0f, 1.0f) * 0x3 + 0.5f); + + pixel->packed32 = (red << 0) | (green << 10) | (blue << 20) | (alpha << 30); + } + + static void setPixelRG11B10F(const Color& color, ImageData::Pixel* pixel) + { + float11_t red = float32to11(color.r); + float11_t green = float32to11(color.g); + float10_t blue = float32to10(color.b); + + pixel->packed32 = (red << 0) | (green << 11) | (blue << 22); + } + + static void getPixelR8(const ImageData::Pixel* pixel, Color& color) + { + color.r = pixel->rgba8[0] / 255.0f; + color.g = 0.0f; + color.b = 0.0f; + color.a = 1.0f; + } + + static void getPixelRG8(const ImageData::Pixel* pixel, Color& color) + { + color.r = pixel->rgba8[0] / 255.0f; + color.g = pixel->rgba8[1] / 255.0f; + color.b = 0.0f; + color.a = 1.0f; + } + + static void getPixelRGBA8(const ImageData::Pixel* pixel, Color& color) + { + if (Console::is(Console::CTR)) + { + color = Color(pixel->packed32); + return; + } + + color.r = pixel->rgba8[0] / 255.0f; + color.g = pixel->rgba8[1] / 255.0f; + color.b = pixel->rgba8[2] / 255.0f; + color.a = pixel->rgba8[3] / 255.0f; + } + + static void getPixelR16(const ImageData::Pixel* pixel, Color& color) + { + color.r = pixel->rgba16[0] / 65535.0f; + color.g = 0.0f; + color.b = 0.0f; + color.a = 1.0f; + } + + static void getPixelRG16(const ImageData::Pixel* pixel, Color& color) + { + color.r = pixel->rgba16[0] / 65535.0f; + color.g = pixel->rgba16[1] / 65535.0f; + color.b = 0.0f; + color.a = 1.0f; + } + + static void getPixelRGBA16(const ImageData::Pixel* pixel, Color& color) + { + color.r = pixel->rgba16[0] / 65535.0f; + color.g = pixel->rgba16[1] / 65535.0f; + color.b = pixel->rgba16[2] / 65535.0f; + color.a = pixel->rgba16[3] / 65535.0f; + } + + static void getPixelR16F(const ImageData::Pixel* pixel, Color& color) + { + color.r = float16to32(pixel->rgba16f[0]); + color.g = 0.0f; + color.b = 0.0f; + color.a = 1.0f; + } + + static void getPixelRG16F(const ImageData::Pixel* pixel, Color& color) + { + color.r = float16to32(pixel->rgba16f[0]); + color.g = float16to32(pixel->rgba16f[1]); + color.b = 0.0f; + color.a = 1.0f; + } + + static void getPixelRGBA16F(const ImageData::Pixel* pixel, Color& color) + { + color.r = float16to32(pixel->rgba16f[0]); + color.g = float16to32(pixel->rgba16f[1]); + color.b = float16to32(pixel->rgba16f[2]); + color.a = float16to32(pixel->rgba16f[3]); + } + + static void getPixelR32F(const ImageData::Pixel* pixel, Color& color) + { + color.r = pixel->rgba32f[0]; + color.g = 0.0f; + color.b = 0.0f; + color.a = 1.0f; + } + + static void getPixelRG32F(const ImageData::Pixel* pixel, Color& color) + { + color.r = pixel->rgba32f[0]; + color.g = pixel->rgba32f[1]; + color.b = 0.0f; + color.a = 1.0f; + } + + static void getPixelRGBA32F(const ImageData::Pixel* pixel, Color& color) + { + color.r = pixel->rgba32f[0]; + color.g = pixel->rgba32f[1]; + color.b = pixel->rgba32f[2]; + color.a = pixel->rgba32f[3]; + } + + static void getPixelRGBA4(const ImageData::Pixel* pixel, Color& color) + { + color.r = ((pixel->packed16 >> 12) & 0xF) / (float)0xF; + color.g = ((pixel->packed16 >> 8) & 0xF) / (float)0xF; + color.b = ((pixel->packed16 >> 4) & 0xF) / (float)0xF; + color.a = ((pixel->packed16 >> 0) & 0xF) / (float)0xF; + } + + static void getPixelRGB5A1(const ImageData::Pixel* pixel, Color& color) + { + color.r = ((pixel->packed16 >> 11) & 0x1F) / (float)0x1F; + color.g = ((pixel->packed16 >> 6) & 0x1F) / (float)0x1F; + color.b = ((pixel->packed16 >> 1) & 0x1F) / (float)0x1F; + color.a = ((pixel->packed16 >> 0) & 0x1) / (float)0x1; + } + + static void getPixelRGB565(const ImageData::Pixel* pixel, Color& color) + { + color.r = ((pixel->packed16 >> 11) & 0x1F) / (float)0x1F; + color.g = ((pixel->packed16 >> 5) & 0x3F) / (float)0x3F; + color.b = ((pixel->packed16 >> 0) & 0x1F) / (float)0x1F; + color.a = 1.0f; + } + + static void getPixelRGB10A2(const ImageData::Pixel* pixel, Color& color) + { + color.r = ((pixel->packed32 >> 0) & 0x3FF) / (float)0x3FF; + color.g = ((pixel->packed32 >> 10) & 0x3FF) / (float)0x3FF; + color.b = ((pixel->packed32 >> 20) & 0x3FF) / (float)0x3FF; + color.a = ((pixel->packed32 >> 30) & 0x3) / (float)0x3; + } + + static void getPixelRG11B10F(const ImageData::Pixel* pixel, Color& color) + { + color.r = float11to32((float11_t)(pixel->packed32 >> 0) & 0x7FF); + color.g = float11to32((float11_t)(pixel->packed32 >> 11) & 0x7FF); + color.b = float10to32((float10_t)(pixel->packed32 >> 22) & 0x7FF); + color.a = 1.0f; + } + + void ImageData::setPixel(int x, int y, const Color& color) + { + if (!this->inside(x, y)) + throw love::Exception(E_OUT_OF_RANGE_PIXEL, x, y); + + Pixel* pixel = nullptr; + size_t pixelSize = this->getPixelSize(); + + if (Console::is(Console::CTR)) + { + const auto _width = NextPo2(this->getWidth()); + pixel = (Pixel*)Color::fromTile(this->data, _width, x, y); + } + else + pixel = (Pixel*)(this->data + ((y * this->width + x) * pixelSize)); + + if (this->pixelSetFunction == nullptr) + { + throw love::Exception(E_PIXELFORMAT_NOT_SUPPORTED, love::getConstant(this->format)); + } + + this->pixelSetFunction(color, pixel); + } + + void ImageData::getPixel(int x, int y, Color& color) const + { + if (!this->inside(x, y)) + throw love::Exception(E_OUT_OF_RANGE_PIXEL, x, y); + + const Pixel* pixel = nullptr; + size_t pixelSize = this->getPixelSize(); + + if (Console::is(Console::CTR)) + { + const auto _width = NextPo2(this->getWidth()); + pixel = (const Pixel*)Color::fromTile(this->data, _width, x, y); + } + else + pixel = (const Pixel*)(this->data + ((y * this->width + x) * pixelSize)); + + this->pixelGetFunction(pixel, color); + } + + Color ImageData::getPixel(int x, int y) const + { + Color color {}; + this->getPixel(x, y, color); + + return color; + } + + union Row + { + uint8_t* u8; + uint16_t* u16; + float16_t* f16; + float* f32; + }; + + static void pasteRGBA8toRGBA16(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.u16[i] = (uint16_t)src.u8[i] << 8u; + } + + static void pasteRGBA8toRGBA16F(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.f16[i] = float32to16(src.u8[i] / 255.0f); + } + + static void pasteRGBA8toRGBA32F(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.f32[i] = src.u8[i] / 255.0f; + } + + static void pasteRGBA16toRGBA8(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.u8[i] = src.u16[i] >> 8u; + } + + static void pasteRGBA16toRGBA16F(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.f16[i] = float32to16(src.u16[i] / 65535.0f); + } + + static void pasteRGBA16toRGBA32F(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.f32[i] = src.u16[i] / 65535.0f; + } + + static float clamp01(const float x) + { + return std::clamp(x, 0.0f, 1.0f); + } + + static void pasteRGBA16FtoRGBA8(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.u8[i] = (uint8_t)(clamp01(float16to32(src.f16[i])) * 255.0f + 0.5f); + } + + static void pasteRGBA16FtoRGBA16(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.u16[i] = (uint16_t)(clamp01(float16to32(src.f16[i])) * 65535.0f + 0.5f); + } + + static void pasteRGBA16FtoRGBA32F(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.f32[i] = float16to32(src.f16[i]); + } + + static void pasteRGBA32FtoRGBA8(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.u8[i] = (uint8_t)(clamp01(src.f32[i]) * 255.0f + 0.5f); + } + + static void pasteRGBA32FtoRGBA16(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.u16[i] = (uint16_t)(clamp01(src.f32[i]) * 65535.0f + 0.5f); + } + + static void pasteRGBA32FtoRGBA16F(Row src, Row dst, int w) + { + for (int i = 0; i < w * 4; i++) + dst.f16[i] = float32to16(src.f32[i]); + } + + void ImageData::paste(ImageData* src, int dx, int dy, int sx, int sy, int sw, int sh) + { + const auto destFormat = this->getFormat(); + const auto srcFormat = src->getFormat(); + + int srcWidth = src->getWidth(); + int srcHeight = src->getHeight(); + + int destWidth = this->getWidth(); + int destHeight = this->getHeight(); + + size_t srcPixelSize = src->getPixelSize(); + size_t destPixelSize = this->getPixelSize(); + + if (sx >= srcWidth || sx + sw < 0 || sy >= srcHeight || sy + sh < 0 || dx >= destWidth || + dx + sw < 0 || dy >= destHeight || dy + sh < 0) + { + return; + } + + if (dx < 0) + { + srcWidth += dx; + sx -= dx; + dx = 0; + } + + if (dy < 0) + { + srcHeight += dy; + sy -= dy; + dy = 0; + } + + if (sx < 0) + { + srcWidth += sx; + dx -= sx; + sx = 0; + } + + if (sy < 0) + { + srcHeight += sy; + dy -= sy; + sy = 0; + } + + if (dx + sw > destWidth) + sw = destWidth - dx; + + if (dy + sh > destHeight) + sh = destHeight - dy; + + if (sx + sw > srcWidth) + sw = srcWidth - sx; + + if (sy + sh > srcHeight) + sh = srcHeight - sy; + + auto* source = (uint8_t*)src->getData(); + auto* dest = (uint8_t*)this->getData(); + + auto getFunction = src->getPixelGetFunction(); + auto setFunction = this->pixelSetFunction; + + if (srcFormat == destFormat && + (sw == destWidth && destWidth == srcWidth && sh == destHeight && destHeight == srcHeight)) + { + memcpy(dest, source, srcPixelSize * sw * sh); + } + else if (sw > 0) + { + // Otherwise, copy each row individually. + for (int i = 0; i < sh; i++) + { + Row rowsrc = { source + (sx + (i + sy) * srcWidth) * srcPixelSize }; + Row rowdst = { dest + (dx + (i + dy) * destWidth) * destPixelSize }; + + if (srcFormat == destFormat) + std::memcpy(rowdst.u8, rowsrc.u8, srcPixelSize * sw); + else if (srcFormat == PIXELFORMAT_RGBA8_UNORM && destFormat == PIXELFORMAT_RGBA16_UNORM) + pasteRGBA8toRGBA16(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA8_UNORM && destFormat == PIXELFORMAT_RGBA16_FLOAT) + pasteRGBA8toRGBA16F(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA8_UNORM && destFormat == PIXELFORMAT_RGBA32_FLOAT) + pasteRGBA8toRGBA32F(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA16_UNORM && destFormat == PIXELFORMAT_RGBA8_UNORM) + pasteRGBA16toRGBA8(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA16_UNORM && destFormat == PIXELFORMAT_RGBA16_FLOAT) + pasteRGBA16toRGBA16F(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA16_UNORM && destFormat == PIXELFORMAT_RGBA32_FLOAT) + pasteRGBA16toRGBA32F(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA16_FLOAT && destFormat == PIXELFORMAT_RGBA8_UNORM) + pasteRGBA16FtoRGBA8(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA16_FLOAT && destFormat == PIXELFORMAT_RGBA16_UNORM) + pasteRGBA16FtoRGBA16(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA16_FLOAT && destFormat == PIXELFORMAT_RGBA32_FLOAT) + pasteRGBA16FtoRGBA32F(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA32_FLOAT && destFormat == PIXELFORMAT_RGBA8_UNORM) + pasteRGBA32FtoRGBA8(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA32_FLOAT && destFormat == PIXELFORMAT_RGBA16_UNORM) + pasteRGBA32FtoRGBA16(rowsrc, rowdst, sw); + else if (srcFormat == PIXELFORMAT_RGBA32_FLOAT && destFormat == PIXELFORMAT_RGBA16_FLOAT) + pasteRGBA32FtoRGBA16F(rowsrc, rowdst, sw); + else if (getFunction != nullptr && setFunction != nullptr) + { + // Slow path: convert src -> Colorf -> dst. + Color c; + for (int x = 0; x < sw; x++) + { + auto srcp = (const Pixel*)(rowsrc.u8 + x * srcPixelSize); + auto dstp = (Pixel*)(rowdst.u8 + x * destPixelSize); + + getFunction(srcp, c); + setFunction(c, dstp); + } + } + else if (getFunction == nullptr) + { + const char* name = love::getConstant(srcFormat); + throw love::Exception(E_CANNOT_CONVERT_FROM_FORMAT, name); + } + else + { + const char* name = love::getConstant(srcFormat); + throw love::Exception(E_CANNOT_CONVERT_TO_FORMAT, name); + } + } + } + } + + size_t ImageData::getPixelSize() const + { + return love::getPixelFormatBlockSize(this->format); + } + + bool ImageData::validPixelFormat(PixelFormat format) + { + return isPixelFormatColor(format) && !isPixelFormatCompressed(format); + } + + ImageData::PixelSetFunction ImageData::getPixelSetFunction(PixelFormat format) + { + switch (format) + { + case PIXELFORMAT_R8_UNORM: + return setPixelR8; + case PIXELFORMAT_RG8_UNORM: + return setPixelRG8; + case PIXELFORMAT_RGBA8_UNORM: + return setPixelRGBA8; + case PIXELFORMAT_R16_UNORM: + return setPixelR16; + case PIXELFORMAT_RG16_UNORM: + return setPixelRG16; + case PIXELFORMAT_RGBA16_UNORM: + return setPixelRGBA16; + case PIXELFORMAT_R16_FLOAT: + return setPixelR16F; + case PIXELFORMAT_RG16_FLOAT: + return setPixelRG16F; + case PIXELFORMAT_RGBA16_FLOAT: + return setPixelRGBA16F; + case PIXELFORMAT_R32_FLOAT: + return setPixelR32F; + case PIXELFORMAT_RG32_FLOAT: + return setPixelRG32F; + case PIXELFORMAT_RGBA32_FLOAT: + return setPixelRGBA32F; + case PIXELFORMAT_RGBA4_UNORM: + return setPixelRGBA4; + case PIXELFORMAT_RGB5A1_UNORM: + return setPixelRGB5A1; + case PIXELFORMAT_RGB565_UNORM: + return setPixelRGB565; + case PIXELFORMAT_RGB10A2_UNORM: + return setPixelRGB10A2; + case PIXELFORMAT_RG11B10_FLOAT: + return setPixelRG11B10F; + default: + return nullptr; + } + } + + ImageData::PixelGetFunction ImageData::getPixelGetFunction(PixelFormat format) + { + switch (format) + { + case PIXELFORMAT_R8_UNORM: + return getPixelR8; + case PIXELFORMAT_RG8_UNORM: + return getPixelRG8; + case PIXELFORMAT_RGBA8_UNORM: + return getPixelRGBA8; + case PIXELFORMAT_R16_UNORM: + return getPixelR16; + case PIXELFORMAT_RG16_UNORM: + return getPixelRG16; + case PIXELFORMAT_RGBA16_UNORM: + return getPixelRGBA16; + case PIXELFORMAT_R16_FLOAT: + return getPixelR16F; + case PIXELFORMAT_RG16_FLOAT: + return getPixelRG16F; + case PIXELFORMAT_RGBA16_FLOAT: + return getPixelRGBA16F; + case PIXELFORMAT_R32_FLOAT: + return getPixelR32F; + case PIXELFORMAT_RG32_FLOAT: + return getPixelRG32F; + case PIXELFORMAT_RGBA32_FLOAT: + return getPixelRGBA32F; + case PIXELFORMAT_RGBA4_UNORM: + return getPixelRGBA4; + case PIXELFORMAT_RGB5A1_UNORM: + return getPixelRGB5A1; + case PIXELFORMAT_RGB565_UNORM: + return getPixelRGB565; + case PIXELFORMAT_RGB10A2_UNORM: + return getPixelRGB10A2; + case PIXELFORMAT_RG11B10_FLOAT: + return getPixelRG11B10F; + default: + return nullptr; + } + } +} // namespace love diff --git a/source/modules/image/ImageDataBase.cpp b/source/modules/image/ImageDataBase.cpp new file mode 100644 index 000000000..ef5d3e68a --- /dev/null +++ b/source/modules/image/ImageDataBase.cpp @@ -0,0 +1,11 @@ +#include "modules/image/ImageDataBase.hpp" + +namespace love +{ + ImageDataBase::ImageDataBase(PixelFormat format, int width, int height) : + format(format), + width(width), + height(height), + linear(false) + {} +} // namespace love diff --git a/source/modules/image/magpie/ASTCHandler.cpp b/source/modules/image/magpie/ASTCHandler.cpp new file mode 100644 index 000000000..e193349e5 --- /dev/null +++ b/source/modules/image/magpie/ASTCHandler.cpp @@ -0,0 +1,113 @@ +#include "common/Exception.hpp" + +#include "modules/image/magpie/ASTCHandler.hpp" + +#include + +#define E_COULD_NOT_PARSE "Could not parse .astc file: unsupported ASTC format {:d}x{:d}x{:d}." + +namespace love +{ + static constexpr uint32_t ASTC_MAGIC = 0x5CA1AB13; + +#pragma pack(push, 1) + struct ASTCHeader + { + uint8_t identifier[4]; + uint8_t blockdimX; + uint8_t blockdimY; + uint8_t blockdimZ; + uint8_t sizeX[3]; + uint8_t sizeY[3]; + uint8_t sizeZ[3]; + }; +#pragma pack(pop) + + static PixelFormat convertFormat(uint32_t blockX, uint32_t blockY, uint32_t blockZ) + { + if (blockZ > 1) + return PIXELFORMAT_UNKNOWN; + + if (blockX == 4 && blockY == 4) + return PIXELFORMAT_ASTC_4x4_UNORM; + else if (blockX == 5 && blockY == 4) + return PIXELFORMAT_ASTC_5x4_UNORM; + else if (blockX == 5 && blockY == 5) + return PIXELFORMAT_ASTC_5x5_UNORM; + else if (blockX == 6 && blockY == 5) + return PIXELFORMAT_ASTC_6x5_UNORM; + else if (blockX == 6 && blockY == 6) + return PIXELFORMAT_ASTC_6x6_UNORM; + else if (blockX == 8 && blockY == 5) + return PIXELFORMAT_ASTC_8x5_UNORM; + else if (blockX == 8 && blockY == 6) + return PIXELFORMAT_ASTC_8x6_UNORM; + else if (blockX == 8 && blockY == 8) + return PIXELFORMAT_ASTC_8x8_UNORM; + else if (blockX == 10 && blockY == 5) + return PIXELFORMAT_ASTC_10x5_UNORM; + else if (blockX == 10 && blockY == 6) + return PIXELFORMAT_ASTC_10x6_UNORM; + else if (blockX == 10 && blockY == 8) + return PIXELFORMAT_ASTC_10x8_UNORM; + else if (blockX == 10 && blockY == 10) + return PIXELFORMAT_ASTC_10x10_UNORM; + else if (blockX == 12 && blockY == 10) + return PIXELFORMAT_ASTC_12x10_UNORM; + else if (blockX == 12 && blockY == 12) + return PIXELFORMAT_ASTC_12x12_UNORM; + + return PIXELFORMAT_UNKNOWN; + } + + bool ASTCHandler::canParseCompressed(Data* data) const + { + if (data->getSize() <= sizeof(ASTCHeader)) + return false; + + const auto* header = (const ASTCHeader*)data->getData(); + + const auto header0 = (uint32_t)header->identifier[0]; + const auto header1 = (uint32_t)header->identifier[1] << 8; + const auto header2 = (uint32_t)header->identifier[2] << 16; + const auto header3 = (uint32_t)header->identifier[3] << 24; + + return (header0 + header1 + header2 + header3) == ASTC_MAGIC; + } + + StrongRef ASTCHandler::parseCompressed(Data* filedata, CompressedSlices& images, + PixelFormat& format) const + { + if (!this->canParseCompressed(filedata)) + throw love::Exception("Could not decode compressed data (not an .astc file?)"); + + ASTCHeader header = *(const ASTCHeader*)filedata->getData(); + auto convertedFormat = convertFormat(header.blockdimX, header.blockdimY, header.blockdimZ); + + if (convertedFormat == PIXELFORMAT_UNKNOWN) + throw love::Exception(E_COULD_NOT_PARSE, header.blockdimX, header.blockdimY, header.blockdimZ); + + const auto sizeX = header.sizeX[0] + (header.sizeX[1] << 8) + (header.sizeX[2] << 16); + const auto sizeY = header.sizeY[0] + (header.sizeY[1] << 8) + (header.sizeY[2] << 16); + const auto sizeZ = header.sizeZ[0] + (header.sizeZ[1] << 8) + (header.sizeZ[2] << 16); + + const auto blocksX = (sizeX + header.blockdimX - 1) / header.blockdimX; + const auto blocksY = (sizeY + header.blockdimY - 1) / header.blockdimY; + const auto blocksZ = (sizeZ + header.blockdimZ - 1) / header.blockdimZ; + + const auto totalSize = (size_t)blocksX * blocksY * blocksZ * 16; + + if (totalSize + sizeof(ASTCHeader) > filedata->getSize()) + throw love::Exception("Could not parse .astc file: file is too small."); + + StrongRef memory(new ByteData(totalSize, false), Acquire::NO_RETAIN); + std::memcpy(memory->getData(), (uint8_t*)filedata->getData() + sizeof(ASTCHeader), totalSize); + + // clang-format off + images.emplace_back(new CompressedSlice(convertedFormat, sizeX, sizeY, memory, 0, totalSize), Acquire::NO_RETAIN); + // clang-format on + + format = convertedFormat; + return memory; + } +} // namespace love diff --git a/source/modules/image/magpie/JPGHandler.cpp b/source/modules/image/magpie/JPGHandler.cpp new file mode 100644 index 000000000..57bdb37b8 --- /dev/null +++ b/source/modules/image/magpie/JPGHandler.cpp @@ -0,0 +1,65 @@ +#include "common/Exception.hpp" + +#include "modules/image/magpie/JPGHandler.hpp" + +#include + +namespace love +{ + bool JPGHandler::canDecode(Data* data) const + { + auto handle = tjInitDecompress(); + + if (!handle) + return false; + + int width, height, samples = 0; + const auto size = data->getSize(); + + if (tjDecompressHeader2(handle, (uint8_t*)data->getData(), size, &width, &height, &samples) < 0) + { + tjDestroy(handle); + return false; + } + + tjDestroy(handle); + return true; + } + + FormatHandler::DecodedImage JPGHandler::decode(Data* data) const + { + auto handle = tjInitDecompress(); + + if (handle == NULL) + throw love::Exception("Failed to initialize TurboJPEG decompressor"); + + int width, height, samples = 0; + const auto size = data->getSize(); + + if (tjDecompressHeader2(handle, (uint8_t*)data->getData(), size, &width, &height, &samples) < 0) + { + tjDestroy(handle); + throw love::Exception("Failed to read JPEG image header"); + } + + DecodedImage image {}; + image.width = width; + image.height = height; + image.format = PIXELFORMAT_RGBA8_UNORM; + image.size = (width * height) * sizeof(uint32_t); + image.data = new uint8_t[image.size]; + + const auto format = TJPF_RGBA; + const auto flags = TJFLAG_ACCURATEDCT; + + if (tjDecompress2(handle, (uint8_t*)data->getData(), size, image.data, width, 0, height, format, + flags) < 0) + { + tjDestroy(handle); + throw love::Exception("Failed to decompress JPEG image: {:s}", tjGetErrorStr()); + } + + tjDestroy(handle); + return image; + } +} // namespace love diff --git a/source/modules/image/magpie/KTXHandler.cpp b/source/modules/image/magpie/KTXHandler.cpp new file mode 100644 index 000000000..17b450c2b --- /dev/null +++ b/source/modules/image/magpie/KTXHandler.cpp @@ -0,0 +1,349 @@ +#include "common/Exception.hpp" + +#include "modules/image/magpie/KTXHandler.hpp" + +#include + +namespace love +{ +#define KTX_IDENTIFIER_REF \ + { \ + 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A \ + } +#define KTX_ENDIAN_REF (0x04030201) +#define KTX_ENDIAN_REF_REV (0x01020304) +#define KTX_HEADER_SIZE (64) + + struct KTXHeader + { + uint8_t identifier[12]; + uint32_t endianness; + uint32_t glType; + uint32_t glTypeSize; + uint32_t glFormat; + uint32_t glInternalFormat; + uint32_t glBaseInternalFormat; + uint32_t pixelWidth; + uint32_t pixelHeight; + uint32_t pixelDepth; + uint32_t numberOfArrayElements; + uint32_t numberOfFaces; + uint32_t numberOfMipmapLevels; + uint32_t bytesOfKeyValueData; + }; + + static_assert(sizeof(KTXHeader) == KTX_HEADER_SIZE, "Real size of KTX header doesn't match struct size!"); + + enum KTXGLInternalFormat + { + KTX_GL_ETC1_RGB8_OES = 0x8D64, + + // ETC2 and EAC. + KTX_GL_COMPRESSED_R11_EAC = 0x9270, + KTX_GL_COMPRESSED_SIGNED_R11_EAC = 0x9271, + KTX_GL_COMPRESSED_RG11_EAC = 0x9272, + KTX_GL_COMPRESSED_SIGNED_RG11_EAC = 0x9273, + KTX_GL_COMPRESSED_RGB8_ETC2 = 0x9274, + KTX_GL_COMPRESSED_SRGB8_ETC2 = 0x9275, + KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276, + KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277, + KTX_GL_COMPRESSED_RGBA8_ETC2_EAC = 0x9278, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279, + + // PVRTC1. + KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00, + KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01, + KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02, + KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03, + + // DXT1, DXT3, and DXT5. + KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0, + KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2, + KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3, + KTX_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C, + KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E, + KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F, + + // BC4 and BC5. + KTX_GL_COMPRESSED_RED_RGTC1 = 0x8DBB, + KTX_GL_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC, + KTX_GL_COMPRESSED_RG_RGTC2 = 0x8DBD, + KTX_GL_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE, + + // BC6 and BC7. + KTX_GL_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C, + KTX_GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D, + KTX_GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E, + KTX_GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F, + + // ASTC. + KTX_GL_COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0, + KTX_GL_COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1, + KTX_GL_COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2, + KTX_GL_COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3, + KTX_GL_COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4, + KTX_GL_COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5, + KTX_GL_COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6, + KTX_GL_COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7, + KTX_GL_COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8, + KTX_GL_COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9, + KTX_GL_COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA, + KTX_GL_COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB, + KTX_GL_COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC, + KTX_GL_COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD + }; + + PixelFormat convertFormat(uint32_t glformat) + { + // hnnngg ASTC... + + switch (glformat) + { + case KTX_GL_ETC1_RGB8_OES: + return PIXELFORMAT_ETC1_UNORM; + + // EAC and ETC2. + case KTX_GL_COMPRESSED_R11_EAC: + return PIXELFORMAT_EAC_R_UNORM; + case KTX_GL_COMPRESSED_SIGNED_R11_EAC: + return PIXELFORMAT_EAC_R_SNORM; + case KTX_GL_COMPRESSED_RG11_EAC: + return PIXELFORMAT_EAC_RG_UNORM; + case KTX_GL_COMPRESSED_SIGNED_RG11_EAC: + return PIXELFORMAT_EAC_RG_SNORM; + case KTX_GL_COMPRESSED_RGB8_ETC2: + return PIXELFORMAT_ETC2_RGB_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ETC2: + return PIXELFORMAT_ETC2_RGB_sRGB; + case KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + return PIXELFORMAT_ETC2_RGBA1_UNORM; + case KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + return PIXELFORMAT_ETC2_RGBA1_sRGB; + case KTX_GL_COMPRESSED_RGBA8_ETC2_EAC: + return PIXELFORMAT_ETC2_RGBA_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + return PIXELFORMAT_ETC2_RGBA_sRGB; + + // PVRTC. + case KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: + return PIXELFORMAT_PVR1_RGB4_UNORM; + case KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: + return PIXELFORMAT_PVR1_RGB2_UNORM; + case KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG: + return PIXELFORMAT_PVR1_RGBA4_UNORM; + case KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: + return PIXELFORMAT_PVR1_RGBA2_UNORM; + + // DXT. + case KTX_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: + return PIXELFORMAT_DXT1_sRGB; + case KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + return PIXELFORMAT_DXT1_UNORM; + case KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: + return PIXELFORMAT_DXT3_sRGB; + case KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + return PIXELFORMAT_DXT3_UNORM; + case KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + return PIXELFORMAT_DXT5_sRGB; + case KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + return PIXELFORMAT_DXT5_UNORM; + + // BC4 and BC5. + case KTX_GL_COMPRESSED_RED_RGTC1: + return PIXELFORMAT_BC4_UNORM; + case KTX_GL_COMPRESSED_SIGNED_RED_RGTC1: + return PIXELFORMAT_BC4_SNORM; + case KTX_GL_COMPRESSED_RG_RGTC2: + return PIXELFORMAT_BC5_UNORM; + case KTX_GL_COMPRESSED_SIGNED_RG_RGTC2: + return PIXELFORMAT_BC5_SNORM; + + // BC6 and BC7. + case KTX_GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + return PIXELFORMAT_BC7_sRGB; + case KTX_GL_COMPRESSED_RGBA_BPTC_UNORM: + return PIXELFORMAT_BC7_UNORM; + case KTX_GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT: + return PIXELFORMAT_BC6H_FLOAT; + case KTX_GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: + return PIXELFORMAT_BC6H_UFLOAT; + + // ASTC. + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR: + return PIXELFORMAT_ASTC_4x4_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_4x4_KHR: + return PIXELFORMAT_ASTC_4x4_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR: + return PIXELFORMAT_ASTC_5x4_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_5x4_KHR: + return PIXELFORMAT_ASTC_5x4_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR: + return PIXELFORMAT_ASTC_5x5_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_5x5_KHR: + return PIXELFORMAT_ASTC_5x5_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR: + return PIXELFORMAT_ASTC_6x5_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_6x5_KHR: + return PIXELFORMAT_ASTC_6x5_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR: + return PIXELFORMAT_ASTC_6x6_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_6x6_KHR: + return PIXELFORMAT_ASTC_6x6_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR: + return PIXELFORMAT_ASTC_8x5_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_8x5_KHR: + return PIXELFORMAT_ASTC_8x5_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR: + return PIXELFORMAT_ASTC_8x6_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_8x6_KHR: + return PIXELFORMAT_ASTC_8x6_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR: + return PIXELFORMAT_ASTC_8x8_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_8x8_KHR: + return PIXELFORMAT_ASTC_8x8_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR: + return PIXELFORMAT_ASTC_10x5_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_10x5_KHR: + return PIXELFORMAT_ASTC_10x5_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR: + return PIXELFORMAT_ASTC_10x6_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_10x6_KHR: + return PIXELFORMAT_ASTC_10x6_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR: + return PIXELFORMAT_ASTC_10x8_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_10x8_KHR: + return PIXELFORMAT_ASTC_10x8_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR: + return PIXELFORMAT_ASTC_10x10_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_10x10_KHR: + return PIXELFORMAT_ASTC_10x10_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR: + return PIXELFORMAT_ASTC_12x10_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_12x10_KHR: + return PIXELFORMAT_ASTC_12x10_UNORM; + case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR: + return PIXELFORMAT_ASTC_12x12_sRGB; + case KTX_GL_COMPRESSED_RGBA_ASTC_12x12_KHR: + return PIXELFORMAT_ASTC_12x12_UNORM; + default: + return PIXELFORMAT_UNKNOWN; + } + } + + bool KTXHandler::canParseCompressed(Data* data) const + { + if (data->getSize() < KTX_HEADER_SIZE) + return false; + + auto* header = (KTXHeader*)data->getData(); + uint8_t identifier[12] = KTX_IDENTIFIER_REF; + + if (memcmp(header->identifier, identifier, sizeof(identifier)) != 0) + return false; + + if (header->endianness != KTX_ENDIAN_REF && header->endianness != KTX_ENDIAN_REF_REV) + return false; + + return true; + } + + StrongRef KTXHandler::parseCompressed(Data* filedata, CompressedSlices& images, + PixelFormat& format) const + { + if (!this->canParseCompressed(filedata)) + throw love::Exception("Could not decode compressed data (not a KTX file?)"); + + auto header = *(KTXHeader*)filedata->getData(); + + if (header.endianness == KTX_ENDIAN_REF_REV) + { + uint32_t* array = (uint32_t*)&header.glType; + + for (int i = 0; i < 12; i++) + array[i] = swap_uint32(array[i]); + } + + header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1); + auto convertedFormat = convertFormat(header.glInternalFormat); + + if (convertedFormat == PIXELFORMAT_UNKNOWN) + throw love::Exception("Unsupported image format in KTX file."); + + if (header.numberOfArrayElements > 0) + throw love::Exception("Texture arrays in KTX files are not supported."); + + if (header.pixelDepth > 1) + throw love::Exception("3D textures in KTX files are not supported."); + + if (header.numberOfFaces > 1) + throw love::Exception("Cubemap textures in KTX files are not supported."); + + size_t offset = sizeof(KTXHeader) + header.bytesOfKeyValueData; + const uint8_t* bytes = (const uint8_t*)filedata->getData(); + size_t totalSize = 0; + + for (int index = 0; index < (int)header.numberOfMipmapLevels; index++) + { + if (offset + sizeof(uint32_t) > filedata->getSize()) + throw love::Exception("Could not parse KTX file: unexpected EOF."); + + uint32_t mipmapSize = *(const uint32_t*)(bytes + offset); + + if (header.endianness == KTX_ENDIAN_REF_REV) + mipmapSize = swap_uint32(mipmapSize); + + offset += sizeof(uint32_t); + + uint32_t mipmapSizePadded = (mipmapSize + 3) & ~uint32_t(3); + + totalSize += mipmapSizePadded; + offset += mipmapSizePadded; + } + + StrongRef memory(new ByteData(totalSize, false), Acquire::NO_RETAIN); + + offset = sizeof(KTXHeader) + header.bytesOfKeyValueData; + size_t dataOffset = 0; + + for (int index = 0; index < (int)header.numberOfMipmapLevels; index++) + { + uint32_t mipmapSize = *(const uint32_t*)(bytes + offset); + + if (header.endianness == KTX_ENDIAN_REF_REV) + mipmapSize = swap_uint32(mipmapSize); + + offset += sizeof(uint32_t); + + uint32_t mipmapSizePadded = (mipmapSize + 3) & ~uint32_t(3); + + int width = std::max(header.pixelWidth >> index, 1); + int height = std::max(header.pixelHeight >> index, 1); + + std::memcpy((uint8_t*)memory->getData() + dataOffset, bytes + offset, mipmapSize); + + auto* slice = new CompressedSlice(convertedFormat, width, height, memory, dataOffset, mipmapSize); + images.push_back(slice); + slice->release(); + + offset += mipmapSizePadded; + dataOffset += mipmapSizePadded; + } + + format = convertedFormat; + return memory; + } +} // namespace love diff --git a/source/modules/image/magpie/PKMHandler.cpp b/source/modules/image/magpie/PKMHandler.cpp new file mode 100644 index 000000000..59135916e --- /dev/null +++ b/source/modules/image/magpie/PKMHandler.cpp @@ -0,0 +1,112 @@ +#include "common/Exception.hpp" +#include "common/int.hpp" + +#include "modules/image/magpie/PKMHandler.hpp" + +#include + +namespace love +{ + static constexpr uint8_t PKM_MAGIC[4] = { 'P', 'K', 'M', ' ' }; + + struct PKMHeader + { + uint8_t identifier[4]; + uint8_t version[2]; + uint16_t textureFormatBig; + uint16_t extendedWidthBig; + uint16_t extendedHeightBig; + uint16_t widthBig; + uint16_t heightBig; + }; + + enum PKMTextureFormat + { + ETC1_RGB_NO_MIPMAPS = 0, + ETC2PACKAGE_RGB_NO_MIPMAPS, + ETC2PACKAGE_RGBA_NO_MIPMAPS_OLD, + ETC2PACKAGE_RGBA_NO_MIPMAPS, + ETC2PACKAGE_RGBA1_NO_MIPMAPS, + ETC2PACKAGE_R_NO_MIPMAPS, + ETC2PACKAGE_RG_NO_MIPMAPS, + ETC2PACKAGE_R_SIGNED_NO_MIPMAPS, + ETC2PACKAGE_RG_SIGNED_NO_MIPMAPS + }; + + static PixelFormat convertFormat(uint16_t texformat) + { + switch (texformat) + { + case ETC1_RGB_NO_MIPMAPS: + return PIXELFORMAT_ETC1_UNORM; + case ETC2PACKAGE_RGB_NO_MIPMAPS: + return PIXELFORMAT_ETC2_RGB_UNORM; + case ETC2PACKAGE_RGBA_NO_MIPMAPS_OLD: + case ETC2PACKAGE_RGBA_NO_MIPMAPS: + return PIXELFORMAT_ETC2_RGBA_UNORM; + case ETC2PACKAGE_RGBA1_NO_MIPMAPS: + return PIXELFORMAT_ETC2_RGBA1_UNORM; + case ETC2PACKAGE_R_NO_MIPMAPS: + return PIXELFORMAT_EAC_R_UNORM; + case ETC2PACKAGE_RG_NO_MIPMAPS: + return PIXELFORMAT_EAC_RG_UNORM; + case ETC2PACKAGE_R_SIGNED_NO_MIPMAPS: + return PIXELFORMAT_EAC_R_SNORM; + case ETC2PACKAGE_RG_SIGNED_NO_MIPMAPS: + return PIXELFORMAT_EAC_RG_SNORM; + default: + return PIXELFORMAT_UNKNOWN; + } + } + + bool PKMHandler::canParseCompressed(Data* data) const + { + if (data->getSize() <= sizeof(PKMHeader)) + return false; + + const auto* header = (const PKMHeader*)data->getData(); + + if (std::memcmp(header->identifier, PKM_MAGIC, sizeof(PKM_MAGIC)) != 0) + return false; + + if ((header->version[0] != '2' && header->version[0] != '1') || header->version[1] != '0') + return false; + + return true; + } + + StrongRef PKMHandler::parseCompressed(Data* filedata, CompressedSlices& images, + PixelFormat& format) const + { + if (!this->canParseCompressed(filedata)) + throw love::Exception("Could not decode compressed data (not a PKM file?)"); + + auto header = *(const PKMHeader*)filedata->getData(); + + header.textureFormatBig = swap16_big(header.textureFormatBig); + header.extendedWidthBig = swap16_big(header.extendedWidthBig); + header.extendedHeightBig = swap16_big(header.extendedHeightBig); + header.widthBig = swap16_big(header.widthBig); + header.heightBig = swap16_big(header.heightBig); + + auto convertedFormat = convertFormat(header.textureFormatBig); + + if (convertedFormat == PIXELFORMAT_UNKNOWN) + throw love::Exception("Could not parse PKM file: unsupported texture format."); + + size_t totalSize = filedata->getSize() - sizeof(PKMHeader); + + StrongRef memory(new ByteData(totalSize, false), Acquire::NO_RETAIN); + std::memcpy(memory->getData(), (uint8_t*)filedata->getData() + sizeof(PKMHeader), totalSize); + + int width = header.widthBig; + int height = header.heightBig; + + // clang-format off + images.emplace_back(new CompressedSlice(convertedFormat, width, height, memory, 0, totalSize), Acquire::NO_RETAIN); + // clang-format on + + format = convertedFormat; + return memory; + } +} // namespace love diff --git a/source/modules/image/magpie/PNGHandler.cpp b/source/modules/image/magpie/PNGHandler.cpp new file mode 100644 index 000000000..50cb448a4 --- /dev/null +++ b/source/modules/image/magpie/PNGHandler.cpp @@ -0,0 +1,103 @@ +#include "common/Exception.hpp" + +#include "modules/image/magpie/PNGHandler.hpp" + +#include + +#include + +namespace love +{ + bool PNGHandler::canDecode(Data* data) const + { + png_image image {}; + image.version = PNG_IMAGE_VERSION; + + png_image_begin_read_from_memory(&image, data->getData(), data->getSize()); + + if (PNG_IMAGE_FAILED(image)) + { + png_image_free(&image); + return false; + } + + png_image_free(&image); + return true; + } + + FormatHandler::DecodedImage PNGHandler::decode(Data* data) const + { + png_image image {}; + image.version = PNG_IMAGE_VERSION; + + png_image_begin_read_from_memory(&image, data->getData(), data->getSize()); + + if (PNG_IMAGE_FAILED(image)) + { + png_image_free(&image); + throw love::Exception("Failed to read PNG image: {:s}", image.message); + } + + image.format = PNG_FORMAT_RGBA; + + DecodedImage result {}; + result.width = image.width; + result.height = image.height; + result.format = PIXELFORMAT_RGBA8_UNORM; + result.size = (image.width * image.height) * sizeof(uint32_t); + result.data = new uint8_t[result.size]; + + png_image_finish_read(&image, nullptr, result.data, PNG_IMAGE_ROW_STRIDE(image), nullptr); + + if (PNG_IMAGE_FAILED(image)) + { + png_image_free(&image); + throw love::Exception("Failed to read PNG image: {:s}", image.message); + } + + png_image_free(&image); + return result; + } + + bool PNGHandler::canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat) const + { + return encodedFormat == ENCODED_PNG && + (rawFormat == PIXELFORMAT_RGBA8_UNORM || rawFormat == PIXELFORMAT_RGBA16_UNORM); + } + + FormatHandler::EncodedImage PNGHandler::encode(const DecodedImage& decoded, + EncodedFormat encodedFormat) const + { + if (!this->canEncode(decoded.format, encodedFormat)) + throw love::Exception("PNG encoder cannot encode to non-PNG format."); + + EncodedImage result {}; + + png_image image {}; + image.width = decoded.width; + image.height = decoded.height; + image.version = PNG_IMAGE_VERSION; + image.format = PNG_FORMAT_RGBA; + + png_image_write_get_memory_size(image, result.size, 1, decoded.data, PNG_IMAGE_ROW_STRIDE(image), + NULL); + + if (PNG_IMAGE_FAILED(image)) + { + png_image_free(&image); + throw love::Exception("Failed to write PNG image: {:s}", image.message); + } + + result.data = new uint8_t[result.size]; + png_image_write_to_memory(&image, result.data, &result.size, 1, decoded.data, + PNG_IMAGE_ROW_STRIDE(image), NULL); + + if (PNG_IMAGE_FAILED(image)) + { + png_image_free(&image); + throw love::Exception("Failed to write PNG image: {:s}", image.message); + } + + return result; + } +} // namespace love diff --git a/source/modules/image/magpie/T3XHandler.cpp b/source/modules/image/magpie/T3XHandler.cpp new file mode 100644 index 000000000..fe9b9bf91 --- /dev/null +++ b/source/modules/image/magpie/T3XHandler.cpp @@ -0,0 +1,171 @@ +#include "common/Exception.hpp" +#include "driver/display/citro3d.hpp" + +#include "modules/image/magpie/T3XHandler.hpp" + +#include <3ds.h> + +#include +#include + +namespace love +{ + struct __attribute__((packed)) Tex3DSHeader + { + uint16_t numSubTextures; //< 1 + + uint8_t width_log2 : 3; //< log2(texWidth) - 3 (powTwoWidth) + uint8_t height_log2 : 3; //< log2(texHeight) - 3 (powTwoHeight) + + uint8_t type : 1; //< 0 = GPU_TEX_2D + uint8_t format; //< 0 = GPU_RGBA8 + uint8_t mipmapLevels; //< 0 + + uint16_t width; //< subtexWidth (sourceWidth) + uint16_t height; //< subtexHeight (sourceHeight) + + uint16_t left; //< left = border + uint16_t top; //< top = texHeight + uint16_t right; //< right = subtexWidth + uint16_t bottom; //< bottom = texHeight - subtexHeight + }; + + static bool isValidTex3DSTexture(Data* data, PixelFormat& format) + { + Tex3DSHeader header {}; + std::memcpy(&header, data->getData(), sizeof(Tex3DSHeader)); + + if (header.numSubTextures != 1) + return false; + else if (header.type != GPU_TEX_2D) + return false; + + if (!citro3d::getConstant((GPU_TEXCOLOR)header.format, format)) + return false; + + return true; + } + + static void validateDimensions(const size_t width, const size_t height) + { + if (width > LOVE_TEX3DS_MAX || height > LOVE_TEX3DS_MAX) + throw love::Exception("Texture dimensions exceed 3DS limits."); + else if (width < LOVE_TEX3DS_MIN || height < LOVE_TEX3DS_MIN) + throw love::Exception("Texture dimensions are too small."); + } + + bool T3XHandler::canDecode(Data* data) const + { + auto format = PIXELFORMAT_MAX_ENUM; + if (!isValidTex3DSTexture(data, format)) + return false; + + return !love::isPixelFormatCompressed(format); + } + + FormatHandler::DecodedImage T3XHandler::decode(Data* data) const + { + Tex3DSHeader header {}; + std::memcpy(&header, data->getData(), sizeof(Tex3DSHeader)); + + PixelFormat result = PIXELFORMAT_MAX_ENUM; + const auto format = (GPU_TEXCOLOR)header.format; + + citro3d::getConstant(format, result); + + const size_t width = 1 << (header.width_log2 + 3); + const size_t height = 1 << (header.height_log2 + 3); + + validateDimensions(width, height); + + DecodedImage image {}; + image.width = header.width; + image.height = header.height; + image.format = result; + + const auto size = data->getSize() - sizeof(Tex3DSHeader); + const auto compressed = (uint8_t*)data->getData() + sizeof(Tex3DSHeader); + + size_t uncompressedSize = 0; + if (decompressHeader(nullptr, &uncompressedSize, nullptr, compressed, size) < 0) + throw love::Exception("Failed to decompress texture header."); + + image.size = (header.width * header.height * love::getPixelFormatBlockSize(result)); + + try + { + image.data = new uint8_t[uncompressedSize]; + } + catch (std::bad_alloc&) + { + throw love::Exception(E_OUT_OF_MEMORY); + } + + if (!decompress(image.data, uncompressedSize, nullptr, compressed, size)) + { + delete[] image.data; + throw love::Exception("Failed to decompress texture."); + } + + return image; + } + + bool T3XHandler::canParseCompressed(Data* data) const + { + auto format = PIXELFORMAT_MAX_ENUM; + if (!isValidTex3DSTexture(data, format)) + return false; + + return love::isPixelFormatCompressed(format); + } + + StrongRef T3XHandler::parseCompressed(Data* filedata, CompressedSlices& images, + PixelFormat& format) const + { + Tex3DSHeader header {}; + std::memcpy(&header, filedata->getData(), sizeof(Tex3DSHeader)); + + auto convertedFormat = PIXELFORMAT_MAX_ENUM; + if (!citro3d::getConstant((GPU_TEXCOLOR)header.format, convertedFormat)) + throw love::Exception("Invalid texture format."); + + const size_t width = 1 << (header.width_log2 + 3); + const size_t height = 1 << (header.height_log2 + 3); + + validateDimensions(width, height); + + const auto size = filedata->getSize() - sizeof(Tex3DSHeader); + const auto compressed = (uint8_t*)filedata->getData() + sizeof(Tex3DSHeader); + + size_t uncompressedSize = 0; + if (decompressHeader(nullptr, &uncompressedSize, nullptr, compressed, size) < 0) + throw love::Exception("Failed to decompress texture header."); + + uint8_t* data = nullptr; + + try + { + data = new uint8_t[uncompressedSize]; + } + catch (std::bad_alloc&) + { + throw love::Exception(E_OUT_OF_MEMORY); + } + + if (!decompress(data, uncompressedSize, nullptr, compressed, size)) + { + delete[] data; + throw love::Exception("Failed to decompress texture."); + } + + StrongRef memory(new ByteData(uncompressedSize, false), Acquire::NO_RETAIN); + std::memcpy(memory->getData(), data, uncompressedSize); + + // clang-format off + images.emplace_back(new CompressedSlice(convertedFormat, header.width, header.height, memory, 0, uncompressedSize), Acquire::NO_RETAIN); + // clang-format on + + format = convertedFormat; + return memory; + } +} // namespace love diff --git a/source/modules/image/magpie/ddsHandler.cpp b/source/modules/image/magpie/ddsHandler.cpp new file mode 100644 index 000000000..cd17fc9d1 --- /dev/null +++ b/source/modules/image/magpie/ddsHandler.cpp @@ -0,0 +1,236 @@ +#include "common/Exception.hpp" + +#include "modules/image/ImageData.hpp" +#include "modules/image/magpie/ddsHandler.hpp" + +#include +using namespace dds::dxinfo; + +#include + +#define E_COULD_NOT_PARSE "Could not parse compressed data: {:s}" + +namespace love +{ + static PixelFormat convertFormat(dds::dxinfo::DXGIFormat format) + { + switch (format) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + return PIXELFORMAT_RGBA32_FLOAT; + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + return PIXELFORMAT_RGBA16_FLOAT; + case DXGI_FORMAT_R16G16B16A16_UNORM: + return PIXELFORMAT_RGBA16_UNORM; + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + return PIXELFORMAT_RG32_FLOAT; + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + return PIXELFORMAT_RGB10A2_UNORM; + case DXGI_FORMAT_R11G11B10_FLOAT: + return PIXELFORMAT_RG11B10_FLOAT; + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + return PIXELFORMAT_RGBA8_UNORM; + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return PIXELFORMAT_RGBA8_sRGB; + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + return PIXELFORMAT_RG16_FLOAT; + case DXGI_FORMAT_R16G16_UNORM: + return PIXELFORMAT_RG16_UNORM; + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_R32_FLOAT: + return PIXELFORMAT_R32_FLOAT; + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + return PIXELFORMAT_RG8_UNORM; + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + return PIXELFORMAT_R16_FLOAT; + case DXGI_FORMAT_R16_UNORM: + return PIXELFORMAT_R16_UNORM; + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_A8_UNORM: + return PIXELFORMAT_R8_UNORM; + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + return PIXELFORMAT_DXT1_UNORM; + case DXGI_FORMAT_BC1_UNORM_SRGB: + return PIXELFORMAT_DXT1_sRGB; + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + return PIXELFORMAT_DXT3_UNORM; + case DXGI_FORMAT_BC2_UNORM_SRGB: + return PIXELFORMAT_DXT3_sRGB; + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + return PIXELFORMAT_DXT5_UNORM; + case DXGI_FORMAT_BC3_UNORM_SRGB: + return PIXELFORMAT_DXT5_sRGB; + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + return PIXELFORMAT_BC4_UNORM; + case DXGI_FORMAT_BC4_SNORM: + return PIXELFORMAT_BC4_SNORM; + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + return PIXELFORMAT_BC5_UNORM; + case DXGI_FORMAT_BC5_SNORM: + return PIXELFORMAT_BC5_SNORM; + case DXGI_FORMAT_B5G6R5_UNORM: + return PIXELFORMAT_RGB565_UNORM; + case DXGI_FORMAT_B5G5R5A1_UNORM: + return PIXELFORMAT_RGB5A1_UNORM; + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8A8_TYPELESS: + return PIXELFORMAT_BGRA8_UNORM; + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + return PIXELFORMAT_BGRA8_sRGB; + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + return PIXELFORMAT_BC6H_UFLOAT; + case DXGI_FORMAT_BC6H_SF16: + return PIXELFORMAT_BC6H_FLOAT; + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + return PIXELFORMAT_BC7_UNORM; + case DXGI_FORMAT_BC7_UNORM_SRGB: + return PIXELFORMAT_BC7_sRGB; + default: + return PIXELFORMAT_UNKNOWN; + } + } + + bool DDSHandler::canDecode(Data* data) const + { + DXGIFormat dxFormat = dds::getDDSPixelFormat(data->getData(), data->getSize()); + PixelFormat format = convertFormat(dxFormat); + + if (format == PIXELFORMAT_BGRA8_UNORM) + format = PIXELFORMAT_RGBA8_UNORM; + else if (format == PIXELFORMAT_BGRA8_sRGB) + format = PIXELFORMAT_RGBA8_sRGB; + + return ImageData::validPixelFormat(format); + } + + FormatHandler::DecodedImage DDSHandler::decode(Data* data) const + { + dds::Parser parser(data->getData(), data->getSize()); + + DecodedImage image {}; + image.format = convertFormat(parser.getFormat()); + + bool bgra = false; + + if (image.format == PIXELFORMAT_BGRA8_UNORM) + { + image.format = PIXELFORMAT_RGBA8_UNORM; + bgra = true; + } + else if (image.format == PIXELFORMAT_BGRA8_sRGB) + { + image.format = PIXELFORMAT_RGBA8_sRGB; + bgra = true; + } + + if (!ImageData::validPixelFormat(image.format)) + throw love::Exception("Could not parse DDS pixel data: Unsupported format."); + + if (parser.getMipmapCount() == 0) + throw love::Exception("Could not parse DDS pixel data: No readable texture data."); + + const auto* ddsImage = parser.getImageData(0); + + try + { + image.data = new uint8_t[ddsImage->dataSize]; + } + catch (std::bad_alloc&) + { + throw love::Exception(E_OUT_OF_MEMORY); + } + + std::memcpy(image.data, ddsImage->data, ddsImage->dataSize); + + image.size = ddsImage->dataSize; + image.width = ddsImage->width; + image.height = ddsImage->height; + + if (bgra) + { + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + size_t offset = (y * image.width + x) * 4; + + uint8_t b = image.data[offset + 0]; + uint8_t r = image.data[offset + 2]; + + image.data[offset + 0] = r; + image.data[offset + 2] = b; + } + } + } + + return image; + } + + bool DDSHandler::canParseCompressed(Data* data) const + { + return dds::isCompressedDDS(data->getData(), data->getSize()); + } + + StrongRef DDSHandler::parseCompressed(Data* data, CompressedSlices& images, + PixelFormat& format) const + { + if (!dds::isCompressedDDS(data->getData(), data->getSize())) + throw love::Exception("Could not decode compressed data (not a DDS file?)"); + + auto textureFormat = PIXELFORMAT_UNKNOWN; + size_t dataSize = 0; + + images.clear(); + + dds::Parser parser(data->getData(), data->getSize()); + textureFormat = convertFormat(parser.getFormat()); + + if (textureFormat == PIXELFORMAT_UNKNOWN) + throw love::Exception(E_COULD_NOT_PARSE, "unsupported format."); + + if (parser.getMipmapCount() == 0) + throw love::Exception(E_COULD_NOT_PARSE, "no readable texture data."); + + for (size_t i = 0; i < parser.getMipmapCount(); i++) + { + const auto* image = parser.getImageData(i); + dataSize += image->dataSize; + } + + StrongRef memory(new ByteData(dataSize, false), Acquire::NO_RETAIN); + size_t offset = 0; + + for (size_t i = 0; i < parser.getMipmapCount(); i++) + { + const auto* image = parser.getImageData(i); + + std::memcpy((uint8_t*)memory->getData() + offset, image->data, image->dataSize); + + // clang-format off + auto* slice = new CompressedSlice(textureFormat, image->width, image->height, memory, offset, image->dataSize); + // clang-format on + + images.emplace_back(slice, Acquire::NO_RETAIN); + offset += image->dataSize; + } + + format = textureFormat; + return memory; + } +} // namespace love diff --git a/source/modules/image/wrap_CompressedImageData.cpp b/source/modules/image/wrap_CompressedImageData.cpp new file mode 100644 index 000000000..6564b195b --- /dev/null +++ b/source/modules/image/wrap_CompressedImageData.cpp @@ -0,0 +1,134 @@ +#include "modules/image/wrap_CompressedImageData.hpp" +#include "modules/data/wrap_Data.hpp" + +using namespace love; + +int Wrap_CompressedImageData::clone(lua_State* L) +{ + auto* self = luax_checkcompressedimagedata(L, 1); + CompressedImageData* clone = nullptr; + + luax_catchexcept(L, [&]() { clone = self->clone(); }); + + luax_pushtype(L, clone); + clone->release(); + + return 1; +} + +int Wrap_CompressedImageData::getWidth(lua_State* L) +{ + auto* self = luax_checkcompressedimagedata(L, 1); + + int mipmap = (int)luaL_optinteger(L, 2, 1); + int width = 0; + + luax_catchexcept(L, [&]() { width = self->getWidth(mipmap - 1); }); + + lua_pushinteger(L, width); + + return 1; +} + +int Wrap_CompressedImageData::getHeight(lua_State* L) +{ + auto* self = luax_checkcompressedimagedata(L, 1); + + int mipmap = (int)luaL_optinteger(L, 2, 1); + int height = 0; + + luax_catchexcept(L, [&]() { height = self->getHeight(mipmap - 1); }); + + lua_pushinteger(L, height); + + return 1; +} + +int Wrap_CompressedImageData::getDimensions(lua_State* L) +{ + auto* self = luax_checkcompressedimagedata(L, 1); + + int mipmap = (int)luaL_optinteger(L, 2, 1); + int width = 0; + int height = 0; + + luax_catchexcept(L, [&]() { + width = self->getWidth(mipmap - 1); + height = self->getHeight(mipmap - 1); + }); + + lua_pushinteger(L, width); + lua_pushinteger(L, height); + + return 2; +} + +int Wrap_CompressedImageData::getMipmapCount(lua_State* L) +{ + auto* self = luax_checkcompressedimagedata(L, 1); + + lua_pushinteger(L, self->getMipmapCount()); + + return 1; +} + +int Wrap_CompressedImageData::getFormat(lua_State* L) +{ + auto* self = luax_checkcompressedimagedata(L, 1); + + auto format = self->getFormat(); + std::string_view name; + + if (getConstant(format, name)) + luax_pushstring(L, name); + else + lua_pushstring(L, "unknown"); + + return 1; +} + +int Wrap_CompressedImageData::setLinear(lua_State* L) +{ + auto* self = luax_checkcompressedimagedata(L, 1); + bool linear = luax_toboolean(L, 2); + + self->setLinear(linear); + + return 0; +} + +int Wrap_CompressedImageData::isLinear(lua_State* L) +{ + auto* self = luax_checkcompressedimagedata(L, 1); + + lua_pushboolean(L, self->isLinear()); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "clone", Wrap_CompressedImageData::clone }, + { "getWidth", Wrap_CompressedImageData::getWidth }, + { "getHeight", Wrap_CompressedImageData::getHeight }, + { "getDimensions", Wrap_CompressedImageData::getDimensions }, + { "getMipmapCount", Wrap_CompressedImageData::getMipmapCount }, + { "getFormat", Wrap_CompressedImageData::getFormat }, + { "setLinear", Wrap_CompressedImageData::setLinear }, + { "isLinear", Wrap_CompressedImageData::isLinear } +}; +// clang-format on + +namespace love +{ + CompressedImageData* luax_checkcompressedimagedata(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_compressedimagedata(lua_State* L) + { + return luax_register_type(L, &CompressedImageData::type, Wrap_Data::functions, functions); + } +} // namespace love diff --git a/source/modules/image/wrap_Image.cpp b/source/modules/image/wrap_Image.cpp new file mode 100644 index 000000000..6c7be2662 --- /dev/null +++ b/source/modules/image/wrap_Image.cpp @@ -0,0 +1,148 @@ +#include "common/Data.hpp" + +#include "modules/image/wrap_CompressedImageData.hpp" +#include "modules/image/wrap_Image.hpp" +#include "modules/image/wrap_ImageData.hpp" + +#include "modules/data/wrap_Data.hpp" +#include "modules/filesystem/wrap_Filesystem.hpp" + +using namespace love; + +#define instance() (Module::getInstance(Module::M_IMAGE)) + +int Wrap_Image::newImageData(lua_State* L) +{ + if (lua_isnumber(L, 1)) + { + int width = luaL_checkinteger(L, 1); + int height = luaL_checkinteger(L, 2); + + if (width <= 0 || height <= 0) + return luaL_error(L, "Invalid image size."); + + auto format = PIXELFORMAT_RGBA8_UNORM; + + if (!lua_isnoneornil(L, 3)) + { + const char* formatName = luaL_checkstring(L, 3); + if (!love::getConstant(formatName, format)) + return luax_enumerror(L, "pixel format", formatName); + } + + size_t numBytes = 0; + const char* bytes = nullptr; + + if (luax_istype(L, 4, Data::type)) + { + auto* data = luax_checkdata(L, 4); + bytes = (const char*)data->getData(); + numBytes = data->getSize(); + } + else if (!lua_isnoneornil(L, 4)) + bytes = luaL_checklstring(L, 4, &numBytes); + + ImageData* imageData = nullptr; + luax_catchexcept(L, [&]() { imageData = instance()->newImageData(width, height, format); }); + + if (bytes) + { + if (numBytes != imageData->getSize()) + { + imageData->release(); + return luaL_error(L, "Data size does not match ImageData size."); + } + + std::memcpy(imageData->getData(), bytes, imageData->getSize()); + } + + luax_pushtype(L, imageData); + imageData->release(); + + return 1; + } + else if (luax_cangetdata(L, 1)) + { + auto* data = luax_getdata(L, 1); + ImageData* imageData = nullptr; + + // clang-format off + luax_catchexcept(L, + [&]() { imageData = instance()->newImageData(data); }, + [&](bool) { data->release(); } + ); + // clang-format on + + luax_pushtype(L, imageData); + imageData->release(); + + return 1; + } + else + return luax_typeerror(L, 1, "value"); +} + +int Wrap_Image::newCompressedData(lua_State* L) +{ + auto* data = luax_getdata(L, 1); + + CompressedImageData* compressedData = nullptr; + + // clang-format off + luax_catchexcept(L, + [&]() { compressedData = instance()->newCompressedData(data); }, + [&](bool) { data->release(); } + ); + // clang-format on + + luax_pushtype(L, CompressedImageData::type, compressedData); + compressedData->release(); + + return 1; +} + +int Wrap_Image::isCompressed(lua_State* L) +{ + Data* data = luax_getdata(L, 1); + + bool compressed = instance()->isCompressed(data); + data->release(); + + luax_pushboolean(L, compressed); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "newImageData", Wrap_Image::newImageData }, + { "newCompressedData", Wrap_Image::newCompressedData }, + { "isCompressed", Wrap_Image::isCompressed } +}; + +static constexpr lua_CFunction types[] = +{ + love::open_imagedata, + love::open_compressedimagedata +}; +// clang-format on + +int Wrap_Image::open(lua_State* L) +{ + auto* instance = instance(); + + if (instance == nullptr) + luax_catchexcept(L, [&]() { instance = new Image(); }); + else + instance->retain(); + + WrappedModule module {}; + module.instance = instance; + module.name = "image"; + module.type = &Module::type; + module.functions = functions; + module.types = types; + + return luax_register_module(L, module); +} diff --git a/source/modules/image/wrap_ImageData.cpp b/source/modules/image/wrap_ImageData.cpp new file mode 100644 index 000000000..c99b3e2ac --- /dev/null +++ b/source/modules/image/wrap_ImageData.cpp @@ -0,0 +1,279 @@ +#include "modules/image/wrap_ImageData.hpp" + +#include "modules/data/wrap_Data.hpp" +#include "modules/filesystem/physfs/File.hpp" +#include "modules/filesystem/physfs/Filesystem.hpp" + +using namespace love; + +int Wrap_ImageData::clone(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + ImageData* clone = nullptr; + + luax_catchexcept(L, [&]() { clone = self->clone(); }); + + luax_pushtype(L, clone); + clone->release(); + + return 1; +} + +int Wrap_ImageData::getFormat(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + auto format = self->getFormat(); + + std::string_view name; + if (!love::getConstant(format, name)) + return luaL_error(L, "Unknown pixel format."); + + luax_pushstring(L, name); + + return 1; +} + +int Wrap_ImageData::setLinear(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + bool linear = luax_toboolean(L, 2); + + self->setLinear(linear); + + return 0; +} + +int Wrap_ImageData::isLinear(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + + luax_pushboolean(L, self->isLinear()); + + return 1; +} + +int Wrap_ImageData::getWidth(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + + lua_pushinteger(L, self->getWidth()); + + return 1; +} + +int Wrap_ImageData::getHeight(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + + lua_pushinteger(L, self->getHeight()); + + return 1; +} + +int Wrap_ImageData::getDimensions(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + + lua_pushinteger(L, self->getWidth()); + lua_pushinteger(L, self->getHeight()); + + return 2; +} + +int Wrap_ImageData::getPixel(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + int x = luaL_checkinteger(L, 2); + int y = luaL_checkinteger(L, 3); + + Color color {}; + luax_catchexcept(L, [&]() { self->getPixel(x, y, color); }); + + lua_pushinteger(L, color.r); + lua_pushinteger(L, color.g); + lua_pushinteger(L, color.b); + lua_pushinteger(L, color.a); + + return 4; +} + +int Wrap_ImageData::setPixel(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + int x = luaL_checkinteger(L, 2); + int y = luaL_checkinteger(L, 3); + + int components = love::getPixelFormatColorComponents(self->getFormat()); + + Color color {}; + + if (lua_istable(L, 4)) + { + for (int index = 1; index <= components; index++) + lua_rawgeti(L, 4, index); + + color.r = (float)luaL_checknumber(L, -components); + if (components > 1) + color.g = (float)luaL_checknumber(L, (-components) + 1); + if (components > 2) + color.b = (float)luaL_checknumber(L, (-components) + 2); + if (components > 3) + color.a = (float)luaL_optnumber(L, (-components) + 3, 1.0); + + lua_pop(L, components); + } + else + { + color.r = (float)luaL_checknumber(L, 4); + if (components > 1) + color.g = (float)luaL_checknumber(L, 5); + if (components > 2) + color.b = (float)luaL_checknumber(L, 6); + if (components > 3) + color.a = (float)luaL_optnumber(L, 7, 1.0); + } + + luax_catchexcept(L, [&]() { self->setPixel(x, y, color); }); + + return 0; +} + +int Wrap_ImageData::mapPixel(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + + int sx = luaL_optinteger(L, 3, 0); + int sy = luaL_optinteger(L, 4, 0); + int width = luaL_optinteger(L, 5, self->getWidth()); + int height = luaL_optinteger(L, 6, self->getHeight()); + + if (!(self->inside(sx, sy) && self->inside(sx + width - 1, sy + height - 1))) + return luaL_error(L, "Invalid rectangle dimensions."); + + int imageWidth = self->getWidth(); + auto format = self->getFormat(); + int components = love::getPixelFormatColorComponents(format); + + auto pixelSetFunction = self->getPixelSetFunction(); + auto pixelGetFunction = self->getPixelGetFunction(); + + uint8_t* data = (uint8_t*)self->getData(); + size_t pixelSize = self->getPixelSize(); + + for (int y = sy; y < sy + height; y++) + { + for (int x = sx; x < sx + width; x++) + { + auto* pixel = (ImageData::Pixel*)(data + (y * imageWidth + x) * pixelSize); + + Color color {}; + pixelGetFunction(pixel, color); + + lua_pushvalue(L, 2); // ImageData + + lua_pushnumber(L, x); + lua_pushnumber(L, y); + + lua_pushinteger(L, color.r); + lua_pushinteger(L, color.g); + lua_pushinteger(L, color.b); + lua_pushinteger(L, color.a); + + lua_call(L, 6, 4); + + color.r = (float)luaL_checknumber(L, -4); + + if (components > 1) + color.g = (float)luaL_checknumber(L, -3); + + if (components > 2) + color.b = (float)luaL_checknumber(L, -2); + + if (components > 3) + color.a = (float)luaL_optnumber(L, -1, 1.0); + + pixelSetFunction(color, pixel); + lua_pop(L, 4); + } + } + + return 0; +} + +int Wrap_ImageData::paste(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + auto* src = luax_checkimagedata(L, 2); + + int dx = luaL_checkinteger(L, 3); + int dy = luaL_checkinteger(L, 4); + int sx = luaL_optinteger(L, 5, 0); + int sy = luaL_optinteger(L, 6, 0); + int sw = luaL_optinteger(L, 7, src->getWidth()); + int sh = luaL_optinteger(L, 8, src->getHeight()); + + self->paste((ImageData*)src, dx, dy, sx, sy, sw, sh); + + return 0; +} + +int Wrap_ImageData::encode(lua_State* L) +{ + auto* self = luax_checkimagedata(L, 1); + + FormatHandler::EncodedFormat format; + const char* formatName = luaL_checkstring(L, 2); + + if (!ImageData::getConstant(formatName, format)) + return luax_enumerror(L, "encoded image format", ImageData::EncodedFormats, formatName); + + bool hasFilename = false; + + std::string filename = std::format("Image.{:s}", formatName); + + if (!lua_isnoneornil(L, 3)) + { + hasFilename = true; + filename = luaL_checkstring(L, 3); + } + + FileData* fileData = nullptr; + luax_catchexcept(L, [&]() { fileData = self->encode(format, filename.c_str(), hasFilename); }); + + luax_pushtype(L, fileData); + fileData->release(); + + return 1; +} + +// clang-format off +static constexpr luaL_Reg functions[] = +{ + { "clone", Wrap_ImageData::clone }, + { "getFormat", Wrap_ImageData::getFormat }, + { "setLinear", Wrap_ImageData::setLinear }, + { "isLinear", Wrap_ImageData::isLinear }, + { "getWidth", Wrap_ImageData::getWidth }, + { "getHeight", Wrap_ImageData::getHeight }, + { "getDimensions", Wrap_ImageData::getDimensions }, + { "getPixel", Wrap_ImageData::getPixel }, + { "setPixel", Wrap_ImageData::setPixel }, + { "paste", Wrap_ImageData::paste }, + { "mapPixel", Wrap_ImageData::mapPixel }, + { "encode", Wrap_ImageData::encode } +}; +// clang-format on + +namespace love +{ + ImageData* luax_checkimagedata(lua_State* L, int index) + { + return luax_checktype(L, index); + } + + int open_imagedata(lua_State* L) + { + return luax_register_type(L, &ImageData::type, Wrap_Data::functions, functions); + } +} // namespace love diff --git a/source/modules/joystick/JoystickModule.cpp b/source/modules/joystick/JoystickModule.cpp index 0b024b9c0..0519824d6 100644 --- a/source/modules/joystick/JoystickModule.cpp +++ b/source/modules/joystick/JoystickModule.cpp @@ -54,7 +54,7 @@ namespace love JoystickBase* JoystickModule::addJoystick(int64_t deviceId) { - if (deviceId < 0 || (size_t)deviceId >= joystick::getJoystickCount()) + if (deviceId < 0 || (int)deviceId >= joystick::getJoystickCount()) return nullptr; std::string guid = this->getDeviceGUID(deviceId); @@ -71,11 +71,9 @@ namespace love } } - // TODO: make a method (or use define info) - // for when Wii U handles are dealt with (WPADHandle, VPADHandle classes) if (!joystick) { - joystick = new Joystick(this->joysticks.size()); + joystick = love::joystick::openJoystick(this->joysticks.size()); this->joysticks.push_back(joystick); } diff --git a/source/modules/joystick/wrap_Joystick.cpp b/source/modules/joystick/wrap_Joystick.cpp index 3ea30647f..4da21f819 100644 --- a/source/modules/joystick/wrap_Joystick.cpp +++ b/source/modules/joystick/wrap_Joystick.cpp @@ -67,7 +67,7 @@ int Wrap_Joystick::getJoystickType(lua_State* L) auto* self = luax_checkjoystick(L, 1); std::string_view type = "Unknown"; - Joystick::getConstant(self->getJoystickType(), type); + JoystickBase::getConstant(self->getJoystickType(), type); luax_pushstring(L, type); @@ -106,7 +106,7 @@ int Wrap_Joystick::getAxis(lua_State* L) auto* self = luax_checkjoystick(L, 1); int axis = luaL_checkinteger(L, 2) - 1; - auto current = (Joystick::GamepadAxis)axis; + auto current = (JoystickBase::GamepadAxis)axis; lua_pushnumber(L, self->getAxis(current)); return 1; @@ -144,7 +144,7 @@ int Wrap_Joystick::isDown(lua_State* L) if (argc == 0) luaL_checkinteger(L, 2); - std::vector buttons {}; + std::vector buttons {}; buttons.reserve(argc); if (isTable) @@ -154,7 +154,7 @@ int Wrap_Joystick::isDown(lua_State* L) lua_rawgeti(L, 2, index + 1); int button = luaL_checkinteger(L, -1) - 1; - buttons.push_back(Joystick::GamepadButton(button)); + buttons.push_back(JoystickBase::GamepadButton(button)); lua_pop(L, 1); } } @@ -163,7 +163,7 @@ int Wrap_Joystick::isDown(lua_State* L) for (int index = 0; index < argc; index++) { int button = luaL_checkinteger(L, index + 2) - 1; - buttons.push_back(Joystick::GamepadButton(button)); + buttons.push_back(JoystickBase::GamepadButton(button)); } } @@ -205,7 +205,7 @@ int Wrap_Joystick::getGamepadType(lua_State* L) auto* self = luax_checkjoystick(L, 1); std::string_view type = "Unknown"; - Joystick::getConstant(self->getGamepadType(), type); + JoystickBase::getConstant(self->getGamepadType(), type); luax_pushstring(L, type); @@ -217,9 +217,9 @@ int Wrap_Joystick::getGamepadAxis(lua_State* L) auto* self = luax_checkjoystick(L, 1); const char* name = luaL_checkstring(L, 2); - Joystick::GamepadAxis axis; - if (!Joystick::getConstant(name, axis)) - return luax_enumerror(L, "gamepad axis", Joystick::GamepadButtons, name); + JoystickBase::GamepadAxis axis; + if (!JoystickBase::getConstant(name, axis)) + return luax_enumerror(L, "gamepad axis", JoystickBase::GamepadButtons, name); lua_pushnumber(L, self->getAxis(axis)); @@ -236,10 +236,10 @@ int Wrap_Joystick::isGamepadDown(lua_State* L) if (argc == 0) luaL_checkstring(L, 2); - std::vector buttons {}; + std::vector buttons {}; buttons.reserve(argc); - Joystick::GamepadButton button; + JoystickBase::GamepadButton button; if (isTable) { @@ -248,8 +248,8 @@ int Wrap_Joystick::isGamepadDown(lua_State* L) lua_rawgeti(L, 2, index + 1); const char* string = luaL_checkstring(L, -1); - if (!Joystick::getConstant(string, button)) - return luax_enumerror(L, "gamepad button", Joystick::GamepadButtons, string); + if (!JoystickBase::getConstant(string, button)) + return luax_enumerror(L, "gamepad button", JoystickBase::GamepadButtons, string); buttons.push_back(button); lua_pop(L, 1); @@ -261,8 +261,8 @@ int Wrap_Joystick::isGamepadDown(lua_State* L) { const char* string = luaL_checkstring(L, index + 2); - if (!Joystick::getConstant(string, button)) - return luax_enumerror(L, "gamepad button", Joystick::GamepadButtons, string); + if (!JoystickBase::getConstant(string, button)) + return luax_enumerror(L, "gamepad button", JoystickBase::GamepadButtons, string); buttons.push_back(button); } @@ -319,7 +319,7 @@ int Wrap_Joystick::setVibration(lua_State* L) luax_pushboolean(L, success); - return 0; + return 1; } int Wrap_Joystick::getVibration(lua_State* L) @@ -430,6 +430,49 @@ static constexpr luaL_Reg functions[] = { { "getSensorData", Wrap_Joystick::getSensorData }, { "getConnectedIndex", Wrap_JoystickModule::getIndex } }; + +#if !defined(__WIIU__) +static constexpr std::span extFunctions = {}; +#else +#include "modules/joystick/kpad/Joystick.hpp" + +int Wrap_Joystick::getPosition(lua_State*L) +{ + auto* base = luax_checkjoystick(L, 1); + const auto type = base->getGamepadType(); + + if (type != GAMEPAD_TYPE_NINTENDO_WII_REMOTE && type != GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK) + return luaL_error(L, "Invalid controller! Must be of type Wii Remote"); + + auto position = ((kpad::Joystick*)base)->getPosition(); + + lua_pushnumber(L, position.x); + lua_pushnumber(L, position.y); + + return 2; +} + +int Wrap_Joystick::getAngle(lua_State*L) +{ + auto* base = luax_checkjoystick(L, 1); + const auto type = base->getGamepadType(); + + if (type != GAMEPAD_TYPE_NINTENDO_WII_REMOTE && type != GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK) + return luaL_error(L, "Invalid controller! Must be of type Wii Remote"); + + auto angle = ((kpad::Joystick*)base)->getAngle(); + + lua_pushnumber(L, angle.x); + lua_pushnumber(L, angle.y); + + return 2; +} + +static constexpr luaL_Reg extFunctions[] = { + { "getPosition", Wrap_Joystick::getPosition }, + { "getAngle", Wrap_Joystick::getAngle } +}; +#endif // clang-format on namespace love @@ -441,6 +484,6 @@ namespace love int open_joystick(lua_State* L) { - return luax_register_type(L, &JoystickBase::type, functions); + return luax_register_type(L, &JoystickBase::type, functions, extFunctions); } } // namespace love diff --git a/source/modules/keyboard/wrap_Keyboard.cpp b/source/modules/keyboard/wrap_Keyboard.cpp index 21d8adbcf..d5f00d725 100644 --- a/source/modules/keyboard/wrap_Keyboard.cpp +++ b/source/modules/keyboard/wrap_Keyboard.cpp @@ -1,3 +1,5 @@ +#include "common/Reference.hpp" + #include "modules/keyboard/wrap_Keyboard.hpp" using namespace love; @@ -53,6 +55,43 @@ int Wrap_Keyboard::isModifierActive(lua_State* L) return 1; } +static Keyboard::KeyboardResult textInputValidationCallback(const Keyboard::KeyboardValidationInfo* info, + const char* text, Keyboard::ValidationError error) +{ + lua_State* L = (lua_State*)info->luaState; + auto* reference = (Reference*)info->data; + + if (reference == nullptr) + luaL_error(L, "Internal error in text input validation callback."); + + reference->push(L); + delete reference; + + luax_pushstring(L, text); + lua_call(L, 1, 2); + + const char* result = lua_tostring(L, -2); + + Keyboard::KeyboardResult out; + if (!Keyboard::getConstant(result, out)) + luax_enumerror(L, "keyboard result", Keyboard::KeyboardResults, result); + + const char* message = lua_tostring(L, -1); + + if (message != nullptr) + { +#if defined(__3DS__) + *error = message; +#elif defined(__SWITCH__) + std::memcpy(error, message, std::strlen(message)); +#endif + } + + lua_pop(L, 2); + + return out; +} + int Wrap_Keyboard::setTextInput(lua_State* L) { // clang-format off @@ -63,7 +102,8 @@ int Wrap_Keyboard::setTextInput(lua_State* L) type : Keyboard::TYPE_NORMAL, password : false, hint : "", - maxLength : Keyboard::DEFAULT_INPUT_LENGTH + maxLength : Keyboard::DEFAULT_INPUT_LENGTH, + callback : nullptr }; lua_getfield(L, 1, Keyboard::getConstant(Keyboard::OPTION_TYPE)); @@ -94,6 +134,22 @@ int Wrap_Keyboard::setTextInput(lua_State* L) options.maxLength = luaL_checkinteger(L, -1); lua_pop(L, 1); + lua_getfield(L, 1, Keyboard::getConstant(Keyboard::OPTION_CALLBACK)); + if (!lua_isnoneornil(L, -1)) + { + Keyboard::KeyboardValidationInfo info {}; + + luaL_checktype(L, -1, LUA_TFUNCTION); + lua_pushvalue(L, -1); + + info.data = luax_refif(L, LUA_TFUNCTION); + lua_pop(L, 1); + info.callback = textInputValidationCallback; + + options.callback = info; + } + lua_pop(L, 1); + instance()->setTextInput(options); return 0; diff --git a/source/modules/love/love.cpp b/source/modules/love/love.cpp index cb8cef6ac..2f035780b 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -1,13 +1,17 @@ #include "common/luax.hpp" #include "common/version.hpp" +#include + #include "modules/love/love.hpp" #include "modules/audio/wrap_Audio.hpp" #include "modules/data/wrap_DataModule.hpp" #include "modules/event/wrap_Event.hpp" #include "modules/filesystem/wrap_Filesystem.hpp" +#include "modules/font/wrap_Font.hpp" #include "modules/graphics/wrap_Graphics.hpp" +#include "modules/image/wrap_Image.hpp" #include "modules/joystick/wrap_JoystickModule.hpp" #include "modules/keyboard/wrap_Keyboard.hpp" #include "modules/math/wrap_MathModule.hpp" @@ -21,13 +25,16 @@ #include "boot.hpp" +// #region DEBUG CONSOLE #include #include +#include #include #if defined(__WIIU__) #include #endif +// #endregion #include #include @@ -49,14 +56,16 @@ static constexpr char nogame_lua[] = { }; // clang-format off -static constexpr std::array modules = +static constexpr std::array modules = {{ { "love.audio", Wrap_Audio::open }, { "love.data", Wrap_DataModule::open }, { "love.event", Wrap_Event::open }, { "love.filesystem", Wrap_Filesystem::open }, + { "love.font", Wrap_FontModule::open }, { "love.graphics", Wrap_Graphics::open }, { "love.joystick", Wrap_JoystickModule::open }, + { "love.image", Wrap_Image::open }, { "love.keyboard", Wrap_Keyboard::open }, { "love.math", Wrap_MathModule::open }, { "love.sensor", Wrap_Sensor::open }, @@ -73,8 +82,6 @@ static constexpr std::array modules = }}; // clang-format on -#include "utility/logfile.hpp" - const char* love_getVersion() { return __LOVE_VERSION__; @@ -141,6 +148,37 @@ static int love_setDeprecationOutput(lua_State* L) return 0; } +static constexpr const char* ERROR_PANIC = "PANIC: unprotected error in call to Lua API (%s)"; + +/* + * If an error happens outside any protected environment, Lua calls a panic function and then calls + * exit(EXIT_FAILURE), thus exiting the host application. We want to inform the user of the error. + * However, this will only be informative via the console. + */ +static int love_atpanic(lua_State* L) +{ + char message[0x80] {}; + snprintf(message, sizeof(message), ERROR_PANIC, lua_tostring(L, -1)); + + printf("%s\n", message); + return 0; +} + +// #if __DEBUG__ +// static void love_atcpanic() +// { +// try +// { +// throw; +// } +// catch (const std::exception& e) +// { +// std::printf("Uncaught exception: %s", e.what()); +// std::exit(EXIT_FAILURE); +// } +// } +// #endif + static void luax_addcompatibilityalias(lua_State* L, const char* module, const char* name, const char* alias) { lua_getglobal(L, module); @@ -207,6 +245,9 @@ int love_initialize(lua_State* L) lua_pushstring(L, __OS__); lua_setfield(L, -2, "_os"); + lua_pushstring(L, __CONSOLE__); + lua_setfield(L, -2, "_console"); + lua_pushcfunction(L, love_setDeprecationOutput); lua_setfield(L, -2, "setDeprecationOutput"); @@ -221,10 +262,16 @@ int love_initialize(lua_State* L) luax_addcompatibilityalias(L, "string", "gmatch", "gfind"); #endif - // love::luasocket::preload(L); + love::luasocket::preload(L); love::luax_preload(L, luaopen_luautf8, "utf8"); love::luax_preload(L, luaopen_https, "https"); + lua_atpanic(L, love_atpanic); + +#if __DEBUG__ + // std::set_terminate(love_atcpanic); +#endif + return 1; } @@ -232,7 +279,7 @@ int love_initialize(lua_State* L) * @brief Initializes the console output. * * Users will need to use telnet on Windows or netcat on Linux/macOS: - * `telnet/netcat 192.168.x.x 8000` + * `telnet/nc 192.168.x.x 8000` */ #define SEND(sockfd, s) send(sockfd, s, strlen(s), 0) @@ -269,6 +316,31 @@ int love_openConsole(lua_State* L) return 1; } + fd_set set; + FD_ZERO(&set); + FD_SET(lsockfd, &set); + + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + + const auto result = select(lsockfd + 1, &set, NULL, NULL, &timeout); + + if (result == -1) + { + // select(...) failed + love::luax_pushboolean(L, false); + close(lsockfd); + return 1; + } + else if (result == 0) + { + // timeout occurred + love::luax_pushboolean(L, false); + close(lsockfd); + return 1; + } + int sockfd = accept(lsockfd, NULL, NULL); close(lsockfd); diff --git a/source/modules/love/scripts/boot.lua b/source/modules/love/scripts/boot.lua index 6d7bd4a21..43ac59422 100644 --- a/source/modules/love/scripts/boot.lua +++ b/source/modules/love/scripts/boot.lua @@ -173,7 +173,7 @@ function love.init() timer = true, joystick = true, touch = true, - image = false, + image = true, graphics = true, audio = true, math = true, @@ -181,7 +181,7 @@ function love.init() sensor = true, sound = true, system = true, - font = false, + font = true, thread = true, window = true, video = false, @@ -315,8 +315,6 @@ function love.init() local success, msg = pcall(require, "love." .. v) if v == "audio" and not success and msg:find("ndsp") then error(msg) - else - print(msg) end end end diff --git a/source/modules/love/scripts/callbacks.lua b/source/modules/love/scripts/callbacks.lua index c68c85d69..9de4c0706 100644 --- a/source/modules/love/scripts/callbacks.lua +++ b/source/modules/love/scripts/callbacks.lua @@ -204,26 +204,10 @@ end ----------------------------------------------------------- -- we need to fix some bugs/inconsistencies on Wii U -local is_wii_u = love._os == "Cafe" - --- Checks if `love.draw("gamepad")` should fire --- This happens only when the software keyboard is shown --- For some reason the software keyboard has some kind of depth testing enabled -local function shouldDraw(screen) - if not is_wii_u then - return true - end - - -- when gamepad and keyboard shown, do not draw - if screen == "gamepad" and love.keyboard.hasTextInput() then - return false - end - -- when tv, always draw - return true -end +-- local is_wii_u = love._os == "Cafe" -local function get3DDepth(screen) - if love._console ~= "3ds" then +local function get_3d_depth(screen) + if love._console ~= "3DS" then return nil end @@ -237,6 +221,8 @@ local function get3DDepth(screen) return depth end +-- local copyCurrentScanBuffer = love.graphics.copyCurrentScanBuffer or function() end + function love.run() if love.load then love.load(love.parsedGameArguments, love.rawGameArguments) @@ -279,16 +265,18 @@ function love.run() if love.graphics and love.graphics.isActive() then local screens = love.graphics.getScreens() - + for _, screen in ipairs(screens) do - love.graphics.origin() - love.graphics.setActiveScreen(screen) + + love.graphics.origin() love.graphics.clear(love.graphics.getBackgroundColor()) - if love.draw and shouldDraw(screen) then - love.draw(screen, get3DDepth(screen)) + if love.draw then + love.draw(screen, get_3d_depth(screen)) end + + love.graphics.copyCurrentScanBuffer() end love.graphics.present() diff --git a/source/modules/window/wrap_Window.cpp b/source/modules/window/wrap_Window.cpp index d5cc99aa0..800d87569 100644 --- a/source/modules/window/wrap_Window.cpp +++ b/source/modules/window/wrap_Window.cpp @@ -277,7 +277,7 @@ int Wrap_Window::isOpen(lua_State* L) return 1; } -int Wrap_Window::close(lua_State* L) +int Wrap_Window::close(lua_State*) { instance()->close(); @@ -297,7 +297,7 @@ int Wrap_Window::getDesktopDimensions(lua_State* L) return 2; } -int Wrap_Window::setVSync(lua_State* L) +int Wrap_Window::setVSync(lua_State*) { return 0; } @@ -468,12 +468,12 @@ int Wrap_Window::showMessageBox(lua_State* L) return 1; } -int Wrap_Window::setTitle(lua_State* L) +int Wrap_Window::setTitle(lua_State*) { return 0; } -int Wrap_Window::setIcon(lua_State* L) +int Wrap_Window::setIcon(lua_State*) { return 0; } diff --git a/source/utility/guid.cpp b/source/utility/guid.cpp index 074962f21..8d11fda8d 100644 --- a/source/utility/guid.cpp +++ b/source/utility/guid.cpp @@ -22,8 +22,8 @@ namespace love { 12, 6, 0, "{2418ED2A-7918-4D2C-B390-6127D5FEB977}", true, true, true, true }, // GAMEPAD_TYPE_NEW_NINTENDO_3DS_XL { 12, 2, 0, "{3846FA8C-25AB-44CB-91FD-D58FB1F572CC}", false, false, true, true }, // GAMEPAD_TYPE_NINTENDO_2DS { 12, 6, 0, "{560CDDB3-0594-4A52-A3D5-617060D2001B}", true, true, true, true }, // GAMEPAD_TYPE_NEW_NINTENDO_2DS_XL - { 10, 0, 0, "{02DC4D7B-2480-4678-BB06-D9AEDC3DE29B}", false, false, true, true }, // GAMEPAD_TYPE_NINTENDO_WII_REMOTE - { 12, 2, 0, "{C0E2DDE5-25DF-4F7D-AEA6-4F25DE2FC385}", false, false, true, true }, // GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK + { 10, 0, 0, "{02DC4D7B-2480-4678-BB06-D9AEDC3DE29B}", false, false, false, true }, // GAMEPAD_TYPE_NINTENDO_WII_REMOTE + { 12, 2, 0, "{C0E2DDE5-25DF-4F7D-AEA6-4F25DE2FC385}", false, false, false, true }, // GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK { 12, 6, 0, "{B4F6A311-8228-477D-857B-B875D891C46D}", true, true, false, false }, // GAMEPAD_TYPE_NINTENDO_WII_CLASSIC { 14, 6, 0, "{62998927-C43D-41F5-B6B1-D22CBF031D91}", true, true, true, true }, // GAMEPAD_TYPE_WII_U_GAMEPAD { 14, 6, 0, "{36895D3B-A724-4F46-994C-64BCE736EBCB}", true, true, false, false }, // GAMEPAD_TYPE_WII_U_PRO