From b773a8f92cce9468f500058eb8c5bd76d1c84961 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Tue, 30 Apr 2024 13:49:25 -0400 Subject: [PATCH 01/49] image module, graphics work --- .gitignore | 2 + CMakeLists.txt | 63 +- debug/debug.py | 4 + gdb.txt | 2 + include/common/Color.hpp | 55 ++ include/common/Vector.hpp | 3 +- include/common/float.hpp | 43 + include/common/int.hpp | 19 +- include/common/luax.hpp | 6 +- include/common/math.hpp | 4 +- include/common/pixelformat.hpp | 126 +++ include/common/screen.hpp | 2 +- include/driver/display/Renderer.tcc | 19 + include/modules/font/TextShaper.hpp | 139 +++ include/modules/graphics/BatchedDrawState.hpp | 29 + include/modules/graphics/Buffer.hpp | 239 +++++ include/modules/graphics/Drawable.hpp | 20 + include/modules/graphics/Graphics.tcc | 345 +++---- include/modules/graphics/Quad.hpp | 69 ++ include/modules/graphics/Resource.hpp | 15 + include/modules/graphics/Shader.tcc | 85 ++ include/modules/graphics/StreamBuffer.tcc | 85 ++ include/modules/graphics/Texture.tcc | 377 ++++++++ include/modules/graphics/samplerstate.hpp | 85 ++ include/modules/graphics/vertex.hpp | 354 ++++++- include/modules/graphics/wrap_Graphics.hpp | 8 + include/modules/graphics/wrap_Texture.hpp | 78 ++ include/modules/image/CompressedImageData.hpp | 60 ++ include/modules/image/CompressedSlice.hpp | 44 + include/modules/image/FormatHandler.hpp | 62 ++ include/modules/image/Image.hpp | 38 + include/modules/image/ImageData.hpp | 102 ++ include/modules/image/ImageDataBase.hpp | 48 + include/modules/image/magpie/ASTCHandler.hpp | 18 + include/modules/image/magpie/JPGHandler.hpp | 14 + include/modules/image/magpie/KTXHandler.hpp | 20 + include/modules/image/magpie/PKMHandler.hpp | 18 + include/modules/image/magpie/PNGHandler.hpp | 21 + include/modules/image/magpie/T3XHandler.hpp | 20 + include/modules/image/magpie/ddsHandler.hpp | 22 + .../image/wrap_CompressedImageData.hpp | 30 + include/modules/image/wrap_Image.hpp | 15 + include/modules/image/wrap_ImageData.hpp | 38 + include/modules/window/Window.tcc | 6 +- platform/ctr/CMakeLists.txt | 3 + platform/ctr/include/boot.hpp | 2 - .../ctr/include/driver/display/Renderer.hpp | 24 +- .../driver/display/citro3d/VertexRing.hpp | 66 ++ .../ctr/include/modules/graphics/Graphics.hpp | 43 +- .../ctr/include/modules/graphics/Shader.hpp | 45 + .../include/modules/graphics/StreamBuffer.hpp | 8 + .../ctr/include/modules/graphics/Texture.hpp | 43 + .../ctr/source/driver/display/Renderer.cpp | 59 +- .../ctr/source/modules/graphics/Graphics.cpp | 174 +++- .../ctr/source/modules/graphics/Shader.cpp | 106 +++ .../source/modules/graphics/StreamBuffer.cpp | 70 ++ .../ctr/source/modules/graphics/Texture.cpp | 283 ++++++ source/common/float.cpp | 262 +++++ source/common/luax.cpp | 38 + source/common/pixelformat.cpp | 123 --- source/modules/filesystem/wrap_Filesystem.cpp | 26 +- source/modules/graphics/Graphics.cpp | 424 +++++++++ source/modules/graphics/Quad.cpp | 37 + source/modules/graphics/Shader.cpp | 7 + source/modules/graphics/StreamBuffer.cpp | 32 + source/modules/graphics/Texture.cpp | 343 +++++++ source/modules/graphics/samplerstate.cpp | 60 ++ source/modules/graphics/vertex.cpp | 256 +++++ source/modules/graphics/wrap_Graphics.cpp | 338 ++++++- source/modules/graphics/wrap_Texture.cpp | 492 ++++++++++ source/modules/image/CompressedImageData.cpp | 135 +++ source/modules/image/CompressedSlice.cpp | 29 + source/modules/image/FormatHandler.cpp | 52 + source/modules/image/Image.cpp | 90 ++ source/modules/image/ImageData.cpp | 898 ++++++++++++++++++ source/modules/image/ImageDataBase.cpp | 11 + source/modules/image/magpie/ASTCHandler.cpp | 113 +++ source/modules/image/magpie/JPGHandler.cpp | 65 ++ source/modules/image/magpie/KTXHandler.cpp | 349 +++++++ source/modules/image/magpie/PKMHandler.cpp | 112 +++ source/modules/image/magpie/PNGHandler.cpp | 101 ++ source/modules/image/magpie/T3XHandler.cpp | 172 ++++ source/modules/image/magpie/ddsHandler.cpp | 236 +++++ .../image/wrap_CompressedImageData.cpp | 134 +++ source/modules/image/wrap_Image.cpp | 148 +++ source/modules/image/wrap_ImageData.cpp | 279 ++++++ source/modules/love/love.cpp | 4 +- source/modules/love/scripts/boot.lua | 4 +- source/modules/window/wrap_Window.cpp | 8 +- 89 files changed, 8719 insertions(+), 437 deletions(-) create mode 100644 gdb.txt create mode 100644 include/common/float.hpp create mode 100644 include/modules/font/TextShaper.hpp create mode 100644 include/modules/graphics/BatchedDrawState.hpp create mode 100644 include/modules/graphics/Buffer.hpp create mode 100644 include/modules/graphics/Drawable.hpp create mode 100644 include/modules/graphics/Quad.hpp create mode 100644 include/modules/graphics/Resource.hpp create mode 100644 include/modules/graphics/Shader.tcc create mode 100644 include/modules/graphics/StreamBuffer.tcc create mode 100644 include/modules/graphics/Texture.tcc create mode 100644 include/modules/graphics/samplerstate.hpp create mode 100644 include/modules/graphics/wrap_Texture.hpp create mode 100644 include/modules/image/CompressedImageData.hpp create mode 100644 include/modules/image/CompressedSlice.hpp create mode 100644 include/modules/image/FormatHandler.hpp create mode 100644 include/modules/image/Image.hpp create mode 100644 include/modules/image/ImageData.hpp create mode 100644 include/modules/image/ImageDataBase.hpp create mode 100644 include/modules/image/magpie/ASTCHandler.hpp create mode 100644 include/modules/image/magpie/JPGHandler.hpp create mode 100644 include/modules/image/magpie/KTXHandler.hpp create mode 100644 include/modules/image/magpie/PKMHandler.hpp create mode 100644 include/modules/image/magpie/PNGHandler.hpp create mode 100644 include/modules/image/magpie/T3XHandler.hpp create mode 100644 include/modules/image/magpie/ddsHandler.hpp create mode 100644 include/modules/image/wrap_CompressedImageData.hpp create mode 100644 include/modules/image/wrap_Image.hpp create mode 100644 include/modules/image/wrap_ImageData.hpp create mode 100644 platform/ctr/include/driver/display/citro3d/VertexRing.hpp create mode 100644 platform/ctr/include/modules/graphics/Shader.hpp create mode 100644 platform/ctr/include/modules/graphics/StreamBuffer.hpp create mode 100644 platform/ctr/include/modules/graphics/Texture.hpp create mode 100644 platform/ctr/source/modules/graphics/Shader.cpp create mode 100644 platform/ctr/source/modules/graphics/StreamBuffer.cpp create mode 100644 platform/ctr/source/modules/graphics/Texture.cpp create mode 100644 source/common/float.cpp create mode 100644 source/modules/graphics/Graphics.cpp create mode 100644 source/modules/graphics/Quad.cpp create mode 100644 source/modules/graphics/Shader.cpp create mode 100644 source/modules/graphics/StreamBuffer.cpp create mode 100644 source/modules/graphics/Texture.cpp create mode 100644 source/modules/graphics/samplerstate.cpp create mode 100644 source/modules/graphics/vertex.cpp create mode 100644 source/modules/graphics/wrap_Texture.cpp create mode 100644 source/modules/image/CompressedImageData.cpp create mode 100644 source/modules/image/CompressedSlice.cpp create mode 100644 source/modules/image/FormatHandler.cpp create mode 100644 source/modules/image/Image.cpp create mode 100644 source/modules/image/ImageData.cpp create mode 100644 source/modules/image/ImageDataBase.cpp create mode 100644 source/modules/image/magpie/ASTCHandler.cpp create mode 100644 source/modules/image/magpie/JPGHandler.cpp create mode 100644 source/modules/image/magpie/KTXHandler.cpp create mode 100644 source/modules/image/magpie/PKMHandler.cpp create mode 100644 source/modules/image/magpie/PNGHandler.cpp create mode 100644 source/modules/image/magpie/T3XHandler.cpp create mode 100644 source/modules/image/magpie/ddsHandler.cpp create mode 100644 source/modules/image/wrap_CompressedImageData.cpp create mode 100644 source/modules/image/wrap_Image.cpp create mode 100644 source/modules/image/wrap_ImageData.cpp diff --git a/.gitignore b/.gitignore index e250eac9..4cbcdd63 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ target/ *.3dsx *.nro *.wuhb +*.py +__pycache__/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 947b7a02..f85ec6c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,6 @@ target_compile_options(${PROJECT_NAME} PRIVATE $<$:-fexceptions -fno-rtti> ) - if(NINTENDO_3DS) add_subdirectory(platform/ctr) @@ -76,6 +75,10 @@ if(NINTENDO_3DS) set(APP_LIBS citro3d) set(OUTPUT_FILENAME "${PROJECT_NAME}.3dsx") + + target_sources(${PROJECT_NAME} PRIVATE + source/modules/image/magpie/T3XHandler.cpp + ) endif() if (NINTENDO_SWITCH) @@ -108,6 +111,15 @@ 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 + ) endif() if (NINTENDO_WIIU) @@ -135,6 +147,15 @@ 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 + ) endif() add_custom_target(test @@ -299,8 +320,10 @@ include_directories( # 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 @@ -310,7 +333,7 @@ source/common/pixelformat.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 +344,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 +352,29 @@ 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/graphics/Graphics.cpp source/modules/graphics/renderstate.cpp +source/modules/graphics/samplerstate.cpp +source/modules/graphics/StreamBuffer.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/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 +391,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 +409,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 index 9524dbf7..c6d483c6 100644 --- a/debug/debug.py +++ b/debug/debug.py @@ -32,6 +32,7 @@ def main() -> None: try: tcp_socket.connect((args.host, 8000)) + # tcp_socket.settimeout(3) except (ConnectionRefusedError, TimeoutError): print(f"Failed to connect to {args.host}:8000") sys.exit(1) @@ -64,6 +65,9 @@ def main() -> None: break except KeyboardInterrupt: break + except TimeoutError: + print("Connection timed out") + break tcp_socket.close() diff --git a/gdb.txt b/gdb.txt new file mode 100644 index 00000000..f8b80b7a --- /dev/null +++ b/gdb.txt @@ -0,0 +1,2 @@ +C:\msys64\home\jpost/.gdbinit:23: Error in sourced command file: +Undefined item: "UTF-8". diff --git a/include/common/Color.hpp b/include/common/Color.hpp index fbd6f727..63127fa1 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/Vector.hpp b/include/common/Vector.hpp index 93e35ea0..5ebe7d50 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/float.hpp b/include/common/float.hpp new file mode 100644 index 00000000..f7c737ba --- /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 562e6b03..f29af368 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 2c8e04cd..93492a76 100644 --- a/include/common/luax.hpp +++ b/include/common/luax.hpp @@ -100,10 +100,14 @@ namespace love 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); diff --git a/include/common/math.hpp b/include/common/math.hpp index 88914f8c..b9435650 100644 --- a/include/common/math.hpp +++ b/include/common/math.hpp @@ -40,8 +40,8 @@ #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 { diff --git a/include/common/pixelformat.hpp b/include/common/pixelformat.hpp index 47af5ae1..da8fc680 100644 --- a/include/common/pixelformat.hpp +++ b/include/common/pixelformat.hpp @@ -20,6 +20,8 @@ #pragma once +#include "common/Map.hpp" + #include #include @@ -269,4 +271,128 @@ 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 }, + + { "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 4ae8e8c8..a0af5ff2 100644 --- a/include/common/screen.hpp +++ b/include/common/screen.hpp @@ -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(); diff --git a/include/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index 472e66cd..ddc1f345 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -3,6 +3,7 @@ #include "common/Singleton.tcc" #include "modules/graphics/renderstate.hpp" +#include "modules/graphics/vertex.hpp" #include @@ -20,6 +21,16 @@ namespace love RENDERER_INFO_DEVICE }; + size_t getVertexCount() const + { + return this->renderCtx.vertexCount; + } + + Vertex* getVertices() + { + return this->data; + } + protected: struct ContextBase { @@ -36,5 +47,13 @@ namespace love bool initialized = false; bool inFrame = false; + + Vertex* data; + + struct RenderContext + { + size_t vertexCount; + size_t indexCount; + } renderCtx; }; } // namespace love diff --git a/include/modules/font/TextShaper.hpp b/include/modules/font/TextShaper.hpp new file mode 100644 index 00000000..9d0bec0c --- /dev/null +++ b/include/modules/font/TextShaper.hpp @@ -0,0 +1,139 @@ +#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(); + + void setLineHeight(float height); + + float getLineHeight() const; + + 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) const; + + float getKerning(const std::string& left, const std::string& right) const; + + float getGlyphAdvance(uint32_t codepoint, GlyphIndex* glyphIndex = nullptr) const; + + int getWidth(const std::string& text) const; + + void getWrap(const std::vector& text, float limit, std::vector& lines, + std::vector* lineWidths = nullptr) const; + + void getWrap(const ColoredCodepoints& codepoints, float limit, std::vector& lineRanges, + std::vector* lineWidths = nullptr) const; + + 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) const = 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/graphics/BatchedDrawState.hpp b/include/modules/graphics/BatchedDrawState.hpp new file mode 100644 index 00000000..a0ff5df9 --- /dev/null +++ b/include/modules/graphics/BatchedDrawState.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/vertex.hpp" + +#include "modules/graphics/StreamBuffer.hpp" + +namespace love +{ + struct BatchedDrawState + { + StreamBufferBase* vb[2]; + StreamBufferBase* indexBuffer = nullptr; + + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + CommonFormat formats[2] = { CommonFormat::NONE, CommonFormat::NONE }; + StrongRef texture; + ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; + + StreamBufferBase::MapInfo vbMap[2] = { StreamBufferBase::MapInfo(), StreamBufferBase::MapInfo() }; + StreamBufferBase::MapInfo indexBufferMap = StreamBufferBase::MapInfo(); + + int vertexCount; + int indexCount; + + bool flushing = false; + }; +} // namespace love diff --git a/include/modules/graphics/Buffer.hpp b/include/modules/graphics/Buffer.hpp new file mode 100644 index 00000000..f18708a9 --- /dev/null +++ b/include/modules/graphics/Buffer.hpp @@ -0,0 +1,239 @@ +/** + * 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 +{ + namespace graphics + { + + class Graphics; + + /** + * A block of GPU-owned memory. + **/ + class Buffer : 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() + {} + }; + + Buffer(Graphics* gfx, const Settings& settings, const std::vector& format, + size_t size, size_t arraylength); + + virtual ~Buffer(); + + 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(Buffer* 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(Buffer& buffer, MapType maptype = MAP_WRITE_INVALIDATE) : buffer(buffer) + { + data = buffer.map(maptype, 0, buffer.getSize()); + } + + ~Mapper() + { + buffer.unmap(0, buffer.getSize()); + } + + Buffer& 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 graphics +} // namespace love diff --git a/include/modules/graphics/Drawable.hpp b/include/modules/graphics/Drawable.hpp new file mode 100644 index 00000000..cc6f245f --- /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 Graphics; + + class Drawable : public Object + { + public: + static inline Type type = Type("Drawable", &Object::type); + + virtual ~Drawable() + {} + + virtual void draw(Graphics& graphics, const Matrix4& transform) = 0; + }; +} // namespace love diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index bd0b8803..15fbefb6 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -9,8 +9,15 @@ #include "common/int.hpp" #include "common/pixelformat.hpp" +#include "modules/math/MathModule.hpp" + +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/Texture.tcc" #include "modules/graphics/Volatile.hpp" #include "modules/graphics/renderstate.hpp" +#include "modules/graphics/samplerstate.hpp" + +#include "modules/graphics/BatchedDrawState.hpp" #include #include @@ -25,27 +32,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: @@ -152,124 +174,119 @@ namespace love bool useCustomProjection = false; Matrix4 customProjection; - // SamplerState defaultSampleState = SamplerState(); + SamplerState defaultSampleState = SamplerState(); }; - 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()); + class TempTransform + { + public: + TempTransform(GraphicsBase* graphics) : graphics(graphics) + { + graphics->pushTransform(); + } - this->pixelScaleStack.reserve(16); - this->pixelScaleStack.push_back(1.0); + TempTransform(GraphicsBase* graphics, const Matrix4& transform) : graphics(graphics) + { + graphics->pushTransform(); + graphics->transformStack.back() *= transform; + } - this->states.reserve(10); - this->states.push_back(DisplayState()); - } + ~TempTransform() + { + graphics->popTransform(); + } - virtual ~GraphicsBase() - { - this->states.clear(); - } + private: + GraphicsBase* graphics; + }; - void restoreState(const DisplayState& state) - { - this->setColor(state.color); - this->setBackgroundColor(state.backgroundColor); + GraphicsBase(const char* name); - this->setBlendState(state.blend); + virtual ~GraphicsBase(); - this->setLineWidth(state.lineWidth); - this->setLineStyle(state.lineStyle); - this->setLineJoin(state.lineJoin); + void restoreState(const DisplayState& state); - this->setPointSize(state.pointSize); + struct BatchedVertexData + { + void* stream[2]; + }; - if (state.scissor) - this->setScissor(state.scissorRect); - else - this->setScissor(); + struct BatchedDrawCommand + { + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + CommonFormat formats[2] = { CommonFormat::NONE, CommonFormat::NONE }; + TriangleIndexMode indexMode = TRIANGLEINDEX_NONE; + int vertexCount = 0; + TextureBase* texture = nullptr; + ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; + }; - this->setMeshCullMode(state.meshCullMode); - this->setFrontFaceWinding(state.winding); + struct DrawIndexedCommand + { + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; - // this->setFont(state.font.get()); - // this->setShader(state.shader.get()); - // this->setRenderTargets(state.renderTargets); + const VertexAttributes* attributes; + const BufferBindings* buffers; - // this->setStencilState(state.stencil); - // this->setDepthMode(state.depthTest, state.depthWrite); + int indexCount = 0; + int instanceCount = 1; - this->setColorMask(state.colorMask); + IndexDataType indexType = INDEX_UINT16; + Resource* indexBuffer; + size_t indexBufferOffset = 0; - // this->setDefaultSamplerState(state.defaultSampleState); + Buffer* indirectBuffer = nullptr; + size_t indirectBufferOffset = 0; - if (state.useCustomProjection) - this->updateDeviceProjection(state.customProjection); - else - this->resetProjection(); - } + TextureBase* texture = nullptr; - void restoreStateChecked(const DisplayState& state) - { - const auto& current = this->states.back(); + CullMode cullMode = CULL_NONE; - if (state.color != current.color) - this->setColor(state.color); + DrawIndexedCommand(const VertexAttributes* attributes, const BufferBindings* buffers, + Resource* indexBuffer) : + attributes(attributes), + buffers(buffers), + indexBuffer(indexBuffer) + {} + }; - this->setBackgroundColor(state.backgroundColor); + struct DrawCommand + { + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; - if (state.blend != current.blend) - this->setBlendState(state.blend); + const VertexAttributes* attributes; + const BufferBindings* buffers; - this->setLineWidth(state.lineWidth); - this->setLineStyle(state.lineStyle); - this->setLineJoin(state.lineJoin); + int vertexStart = 0; + int vertexCount = 0; + int instanceCount = 1; - if (state.pointSize != current.pointSize) - this->setPointSize(state.pointSize); + Resource* vertexBuffer; - if (state.scissor != current.scissor || - (state.scissor && !(state.scissorRect != current.scissorRect))) - { - if (state.scissor) - this->setScissor(state.scissorRect); - else - this->setScissor(); - } + Buffer* indirectBuffer = nullptr; + size_t indirectBufferOffset = 0; + + TextureBase* texture = nullptr; - this->setMeshCullMode(state.meshCullMode); + CullMode cullMode = CULL_NONE; - if (state.winding != current.winding) - this->setFrontFaceWinding(state.winding); + DrawCommand(const VertexAttributes* attributes, const BufferBindings* buffers) : + attributes(attributes), + buffers(buffers) + {} + }; - // this->setFont(state.font.get()); - // this->setShader(state.shader.get()); + BatchedVertexData requestBatchedDraw(const BatchedDrawCommand& command); - // if (this->stencil != state.stencil) - // this->setStencilState(state.stencil); + void flushBatchedDraws(); - // if (this->depthTest != state.depthTest || this->depthWrite != state.depthWrite) - // this->setDepthMode(state.depthTest, state.depthWrite); + static void flushBatchedDrawsGlobal(); - if (state.colorMask != current.colorMask) - this->setColorMask(state.colorMask); + void restoreStateChecked(const DisplayState& state); - // this->setDefaultSamplerState(state.defaultSampleState); + virtual void draw(const DrawCommand& command) = 0; - if (state.useCustomProjection) - this->updateDeviceProjection(state.customProjection); - else if (current.useCustomProjection) - this->resetProjection(); - } + virtual void draw(const DrawIndexedCommand& command) = 0; /* TODO: implement when they exist */ bool isRenderTargetActive() const @@ -302,28 +319,20 @@ namespace love this->states.back().backgroundColor = color; } - void setScissor(const Rect& scissor) + const SamplerState& getDefaultSamplerState() 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); - - state.scissorRect = rect; - state.scissor = true; + return this->states.back().defaultSampleState; } - void setScissor() + void setDefaultSamplerState(const SamplerState& state) { - this->states.back().scissor = false; - static_cast(this)->setScissorImpl(); + this->states.back().defaultSampleState = state; } + void setScissor(const Rect& scissor); + + void setScissor(); + void intersectScissor(const Rect& scissor) { auto current = this->states.back().scissorRect; @@ -369,52 +378,22 @@ namespace love return this->states.back().meshCullMode; } - void setFrontFaceWinding(Winding winding) - { - static_cast(this)->setFrontFaceWindingImpl(winding); - this->states.back().winding = winding; - } - - Winding getFrontFaceWinding() const - { - return this->states.back().winding; - } + void setFrontFaceWinding(Winding winding); - void setColorMask(ColorChannelMask mask) - { - static_cast(this)->setColorMaskImpl(mask); - this->states.back().colorMask = mask; - } + Winding getFrontFaceWinding() const; - ColorChannelMask getColorMask() const - { - return this->states.back().colorMask; - } + void setColorMask(ColorChannelMask mask); - void setBlendMode(BlendMode mode, BlendAlpha alphaMode) - { - if (alphaMode == BLENDALPHA_MULTIPLY && !isAlphaMultiplyBlendSupported(mode)) - { - std::string_view modeName = "unknown"; - love::getConstant(mode, modeName); + ColorChannelMask getColorMask() const; - throw love::Exception("The '{}' blend mode must be used with premultiplied alpha.", - modeName); - } - - this->setBlendState(computeBlendState(mode, alphaMode)); - } + void setBlendMode(BlendMode mode, BlendAlpha alphaMode); 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; - } + void setBlendState(const BlendState& blend); const BlendState& getBlendState() const { @@ -477,14 +456,6 @@ namespace love } } - // bool isPixelFormatSupported(PixelFormat format, uint32_t usage) - // { - // format = getSizedFormat(format); - - // bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; - // return (usage & pixelFormatUsage[format][readable ? 1 : 0]) == usage; - // } - RendererInfo getRendererInfo() const { @@ -496,12 +467,7 @@ namespace love return info; } - Stats getStats() const - { - Stats stats {}; - - return stats; - } + Stats getStats() const; size_t getStackDepth() const { @@ -649,52 +615,6 @@ namespace love this->origin(); } - void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) - { - static_cast(this)->clearImpl(color, stencil, depth); - } - - void clear(const std::vector& colors, OptionalInt stencil, - OptionalDouble depth) - { - if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) - return; - - const int numColors = (int)colors.size(); - - if (numColors <= 1) - { - this->clear(colors.size() > 0 ? colors[0] : OptionalColor(), stencil, depth); - return; - } - } - - void present() - { - static_cast(this)->presentImpl(); - } - - 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); - - this->created = true; - - if (!Volatile::loadAll()) - std::printf("Failed to load all volatile objects.\n"); - - this->restoreState(this->states.back()); - - return true; - } - - void unsetMode() - { - static_cast(this)->unsetModeImpl(); - } - double getCurrentDPIScale() { return 1.0; @@ -751,5 +671,10 @@ namespace love int pixelWidth; int pixelHeight; + + BatchedDrawState batchedDrawState; + + int drawCallsBatched; + int drawCalls; }; } // namespace love diff --git a/include/modules/graphics/Quad.hpp b/include/modules/graphics/Quad.hpp new file mode 100644 index 00000000..c0e1db52 --- /dev/null +++ b/include/modules/graphics/Quad.hpp @@ -0,0 +1,69 @@ +#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; + } + + 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; + }; +} // namespace love diff --git a/include/modules/graphics/Resource.hpp b/include/modules/graphics/Resource.hpp new file mode 100644 index 00000000..a42ee65e --- /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 00000000..dca5c039 --- /dev/null +++ b/include/modules/graphics/Shader.tcc @@ -0,0 +1,85 @@ +#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; + + enum StandardShader + { + STANDARD_DEFAULT, + STANDARD_TEXTURE, + STANDARD_VIDEO, + STANDARD_MAX_ENUM + }; + + static ShaderBase* current; + static ShaderBase* standardShaders[STANDARD_MAX_ENUM]; + + virtual ~ShaderBase() + { + for (int index = 0; index < STANDARD_MAX_ENUM; index++) + { + if (this == standardShaders[index]) + standardShaders[index] = nullptr; + } + + if (current == this) + this->attachDefault(STANDARD_DEFAULT); + } + + struct Uniform + { + uint32_t mdlvMtx; + uint32_t projMtx; + }; + + Uniform getUniforms() const + { + return this->locations; + } + + virtual void attach() = 0; + + static void attachDefault(StandardShader type) + { + auto* defaultShader = standardShaders[type]; + + if (defaultShader == nullptr) + { + current = nullptr; + return; + } + + if (current != defaultShader) + defaultShader->attach(); + } + + static bool isDefaultActive() + { + for (int index = 0; index < STANDARD_MAX_ENUM; index++) + { + if (current == standardShaders[index]) + return true; + } + + return false; + } + + protected: + Uniform locations; + }; +} // namespace love diff --git a/include/modules/graphics/StreamBuffer.tcc b/include/modules/graphics/StreamBuffer.tcc new file mode 100644 index 00000000..52d4fc6a --- /dev/null +++ b/include/modules/graphics/StreamBuffer.tcc @@ -0,0 +1,85 @@ +/** + * 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/int.hpp" + +#include "modules/graphics/Resource.hpp" +#include "modules/graphics/vertex.hpp" + +// C +#include + +namespace love +{ + class StreamBufferBase : public Object, public Resource + { + public: + struct MapInfo + { + uint8_t* data = nullptr; + size_t size = 0; + + MapInfo() + {} + + MapInfo(uint8_t* data, size_t size) : data(data), size(size) + {} + }; + + virtual ~StreamBufferBase() + {} + + size_t getSize() const + { + return bufferSize; + } + + BufferUsage getMode() const + { + return mode; + } + + size_t getUsableSize() const + { + return bufferSize - frameGPUReadOffset; + } + + virtual size_t getGPUReadOffset() const = 0; + + virtual MapInfo map(size_t minsize) = 0; + virtual size_t unmap(size_t usedsize) = 0; + virtual void markUsed(size_t usedsize) = 0; + + virtual void nextFrame() + {} + + protected: + StreamBufferBase(BufferUsage mode, size_t size); + + size_t bufferSize; + size_t frameGPUReadOffset; + BufferUsage mode; + + }; // StreamBuffer +} // namespace love diff --git a/include/modules/graphics/Texture.tcc b/include/modules/graphics/Texture.tcc new file mode 100644 index 00000000..0902578c --- /dev/null +++ b/include/modules/graphics/Texture.tcc @@ -0,0 +1,377 @@ +#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 Buffer; + + 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 draw(Graphics& graphics, const Matrix4& matrix) = 0; + + virtual void draw(Graphics& graphics, Quad* quad, const Matrix4& matrix) = 0; + + bool isCompressed() const + { + return love::isPixelFormatCompressed(this->format); + } + + /* TODO: make the wrapper also check isGammaCorrect */ + bool isFormatLinear() const + { + return !isPixelFormatSRGB(this->format); + } + + 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; + } + + 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); + + 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/samplerstate.hpp b/include/modules/graphics/samplerstate.hpp new file mode 100644 index 00000000..b7152435 --- /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 23b2be53..9f52aa27 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,214 @@ 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_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 STPf_RGBAf + { + 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 Vertex + struct XYf_STPf_RGBAf { - Vector3 position; - Vector2 texcoord; + float x, y; + float s, t, p; Color color; }; + using Vertex = XYf_STf_RGBAf; + inline CommonFormat getSinglePositionFormat(bool is2D) { - return is2D ? XYf : XYZf; + return is2D ? CommonFormat::XYf : CommonFormat::XYf; } + 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; + } info[MAX]; + + void set(uint32_t index, Resource* r, size_t offset) + { + useBits |= (1u << index); + info[index] = { r, offset }; + } + + 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; + }; + // clang-format off STRINGMAP_DECLARE(PrimitiveTypes, PrimitiveType, { "triangles", PRIMITIVE_TRIANGLES }, diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index d0d5e27e..9d07fe51 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -149,6 +149,14 @@ namespace Wrap_Graphics int inverseTransformPoint(lua_State* L); + int newTexture(lua_State* L); + + int newImage(lua_State* L); + + int newCanvas(lua_State* L); + + int draw(lua_State* L); + int getScreens(lua_State* L); int setActiveScreen(lua_State* L); diff --git a/include/modules/graphics/wrap_Texture.hpp b/include/modules/graphics/wrap_Texture.hpp new file mode 100644 index 00000000..8d6aaac2 --- /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 00000000..a4dcf61a --- /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 00000000..64ca6c45 --- /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 00000000..88dee921 --- /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 00000000..c1b6d4b0 --- /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 00000000..24e5540a --- /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 00000000..729ce52d --- /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 00000000..519084d9 --- /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 00000000..9e4a2700 --- /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 00000000..55cc7d71 --- /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 00000000..281603c9 --- /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 00000000..0895e3e7 --- /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 00000000..fe84df9c --- /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 00000000..ac1d5aef --- /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 00000000..5e2906f4 --- /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 00000000..0a4e7b31 --- /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 00000000..1a406055 --- /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/window/Window.tcc b/include/modules/window/Window.tcc index 9656c2ec..8d181b96 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 @@ -297,7 +297,7 @@ namespace love // clang-format on protected: - void close(bool allowExceptions) + void close(bool) { this->open = false; } diff --git a/platform/ctr/CMakeLists.txt b/platform/ctr/CMakeLists.txt index 83a76dc4..bee8c1ff 100644 --- a/platform/ctr/CMakeLists.txt +++ b/platform/ctr/CMakeLists.txt @@ -43,6 +43,9 @@ source/driver/display/Renderer.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp source/modules/graphics/Graphics.cpp +source/modules/graphics/StreamBuffer.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/ctr/include/boot.hpp b/platform/ctr/include/boot.hpp index 64904c5d..d1c901f6 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/Renderer.hpp b/platform/ctr/include/driver/display/Renderer.hpp index dff5e823..3c4b41c3 100644 --- a/platform/ctr/include/driver/display/Renderer.hpp +++ b/platform/ctr/include/driver/display/Renderer.hpp @@ -8,7 +8,8 @@ #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" namespace love { @@ -41,6 +42,8 @@ namespace love void setVertexWinding(Winding winding); + void setSamplerState(C3D_Tex* texture, SamplerState state); + void setWideMode(bool wide) { this->modeChanged([this, wide]() { gfxSetWide(wide); }); @@ -69,10 +72,11 @@ namespace love ); 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 } ); ENUMMAP_DECLARE(CullModes, CullMode, GPU_CULLMODE, @@ -102,9 +106,18 @@ namespace love { BLENDFACTOR_ONE_MINUS_DST_ALPHA, GPU_ONE_MINUS_DST_ALPHA }, { BLENDFACTOR_SRC_ALPHA_SATURATED, GPU_SRC_ALPHA_SATURATE } ); + + ENUMMAP_DECLARE(IndexDataTypes, IndexDataType, decltype(C3D_UNSIGNED_BYTE), + { INDEX_UINT16, C3D_UNSIGNED_SHORT } + ); // clang-format on private: + static GPU_TEXTURE_WRAP_PARAM getWrapMode(SamplerState::WrapMode mode); + + static constexpr int MAX_OBJECTS = 0x1000; + static constexpr int VERTEX_BUFFER_SIZE = 6 * MAX_OBJECTS; // 6 vertices per object + void ensureInFrame(); void createFramebuffers(); @@ -123,6 +136,7 @@ namespace love { C3D_Mtx modelView; C3D_Mtx projection; + Framebuffer target; } context; diff --git a/platform/ctr/include/driver/display/citro3d/VertexRing.hpp b/platform/ctr/include/driver/display/citro3d/VertexRing.hpp new file mode 100644 index 00000000..598c5dc9 --- /dev/null +++ b/platform/ctr/include/driver/display/citro3d/VertexRing.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include <3ds.h> + +namespace love +{ + class VertexRing + { + public: + VertexRing() : memory(nullptr), sliceSize(0), currentSlice(0), sliceCount(0) + {} + + VertexRing(const VertexRing&) = delete; + + VertexRing& operator=(const VertexRing&) = delete; + + ~VertexRing() + { + linearFree(this->memory); + } + + bool allocate(size_t numSlices, size_t size) + { + if (this->memory) + linearFree(this->memory); + + this->memory = linearAlloc(size * numSlices); + + if (this->memory == nullptr) + return false; + + this->sliceSize = size; + this->sliceCount = numSlices; + + return true; + } + + size_t getSize() + { + return this->sliceSize * this->sliceCount; + } + + void* getBuffer() const + { + return this->memory; + } + + void* begin() const + { + const auto offset = this->currentSlice * this->sliceSize; + return (void*)((char*)this->memory + offset); + } + + void end() + { + this->currentSlice = (this->currentSlice + 1) % this->sliceCount; + } + + private: + void* memory; + size_t sliceSize; + + size_t currentSlice; + size_t sliceCount; + }; +} // namespace love diff --git a/platform/ctr/include/modules/graphics/Graphics.hpp b/platform/ctr/include/modules/graphics/Graphics.hpp index 4ead2d97..c555e96e 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -1,41 +1,58 @@ #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); + void clear(OptionalColor color, OptionalInt depth, OptionalDouble stencil); - using GraphicsBase::clear; + void clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth); - void presentImpl(); + void present(); - void setScissorImpl(const Rect& scissor); + void setScissor(const Rect& scissor); - void setScissorImpl(); + void setScissor(); - void setFrontFaceWindingImpl(Winding winding); + void setFrontFaceWinding(Winding winding); - void setColorMaskImpl(ColorChannelMask mask); + void setColorMask(ColorChannelMask mask); - void setBlendStateImpl(const BlendState& state); + void setBlendState(const BlendState& state); - void getRendererInfoImpl(RendererInfo& info) const; + bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa); - bool setModeImpl(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa); + void draw(Drawable* drawable, const Matrix4& matrix); + + void draw(TextureBase* texture, Quad* quad, const Matrix4& matrix); + + virtual void draw(const DrawCommand& command) override; + + virtual void draw(const DrawIndexedCommand& command) override; bool isActive() const; - void unsetModeImpl(); + void unsetMode(); void setViewport(int x, int y, int width, int height); + Texture* newTexture(const Texture::Settings& settings, const Texture::Slices* data = nullptr); + + bool isPixelFormatSupported(PixelFormat format, uint32_t usage); + + /* TODO: implement properly */ + bool isRenderTargetActive(Texture* texture) + { + return false; + } + 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 00000000..0d1a595a --- /dev/null +++ b/platform/ctr/include/modules/graphics/Shader.hpp @@ -0,0 +1,45 @@ +#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: + Shader(); + + virtual ~Shader(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + void attach() override; + + ptrdiff_t getHandle() const override; + + private: + struct TextureUnit + { + C3D_Tex* texture = nullptr; + TextureType type = TextureType::TEXTURE_2D; + bool isTexelBuffer = false; + bool active = false; + }; + + bool validate(const char* filepath, std::string& error); + + DVLB_s* dvlb = nullptr; + shaderProgram_s program; + + std::vector data; + std::vector textureUnits; + + Uniform locations; + }; +} // namespace love diff --git a/platform/ctr/include/modules/graphics/StreamBuffer.hpp b/platform/ctr/include/modules/graphics/StreamBuffer.hpp new file mode 100644 index 00000000..6cc41578 --- /dev/null +++ b/platform/ctr/include/modules/graphics/StreamBuffer.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "modules/graphics/StreamBuffer.tcc" + +namespace love +{ + StreamBufferBase* createStreamBuffer(BufferUsage usage, size_t size); +} diff --git a/platform/ctr/include/modules/graphics/Texture.hpp b/platform/ctr/include/modules/graphics/Texture.hpp new file mode 100644 index 00000000..f99a8b3c --- /dev/null +++ b/platform/ctr/include/modules/graphics/Texture.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/Volatile.hpp" + +#include + +namespace love +{ + class Texture final : public TextureBase, public Volatile + { + public: + Texture(Graphics& graphics, const Settings& settings, const Slices* data); + + virtual ~Texture(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + ptrdiff_t getHandle() const override; + + void draw(Graphics& graphics, const Matrix4& matrix); + + void draw(Graphics& graphics, Quad* quad, const Matrix4& matrix); + + void setSamplerState(const SamplerState& state); + + SamplerState validateSamplerState(SamplerState state) const; + + 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/source/driver/display/Renderer.cpp b/platform/ctr/source/driver/display/Renderer.cpp index 0681fa24..f336be40 100644 --- a/platform/ctr/source/driver/display/Renderer.cpp +++ b/platform/ctr/source/driver/display/Renderer.cpp @@ -1,6 +1,7 @@ #include "common/screen.hpp" #include "driver/display/Renderer.hpp" +#include "modules/graphics/Shader.hpp" namespace love { @@ -22,6 +23,13 @@ namespace love 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, 3); + AttrInfo_AddLoader(attributes, 1, GPU_FLOAT, 4); + AttrInfo_AddLoader(attributes, 2, GPU_FLOAT, 2); + Mtx_Identity(&this->context.modelView); Mtx_Identity(&this->context.projection); @@ -40,9 +48,10 @@ namespace love void Renderer::createFramebuffers() { - const auto& info = getScreenInfo(); + const auto& info = getScreenInfo(); + const int numFramebuffers = info.size(); - for (size_t index = 0; index < info.size(); ++index) + for (size_t index = 0; index < numFramebuffers; ++index) { Framebuffer target {}; target.create(info[index]); @@ -85,6 +94,19 @@ namespace love C3D_FrameDrawOn(this->context.target.get()); this->setViewport(viewport, this->context.target.get()->linked); + + if (this->context.dirtyProjection) + { + if (Shader::current != nullptr) + { + auto uniforms = Shader::current->getUniforms(); + + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uniforms.projMtx, &this->context.projection); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uniforms.mdlvMtx, &this->context.modelView); + } + + this->context.dirtyProjection = false; + } } void Renderer::present() @@ -96,6 +118,9 @@ namespace love } } + // void Renderer::render(BatchedDrawCommand& command) + // {} + void Renderer::setViewport(const Rect& viewport, bool tilt) { if (this->context.viewport == viewport) @@ -147,7 +172,7 @@ namespace love this->context.cullMode = mode; } - void Renderer::setVertexWinding(Winding winding) + void Renderer::setVertexWinding(Winding) {} void Renderer::setColorMask(ColorChannelMask mask) @@ -194,4 +219,32 @@ namespace love C3D_AlphaBlend(operationRGB, operationA, sourceColor, destColor, sourceAlpha, destAlpha); this->context.blendState = state; } + + void Renderer::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 = Renderer::getWrapMode(state.wrapU); + auto wrapV = Renderer::getWrapMode(state.wrapV); + + C3D_TexSetWrap(texture, wrapU, wrapV); + } + + GPU_TEXTURE_WRAP_PARAM Renderer::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; + } + } } // namespace love diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index 2692c0e4..696d3dda 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 "modules/graphics/Graphics.hpp" +#include "modules/graphics/Shader.hpp" #include "modules/window/Window.hpp" +#define BUFFER_OFFSET(i) ((char*)NULL + (i)) + namespace love { Graphics::Graphics() : GraphicsBase("love.graphics.citro3d") @@ -24,7 +27,7 @@ namespace love } } - void Graphics::clearImpl(OptionalColor color, OptionalInt depth, OptionalDouble stencil) + void Graphics::clear(OptionalColor color, OptionalInt depth, OptionalDouble stencil) { Renderer::getInstance().bindFramebuffer(); @@ -38,45 +41,106 @@ namespace love Renderer::getInstance().clearDepthStencil(stencil.value, 0xFF, depth.value); } - void Graphics::presentImpl() + void Graphics::clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) { + if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) + return; + + const int numColors = (int)colors.size(); + + if (numColors <= 1) + { + this->clear(colors.size() > 0 ? colors[0] : OptionalColor(), stencil, depth); + return; + } + + this->flushBatchedDraws(); + } + + void Graphics::present() + { + if (!this->isActive()) + return; + + this->flushBatchedDraws(); Renderer::getInstance().present(); + + this->drawCalls = 0; + this->drawCallsBatched = 0; } - void Graphics::setScissorImpl(const Rect& scissor) + void Graphics::setScissor(const Rect& scissor) { + GraphicsBase::setScissor(scissor); Renderer::getInstance().setScissor(scissor); } - void Graphics::setScissorImpl() + void Graphics::setScissor() { + GraphicsBase::setScissor(); Renderer::getInstance().setScissor(Rect::EMPTY); } - void Graphics::setFrontFaceWindingImpl(Winding winding) + void Graphics::setFrontFaceWinding(Winding winding) { + GraphicsBase::setFrontFaceWinding(winding); Renderer::getInstance().setVertexWinding(winding); } - void Graphics::setColorMaskImpl(ColorChannelMask mask) + void Graphics::setColorMask(ColorChannelMask mask) { + GraphicsBase::setColorMask(mask); Renderer::getInstance().setColorMask(mask); } - void Graphics::setBlendStateImpl(const BlendState& state) + void Graphics::setBlendState(const BlendState& state) { + GraphicsBase::setBlendState(state); Renderer::getInstance().setBlendState(state); } - 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(); + this->created = true; + + if (!Volatile::loadAll()) + std::printf("Failed to load all volatile objects.\n"); + + // clang-format off + if (this->batchedDrawState.vb[0] == nullptr) + { + this->batchedDrawState.vb[0] = createStreamBuffer(BUFFERUSAGE_VERTEX, 1024 * 1024 * 1); + this->batchedDrawState.vb[1] = createStreamBuffer(BUFFERUSAGE_VERTEX, 256 * 1024 * 1); + this->batchedDrawState.indexBuffer = createStreamBuffer(BUFFERUSAGE_INDEX, sizeof(uint16_t) * LOVE_UINT16_MAX); + } + // clang-format on + + 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() {} bool Graphics::isActive() const @@ -90,6 +154,96 @@ namespace love Renderer::getInstance().setViewport({ x, y, width, height }, true); } + Texture* Graphics::newTexture(const Texture::Settings& settings, const Texture::Slices* data) + { + return new Texture(*this, settings, data); + } + + void Graphics::draw(Drawable* drawable, const Matrix4& matrix) + { + drawable->draw(*this, matrix); + } + + void Graphics::draw(TextureBase* texture, Quad* quad, const Matrix4& matrix) + { + texture->draw(*this, quad, matrix); + } + + bool Graphics::isPixelFormatSupported(PixelFormat format, uint32_t usage) + { + format = this->getSizedFormat(format); + bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; + + GPU_TEXCOLOR color; + bool supported = Renderer::getConstant(format, color); + + return readable && supported; + } + + void Graphics::draw(const DrawCommand& command) + { + // C3D_SetBufInfo((C3D_BufInfo*)command->getHandle()); + + // auto* env = C3D_GetTexEnv(0); + // C3D_TexEnvInit(env); + + // if (command.texture == nullptr) + // { + // C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + // C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + // } + // else + // { + // C3D_TexBind(0, (C3D_Tex*)command.texture->getHandle()); + // C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + // C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + // } + + // GPU_Primitive_t primitive; + // if (!Renderer::getConstant(command.primitiveType, primitive)) + // throw love::Exception("Invalid primitive type: {:d}", (int)command.primitiveType); + + // C3D_DrawArrays(primitive, command.vertexStart, command.vertexCount); + + // ++this->drawCalls; + } + + void Graphics::draw(const DrawIndexedCommand& command) + { + std::printf("Binding C3D_BufInfo\n"); + C3D_SetBufInfo((C3D_BufInfo*)command.indexBuffer->getHandle()); + std::printf("Resetting TexEnv\n"); + auto* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + if (command.texture == nullptr) + { + std::printf("Setting up texture environment for no texture\n"); + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + } + else + { + std::printf("Setting up texture environment for texture\n"); + C3D_TexBind(0, (C3D_Tex*)command.texture->getHandle()); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + } + + GPU_Primitive_t primitive; + if (!Renderer::getConstant(command.primitiveType, primitive)) + throw love::Exception("Invalid primitive type: {:d}", (int)command.primitiveType); + + decltype(C3D_UNSIGNED_BYTE) type; + if (!Renderer::getConstant(command.indexType, type)) + throw love::Exception("Invalid index type: {:d}", (int)command.indexType); + std::printf("Drawing elements\n"); + const void* indices = BUFFER_OFFSET(command.indexBufferOffset); + C3D_DrawElements(primitive, command.indexCount, type, indices); + + ++this->drawCalls; + } + bool Graphics::is3D() const { return gfxIs3D(); diff --git a/platform/ctr/source/modules/graphics/Shader.cpp b/platform/ctr/source/modules/graphics/Shader.cpp new file mode 100644 index 00000000..9ff7ab1f --- /dev/null +++ b/platform/ctr/source/modules/graphics/Shader.cpp @@ -0,0 +1,106 @@ +#include "modules/graphics/Shader.hpp" +#include "driver/display/Renderer.hpp" + +#define SHADERS_DIR "romfs:/shaders/" + +#define DEFAULT_SHADER (SHADERS_DIR "main_v_pica.shbin") + +namespace love +{ + Shader::Shader() : locations {} + { + 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->locations.mdlvMtx = shaderInstanceGetUniformLocation(this->program.vertexShader, "mdlvMtx"); + this->locations.projMtx = shaderInstanceGetUniformLocation(this->program.vertexShader, "projMtx"); + + return true; + } + + void Shader::unloadVolatile() + { + shaderProgramFree(&this->program); + DVLB_Free(this->dvlb); + } + + void Shader::attach() + { + if (current == this) + return; + + 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/StreamBuffer.cpp b/platform/ctr/source/modules/graphics/StreamBuffer.cpp new file mode 100644 index 00000000..cac3b13c --- /dev/null +++ b/platform/ctr/source/modules/graphics/StreamBuffer.cpp @@ -0,0 +1,70 @@ +#include "common/Exception.hpp" + +#include "modules/graphics/StreamBuffer.hpp" + +#include + +namespace love +{ + class StreamBufferClientMemory final : public StreamBufferBase + { + public: + StreamBufferClientMemory(BufferUsage mode, size_t size) : + StreamBufferBase(mode, size), + data(nullptr), + vbo(new C3D_BufInfo()) + { + this->data = (uint8_t*)linearAlloc(size); + + if (this->data == nullptr) + throw love::Exception("Failed to allocate memory for StreamBufferClientMemory"); + + BufInfo_Init(this->vbo); + + int result = BufInfo_Add(this->vbo, this->data, sizeof(Vertex), 3, 0x210); + + if (result != 0) + throw love::Exception("Failed to create StreamBufferClientMemory"); + } + + virtual ~StreamBufferClientMemory() + { + delete this->vbo; + this->vbo = nullptr; + + linearFree(this->data); + } + + size_t getGPUReadOffset() const override + { + return (size_t)this->data; + } + + MapInfo map(size_t) override + { + return MapInfo(this->data, this->bufferSize); + } + + size_t unmap(size_t) override + { + return (size_t)this->data; + } + + void markUsed(size_t) override + {} + + ptrdiff_t getHandle() const override + { + return (ptrdiff_t)this->vbo; + } + + private: + uint8_t* data; + C3D_BufInfo* vbo; + }; + + StreamBufferBase* createStreamBuffer(BufferUsage usage, size_t size) + { + return new StreamBufferClientMemory(usage, size); + } +} // 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 00000000..77038339 --- /dev/null +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -0,0 +1,283 @@ +#include "modules/graphics/Texture.hpp" +#include "modules/graphics/Graphics.hpp" + +#include "driver/display/Renderer.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!"); + + target = C3D_RenderTargetCreateFromTex(texture, GPU_TEXFACE_2D, 0, GPU_RB_DEPTH16); + } + + static void createTextureObject(C3D_Tex*& texture, PixelFormat format, uint16_t width, uint16_t height, + bool clear) + { + GPU_TEXCOLOR gpuFormat; + if (!love::Renderer::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(Graphics& graphics, const Settings& settings, const Slices* data) : + TextureBase(graphics, settings, data), + slices(settings.type) + { + this->validateDimensions(true); + + this->validatePixelFormat(graphics); + + 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); + } + + void Texture::createTexture() + { + const bool hasData = this->slices.get(0, 0) != nullptr; + const bool clear = !hasData; + + const auto powTwoWidth = NextPo2(this->pixelWidth); + const auto powTwoHeight = NextPo2(this->pixelHeight); + + if (this->isRenderTarget()) + createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight, clear); + else + { + createTextureObject(this->texture, this->format, powTwoWidth, powTwoHeight, clear); + + const auto size = love::getPixelFormatSliceSize(this->format, powTwoWidth, powTwoHeight, false); + + if (!hasData) + std::memset(this->texture->data, 0, size); + else + std::memcpy(this->texture->data, this->slices.get(0, 0)->getData(), size); + + C3D_TexFlush(this->texture); + } + + this->setSamplerState(this->samplerState); + } + + 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) + { + for (size_t index = 0; index < 4; index++) + quad->setTextureCoordinate(index, { textureCoords[index].x, 1.0f - textureCoords[index].y }); + + return; + } + + 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)); + } + + void Texture::draw(Graphics& graphics, const Matrix4& matrix) + { + this->draw(graphics, this->quad, matrix); + } + + void Texture::draw(Graphics& 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& 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); + + const auto& transform = graphics.getTransform(); + bool is2D = transform.isAffine2DTransform(); + + Graphics::BatchedDrawCommand command {}; + command.formats[0] = getSinglePositionFormat(is2D); + command.formats[1] = CommonFormat::STf_RGBAf; + command.indexMode = TRIANGLEINDEX_QUADS; + command.vertexCount = 4; + command.texture = this; + + Graphics::BatchedVertexData data = graphics.requestBatchedDraw(command); + + Matrix4 translated(transform, matrix); + + if (is2D) + translated.transformXY((Vector2*)data.stream[0], quad->getVertexPositions(), 4); + else + translated.transformXY0((Vector3*)data.stream[0], quad->getVertexPositions(), 4); + + const auto* texCoords = quad->getTextureCoordinates(); + STf_RGBAf* vertices = (STf_RGBAf*)data.stream[1]; + + for (int index = 0; index < 4; index++) + { + vertices[index].s = texCoords[index].x; + vertices[index].t = texCoords[index].y; + vertices[index].color = graphics.getColor(); + } + } + + void Texture::setSamplerState(const SamplerState& state) + { + this->samplerState = this->validateSamplerState(state); + Renderer::getInstance().setSamplerState(this->texture, this->samplerState); + } + + bool Texture::validateDimensions(bool throwException) const + { + bool success = true; + + int largestSize = 0; + const char* name = nullptr; + + const bool widthIsLarge = ((size_t)this->pixelWidth > LOVE_TEX3DS_MAX); + const bool heightIsLarge = ((size_t)this->pixelHeight > LOVE_TEX3DS_MAX); + + // clang-format off + if ((this->textureType == TEXTURE_2D || this->textureType == TEXTURE_2D_ARRAY) && (widthIsLarge || heightIsLarge)) + { + success = false; + largestSize = std::max(this->pixelWidth, this->pixelHeight); + name = this->pixelWidth > this->pixelHeight ? "pixel width" : "pixel height"; + } + + if (throwException && name != nullptr) + throw love::Exception("Cannot create texture: {:s} of {:d} is too large for this system.", name, largestSize); + // clang-format on + + return success; + } + + SamplerState Texture::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; + + return state; + } + + void Texture::validatePixelFormat(Graphics& graphics) const + { + uint32_t usage = PIXELFORMATUSAGEFLAGS_NONE; + + if (renderTarget) + usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; + if (readable) + usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; + if (computeWrite) + usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; + + if (!graphics.isPixelFormatSupported(format, (PixelFormatUsageFlags)usage)) + { + std::string_view name = "unknown"; + love::getConstant(format, name); + + throw love::Exception("The pixel format '{:s}' is not supported by this system.", name); + } + } + + ptrdiff_t Texture::getHandle() const + { + return (ptrdiff_t)this->texture; + } +} // namespace love diff --git a/source/common/float.cpp b/source/common/float.cpp new file mode 100644 index 00000000..9b07078f --- /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 cc8245a3..20a854eb 100644 --- a/source/common/luax.cpp +++ b/source/common/luax.cpp @@ -617,6 +617,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 +652,25 @@ namespace love return result; } + int luax_checkintflag(lua_State* L, int tableIndex, const char* key) + { + lua_getfield(L, tableIndex, key); + + int result; + + 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); diff --git a/source/common/pixelformat.cpp b/source/common/pixelformat.cpp index 5af2e6ed..9887980a 100644 --- a/source/common/pixelformat.cpp +++ b/source/common/pixelformat.cpp @@ -136,129 +136,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/modules/filesystem/wrap_Filesystem.cpp b/source/modules/filesystem/wrap_Filesystem.cpp index 9e76fc75..553077ba 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/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp new file mode 100644 index 00000000..ade2e6a6 --- /dev/null +++ b/source/modules/graphics/Graphics.cpp @@ -0,0 +1,424 @@ +#include "modules/graphics/Graphics.tcc" + +namespace love +{ + GraphicsBase::GraphicsBase(const char* name) : + Module(M_GRAPHICS, name), + width(0), + height(0), + pixelWidth(0), + pixelHeight(0), + created(false), + active(true), + deviceProjectionMatrix(), + batchedDrawState() + { + 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() + + { + this->states.clear(); + + if (this->batchedDrawState.vb[0]) + this->batchedDrawState.vb[0]->release(); + + if (this->batchedDrawState.vb[1]) + this->batchedDrawState.vb[1]->release(); + + if (this->batchedDrawState.indexBuffer) + this->batchedDrawState.indexBuffer->release(); + } + + 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.defaultSampleState); + + 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.defaultSampleState); + + if (state.useCustomProjection) + this->updateDeviceProjection(state.customProjection); + else if (current.useCustomProjection) + this->resetProjection(); + } + + GraphicsBase::BatchedVertexData GraphicsBase::requestBatchedDraw( + const GraphicsBase::BatchedDrawCommand& command) + { + BatchedDrawState& state = this->batchedDrawState; + + bool shouldFlush = false; + bool shouldResize = false; + + if (command.primitiveMode != state.primitiveMode || command.formats[0] != state.formats[0] || + command.formats[1] != state.formats[1] || + ((command.indexMode != TRIANGLEINDEX_NONE) != (state.indexCount > 0)) || + command.texture != state.texture || command.shaderType != state.shaderType) + { + shouldFlush = true; + } + + 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 requestedSize = requestedIndexCount * sizeof(uint16_t); + + size_t newDataSizes[2] = { 0, 0 }; //< position, vertices + size_t bufferSizes[3] = { 0, 0, 0 }; + + for (int index = 0; index < 2; index++) + { + if (command.formats[index] == CommonFormat::NONE) + continue; + + size_t stride = getFormatStride(command.formats[index]); + size_t dataSize = stride * totalVertices; + + if (state.vbMap[index].data != nullptr && dataSize > state.vbMap[index].size) + shouldFlush = true; + + if (dataSize > state.vb[index]->getUsableSize()) + { + bufferSizes[index] = std::max(dataSize, state.vb[index]->getSize() * 2); + shouldResize = true; + } + + newDataSizes[index] = 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[2] = std::max(dataSize, state.indexBuffer->getSize() * 2); + shouldResize = true; + } + } + + if (shouldFlush || shouldResize) + { + flushBatchedDraws(); + + state.primitiveMode = command.primitiveMode; + state.formats[0] = command.formats[0]; + state.formats[1] = command.formats[1]; + state.texture = command.texture; + state.shaderType = command.shaderType; + } + + if (state.vertexCount == 0) + { + if (ShaderBase::isDefaultActive()) + ShaderBase::attachDefault(state.shaderType); + } + + if (shouldResize) + { + for (int index = 0; index < 2; index++) + { + if (state.vb[index]->getSize() < bufferSizes[index]) + { + state.vb[index]->release(); + state.vb[index] = createStreamBuffer(BUFFERUSAGE_VERTEX, bufferSizes[index]); + } + } + + if (state.indexBuffer->getSize() < bufferSizes[2]) + { + state.indexBuffer->release(); + state.indexBuffer = createStreamBuffer(BUFFERUSAGE_INDEX, bufferSizes[2]); + } + } + + if (command.indexMode != TRIANGLEINDEX_NONE) + { + if (state.indexBufferMap.data == nullptr) + state.indexBufferMap = state.indexBuffer->map(requestedSize); + + uint16_t* indices = (uint16_t*)state.indexBufferMap.data; + fillIndices(command.indexMode, state.vertexCount, command.vertexCount, indices); + + state.indexBufferMap.data += requestedSize; + } + + BatchedVertexData data {}; + + for (int index = 0; index < 2; index++) + { + if (newDataSizes[index] > 0) + { + if (state.vbMap[index].data == nullptr) + state.vbMap[index] = state.vb[index]->map(newDataSizes[index]); + + data.stream[index] = state.vbMap[index].data; + state.vbMap[index].data += newDataSizes[index]; + } + } + + if (state.vertexCount > 0) + this->drawCallsBatched++; + + state.vertexCount += command.vertexCount; + state.indexCount += requestedIndexCount; + + return data; + } + + void GraphicsBase::flushBatchedDraws() + { + BatchedDrawState& state = this->batchedDrawState; + + if ((state.vertexCount == 0 && state.indexCount == 0) || state.flushing) + return; + + VertexAttributes attributes {}; + BufferBindings buffers {}; + + size_t usedSizes[3] = { 0, 0, 0 }; + + for (int index = 0; index < 2; index++) + { + if (state.formats[index] == CommonFormat::NONE) + continue; + + attributes.setCommonFormat(state.formats[index], (uint8_t)index); + + usedSizes[index] = getFormatStride(state.formats[index]) * state.vertexCount; + + size_t offset = state.vb[index]->unmap(usedSizes[index]); + buffers.set(index, state.vb[index], offset); + + state.vbMap[index] = StreamBufferBase::MapInfo(); + } + + if (attributes.enableBits == 0) + return; + + state.flushing = true; + + Color newColor = this->getColor(); + if (attributes.isEnabled(ATTRIB_COLOR)) + this->setColor(Color::WHITE); + + this->pushIdentityTransform(); + + if (state.indexCount > 0) + { + usedSizes[2] = sizeof(uint16_t) * state.indexCount; + + DrawIndexedCommand command(&attributes, &buffers, state.indexBuffer); + command.primitiveType = state.primitiveMode; + command.indexCount = state.indexCount; + command.indexType = INDEX_UINT16; + command.indexBufferOffset = state.indexBuffer->unmap(usedSizes[2]); + command.texture = nullptr; + + this->draw(command); + state.indexBufferMap = StreamBufferBase::MapInfo(); + } + else + { + DrawCommand command(&attributes, &buffers); + command.primitiveType = state.primitiveMode; + command.vertexCount = 0; + command.vertexStart = state.vertexCount; + command.texture = state.texture; + + this->draw(command); + } + + for (int index = 0; index < 2; index++) + { + if (usedSizes[index] > 0) + state.vb[index]->markUsed(usedSizes[index]); + } + + if (usedSizes[2] > 0) + state.indexBuffer->markUsed(usedSizes[2]); + + this->popTransform(); + + if (attributes.isEnabled(ATTRIB_COLOR)) + this->setColor(newColor); + + state.vertexCount = 0; + state.indexCount = 0; + state.flushing = false; + } + + void GraphicsBase::flushBatchedDrawsGlobal() + { + auto* instance = getInstance(M_GRAPHICS); + + if (instance != nullptr) + instance->flushBatchedDraws(); + } + + void GraphicsBase::setScissor(const Rect& scissor) + { + 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() + { + this->states.back().scissor = false; + } + + 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; + + return stats; + } + + void GraphicsBase::setFrontFaceWinding(Winding winding) + { + this->states.back().winding = winding; + } + + Winding GraphicsBase::getFrontFaceWinding() const + { + return this->states.back().winding; + } + + void GraphicsBase::setColorMask(ColorChannelMask mask) + { + 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) + { + this->states.back().blend = state; + } +} // namespace love diff --git a/source/modules/graphics/Quad.cpp b/source/modules/graphics/Quad.cpp new file mode 100644 index 00000000..3db97465 --- /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& viewport, double sourceWidth, double sourceHeight) + { + this->viewport = viewport; + this->sourceWidth = sourceWidth; + this->sourceHeight = sourceHeight; + + this->vertexPositions[0] = Vector2(0.0f, 0.0f); + this->vertexPositions[1] = Vector2(0.0f, viewport.h); + this->vertexPositions[2] = Vector2(viewport.w, viewport.h); + this->vertexPositions[3] = Vector2(viewport.w, 0.0f); + + // clang-format off + this->textureCoordinates[0] = Vector2(viewport.x / sourceWidth, viewport.y / sourceHeight); + this->textureCoordinates[1] = Vector2(viewport.x / sourceWidth, (viewport.y + viewport.h) / sourceHeight); + this->textureCoordinates[2] = Vector2((viewport.x + viewport.w) / sourceWidth, (viewport.y + viewport.h) / sourceHeight); + this->textureCoordinates[3] = Vector2((viewport.x + viewport.w) / sourceWidth, viewport.y / 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 00000000..1f557ec4 --- /dev/null +++ b/source/modules/graphics/Shader.cpp @@ -0,0 +1,7 @@ +#include "modules/graphics/Shader.tcc" + +namespace love +{ + ShaderBase* ShaderBase::current = nullptr; + ShaderBase* ShaderBase::standardShaders[STANDARD_MAX_ENUM] = { nullptr }; +} // namespace love diff --git a/source/modules/graphics/StreamBuffer.cpp b/source/modules/graphics/StreamBuffer.cpp new file mode 100644 index 00000000..687fdaa0 --- /dev/null +++ b/source/modules/graphics/StreamBuffer.cpp @@ -0,0 +1,32 @@ +/** + * 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/Exception.hpp" + +#include "modules/graphics/StreamBuffer.tcc" + +namespace love +{ + StreamBufferBase::StreamBufferBase(BufferUsage mode, size_t size) : + bufferSize(size), + frameGPUReadOffset(0), + mode(mode) + {} +} // namespace love diff --git a/source/modules/graphics/Texture.cpp b/source/modules/graphics/Texture.cpp new file mode 100644 index 00000000..71d8046d --- /dev/null +++ b/source/modules/graphics/Texture.cpp @@ -0,0 +1,343 @@ +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/Graphics.tcc" + +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 } + { + 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_NONE; + + 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; + + if (this->mipmapCount != totalMipmapCount) + throw love::Exception("Custom mipmap ranges for a texture are not supported."); + } + + 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."); + + this->validateViewFormats(); + + 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::validateViewFormats() const + {} + + 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/samplerstate.cpp b/source/modules/graphics/samplerstate.cpp new file mode 100644 index 00000000..09a2d025 --- /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 00000000..ec2ed52d --- /dev/null +++ b/source/modules/graphics/vertex.cpp @@ -0,0 +1,256 @@ +#include "modules/graphics/vertex.hpp" + +namespace love +{ + 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---3 + // | \ | + // 1---2 + 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 + 3; + indices[ii + 5] = vi + 0; + } + } + 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_UNORM8_VEC4, uint16_t(sizeof(float) * 2), bufferindex); + break; + case CommonFormat::STPf_RGBAf: + set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, 0, bufferindex); + set(ATTRIB_COLOR, DATAFORMAT_UNORM8_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_UNORM8_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_UNORM8_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_UNORM8_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]; + } +} // namespace love diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 584e6a3c..fe28e449 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -2,6 +2,16 @@ #include "modules/graphics/wrap_Graphics.hpp" +#include "modules/filesystem/wrap_Filesystem.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 +24,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(); @@ -547,8 +557,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 +674,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 +731,309 @@ 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; + 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::newImage(lua_State* L) +{ + return newTexture(L); +} + +int Wrap_Graphics::draw(lua_State* L) +{ + Drawable* drawable = nullptr; + Texture* 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::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.textures); + lua_setfield(L, -2, "textures"); + + lua_pushnumber(L, (lua_Number)stats.textureMemory); + lua_setfield(L, -2, "texturememory"); + + return 1; +} + // Homebrew Stuff™ int Wrap_Graphics::getScreens(lua_State* L) @@ -846,10 +1158,27 @@ static constexpr luaL_Reg functions[] = { "replaceTransform", Wrap_Graphics::replaceTransform }, { "transformPoint", Wrap_Graphics::transformPoint }, { "inverseTransformPoint", Wrap_Graphics::inverseTransformPoint }, + { "getStats", Wrap_Graphics::getStats }, + + { "draw", Wrap_Graphics::draw }, + + { "newTexture", Wrap_Graphics::newTexture }, + { "newImage", Wrap_Graphics::newImage }, { "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 +}; // clang-format on int Wrap_Graphics::open(lua_State* L) @@ -866,6 +1195,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_Texture.cpp b/source/modules/graphics/wrap_Texture.cpp new file mode 100644 index 00000000..6c2b587d --- /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 00000000..af282c30 --- /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 00000000..27a7c7fc --- /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 00000000..773afbde --- /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 00000000..0ea9aa7b --- /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 00000000..47f0974e --- /dev/null +++ b/source/modules/image/ImageData.cpp @@ -0,0 +1,898 @@ +#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; + } + } + + void ImageData::create(int width, int height, PixelFormat format, void* data) + { + const auto dataSize = love::getPixelFormatSliceSize(format, width, height); + + try + { + this->data = new uint8_t[dataSize]; + } + catch (std::bad_alloc&) + { + throw love::Exception(E_OUT_OF_MEMORY); + } + + if (data) + { + if (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 00000000..ef5d3e68 --- /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 00000000..e193349e --- /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 00000000..72701bbb --- /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 00000000..e7e5b5c1 --- /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 00000000..59135916 --- /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 00000000..d387bfae --- /dev/null +++ b/source/modules/image/magpie/PNGHandler.cpp @@ -0,0 +1,101 @@ +#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]; + + 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 00000000..0cfef44a --- /dev/null +++ b/source/modules/image/magpie/T3XHandler.cpp @@ -0,0 +1,172 @@ +#include "common/Exception.hpp" + +#include "driver/display/Renderer.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 (!Renderer::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; + + Renderer::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 (!Renderer::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 00000000..cd17fc9d --- /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 00000000..6564b195 --- /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 00000000..6c7be266 --- /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 00000000..c99b3e2a --- /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/love/love.cpp b/source/modules/love/love.cpp index cb8cef6a..ba26e338 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -8,6 +8,7 @@ #include "modules/event/wrap_Event.hpp" #include "modules/filesystem/wrap_Filesystem.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" @@ -49,7 +50,7 @@ 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 }, @@ -57,6 +58,7 @@ static constexpr std::array modules = { "love.filesystem", Wrap_Filesystem::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 }, diff --git a/source/modules/love/scripts/boot.lua b/source/modules/love/scripts/boot.lua index 6d7bd4a2..5fa2bbd8 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, @@ -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/window/wrap_Window.cpp b/source/modules/window/wrap_Window.cpp index d5cc99aa..800d8756 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; } From 0aced702938c161871ebb19c6e133a88b07fc906 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Wed, 1 May 2024 19:14:15 -0400 Subject: [PATCH 02/49] =?UTF-8?q?we're=20getting=20there=E2=84=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 - include/driver/display/Renderer.tcc | 216 +++++++++++++++- include/driver/graphics/DrawCommand.hpp | 122 +++++++++ include/modules/graphics/BatchedDrawState.hpp | 29 --- include/modules/graphics/Graphics.tcc | 84 ------- include/modules/graphics/vertex.hpp | 7 + platform/ctr/CMakeLists.txt | 1 - platform/ctr/assets/shaders/main.v.pica | 11 +- .../include/driver/display/Framebuffer.hpp | 5 +- .../ctr/include/driver/display/Renderer.hpp | 68 +++++ .../ctr/include/modules/graphics/Graphics.hpp | 6 +- .../ctr/include/modules/graphics/Shader.hpp | 2 + .../include/modules/graphics/StreamBuffer.hpp | 8 - platform/ctr/romfs/shaders/main_v_pica.shbin | Bin 356 -> 368 bytes .../ctr/source/driver/display/Framebuffer.cpp | 29 +-- .../ctr/source/driver/display/Renderer.cpp | 84 +++++-- .../ctr/source/modules/graphics/Graphics.cpp | 78 +----- .../ctr/source/modules/graphics/Shader.cpp | 6 + .../source/modules/graphics/StreamBuffer.cpp | 18 +- .../ctr/source/modules/graphics/Texture.cpp | 23 +- source/modules/graphics/Graphics.cpp | 235 +----------------- source/modules/graphics/Quad.cpp | 18 +- source/modules/graphics/StreamBuffer.cpp | 32 --- source/modules/graphics/vertex.cpp | 19 +- 24 files changed, 531 insertions(+), 571 deletions(-) create mode 100644 include/driver/graphics/DrawCommand.hpp delete mode 100644 include/modules/graphics/BatchedDrawState.hpp delete mode 100644 platform/ctr/include/modules/graphics/StreamBuffer.hpp delete mode 100644 source/modules/graphics/StreamBuffer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f85ec6c7..b06f442a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -358,7 +358,6 @@ source/modules/filesystem/wrap_Filesystem.cpp source/modules/graphics/Graphics.cpp source/modules/graphics/renderstate.cpp source/modules/graphics/samplerstate.cpp -source/modules/graphics/StreamBuffer.cpp source/modules/graphics/Shader.cpp source/modules/graphics/Quad.cpp source/modules/graphics/Volatile.cpp diff --git a/include/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index ddc1f345..8c235e07 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -2,10 +2,16 @@ #include "common/Singleton.tcc" +#include "modules/graphics/Shader.tcc" #include "modules/graphics/renderstate.hpp" #include "modules/graphics/vertex.hpp" +#include "driver/graphics/DrawCommand.hpp" + #include +#include + +#define BUFFER_OFFSET(i) ((char*)NULL + (i)) namespace love { @@ -21,6 +27,39 @@ namespace love RENDERER_INFO_DEVICE }; + struct BatchedVertexData + { + Vertex* stream; + }; + + struct BatchedDrawState + { + Vertex* vertices; + size_t verticesSize; + + StreamBuffer::MapInfo vertexMap = StreamBuffer::MapInfo(); + + StreamBuffer* indexBuffer; + StreamBuffer::MapInfo indexMap = StreamBuffer::MapInfo(); + + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + ShaderBase::StandardShader shader = ShaderBase::STANDARD_DEFAULT; + CommonFormat format = CommonFormat::NONE; + + StrongRef texture; + + int vertexCount = 0; + int indexCount = 0; + + bool flushing = false; + }; + + RendererBase() : batchedDrawState {} + { + size_t size = sizeof(uint16_t) * LOVE_UINT16_MAX; + this->batchedDrawState.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, size); + } + size_t getVertexCount() const { return this->renderCtx.vertexCount; @@ -31,6 +70,171 @@ namespace love return this->data; } + BatchedVertexData requestBatchDraw(const DrawCommand& 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.shader) + { + 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 }; + + { + size_t stride = getFormatStride(command.format); + size_t dataSize = stride * totalVertices; + + if (state.vertices != nullptr && dataSize > state.verticesSize) + shouldFlush = true; + + if (dataSize > state.verticesSize) + { + bufferSizes[0] = std::max(dataSize, state.verticesSize * 1.1f); + shouldResize = true; + } + + newDataSize = stride * command.vertexCount; + } + + if (command.indexMode != TRIANGLEINDEX_NONE) + { + size_t dataSize = (state.indexCount + requestedIndexCount) * sizeof(uint16_t); + + if (state.indexMap.data != nullptr && dataSize > state.indexMap.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.shader = command.shaderType; + } + + if (state.vertexCount == 0) + { + if (ShaderBase::isDefaultActive()) + ShaderBase::attachDefault(state.shader); + } + + if (shouldResize) + { + if (state.verticesSize < bufferSizes[0]) + { + linearFree(state.vertices); + state.vertices = (Vertex*)linearAlloc(bufferSizes[0]); + } + + if (state.indexBuffer->getSize() < bufferSizes[1]) + { + state.indexBuffer->release(); + state.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, bufferSizes[1]); + } + } + + if (command.indexMode != TRIANGLEINDEX_NONE) + { + if (state.indexMap.data == nullptr) + state.indexMap = state.indexBuffer->map(); + + uint16_t* indices = (uint16_t*)state.indexMap.data; + fillIndices(command.indexMode, state.vertexCount, command.vertexCount, indices); + + state.indexMap.data += requestedIndexSize; + } + + BatchedVertexData data {}; + + if (newDataSize > 0) + { + if (state.vertexMap.data == nullptr) + { + const auto size = command.vertexCount * sizeof(Vertex); + state.vertexMap = StreamBuffer::MapInfo((uint8_t*)state.vertices, size); + } + + data.stream = (Vertex*)state.vertexMap.data; + state.vertexMap.data += newDataSize; + } + + state.vertexCount += command.vertexCount; + state.indexCount += requestedIndexCount; + + return data; + } + + virtual void updateUniforms() = 0; + + void flushBatchedDraws() + { + BatchedDrawState& state = this->batchedDrawState; + + if ((state.vertexCount == 0 && state.indexCount == 0) || state.flushing) + return; + + size_t usedSizes[2] = { 0, 0 }; + + this->updateUniforms(); + + if (state.format != CommonFormat::NONE) + state.vertexMap = StreamBuffer::MapInfo(); + + state.flushing = true; + + if (state.indexCount > 0) + { + DrawIndexedCommand command {}; + command.primitiveType = state.primitiveMode; + command.indexCount = state.indexCount; + command.indexType = INDEX_UINT16; + command.indexBufferOffset = state.indexBuffer->unmap(); + command.texture = nullptr; + + this->draw(command); + state.indexMap = StreamBuffer::MapInfo(); + } + + if (usedSizes[1] > 0) + state.indexBuffer->markUsed(usedSizes[1]); + + state.vertexCount = 0; + state.indexCount = 0; + state.flushing = false; + } + + static void flushBatchedDrawsGlobal() + { + RendererBase::getInstance().flushBatchedDraws(); + } + protected: struct ContextBase { @@ -43,17 +247,15 @@ namespace love Rect scissor; Rect viewport; + + ShaderBase::StandardShader shader = ShaderBase::STANDARD_DEFAULT; }; + virtual void draw(const DrawIndexedCommand& command) = 0; + bool initialized = false; bool inFrame = false; - Vertex* data; - - struct RenderContext - { - size_t vertexCount; - size_t indexCount; - } renderCtx; + BatchedDrawState batchedDrawState; }; } // namespace love diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp new file mode 100644 index 00000000..fc13dec6 --- /dev/null +++ b/include/driver/graphics/DrawCommand.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "common/Exception.hpp" + +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/Texture.tcc" +#include "modules/graphics/vertex.hpp" + +#if defined(__3DS__) + #include <3ds.h> + + #define _alloc(size) linearAlloc(size) + #define _free(ptr) linearFree(ptr) +#else + #define _alloc(size) malloc(size) + #define _free(ptr) free(ptr) +#endif + +namespace love +{ + class StreamBuffer final : public Object + { + public: + struct MapInfo + { + uint8_t* data = nullptr; + size_t size = 0; + + MapInfo() + {} + + MapInfo(uint8_t* data, size_t size) : data(data), size(size) + {} + }; + + StreamBuffer(BufferUsage usage, size_t size) : + usage(usage), + data(nullptr), + bufferSize(size), + frameGPUReadOffset(0) + { + this->data = (uint8_t*)_alloc(size); + + if (this->data == nullptr) + throw love::Exception(E_OUT_OF_MEMORY); + + std::memset(this->data, 0, size); + } + + virtual ~StreamBuffer() + { + _free(this->data); + } + + MapInfo map() + { + return MapInfo(this->data, this->bufferSize); + } + + size_t unmap() + { + return (size_t)this->data; + } + + size_t getSize() const + { + return this->bufferSize; + } + + size_t getUsableSize() const + { + return this->bufferSize - this->frameGPUReadOffset; + } + + BufferUsage getMode() const + { + return this->usage; + } + + void markUsed(size_t) + {} + + ptrdiff_t getHandle() const + { + return 0; + } + + private: + BufferUsage usage; + + uint8_t* data; + size_t bufferSize; + + size_t frameGPUReadOffset; + }; + + struct DrawCommand + { + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; + TriangleIndexMode indexMode = TRIANGLEINDEX_NONE; + CommonFormat format = CommonFormat::NONE; + + TextureBase* texture = nullptr; + int vertexCount = 0; + }; + + struct DrawIndexedCommand + { + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; + int indexCount = 0; + int instanceCount = 1; + + IndexDataType indexType = INDEX_UINT16; + size_t lastPosition = 0; + + size_t indexBufferOffset = 0; + + TextureBase* texture; + CullMode cullMode = CULL_NONE; + }; +} // namespace love diff --git a/include/modules/graphics/BatchedDrawState.hpp b/include/modules/graphics/BatchedDrawState.hpp deleted file mode 100644 index a0ff5df9..00000000 --- a/include/modules/graphics/BatchedDrawState.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "modules/graphics/Shader.tcc" -#include "modules/graphics/Texture.tcc" -#include "modules/graphics/vertex.hpp" - -#include "modules/graphics/StreamBuffer.hpp" - -namespace love -{ - struct BatchedDrawState - { - StreamBufferBase* vb[2]; - StreamBufferBase* indexBuffer = nullptr; - - PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; - CommonFormat formats[2] = { CommonFormat::NONE, CommonFormat::NONE }; - StrongRef texture; - ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; - - StreamBufferBase::MapInfo vbMap[2] = { StreamBufferBase::MapInfo(), StreamBufferBase::MapInfo() }; - StreamBufferBase::MapInfo indexBufferMap = StreamBufferBase::MapInfo(); - - int vertexCount; - int indexCount; - - bool flushing = false; - }; -} // namespace love diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 15fbefb6..a2a0235e 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -17,8 +17,6 @@ #include "modules/graphics/renderstate.hpp" #include "modules/graphics/samplerstate.hpp" -#include "modules/graphics/BatchedDrawState.hpp" - #include #include @@ -206,88 +204,8 @@ namespace love void restoreState(const DisplayState& state); - struct BatchedVertexData - { - void* stream[2]; - }; - - struct BatchedDrawCommand - { - PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; - CommonFormat formats[2] = { CommonFormat::NONE, CommonFormat::NONE }; - TriangleIndexMode indexMode = TRIANGLEINDEX_NONE; - int vertexCount = 0; - TextureBase* texture = nullptr; - ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; - }; - - struct DrawIndexedCommand - { - PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; - - const VertexAttributes* attributes; - const BufferBindings* buffers; - - int indexCount = 0; - int instanceCount = 1; - - IndexDataType indexType = INDEX_UINT16; - Resource* indexBuffer; - size_t indexBufferOffset = 0; - - Buffer* indirectBuffer = nullptr; - size_t indirectBufferOffset = 0; - - TextureBase* texture = nullptr; - - CullMode cullMode = CULL_NONE; - - DrawIndexedCommand(const VertexAttributes* attributes, const BufferBindings* buffers, - Resource* indexBuffer) : - attributes(attributes), - buffers(buffers), - indexBuffer(indexBuffer) - {} - }; - - struct DrawCommand - { - PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; - - const VertexAttributes* attributes; - const BufferBindings* buffers; - - int vertexStart = 0; - int vertexCount = 0; - int instanceCount = 1; - - Resource* vertexBuffer; - - Buffer* indirectBuffer = nullptr; - size_t indirectBufferOffset = 0; - - TextureBase* texture = nullptr; - - CullMode cullMode = CULL_NONE; - - DrawCommand(const VertexAttributes* attributes, const BufferBindings* buffers) : - attributes(attributes), - buffers(buffers) - {} - }; - - BatchedVertexData requestBatchedDraw(const BatchedDrawCommand& command); - - void flushBatchedDraws(); - - static void flushBatchedDrawsGlobal(); - void restoreStateChecked(const DisplayState& state); - virtual void draw(const DrawCommand& command) = 0; - - virtual void draw(const DrawIndexedCommand& command) = 0; - /* TODO: implement when they exist */ bool isRenderTargetActive() const { @@ -672,8 +590,6 @@ namespace love int pixelWidth; int pixelHeight; - BatchedDrawState batchedDrawState; - int drawCallsBatched; int drawCalls; }; diff --git a/include/modules/graphics/vertex.hpp b/include/modules/graphics/vertex.hpp index 9f52aa27..57a1f26c 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -235,6 +235,13 @@ namespace love using Vertex = XYf_STf_RGBAf; + inline void DEBUG_VERTEX(const Vertex& v) + { + 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); + } + inline CommonFormat getSinglePositionFormat(bool is2D) { return is2D ? CommonFormat::XYf : CommonFormat::XYf; diff --git a/platform/ctr/CMakeLists.txt b/platform/ctr/CMakeLists.txt index bee8c1ff..a9c485a5 100644 --- a/platform/ctr/CMakeLists.txt +++ b/platform/ctr/CMakeLists.txt @@ -43,7 +43,6 @@ source/driver/display/Renderer.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp source/modules/graphics/Graphics.cpp -source/modules/graphics/StreamBuffer.cpp source/modules/graphics/Shader.cpp source/modules/graphics/Texture.cpp source/modules/joystick/Joystick.cpp diff --git a/platform/ctr/assets/shaders/main.v.pica b/platform/ctr/assets/shaders/main.v.pica index 89815251..dc192012 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/driver/display/Framebuffer.hpp b/platform/ctr/include/driver/display/Framebuffer.hpp index 2ccf670c..5aee3daf 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 @@ -42,8 +40,7 @@ namespace love 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/Renderer.hpp index 3c4b41c3..ac3898cd 100644 --- a/platform/ctr/include/driver/display/Renderer.hpp +++ b/platform/ctr/include/driver/display/Renderer.hpp @@ -11,11 +11,21 @@ #include "modules/graphics/renderstate.hpp" #include "modules/graphics/samplerstate.hpp" +#include "driver/graphics/DrawCommand.hpp" + namespace love { class Renderer : public RendererBase { public: + enum TexEnvMode + { + TEXENV_PRIMITIVE, + TEXENV_TEXTURE, + TEXENV_FONT, + TEXENV_MAX_ENUM + }; + Renderer(); void initialize(); @@ -44,6 +54,10 @@ namespace love void setSamplerState(C3D_Tex* texture, SamplerState state); + void draw(const DrawIndexedCommand& command) override; + + virtual void updateUniforms() override; + void setWideMode(bool wide) { this->modeChanged([this, wide]() { gfxSetWide(wide); }); @@ -132,10 +146,64 @@ namespace love this->createFramebuffers(); } + void setPrimitiveAttribute() + { + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + } + + void setTextureAttribute() + { + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + } + + void setFontAttribute() + { + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + 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); + } + + void setAttribute(TexEnvMode mode) + { + if (this->context.mode == mode || mode == TEXENV_MAX_ENUM) + return; + + switch (mode) + { + default: + case TEXENV_PRIMITIVE: + this->setPrimitiveAttribute(); + break; + case TEXENV_TEXTURE: + this->setTextureAttribute(); + break; + case TEXENV_FONT: + this->setFontAttribute(); + break; + } + + this->context.mode = mode; + } + struct Context : public ContextBase { C3D_Mtx modelView; C3D_Mtx projection; + TexEnvMode mode = TEXENV_MAX_ENUM; + C3D_BufInfo buffer; Framebuffer target; } context; diff --git a/platform/ctr/include/modules/graphics/Graphics.hpp b/platform/ctr/include/modules/graphics/Graphics.hpp index c555e96e..1b285d59 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -31,11 +31,7 @@ namespace love void draw(Drawable* drawable, const Matrix4& matrix); - void draw(TextureBase* texture, Quad* quad, const Matrix4& matrix); - - virtual void draw(const DrawCommand& command) override; - - virtual void draw(const DrawIndexedCommand& command) override; + void draw(Texture* texture, Quad* quad, const Matrix4& matrix); bool isActive() const; diff --git a/platform/ctr/include/modules/graphics/Shader.hpp b/platform/ctr/include/modules/graphics/Shader.hpp index 0d1a595a..02c741f7 100644 --- a/platform/ctr/include/modules/graphics/Shader.hpp +++ b/platform/ctr/include/modules/graphics/Shader.hpp @@ -23,6 +23,8 @@ namespace love ptrdiff_t getHandle() const override; + void updateUniforms(const C3D_Mtx& mdlView, const C3D_Mtx& proj); + private: struct TextureUnit { diff --git a/platform/ctr/include/modules/graphics/StreamBuffer.hpp b/platform/ctr/include/modules/graphics/StreamBuffer.hpp deleted file mode 100644 index 6cc41578..00000000 --- a/platform/ctr/include/modules/graphics/StreamBuffer.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "modules/graphics/StreamBuffer.tcc" - -namespace love -{ - StreamBufferBase* createStreamBuffer(BufferUsage usage, size_t size); -} diff --git a/platform/ctr/romfs/shaders/main_v_pica.shbin b/platform/ctr/romfs/shaders/main_v_pica.shbin index 0a1c138687ab0c2e711764ce51e875bc9e696780..2fbf8cde7d56ad5b223899bb4872e83e5702e7f0 100644 GIT binary patch delta 199 zcmaFD^nt0~CCtZ(k%56>4G;s_0YHjD1Bm&6I01+`fOr8AqX9q05A1$SK+No+#KjV! z#KoGS#Kl&j#MR8f#L*(b#L=q3#L;HK#KA70;KQ!q-~-g(k;4ozWf2#I2HHEZ*piWN z;#z-2W(GzEexNWD5Q1nJ;07`nflzF+ETf7*NNR<1etuC3LuQ_HeolVT0T2V(0YHjD1BiKnI0lH>fp`iKqX9q05A1$S9!gxy5lUPv z8A@EN6-r!9984U|5=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 }; + + this->setScissor(Rect::EMPTY); } void Framebuffer::destroy() @@ -46,19 +55,6 @@ namespace love 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; - } - void Framebuffer::setScissor(const Rect& scissor) { const bool enabled = scissor != Rect::EMPTY; @@ -67,10 +63,9 @@ namespace love 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); + result = calculateBounds(this->scissor, this->width, this->height); + C3D_SetScissor(mode, result.y, result.x, result.h, result.w); this->scissor = result; } } // namespace love diff --git a/platform/ctr/source/driver/display/Renderer.cpp b/platform/ctr/source/driver/display/Renderer.cpp index f336be40..67ee46f5 100644 --- a/platform/ctr/source/driver/display/Renderer.cpp +++ b/platform/ctr/source/driver/display/Renderer.cpp @@ -26,15 +26,31 @@ namespace love C3D_AttrInfo* attributes = C3D_GetAttrInfo(); AttrInfo_Init(attributes); - AttrInfo_AddLoader(attributes, 0, GPU_FLOAT, 3); - AttrInfo_AddLoader(attributes, 1, GPU_FLOAT, 4); - AttrInfo_AddLoader(attributes, 2, GPU_FLOAT, 2); + 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->batchedDrawState.vertices = (Vertex*)linearAlloc(sizeof(Vertex) * LOVE_UINT16_MAX); + this->batchedDrawState.verticesSize = LOVE_UINT16_MAX; + + if (!this->batchedDrawState.vertices) + throw love::Exception("Failed to allocate vertex buffer."); + + BufInfo_Init(&this->context.buffer); + + if (BufInfo_Add(&this->context.buffer, this->batchedDrawState.vertices, sizeof(Vertex), 3, 0x210) < 0) + { + linearFree(this->batchedDrawState.vertices); + throw love::Exception("Failed to initialize vertex buffer."); + } + + C3D_SetBufInfo(&this->context.buffer); + this->initialized = true; } @@ -42,6 +58,8 @@ namespace love { this->destroyFramebuffers(); + linearFree(this->batchedDrawState.vertices); + C3D_Fini(); gfxExit(); } @@ -88,13 +106,29 @@ namespace love void Renderer::bindFramebuffer() { this->ensureInFrame(); + this->flushBatchedDraws(); 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); + C3D_FrameDrawOn(this->context.target.get()); + } + + void Renderer::present() + { + if (this->inFrame) + { + this->flushBatchedDraws(); + + C3D_FrameEnd(0); + this->inFrame = false; + } + } + + void Renderer::updateUniforms() + { if (this->context.dirtyProjection) { if (Shader::current != nullptr) @@ -109,23 +143,8 @@ namespace love } } - void Renderer::present() - { - if (this->inFrame) - { - C3D_FrameEnd(0); - this->inFrame = false; - } - } - - // void Renderer::render(BatchedDrawCommand& command) - // {} - 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) @@ -146,9 +165,10 @@ namespace love } } + // clang-format off 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); + ortho(&this->context.projection, 0.0f, viewport.w, viewport.h, 0.0f, Framebuffer::Z_NEAR, Framebuffer::Z_FAR, true); + // clang-format on this->context.dirtyProjection = true; C3D_SetViewport(0, 0, (uint32_t)viewport.w, (uint32_t)viewport.h); @@ -233,6 +253,28 @@ namespace love C3D_TexSetWrap(texture, wrapU, wrapV); } + void Renderer::draw(const DrawIndexedCommand& command) + { + if (command.texture != nullptr) + { + this->setAttribute(TEXENV_TEXTURE); + C3D_TexBind(0, (C3D_Tex*)command.texture->getHandle()); + } + else + this->setAttribute(TEXENV_PRIMITIVE); + + GPU_Primitive_t primitiveType; + if (!Renderer::getConstant(command.primitiveType, primitiveType)) + throw love::Exception("Invalid primitive type: {:d}.", (int)command.primitiveType); + + decltype(C3D_UNSIGNED_BYTE) indexType; + if (!Renderer::getConstant(command.indexType, indexType)) + throw love::Exception("Invalid index type: {:d}.", (int)command.indexType); + + const void* elements = (const void*)command.indexBufferOffset; + C3D_DrawElements(primitiveType, command.indexCount, indexType, elements); + } + GPU_TEXTURE_WRAP_PARAM Renderer::getWrapMode(SamplerState::WrapMode mode) { switch (mode) diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index 696d3dda..e8ba4d95 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -53,8 +53,6 @@ namespace love this->clear(colors.size() > 0 ? colors[0] : OptionalColor(), stencil, depth); return; } - - this->flushBatchedDraws(); } void Graphics::present() @@ -62,7 +60,6 @@ namespace love if (!this->isActive()) return; - this->flushBatchedDraws(); Renderer::getInstance().present(); this->drawCalls = 0; @@ -109,15 +106,6 @@ namespace love if (!Volatile::loadAll()) std::printf("Failed to load all volatile objects.\n"); - // clang-format off - if (this->batchedDrawState.vb[0] == nullptr) - { - this->batchedDrawState.vb[0] = createStreamBuffer(BUFFERUSAGE_VERTEX, 1024 * 1024 * 1); - this->batchedDrawState.vb[1] = createStreamBuffer(BUFFERUSAGE_VERTEX, 256 * 1024 * 1); - this->batchedDrawState.indexBuffer = createStreamBuffer(BUFFERUSAGE_INDEX, sizeof(uint16_t) * LOVE_UINT16_MAX); - } - // clang-format on - this->restoreState(this->states.back()); for (int index = 0; index < 1; index++) @@ -164,7 +152,7 @@ namespace love drawable->draw(*this, matrix); } - void Graphics::draw(TextureBase* texture, Quad* quad, const Matrix4& matrix) + void Graphics::draw(Texture* texture, Quad* quad, const Matrix4& matrix) { texture->draw(*this, quad, matrix); } @@ -180,70 +168,6 @@ namespace love return readable && supported; } - void Graphics::draw(const DrawCommand& command) - { - // C3D_SetBufInfo((C3D_BufInfo*)command->getHandle()); - - // auto* env = C3D_GetTexEnv(0); - // C3D_TexEnvInit(env); - - // if (command.texture == nullptr) - // { - // C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - // C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); - // } - // else - // { - // C3D_TexBind(0, (C3D_Tex*)command.texture->getHandle()); - // C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - // C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); - // } - - // GPU_Primitive_t primitive; - // if (!Renderer::getConstant(command.primitiveType, primitive)) - // throw love::Exception("Invalid primitive type: {:d}", (int)command.primitiveType); - - // C3D_DrawArrays(primitive, command.vertexStart, command.vertexCount); - - // ++this->drawCalls; - } - - void Graphics::draw(const DrawIndexedCommand& command) - { - std::printf("Binding C3D_BufInfo\n"); - C3D_SetBufInfo((C3D_BufInfo*)command.indexBuffer->getHandle()); - std::printf("Resetting TexEnv\n"); - auto* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - - if (command.texture == nullptr) - { - std::printf("Setting up texture environment for no texture\n"); - C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); - } - else - { - std::printf("Setting up texture environment for texture\n"); - C3D_TexBind(0, (C3D_Tex*)command.texture->getHandle()); - C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); - } - - GPU_Primitive_t primitive; - if (!Renderer::getConstant(command.primitiveType, primitive)) - throw love::Exception("Invalid primitive type: {:d}", (int)command.primitiveType); - - decltype(C3D_UNSIGNED_BYTE) type; - if (!Renderer::getConstant(command.indexType, type)) - throw love::Exception("Invalid index type: {:d}", (int)command.indexType); - std::printf("Drawing elements\n"); - const void* indices = BUFFER_OFFSET(command.indexBufferOffset); - C3D_DrawElements(primitive, command.indexCount, type, indices); - - ++this->drawCalls; - } - bool Graphics::is3D() const { return gfxIs3D(); diff --git a/platform/ctr/source/modules/graphics/Shader.cpp b/platform/ctr/source/modules/graphics/Shader.cpp index 9ff7ab1f..0dadd81d 100644 --- a/platform/ctr/source/modules/graphics/Shader.cpp +++ b/platform/ctr/source/modules/graphics/Shader.cpp @@ -51,6 +51,12 @@ namespace love current = this; } + void Shader::updateUniforms(const C3D_Mtx& mdlView, const C3D_Mtx& proj) + { + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->locations.mdlvMtx, &mdlView); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->locations.projMtx, &proj); + } + ptrdiff_t Shader::getHandle() const { return 0; diff --git a/platform/ctr/source/modules/graphics/StreamBuffer.cpp b/platform/ctr/source/modules/graphics/StreamBuffer.cpp index cac3b13c..45bb2b67 100644 --- a/platform/ctr/source/modules/graphics/StreamBuffer.cpp +++ b/platform/ctr/source/modules/graphics/StreamBuffer.cpp @@ -9,29 +9,16 @@ namespace love class StreamBufferClientMemory final : public StreamBufferBase { public: - StreamBufferClientMemory(BufferUsage mode, size_t size) : - StreamBufferBase(mode, size), - data(nullptr), - vbo(new C3D_BufInfo()) + StreamBufferClientMemory(BufferUsage mode, size_t size) : StreamBufferBase(mode, size), data(nullptr) { this->data = (uint8_t*)linearAlloc(size); if (this->data == nullptr) throw love::Exception("Failed to allocate memory for StreamBufferClientMemory"); - - BufInfo_Init(this->vbo); - - int result = BufInfo_Add(this->vbo, this->data, sizeof(Vertex), 3, 0x210); - - if (result != 0) - throw love::Exception("Failed to create StreamBufferClientMemory"); } virtual ~StreamBufferClientMemory() { - delete this->vbo; - this->vbo = nullptr; - linearFree(this->data); } @@ -55,12 +42,11 @@ namespace love ptrdiff_t getHandle() const override { - return (ptrdiff_t)this->vbo; + return 0; } private: uint8_t* data; - C3D_BufInfo* vbo; }; StreamBufferBase* createStreamBuffer(BufferUsage usage, size_t size) diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp index 77038339..b374dbed 100644 --- a/platform/ctr/source/modules/graphics/Texture.cpp +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -2,6 +2,7 @@ #include "modules/graphics/Graphics.hpp" #include "driver/display/Renderer.hpp" +#include "driver/graphics/DrawCommand.hpp" namespace love { @@ -173,38 +174,34 @@ namespace love const auto& viewport = quad->getViewport(); - Vector2 physicalDim = { (float)this->texture->width, (float)this->texture->height }; - Vector2 virtualDim = { (float)this->pixelWidth, (float)this->pixelHeight }; + 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); const auto& transform = graphics.getTransform(); bool is2D = transform.isAffine2DTransform(); - Graphics::BatchedDrawCommand command {}; - command.formats[0] = getSinglePositionFormat(is2D); - command.formats[1] = CommonFormat::STf_RGBAf; + DrawCommand command {}; + command.format = CommonFormat::XYf_STf_RGBAf; command.indexMode = TRIANGLEINDEX_QUADS; command.vertexCount = 4; command.texture = this; - Graphics::BatchedVertexData data = graphics.requestBatchedDraw(command); + auto data = Renderer::getInstance().requestBatchDraw(command); Matrix4 translated(transform, matrix); if (is2D) - translated.transformXY((Vector2*)data.stream[0], quad->getVertexPositions(), 4); - else - translated.transformXY0((Vector3*)data.stream[0], quad->getVertexPositions(), 4); + translated.transformXY(data.stream, quad->getVertexPositions(), 4); const auto* texCoords = quad->getTextureCoordinates(); - STf_RGBAf* vertices = (STf_RGBAf*)data.stream[1]; for (int index = 0; index < 4; index++) { - vertices[index].s = texCoords[index].x; - vertices[index].t = texCoords[index].y; - vertices[index].color = graphics.getColor(); + data.stream[index].s = texCoords[index].x; + data.stream[index].t = texCoords[index].y; + data.stream[index].color = graphics.getColor(); } } diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index ade2e6a6..befdb4f8 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -10,8 +10,7 @@ namespace love pixelHeight(0), created(false), active(true), - deviceProjectionMatrix(), - batchedDrawState() + deviceProjectionMatrix() { this->transformStack.reserve(16); this->transformStack.push_back(Matrix4()); @@ -24,18 +23,8 @@ namespace love } GraphicsBase::~GraphicsBase() - { this->states.clear(); - - if (this->batchedDrawState.vb[0]) - this->batchedDrawState.vb[0]->release(); - - if (this->batchedDrawState.vb[1]) - this->batchedDrawState.vb[1]->release(); - - if (this->batchedDrawState.indexBuffer) - this->batchedDrawState.indexBuffer->release(); } void GraphicsBase::restoreState(const DisplayState& state) @@ -130,226 +119,6 @@ namespace love this->resetProjection(); } - GraphicsBase::BatchedVertexData GraphicsBase::requestBatchedDraw( - const GraphicsBase::BatchedDrawCommand& command) - { - BatchedDrawState& state = this->batchedDrawState; - - bool shouldFlush = false; - bool shouldResize = false; - - if (command.primitiveMode != state.primitiveMode || command.formats[0] != state.formats[0] || - command.formats[1] != state.formats[1] || - ((command.indexMode != TRIANGLEINDEX_NONE) != (state.indexCount > 0)) || - command.texture != state.texture || command.shaderType != state.shaderType) - { - shouldFlush = true; - } - - 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 requestedSize = requestedIndexCount * sizeof(uint16_t); - - size_t newDataSizes[2] = { 0, 0 }; //< position, vertices - size_t bufferSizes[3] = { 0, 0, 0 }; - - for (int index = 0; index < 2; index++) - { - if (command.formats[index] == CommonFormat::NONE) - continue; - - size_t stride = getFormatStride(command.formats[index]); - size_t dataSize = stride * totalVertices; - - if (state.vbMap[index].data != nullptr && dataSize > state.vbMap[index].size) - shouldFlush = true; - - if (dataSize > state.vb[index]->getUsableSize()) - { - bufferSizes[index] = std::max(dataSize, state.vb[index]->getSize() * 2); - shouldResize = true; - } - - newDataSizes[index] = 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[2] = std::max(dataSize, state.indexBuffer->getSize() * 2); - shouldResize = true; - } - } - - if (shouldFlush || shouldResize) - { - flushBatchedDraws(); - - state.primitiveMode = command.primitiveMode; - state.formats[0] = command.formats[0]; - state.formats[1] = command.formats[1]; - state.texture = command.texture; - state.shaderType = command.shaderType; - } - - if (state.vertexCount == 0) - { - if (ShaderBase::isDefaultActive()) - ShaderBase::attachDefault(state.shaderType); - } - - if (shouldResize) - { - for (int index = 0; index < 2; index++) - { - if (state.vb[index]->getSize() < bufferSizes[index]) - { - state.vb[index]->release(); - state.vb[index] = createStreamBuffer(BUFFERUSAGE_VERTEX, bufferSizes[index]); - } - } - - if (state.indexBuffer->getSize() < bufferSizes[2]) - { - state.indexBuffer->release(); - state.indexBuffer = createStreamBuffer(BUFFERUSAGE_INDEX, bufferSizes[2]); - } - } - - if (command.indexMode != TRIANGLEINDEX_NONE) - { - if (state.indexBufferMap.data == nullptr) - state.indexBufferMap = state.indexBuffer->map(requestedSize); - - uint16_t* indices = (uint16_t*)state.indexBufferMap.data; - fillIndices(command.indexMode, state.vertexCount, command.vertexCount, indices); - - state.indexBufferMap.data += requestedSize; - } - - BatchedVertexData data {}; - - for (int index = 0; index < 2; index++) - { - if (newDataSizes[index] > 0) - { - if (state.vbMap[index].data == nullptr) - state.vbMap[index] = state.vb[index]->map(newDataSizes[index]); - - data.stream[index] = state.vbMap[index].data; - state.vbMap[index].data += newDataSizes[index]; - } - } - - if (state.vertexCount > 0) - this->drawCallsBatched++; - - state.vertexCount += command.vertexCount; - state.indexCount += requestedIndexCount; - - return data; - } - - void GraphicsBase::flushBatchedDraws() - { - BatchedDrawState& state = this->batchedDrawState; - - if ((state.vertexCount == 0 && state.indexCount == 0) || state.flushing) - return; - - VertexAttributes attributes {}; - BufferBindings buffers {}; - - size_t usedSizes[3] = { 0, 0, 0 }; - - for (int index = 0; index < 2; index++) - { - if (state.formats[index] == CommonFormat::NONE) - continue; - - attributes.setCommonFormat(state.formats[index], (uint8_t)index); - - usedSizes[index] = getFormatStride(state.formats[index]) * state.vertexCount; - - size_t offset = state.vb[index]->unmap(usedSizes[index]); - buffers.set(index, state.vb[index], offset); - - state.vbMap[index] = StreamBufferBase::MapInfo(); - } - - if (attributes.enableBits == 0) - return; - - state.flushing = true; - - Color newColor = this->getColor(); - if (attributes.isEnabled(ATTRIB_COLOR)) - this->setColor(Color::WHITE); - - this->pushIdentityTransform(); - - if (state.indexCount > 0) - { - usedSizes[2] = sizeof(uint16_t) * state.indexCount; - - DrawIndexedCommand command(&attributes, &buffers, state.indexBuffer); - command.primitiveType = state.primitiveMode; - command.indexCount = state.indexCount; - command.indexType = INDEX_UINT16; - command.indexBufferOffset = state.indexBuffer->unmap(usedSizes[2]); - command.texture = nullptr; - - this->draw(command); - state.indexBufferMap = StreamBufferBase::MapInfo(); - } - else - { - DrawCommand command(&attributes, &buffers); - command.primitiveType = state.primitiveMode; - command.vertexCount = 0; - command.vertexStart = state.vertexCount; - command.texture = state.texture; - - this->draw(command); - } - - for (int index = 0; index < 2; index++) - { - if (usedSizes[index] > 0) - state.vb[index]->markUsed(usedSizes[index]); - } - - if (usedSizes[2] > 0) - state.indexBuffer->markUsed(usedSizes[2]); - - this->popTransform(); - - if (attributes.isEnabled(ATTRIB_COLOR)) - this->setColor(newColor); - - state.vertexCount = 0; - state.indexCount = 0; - state.flushing = false; - } - - void GraphicsBase::flushBatchedDrawsGlobal() - { - auto* instance = getInstance(M_GRAPHICS); - - if (instance != nullptr) - instance->flushBatchedDraws(); - } - void GraphicsBase::setScissor(const Rect& scissor) { auto& state = this->states.back(); @@ -374,8 +143,6 @@ namespace love Stats stats {}; stats.drawCalls = this->drawCalls; - if (this->batchedDrawState.vertexCount > 0) - stats.drawCalls++; stats.drawCallsBatched = this->drawCallsBatched; stats.textures = TextureBase::textureCount; diff --git a/source/modules/graphics/Quad.cpp b/source/modules/graphics/Quad.cpp index 3db97465..1cab82db 100644 --- a/source/modules/graphics/Quad.cpp +++ b/source/modules/graphics/Quad.cpp @@ -16,22 +16,22 @@ namespace love Quad::~Quad() {} - void Quad::refresh(const Viewport& viewport, double sourceWidth, double sourceHeight) + void Quad::refresh(const Viewport& v, double sourceWidth, double sourceHeight) { - this->viewport = viewport; + this->viewport = v; this->sourceWidth = sourceWidth; this->sourceHeight = sourceHeight; this->vertexPositions[0] = Vector2(0.0f, 0.0f); - this->vertexPositions[1] = Vector2(0.0f, viewport.h); - this->vertexPositions[2] = Vector2(viewport.w, viewport.h); - this->vertexPositions[3] = Vector2(viewport.w, 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(viewport.x / sourceWidth, viewport.y / sourceHeight); - this->textureCoordinates[1] = Vector2(viewport.x / sourceWidth, (viewport.y + viewport.h) / sourceHeight); - this->textureCoordinates[2] = Vector2((viewport.x + viewport.w) / sourceWidth, (viewport.y + viewport.h) / sourceHeight); - this->textureCoordinates[3] = Vector2((viewport.x + viewport.w) / sourceWidth, viewport.y / sourceHeight); + 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/StreamBuffer.cpp b/source/modules/graphics/StreamBuffer.cpp deleted file mode 100644 index 687fdaa0..00000000 --- a/source/modules/graphics/StreamBuffer.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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/Exception.hpp" - -#include "modules/graphics/StreamBuffer.tcc" - -namespace love -{ - StreamBufferBase::StreamBufferBase(BufferUsage mode, size_t size) : - bufferSize(size), - frameGPUReadOffset(0), - mode(mode) - {} -} // namespace love diff --git a/source/modules/graphics/vertex.cpp b/source/modules/graphics/vertex.cpp index ec2ed52d..5bee17ac 100644 --- a/source/modules/graphics/vertex.cpp +++ b/source/modules/graphics/vertex.cpp @@ -64,8 +64,9 @@ namespace love indices[i++] = vertexStart + index + 1 + (index & 1); indices[i++] = vertexStart + index + 2 - (index & 1); } + + break; } - break; case TRIANGLEINDEX_FAN: { int i = 0; @@ -75,13 +76,14 @@ namespace love indices[i++] = vertexStart + index - 1; indices[i++] = vertexStart + index; } + + break; } - break; case TRIANGLEINDEX_QUADS: { - // 0---3 - // | \ | - // 1---2 + // 0---2 + // | / | + // 1---3 int count = vertexCount / 4; for (int i = 0; i < count; i++) { @@ -93,11 +95,12 @@ namespace love indices[ii + 2] = vi + 2; indices[ii + 3] = vi + 2; - indices[ii + 4] = vi + 3; - indices[ii + 5] = vi + 0; + indices[ii + 4] = vi + 1; + indices[ii + 5] = vi + 3; } + + break; } - break; } } From fc6cb887cfc5c657297d6b58926b136a79216ed4 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 2 May 2024 11:36:21 -0400 Subject: [PATCH 03/49] minor adjustments --- include/common/math.hpp | 8 +- include/driver/display/Renderer.tcc | 27 ------ include/driver/graphics/DrawCommand.hpp | 27 ++++++ include/modules/graphics/StreamBuffer.tcc | 85 ------------------- include/modules/graphics/vertex.hpp | 2 +- include/modules/graphics/wrap_Graphics.hpp | 72 +++++++++------- .../include/driver/display/Framebuffer.hpp | 5 +- .../ctr/include/driver/display/Renderer.hpp | 3 - .../driver/display/citro3d/VertexRing.hpp | 66 -------------- platform/ctr/source/common/screen.cpp | 14 +-- .../ctr/source/driver/display/Framebuffer.cpp | 18 ++-- .../ctr/source/driver/display/Renderer.cpp | 10 +-- source/modules/graphics/vertex.cpp | 1 + 13 files changed, 99 insertions(+), 239 deletions(-) delete mode 100644 include/modules/graphics/StreamBuffer.tcc delete mode 100644 platform/ctr/include/driver/display/citro3d/VertexRing.hpp diff --git a/include/common/math.hpp b/include/common/math.hpp index b9435650..010e81ac 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 @@ -47,7 +48,7 @@ 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; @@ -71,6 +72,11 @@ namespace love } }; + inline void DEBUG_RECT(const Rect& rectangle) + { + std::printf("x: %d, y: %d, w: %d, h: %d\n", rectangle.x, rectangle.y, rectangle.w, rectangle.h); + } + /* ** Clamps 3DS textures between min ** and max texture size to prevent diff --git a/include/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index 8c235e07..6dcc1db3 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -27,33 +27,6 @@ namespace love RENDERER_INFO_DEVICE }; - struct BatchedVertexData - { - Vertex* stream; - }; - - struct BatchedDrawState - { - Vertex* vertices; - size_t verticesSize; - - StreamBuffer::MapInfo vertexMap = StreamBuffer::MapInfo(); - - StreamBuffer* indexBuffer; - StreamBuffer::MapInfo indexMap = StreamBuffer::MapInfo(); - - PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; - ShaderBase::StandardShader shader = ShaderBase::STANDARD_DEFAULT; - CommonFormat format = CommonFormat::NONE; - - StrongRef texture; - - int vertexCount = 0; - int indexCount = 0; - - bool flushing = false; - }; - RendererBase() : batchedDrawState {} { size_t size = sizeof(uint16_t) * LOVE_UINT16_MAX; diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp index fc13dec6..fe2c83fc 100644 --- a/include/driver/graphics/DrawCommand.hpp +++ b/include/driver/graphics/DrawCommand.hpp @@ -119,4 +119,31 @@ namespace love TextureBase* texture; CullMode cullMode = CULL_NONE; }; + + struct BatchedVertexData + { + Vertex* stream; + }; + + struct BatchedDrawState + { + Vertex* vertices; + size_t verticesSize; + + StreamBuffer::MapInfo vertexMap = StreamBuffer::MapInfo(); + + StreamBuffer* indexBuffer; + StreamBuffer::MapInfo indexMap = StreamBuffer::MapInfo(); + + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + ShaderBase::StandardShader shader = ShaderBase::STANDARD_DEFAULT; + CommonFormat format = CommonFormat::NONE; + + StrongRef texture; + + int vertexCount = 0; + int indexCount = 0; + + bool flushing = false; + }; } // namespace love diff --git a/include/modules/graphics/StreamBuffer.tcc b/include/modules/graphics/StreamBuffer.tcc deleted file mode 100644 index 52d4fc6a..00000000 --- a/include/modules/graphics/StreamBuffer.tcc +++ /dev/null @@ -1,85 +0,0 @@ -/** - * 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/int.hpp" - -#include "modules/graphics/Resource.hpp" -#include "modules/graphics/vertex.hpp" - -// C -#include - -namespace love -{ - class StreamBufferBase : public Object, public Resource - { - public: - struct MapInfo - { - uint8_t* data = nullptr; - size_t size = 0; - - MapInfo() - {} - - MapInfo(uint8_t* data, size_t size) : data(data), size(size) - {} - }; - - virtual ~StreamBufferBase() - {} - - size_t getSize() const - { - return bufferSize; - } - - BufferUsage getMode() const - { - return mode; - } - - size_t getUsableSize() const - { - return bufferSize - frameGPUReadOffset; - } - - virtual size_t getGPUReadOffset() const = 0; - - virtual MapInfo map(size_t minsize) = 0; - virtual size_t unmap(size_t usedsize) = 0; - virtual void markUsed(size_t usedsize) = 0; - - virtual void nextFrame() - {} - - protected: - StreamBufferBase(BufferUsage mode, size_t size); - - size_t bufferSize; - size_t frameGPUReadOffset; - BufferUsage mode; - - }; // StreamBuffer -} // namespace love diff --git a/include/modules/graphics/vertex.hpp b/include/modules/graphics/vertex.hpp index 57a1f26c..63d9b323 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -244,7 +244,7 @@ namespace love inline CommonFormat getSinglePositionFormat(bool is2D) { - return is2D ? CommonFormat::XYf : CommonFormat::XYf; + return is2D ? CommonFormat::XYf : CommonFormat::XYZf; } struct DataFormatInfo diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index 9d07fe51..554b20ab 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 { diff --git a/platform/ctr/include/driver/display/Framebuffer.hpp b/platform/ctr/include/driver/display/Framebuffer.hpp index 5aee3daf..368374c2 100644 --- a/platform/ctr/include/driver/display/Framebuffer.hpp +++ b/platform/ctr/include/driver/display/Framebuffer.hpp @@ -35,8 +35,11 @@ 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) | diff --git a/platform/ctr/include/driver/display/Renderer.hpp b/platform/ctr/include/driver/display/Renderer.hpp index ac3898cd..ddd52968 100644 --- a/platform/ctr/include/driver/display/Renderer.hpp +++ b/platform/ctr/include/driver/display/Renderer.hpp @@ -129,9 +129,6 @@ namespace love private: static GPU_TEXTURE_WRAP_PARAM getWrapMode(SamplerState::WrapMode mode); - static constexpr int MAX_OBJECTS = 0x1000; - static constexpr int VERTEX_BUFFER_SIZE = 6 * MAX_OBJECTS; // 6 vertices per object - void ensureInFrame(); void createFramebuffers(); diff --git a/platform/ctr/include/driver/display/citro3d/VertexRing.hpp b/platform/ctr/include/driver/display/citro3d/VertexRing.hpp deleted file mode 100644 index 598c5dc9..00000000 --- a/platform/ctr/include/driver/display/citro3d/VertexRing.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include <3ds.h> - -namespace love -{ - class VertexRing - { - public: - VertexRing() : memory(nullptr), sliceSize(0), currentSlice(0), sliceCount(0) - {} - - VertexRing(const VertexRing&) = delete; - - VertexRing& operator=(const VertexRing&) = delete; - - ~VertexRing() - { - linearFree(this->memory); - } - - bool allocate(size_t numSlices, size_t size) - { - if (this->memory) - linearFree(this->memory); - - this->memory = linearAlloc(size * numSlices); - - if (this->memory == nullptr) - return false; - - this->sliceSize = size; - this->sliceCount = numSlices; - - return true; - } - - size_t getSize() - { - return this->sliceSize * this->sliceCount; - } - - void* getBuffer() const - { - return this->memory; - } - - void* begin() const - { - const auto offset = this->currentSlice * this->sliceSize; - return (void*)((char*)this->memory + offset); - } - - void end() - { - this->currentSlice = (this->currentSlice + 1) % this->sliceCount; - } - - private: - void* memory; - size_t sliceSize; - - size_t currentSlice; - size_t sliceCount; - }; -} // namespace love diff --git a/platform/ctr/source/common/screen.cpp b/platform/ctr/source/common/screen.cpp index efe65aa3..bd5faa91 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/display/Framebuffer.cpp b/platform/ctr/source/driver/display/Framebuffer.cpp index 55514b72..92f47973 100644 --- a/platform/ctr/source/driver/display/Framebuffer.cpp +++ b/platform/ctr/source/driver/display/Framebuffer.cpp @@ -13,10 +13,10 @@ 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.width, info.height, 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, DISPLAY_TRANSFER_FLAGS); @@ -30,8 +30,6 @@ namespace love this->viewport = { 0, 0, this->width, this->height }; this->scissor = { 0, 0, this->width, this->height }; - - this->setScissor(Rect::EMPTY); } void Framebuffer::destroy() @@ -43,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; @@ -52,7 +50,7 @@ namespace love const uint32_t bottom = width - bounds.x; // clang-format on - return { (int)left, (int)top, (int)right, (int)bottom }; + out = { (int)left, (int)top, (int)right, (int)bottom }; } void Framebuffer::setScissor(const Rect& scissor) @@ -60,12 +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 = calculateBounds(this->scissor, this->width, this->height); + Rect result {}; + calculateBounds(scissor, result, this->width, this->height); - C3D_SetScissor(mode, result.y, result.x, result.h, result.w); 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 index 67ee46f5..c3baa8eb 100644 --- a/platform/ctr/source/driver/display/Renderer.cpp +++ b/platform/ctr/source/driver/display/Renderer.cpp @@ -111,9 +111,8 @@ namespace love this->context.target = this->targets[currentScreen]; auto viewport = this->context.target.getViewport(); - this->setViewport(viewport, this->context.target.get()->linked); - C3D_FrameDrawOn(this->context.target.get()); + this->setViewport(viewport, this->context.target.get()->linked); } void Renderer::present() @@ -145,22 +144,20 @@ namespace love void Renderer::setViewport(const Rect& viewport, bool tilt) { - this->context.viewport = viewport; + this->context.viewport = viewport; + this->context.dirtyProjection = true; 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; } } @@ -170,7 +167,6 @@ namespace love ortho(&this->context.projection, 0.0f, viewport.w, viewport.h, 0.0f, Framebuffer::Z_NEAR, Framebuffer::Z_FAR, true); // clang-format on - this->context.dirtyProjection = true; C3D_SetViewport(0, 0, (uint32_t)viewport.w, (uint32_t)viewport.h); } diff --git a/source/modules/graphics/vertex.cpp b/source/modules/graphics/vertex.cpp index 5bee17ac..2ec93121 100644 --- a/source/modules/graphics/vertex.cpp +++ b/source/modules/graphics/vertex.cpp @@ -45,6 +45,7 @@ namespace love case CommonFormat::XYf_STPf_RGBAf: return sizeof(XYf_STPf_RGBAf); } + return 0; } From 3d0c8d6aeadaf0cf00863250960a59f96bb352a9 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 2 May 2024 12:03:48 -0400 Subject: [PATCH 04/49] minor update --- gdb.txt | 2 -- platform/ctr/source/driver/display/Renderer.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 gdb.txt diff --git a/gdb.txt b/gdb.txt deleted file mode 100644 index f8b80b7a..00000000 --- a/gdb.txt +++ /dev/null @@ -1,2 +0,0 @@ -C:\msys64\home\jpost/.gdbinit:23: Error in sourced command file: -Undefined item: "UTF-8". diff --git a/platform/ctr/source/driver/display/Renderer.cpp b/platform/ctr/source/driver/display/Renderer.cpp index c3baa8eb..b5fe6231 100644 --- a/platform/ctr/source/driver/display/Renderer.cpp +++ b/platform/ctr/source/driver/display/Renderer.cpp @@ -232,8 +232,8 @@ namespace love if (!Renderer::getConstant(state.dstFactorA, destAlpha)) return; - C3D_AlphaBlend(operationRGB, operationA, sourceColor, destColor, sourceAlpha, destAlpha); this->context.blendState = state; + C3D_AlphaBlend(operationRGB, operationA, sourceColor, destColor, sourceAlpha, destAlpha); } void Renderer::setSamplerState(C3D_Tex* texture, SamplerState state) From 79e9fcf2f5be8591273a9b50a83a863233e21ccf Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 2 May 2024 15:43:30 -0400 Subject: [PATCH 05/49] yay primitives and textures --- include/common/luax.hpp | 30 ++ include/driver/display/Renderer.tcc | 176 +------ include/modules/graphics/Graphics.tcc | 52 ++ include/modules/graphics/Shader.tcc | 14 - include/modules/graphics/wrap_Graphics.hpp | 12 + .../ctr/include/driver/display/Renderer.hpp | 67 +-- .../ctr/include/modules/graphics/Graphics.hpp | 4 + .../ctr/include/modules/graphics/Shader.hpp | 24 +- .../include/modules/graphics/attributes.hpp | 65 +++ .../ctr/source/driver/display/Framebuffer.cpp | 2 +- .../ctr/source/driver/display/Renderer.cpp | 68 +-- .../ctr/source/modules/graphics/Graphics.cpp | 50 +- .../ctr/source/modules/graphics/Shader.cpp | 45 +- .../ctr/source/modules/graphics/Texture.cpp | 2 +- source/common/luax.cpp | 7 +- source/modules/graphics/Graphics.cpp | 466 +++++++++++++++++- source/modules/graphics/wrap_Graphics.cpp | 270 ++++++++++ 17 files changed, 1022 insertions(+), 332 deletions(-) create mode 100644 platform/ctr/include/modules/graphics/attributes.hpp diff --git a/include/common/luax.hpp b/include/common/luax.hpp index 93492a76..c8b38ba6 100644 --- a/include/common/luax.hpp +++ b/include/common/luax.hpp @@ -96,6 +96,36 @@ 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); diff --git a/include/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index 6dcc1db3..d2892205 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -27,11 +27,8 @@ namespace love RENDERER_INFO_DEVICE }; - RendererBase() : batchedDrawState {} - { - size_t size = sizeof(uint16_t) * LOVE_UINT16_MAX; - this->batchedDrawState.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, size); - } + RendererBase() + {} size_t getVertexCount() const { @@ -43,170 +40,7 @@ namespace love return this->data; } - BatchedVertexData requestBatchDraw(const DrawCommand& 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.shader) - { - 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 }; - - { - size_t stride = getFormatStride(command.format); - size_t dataSize = stride * totalVertices; - - if (state.vertices != nullptr && dataSize > state.verticesSize) - shouldFlush = true; - - if (dataSize > state.verticesSize) - { - bufferSizes[0] = std::max(dataSize, state.verticesSize * 1.1f); - shouldResize = true; - } - - newDataSize = stride * command.vertexCount; - } - - if (command.indexMode != TRIANGLEINDEX_NONE) - { - size_t dataSize = (state.indexCount + requestedIndexCount) * sizeof(uint16_t); - - if (state.indexMap.data != nullptr && dataSize > state.indexMap.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.shader = command.shaderType; - } - - if (state.vertexCount == 0) - { - if (ShaderBase::isDefaultActive()) - ShaderBase::attachDefault(state.shader); - } - - if (shouldResize) - { - if (state.verticesSize < bufferSizes[0]) - { - linearFree(state.vertices); - state.vertices = (Vertex*)linearAlloc(bufferSizes[0]); - } - - if (state.indexBuffer->getSize() < bufferSizes[1]) - { - state.indexBuffer->release(); - state.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, bufferSizes[1]); - } - } - - if (command.indexMode != TRIANGLEINDEX_NONE) - { - if (state.indexMap.data == nullptr) - state.indexMap = state.indexBuffer->map(); - - uint16_t* indices = (uint16_t*)state.indexMap.data; - fillIndices(command.indexMode, state.vertexCount, command.vertexCount, indices); - - state.indexMap.data += requestedIndexSize; - } - - BatchedVertexData data {}; - - if (newDataSize > 0) - { - if (state.vertexMap.data == nullptr) - { - const auto size = command.vertexCount * sizeof(Vertex); - state.vertexMap = StreamBuffer::MapInfo((uint8_t*)state.vertices, size); - } - - data.stream = (Vertex*)state.vertexMap.data; - state.vertexMap.data += newDataSize; - } - - state.vertexCount += command.vertexCount; - state.indexCount += requestedIndexCount; - - return data; - } - - virtual void updateUniforms() = 0; - - void flushBatchedDraws() - { - BatchedDrawState& state = this->batchedDrawState; - - if ((state.vertexCount == 0 && state.indexCount == 0) || state.flushing) - return; - - size_t usedSizes[2] = { 0, 0 }; - - this->updateUniforms(); - - if (state.format != CommonFormat::NONE) - state.vertexMap = StreamBuffer::MapInfo(); - - state.flushing = true; - - if (state.indexCount > 0) - { - DrawIndexedCommand command {}; - command.primitiveType = state.primitiveMode; - command.indexCount = state.indexCount; - command.indexType = INDEX_UINT16; - command.indexBufferOffset = state.indexBuffer->unmap(); - command.texture = nullptr; - - this->draw(command); - state.indexMap = StreamBuffer::MapInfo(); - } - - if (usedSizes[1] > 0) - state.indexBuffer->markUsed(usedSizes[1]); - - state.vertexCount = 0; - state.indexCount = 0; - state.flushing = false; - } - - static void flushBatchedDrawsGlobal() - { - RendererBase::getInstance().flushBatchedDraws(); - } + virtual void prepareDraw() = 0; protected: struct ContextBase @@ -224,11 +58,7 @@ namespace love ShaderBase::StandardShader shader = ShaderBase::STANDARD_DEFAULT; }; - virtual void draw(const DrawIndexedCommand& command) = 0; - bool initialized = false; bool inFrame = false; - - BatchedDrawState batchedDrawState; }; } // namespace love diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index a2a0235e..d75aed5f 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -17,6 +17,8 @@ #include "modules/graphics/renderstate.hpp" #include "modules/graphics/samplerstate.hpp" +#include "driver/graphics/DrawCommand.hpp" + #include #include @@ -385,6 +387,14 @@ namespace love return info; } + BatchedVertexData requestBatchDraw(const DrawCommand& command); + + void flushBatchedDraws(); + + static void flushBatchedDrawsGlobal(); + + virtual void draw(const DrawIndexedCommand& command) = 0; + Stats getStats() const; size_t getStackDepth() const @@ -543,6 +553,42 @@ namespace love return 1.0; } + void polyline(const std::span vertices); + + void polygon(DrawMode mode, const std::span vertices, bool skipLastVertex = true); + + void rectangle(DrawMode mode, float x, float y, float w, float h); + + 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); + + void points(const Vector2* points, const Color* colors, int count); + + template + T* getScratchBuffer(size_t count) + { + size_t bytes = count * sizeof(T); + + if (this->scratchBuffer.size() < bytes) + this->scratchBuffer.resize(bytes); + + return (T*)this->scratchBuffer.data(); + } + // clang-format off STRINGMAP_DECLARE(DrawModes, DrawMode, { "line", DRAW_LINE }, @@ -572,6 +618,9 @@ namespace love ); // clang-format on + private: + int calculateEllipsePoints(float a, float b) const; + protected: bool created; bool active; @@ -592,5 +641,8 @@ namespace love int drawCallsBatched; int drawCalls; + + BatchedDrawState batchedDrawState; + std::vector scratchBuffer; }; } // namespace love diff --git a/include/modules/graphics/Shader.tcc b/include/modules/graphics/Shader.tcc index dca5c039..02d44488 100644 --- a/include/modules/graphics/Shader.tcc +++ b/include/modules/graphics/Shader.tcc @@ -41,17 +41,6 @@ namespace love this->attachDefault(STANDARD_DEFAULT); } - struct Uniform - { - uint32_t mdlvMtx; - uint32_t projMtx; - }; - - Uniform getUniforms() const - { - return this->locations; - } - virtual void attach() = 0; static void attachDefault(StandardShader type) @@ -78,8 +67,5 @@ namespace love return false; } - - protected: - Uniform locations; }; } // namespace love diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index 554b20ab..6b2959ac 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -167,6 +167,18 @@ namespace Wrap_Graphics int newCanvas(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 draw(lua_State* L); int getScreens(lua_State* L); diff --git a/platform/ctr/include/driver/display/Renderer.hpp b/platform/ctr/include/driver/display/Renderer.hpp index ddd52968..d9940e83 100644 --- a/platform/ctr/include/driver/display/Renderer.hpp +++ b/platform/ctr/include/driver/display/Renderer.hpp @@ -18,18 +18,12 @@ namespace love class Renderer : public RendererBase { public: - enum TexEnvMode - { - TEXENV_PRIMITIVE, - TEXENV_TEXTURE, - TEXENV_FONT, - TEXENV_MAX_ENUM - }; - Renderer(); void initialize(); + void setupContext(BatchedDrawState& state); + ~Renderer(); void clear(const Color& color); @@ -54,9 +48,7 @@ namespace love void setSamplerState(C3D_Tex* texture, SamplerState state); - void draw(const DrawIndexedCommand& command) override; - - virtual void updateUniforms() override; + virtual void prepareDraw() override; void setWideMode(bool wide) { @@ -143,63 +135,10 @@ namespace love this->createFramebuffers(); } - void setPrimitiveAttribute() - { - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - - C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); - } - - void setTextureAttribute() - { - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - - C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); - } - - void setFontAttribute() - { - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - - 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); - } - - void setAttribute(TexEnvMode mode) - { - if (this->context.mode == mode || mode == TEXENV_MAX_ENUM) - return; - - switch (mode) - { - default: - case TEXENV_PRIMITIVE: - this->setPrimitiveAttribute(); - break; - case TEXENV_TEXTURE: - this->setTextureAttribute(); - break; - case TEXENV_FONT: - this->setFontAttribute(); - break; - } - - this->context.mode = mode; - } - struct Context : public ContextBase { C3D_Mtx modelView; C3D_Mtx projection; - TexEnvMode mode = TEXENV_MAX_ENUM; C3D_BufInfo buffer; Framebuffer target; diff --git a/platform/ctr/include/modules/graphics/Graphics.hpp b/platform/ctr/include/modules/graphics/Graphics.hpp index 1b285d59..ca28b142 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -33,6 +33,10 @@ namespace love void draw(Texture* texture, Quad* quad, const Matrix4& matrix); + void draw(const DrawIndexedCommand& command) override; + + void points(Vector2* positions, const Color* colors, int count); + bool isActive() const; void unsetMode(); diff --git a/platform/ctr/include/modules/graphics/Shader.hpp b/platform/ctr/include/modules/graphics/Shader.hpp index 02c741f7..9fe69e55 100644 --- a/platform/ctr/include/modules/graphics/Shader.hpp +++ b/platform/ctr/include/modules/graphics/Shader.hpp @@ -11,6 +11,12 @@ namespace love class Shader final : public ShaderBase, public Volatile { public: + struct UniformInfo + { + int8_t location; + std::string name; + }; + Shader(); virtual ~Shader(); @@ -23,25 +29,19 @@ namespace love ptrdiff_t getHandle() const override; - void updateUniforms(const C3D_Mtx& mdlView, const C3D_Mtx& proj); + const UniformInfo getUniform(const std::string& name) const; - private: - struct TextureUnit - { - C3D_Tex* texture = nullptr; - TextureType type = TextureType::TEXTURE_2D; - bool isTexelBuffer = false; - bool active = false; - }; + bool hasUniform(const std::string& name) const; + + void updateBuiltinUniforms(const 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; - std::vector textureUnits; - - Uniform locations; + UniformInfo uniforms[0x02]; }; } // namespace love diff --git a/platform/ctr/include/modules/graphics/attributes.hpp b/platform/ctr/include/modules/graphics/attributes.hpp new file mode 100644 index 00000000..222bd1d8 --- /dev/null +++ b/platform/ctr/include/modules/graphics/attributes.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "modules/graphics/vertex.hpp" + +#include <3ds.h> +#include + +namespace love +{ + enum TexEnvMode + { + TEXENV_MODE_PRIMITIVE, + TEXENV_MODE_TEXTURE, + TEXENV_MODE_FONT, + TEXENV_MODE_MAX_ENUM + }; + + static TexEnvMode s_TexEnvMode = TEXENV_MODE_MAX_ENUM; + + void setTexEnvAttribute(TexEnvMode mode) + { + if (s_TexEnvMode == mode || mode == TEXENV_MODE_MAX_ENUM) + return; + + switch (mode) + { + default: + case TEXENV_MODE_PRIMITIVE: + { + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + + break; + } + case TEXENV_MODE_TEXTURE: + { + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + + break; + } + case TEXENV_MODE_FONT: + { + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + 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; + } + } + + s_TexEnvMode = mode; + } +} // namespace love diff --git a/platform/ctr/source/driver/display/Framebuffer.cpp b/platform/ctr/source/driver/display/Framebuffer.cpp index 92f47973..a502e843 100644 --- a/platform/ctr/source/driver/display/Framebuffer.cpp +++ b/platform/ctr/source/driver/display/Framebuffer.cpp @@ -13,7 +13,7 @@ namespace love { const auto side = (gfxIs3D() && info.name == "right") ? GFX_RIGHT : GFX_LEFT; - this->target = C3D_RenderTargetCreate(info.width, info.height, COLOR_FORMAT, DEPTH_FORMAT); + this->target = C3D_RenderTargetCreate(info.height, info.width, COLOR_FORMAT, DEPTH_FORMAT); if (!this->target) throw love::Exception("Failed to create render target '{:s}'.", info.name); diff --git a/platform/ctr/source/driver/display/Renderer.cpp b/platform/ctr/source/driver/display/Renderer.cpp index b5fe6231..46bfa12a 100644 --- a/platform/ctr/source/driver/display/Renderer.cpp +++ b/platform/ctr/source/driver/display/Renderer.cpp @@ -35,31 +35,31 @@ namespace love this->set3DMode(true); - this->batchedDrawState.vertices = (Vertex*)linearAlloc(sizeof(Vertex) * LOVE_UINT16_MAX); - this->batchedDrawState.verticesSize = LOVE_UINT16_MAX; + this->initialized = true; + } - if (!this->batchedDrawState.vertices) - throw love::Exception("Failed to allocate vertex buffer."); + void Renderer::setupContext(BatchedDrawState& state) + { + size_t size = sizeof(uint16_t) * LOVE_UINT16_MAX; + state.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, size); BufInfo_Init(&this->context.buffer); - if (BufInfo_Add(&this->context.buffer, this->batchedDrawState.vertices, sizeof(Vertex), 3, 0x210) < 0) - { - linearFree(this->batchedDrawState.vertices); - throw love::Exception("Failed to initialize vertex buffer."); - } + state.vertices = (Vertex*)linearAlloc(sizeof(Vertex) * LOVE_UINT16_MAX); + state.verticesSize = LOVE_UINT16_MAX; - C3D_SetBufInfo(&this->context.buffer); + int result = BufInfo_Add(&this->context.buffer, state.vertices, sizeof(Vertex), 3, 0x210); - this->initialized = true; + if (result < 0) + throw love::Exception("Failed to add buffer to BufInfo: {:d}", result); + + C3D_SetBufInfo(&this->context.buffer); } Renderer::~Renderer() { this->destroyFramebuffers(); - linearFree(this->batchedDrawState.vertices); - C3D_Fini(); gfxExit(); } @@ -106,7 +106,7 @@ namespace love void Renderer::bindFramebuffer() { this->ensureInFrame(); - this->flushBatchedDraws(); + GraphicsBase::flushBatchedDrawsGlobal(); this->context.target = this->targets[currentScreen]; auto viewport = this->context.target.getViewport(); @@ -119,29 +119,13 @@ namespace love { if (this->inFrame) { - this->flushBatchedDraws(); + GraphicsBase::flushBatchedDrawsGlobal(); C3D_FrameEnd(0); this->inFrame = false; } } - void Renderer::updateUniforms() - { - if (this->context.dirtyProjection) - { - if (Shader::current != nullptr) - { - auto uniforms = Shader::current->getUniforms(); - - C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uniforms.projMtx, &this->context.projection); - C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uniforms.mdlvMtx, &this->context.modelView); - } - - this->context.dirtyProjection = false; - } - } - void Renderer::setViewport(const Rect& viewport, bool tilt) { this->context.viewport = viewport; @@ -249,26 +233,14 @@ namespace love C3D_TexSetWrap(texture, wrapU, wrapV); } - void Renderer::draw(const DrawIndexedCommand& command) + void Renderer::prepareDraw() { - if (command.texture != nullptr) + if (Shader::current != nullptr) { - this->setAttribute(TEXENV_TEXTURE); - C3D_TexBind(0, (C3D_Tex*)command.texture->getHandle()); + // clang-format off + ((Shader*)Shader::current)->updateBuiltinUniforms(this->context.modelView, this->context.projection); + // clang-format on } - else - this->setAttribute(TEXENV_PRIMITIVE); - - GPU_Primitive_t primitiveType; - if (!Renderer::getConstant(command.primitiveType, primitiveType)) - throw love::Exception("Invalid primitive type: {:d}.", (int)command.primitiveType); - - decltype(C3D_UNSIGNED_BYTE) indexType; - if (!Renderer::getConstant(command.indexType, indexType)) - throw love::Exception("Invalid index type: {:d}.", (int)command.indexType); - - const void* elements = (const void*)command.indexBufferOffset; - C3D_DrawElements(primitiveType, command.indexCount, indexType, elements); } GPU_TEXTURE_WRAP_PARAM Renderer::getWrapMode(SamplerState::WrapMode mode) diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index e8ba4d95..afae78b8 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -4,7 +4,7 @@ #include "modules/graphics/Shader.hpp" #include "modules/window/Window.hpp" -#define BUFFER_OFFSET(i) ((char*)NULL + (i)) +#include "modules/graphics/attributes.hpp" namespace love { @@ -100,6 +100,7 @@ namespace love bool backBufferDepth, int msaa) { Renderer::getInstance().initialize(); + Renderer::getInstance().setupContext(this->batchedDrawState); this->created = true; @@ -147,6 +148,29 @@ namespace love return new Texture(*this, settings, data); } + void Graphics::points(Vector2* positions, const Color* colors, int count) + { + // Vector2* coords = positions; + + // float twoPi = float(LOVE_M_PI * 2); + // float shift = twoPi / count; + // float phi = 0.0f; + + // float x = coords[0].x; + // float y = coords[0].y; + + // float radius = this->states.back().pointSize; + + // for (int index = 0; index < count; ++index, phi += shift) + // { + // coords[index].x = x + radius * std::cos(phi); + // coords[index].y = y + radius * std::sin(phi); + // } + + // coords[count] = coords[0]; + // this->polygon(DRAW_FILL, std::span(coords, count + 2), false); + } + void Graphics::draw(Drawable* drawable, const Matrix4& matrix) { drawable->draw(*this, matrix); @@ -157,6 +181,30 @@ namespace love texture->draw(*this, quad, matrix); } + void Graphics::draw(const DrawIndexedCommand& command) + { + Renderer::getInstance().prepareDraw(); + + if (command.texture != nullptr) + { + setTexEnvAttribute(TEXENV_MODE_TEXTURE); + C3D_TexBind(0, (C3D_Tex*)command.texture->getHandle()); + } + else + setTexEnvAttribute(TEXENV_MODE_PRIMITIVE); + + GPU_Primitive_t primitiveType; + if (!Renderer::getConstant(command.primitiveType, primitiveType)) + throw love::Exception("Invalid primitive type: {:d}.", (int)command.primitiveType); + + decltype(C3D_UNSIGNED_BYTE) indexType; + if (!Renderer::getConstant(command.indexType, indexType)) + throw love::Exception("Invalid index type: {:d}.", (int)command.indexType); + + const void* elements = (const void*)command.indexBufferOffset; + C3D_DrawElements(primitiveType, command.indexCount, indexType, elements); + } + bool Graphics::isPixelFormatSupported(PixelFormat format, uint32_t usage) { format = this->getSizedFormat(format); diff --git a/platform/ctr/source/modules/graphics/Shader.cpp b/platform/ctr/source/modules/graphics/Shader.cpp index 0dadd81d..c5f5e5c2 100644 --- a/platform/ctr/source/modules/graphics/Shader.cpp +++ b/platform/ctr/source/modules/graphics/Shader.cpp @@ -7,7 +7,7 @@ namespace love { - Shader::Shader() : locations {} + Shader::Shader() : uniforms {} { this->loadVolatile(); } @@ -30,8 +30,8 @@ namespace love shaderProgramInit(&this->program); shaderProgramSetVsh(&this->program, &this->dvlb->DVLE[0]); - this->locations.mdlvMtx = shaderInstanceGetUniformLocation(this->program.vertexShader, "mdlvMtx"); - this->locations.projMtx = shaderInstanceGetUniformLocation(this->program.vertexShader, "projMtx"); + this->uniforms[0] = this->getUniform("mdlvMtx"); + this->uniforms[1] = this->getUniform("projMtx"); return true; } @@ -42,19 +42,42 @@ namespace love DVLB_Free(this->dvlb); } - void Shader::attach() + 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; + } + + void Shader::updateBuiltinUniforms(const C3D_Mtx& mdlvMtx, const C3D_Mtx& projMtx) { - if (current == this) - return; + if (this->hasUniform("mdlvMtx")) + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->uniforms[0].location, &mdlvMtx); - C3D_BindProgram(&this->program); - current = this; + if (this->hasUniform("projMtx")) + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->uniforms[1].location, &projMtx); } - void Shader::updateUniforms(const C3D_Mtx& mdlView, const C3D_Mtx& proj) + void Shader::attach() { - C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->locations.mdlvMtx, &mdlView); - C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->locations.projMtx, &proj); + if (current != this) + { + C3D_BindProgram(&this->program); + current = this; + } } ptrdiff_t Shader::getHandle() const diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp index b374dbed..b2b1367a 100644 --- a/platform/ctr/source/modules/graphics/Texture.cpp +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -188,7 +188,7 @@ namespace love command.vertexCount = 4; command.texture = this; - auto data = Renderer::getInstance().requestBatchDraw(command); + auto data = graphics.requestBatchDraw(command); Matrix4 translated(transform, matrix); diff --git a/source/common/luax.cpp b/source/common/luax.cpp index 20a854eb..a575c751 100644 --- a/source/common/luax.cpp +++ b/source/common/luax.cpp @@ -656,7 +656,7 @@ namespace love { lua_getfield(L, tableIndex, key); - int result; + int result = 0; if (!lua_isnumber(L, -1)) { @@ -854,11 +854,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 diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index befdb4f8..b75a2f2f 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -10,7 +10,8 @@ namespace love pixelHeight(0), created(false), active(true), - deviceProjectionMatrix() + deviceProjectionMatrix(), + batchedDrawState() { this->transformStack.reserve(16); this->transformStack.push_back(Matrix4()); @@ -188,4 +189,467 @@ namespace love { this->states.back().blend = state; } + + BatchedVertexData GraphicsBase::requestBatchDraw(const DrawCommand& 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.shader) + { + 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 }; + + { + size_t stride = getFormatStride(command.format); + size_t dataSize = stride * totalVertices; + + if (state.vertices != nullptr && dataSize > state.verticesSize) + shouldFlush = true; + + if (dataSize > state.verticesSize) + { + bufferSizes[0] = std::max(dataSize, state.verticesSize * 1.1f); + shouldResize = true; + } + + newDataSize = stride * command.vertexCount; + } + + if (command.indexMode != TRIANGLEINDEX_NONE) + { + size_t dataSize = (state.indexCount + requestedIndexCount) * sizeof(uint16_t); + + if (state.indexMap.data != nullptr && dataSize > state.indexMap.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.shader = command.shaderType; + } + + if (state.vertexCount == 0) + { + if (ShaderBase::isDefaultActive()) + ShaderBase::attachDefault(state.shader); + } + + if (shouldResize) + { + if (state.verticesSize < bufferSizes[0]) + { + linearFree(state.vertices); + state.vertices = (Vertex*)linearAlloc(bufferSizes[0]); + } + + if (state.indexBuffer->getSize() < bufferSizes[1]) + { + state.indexBuffer->release(); + state.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, bufferSizes[1]); + } + } + + if (command.indexMode != TRIANGLEINDEX_NONE) + { + if (state.indexMap.data == nullptr) + state.indexMap = state.indexBuffer->map(); + + uint16_t* indices = (uint16_t*)state.indexMap.data; + fillIndices(command.indexMode, state.vertexCount, command.vertexCount, indices); + + state.indexMap.data += requestedIndexSize; + } + + BatchedVertexData data {}; + + if (newDataSize > 0) + { + if (state.vertexMap.data == nullptr) + { + const auto size = command.vertexCount * sizeof(Vertex); + state.vertexMap = StreamBuffer::MapInfo((uint8_t*)state.vertices, size); + } + + data.stream = (Vertex*)state.vertexMap.data; + state.vertexMap.data += newDataSize; + } + + state.vertexCount += command.vertexCount; + state.indexCount += requestedIndexCount; + + return data; + } + + void GraphicsBase::flushBatchedDraws() + { + BatchedDrawState& state = this->batchedDrawState; + + if ((state.vertexCount == 0 && state.indexCount == 0) || state.flushing) + return; + + size_t usedSizes[2] = { 0, 0 }; + + if (state.format != CommonFormat::NONE) + state.vertexMap = StreamBuffer::MapInfo(); + + state.flushing = true; + + if (state.indexCount > 0) + { + DrawIndexedCommand command {}; + command.primitiveType = state.primitiveMode; + command.indexCount = state.indexCount; + command.indexType = INDEX_UINT16; + command.indexBufferOffset = state.indexBuffer->unmap(); + command.texture = state.texture; + + this->draw(command); + state.indexMap = StreamBuffer::MapInfo(); + } + + if (usedSizes[1] > 0) + state.indexBuffer->markUsed(usedSizes[1]); + + state.vertexCount = 0; + state.indexCount = 0; + state.flushing = false; + } + + void GraphicsBase::flushBatchedDrawsGlobal() + { + auto* instance = Module::getInstance(Module::M_GRAPHICS); + + if (instance != nullptr) + instance->flushBatchedDraws(); + } + + void GraphicsBase::polyline(const std::span vertices) + {} + + void GraphicsBase::polygon(DrawMode mode, std::span vertices, bool skipLastVertex) + { + if (mode == DRAW_LINE) + return this->polyline(vertices); + + const auto& transform = this->getTransform(); + bool is2D = transform.isAffine2DTransform(); + + DrawCommand command {}; + command.format = CommonFormat::XYf_STf_RGBAf; + command.indexMode = TRIANGLEINDEX_FAN; + command.vertexCount = (int)vertices.size() - (skipLastVertex ? 1 : 0); + + BatchedVertexData data = this->requestBatchDraw(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); + + for (int index = 0; index < command.vertexCount; index++) + { + data.stream[index].s = vertices[index].x * invSize.x - start.x; + data.stream[index].t = vertices[index].y * invSize.y - start.y; + data.stream[index].color = this->getColor(); + } + + if (is2D) + transform.transformXY(data.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) + { + // clang-format off + std::array coords = { Vector2(x, y), Vector2(x, y + h), Vector2(x + w, y + h), Vector2(x + w, y), Vector2(x, y) }; + // clang-format on + + 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 * std::cos(phi); + coords[index].y = y + b * std::sin(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 index = 0; index <= points; ++index, phi += shift) + { + coordinates[index].x = x + radius * std::cos(phi); + coordinates[index].y = y + radius * std::sin(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(); + + DrawCommand command {}; + command.primitiveMode = PRIMITIVE_POINTS; + command.format = CommonFormat::XYf_STf_RGBAf; + command.vertexCount = count; + + BatchedVertexData data = this->requestBatchDraw(command); + + if (is2D) + transform.transformXY(data.stream, positions, command.vertexCount); + + if (!colors) + { + Color color = this->getColor(); + + for (int index = 0; index < command.vertexCount; index++) + data.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); + + data.stream[index].color = current; + } + } + else + { + for (int index = 0; index < command.vertexCount; index++) + data.stream[index].color = colors[index]; + } + } } // namespace love diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index fe28e449..0fa2ba9b 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -1034,6 +1034,269 @@ int Wrap_Graphics::getStats(lua_State* L) 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; +} + // Homebrew Stuff™ int Wrap_Graphics::getScreens(lua_State* L) @@ -1162,6 +1425,13 @@ static constexpr luaL_Reg functions[] = { "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 }, + { "newTexture", Wrap_Graphics::newTexture }, { "newImage", Wrap_Graphics::newImage }, From 6dbd00a2bff7d1110258520b061648356e1f9683 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Fri, 3 May 2024 17:59:21 -0400 Subject: [PATCH 06/49] cleanup --- include/driver/graphics/DrawCommand.hpp | 129 ++-------- include/driver/graphics/StreamBuffer.tcc | 77 ++++++ include/modules/graphics/Drawable.hpp | 4 +- include/modules/graphics/Graphics.tcc | 23 +- include/modules/graphics/Texture.tcc | 17 +- .../ctr/include/driver/display/Renderer.hpp | 41 ++- .../include/driver/graphics/StreamBuffer.hpp | 52 ++++ .../ctr/include/modules/graphics/Graphics.hpp | 12 +- .../ctr/include/modules/graphics/Texture.hpp | 16 +- .../ctr/source/driver/display/Renderer.cpp | 23 +- .../ctr/source/modules/graphics/Graphics.cpp | 138 +++++++--- .../ctr/source/modules/graphics/Shader.cpp | 5 + .../source/modules/graphics/StreamBuffer.cpp | 56 ----- .../ctr/source/modules/graphics/Texture.cpp | 235 +++++++++++------- source/modules/graphics/Graphics.cpp | 226 ++++++++++++----- source/modules/graphics/Texture.cpp | 50 +++- source/modules/graphics/wrap_Graphics.cpp | 6 +- 17 files changed, 714 insertions(+), 396 deletions(-) create mode 100644 include/driver/graphics/StreamBuffer.tcc create mode 100644 platform/ctr/include/driver/graphics/StreamBuffer.hpp delete mode 100644 platform/ctr/source/modules/graphics/StreamBuffer.cpp diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp index fe2c83fc..2db24e57 100644 --- a/include/driver/graphics/DrawCommand.hpp +++ b/include/driver/graphics/DrawCommand.hpp @@ -6,95 +6,11 @@ #include "modules/graphics/Texture.tcc" #include "modules/graphics/vertex.hpp" -#if defined(__3DS__) - #include <3ds.h> - - #define _alloc(size) linearAlloc(size) - #define _free(ptr) linearFree(ptr) -#else - #define _alloc(size) malloc(size) - #define _free(ptr) free(ptr) -#endif +#include "driver/graphics/StreamBuffer.hpp" namespace love { - class StreamBuffer final : public Object - { - public: - struct MapInfo - { - uint8_t* data = nullptr; - size_t size = 0; - - MapInfo() - {} - - MapInfo(uint8_t* data, size_t size) : data(data), size(size) - {} - }; - - StreamBuffer(BufferUsage usage, size_t size) : - usage(usage), - data(nullptr), - bufferSize(size), - frameGPUReadOffset(0) - { - this->data = (uint8_t*)_alloc(size); - - if (this->data == nullptr) - throw love::Exception(E_OUT_OF_MEMORY); - - std::memset(this->data, 0, size); - } - - virtual ~StreamBuffer() - { - _free(this->data); - } - - MapInfo map() - { - return MapInfo(this->data, this->bufferSize); - } - - size_t unmap() - { - return (size_t)this->data; - } - - size_t getSize() const - { - return this->bufferSize; - } - - size_t getUsableSize() const - { - return this->bufferSize - this->frameGPUReadOffset; - } - - BufferUsage getMode() const - { - return this->usage; - } - - void markUsed(size_t) - {} - - ptrdiff_t getHandle() const - { - return 0; - } - - private: - BufferUsage usage; - - uint8_t* data; - size_t bufferSize; - - size_t frameGPUReadOffset; - }; - - struct DrawCommand + struct BatchedDrawCommand { PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; @@ -111,39 +27,46 @@ namespace love int indexCount = 0; int instanceCount = 1; - IndexDataType indexType = INDEX_UINT16; - size_t lastPosition = 0; - + IndexDataType indexType = INDEX_UINT16; size_t indexBufferOffset = 0; - TextureBase* texture; - CullMode cullMode = CULL_NONE; + TextureBase* texture = nullptr; + CullMode cullMode = CULL_NONE; + }; + + struct DrawCommand + { + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; + + int vertexStart = 0; + int vertexCount = 0; + int instanceCount = 1; + + TextureBase* texture = nullptr; + CullMode cullMode = CULL_NONE; }; struct BatchedVertexData { - Vertex* stream; + void* stream; }; struct BatchedDrawState { - Vertex* vertices; - size_t verticesSize; - - StreamBuffer::MapInfo vertexMap = StreamBuffer::MapInfo(); - - StreamBuffer* indexBuffer; - StreamBuffer::MapInfo indexMap = StreamBuffer::MapInfo(); - - PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; - ShaderBase::StandardShader shader = ShaderBase::STANDARD_DEFAULT; - CommonFormat format = CommonFormat::NONE; + 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; + StreamBuffer::MapInfo vertexMap = StreamBuffer::MapInfo(); + StreamBuffer::MapInfo indexMap = StreamBuffer::MapInfo(); + bool flushing = false; }; } // namespace love diff --git a/include/driver/graphics/StreamBuffer.tcc b/include/driver/graphics/StreamBuffer.tcc new file mode 100644 index 00000000..e177d930 --- /dev/null +++ b/include/driver/graphics/StreamBuffer.tcc @@ -0,0 +1,77 @@ +#pragma once + +#include "common/Object.hpp" + +#include "modules/graphics/vertex.hpp" + +namespace love +{ + class StreamBufferBase : public Object, public Resource + { + public: + struct MapInfo + { + uint8_t* data = nullptr; + size_t size = 0; + + MapInfo() + {} + + MapInfo(uint8_t* data, size_t size) : data(data), size(size) + {} + }; + + StreamBufferBase(BufferUsage usage, size_t size) : + usage(usage), + data(nullptr), + bufferSize(size), + frameGPUReadOffset(0) + {} + + virtual ~StreamBufferBase() + {} + + MapInfo map() + { + return MapInfo(this->data, this->bufferSize - this->frameGPUReadOffset); + } + + size_t unmap(size_t) + { + return (size_t)this->data; + } + + size_t getSize() const + { + return this->bufferSize; + } + + size_t getUsableSize() const + { + return this->bufferSize - this->frameGPUReadOffset; + } + + BufferUsage getMode() const + { + return this->usage; + } + + void markUsed(size_t size) + { + this->frameGPUReadOffset += size; + } + + void nextFrame() + { + this->frameGPUReadOffset = 0; + } + + protected: + BufferUsage usage; + + uint8_t* data; + size_t bufferSize; + + size_t frameGPUReadOffset; + }; +} // namespace love diff --git a/include/modules/graphics/Drawable.hpp b/include/modules/graphics/Drawable.hpp index cc6f245f..ff88b387 100644 --- a/include/modules/graphics/Drawable.hpp +++ b/include/modules/graphics/Drawable.hpp @@ -5,7 +5,7 @@ namespace love { - class Graphics; + class GraphicsBase; class Drawable : public Object { @@ -15,6 +15,6 @@ namespace love virtual ~Drawable() {} - virtual void draw(Graphics& graphics, const Matrix4& transform) = 0; + virtual void draw(GraphicsBase* graphics, const Matrix4& transform) = 0; }; } // namespace love diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index d75aed5f..2ac28168 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -216,6 +216,7 @@ namespace love void setActive(bool active) { + this->flushBatchedDraws(); this->active = active; } @@ -308,6 +309,13 @@ namespace love void setBlendMode(BlendMode mode, BlendAlpha alphaMode); + virtual TextureBase* newTexture(const TextureBase::Settings& settings, + const TextureBase::Slices* data = nullptr) = 0; + + TextureBase* getDefaultTexture(TextureType type); + + TextureBase* getDefaultTexture(TextureBase* texture); + BlendMode getBlendMode(BlendAlpha& alphaMode) const { return computeBlendMode(this->states.back().blend, alphaMode); @@ -352,6 +360,9 @@ namespace love void setPointSize(float size) { + if (size != this->states.back().pointSize) + this->flushBatchedDraws(); + this->states.back().pointSize = size; } @@ -387,7 +398,7 @@ namespace love return info; } - BatchedVertexData requestBatchDraw(const DrawCommand& command); + BatchedVertexData requestBatchDraw(const BatchedDrawCommand& command); void flushBatchedDraws(); @@ -529,6 +540,8 @@ namespace love void resetProjection() { + this->flushBatchedDraws(); + auto& state = this->states.back(); state.useCustomProjection = false; @@ -578,6 +591,10 @@ namespace love 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) { @@ -619,9 +636,11 @@ namespace love // clang-format on private: - int calculateEllipsePoints(float a, float b) const; + TextureBase* defaultTextures[TEXTURE_MAX_ENUM]; protected: + int calculateEllipsePoints(float a, float b) const; + bool created; bool active; diff --git a/include/modules/graphics/Texture.tcc b/include/modules/graphics/Texture.tcc index 0902578c..b85a9d0a 100644 --- a/include/modules/graphics/Texture.tcc +++ b/include/modules/graphics/Texture.tcc @@ -211,9 +211,12 @@ namespace love return this->viewFormats; } - virtual void draw(Graphics& graphics, const Matrix4& matrix) = 0; + virtual void updateQuad(Quad* quad) + {} + + virtual void draw(GraphicsBase* graphics, const Matrix4& matrix) override; - virtual void draw(Graphics& graphics, Quad* quad, const Matrix4& matrix) = 0; + void draw(GraphicsBase* graphics, Quad* quad, const Matrix4& matrix); bool isCompressed() const { @@ -291,6 +294,14 @@ namespace love return this->samplerState; } + virtual void setSamplerState(const SamplerState& state) = 0; + + virtual void replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, + bool reloadMipmaps) = 0; + + virtual void replacePixels(const void* data, size_t size, int slice, int mipmap, const Rect& rect, + bool reloadMipmaps) = 0; + static int getTotalMipmapCount(int width, int height) { return (int)std::log2(std::max(width, height)) + 1; @@ -335,7 +346,7 @@ namespace love // clang-format on protected: - TextureBase(GraphicsBase& graphics, const Settings& settings, const Slices* slices); + TextureBase(GraphicsBase* graphics, const Settings& settings, const Slices* slices); virtual ~TextureBase(); diff --git a/platform/ctr/include/driver/display/Renderer.hpp b/platform/ctr/include/driver/display/Renderer.hpp index d9940e83..bdd54c17 100644 --- a/platform/ctr/include/driver/display/Renderer.hpp +++ b/platform/ctr/include/driver/display/Renderer.hpp @@ -50,6 +50,36 @@ namespace love virtual void prepareDraw() override; + static decltype(C3D_UNSIGNED_BYTE) getIndexType(IndexDataType type) + { + switch (type) + { + case INDEX_UINT16: + default: + return C3D_UNSIGNED_SHORT; + } + + throw love::Exception("Invalid index type: {:d}.", (int)type); + return C3D_UNSIGNED_BYTE; + } + + static GPU_Primitive_t 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); + } + void setWideMode(bool wide) { this->modeChanged([this, wide]() { gfxSetWide(wide); }); @@ -71,12 +101,6 @@ namespace love } // 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 } - ); - ENUMMAP_DECLARE(PixelFormats, PixelFormat, GPU_TEXCOLOR, { PIXELFORMAT_RGBA8_UNORM, GPU_RGBA8 }, { PIXELFORMAT_RGBA4_UNORM, GPU_RGBA4 }, @@ -112,10 +136,6 @@ namespace love { BLENDFACTOR_ONE_MINUS_DST_ALPHA, GPU_ONE_MINUS_DST_ALPHA }, { BLENDFACTOR_SRC_ALPHA_SATURATED, GPU_SRC_ALPHA_SATURATE } ); - - ENUMMAP_DECLARE(IndexDataTypes, IndexDataType, decltype(C3D_UNSIGNED_BYTE), - { INDEX_UINT16, C3D_UNSIGNED_SHORT } - ); // clang-format on private: @@ -139,7 +159,6 @@ namespace love { C3D_Mtx modelView; C3D_Mtx projection; - C3D_BufInfo buffer; Framebuffer target; } context; diff --git a/platform/ctr/include/driver/graphics/StreamBuffer.hpp b/platform/ctr/include/driver/graphics/StreamBuffer.hpp new file mode 100644 index 00000000..719989b8 --- /dev/null +++ b/platform/ctr/include/driver/graphics/StreamBuffer.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "driver/graphics/StreamBuffer.tcc" + +#include <3ds.h> +#include + +namespace love +{ + class StreamBuffer final : public StreamBufferBase + { + public: + StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size), buffer(nullptr) + { + this->data = (uint8_t*)linearAlloc(size); + + if (this->data == nullptr) + throw love::Exception(E_OUT_OF_MEMORY); + + if (usage != BUFFERUSAGE_VERTEX) + return; + + this->buffer = new C3D_BufInfo(); + BufInfo_Init(this->buffer); + } + + ~StreamBuffer() + { + linearFree(this->data); + + delete this->buffer; + this->buffer = nullptr; + } + + void bind(const void* offset) + { + if (this->buffer == nullptr) + return; + + BufInfo_Add(this->buffer, offset, sizeof(Vertex), 3, 0x210); + C3D_SetBufInfo(this->buffer); + } + + ptrdiff_t getHandle() const override + { + return (ptrdiff_t)this->buffer; + } + + private: + C3D_BufInfo* buffer; + }; +} // namespace love diff --git a/platform/ctr/include/modules/graphics/Graphics.hpp b/platform/ctr/include/modules/graphics/Graphics.hpp index ca28b142..5e17638d 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -10,6 +10,8 @@ namespace love public: Graphics(); + ~Graphics(); + void clear(OptionalColor color, OptionalInt depth, OptionalDouble stencil); void clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth); @@ -29,12 +31,10 @@ namespace love bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, bool backBufferDepth, int msaa); - void draw(Drawable* drawable, const Matrix4& matrix); - - void draw(Texture* texture, Quad* quad, const Matrix4& matrix); - void draw(const DrawIndexedCommand& command) override; + using GraphicsBase::draw; + void points(Vector2* positions, const Color* colors, int count); bool isActive() const; @@ -43,7 +43,9 @@ namespace love void setViewport(int x, int y, int width, int height); - Texture* newTexture(const Texture::Settings& settings, const Texture::Slices* data = nullptr); + // clang-format off + virtual TextureBase* newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data = nullptr) override; + // clang-format on bool isPixelFormatSupported(PixelFormat format, uint32_t usage); diff --git a/platform/ctr/include/modules/graphics/Texture.hpp b/platform/ctr/include/modules/graphics/Texture.hpp index f99a8b3c..20b156df 100644 --- a/platform/ctr/include/modules/graphics/Texture.hpp +++ b/platform/ctr/include/modules/graphics/Texture.hpp @@ -10,7 +10,7 @@ namespace love class Texture final : public TextureBase, public Volatile { public: - Texture(Graphics& graphics, const Settings& settings, const Slices* data); + Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data); virtual ~Texture(); @@ -20,17 +20,21 @@ namespace love ptrdiff_t getHandle() const override; - void draw(Graphics& graphics, const Matrix4& matrix); + void updateQuad(Quad* quad); - void draw(Graphics& graphics, Quad* quad, const Matrix4& matrix); + void setSamplerState(const SamplerState& state) override; - void setSamplerState(const SamplerState& state); + void replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, + bool reloadMipmaps) override; + + void replacePixels(const void* data, size_t size, int slice, int mipmap, const Rect& rect, + bool reloadMipmaps) override; SamplerState validateSamplerState(SamplerState state) const; - bool validateDimensions(bool throwException) const; + // bool validateDimensions(bool throwException) const; - void validatePixelFormat(Graphics& graphics) const; + // void validatePixelFormat(Graphics& graphics) const; private: void createTexture(); diff --git a/platform/ctr/source/driver/display/Renderer.cpp b/platform/ctr/source/driver/display/Renderer.cpp index 46bfa12a..c780a3ee 100644 --- a/platform/ctr/source/driver/display/Renderer.cpp +++ b/platform/ctr/source/driver/display/Renderer.cpp @@ -39,22 +39,7 @@ namespace love } void Renderer::setupContext(BatchedDrawState& state) - { - size_t size = sizeof(uint16_t) * LOVE_UINT16_MAX; - state.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, size); - - BufInfo_Init(&this->context.buffer); - - state.vertices = (Vertex*)linearAlloc(sizeof(Vertex) * LOVE_UINT16_MAX); - state.verticesSize = LOVE_UINT16_MAX; - - int result = BufInfo_Add(&this->context.buffer, state.vertices, sizeof(Vertex), 3, 0x210); - - if (result < 0) - throw love::Exception("Failed to add buffer to BufInfo: {:d}", result); - - C3D_SetBufInfo(&this->context.buffer); - } + {} Renderer::~Renderer() { @@ -235,12 +220,10 @@ namespace love void Renderer::prepareDraw() { + // clang-format off if (Shader::current != nullptr) - { - // clang-format off ((Shader*)Shader::current)->updateBuiltinUniforms(this->context.modelView, this->context.projection); - // clang-format on - } + // clang-format on } GPU_TEXTURE_WRAP_PARAM Renderer::getWrapMode(SamplerState::WrapMode mode) diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index afae78b8..ce85d191 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -27,13 +27,16 @@ namespace love } } + Graphics::~Graphics() + {} + void Graphics::clear(OptionalColor color, OptionalInt depth, OptionalDouble stencil) { Renderer::getInstance().bindFramebuffer(); if (color.hasValue) { - // gammaCorrectColor(color.value); + gammaCorrectColor(color.value); Renderer::getInstance().clear(color.value); } @@ -53,6 +56,8 @@ namespace love this->clear(colors.size() > 0 ? colors[0] : OptionalColor(), stencil, depth); return; } + + this->flushBatchedDraws(); } void Graphics::present() @@ -60,8 +65,12 @@ namespace love if (!this->isActive()) return; + this->flushBatchedDraws(); Renderer::getInstance().present(); + this->batchedDrawState.vertexBuffer->nextFrame(); + this->batchedDrawState.indexBuffer->nextFrame(); + this->drawCalls = 0; this->drawCallsBatched = 0; } @@ -93,14 +102,23 @@ namespace love void Graphics::setBlendState(const BlendState& state) { GraphicsBase::setBlendState(state); - Renderer::getInstance().setBlendState(state); + + if (state.enable) + Renderer::getInstance().setBlendState(state); } bool Graphics::setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, bool backBufferDepth, int msaa) { Renderer::getInstance().initialize(); - Renderer::getInstance().setupContext(this->batchedDrawState); + + // clang-format off + if (this->batchedDrawState.vertexBuffer == nullptr) + { + this->batchedDrawState.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, sizeof(uint16_t) * LOVE_UINT16_MAX); + this->batchedDrawState.vertexBuffer = new StreamBuffer(BUFFERUSAGE_VERTEX, 64 * 1024 * 1); + } + // clang-format on this->created = true; @@ -130,7 +148,12 @@ namespace love } void Graphics::unsetMode() - {} + { + if (!this->isCreated()) + return; + + this->flushBatchedDraws(); + } bool Graphics::isActive() const { @@ -143,42 +166,87 @@ namespace love Renderer::getInstance().setViewport({ x, y, width, height }, true); } - Texture* Graphics::newTexture(const Texture::Settings& settings, const Texture::Slices* data) + TextureBase* Graphics::newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data) { - return new Texture(*this, settings, data); + return new Texture(this, settings, data); } void Graphics::points(Vector2* positions, const Color* colors, int count) { - // Vector2* coords = positions; + const float twoPi = float(LOVE_M_PI * 2); + const int extraPoints = 2; - // float twoPi = float(LOVE_M_PI * 2); - // float shift = twoPi / count; - // float phi = 0.0f; + const float pointSize = this->states.back().pointSize; - // float x = coords[0].x; - // float y = coords[0].y; + int points = this->calculateEllipsePoints(pointSize, pointSize); + const float shift = twoPi / (float)points; - // float radius = this->states.back().pointSize; + const Matrix4& transform = this->getTransform(); + bool is2D = transform.isAffine2DTransform(); - // for (int index = 0; index < count; ++index, phi += shift) - // { - // coords[index].x = x + radius * std::cos(phi); - // coords[index].y = y + radius * std::sin(phi); - // } + BatchedDrawCommand command {}; + command.format = CommonFormat::XYf_STf_RGBAf; + command.indexMode = TRIANGLEINDEX_FAN; + command.vertexCount = count * (points + extraPoints); - // coords[count] = coords[0]; - // this->polygon(DRAW_FILL, std::span(coords, count + 2), false); - } + BatchedVertexData data = this->requestBatchDraw(command); - void Graphics::draw(Drawable* drawable, const Matrix4& matrix) - { - drawable->draw(*this, matrix); - } + XYf_STf_RGBAf* stream = (XYf_STf_RGBAf*)data.stream; - void Graphics::draw(Texture* texture, Quad* quad, const Matrix4& matrix) - { - texture->draw(*this, quad, matrix); + for (int index = 0; index < count; index++) + { + const float x = positions[index].x; + const float y = positions[index].y; + + float phi = 0.0f; + + stream[0].x = x; + stream[0].y = y; + + for (int j = 1; j <= points; ++j, phi += shift) + { + stream[j].x = x + pointSize * std::cos(phi); + stream[j].y = y + pointSize * std::sin(phi); + } + + stream[points + 1] = stream[0]; + stream += (points + extraPoints); + } + + 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 Graphics::draw(const DrawIndexedCommand& command) @@ -193,16 +261,12 @@ namespace love else setTexEnvAttribute(TEXENV_MODE_PRIMITIVE); - GPU_Primitive_t primitiveType; - if (!Renderer::getConstant(command.primitiveType, primitiveType)) - throw love::Exception("Invalid primitive type: {:d}.", (int)command.primitiveType); - - decltype(C3D_UNSIGNED_BYTE) indexType; - if (!Renderer::getConstant(command.indexType, indexType)) - throw love::Exception("Invalid index type: {:d}.", (int)command.indexType); + const void* elements = (const void*)command.indexBufferOffset; + const auto primitiveType = Renderer::getPrimitiveType(command.primitiveType); + const auto dataType = Renderer::getIndexType(command.indexType); - const void* elements = (const void*)command.indexBufferOffset; - C3D_DrawElements(primitiveType, command.indexCount, indexType, elements); + C3D_DrawElements(primitiveType, command.indexCount, dataType, elements); + ++this->drawCalls; } bool Graphics::isPixelFormatSupported(PixelFormat format, uint32_t usage) diff --git a/platform/ctr/source/modules/graphics/Shader.cpp b/platform/ctr/source/modules/graphics/Shader.cpp index c5f5e5c2..fe3c3de2 100644 --- a/platform/ctr/source/modules/graphics/Shader.cpp +++ b/platform/ctr/source/modules/graphics/Shader.cpp @@ -64,6 +64,9 @@ namespace love void Shader::updateBuiltinUniforms(const C3D_Mtx& mdlvMtx, const C3D_Mtx& projMtx) { + if (current == this) + Graphics::flushBatchedDrawsGlobal(); + if (this->hasUniform("mdlvMtx")) C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->uniforms[0].location, &mdlvMtx); @@ -75,6 +78,8 @@ namespace love { if (current != this) { + Graphics::flushBatchedDrawsGlobal(); + C3D_BindProgram(&this->program); current = this; } diff --git a/platform/ctr/source/modules/graphics/StreamBuffer.cpp b/platform/ctr/source/modules/graphics/StreamBuffer.cpp deleted file mode 100644 index 45bb2b67..00000000 --- a/platform/ctr/source/modules/graphics/StreamBuffer.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "common/Exception.hpp" - -#include "modules/graphics/StreamBuffer.hpp" - -#include - -namespace love -{ - class StreamBufferClientMemory final : public StreamBufferBase - { - public: - StreamBufferClientMemory(BufferUsage mode, size_t size) : StreamBufferBase(mode, size), data(nullptr) - { - this->data = (uint8_t*)linearAlloc(size); - - if (this->data == nullptr) - throw love::Exception("Failed to allocate memory for StreamBufferClientMemory"); - } - - virtual ~StreamBufferClientMemory() - { - linearFree(this->data); - } - - size_t getGPUReadOffset() const override - { - return (size_t)this->data; - } - - MapInfo map(size_t) override - { - return MapInfo(this->data, this->bufferSize); - } - - size_t unmap(size_t) override - { - return (size_t)this->data; - } - - void markUsed(size_t) override - {} - - ptrdiff_t getHandle() const override - { - return 0; - } - - private: - uint8_t* data; - }; - - StreamBufferBase* createStreamBuffer(BufferUsage usage, size_t size) - { - return new StreamBufferClientMemory(usage, size); - } -} // namespace love diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp index b2b1367a..107cdb27 100644 --- a/platform/ctr/source/modules/graphics/Texture.cpp +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -30,13 +30,13 @@ namespace love throw love::Exception("Failed to create texture object!"); } - Texture::Texture(Graphics& graphics, const Settings& settings, const Slices* data) : + Texture::Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data) : TextureBase(graphics, settings, data), slices(settings.type) { - this->validateDimensions(true); + // this->validateDimensions(true); - this->validatePixelFormat(graphics); + // this->validatePixelFormat(graphics); if (data != nullptr) slices = *data; @@ -111,20 +111,28 @@ namespace love const auto powTwoWidth = NextPo2(this->pixelWidth); const auto powTwoHeight = NextPo2(this->pixelHeight); - if (this->isRenderTarget()) - createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight, clear); - else + try { - createTextureObject(this->texture, this->format, powTwoWidth, powTwoHeight, clear); + if (this->isRenderTarget()) + createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight, clear); + else + { + createTextureObject(this->texture, this->format, powTwoWidth, powTwoHeight, clear); - const auto size = love::getPixelFormatSliceSize(this->format, powTwoWidth, powTwoHeight, false); + const auto size = + love::getPixelFormatSliceSize(this->format, powTwoWidth, powTwoHeight, false); - if (!hasData) - std::memset(this->texture->data, 0, size); - else - std::memcpy(this->texture->data, this->slices.get(0, 0)->getData(), size); + if (!hasData) + std::memset(this->texture->data, 0, size); + else + std::memcpy(this->texture->data, this->slices.get(0, 0)->getData(), size); - C3D_TexFlush(this->texture); + C3D_TexFlush(this->texture); + } + } + catch (love::Exception& e) + { + throw; } this->setSamplerState(this->samplerState); @@ -159,50 +167,14 @@ namespace love quad->setTextureCoordinate(3, getVertex(virtualSize.x, 0.0f, virtualSize, realSize)); } - void Texture::draw(Graphics& graphics, const Matrix4& matrix) + void Texture::updateQuad(Quad* quad) { - this->draw(graphics, this->quad, matrix); - } - - void Texture::draw(Graphics& 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& 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); - - const auto& transform = graphics.getTransform(); - bool is2D = transform.isAffine2DTransform(); - - DrawCommand command {}; - command.format = CommonFormat::XYf_STf_RGBAf; - command.indexMode = TRIANGLEINDEX_QUADS; - command.vertexCount = 4; - command.texture = this; - - auto data = graphics.requestBatchDraw(command); - - Matrix4 translated(transform, matrix); - - if (is2D) - translated.transformXY(data.stream, quad->getVertexPositions(), 4); - - const auto* texCoords = quad->getTextureCoordinates(); - - for (int index = 0; index < 4; index++) - { - data.stream[index].s = texCoords[index].x; - data.stream[index].t = texCoords[index].y; - data.stream[index].color = graphics.getColor(); - } } void Texture::setSamplerState(const SamplerState& state) @@ -211,31 +183,6 @@ namespace love Renderer::getInstance().setSamplerState(this->texture, this->samplerState); } - bool Texture::validateDimensions(bool throwException) const - { - bool success = true; - - int largestSize = 0; - const char* name = nullptr; - - const bool widthIsLarge = ((size_t)this->pixelWidth > LOVE_TEX3DS_MAX); - const bool heightIsLarge = ((size_t)this->pixelHeight > LOVE_TEX3DS_MAX); - - // clang-format off - if ((this->textureType == TEXTURE_2D || this->textureType == TEXTURE_2D_ARRAY) && (widthIsLarge || heightIsLarge)) - { - success = false; - largestSize = std::max(this->pixelWidth, this->pixelHeight); - name = this->pixelWidth > this->pixelHeight ? "pixel width" : "pixel height"; - } - - if (throwException && name != nullptr) - throw love::Exception("Cannot create texture: {:s} of {:d} is too large for this system.", name, largestSize); - // clang-format on - - return success; - } - SamplerState Texture::validateSamplerState(SamplerState state) const { if (this->readable) @@ -250,29 +197,143 @@ namespace love if (this->textureType == TEXTURE_CUBE) state.wrapU = state.wrapV = state.wrapW = SamplerState::WRAP_CLAMP; + Graphics::flushBatchedDrawsGlobal(); + return state; } - void Texture::validatePixelFormat(Graphics& graphics) const + void Texture::replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, bool reloadMipmaps) { - uint32_t usage = PIXELFORMATUSAGEFLAGS_NONE; + 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 (data->getFormat() != this->getPixelFormat()) + throw love::Exception("Image data format does not match Texture format."); - if (renderTarget) - usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; - if (readable) - usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; - if (computeWrite) - usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; + if (mipmap < 0 || mipmap >= this->getMipmapCount()) + throw love::Exception("Invalid mipmap level: {:d}.", mipmap); - if (!graphics.isPixelFormatSupported(format, (PixelFormatUsageFlags)usage)) + 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())) { - std::string_view name = "unknown"; - love::getConstant(format, name); + throw love::Exception("Invalid slice: {:d}.", slice); + } + + Rect rectangle = { x, y, data->getWidth(), data->getHeight() }; - throw love::Exception("The pixel format '{:s}' is not supported by this system.", name); + int mipmapWidth = this->getPixelWidth(mipmap); + int mipmapHeight = this->getPixelHeight(mipmap); + + if (rectangle.x < 0 || rectangle.y < 0 || rectangle.w <= 0 || rectangle.h <= 0 || + (rectangle.x + rectangle.w) > mipmapWidth || (rectangle.y + rectangle.h) > mipmapHeight) + { + throw love::Exception("Invalid rectangle dimensions (x: {:d}, y: {:d}, w: {:d}, h: {:d}).", + rectangle.x, rectangle.y, rectangle.w, rectangle.h); } + + Graphics::flushBatchedDrawsGlobal(); + + this->replacePixels(data->getData(), data->getSize(), slice, mipmap, rectangle, reloadMipmaps); } + 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::replacePixels(const void* data, size_t size, int slice, int mipmap, const Rect& rect, + bool reloadMipmaps) + { + // clang-format off + switch (this->getPixelFormat()) + { + case PIXELFORMAT_RGB565_UNORM: + replaceTiledPixels(data, this->texture->data, rect, this->texture->width, this->texture->height); + break; + case PIXELFORMAT_RGBA8_UNORM: + default: + replaceTiledPixels(data, this->texture->data, rect, this->texture->width, this->texture->height); + break; + } + // clang-format on + + C3D_TexFlush(this->texture); + + Graphics::flushBatchedDrawsGlobal(); + } + + // void Texture::validatePixelFormat(Graphics& graphics) const + // { + // uint32_t usage = PIXELFORMATUSAGEFLAGS_NONE; + + // if (renderTarget) + // usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; + // if (readable) + // usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; + // if (computeWrite) + // usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; + + // if (!graphics.isPixelFormatSupported(format, (PixelFormatUsageFlags)usage)) + // { + // std::string_view name = "unknown"; + // love::getConstant(format, name); + + // throw love::Exception("The pixel format '{:s}' is not supported by this system.", name); + // } + // } + + // bool Texture::validateDimensions(bool throwException) const + // { + // bool success = true; + + // int largestSize = 0; + // const char* name = nullptr; + + // const bool widthIsLarge = ((size_t)this->pixelWidth > LOVE_TEX3DS_MAX); + // const bool heightIsLarge = ((size_t)this->pixelHeight > LOVE_TEX3DS_MAX); + + // // clang-format off + // if ((this->textureType == TEXTURE_2D || this->textureType == TEXTURE_2D_ARRAY) && (widthIsLarge || + // heightIsLarge)) + // { + // success = false; + // largestSize = std::max(this->pixelWidth, this->pixelHeight); + // name = this->pixelWidth > this->pixelHeight ? "pixel width" : "pixel height"; + // } + + // if (throwException && name != nullptr) + // throw love::Exception("Cannot create texture: {:s} of {:d} is too large for this system.", + // name, largestSize); + // // clang-format on + + // return success; + // } + ptrdiff_t Texture::getHandle() const { return (ptrdiff_t)this->texture; diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index b75a2f2f..69900eb1 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -1,5 +1,7 @@ #include "modules/graphics/Graphics.tcc" +#include "common/Console.hpp" + namespace love { GraphicsBase::GraphicsBase(const char* name) : @@ -25,7 +27,22 @@ namespace love 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(); + + if (this->batchedDrawState.vertexBuffer) + this->batchedDrawState.vertexBuffer->release(); + + if (this->batchedDrawState.indexBuffer) + this->batchedDrawState.indexBuffer->release(); } void GraphicsBase::restoreState(const DisplayState& state) @@ -67,7 +84,6 @@ namespace love } void GraphicsBase::restoreStateChecked(const DisplayState& state) - { const auto& current = this->states.back(); @@ -122,6 +138,8 @@ namespace love void GraphicsBase::setScissor(const Rect& scissor) { + this->flushBatchedDraws(); + auto& state = this->states.back(); Rect rect {}; @@ -136,6 +154,9 @@ namespace love void GraphicsBase::setScissor() { + if (this->states.back().scissor) + this->flushBatchedDraws(); + this->states.back().scissor = false; } @@ -144,6 +165,8 @@ namespace love Stats stats {}; stats.drawCalls = this->drawCalls; + if (this->batchedDrawState.vertexCount > 0) + stats.drawCalls++; stats.drawCallsBatched = this->drawCallsBatched; stats.textures = TextureBase::textureCount; @@ -154,6 +177,9 @@ namespace love void GraphicsBase::setFrontFaceWinding(Winding winding) { + if (this->states.back().winding != winding) + this->flushBatchedDraws(); + this->states.back().winding = winding; } @@ -164,6 +190,7 @@ namespace love void GraphicsBase::setColorMask(ColorChannelMask mask) { + this->flushBatchedDraws(); this->states.back().colorMask = mask; } @@ -187,10 +214,13 @@ namespace love void GraphicsBase::setBlendState(const BlendState& state) { + if (!(state == this->states.back().blend)) + this->flushBatchedDraws(); + this->states.back().blend = state; } - BatchedVertexData GraphicsBase::requestBatchDraw(const DrawCommand& command) + BatchedVertexData GraphicsBase::requestBatchDraw(const BatchedDrawCommand& command) { BatchedDrawState& state = this->batchedDrawState; @@ -202,7 +232,7 @@ namespace love || command.format != state.format || ((command.indexMode != TRIANGLEINDEX_NONE) != (state.indexCount > 0)) || command.texture != state.texture - || command.shaderType != state.shader) + || command.shaderType != state.shaderType) { shouldFlush = true; } @@ -219,16 +249,17 @@ namespace love 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.vertices != nullptr && dataSize > state.verticesSize) + if (state.vertexMap.data != nullptr && dataSize > state.vertexMap.size) shouldFlush = true; - if (dataSize > state.verticesSize) + if (dataSize > state.vertexBuffer->getUsableSize()) { - bufferSizes[0] = std::max(dataSize, state.verticesSize * 1.1f); + bufferSizes[0] = std::max(dataSize, state.vertexBuffer->getSize() * 2); shouldResize = true; } @@ -244,7 +275,7 @@ namespace love if (dataSize > state.indexBuffer->getUsableSize()) { - bufferSizes[1] = std::max(dataSize, state.indexBuffer->getSize() * 2); + bufferSizes[1] = std::max(dataSize, state.indexBuffer->getSize() * 2); shouldResize = true; } } @@ -256,21 +287,21 @@ namespace love state.primitiveMode = command.primitiveMode; state.format = command.format; state.texture = command.texture; - state.shader = command.shaderType; + state.shaderType = command.shaderType; } if (state.vertexCount == 0) { if (ShaderBase::isDefaultActive()) - ShaderBase::attachDefault(state.shader); + ShaderBase::attachDefault(state.shaderType); } if (shouldResize) { - if (state.verticesSize < bufferSizes[0]) + if (state.vertexBuffer->getSize() < bufferSizes[0]) { - linearFree(state.vertices); - state.vertices = (Vertex*)linearAlloc(bufferSizes[0]); + state.vertexBuffer->release(); + state.vertexBuffer = new StreamBuffer(BUFFERUSAGE_VERTEX, bufferSizes[0]); } if (state.indexBuffer->getSize() < bufferSizes[1]) @@ -296,15 +327,15 @@ namespace love if (newDataSize > 0) { if (state.vertexMap.data == nullptr) - { - const auto size = command.vertexCount * sizeof(Vertex); - state.vertexMap = StreamBuffer::MapInfo((uint8_t*)state.vertices, size); - } + state.vertexMap = state.vertexBuffer->map(); - data.stream = (Vertex*)state.vertexMap.data; + data.stream = state.vertexMap.data; state.vertexMap.data += newDataSize; } + if (state.vertexCount > 0) + this->drawCallsBatched++; + state.vertexCount += command.vertexCount; state.indexCount += requestedIndexCount; @@ -321,26 +352,42 @@ namespace love size_t usedSizes[2] = { 0, 0 }; if (state.format != CommonFormat::NONE) + { + usedSizes[0] = getFormatStride(state.format) * state.vertexCount; + + auto offset = state.vertexBuffer->unmap(usedSizes[0]); + state.vertexBuffer->bind((const void*)offset); + state.vertexMap = StreamBuffer::MapInfo(); + } state.flushing = true; + this->pushIdentityTransform(); + if (state.indexCount > 0) { + usedSizes[1] = sizeof(uint16_t) * state.indexCount; + DrawIndexedCommand command {}; command.primitiveType = state.primitiveMode; command.indexCount = state.indexCount; command.indexType = INDEX_UINT16; - command.indexBufferOffset = state.indexBuffer->unmap(); + command.indexBufferOffset = state.indexBuffer->unmap(usedSizes[1]); command.texture = state.texture; - this->draw(command); + state.indexMap = StreamBuffer::MapInfo(); } + if (usedSizes[0] > 0) + state.vertexBuffer->markUsed(usedSizes[0]); + if (usedSizes[1] > 0) state.indexBuffer->markUsed(usedSizes[1]); + this->popTransform(); + state.vertexCount = 0; state.indexCount = 0; state.flushing = false; @@ -354,49 +401,99 @@ namespace love instance->flushBatchedDraws(); } + TextureBase* GraphicsBase::getDefaultTexture(TextureBase* texture) + { + if (texture != nullptr) + return texture; + + return getDefaultTexture(TEXTURE_2D); + } + + TextureBase* GraphicsBase::getDefaultTexture(TextureType type) + { + TextureBase* texture = this->defaultTextures[type]; + + if (texture != nullptr) + return texture; + + TextureBase::Settings settings {}; + settings.type = type; + + 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 + 1, settings.height + 1 }, false); + // clang-format on + + this->defaultTextures[type] = texture; + + return texture; + } + void GraphicsBase::polyline(const std::span vertices) {} void GraphicsBase::polygon(DrawMode mode, std::span vertices, bool skipLastVertex) { if (mode == DRAW_LINE) - return this->polyline(vertices); + this->polyline(vertices); + else + { + const auto& transform = this->getTransform(); + bool is2D = transform.isAffine2DTransform(); - 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() - (skipLastVertex ? 1 : 0); - DrawCommand command {}; - command.format = CommonFormat::XYf_STf_RGBAf; - command.indexMode = TRIANGLEINDEX_FAN; - command.vertexCount = (int)vertices.size() - (skipLastVertex ? 1 : 0); + BatchedVertexData data = this->requestBatchDraw(command); - BatchedVertexData data = this->requestBatchDraw(command); + constexpr float inf = std::numeric_limits::infinity(); + Vector2 minimum(inf, inf); + Vector2 maximum(-inf, -inf); - 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); + } - 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); - 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++) - { - data.stream[index].s = vertices[index].x * invSize.x - start.x; - data.stream[index].t = vertices[index].y * invSize.y - start.y; - data.stream[index].color = this->getColor(); - } + 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(); + } - if (is2D) - transform.transformXY(data.stream, vertices.data(), command.vertexCount); + if (is2D) + transform.transformXY(stream, vertices.data(), command.vertexCount); + } } int GraphicsBase::calculateEllipsePoints(float a, float b) const @@ -557,10 +654,10 @@ namespace love int numCoords = 0; const auto createPoints = [&](Vector2* coordinates) { - for (int index = 0; index <= points; ++index, phi += shift) + for (int i = 0; i <= points; ++i, phi += shift) { - coordinates[index].x = x + radius * std::cos(phi); - coordinates[index].y = y + radius * std::sin(phi); + coordinates[i].x = x + radius * cosf(phi); + coordinates[i].y = y + radius * sinf(phi); } }; @@ -576,15 +673,14 @@ namespace love else if (arcMode == ARC_OPEN) { numCoords = points + 1; + coords = this->getScratchBuffer(numCoords); - coords = this->getScratchBuffer(numCoords); createPoints(coords); } else { numCoords = points + 2; - - coords = this->getScratchBuffer(numCoords); + coords = this->getScratchBuffer(numCoords); createPoints(coords); coords[numCoords - 1] = coords[0]; @@ -610,22 +706,24 @@ namespace love const Matrix4& transform = this->getTransform(); bool is2D = transform.isAffine2DTransform(); - DrawCommand command {}; + BatchedDrawCommand command {}; command.primitiveMode = PRIMITIVE_POINTS; command.format = CommonFormat::XYf_STf_RGBAf; command.vertexCount = count; BatchedVertexData data = this->requestBatchDraw(command); + XYf_STf_RGBAf* stream = (XYf_STf_RGBAf*)data.stream; + if (is2D) - transform.transformXY(data.stream, positions, command.vertexCount); + transform.transformXY(stream, positions, command.vertexCount); if (!colors) { Color color = this->getColor(); for (int index = 0; index < command.vertexCount; index++) - data.stream[index].color = color; + stream[index].color = color; return; } @@ -643,13 +741,23 @@ namespace love current *= color; unGammaCorrectColor(current); - data.stream[index].color = current; + stream[index].color = current; } } else { for (int index = 0; index < command.vertexCount; index++) - data.stream[index].color = colors[index]; + stream[index].color = colors[index]; } } + + void GraphicsBase::draw(Drawable* drawable, const Matrix4& matrix) + { + drawable->draw(this, matrix); + } + + void GraphicsBase::draw(TextureBase* texture, Quad* quad, const Matrix4& matrix) + { + texture->draw(this, quad, matrix); + } } // namespace love diff --git a/source/modules/graphics/Texture.cpp b/source/modules/graphics/Texture.cpp index 71d8046d..ffb1998c 100644 --- a/source/modules/graphics/Texture.cpp +++ b/source/modules/graphics/Texture.cpp @@ -1,6 +1,8 @@ #include "modules/graphics/Texture.tcc" #include "modules/graphics/Graphics.tcc" +#include "common/Console.hpp" + using namespace love; int TextureBase::textureCount = 0; @@ -168,7 +170,7 @@ namespace love // #endregion - TextureBase::TextureBase(GraphicsBase& graphics, const Settings& settings, const Slices* slices) : + TextureBase::TextureBase(GraphicsBase* graphics, const Settings& settings, const Slices* slices) : textureType(settings.type), format(settings.format), renderTarget(settings.renderTarget), @@ -244,7 +246,7 @@ namespace love else this->readable = !this->renderTarget || !love::isPixelFormatDepthStencil(this->format); - this->format = graphics.getSizedFormat(this->format); + this->format = graphics->getSizedFormat(this->format); if (!love::isGammaCorrect() || settings.linear) format = love::getLinearPixelFormat(format); @@ -319,6 +321,50 @@ namespace love totalGraphicsMemory += size; } + void TextureBase::draw(GraphicsBase* graphics, const Matrix4& matrix) + { + this->draw(graphics, this->quad, matrix); + } + + 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; + + auto data = graphics->requestBatchDraw(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(); + + for (int index = 0; index < 4; index++) + { + stream[index].s = texCoords[index].x; + stream[index].t = texCoords[index].y; + stream[index].color = graphics->getColor(); + } + } + void TextureBase::validateViewFormats() const {} diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 0fa2ba9b..15e7df7d 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -895,7 +895,7 @@ static void luax_checktexturesettings(lua_State* L, int index, bool optional, bo static int pushNewTexture(lua_State* L, TextureBase::Slices* slices, const Texture::Settings& settings) { - StrongRef texture; + StrongRef texture; // clang-format off luax_catchexcept(L, @@ -981,8 +981,8 @@ int Wrap_Graphics::newImage(lua_State* L) int Wrap_Graphics::draw(lua_State* L) { - Drawable* drawable = nullptr; - Texture* texture = nullptr; + Drawable* drawable = nullptr; + TextureBase* texture = nullptr; Quad* quad = luax_totype(L, 2); int start = 2; From 0584a6be3b594b1f51f560cc6f858b5cdbeea1fa Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sat, 4 May 2024 16:59:45 -0400 Subject: [PATCH 07/49] expose Quads to lua --- CMakeLists.txt | 1 + include/driver/graphics/DrawCommand.hpp | 3 + include/driver/graphics/StreamBuffer.tcc | 17 +++- include/modules/graphics/Graphics.tcc | 6 +- include/modules/graphics/Quad.hpp | 12 +++ include/modules/graphics/Shader.tcc | 39 +------- include/modules/graphics/wrap_Quad.hpp | 24 +++++ .../include/driver/graphics/StreamBuffer.hpp | 37 ++++--- .../ctr/source/modules/graphics/Graphics.cpp | 74 +++----------- .../ctr/source/modules/graphics/Texture.cpp | 17 ++-- source/modules/graphics/Graphics.cpp | 15 ++- source/modules/graphics/Shader.cpp | 39 ++++++++ source/modules/graphics/Texture.cpp | 2 +- source/modules/graphics/wrap_Graphics.cpp | 7 +- source/modules/graphics/wrap_Quad.cpp | 96 +++++++++++++++++++ 15 files changed, 252 insertions(+), 137 deletions(-) create mode 100644 include/modules/graphics/wrap_Quad.hpp create mode 100644 source/modules/graphics/wrap_Quad.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b06f442a..603a6afe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -364,6 +364,7 @@ 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_Quad.cpp source/modules/graphics/Texture.cpp source/modules/image/CompressedImageData.cpp source/modules/image/CompressedSlice.cpp diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp index 2db24e57..c3b8d050 100644 --- a/include/driver/graphics/DrawCommand.hpp +++ b/include/driver/graphics/DrawCommand.hpp @@ -69,4 +69,7 @@ namespace love bool flushing = false; }; + + static constexpr auto INIT_INDEX_BUFFER_SIZE = sizeof(uint16_t) * LOVE_UINT16_MAX; + static constexpr auto INIT_VERTEX_BUFFER_SIZE = 64 * 1024 * 1; } // namespace love diff --git a/include/driver/graphics/StreamBuffer.tcc b/include/driver/graphics/StreamBuffer.tcc index e177d930..cea98416 100644 --- a/include/driver/graphics/StreamBuffer.tcc +++ b/include/driver/graphics/StreamBuffer.tcc @@ -25,6 +25,7 @@ namespace love usage(usage), data(nullptr), bufferSize(size), + frameIndex(0), frameGPUReadOffset(0) {} @@ -33,14 +34,16 @@ namespace love MapInfo map() { - return MapInfo(this->data, this->bufferSize - this->frameGPUReadOffset); - } + MapInfo info {}; - size_t unmap(size_t) - { - return (size_t)this->data; + info.size = this->bufferSize - this->frameGPUReadOffset; + info.data = this->data + (this->frameIndex * this->bufferSize) + this->frameGPUReadOffset; + + return info; } + virtual size_t unmap(size_t) = 0; + size_t getSize() const { return this->bufferSize; @@ -63,15 +66,19 @@ namespace love void nextFrame() { + this->frameIndex = (this->frameIndex + 1) % BUFFER_FRAMES; this->frameGPUReadOffset = 0; } protected: + static constexpr int BUFFER_FRAMES = 2; + BufferUsage usage; uint8_t* data; size_t bufferSize; + size_t frameIndex; size_t frameGPUReadOffset; }; } // namespace love diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 2ac28168..8b9451fa 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -398,7 +398,7 @@ namespace love return info; } - BatchedVertexData requestBatchDraw(const BatchedDrawCommand& command); + BatchedVertexData requestBatchedDraw(const BatchedDrawCommand& command); void flushBatchedDraws(); @@ -566,9 +566,9 @@ namespace love return 1.0; } - void polyline(const std::span vertices); + void polyline(std::span vertices); - void polygon(DrawMode mode, const std::span vertices, bool skipLastVertex = true); + void polygon(DrawMode mode, std::span vertices, bool skipLastVertex = true); void rectangle(DrawMode mode, float x, float y, float w, float h); diff --git a/include/modules/graphics/Quad.hpp b/include/modules/graphics/Quad.hpp index c0e1db52..ca4e95c8 100644 --- a/include/modules/graphics/Quad.hpp +++ b/include/modules/graphics/Quad.hpp @@ -38,6 +38,16 @@ namespace love return this->viewport; } + void setLayer(int layer) + { + this->layer = layer; + } + + int getLayer() const + { + return this->layer; + } + double getTextureWidth() const { return this->sourceWidth; @@ -65,5 +75,7 @@ namespace love Viewport viewport; double sourceWidth; double sourceHeight; + + int layer; }; } // namespace love diff --git a/include/modules/graphics/Shader.tcc b/include/modules/graphics/Shader.tcc index 02d44488..e1d40723 100644 --- a/include/modules/graphics/Shader.tcc +++ b/include/modules/graphics/Shader.tcc @@ -18,6 +18,8 @@ namespace love public: static Type type; + static int shaderSwitches; + enum StandardShader { STANDARD_DEFAULT, @@ -29,43 +31,12 @@ namespace love static ShaderBase* current; static ShaderBase* standardShaders[STANDARD_MAX_ENUM]; - virtual ~ShaderBase() - { - for (int index = 0; index < STANDARD_MAX_ENUM; index++) - { - if (this == standardShaders[index]) - standardShaders[index] = nullptr; - } - - if (current == this) - this->attachDefault(STANDARD_DEFAULT); - } + virtual ~ShaderBase(); virtual void attach() = 0; - static void attachDefault(StandardShader type) - { - auto* defaultShader = standardShaders[type]; - - if (defaultShader == nullptr) - { - current = nullptr; - return; - } - - if (current != defaultShader) - defaultShader->attach(); - } - - static bool isDefaultActive() - { - for (int index = 0; index < STANDARD_MAX_ENUM; index++) - { - if (current == standardShaders[index]) - return true; - } + static void attachDefault(StandardShader type); - return false; - } + static bool isDefaultActive(); }; } // namespace love diff --git a/include/modules/graphics/wrap_Quad.hpp b/include/modules/graphics/wrap_Quad.hpp new file mode 100644 index 00000000..cf5e134b --- /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/platform/ctr/include/driver/graphics/StreamBuffer.hpp b/platform/ctr/include/driver/graphics/StreamBuffer.hpp index 719989b8..41728fcf 100644 --- a/platform/ctr/include/driver/graphics/StreamBuffer.hpp +++ b/platform/ctr/include/driver/graphics/StreamBuffer.hpp @@ -1,30 +1,42 @@ #pragma once #include "driver/graphics/StreamBuffer.tcc" +#include "modules/graphics/Volatile.hpp" #include <3ds.h> #include namespace love { - class StreamBuffer final : public StreamBufferBase + class StreamBuffer final : public StreamBufferBase, public Volatile { public: StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size), buffer(nullptr) { - this->data = (uint8_t*)linearAlloc(size); + this->loadVolatile(); + } + + bool loadVolatile() + { + const auto size = this->getSize() * BUFFER_FRAMES; + this->data = (uint8_t*)linearAlloc(size); if (this->data == nullptr) - throw love::Exception(E_OUT_OF_MEMORY); + return false; - if (usage != BUFFERUSAGE_VERTEX) - return; + if (this->usage != BufferUsage::BUFFERUSAGE_VERTEX) + return true; this->buffer = new C3D_BufInfo(); BufInfo_Init(this->buffer); + + BufInfo_Add(this->buffer, this->data, sizeof(Vertex), 3, 0x210); + C3D_SetBufInfo(this->buffer); + + return true; } - ~StreamBuffer() + void unloadVolatile() { linearFree(this->data); @@ -32,18 +44,19 @@ namespace love this->buffer = nullptr; } - void bind(const void* offset) + ~StreamBuffer() { - if (this->buffer == nullptr) - return; + this->unloadVolatile(); + } - BufInfo_Add(this->buffer, offset, sizeof(Vertex), 3, 0x210); - C3D_SetBufInfo(this->buffer); + size_t unmap(size_t) override + { + return (size_t)this->data; } ptrdiff_t getHandle() const override { - return (ptrdiff_t)this->buffer; + return 0; } private: diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index ce85d191..048d940f 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -115,8 +115,8 @@ namespace love // clang-format off if (this->batchedDrawState.vertexBuffer == nullptr) { - this->batchedDrawState.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, sizeof(uint16_t) * LOVE_UINT16_MAX); - this->batchedDrawState.vertexBuffer = new StreamBuffer(BUFFERUSAGE_VERTEX, 64 * 1024 * 1); + this->batchedDrawState.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, INIT_INDEX_BUFFER_SIZE); + this->batchedDrawState.vertexBuffer = new StreamBuffer(BUFFERUSAGE_VERTEX, INIT_VERTEX_BUFFER_SIZE); } // clang-format on @@ -173,65 +173,16 @@ namespace love void Graphics::points(Vector2* positions, const Color* colors, int count) { - const float twoPi = float(LOVE_M_PI * 2); - const int extraPoints = 2; - - const float pointSize = this->states.back().pointSize; - - int points = this->calculateEllipsePoints(pointSize, pointSize); - const float shift = twoPi / (float)points; - - const Matrix4& transform = this->getTransform(); - bool is2D = transform.isAffine2DTransform(); - - BatchedDrawCommand command {}; - command.format = CommonFormat::XYf_STf_RGBAf; - command.indexMode = TRIANGLEINDEX_FAN; - command.vertexCount = count * (points + extraPoints); - - BatchedVertexData data = this->requestBatchDraw(command); - - XYf_STf_RGBAf* stream = (XYf_STf_RGBAf*)data.stream; + const auto pointSize = this->states.back().pointSize; for (int index = 0; index < count; index++) { - const float x = positions[index].x; - const float y = positions[index].y; - - float phi = 0.0f; + const auto& position = positions[index]; + auto& color = colors[index]; - stream[0].x = x; - stream[0].y = y; + gammaCorrectColor(this->getColor()); - for (int j = 1; j <= points; ++j, phi += shift) - { - stream[j].x = x + pointSize * std::cos(phi); - stream[j].y = y + pointSize * std::sin(phi); - } - - stream[points + 1] = stream[0]; - stream += (points + extraPoints); - } - - 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++) + if (isGammaCorrect()) { Color current = colors[index]; @@ -239,13 +190,12 @@ namespace love current *= color; unGammaCorrectColor(current); - stream[index].color = current; + this->setColor(current); } - } - else - { - for (int index = 0; index < command.vertexCount; index++) - stream[index].color = colors[index]; + else + this->setColor(color); + + this->circle(DRAW_FILL, position.x, position.y, pointSize); } } diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp index 107cdb27..8db7bd70 100644 --- a/platform/ctr/source/modules/graphics/Texture.cpp +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -7,7 +7,7 @@ namespace love { static void createFramebufferObject(C3D_RenderTarget*& target, C3D_Tex* texture, uint16_t width, - uint16_t height, bool clear) + uint16_t height) { texture = new C3D_Tex(); @@ -17,8 +17,7 @@ namespace love target = C3D_RenderTargetCreateFromTex(texture, GPU_TEXFACE_2D, 0, GPU_RB_DEPTH16); } - static void createTextureObject(C3D_Tex*& texture, PixelFormat format, uint16_t width, uint16_t height, - bool clear) + static void createTextureObject(C3D_Tex*& texture, PixelFormat format, uint16_t width, uint16_t height) { GPU_TEXCOLOR gpuFormat; if (!love::Renderer::getConstant(format, gpuFormat)) @@ -114,17 +113,13 @@ namespace love try { if (this->isRenderTarget()) - createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight, clear); + createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight); else { - createTextureObject(this->texture, this->format, powTwoWidth, powTwoHeight, clear); + createTextureObject(this->texture, this->format, powTwoWidth, powTwoHeight); + auto size = love::getPixelFormatSliceSize(this->format, powTwoWidth, powTwoHeight, false); - const auto size = - love::getPixelFormatSliceSize(this->format, powTwoWidth, powTwoHeight, false); - - if (!hasData) - std::memset(this->texture->data, 0, size); - else + if (hasData) std::memcpy(this->texture->data, this->slices.get(0, 0)->getData(), size); C3D_TexFlush(this->texture); diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 69900eb1..0616e8c9 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -171,6 +171,7 @@ namespace love stats.drawCallsBatched = this->drawCallsBatched; stats.textures = TextureBase::textureCount; stats.textureMemory = TextureBase::totalGraphicsMemory; + stats.shaderSwitches = ShaderBase::shaderSwitches; return stats; } @@ -220,7 +221,7 @@ namespace love this->states.back().blend = state; } - BatchedVertexData GraphicsBase::requestBatchDraw(const BatchedDrawCommand& command) + BatchedVertexData GraphicsBase::requestBatchedDraw(const BatchedDrawCommand& command) { BatchedDrawState& state = this->batchedDrawState; @@ -355,9 +356,7 @@ namespace love { usedSizes[0] = getFormatStride(state.format) * state.vertexCount; - auto offset = state.vertexBuffer->unmap(usedSizes[0]); - state.vertexBuffer->bind((const void*)offset); - + state.vertexBuffer->unmap(usedSizes[0]); state.vertexMap = StreamBuffer::MapInfo(); } @@ -447,10 +446,10 @@ namespace love return texture; } - void GraphicsBase::polyline(const std::span vertices) + void GraphicsBase::polyline(std::span vertices) {} - void GraphicsBase::polygon(DrawMode mode, std::span vertices, bool skipLastVertex) + void GraphicsBase::polygon(DrawMode mode, std::span vertices, bool skipLastVertex) { if (mode == DRAW_LINE) this->polyline(vertices); @@ -464,7 +463,7 @@ namespace love command.indexMode = TRIANGLEINDEX_FAN; command.vertexCount = (int)vertices.size() - (skipLastVertex ? 1 : 0); - BatchedVertexData data = this->requestBatchDraw(command); + BatchedVertexData data = this->requestBatchedDraw(command); constexpr float inf = std::numeric_limits::infinity(); Vector2 minimum(inf, inf); @@ -711,7 +710,7 @@ namespace love command.format = CommonFormat::XYf_STf_RGBAf; command.vertexCount = count; - BatchedVertexData data = this->requestBatchDraw(command); + BatchedVertexData data = this->requestBatchedDraw(command); XYf_STf_RGBAf* stream = (XYf_STf_RGBAf*)data.stream; diff --git a/source/modules/graphics/Shader.cpp b/source/modules/graphics/Shader.cpp index 1f557ec4..ca214446 100644 --- a/source/modules/graphics/Shader.cpp +++ b/source/modules/graphics/Shader.cpp @@ -4,4 +4,43 @@ namespace love { ShaderBase* ShaderBase::current = nullptr; ShaderBase* ShaderBase::standardShaders[STANDARD_MAX_ENUM] = { nullptr }; + + int ShaderBase::shaderSwitches = 0; + + 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/Texture.cpp b/source/modules/graphics/Texture.cpp index ffb1998c..f4f76d23 100644 --- a/source/modules/graphics/Texture.cpp +++ b/source/modules/graphics/Texture.cpp @@ -343,7 +343,7 @@ namespace love command.vertexCount = 4; command.texture = this; - auto data = graphics->requestBatchDraw(command); + auto data = graphics->requestBatchedDraw(command); Matrix4 translated(transform, matrix); diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 15e7df7d..97b7ede7 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -4,6 +4,7 @@ #include "modules/filesystem/wrap_Filesystem.hpp" +#include "modules/graphics/wrap_Quad.hpp" #include "modules/graphics/wrap_Texture.hpp" #include "modules/image/Image.hpp" @@ -1025,6 +1026,9 @@ int Wrap_Graphics::getStats(lua_State* L) 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"); @@ -1447,7 +1451,8 @@ static int open_drawable(lua_State* L) static constexpr lua_CFunction types[] = { open_drawable, - love::open_texture + love::open_texture, + love::open_quad }; // clang-format on diff --git a/source/modules/graphics/wrap_Quad.cpp b/source/modules/graphics/wrap_Quad.cpp new file mode 100644 index 00000000..3e2c0726 --- /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 From 986bc4852da80d904d9268eb8c48a1126490cd7e Mon Sep 17 00:00:00 2001 From: TurtleP Date: Mon, 13 May 2024 17:31:24 -0400 Subject: [PATCH 08/49] push 3ds work --- include/common/error.hpp | 45 +- include/common/screen.hpp | 13 + include/driver/display/Renderer.tcc | 25 +- include/driver/graphics/DrawCommand.hpp | 34 +- include/driver/graphics/StreamBuffer.tcc | 110 +++-- include/modules/graphics/Graphics.tcc | 411 +++++++++++------- include/modules/graphics/Texture.tcc | 12 + include/modules/graphics/vertex.hpp | 5 +- platform/ctr/CMakeLists.txt | 2 +- .../display/{Renderer.hpp => citro3d.hpp} | 94 ++-- .../include/driver/graphics/StreamBuffer.hpp | 60 +-- .../ctr/include/modules/graphics/Graphics.hpp | 40 +- .../ctr/include/modules/graphics/Texture.hpp | 2 + .../include/modules/graphics/attributes.hpp | 65 --- platform/ctr/source/boot.cpp | 2 - .../ctr/source/driver/display/Renderer.cpp | 243 ----------- .../ctr/source/driver/display/citro3d.cpp | 405 +++++++++++++++++ .../ctr/source/modules/graphics/Graphics.cpp | 271 +++++++++--- .../ctr/source/modules/graphics/Shader.cpp | 5 +- .../ctr/source/modules/graphics/Texture.cpp | 24 +- source/modules/audio/Source.cpp | 23 +- source/modules/data/ByteData.cpp | 2 +- source/modules/data/DataModule.cpp | 17 +- source/modules/data/DataView.cpp | 10 +- source/modules/graphics/Graphics.cpp | 384 ++++++++++++++-- source/modules/graphics/Texture.cpp | 5 +- source/modules/graphics/vertex.cpp | 10 +- source/modules/graphics/wrap_Graphics.cpp | 61 ++- source/modules/image/magpie/T3XHandler.cpp | 9 +- source/modules/love/scripts/callbacks.lua | 13 +- 30 files changed, 1589 insertions(+), 813 deletions(-) rename platform/ctr/include/driver/display/{Renderer.hpp => citro3d.hpp} (73%) delete mode 100644 platform/ctr/include/modules/graphics/attributes.hpp delete mode 100644 platform/ctr/source/driver/display/Renderer.cpp create mode 100644 platform/ctr/source/driver/display/citro3d.cpp diff --git a/include/common/error.hpp b/include/common/error.hpp index 2423c8e7..0e9149be 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,12 @@ 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 + // 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/screen.hpp b/include/common/screen.hpp index a0af5ff2..72bfa1a4 100644 --- a/include/common/screen.hpp +++ b/include/common/screen.hpp @@ -47,4 +47,17 @@ namespace love return INVALID_SCREEN; } + + inline void nextScreen() + { + const auto& info = getScreenInfo(); + + if (currentScreen == INVALID_SCREEN) + currentScreen = DEFAULT_SCREEN; + else + currentScreen = (Screen)((int)currentScreen + 1); + + if (currentScreen >= (Screen)info.size()) + currentScreen = DEFAULT_SCREEN; + } } // namespace love diff --git a/include/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index d2892205..d39a7eea 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -11,35 +11,14 @@ #include #include -#define BUFFER_OFFSET(i) ((char*)NULL + (i)) - namespace love { - template - class RendererBase : public Singleton + class RendererBase { public: - enum RENDERER_INFO - { - RENDERER_INFO_NAME, - RENDERER_INFO_VERSION, - RENDERER_INFO_VENDOR, - RENDERER_INFO_DEVICE - }; - RendererBase() {} - size_t getVertexCount() const - { - return this->renderCtx.vertexCount; - } - - Vertex* getVertices() - { - return this->data; - } - virtual void prepareDraw() = 0; protected: @@ -54,8 +33,6 @@ namespace love Rect scissor; Rect viewport; - - ShaderBase::StandardShader shader = ShaderBase::STANDARD_DEFAULT; }; bool initialized = false; diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp index c3b8d050..3048d508 100644 --- a/include/driver/graphics/DrawCommand.hpp +++ b/include/driver/graphics/DrawCommand.hpp @@ -27,11 +27,23 @@ namespace love 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; + + DrawIndexedCommand(const VertexAttributes* attributes, const BufferBindings* buffers, + Resource* indexBuffer) : + attributes(attributes), + buffers(buffers), + indexBuffer(indexBuffer) + {} }; struct DrawCommand @@ -53,8 +65,8 @@ namespace love struct BatchedDrawState { - StreamBuffer* vertexBuffer = nullptr; - StreamBuffer* indexBuffer = nullptr; + StreamBuffer* vertexBuffer = nullptr; + StreamBuffer* indexBuffer = nullptr; PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; CommonFormat format = CommonFormat::NONE; @@ -64,12 +76,22 @@ namespace love int vertexCount = 0; int indexCount = 0; - StreamBuffer::MapInfo vertexMap = StreamBuffer::MapInfo(); - StreamBuffer::MapInfo indexMap = StreamBuffer::MapInfo(); + MapInfo vertexBufferMap = MapInfo(); + MapInfo indexBufferMap = MapInfo(); bool flushing = false; }; - static constexpr auto INIT_INDEX_BUFFER_SIZE = sizeof(uint16_t) * LOVE_UINT16_MAX; - static constexpr auto INIT_VERTEX_BUFFER_SIZE = 64 * 1024 * 1; + 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 index cea98416..6a1706d5 100644 --- a/include/driver/graphics/StreamBuffer.tcc +++ b/include/driver/graphics/StreamBuffer.tcc @@ -4,49 +4,40 @@ #include "modules/graphics/vertex.hpp" +#include + namespace love { - class StreamBufferBase : public Object, public Resource + template + struct MapInfo { - public: - struct MapInfo - { - uint8_t* data = nullptr; - size_t size = 0; - - MapInfo() - {} + T* data = nullptr; + size_t size = 0; - MapInfo(uint8_t* data, size_t size) : data(data), size(size) - {} - }; + MapInfo() + {} - StreamBufferBase(BufferUsage usage, size_t size) : - usage(usage), - data(nullptr), - bufferSize(size), - frameIndex(0), - frameGPUReadOffset(0) + 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() {} - MapInfo map() + size_t getSize() const { - MapInfo info {}; - - info.size = this->bufferSize - this->frameGPUReadOffset; - info.data = this->data + (this->frameIndex * this->bufferSize) + this->frameGPUReadOffset; - - return info; + return this->bufferSize; } - virtual size_t unmap(size_t) = 0; - - size_t getSize() const + BufferUsage getMode() const { - return this->bufferSize; + return this->usage; } size_t getUsableSize() const @@ -54,31 +45,62 @@ namespace love return this->bufferSize - this->frameGPUReadOffset; } - BufferUsage getMode() const + /* + ** Get the offset of the buffer that the GPU can read from. + ** This is the offset of the current frame. + ** @return The offset in bytes. + */ + size_t getGPUReadOffset() const { - return this->usage; + return (this->frameIndex * this->bufferSize) + this->frameGPUReadOffset; } - void markUsed(size_t size) + int getBufferIndex() const { - this->frameGPUReadOffset += size; + return (this->frameIndex * this->bufferSize) + this->index; } - void nextFrame() - { - this->frameIndex = (this->frameIndex + 1) % BUFFER_FRAMES; - this->frameGPUReadOffset = 0; - } + /* + ** Map the buffer for writing. + ** @return A pointer to the buffer. + */ + virtual MapInfo map(size_t) = 0; - protected: - static constexpr int BUFFER_FRAMES = 2; + /* + ** Unmap the buffer. + ** @return Offset of the buffer that was written to. + */ + virtual size_t unmap(size_t) = 0; - BufferUsage usage; + /* + ** Mark the buffer as used. + ** @param count The count of T of the buffer that was used. + */ + virtual void markUsed(int count) = 0; - uint8_t* data; - size_t bufferSize; + void clear() + {} - size_t frameIndex; + /* + ** Advance to the next frame. + */ + virtual void nextFrame() = 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/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 8b9451fa..914c3e12 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -103,6 +103,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, @@ -116,6 +158,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; @@ -135,6 +184,138 @@ namespace love int buffers; int64_t textureMemory; int64_t bufferMemory; + float cpuProcessingTime; + float gpuDrawingTime; + }; + + class TempTransform + { + public: + TempTransform(GraphicsBase* graphics) : graphics(graphics) + { + graphics->pushTransform(); + } + + TempTransform(GraphicsBase* graphics, const Matrix4& transform) : graphics(graphics) + { + graphics->pushTransform(); + graphics->transformStack.back() *= transform; + } + + ~TempTransform() + { + graphics->popTransform(); + } + + private: + GraphicsBase* graphics; + }; + + struct ScreenshotInfo; + typedef void (*ScreenshotCallback)(const ScreenshotInfo* info, ImageData* i, void* ud); + + struct ScreenshotInfo + { + ScreenshotCallback callback = nullptr; + void* data = nullptr; + }; + + struct RenderTargetStrongRef; + + struct RenderTarget + { + TextureBase* texture; + int slice; + int mipmap; + + RenderTarget(TextureBase* texture, int slice = 0, int mipmap = 0) : + texture(texture), + slice(slice), + mipmap(mipmap) + {} + + RenderTarget() : texture(nullptr), slice(0), mipmap(0) + {} + + bool operator!=(const RenderTarget& other) const + { + return this->texture != other.texture || this->slice != other.slice || + this->mipmap != other.mipmap; + } + + bool operator!=(const RenderTargetStrongRef& other) const + { + return this->texture != other.texture.get() || this->slice != other.slice || + this->mipmap != other.mipmap; + } + }; + + struct RenderTargetStrongRef + { + StrongRef texture; + int slice = 0; + int mipmap = 0; + + RenderTargetStrongRef(TextureBase* texture, int slice = 0, int mipmap = 0) : + texture(texture), + slice(slice), + mipmap(mipmap) + {} + + 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; + } + }; + + struct RenderTargetsStrongRef + { + std::vector colors; + RenderTargetStrongRef depthStencil; + uint32_t temporaryFlags; + + RenderTargetsStrongRef() : depthStencil(nullptr), temporaryFlags(0) + {} + }; + + struct RenderTargets + { + std::vector colors; + RenderTarget depthStencil; + uint32_t temporaryFlags; + + RenderTargets() : depthStencil(nullptr), temporaryFlags(0) + {} + + const RenderTarget& getFirstTarget() const + { + return this->colors.empty() ? this->depthStencil : this->colors[0]; + } + + bool operator==(const RenderTargets& other) const + { + size_t numColors = this->colors.size(); + if (numColors != other.colors.size()) + return false; + + for (size_t index = 0; index < numColors; index++) + { + if (this->colors[index] != other.colors[index]) + return false; + } + + if (this->depthStencil != other.depthStencil || this->temporaryFlags != other.temporaryFlags) + return false; + + return true; + } }; struct DisplayState @@ -167,7 +348,7 @@ namespace love // StrongRef font; // StrongRef shader; - // RenderTargetsStrongRef renderTargets; + RenderTargetsStrongRef renderTargets; ColorChannelMask colorMask; @@ -177,29 +358,6 @@ namespace love SamplerState defaultSampleState = SamplerState(); }; - class TempTransform - { - public: - TempTransform(GraphicsBase* graphics) : graphics(graphics) - { - graphics->pushTransform(); - } - - TempTransform(GraphicsBase* graphics, const Matrix4& transform) : graphics(graphics) - { - graphics->pushTransform(); - graphics->transformStack.back() *= transform; - } - - ~TempTransform() - { - graphics->popTransform(); - } - - private: - GraphicsBase* graphics; - }; - GraphicsBase(const char* name); virtual ~GraphicsBase(); @@ -208,11 +366,11 @@ namespace love void restoreStateChecked(const DisplayState& state); - /* TODO: implement when they exist */ - bool isRenderTargetActive() const - { - return false; - } + bool isRenderTargetActive() const; + + bool isRenderTargetActive(TextureBase* texture) const; + + bool isRenderTargetActive(TextureBase* texture, int slice) const; void setActive(bool active) { @@ -250,39 +408,13 @@ namespace love this->states.back().defaultSampleState = state; } - void setScissor(const Rect& scissor); - - void setScissor(); - - void 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(); - } + 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); - - Rect newScisssor = { x1, y1, std::max(0, x2 - x1), std::max(0, y2 - y1) }; - this->setScissor(newScisssor); - } + virtual void setScissor() = 0; - bool getScissor(Rect& scissor) const - { - const auto& state = this->states.back(); + void intersectScissor(const Rect& scissor); - scissor = state.scissorRect; - return state.scissor; - } + bool getScissor(Rect& scissor) const; Color getBackgroundColor() const { @@ -299,11 +431,11 @@ namespace love return this->states.back().meshCullMode; } - void setFrontFaceWinding(Winding winding); + virtual void setFrontFaceWinding(Winding winding) = 0; Winding getFrontFaceWinding() const; - void setColorMask(ColorChannelMask mask); + virtual void setColorMask(ColorChannelMask mask) = 0; ColorChannelMask getColorMask() const; @@ -312,16 +444,20 @@ namespace love virtual TextureBase* newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data = nullptr) = 0; - TextureBase* getDefaultTexture(TextureType type); + 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); + virtual void setBlendState(const BlendState& blend) = 0; + + void captureScreenshot(const ScreenshotInfo& info); const BlendState& getBlendState() const { @@ -371,32 +507,14 @@ namespace love 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; } - RendererInfo getRendererInfo() const - { + PixelFormat getSizedFormat(PixelFormat format); - RendererInfo info { .name = __RENDERER_NAME__, - .version = __RENDERER_VERSION__, - .vendor = __RENDERER_VENDOR__, - .device = __RENDERER_DEVICE__ }; - - return info; - } + RendererInfo getRendererInfo() const; BatchedVertexData requestBatchedDraw(const BatchedDrawCommand& command); @@ -404,8 +522,14 @@ namespace love static void flushBatchedDrawsGlobal(); + void advanceStreamBuffers(); + + static void advanceStreamBuffersGlobal(); + virtual void draw(const DrawIndexedCommand& command) = 0; + virtual void draw(const DrawCommand& command) = 0; + Stats getStats() const; size_t getStackDepth() const @@ -413,38 +537,9 @@ namespace love return this->stackTypeStack.size(); } - 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()); + void push(StackType type = STACK_TRANSFORM); - this->stackTypeStack.push_back(type); - } - - void 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 pop(); const Matrix4& getTransform() const { @@ -493,40 +588,17 @@ namespace love this->pixelScaleStack.back() = 1.0; } - void applyTransform(const Matrix4& transform) - { - Matrix4& current = this->transformStack.back(); - current *= transform; + int getWidth() const; - float sx, sy; - current.getApproximateScale(sx, sy); - this->pixelScaleStack.back() *= (sx + sy) / 2.0; - } + int getHeight() const; - void replaceTransform(const Matrix4& transform) - { - this->transformStack.back() = transform; + void applyTransform(const Matrix4& transform); - float sx, sy; - transform.getApproximateScale(sx, sy); - this->pixelScaleStack.back() = (sx + sy) / 2.0; - } + void replaceTransform(const Matrix4& transform); - Vector2 transformPoint(Vector2 point) const - { - Vector2 result {}; - this->transformStack.back().transformXY(&result, &point, 1); + Vector2 transformPoint(Vector2 point) const; - return result; - } - - 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) { @@ -538,37 +610,35 @@ namespace love return this->deviceProjectionMatrix; } - void resetProjection() - { - this->flushBatchedDraws(); + void resetProjection(); - auto& state = this->states.back(); + void reset(); - state.useCustomProjection = false; - this->updateDeviceProjection(Matrix4::ortho(0.0f, 0, 0, 0.0f, -10.0f, 10.0f)); - } + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) = 0; - void reset() - { - DisplayState state {}; - this->restoreState(state); + virtual void clear(const std::vector& colors, OptionalInt stencil, + OptionalDouble depth) = 0; - this->origin(); - } + virtual void present(void* screenshotCallbackData) = 0; - double getCurrentDPIScale() - { - return 1.0; - } + virtual bool setMode(int width, int height, int pixelwidth, int pixelheight, bool backbufferstencil, + bool backbufferdepth, int msaa) = 0; - double getScreenDPIScale() - { - return 1.0; - } + virtual void unsetMode() = 0; + + virtual void setActiveScreen() + {} + + virtual void setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) = 0; + + double getCurrentDPIScale() const; + + double getScreenDPIScale() const; void polyline(std::span vertices); - void polygon(DrawMode mode, std::span vertices, bool skipLastVertex = true); + void polygon(DrawMode mode, std::span vertices, bool skipLastFilledVertex = true); void rectangle(DrawMode mode, float x, float y, float w, float h); @@ -663,5 +733,12 @@ namespace love BatchedDrawState batchedDrawState; std::vector scratchBuffer; + + float cpuProcessingTime; + float gpuDrawingTime; + + Capabilities capabilities; + + std::vector pendingScreenshotCallbacks; }; } // namespace love diff --git a/include/modules/graphics/Texture.tcc b/include/modules/graphics/Texture.tcc index b85a9d0a..31b848c6 100644 --- a/include/modules/graphics/Texture.tcc +++ b/include/modules/graphics/Texture.tcc @@ -229,6 +229,16 @@ namespace love 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); @@ -294,6 +304,8 @@ namespace love return this->samplerState; } + virtual ptrdiff_t getRenderTargetHandle() const = 0; + virtual void setSamplerState(const SamplerState& state) = 0; virtual void replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, diff --git a/include/modules/graphics/vertex.hpp b/include/modules/graphics/vertex.hpp index 63d9b323..8fff9f47 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -278,12 +278,13 @@ namespace love { Resource* buffer; size_t offset; + int vertexCount; } info[MAX]; - void set(uint32_t index, Resource* r, size_t offset) + void set(uint32_t index, Resource* r, size_t offset, int vertexCount) { useBits |= (1u << index); - info[index] = { r, offset }; + info[index] = { r, offset, vertexCount }; } void disable(uint32_t index) diff --git a/platform/ctr/CMakeLists.txt b/platform/ctr/CMakeLists.txt index a9c485a5..b9388d4a 100644 --- a/platform/ctr/CMakeLists.txt +++ b/platform/ctr/CMakeLists.txt @@ -39,7 +39,7 @@ 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/graphics/Graphics.cpp diff --git a/platform/ctr/include/driver/display/Renderer.hpp b/platform/ctr/include/driver/display/citro3d.hpp similarity index 73% rename from platform/ctr/include/driver/display/Renderer.hpp rename to platform/ctr/include/driver/display/citro3d.hpp index bdd54c17..6c4f9024 100644 --- a/platform/ctr/include/driver/display/Renderer.hpp +++ b/platform/ctr/include/driver/display/citro3d.hpp @@ -13,30 +13,43 @@ #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(); - void setupContext(BatchedDrawState& state); + void setupContext(); - ~Renderer(); + ~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); @@ -50,35 +63,14 @@ namespace love virtual void prepareDraw() override; - static decltype(C3D_UNSIGNED_BYTE) getIndexType(IndexDataType type) - { - switch (type) - { - case INDEX_UINT16: - default: - return C3D_UNSIGNED_SHORT; - } - - throw love::Exception("Invalid index type: {:d}.", (int)type); - return C3D_UNSIGNED_BYTE; - } + void setVertexAttributes(const VertexAttributes& attributes, const BufferBindings& buffers); - static GPU_Primitive_t 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); - } + void bindTextureToUnit(TextureType target, C3D_Tex* texture, int unit, bool restorePrevious, + bool bindForEdit = true); + + void bindTextureToUnit(TextureBase* texture, int unit, bool restorePrevious, bool bindForEdit = true); + + C3D_RenderTarget* getInternalBackbuffer() const; void setWideMode(bool wide) { @@ -100,6 +92,12 @@ namespace love return gfxIs3D(); } + void ensureInFrame(); + + // static C3D_IndexType getIndexType(IndexDataType type); + + static GPU_Primitive_t getPrimitiveType(PrimitiveType type); + // clang-format off ENUMMAP_DECLARE(PixelFormats, PixelFormat, GPU_TEXCOLOR, { PIXELFORMAT_RGBA8_UNORM, GPU_RGBA8 }, @@ -141,7 +139,21 @@ namespace love private: static GPU_TEXTURE_WRAP_PARAM getWrapMode(SamplerState::WrapMode mode); - void ensureInFrame(); + 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(); @@ -155,14 +167,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 index 41728fcf..ce5bb783 100644 --- a/platform/ctr/include/driver/graphics/StreamBuffer.hpp +++ b/platform/ctr/include/driver/graphics/StreamBuffer.hpp @@ -8,58 +8,64 @@ namespace love { - class StreamBuffer final : public StreamBufferBase, public Volatile + template + class StreamBuffer final : public StreamBufferBase { public: - StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size), buffer(nullptr) + StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size), buffer {} { - this->loadVolatile(); - } - - bool loadVolatile() - { - const auto size = this->getSize() * BUFFER_FRAMES; - this->data = (uint8_t*)linearAlloc(size); + this->data = (T*)linearAlloc(this->bufferSize); if (this->data == nullptr) - return false; - - if (this->usage != BufferUsage::BUFFERUSAGE_VERTEX) - return true; + throw love::Exception(E_OUT_OF_MEMORY); - this->buffer = new C3D_BufInfo(); - BufInfo_Init(this->buffer); + if (usage != BUFFERUSAGE_VERTEX) + return; - BufInfo_Add(this->buffer, this->data, sizeof(Vertex), 3, 0x210); - C3D_SetBufInfo(this->buffer); + BufInfo_Init(&this->buffer); + BufInfo_Add(&this->buffer, this->data, sizeof(Vertex), 3, 0x210); - return true; + C3D_SetBufInfo(&this->buffer); } - void unloadVolatile() + ~StreamBuffer() { linearFree(this->data); - - delete this->buffer; - this->buffer = nullptr; } - ~StreamBuffer() + MapInfo map(size_t) override { - this->unloadVolatile(); + MapInfo info {}; + info.data = &this->data[this->index]; + info.size = this->bufferSize - this->frameGPUReadOffset; + + return info; } size_t unmap(size_t) override { - return (size_t)this->data; + return this->index; + } + + void markUsed(int count) override + { + this->index += count; + this->frameGPUReadOffset += (count * sizeof(T)); + } + + void nextFrame() override + { + this->index = 0; + this->frameGPUReadOffset = 0; } ptrdiff_t getHandle() const override { - return 0; + return (ptrdiff_t)this->data; } private: - C3D_BufInfo* buffer; + T* data; + C3D_BufInfo buffer; }; } // namespace love diff --git a/platform/ctr/include/modules/graphics/Graphics.hpp b/platform/ctr/include/modules/graphics/Graphics.hpp index 5e17638d..a2c53ecf 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -12,49 +12,55 @@ namespace love ~Graphics(); - void clear(OptionalColor color, OptionalInt depth, OptionalDouble stencil); + virtual void initCapabilities() override; - void clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth); + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) override; - void present(); + virtual void clear(const std::vector& colors, OptionalInt stencil, + OptionalDouble depth) override; - void setScissor(const Rect& scissor); + virtual void present(void* screenshotCallbackData) override; - void setScissor(); + virtual void setScissor(const Rect& scissor) override; - void setFrontFaceWinding(Winding winding); + virtual void setScissor() override; - void setColorMask(ColorChannelMask mask); + virtual void setFrontFaceWinding(Winding winding) override; - void setBlendState(const BlendState& state); + virtual void setColorMask(ColorChannelMask mask) override; - bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, - bool backBufferDepth, int msaa); + virtual void setBlendState(const BlendState& state) 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; 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 unsetMode(); + 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 isPixelFormatSupported(PixelFormat format, uint32_t usage); - /* TODO: implement properly */ - bool isRenderTargetActive(Texture* texture) - { - return false; - } - bool is3D() const; void set3D(bool enable); diff --git a/platform/ctr/include/modules/graphics/Texture.hpp b/platform/ctr/include/modules/graphics/Texture.hpp index 20b156df..76b9b668 100644 --- a/platform/ctr/include/modules/graphics/Texture.hpp +++ b/platform/ctr/include/modules/graphics/Texture.hpp @@ -20,6 +20,8 @@ namespace love ptrdiff_t getHandle() const override; + ptrdiff_t getRenderTargetHandle() const override; + void updateQuad(Quad* quad); void setSamplerState(const SamplerState& state) override; diff --git a/platform/ctr/include/modules/graphics/attributes.hpp b/platform/ctr/include/modules/graphics/attributes.hpp deleted file mode 100644 index 222bd1d8..00000000 --- a/platform/ctr/include/modules/graphics/attributes.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "modules/graphics/vertex.hpp" - -#include <3ds.h> -#include - -namespace love -{ - enum TexEnvMode - { - TEXENV_MODE_PRIMITIVE, - TEXENV_MODE_TEXTURE, - TEXENV_MODE_FONT, - TEXENV_MODE_MAX_ENUM - }; - - static TexEnvMode s_TexEnvMode = TEXENV_MODE_MAX_ENUM; - - void setTexEnvAttribute(TexEnvMode mode) - { - if (s_TexEnvMode == mode || mode == TEXENV_MODE_MAX_ENUM) - return; - - switch (mode) - { - default: - case TEXENV_MODE_PRIMITIVE: - { - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - - C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); - - break; - } - case TEXENV_MODE_TEXTURE: - { - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - - C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); - - break; - } - case TEXENV_MODE_FONT: - { - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - - 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; - } - } - - s_TexEnvMode = mode; - } -} // namespace love diff --git a/platform/ctr/source/boot.cpp b/platform/ctr/source/boot.cpp index 5740c053..598ecb53 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/driver/display/Renderer.cpp b/platform/ctr/source/driver/display/Renderer.cpp deleted file mode 100644 index c780a3ee..00000000 --- a/platform/ctr/source/driver/display/Renderer.cpp +++ /dev/null @@ -1,243 +0,0 @@ -#include "common/screen.hpp" - -#include "driver/display/Renderer.hpp" -#include "modules/graphics/Shader.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); - - 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 Renderer::setupContext(BatchedDrawState& state) - {} - - Renderer::~Renderer() - { - this->destroyFramebuffers(); - - C3D_Fini(); - gfxExit(); - } - - void Renderer::createFramebuffers() - { - const auto& info = getScreenInfo(); - const int 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 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(); - GraphicsBase::flushBatchedDrawsGlobal(); - - 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) - { - GraphicsBase::flushBatchedDrawsGlobal(); - - C3D_FrameEnd(0); - this->inFrame = false; - } - } - - void Renderer::setViewport(const Rect& viewport, bool tilt) - { - this->context.viewport = viewport; - this->context.dirtyProjection = true; - - 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()); - return; - } - else if (viewport.w == 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, viewport.w, viewport.h, 0.0f, Framebuffer::Z_NEAR, Framebuffer::Z_FAR, true); - // clang-format on - - 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) - {} - - 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; - - this->context.blendState = state; - C3D_AlphaBlend(operationRGB, operationA, sourceColor, destColor, sourceAlpha, destAlpha); - } - - void Renderer::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 = Renderer::getWrapMode(state.wrapU); - auto wrapV = Renderer::getWrapMode(state.wrapV); - - C3D_TexSetWrap(texture, wrapU, wrapV); - } - - void Renderer::prepareDraw() - { - // clang-format off - if (Shader::current != nullptr) - ((Shader*)Shader::current)->updateBuiltinUniforms(this->context.modelView, this->context.projection); - // clang-format on - } - - GPU_TEXTURE_WRAP_PARAM Renderer::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; - } - } -} // 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 00000000..58d2a19b --- /dev/null +++ b/platform/ctr/source/driver/display/citro3d.cpp @@ -0,0 +1,405 @@ +#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() + { + // clang-format off + if (Shader::current != nullptr && this->context.dirtyProjection) + { + ((Shader*)Shader::current)->updateBuiltinUniforms(this->context.modelView, this->context.projection); + this->context.dirtyProjection = false; + } + // clang-format on + } + + void citro3d::bindTextureToUnit(TextureType target, C3D_Tex* texture, int unit, bool restorePrevious, + bool bindForEdit) + { + if (texture == nullptr) + return this->updateTexEnvMode(TEXENV_MODE_PRIMITIVE); + + int oldTextureUnit = this->context.currentTextureUnit; + + int activeUnit = 0; + if (oldTextureUnit != unit) + activeUnit = getTextureUnit(GPU_TEXUNIT0) + unit; + + this->updateTexEnvMode(TEXENV_MODE_TEXTURE); + this->context.boundTextures[target][unit] = texture; + + C3D_TexBind(activeUnit, texture); + + if (restorePrevious && oldTextureUnit != unit) + activeUnit = getTextureUnit(GPU_TEXUNIT0) + unit; + else + this->context.currentTextureUnit = unit; + } + + void citro3d::bindTextureToUnit(TextureBase* texture, int unit, bool restorePrevious, bool bindForEdit) + { + if (texture == nullptr) + return this->updateTexEnvMode(TEXENV_MODE_PRIMITIVE); + + auto textureType = texture->getTextureType(); + auto* handle = (C3D_Tex*)texture->getHandle(); + + this->bindTextureToUnit(textureType, handle, unit, restorePrevious, bindForEdit); + } + + void citro3d::setVertexAttributes(const VertexAttributes& attributes, const BufferBindings& buffers) + {} + + 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/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index 048d940f..59489eac 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -1,11 +1,9 @@ -#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/attributes.hpp" - namespace love { Graphics::Graphics() : GraphicsBase("love.graphics.citro3d") @@ -30,18 +28,96 @@ namespace love Graphics::~Graphics() {} - void Graphics::clear(OptionalColor color, OptionalInt depth, OptionalDouble stencil) + void Graphics::initCapabilities() { - Renderer::getInstance().bindFramebuffer(); + // 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::setActiveScreen() + { + c3d.ensureInFrame(); + } + + void Graphics::clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) + { + 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); + c3d.clear(color.value); } - if (stencil.hasValue && depth.hasValue) - Renderer::getInstance().clearDepthStencil(stencil.value, 0xFF, depth.value); + c3d.bindFramebuffer(c3d.getInternalBackbuffer()); + } + + C3D_RenderTarget* Graphics::getInternalBackbuffer() const + { + return c3d.getInternalBackbuffer(); } void Graphics::clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) @@ -49,78 +125,147 @@ namespace love if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) return; - const int numColors = (int)colors.size(); + int numColors = (int)colors.size(); + + const auto& targets = this->states.back().renderTargets.colors; + const int numColorTargets = targets.size(); - if (numColors <= 1) + // clang-format off + 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; } + // clang-format on 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::present() + void Graphics::present(void* screenshotCallbackData) { if (!this->isActive()) return; - this->flushBatchedDraws(); - Renderer::getInstance().present(); + if (this->isRenderTargetActive()) + throw love::Exception("present cannot be called while a render target is active."); - this->batchedDrawState.vertexBuffer->nextFrame(); - this->batchedDrawState.indexBuffer->nextFrame(); + c3d.present(); this->drawCalls = 0; this->drawCallsBatched = 0; + + this->cpuProcessingTime = C3D_GetProcessingTime(); + this->gpuDrawingTime = C3D_GetDrawingTime(); } void Graphics::setScissor(const Rect& scissor) { - GraphicsBase::setScissor(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::setScissor() { - GraphicsBase::setScissor(); - Renderer::getInstance().setScissor(Rect::EMPTY); + if (this->states.back().scissor) + this->flushBatchedDraws(); + + this->states.back().scissor = false; + c3d.setScissor(Rect::EMPTY); } void Graphics::setFrontFaceWinding(Winding winding) { - GraphicsBase::setFrontFaceWinding(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::setColorMask(ColorChannelMask mask) { - GraphicsBase::setColorMask(mask); - Renderer::getInstance().setColorMask(mask); + this->flushBatchedDraws(); + + c3d.setColorMask(mask); + this->states.back().colorMask = mask; } void Graphics::setBlendState(const BlendState& state) { - GraphicsBase::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) - Renderer::getInstance().setBlendState(state); + c3d.setBlendState(state); + + this->states.back().blend = state; } bool Graphics::setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, bool backBufferDepth, int msaa) { - Renderer::getInstance().initialize(); + c3d.initialize(); - // clang-format off - if (this->batchedDrawState.vertexBuffer == nullptr) + this->created = true; + this->initCapabilities(); + + c3d.setupContext(); + + try { - this->batchedDrawState.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, INIT_INDEX_BUFFER_SIZE); - this->batchedDrawState.vertexBuffer = new StreamBuffer(BUFFERUSAGE_VERTEX, INIT_VERTEX_BUFFER_SIZE); + 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; } - // clang-format on - - this->created = true; if (!Volatile::loadAll()) std::printf("Failed to load all volatile objects.\n"); @@ -155,6 +300,25 @@ namespace love 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 { auto* window = Module::getInstance(M_WINDOW); @@ -163,7 +327,7 @@ 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) @@ -201,21 +365,30 @@ namespace love void Graphics::draw(const DrawIndexedCommand& command) { - Renderer::getInstance().prepareDraw(); + c3d.prepareDraw(); + // c3d.setVertexAttributes(*command.attributes, *command.buffers); + c3d.bindTextureToUnit(command.texture, 0, false); - if (command.texture != nullptr) - { - setTexEnvAttribute(TEXENV_MODE_TEXTURE); - C3D_TexBind(0, (C3D_Tex*)command.texture->getHandle()); - } - else - setTexEnvAttribute(TEXENV_MODE_PRIMITIVE); + 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(); + // c3d.setVertexAttributes(*command.attributes, *command.buffers); + c3d.bindTextureToUnit(command.texture, 0, false); - const void* elements = (const void*)command.indexBufferOffset; - const auto primitiveType = Renderer::getPrimitiveType(command.primitiveType); - const auto dataType = Renderer::getIndexType(command.indexType); + const auto primitiveType = citro3d::getPrimitiveType(command.primitiveType); - C3D_DrawElements(primitiveType, command.indexCount, dataType, elements); + C3D_DrawArrays(primitiveType, command.vertexStart, command.vertexCount); ++this->drawCalls; } @@ -225,7 +398,7 @@ namespace love bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; GPU_TEXCOLOR color; - bool supported = Renderer::getConstant(format, color); + bool supported = citro3d::getConstant(format, color); return readable && supported; } @@ -237,7 +410,7 @@ namespace love void Graphics::set3D(bool enable) { - Renderer::getInstance().set3DMode(enable); + c3d.set3DMode(enable); } bool Graphics::isWide() const @@ -247,7 +420,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 index fe3c3de2..91cbf97a 100644 --- a/platform/ctr/source/modules/graphics/Shader.cpp +++ b/platform/ctr/source/modules/graphics/Shader.cpp @@ -1,5 +1,5 @@ #include "modules/graphics/Shader.hpp" -#include "driver/display/Renderer.hpp" +#include "driver/display/citro3d.hpp" #define SHADERS_DIR "romfs:/shaders/" @@ -64,9 +64,6 @@ namespace love void Shader::updateBuiltinUniforms(const C3D_Mtx& mdlvMtx, const C3D_Mtx& projMtx) { - if (current == this) - Graphics::flushBatchedDrawsGlobal(); - if (this->hasUniform("mdlvMtx")) C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, this->uniforms[0].location, &mdlvMtx); diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp index 8db7bd70..d841c7c8 100644 --- a/platform/ctr/source/modules/graphics/Texture.cpp +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -1,26 +1,35 @@ #include "modules/graphics/Texture.hpp" #include "modules/graphics/Graphics.hpp" -#include "driver/display/Renderer.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) + 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 (!love::Renderer::getConstant(format, gpuFormat)) + if (!citro3d::getConstant(format, gpuFormat)) throw love::Exception("Invalid GPU texture format: {:s}.", love::getConstant(format)); texture = new C3D_Tex(); @@ -113,7 +122,7 @@ namespace love try { if (this->isRenderTarget()) - createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight); + createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight, clear); else { createTextureObject(this->texture, this->format, powTwoWidth, powTwoHeight); @@ -175,7 +184,7 @@ namespace love void Texture::setSamplerState(const SamplerState& state) { this->samplerState = this->validateSamplerState(state); - Renderer::getInstance().setSamplerState(this->texture, this->samplerState); + c3d.setSamplerState(this->texture, this->samplerState); } SamplerState Texture::validateSamplerState(SamplerState state) const @@ -333,4 +342,9 @@ namespace love { return (ptrdiff_t)this->texture; } + + ptrdiff_t Texture::getRenderTargetHandle() const + { + return (ptrdiff_t)this->target; + } } // namespace love diff --git a/source/modules/audio/Source.cpp b/source/modules/audio/Source.cpp index f67a25cc..a5c46fcc 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 } @@ -492,9 +485,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); } diff --git a/source/modules/data/ByteData.cpp b/source/modules/data/ByteData.cpp index 86a01e36..bf974998 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 419686e6..2a74aab6 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 5c5ad121..bed691bb 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/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 0616e8c9..e4d9fb7a 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -1,19 +1,26 @@ #include "modules/graphics/Graphics.tcc" #include "common/Console.hpp" +#include "common/screen.hpp" 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), - created(false), - active(true), - deviceProjectionMatrix(), - batchedDrawState() + drawCallsBatched(0), + drawCalls(0), + batchedDrawState(), + cpuProcessingTime(0.0f), + gpuDrawingTime(0.0f), + capabilities() { this->transformStack.reserve(16); this->transformStack.push_back(Matrix4()); @@ -45,6 +52,34 @@ namespace love this->batchedDrawState.indexBuffer->release(); } + 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); @@ -255,7 +290,7 @@ namespace love size_t stride = getFormatStride(command.format); size_t dataSize = stride * totalVertices; - if (state.vertexMap.data != nullptr && dataSize > state.vertexMap.size) + if (state.vertexBufferMap.data != nullptr && dataSize > state.vertexBufferMap.size) shouldFlush = true; if (dataSize > state.vertexBuffer->getUsableSize()) @@ -271,7 +306,7 @@ namespace love { size_t dataSize = (state.indexCount + requestedIndexCount) * sizeof(uint16_t); - if (state.indexMap.data != nullptr && dataSize > state.indexMap.size) + if (state.indexBufferMap.data != nullptr && dataSize > state.indexBufferMap.size) shouldFlush = true; if (dataSize > state.indexBuffer->getUsableSize()) @@ -302,36 +337,36 @@ namespace love if (state.vertexBuffer->getSize() < bufferSizes[0]) { state.vertexBuffer->release(); - state.vertexBuffer = new StreamBuffer(BUFFERUSAGE_VERTEX, bufferSizes[0]); + state.vertexBuffer = newVertexBuffer(bufferSizes[0]); } if (state.indexBuffer->getSize() < bufferSizes[1]) { state.indexBuffer->release(); - state.indexBuffer = new StreamBuffer(BUFFERUSAGE_INDEX, bufferSizes[1]); + state.indexBuffer = newIndexBuffer(bufferSizes[1]); } } if (command.indexMode != TRIANGLEINDEX_NONE) { - if (state.indexMap.data == nullptr) - state.indexMap = state.indexBuffer->map(); + if (state.indexBufferMap.data == nullptr) + state.indexBufferMap = state.indexBuffer->map(requestedIndexSize); - uint16_t* indices = (uint16_t*)state.indexMap.data; + auto* indices = state.indexBufferMap.data; fillIndices(command.indexMode, state.vertexCount, command.vertexCount, indices); - state.indexMap.data += requestedIndexSize; + state.indexBufferMap.data += requestedIndexCount; } BatchedVertexData data {}; if (newDataSize > 0) { - if (state.vertexMap.data == nullptr) - state.vertexMap = state.vertexBuffer->map(); + if (state.vertexBufferMap.data == nullptr) + state.vertexBufferMap = state.vertexBuffer->map(newDataSize); - data.stream = state.vertexMap.data; - state.vertexMap.data += newDataSize; + data.stream = state.vertexBufferMap.data; + state.vertexBufferMap.data += command.vertexCount; } if (state.vertexCount > 0) @@ -350,25 +385,39 @@ namespace love if ((state.vertexCount == 0 && state.indexCount == 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] = getFormatStride(state.format) * state.vertexCount; - state.vertexBuffer->unmap(usedSizes[0]); - state.vertexMap = StreamBuffer::MapInfo(); + 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); + this->pushIdentityTransform(); if (state.indexCount > 0) { usedSizes[1] = sizeof(uint16_t) * state.indexCount; - DrawIndexedCommand command {}; + DrawIndexedCommand command(&attributes, &buffers, state.indexBuffer); command.primitiveType = state.primitiveMode; command.indexCount = state.indexCount; command.indexType = INDEX_UINT16; @@ -376,17 +425,30 @@ namespace love command.texture = state.texture; this->draw(command); - state.indexMap = StreamBuffer::MapInfo(); + state.indexBufferMap = MapInfo(); + } + else + { + DrawCommand command {}; + command.primitiveType = state.primitiveMode; + command.vertexStart = 0; + command.vertexCount = state.vertexCount; + command.texture = state.texture; + + this->draw(command); } if (usedSizes[0] > 0) - state.vertexBuffer->markUsed(usedSizes[0]); + state.vertexBuffer->markUsed(state.vertexCount); if (usedSizes[1] > 0) - state.indexBuffer->markUsed(usedSizes[1]); + state.indexBuffer->markUsed(state.indexCount); this->popTransform(); + if (attributes.isEnabled(ATTRIB_COLOR)) + this->setColor(originalColor); + state.vertexCount = 0; state.indexCount = 0; state.flushing = false; @@ -400,15 +462,219 @@ namespace love instance->flushBatchedDraws(); } + void GraphicsBase::advanceStreamBuffers() + { + if (this->batchedDrawState.vertexBuffer) + this->batchedDrawState.vertexBuffer->nextFrame(); + + if (this->batchedDrawState.indexBuffer) + this->batchedDrawState.indexBuffer->nextFrame(); + } + + 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); + 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.width; + } + + 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; } - TextureBase* GraphicsBase::getDefaultTexture(TextureType type) + 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]; @@ -416,7 +682,23 @@ namespace love return texture; TextureBase::Settings settings {}; - settings.type = type; + 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)) { @@ -438,7 +720,7 @@ namespace love // 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 + 1, settings.height + 1 }, false); + texture->replacePixels(pixel, sizeof(pixel), slice, 0, { 0, 0, settings.width, settings.height }, false); // clang-format on this->defaultTextures[type] = texture; @@ -449,7 +731,7 @@ namespace love void GraphicsBase::polyline(std::span vertices) {} - void GraphicsBase::polygon(DrawMode mode, std::span vertices, bool skipLastVertex) + void GraphicsBase::polygon(DrawMode mode, std::span vertices, bool skipLastFilledVertex) { if (mode == DRAW_LINE) this->polyline(vertices); @@ -461,33 +743,42 @@ namespace love BatchedDrawCommand command {}; command.format = CommonFormat::XYf_STf_RGBAf; command.indexMode = TRIANGLEINDEX_FAN; - command.vertexCount = (int)vertices.size() - (skipLastVertex ? 1 : 0); + 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); + // 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); - } + // 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); + // 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 = vertices[index].x * invSize.x - start.x; - stream[index].t = vertices[index].y * invSize.y - start.y; - stream[index].color = this->getColor(); + stream[index].s = 0.0f; + stream[index].t = 0.0f; + stream[index].color = color; } if (is2D) @@ -504,9 +795,8 @@ namespace love void GraphicsBase::rectangle(DrawMode mode, float x, float y, float w, float h) { - // clang-format off - std::array coords = { Vector2(x, y), Vector2(x, y + h), Vector2(x + w, y + h), Vector2(x + w, y), Vector2(x, y) }; - // clang-format on + 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); } diff --git a/source/modules/graphics/Texture.cpp b/source/modules/graphics/Texture.cpp index f4f76d23..9bf98e59 100644 --- a/source/modules/graphics/Texture.cpp +++ b/source/modules/graphics/Texture.cpp @@ -343,7 +343,7 @@ namespace love command.vertexCount = 4; command.texture = this; - auto data = graphics->requestBatchedDraw(command); + BatchedVertexData data = graphics->requestBatchedDraw(command); Matrix4 translated(transform, matrix); @@ -356,12 +356,13 @@ namespace love 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 = graphics->getColor(); + stream[index].color = color; } } diff --git a/source/modules/graphics/vertex.cpp b/source/modules/graphics/vertex.cpp index 2ec93121..e87bc210 100644 --- a/source/modules/graphics/vertex.cpp +++ b/source/modules/graphics/vertex.cpp @@ -134,11 +134,11 @@ namespace love break; case CommonFormat::STf_RGBAf: set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, 0, bufferindex); - set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16_t(sizeof(float) * 2), 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_UNORM8_VEC4, uint16_t(sizeof(float) * 3), 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); @@ -151,18 +151,18 @@ namespace love 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_UNORM8_VEC4, uint16_t(sizeof(float) * 4), 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_UNORM8_VEC4, uint16_t(sizeof(float) * 2 + sizeof(uint16_t) * 2), + 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_UNORM8_VEC4, uint16_t(sizeof(float) * 5), bufferindex); + set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, uint16_t(sizeof(float) * 5), bufferindex); break; } } diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 97b7ede7..dab5f5f3 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -123,7 +123,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; } @@ -1035,6 +1035,12 @@ int Wrap_Graphics::getStats(lua_State* L) 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; } @@ -1301,6 +1307,50 @@ int Wrap_Graphics::points(lua_State* L) 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) @@ -1324,6 +1374,8 @@ int Wrap_Graphics::setActiveScreen(lua_State* L) love::currentScreen = screen; + instance()->setActiveScreen(); + return 0; } @@ -1427,6 +1479,13 @@ static constexpr luaL_Reg functions[] = { "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 }, diff --git a/source/modules/image/magpie/T3XHandler.cpp b/source/modules/image/magpie/T3XHandler.cpp index 0cfef44a..fe9b9bf9 100644 --- a/source/modules/image/magpie/T3XHandler.cpp +++ b/source/modules/image/magpie/T3XHandler.cpp @@ -1,6 +1,5 @@ #include "common/Exception.hpp" - -#include "driver/display/Renderer.hpp" +#include "driver/display/citro3d.hpp" #include "modules/image/magpie/T3XHandler.hpp" @@ -41,7 +40,7 @@ namespace love else if (header.type != GPU_TEX_2D) return false; - if (!Renderer::getConstant((GPU_TEXCOLOR)header.format, format)) + if (!citro3d::getConstant((GPU_TEXCOLOR)header.format, format)) return false; return true; @@ -72,7 +71,7 @@ namespace love PixelFormat result = PIXELFORMAT_MAX_ENUM; const auto format = (GPU_TEXCOLOR)header.format; - Renderer::getConstant(format, result); + citro3d::getConstant(format, result); const size_t width = 1 << (header.width_log2 + 3); const size_t height = 1 << (header.height_log2 + 3); @@ -127,7 +126,7 @@ namespace love std::memcpy(&header, filedata->getData(), sizeof(Tex3DSHeader)); auto convertedFormat = PIXELFORMAT_MAX_ENUM; - if (!Renderer::getConstant((GPU_TEXCOLOR)header.format, convertedFormat)) + if (!citro3d::getConstant((GPU_TEXCOLOR)header.format, convertedFormat)) throw love::Exception("Invalid texture format."); const size_t width = 1 << (header.width_log2 + 3); diff --git a/source/modules/love/scripts/callbacks.lua b/source/modules/love/scripts/callbacks.lua index c68c85d6..bc82fd0a 100644 --- a/source/modules/love/scripts/callbacks.lua +++ b/source/modules/love/scripts/callbacks.lua @@ -248,6 +248,11 @@ function love.run() local delta = 0 + local screens, current_screen = nil, 1 + if love.graphics and love.graphics.isActive() then + screens = love.graphics.getScreens() + end + return function() if love.window and g_windowShown then return @@ -278,15 +283,13 @@ function love.run() end 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 + if love.draw then love.draw(screen, get3DDepth(screen)) end end From 1d05f9db7af03aa52e43a1e71dfef990e8177071 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Wed, 29 May 2024 11:58:06 -0400 Subject: [PATCH 09/49] 3ds: fonts almost work --- CMakeLists.txt | 10 + include/common/Map.hpp | 37 -- include/common/error.hpp | 24 ++ include/common/pixelformat.hpp | 3 + include/driver/display/Renderer.tcc | 5 + include/driver/graphics/DrawCommand.hpp | 10 + include/modules/font/Font.tcc | 73 ++++ include/modules/font/GenericShaper.hpp | 22 ++ include/modules/font/GlyphData.hpp | 134 +++++++ include/modules/font/Rasterizer.hpp | 112 ++++++ include/modules/font/TextShaper.hpp | 31 +- include/modules/font/freetype/Font.cpp | 1 + .../font/freetype/TrueTypeRasterizer.hpp | 32 ++ include/modules/font/wrap_Font.hpp | 18 + include/modules/font/wrap_GlyphData.hpp | 35 ++ include/modules/font/wrap_Rasterizer.hpp | 30 ++ include/modules/graphics/Font.tcc | 182 +++++++++ include/modules/graphics/Graphics.tcc | 30 +- include/modules/graphics/Texture.tcc | 25 +- include/modules/graphics/wrap_Font.hpp | 13 + include/modules/graphics/wrap_Graphics.hpp | 4 + platform/ctr/CMakeLists.txt | 3 + .../ctr/include/driver/display/citro3d.hpp | 11 +- .../include/modules/font/BCFNTRasterizer.hpp | 55 +++ platform/ctr/include/modules/font/Font.hpp | 31 ++ .../ctr/include/modules/graphics/Font.hpp | 20 + .../ctr/include/modules/graphics/Graphics.hpp | 8 +- .../ctr/include/modules/graphics/Texture.hpp | 8 +- .../ctr/source/driver/display/citro3d.cpp | 35 +- .../source/modules/font/BCFNTRasterizer.cpp | 174 ++++++++ platform/ctr/source/modules/font/Font.cpp | 121 ++++++ platform/ctr/source/modules/graphics/Font.cpp | 96 +++++ .../ctr/source/modules/graphics/Graphics.cpp | 51 ++- .../ctr/source/modules/graphics/Texture.cpp | 271 ++++++------- source/common/pixelformat.cpp | 1 + source/modules/font/Font.cpp | 32 ++ source/modules/font/GenericShaper.cpp | 197 +++++++++ source/modules/font/GlyphData.cpp | 95 +++++ source/modules/font/Rasterizer.cpp | 63 +++ source/modules/font/TextShaper.cpp | 351 ++++++++++++++++ source/modules/font/wrap_Font.cpp | 104 +++++ source/modules/font/wrap_GlyphData.cpp | 142 +++++++ source/modules/font/wrap_Rasterizer.cpp | 135 +++++++ source/modules/graphics/Font.cpp | 374 ++++++++++++++++++ source/modules/graphics/Graphics.cpp | 60 ++- source/modules/graphics/Texture.cpp | 297 +++++++++++++- source/modules/graphics/wrap_Font.cpp | 50 +++ source/modules/graphics/wrap_Graphics.cpp | 53 +++ source/modules/love/love.cpp | 4 +- source/modules/love/scripts/boot.lua | 2 +- 50 files changed, 3400 insertions(+), 275 deletions(-) create mode 100644 include/modules/font/Font.tcc create mode 100644 include/modules/font/GenericShaper.hpp create mode 100644 include/modules/font/GlyphData.hpp create mode 100644 include/modules/font/Rasterizer.hpp create mode 100644 include/modules/font/freetype/Font.cpp create mode 100644 include/modules/font/freetype/TrueTypeRasterizer.hpp create mode 100644 include/modules/font/wrap_Font.hpp create mode 100644 include/modules/font/wrap_GlyphData.hpp create mode 100644 include/modules/font/wrap_Rasterizer.hpp create mode 100644 include/modules/graphics/Font.tcc create mode 100644 include/modules/graphics/wrap_Font.hpp create mode 100644 platform/ctr/include/modules/font/BCFNTRasterizer.hpp create mode 100644 platform/ctr/include/modules/font/Font.hpp create mode 100644 platform/ctr/include/modules/graphics/Font.hpp create mode 100644 platform/ctr/source/modules/font/BCFNTRasterizer.cpp create mode 100644 platform/ctr/source/modules/font/Font.cpp create mode 100644 platform/ctr/source/modules/graphics/Font.cpp create mode 100644 source/modules/font/Font.cpp create mode 100644 source/modules/font/GenericShaper.cpp create mode 100644 source/modules/font/GlyphData.cpp create mode 100644 source/modules/font/Rasterizer.cpp create mode 100644 source/modules/font/TextShaper.cpp create mode 100644 source/modules/font/wrap_Font.cpp create mode 100644 source/modules/font/wrap_GlyphData.cpp create mode 100644 source/modules/font/wrap_Rasterizer.cpp create mode 100644 source/modules/graphics/Font.cpp create mode 100644 source/modules/graphics/wrap_Font.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 603a6afe..e5418689 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,7 +355,16 @@ source/modules/filesystem/FileData.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/Graphics.cpp +source/modules/graphics/Font.cpp source/modules/graphics/renderstate.cpp source/modules/graphics/samplerstate.cpp source/modules/graphics/Shader.cpp @@ -364,6 +373,7 @@ 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_Font.cpp source/modules/graphics/wrap_Quad.cpp source/modules/graphics/Texture.cpp source/modules/image/CompressedImageData.cpp diff --git a/include/common/Map.hpp b/include/common/Map.hpp index be97fa8c..66f56cf2 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/error.hpp b/include/common/error.hpp index 0e9149be..db620a36 100644 --- a/include/common/error.hpp +++ b/include/common/error.hpp @@ -56,6 +56,30 @@ namespace love #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/pixelformat.hpp b/include/common/pixelformat.hpp index da8fc680..416f9c8f 100644 --- a/include/common/pixelformat.hpp +++ b/include/common/pixelformat.hpp @@ -51,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, @@ -290,6 +291,8 @@ namespace love { "r32i", PIXELFORMAT_R32_INT }, { "r32ui", PIXELFORMAT_R32_UINT }, + { "a4", PIXELFORMAT_A4_UNORM }, + { "rg8", PIXELFORMAT_RG8_UNORM }, { "rg8i", PIXELFORMAT_RG8_INT }, { "rg8ui", PIXELFORMAT_RG8_UINT }, diff --git a/include/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index d39a7eea..11a09412 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -21,6 +21,11 @@ namespace love virtual void prepareDraw() = 0; + bool isInFrame() const + { + return this->inFrame; + } + protected: struct ContextBase { diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp index 3048d508..7f3ec330 100644 --- a/include/driver/graphics/DrawCommand.hpp +++ b/include/driver/graphics/DrawCommand.hpp @@ -19,6 +19,8 @@ namespace love TextureBase* texture = nullptr; int vertexCount = 0; + + bool isFont = false; }; struct DrawIndexedCommand @@ -38,6 +40,8 @@ namespace love TextureBase* texture = nullptr; CullMode cullMode = CULL_NONE; + bool isFont = false; + DrawIndexedCommand(const VertexAttributes* attributes, const BufferBindings* buffers, Resource* indexBuffer) : attributes(attributes), @@ -56,6 +60,8 @@ namespace love TextureBase* texture = nullptr; CullMode cullMode = CULL_NONE; + + bool isFont = false; }; struct BatchedVertexData @@ -76,10 +82,14 @@ namespace love int vertexCount = 0; int indexCount = 0; + int lastVertexCount = 0; + int lastIndexCount = 0; + MapInfo vertexBufferMap = MapInfo(); MapInfo indexBufferMap = MapInfo(); bool flushing = false; + bool isFont = false; }; constexpr size_t INIT_VERTEX_BUFFER_SIZE = sizeof(Vertex) * 4096 * 1; diff --git a/include/modules/font/Font.tcc b/include/modules/font/Font.tcc new file mode 100644 index 00000000..fdf38d7c --- /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 00000000..15842fe2 --- /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 00000000..6aaa1182 --- /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 00000000..772f9cda --- /dev/null +++ b/include/modules/font/Rasterizer.hpp @@ -0,0 +1,112 @@ +#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; + }; + + 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; + } + + 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 index 9d0bec0c..748ba0c4 100644 --- a/include/modules/font/TextShaper.hpp +++ b/include/modules/font/TextShaper.hpp @@ -78,11 +78,20 @@ namespace love return this->useSpacesForTab; } - float getHeight(); + float getHeight() const + { + return this->height; + } - void setLineHeight(float height); + void setLineHeight(float height) + { + this->lineHeight = height; + } - float getLineHeight() const; + float getLineHeight() const + { + return this->lineHeight; + } int getAscent() const; @@ -94,25 +103,25 @@ namespace love bool hasGlyphs(const std::string& text) const; - float getKerning(uint32_t left, uint32_t right) const; + float getKerning(uint32_t left, uint32_t right); - float getKerning(const std::string& left, const std::string& right) const; + float getKerning(const std::string& left, const std::string& right); - float getGlyphAdvance(uint32_t codepoint, GlyphIndex* glyphIndex = nullptr) const; + float getGlyphAdvance(uint32_t codepoint, GlyphIndex* glyphIndex = nullptr); - int getWidth(const std::string& text) const; + int getWidth(const std::string& text); void getWrap(const std::vector& text, float limit, std::vector& lines, - std::vector* lineWidths = nullptr) const; + std::vector* lineWidths = nullptr); void getWrap(const ColoredCodepoints& codepoints, float limit, std::vector& lineRanges, - std::vector* lineWidths = nullptr) const; + 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) const = 0; + float extraSpacing, std::vector* positions, + std::vector* colors, TextInfo* info) = 0; virtual int computeWordWrapIndex(const ColoredCodepoints& codepoints, Range range, float wraplimit, float* width) = 0; diff --git a/include/modules/font/freetype/Font.cpp b/include/modules/font/freetype/Font.cpp new file mode 100644 index 00000000..411906b6 --- /dev/null +++ b/include/modules/font/freetype/Font.cpp @@ -0,0 +1 @@ +#include "modules/font/Font.hpp" diff --git a/include/modules/font/freetype/TrueTypeRasterizer.hpp b/include/modules/font/freetype/TrueTypeRasterizer.hpp new file mode 100644 index 00000000..3ec5fc7a --- /dev/null +++ b/include/modules/font/freetype/TrueTypeRasterizer.hpp @@ -0,0 +1,32 @@ +#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() + {} + + // clang-format off + STRINGMAP_DECLARE(Hintings, Hinting, + { "normal", HINTING_NORMAL }, + { "light", HINTING_LIGHT }, + { "mono", HINTING_MONO }, + { "none", HINTING_NONE } + ); + // clang-format on + }; +} // namespace love diff --git a/include/modules/font/wrap_Font.hpp b/include/modules/font/wrap_Font.hpp new file mode 100644 index 00000000..5acc1d74 --- /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 00000000..77e91191 --- /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 00000000..c20c8b62 --- /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/Font.tcc b/include/modules/graphics/Font.tcc new file mode 100644 index 00000000..6e8d6a9d --- /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) = 0; + + 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(); + }; +} // namespace love diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 914c3e12..0d81b579 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -9,8 +9,10 @@ #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/Shader.tcc" #include "modules/graphics/Texture.tcc" #include "modules/graphics/Volatile.hpp" @@ -345,7 +347,7 @@ namespace love CullMode meshCullMode = CULL_NONE; Winding winding = WINDING_CCW; - // StrongRef font; + StrongRef font; // StrongRef shader; RenderTargetsStrongRef renderTargets; @@ -355,7 +357,7 @@ namespace love bool useCustomProjection = false; Matrix4 customProjection; - SamplerState defaultSampleState = SamplerState(); + SamplerState defaultSamplerState = SamplerState(); }; GraphicsBase(const char* name); @@ -400,12 +402,12 @@ namespace love const SamplerState& getDefaultSamplerState() const { - return this->states.back().defaultSampleState; + return this->states.back().defaultSamplerState; } void setDefaultSamplerState(const SamplerState& state) { - this->states.back().defaultSampleState = state; + this->states.back().defaultSamplerState = state; } virtual void setScissor(const Rect& scissor) = 0; @@ -444,6 +446,22 @@ namespace love 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; + + 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); @@ -632,6 +650,8 @@ namespace love virtual void setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, bool hasSRGBTexture) = 0; + virtual bool isPixelFormatSupported(PixelFormat format, uint32_t usage) = 0; + double getCurrentDPIScale() const; double getScreenDPIScale() const; @@ -739,6 +759,8 @@ namespace love Capabilities capabilities; + StrongRef defaultFont; + std::vector pendingScreenshotCallbacks; }; } // namespace love diff --git a/include/modules/graphics/Texture.tcc b/include/modules/graphics/Texture.tcc index 31b848c6..0a34beda 100644 --- a/include/modules/graphics/Texture.tcc +++ b/include/modules/graphics/Texture.tcc @@ -246,8 +246,7 @@ namespace love int getSliceCount(int mip) const; - void generateMipmaps() - {} + void generateMipmaps(); const std::string& getDebugName() const { @@ -308,11 +307,12 @@ namespace love virtual void setSamplerState(const SamplerState& state) = 0; - virtual void replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, - bool reloadMipmaps) = 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); - virtual void replacePixels(const void* data, size_t size, int slice, int mipmap, const Rect& rect, - bool reloadMipmaps) = 0; + SamplerState validateSamplerState(SamplerState state) const; static int getTotalMipmapCount(int width, int height) { @@ -364,6 +364,19 @@ namespace love 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; diff --git a/include/modules/graphics/wrap_Font.hpp b/include/modules/graphics/wrap_Font.hpp new file mode 100644 index 00000000..397a37b3 --- /dev/null +++ b/include/modules/graphics/wrap_Font.hpp @@ -0,0 +1,13 @@ +#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 diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index 6b2959ac..3c07f857 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -181,6 +181,10 @@ namespace Wrap_Graphics int draw(lua_State* L); + int newFont(lua_State* L); + + int print(lua_State* L); + int getScreens(lua_State* L); int setActiveScreen(lua_State* L); diff --git a/platform/ctr/CMakeLists.txt b/platform/ctr/CMakeLists.txt index b9388d4a..7ed60e1d 100644 --- a/platform/ctr/CMakeLists.txt +++ b/platform/ctr/CMakeLists.txt @@ -42,9 +42,12 @@ source/driver/display/Framebuffer.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/include/driver/display/citro3d.hpp b/platform/ctr/include/driver/display/citro3d.hpp index 6c4f9024..97592e55 100644 --- a/platform/ctr/include/driver/display/citro3d.hpp +++ b/platform/ctr/include/driver/display/citro3d.hpp @@ -65,10 +65,9 @@ namespace love void setVertexAttributes(const VertexAttributes& attributes, const BufferBindings& buffers); - void bindTextureToUnit(TextureType target, C3D_Tex* texture, int unit, bool restorePrevious, - bool bindForEdit = true); + void bindTextureToUnit(TextureType target, C3D_Tex* texture, int unit, bool isFont = false); - void bindTextureToUnit(TextureBase* texture, int unit, bool restorePrevious, bool bindForEdit = true); + void bindTextureToUnit(TextureBase* texture, int unit, bool isFont = false); C3D_RenderTarget* getInternalBackbuffer() const; @@ -94,7 +93,7 @@ namespace love void ensureInFrame(); - // static C3D_IndexType getIndexType(IndexDataType type); + static GPU_TEXTURE_MODE_PARAM getTextureType(TextureType type); static GPU_Primitive_t getPrimitiveType(PrimitiveType type); @@ -104,7 +103,9 @@ namespace love { PIXELFORMAT_RGBA4_UNORM, GPU_RGBA4 }, { PIXELFORMAT_RGB565_UNORM, GPU_RGB565 }, { PIXELFORMAT_RGB5A1_UNORM, GPU_RGBA5551 }, - { PIXELFORMAT_ETC1_UNORM, GPU_ETC1 } + { PIXELFORMAT_ETC1_UNORM, GPU_ETC1 }, + { PIXELFORMAT_LA8_UNORM, GPU_LA8 }, + { PIXELFORMAT_A4_UNORM, GPU_A4 } ); ENUMMAP_DECLARE(CullModes, CullMode, GPU_CULLMODE, diff --git a/platform/ctr/include/modules/font/BCFNTRasterizer.hpp b/platform/ctr/include/modules/font/BCFNTRasterizer.hpp new file mode 100644 index 00000000..cae2e125 --- /dev/null +++ b/platform/ctr/include/modules/font/BCFNTRasterizer.hpp @@ -0,0 +1,55 @@ +#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: + int glyphCount; + float scale; + }; +} // 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 00000000..ff7fed21 --- /dev/null +++ b/platform/ctr/include/modules/font/Font.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "modules/font/Font.tcc" + +#include <3ds.h> + +namespace love +{ + class FontModule : public FontModuleBase + { + public: + FontModule(); + + 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; + + 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/Font.hpp b/platform/ctr/include/modules/graphics/Font.hpp new file mode 100644 index 00000000..e29fa15e --- /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 a2c53ecf..2877cc96 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -31,12 +31,18 @@ namespace love 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; @@ -59,8 +65,6 @@ namespace love virtual TextureBase* newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data = nullptr) override; // clang-format on - bool isPixelFormatSupported(PixelFormat format, uint32_t usage); - bool is3D() const; void set3D(bool enable); diff --git a/platform/ctr/include/modules/graphics/Texture.hpp b/platform/ctr/include/modules/graphics/Texture.hpp index 76b9b668..3d565068 100644 --- a/platform/ctr/include/modules/graphics/Texture.hpp +++ b/platform/ctr/include/modules/graphics/Texture.hpp @@ -26,13 +26,9 @@ namespace love void setSamplerState(const SamplerState& state) override; - void replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, - bool reloadMipmaps) override; + void uploadByteData(const void* data, size_t size, int slice, int mipmap, const Rect& rect) override; - void replacePixels(const void* data, size_t size, int slice, int mipmap, const Rect& rect, - bool reloadMipmaps) override; - - SamplerState validateSamplerState(SamplerState state) const; + void generateMipmapsInternal() override; // bool validateDimensions(bool throwException) const; diff --git a/platform/ctr/source/driver/display/citro3d.cpp b/platform/ctr/source/driver/display/citro3d.cpp index 58d2a19b..904798dd 100644 --- a/platform/ctr/source/driver/display/citro3d.cpp +++ b/platform/ctr/source/driver/display/citro3d.cpp @@ -316,30 +316,17 @@ namespace love // clang-format on } - void citro3d::bindTextureToUnit(TextureType target, C3D_Tex* texture, int unit, bool restorePrevious, - bool bindForEdit) + void citro3d::bindTextureToUnit(TextureType target, C3D_Tex* texture, int unit, bool isFont) { if (texture == nullptr) return this->updateTexEnvMode(TEXENV_MODE_PRIMITIVE); - int oldTextureUnit = this->context.currentTextureUnit; + isFont ? this->updateTexEnvMode(TEXENV_MODE_FONT) : this->updateTexEnvMode(TEXENV_MODE_TEXTURE); - int activeUnit = 0; - if (oldTextureUnit != unit) - activeUnit = getTextureUnit(GPU_TEXUNIT0) + unit; - - this->updateTexEnvMode(TEXENV_MODE_TEXTURE); - this->context.boundTextures[target][unit] = texture; - - C3D_TexBind(activeUnit, texture); - - if (restorePrevious && oldTextureUnit != unit) - activeUnit = getTextureUnit(GPU_TEXUNIT0) + unit; - else - this->context.currentTextureUnit = unit; + C3D_TexBind(0, texture); } - void citro3d::bindTextureToUnit(TextureBase* texture, int unit, bool restorePrevious, bool bindForEdit) + void citro3d::bindTextureToUnit(TextureBase* texture, int unit, bool isFont) { if (texture == nullptr) return this->updateTexEnvMode(TEXENV_MODE_PRIMITIVE); @@ -347,12 +334,24 @@ namespace love auto textureType = texture->getTextureType(); auto* handle = (C3D_Tex*)texture->getHandle(); - this->bindTextureToUnit(textureType, handle, unit, restorePrevious, bindForEdit); + 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) diff --git a/platform/ctr/source/modules/font/BCFNTRasterizer.cpp b/platform/ctr/source/modules/font/BCFNTRasterizer.cpp new file mode 100644 index 00000000..87cdd36e --- /dev/null +++ b/platform/ctr/source/modules/font/BCFNTRasterizer.cpp @@ -0,0 +1,174 @@ +#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; + } + + 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); + + fontFixPointers((CFNT_s*)data->getData()); + + auto* fontInfo = fontGetInfo((CFNT_s*)data->getData()); + 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); + + for (auto map = fontInfo->cmap; map != nullptr; map = map->next) + this->glyphCount += (map->codeEnd - map->codeBegin) + 1; + + this->data = data; + } + + BCFNTRasterizer::~BCFNTRasterizer() + {} + + TextShaper* BCFNTRasterizer::newTextShaper() + { + return new GenericShaper(this); + } + + bool BCFNTRasterizer::hasGlyph(uint32_t codepoint) const + { + const int index = this->getGlyphIndex(codepoint); + const auto* info = fontGetInfo((CFNT_s*)this->data->getData()); + + 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, (CFNT_s*)this->data->getData(), 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, (CFNT_s*)this->data->getData(), 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((CFNT_s*)this->data->getData()); + + 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((CFNT_s*)this->data->getData(), codepoint); + } + + int BCFNTRasterizer::getGlyphCount() const + { + return this->glyphCount; + } + + ptrdiff_t BCFNTRasterizer::getHandle() const + { + return (ptrdiff_t)this->data->getData(); + } +} // 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 00000000..e439a571 --- /dev/null +++ b/platform/ctr/source/modules/font/Font.cpp @@ -0,0 +1,121 @@ +#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 + + 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)); + } + + 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/Font.cpp b/platform/ctr/source/modules/graphics/Font.cpp new file mode 100644 index 00000000..74c272f2 --- /dev/null +++ b/platform/ctr/source/modules/graphics/Font.cpp @@ -0,0 +1,96 @@ +#include "modules/graphics/Font.hpp" +#include "modules/graphics/Graphics.tcc" + +#include "utility/logfile.hpp" + +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() + { + CFNT_s* font = (CFNT_s*)this->shaper->getRasterizers()[0]->getHandle(); + const auto* info = fontGetGlyphInfo(font); + + this->textures.reserve(info->nSheets); + + auto graphics = Module::getInstance(Module::M_GRAPHICS); + + 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); + + Rect rect { 0, 0, settings.width, settings.height }; + + texture->replacePixels(data, info->sheetSize, 0, 0, rect, false); + 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 59489eac..84206d57 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -4,6 +4,8 @@ #include "modules/graphics/Shader.hpp" #include "modules/window/Window.hpp" +#include "modules/graphics/Font.hpp" + namespace love { Graphics::Graphics() : GraphicsBase("love.graphics.citro3d") @@ -130,13 +132,13 @@ namespace love const auto& targets = this->states.back().renderTargets.colors; const int numColorTargets = targets.size(); - // clang-format off - if (numColors <= 1 && (numColorTargets == 0 || (numColorTargets == 1 && targets[0].texture.get() != nullptr && !isPixelFormatInteger(targets[0].texture->getPixelFormat())))) + 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; } - // clang-format on this->flushBatchedDraws(); @@ -244,6 +246,23 @@ namespace love this->states.back().blend = state; } + 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) { @@ -363,11 +382,22 @@ namespace love } } + 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(); // c3d.setVertexAttributes(*command.attributes, *command.buffers); - c3d.bindTextureToUnit(command.texture, 0, false); + c3d.bindTextureToUnit(command.texture, 0, command.isFont); const auto* indices = (const uint16_t*)command.indexBuffer->getHandle(); const size_t offset = command.indexBufferOffset; @@ -384,7 +414,7 @@ namespace love { c3d.prepareDraw(); // c3d.setVertexAttributes(*command.attributes, *command.buffers); - c3d.bindTextureToUnit(command.texture, 0, false); + c3d.bindTextureToUnit(command.texture, 0, command.isFont); const auto primitiveType = citro3d::getPrimitiveType(command.primitiveType); @@ -392,17 +422,6 @@ namespace love ++this->drawCalls; } - 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; - } - bool Graphics::is3D() const { return gfxIs3D(); diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp index d841c7c8..23198900 100644 --- a/platform/ctr/source/modules/graphics/Texture.cpp +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -42,10 +42,6 @@ namespace love TextureBase(graphics, settings, data), slices(settings.type) { - // this->validateDimensions(true); - - // this->validatePixelFormat(graphics); - if (data != nullptr) slices = *data; @@ -111,35 +107,94 @@ namespace love this->setGraphicsMemorySize(0); } - void Texture::createTexture() + static void* getTextureFace(C3D_Tex* texture, GPU_TEXFACE face, int mipmap, uint32_t* size = nullptr) { - const bool hasData = this->slices.get(0, 0) != nullptr; - const bool clear = !hasData; + 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); - try + /* + ** Textures cannot be initialized in VRAM unless they are render targets. + */ + if (!this->isRenderTarget()) { - if (this->isRenderTarget()) - createFramebufferObject(this->target, this->texture, powTwoWidth, powTwoHeight, clear); - else + try { createTextureObject(this->texture, this->format, powTwoWidth, powTwoHeight); - auto size = love::getPixelFormatSliceSize(this->format, powTwoWidth, powTwoHeight, false); + } + 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); + } + } + } - if (hasData) - std::memcpy(this->texture->data, this->slices.get(0, 0)->getData(), size); + bool hasData = this->slices.get(0, 0) != nullptr; - C3D_TexFlush(this->texture); + 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; } } - catch (love::Exception& e) + else if (!hasData) { - throw; + 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, @@ -157,18 +212,18 @@ namespace love quad->refresh(viewport, realSize.x, realSize.y); const auto* textureCoords = quad->getTextureCoordinates(); - if (!isRenderTarget) + if (isRenderTarget) { - for (size_t index = 0; index < 4; index++) - quad->setTextureCoordinate(index, { textureCoords[index].x, 1.0f - textureCoords[index].y }); + 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; } - 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)); + for (size_t index = 0; index < 4; index++) + quad->setTextureCoordinate(index, { textureCoords[index].x, 1.0f - textureCoords[index].y }); } void Texture::updateQuad(Quad* quad) @@ -187,70 +242,6 @@ namespace love c3d.setSamplerState(this->texture, this->samplerState); } - SamplerState Texture::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; - - Graphics::flushBatchedDrawsGlobal(); - - return state; - } - - void Texture::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 (data->getFormat() != this->getPixelFormat()) - throw love::Exception("Image data format does not match Texture format."); - - if (mipmap < 0 || mipmap >= this->getMipmapCount()) - throw love::Exception("Invalid mipmap level: {: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: {:d}.", slice); - } - - Rect rectangle = { x, y, data->getWidth(), data->getHeight() }; - - int mipmapWidth = this->getPixelWidth(mipmap); - int mipmapHeight = this->getPixelHeight(mipmap); - - if (rectangle.x < 0 || rectangle.y < 0 || rectangle.w <= 0 || rectangle.h <= 0 || - (rectangle.x + rectangle.w) > mipmapWidth || (rectangle.y + rectangle.h) > mipmapHeight) - { - throw love::Exception("Invalid rectangle dimensions (x: {:d}, y: {:d}, w: {:d}, h: {:d}).", - rectangle.x, rectangle.y, rectangle.w, rectangle.h); - } - - Graphics::flushBatchedDrawsGlobal(); - - this->replacePixels(data->getData(), data->getSize(), slice, mipmap, rectangle, reloadMipmaps); - } - template void replaceTiledPixels(const void* source, void* texture, const Rect& rect, const int width, const int height) @@ -270,73 +261,57 @@ namespace love } } - void Texture::replacePixels(const void* data, size_t size, int slice, int mipmap, const Rect& rect, - bool reloadMipmaps) + void Texture::uploadByteData(const void* data, size_t size, int level, int slice, const Rect& rect) { - // clang-format off - switch (this->getPixelFormat()) + 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); + + std::printf("Data Size: %zu/Texture Size: %zu\n", size, this->texture->size); + + // copy it directly if the size is the whole thing + if (rect == Rect(0, 0, mipWidth, mipHeight)) + std::memcpy(textureData, data, size); + else { - case PIXELFORMAT_RGB565_UNORM: - replaceTiledPixels(data, this->texture->data, rect, this->texture->width, this->texture->height); - break; - case PIXELFORMAT_RGBA8_UNORM: - default: - replaceTiledPixels(data, this->texture->data, rect, this->texture->width, this->texture->height); - break; + 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; + } } - // clang-format on C3D_TexFlush(this->texture); - - Graphics::flushBatchedDrawsGlobal(); } - // void Texture::validatePixelFormat(Graphics& graphics) const - // { - // uint32_t usage = PIXELFORMATUSAGEFLAGS_NONE; - - // if (renderTarget) - // usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; - // if (readable) - // usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; - // if (computeWrite) - // usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; - - // if (!graphics.isPixelFormatSupported(format, (PixelFormatUsageFlags)usage)) - // { - // std::string_view name = "unknown"; - // love::getConstant(format, name); - - // throw love::Exception("The pixel format '{:s}' is not supported by this system.", name); - // } - // } - - // bool Texture::validateDimensions(bool throwException) const - // { - // bool success = true; - - // int largestSize = 0; - // const char* name = nullptr; - - // const bool widthIsLarge = ((size_t)this->pixelWidth > LOVE_TEX3DS_MAX); - // const bool heightIsLarge = ((size_t)this->pixelHeight > LOVE_TEX3DS_MAX); - - // // clang-format off - // if ((this->textureType == TEXTURE_2D || this->textureType == TEXTURE_2D_ARRAY) && (widthIsLarge || - // heightIsLarge)) - // { - // success = false; - // largestSize = std::max(this->pixelWidth, this->pixelHeight); - // name = this->pixelWidth > this->pixelHeight ? "pixel width" : "pixel height"; - // } - - // if (throwException && name != nullptr) - // throw love::Exception("Cannot create texture: {:s} of {:d} is too large for this system.", - // name, largestSize); - // // clang-format on - - // return success; - // } + 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 { diff --git a/source/common/pixelformat.cpp b/source/common/pixelformat.cpp index 9887980a..ffab5155 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 diff --git a/source/modules/font/Font.cpp b/source/modules/font/Font.cpp new file mode 100644 index 00000000..f70b5320 --- /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 00000000..4cea57db --- /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 00000000..efcd123c --- /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 00000000..6ab974a1 --- /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 00000000..5ba56cb4 --- /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/wrap_Font.cpp b/source/modules/font/wrap_Font.cpp new file mode 100644 index 00000000..25387cbb --- /dev/null +++ b/source/modules/font/wrap_Font.cpp @@ -0,0 +1,104 @@ +#include "modules/font/wrap_Font.hpp" + +#include "modules/font/font.hpp" + +#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); +} + +int Wrap_FontModule::newTrueTypeRasterizer(lua_State* L) +{ + return 0; +} + +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 00000000..296ef1cc --- /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 00000000..9911fed2 --- /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/Font.cpp b/source/modules/graphics/Font.cpp new file mode 100644 index 00000000..996c0afc --- /dev/null +++ b/source/modules/graphics/Font.cpp @@ -0,0 +1,374 @@ +#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(); + } + + void FontBase::createTexture() + { + auto* graphics = Module::getInstance(Module::M_GRAPHICS); + graphics->flushBatchedDraws(); + + TextureBase* texture = nullptr; + TextureSize size = { this->textureWidth, this->textureHeight }; + TextureSize nextSize = this->getNextTextureSize(); + + bool recreateTexture = false; + + if ((nextSize.width > size.width || nextSize.height > size.height) && !this->textures.empty()) + { + recreateTexture = true; + size = nextSize; + this->textures.pop_back(); + } + } + + GlyphData* FontBase::getRasterizerGlyphData(TextShaper::GlyphIndex glyphindex, float& dpiscale) + { + const auto& rasterizer = shaper->getRasterizers()[glyphindex.rasterizerIndex]; + dpiScale = rasterizer->getDPIScale(); + + return rasterizer->getGlyphData(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); + } + + 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 curcolori = 0; + int ncolors = (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); + curcolori = 0; + currentColor = constantcolor; + continue; + } + + if (curcolori < ncolors && colors[curcolori].index == i) + { + Color c = colors[curcolori].color; + + c.r = std::min(std::max(c.r, 0.0f), 1.0f); + c.g = std::min(std::max(c.g, 0.0f), 1.0f); + c.b = std::min(std::max(c.b, 0.0f), 1.0f); + c.a = std::min(std::max(c.a, 0.0f), 1.0f); + + gammaCorrectColor(c); + c *= linearConstantColor; + unGammaCorrectColor(c); + + currentColor = c; + curcolori++; + } + + 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; + } + + 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; + + auto data = graphics->requestBatchedDraw(command); + GlyphVertex* vertexdata = (GlyphVertex*)data.stream; + + memcpy(vertexdata, &vertices[cmd.startVertex], sizeof(GlyphVertex) * cmd.vertexCount); + 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; + } +} // namespace love diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index e4d9fb7a..b931432d 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -110,7 +110,7 @@ namespace love this->setColorMask(state.colorMask); - this->setDefaultSamplerState(state.defaultSampleState); + this->setDefaultSamplerState(state.defaultSamplerState); if (state.useCustomProjection) this->updateDeviceProjection(state.customProjection); @@ -163,7 +163,7 @@ namespace love if (state.colorMask != current.colorMask) this->setColorMask(state.colorMask); - this->setDefaultSamplerState(state.defaultSampleState); + this->setDefaultSamplerState(state.defaultSamplerState); if (state.useCustomProjection) this->updateDeviceProjection(state.customProjection); @@ -324,6 +324,7 @@ namespace love state.format = command.format; state.texture = command.texture; state.shaderType = command.shaderType; + state.isFont = command.isFont; } if (state.vertexCount == 0) @@ -372,16 +373,49 @@ namespace love if (state.vertexCount > 0) this->drawCallsBatched++; + state.lastVertexCount += command.vertexCount; + state.lastIndexCount += requestedIndexCount; + state.vertexCount += command.vertexCount; state.indexCount += requestedIndexCount; return data; } + 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::flushBatchedDraws() { BatchedDrawState& state = this->batchedDrawState; + if ((state.lastIndexCount == 0 && state.lastVertexCount == 0) || state.flushing) + return; + if ((state.vertexCount == 0 && state.indexCount == 0) || state.flushing) return; @@ -394,7 +428,7 @@ namespace love { attributes.setCommonFormat(state.format, (uint8_t)0); - usedSizes[0] = getFormatStride(state.format) * state.vertexCount; + usedSizes[0] = state.lastVertexCount; size_t offset = state.vertexBuffer->unmap(usedSizes[0]); buffers.set(0, state.vertexBuffer, offset, state.vertexCount); @@ -405,7 +439,7 @@ namespace love if (attributes.enableBits == 0) return; - state.flushing = true; + // state.flushing = true; auto originalColor = this->getColor(); if (attributes.isEnabled(ATTRIB_COLOR)) @@ -415,11 +449,11 @@ namespace love if (state.indexCount > 0) { - usedSizes[1] = sizeof(uint16_t) * state.indexCount; + usedSizes[1] = state.lastIndexCount; DrawIndexedCommand command(&attributes, &buffers, state.indexBuffer); command.primitiveType = state.primitiveMode; - command.indexCount = state.indexCount; + command.indexCount = state.lastIndexCount; command.indexType = INDEX_UINT16; command.indexBufferOffset = state.indexBuffer->unmap(usedSizes[1]); command.texture = state.texture; @@ -432,26 +466,25 @@ namespace love DrawCommand command {}; command.primitiveType = state.primitiveMode; command.vertexStart = 0; - command.vertexCount = state.vertexCount; + command.vertexCount = state.lastVertexCount; command.texture = state.texture; this->draw(command); } if (usedSizes[0] > 0) - state.vertexBuffer->markUsed(state.vertexCount); + state.vertexBuffer->markUsed(usedSizes[0]); if (usedSizes[1] > 0) - state.indexBuffer->markUsed(state.indexCount); + state.indexBuffer->markUsed(usedSizes[1]); this->popTransform(); if (attributes.isEnabled(ATTRIB_COLOR)) this->setColor(originalColor); - state.vertexCount = 0; - state.indexCount = 0; - state.flushing = false; + state.lastVertexCount = 0; + state.lastIndexCount = 0; } void GraphicsBase::flushBatchedDrawsGlobal() @@ -469,6 +502,9 @@ namespace love if (this->batchedDrawState.indexBuffer) this->batchedDrawState.indexBuffer->nextFrame(); + + this->batchedDrawState.vertexCount = 0; + this->batchedDrawState.indexCount = 0; } void GraphicsBase::advanceStreamBuffersGlobal() diff --git a/source/modules/graphics/Texture.cpp b/source/modules/graphics/Texture.cpp index 9bf98e59..79f13c92 100644 --- a/source/modules/graphics/Texture.cpp +++ b/source/modules/graphics/Texture.cpp @@ -192,6 +192,7 @@ namespace love 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) @@ -251,7 +252,7 @@ namespace love format = love::getLinearPixelFormat(format); if (this->mipmapsMode == MIPMAPS_AUTO && this->isCompressed()) - this->mipmapsMode = MIPMAPS_NONE; + this->mipmapsMode = MIPMAPS_MANUAL; if (this->mipmapsMode != MIPMAPS_NONE) { @@ -262,8 +263,18 @@ namespace love else this->mipmapCount = totalMipmapCount; - if (this->mipmapCount != totalMipmapCount) - throw love::Exception("Custom mipmap ranges for a texture are not supported."); + // 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) @@ -287,7 +298,37 @@ namespace love if (this->isCompressed() && this->renderTarget) throw love::Exception("Compressed textures cannot be render targets."); - this->validateViewFormats(); + 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; @@ -331,8 +372,8 @@ namespace love 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."); + if (this->renderTarget && graphics->isRenderTargetActive(this)) + throw love::Exception("Cannot render a Texture to itself."); const auto& transform = graphics->getTransform(); bool is2D = transform.isAffine2DTransform(); @@ -366,8 +407,248 @@ namespace love } } - void TextureBase::validateViewFormats() const - {} + void TextureBase::uploadImageData(ImageDataBase* data, int level, int slice, int x, int y) + { + Rect rectangle = { x, y, data->getWidth(), data->getHeight() }; + + const auto size = getPixelFormatSliceSize(this->format, data->getWidth(), data->getHeight()); + this->uploadByteData(data->getData(), size, 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); + + 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; + + 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; + } + } + + 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 { diff --git a/source/modules/graphics/wrap_Font.cpp b/source/modules/graphics/wrap_Font.cpp new file mode 100644 index 00000000..b1b8c876 --- /dev/null +++ b/source/modules/graphics/wrap_Font.cpp @@ -0,0 +1,50 @@ +#include "modules/graphics/wrap_Font.hpp" + +#include + +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::WHITE; + + 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); + } + } +} // namespace love diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index dab5f5f3..ec087c0a 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -4,6 +4,7 @@ #include "modules/filesystem/wrap_Filesystem.hpp" +#include "modules/graphics/wrap_Font.hpp" #include "modules/graphics/wrap_Quad.hpp" #include "modules/graphics/wrap_Texture.hpp" @@ -1011,6 +1012,55 @@ int Wrap_Graphics::draw(lua_State* L) return 0; } +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::getStats(lua_State* L) { auto stats = instance()->getStats(); @@ -1498,6 +1548,9 @@ static constexpr luaL_Reg functions[] = { "newTexture", Wrap_Graphics::newTexture }, { "newImage", Wrap_Graphics::newImage }, + { "newFont", Wrap_Graphics::newFont }, + { "print", Wrap_Graphics::print }, + { "setActiveScreen", Wrap_Graphics::setActiveScreen }, { "getScreens", Wrap_Graphics::getScreens }, }; diff --git a/source/modules/love/love.cpp b/source/modules/love/love.cpp index ba26e338..494b5385 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -7,6 +7,7 @@ #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" @@ -50,12 +51,13 @@ 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 }, diff --git a/source/modules/love/scripts/boot.lua b/source/modules/love/scripts/boot.lua index 5fa2bbd8..43ac5942 100644 --- a/source/modules/love/scripts/boot.lua +++ b/source/modules/love/scripts/boot.lua @@ -181,7 +181,7 @@ function love.init() sensor = true, sound = true, system = true, - font = false, + font = true, thread = true, window = true, video = false, From 66872ae378ff61733a467a9980ea30bea316ee69 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Wed, 29 May 2024 13:38:05 -0400 Subject: [PATCH 10/49] fonts work, but not using color properly --- .github/workflows/Nintendo 3DS.yml | 14 ++--- .github/workflows/Nintendo Switch.yml | 18 +++---- .github/workflows/Nintendo Wii U.yml | 18 +++---- include/modules/font/Rasterizer.hpp | 9 ++++ include/modules/graphics/Graphics.tcc | 4 +- platform/ctr/include/modules/font/Font.hpp | 2 + .../source/modules/font/BCFNTRasterizer.cpp | 5 +- .../ctr/source/modules/graphics/Texture.cpp | 2 - source/modules/font/wrap_Font.cpp | 53 ++++++++++++++++++- source/modules/graphics/Font.cpp | 4 +- 10 files changed, 87 insertions(+), 42 deletions(-) diff --git a/.github/workflows/Nintendo 3DS.yml b/.github/workflows/Nintendo 3DS.yml index 880f5e5f..a7b8b0c5 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 diff --git a/.github/workflows/Nintendo Switch.yml b/.github/workflows/Nintendo Switch.yml index 153ca84b..71a73c69 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 d6741b52..4cc87ff5 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: catnipt -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/include/modules/font/Rasterizer.hpp b/include/modules/font/Rasterizer.hpp index 772f9cda..2e8d7a60 100644 --- a/include/modules/font/Rasterizer.hpp +++ b/include/modules/font/Rasterizer.hpp @@ -102,6 +102,15 @@ namespace love 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; diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 0d81b579..81d1440a 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -325,8 +325,8 @@ namespace love DisplayState() {} - Color color = Color::WHITE; - Color backgroundColor = Color::BLACK; + Color color = Color(1.0f, 1.0f, 1.0f, 1.0f); + Color backgroundColor = Color(0.0f, 0.0f, 0.0f, 1.0f); BlendState blend = computeBlendState(BLEND_ALPHA, BLENDALPHA_MULTIPLY); diff --git a/platform/ctr/include/modules/font/Font.hpp b/platform/ctr/include/modules/font/Font.hpp index ff7fed21..3003e1dc 100644 --- a/platform/ctr/include/modules/font/Font.hpp +++ b/platform/ctr/include/modules/font/Font.hpp @@ -18,6 +18,8 @@ namespace love Rasterizer* newTrueTypeRasterizer(Data* data, int size, const Rasterizer::Settings& settings) const override; + using FontModuleBase::newTrueTypeRasterizer; + private: static constexpr auto FONT_ARCHIVE_TITLE = 0x0004009B00014002ULL; diff --git a/platform/ctr/source/modules/font/BCFNTRasterizer.cpp b/platform/ctr/source/modules/font/BCFNTRasterizer.cpp index 87cdd36e..0d8acccc 100644 --- a/platform/ctr/source/modules/font/BCFNTRasterizer.cpp +++ b/platform/ctr/source/modules/font/BCFNTRasterizer.cpp @@ -82,7 +82,9 @@ namespace love if (this->size == 0) throw love::Exception("Invalid font size: {:d}", this->size); - fontFixPointers((CFNT_s*)data->getData()); + /* if we already have this data loaded, fixing this (again) is a bad time™ */ + if ((uintptr_t)fontGetInfo((CFNT_s*)data->getData())->tglp < (uintptr_t)data->getData()) + fontFixPointers((CFNT_s*)data->getData()); auto* fontInfo = fontGetInfo((CFNT_s*)data->getData()); auto* sheetInfo = fontInfo->tglp; @@ -94,6 +96,7 @@ namespace love 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; diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp index 23198900..0394013f 100644 --- a/platform/ctr/source/modules/graphics/Texture.cpp +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -272,8 +272,6 @@ namespace love const auto mipWidth = this->getPixelWidth(level); const auto mipHeight = this->getPixelHeight(level); - std::printf("Data Size: %zu/Texture Size: %zu\n", size, this->texture->size); - // copy it directly if the size is the whole thing if (rect == Rect(0, 0, mipWidth, mipHeight)) std::memcpy(textureData, data, size); diff --git a/source/modules/font/wrap_Font.cpp b/source/modules/font/wrap_Font.cpp index 25387cbb..78a20322 100644 --- a/source/modules/font/wrap_Font.cpp +++ b/source/modules/font/wrap_Font.cpp @@ -36,9 +36,60 @@ int Wrap_FontModule::newRasterizer(lua_State* L) 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; +} + int Wrap_FontModule::newTrueTypeRasterizer(lua_State* L) { - return 0; + 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); }); + } + + luax_pushtype(L, rasterizer); + rasterizer->release(); + + return 1; } int Wrap_FontModule::newBMFontRasterizer(lua_State* L) diff --git a/source/modules/graphics/Font.cpp b/source/modules/graphics/Font.cpp index 996c0afc..fba4fec1 100644 --- a/source/modules/graphics/Font.cpp +++ b/source/modules/graphics/Font.cpp @@ -120,7 +120,7 @@ namespace love const auto& rasterizer = shaper->getRasterizers()[glyphindex.rasterizerIndex]; dpiScale = rasterizer->getDPIScale(); - return rasterizer->getGlyphData(glyphindex.index); + return rasterizer->getGlyphDataForIndex(glyphindex.index); } const FontBase::Glyph& FontBase::findGlyph(TextShaper::GlyphIndex glyphindex) @@ -295,7 +295,7 @@ namespace love auto data = graphics->requestBatchedDraw(command); GlyphVertex* vertexdata = (GlyphVertex*)data.stream; - memcpy(vertexdata, &vertices[cmd.startVertex], sizeof(GlyphVertex) * cmd.vertexCount); + std::copy_n(vertexdata, cmd.vertexCount, &vertices[cmd.startVertex]); m.transformXY(vertexdata, &vertices[cmd.startVertex], cmd.vertexCount); } } From 3ee72ebf4a9ade8359b52151f78906b503e09e60 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Wed, 29 May 2024 13:41:11 -0400 Subject: [PATCH 11/49] fix file casing --- source/modules/font/wrap_Font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/modules/font/wrap_Font.cpp b/source/modules/font/wrap_Font.cpp index 78a20322..24db61a6 100644 --- a/source/modules/font/wrap_Font.cpp +++ b/source/modules/font/wrap_Font.cpp @@ -1,6 +1,6 @@ #include "modules/font/wrap_Font.hpp" -#include "modules/font/font.hpp" +#include "modules/font/Font.hpp" #include "modules/font/wrap_GlyphData.hpp" #include "modules/font/wrap_Rasterizer.hpp" From 3ca6b1a98606aa85237f79108785db57ed7a0df7 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Wed, 29 May 2024 13:46:54 -0400 Subject: [PATCH 12/49] fix arguments order, bleh --- source/modules/graphics/Font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/modules/graphics/Font.cpp b/source/modules/graphics/Font.cpp index fba4fec1..9e69ffcf 100644 --- a/source/modules/graphics/Font.cpp +++ b/source/modules/graphics/Font.cpp @@ -295,7 +295,7 @@ namespace love auto data = graphics->requestBatchedDraw(command); GlyphVertex* vertexdata = (GlyphVertex*)data.stream; - std::copy_n(vertexdata, cmd.vertexCount, &vertices[cmd.startVertex]); + std::copy_n(&vertices[cmd.startVertex], cmd.vertexCount, vertexdata); m.transformXY(vertexdata, &vertices[cmd.startVertex], cmd.vertexCount); } } From cc2cc95d97040982f9bd941f953a9453fc7b4b8d Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 30 May 2024 10:24:28 -0400 Subject: [PATCH 13/49] fix da fonts on 3ds :D --- include/modules/graphics/Graphics.tcc | 21 +- include/modules/graphics/Texture.tcc | 7 + include/modules/graphics/wrap_Font.hpp | 31 +++ include/modules/graphics/wrap_Graphics.hpp | 6 + .../ctr/include/modules/graphics/Texture.hpp | 5 + platform/ctr/source/modules/graphics/Font.cpp | 5 +- source/modules/graphics/Font.cpp | 174 ++++++++++-- source/modules/graphics/Graphics.cpp | 32 ++- source/modules/graphics/wrap_Font.cpp | 247 +++++++++++++++++- source/modules/graphics/wrap_Graphics.cpp | 98 ++++++- 10 files changed, 578 insertions(+), 48 deletions(-) diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 81d1440a..43eeb349 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -400,6 +400,22 @@ namespace love this->states.back().backgroundColor = color; } + Color getBackgroundColor() const + { + return this->states.back().backgroundColor; + } + + void setFont(FontBase* font) + { + this->states.back().font.set(font); + } + + FontBase* getFont() + { + this->checkSetDefaultFont(); + return this->states.back().font.get(); + } + const SamplerState& getDefaultSamplerState() const { return this->states.back().defaultSamplerState; @@ -418,11 +434,6 @@ namespace love bool getScissor(Rect& scissor) const; - Color getBackgroundColor() const - { - return this->states.back().backgroundColor; - } - void setMeshCullMode(CullMode mode) { this->states.back().meshCullMode = mode; diff --git a/include/modules/graphics/Texture.tcc b/include/modules/graphics/Texture.tcc index 0a34beda..2e3f1dfe 100644 --- a/include/modules/graphics/Texture.tcc +++ b/include/modules/graphics/Texture.tcc @@ -305,6 +305,13 @@ namespace love virtual ptrdiff_t getRenderTargetHandle() 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(void* data) = 0; + virtual void setSamplerState(const SamplerState& state) = 0; void replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, bool reloadMipmaps); diff --git a/include/modules/graphics/wrap_Font.hpp b/include/modules/graphics/wrap_Font.hpp index 397a37b3..8d6cc8b9 100644 --- a/include/modules/graphics/wrap_Font.hpp +++ b/include/modules/graphics/wrap_Font.hpp @@ -11,3 +11,34 @@ namespace love 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 3c07f857..60f61b40 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -167,6 +167,10 @@ namespace Wrap_Graphics 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); @@ -185,6 +189,8 @@ namespace Wrap_Graphics int print(lua_State* L); + int printf(lua_State* L); + int getScreens(lua_State* L); int setActiveScreen(lua_State* L); diff --git a/platform/ctr/include/modules/graphics/Texture.hpp b/platform/ctr/include/modules/graphics/Texture.hpp index 3d565068..ab39375c 100644 --- a/platform/ctr/include/modules/graphics/Texture.hpp +++ b/platform/ctr/include/modules/graphics/Texture.hpp @@ -30,6 +30,11 @@ namespace love void generateMipmapsInternal() override; + void setHandleData(void* data) override + { + this->texture->data = data; + } + // bool validateDimensions(bool throwException) const; // void validatePixelFormat(Graphics& graphics) const; diff --git a/platform/ctr/source/modules/graphics/Font.cpp b/platform/ctr/source/modules/graphics/Font.cpp index 74c272f2..b4e7f228 100644 --- a/platform/ctr/source/modules/graphics/Font.cpp +++ b/platform/ctr/source/modules/graphics/Font.cpp @@ -29,6 +29,7 @@ namespace love auto graphics = Module::getInstance(Module::M_GRAPHICS); + /* the texture and font data are the same size */ for (size_t index = 0; index < info->nSheets; index++) { TextureBase::Settings settings {}; @@ -39,9 +40,7 @@ namespace love auto* texture = graphics->newTexture(settings, nullptr); auto* data = fontGetGlyphSheetTex(font, index); - Rect rect { 0, 0, settings.width, settings.height }; - - texture->replacePixels(data, info->sheetSize, 0, 0, rect, false); + texture->setHandleData(data); texture->setSamplerState(this->samplerState); this->textures.emplace_back(texture, Acquire::NO_RETAIN); diff --git a/source/modules/graphics/Font.cpp b/source/modules/graphics/Font.cpp index 9e69ffcf..8747ccb4 100644 --- a/source/modules/graphics/Font.cpp +++ b/source/modules/graphics/Font.cpp @@ -185,34 +185,33 @@ namespace love } std::vector FontBase::generateVertices(const ColoredCodepoints& codepoints, - Range range, const Color& constantcolor, + Range range, const Color& constantColor, std::vector& vertices, float extra_spacing, Vector2 offset, TextShaper::TextInfo* info) { - std::vector glyphpositions; + std::vector glyphPositions {}; std::vector colors; - this->shaper->computeGlyphPositions(codepoints, range, offset, extra_spacing, &glyphpositions, + this->shaper->computeGlyphPositions(codepoints, range, offset, extra_spacing, &glyphPositions, &colors, info); size_t vertexStartSize = vertices.size(); - vertices.reserve(vertexStartSize + glyphpositions.size() * 4); + vertices.reserve(vertexStartSize + glyphPositions.size() * 4); - Color linearConstantColor = gammaCorrectColor(constantcolor); - Color currentColor = constantcolor; + Color linearConstantColor = gammaCorrectColor(constantColor); + Color currentColor = constantColor; - int curcolori = 0; - int ncolors = (int)colors.size(); + 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++) + for (int i = 0; i < (int)glyphPositions.size(); i++) { - const auto& info = glyphpositions[i]; - - uint32_t cacheid = textureCacheID; + const auto& info = glyphPositions[i]; + uint32_t cacheid = textureCacheID; const Glyph& glyph = findGlyph(info.glyphIndex); // If findGlyph invalidates the texture cache, restart the loop. @@ -221,26 +220,26 @@ namespace love i = -1; // The next iteration will increment this to 0. commands.clear(); vertices.resize(vertexStartSize); - curcolori = 0; - currentColor = constantcolor; + currentColorIndex = 0; + currentColor = constantColor; continue; } - if (curcolori < ncolors && colors[curcolori].index == i) + if (currentColorIndex < numColors && colors[currentColorIndex].index == i) { - Color c = colors[curcolori].color; + Color c = colors[currentColorIndex].color; - c.r = std::min(std::max(c.r, 0.0f), 1.0f); - c.g = std::min(std::max(c.g, 0.0f), 1.0f); - c.b = std::min(std::max(c.b, 0.0f), 1.0f); - c.a = std::min(std::max(c.a, 0.0f), 1.0f); + 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; - curcolori++; + currentColorIndex++; } if (glyph.texture != nullptr) @@ -274,6 +273,110 @@ namespace love 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; + } + void FontBase::printv(GraphicsBase* graphics, const Matrix4& matrix, const std::vector& drawcommands, const std::vector& vertices) @@ -371,4 +474,33 @@ namespace love { 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 index b931432d..87bfba5d 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -101,7 +101,7 @@ namespace love this->setMeshCullMode(state.meshCullMode); this->setFrontFaceWinding(state.winding); - // this->setFont(state.font.get()); + this->setFont(state.font.get()); // this->setShader(state.shader.get()); // this->setRenderTargets(state.renderTargets); @@ -151,7 +151,7 @@ namespace love if (state.winding != current.winding) this->setFrontFaceWinding(state.winding); - // this->setFont(state.font.get()); + this->setFont(state.font.get()); // this->setShader(state.shader.get()); // if (this->stencil != state.stencil) @@ -409,6 +409,21 @@ namespace love 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::flushBatchedDraws() { BatchedDrawState& state = this->batchedDrawState; @@ -441,9 +456,9 @@ namespace love // state.flushing = true; - auto originalColor = this->getColor(); - if (attributes.isEnabled(ATTRIB_COLOR)) - this->setColor(Color::WHITE); + // auto originalColor = this->getColor(); + // if (attributes.isEnabled(ATTRIB_COLOR)) + // this->setColor(Color::WHITE); this->pushIdentityTransform(); @@ -457,6 +472,7 @@ namespace love 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(); @@ -478,10 +494,10 @@ namespace love if (usedSizes[1] > 0) state.indexBuffer->markUsed(usedSizes[1]); - this->popTransform(); + // this->popTransform(); - if (attributes.isEnabled(ATTRIB_COLOR)) - this->setColor(originalColor); + // if (attributes.isEnabled(ATTRIB_COLOR)) + // this->setColor(originalColor); state.lastVertexCount = 0; state.lastIndexCount = 0; diff --git a/source/modules/graphics/wrap_Font.cpp b/source/modules/graphics/wrap_Font.cpp index b1b8c876..e5d5d41c 100644 --- a/source/modules/graphics/wrap_Font.cpp +++ b/source/modules/graphics/wrap_Font.cpp @@ -2,6 +2,244 @@ #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) @@ -12,7 +250,7 @@ namespace love void luax_checkcoloredstring(lua_State* L, int index, std::vector& strings) { ColoredString coloredString {}; - coloredString.color = Color::WHITE; + coloredString.color = Color(1.0f, 1.0f, 1.0f, 1.0f); if (lua_istable(L, index)) { @@ -47,4 +285,11 @@ namespace love strings.push_back(coloredString); } } + + int open_font(lua_State* L) + { + luax_register_type(L, &FontBase::type, functions); + + return 0; + } } // namespace love diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index ec087c0a..42412894 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -138,19 +138,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); @@ -1012,6 +1012,24 @@ int Wrap_Graphics::draw(lua_State* L) 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; +} + int Wrap_Graphics::newFont(lua_State* L) { luax_checkgraphicscreated(L); @@ -1061,6 +1079,62 @@ int Wrap_Graphics::print(lua_State* L) 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(); @@ -1549,10 +1623,13 @@ static constexpr luaL_Reg functions[] = { "newImage", Wrap_Graphics::newImage }, { "newFont", Wrap_Graphics::newFont }, + { "setFont", Wrap_Graphics::setFont }, + { "getFont", Wrap_Graphics::getFont }, { "print", Wrap_Graphics::print }, + { "printf", Wrap_Graphics::printf }, { "setActiveScreen", Wrap_Graphics::setActiveScreen }, - { "getScreens", Wrap_Graphics::getScreens }, + { "getScreens", Wrap_Graphics::getScreens } }; static int open_drawable(lua_State* L) @@ -1564,7 +1641,8 @@ static constexpr lua_CFunction types[] = { open_drawable, love::open_texture, - love::open_quad + love::open_quad, + love::open_font }; // clang-format on From 22e8aecc9b14b3476c46d51d1a2af01d4ddfe26c Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 30 May 2024 12:03:36 -0400 Subject: [PATCH 14/49] text batches are working :D --- CMakeLists.txt | 3 + include/common/memory.hpp | 6 + include/modules/graphics/Buffer.hpp | 239 ------------------- include/modules/graphics/Buffer.tcc | 234 +++++++++++++++++++ include/modules/graphics/Graphics.tcc | 3 + include/modules/graphics/TextBatch.hpp | 75 ++++++ include/modules/graphics/Texture.tcc | 2 +- include/modules/graphics/vertex.hpp | 6 + include/modules/graphics/wrap_Graphics.hpp | 2 + include/modules/graphics/wrap_TextBatch.hpp | 34 +++ source/modules/graphics/Buffer.cpp | 190 +++++++++++++++ source/modules/graphics/Graphics.cpp | 5 + source/modules/graphics/TextBatch.cpp | 241 ++++++++++++++++++++ source/modules/graphics/memory.cpp | 9 + source/modules/graphics/wrap_Graphics.cpp | 25 ++ source/modules/graphics/wrap_TextBatch.cpp | 208 +++++++++++++++++ 16 files changed, 1042 insertions(+), 240 deletions(-) create mode 100644 include/common/memory.hpp delete mode 100644 include/modules/graphics/Buffer.hpp create mode 100644 include/modules/graphics/Buffer.tcc create mode 100644 include/modules/graphics/TextBatch.hpp create mode 100644 include/modules/graphics/wrap_TextBatch.hpp create mode 100644 source/modules/graphics/Buffer.cpp create mode 100644 source/modules/graphics/TextBatch.cpp create mode 100644 source/modules/graphics/memory.cpp create mode 100644 source/modules/graphics/wrap_TextBatch.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e5418689..a59266c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,8 +363,10 @@ 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/Font.cpp +source/modules/graphics/TextBatch.cpp source/modules/graphics/renderstate.cpp source/modules/graphics/samplerstate.cpp source/modules/graphics/Shader.cpp @@ -373,6 +375,7 @@ 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 diff --git a/include/common/memory.hpp b/include/common/memory.hpp new file mode 100644 index 00000000..1ef17472 --- /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/modules/graphics/Buffer.hpp b/include/modules/graphics/Buffer.hpp deleted file mode 100644 index f18708a9..00000000 --- a/include/modules/graphics/Buffer.hpp +++ /dev/null @@ -1,239 +0,0 @@ -/** - * 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 -{ - namespace graphics - { - - class Graphics; - - /** - * A block of GPU-owned memory. - **/ - class Buffer : 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() - {} - }; - - Buffer(Graphics* gfx, const Settings& settings, const std::vector& format, - size_t size, size_t arraylength); - - virtual ~Buffer(); - - 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(Buffer* 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(Buffer& buffer, MapType maptype = MAP_WRITE_INVALIDATE) : buffer(buffer) - { - data = buffer.map(maptype, 0, buffer.getSize()); - } - - ~Mapper() - { - buffer.unmap(0, buffer.getSize()); - } - - Buffer& 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 graphics -} // namespace love diff --git a/include/modules/graphics/Buffer.tcc b/include/modules/graphics/Buffer.tcc new file mode 100644 index 00000000..a47d3b92 --- /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/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 43eeb349..4201d4c6 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -14,6 +14,7 @@ #include "modules/graphics/Font.tcc" #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" @@ -461,6 +462,8 @@ namespace love virtual FontBase* newDefaultFont(int size, const Rasterizer::Settings& settings) = 0; + TextBatch* newTextBatch(FontBase* font, const std::vector& text = {}); + void checkSetDefaultFont(); void print(const std::vector& string, const Matrix4& matrix); diff --git a/include/modules/graphics/TextBatch.hpp b/include/modules/graphics/TextBatch.hpp new file mode 100644 index 00000000..afc4b739 --- /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 index 2e3f1dfe..ace02341 100644 --- a/include/modules/graphics/Texture.tcc +++ b/include/modules/graphics/Texture.tcc @@ -25,7 +25,7 @@ namespace love { class GraphicsBase; - class Buffer; + class BufferBase; enum TextureType { diff --git a/include/modules/graphics/vertex.hpp b/include/modules/graphics/vertex.hpp index 8fff9f47..49f929c8 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -406,5 +406,11 @@ namespace love { "cw", WINDING_CW }, { "ccw", WINDING_CCW } ); + + STRINGMAP_DECLARE(BufferAttributes, BuiltinVertexAttribute, + { "VertexPosition", ATTRIB_POS }, + { "VertexTexCoord", ATTRIB_TEXCOORD }, + { "VertexColor", ATTRIB_COLOR } + ); // clang-format on } // namespace love diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index 60f61b40..39f50f32 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -185,6 +185,8 @@ namespace Wrap_Graphics int draw(lua_State* L); + int newTextBatch(lua_State* L); + int newFont(lua_State* L); int print(lua_State* L); diff --git a/include/modules/graphics/wrap_TextBatch.hpp b/include/modules/graphics/wrap_TextBatch.hpp new file mode 100644 index 00000000..9272a81a --- /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/source/modules/graphics/Buffer.cpp b/source/modules/graphics/Buffer.cpp new file mode 100644 index 00000000..26b14b19 --- /dev/null +++ b/source/modules/graphics/Buffer.cpp @@ -0,0 +1,190 @@ +#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 & BUFFERUSAGE_INDEX; + bool vertexBuffer = usageFlags & BUFFERUSAGE_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 }, + }; + } + + return {}; + } +} // namespace love diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 87bfba5d..d630c850 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -382,6 +382,11 @@ namespace love return data; } + TextBatch* GraphicsBase::newTextBatch(FontBase* font, const std::vector& text) + { + return new TextBatch(font, text); + } + void GraphicsBase::checkSetDefaultFont() { if (this->states.back().font.get() != nullptr) diff --git a/source/modules/graphics/TextBatch.cpp b/source/modules/graphics/TextBatch.cpp new file mode 100644 index 00000000..17ced29a --- /dev/null +++ b/source/modules/graphics/TextBatch.cpp @@ -0,0 +1,241 @@ +#include "modules/graphics/TextBatch.hpp" +#include "modules/graphics/Graphics.tcc" + +#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; + } + + 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()) + { + size_t offset = this->modifiedVertices.getOffset(); + size_t size = this->modifiedVertices.getSize(); + + 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.isFont = true; + + 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/memory.cpp b/source/modules/graphics/memory.cpp new file mode 100644 index 00000000..23647ecf --- /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/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 42412894..504408fa 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -1030,6 +1030,29 @@ int Wrap_Graphics::getFont(lua_State* L) return 1; } +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); @@ -1622,6 +1645,8 @@ static constexpr luaL_Reg functions[] = { "newTexture", Wrap_Graphics::newTexture }, { "newImage", Wrap_Graphics::newImage }, + { "newTextBatch", Wrap_Graphics::newTextBatch }, + { "newFont", Wrap_Graphics::newFont }, { "setFont", Wrap_Graphics::setFont }, { "getFont", Wrap_Graphics::getFont }, diff --git a/source/modules/graphics/wrap_TextBatch.cpp b/source/modules/graphics/wrap_TextBatch.cpp new file mode 100644 index 00000000..e80170a3 --- /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 From f239e3d9834a3511d11afbfa315b4cd4a4ed69aa Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 30 May 2024 17:58:02 -0400 Subject: [PATCH 15/49] try to do some mesh stuff, add polylined stuff --- CMakeLists.txt | 3 + include/common/Matrix.hpp | 20 +- include/driver/display/Renderer.tcc | 2 +- include/modules/graphics/Graphics.tcc | 3 + include/modules/graphics/Mesh.hpp | 102 ++++ include/modules/graphics/Polyline.hpp | 171 ++++++ include/modules/graphics/vertex.hpp | 11 + include/modules/graphics/wrap_Graphics.hpp | 2 + include/modules/graphics/wrap_Mesh.hpp | 45 ++ .../ctr/include/driver/display/citro3d.hpp | 2 +- .../ctr/include/modules/graphics/Buffer.hpp | 53 ++ .../ctr/include/modules/graphics/Shader.hpp | 2 +- .../ctr/source/driver/display/citro3d.cpp | 4 +- .../ctr/source/modules/graphics/Buffer.cpp | 39 ++ .../ctr/source/modules/graphics/Graphics.cpp | 4 +- .../ctr/source/modules/graphics/Shader.cpp | 14 +- source/common/Matrix.cpp | 85 +-- source/modules/graphics/Graphics.cpp | 30 +- source/modules/graphics/Mesh.cpp | 347 ++++++++++++ source/modules/graphics/Polyline.cpp | 497 ++++++++++++++++++ source/modules/graphics/vertex.cpp | 23 + source/modules/graphics/wrap_Graphics.cpp | 7 + source/modules/graphics/wrap_Mesh.cpp | 228 ++++++++ 23 files changed, 1634 insertions(+), 60 deletions(-) create mode 100644 include/modules/graphics/Mesh.hpp create mode 100644 include/modules/graphics/Polyline.hpp create mode 100644 include/modules/graphics/wrap_Mesh.hpp create mode 100644 platform/ctr/include/modules/graphics/Buffer.hpp create mode 100644 platform/ctr/source/modules/graphics/Buffer.cpp create mode 100644 source/modules/graphics/Mesh.cpp create mode 100644 source/modules/graphics/Polyline.cpp create mode 100644 source/modules/graphics/wrap_Mesh.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a59266c8..196dd8e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -366,6 +366,9 @@ source/modules/font/wrap_GlyphData.cpp source/modules/graphics/Buffer.cpp source/modules/graphics/Graphics.cpp source/modules/graphics/Font.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 diff --git a/include/common/Matrix.hpp b/include/common/Matrix.hpp index 766b5f25..e4d41989 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/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index 11a09412..79c67086 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -19,7 +19,7 @@ namespace love RendererBase() {} - virtual void prepareDraw() = 0; + virtual void prepareDraw(GraphicsBase* graphics) = 0; bool isInFrame() const { diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 4201d4c6..41243701 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -13,6 +13,7 @@ #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" @@ -462,6 +463,8 @@ namespace love 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(); diff --git a/include/modules/graphics/Mesh.hpp b/include/modules/graphics/Mesh.hpp new file mode 100644 index 00000000..055381c0 --- /dev/null +++ b/include/modules/graphics/Mesh.hpp @@ -0,0 +1,102 @@ +#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); + + 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 00000000..adf6290d --- /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/vertex.hpp b/include/modules/graphics/vertex.hpp index 49f929c8..bf5f04a5 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -388,6 +388,12 @@ namespace love 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 }, @@ -412,5 +418,10 @@ namespace love { "VertexTexCoord", ATTRIB_TEXCOORD }, { "VertexColor", ATTRIB_COLOR } ); + + STRINGMAP_DECLARE(IndexDataTypes, IndexDataType, + { "uint16", INDEX_UINT16 }, + { "uint32", INDEX_UINT32 } + ); // clang-format on } // namespace love diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index 39f50f32..c1a4b921 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -185,6 +185,8 @@ namespace Wrap_Graphics int draw(lua_State* L); + int newMesh(lua_State* L); + int newTextBatch(lua_State* L); int newFont(lua_State* L); diff --git a/include/modules/graphics/wrap_Mesh.hpp b/include/modules/graphics/wrap_Mesh.hpp new file mode 100644 index 00000000..4117df90 --- /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/platform/ctr/include/driver/display/citro3d.hpp b/platform/ctr/include/driver/display/citro3d.hpp index 97592e55..603095f8 100644 --- a/platform/ctr/include/driver/display/citro3d.hpp +++ b/platform/ctr/include/driver/display/citro3d.hpp @@ -61,7 +61,7 @@ namespace love void setSamplerState(C3D_Tex* texture, SamplerState state); - virtual void prepareDraw() override; + virtual void prepareDraw(GraphicsBase* graphics) override; void setVertexAttributes(const VertexAttributes& attributes, const BufferBindings& buffers); diff --git a/platform/ctr/include/modules/graphics/Buffer.hpp b/platform/ctr/include/modules/graphics/Buffer.hpp new file mode 100644 index 00000000..81607f10 --- /dev/null +++ b/platform/ctr/include/modules/graphics/Buffer.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "common/Range.hpp" + +#include "modules/graphics/Buffer.tcc" +#include "modules/graphics/Volatile.hpp" + +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; + }; +} // namespace love diff --git a/platform/ctr/include/modules/graphics/Shader.hpp b/platform/ctr/include/modules/graphics/Shader.hpp index 9fe69e55..2e8c8126 100644 --- a/platform/ctr/include/modules/graphics/Shader.hpp +++ b/platform/ctr/include/modules/graphics/Shader.hpp @@ -33,7 +33,7 @@ namespace love bool hasUniform(const std::string& name) const; - void updateBuiltinUniforms(const C3D_Mtx& mdlvMtx, const C3D_Mtx& projMtx); + void updateBuiltinUniforms(GraphicsBase* graphics, C3D_Mtx mdlvMtx, const C3D_Mtx& projMtx); private: bool validate(const char* filepath, std::string& error); diff --git a/platform/ctr/source/driver/display/citro3d.cpp b/platform/ctr/source/driver/display/citro3d.cpp index 904798dd..c054f1c6 100644 --- a/platform/ctr/source/driver/display/citro3d.cpp +++ b/platform/ctr/source/driver/display/citro3d.cpp @@ -305,12 +305,12 @@ namespace love C3D_TexSetLodBias(texture, state.lodBias); } - void citro3d::prepareDraw() + void citro3d::prepareDraw(GraphicsBase* graphics) { // clang-format off if (Shader::current != nullptr && this->context.dirtyProjection) { - ((Shader*)Shader::current)->updateBuiltinUniforms(this->context.modelView, this->context.projection); + ((Shader*)Shader::current)->updateBuiltinUniforms(graphics, this->context.modelView, this->context.projection); this->context.dirtyProjection = false; } // clang-format on diff --git a/platform/ctr/source/modules/graphics/Buffer.cpp b/platform/ctr/source/modules/graphics/Buffer.cpp new file mode 100644 index 00000000..8a470628 --- /dev/null +++ b/platform/ctr/source/modules/graphics/Buffer.cpp @@ -0,0 +1,39 @@ +#include "modules/graphics/Buffer.hpp" + +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 & BUFFERUSAGE_VERTEX) + mapUsage = BUFFERUSAGE_VERTEX; + else if (usageFlags & BUFFERUSAGE_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); + } + } +} // namespace love diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index 84206d57..f5da2d0d 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -395,7 +395,7 @@ namespace love void Graphics::draw(const DrawIndexedCommand& command) { - c3d.prepareDraw(); + c3d.prepareDraw(this); // c3d.setVertexAttributes(*command.attributes, *command.buffers); c3d.bindTextureToUnit(command.texture, 0, command.isFont); @@ -412,7 +412,7 @@ namespace love void Graphics::draw(const DrawCommand& command) { - c3d.prepareDraw(); + c3d.prepareDraw(this); // c3d.setVertexAttributes(*command.attributes, *command.buffers); c3d.bindTextureToUnit(command.texture, 0, command.isFont); diff --git a/platform/ctr/source/modules/graphics/Shader.cpp b/platform/ctr/source/modules/graphics/Shader.cpp index 91cbf97a..5a008909 100644 --- a/platform/ctr/source/modules/graphics/Shader.cpp +++ b/platform/ctr/source/modules/graphics/Shader.cpp @@ -62,10 +62,22 @@ namespace love return false; } - void Shader::updateBuiltinUniforms(const C3D_Mtx& mdlvMtx, const C3D_Mtx& projMtx) + 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); diff --git a/source/common/Matrix.cpp b/source/common/Matrix.cpp index 600cb464..c0485691 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/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index d630c850..bb11b5a2 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -1,4 +1,5 @@ #include "modules/graphics/Graphics.tcc" +#include "modules/graphics/Polyline.hpp" #include "common/Console.hpp" #include "common/screen.hpp" @@ -499,7 +500,7 @@ namespace love if (usedSizes[1] > 0) state.indexBuffer->markUsed(usedSizes[1]); - // this->popTransform(); + this->popTransform(); // if (attributes.isEnabled(ATTRIB_COLOR)) // this->setColor(originalColor); @@ -786,7 +787,32 @@ namespace love } 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) { diff --git a/source/modules/graphics/Mesh.cpp b/source/modules/graphics/Mesh.cpp new file mode 100644 index 00000000..eee2671d --- /dev/null +++ b/source/modules/graphics/Mesh.cpp @@ -0,0 +1,347 @@ +#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) : + primitiveType(mode) + { + if (vertexCount <= 0) + throw love::Exception("Mesh vertex count cannot be zero."); + + this->buffer.resize(vertexCount); + } + + 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; + } + + 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 00000000..4c0f40d8 --- /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/vertex.cpp b/source/modules/graphics/vertex.cpp index e87bc210..3250f427 100644 --- a/source/modules/graphics/vertex.cpp +++ b/source/modules/graphics/vertex.cpp @@ -257,4 +257,27 @@ namespace love { 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_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 504408fa..387e51b2 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -1030,6 +1030,11 @@ int Wrap_Graphics::getFont(lua_State* L) return 1; } +int Wrap_Graphics::newMesh(lua_State* L) +{ + return 0; +} + int Wrap_Graphics::newTextBatch(lua_State* L) { luax_checkgraphicscreated(L); @@ -1645,6 +1650,8 @@ static constexpr luaL_Reg functions[] = { "newTexture", Wrap_Graphics::newTexture }, { "newImage", Wrap_Graphics::newImage }, + { "newMesh", Wrap_Graphics::newMesh }, + { "newTextBatch", Wrap_Graphics::newTextBatch }, { "newFont", Wrap_Graphics::newFont }, diff --git a/source/modules/graphics/wrap_Mesh.cpp b/source/modules/graphics/wrap_Mesh.cpp new file mode 100644 index 00000000..037b6ce3 --- /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 From afaf9c3590389cda089bcbc2a95fd25e364e6e4a Mon Sep 17 00:00:00 2001 From: TurtleP Date: Wed, 26 Jun 2024 15:59:40 -0400 Subject: [PATCH 16/49] Wii U compiles --- CMakeLists.txt | 23 +- include/driver/graphics/DrawCommand.hpp | 7 +- include/driver/graphics/StreamBuffer.tcc | 39 +- include/modules/font/Rasterizer.hpp | 1 + include/modules/font/freetype/Font.cpp | 1 - include/modules/font/freetype/Font.hpp | 59 ++ .../font/freetype/TrueTypeRasterizer.hpp | 35 +- include/modules/graphics/Font.hpp | 17 + include/modules/graphics/Font.tcc | 4 +- include/modules/graphics/Mesh.hpp | 2 + include/modules/graphics/Texture.tcc | 2 + include/modules/graphics/vertex.hpp | 18 + include/utility/logfile.hpp | 17 +- platform/cafe/CMakeLists.txt | 4 +- platform/cafe/include/driver/display/GX2.hpp | 193 ++++++ .../cafe/include/driver/display/Renderer.hpp | 109 ---- .../cafe/include/driver/display/Uniform.hpp | 26 + .../cafe/include/driver/display/utility.hpp | 2 + .../include/driver/graphics/StreamBuffer.hpp | 83 +++ .../include/modules/graphics/Graphics.hpp | 58 +- .../cafe/include/modules/graphics/Shader.hpp | 45 ++ .../cafe/include/modules/graphics/Texture.hpp | 46 ++ platform/cafe/source/boot.cpp | 10 +- platform/cafe/source/driver/display/GX2.cpp | 412 +++++++++++++ .../cafe/source/driver/display/Renderer.cpp | 293 --------- .../cafe/source/modules/graphics/Graphics.cpp | 350 ++++++++++- .../cafe/source/modules/graphics/Shader.cpp | 151 +++++ .../cafe/source/modules/graphics/Texture.cpp | 211 +++++++ .../include/driver/graphics/StreamBuffer.hpp | 23 +- .../ctr/include/modules/graphics/Buffer.hpp | 5 + .../ctr/include/modules/graphics/Texture.hpp | 2 + .../ctr/source/modules/graphics/Buffer.cpp | 100 ++- platform/ctr/source/modules/graphics/Font.cpp | 3 + .../ctr/source/modules/graphics/Texture.cpp | 5 + platform/hac/CMakeLists.txt | 2 +- .../driver/display/{Renderer.hpp => deko.hpp} | 29 +- .../include/driver/graphics/StreamBuffer.hpp | 42 ++ .../hac/include/modules/graphics/Graphics.hpp | 36 +- .../hac/include/modules/graphics/Shader.hpp | 54 ++ .../driver/display/{Renderer.cpp => deko.cpp} | 95 +-- .../hac/source/modules/graphics/Graphics.cpp | 220 ++++++- .../hac/source/modules/graphics/Shader.cpp | 177 ++++++ source/modules/font/freetype/Font.cpp | 75 +++ .../font/freetype/TrueTypeRasterizer.cpp | 233 +++++++ source/modules/font/wrap_Font.cpp | 6 +- source/modules/graphics/Font.cpp | 496 ++------------- source/modules/graphics/FontBase.cpp | 569 ++++++++++++++++++ source/modules/graphics/Graphics.cpp | 7 +- source/modules/graphics/Mesh.cpp | 8 + source/modules/graphics/TextBatch.cpp | 16 +- source/modules/graphics/wrap_Graphics.cpp | 101 +++- source/modules/image/magpie/KTXHandler.cpp | 2 +- source/modules/love/scripts/callbacks.lua | 18 +- 53 files changed, 3450 insertions(+), 1092 deletions(-) delete mode 100644 include/modules/font/freetype/Font.cpp create mode 100644 include/modules/font/freetype/Font.hpp create mode 100644 include/modules/graphics/Font.hpp create mode 100644 platform/cafe/include/driver/display/GX2.hpp delete mode 100644 platform/cafe/include/driver/display/Renderer.hpp create mode 100644 platform/cafe/include/driver/display/Uniform.hpp create mode 100644 platform/cafe/include/driver/graphics/StreamBuffer.hpp create mode 100644 platform/cafe/include/modules/graphics/Shader.hpp create mode 100644 platform/cafe/include/modules/graphics/Texture.hpp create mode 100644 platform/cafe/source/driver/display/GX2.cpp delete mode 100644 platform/cafe/source/driver/display/Renderer.cpp create mode 100644 platform/cafe/source/modules/graphics/Shader.cpp create mode 100644 platform/cafe/source/modules/graphics/Texture.cpp rename platform/hac/include/driver/display/{Renderer.hpp => deko.hpp} (90%) create mode 100644 platform/hac/include/driver/graphics/StreamBuffer.hpp create mode 100644 platform/hac/include/modules/graphics/Shader.hpp rename platform/hac/source/driver/display/{Renderer.cpp => deko.cpp} (67%) create mode 100644 platform/hac/source/modules/graphics/Shader.cpp create mode 100644 source/modules/font/freetype/Font.cpp create mode 100644 source/modules/font/freetype/TrueTypeRasterizer.cpp create mode 100644 source/modules/graphics/FontBase.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 196dd8e8..4eafd6ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,14 @@ if (NINTENDO_SWITCH) source/modules/image/magpie/PKMHandler.cpp source/modules/image/magpie/PNGHandler.cpp ) + + add_library(ddsparse + libraries/ddsparse/ddsparse.cpp + libraries/ddsparse/ddsparse.h + libraries/ddsparse/ddsinfo.h + ) + + target_link_libraries(${PROJECT_NAME} PRIVATE ddsparse) endif() if (NINTENDO_WIIU) @@ -155,7 +163,19 @@ if (NINTENDO_WIIU) 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/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) endif() add_custom_target(test @@ -316,6 +336,7 @@ include_directories( libraries/physfs libraries/wuff libraries/utf8 + libraries/ddsparse ) # find source -type f -name \*.cpp | clip @@ -365,7 +386,7 @@ 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/Font.cpp +source/modules/graphics/FontBase.cpp source/modules/graphics/Mesh.cpp source/modules/graphics/Polyline.cpp source/modules/graphics/wrap_Mesh.cpp diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp index 7f3ec330..d135550c 100644 --- a/include/driver/graphics/DrawCommand.hpp +++ b/include/driver/graphics/DrawCommand.hpp @@ -20,7 +20,8 @@ namespace love TextureBase* texture = nullptr; int vertexCount = 0; - bool isFont = false; + bool isFont = false; + bool pushTransform = true; }; struct DrawIndexedCommand @@ -89,7 +90,9 @@ namespace love MapInfo indexBufferMap = MapInfo(); bool flushing = false; - bool isFont = false; + + bool isFont = false; + bool pushTransform = true; }; constexpr size_t INIT_VERTEX_BUFFER_SIZE = sizeof(Vertex) * 4096 * 1; diff --git a/include/driver/graphics/StreamBuffer.tcc b/include/driver/graphics/StreamBuffer.tcc index 6a1706d5..188e0985 100644 --- a/include/driver/graphics/StreamBuffer.tcc +++ b/include/driver/graphics/StreamBuffer.tcc @@ -45,11 +45,6 @@ namespace love return this->bufferSize - this->frameGPUReadOffset; } - /* - ** Get the offset of the buffer that the GPU can read from. - ** This is the offset of the current frame. - ** @return The offset in bytes. - */ size_t getGPUReadOffset() const { return (this->frameIndex * this->bufferSize) + this->frameGPUReadOffset; @@ -60,31 +55,29 @@ namespace love return (this->frameIndex * this->bufferSize) + this->index; } - /* - ** Map the buffer for writing. - ** @return A pointer to the buffer. - */ - virtual MapInfo map(size_t) = 0; + // MapInfo map(size_t) = 0; - /* - ** Unmap the buffer. - ** @return Offset of the buffer that was written to. - */ - virtual size_t unmap(size_t) = 0; + virtual size_t unmap(size_t) + { + return this->index; + } - /* - ** Mark the buffer as used. - ** @param count The count of T of the buffer that was used. - */ - virtual void markUsed(int count) = 0; + void markUsed(int count) + { + this->index += count; + this->frameGPUReadOffset += (count * sizeof(T)); + } - void clear() - {} + void nextFrame() + { + this->index = 0; + this->frameGPUReadOffset = 0; + } /* ** Advance to the next frame. */ - virtual void nextFrame() = 0; + // virtual void nextFrame() = 0; protected: StreamBufferBase(BufferUsage usage, size_t size) : diff --git a/include/modules/font/Rasterizer.hpp b/include/modules/font/Rasterizer.hpp index 2e8d7a60..76e7736a 100644 --- a/include/modules/font/Rasterizer.hpp +++ b/include/modules/font/Rasterizer.hpp @@ -35,6 +35,7 @@ namespace love { Hinting hinting = HINTING_NORMAL; OptionalFloat dpiScale; + bool sdf = false; }; enum DataType diff --git a/include/modules/font/freetype/Font.cpp b/include/modules/font/freetype/Font.cpp deleted file mode 100644 index 411906b6..00000000 --- a/include/modules/font/freetype/Font.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "modules/font/Font.hpp" diff --git a/include/modules/font/freetype/Font.hpp b/include/modules/font/freetype/Font.hpp new file mode 100644 index 00000000..5582269b --- /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 index 3ec5fc7a..8d6a1254 100644 --- a/include/modules/font/freetype/TrueTypeRasterizer.hpp +++ b/include/modules/font/freetype/TrueTypeRasterizer.hpp @@ -17,8 +17,32 @@ namespace love TrueTypeRasterizer(FT_Library library, Data* data, int size, const Settings& settings, float defaultDPIScale); - virtual ~TrueTypeRasterizer() - {} + 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, @@ -28,5 +52,12 @@ namespace love { "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/graphics/Font.hpp b/include/modules/graphics/Font.hpp new file mode 100644 index 00000000..7231b224 --- /dev/null +++ b/include/modules/graphics/Font.hpp @@ -0,0 +1,17 @@ +#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/include/modules/graphics/Font.tcc b/include/modules/graphics/Font.tcc index 6e8d6a9d..f1b9bbc1 100644 --- a/include/modules/graphics/Font.tcc +++ b/include/modules/graphics/Font.tcc @@ -153,7 +153,7 @@ namespace love void printv(GraphicsBase* gfx, const Matrix4& t, const std::vector& drawcommands, const std::vector& vertices); - virtual const Glyph& addGlyph(TextShaper::GlyphIndex glyphindex) = 0; + virtual const Glyph& addGlyph(TextShaper::GlyphIndex glyphindex); StrongRef shaper; @@ -177,6 +177,6 @@ namespace love static constexpr int TEXTURE_PADDING = 2; - virtual void createTexture(); + virtual void createTexture() = 0; }; } // namespace love diff --git a/include/modules/graphics/Mesh.hpp b/include/modules/graphics/Mesh.hpp index 055381c0..a287e7f5 100644 --- a/include/modules/graphics/Mesh.hpp +++ b/include/modules/graphics/Mesh.hpp @@ -73,6 +73,8 @@ namespace love void draw(GraphicsBase* graphics, const Matrix4& matrix, int instanceCount); + // static std::vector getDefaultVertexFormat(); + private: friend class SpriteBatch; diff --git a/include/modules/graphics/Texture.tcc b/include/modules/graphics/Texture.tcc index ace02341..05cf5877 100644 --- a/include/modules/graphics/Texture.tcc +++ b/include/modules/graphics/Texture.tcc @@ -305,6 +305,8 @@ namespace love 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. diff --git a/include/modules/graphics/vertex.hpp b/include/modules/graphics/vertex.hpp index bf5f04a5..4b36766c 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -235,6 +235,12 @@ namespace love 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); + inline void DEBUG_VERTEX(const Vertex& v) { std::printf("Position: %.2f, %.2f\n", v.x, v.y); @@ -242,6 +248,11 @@ namespace love std::printf("Color: %.2f, %.2f, %.2f, %.2f\n", v.color.r, v.color.g, v.color.b, v.color.a); } + inline void DEBUG_VERTEX(const Vertex* v) + { + DEBUG_VERTEX(*v); + } + inline CommonFormat getSinglePositionFormat(bool is2D) { return is2D ? CommonFormat::XYf : CommonFormat::XYZf; @@ -423,5 +434,12 @@ namespace love { "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/utility/logfile.hpp b/include/utility/logfile.hpp index 8ccea9c8..2a90fc0d 100644 --- a/include/utility/logfile.hpp +++ b/include/utility/logfile.hpp @@ -19,7 +19,9 @@ class Log } ~Log() - {} + { + std::fclose(file); + } template void write(std::source_location location, std::format_string format, Args&&... args) @@ -34,17 +36,22 @@ class Log auto buffer = std::format(BUFFER_FORMAT, filename, line, column, output); - std::fwrite(buffer.c_str(), 1, buffer.size(), stdout); - std::fflush(stdout); + std::fwrite(buffer.c_str(), 1, buffer.size(), this->file); + std::fflush(this->file); } private: static constexpr const char* BUFFER_FORMAT = "{:s}({:d}:{:d}): {:s}\n"; + std::FILE* file; + + Log() + { + file = std::fopen("debug.log", "w"); + } }; #if __DEBUG__ == 0 #define LOG(...) #else - #define LOG(format, ...) \ - Log::getInstance().write(std::source_location::current(), format, ##__VA_ARGS__); + #define LOG(format, ...) Log::getInstance().write(std::source_location::current(), format, ##__VA_ARGS__); #endif diff --git a/platform/cafe/CMakeLists.txt b/platform/cafe/CMakeLists.txt index 7a9188c4..95625371 100644 --- a/platform/cafe/CMakeLists.txt +++ b/platform/cafe/CMakeLists.txt @@ -26,10 +26,12 @@ 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/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/cafe/include/driver/display/GX2.hpp b/platform/cafe/include/driver/display/GX2.hpp new file mode 100644 index 00000000..83062301 --- /dev/null +++ b/platform/cafe/include/driver/display/GX2.hpp @@ -0,0 +1,193 @@ +#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); + + // 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 + { + Uniform* transform; + + bool cullBack; + bool cullFront; + + GX2FrontFace winding; + bool depthWrite; + bool depthTest; + GX2CompareFunction compareMode; + + uint32_t writeMask; + + GX2ColorBuffer* boundFramebuffer = nullptr; + } context; + + bool inForeground; + + void* commandBuffer; + GX2ContextState* state; + }; + + 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 84e959c3..00000000 --- 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 00000000..8e311f9b --- /dev/null +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "common/Matrix.hpp" + +#include +#include + +namespace love +{ + static void updateGlmMatrix(glm::mat4& out, const glm::mat4& matrix) + { + unsigned int* destination = (unsigned int*)glm::value_ptr(out); + unsigned int* source = (unsigned int*)glm::value_ptr(matrix); + + const size_t count = sizeof(glm::mat4) / sizeof(unsigned int); + + for (size_t i = 0; i < count; i++) + destination[i] = __builtin_bswap32(source[i]); + } + + struct Uniform + { + glm::mat4 projection = glm::mat4(1.0f); + glm::mat4 modelView = glm::mat4(1.0f); + }; +} // namespace love diff --git a/platform/cafe/include/driver/display/utility.hpp b/platform/cafe/include/driver/display/utility.hpp index 264431af..afdb0a7c 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 00000000..f3bfaa1c --- /dev/null +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "driver/graphics/StreamBuffer.tcc" +#include "modules/graphics/Volatile.hpp" + +#include +#include + +namespace love +{ + template + class StreamBuffer final : public StreamBufferBase + { + public: + StreamBuffer(BufferUsage usage, size_t size) : + StreamBufferBase(usage, size), + buffer(new GX2RBuffer()) + { + this->buffer->elemCount = size / sizeof(T); + this->buffer->elemSize = sizeof(T); + this->buffer->flags = getBufferFlags(usage); + + if (!GX2RCreateBuffer(this->buffer)) + throw love::Exception("Failed to create StreamBuffer"); + + if (usage == BufferUsage::BUFFERUSAGE_VERTEX) + GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); + } + + MapInfo map(size_t) + { + MapInfo info {}; + + info.data = &((T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE))[this->index]; + info.size = this->bufferSize - this->frameGPUReadOffset; + + return info; + } + + size_t unmap(size_t) + { + GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + return this->index; + } + + void debug(std::function func) + { + T* data = (T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + func(data); + GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + } + + ~StreamBuffer() + { + GX2RDestroyBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + delete this->buffer; + } + + ptrdiff_t getHandle() const override + { + return (ptrdiff_t)this->buffer; + } + + private: + static constexpr auto BASE_FLAGS = + GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ; + + static GX2RResourceFlags getBufferFlags(BufferUsage usage) + { + switch (usage) + { + case BufferUsage::BUFFERUSAGE_VERTEX: + return BASE_FLAGS | GX2R_RESOURCE_BIND_VERTEX_BUFFER; + case BufferUsage::BUFFERUSAGE_INDEX: + return BASE_FLAGS | GX2R_RESOURCE_BIND_INDEX_BUFFER; + default: + throw love::Exception("Invalid buffer usage"); + } + } + + GX2RBuffer* buffer; + }; +} // namespace love diff --git a/platform/cafe/include/modules/graphics/Graphics.hpp b/platform/cafe/include/modules/graphics/Graphics.hpp index 4ead2d97..e4499feb 100644 --- a/platform/cafe/include/modules/graphics/Graphics.hpp +++ b/platform/cafe/include/modules/graphics/Graphics.hpp @@ -2,48 +2,66 @@ #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 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; - bool isActive() const; + virtual FontBase* newFont(Rasterizer* data) override; - void unsetModeImpl(); + virtual FontBase* newDefaultFont(int size, const Rasterizer::Settings& settings) override; - void setViewport(int x, int y, int width, int height); + 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; - bool is3D() const; + void draw(const DrawCommand& command) override; - void set3D(bool enable); + using GraphicsBase::draw; - bool isWide() const; + bool isActive() const; + + virtual void unsetMode() override; + + void setActiveScreen() override; + + void setViewport(int x, int y, int width, int height); - void setWide(bool enable); + GX2ColorBuffer getInternalBackbuffer() const; - 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 00000000..99abdd89 --- /dev/null +++ b/platform/cafe/include/modules/graphics/Shader.hpp @@ -0,0 +1,45 @@ +#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 + { + GX2UniformBlock* block; + std::string name; + }; + + Shader(StandardShader shader); + + virtual ~Shader(); + + bool loadVolatile() override; + + void unloadVolatile() override; + + void attach() override; + + ptrdiff_t getHandle() const override; + + void updateBuiltinUniforms(GraphicsBase* graphics, Uniform* transform); + + 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; + }; +} // 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 00000000..e1440e5b --- /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(void* data) override + {} + + private: + void createTexture(); + + Slices slices; + + GX2Texture* texture = nullptr; + GX2ColorBuffer* target = nullptr; + GX2Sampler* sampler = nullptr; + }; +} // namespace love diff --git a/platform/cafe/source/boot.cpp b/platform/cafe/source/boot.cpp index 9e723f2a..0222b51d 100644 --- a/platform/cafe/source/boot.cpp +++ b/platform/cafe/source/boot.cpp @@ -25,14 +25,14 @@ 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), []() { } } + // { "bsp", BIND(bspInitializeShimInterface), []() { } } }}; // clang-format on @@ -62,14 +62,14 @@ namespace love } } - return std::string {}; + return "fs:/vol/external01/lovepotion.wuhb"; } int preInit() { /* we aren't running Aroma */ - if (getApplicationPath().empty()) - return -1; + // if (getApplicationPath().empty()) + // return -1; for (const auto& service : services) { diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp new file mode 100644 index 00000000..016be197 --- /dev/null +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -0,0 +1,412 @@ +#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 + +#include "utility/logfile.hpp" + +namespace love +{ +#define Keyboard() (Module::getInstance(Module::M_KEYBOARD)) + + GX2::GX2() : context {}, inForeground(false), commandBuffer(nullptr), state(nullptr), targets {} + {} + + GX2::~GX2() + { + if (this->inForeground) + this->onForegroundReleased(); + + GX2Shutdown(); + + 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() + { + 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->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->context.transform = (Uniform*)memalign(0x100, sizeof(Uniform)); + updateGlmMatrix(this->context.transform->modelView, 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) + { + Framebuffer framebuffer {}; + framebuffer.create(info[index]); + + this->targets[index] = std::move(framebuffer); + } + } + + 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() + { + if (!this->inFrame) + { + GX2SetContextState(this->state); + this->inFrame = true; + } + } + + void GX2::clear(const Color& color) + { + 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->setViewport({ 0, 0, target->surface.width, target->surface.height }); + } + } + + void GX2::setSamplerState(TextureBase* texture, const SamplerState& state) + { + auto sampler = (GX2Sampler*)texture->getSamplerHandle(); + + 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*)ShaderBase::current != nullptr) + { + auto* shader = (Shader*)ShaderBase::current; + shader->updateBuiltinUniforms(graphics, this->context.transform); + } + } + + 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(); + + 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, location); + GX2SetPixelSampler(sampler, location); + } + + void GX2::present() + { + if (this->inFrame) + { + Graphics::flushBatchedDrawsGlobal(); + + GX2DrawDone(); + + Graphics::advanceStreamBuffersGlobal(); + this->inFrame = false; + } + + if (Keyboard()->hasTextInput()) + { + nn::swkbd::DrawDRC(); + GX2SetContextState(this->state); + } + + for (auto& target : this->targets) + target.copyScanBuffer(); + + GX2SwapScanBuffers(); + GX2Flush(); + GX2WaitForFlip(); + } + + void GX2::setViewport(const Rect& viewport) + { + bool isEmptyViewport = viewport == Rect::EMPTY; + + const int width = isEmptyViewport ? this->context.boundFramebuffer->surface.width : viewport.w; + const int height = isEmptyViewport ? this->context.boundFramebuffer->surface.height : viewport.h; + + const int x = isEmptyViewport ? 0 : viewport.x; + const int y = isEmptyViewport ? 0 : viewport.y; + + GX2SetViewport(x, y, width, height, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); + + auto ortho = glm::ortho(x, width, height, y, (int)Framebuffer::Z_NEAR, (int)Framebuffer::Z_FAR); + updateGlmMatrix(this->context.transform->projection, ortho); + + this->context.viewport = viewport; + } + + void GX2::setScissor(const Rect& scissor) + { + bool isEmptyScissor = scissor == Rect::EMPTY; + + const int x = isEmptyScissor ? 0 : scissor.x; + const int y = isEmptyScissor ? 0 : scissor.y; + + const int width = isEmptyScissor ? this->context.boundFramebuffer->surface.width : scissor.w; + const int height = isEmptyScissor ? this->context.boundFramebuffer->surface.height : scissor.h; + + GX2SetScissor(x, y, width, height); + 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(this->context.winding, 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 25a23263..00000000 --- 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/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index 950206b0..0adbc376 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -1,11 +1,18 @@ -#include "driver/display/Renderer.hpp" +#include "driver/display/GX2.hpp" #include "modules/graphics/Graphics.hpp" #include "modules/window/Window.hpp" +#include "modules/graphics/Font.hpp" +#include "modules/graphics/Shader.hpp" +#include "modules/graphics/Texture.hpp" + +#include +#include + namespace love { - Graphics::Graphics() : GraphicsBase("love.graphics.citro3d") + Graphics::Graphics() : GraphicsBase("love.graphics.gx2") { auto* window = Module::getInstance(M_WINDOW); @@ -24,60 +31,332 @@ namespace love } } - void Graphics::clearImpl(OptionalColor color, OptionalInt depth, OptionalDouble stencil) + Graphics::~Graphics() + {} + + void Graphics::initCapabilities() { - Renderer::getInstance().bindFramebuffer(); + // 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) + { + 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); } - if (stencil.hasValue && depth.hasValue) - Renderer::getInstance().clearDepthStencil(stencil.value, 0xFF, depth.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); + } + + gx2.bindFramebuffer(&gx2.getInternalBackbuffer()); + } + + 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; + + // this->cpuProcessingTime = C3D_GetProcessingTime(); + // this->gpuDrawingTime = C3D_GetDrawingTime(); + } + + 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; + + gx2.setScissor(rectangle); + + state.scissor = true; + state.scissorRect = scissor; + } + + void Graphics::setScissor() + { + if (this->states.back().scissor) + this->flushBatchedDraws(); + + this->states.back().scissor = false; + gx2.setScissor(Rect::EMPTY); + } + + void Graphics::setFrontFaceWinding(Winding 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; // ??? + + gx2.setVertexWinding(winding); } - void Graphics::presentImpl() + void Graphics::setColorMask(ColorChannelMask mask) { - Renderer::getInstance().present(); + this->flushBatchedDraws(); + + gx2.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) + gx2.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; + + GX2SurfaceFormat color; + bool supported = GX2::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) + gx2.bindFramebuffer(&gx2.getInternalBackbuffer()); + else + gx2.bindFramebuffer((GX2ColorBuffer*)targets.getFirstTarget().texture->getRenderTargetHandle()); + + bool tilt = isWindow ? true : false; + gx2.setViewport({ 0, 0, pixelWidth, pixelHeight }); + + if (state.scissor) + gx2.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(); + 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 < 1; 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 +366,25 @@ 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); + + auto* buffer = (GX2RBuffer*)command.indexBuffer->getHandle(); + const size_t offset = command.indexBufferOffset; + + const auto mode = GX2::getPrimitiveType(command.primitiveType); + const auto indexType = GX2::getIndexType(command.indexType); + + GX2RDrawIndexed(mode, buffer, indexType, command.indexCount, offset, 0, command.instanceCount); + + ++this->drawCalls; } + + void Graphics::draw(const DrawCommand& command) + {} } // 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 00000000..5287fa89 --- /dev/null +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -0,0 +1,151 @@ +#include "modules/graphics/Shader.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") + +#include "utility/logfile.hpp" + +namespace love +{ + Shader::Shader(StandardShader type) + { + std::string error; + + switch (type) + { + 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; + } + } + + // 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 + + WHBGfxInitFetchShader(&this->program); + } + + Shader::~Shader() + { + this->unloadVolatile(); + } + + bool Shader::loadVolatile() + { + return true; + } + + void Shader::unloadVolatile() + { + WHBGfxFreeShaderGroup(&this->program); + } + + uint32_t Shader::getPixelSamplerLocation(int index) + { + size_t count = this->program.pixelShader->samplerVarCount; + + if (index > count) + throw love::Exception("Invalid sampler index"); + + return this->program.pixelShader->samplerVars[index].location; + } + + void Shader::updateBuiltinUniforms(GraphicsBase* graphics, Uniform* transform) + { + if (current != this) + return; + + GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, (void*)transform, sizeof(Uniform)); + GX2SetVertexUniformBlock(1, sizeof(Uniform), (const void*)transform); + } + + 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; + } + } + + bool Shader::validate(const char* filepath, std::string& error) + { + FILE* file = std::fopen(filepath, "r"); + + 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 00000000..afd1fdda --- /dev/null +++ b/platform/cafe/source/modules/graphics/Texture.cpp @@ -0,0 +1,211 @@ +#include "driver/display/GX2.hpp" + +#include "modules/graphics/Texture.hpp" + +#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, 0, sizeof(GX2Texture)); + + 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 = 1; + 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(nullptr) + { + 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) + delete this->texture; + + if (this->target != nullptr) + delete this->texture; + + 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++) + { + } + } + } + + 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 < rect.h; y++) + { + const auto row = (y * rect.w * pixelSize); + const auto dest = (rect.x + (y + rect.y) * pitch) * pixelSize; + + std::memcpy(destination + dest, source + row, rect.w * pixelSize); + } + + const auto imageSize = this->texture->surface.imageSize; + GX2Invalidate(GX2_INVALIDATE_MODE_CPU_TEXTURE, destination, imageSize); + } + + 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)(&this->sampler); + } +} // namespace love diff --git a/platform/ctr/include/driver/graphics/StreamBuffer.hpp b/platform/ctr/include/driver/graphics/StreamBuffer.hpp index ce5bb783..ee92a7a9 100644 --- a/platform/ctr/include/driver/graphics/StreamBuffer.hpp +++ b/platform/ctr/include/driver/graphics/StreamBuffer.hpp @@ -28,12 +28,7 @@ namespace love C3D_SetBufInfo(&this->buffer); } - ~StreamBuffer() - { - linearFree(this->data); - } - - MapInfo map(size_t) override + MapInfo map(size_t) { MapInfo info {}; info.data = &this->data[this->index]; @@ -42,21 +37,9 @@ namespace love return info; } - size_t unmap(size_t) override - { - return this->index; - } - - void markUsed(int count) override - { - this->index += count; - this->frameGPUReadOffset += (count * sizeof(T)); - } - - void nextFrame() override + ~StreamBuffer() { - this->index = 0; - this->frameGPUReadOffset = 0; + linearFree(this->data); } ptrdiff_t getHandle() const override diff --git a/platform/ctr/include/modules/graphics/Buffer.hpp b/platform/ctr/include/modules/graphics/Buffer.hpp index 81607f10..ea53d3dc 100644 --- a/platform/ctr/include/modules/graphics/Buffer.hpp +++ b/platform/ctr/include/modules/graphics/Buffer.hpp @@ -5,6 +5,8 @@ #include "modules/graphics/Buffer.tcc" #include "modules/graphics/Volatile.hpp" +#include + namespace love { class Buffer : public BufferBase, public Volatile @@ -49,5 +51,8 @@ namespace love bool ownsMemoryMap = false; Range mappedRange; + + C3D_BufInfo* buffer; + C3D_Tex* texture; }; } // namespace love diff --git a/platform/ctr/include/modules/graphics/Texture.hpp b/platform/ctr/include/modules/graphics/Texture.hpp index ab39375c..f519b1a0 100644 --- a/platform/ctr/include/modules/graphics/Texture.hpp +++ b/platform/ctr/include/modules/graphics/Texture.hpp @@ -22,6 +22,8 @@ namespace love ptrdiff_t getRenderTargetHandle() const override; + ptrdiff_t getSamplerHandle() const override; + void updateQuad(Quad* quad); void setSamplerState(const SamplerState& state) override; diff --git a/platform/ctr/source/modules/graphics/Buffer.cpp b/platform/ctr/source/modules/graphics/Buffer.cpp index 8a470628..37ab8c59 100644 --- a/platform/ctr/source/modules/graphics/Buffer.cpp +++ b/platform/ctr/source/modules/graphics/Buffer.cpp @@ -1,18 +1,23 @@ #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) + BufferBase(graphics, settings, format, size, arraylength), { this->size = this->getSize(); arraylength = this->getArrayLength(); - if (usageFlags & BUFFERUSAGE_VERTEX) - mapUsage = BUFFERUSAGE_VERTEX; - else if (usageFlags & BUFFERUSAGE_INDEX) + if (usageFlags & BUFFERUSAGEFLAG_VERTEX) + { + mapUsage = BUFFERUSAGE_VERTEX; + this->buffer = new C3D_BufInfo(); + } + else if (usageFlags & BUFFERUSAGEFLAG_INDEX) mapUsage = BUFFERUSAGE_INDEX; if (dataUsage == BUFFERDATAUSAGE_STREAM) @@ -36,4 +41,91 @@ namespace love 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 index b4e7f228..dfb8459d 100644 --- a/platform/ctr/source/modules/graphics/Font.cpp +++ b/platform/ctr/source/modules/graphics/Font.cpp @@ -22,6 +22,9 @@ namespace love 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); diff --git a/platform/ctr/source/modules/graphics/Texture.cpp b/platform/ctr/source/modules/graphics/Texture.cpp index 0394013f..b0496af7 100644 --- a/platform/ctr/source/modules/graphics/Texture.cpp +++ b/platform/ctr/source/modules/graphics/Texture.cpp @@ -320,4 +320,9 @@ namespace love { return (ptrdiff_t)this->target; } + + ptrdiff_t Texture::getSamplerHandle() const + { + return 0; + } } // namespace love diff --git a/platform/hac/CMakeLists.txt b/platform/hac/CMakeLists.txt index 4901221d..938d8570 100644 --- a/platform/hac/CMakeLists.txt +++ b/platform/hac/CMakeLists.txt @@ -34,7 +34,7 @@ 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/deko3d.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp source/modules/graphics/Graphics.cpp diff --git a/platform/hac/include/driver/display/Renderer.hpp b/platform/hac/include/driver/display/deko.hpp similarity index 90% rename from platform/hac/include/driver/display/Renderer.hpp rename to platform/hac/include/driver/display/deko.hpp index 43bb4a38..b293f1ee 100644 --- a/platform/hac/include/driver/display/Renderer.hpp +++ b/platform/hac/include/driver/display/deko.hpp @@ -10,6 +10,7 @@ #include "driver/display/deko3d/CCmdVtxRing.h" #include "driver/display/deko3d/CDescriptorSet.h" +#include "modules/graphics/Shader.hpp" #include "modules/graphics/vertex.hpp" /* Enforces GLSL std140/std430 alignment rules for glm types */ @@ -23,7 +24,7 @@ namespace love { - class Renderer : public RendererBase + class deko3d : public RendererBase { public: enum MemoryPool @@ -39,17 +40,19 @@ namespace love QUEUE_TYPE_IMAGES }; - Renderer(); + deko3d(); void initialize(); - ~Renderer(); + ~deko3d(); void clear(const Color& color); void clearDepthStencil(int stencil, uint8_t mask, double depth); - void bindFramebuffer(); + dk::Image& getInternalBackbuffer(); + + void bindFramebuffer(dk::Image& target); void present(); @@ -65,6 +68,10 @@ namespace love void setVertexWinding(Winding winding); + void prepareDraw(GraphicsBase* graphics) override; + + void useProgram(const Shader::Program& program); + void onModeChanged() { this->destroyFramebuffers(); @@ -85,6 +92,11 @@ namespace love } } + dk::Device& getDevice() + { + return this->device; + } + dk::Queue& getQueue(QueueType type) { switch (type) @@ -137,8 +149,7 @@ namespace love 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 CPU_USE_FLAGS = (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached); static constexpr int SHADER_POOL_SIZE = 0x20000; static constexpr int SHADER_USE_FLAGS = @@ -161,6 +172,8 @@ namespace love dk::RasterizerState rasterizer; dk::ColorWriteState colorWrite; dk::BlendState blend; + + dk::Image* boundFramebuffer; } context; struct Transform @@ -171,7 +184,7 @@ namespace love static constexpr auto TRANSFORM_SIZE = sizeof(Transform); - CMemPool::Handle unformBuffer; + CMemPool::Handle uniformBuffer; dk::UniqueDevice device; @@ -193,4 +206,6 @@ namespace love CCmdMemRing<2> commands; std::array targets; }; + + extern deko3d d3d; } // 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 00000000..783865f9 --- /dev/null +++ b/platform/hac/include/driver/graphics/StreamBuffer.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "driver/display/deko.hpp" +#include "driver/graphics/StreamBuffer.tcc" + +namespace love +{ + template + class StreamBuffer final : public StreamBufferBase + { + public: + StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size) + { + size_t align = (size + DK_CMDMEM_ALIGNMENT - 1) & ~(DK_CMDMEM_ALIGNMENT - 1); + + this->memory = dk::MemBlockMaker { d3d.getDevice(), align } + .setFlags(DkMemBlockFlags_GpuCached | DkMemBlockFlags_CpuUncached) + .create(); + } + + ~StreamBuffer() + {} + + MapInfo StreamBuffer::map(size_t) override + { + MapInfo info {}; + info.data = &this->memory.getCpuAddr()[this->index]; + info.size = this->bufferSize - this->frameGPUReadOffset; + + return info; + } + + ptrdiff_t getHandle() const override + { + return 0; + } + + private: + dk::UniqueMemBlock 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 e9d847b0..9b370e3a 100644 --- a/platform/hac/include/modules/graphics/Graphics.hpp +++ b/platform/hac/include/modules/graphics/Graphics.hpp @@ -4,45 +4,43 @@ 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 clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) override; - using GraphicsBase::clear; + virtual void clear(const std::vector& colors, OptionalInt stencil, + OptionalDouble depth) override; - void presentImpl(); + void present(void* screenshotCallbackData) override; - void setScissorImpl(const Rect& scissor); + void setScissor(const Rect& scissor) override; - void setScissorImpl(); + void setScissor() override; - void setFrontFaceWindingImpl(Winding winding); + void setFrontFaceWinding(Winding winding) override; - void setColorMaskImpl(ColorChannelMask mask); + void setColorMask(ColorChannelMask mask) override; - void setBlendStateImpl(const BlendState& state); + void setBlendState(const BlendState& state) override; - void getRendererInfoImpl(RendererInfo& info) const; - - bool setModeImpl(int width, int height, int pixelWidth, int pixelHeight, - bool backBufferStencil, bool backBufferDepth, int msaa); + bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa) override; bool isActive() const; - void unsetModeImpl(); + void unsetMode() override; 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 00000000..a94752f0 --- /dev/null +++ b/platform/hac/include/modules/graphics/Shader.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "modules/graphics/Shader.tcc" +#include "modules/graphics/Volatile.hpp" + +#include "driver/display/deko3d/CMemPool.h" + +#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(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/source/driver/display/Renderer.cpp b/platform/hac/source/driver/display/deko.cpp similarity index 67% rename from platform/hac/source/driver/display/Renderer.cpp rename to platform/hac/source/driver/display/deko.cpp index 4afd9da9..f3cdbcac 100644 --- a/platform/hac/source/driver/display/Renderer.cpp +++ b/platform/hac/source/driver/display/deko.cpp @@ -1,8 +1,8 @@ -#include "driver/display/Renderer.hpp" +#include "driver/display/deko.hpp" namespace love { - Renderer::Renderer() : + deko3d::deko3d() : transform {}, device(dk::DeviceMaker {}.setFlags(DkDeviceFlags_DepthMinusOneToOne).create()), mainQueue(dk::QueueMaker { this->device }.setFlags(DkQueueFlags_Graphics).create()), @@ -13,14 +13,16 @@ namespace love data(CMemPool(this->device, CPU_USE_FLAGS, CPU_POOL_SIZE)), code(CMemPool(this->device, SHADER_USE_FLAGS, SHADER_POOL_SIZE)), framebufferSlot(-1) - {} + { + std::memset(&this->context, 0, sizeof(this->context)); + } - void Renderer::initialize() + void deko3d::initialize() { if (this->initialized) return; - this->unformBuffer = this->data.allocate(TRANSFORM_SIZE, DK_UNIFORM_BUF_ALIGNMENT); + this->uniformBuffer = this->data.allocate(TRANSFORM_SIZE, DK_UNIFORM_BUF_ALIGNMENT); this->transform.modelView = glm::mat4(1.0f); this->commands.allocate(this->data, COMMAND_SIZE); @@ -29,13 +31,13 @@ namespace love this->initialized = true; } - Renderer::~Renderer() + deko3d::~deko3d() { this->destroyFramebuffers(); - this->unformBuffer.destroy(); + this->uniformBuffer.destroy(); } - void Renderer::createFramebuffers() + void deko3d::createFramebuffers() { const auto& info = getScreenInfo()[0]; @@ -47,8 +49,7 @@ namespace love this->targets[index] = &this->framebuffers[index].getImage(); } - this->swapchain = - dk::SwapchainMaker { this->device, nwindowGetDefault(), this->targets }.create(); + 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 }; @@ -57,7 +58,7 @@ namespace love this->setScissor(this->context.scissor); } - void Renderer::destroyFramebuffers() + void deko3d::destroyFramebuffers() { if (!this->swapchain) return; @@ -74,7 +75,7 @@ namespace love this->depthbuffer.destroy(); } - void Renderer::ensureInFrame() + void deko3d::ensureInFrame() { if (!this->inFrame) { @@ -83,33 +84,53 @@ namespace love } } - void Renderer::clear(const Color& color) + void deko3d::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) + void deko3d::clearDepthStencil(int depth, uint8_t mask, double stencil) { this->commandBuffer.clearDepthStencil(true, depth, mask, stencil); } - void Renderer::bindFramebuffer() + dk::Image& deko3d::getInternalBackbuffer() + { + return this->framebuffers[this->framebufferSlot].getImage(); + } + + void deko3d::useProgram(const Shader::Program& program) + { + // clang-format off + this->commandBuffer.bindShaders(DkStageFlag_GraphicsMask, { &program.vertex.shader, &program.fragment.shader }); + this->commandBuffer.bindUniformBuffer(DkStage_Vertex, 0, this->uniformBuffer.getGpuAddr(), this->uniformBuffer.getSize()); + // clang-format off + } + + void deko3d::bindFramebuffer(dk::Image& framebuffer) { if (!this->swapchain) return; this->ensureInFrame(); + bool bindingModified = false; - if (this->framebufferSlot < 0) - this->framebufferSlot = this->mainQueue.acquireImage(this->swapchain); + if (this->context.boundFramebuffer != &framebuffer) + { + bindingModified = true; + this->context.boundFramebuffer = &framebuffer; + } - dk::ImageView target { this->framebuffers[this->framebufferSlot].getImage() }; - dk::ImageView depth { this->depthbuffer.getImage() }; + if (bindingModified) + { + dk::ImageView depth { this->depthbuffer.getImage() }; + dk::ImageView target { framebuffer }; - this->commandBuffer.bindRenderTargets(&target, &depth); + this->commandBuffer.bindRenderTargets(&target, &depth); + } } - void Renderer::present() + void deko3d::present() { if (!this->swapchain) return; @@ -122,28 +143,28 @@ namespace love this->inFrame = false; } - this->framebufferSlot = -1; + this->framebufferSlot = this->mainQueue.acquireImage(this->swapchain); } - void Renderer::setVertexWinding(Winding winding) + void deko3d::setVertexWinding(Winding winding) { DkFrontFace face; - if (!Renderer::getConstant(winding, face)) + if (!deko3d::getConstant(winding, face)) return; this->context.rasterizer.setFrontFace(face); } - void Renderer::setCullMode(CullMode mode) + void deko3d::setCullMode(CullMode mode) { DkFace cullMode; - if (!Renderer::getConstant(mode, cullMode)) + if (!deko3d::getConstant(mode, cullMode)) return; this->context.rasterizer.setCullMode(cullMode); } - void Renderer::setColorMask(ColorChannelMask mask) + void deko3d::setColorMask(ColorChannelMask mask) { const auto red = (DkColorMask_R * mask.r); const auto green = (DkColorMask_G * mask.g); @@ -153,33 +174,33 @@ namespace love this->context.colorWrite.setMask(0, (red + green + blue + alpha)); } - void Renderer::setBlendState(const BlendState& state) + void deko3d::setBlendState(const BlendState& state) { if (this->context.blendState == state) return; DkBlendOp operationRGB; - if (!Renderer::getConstant(state.operationRGB, operationRGB)) + if (!deko3d::getConstant(state.operationRGB, operationRGB)) return; DkBlendOp operationA; - if (!Renderer::getConstant(state.operationA, operationA)) + if (!deko3d::getConstant(state.operationA, operationA)) return; DkBlendFactor sourceColor; - if (!Renderer::getConstant(state.srcFactorRGB, sourceColor)) + if (!deko3d::getConstant(state.srcFactorRGB, sourceColor)) return; DkBlendFactor destColor; - if (!Renderer::getConstant(state.dstFactorRGB, destColor)) + if (!deko3d::getConstant(state.dstFactorRGB, destColor)) return; DkBlendFactor sourceAlpha; - if (!Renderer::getConstant(state.srcFactorA, sourceAlpha)) + if (!deko3d::getConstant(state.srcFactorA, sourceAlpha)) return; DkBlendFactor destAlpha; - if (!Renderer::getConstant(state.dstFactorA, destAlpha)) + if (!deko3d::getConstant(state.dstFactorA, destAlpha)) return; this->context.blend.setColorBlendOp(operationRGB); @@ -205,7 +226,7 @@ namespace love return scissor; } - void Renderer::setScissor(const Rect& scissor) + void deko3d::setScissor(const Rect& scissor) { this->ensureInFrame(); DkScissor _scissor {}; @@ -232,7 +253,7 @@ namespace love return viewport; } - void Renderer::setViewport(const Rect& viewport) + void deko3d::setViewport(const Rect& viewport) { this->ensureInFrame(); DkViewport _viewport {}; @@ -244,4 +265,6 @@ namespace love this->commandBuffer.setViewports(0, { _viewport }); } + + deko3d d3d; } // namespace love diff --git a/platform/hac/source/modules/graphics/Graphics.cpp b/platform/hac/source/modules/graphics/Graphics.cpp index 706bf89b..8ca4b5f9 100644 --- a/platform/hac/source/modules/graphics/Graphics.cpp +++ b/platform/hac/source/modules/graphics/Graphics.cpp @@ -1,8 +1,10 @@ -#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" + namespace love { Graphics::Graphics() : @@ -28,11 +30,11 @@ namespace love } } - void Graphics::backbufferChanged(int width, int height, int pixelWidth, int pixelHeight, - bool stencil, bool depth, int msaa) + 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; + 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 +51,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 +62,221 @@ 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; + } + } + } + + 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); } - if (stencil.hasValue && depth.hasValue) - Renderer::getInstance().clearDepthStencil(stencil.value, 0xFF, depth.value); + d3d.bindFramebuffer(d3d.getInternalBackbuffer()); } - void Graphics::presentImpl() + void Graphics::clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) { - Renderer::getInstance().present(); + 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); + d3d.clear(value); + } + + d3d.bindFramebuffer(d3d.getInternalBackbuffer()); } - void Graphics::setScissorImpl(const Rect& scissor) + void Graphics::present(void* screenshotCallback) { - Renderer::getInstance().setScissor(scissor); + 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::setScissorImpl() + void Graphics::setScissor(const Rect& scissor) { - Renderer::getInstance().setScissor(Rect::EMPTY); + 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::setFrontFaceWindingImpl(Winding winding) + void Graphics::setScissor() { - Renderer::getInstance().setVertexWinding(winding); + if (this->states.back().scissor) + this->flushBatchedDraws(); + + this->states.back().scissor = false; + d3d.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; // ??? + + d3d.setVertexWinding(winding); } - void Graphics::setBlendStateImpl(const BlendState& state) + void Graphics::setColorMask(ColorChannelMask mask) { - Renderer::getInstance().setBlendState(state); + 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; } - 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(); + + // d3d.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 < 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; - this->backbufferChanged(width, height, pixelWidth, pixelHeight, backBufferStencil, - backBufferDepth, msaa); + this->backbufferChanged(width, height, pixelWidth, pixelHeight, backBufferStencil, backBufferDepth, + msaa); return true; } - void Graphics::unsetModeImpl() + void Graphics::unsetMode() {} bool Graphics::isActive() const diff --git a/platform/hac/source/modules/graphics/Shader.cpp b/platform/hac/source/modules/graphics/Shader.cpp new file mode 100644 index 00000000..6df02576 --- /dev/null +++ b/platform/hac/source/modules/graphics/Shader.cpp @@ -0,0 +1,177 @@ +#include "modules/graphics/Shader.hpp" +#include "driver/display/deko.hpp" + +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; + }; + +#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") + + Shader::Shader(StandardShader 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.c_str()); + + error.clear(); + + if (!this->validate(this->program.fragment, fragmentStage, error)) + throw love::Exception("Invalid fragment shader: %s", error.c_str()); + } + + void Shader::attach() + { + if (Shader::current != this) + { + Shader::current = this; + d3d.useProgram(this->program); + ++shaderSwitches; + } + } + + bool Shader::loadVolatile() + { + return true; + } + + void Shader::unloadVolatile() + {} + + 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/source/modules/font/freetype/Font.cpp b/source/modules/font/freetype/Font.cpp new file mode 100644 index 00000000..4149f873 --- /dev/null +++ b/source/modules/font/freetype/Font.cpp @@ -0,0 +1,75 @@ +#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) + { + std::unique_ptr data; + plGetSharedFontByType(data.get(), type); + + std::string_view name {}; + FontModule::getConstant(type, name); + + if (data == 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 00000000..0071f3e4 --- /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 index 24db61a6..c15eb70e 100644 --- a/source/modules/font/wrap_Font.cpp +++ b/source/modules/font/wrap_Font.cpp @@ -1,6 +1,10 @@ #include "modules/font/wrap_Font.hpp" -#include "modules/font/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" diff --git a/source/modules/graphics/Font.cpp b/source/modules/graphics/Font.cpp index 8747ccb4..bce6d39c 100644 --- a/source/modules/graphics/Font.cpp +++ b/source/modules/graphics/Font.cpp @@ -1,102 +1,14 @@ -#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 +#include "modules/graphics/Font.hpp" +#include "modules/graphics/Graphics.hpp" 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) + Font::Font(Rasterizer* rasterizer, const SamplerState& samplerState) : FontBase(rasterizer, samplerState) { - 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; + this->loadVolatile(); } - 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(); - } - - void FontBase::createTexture() + void Font::createTexture() { auto* graphics = Module::getInstance(Module::M_GRAPHICS); graphics->flushBatchedDraws(); @@ -113,394 +25,76 @@ namespace love size = nextSize; this->textures.pop_back(); } - } - - 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); - } - - 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); + TextureBase::Settings settings {}; + settings.format = pixelFormat; + settings.width = size.width; + settings.height = size.height; - return commands; - } + texture = graphics->newTexture(settings, nullptr); + texture->setSamplerState(samplerState); - 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]; + auto dataSize = getPixelFormatSliceSize(pixelFormat, size.width, size.height); + auto pixelCount = size.width * size.height; - 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); + std::vector empty(dataSize, 0); - switch (align) + if (this->shaper->getRasterizers()[0]->getDataType() == Rasterizer::DATA_TRUETYPE) { - case ALIGN_RIGHT: - offset.x = floorf(wrap - width); - break; - case ALIGN_CENTER: - offset.x = floorf((wrap - width) / 2.0f); - break; - case ALIGN_JUSTIFY: + if (this->pixelFormat == PIXELFORMAT_LA8_UNORM) { - 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; + for (size_t i = 0; i < pixelCount; i++) + empty[i * 2 + 0] = 255; } - 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()) + else if (this->pixelFormat == PIXELFORMAT_RGBA8_UNORM) { - auto prevcmd = drawcommands.back(); - if (prevcmd.texture == firstcmd->texture && - (prevcmd.startVertex + prevcmd.vertexCount) == firstcmd->startVertex) + for (size_t i = 0; i < pixelCount; i++) { - drawcommands.back().vertexCount += firstcmd->vertexCount; - ++firstcmd; + empty[i * 4 + 0] = 255; + empty[i * 4 + 1] = 255; + empty[i * 4 + 2] = 255; } } - - // 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); + Rect rect = { 0, 0, size.width, size.height }; + texture->replacePixels(empty.data(), empty.size(), 0, 0, rect, false); } - return drawcommands; - } - - void FontBase::printv(GraphicsBase* graphics, const Matrix4& matrix, - const std::vector& drawcommands, - const std::vector& vertices) - { - if (vertices.empty() || drawcommands.empty()) - return; + textures.emplace_back(texture); + this->textureWidth = size.width; + this->textureHeight = size.height; - Matrix4 m(graphics->getTransform(), matrix); + this->rowHeight = this->textureX = this->textureY = TEXTURE_PADDING; - for (const DrawCommand& cmd : drawcommands) + if (recreateTexture) { - BatchedDrawCommand command {}; - command.format = CommonFormat::XYf_STf_RGBAf; - command.indexMode = TRIANGLEINDEX_QUADS; - command.vertexCount = cmd.vertexCount; - command.texture = cmd.texture; - command.isFont = true; - - 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); - } + this->textureCacheID++; + std::vector glyphsToAdd; - bool FontBase::hasGlyphs(const std::string& text) const - { - return this->shaper->hasGlyphs(text); - } + for (const auto& glyph : this->glyphs) + glyphsToAdd.push_back(unpackGlyphIndex(glyph.first)); - float FontBase::getDPIScale() const - { - return this->dpiScale; - } + this->glyphs.clear(); - uint32_t FontBase::getTextureCacheID() const - { - return this->textureCacheID; + for (const auto& glyphIndex : glyphsToAdd) + this->addGlyph(glyphIndex); + } } - void FontBase::setFallbacks(const std::vector& fallbacks) + bool Font::loadVolatile() { - std::vector rasterizers {}; - - for (const FontBase* font : fallbacks) - rasterizers.push_back(font->shaper->getRasterizers()[0]); - - this->shaper->setFallbacks(rasterizers); + this->textureCacheID++; this->glyphs.clear(); + this->textures.clear(); + this->createTexture(); - if constexpr (!Console::is(Console::CTR)) - { - this->textureCacheID++; - - while (this->textures.size() > 1) - this->textures.pop_back(); - } + return true; } - void FontBase::setSamplerState(const SamplerState& state) + const FontBase::Glyph& Font::addGlyph(TextShaper::GlyphIndex glyphIndex) { - this->samplerState.minFilter = state.minFilter; - this->samplerState.magFilter = state.magFilter; - this->samplerState.maxAnisotropy = state.maxAnisotropy; - - for (const auto& texture : this->textures) - texture->setSamplerState(state); + return FontBase::addGlyph(glyphIndex); } } // namespace love diff --git a/source/modules/graphics/FontBase.cpp b/source/modules/graphics/FontBase.cpp new file mode 100644 index 00000000..e5a79ec9 --- /dev/null +++ b/source/modules/graphics/FontBase.cpp @@ -0,0 +1,569 @@ +#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(); + } + 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; + } + + 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; + + 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 index bb11b5a2..561a3c17 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -326,6 +326,7 @@ namespace love state.texture = command.texture; state.shaderType = command.shaderType; state.isFont = command.isFont; + state.pushTransform = command.pushTransform; } if (state.vertexCount == 0) @@ -466,7 +467,8 @@ namespace love // if (attributes.isEnabled(ATTRIB_COLOR)) // this->setColor(Color::WHITE); - this->pushIdentityTransform(); + if (state.pushTransform) + this->pushIdentityTransform(); if (state.indexCount > 0) { @@ -500,7 +502,8 @@ namespace love if (usedSizes[1] > 0) state.indexBuffer->markUsed(usedSizes[1]); - this->popTransform(); + if (state.pushTransform) + this->popTransform(); // if (attributes.isEnabled(ATTRIB_COLOR)) // this->setColor(originalColor); diff --git a/source/modules/graphics/Mesh.cpp b/source/modules/graphics/Mesh.cpp index eee2671d..8d192840 100644 --- a/source/modules/graphics/Mesh.cpp +++ b/source/modules/graphics/Mesh.cpp @@ -33,12 +33,15 @@ namespace love } 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() @@ -277,6 +280,11 @@ namespace love 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); diff --git a/source/modules/graphics/TextBatch.cpp b/source/modules/graphics/TextBatch.cpp index 17ced29a..26631abe 100644 --- a/source/modules/graphics/TextBatch.cpp +++ b/source/modules/graphics/TextBatch.cpp @@ -214,23 +214,19 @@ namespace love totalVertices = std::max(command.startVertex + command.vertexCount, totalVertices); if (this->modifiedVertices.isValid()) - { - size_t offset = this->modifiedVertices.getOffset(); - size_t size = this->modifiedVertices.getSize(); - 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.isFont = true; + command.format = CommonFormat::XYf_STf_RGBAf; + command.indexMode = TRIANGLEINDEX_QUADS; + command.vertexCount = cmd.vertexCount; + command.texture = cmd.texture; + command.isFont = true; + command.pushTransform = false; auto data = graphics->requestBatchedDraw(command); FontBase::GlyphVertex* vertexdata = (FontBase::GlyphVertex*)data.stream; diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 387e51b2..9eaeb3b2 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -1030,9 +1030,106 @@ int Wrap_Graphics::getFont(lua_State* L) 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) { - return 0; + 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 1; } int Wrap_Graphics::newTextBatch(lua_State* L) @@ -1650,7 +1747,7 @@ static constexpr luaL_Reg functions[] = { "newTexture", Wrap_Graphics::newTexture }, { "newImage", Wrap_Graphics::newImage }, - { "newMesh", Wrap_Graphics::newMesh }, + // { "newMesh", Wrap_Graphics::newMesh }, { "newTextBatch", Wrap_Graphics::newTextBatch }, diff --git a/source/modules/image/magpie/KTXHandler.cpp b/source/modules/image/magpie/KTXHandler.cpp index e7e5b5c1..17b450c2 100644 --- a/source/modules/image/magpie/KTXHandler.cpp +++ b/source/modules/image/magpie/KTXHandler.cpp @@ -280,7 +280,7 @@ namespace love header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1); auto convertedFormat = convertFormat(header.glInternalFormat); - if (convertedFormat = PIXELFORMAT_UNKNOWN) + if (convertedFormat == PIXELFORMAT_UNKNOWN) throw love::Exception("Unsupported image format in KTX file."); if (header.numberOfArrayElements > 0) diff --git a/source/modules/love/scripts/callbacks.lua b/source/modules/love/scripts/callbacks.lua index bc82fd0a..5df1c82d 100644 --- a/source/modules/love/scripts/callbacks.lua +++ b/source/modules/love/scripts/callbacks.lua @@ -204,23 +204,7 @@ 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 From 3e91b4a2425038f2a4bbb2aaa9bd5a7d6517bb7f Mon Sep 17 00:00:00 2001 From: Serena Postelnek Date: Thu, 27 Jun 2024 07:12:20 -0400 Subject: [PATCH 17/49] Update Nintendo Wii U.yml --- .github/workflows/Nintendo Wii U.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Nintendo Wii U.yml b/.github/workflows/Nintendo Wii U.yml index 4cc87ff5..b14de43d 100644 --- a/.github/workflows/Nintendo Wii U.yml +++ b/.github/workflows/Nintendo Wii U.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - name: Build - run: catnipt -T WiiU + run: catnip -T WiiU - id: commit uses: prompt/actions-commit-hash@v3 From b7429255ce48435e5a2553a2e50512bcbcde54bc Mon Sep 17 00:00:00 2001 From: Serena Postelnek Date: Thu, 27 Jun 2024 07:15:26 -0400 Subject: [PATCH 18/49] Update Font.cpp --- platform/ctr/source/modules/graphics/Font.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/platform/ctr/source/modules/graphics/Font.cpp b/platform/ctr/source/modules/graphics/Font.cpp index dfb8459d..c1457ff6 100644 --- a/platform/ctr/source/modules/graphics/Font.cpp +++ b/platform/ctr/source/modules/graphics/Font.cpp @@ -30,8 +30,6 @@ namespace love this->textures.reserve(info->nSheets); - auto graphics = Module::getInstance(Module::M_GRAPHICS); - /* the texture and font data are the same size */ for (size_t index = 0; index < info->nSheets; index++) { From d64a0a10f1ddaa38cdd73a521f045154b13e245b Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 27 Jun 2024 10:24:08 -0400 Subject: [PATCH 19/49] use UINT_LE? --- include/modules/graphics/Graphics.tcc | 6 +- platform/cafe/include/driver/display/GX2.hpp | 7 +- .../cafe/include/driver/display/Uniform.hpp | 18 +++-- .../include/driver/graphics/StreamBuffer.hpp | 69 ++++++++++--------- .../cafe/include/modules/graphics/Shader.hpp | 8 +-- platform/cafe/source/driver/display/GX2.cpp | 25 ++++--- .../cafe/source/modules/graphics/Graphics.cpp | 4 +- .../cafe/source/modules/graphics/Shader.cpp | 8 +-- platform/hac/shaders/transform_vsh.glsl | 2 +- source/modules/graphics/Graphics.cpp | 19 ++++- source/modules/graphics/wrap_Graphics.cpp | 4 +- 11 files changed, 96 insertions(+), 74 deletions(-) diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 41243701..d6b103fe 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -350,7 +350,7 @@ namespace love Winding winding = WINDING_CCW; StrongRef font; - // StrongRef shader; + StrongRef shader; RenderTargetsStrongRef renderTargets; @@ -432,6 +432,10 @@ namespace love virtual void setScissor() = 0; + void setShader(); + + void setShader(ShaderBase* shader); + void intersectScissor(const Rect& scissor); bool getScissor(Rect& scissor) const; diff --git a/platform/cafe/include/driver/display/GX2.hpp b/platform/cafe/include/driver/display/GX2.hpp index 83062301..4b6bd722 100644 --- a/platform/cafe/include/driver/display/GX2.hpp +++ b/platform/cafe/include/driver/display/GX2.hpp @@ -5,7 +5,6 @@ #include "driver/display/Framebuffer.hpp" #include "driver/display/Renderer.tcc" -#include "driver/display/Uniform.hpp" #include "modules/graphics/vertex.hpp" @@ -150,9 +149,9 @@ namespace love { default: case INDEX_UINT16: - return GX2_INDEX_TYPE_U16; + return GX2_INDEX_TYPE_U16_LE; case INDEX_UINT32: - return GX2_INDEX_TYPE_U32; + return GX2_INDEX_TYPE_U32_LE; } } // clang-format on @@ -168,8 +167,6 @@ namespace love struct Context : public ContextBase { - Uniform* transform; - bool cullBack; bool cullFront; diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp index 8e311f9b..0289537d 100644 --- a/platform/cafe/include/driver/display/Uniform.hpp +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -9,18 +9,28 @@ namespace love { static void updateGlmMatrix(glm::mat4& out, const glm::mat4& matrix) { - unsigned int* destination = (unsigned int*)glm::value_ptr(out); - unsigned int* source = (unsigned int*)glm::value_ptr(matrix); + unsigned int* destination = (unsigned int*)glm::value_ptr(out); + const unsigned int* source = (const unsigned int*)glm::value_ptr(matrix); const size_t count = sizeof(glm::mat4) / sizeof(unsigned int); for (size_t i = 0; i < count; i++) destination[i] = __builtin_bswap32(source[i]); + + out = glm::make_mat4(destination); } struct Uniform { - glm::mat4 projection = glm::mat4(1.0f); - glm::mat4 modelView = glm::mat4(1.0f); + glm::mat4 modelView; + glm::mat4 projection; + + public: + Uniform() + { + updateGlmMatrix(this->modelView, glm::mat4(1.0f)); + } }; + + static Uniform* uniform; } // namespace love diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index f3bfaa1c..eb9e1b99 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -16,68 +16,71 @@ namespace love StreamBufferBase(usage, size), buffer(new GX2RBuffer()) { - this->buffer->elemCount = size / sizeof(T); - this->buffer->elemSize = sizeof(T); - this->buffer->flags = getBufferFlags(usage); - if (!GX2RCreateBuffer(this->buffer)) - throw love::Exception("Failed to create StreamBuffer"); + if (this->usage != BufferUsage::BUFFERUSAGE_VERTEX) + { + try + { + this->data = new T[size]; + } + catch (std::bad_alloc&) + { + throw love::Exception("Failed to allocate StreamBuffer"); + } + } + else + { + this->buffer->elemCount = size; + this->buffer->elemSize = sizeof(T); + this->buffer->flags = USAGE_FLAGS; + + if (!GX2RCreateBuffer(this->buffer)) + throw love::Exception("Failed to create StreamBuffer"); - if (usage == BufferUsage::BUFFERUSAGE_VERTEX) GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); + } } MapInfo map(size_t) { MapInfo info {}; - info.data = &((T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE))[this->index]; - info.size = this->bufferSize - this->frameGPUReadOffset; + if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) + info.data = &((T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE))[this->index]; + else + info.data = &this->data[this->index]; + info.size = this->bufferSize; return info; } size_t unmap(size_t) { - GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); - return this->index; - } + if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) + GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); - void debug(std::function func) - { - T* data = (T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); - func(data); - GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + return this->index; } ~StreamBuffer() { - GX2RDestroyBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) + GX2RDestroyBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + delete this->buffer; + delete[] this->data; } ptrdiff_t getHandle() const override { - return (ptrdiff_t)this->buffer; + return (ptrdiff_t)this->data; } private: - static constexpr auto BASE_FLAGS = - GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ; - - static GX2RResourceFlags getBufferFlags(BufferUsage usage) - { - switch (usage) - { - case BufferUsage::BUFFERUSAGE_VERTEX: - return BASE_FLAGS | GX2R_RESOURCE_BIND_VERTEX_BUFFER; - case BufferUsage::BUFFERUSAGE_INDEX: - return BASE_FLAGS | GX2R_RESOURCE_BIND_INDEX_BUFFER; - default: - throw love::Exception("Invalid buffer usage"); - } - } + static constexpr auto USAGE_FLAGS = GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | + GX2R_RESOURCE_USAGE_GPU_READ | GX2R_RESOURCE_BIND_VERTEX_BUFFER; + T* data; GX2RBuffer* buffer; }; } // namespace love diff --git a/platform/cafe/include/modules/graphics/Shader.hpp b/platform/cafe/include/modules/graphics/Shader.hpp index 99abdd89..2854cb97 100644 --- a/platform/cafe/include/modules/graphics/Shader.hpp +++ b/platform/cafe/include/modules/graphics/Shader.hpp @@ -13,12 +13,6 @@ namespace love class Shader final : public ShaderBase, public Volatile { public: - struct UniformInfo - { - GX2UniformBlock* block; - std::string name; - }; - Shader(StandardShader shader); virtual ~Shader(); @@ -31,7 +25,7 @@ namespace love ptrdiff_t getHandle() const override; - void updateBuiltinUniforms(GraphicsBase* graphics, Uniform* transform); + void updateBuiltinUniforms(GraphicsBase* graphics); uint32_t getPixelSamplerLocation(int index); diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index 016be197..b7f13a80 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -22,7 +22,7 @@ namespace love { #define Keyboard() (Module::getInstance(Module::M_KEYBOARD)) - GX2::GX2() : context {}, inForeground(false), commandBuffer(nullptr), state(nullptr), targets {} + GX2::GX2() : context {}, inForeground(false), commandBuffer(nullptr), targets {}, state(nullptr) {} GX2::~GX2() @@ -137,9 +137,7 @@ namespace love this->context.depthWrite = false; this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; - this->context.transform = (Uniform*)memalign(0x100, sizeof(Uniform)); - updateGlmMatrix(this->context.transform->modelView, glm::mat4(1.0f)); - + uniform = (Uniform*)std::aligned_alloc(0x100, sizeof(Uniform)); this->bindFramebuffer(&this->targets[0].get()); this->initialized = true; @@ -213,7 +211,9 @@ namespace love if (bindingModified) { GX2SetColorBuffer(target, GX2_RENDER_TARGET_0); - this->setViewport({ 0, 0, target->surface.width, target->surface.height }); + + this->setViewport(Rect::EMPTY); + this->setScissor(Rect::EMPTY); } } @@ -257,7 +257,7 @@ namespace love if ((Shader*)ShaderBase::current != nullptr) { auto* shader = (Shader*)ShaderBase::current; - shader->updateBuiltinUniforms(graphics, this->context.transform); + shader->updateBuiltinUniforms(graphics); } } @@ -281,7 +281,7 @@ namespace love auto* shader = (Shader*)ShaderBase::current; auto location = shader->getPixelSamplerLocation(0); - GX2SetPixelTexture(texture, location); + GX2SetPixelTexture(texture, unit); GX2SetPixelSampler(sampler, location); } @@ -315,16 +315,16 @@ namespace love { bool isEmptyViewport = viewport == Rect::EMPTY; - const int width = isEmptyViewport ? this->context.boundFramebuffer->surface.width : viewport.w; - const int height = isEmptyViewport ? this->context.boundFramebuffer->surface.height : viewport.h; - const int x = isEmptyViewport ? 0 : viewport.x; const int y = isEmptyViewport ? 0 : viewport.y; + const int width = isEmptyViewport ? this->context.boundFramebuffer->surface.width : viewport.w; + const int height = isEmptyViewport ? this->context.boundFramebuffer->surface.height : viewport.h; + GX2SetViewport(x, y, width, height, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); auto ortho = glm::ortho(x, width, height, y, (int)Framebuffer::Z_NEAR, (int)Framebuffer::Z_FAR); - updateGlmMatrix(this->context.transform->projection, ortho); + updateGlmMatrix(love::uniform->projection, ortho); this->context.viewport = viewport; } @@ -360,8 +360,7 @@ namespace love if (!GX2::getConstant(winding, windingMode)) return; - GX2SetCullOnlyControl(this->context.winding, this->context.cullBack, this->context.cullFront); - + GX2SetCullOnlyControl(windingMode, this->context.cullBack, this->context.cullFront); this->context.winding = windingMode; } diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index 0adbc376..9b61a4c0 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -374,13 +374,13 @@ namespace love gx2.prepareDraw(this); gx2.bindTextureToUnit(command.texture, 0); - auto* buffer = (GX2RBuffer*)command.indexBuffer->getHandle(); + auto* buffer = (uint16_t*)command.indexBuffer->getHandle(); const size_t offset = command.indexBufferOffset; const auto mode = GX2::getPrimitiveType(command.primitiveType); const auto indexType = GX2::getIndexType(command.indexType); - GX2RDrawIndexed(mode, buffer, indexType, command.indexCount, offset, 0, command.instanceCount); + GX2DrawIndexedEx(mode, command.indexCount, indexType, buffer, offset, command.instanceCount); ++this->drawCalls; } diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index 5287fa89..8cbc1ff2 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -12,8 +12,6 @@ #define DEFAULT_TEXTURE_SHADER (SHADERS_DIR "texture.gsh") // #define DEFAULT_VIDEO_SHADER (SHADERS_DIR "video.gsh") -#include "utility/logfile.hpp" - namespace love { Shader::Shader(StandardShader type) @@ -72,13 +70,13 @@ namespace love return this->program.pixelShader->samplerVars[index].location; } - void Shader::updateBuiltinUniforms(GraphicsBase* graphics, Uniform* transform) + void Shader::updateBuiltinUniforms(GraphicsBase* graphics) { if (current != this) return; - GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, (void*)transform, sizeof(Uniform)); - GX2SetVertexUniformBlock(1, sizeof(Uniform), (const void*)transform); + GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, (void*)&love::uniform, sizeof(Uniform)); + GX2SetVertexUniformBlock(1, sizeof(Uniform), (const void*)&love::uniform); } ptrdiff_t Shader::getHandle() const diff --git a/platform/hac/shaders/transform_vsh.glsl b/platform/hac/shaders/transform_vsh.glsl index 360c720e..7a3d233b 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; diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 561a3c17..fa474081 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -103,7 +103,7 @@ namespace love this->setFrontFaceWinding(state.winding); this->setFont(state.font.get()); - // this->setShader(state.shader.get()); + this->setShader(state.shader.get()); // this->setRenderTargets(state.renderTargets); // this->setStencilState(state.stencil); @@ -153,7 +153,7 @@ namespace love this->setFrontFaceWinding(state.winding); this->setFont(state.font.get()); - // this->setShader(state.shader.get()); + this->setShader(state.shader.get()); // if (this->stencil != state.stencil) // this->setStencilState(state.stencil); @@ -196,6 +196,21 @@ namespace love 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 {}; diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 9eaeb3b2..0c31da6f 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -14,6 +14,8 @@ #include "modules/image/wrap_Image.hpp" #include "modules/image/wrap_ImageData.hpp" +#include "utility/logfile.hpp" + using namespace love; #define instance() (Module::getInstance(Module::M_GRAPHICS)) @@ -1606,7 +1608,7 @@ int Wrap_Graphics::getScreens(lua_State* L) { auto screens = love::getScreenInfo(); lua_createtable(L, screens.size(), 0); - + LOG("Found {:d} screens", screens.size()); for (size_t i = 0; i < screens.size(); i++) { luax_pushstring(L, screens[i].name); From 6415ec7c9e94a3fd720e23bd906b3114322f23ad Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 27 Jun 2024 11:18:10 -0400 Subject: [PATCH 20/49] push changes --- platform/cafe/include/driver/display/GX2.hpp | 4 ++-- platform/cafe/include/driver/graphics/StreamBuffer.hpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/platform/cafe/include/driver/display/GX2.hpp b/platform/cafe/include/driver/display/GX2.hpp index 4b6bd722..605bc1c6 100644 --- a/platform/cafe/include/driver/display/GX2.hpp +++ b/platform/cafe/include/driver/display/GX2.hpp @@ -149,9 +149,9 @@ namespace love { default: case INDEX_UINT16: - return GX2_INDEX_TYPE_U16_LE; + return GX2_INDEX_TYPE_U16; case INDEX_UINT32: - return GX2_INDEX_TYPE_U32_LE; + return GX2_INDEX_TYPE_U32; } } // clang-format on diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index eb9e1b99..c3fc8d07 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -36,8 +36,6 @@ namespace love if (!GX2RCreateBuffer(this->buffer)) throw love::Exception("Failed to create StreamBuffer"); - - GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); } } @@ -57,7 +55,10 @@ namespace love size_t unmap(size_t) { if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) + { GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); + } return this->index; } From 7ff0e70cf0fbb0a5707cdb3bd9bb2ef5df5a6695 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 27 Jun 2024 12:02:43 -0400 Subject: [PATCH 21/49] push some changes --- platform/cafe/include/driver/display/GX2.hpp | 4 +++- platform/cafe/include/driver/display/Uniform.hpp | 12 +++++++----- .../cafe/include/driver/graphics/StreamBuffer.hpp | 2 +- platform/cafe/include/modules/graphics/Shader.hpp | 2 +- platform/cafe/source/driver/display/GX2.cpp | 9 ++++++--- platform/cafe/source/modules/graphics/Graphics.cpp | 7 ++++--- platform/cafe/source/modules/graphics/Shader.cpp | 6 +++--- 7 files changed, 25 insertions(+), 17 deletions(-) diff --git a/platform/cafe/include/driver/display/GX2.hpp b/platform/cafe/include/driver/display/GX2.hpp index 605bc1c6..b135955a 100644 --- a/platform/cafe/include/driver/display/GX2.hpp +++ b/platform/cafe/include/driver/display/GX2.hpp @@ -5,9 +5,9 @@ #include "driver/display/Framebuffer.hpp" #include "driver/display/Renderer.tcc" +#include "driver/display/Uniform.hpp" #include "modules/graphics/vertex.hpp" - #include #include #include @@ -180,6 +180,8 @@ namespace love GX2ColorBuffer* boundFramebuffer = nullptr; } context; + Uniform* uniform; + bool inForeground; void* commandBuffer; diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp index 0289537d..3932e845 100644 --- a/platform/cafe/include/driver/display/Uniform.hpp +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -5,10 +5,14 @@ #include #include +#include "utility/logfile.hpp" + namespace love { - static void updateGlmMatrix(glm::mat4& out, const glm::mat4& matrix) + static glm::mat4 updateMatrix(const glm::mat4& matrix) { + glm::mat4 out(1.0f); + unsigned int* destination = (unsigned int*)glm::value_ptr(out); const unsigned int* source = (const unsigned int*)glm::value_ptr(matrix); @@ -17,7 +21,7 @@ namespace love for (size_t i = 0; i < count; i++) destination[i] = __builtin_bswap32(source[i]); - out = glm::make_mat4(destination); + return glm::make_mat4(destination); } struct Uniform @@ -28,9 +32,7 @@ namespace love public: Uniform() { - updateGlmMatrix(this->modelView, glm::mat4(1.0f)); + this->modelView = updateMatrix(glm::mat4(1.0f)); } }; - - static Uniform* uniform; } // namespace love diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index c3fc8d07..09903aa5 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -48,7 +48,7 @@ namespace love else info.data = &this->data[this->index]; - info.size = this->bufferSize; + info.size = this->bufferSize - this->frameGPUReadOffset; return info; } diff --git a/platform/cafe/include/modules/graphics/Shader.hpp b/platform/cafe/include/modules/graphics/Shader.hpp index 2854cb97..133b6595 100644 --- a/platform/cafe/include/modules/graphics/Shader.hpp +++ b/platform/cafe/include/modules/graphics/Shader.hpp @@ -25,7 +25,7 @@ namespace love ptrdiff_t getHandle() const override; - void updateBuiltinUniforms(GraphicsBase* graphics); + void updateBuiltinUniforms(GraphicsBase* graphics, Uniform* uniform); uint32_t getPixelSamplerLocation(int index); diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index b7f13a80..e0616587 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -32,6 +32,8 @@ namespace love GX2Shutdown(); + delete this->uniform; + free(this->state); this->state = nullptr; @@ -137,7 +139,8 @@ namespace love this->context.depthWrite = false; this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; - uniform = (Uniform*)std::aligned_alloc(0x100, sizeof(Uniform)); + this->uniform = new (std::align_val_t(0x100)) Uniform(); + this->bindFramebuffer(&this->targets[0].get()); this->initialized = true; @@ -257,7 +260,7 @@ namespace love if ((Shader*)ShaderBase::current != nullptr) { auto* shader = (Shader*)ShaderBase::current; - shader->updateBuiltinUniforms(graphics); + shader->updateBuiltinUniforms(graphics, this->uniform); } } @@ -324,7 +327,7 @@ namespace love GX2SetViewport(x, y, width, height, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); auto ortho = glm::ortho(x, width, height, y, (int)Framebuffer::Z_NEAR, (int)Framebuffer::Z_FAR); - updateGlmMatrix(love::uniform->projection, ortho); + this->uniform->projection = updateMatrix(ortho); this->context.viewport = viewport; } diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index 9b61a4c0..7428b65d 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -10,6 +10,8 @@ #include #include +#include "utility/logfile.hpp" + namespace love { Graphics::Graphics() : GraphicsBase("love.graphics.gx2") @@ -273,7 +275,6 @@ namespace love else gx2.bindFramebuffer((GX2ColorBuffer*)targets.getFirstTarget().texture->getRenderTargetHandle()); - bool tilt = isWindow ? true : false; gx2.setViewport({ 0, 0, pixelWidth, pixelHeight }); if (state.scissor) @@ -379,8 +380,8 @@ namespace love const auto mode = GX2::getPrimitiveType(command.primitiveType); const auto indexType = GX2::getIndexType(command.indexType); - - GX2DrawIndexedEx(mode, command.indexCount, indexType, buffer, offset, command.instanceCount); + LOG("{:d}", (int)indexType); + GX2DrawIndexedEx(mode, command.indexCount, indexType, &buffer[offset], 0, command.instanceCount); ++this->drawCalls; } diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index 8cbc1ff2..7cca7180 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -70,13 +70,13 @@ namespace love return this->program.pixelShader->samplerVars[index].location; } - void Shader::updateBuiltinUniforms(GraphicsBase* graphics) + void Shader::updateBuiltinUniforms(GraphicsBase* graphics, Uniform* uniform) { if (current != this) return; - GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, (void*)&love::uniform, sizeof(Uniform)); - GX2SetVertexUniformBlock(1, sizeof(Uniform), (const void*)&love::uniform); + GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, (void*)uniform, sizeof(Uniform)); + GX2SetVertexUniformBlock(1, sizeof(Uniform), (const void*)uniform); } ptrdiff_t Shader::getHandle() const From 7b2f1d7c3411b54bc715ad901697f1e06f141b31 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Fri, 28 Jun 2024 08:13:23 -0400 Subject: [PATCH 22/49] actually find the location of the uniform block --- platform/cafe/content/shaders/video.gsh | Bin 0 -> 2224 bytes .../cafe/include/driver/display/Uniform.hpp | 6 ----- .../include/driver/graphics/StreamBuffer.hpp | 5 ++-- .../cafe/include/modules/graphics/Shader.hpp | 2 ++ platform/cafe/source/driver/display/GX2.cpp | 3 ++- .../cafe/source/modules/graphics/Shader.cpp | 25 ++++++++++++++++-- source/modules/graphics/Graphics.cpp | 7 ++++- source/modules/graphics/wrap_Graphics.cpp | 2 +- 8 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 platform/cafe/content/shaders/video.gsh diff --git a/platform/cafe/content/shaders/video.gsh b/platform/cafe/content/shaders/video.gsh new file mode 100644 index 0000000000000000000000000000000000000000..4f61a2e7ace11a3d3a9be65cb7bcb00c488e281d GIT binary patch literal 2224 zcmd^Ay-yTT5TCc4=OU}Og@uLYHW#86ghOL#!bVKQh&c*+j}-`K;2t?Xa>l^!=9(*r z*@D83jiKNVurg7Ii3u?j6qY2CtI$|bP|BXa+1)$&IHHXq(Me{1GjD$LK6Yo`J{T+C zCL#lR9x9*;v?=!n9=(8LKsm9TXim2D?tiKh`0O<#yu^wanj1B05_v?v5mP{ z?t{zkfF%WcyG5Oy!!aA*B4U!$Zjfmb&G>hHqH2pjrxX3KF%fAWzsT@=14X z?Sl+4JMmfuK77?m1%5Lg&JRB3 zx8(dHk7sCpk@GT!?el)EobB^|!?*~vjET4#sF{3=dPt%k4%Zjo7YaUn<6Z)ahKSi*_Il!7{K>v>haBxQ&P88qfG{8SmDuL~?~H9_t}rqxpFFkTQ*4)h+0WTL zAz14(uLs>n|DA{*>)S5ySPXD^KP(-vyIOoF{70JSCX<=!x??cXfX~lr(pX~rA4K?R z6vIwRtSmETX*I;$ftq$ajq^#1-$%fF+?E0Q9Ji(sfDbgmXUqpH%b*kO zA1w90eYnXv&e!Y0w2{ZRtj+5n&t6T7Fi(r}5O?a7G&%3at&R5|jlng}h5l91(WPQ9 ijeFeHzkm65ksCw2WZ#K=F1>}Td*noZi+VazoIe4|1xeTd literal 0 HcmV?d00001 diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp index 3932e845..045a6c3a 100644 --- a/platform/cafe/include/driver/display/Uniform.hpp +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -28,11 +28,5 @@ namespace love { glm::mat4 modelView; glm::mat4 projection; - - public: - Uniform() - { - this->modelView = updateMatrix(glm::mat4(1.0f)); - } }; } // namespace love diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index 09903aa5..34dadad0 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -36,6 +36,8 @@ namespace love if (!GX2RCreateBuffer(this->buffer)) throw love::Exception("Failed to create StreamBuffer"); + + GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); } } @@ -55,10 +57,7 @@ namespace love size_t unmap(size_t) { if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) - { GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); - GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); - } return this->index; } diff --git a/platform/cafe/include/modules/graphics/Shader.hpp b/platform/cafe/include/modules/graphics/Shader.hpp index 133b6595..030774cb 100644 --- a/platform/cafe/include/modules/graphics/Shader.hpp +++ b/platform/cafe/include/modules/graphics/Shader.hpp @@ -35,5 +35,7 @@ namespace love bool validate(const char* filepath, std::string& error); WHBGfxShaderGroup program; + + uint32_t uniformLocation; }; } // namespace love diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index e0616587..a39c651f 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -139,7 +139,8 @@ namespace love this->context.depthWrite = false; this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; - this->uniform = new (std::align_val_t(0x100)) Uniform(); + this->uniform = (Uniform*)memalign(0x100, sizeof(Uniform)); + this->uniform->modelView = updateMatrix(glm::mat4(1.0f)); this->bindFramebuffer(&this->targets[0].get()); diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index 7cca7180..2788a158 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -10,7 +10,7 @@ #define DEFAULT_PRIMITIVE_SHADER (SHADERS_DIR "color.gsh") #define DEFAULT_TEXTURE_SHADER (SHADERS_DIR "texture.gsh") -// #define DEFAULT_VIDEO_SHADER (SHADERS_DIR "video.gsh") +#define DEFAULT_VIDEO_SHADER (SHADERS_DIR "video.gsh") namespace love { @@ -34,6 +34,13 @@ namespace love 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 @@ -42,6 +49,17 @@ namespace love WHBGfxInitShaderAttribute(&this->program, "inColor", 0, COLOR_OFFSET, GX2_ATTRIB_FORMAT_FLOAT_32_32_32_32); // clang-format on + const int count = this->program.vertexShader->uniformBlockCount; + + for (int i = 0; i < count; i++) + { + if (std::strcmp(this->program.vertexShader->uniformBlocks[i].name, "Transformation") == 0) + { + this->uniformLocation = this->program.vertexShader->uniformBlocks[i].offset; + break; + } + } + WHBGfxInitFetchShader(&this->program); } @@ -75,8 +93,11 @@ namespace love if (current != this) return; + auto transform = graphics->getTransform(); + // we will update the uniform block with the new transformation matrix + GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, (void*)uniform, sizeof(Uniform)); - GX2SetVertexUniformBlock(1, sizeof(Uniform), (const void*)uniform); + GX2SetVertexUniformBlock(this->uniformLocation, sizeof(Uniform), (const void*)uniform); } ptrdiff_t Shader::getHandle() const diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index fa474081..54a8e2ca 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -4,6 +4,8 @@ #include "common/Console.hpp" #include "common/screen.hpp" +#include "utility/logfile.hpp" + namespace love { GraphicsBase::GraphicsBase(const char* name) : @@ -656,7 +658,7 @@ namespace love int GraphicsBase::getHeight() const { auto& info = love::getScreenInfo(currentScreen); - return info.width; + return info.height; } GraphicsBase::RendererInfo GraphicsBase::getRendererInfo() const @@ -884,6 +886,9 @@ namespace love if (is2D) transform.transformXY(stream, vertices.data(), command.vertexCount); + + for (int index = 0; index < command.vertexCount; index++) + LOG("Vertex {:d}: ({:.2f}, {:.2f})", index, stream[index].x, stream[index].y); } } diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 0c31da6f..5045030c 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -1608,7 +1608,7 @@ int Wrap_Graphics::getScreens(lua_State* L) { auto screens = love::getScreenInfo(); lua_createtable(L, screens.size(), 0); - LOG("Found {:d} screens", screens.size()); + for (size_t i = 0; i < screens.size(); i++) { luax_pushstring(L, screens[i].name); From ed5d48a13bcf78d7f1376d92e4a52de1902dc6f2 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Tue, 2 Jul 2024 12:35:19 -0400 Subject: [PATCH 23/49] push code changes --- CMakeLists.txt | 2 +- include/common/screen.hpp | 9 +++ include/modules/graphics/Graphics.tcc | 2 +- platform/cafe/CMakeLists.txt | 1 + platform/cafe/content/shaders/color.gsh | Bin 1576 -> 1504 bytes .../include/driver/display/Framebuffer.hpp | 15 ++-- platform/cafe/include/driver/display/GX2.hpp | 1 + .../cafe/include/driver/display/Uniform.hpp | 17 +---- .../include/driver/graphics/StreamBuffer.hpp | 66 ++++++++---------- .../include/modules/graphics/Graphics.hpp | 2 + .../cafe/include/modules/graphics/Shader.hpp | 3 +- .../cafe/include/modules/system/System.hpp | 2 +- .../source/driver/display/Framebuffer.cpp | 25 ++++--- platform/cafe/source/driver/display/GX2.cpp | 34 ++++++--- .../cafe/source/driver/display/Uniform.cpp | 21 ++++++ .../cafe/source/modules/graphics/Graphics.cpp | 13 ++-- .../cafe/source/modules/graphics/Shader.cpp | 24 +++---- .../ctr/include/modules/graphics/Graphics.hpp | 2 + .../ctr/source/modules/graphics/Graphics.cpp | 3 + source/modules/graphics/Graphics.cpp | 9 +-- source/modules/graphics/Shader.cpp | 9 ++- source/modules/graphics/wrap_Graphics.cpp | 3 - 22 files changed, 144 insertions(+), 119 deletions(-) create mode 100644 platform/cafe/source/driver/display/Uniform.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4eafd6ce..04c7ad8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(lovepotion LANGUAGES C CXX) add_executable(${PROJECT_NAME}) dkp_target_generate_symbol_list(${PROJECT_NAME}) -target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) find_package(Git QUIET) if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") diff --git a/include/common/screen.hpp b/include/common/screen.hpp index 72bfa1a4..b790d314 100644 --- a/include/common/screen.hpp +++ b/include/common/screen.hpp @@ -48,6 +48,15 @@ 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; + } + inline void nextScreen() { const auto& info = getScreenInfo(); diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index d6b103fe..156af4fc 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -496,7 +496,7 @@ namespace love virtual void setBlendState(const BlendState& blend) = 0; - void captureScreenshot(const ScreenshotInfo& info); + virtual void captureScreenshot(const ScreenshotInfo& info) = 0; const BlendState& getBlendState() const { diff --git a/platform/cafe/CMakeLists.txt b/platform/cafe/CMakeLists.txt index 95625371..78104379 100644 --- a/platform/cafe/CMakeLists.txt +++ b/platform/cafe/CMakeLists.txt @@ -27,6 +27,7 @@ source/driver/audio/DigitalSound.cpp source/driver/audio/SoundChannel.cpp source/driver/display/Framebuffer.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 diff --git a/platform/cafe/content/shaders/color.gsh b/platform/cafe/content/shaders/color.gsh index edbc8688caeae949ff3ddb6162fcbfed3cd27126..cb182b8aac048ab1907d125784cff26916d60c4f 100644 GIT binary patch delta 388 zcmZ3%^MHGTIitcv3x8H71|XO?p|H1VFJVNMZs|u{l67ZXgB$CZL)nK$;WG0}?>~8YDhr zZc0v>Z%GA1K~a7dke>XXQBen^_Cf-~3lI$hK!ZERXzw@(s-lkMipuOkkYA@PNPmfdGis R2l5#jfX3Bt7Gx1;0s#A!R>S}R delta 443 zcmX|*u}Z^G7={1;Higy(6DP%@D0I-pEP{?19I{9e`T%JVQE8h}t9CLeUEI7ii>tcy z0ek=v2XX3dhf5y82Z){hmsKz}@7CtHlD+Y4RwxqR%cr%xf|71bJ=FFe2<;V1GOOI^Vs*Dmp`q&If!UCmi4@ ztUPDY=rubRCt*Rqgji7%E{RL=iLT;3qpC2A#i;ydyrkIcXlo;@6u0!P#&XOvo^ zW)CwXb%ZBrWCV5G$0G_tL6RdvpKOkdH{GYKl-ALYGcye{C0UZPGG%Lbz!Vd?;27@T Qyz74cJw@h|^Q;Qu7h;TT0RR91 diff --git a/platform/cafe/include/driver/display/Framebuffer.hpp b/platform/cafe/include/driver/display/Framebuffer.hpp index f5eef220..5d21960d 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() - { - return this->modelView; - } - - glm::mat4& getProjection() + Uniform* getUniform() { - return this->projection; + return this->uniform; } bool allocateScanBuffer(MEMHeapHandle handle); @@ -67,8 +61,7 @@ namespace love uint8_t mode; uint8_t id; - glm::mat4 modelView; - glm::mat4 projection; + Uniform* uniform; void* scanBuffer; uint32_t scanBufferSize; diff --git a/platform/cafe/include/driver/display/GX2.hpp b/platform/cafe/include/driver/display/GX2.hpp index b135955a..26ae7ee0 100644 --- a/platform/cafe/include/driver/display/GX2.hpp +++ b/platform/cafe/include/driver/display/GX2.hpp @@ -186,6 +186,7 @@ namespace love void* commandBuffer; GX2ContextState* state; + bool dirtyProjection; }; extern GX2 gx2; diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp index 045a6c3a..a73ae343 100644 --- a/platform/cafe/include/driver/display/Uniform.hpp +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -5,24 +5,9 @@ #include #include -#include "utility/logfile.hpp" - namespace love { - static glm::mat4 updateMatrix(const glm::mat4& matrix) - { - glm::mat4 out(1.0f); - - unsigned int* destination = (unsigned int*)glm::value_ptr(out); - const unsigned int* source = (const unsigned int*)glm::value_ptr(matrix); - - const size_t count = sizeof(glm::mat4) / sizeof(unsigned int); - - for (size_t i = 0; i < count; i++) - destination[i] = __builtin_bswap32(source[i]); - - return glm::make_mat4(destination); - } + glm::mat4 updateMatrix(const glm::mat4& matrix); struct Uniform { diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index 34dadad0..e15f83f3 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -6,6 +6,8 @@ #include #include +#include "utility/logfile.hpp" + namespace love { template @@ -16,71 +18,65 @@ namespace love StreamBufferBase(usage, size), buffer(new GX2RBuffer()) { + this->buffer->elemCount = size; + this->buffer->elemSize = sizeof(T); + this->buffer->flags = USAGE_FLAGS | getUsageFlags(usage); - if (this->usage != BufferUsage::BUFFERUSAGE_VERTEX) - { - try - { - this->data = new T[size]; - } - catch (std::bad_alloc&) - { - throw love::Exception("Failed to allocate StreamBuffer"); - } - } - else - { - this->buffer->elemCount = size; - this->buffer->elemSize = sizeof(T); - this->buffer->flags = USAGE_FLAGS; - - if (!GX2RCreateBuffer(this->buffer)) - throw love::Exception("Failed to create StreamBuffer"); - - GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); - } + if (!GX2RCreateBuffer(this->buffer)) + throw love::Exception("Failed to create StreamBuffer"); } MapInfo map(size_t) { MapInfo info {}; - if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) - info.data = &((T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE))[this->index]; - else - info.data = &this->data[this->index]; + auto* buffer = (T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + info.data = &buffer[this->index]; info.size = this->bufferSize - this->frameGPUReadOffset; + return info; } size_t unmap(size_t) { + LOG("Unmapping StreamBuffer") + GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) - GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); return this->index; } ~StreamBuffer() { - if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) - GX2RDestroyBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); - + GX2RDestroyBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); delete this->buffer; - delete[] this->data; } ptrdiff_t getHandle() const override { - return (ptrdiff_t)this->data; + return (ptrdiff_t)this->buffer; } private: - static constexpr auto USAGE_FLAGS = GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | - GX2R_RESOURCE_USAGE_GPU_READ | GX2R_RESOURCE_BIND_VERTEX_BUFFER; + static constexpr auto USAGE_FLAGS = + GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ; + + static GX2RResourceFlags getUsageFlags(BufferUsage usage) + { + switch (usage) + { + case BufferUsage::BUFFERUSAGE_VERTEX: + return GX2R_RESOURCE_BIND_VERTEX_BUFFER; + case BufferUsage::BUFFERUSAGE_INDEX: + return GX2R_RESOURCE_BIND_INDEX_BUFFER; + default: + throw love::Exception("Invalid buffer usage"); + } + } - T* data; GX2RBuffer* buffer; }; } // namespace love diff --git a/platform/cafe/include/modules/graphics/Graphics.hpp b/platform/cafe/include/modules/graphics/Graphics.hpp index e4499feb..76d0f10e 100644 --- a/platform/cafe/include/modules/graphics/Graphics.hpp +++ b/platform/cafe/include/modules/graphics/Graphics.hpp @@ -15,6 +15,8 @@ namespace love virtual void initCapabilities() override; + virtual void captureScreenshot(const ScreenshotInfo& info) override; + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) override; virtual void clear(const std::vector& colors, OptionalInt stencil, diff --git a/platform/cafe/include/modules/graphics/Shader.hpp b/platform/cafe/include/modules/graphics/Shader.hpp index 030774cb..0b72c36b 100644 --- a/platform/cafe/include/modules/graphics/Shader.hpp +++ b/platform/cafe/include/modules/graphics/Shader.hpp @@ -36,6 +36,7 @@ namespace love bool validate(const char* filepath, std::string& error); WHBGfxShaderGroup program; - uint32_t uniformLocation; + uint32_t modelViewLocation; + uint32_t projectionLocation; }; } // namespace love diff --git a/platform/cafe/include/modules/system/System.hpp b/platform/cafe/include/modules/system/System.hpp index cd50c8d8..9414f4e4 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/source/driver/display/Framebuffer.cpp b/platform/cafe/source/driver/display/Framebuffer.cpp index de61dc4f..4cb582f5 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -3,19 +3,20 @@ #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*)malloc(sizeof(Uniform)); + this->uniform->modelView = glm::mat4(1.0f); + } void Framebuffer::destroy() - {} + { + std::free(this->uniform); + } bool Framebuffer::allocateScanBuffer(MEMHeapHandle handle) { @@ -115,6 +116,9 @@ namespace love this->viewport = { 0, 0, info.width, info.height }; this->scissor = { 0, 0, info.width, info.height }; + + this->uniform->projection = + glm::ortho(0.0f, (float)info.width, (float)info.height, 0.0f, Z_NEAR, Z_FAR); } void Framebuffer::setScissor(const Rect& scissor) @@ -134,7 +138,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 index a39c651f..6a36aa86 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -22,7 +22,13 @@ namespace love { #define Keyboard() (Module::getInstance(Module::M_KEYBOARD)) - GX2::GX2() : context {}, inForeground(false), commandBuffer(nullptr), targets {}, state(nullptr) + GX2::GX2() : + targets {}, + context {}, + inForeground(false), + commandBuffer(nullptr), + state(nullptr), + dirtyProjection(false) {} GX2::~GX2() @@ -139,8 +145,8 @@ namespace love this->context.depthWrite = false; this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; - this->uniform = (Uniform*)memalign(0x100, sizeof(Uniform)); - this->uniform->modelView = updateMatrix(glm::mat4(1.0f)); + this->uniform = (Uniform*)malloc(sizeof(Uniform)); + this->uniform->modelView = glm::mat4(1.0f); this->bindFramebuffer(&this->targets[0].get()); @@ -192,6 +198,9 @@ namespace love void GX2::clear(const Color& color) { + if (!this->inFrame) + return; + GX2ClearColor(this->getFramebuffer(), color.r, color.g, color.b, color.a); GX2SetContextState(this->state); } @@ -258,10 +267,11 @@ namespace love void GX2::prepareDraw(GraphicsBase* graphics) { - if ((Shader*)ShaderBase::current != nullptr) + if (Shader::current != nullptr && this->dirtyProjection) { auto* shader = (Shader*)ShaderBase::current; shader->updateBuiltinUniforms(graphics, this->uniform); + this->dirtyProjection = false; } } @@ -317,6 +327,9 @@ namespace love void GX2::setViewport(const Rect& viewport) { + if (!this->inFrame) + return; + bool isEmptyViewport = viewport == Rect::EMPTY; const int x = isEmptyViewport ? 0 : viewport.x; @@ -326,15 +339,20 @@ namespace love const int height = isEmptyViewport ? this->context.boundFramebuffer->surface.height : viewport.h; GX2SetViewport(x, y, width, height, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); - - auto ortho = glm::ortho(x, width, height, y, (int)Framebuffer::Z_NEAR, (int)Framebuffer::Z_FAR); - this->uniform->projection = updateMatrix(ortho); - this->context.viewport = viewport; + + // clang-format off + auto ortho = glm::ortho(0.0f, (float)width, (float)height, 0.0f, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); + this->uniform->projection = ortho; + this->dirtyProjection = true; + // clang-format on } void GX2::setScissor(const Rect& scissor) { + if (!this->inFrame) + return; + bool isEmptyScissor = scissor == Rect::EMPTY; const int x = isEmptyScissor ? 0 : scissor.x; diff --git a/platform/cafe/source/driver/display/Uniform.cpp b/platform/cafe/source/driver/display/Uniform.cpp new file mode 100644 index 00000000..aef3733e --- /dev/null +++ b/platform/cafe/source/driver/display/Uniform.cpp @@ -0,0 +1,21 @@ +#include "driver/display/Uniform.hpp" + +#include + +namespace love +{ + glm::mat4 updateMatrix(const glm::mat4& matrix) + { + glm::mat4 out(1.0f); + + uint32_t* destination = (uint32_t*)glm::value_ptr(out); + const uint32_t* source = (const uint32_t*)glm::value_ptr(matrix); + + const size_t count = sizeof(glm::mat4) / sizeof(uint32_t); + + for (size_t i = 0; i < count; i++) + destination[i] = __builtin_bswap32(source[i]); + + return out; + } +} // namespace love diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index 7428b65d..207425b0 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -10,8 +10,6 @@ #include #include -#include "utility/logfile.hpp" - namespace love { Graphics::Graphics() : GraphicsBase("love.graphics.gx2") @@ -36,6 +34,9 @@ namespace love Graphics::~Graphics() {} + void Graphics::captureScreenshot(const ScreenshotInfo& info) + {} + void Graphics::initCapabilities() { // clang-format off @@ -178,9 +179,6 @@ namespace love this->drawCalls = 0; this->drawCallsBatched = 0; - - // this->cpuProcessingTime = C3D_GetProcessingTime(); - // this->gpuDrawingTime = C3D_GetDrawingTime(); } void Graphics::setScissor(const Rect& scissor) @@ -375,14 +373,13 @@ namespace love gx2.prepareDraw(this); gx2.bindTextureToUnit(command.texture, 0); - auto* buffer = (uint16_t*)command.indexBuffer->getHandle(); + auto* buffer = (GX2RBuffer*)command.indexBuffer->getHandle(); const size_t offset = command.indexBufferOffset; const auto mode = GX2::getPrimitiveType(command.primitiveType); const auto indexType = GX2::getIndexType(command.indexType); - LOG("{:d}", (int)indexType); - GX2DrawIndexedEx(mode, command.indexCount, indexType, &buffer[offset], 0, command.instanceCount); + GX2RDrawIndexed(mode, buffer, indexType, command.indexCount, offset, 0, command.instanceCount); ++this->drawCalls; } diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index 2788a158..542f4c54 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -20,6 +20,7 @@ namespace love switch (type) { + default: case STANDARD_DEFAULT: { if (!this->validate(DEFAULT_PRIMITIVE_SHADER, error)) @@ -49,18 +50,11 @@ namespace love WHBGfxInitShaderAttribute(&this->program, "inColor", 0, COLOR_OFFSET, GX2_ATTRIB_FORMAT_FLOAT_32_32_32_32); // clang-format on - const int count = this->program.vertexShader->uniformBlockCount; + this->modelViewLocation = GX2GetVertexUniformVar(this->program.vertexShader, "mdlvMtx")->offset; + this->projectionLocation = GX2GetVertexUniformVar(this->program.vertexShader, "projMtx")->offset; - for (int i = 0; i < count; i++) - { - if (std::strcmp(this->program.vertexShader->uniformBlocks[i].name, "Transformation") == 0) - { - this->uniformLocation = this->program.vertexShader->uniformBlocks[i].offset; - break; - } - } - - WHBGfxInitFetchShader(&this->program); + if (!WHBGfxInitFetchShader(&this->program)) + throw love::Exception("Failed to initialize Fetch Shader"); } Shader::~Shader() @@ -96,8 +90,8 @@ namespace love auto transform = graphics->getTransform(); // we will update the uniform block with the new transformation matrix - GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, (void*)uniform, sizeof(Uniform)); - GX2SetVertexUniformBlock(this->uniformLocation, sizeof(Uniform), (const void*)uniform); + GX2SetVertexUniformReg(this->modelViewLocation, 16, (const void*)&uniform->modelView); + GX2SetVertexUniformReg(this->projectionLocation, 16, (const void*)&uniform->projection); } ptrdiff_t Shader::getHandle() const @@ -111,7 +105,7 @@ namespace love { Graphics::flushBatchedDrawsGlobal(); - GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); + GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_REGISTER); GX2SetFetchShader(&this->program.fetchShader); GX2SetVertexShader(this->program.vertexShader); @@ -123,7 +117,7 @@ namespace love bool Shader::validate(const char* filepath, std::string& error) { - FILE* file = std::fopen(filepath, "r"); + FILE* file = std::fopen(filepath, "rb"); if (!file) { diff --git a/platform/ctr/include/modules/graphics/Graphics.hpp b/platform/ctr/include/modules/graphics/Graphics.hpp index 2877cc96..451af363 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -14,6 +14,8 @@ namespace love virtual void initCapabilities() override; + virtual void captureScreenshot(const ScreenshotInfo& info) override; + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) override; virtual void clear(const std::vector& colors, OptionalInt stencil, diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index f5da2d0d..b45d8a0c 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -75,6 +75,9 @@ namespace love this->capabilities.textureTypes[TEXTURE_2D_ARRAY] = false; } + void Graphics::captureScreenshot(const ScreenshotInfo& info) + {} + void Graphics::setActiveScreen() { c3d.ensureInFrame(); diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 54a8e2ca..3550fdee 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -4,8 +4,6 @@ #include "common/Console.hpp" #include "common/screen.hpp" -#include "utility/logfile.hpp" - namespace love { GraphicsBase::GraphicsBase(const char* name) : @@ -886,9 +884,6 @@ namespace love if (is2D) transform.transformXY(stream, vertices.data(), command.vertexCount); - - for (int index = 0; index < command.vertexCount; index++) - LOG("Vertex {:d}: ({:.2f}, {:.2f})", index, stream[index].x, stream[index].y); } } @@ -1151,8 +1146,8 @@ namespace love drawable->draw(this, matrix); } - void GraphicsBase::draw(TextureBase* texture, Quad* quad, const Matrix4& matrix) + void GraphicsBase::draw(TextureBase* texture, Quad* q, const Matrix4& matrix) { - texture->draw(this, quad, matrix); + texture->draw(this, q, matrix); } } // namespace love diff --git a/source/modules/graphics/Shader.cpp b/source/modules/graphics/Shader.cpp index ca214446..0638b69b 100644 --- a/source/modules/graphics/Shader.cpp +++ b/source/modules/graphics/Shader.cpp @@ -1,3 +1,5 @@ +#include "common/Console.hpp" + #include "modules/graphics/Shader.tcc" namespace love @@ -29,7 +31,12 @@ namespace love return; } - if (current != defaultShader) + if constexpr (!Console::is(Console::CAFE)) + { + if (current != defaultShader) + defaultShader->attach(); + } + else defaultShader->attach(); } diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 5045030c..6fdbb084 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -14,8 +14,6 @@ #include "modules/image/wrap_Image.hpp" #include "modules/image/wrap_ImageData.hpp" -#include "utility/logfile.hpp" - using namespace love; #define instance() (Module::getInstance(Module::M_GRAPHICS)) @@ -1624,7 +1622,6 @@ int Wrap_Graphics::setActiveScreen(lua_State* L) Screen screen = love::getScreenId(name); love::currentScreen = screen; - instance()->setActiveScreen(); return 0; From 679213dc611262b45a2a36de78d6c2182b78917b Mon Sep 17 00:00:00 2001 From: TurtleP Date: Tue, 2 Jul 2024 12:50:05 -0400 Subject: [PATCH 24/49] fix this for CI --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04c7ad8f..4eafd6ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(lovepotion LANGUAGES C CXX) add_executable(${PROJECT_NAME}) dkp_target_generate_symbol_list(${PROJECT_NAME}) -target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) find_package(Git QUIET) if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") From 29241ffe9371b266c02b3e6ee791b1847d8bd1ca Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 21 Jul 2024 11:28:37 -0400 Subject: [PATCH 25/49] fix CI stuff --- .github/workflows/Nintendo 3DS.yml | 8 +- .github/workflows/Nintendo Switch.yml | 8 +- .github/workflows/Nintendo Wii U.yml | 8 +- platform/cafe/content/shaders/color.gsh | Bin 1504 -> 1576 bytes .../include/driver/display/Framebuffer.hpp | 7 ++ platform/cafe/include/driver/display/GX2.hpp | 2 + .../include/driver/graphics/StreamBuffer.hpp | 37 ++++----- .../cafe/include/modules/graphics/Shader.hpp | 13 +++- .../source/driver/display/Framebuffer.cpp | 31 ++++++-- platform/cafe/source/driver/display/GX2.cpp | 73 ++++++++---------- .../cafe/source/modules/graphics/Shader.cpp | 32 ++++++-- .../hac/include/modules/graphics/Graphics.hpp | 41 +++++++--- platform/hac/shaders/transform_vsh.glsl | 2 +- 13 files changed, 161 insertions(+), 101 deletions(-) diff --git a/.github/workflows/Nintendo 3DS.yml b/.github/workflows/Nintendo 3DS.yml index a7b8b0c5..b70820b2 100644 --- a/.github/workflows/Nintendo 3DS.yml +++ b/.github/workflows/Nintendo 3DS.yml @@ -24,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 @@ -48,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 71a73c69..0bbd90dc 100644 --- a/.github/workflows/Nintendo Switch.yml +++ b/.github/workflows/Nintendo Switch.yml @@ -24,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 @@ -48,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 b14de43d..40167de1 100644 --- a/.github/workflows/Nintendo Wii U.yml +++ b/.github/workflows/Nintendo Wii U.yml @@ -24,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 @@ -48,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/platform/cafe/content/shaders/color.gsh b/platform/cafe/content/shaders/color.gsh index cb182b8aac048ab1907d125784cff26916d60c4f..edbc8688caeae949ff3ddb6162fcbfed3cd27126 100644 GIT binary patch delta 443 zcmX|*u}Z^G7={1;Higy(6DP%@D0I-pEP{?19I{9e`T%JVQE8h}t9CLeUEI7ii>tcy z0ek=v2XX3dhf5y82Z){hmsKz}@7CtHlD+Y4RwxqR%cr%xf|71bJ=FFe2<;V1GOOI^Vs*Dmp`q&If!UCmi4@ ztUPDY=rubRCt*Rqgji7%E{RL=iLT;3qpC2A#i;ydyrkIcXlo;@6u0!P#&XOvo^ zW)CwXb%ZBrWCV5G$0G_tL6RdvpKOkdH{GYKl-ALYGcye{C0UZPGG%Lbz!Vd?;27@T Qyz74cJw@h|^Q;Qu7h;TT0RR91 delta 388 zcmZ3%^MHGTIitcv3x8H71|XO?p|H1VFJVNMZs|u{l67ZXgB$CZL)nK$;WG0}?>~8YDhr zZc0v>Z%GA1K~a7dke>XXQBen^_Cf-~3lI$hK!ZERXzw@(s-lkMipuOkkYA@PNPmfdGis R2l5#jfX3Bt7Gx1;0s#A!R>S}R diff --git a/platform/cafe/include/driver/display/Framebuffer.hpp b/platform/cafe/include/driver/display/Framebuffer.hpp index 5d21960d..b00ccd16 100644 --- a/platform/cafe/include/driver/display/Framebuffer.hpp +++ b/platform/cafe/include/driver/display/Framebuffer.hpp @@ -49,6 +49,11 @@ namespace love void copyScanBuffer(); + void useState() + { + GX2SetContextState(this->state); + } + private: static constexpr auto FORMAT = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; static constexpr auto BUFFER_MODE = GX2_BUFFERING_MODE_DOUBLE; @@ -65,5 +70,7 @@ namespace love void* scanBuffer; uint32_t scanBufferSize; + + GX2ContextState* state; }; } // namespace love diff --git a/platform/cafe/include/driver/display/GX2.hpp b/platform/cafe/include/driver/display/GX2.hpp index 26ae7ee0..71e9b602 100644 --- a/platform/cafe/include/driver/display/GX2.hpp +++ b/platform/cafe/include/driver/display/GX2.hpp @@ -72,6 +72,8 @@ namespace love void bindTextureToUnit(GX2Texture* texture, GX2Sampler* sampler, int unit); + void setMode(int width, int height); + // clang-format off ENUMMAP_DECLARE(PixelFormats, PixelFormat, GX2SurfaceFormat, { PIXELFORMAT_R8_UNORM, GX2_SURFACE_FORMAT_UNORM_R8 }, diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index e15f83f3..949fb918 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -3,6 +3,7 @@ #include "driver/graphics/StreamBuffer.tcc" #include "modules/graphics/Volatile.hpp" +#include #include #include @@ -14,13 +15,19 @@ namespace love class StreamBuffer final : public StreamBufferBase { public: - StreamBuffer(BufferUsage usage, size_t size) : - StreamBufferBase(usage, size), - buffer(new GX2RBuffer()) + StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size) { + this->buffer = new GX2RBuffer(); + + if (this->buffer == nullptr) + throw love::Exception(E_OUT_OF_MEMORY); + + auto flags = (usage == BufferUsage::BUFFERUSAGE_VERTEX) ? GX2R_RESOURCE_BIND_VERTEX_BUFFER + : GX2R_RESOURCE_BIND_INDEX_BUFFER; + this->buffer->elemCount = size; this->buffer->elemSize = sizeof(T); - this->buffer->flags = USAGE_FLAGS | getUsageFlags(usage); + this->buffer->flags = flags | BUFFER_CREATE_FLAGS; if (!GX2RCreateBuffer(this->buffer)) throw love::Exception("Failed to create StreamBuffer"); @@ -30,9 +37,9 @@ namespace love { MapInfo info {}; - auto* buffer = (T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + auto* data = (T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); - info.data = &buffer[this->index]; + info.data = &data[this->index]; info.size = this->bufferSize - this->frameGPUReadOffset; return info; @@ -40,11 +47,10 @@ namespace love size_t unmap(size_t) { - LOG("Unmapping StreamBuffer") GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) - GX2RSetAttributeBuffer(this->buffer, 0, sizeof(T), 0); + GX2RSetAttributeBuffer(this->buffer, 0, this->buffer->elemSize, 0); return this->index; } @@ -61,22 +67,9 @@ namespace love } private: - static constexpr auto USAGE_FLAGS = + static constexpr auto BUFFER_CREATE_FLAGS = GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ; - static GX2RResourceFlags getUsageFlags(BufferUsage usage) - { - switch (usage) - { - case BufferUsage::BUFFERUSAGE_VERTEX: - return GX2R_RESOURCE_BIND_VERTEX_BUFFER; - case BufferUsage::BUFFERUSAGE_INDEX: - return GX2R_RESOURCE_BIND_INDEX_BUFFER; - default: - throw love::Exception("Invalid buffer usage"); - } - } - GX2RBuffer* buffer; }; } // namespace love diff --git a/platform/cafe/include/modules/graphics/Shader.hpp b/platform/cafe/include/modules/graphics/Shader.hpp index 0b72c36b..ba11250d 100644 --- a/platform/cafe/include/modules/graphics/Shader.hpp +++ b/platform/cafe/include/modules/graphics/Shader.hpp @@ -13,6 +13,12 @@ namespace love class Shader final : public ShaderBase, public Volatile { public: + struct UniformInfo + { + uint32_t location; + std::string name; + }; + Shader(StandardShader shader); virtual ~Shader(); @@ -25,6 +31,10 @@ namespace love 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); @@ -36,7 +46,6 @@ namespace love bool validate(const char* filepath, std::string& error); WHBGfxShaderGroup program; - uint32_t modelViewLocation; - uint32_t projectionLocation; + UniformInfo uniform; }; } // namespace love diff --git a/platform/cafe/source/driver/display/Framebuffer.cpp b/platform/cafe/source/driver/display/Framebuffer.cpp index 4cb582f5..5b8c2b85 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -5,12 +5,16 @@ #include +#include "utility/logfile.hpp" + namespace love { Framebuffer::Framebuffer() : target {}, depth {}, scanBuffer(nullptr), scanBufferSize(0) { - this->uniform = (Uniform*)malloc(sizeof(Uniform)); - this->uniform->modelView = glm::mat4(1.0f); + this->uniform = (Uniform*)memalign(GX2_UNIFORM_BLOCK_ALIGNMENT, sizeof(Uniform)); + + this->uniform->modelView = glm::mat4(1.0f); + this->uniform->projection = glm::mat4(1.0f); } void Framebuffer::destroy() @@ -74,7 +78,7 @@ namespace love void Framebuffer::copyScanBuffer() { - GX2CopyColorBufferToScanBuffer(&this->target, (GX2ScanTarget)this->mode); + GX2CopyColorBufferToScanBuffer(&this->target, (GX2ScanTarget)this->id); } void Framebuffer::create(const ScreenInfo& info) @@ -96,7 +100,6 @@ namespace love GX2CalcTVSize(mode, FORMAT, BUFFER_MODE, &this->scanBufferSize, &unknown); GX2SetTVScale(info.width, info.height); - this->mode = mode; } else @@ -106,19 +109,31 @@ namespace love GX2CalcDRCSize(mode, FORMAT, BUFFER_MODE, &this->scanBufferSize, &unknown); GX2SetDRCScale(info.width, info.height); - this->mode = mode; } + this->id = 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->uniform->projection = - glm::ortho(0.0f, (float)info.width, (float)info.height, 0.0f, Z_NEAR, Z_FAR); + auto 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(glm::mat4(1.0f)); + for (size_t index = 0; index < count; index++) + dstModel[index] = __builtin_bswap32(model[index]); + + uint32_t* projection = (uint32_t*)glm::value_ptr(ortho); + for (size_t index = 0; index < count; index++) + dstProj[index] = __builtin_bswap32(projection[index]); } void Framebuffer::setScissor(const Rect& scissor) diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index 6a36aa86..295f4b39 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -140,14 +140,15 @@ namespace love Keyboard()->initialize(); this->context.winding = GX2_FRONT_FACE_CCW; - this->context.cullBack = true; + this->context.cullBack = false; + this->context.cullFront = false; this->context.depthTest = false; - this->context.depthWrite = false; + this->context.depthWrite = true; this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; - this->uniform = (Uniform*)malloc(sizeof(Uniform)); - this->uniform->modelView = glm::mat4(1.0f); + this->uniform = (Uniform*)memalign(GX2_UNIFORM_BLOCK_ALIGNMENT, sizeof(Uniform)); + love::currentScreen = (Screen)0; this->bindFramebuffer(&this->targets[0].get()); this->initialized = true; @@ -203,6 +204,12 @@ namespace love GX2ClearColor(this->getFramebuffer(), color.r, color.g, color.b, color.a); GX2SetContextState(this->state); + + if (ShaderBase::current != nullptr) + { + auto* shader = (Shader*)ShaderBase::current; + shader->attach(); + } } void GX2::clearDepthStencil(int depth, uint8_t mask, double stencil) @@ -224,12 +231,19 @@ namespace love if (bindingModified) { GX2SetColorBuffer(target, GX2_RENDER_TARGET_0); - - this->setViewport(Rect::EMPTY); - this->setScissor(Rect::EMPTY); + 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(); @@ -267,11 +281,10 @@ namespace love void GX2::prepareDraw(GraphicsBase* graphics) { - if (Shader::current != nullptr && this->dirtyProjection) + if (Shader::current != nullptr) { auto* shader = (Shader*)ShaderBase::current; shader->updateBuiltinUniforms(graphics, this->uniform); - this->dirtyProjection = false; } } @@ -325,43 +338,23 @@ namespace love GX2WaitForFlip(); } - void GX2::setViewport(const Rect& viewport) + void GX2::setViewport(const Rect& rect) { - if (!this->inFrame) - return; + Rect view = rect; + if (rect == Rect::EMPTY) + view = this->targets[love::currentScreen].getViewport(); - bool isEmptyViewport = viewport == Rect::EMPTY; - - const int x = isEmptyViewport ? 0 : viewport.x; - const int y = isEmptyViewport ? 0 : viewport.y; - - const int width = isEmptyViewport ? this->context.boundFramebuffer->surface.width : viewport.w; - const int height = isEmptyViewport ? this->context.boundFramebuffer->surface.height : viewport.h; - - GX2SetViewport(x, y, width, height, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); - this->context.viewport = viewport; - - // clang-format off - auto ortho = glm::ortho(0.0f, (float)width, (float)height, 0.0f, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); - this->uniform->projection = ortho; - this->dirtyProjection = true; - // clang-format on + GX2SetViewport(view.x, view.y, view.w, view.h, Framebuffer::Z_NEAR, Framebuffer::Z_FAR); + this->context.viewport = view; } - void GX2::setScissor(const Rect& scissor) + void GX2::setScissor(const Rect& rect) { - if (!this->inFrame) - return; - - bool isEmptyScissor = scissor == Rect::EMPTY; - - const int x = isEmptyScissor ? 0 : scissor.x; - const int y = isEmptyScissor ? 0 : scissor.y; - - const int width = isEmptyScissor ? this->context.boundFramebuffer->surface.width : scissor.w; - const int height = isEmptyScissor ? this->context.boundFramebuffer->surface.height : scissor.h; + Rect scissor = rect; + if (rect == Rect::EMPTY) + scissor = this->targets[love::currentScreen].getScissor(); - GX2SetScissor(x, y, width, height); + GX2SetScissor(scissor.x, scissor.y, scissor.w, scissor.h); this->context.scissor = scissor; } diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index 542f4c54..101daf66 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -14,7 +14,7 @@ namespace love { - Shader::Shader(StandardShader type) + Shader::Shader(StandardShader type) : uniform {} { std::string error; @@ -50,8 +50,7 @@ namespace love WHBGfxInitShaderAttribute(&this->program, "inColor", 0, COLOR_OFFSET, GX2_ATTRIB_FORMAT_FLOAT_32_32_32_32); // clang-format on - this->modelViewLocation = GX2GetVertexUniformVar(this->program.vertexShader, "mdlvMtx")->offset; - this->projectionLocation = GX2GetVertexUniformVar(this->program.vertexShader, "projMtx")->offset; + this->uniform = this->getUniform("Transformation"); if (!WHBGfxInitFetchShader(&this->program)) throw love::Exception("Failed to initialize Fetch Shader"); @@ -82,16 +81,35 @@ namespace love 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(); - // we will update the uniform block with the new transformation matrix - GX2SetVertexUniformReg(this->modelViewLocation, 16, (const void*)&uniform->modelView); - GX2SetVertexUniformReg(this->projectionLocation, 16, (const void*)&uniform->projection); + GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, uniform, sizeof(Uniform)); + GX2SetVertexUniformBlock(this->uniform.location, sizeof(Uniform), uniform); } ptrdiff_t Shader::getHandle() const @@ -105,7 +123,7 @@ namespace love { Graphics::flushBatchedDrawsGlobal(); - GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_REGISTER); + GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); GX2SetFetchShader(&this->program.fetchShader); GX2SetVertexShader(this->program.vertexShader); diff --git a/platform/hac/include/modules/graphics/Graphics.hpp b/platform/hac/include/modules/graphics/Graphics.hpp index 9b370e3a..0833febc 100644 --- a/platform/hac/include/modules/graphics/Graphics.hpp +++ b/platform/hac/include/modules/graphics/Graphics.hpp @@ -18,29 +18,52 @@ namespace love this->backBufferHasDepth, this->requestedBackbufferMSAA); } + virtual void initCapabilities() override; + + virtual void captureScreenshot(const ScreenshotInfo& info) override; + virtual void clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) override; virtual void clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) override; - void present(void* screenshotCallbackData) override; + virtual void present(void* screenshotCallbackData) override; + + virtual void setScissor(const Rect& scissor) override; + + virtual void setScissor() override; + + virtual void setFrontFaceWinding(Winding winding) override; + + virtual void setColorMask(ColorChannelMask mask) override; - void setScissor(const Rect& scissor) override; + virtual void setBlendState(const BlendState& state) override; - void setScissor() override; + virtual FontBase* newFont(Rasterizer* data) override; - void setFrontFaceWinding(Winding winding) override; + virtual FontBase* newDefaultFont(int size, const Rasterizer::Settings& settings) override; - void setColorMask(ColorChannelMask mask) override; + virtual bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, + bool backBufferDepth, int msaa) override; - void setBlendState(const BlendState& state) override; + virtual void setRenderTargetsInternal(const RenderTargets& targets, int pixelWidth, int pixelHeight, + bool hasSRGBTexture) override; - bool setMode(int width, int height, int pixelWidth, int pixelHeight, bool backBufferStencil, - bool backBufferDepth, int msaa) 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 unsetMode() override; + 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/shaders/transform_vsh.glsl b/platform/hac/shaders/transform_vsh.glsl index 7a3d233b..da5bb586 100644 --- a/platform/hac/shaders/transform_vsh.glsl +++ b/platform/hac/shaders/transform_vsh.glsl @@ -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; From ca153c54e2f1b456cc7065b55303bdd356de2bad Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 21 Jul 2024 12:02:28 -0400 Subject: [PATCH 26/49] fix error on setting looping for stream sources, add catchexception to setLooping --- source/modules/audio/Source.cpp | 2 +- source/modules/audio/wrap_Source.cpp | 2 +- source/modules/love/love.cpp | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/source/modules/audio/Source.cpp b/source/modules/audio/Source.cpp index a5c46fcc..516d27a2 100644 --- a/source/modules/audio/Source.cpp +++ b/source/modules/audio/Source.cpp @@ -533,7 +533,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) diff --git a/source/modules/audio/wrap_Source.cpp b/source/modules/audio/wrap_Source.cpp index 62d372eb..03be269b 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/love/love.cpp b/source/modules/love/love.cpp index 494b5385..82d0539c 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -145,6 +145,22 @@ 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; +} + static void luax_addcompatibilityalias(lua_State* L, const char* module, const char* name, const char* alias) { lua_getglobal(L, module); @@ -229,6 +245,8 @@ int love_initialize(lua_State* L) love::luax_preload(L, luaopen_luautf8, "utf8"); love::luax_preload(L, luaopen_https, "https"); + lua_atpanic(L, love_atpanic); + return 1; } From 40ff82fac5272e738279c54847ceebbdc1ad0229 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 21 Jul 2024 12:04:41 -0400 Subject: [PATCH 27/49] curse you, semicolons --- source/modules/audio/wrap_Source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/modules/audio/wrap_Source.cpp b/source/modules/audio/wrap_Source.cpp index 03be269b..e3619bd9 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); - luax_catchexcept(L, [&]() { self->setLooping(looping) }); + luax_catchexcept(L, [&]() { self->setLooping(looping); }); return 0; } From 6163a4b79250787f36c19f3335e894f1a3d352e6 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 21 Jul 2024 13:06:31 -0400 Subject: [PATCH 28/49] fix gamepad axis not hitting zero on 3ds --- platform/ctr/source/driver/EventQueue.cpp | 6 ++++++ source/modules/audio/Source.cpp | 2 +- source/modules/love/love.cpp | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/platform/ctr/source/driver/EventQueue.cpp b/platform/ctr/source/driver/EventQueue.cpp index 60412919..57388716 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/source/modules/audio/Source.cpp b/source/modules/audio/Source.cpp index 516d27a2..439da4e7 100644 --- a/source/modules/audio/Source.cpp +++ b/source/modules/audio/Source.cpp @@ -613,7 +613,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/love/love.cpp b/source/modules/love/love.cpp index 82d0539c..f9183073 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -161,6 +161,19 @@ static int love_atpanic(lua_State* L) return 0; } +static void love_atcpanic() +{ + try + { + throw; + } + catch (const std::exception& e) + { + std::printf("Uncaught exception: %s", e.what()); + std::exit(EXIT_FAILURE); + } +} + static void luax_addcompatibilityalias(lua_State* L, const char* module, const char* name, const char* alias) { lua_getglobal(L, module); @@ -247,6 +260,10 @@ int love_initialize(lua_State* L) lua_atpanic(L, love_atpanic); +#if __DEBUG__ + std::set_terminate(love_atcpanic); +#endif + return 1; } From 55f91c5f950fbed65aac5ba07c33346fb6c1e944 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 21 Jul 2024 13:24:48 -0400 Subject: [PATCH 29/49] love.graphics.line --- include/modules/graphics/wrap_Graphics.hpp | 2 + .../ctr/source/modules/graphics/Graphics.cpp | 9 +++- source/modules/graphics/wrap_Graphics.cpp | 51 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index c1a4b921..ad059cb6 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -183,6 +183,8 @@ namespace Wrap_Graphics int points(lua_State* L); + int line(lua_State* L); + int draw(lua_State* L); int newMesh(lua_State* L); diff --git a/platform/ctr/source/modules/graphics/Graphics.cpp b/platform/ctr/source/modules/graphics/Graphics.cpp index b45d8a0c..c912fabc 100644 --- a/platform/ctr/source/modules/graphics/Graphics.cpp +++ b/platform/ctr/source/modules/graphics/Graphics.cpp @@ -364,7 +364,14 @@ namespace love for (int index = 0; index < count; index++) { const auto& position = positions[index]; - auto& color = colors[index]; + + if (!colors) + { + this->circle(DRAW_FILL, position.x, position.y, pointSize); + return; + } + + auto& color = colors[index]; gammaCorrectColor(this->getColor()); diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 6fdbb084..501341e7 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -1556,6 +1556,56 @@ int Wrap_Graphics::points(lua_State* L) 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()); @@ -1742,6 +1792,7 @@ static constexpr luaL_Reg functions[] = { "ellipse", Wrap_Graphics::ellipse }, { "arc", Wrap_Graphics::arc }, { "points", Wrap_Graphics::points }, + { "line", Wrap_Graphics::line }, { "newTexture", Wrap_Graphics::newTexture }, { "newImage", Wrap_Graphics::newImage }, From d842bf4183e31b88df2b2ed2dc18c3e14cfb0fea Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 11 Aug 2024 13:55:59 -0400 Subject: [PATCH 30/49] luasocket + add callback check for keyboards --- CMakeLists.txt | 49 +- include/common/Reference.hpp | 82 + include/common/luax.hpp | 2 + include/modules/keyboard/Keyboard.tcc | 52 +- libraries/luasocket/libluasocket/auxiliar.c | 154 ++ libraries/luasocket/libluasocket/auxiliar.h | 54 + libraries/luasocket/libluasocket/buffer.c | 273 ++ libraries/luasocket/libluasocket/buffer.h | 52 + libraries/luasocket/libluasocket/compat.c | 39 + libraries/luasocket/libluasocket/compat.h | 22 + libraries/luasocket/libluasocket/except.c | 129 + libraries/luasocket/libluasocket/except.h | 46 + libraries/luasocket/libluasocket/ftp.lua | 332 +++ libraries/luasocket/libluasocket/headers.lua | 107 + libraries/luasocket/libluasocket/http.lua | 427 +++ libraries/luasocket/libluasocket/inet.c | 537 ++++ libraries/luasocket/libluasocket/inet.h | 56 + libraries/luasocket/libluasocket/io.c | 28 + libraries/luasocket/libluasocket/io.h | 70 + libraries/luasocket/libluasocket/ltn12.lua | 321 +++ libraries/luasocket/libluasocket/luasocket.c | 104 + libraries/luasocket/libluasocket/luasocket.h | 36 + libraries/luasocket/libluasocket/makefile | 461 ++++ libraries/luasocket/libluasocket/mbox.lua | 96 + libraries/luasocket/libluasocket/mime.c | 852 ++++++ libraries/luasocket/libluasocket/mime.h | 22 + libraries/luasocket/libluasocket/mime.lua | 84 + libraries/luasocket/libluasocket/options.c | 480 ++++ libraries/luasocket/libluasocket/options.h | 113 + libraries/luasocket/libluasocket/pierror.h | 28 + libraries/luasocket/libluasocket/select.c | 214 ++ libraries/luasocket/libluasocket/select.h | 23 + libraries/luasocket/libluasocket/serial.c | 171 ++ libraries/luasocket/libluasocket/smtp.lua | 259 ++ libraries/luasocket/libluasocket/socket.h | 75 + libraries/luasocket/libluasocket/socket.lua | 152 ++ libraries/luasocket/libluasocket/tcp.c | 480 ++++ libraries/luasocket/libluasocket/tcp.h | 43 + libraries/luasocket/libluasocket/timeout.c | 226 ++ libraries/luasocket/libluasocket/timeout.h | 40 + libraries/luasocket/libluasocket/tp.lua | 137 + libraries/luasocket/libluasocket/udp.c | 488 ++++ libraries/luasocket/libluasocket/udp.h | 39 + libraries/luasocket/libluasocket/unix.c | 69 + libraries/luasocket/libluasocket/unix.h | 26 + libraries/luasocket/libluasocket/unixdgram.c | 405 +++ libraries/luasocket/libluasocket/unixdgram.h | 28 + libraries/luasocket/libluasocket/unixstream.c | 355 +++ libraries/luasocket/libluasocket/unixstream.h | 29 + libraries/luasocket/libluasocket/url.lua | 333 +++ libraries/luasocket/libluasocket/usocket.c | 454 ++++ libraries/luasocket/libluasocket/usocket.h | 59 + libraries/luasocket/libluasocket/wsocket.c | 434 +++ libraries/luasocket/libluasocket/wsocket.h | 33 + libraries/luasocket/luasocket.cpp | 130 + libraries/luasocket/luasocket.hpp | 41 + platform/cafe/libraries/luasocket.patch | 2109 +++++++++++++++ .../include/modules/font/BCFNTRasterizer.hpp | 4 + platform/ctr/include/modules/font/Font.hpp | 13 + .../ctr/include/modules/keyboard/Keyboard.hpp | 6 + platform/ctr/libraries/luasocket.patch | 2397 +++++++++++++++++ .../source/modules/font/BCFNTRasterizer.cpp | 38 +- platform/ctr/source/modules/font/Font.cpp | 7 +- .../ctr/source/modules/keyboard/Keyboard.cpp | 12 + platform/hac/libraries/luasocket.patch | 2133 +++++++++++++++ source/common/Reference.cpp | 78 + source/common/luax.cpp | 13 + source/modules/font/wrap_Font.cpp | 44 + source/modules/graphics/Graphics.cpp | 4 +- source/modules/graphics/wrap_Graphics.cpp | 4 +- source/modules/graphics/wrap_TextBatch.cpp | 2 +- source/modules/keyboard/wrap_Keyboard.cpp | 58 +- source/modules/love/love.cpp | 7 +- source/modules/love/scripts/callbacks.lua | 2 +- 74 files changed, 16753 insertions(+), 29 deletions(-) create mode 100644 include/common/Reference.hpp create mode 100644 libraries/luasocket/libluasocket/auxiliar.c create mode 100644 libraries/luasocket/libluasocket/auxiliar.h create mode 100644 libraries/luasocket/libluasocket/buffer.c create mode 100644 libraries/luasocket/libluasocket/buffer.h create mode 100644 libraries/luasocket/libluasocket/compat.c create mode 100644 libraries/luasocket/libluasocket/compat.h create mode 100644 libraries/luasocket/libluasocket/except.c create mode 100644 libraries/luasocket/libluasocket/except.h create mode 100644 libraries/luasocket/libluasocket/ftp.lua create mode 100644 libraries/luasocket/libluasocket/headers.lua create mode 100644 libraries/luasocket/libluasocket/http.lua create mode 100644 libraries/luasocket/libluasocket/inet.c create mode 100644 libraries/luasocket/libluasocket/inet.h create mode 100644 libraries/luasocket/libluasocket/io.c create mode 100644 libraries/luasocket/libluasocket/io.h create mode 100644 libraries/luasocket/libluasocket/ltn12.lua create mode 100644 libraries/luasocket/libluasocket/luasocket.c create mode 100644 libraries/luasocket/libluasocket/luasocket.h create mode 100644 libraries/luasocket/libluasocket/makefile create mode 100644 libraries/luasocket/libluasocket/mbox.lua create mode 100644 libraries/luasocket/libluasocket/mime.c create mode 100644 libraries/luasocket/libluasocket/mime.h create mode 100644 libraries/luasocket/libluasocket/mime.lua create mode 100644 libraries/luasocket/libluasocket/options.c create mode 100644 libraries/luasocket/libluasocket/options.h create mode 100644 libraries/luasocket/libluasocket/pierror.h create mode 100644 libraries/luasocket/libluasocket/select.c create mode 100644 libraries/luasocket/libluasocket/select.h create mode 100644 libraries/luasocket/libluasocket/serial.c create mode 100644 libraries/luasocket/libluasocket/smtp.lua create mode 100644 libraries/luasocket/libluasocket/socket.h create mode 100644 libraries/luasocket/libluasocket/socket.lua create mode 100644 libraries/luasocket/libluasocket/tcp.c create mode 100644 libraries/luasocket/libluasocket/tcp.h create mode 100644 libraries/luasocket/libluasocket/timeout.c create mode 100644 libraries/luasocket/libluasocket/timeout.h create mode 100644 libraries/luasocket/libluasocket/tp.lua create mode 100644 libraries/luasocket/libluasocket/udp.c create mode 100644 libraries/luasocket/libluasocket/udp.h create mode 100644 libraries/luasocket/libluasocket/unix.c create mode 100644 libraries/luasocket/libluasocket/unix.h create mode 100644 libraries/luasocket/libluasocket/unixdgram.c create mode 100644 libraries/luasocket/libluasocket/unixdgram.h create mode 100644 libraries/luasocket/libluasocket/unixstream.c create mode 100644 libraries/luasocket/libluasocket/unixstream.h create mode 100644 libraries/luasocket/libluasocket/url.lua create mode 100644 libraries/luasocket/libluasocket/usocket.c create mode 100644 libraries/luasocket/libluasocket/usocket.h create mode 100644 libraries/luasocket/libluasocket/wsocket.c create mode 100644 libraries/luasocket/libluasocket/wsocket.h create mode 100644 libraries/luasocket/luasocket.cpp create mode 100644 libraries/luasocket/luasocket.hpp create mode 100644 platform/cafe/libraries/luasocket.patch create mode 100644 platform/ctr/libraries/luasocket.patch create mode 100644 platform/hac/libraries/luasocket.patch create mode 100644 source/common/Reference.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4eafd6ce..ace4e9b3 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) @@ -79,6 +81,8 @@ if(NINTENDO_3DS) 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) @@ -212,6 +216,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 @@ -323,7 +368,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 ) @@ -333,6 +378,7 @@ include_directories( libraries/dr libraries/lua53 libraries/noise1234 + libraries/luasocket libraries/physfs libraries/wuff libraries/utf8 @@ -351,6 +397,7 @@ 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 diff --git a/include/common/Reference.hpp b/include/common/Reference.hpp new file mode 100644 index 00000000..7d6267cf --- /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/luax.hpp b/include/common/luax.hpp index c8b38ba6..bd3b6ad7 100644 --- a/include/common/luax.hpp +++ b/include/common/luax.hpp @@ -299,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/modules/keyboard/Keyboard.tcc b/include/modules/keyboard/Keyboard.tcc index 5494b3db..5c80e55f 100644 --- a/include/modules/keyboard/Keyboard.tcc +++ b/include/modules/keyboard/Keyboard.tcc @@ -5,12 +5,39 @@ #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*; +#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 +47,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 +60,7 @@ namespace love OPTION_PASSCODE, OPTION_HINT, OPTION_MAX_LENGTH, + OPTION_CALLBACK, OPTION_MAX_ENUM }; @@ -46,11 +75,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 +111,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 +120,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/libraries/luasocket/libluasocket/auxiliar.c b/libraries/luasocket/libluasocket/auxiliar.c new file mode 100644 index 00000000..93a66a09 --- /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 00000000..e8c3ead8 --- /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 00000000..7148be34 --- /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 00000000..a0901fcc --- /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 00000000..34ffdaf7 --- /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 00000000..fa2d7d7c --- /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 00000000..9c3317f2 --- /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 00000000..71c31fd4 --- /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 00000000..a2f6563b --- /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 00000000..05818703 --- /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 00000000..1d767292 --- /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 00000000..138c9abe --- /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 00000000..5618b61b --- /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 00000000..5ad4b3af --- /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 00000000..b8a54df6 --- /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 00000000..e210b568 --- /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 00000000..0fd99f70 --- /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 00000000..1017fbaa --- /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 00000000..06f4d192 --- /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 00000000..f343d716 --- /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 00000000..05602f56 --- /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 00000000..4d938f46 --- /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 00000000..4831ebc4 --- /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 00000000..3280c51d --- /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 00000000..456eeb5f --- /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 00000000..cb773ab7 --- /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 00000000..bb47c459 --- /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 00000000..5d45fe75 --- /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 00000000..21485d3e --- /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 00000000..8a8bf4de --- /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 00000000..2555bab6 --- /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 00000000..f70f3e1e --- /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 00000000..e84db845 --- /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 00000000..9b282efe --- /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 00000000..2bdc0698 --- /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 00000000..9e5250d3 --- /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 00000000..03a5344d --- /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 00000000..712ad50f --- /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 00000000..07d5247f --- /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 00000000..268d8b21 --- /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 00000000..c2035618 --- /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 00000000..69093d73 --- /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 00000000..a1a0166b --- /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 00000000..02aced9c --- /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 00000000..7916affa --- /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 00000000..3bf1c17e --- /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 00000000..69635daa --- /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 00000000..45f2f99f --- /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 00000000..6cb1e415 --- /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 00000000..39866402 --- /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 00000000..9c973a1f --- /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 00000000..4ef5879c --- /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/libraries/luasocket.patch b/platform/cafe/libraries/luasocket.patch new file mode 100644 index 00000000..8d2a9fb6 --- /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/ctr/include/modules/font/BCFNTRasterizer.hpp b/platform/ctr/include/modules/font/BCFNTRasterizer.hpp index cae2e125..ff4032ba 100644 --- a/platform/ctr/include/modules/font/BCFNTRasterizer.hpp +++ b/platform/ctr/include/modules/font/BCFNTRasterizer.hpp @@ -49,7 +49,11 @@ namespace love 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 index 3003e1dc..0c9d1624 100644 --- a/platform/ctr/include/modules/font/Font.hpp +++ b/platform/ctr/include/modules/font/Font.hpp @@ -4,6 +4,8 @@ #include <3ds.h> +using SystemFontType = CFG_Region; + namespace love { class FontModule : public FontModuleBase @@ -11,6 +13,8 @@ namespace love 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; @@ -20,6 +24,15 @@ namespace love 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; diff --git a/platform/ctr/include/modules/keyboard/Keyboard.hpp b/platform/ctr/include/modules/keyboard/Keyboard.hpp index d1f3b91a..6b409556 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 00000000..01bbcb47 --- /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/source/modules/font/BCFNTRasterizer.cpp b/platform/ctr/source/modules/font/BCFNTRasterizer.cpp index 0d8acccc..6fe30faa 100644 --- a/platform/ctr/source/modules/font/BCFNTRasterizer.cpp +++ b/platform/ctr/source/modules/font/BCFNTRasterizer.cpp @@ -74,6 +74,11 @@ namespace love 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; @@ -82,11 +87,21 @@ namespace love 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((CFNT_s*)data->getData())->tglp < (uintptr_t)data->getData()) - fontFixPointers((CFNT_s*)data->getData()); + if ((uintptr_t)fontGetInfo(this->getUserdata(data))->tglp < (uintptr_t)this->getUserdata(data)) + fontFixPointers(this->getUserdata(data)); - auto* fontInfo = fontGetInfo((CFNT_s*)data->getData()); + auto* fontInfo = fontGetInfo(this->getUserdata(data)); auto* sheetInfo = fontInfo->tglp; this->scale = std::floor(this->size * this->dpiScale + 0.5f) / sheetInfo->cellHeight; @@ -104,7 +119,10 @@ namespace love } BCFNTRasterizer::~BCFNTRasterizer() - {} + { + if (this->userdata) + linearFree(this->userdata); + } TextShaper* BCFNTRasterizer::newTextShaper() { @@ -114,7 +132,7 @@ namespace love bool BCFNTRasterizer::hasGlyph(uint32_t codepoint) const { const int index = this->getGlyphIndex(codepoint); - const auto* info = fontGetInfo((CFNT_s*)this->data->getData()); + const auto* info = fontGetInfo(this->getUserdata(this->data)); return index != info->alterCharIndex && codepoint != '\t'; } @@ -125,7 +143,7 @@ namespace love const auto flag = GLYPH_POS_CALC_VTXCOORD; fontGlyphPos_s result {}; - fontCalcGlyphPos(&result, (CFNT_s*)this->data->getData(), index, flag, this->scale, this->scale); + fontCalcGlyphPos(&result, this->getUserdata(this->data), index, flag, this->scale, this->scale); return result.xAdvance; } @@ -135,7 +153,7 @@ namespace love fontGlyphPos_s result {}; const auto flag = GLYPH_POS_CALC_VTXCOORD; - fontCalcGlyphPos(&result, (CFNT_s*)this->data->getData(), index, flag, this->scale, this->scale); + fontCalcGlyphPos(&result, this->getUserdata(this->data), index, flag, this->scale, this->scale); GlyphMetrics metrics {}; metrics.height = this->metrics.height; @@ -151,7 +169,7 @@ namespace love sheet.right = result.texcoord.right; sheet.bottom = result.texcoord.bottom; - const auto* info = fontGetGlyphInfo((CFNT_s*)this->data->getData()); + const auto* info = fontGetGlyphInfo(this->getUserdata(this->data)); PixelFormat format; if (!citro3d::getConstant((GPU_TEXCOLOR)info->sheetFmt, format)) @@ -162,7 +180,7 @@ namespace love int BCFNTRasterizer::getGlyphIndex(uint32_t codepoint) const { - return love::fontGlyphIndexFromCodePoint((CFNT_s*)this->data->getData(), codepoint); + return love::fontGlyphIndexFromCodePoint(this->getUserdata(this->data), codepoint); } int BCFNTRasterizer::getGlyphCount() const @@ -172,6 +190,6 @@ namespace love ptrdiff_t BCFNTRasterizer::getHandle() const { - return (ptrdiff_t)this->data->getData(); + 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 index e439a571..682be80b 100644 --- a/platform/ctr/source/modules/font/Font.cpp +++ b/platform/ctr/source/modules/font/Font.cpp @@ -19,6 +19,11 @@ namespace love } // #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; @@ -103,7 +108,7 @@ namespace love FontModule::FontModule() : FontModuleBase("love.font.ctr") { - this->defaultFontData.set(new SystemFont(CFG_REGION_USA)); + this->defaultFontData.set(new SystemFont(CFG_REGION_USA), Acquire::NO_RETAIN); } Rasterizer* FontModule::newRasterizer(FileData* data) const diff --git a/platform/ctr/source/modules/keyboard/Keyboard.cpp b/platform/ctr/source/modules/keyboard/Keyboard.cpp index 25e3c929..5c5b6f01 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/libraries/luasocket.patch b/platform/hac/libraries/luasocket.patch new file mode 100644 index 00000000..6ba9fa36 --- /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/source/common/Reference.cpp b/source/common/Reference.cpp new file mode 100644 index 00000000..99e20fac --- /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/luax.cpp b/source/common/luax.cpp index a575c751..45a5a8fb 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 @@ -989,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/modules/font/wrap_Font.cpp b/source/modules/font/wrap_Font.cpp index c15eb70e..baec05bc 100644 --- a/source/modules/font/wrap_Font.cpp +++ b/source/modules/font/wrap_Font.cpp @@ -75,6 +75,25 @@ static Rasterizer::Settings luax_checktruetypesettings(lua_State* L, int index) 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; @@ -89,6 +108,31 @@ int Wrap_FontModule::newTrueTypeRasterizer(lua_State* L) 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(); diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 3550fdee..41c35fd7 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -991,8 +991,8 @@ namespace love for (int index = 0; index < points; ++index, phi += shift) { - coords[index].x = x + a * std::cos(phi); - coords[index].y = y + b * std::sin(phi); + coords[index].x = x + a * std::cosf(phi); + coords[index].y = y + b * std::sinf(phi); } coords[points] = coords[0]; diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 501341e7..a57a68cb 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -6,6 +6,7 @@ #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" @@ -1821,7 +1822,8 @@ static constexpr lua_CFunction types[] = open_drawable, love::open_texture, love::open_quad, - love::open_font + love::open_font, + love::open_textbatch }; // clang-format on diff --git a/source/modules/graphics/wrap_TextBatch.cpp b/source/modules/graphics/wrap_TextBatch.cpp index e80170a3..cd7c3291 100644 --- a/source/modules/graphics/wrap_TextBatch.cpp +++ b/source/modules/graphics/wrap_TextBatch.cpp @@ -190,7 +190,7 @@ static constexpr luaL_Reg functions[] = { "getFont", Wrap_TextBatch::getFont }, { "getWidth", Wrap_TextBatch::getWidth }, { "getHeight", Wrap_TextBatch::getHeight }, - { "getDimensions", Wrap_TextBatch::getDimensions }, + { "getDimensions", Wrap_TextBatch::getDimensions } }; // clang-format on diff --git a/source/modules/keyboard/wrap_Keyboard.cpp b/source/modules/keyboard/wrap_Keyboard.cpp index 21d8adbc..d5f00d72 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 f9183073..6becc83d 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -1,6 +1,8 @@ #include "common/luax.hpp" #include "common/version.hpp" +#include + #include "modules/love/love.hpp" #include "modules/audio/wrap_Audio.hpp" @@ -240,6 +242,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"); @@ -254,7 +259,7 @@ 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"); diff --git a/source/modules/love/scripts/callbacks.lua b/source/modules/love/scripts/callbacks.lua index 5df1c82d..3f61238d 100644 --- a/source/modules/love/scripts/callbacks.lua +++ b/source/modules/love/scripts/callbacks.lua @@ -207,7 +207,7 @@ end -- local is_wii_u = love._os == "Cafe" local function get3DDepth(screen) - if love._console ~= "3ds" then + if love._console ~= "3DS" then return nil end From c300387a56e5bf9922c1b8ca4f7a50b1ba09797d Mon Sep 17 00:00:00 2001 From: TurtleP Date: Mon, 12 Aug 2024 12:08:10 -0400 Subject: [PATCH 31/49] remove logfile - it's useless now --- include/utility/logfile.hpp | 57 ------------------- .../include/driver/graphics/StreamBuffer.hpp | 2 - platform/cafe/source/boot.cpp | 2 - .../cafe/source/driver/audio/SoundChannel.cpp | 2 - .../source/driver/display/Framebuffer.cpp | 2 - platform/cafe/source/driver/display/GX2.cpp | 2 - .../cafe/source/modules/joystick/Joystick.cpp | 5 +- .../modules/joystick/JoystickModule.cpp | 2 - platform/ctr/source/modules/graphics/Font.cpp | 2 - .../ctr/source/modules/joystick/Joystick.cpp | 2 - source/modules/filesystem/physfs/File.cpp | 2 - source/modules/filesystem/wrap_File.cpp | 2 - source/modules/love/love.cpp | 6 +- 13 files changed, 4 insertions(+), 84 deletions(-) delete mode 100644 include/utility/logfile.hpp diff --git a/include/utility/logfile.hpp b/include/utility/logfile.hpp deleted file mode 100644 index 2a90fc0d..00000000 --- a/include/utility/logfile.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include -#include -#include - -class Log -{ - public: - static Log& getInstance() - { - static Log instance; - return instance; - } - - ~Log() - { - std::fclose(file); - } - - 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(), this->file); - std::fflush(this->file); - } - - private: - static constexpr const char* BUFFER_FORMAT = "{:s}({:d}:{:d}): {:s}\n"; - std::FILE* file; - - Log() - { - file = std::fopen("debug.log", "w"); - } -}; - -#if __DEBUG__ == 0 - #define LOG(...) -#else - #define LOG(format, ...) Log::getInstance().write(std::source_location::current(), format, ##__VA_ARGS__); -#endif diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index 949fb918..b9552545 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -7,8 +7,6 @@ #include #include -#include "utility/logfile.hpp" - namespace love { template diff --git a/platform/cafe/source/boot.cpp b/platform/cafe/source/boot.cpp index 0222b51d..281e88e8 100644 --- a/platform/cafe/source/boot.cpp +++ b/platform/cafe/source/boot.cpp @@ -20,8 +20,6 @@ #include -#include "utility/logfile.hpp" - namespace love { // clang-format off diff --git a/platform/cafe/source/driver/audio/SoundChannel.cpp b/platform/cafe/source/driver/audio/SoundChannel.cpp index 3ec745c8..678e20b0 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 5b8c2b85..e0130bf1 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -5,8 +5,6 @@ #include -#include "utility/logfile.hpp" - namespace love { Framebuffer::Framebuffer() : target {}, depth {}, scanBuffer(nullptr), scanBufferSize(0) diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index 295f4b39..fa2a87eb 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -16,8 +16,6 @@ #include -#include "utility/logfile.hpp" - namespace love { #define Keyboard() (Module::getInstance(Module::M_KEYBOARD)) diff --git a/platform/cafe/source/modules/joystick/Joystick.cpp b/platform/cafe/source/modules/joystick/Joystick.cpp index 86c602ec..96d52901 100644 --- a/platform/cafe/source/modules/joystick/Joystick.cpp +++ b/platform/cafe/source/modules/joystick/Joystick.cpp @@ -1,7 +1,5 @@ #include "modules/joystick/Joystick.hpp" -#include "utility/logfile.hpp" - namespace love { Joystick::Joystick(int id) : JoystickBase(id) @@ -208,8 +206,7 @@ namespace love return false; } - static bool isProControllerDown(const KPADStatus& status, - std::span buttons) + static bool isProControllerDown(const KPADStatus& status, std::span buttons) { WPADProButton result; diff --git a/platform/cafe/source/modules/joystick/JoystickModule.cpp b/platform/cafe/source/modules/joystick/JoystickModule.cpp index 5cd6b14d..3391a1e8 100644 --- a/platform/cafe/source/modules/joystick/JoystickModule.cpp +++ b/platform/cafe/source/modules/joystick/JoystickModule.cpp @@ -3,8 +3,6 @@ #include #include -#include "utility/logfile.hpp" - namespace love::joystick { int getJoystickCount() diff --git a/platform/ctr/source/modules/graphics/Font.cpp b/platform/ctr/source/modules/graphics/Font.cpp index c1457ff6..93d411d1 100644 --- a/platform/ctr/source/modules/graphics/Font.cpp +++ b/platform/ctr/source/modules/graphics/Font.cpp @@ -1,8 +1,6 @@ #include "modules/graphics/Font.hpp" #include "modules/graphics/Graphics.tcc" -#include "utility/logfile.hpp" - namespace love { Font::Font(Rasterizer* rasterizer, const SamplerState& samplerState) : FontBase(rasterizer, samplerState) diff --git a/platform/ctr/source/modules/joystick/Joystick.cpp b/platform/ctr/source/modules/joystick/Joystick.cpp index ad874915..2c3a9481 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/source/modules/filesystem/physfs/File.cpp b/source/modules/filesystem/physfs/File.cpp index cff8fd85..c243c44c 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 ae04587e..0fc60368 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/love/love.cpp b/source/modules/love/love.cpp index 6becc83d..19314cb6 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -79,8 +79,6 @@ static constexpr std::array modules = }}; // clang-format on -#include "utility/logfile.hpp" - const char* love_getVersion() { return __LOVE_VERSION__; @@ -163,6 +161,7 @@ static int love_atpanic(lua_State* L) return 0; } +#if __DEBUG__ static void love_atcpanic() { try @@ -175,6 +174,7 @@ static void love_atcpanic() std::exit(EXIT_FAILURE); } } +#endif static void luax_addcompatibilityalias(lua_State* L, const char* module, const char* name, const char* alias) { @@ -276,7 +276,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) From a9ec4eff3c9e16b7d0dc1c5490eff159b223e01d Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 29 Sep 2024 12:46:25 -0400 Subject: [PATCH 32/49] push whatever this was --- CMakeLists.txt | 2 ++ include/modules/keyboard/Keyboard.tcc | 2 ++ .../include/driver/graphics/StreamBuffer.hpp | 26 +++++++------------ platform/cafe/source/driver/display/GX2.cpp | 19 +++++--------- .../cafe/source/modules/graphics/Graphics.cpp | 15 ++++++----- .../cafe/source/modules/graphics/Shader.cpp | 20 +++++++------- source/modules/graphics/Graphics.cpp | 6 +++-- source/modules/graphics/wrap_Graphics.cpp | 2 +- 8 files changed, 43 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ace4e9b3..9d37dc00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,8 @@ if (NINTENDO_WIIU) 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 diff --git a/include/modules/keyboard/Keyboard.tcc b/include/modules/keyboard/Keyboard.tcc index 5c80e55f..7d02827b 100644 --- a/include/modules/keyboard/Keyboard.tcc +++ b/include/modules/keyboard/Keyboard.tcc @@ -26,6 +26,8 @@ namespace love using ValidationError = const char**; #elif defined(__SWITCH__) using ValidationError = char*; +#else + using ValidationError = void*; #endif typedef KeyboardResult (*KeyboardValidationCallback)(const KeyboardValidationInfo* info, diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index b9552545..145b8109 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -15,19 +15,14 @@ namespace love public: StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size) { - this->buffer = new GX2RBuffer(); - - if (this->buffer == nullptr) - throw love::Exception(E_OUT_OF_MEMORY); - auto flags = (usage == BufferUsage::BUFFERUSAGE_VERTEX) ? GX2R_RESOURCE_BIND_VERTEX_BUFFER : GX2R_RESOURCE_BIND_INDEX_BUFFER; - this->buffer->elemCount = size; - this->buffer->elemSize = sizeof(T); - this->buffer->flags = flags | BUFFER_CREATE_FLAGS; + this->buffer.elemCount = size; + this->buffer.elemSize = sizeof(T); + this->buffer.flags = flags | BUFFER_CREATE_FLAGS; - if (!GX2RCreateBuffer(this->buffer)) + if (!GX2RCreateBuffer(&this->buffer)) throw love::Exception("Failed to create StreamBuffer"); } @@ -35,7 +30,7 @@ namespace love { MapInfo info {}; - auto* data = (T*)GX2RLockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + auto* data = (T*)GX2RLockBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); info.data = &data[this->index]; info.size = this->bufferSize - this->frameGPUReadOffset; @@ -45,29 +40,28 @@ namespace love size_t unmap(size_t) { - GX2RUnlockBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); + GX2RUnlockBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); if (this->usage == BufferUsage::BUFFERUSAGE_VERTEX) - GX2RSetAttributeBuffer(this->buffer, 0, this->buffer->elemSize, 0); + GX2RSetAttributeBuffer(&this->buffer, 0, this->buffer.elemSize, 0); return this->index; } ~StreamBuffer() { - GX2RDestroyBufferEx(this->buffer, GX2R_RESOURCE_BIND_NONE); - delete this->buffer; + GX2RDestroyBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); } ptrdiff_t getHandle() const override { - return (ptrdiff_t)this->buffer; + 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; + GX2RBuffer buffer; }; } // namespace love diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index fa2a87eb..e1d5718d 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -112,8 +112,6 @@ namespace love GX2Init(attributes); - this->createFramebuffers(); - this->state = (GX2ContextState*)memalign(GX2_CONTEXT_STATE_ALIGNMENT, sizeof(GX2ContextState)); if (!this->state) @@ -122,8 +120,10 @@ namespace love GX2SetupContextStateEx(this->state, false); GX2SetContextState(this->state); + this->createFramebuffers(); + GX2SetDepthOnlyControl(false, false, GX2_COMPARE_FUNC_ALWAYS); - GX2SetAlphaTest(false, GX2_COMPARE_FUNC_ALWAYS, 0.0f); + // GX2SetAlphaTest(false, GX2_COMPARE_FUNC_ALWAYS, 0.0f); GX2SetColorControl(GX2_LOGIC_OP_COPY, 0xFF, false, true); GX2SetSwapInterval(1); @@ -157,12 +157,7 @@ namespace love 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); - } + this->targets[index].create(info[index]); } GX2ColorBuffer& GX2::getInternalBackbuffer() @@ -190,7 +185,7 @@ namespace love { if (!this->inFrame) { - GX2SetContextState(this->state); + // GX2SetContextState(this->state); this->inFrame = true; } } @@ -228,6 +223,7 @@ namespace love if (bindingModified) { + // this->targets[love::currentScreen].useState(); GX2SetColorBuffer(target, GX2_RENDER_TARGET_0); this->setMode(target->surface.width, target->surface.height); } @@ -323,10 +319,7 @@ namespace love } if (Keyboard()->hasTextInput()) - { nn::swkbd::DrawDRC(); - GX2SetContextState(this->state); - } for (auto& target : this->targets) target.copyScanBuffer(); diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index 207425b0..91fe7262 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -373,13 +373,14 @@ namespace love gx2.prepareDraw(this); gx2.bindTextureToUnit(command.texture, 0); - auto* buffer = (GX2RBuffer*)command.indexBuffer->getHandle(); - const size_t offset = command.indexBufferOffset; - - const auto mode = GX2::getPrimitiveType(command.primitiveType); - const auto indexType = GX2::getIndexType(command.indexType); - - GX2RDrawIndexed(mode, buffer, indexType, command.indexCount, offset, 0, command.instanceCount); + 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; } diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index 101daf66..259e694e 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -108,7 +108,7 @@ namespace love auto transform = graphics->getTransform(); - GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, uniform, sizeof(Uniform)); + GX2Invalidate(GX2_INVALIDATE_MODE_UNIFORM_BLOCK, uniform, sizeof(Uniform)); GX2SetVertexUniformBlock(this->uniform.location, sizeof(Uniform), uniform); } @@ -119,18 +119,18 @@ namespace love void Shader::attach() { - if (current != this) - { - Graphics::flushBatchedDrawsGlobal(); + // if (current != this) + // { + Graphics::flushBatchedDrawsGlobal(); - GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); + GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); - GX2SetFetchShader(&this->program.fetchShader); - GX2SetVertexShader(this->program.vertexShader); - GX2SetPixelShader(this->program.pixelShader); + GX2SetFetchShader(&this->program.fetchShader); + GX2SetVertexShader(this->program.vertexShader); + GX2SetPixelShader(this->program.pixelShader); - current = this; - } + current = this; + // } } bool Shader::validate(const char* filepath, std::string& error) diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 41c35fd7..74e2c651 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -4,6 +4,8 @@ #include "common/Console.hpp" #include "common/screen.hpp" +#include + namespace love { GraphicsBase::GraphicsBase(const char* name) : @@ -991,8 +993,8 @@ namespace love for (int index = 0; index < points; ++index, phi += shift) { - coords[index].x = x + a * std::cosf(phi); - coords[index].y = y + b * std::sinf(phi); + coords[index].x = x + a * cosf(phi); + coords[index].y = y + b * sinf(phi); } coords[points] = coords[0]; diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index a57a68cb..33122efb 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -872,7 +872,7 @@ static void luax_checktexturesettings(lua_State* L, int index, bool optional, bo lua_rawgeti(L, -1, index); const char* formatName = luaL_checkstring(L, -1); - PixelFormat format; + PixelFormat format = PIXELFORMAT_UNKNOWN; if (!love::getConstant(formatName, format)) luax_enumerror(L, "pixel format", formatName); From fc07b10da624e1a7d62384083166261ecdb4f022 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 29 Sep 2024 12:47:00 -0400 Subject: [PATCH 33/49] remove debug scripts because no need --- debug/debug.py | 79 -------------------------------------------------- debug/meta.py | 56 ----------------------------------- 2 files changed, 135 deletions(-) delete mode 100644 debug/debug.py delete mode 100644 debug/meta.py diff --git a/debug/debug.py b/debug/debug.py deleted file mode 100644 index c6d483c6..00000000 --- a/debug/debug.py +++ /dev/null @@ -1,79 +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)) - # tcp_socket.settimeout(3) - 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 - except TimeoutError: - print("Connection timed out") - 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 dd9dc76a..00000000 --- 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() From 902f6e256293b32053716ad50d021fff2d6a78af Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sun, 29 Sep 2024 15:22:47 -0400 Subject: [PATCH 34/49] we have the gamepad rendering --- .../include/driver/display/Framebuffer.hpp | 6 +++--- .../cafe/include/driver/display/Uniform.hpp | 14 +++++++++++++ .../source/driver/display/Framebuffer.cpp | 10 +++++----- platform/cafe/source/driver/display/GX2.cpp | 7 +++---- .../cafe/source/modules/graphics/Shader.cpp | 20 +++++++++---------- 5 files changed, 35 insertions(+), 22 deletions(-) diff --git a/platform/cafe/include/driver/display/Framebuffer.hpp b/platform/cafe/include/driver/display/Framebuffer.hpp index b00ccd16..cd6ad0f1 100644 --- a/platform/cafe/include/driver/display/Framebuffer.hpp +++ b/platform/cafe/include/driver/display/Framebuffer.hpp @@ -56,15 +56,15 @@ namespace love private: static constexpr auto FORMAT = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; - static constexpr auto BUFFER_MODE = GX2_BUFFERING_MODE_DOUBLE; + static constexpr auto BUFFER_MODE = GX2_BUFFERING_MODE_SINGLE; static constexpr auto INVALIDATE_COLOR_BUFFER = GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_COLOR_BUFFER; GX2ColorBuffer target; GX2DepthBuffer depth; - uint8_t mode; - uint8_t id; + uint8_t renderMode; + GX2ScanTarget id; Uniform* uniform; diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp index a73ae343..34490afd 100644 --- a/platform/cafe/include/driver/display/Uniform.hpp +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -13,5 +13,19 @@ namespace love { glm::mat4 modelView; glm::mat4 projection; + + void debug() + { + const auto count = sizeof(glm::mat4) / sizeof(uint32_t); + + uint32_t* model = (uint32_t*)glm::value_ptr(this->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(this->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/driver/display/Framebuffer.cpp b/platform/cafe/source/driver/display/Framebuffer.cpp index e0130bf1..bf7c0349 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -32,12 +32,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); } @@ -98,7 +98,7 @@ namespace love GX2CalcTVSize(mode, FORMAT, BUFFER_MODE, &this->scanBufferSize, &unknown); GX2SetTVScale(info.width, info.height); - this->mode = mode; + this->renderMode = mode; } else { @@ -107,10 +107,10 @@ namespace love GX2CalcDRCSize(mode, FORMAT, BUFFER_MODE, &this->scanBufferSize, &unknown); GX2SetDRCScale(info.width, info.height); - this->mode = mode; + this->renderMode = mode; } - this->id = info.id; + this->id = (GX2ScanTarget)info.id; this->width = info.width; this->height = info.height; diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index e1d5718d..7cb0fe71 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -185,7 +185,7 @@ namespace love { if (!this->inFrame) { - // GX2SetContextState(this->state); + GX2SetContextState(this->state); this->inFrame = true; } } @@ -223,7 +223,6 @@ namespace love if (bindingModified) { - // this->targets[love::currentScreen].useState(); GX2SetColorBuffer(target, GX2_RENDER_TARGET_0); this->setMode(target->surface.width, target->surface.height); } @@ -318,8 +317,8 @@ namespace love this->inFrame = false; } - if (Keyboard()->hasTextInput()) - nn::swkbd::DrawDRC(); + // if (Keyboard()->hasTextInput()) + // nn::swkbd::DrawDRC(); for (auto& target : this->targets) target.copyScanBuffer(); diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index 259e694e..101daf66 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -108,7 +108,7 @@ namespace love auto transform = graphics->getTransform(); - GX2Invalidate(GX2_INVALIDATE_MODE_UNIFORM_BLOCK, uniform, sizeof(Uniform)); + GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, uniform, sizeof(Uniform)); GX2SetVertexUniformBlock(this->uniform.location, sizeof(Uniform), uniform); } @@ -119,18 +119,18 @@ namespace love void Shader::attach() { - // if (current != this) - // { - Graphics::flushBatchedDrawsGlobal(); + if (current != this) + { + Graphics::flushBatchedDrawsGlobal(); - GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); + GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); - GX2SetFetchShader(&this->program.fetchShader); - GX2SetVertexShader(this->program.vertexShader); - GX2SetPixelShader(this->program.pixelShader); + GX2SetFetchShader(&this->program.fetchShader); + GX2SetVertexShader(this->program.vertexShader); + GX2SetPixelShader(this->program.pixelShader); - current = this; - // } + current = this; + } } bool Shader::validate(const char* filepath, std::string& error) From 15b0fcd8e22b059aa379e759b64d9fd60cf9eccf Mon Sep 17 00:00:00 2001 From: TurtleP Date: Tue, 29 Oct 2024 16:48:38 -0400 Subject: [PATCH 35/49] push changes, wii u renders on debug build but not release --- .gitignore | 1 + CMakeLists.txt | 2 +- include/common/Exception.hpp | 21 + include/common/screen.hpp | 15 +- include/driver/EventQueue.tcc | 2 +- include/modules/graphics/Font.hpp | 5 +- include/modules/graphics/Shader.tcc | 4 + include/modules/graphics/freetype/Font.hpp | 20 + include/modules/graphics/wrap_Graphics.hpp | 4 + include/modules/joystick/Joystick.tcc | 12 +- include/modules/joystick/JoystickModule.hpp | 2 +- include/modules/joystick/wrap_Joystick.hpp | 2 +- platform/cafe/CMakeLists.txt | 3 +- platform/cafe/include/driver/EventQueue.hpp | 6 +- .../include/driver/display/Framebuffer.hpp | 6 +- platform/cafe/include/driver/display/GX2.hpp | 2 + .../cafe/include/driver/display/Uniform.hpp | 15 +- .../include/modules/graphics/Graphics.hpp | 2 + .../cafe/include/modules/graphics/Texture.hpp | 2 +- .../include/modules/joystick/Joystick.hpp | 168 ------- .../modules/joystick/kpad/Joystick.hpp | 203 +++++++++ .../modules/joystick/vpad/Joystick.hpp | 122 +++++ platform/cafe/source/boot.cpp | 3 + platform/cafe/source/driver/EventQueue.cpp | 12 +- .../source/driver/display/Framebuffer.cpp | 4 +- platform/cafe/source/driver/display/GX2.cpp | 51 +-- .../cafe/source/driver/display/Uniform.cpp | 19 +- .../cafe/source/modules/graphics/Graphics.cpp | 11 +- .../cafe/source/modules/graphics/Shader.cpp | 11 +- .../cafe/source/modules/graphics/Texture.cpp | 30 +- .../cafe/source/modules/joystick/Joystick.cpp | 421 ------------------ .../modules/joystick/JoystickModule.cpp | 8 +- .../source/modules/joystick/kpad/Joystick.cpp | 404 +++++++++++++++++ .../source/modules/joystick/vpad/Joystick.cpp | 290 ++++++++++++ .../ctr/include/driver/display/citro3d.hpp | 3 + .../ctr/source/modules/graphics/Shader.cpp | 2 +- source/modules/audio/Source.cpp | 1 + source/modules/event/Event.cpp | 4 +- source/modules/graphics/FontBase.cpp | 19 +- source/modules/graphics/Graphics.cpp | 8 +- source/modules/graphics/Shader.cpp | 12 +- source/modules/graphics/TextBatch.cpp | 8 +- source/modules/graphics/Texture.cpp | 8 +- .../modules/graphics/{ => freetype}/Font.cpp | 75 ++-- source/modules/graphics/wrap_Graphics.cpp | 39 +- source/modules/image/ImageData.cpp | 6 +- source/modules/image/magpie/JPGHandler.cpp | 2 +- source/modules/image/magpie/PNGHandler.cpp | 4 +- source/modules/joystick/JoystickModule.cpp | 2 +- source/modules/joystick/wrap_Joystick.cpp | 30 +- source/modules/love/love.cpp | 28 ++ source/modules/love/scripts/callbacks.lua | 15 +- 52 files changed, 1376 insertions(+), 773 deletions(-) create mode 100644 include/modules/graphics/freetype/Font.hpp delete mode 100644 platform/cafe/include/modules/joystick/Joystick.hpp create mode 100644 platform/cafe/include/modules/joystick/kpad/Joystick.hpp create mode 100644 platform/cafe/include/modules/joystick/vpad/Joystick.hpp delete mode 100644 platform/cafe/source/modules/joystick/Joystick.cpp create mode 100644 platform/cafe/source/modules/joystick/kpad/Joystick.cpp create mode 100644 platform/cafe/source/modules/joystick/vpad/Joystick.cpp rename source/modules/graphics/{ => freetype}/Font.cpp (51%) diff --git a/.gitignore b/.gitignore index 4cbcdd63..bd4ddc85 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ target/ *.wuhb *.py __pycache__/ +todo.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d37dc00..aff940dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,7 +169,7 @@ if (NINTENDO_WIIU) source/modules/image/magpie/PNGHandler.cpp source/modules/font/freetype/Font.cpp source/modules/font/freetype/TrueTypeRasterizer.cpp - source/modules/graphics/Font.cpp + source/modules/graphics/freetype/Font.cpp ) add_library(ddsparse diff --git a/include/common/Exception.hpp b/include/common/Exception.hpp index 7b526535..3657d731 100644 --- a/include/common/Exception.hpp +++ b/include/common/Exception.hpp @@ -27,4 +27,25 @@ namespace love private: std::string message; }; + +#if !(__DEBUG__) + #define LOG() +#else + #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/screen.hpp b/include/common/screen.hpp index b790d314..6e87adf3 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(); @@ -56,17 +56,4 @@ namespace love currentScreen = id; } - - inline void nextScreen() - { - const auto& info = getScreenInfo(); - - if (currentScreen == INVALID_SCREEN) - currentScreen = DEFAULT_SCREEN; - else - currentScreen = (Screen)((int)currentScreen + 1); - - if (currentScreen >= (Screen)info.size()) - currentScreen = DEFAULT_SCREEN; - } } // namespace love diff --git a/include/driver/EventQueue.tcc b/include/driver/EventQueue.tcc index 062c19cc..bf2d5885 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/modules/graphics/Font.hpp b/include/modules/graphics/Font.hpp index 7231b224..93b134e0 100644 --- a/include/modules/graphics/Font.hpp +++ b/include/modules/graphics/Font.hpp @@ -1,3 +1,5 @@ +#pragma once + #include "modules/graphics/Font.tcc" namespace love @@ -10,8 +12,5 @@ namespace love virtual void createTexture() override; bool loadVolatile() override; - - private: - virtual const FontBase::Glyph& addGlyph(TextShaper::GlyphIndex glyphIndex) override; }; } // namespace love diff --git a/include/modules/graphics/Shader.tcc b/include/modules/graphics/Shader.tcc index e1d40723..9d61d13f 100644 --- a/include/modules/graphics/Shader.tcc +++ b/include/modules/graphics/Shader.tcc @@ -31,6 +31,8 @@ namespace love static ShaderBase* current; static ShaderBase* standardShaders[STANDARD_MAX_ENUM]; + ShaderBase(StandardShader type); + virtual ~ShaderBase(); virtual void attach() = 0; @@ -38,5 +40,7 @@ namespace love static void attachDefault(StandardShader type); static bool isDefaultActive(); + + StandardShader shaderType; }; } // namespace love diff --git a/include/modules/graphics/freetype/Font.hpp b/include/modules/graphics/freetype/Font.hpp new file mode 100644 index 00000000..4e4567c3 --- /dev/null +++ b/include/modules/graphics/freetype/Font.hpp @@ -0,0 +1,20 @@ +#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; + + private: + virtual const FontBase::Glyph& addGlyph(TextShaper::GlyphIndex glyphIndex) override; + }; +} // namespace love diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index ad059cb6..ac7a9cbe 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -199,6 +199,8 @@ namespace Wrap_Graphics int getScreens(lua_State* L); + int getActiveScreen(lua_State* L); + int setActiveScreen(lua_State* L); int is3D(lua_State* L); @@ -211,5 +213,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/joystick/Joystick.tcc b/include/modules/joystick/Joystick.tcc index ae02ecaf..de910950 100644 --- a/include/modules/joystick/Joystick.tcc +++ b/include/modules/joystick/Joystick.tcc @@ -53,6 +53,8 @@ namespace love GAMEPAD_BUTTON_DPAD_DOWN, GAMEPAD_BUTTON_DPAD_LEFT, GAMEPAD_BUTTON_DPAD_RIGHT, + GAMEPAD_BUTTON_MISC1, + GAMEPAD_BUTTON_MISC2, GAMEPAD_BUTTON_MAX_ENUM }; @@ -202,7 +204,9 @@ namespace love { "dpup", GAMEPAD_BUTTON_DPAD_UP }, { "dpdown", GAMEPAD_BUTTON_DPAD_DOWN }, { "dpleft", GAMEPAD_BUTTON_DPAD_LEFT }, - { "dpright", GAMEPAD_BUTTON_DPAD_RIGHT } + { "dpright", GAMEPAD_BUTTON_DPAD_RIGHT }, + { "misc1", GAMEPAD_BUTTON_MISC1 }, + { "misc2", GAMEPAD_BUTTON_MISC2 } ); STRINGMAP_DECLARE(GamepadAxes, GamepadAxis, @@ -260,11 +264,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 cb04b871..373340e9 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 61ac75a7..827c7e6d 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 { diff --git a/platform/cafe/CMakeLists.txt b/platform/cafe/CMakeLists.txt index 78104379..02e5d280 100644 --- a/platform/cafe/CMakeLists.txt +++ b/platform/cafe/CMakeLists.txt @@ -33,7 +33,8 @@ 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/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/include/driver/EventQueue.hpp b/platform/cafe/include/driver/EventQueue.hpp index 4c5524e6..c183fc3e 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 cd6ad0f1..0d8eda37 100644 --- a/platform/cafe/include/driver/display/Framebuffer.hpp +++ b/platform/cafe/include/driver/display/Framebuffer.hpp @@ -49,14 +49,14 @@ namespace love void copyScanBuffer(); - void useState() + operator GX2ContextState*() { - GX2SetContextState(this->state); + return this->state; } private: static constexpr auto FORMAT = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; - static constexpr auto BUFFER_MODE = GX2_BUFFERING_MODE_SINGLE; + static constexpr auto BUFFER_MODE = GX2_BUFFERING_MODE_DOUBLE; static constexpr auto INVALIDATE_COLOR_BUFFER = GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_COLOR_BUFFER; diff --git a/platform/cafe/include/driver/display/GX2.hpp b/platform/cafe/include/driver/display/GX2.hpp index 71e9b602..19479701 100644 --- a/platform/cafe/include/driver/display/GX2.hpp +++ b/platform/cafe/include/driver/display/GX2.hpp @@ -74,6 +74,8 @@ namespace love void setMode(int width, int height); + void copyCurrentScanBuffer(); + // clang-format off ENUMMAP_DECLARE(PixelFormats, PixelFormat, GX2SurfaceFormat, { PIXELFORMAT_R8_UNORM, GX2_SURFACE_FORMAT_UNORM_R8 }, diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp index 34490afd..d210d665 100644 --- a/platform/cafe/include/driver/display/Uniform.hpp +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -7,7 +7,8 @@ namespace love { - glm::mat4 updateMatrix(const glm::mat4& matrix); + // void updateMatrix(glm::mat4& modelView, const Matrix4& matrix); + glm::mat4 createTransposedSwappedMatrix(const Matrix4& matrix); struct Uniform { @@ -26,6 +27,18 @@ namespace love for (size_t index = 0; index < count; index++) std::printf("projection[%zu] = %u\n", index, projection[index]); } + + void update(const Matrix4& matrix) + { + const auto count = sizeof(glm::mat4) / sizeof(uint32_t); + uint32_t* destination = (uint32_t*)glm::value_ptr(this->modelView); + + glm::mat4 sourceMatrix = glm::transpose(glm::make_mat4(matrix.getElements())); + uint32_t* source = (uint32_t*)glm::value_ptr(sourceMatrix); + + for (int i = 0; i < count; ++i) + destination[i] = __builtin_bswap32(source[i]); + } }; } // namespace love diff --git a/platform/cafe/include/modules/graphics/Graphics.hpp b/platform/cafe/include/modules/graphics/Graphics.hpp index 76d0f10e..903c4e4e 100644 --- a/platform/cafe/include/modules/graphics/Graphics.hpp +++ b/platform/cafe/include/modules/graphics/Graphics.hpp @@ -62,6 +62,8 @@ namespace love GX2ColorBuffer getInternalBackbuffer() const; + void copyCurrentScanBuffer(); + // clang-format off virtual TextureBase* newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data = nullptr) override; // clang-format on diff --git a/platform/cafe/include/modules/graphics/Texture.hpp b/platform/cafe/include/modules/graphics/Texture.hpp index e1440e5b..3a4c25e8 100644 --- a/platform/cafe/include/modules/graphics/Texture.hpp +++ b/platform/cafe/include/modules/graphics/Texture.hpp @@ -41,6 +41,6 @@ namespace love GX2Texture* texture = nullptr; GX2ColorBuffer* target = nullptr; - GX2Sampler* sampler = 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 935515d2..00000000 --- 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 00000000..c534168e --- /dev/null +++ b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp @@ -0,0 +1,203 @@ +#pragma once + +#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; + + 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_LEFTSHOULDER, WPAD_NUNCHUK_BUTTON_C } + ); + + 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 00000000..6e87e5e0 --- /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->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 + }; + + // 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 vpadStatus; + VPADReadError vpadError; + + mutable struct State + { + uint32_t pressed; + uint32_t released; + uint32_t held; + } state; + }; + } // namespace vpad +} // namespace love diff --git a/platform/cafe/source/boot.cpp b/platform/cafe/source/boot.cpp index 281e88e8..927589b1 100644 --- a/platform/cafe/source/boot.cpp +++ b/platform/cafe/source/boot.cpp @@ -75,6 +75,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 b274d5a1..bf4dcc01 100644 --- a/platform/cafe/source/driver/EventQueue.cpp +++ b/platform/cafe/source/driver/EventQueue.cpp @@ -60,13 +60,13 @@ namespace love continue; if (joystick->getGamepadType() == GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD) - this->gamepad = (Joystick*)joystick; + this->gamepad = (vpad::Joystick*)joystick; 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); @@ -75,11 +75,11 @@ namespace love this->sendGamepadButtonEvent(SUBTYPE_GAMEPADUP, 0, 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)); + float value = joystick->getAxis(JoystickBase::GamepadAxis(input)); this->sendGamepadAxisEvent(0, input, value); } } diff --git a/platform/cafe/source/driver/display/Framebuffer.cpp b/platform/cafe/source/driver/display/Framebuffer.cpp index bf7c0349..5e5a0369 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -1,3 +1,5 @@ +#include "common/Exception.hpp" + #include "driver/display/Framebuffer.hpp" #include "driver/display/utility.hpp" @@ -76,7 +78,7 @@ namespace love void Framebuffer::copyScanBuffer() { - GX2CopyColorBufferToScanBuffer(&this->target, (GX2ScanTarget)this->id); + GX2CopyColorBufferToScanBuffer(&this->target, this->id); } void Framebuffer::create(const ScreenInfo& info) diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index 7cb0fe71..bd3037a7 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -74,6 +74,8 @@ namespace love int GX2::onForegroundReleased() { + GX2DrawDone(); + auto foregroundHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_FG); auto memOneHeap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1); @@ -145,8 +147,6 @@ namespace love this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; this->uniform = (Uniform*)memalign(GX2_UNIFORM_BLOCK_ALIGNMENT, sizeof(Uniform)); - - love::currentScreen = (Screen)0; this->bindFramebuffer(&this->targets[0].get()); this->initialized = true; @@ -183,11 +183,21 @@ namespace love void GX2::ensureInFrame() { + GX2SetContextState(this->state); + if (!this->inFrame) - { - GX2SetContextState(this->state); this->inFrame = true; - } + } + + void GX2::copyCurrentScanBuffer() + { + Graphics::flushBatchedDrawsGlobal(); + Graphics::advanceStreamBuffersGlobal(); + + this->targets[love::currentScreen].copyScanBuffer(); + + GX2Flush(); + GX2WaitForFlip(); } void GX2::clear(const Color& color) @@ -197,12 +207,6 @@ namespace love GX2ClearColor(this->getFramebuffer(), color.r, color.g, color.b, color.a); GX2SetContextState(this->state); - - if (ShaderBase::current != nullptr) - { - auto* shader = (Shader*)ShaderBase::current; - shader->attach(); - } } void GX2::clearDepthStencil(int depth, uint8_t mask, double stencil) @@ -239,7 +243,8 @@ namespace love void GX2::setSamplerState(TextureBase* texture, const SamplerState& state) { - auto sampler = (GX2Sampler*)texture->getSamplerHandle(); + auto* sampler = (GX2Sampler*)texture->getSamplerHandle(); + GX2InitSampler(sampler, GX2_TEX_CLAMP_MODE_WRAP, GX2_TEX_XY_FILTER_MODE_LINEAR); GX2TexXYFilterMode minFilter; @@ -293,6 +298,9 @@ namespace love auto* sampler = (GX2Sampler*)texture->getSamplerHandle(); + if (sampler == nullptr) + return; + this->bindTextureToUnit(handle, sampler, unit); } @@ -307,25 +315,8 @@ namespace love void GX2::present() { - if (this->inFrame) - { - Graphics::flushBatchedDrawsGlobal(); - - GX2DrawDone(); - - Graphics::advanceStreamBuffersGlobal(); - this->inFrame = false; - } - - // if (Keyboard()->hasTextInput()) - // nn::swkbd::DrawDRC(); - - for (auto& target : this->targets) - target.copyScanBuffer(); - + this->inFrame = false; GX2SwapScanBuffers(); - GX2Flush(); - GX2WaitForFlip(); } void GX2::setViewport(const Rect& rect) diff --git a/platform/cafe/source/driver/display/Uniform.cpp b/platform/cafe/source/driver/display/Uniform.cpp index aef3733e..ed28100a 100644 --- a/platform/cafe/source/driver/display/Uniform.cpp +++ b/platform/cafe/source/driver/display/Uniform.cpp @@ -1,21 +1,22 @@ #include "driver/display/Uniform.hpp" -#include - namespace love { - glm::mat4 updateMatrix(const glm::mat4& matrix) + glm::mat4 createTransposedSwappedMatrix(const Matrix4& matrix) { - glm::mat4 out(1.0f); + const uint32_t count = sizeof(glm::mat4) / sizeof(uint32_t); - uint32_t* destination = (uint32_t*)glm::value_ptr(out); - const uint32_t* source = (const uint32_t*)glm::value_ptr(matrix); + // Transpose the input matrix to convert from row-major to column-major + glm::mat4 sourceMatrix = glm::transpose(glm::make_mat4(matrix.getElements())); - const size_t count = sizeof(glm::mat4) / sizeof(uint32_t); + // Create a new matrix to hold the endian-swapped version + glm::mat4 resultMatrix; + uint32_t* destination = (uint32_t*)glm::value_ptr(resultMatrix); + const uint32_t* source = (const uint32_t*)glm::value_ptr(sourceMatrix); - for (size_t i = 0; i < count; i++) + for (int i = 0; i < count; ++i) destination[i] = __builtin_bswap32(source[i]); - return out; + return resultMatrix; } } // namespace love diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index 91fe7262..ff60cdb0 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -8,6 +8,8 @@ #include "modules/graphics/Texture.hpp" #include +#include +#include #include namespace love @@ -167,6 +169,11 @@ namespace love gx2.bindFramebuffer(&gx2.getInternalBackbuffer()); } + void Graphics::copyCurrentScanBuffer() + { + gx2.copyCurrentScanBuffer(); + } + void Graphics::present(void* screenshotCallbackData) { if (!this->isActive()) @@ -179,6 +186,7 @@ namespace love this->drawCalls = 0; this->drawCallsBatched = 0; + Shader::shaderSwitches = 0; } void Graphics::setScissor(const Rect& scissor) @@ -329,7 +337,7 @@ namespace love this->restoreState(this->states.back()); - for (int index = 0; index < 1; index++) + for (int index = 0; index < ShaderBase::STANDARD_MAX_ENUM; index++) { auto type = (Shader::StandardShader)index; @@ -372,6 +380,7 @@ namespace love { 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(); diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index 101daf66..bcd6e2c0 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -1,5 +1,7 @@ #include "modules/graphics/Shader.hpp" +#include "common/screen.hpp" + #include #include #include @@ -14,7 +16,7 @@ namespace love { - Shader::Shader(StandardShader type) : uniform {} + Shader::Shader(StandardShader type) : ShaderBase(type), uniform {} { std::string error; @@ -73,6 +75,9 @@ namespace love 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) @@ -106,7 +111,8 @@ namespace love if (current != this) return; - auto transform = graphics->getTransform(); + auto& transform = graphics->getTransform(); + // uniform->update(transform); GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, uniform, sizeof(Uniform)); GX2SetVertexUniformBlock(this->uniform.location, sizeof(Uniform), uniform); @@ -130,6 +136,7 @@ namespace love GX2SetPixelShader(this->program.pixelShader); current = this; + shaderSwitches++; } } diff --git a/platform/cafe/source/modules/graphics/Texture.cpp b/platform/cafe/source/modules/graphics/Texture.cpp index afd1fdda..640f7f78 100644 --- a/platform/cafe/source/modules/graphics/Texture.cpp +++ b/platform/cafe/source/modules/graphics/Texture.cpp @@ -2,6 +2,7 @@ #include "modules/graphics/Texture.hpp" +#include #include #include @@ -15,7 +16,7 @@ namespace love if (!texture) throw love::Exception("Failed to create GX2Texture."); - std::memset(texture, 0, sizeof(GX2Texture)); + std::memset(&texture->surface, 0, sizeof(GX2Surface)); texture->surface.use = GX2_SURFACE_USE_TEXTURE; texture->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D; @@ -33,7 +34,7 @@ namespace love texture->surface.aa = GX2_AA_MODE1X; texture->surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED; texture->viewFirstMip = 0; - texture->viewNumMips = 1; + 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); @@ -53,7 +54,7 @@ namespace love Texture::Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data) : TextureBase(graphics, settings, data), slices(settings.type), - sampler(nullptr) + sampler {} { if (data != nullptr) slices = *data; @@ -93,7 +94,7 @@ namespace love const auto faces = (this->textureType == TEXTURE_CUBE) ? 6 : 1; int slices = this->getDepth(mip) * this->layers * faces; - memorySize += getPixelFormatSliceSize(this->format, width, height) * slices; + memorySize += getPixelFormatSliceSize(this->format, width, height, false) * slices; } this->setGraphicsMemorySize(memorySize); @@ -107,7 +108,7 @@ namespace love delete this->texture; if (this->target != nullptr) - delete this->texture; + delete this->target; this->setGraphicsMemorySize(0); } @@ -162,6 +163,11 @@ namespace love { } } + + this->setSamplerState(this->samplerState); + + if (this->slices.getMipmapCount() <= 1 && this->getMipmapsMode() != MIPMAPS_NONE) + this->generateMipmaps(); } void Texture::setSamplerState(const SamplerState& state) @@ -179,16 +185,18 @@ namespace love size_t pixelSize = getPixelFormatBlockSize(this->format); - for (uint32_t y = 0; y < rect.h; y++) + for (uint32_t y = 0; y < (uint32_t)rect.h; y++) { - const auto row = (y * rect.w * pixelSize); - const auto dest = (rect.x + (y + rect.y) * pitch) * pixelSize; + const auto srcRow = (y * rect.w * pixelSize); + const auto destRow = (rect.x + (y + rect.y) * pitch) * pixelSize; - std::memcpy(destination + dest, source + row, rect.w * pixelSize); + std::memcpy(destination + destRow, source + srcRow, rect.w * pixelSize); } const auto imageSize = this->texture->surface.imageSize; - GX2Invalidate(GX2_INVALIDATE_MODE_CPU_TEXTURE, destination, imageSize); + + GX2Invalidate(GX2_INVALIDATE_MODE_CPU_TEXTURE, this->texture->surface.image, imageSize); + GX2Flush(); } void Texture::generateMipmapsInternal() @@ -206,6 +214,6 @@ namespace love ptrdiff_t Texture::getSamplerHandle() const { - return (ptrdiff_t)(&this->sampler); + 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 96d52901..00000000 --- a/platform/cafe/source/modules/joystick/Joystick.cpp +++ /dev/null @@ -1,421 +0,0 @@ -#include "modules/joystick/Joystick.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 3391a1e8..6cc8b846 100644 --- a/platform/cafe/source/modules/joystick/JoystickModule.cpp +++ b/platform/cafe/source/modules/joystick/JoystickModule.cpp @@ -3,6 +3,9 @@ #include #include +#include "modules/joystick/kpad/Joystick.hpp" +#include "modules/joystick/vpad/Joystick.hpp" + namespace love::joystick { int getJoystickCount() @@ -37,6 +40,9 @@ namespace love::joystick */ 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 00000000..84d53d20 --- /dev/null +++ b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp @@ -0,0 +1,404 @@ +#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 = 0; + + WPADExtensionType extension; + if (WPADProbe(WPADChan(index - 1), &extension) < 0) + return false; + + switch (extension) + { + default: + 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_NUNCHUCK; + break; + } + case WPAD_EXT_PRO_CONTROLLER: + { + this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_U_PRO; + 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; + + return this->status.classic.hold & WPAD_CLASSIC_BUTTON_ZR ? 1.0f : 0.0f; + } + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + { + if (axis == GAMEPAD_AXIS_TRIGGERLEFT) + return this->status.nunchuk.hold & WPAD_NUNCHUK_BUTTON_Z ? 1.0f : 0.0f; + + 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; + + return this->status.pro.hold & WPAD_PRO_TRIGGER_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; + } + + 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_NUNCHUCK: + break; + 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_NUNCHUCK: + break; + 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_NUNCHUCK: + 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); + + return data; + } + } // 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 00000000..6072a06e --- /dev/null +++ b/platform/cafe/source/modules/joystick/vpad/Joystick.cpp @@ -0,0 +1,290 @@ +#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->vpadStatus, 1, &this->vpadError); + + const auto& status = this->vpadStatus; + if (this->vpadError != 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->vpadError) + { + 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->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; + } + + 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: + { + 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 vpad +} // namespace love diff --git a/platform/ctr/include/driver/display/citro3d.hpp b/platform/ctr/include/driver/display/citro3d.hpp index 603095f8..461f21bb 100644 --- a/platform/ctr/include/driver/display/citro3d.hpp +++ b/platform/ctr/include/driver/display/citro3d.hpp @@ -93,6 +93,9 @@ namespace love void ensureInFrame(); + void copyCurrentScanBuffer() + {} + static GPU_TEXTURE_MODE_PARAM getTextureType(TextureType type); static GPU_Primitive_t getPrimitiveType(PrimitiveType type); diff --git a/platform/ctr/source/modules/graphics/Shader.cpp b/platform/ctr/source/modules/graphics/Shader.cpp index 5a008909..40d39002 100644 --- a/platform/ctr/source/modules/graphics/Shader.cpp +++ b/platform/ctr/source/modules/graphics/Shader.cpp @@ -7,7 +7,7 @@ namespace love { - Shader::Shader() : uniforms {} + Shader::Shader() : ShaderBase(STANDARD_DEFAULT), uniforms {} { this->loadVolatile(); } diff --git a/source/modules/audio/Source.cpp b/source/modules/audio/Source.cpp index 439da4e7..5445181f 100644 --- a/source/modules/audio/Source.cpp +++ b/source/modules/audio/Source.cpp @@ -98,6 +98,7 @@ namespace love } Source::Source(const Source& other) : + Object(other), sourceType(other.sourceType), pool(other.pool), valid(false), diff --git a/source/modules/event/Event.cpp b/source/modules/event/Event.cpp index c48b19f8..2e5bffad 100644 --- a/source/modules/event/Event.cpp +++ b/source/modules/event/Event.cpp @@ -175,7 +175,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 +194,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 diff --git a/source/modules/graphics/FontBase.cpp b/source/modules/graphics/FontBase.cpp index e5a79ec9..9447d605 100644 --- a/source/modules/graphics/FontBase.cpp +++ b/source/modules/graphics/FontBase.cpp @@ -160,6 +160,19 @@ namespace love "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); @@ -184,7 +197,7 @@ namespace love { glyph.vertices[i] = vertices[i]; glyph.vertices[i].x += gd->getBearingX(); - glyph.vertices[i].y += gd->getBearingY(); + glyph.vertices[i].y -= gd->getBearingY(); } this->textureX += width + TEXTURE_PADDING; @@ -440,6 +453,9 @@ namespace love 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) @@ -457,6 +473,7 @@ namespace love command.vertexCount = cmd.vertexCount; command.texture = cmd.texture; command.isFont = true; + command.shaderType = shaderType; auto data = graphics->requestBatchedDraw(command); GlyphVertex* vertexdata = (GlyphVertex*)data.stream; diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 74e2c651..33a015d0 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -346,7 +346,7 @@ namespace love state.pushTransform = command.pushTransform; } - if (state.vertexCount == 0) + if (state.lastVertexCount == 0) { if (ShaderBase::isDefaultActive()) ShaderBase::attachDefault(state.shaderType); @@ -455,7 +455,7 @@ namespace love if ((state.lastIndexCount == 0 && state.lastVertexCount == 0) || state.flushing) return; - if ((state.vertexCount == 0 && state.indexCount == 0) || state.flushing) + if ((state.indexCount == 0 && state.vertexCount == 0) || state.flushing) return; VertexAttributes attributes {}; @@ -478,7 +478,7 @@ namespace love if (attributes.enableBits == 0) return; - // state.flushing = true; + state.flushing = true; // auto originalColor = this->getColor(); // if (attributes.isEnabled(ATTRIB_COLOR)) @@ -498,6 +498,7 @@ namespace love command.indexBufferOffset = state.indexBuffer->unmap(usedSizes[1]); command.texture = state.texture; command.isFont = state.isFont; + this->draw(command); state.indexBufferMap = MapInfo(); @@ -527,6 +528,7 @@ namespace love state.lastVertexCount = 0; state.lastIndexCount = 0; + state.flushing = false; } void GraphicsBase::flushBatchedDrawsGlobal() diff --git a/source/modules/graphics/Shader.cpp b/source/modules/graphics/Shader.cpp index 0638b69b..71a96efb 100644 --- a/source/modules/graphics/Shader.cpp +++ b/source/modules/graphics/Shader.cpp @@ -1,4 +1,4 @@ -#include "common/Console.hpp" +#include "common/Exception.hpp" #include "modules/graphics/Shader.tcc" @@ -9,6 +9,9 @@ namespace love int ShaderBase::shaderSwitches = 0; + ShaderBase::ShaderBase(StandardShader type) : shaderType(type) + {} + ShaderBase::~ShaderBase() { for (int index = 0; index < STANDARD_MAX_ENUM; index++) @@ -31,12 +34,7 @@ namespace love return; } - if constexpr (!Console::is(Console::CAFE)) - { - if (current != defaultShader) - defaultShader->attach(); - } - else + if (current != defaultShader) defaultShader->attach(); } diff --git a/source/modules/graphics/TextBatch.cpp b/source/modules/graphics/TextBatch.cpp index 26631abe..6d3cd623 100644 --- a/source/modules/graphics/TextBatch.cpp +++ b/source/modules/graphics/TextBatch.cpp @@ -1,5 +1,7 @@ -#include "modules/graphics/TextBatch.hpp" +#include "common/Console.hpp" + #include "modules/graphics/Graphics.tcc" +#include "modules/graphics/TextBatch.hpp" #include @@ -192,6 +194,9 @@ namespace love 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()) @@ -225,6 +230,7 @@ namespace love command.indexMode = TRIANGLEINDEX_QUADS; command.vertexCount = cmd.vertexCount; command.texture = cmd.texture; + command.shaderType = shaderType; command.isFont = true; command.pushTransform = false; diff --git a/source/modules/graphics/Texture.cpp b/source/modules/graphics/Texture.cpp index 79f13c92..3e1427c6 100644 --- a/source/modules/graphics/Texture.cpp +++ b/source/modules/graphics/Texture.cpp @@ -367,6 +367,9 @@ namespace love 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) @@ -383,6 +386,7 @@ namespace love command.indexMode = TRIANGLEINDEX_QUADS; command.vertexCount = 4; command.texture = this; + command.shaderType = shader; BatchedVertexData data = graphics->requestBatchedDraw(command); @@ -407,11 +411,13 @@ namespace love } } + static constexpr bool pow2 = (Console::is(Console::CTR) ? true : false); + void TextureBase::uploadImageData(ImageDataBase* data, int level, int slice, int x, int y) { Rect rectangle = { x, y, data->getWidth(), data->getHeight() }; - const auto size = getPixelFormatSliceSize(this->format, data->getWidth(), data->getHeight()); + const auto size = getPixelFormatSliceSize(this->format, data->getWidth(), data->getHeight(), pow2); this->uploadByteData(data->getData(), size, level, slice, rectangle); } diff --git a/source/modules/graphics/Font.cpp b/source/modules/graphics/freetype/Font.cpp similarity index 51% rename from source/modules/graphics/Font.cpp rename to source/modules/graphics/freetype/Font.cpp index bce6d39c..4f9cac66 100644 --- a/source/modules/graphics/Font.cpp +++ b/source/modules/graphics/freetype/Font.cpp @@ -1,5 +1,5 @@ #include "modules/graphics/Font.hpp" -#include "modules/graphics/Graphics.hpp" +#include "modules/graphics/Graphics.tcc" namespace love { @@ -8,18 +8,29 @@ namespace love this->loadVolatile(); } + bool Font::loadVolatile() + { + textureCacheID++; + glyphs.clear(); + textures.clear(); + createTexture(); + return true; + } + void Font::createTexture() { - auto* graphics = Module::getInstance(Module::M_GRAPHICS); - graphics->flushBatchedDraws(); + 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; - if ((nextSize.width > size.width || nextSize.height > size.height) && !this->textures.empty()) + const bool biggerWidth = nextSize.width > size.width; + const bool biggerHeight = nextSize.height > size.height; + + if ((biggerWidth || biggerHeight) && !this->textures.empty()) { recreateTexture = true; size = nextSize; @@ -27,45 +38,44 @@ namespace love } TextureBase::Settings settings {}; - settings.format = pixelFormat; + settings.format = this->pixelFormat; settings.width = size.width; settings.height = size.height; - texture = graphics->newTexture(settings, nullptr); - texture->setSamplerState(samplerState); + texture = module->newTexture(settings, nullptr); + texture->setSamplerState(this->samplerState); { - auto dataSize = getPixelFormatSliceSize(pixelFormat, size.width, size.height); - auto pixelCount = size.width * size.height; + size_t dataSize = getPixelFormatSliceSize(this->pixelFormat, size.width, size.height); + size_t pixelCount = size.width * size.height; std::vector empty(dataSize, 0); - - if (this->shaper->getRasterizers()[0]->getDataType() == Rasterizer::DATA_TRUETYPE) + if (shaper->getRasterizers()[0]->getDataType() == Rasterizer::DATA_TRUETYPE) { if (this->pixelFormat == PIXELFORMAT_LA8_UNORM) { - for (size_t i = 0; i < pixelCount; i++) - empty[i * 2 + 0] = 255; + for (size_t index = 0; index < pixelCount; index++) + empty[index * 2 + 0] = 255; } else if (this->pixelFormat == PIXELFORMAT_RGBA8_UNORM) { - for (size_t i = 0; i < pixelCount; i++) + for (size_t index = 0; index < pixelCount; index++) { - empty[i * 4 + 0] = 255; - empty[i * 4 + 1] = 255; - empty[i * 4 + 2] = 255; + empty[index * 4 + 0] = 255; + empty[index * 4 + 1] = 255; + empty[index * 4 + 2] = 255; } } } - Rect rect = { 0, 0, size.width, size.height }; - texture->replacePixels(empty.data(), empty.size(), 0, 0, rect, false); + Rect rectangle = { 0, 0, size.width, size.height }; + texture->replacePixels(empty.data(), empty.size(), 0, 0, rectangle, false); } - textures.emplace_back(texture); + 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) @@ -73,28 +83,13 @@ namespace love this->textureCacheID++; std::vector glyphsToAdd; - for (const auto& glyph : this->glyphs) - glyphsToAdd.push_back(unpackGlyphIndex(glyph.first)); + for (const auto& glyphPair : this->glyphs) + glyphsToAdd.push_back(unpackGlyphIndex(glyphPair.first)); this->glyphs.clear(); - for (const auto& glyphIndex : glyphsToAdd) + for (auto glyphIndex : glyphsToAdd) this->addGlyph(glyphIndex); } } - - bool Font::loadVolatile() - { - this->textureCacheID++; - this->glyphs.clear(); - this->textures.clear(); - this->createTexture(); - - return true; - } - - const FontBase::Glyph& Font::addGlyph(TextShaper::GlyphIndex glyphIndex) - { - return FontBase::addGlyph(glyphIndex); - } } // namespace love diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index 33122efb..cd2d16a2 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -1667,17 +1667,35 @@ 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); - love::currentScreen = screen; + if (value == INVALID_SCREEN) + return luaL_error(L, "Invalid screen '%s'", name.c_str()); + + love::currentScreen = value; instance()->setActiveScreen(); return 0; } +#if !defined(__WIIU__) +int Wrap_Graphics::copyCurrentScanBuffer(lua_State* L) +{ + return 0; +} +#endif + // clang-format off #if defined(__3DS__) int Wrap_Graphics::is3D(lua_State* L) @@ -1725,7 +1743,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 @@ -1808,8 +1835,10 @@ static constexpr luaL_Reg functions[] = { "print", Wrap_Graphics::print }, { "printf", Wrap_Graphics::printf }, - { "setActiveScreen", Wrap_Graphics::setActiveScreen }, - { "getScreens", Wrap_Graphics::getScreens } + { "getScreens", Wrap_Graphics::getScreens }, + { "getActiveScreen", Wrap_Graphics::getActiveScreen }, + { "setActiveScreen", Wrap_Graphics::setActiveScreen }, + { "copyCurrentScanBuffer", Wrap_Graphics::copyCurrentScanBuffer } }; static int open_drawable(lua_State* L) diff --git a/source/modules/image/ImageData.cpp b/source/modules/image/ImageData.cpp index 47f0974e..950a2bbe 100644 --- a/source/modules/image/ImageData.cpp +++ b/source/modules/image/ImageData.cpp @@ -90,9 +90,11 @@ namespace love } } + 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); + const auto dataSize = love::getPixelFormatSliceSize(format, width, height, needsPowTwo); try { @@ -105,7 +107,7 @@ namespace love if (data) { - if (Console::is(Console::CTR)) + if constexpr (Console::is(Console::CTR)) { if (format == PIXELFORMAT_RGB565_UNORM) copyBytesTiled(this->data, data, width, height); diff --git a/source/modules/image/magpie/JPGHandler.cpp b/source/modules/image/magpie/JPGHandler.cpp index 72701bbb..57bdb37b 100644 --- a/source/modules/image/magpie/JPGHandler.cpp +++ b/source/modules/image/magpie/JPGHandler.cpp @@ -46,7 +46,7 @@ namespace love image.width = width; image.height = height; image.format = PIXELFORMAT_RGBA8_UNORM; - image.size = (width * height * sizeof(uint32_t)); + image.size = (width * height) * sizeof(uint32_t); image.data = new uint8_t[image.size]; const auto format = TJPF_RGBA; diff --git a/source/modules/image/magpie/PNGHandler.cpp b/source/modules/image/magpie/PNGHandler.cpp index d387bfae..50cb448a 100644 --- a/source/modules/image/magpie/PNGHandler.cpp +++ b/source/modules/image/magpie/PNGHandler.cpp @@ -44,9 +44,11 @@ namespace love result.width = image.width; result.height = image.height; result.format = PIXELFORMAT_RGBA8_UNORM; - result.size = (image.width * image.height * sizeof(uint32_t)); + 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); diff --git a/source/modules/joystick/JoystickModule.cpp b/source/modules/joystick/JoystickModule.cpp index 0b024b9c..586387d5 100644 --- a/source/modules/joystick/JoystickModule.cpp +++ b/source/modules/joystick/JoystickModule.cpp @@ -75,7 +75,7 @@ namespace love // 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 3ea30647..925e6e08 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); } diff --git a/source/modules/love/love.cpp b/source/modules/love/love.cpp index 19314cb6..278d462f 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -25,13 +25,16 @@ #include "boot.hpp" +// #region DEBUG CONSOLE #include #include +#include #include #if defined(__WIIU__) #include #endif +// #endregion #include #include @@ -313,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/callbacks.lua b/source/modules/love/scripts/callbacks.lua index 3f61238d..9de4c070 100644 --- a/source/modules/love/scripts/callbacks.lua +++ b/source/modules/love/scripts/callbacks.lua @@ -206,7 +206,7 @@ end -- we need to fix some bugs/inconsistencies on Wii U -- local is_wii_u = love._os == "Cafe" -local function get3DDepth(screen) +local function get_3d_depth(screen) if love._console ~= "3DS" then return nil end @@ -221,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) @@ -232,11 +234,6 @@ function love.run() local delta = 0 - local screens, current_screen = nil, 1 - if love.graphics and love.graphics.isActive() then - screens = love.graphics.getScreens() - end - return function() if love.window and g_windowShown then return @@ -267,6 +264,8 @@ function love.run() end if love.graphics and love.graphics.isActive() then + local screens = love.graphics.getScreens() + for _, screen in ipairs(screens) do love.graphics.setActiveScreen(screen) @@ -274,8 +273,10 @@ function love.run() love.graphics.clear(love.graphics.getBackgroundColor()) if love.draw then - love.draw(screen, get3DDepth(screen)) + love.draw(screen, get_3d_depth(screen)) end + + love.graphics.copyCurrentScanBuffer() end love.graphics.present() From 4ba83b09da46138003291ea96e7f8da7c1f9fb3d Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 31 Oct 2024 10:57:25 -0400 Subject: [PATCH 36/49] code cleanup --- include/common/Color.hpp | 2 +- include/common/Exception.hpp | 4 +- include/common/math.hpp | 5 -- include/modules/data/misc/ZlibCompressor.hpp | 18 +++-- include/modules/graphics/Font.hpp | 16 ---- include/modules/graphics/freetype/Font.hpp | 3 - include/modules/graphics/renderstate.hpp | 10 +-- include/modules/window/Window.tcc | 2 +- .../cafe/include/driver/display/Uniform.hpp | 4 +- .../include/driver/graphics/StreamBuffer.hpp | 23 ++++-- .../modules/joystick/kpad/Joystick.hpp | 4 + .../modules/joystick/vpad/Joystick.hpp | 6 +- platform/cafe/source/boot.cpp | 8 +- platform/cafe/source/driver/EventQueue.cpp | 14 ++-- platform/cafe/source/driver/display/GX2.cpp | 2 +- .../cafe/source/modules/graphics/Graphics.cpp | 2 +- .../cafe/source/modules/graphics/Shader.cpp | 4 +- .../modules/joystick/JoystickModule.cpp | 13 ++- .../source/modules/joystick/kpad/Joystick.cpp | 80 ++++++++++++++++++- .../source/modules/joystick/vpad/Joystick.cpp | 26 +++--- .../cafe/source/modules/window/Window.cpp | 2 +- .../modules/joystick/JoystickModule.cpp | 2 + source/modules/graphics/Texture.cpp | 2 +- source/modules/graphics/freetype/Font.cpp | 2 +- source/modules/graphics/wrap_Font.cpp | 4 +- source/modules/joystick/JoystickModule.cpp | 4 +- source/modules/joystick/wrap_Joystick.cpp | 2 +- source/utility/guid.cpp | 4 +- 28 files changed, 159 insertions(+), 109 deletions(-) delete mode 100644 include/modules/graphics/Font.hpp diff --git a/include/common/Color.hpp b/include/common/Color.hpp index 63127fa1..07b1da36 100644 --- a/include/common/Color.hpp +++ b/include/common/Color.hpp @@ -133,7 +133,7 @@ namespace love 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, + 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) diff --git a/include/common/Exception.hpp b/include/common/Exception.hpp index 3657d731..ba931409 100644 --- a/include/common/Exception.hpp +++ b/include/common/Exception.hpp @@ -28,9 +28,7 @@ namespace love std::string message; }; -#if !(__DEBUG__) - #define LOG() -#else +#if __DEBUG__ #include // Macro to log to both stdout and a debug.log file diff --git a/include/common/math.hpp b/include/common/math.hpp index 010e81ac..7892b727 100644 --- a/include/common/math.hpp +++ b/include/common/math.hpp @@ -72,11 +72,6 @@ namespace love } }; - inline void DEBUG_RECT(const Rect& rectangle) - { - std::printf("x: %d, y: %d, w: %d, h: %d\n", rectangle.x, rectangle.y, rectangle.w, rectangle.h); - } - /* ** Clamps 3DS textures between min ** and max texture size to prevent diff --git a/include/modules/data/misc/ZlibCompressor.hpp b/include/modules/data/misc/ZlibCompressor.hpp index bfc2b232..774f23d2 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/graphics/Font.hpp b/include/modules/graphics/Font.hpp deleted file mode 100644 index 93b134e0..00000000 --- a/include/modules/graphics/Font.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#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/freetype/Font.hpp b/include/modules/graphics/freetype/Font.hpp index 4e4567c3..1907299b 100644 --- a/include/modules/graphics/freetype/Font.hpp +++ b/include/modules/graphics/freetype/Font.hpp @@ -13,8 +13,5 @@ namespace love virtual void createTexture() override; bool loadVolatile() override; - - private: - virtual const FontBase::Glyph& addGlyph(TextShaper::GlyphIndex glyphIndex) override; }; } // namespace love diff --git a/include/modules/graphics/renderstate.hpp b/include/modules/graphics/renderstate.hpp index 0dc4aa8b..e53f587f 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/window/Window.tcc b/include/modules/window/Window.tcc index 8d181b96..88b4916c 100644 --- a/include/modules/window/Window.tcc +++ b/include/modules/window/Window.tcc @@ -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, diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp index d210d665..c4c835b0 100644 --- a/platform/cafe/include/driver/display/Uniform.hpp +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -7,9 +7,6 @@ namespace love { - // void updateMatrix(glm::mat4& modelView, const Matrix4& matrix); - glm::mat4 createTransposedSwappedMatrix(const Matrix4& matrix); - struct Uniform { glm::mat4 modelView; @@ -41,4 +38,5 @@ namespace love } }; + static constexpr auto UNIFORM_SIZE = sizeof(Uniform); } // namespace love diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index 145b8109..f5d81e97 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -9,14 +9,21 @@ 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) + StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size), buffer {} { - auto flags = (usage == BufferUsage::BUFFERUSAGE_VERTEX) ? GX2R_RESOURCE_BIND_VERTEX_BUFFER - : GX2R_RESOURCE_BIND_INDEX_BUFFER; + const auto flags = getBufferUsage(usage); this->buffer.elemCount = size; this->buffer.elemSize = sizeof(T); @@ -26,6 +33,11 @@ namespace love throw love::Exception("Failed to create StreamBuffer"); } + ~StreamBuffer() + { + GX2RDestroyBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); + } + MapInfo map(size_t) { MapInfo info {}; @@ -48,11 +60,6 @@ namespace love return this->index; } - ~StreamBuffer() - { - GX2RDestroyBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); - } - ptrdiff_t getHandle() const override { return (ptrdiff_t)std::addressof(this->buffer); diff --git a/platform/cafe/include/modules/joystick/kpad/Joystick.hpp b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp index c534168e..8b13c5b7 100644 --- a/platform/cafe/include/modules/joystick/kpad/Joystick.hpp +++ b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp @@ -59,6 +59,10 @@ namespace love virtual std::vector getSensorData(Sensor::SensorType type) const override; + std::array getPosition() const; + + std::array getAngle() const; + using JoystickBase::getConstant; // #region Wii Remote diff --git a/platform/cafe/include/modules/joystick/vpad/Joystick.hpp b/platform/cafe/include/modules/joystick/vpad/Joystick.hpp index 6e87e5e0..a3fa4420 100644 --- a/platform/cafe/include/modules/joystick/vpad/Joystick.hpp +++ b/platform/cafe/include/modules/joystick/vpad/Joystick.hpp @@ -21,7 +21,7 @@ namespace love VPADStatus& getVPADStatus() { - return this->vpadStatus; + return this->status; } virtual bool open(int64_t deviceId) override; @@ -108,8 +108,8 @@ namespace love // clang-format on private: - VPADStatus vpadStatus; - VPADReadError vpadError; + VPADStatus status; + VPADReadError error; mutable struct State { diff --git a/platform/cafe/source/boot.cpp b/platform/cafe/source/boot.cpp index 927589b1..c456293c 100644 --- a/platform/cafe/source/boot.cpp +++ b/platform/cafe/source/boot.cpp @@ -29,7 +29,7 @@ namespace love { "vpad", BIND(VPADInit), &VPADShutdown }, { "kpad", BIND(KPADInit), &KPADShutdown }, { "ac", BIND(ACInitialize), &ACFinalize }, - { "fs", BIND(FSInit), &FSShutdown }, + { "fs", BIND(FSInit), &FSShutdown } // { "bsp", BIND(bspInitializeShimInterface), []() { } } }}; // clang-format on @@ -37,10 +37,12 @@ namespace love 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; @@ -60,7 +62,7 @@ namespace love } } - return "fs:/vol/external01/lovepotion.wuhb"; + return DEFAULT_PATH; } int preInit() diff --git a/platform/cafe/source/driver/EventQueue.cpp b/platform/cafe/source/driver/EventQueue.cpp index bf4dcc01..7cbce98f 100644 --- a/platform/cafe/source/driver/EventQueue.cpp +++ b/platform/cafe/source/driver/EventQueue.cpp @@ -59,20 +59,20 @@ namespace love if (joystick == nullptr) continue; - if (joystick->getGamepadType() == GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD) - this->gamepad = (vpad::Joystick*)joystick; + // if (joystick->getGamepadType() == GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD) + // this->gamepad = (vpad::Joystick*)joystick; joystick->update(); + const auto which = joystick->getInstanceID(); for (int input = 0; input < JoystickBase::GAMEPAD_BUTTON_MAX_ENUM; 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 < JoystickBase::GAMEPAD_AXIS_MAX_ENUM; input++) @@ -80,7 +80,7 @@ namespace love if (joystick->isAxisChanged(JoystickBase::GamepadAxis(input))) { float value = joystick->getAxis(JoystickBase::GamepadAxis(input)); - this->sendGamepadAxisEvent(0, input, value); + 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/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index bd3037a7..536d4e12 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -154,7 +154,7 @@ namespace love void GX2::createFramebuffers() { - const auto& info = love::getScreenInfo(); + const auto info = love::getScreenInfo(); for (size_t index = 0; index < info.size(); ++index) this->targets[index].create(info[index]); diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index ff60cdb0..c1419a41 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -3,9 +3,9 @@ #include "modules/graphics/Graphics.hpp" #include "modules/window/Window.hpp" -#include "modules/graphics/Font.hpp" #include "modules/graphics/Shader.hpp" #include "modules/graphics/Texture.hpp" +#include "modules/graphics/freetype/Font.hpp" #include #include diff --git a/platform/cafe/source/modules/graphics/Shader.cpp b/platform/cafe/source/modules/graphics/Shader.cpp index bcd6e2c0..9471678c 100644 --- a/platform/cafe/source/modules/graphics/Shader.cpp +++ b/platform/cafe/source/modules/graphics/Shader.cpp @@ -114,8 +114,8 @@ namespace love auto& transform = graphics->getTransform(); // uniform->update(transform); - GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, uniform, sizeof(Uniform)); - GX2SetVertexUniformBlock(this->uniform.location, sizeof(Uniform), uniform); + GX2Invalidate(INVALIDATE_UNIFORM_BLOCK, uniform, UNIFORM_SIZE); + GX2SetVertexUniformBlock(this->uniform.location, UNIFORM_SIZE, uniform); } ptrdiff_t Shader::getHandle() const diff --git a/platform/cafe/source/modules/joystick/JoystickModule.cpp b/platform/cafe/source/modules/joystick/JoystickModule.cpp index 6cc8b846..8fa8a608 100644 --- a/platform/cafe/source/modules/joystick/JoystickModule.cpp +++ b/platform/cafe/source/modules/joystick/JoystickModule.cpp @@ -12,32 +12,29 @@ 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) { if (index == 0) diff --git a/platform/cafe/source/modules/joystick/kpad/Joystick.cpp b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp index 84d53d20..53d784b7 100644 --- a/platform/cafe/source/modules/joystick/kpad/Joystick.cpp +++ b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp @@ -56,7 +56,7 @@ namespace love int index = (int)deviceId; this->close(); - this->instanceId = 0; + this->instanceId = index; WPADExtensionType extension; if (WPADProbe(WPADChan(index - 1), &extension) < 0) @@ -64,7 +64,6 @@ namespace love switch (extension) { - default: case WPAD_EXT_CORE: { this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_REMOTE; @@ -85,6 +84,11 @@ namespace love this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_U_PRO; break; } + default: + { + this->gamepadType = GAMEPAD_TYPE_UNKNOWN; + break; + } } this->guid = love::getGamepadGUID(this->gamepadType); @@ -232,7 +236,7 @@ namespace love case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: return isButtonDown(buttons); case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: - break; + return isButtonDown(buttons) || isButtonDown(buttons); case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: return isButtonDown(buttons); case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: @@ -254,7 +258,7 @@ namespace love case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: return isButtonUp(buttons); case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: - break; + return isButtonUp(buttons) || isButtonUp(buttons); case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: return isButtonUp(buttons); case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: @@ -398,7 +402,75 @@ namespace love 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_NUNCHUCK: + 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; } + + std::array Joystick::getPosition() const + { + std::array result {}; + + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + { + result[0] = this->status.pos.x; + result[1] = this->status.pos.y; + break; + } + case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: + case GAMEPAD_TYPE_NINTENDO_WII_U_PRO: + default: + break; + } + + return result; + } + + std::array Joystick::getAngle() const + { + std::array result {}; + + switch (this->gamepadType) + { + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + { + result[0] = this->status.angle.x; + result[1] = 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 index 6072a06e..eb44b4d2 100644 --- a/platform/cafe/source/modules/joystick/vpad/Joystick.cpp +++ b/platform/cafe/source/modules/joystick/vpad/Joystick.cpp @@ -19,10 +19,10 @@ namespace love void Joystick::update() { - VPADRead(VPAD_CHAN_0, &this->vpadStatus, 1, &this->vpadError); + VPADRead(VPAD_CHAN_0, &this->status, 1, &this->error); - const auto& status = this->vpadStatus; - if (this->vpadError != VPAD_READ_NO_SAMPLES) + const auto& status = this->status; + if (this->error != VPAD_READ_NO_SAMPLES) this->state = { status.trigger, status.release, status.hold }; } @@ -52,7 +52,7 @@ namespace love bool Joystick::isConnected() const { - switch (this->vpadError) + switch (this->error) { case VPAD_READ_INVALID_CONTROLLER: case VPAD_READ_UNINITIALIZED: @@ -66,7 +66,7 @@ namespace love float Joystick::getAxis(GamepadAxis axis) const { - const auto status = this->vpadStatus; + const auto status = this->status; switch (axis) { @@ -262,21 +262,17 @@ namespace love { 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); + 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: { - const auto& gyroscope = this->vpadStatus.gyro; - - data.push_back(gyroscope.x); - data.push_back(gyroscope.y); - data.push_back(gyroscope.z); + data.push_back(this->status.gyro.x); + data.push_back(this->status.gyro.y); + data.push_back(this->status.gyro.z); return data; } diff --git a/platform/cafe/source/modules/window/Window.cpp b/platform/cafe/source/modules/window/Window.cpp index 4fcec1c9..5ac0316d 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/source/modules/joystick/JoystickModule.cpp b/platform/ctr/source/modules/joystick/JoystickModule.cpp index 9d0cf10f..3d248921 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/source/modules/graphics/Texture.cpp b/source/modules/graphics/Texture.cpp index 3e1427c6..edce4e7c 100644 --- a/source/modules/graphics/Texture.cpp +++ b/source/modules/graphics/Texture.cpp @@ -431,7 +431,7 @@ namespace love throw love::Exception("replacePixels cannot be called on a multisampled Texture."); auto* graphics = Module::getInstance(Module::M_GRAPHICS); - if (graphics == nullptr && graphics->isRenderTargetActive(this)) + 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) diff --git a/source/modules/graphics/freetype/Font.cpp b/source/modules/graphics/freetype/Font.cpp index 4f9cac66..a2a70d0a 100644 --- a/source/modules/graphics/freetype/Font.cpp +++ b/source/modules/graphics/freetype/Font.cpp @@ -1,4 +1,4 @@ -#include "modules/graphics/Font.hpp" +#include "modules/graphics/freetype/Font.hpp" #include "modules/graphics/Graphics.tcc" namespace love diff --git a/source/modules/graphics/wrap_Font.cpp b/source/modules/graphics/wrap_Font.cpp index e5d5d41c..bfe57bc4 100644 --- a/source/modules/graphics/wrap_Font.cpp +++ b/source/modules/graphics/wrap_Font.cpp @@ -288,8 +288,6 @@ namespace love int open_font(lua_State* L) { - luax_register_type(L, &FontBase::type, functions); - - return 0; + return luax_register_type(L, &FontBase::type, functions); } } // namespace love diff --git a/source/modules/joystick/JoystickModule.cpp b/source/modules/joystick/JoystickModule.cpp index 586387d5..0519824d 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,8 +71,6 @@ namespace love } } - // TODO: make a method (or use define info) - // for when Wii U handles are dealt with (WPADHandle, VPADHandle classes) if (!joystick) { joystick = love::joystick::openJoystick(this->joysticks.size()); diff --git a/source/modules/joystick/wrap_Joystick.cpp b/source/modules/joystick/wrap_Joystick.cpp index 925e6e08..f99f0a39 100644 --- a/source/modules/joystick/wrap_Joystick.cpp +++ b/source/modules/joystick/wrap_Joystick.cpp @@ -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) diff --git a/source/utility/guid.cpp b/source/utility/guid.cpp index 074962f2..034a1a73 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_NUNCHUCK { 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 From 0590864c3c79fd97725918de1c9beb0339bbac99 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 31 Oct 2024 23:04:18 -0400 Subject: [PATCH 37/49] fix wii u release not rendering --- include/common/Exception.hpp | 2 +- include/modules/joystick/Joystick.tcc | 78 ++++++++++--------- include/utility/guid.hpp | 2 +- .../include/driver/display/Framebuffer.hpp | 10 +-- .../cafe/include/driver/display/Uniform.hpp | 28 +------ .../include/driver/graphics/StreamBuffer.hpp | 4 + .../modules/joystick/kpad/Joystick.hpp | 3 +- .../source/driver/display/Framebuffer.cpp | 10 ++- platform/cafe/source/driver/display/GX2.cpp | 5 +- .../cafe/source/driver/display/Uniform.cpp | 21 ++--- .../source/modules/joystick/kpad/Joystick.cpp | 32 ++++---- source/utility/guid.cpp | 2 +- 12 files changed, 95 insertions(+), 102 deletions(-) diff --git a/include/common/Exception.hpp b/include/common/Exception.hpp index ba931409..55cd0bef 100644 --- a/include/common/Exception.hpp +++ b/include/common/Exception.hpp @@ -28,7 +28,7 @@ namespace love std::string message; }; -#if __DEBUG__ +#if 1 #include // Macro to log to both stdout and a debug.log file diff --git a/include/modules/joystick/Joystick.tcc b/include/modules/joystick/Joystick.tcc index de910950..aa7fa307 100644 --- a/include/modules/joystick/Joystick.tcc +++ b/include/modules/joystick/Joystick.tcc @@ -55,6 +55,8 @@ namespace love GAMEPAD_BUTTON_DPAD_RIGHT, GAMEPAD_BUTTON_MISC1, GAMEPAD_BUTTON_MISC2, + GAMEPAD_BUTTON_Z, //< Nunchuk only + GAMEPAD_BUTTON_C, //< Nunchuk only GAMEPAD_BUTTON_MAX_ENUM }; @@ -190,23 +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 }, - { "misc1", GAMEPAD_BUTTON_MISC1 }, - { "misc2", GAMEPAD_BUTTON_MISC2 } + { "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, @@ -219,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, diff --git a/include/utility/guid.hpp b/include/utility/guid.hpp index 0165e447..376d348b 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/platform/cafe/include/driver/display/Framebuffer.hpp b/platform/cafe/include/driver/display/Framebuffer.hpp index 0d8eda37..074aaf49 100644 --- a/platform/cafe/include/driver/display/Framebuffer.hpp +++ b/platform/cafe/include/driver/display/Framebuffer.hpp @@ -49,11 +49,6 @@ namespace love void copyScanBuffer(); - operator GX2ContextState*() - { - return this->state; - } - private: static constexpr auto FORMAT = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; static constexpr auto BUFFER_MODE = GX2_BUFFERING_MODE_DOUBLE; @@ -66,11 +61,10 @@ namespace love uint8_t renderMode; GX2ScanTarget id; - Uniform* uniform; + Uniform* uniform = nullptr; + glm::mat4 tmpModel; void* scanBuffer; uint32_t scanBufferSize; - - GX2ContextState* state; }; } // namespace love diff --git a/platform/cafe/include/driver/display/Uniform.hpp b/platform/cafe/include/driver/display/Uniform.hpp index c4c835b0..40b57bfc 100644 --- a/platform/cafe/include/driver/display/Uniform.hpp +++ b/platform/cafe/include/driver/display/Uniform.hpp @@ -1,5 +1,6 @@ #pragma once +#include "common/Exception.hpp" #include "common/Matrix.hpp" #include @@ -11,32 +12,9 @@ namespace love { glm::mat4 modelView; glm::mat4 projection; - - void debug() - { - const auto count = sizeof(glm::mat4) / sizeof(uint32_t); - - uint32_t* model = (uint32_t*)glm::value_ptr(this->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(this->projection); - for (size_t index = 0; index < count; index++) - std::printf("projection[%zu] = %u\n", index, projection[index]); - } - - void update(const Matrix4& matrix) - { - const auto count = sizeof(glm::mat4) / sizeof(uint32_t); - uint32_t* destination = (uint32_t*)glm::value_ptr(this->modelView); - - glm::mat4 sourceMatrix = glm::transpose(glm::make_mat4(matrix.getElements())); - uint32_t* source = (uint32_t*)glm::value_ptr(sourceMatrix); - - for (int i = 0; i < count; ++i) - destination[i] = __builtin_bswap32(source[i]); - } }; + void debugUniform(Uniform* uniform); + static constexpr auto UNIFORM_SIZE = sizeof(Uniform); } // namespace love diff --git a/platform/cafe/include/driver/graphics/StreamBuffer.hpp b/platform/cafe/include/driver/graphics/StreamBuffer.hpp index f5d81e97..4130480d 100644 --- a/platform/cafe/include/driver/graphics/StreamBuffer.hpp +++ b/platform/cafe/include/driver/graphics/StreamBuffer.hpp @@ -33,6 +33,10 @@ namespace love throw love::Exception("Failed to create StreamBuffer"); } + StreamBuffer(StreamBuffer&&) = delete; + + StreamBuffer& operator=(const StreamBuffer&) = delete; + ~StreamBuffer() { GX2RDestroyBufferEx(&this->buffer, GX2R_RESOURCE_BIND_NONE); diff --git a/platform/cafe/include/modules/joystick/kpad/Joystick.hpp b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp index 8b13c5b7..9a62ac7f 100644 --- a/platform/cafe/include/modules/joystick/kpad/Joystick.hpp +++ b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp @@ -94,7 +94,8 @@ namespace love // clang-format off ENUMMAP_DECLARE(NunchuckButtons, GamepadButton, WPADNunchukButton, - { GAMEPAD_BUTTON_LEFTSHOULDER, WPAD_NUNCHUK_BUTTON_C } + { GAMEPAD_BUTTON_C, WPAD_NUNCHUK_BUTTON_C }, + { GAMEPAD_BUTTON_Z, WPAD_NUNCHUK_BUTTON_Z } ); ENUMMAP_DECLARE(NunchuckAxes, GamepadAxis, NunchuckAxis, diff --git a/platform/cafe/source/driver/display/Framebuffer.cpp b/platform/cafe/source/driver/display/Framebuffer.cpp index 5e5a0369..c2aa60f5 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -15,11 +15,14 @@ namespace love 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) @@ -127,13 +130,18 @@ namespace love const size_t count = sizeof(glm::mat4) / sizeof(uint32_t); - uint32_t* model = (uint32_t*)glm::value_ptr(glm::mat4(1.0f)); + uint32_t* model = (uint32_t*)glm::value_ptr(this->tmpModel); for (size_t index = 0; index < count; index++) + { + LOG("%lu/%f", model[index], (float)(model[index])); dstModel[index] = __builtin_bswap32(model[index]); + } uint32_t* projection = (uint32_t*)glm::value_ptr(ortho); for (size_t index = 0; index < count; index++) dstProj[index] = __builtin_bswap32(projection[index]); + + love::debugUniform(this->uniform); } void Framebuffer::setScissor(const Rect& scissor) diff --git a/platform/cafe/source/driver/display/GX2.cpp b/platform/cafe/source/driver/display/GX2.cpp index 536d4e12..9074ea81 100644 --- a/platform/cafe/source/driver/display/GX2.cpp +++ b/platform/cafe/source/driver/display/GX2.cpp @@ -146,7 +146,10 @@ namespace love this->context.depthWrite = true; this->context.compareMode = GX2_COMPARE_FUNC_ALWAYS; - this->uniform = (Uniform*)memalign(GX2_UNIFORM_BLOCK_ALIGNMENT, sizeof(Uniform)); + 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; diff --git a/platform/cafe/source/driver/display/Uniform.cpp b/platform/cafe/source/driver/display/Uniform.cpp index ed28100a..11830db0 100644 --- a/platform/cafe/source/driver/display/Uniform.cpp +++ b/platform/cafe/source/driver/display/Uniform.cpp @@ -2,21 +2,16 @@ namespace love { - glm::mat4 createTransposedSwappedMatrix(const Matrix4& matrix) + void debugUniform(Uniform* uniform) { - const uint32_t count = sizeof(glm::mat4) / sizeof(uint32_t); + const auto count = sizeof(glm::mat4) / sizeof(uint32_t); - // Transpose the input matrix to convert from row-major to column-major - glm::mat4 sourceMatrix = glm::transpose(glm::make_mat4(matrix.getElements())); + uint32_t* model = (uint32_t*)glm::value_ptr(uniform->modelView); + for (size_t index = 0; index < count; index++) + LOG("modelView[%zu] = %u\n", index, model[index]); - // Create a new matrix to hold the endian-swapped version - glm::mat4 resultMatrix; - uint32_t* destination = (uint32_t*)glm::value_ptr(resultMatrix); - const uint32_t* source = (const uint32_t*)glm::value_ptr(sourceMatrix); - - for (int i = 0; i < count; ++i) - destination[i] = __builtin_bswap32(source[i]); - - return resultMatrix; + uint32_t* projection = (uint32_t*)glm::value_ptr(uniform->projection); + for (size_t index = 0; index < count; index++) + LOG("projection[%zu] = %u\n", index, projection[index]); } } // namespace love diff --git a/platform/cafe/source/modules/joystick/kpad/Joystick.cpp b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp index 53d784b7..6ac250da 100644 --- a/platform/cafe/source/modules/joystick/kpad/Joystick.cpp +++ b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp @@ -76,7 +76,7 @@ namespace love } case WPAD_EXT_NUNCHUK: { - this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK; + this->gamepadType = GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK; break; } case WPAD_EXT_PRO_CONTROLLER: @@ -138,13 +138,17 @@ namespace love 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; - return this->status.classic.hold & WPAD_CLASSIC_BUTTON_ZR ? 1.0f : 0.0f; + break; } - case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: { - if (axis == GAMEPAD_AXIS_TRIGGERLEFT) - return this->status.nunchuk.hold & WPAD_NUNCHUK_BUTTON_Z ? 1.0f : 0.0f; + 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; } @@ -160,11 +164,13 @@ namespace love 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; - return this->status.pro.hold & WPAD_PRO_TRIGGER_ZR ? 1.0f : 0.0f; + break; } default: - return 0.0f; + break; } return 0.0f; @@ -235,7 +241,7 @@ namespace love { case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: return isButtonDown(buttons); - case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: return isButtonDown(buttons) || isButtonDown(buttons); case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: return isButtonDown(buttons); @@ -257,7 +263,7 @@ namespace love { case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: return isButtonUp(buttons); - case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: return isButtonUp(buttons) || isButtonUp(buttons); case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: return isButtonUp(buttons); @@ -296,7 +302,7 @@ namespace love { case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: break; - case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: return isAxisValueChanged(axis); case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: return isAxisValueChanged(axis); @@ -411,7 +417,7 @@ namespace love data.push_back(this->status.acc.z); break; } - case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + 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); @@ -436,7 +442,7 @@ namespace love switch (this->gamepadType) { case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: - case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: { result[0] = this->status.pos.x; result[1] = this->status.pos.y; @@ -458,7 +464,7 @@ namespace love switch (this->gamepadType) { case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: - case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK: + case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: { result[0] = this->status.angle.x; result[1] = this->status.angle.y; diff --git a/source/utility/guid.cpp b/source/utility/guid.cpp index 034a1a73..8d11fda8 100644 --- a/source/utility/guid.cpp +++ b/source/utility/guid.cpp @@ -23,7 +23,7 @@ namespace love { 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, false, true }, // GAMEPAD_TYPE_NINTENDO_WII_REMOTE - { 12, 2, 0, "{C0E2DDE5-25DF-4F7D-AEA6-4F25DE2FC385}", false, false, false, true }, // GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUCK + { 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 From 86295280db22bfa615bbe6ea368bd0a832b2ee25 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 31 Oct 2024 23:04:36 -0400 Subject: [PATCH 38/49] reenable vpad on eventqueue --- platform/cafe/source/driver/EventQueue.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/cafe/source/driver/EventQueue.cpp b/platform/cafe/source/driver/EventQueue.cpp index 7cbce98f..3905e5c5 100644 --- a/platform/cafe/source/driver/EventQueue.cpp +++ b/platform/cafe/source/driver/EventQueue.cpp @@ -59,8 +59,8 @@ namespace love if (joystick == nullptr) continue; - // if (joystick->getGamepadType() == GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD) - // this->gamepad = (vpad::Joystick*)joystick; + if (joystick->getGamepadType() == GAMEPAD_TYPE_NINTENDO_WII_U_GAMEPAD) + this->gamepad = (vpad::Joystick*)joystick; joystick->update(); const auto which = joystick->getInstanceID(); From 86c3bd187ac01c3f5d6fc670dc36786025c790c4 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 31 Oct 2024 23:06:40 -0400 Subject: [PATCH 39/49] fix logging things back to how it was --- include/common/Exception.hpp | 2 +- platform/cafe/source/driver/display/Framebuffer.cpp | 5 ----- platform/cafe/source/driver/display/Uniform.cpp | 4 ++-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/include/common/Exception.hpp b/include/common/Exception.hpp index 55cd0bef..ba931409 100644 --- a/include/common/Exception.hpp +++ b/include/common/Exception.hpp @@ -28,7 +28,7 @@ namespace love std::string message; }; -#if 1 +#if __DEBUG__ #include // Macro to log to both stdout and a debug.log file diff --git a/platform/cafe/source/driver/display/Framebuffer.cpp b/platform/cafe/source/driver/display/Framebuffer.cpp index c2aa60f5..f8241019 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -1,5 +1,3 @@ -#include "common/Exception.hpp" - #include "driver/display/Framebuffer.hpp" #include "driver/display/utility.hpp" @@ -132,10 +130,7 @@ namespace love uint32_t* model = (uint32_t*)glm::value_ptr(this->tmpModel); for (size_t index = 0; index < count; index++) - { - LOG("%lu/%f", model[index], (float)(model[index])); dstModel[index] = __builtin_bswap32(model[index]); - } uint32_t* projection = (uint32_t*)glm::value_ptr(ortho); for (size_t index = 0; index < count; index++) diff --git a/platform/cafe/source/driver/display/Uniform.cpp b/platform/cafe/source/driver/display/Uniform.cpp index 11830db0..be4b68dd 100644 --- a/platform/cafe/source/driver/display/Uniform.cpp +++ b/platform/cafe/source/driver/display/Uniform.cpp @@ -8,10 +8,10 @@ namespace love uint32_t* model = (uint32_t*)glm::value_ptr(uniform->modelView); for (size_t index = 0; index < count; index++) - LOG("modelView[%zu] = %u\n", index, model[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++) - LOG("projection[%zu] = %u\n", index, projection[index]); + std::printf("projection[%zu] = %u\n", index, projection[index]); } } // namespace love From 443af2d8a4201faa460e1fecacbe83f456c04e01 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Sat, 2 Nov 2024 11:47:38 -0400 Subject: [PATCH 40/49] add wiimote pos/angle functions for joysticks --- include/modules/graphics/Graphics.tcc | 2 + include/modules/graphics/wrap_Graphics.hpp | 2 + include/modules/joystick/wrap_Joystick.hpp | 4 ++ .../include/driver/display/Framebuffer.hpp | 2 + .../modules/joystick/kpad/Joystick.hpp | 6 ++- .../source/driver/display/Framebuffer.cpp | 6 +-- .../source/modules/joystick/kpad/Joystick.cpp | 33 ++++++++---- source/modules/graphics/Graphics.cpp | 5 ++ source/modules/graphics/wrap_Graphics.cpp | 50 +++++++++++++++++++ source/modules/joystick/wrap_Joystick.cpp | 45 ++++++++++++++++- 10 files changed, 139 insertions(+), 16 deletions(-) diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 156af4fc..3f8042b1 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -460,6 +460,8 @@ namespace love void setBlendMode(BlendMode mode, BlendAlpha alphaMode); + Quad* newQuad(Quad::Viewport viewport, double sourceWidth, double sourceHeight) const; + virtual TextureBase* newTexture(const TextureBase::Settings& settings, const TextureBase::Slices* data = nullptr) = 0; diff --git a/include/modules/graphics/wrap_Graphics.hpp b/include/modules/graphics/wrap_Graphics.hpp index ac7a9cbe..8c351a09 100644 --- a/include/modules/graphics/wrap_Graphics.hpp +++ b/include/modules/graphics/wrap_Graphics.hpp @@ -163,6 +163,8 @@ namespace Wrap_Graphics int newTexture(lua_State* L); + int newQuad(lua_State* L); + int newImage(lua_State* L); int newCanvas(lua_State* L); diff --git a/include/modules/joystick/wrap_Joystick.hpp b/include/modules/joystick/wrap_Joystick.hpp index 827c7e6d..fc9f7f56 100644 --- a/include/modules/joystick/wrap_Joystick.hpp +++ b/include/modules/joystick/wrap_Joystick.hpp @@ -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/platform/cafe/include/driver/display/Framebuffer.hpp b/platform/cafe/include/driver/display/Framebuffer.hpp index 074aaf49..a8c19b4a 100644 --- a/platform/cafe/include/driver/display/Framebuffer.hpp +++ b/platform/cafe/include/driver/display/Framebuffer.hpp @@ -62,7 +62,9 @@ namespace love GX2ScanTarget id; Uniform* uniform = nullptr; + glm::mat4 tmpModel; + glm::highp_mat4 ortho; void* scanBuffer; uint32_t scanBufferSize; diff --git a/platform/cafe/include/modules/joystick/kpad/Joystick.hpp b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp index 9a62ac7f..35b36615 100644 --- a/platform/cafe/include/modules/joystick/kpad/Joystick.hpp +++ b/platform/cafe/include/modules/joystick/kpad/Joystick.hpp @@ -1,5 +1,7 @@ #pragma once +#include "common/Vector.hpp" + #include "modules/joystick/Joystick.tcc" #include @@ -59,9 +61,9 @@ namespace love virtual std::vector getSensorData(Sensor::SensorType type) const override; - std::array getPosition() const; + Vector2 getPosition() const; - std::array getAngle() const; + Vector2 getAngle() const; using JoystickBase::getConstant; diff --git a/platform/cafe/source/driver/display/Framebuffer.cpp b/platform/cafe/source/driver/display/Framebuffer.cpp index f8241019..c5eb39da 100644 --- a/platform/cafe/source/driver/display/Framebuffer.cpp +++ b/platform/cafe/source/driver/display/Framebuffer.cpp @@ -120,7 +120,7 @@ namespace love this->viewport = { 0, 0, info.width, info.height }; this->scissor = { 0, 0, info.width, info.height }; - auto ortho = glm::ortho(0.0f, (float)info.width, (float)info.height, 0.0f, Z_NEAR, Z_FAR); + 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); @@ -132,11 +132,9 @@ namespace love for (size_t index = 0; index < count; index++) dstModel[index] = __builtin_bswap32(model[index]); - uint32_t* projection = (uint32_t*)glm::value_ptr(ortho); + uint32_t* projection = (uint32_t*)glm::value_ptr(this->ortho); for (size_t index = 0; index < count; index++) dstProj[index] = __builtin_bswap32(projection[index]); - - love::debugUniform(this->uniform); } void Framebuffer::setScissor(const Rect& scissor) diff --git a/platform/cafe/source/modules/joystick/kpad/Joystick.cpp b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp index 6ac250da..9280c83d 100644 --- a/platform/cafe/source/modules/joystick/kpad/Joystick.cpp +++ b/platform/cafe/source/modules/joystick/kpad/Joystick.cpp @@ -1,3 +1,5 @@ +#include "common/screen.hpp" + #include "modules/joystick/kpad/Joystick.hpp" namespace love @@ -435,17 +437,27 @@ namespace love return data; } - std::array Joystick::getPosition() const + static Vector2 ndcToScreen(const KPADVec2D& input) { - std::array result {}; + 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: { - result[0] = this->status.pos.x; - result[1] = this->status.pos.y; + if (this->status.posValid) + return ndcToScreen(this->status.pos); + break; } case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: @@ -454,20 +466,23 @@ namespace love break; } - return result; + return Vector2 {}; } - std::array Joystick::getAngle() const + Vector2 Joystick::getAngle() const { - std::array result {}; + Vector2 result {}; switch (this->gamepadType) { case GAMEPAD_TYPE_NINTENDO_WII_REMOTE: case GAMEPAD_TYPE_NINTENDO_WII_REMOTE_NUNCHUK: { - result[0] = this->status.angle.x; - result[1] = this->status.angle.y; + if (!this->status.posValid) + break; + + result.x = this->status.angle.x; + result.y = this->status.angle.y; break; } case GAMEPAD_TYPE_NINTENDO_WII_CLASSIC: diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 33a015d0..61b19b40 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -401,6 +401,11 @@ namespace love return data; } + 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); diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index cd2d16a2..e3f99466 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -977,6 +977,55 @@ int Wrap_Graphics::newTexture(lua_State* L) 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); @@ -1823,6 +1872,7 @@ static constexpr luaL_Reg functions[] = { "line", Wrap_Graphics::line }, { "newTexture", Wrap_Graphics::newTexture }, + { "newQuad", Wrap_Graphics::newQuad }, { "newImage", Wrap_Graphics::newImage }, // { "newMesh", Wrap_Graphics::newMesh }, diff --git a/source/modules/joystick/wrap_Joystick.cpp b/source/modules/joystick/wrap_Joystick.cpp index f99f0a39..d340d6d4 100644 --- a/source/modules/joystick/wrap_Joystick.cpp +++ b/source/modules/joystick/wrap_Joystick.cpp @@ -430,6 +430,49 @@ static constexpr luaL_Reg functions[] = { { "getSensorData", Wrap_Joystick::getSensorData }, { "getConnectedIndex", Wrap_JoystickModule::getIndex } }; + +#if !defined(__WIIU__) +static constexpr luaL_Reg 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 From 1aba604e1180feb29ad5f40c2bd5e8b81134eb4d Mon Sep 17 00:00:00 2001 From: TurtleP Date: Mon, 4 Nov 2024 08:38:40 -0500 Subject: [PATCH 41/49] get switch rendering started-ish --- CMakeLists.txt | 8 ++- include/driver/display/Renderer.tcc | 4 +- include/driver/graphics/StreamBuffer.tcc | 1 + include/modules/graphics/Shader.tcc | 3 + platform/cafe/CMakeLists.txt | 1 + platform/hac/CMakeLists.txt | 2 +- platform/hac/include/driver/display/deko.hpp | 8 ++- .../driver/display/deko3d/CCmdVtxRing.h | 6 +- .../include/driver/graphics/StreamBuffer.hpp | 29 +++++---- .../hac/include/modules/graphics/Graphics.hpp | 4 ++ .../hac/include/modules/graphics/Shader.hpp | 6 +- .../hac/include/modules/graphics/Texture.hpp | 44 +++++++++++++ platform/hac/romfs/shaders/transform_vsh.dksh | Bin 1024 -> 768 bytes platform/hac/source/driver/EventQueue.cpp | 10 +-- platform/hac/source/driver/display/deko.cpp | 20 +++++- .../hac/source/modules/graphics/Graphics.cpp | 59 ++++++++++++++++++ .../hac/source/modules/graphics/Shader.cpp | 19 +++--- .../modules/joystick/JoystickModule.cpp | 1 + source/modules/joystick/wrap_Joystick.cpp | 2 +- 19 files changed, 187 insertions(+), 40 deletions(-) create mode 100644 platform/hac/include/modules/graphics/Texture.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index aff940dd..63307b08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,9 @@ if (NINTENDO_SWITCH) 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 @@ -131,7 +134,10 @@ if (NINTENDO_SWITCH) libraries/ddsparse/ddsinfo.h ) - target_link_libraries(${PROJECT_NAME} PRIVATE ddsparse) + 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) diff --git a/include/driver/display/Renderer.tcc b/include/driver/display/Renderer.tcc index 79c67086..58d9581b 100644 --- a/include/driver/display/Renderer.tcc +++ b/include/driver/display/Renderer.tcc @@ -2,11 +2,11 @@ #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 "driver/graphics/DrawCommand.hpp" +#include "modules/graphics/vertex.hpp" #include #include diff --git a/include/driver/graphics/StreamBuffer.tcc b/include/driver/graphics/StreamBuffer.tcc index 188e0985..3749d63c 100644 --- a/include/driver/graphics/StreamBuffer.tcc +++ b/include/driver/graphics/StreamBuffer.tcc @@ -2,6 +2,7 @@ #include "common/Object.hpp" +#include "modules/graphics/Resource.hpp" #include "modules/graphics/vertex.hpp" #include diff --git a/include/modules/graphics/Shader.tcc b/include/modules/graphics/Shader.tcc index 9d61d13f..de2f850e 100644 --- a/include/modules/graphics/Shader.tcc +++ b/include/modules/graphics/Shader.tcc @@ -31,6 +31,9 @@ namespace love static ShaderBase* current; static ShaderBase* standardShaders[STANDARD_MAX_ENUM]; + ShaderBase() + {} + ShaderBase(StandardShader type); virtual ~ShaderBase(); diff --git a/platform/cafe/CMakeLists.txt b/platform/cafe/CMakeLists.txt index 02e5d280..8894fa71 100644 --- a/platform/cafe/CMakeLists.txt +++ b/platform/cafe/CMakeLists.txt @@ -28,6 +28,7 @@ source/driver/audio/SoundChannel.cpp source/driver/display/Framebuffer.cpp source/driver/display/GX2.cpp source/driver/display/Uniform.cpp +source/driver/graphics/StreamBuffer.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp source/modules/graphics/Graphics.cpp diff --git a/platform/hac/CMakeLists.txt b/platform/hac/CMakeLists.txt index 938d8570..4d058328 100644 --- a/platform/hac/CMakeLists.txt +++ b/platform/hac/CMakeLists.txt @@ -34,7 +34,7 @@ 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/deko3d.cpp +source/driver/display/deko.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp source/modules/graphics/Graphics.cpp diff --git a/platform/hac/include/driver/display/deko.hpp b/platform/hac/include/driver/display/deko.hpp index b293f1ee..87dee541 100644 --- a/platform/hac/include/driver/display/deko.hpp +++ b/platform/hac/include/driver/display/deko.hpp @@ -10,7 +10,7 @@ #include "driver/display/deko3d/CCmdVtxRing.h" #include "driver/display/deko3d/CDescriptorSet.h" -#include "modules/graphics/Shader.hpp" +#include "modules/graphics/Texture.hpp" #include "modules/graphics/vertex.hpp" /* Enforces GLSL std140/std430 alignment rules for glm types */ @@ -68,9 +68,11 @@ namespace love void setVertexWinding(Winding winding); - void prepareDraw(GraphicsBase* graphics) override; + void prepareDraw(GraphicsBase* graphics); - void useProgram(const Shader::Program& program); + void useProgram(const dk::Shader& vertex, const dk::Shader& fragment); + + void bindBuffer(BufferUsage usage, DkGpuAddr buffer, size_t size); void onModeChanged() { diff --git a/platform/hac/include/driver/display/deko3d/CCmdVtxRing.h b/platform/hac/include/driver/display/deko3d/CCmdVtxRing.h index a1a17067..b372acf2 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/graphics/StreamBuffer.hpp b/platform/hac/include/driver/graphics/StreamBuffer.hpp index 783865f9..b3a17dff 100644 --- a/platform/hac/include/driver/graphics/StreamBuffer.hpp +++ b/platform/hac/include/driver/graphics/StreamBuffer.hpp @@ -1,7 +1,10 @@ #pragma once -#include "driver/display/deko.hpp" #include "driver/graphics/StreamBuffer.tcc" +#include "modules/graphics/Volatile.hpp" + +#include "driver/display/deko.hpp" +#include "driver/display/deko3d/CMemPool.h" namespace love { @@ -11,32 +14,34 @@ namespace love public: StreamBuffer(BufferUsage usage, size_t size) : StreamBufferBase(usage, size) { - size_t align = (size + DK_CMDMEM_ALIGNMENT - 1) & ~(DK_CMDMEM_ALIGNMENT - 1); - - this->memory = dk::MemBlockMaker { d3d.getDevice(), align } - .setFlags(DkMemBlockFlags_GpuCached | DkMemBlockFlags_CpuUncached) - .create(); + this->sliceSize = (size + DK_CMDMEM_ALIGNMENT - 1) & ~(DK_CMDMEM_ALIGNMENT - 1); } + StreamBuffer(const StreamBuffer&) = delete; + + StreamBuffer& operator=(const StreamBuffer&) = delete; + ~StreamBuffer() - {} + { + this->memory.destroy(); + } - MapInfo StreamBuffer::map(size_t) override + MapInfo map(size_t) { MapInfo info {}; - info.data = &this->memory.getCpuAddr()[this->index]; + info.data = &((T*)this->memory.getCpuAddr())[this->index]; info.size = this->bufferSize - this->frameGPUReadOffset; return info; } - ptrdiff_t getHandle() const override + ptrdiff_t getHandle() const { - return 0; + return (ptrdiff_t)this->memory.getGpuAddr(); } private: - dk::UniqueMemBlock memory; + 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 0833febc..ebed0e21 100644 --- a/platform/hac/include/modules/graphics/Graphics.hpp +++ b/platform/hac/include/modules/graphics/Graphics.hpp @@ -43,6 +43,10 @@ namespace love 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; diff --git a/platform/hac/include/modules/graphics/Shader.hpp b/platform/hac/include/modules/graphics/Shader.hpp index a94752f0..2b582529 100644 --- a/platform/hac/include/modules/graphics/Shader.hpp +++ b/platform/hac/include/modules/graphics/Shader.hpp @@ -1,10 +1,10 @@ #pragma once +#include "driver/display/deko3d/CMemPool.h" +#include "modules/graphics/Graphics.hpp" #include "modules/graphics/Shader.tcc" #include "modules/graphics/Volatile.hpp" -#include "driver/display/deko3d/CMemPool.h" - #include namespace love @@ -30,6 +30,8 @@ namespace love Stage fragment; } program; + Shader(); + Shader(StandardShader shader); virtual ~Shader(); diff --git a/platform/hac/include/modules/graphics/Texture.hpp b/platform/hac/include/modules/graphics/Texture.hpp new file mode 100644 index 00000000..f04c5e70 --- /dev/null +++ b/platform/hac/include/modules/graphics/Texture.hpp @@ -0,0 +1,44 @@ +#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 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(void* data) override + {} + + private: + void createTexture(); + + Slices slices; + + DkImage texture; + DkSampler sampler; + }; +} // namespace love diff --git a/platform/hac/romfs/shaders/transform_vsh.dksh b/platform/hac/romfs/shaders/transform_vsh.dksh index 394efc1170b7474569472813de352018d785a4c8..7e5ba60483ce37134ee6ac80950af1858b819a24 100644 GIT binary patch delta 351 zcmXw#u}T9$5QhJm-Md{aa_JNlE~fDj1UWHbyP*g84CINOFlKveT7HG4|at;andAm9Pz-UxX}Ruw7nh;j+gl?AM(G7zZ8HK(40`wu|sMR<)F zS^tm#bKy$EzesiwVkx}AN;o*PnYq`hw4I{_Ndj+dWbU)l&X~-7RqOiA1Mfe_b=>rA Q<3}*fKQ1Cnor`)Oe`ji8i2wiq literal 1024 zcmdr}Jx>Bb5S`h*V^5p2(~gZ6w04wYVxmL}6ANrlxd8D4BPy}-3KA&nZLg!XqqXBF zpq-}$&{y~M zNx_vyodNL<6fNx1+(LF($M>W2UHQ!V zjO@*Ivd=o^*`EPgKU^5jbuePxSbZeW-Xd40tS6sWtZ{!MFL#(b9+gBg8j68I?2u diff --git a/platform/hac/source/driver/EventQueue.cpp b/platform/hac/source/driver/EventQueue.cpp index 246c7b28..df412713 100644 --- a/platform/hac/source/driver/EventQueue.cpp +++ b/platform/hac/source/driver/EventQueue.cpp @@ -94,9 +94,9 @@ namespace love 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); @@ -105,11 +105,11 @@ namespace love this->sendGamepadButtonEvent(SUBTYPE_GAMEPADUP, 0, 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)); + float value = joystick->getAxis(JoystickBase::GamepadAxis(input)); this->sendGamepadAxisEvent(0, input, value); } } diff --git a/platform/hac/source/driver/display/deko.cpp b/platform/hac/source/driver/display/deko.cpp index f3cdbcac..ad7d3c78 100644 --- a/platform/hac/source/driver/display/deko.cpp +++ b/platform/hac/source/driver/display/deko.cpp @@ -99,10 +99,10 @@ namespace love return this->framebuffers[this->framebufferSlot].getImage(); } - void deko3d::useProgram(const Shader::Program& program) + void deko3d::useProgram(const dk::Shader& vertex, const dk::Shader& fragment) { // clang-format off - this->commandBuffer.bindShaders(DkStageFlag_GraphicsMask, { &program.vertex.shader, &program.fragment.shader }); + this->commandBuffer.bindShaders(DkStageFlag_GraphicsMask, { &vertex, &fragment }); this->commandBuffer.bindUniformBuffer(DkStage_Vertex, 0, this->uniformBuffer.getGpuAddr(), this->uniformBuffer.getSize()); // clang-format off } @@ -130,6 +130,22 @@ namespace love } } + void deko3d::bindBuffer(BufferUsage usage, DkGpuAddr buffer, size_t size) + { + if (usage == BUFFERUSAGE_VERTEX) + { + this->commandBuffer.bindVtxBuffer(0, buffer, size); + return; + } + else if (usage == BUFFERUSAGE_INDEX) + { + this->commandBuffer.bindIdxBuffer(DkIdxFormat_Uint16, buffer); + } + } + + void deko3d::prepareDraw(GraphicsBase* graphics) + {} + void deko3d::present() { if (!this->swapchain) diff --git a/platform/hac/source/modules/graphics/Graphics.cpp b/platform/hac/source/modules/graphics/Graphics.cpp index 8ca4b5f9..4a19b883 100644 --- a/platform/hac/source/modules/graphics/Graphics.cpp +++ b/platform/hac/source/modules/graphics/Graphics.cpp @@ -30,6 +30,59 @@ namespace love } } + 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::backbufferChanged(int width, int height, int pixelWidth, int pixelHeight, bool stencil, bool depth, int msaa) { @@ -284,4 +337,10 @@ namespace love auto* window = Module::getInstance(M_WINDOW); return this->active && this->created && window != nullptr && window->isOpen(); } + + void Graphics::draw(const DrawIndexedCommand& command) + {} + + void Graphics::draw(const DrawCommand& command) + {} } // namespace love diff --git a/platform/hac/source/modules/graphics/Shader.cpp b/platform/hac/source/modules/graphics/Shader.cpp index 6df02576..af3ca12f 100644 --- a/platform/hac/source/modules/graphics/Shader.cpp +++ b/platform/hac/source/modules/graphics/Shader.cpp @@ -1,6 +1,15 @@ #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 @@ -13,12 +22,8 @@ namespace love uint32_t num_programs; }; -#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") + Shader::Shader() + {} Shader::Shader(StandardShader shader) { @@ -54,7 +59,7 @@ namespace love if (Shader::current != this) { Shader::current = this; - d3d.useProgram(this->program); + d3d.useProgram(this->program.vertex.shader, this->program.fragment.shader); ++shaderSwitches; } } diff --git a/platform/hac/source/modules/joystick/JoystickModule.cpp b/platform/hac/source/modules/joystick/JoystickModule.cpp index fd85f075..dee71c12 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/modules/joystick/wrap_Joystick.cpp b/source/modules/joystick/wrap_Joystick.cpp index d340d6d4..4da21f81 100644 --- a/source/modules/joystick/wrap_Joystick.cpp +++ b/source/modules/joystick/wrap_Joystick.cpp @@ -432,7 +432,7 @@ static constexpr luaL_Reg functions[] = { }; #if !defined(__WIIU__) -static constexpr luaL_Reg extFunctions[] = {}; +static constexpr std::span extFunctions = {}; #else #include "modules/joystick/kpad/Joystick.hpp" From 92b1112be476cd97a2263b8161196d3efe2462a5 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Mon, 4 Nov 2024 08:41:34 -0500 Subject: [PATCH 42/49] why was this here --- platform/cafe/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/platform/cafe/CMakeLists.txt b/platform/cafe/CMakeLists.txt index 8894fa71..02e5d280 100644 --- a/platform/cafe/CMakeLists.txt +++ b/platform/cafe/CMakeLists.txt @@ -28,7 +28,6 @@ source/driver/audio/SoundChannel.cpp source/driver/display/Framebuffer.cpp source/driver/display/GX2.cpp source/driver/display/Uniform.cpp -source/driver/graphics/StreamBuffer.cpp source/driver/EventQueue.cpp source/modules/audio/Source.cpp source/modules/graphics/Graphics.cpp From 1085133e26b6c5cfb80a3a94fc10194a205b919f Mon Sep 17 00:00:00 2001 From: TurtleP Date: Mon, 4 Nov 2024 21:44:59 -0500 Subject: [PATCH 43/49] switch primitives work --- include/driver/graphics/DrawCommand.hpp | 4 +- include/modules/graphics/Graphics.tcc | 8 +- include/modules/graphics/Texture.tcc | 2 +- include/modules/graphics/vertex.hpp | 1 + log.txt | 42 ++++ .../include/modules/graphics/Graphics.hpp | 2 + .../cafe/source/modules/graphics/Graphics.cpp | 8 + .../ctr/include/modules/graphics/Graphics.hpp | 8 + .../ctr/include/modules/graphics/Texture.hpp | 4 +- platform/hac/CMakeLists.txt | 3 + platform/hac/include/driver/display/deko.hpp | 76 ++++++- .../driver/display/deko3d/bitalloc.hpp | 40 ---- .../include/driver/graphics/Attributes.hpp | 50 +++++ .../hac/include/driver/graphics/BitAlloc.hpp | 43 ++++ .../include/driver/graphics/StreamBuffer.hpp | 13 +- .../hac/include/modules/graphics/Graphics.hpp | 2 + .../hac/include/modules/graphics/Texture.hpp | 26 ++- platform/hac/source/boot.cpp | 2 +- .../hac/source/driver/display/Framebuffer.cpp | 3 +- platform/hac/source/driver/display/deko.cpp | 123 +++++++++-- .../source/driver/graphics/StreamBuffer.cpp | 20 ++ .../hac/source/modules/graphics/Graphics.cpp | 85 +++++++- .../hac/source/modules/graphics/Shader.cpp | 22 +- .../hac/source/modules/graphics/Texture.cpp | 205 ++++++++++++++++++ source/modules/font/freetype/Font.cpp | 11 +- 25 files changed, 711 insertions(+), 92 deletions(-) create mode 100644 log.txt delete mode 100644 platform/hac/include/driver/display/deko3d/bitalloc.hpp create mode 100644 platform/hac/include/driver/graphics/Attributes.hpp create mode 100644 platform/hac/include/driver/graphics/BitAlloc.hpp create mode 100644 platform/hac/source/driver/graphics/StreamBuffer.cpp create mode 100644 platform/hac/source/modules/graphics/Texture.cpp diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp index d135550c..7a887716 100644 --- a/include/driver/graphics/DrawCommand.hpp +++ b/include/driver/graphics/DrawCommand.hpp @@ -2,12 +2,12 @@ #include "common/Exception.hpp" +#include "driver/graphics/StreamBuffer.hpp" + #include "modules/graphics/Shader.tcc" #include "modules/graphics/Texture.tcc" #include "modules/graphics/vertex.hpp" -#include "driver/graphics/StreamBuffer.hpp" - namespace love { struct BatchedDrawCommand diff --git a/include/modules/graphics/Graphics.tcc b/include/modules/graphics/Graphics.tcc index 3f8042b1..ceb47047 100644 --- a/include/modules/graphics/Graphics.tcc +++ b/include/modules/graphics/Graphics.tcc @@ -535,13 +535,7 @@ namespace love return this->states.back().lineJoin; } - void setPointSize(float size) - { - if (size != this->states.back().pointSize) - this->flushBatchedDraws(); - - this->states.back().pointSize = size; - } + virtual void setPointSize(float size) = 0; float getPointSize() const { diff --git a/include/modules/graphics/Texture.tcc b/include/modules/graphics/Texture.tcc index 05cf5877..42ea4537 100644 --- a/include/modules/graphics/Texture.tcc +++ b/include/modules/graphics/Texture.tcc @@ -312,7 +312,7 @@ namespace love * This is ONLY used on 3DS due to memory constraints. * The font textures are already on-device! */ - virtual void setHandleData(void* data) = 0; + virtual void setHandleData(ptrdiff_t data) = 0; virtual void setSamplerState(const SamplerState& state) = 0; diff --git a/include/modules/graphics/vertex.hpp b/include/modules/graphics/vertex.hpp index 4b36766c..82244c15 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -185,6 +185,7 @@ namespace love 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. }; diff --git a/log.txt b/log.txt new file mode 100644 index 00000000..66482b45 --- /dev/null +++ b/log.txt @@ -0,0 +1,42 @@ +-- Updating Catnip cache +-- Entering /d/lovepotion/build/main.release +[1/6] Building CXX object CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/deko.o +D:/lovepotion/platform/hac/source/driver/display/deko.cpp: In constructor 'love::deko3d::deko3d()': +D:/lovepotion/platform/hac/source/driver/display/deko.cpp:17:20: warning: 'void* memset(void*, int, size_t)' clearing an object of non-trivial type 'struct love::deko3d::Context'; use assignment or value-initialization instead [-Wclass-memaccess] + 17 | std::memset(&this->context, 0, sizeof(this->context)); + | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In file included from D:/lovepotion/platform/hac/source/driver/display/deko.cpp:1: +D:/lovepotion/platform/hac/include/driver/display/deko.hpp:225:16: note: 'struct love::deko3d::Context' declared here + 225 | struct Context : public ContextBase + | ^~~~~~~ +D:/lovepotion/platform/hac/source/driver/display/deko.cpp: In member function 'virtual void love::deko3d::prepareDraw(love::GraphicsBase*)': +D:/lovepotion/platform/hac/source/driver/display/deko.cpp:158:44: warning: unused parameter 'graphics' [-Wunused-parameter] + 158 | void deko3d::prepareDraw(GraphicsBase* graphics) + | ~~~~~~~~~~~~~~^~~~~~~~ +[2/6] Building CXX object CMakeFiles/lovepotion.dir/source/modules/love/love.o +[3/6] Building CXX object CMakeFiles/lovepotion.dir/source/modules/window/wrap_Window.o +[4/6] Linking CXX executable lovepotion.elf +FAILED: lovepotion.elf lovepotion.lst lovepotion.map /d/lovepotion/build/main.release/lovepotion.lst /d/lovepotion/build/main.release/lovepotion.map +: && /opt/devkitpro/devkitA64/bin/aarch64-none-elf-g++.exe -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec -ffunction-sections -fdata-sections -D__SWITCH__ -g -O2 -DNDEBUG -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec -L/opt/devkitpro/libnx/lib -L/opt/devkitpro/portlibs/switch/lib -fPIE -specs=/opt/devkitpro/libnx/switch.specs -Wl,-Map,/d/lovepotion/build/main.release/lovepotion.map CMakeFiles/lovepotion.dir/platform/hac/source/boot.o CMakeFiles/lovepotion.dir/platform/hac/source/common/screen.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/audio/DigitalSound.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/audio/MemoryPool.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/deko3d/CIntrusiveTree.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/deko3d/CMemPool.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/Framebuffer.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/deko.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/EventQueue.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/audio/Source.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Shader.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/joystick/Joystick.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/joystick/JoystickModule.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/keyboard/Keyboard.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/system/System.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/timer/Timer.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/window/Window.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/ASTCHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/ddsHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/JPGHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/KTXHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/PKMHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/PNGHandler.o CMakeFiles/lovepotion.dir/source/modules/font/freetype/Font.o CMakeFiles/lovepotion.dir/source/modules/font/freetype/TrueTypeRasterizer.o CMakeFiles/lovepotion.dir/source/modules/graphics/freetype/Font.o CMakeFiles/lovepotion.dir/source/main.o CMakeFiles/lovepotion.dir/source/common/b64.o CMakeFiles/lovepotion.dir/source/common/Data.o CMakeFiles/lovepotion.dir/source/common/float.o CMakeFiles/lovepotion.dir/source/common/luax.o CMakeFiles/lovepotion.dir/source/common/Matrix.o CMakeFiles/lovepotion.dir/source/common/Message.o CMakeFiles/lovepotion.dir/source/common/Module.o CMakeFiles/lovepotion.dir/source/common/Object.o CMakeFiles/lovepotion.dir/source/common/pixelformat.o CMakeFiles/lovepotion.dir/source/common/Reference.o CMakeFiles/lovepotion.dir/source/common/Stream.o CMakeFiles/lovepotion.dir/source/common/types.o CMakeFiles/lovepotion.dir/source/common/Variant.o CMakeFiles/lovepotion.dir/source/utility/guid.o CMakeFiles/lovepotion.dir/source/modules/audio/Audio.o CMakeFiles/lovepotion.dir/source/modules/audio/Pool.o CMakeFiles/lovepotion.dir/source/modules/audio/Source.o CMakeFiles/lovepotion.dir/source/modules/audio/wrap_Audio.o CMakeFiles/lovepotion.dir/source/modules/audio/wrap_Source.o CMakeFiles/lovepotion.dir/source/modules/data/ByteData.o CMakeFiles/lovepotion.dir/source/modules/data/CompressedData.o CMakeFiles/lovepotion.dir/source/modules/data/DataModule.o CMakeFiles/lovepotion.dir/source/modules/data/DataStream.o CMakeFiles/lovepotion.dir/source/modules/data/DataView.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_ByteData.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_CompressedData.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_Data.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_DataModule.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_DataView.o CMakeFiles/lovepotion.dir/source/modules/event/Event.o CMakeFiles/lovepotion.dir/source/modules/event/wrap_Event.o CMakeFiles/lovepotion.dir/source/modules/filesystem/FileData.o CMakeFiles/lovepotion.dir/source/modules/filesystem/wrap_File.o CMakeFiles/lovepotion.dir/source/modules/filesystem/wrap_FileData.o CMakeFiles/lovepotion.dir/source/modules/filesystem/wrap_Filesystem.o CMakeFiles/lovepotion.dir/source/modules/font/Font.o CMakeFiles/lovepotion.dir/source/modules/font/GlyphData.o CMakeFiles/lovepotion.dir/source/modules/font/Rasterizer.o CMakeFiles/lovepotion.dir/source/modules/font/TextShaper.o CMakeFiles/lovepotion.dir/source/modules/font/GenericShaper.o CMakeFiles/lovepotion.dir/source/modules/font/wrap_Font.o CMakeFiles/lovepotion.dir/source/modules/font/wrap_Rasterizer.o CMakeFiles/lovepotion.dir/source/modules/font/wrap_GlyphData.o CMakeFiles/lovepotion.dir/source/modules/graphics/Buffer.o CMakeFiles/lovepotion.dir/source/modules/graphics/Graphics.o CMakeFiles/lovepotion.dir/source/modules/graphics/FontBase.o CMakeFiles/lovepotion.dir/source/modules/graphics/Mesh.o CMakeFiles/lovepotion.dir/source/modules/graphics/Polyline.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Mesh.o CMakeFiles/lovepotion.dir/source/modules/graphics/TextBatch.o CMakeFiles/lovepotion.dir/source/modules/graphics/renderstate.o CMakeFiles/lovepotion.dir/source/modules/graphics/samplerstate.o CMakeFiles/lovepotion.dir/source/modules/graphics/Shader.o CMakeFiles/lovepotion.dir/source/modules/graphics/Quad.o CMakeFiles/lovepotion.dir/source/modules/graphics/Volatile.o CMakeFiles/lovepotion.dir/source/modules/graphics/vertex.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Graphics.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_TextBatch.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Font.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Quad.o CMakeFiles/lovepotion.dir/source/modules/graphics/Texture.o CMakeFiles/lovepotion.dir/source/modules/image/CompressedImageData.o CMakeFiles/lovepotion.dir/source/modules/image/CompressedSlice.o CMakeFiles/lovepotion.dir/source/modules/image/FormatHandler.o CMakeFiles/lovepotion.dir/source/modules/image/Image.o CMakeFiles/lovepotion.dir/source/modules/image/ImageData.o CMakeFiles/lovepotion.dir/source/modules/image/ImageDataBase.o CMakeFiles/lovepotion.dir/source/modules/image/wrap_CompressedImageData.o CMakeFiles/lovepotion.dir/source/modules/image/wrap_Image.o CMakeFiles/lovepotion.dir/source/modules/image/wrap_ImageData.o CMakeFiles/lovepotion.dir/source/modules/joystick/JoystickModule.o CMakeFiles/lovepotion.dir/source/modules/joystick/wrap_Joystick.o CMakeFiles/lovepotion.dir/source/modules/joystick/wrap_JoystickModule.o CMakeFiles/lovepotion.dir/source/modules/keyboard/wrap_Keyboard.o CMakeFiles/lovepotion.dir/source/modules/love/love.o CMakeFiles/lovepotion.dir/source/modules/math/BezierCurve.o CMakeFiles/lovepotion.dir/source/modules/math/MathModule.o CMakeFiles/lovepotion.dir/source/modules/math/RandomGenerator.o CMakeFiles/lovepotion.dir/source/modules/math/Transform.o CMakeFiles/lovepotion.dir/source/modules/math/wrap_BezierCurve.o CMakeFiles/lovepotion.dir/source/modules/math/wrap_MathModule.o CMakeFiles/lovepotion.dir/source/modules/math/wrap_RandomGenerator.o CMakeFiles/lovepotion.dir/source/modules/math/wrap_Transform.o CMakeFiles/lovepotion.dir/source/modules/sensor/Sensor.o CMakeFiles/lovepotion.dir/source/modules/sensor/wrap_Sensor.o CMakeFiles/lovepotion.dir/source/modules/sound/Decoder.o CMakeFiles/lovepotion.dir/source/modules/sound/Sound.o CMakeFiles/lovepotion.dir/source/modules/sound/SoundData.o CMakeFiles/lovepotion.dir/source/modules/sound/wrap_Decoder.o CMakeFiles/lovepotion.dir/source/modules/sound/wrap_Sound.o CMakeFiles/lovepotion.dir/source/modules/sound/wrap_SoundData.o CMakeFiles/lovepotion.dir/source/modules/system/wrap_System.o CMakeFiles/lovepotion.dir/source/modules/thread/Channel.o CMakeFiles/lovepotion.dir/source/modules/thread/LuaThread.o CMakeFiles/lovepotion.dir/source/modules/thread/Thread.o CMakeFiles/lovepotion.dir/source/modules/thread/Threadable.o CMakeFiles/lovepotion.dir/source/modules/thread/ThreadModule.o CMakeFiles/lovepotion.dir/source/modules/thread/wrap_Channel.o CMakeFiles/lovepotion.dir/source/modules/thread/wrap_LuaThread.o CMakeFiles/lovepotion.dir/source/modules/thread/wrap_Thread.o CMakeFiles/lovepotion.dir/source/modules/timer/wrap_Timer.o CMakeFiles/lovepotion.dir/source/modules/touch/Touch.o CMakeFiles/lovepotion.dir/source/modules/touch/wrap_Touch.o CMakeFiles/lovepotion.dir/source/modules/window/wrap_Window.o CMakeFiles/lovepotion.dir/source/modules/data/misc/Compressor.o CMakeFiles/lovepotion.dir/source/modules/data/misc/HashFunction.o CMakeFiles/lovepotion.dir/source/modules/filesystem/physfs/File.o CMakeFiles/lovepotion.dir/source/modules/filesystem/physfs/Filesystem.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/FLACDecoder.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/ModPlugDecoder.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/MP3Decoder.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/VorbisDecoder.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/WaveDecoder.o -o lovepotion.elf -ldeko3d /opt/devkitpro/portlibs/switch/lib/libfreetype.a -lbz2 -lpng -lturbojpeg libddsparse.a liblove_physfs.a libwuff.a libnoise1234.a -lvorbisidec -lz libluabit.a liblua53.a -logg -lmodplug liblua-https.a libluasocket.a /opt/devkitpro/portlibs/switch/lib/liblz4.a /opt/devkitpro/portlibs/switch/lib/libcurl.a /opt/devkitpro/libnx/lib/libnx.a /opt/devkitpro/portlibs/switch/lib/libz.a /opt/devkitpro/libnx/lib/libnx.a /opt/devkitpro/portlibs/switch/lib/libz.a /opt/devkitpro/portlibs/switch/lib/liblua5.1.a /opt/devkitpro/devkitA64/aarch64-none-elf/lib/pic/libm.a -lnx -lm && /bin/sh CMakeFiles/lovepotion.dir/post-build.sh 62f60396062a37ea +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0x40): undefined reference to `love::Graphics::newTexture(love::TextureBase::Settings const&, love::TextureBase::Slices const*)' +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0x48): undefined reference to `love::Graphics::newFont(love::Rasterizer*)' +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0x50): undefined reference to `love::Graphics::newDefaultFont(int, love::Rasterizer::Settings const&)' +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0xb0): undefined reference to `love::Graphics::setRenderTargetsInternal(love::GraphicsBase::RenderTargets const&, int, int, bool)' +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0xb8): undefined reference to `love::Graphics::isPixelFormatSupported(love::PixelFormat, unsigned int)' +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o: in function `Wrap_Texture::setDepthSampleMode(lua_State*)': +D:/lovepotion/source/modules/graphics/wrap_Texture.cpp:370:(.text._ZN12Wrap_Texture18setDepthSampleModeEP9lua_State+0xf4): undefined reference to `love::Texture::setSamplerState(love::SamplerState const&)' +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o: in function `Wrap_Texture::setWrap(lua_State*)': +D:/lovepotion/source/modules/graphics/wrap_Texture.cpp:261:(.text._ZN12Wrap_Texture7setWrapEP9lua_State+0x1bc): undefined reference to `love::Texture::setSamplerState(love::SamplerState const&)' +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o: in function `Wrap_Texture::setMipmapFilter(lua_State*)': +D:/lovepotion/source/modules/graphics/wrap_Texture.cpp:219:(.text._ZN12Wrap_Texture15setMipmapFilterEP9lua_State+0x7c): undefined reference to `love::Texture::setSamplerState(love::SamplerState const&)' +C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o: in function `Wrap_Texture::setFilter(lua_State*)': +D:/lovepotion/source/modules/graphics/wrap_Texture.cpp:175:(.text._ZN12Wrap_Texture9setFilterEP9lua_State+0x170): undefined reference to `love::Texture::setSamplerState(love::SamplerState const&)' +collect2.exe: error: ld returned 1 exit status +ninja: build stopped: subcommand failed. +CMake Error at /opt/devkitpro/cmake/Catnip/Verb-Build.cmake:91 (message): + Failed to build main.release +Call Stack (most recent call first): + /opt/devkitpro/cmake/Catnip/Verb-Build.cmake:102 (__catnip_build) + /opt/devkitpro/cmake/catnip-main.cmake:11 (include) + + diff --git a/platform/cafe/include/modules/graphics/Graphics.hpp b/platform/cafe/include/modules/graphics/Graphics.hpp index 903c4e4e..047cd80a 100644 --- a/platform/cafe/include/modules/graphics/Graphics.hpp +++ b/platform/cafe/include/modules/graphics/Graphics.hpp @@ -34,6 +34,8 @@ namespace love 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; diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index c1419a41..e7f08896 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -208,6 +208,14 @@ namespace love state.scissorRect = scissor; } + void Graphics::setPointSize(float size) + { + if (size != this->states.back().pointSize) + this->flushBatchedDraws(); + + this->states.back().pointSize = size; + } + void Graphics::setScissor() { if (this->states.back().scissor) diff --git a/platform/ctr/include/modules/graphics/Graphics.hpp b/platform/ctr/include/modules/graphics/Graphics.hpp index 451af363..dcd7e2f6 100644 --- a/platform/ctr/include/modules/graphics/Graphics.hpp +++ b/platform/ctr/include/modules/graphics/Graphics.hpp @@ -31,6 +31,14 @@ namespace love 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; diff --git a/platform/ctr/include/modules/graphics/Texture.hpp b/platform/ctr/include/modules/graphics/Texture.hpp index f519b1a0..c5d60edc 100644 --- a/platform/ctr/include/modules/graphics/Texture.hpp +++ b/platform/ctr/include/modules/graphics/Texture.hpp @@ -32,9 +32,9 @@ namespace love void generateMipmapsInternal() override; - void setHandleData(void* data) override + void setHandleData(ptrdiff_t data) override { - this->texture->data = data; + this->texture->data = (void*)data; } // bool validateDimensions(bool throwException) const; diff --git a/platform/hac/CMakeLists.txt b/platform/hac/CMakeLists.txt index 4d058328..be610d7a 100644 --- a/platform/hac/CMakeLists.txt +++ b/platform/hac/CMakeLists.txt @@ -35,9 +35,12 @@ source/driver/display/deko3d/CIntrusiveTree.cpp source/driver/display/deko3d/CMemPool.cpp source/driver/display/Framebuffer.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/deko.hpp b/platform/hac/include/driver/display/deko.hpp index 87dee541..217f3f74 100644 --- a/platform/hac/include/driver/display/deko.hpp +++ b/platform/hac/include/driver/display/deko.hpp @@ -6,11 +6,11 @@ #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/CCmdVtxRing.h" #include "driver/display/deko3d/CDescriptorSet.h" -#include "modules/graphics/Texture.hpp" #include "modules/graphics/vertex.hpp" /* Enforces GLSL std140/std430 alignment rules for glm types */ @@ -22,6 +22,8 @@ #include #include +#define MAX_OBJECTS 0x400 + namespace love { class deko3d : public RendererBase @@ -68,6 +70,10 @@ namespace love 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); @@ -111,6 +117,13 @@ namespace love } } + void registerTexture(TextureBase* texture, bool registering); + + void ensureInFrame(); + + void drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, + uint32_t instanceCount); + // clang-format off ENUMMAP_DECLARE(BlendOperations, BlendOperation, DkBlendOp, { BLENDOP_ADD, DkBlendOp_Add }, @@ -144,6 +157,58 @@ namespace love { 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(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: @@ -163,8 +228,6 @@ namespace love static constexpr int COMMAND_SIZE = 0x100000; static constexpr int VERTEX_COMMAND_SIZE = 0x100000; - void ensureInFrame(); - void createFramebuffers(); void destroyFramebuffers(); @@ -174,6 +237,7 @@ namespace love dk::RasterizerState rasterizer; dk::ColorWriteState colorWrite; dk::BlendState blend; + dk::ColorState color; dk::Image* boundFramebuffer; } context; @@ -207,6 +271,10 @@ namespace love CCmdMemRing<2> commands; std::array targets; + + BitwiseAlloc textureHandles; + CDescriptorSet imageSet; + CDescriptorSet samplerSet; }; extern deko3d d3d; 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 e1853229..00000000 --- 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 00000000..57c93d41 --- /dev/null +++ b/platform/hac/include/driver/graphics/Attributes.hpp @@ -0,0 +1,50 @@ +#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 } + }; + // clang-format on + + static bool getAttributes(CommonFormat format, Attributes& out) + { + switch (format) + { + case CommonFormat::XYf_RGBAf: + default: + { + out.attributeState = PrimitiveAttribState; + out.bufferState = PrimitiveBufferState; + return true; + } + } + + return false; + } + } // 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 00000000..939af9e0 --- /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 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; + } + }; +} // namespace love diff --git a/platform/hac/include/driver/graphics/StreamBuffer.hpp b/platform/hac/include/driver/graphics/StreamBuffer.hpp index b3a17dff..84fa19bc 100644 --- a/platform/hac/include/driver/graphics/StreamBuffer.hpp +++ b/platform/hac/include/driver/graphics/StreamBuffer.hpp @@ -1,11 +1,10 @@ #pragma once +#include "driver/display/deko.hpp" + #include "driver/graphics/StreamBuffer.tcc" #include "modules/graphics/Volatile.hpp" -#include "driver/display/deko.hpp" -#include "driver/display/deko3d/CMemPool.h" - namespace love { template @@ -17,6 +16,12 @@ namespace love 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; @@ -35,6 +40,8 @@ namespace love return info; } + size_t unmap(size_t); + ptrdiff_t getHandle() const { return (ptrdiff_t)this->memory.getGpuAddr(); diff --git a/platform/hac/include/modules/graphics/Graphics.hpp b/platform/hac/include/modules/graphics/Graphics.hpp index ebed0e21..db5d6c60 100644 --- a/platform/hac/include/modules/graphics/Graphics.hpp +++ b/platform/hac/include/modules/graphics/Graphics.hpp @@ -39,6 +39,8 @@ namespace love 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; diff --git a/platform/hac/include/modules/graphics/Texture.hpp b/platform/hac/include/modules/graphics/Texture.hpp index f04c5e70..dbf4d2b2 100644 --- a/platform/hac/include/modules/graphics/Texture.hpp +++ b/platform/hac/include/modules/graphics/Texture.hpp @@ -3,6 +3,7 @@ #include "modules/graphics/Texture.tcc" #include "modules/graphics/Volatile.hpp" +#include "driver/display/deko3d/CMemPool.h" #include namespace love @@ -24,21 +25,38 @@ namespace love ptrdiff_t getSamplerHandle() const override; + dk::ImageDescriptor getDescriptorHandle() + { + return this->descriptor; + } + + dk::SamplerDescriptor getSamplerDescriptorHandle() + { + 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(void* data) override - {} + void setHandleData(ptrdiff_t data) override + { + this->handle = (DkResHandle)data; + } private: void createTexture(); Slices slices; - DkImage texture; - DkSampler sampler; + dk::Image image; + dk::ImageDescriptor descriptor; + dk::Sampler sampler; + dk::SamplerDescriptor samplerDescriptor; + + DkResHandle handle; + CMemPool::Handle memory; }; } // namespace love diff --git a/platform/hac/source/boot.cpp b/platform/hac/source/boot.cpp index 066be849..c96ab92a 100644 --- a/platform/hac/source/boot.cpp +++ b/platform/hac/source/boot.cpp @@ -16,7 +16,7 @@ namespace love static constexpr std::array services = {{ { "bsd", BIND(socketInitializeDefault), &socketExit }, - { "pl:u", BIND(plInitialize, PlServiceType_User), &plExit }, + { "pl:u", BIND(plInitialize, PlServiceType_User), &plExit }, { "acc:a", BIND(accountInitialize, AccountServiceType_Application), &accountExit }, { "set", BIND(setInitialize), &setExit }, { "set:sys", BIND(setsysInitialize), &setsysExit }, diff --git a/platform/hac/source/driver/display/Framebuffer.cpp b/platform/hac/source/driver/display/Framebuffer.cpp index 452b43d8..8aca4675 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; diff --git a/platform/hac/source/driver/display/deko.cpp b/platform/hac/source/driver/display/deko.cpp index ad7d3c78..12ff633e 100644 --- a/platform/hac/source/driver/display/deko.cpp +++ b/platform/hac/source/driver/display/deko.cpp @@ -1,4 +1,5 @@ #include "driver/display/deko.hpp" +#include "driver/graphics/Attributes.hpp" namespace love { @@ -7,15 +8,13 @@ namespace love 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) - { - std::memset(&this->context, 0, sizeof(this->context)); - } + framebufferSlot(-1), + context {} + {} void deko3d::initialize() { @@ -26,7 +25,14 @@ namespace love this->transform.modelView = glm::mat4(1.0f); this->commands.allocate(this->data, COMMAND_SIZE); + this->commandBuffer = dk::CmdBufMaker { this->device }.create(); + this->createFramebuffers(); + this->ensureInFrame(); + + this->context.rasterizer.setCullMode(DkFace_None); + this->context.rasterizer.setFrontFace(DkFrontFace_CCW); + this->context.color.setBlendEnable(0, true); this->initialized = true; } @@ -79,6 +85,9 @@ namespace love { if (!this->inFrame) { + if (this->framebufferSlot < 0) + this->framebufferSlot = this->mainQueue.acquireImage(this->swapchain); + this->commands.begin(this->commandBuffer); this->inFrame = true; } @@ -86,16 +95,23 @@ namespace love void deko3d::clear(const Color& color) { + if (!this->context.boundFramebuffer) + 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(); } @@ -107,12 +123,23 @@ namespace love // clang-format off } + void deko3d::registerTexture(TextureBase* texture, bool registering) + { + if (registering) + { + const auto handle = this->textureHandles.allocate(); + texture->setHandleData(handle); + return; + } + + this->textureHandles.reset((DkResHandle)texture->getHandle()); + } + void deko3d::bindFramebuffer(dk::Image& framebuffer) { if (!this->swapchain) return; - this->ensureInFrame(); bool bindingModified = false; if (this->context.boundFramebuffer != &framebuffer) @@ -133,18 +160,36 @@ namespace love void deko3d::bindBuffer(BufferUsage usage, DkGpuAddr buffer, size_t size) { if (usage == BUFFERUSAGE_VERTEX) - { this->commandBuffer.bindVtxBuffer(0, buffer, size); - return; - } else if (usage == BUFFERUSAGE_INDEX) - { this->commandBuffer.bindIdxBuffer(DkIdxFormat_Uint16, buffer); - } + } + + void deko3d::drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, uint32_t instanceCount) + { + vertex::Attributes attributes {}; + bool success = getAttributes(CommonFormat::XYf_RGBAf, attributes); + + if (!success) + return; + + this->commandBuffer.bindVtxAttribState(attributes.attributeState); + this->commandBuffer.bindVtxBufferState(attributes.bufferState); + + this->commandBuffer.drawIndexed(primitive, indexCount, instanceCount, indexOffset, 0, 0); } void deko3d::prepareDraw(GraphicsBase* graphics) - {} + { + this->commandBuffer.bindRasterizerState(this->context.rasterizer); + this->commandBuffer.bindBlendStates(0, this->context.blend); + + // this->commandBuffer.bindColorState(this->context.color); + + this->commandBuffer.pushConstants(this->uniformBuffer.getGpuAddr(), + this->uniformBuffer.getSize(), 0, TRANSFORM_SIZE, + &this->transform); + } void deko3d::present() { @@ -153,13 +198,16 @@ namespace love 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 = this->mainQueue.acquireImage(this->swapchain); + this->framebufferSlot = -1; } void deko3d::setVertexWinding(Winding winding) @@ -171,6 +219,11 @@ namespace love this->context.rasterizer.setFrontFace(face); } + void deko3d::setPointSize(float size) + { + this->commandBuffer.setPointSize(size); + } + void deko3d::setCullMode(CullMode mode) { DkFace cullMode; @@ -230,6 +283,47 @@ namespace love this->context.blend.setDstAlphaBlendFactor(destAlpha); } + void deko3d::setSamplerState(TextureBase* texture, const SamplerState&state) + { + auto* sampler = (dk::Sampler*)texture->getSamplerHandle(); + 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; + + sampler->setFilter(minFilter, magFilter); + + 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); + + // const auto descriptor = ((Texture*)texture)->getDescriptorHandle(); + // this->imageSet.update(this->commandBuffer, index, descriptor); + + // auto samplerDescriptor = ((Texture*)texture)->getSamplerDescriptorHandle(); + // samplerDescriptor.initialize(*sampler); + + // this->samplerSet.update(this->commandBuffer, index, samplerDescriptor); + } + static DkScissor dkScissorFromRect(const Rect& rect) { DkScissor scissor {}; @@ -280,6 +374,9 @@ namespace love _viewport = dkViewportFromRect(viewport); this->commandBuffer.setViewports(0, { _viewport }); + + const auto ortho = glm::ortho(0.0f, (float)viewport.w, (float)viewport.h, 0.0f, -10.0f, 10.0f); + this->transform.projection = ortho; } deko3d d3d; diff --git a/platform/hac/source/driver/graphics/StreamBuffer.cpp b/platform/hac/source/driver/graphics/StreamBuffer.cpp new file mode 100644 index 00000000..ad7f2a89 --- /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.getGpuAddr(), this->memory.getSize()); + return this->index; + } + + template<> + size_t StreamBuffer::unmap(size_t) + { + d3d.bindBuffer(this->usage, this->memory.getGpuAddr(), this->memory.getSize()); + return this->index; + } +} // namespace love diff --git a/platform/hac/source/modules/graphics/Graphics.cpp b/platform/hac/source/modules/graphics/Graphics.cpp index 4a19b883..3a30ffa1 100644 --- a/platform/hac/source/modules/graphics/Graphics.cpp +++ b/platform/hac/source/modules/graphics/Graphics.cpp @@ -4,6 +4,8 @@ #include "modules/window/Window.hpp" #include "modules/graphics/Shader.hpp" +#include "modules/graphics/Texture.hpp" +#include "modules/graphics/freetype/Font.hpp" namespace love { @@ -80,7 +82,7 @@ namespace love void Graphics::setActiveScreen() { - // gx2.ensureInFrame(); + d3d.ensureInFrame(); } void Graphics::backbufferChanged(int width, int height, int pixelWidth, int pixelHeight, bool stencil, @@ -120,6 +122,8 @@ namespace love void Graphics::clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) { + d3d.bindFramebuffer(d3d.getInternalBackbuffer()); + if (color.hasValue) { bool hasIntegerFormat = false; @@ -151,8 +155,6 @@ namespace love gammaCorrectColor(color.value); d3d.clear(color.value); } - - d3d.bindFramebuffer(d3d.getInternalBackbuffer()); } void Graphics::clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) @@ -160,6 +162,8 @@ namespace love if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) return; + d3d.bindFramebuffer(d3d.getInternalBackbuffer()); + int numColors = (int)colors.size(); const auto& targets = this->states.back().renderTargets.colors; @@ -189,8 +193,6 @@ namespace love gammaCorrectColor(value); d3d.clear(value); } - - d3d.bindFramebuffer(d3d.getInternalBackbuffer()); } void Graphics::present(void* screenshotCallback) @@ -235,6 +237,15 @@ namespace love d3d.setScissor(Rect::EMPTY); } + void Graphics::setPointSize(float size) + { + if (size != this->states.back().pointSize) + this->flushBatchedDraws(); + + this->states.back().pointSize = size; + d3d.setPointSize(size); + } + void Graphics::setFrontFaceWinding(Winding winding) { auto& state = this->states.back(); @@ -276,6 +287,57 @@ namespace love this->states.back().blend = state; } + bool Graphics::isPixelFormatSupported(PixelFormat format, uint32_t usage) + { + format = this->getSizedFormat(format); + bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; + + DkImageFormat color; + bool supported = deko3d::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) + d3d.bindFramebuffer(d3d.getInternalBackbuffer()); + else + d3d.bindFramebuffer(*(dk::Image*)targets.getFirstTarget().texture->getRenderTargetHandle()); + + d3d.setViewport({ 0, 0, pixelWidth, pixelHeight }); + + if (state.scissor) + d3d.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) { @@ -290,8 +352,11 @@ namespace love { if (this->batchedDrawState.vertexBuffer == nullptr) { - this->batchedDrawState.indexBuffer = newIndexBuffer(INIT_INDEX_BUFFER_SIZE); + 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&) @@ -339,7 +404,13 @@ namespace love } void Graphics::draw(const DrawIndexedCommand& command) - {} + { + d3d.prepareDraw(this); + + DkPrimitive primitive; + bool success = deko3d::getConstant(command.primitiveType, primitive); + d3d.drawIndexed(primitive, command.indexCount, command.indexBufferOffset, command.instanceCount); + } void Graphics::draw(const DrawCommand& command) {} diff --git a/platform/hac/source/modules/graphics/Shader.cpp b/platform/hac/source/modules/graphics/Shader.cpp index af3ca12f..56699f17 100644 --- a/platform/hac/source/modules/graphics/Shader.cpp +++ b/platform/hac/source/modules/graphics/Shader.cpp @@ -1,6 +1,8 @@ #include "modules/graphics/Shader.hpp" #include "driver/display/deko.hpp" +#include "driver/graphics/Attributes.hpp" + #include #define SHADERS_DIR "romfs:/shaders/" @@ -54,13 +56,23 @@ namespace love throw love::Exception("Invalid fragment shader: %s", error.c_str()); } + Shader::~Shader() + { + this->program.vertex.memory.destroy(); + this->program.fragment.memory.destroy(); + } + void Shader::attach() { if (Shader::current != this) { - Shader::current = this; + Graphics::flushBatchedDrawsGlobal(); + d3d.useProgram(this->program.vertex.shader, this->program.fragment.shader); ++shaderSwitches; + + Shader::current = this; + shaderSwitches++; } } @@ -72,6 +84,14 @@ namespace love void Shader::unloadVolatile() {} + 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 {}; diff --git a/platform/hac/source/modules/graphics/Texture.cpp b/platform/hac/source/modules/graphics/Texture.cpp new file mode 100644 index 00000000..3bb2fecc --- /dev/null +++ b/platform/hac/source/modules/graphics/Texture.cpp @@ -0,0 +1,205 @@ +#include "driver/display/deko.hpp" + +#include "modules/graphics/Texture.hpp" + +namespace love +{ + static void dkImageRectFromRect(const Rect& rectangle, DkImageRect& out) + { + out.x = (uint32_t)rectangle.x; + out.y = (uint32_t)rectangle.y; + out.z = (uint32_t)0; + + out.width = (uint32_t)rectangle.w; + out.height = (uint32_t)rectangle.h; + out.depth = (uint32_t)1; + } + + static void updateTextureObject(dk::Image& image, CMemPool::Handle& memory, + dk::ImageDescriptor& descriptor, const Rect& rect, const void* data, + const size_t size, PixelFormat format) + { + DkImageFormat gpuFormat; + if (!deko3d::getConstant(format, gpuFormat)) + throw love::Exception("Invalid image format."); + + auto& pool = d3d.getMemoryPool(deko3d::MEMORYPOOL_DATA); + auto tempMemory = pool.allocate(size, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); + + if (!tempMemory) + throw love::Exception("Failed to allocate temporary Texture memory."); + + std::memcpy(tempMemory.getCpuAddr(), data, size); + + const auto& device = d3d.getDevice(); + auto tempCommandBuffer = dk::CmdBufMaker { device }.create(); + auto tempCommandMemory = pool.allocate(DK_MEMBLOCK_ALIGNMENT); + + tempCommandBuffer.addMemory(tempCommandMemory.getMemBlock(), tempCommandMemory.getOffset(), + tempCommandMemory.getSize()); + /* set the image layout */ + dk::ImageLayout layout; + dk::ImageLayoutMaker { device } + .setFlags(0) + .setFormat(gpuFormat) + .setDimensions(rect.w, rect.h) + .initialize(layout); + + image.initialize(layout, memory.getMemBlock(), memory.getOffset()); + + dk::ImageView view { image }; + + DkImageRect dkRect {}; + dkImageRectFromRect(rect, dkRect); + + tempCommandBuffer.copyBufferToImage({ tempMemory.getGpuAddr() }, view, dkRect); + + auto& queue = d3d.getQueue(deko3d::QUEUE_TYPE_IMAGES); + queue.submitCommands(tempCommandBuffer.finishList()); + queue.waitIdle(); + + tempCommandMemory.destroy(); + tempMemory.destroy(); + } + + static void createFramebufferObject() + {} + + Texture::Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data) : + TextureBase(graphics, settings, data), + slices(settings.type) + { + if (data != nullptr) + this->slices = *data; + + if (!this->loadVolatile()) + throw love::Exception("Failed to create texture."); + + this->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); + + if (this->memory) + this->memory.destroy(); + + this->setGraphicsMemorySize(0); + } + + void Texture::createTexture() + { + if (!this->isRenderTarget()) + { + 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); + d3d.setSamplerState(this, this->samplerState); + } + + void Texture::uploadByteData(const void* data, size_t size, int level, int slice, const Rect& rect) + { + updateTextureObject(this->image, this->memory, this->descriptor, rect, data, size, this->format); + } + + 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/source/modules/font/freetype/Font.cpp b/source/modules/font/freetype/Font.cpp index 4149f873..0975fa90 100644 --- a/source/modules/font/freetype/Font.cpp +++ b/source/modules/font/freetype/Font.cpp @@ -10,17 +10,18 @@ namespace love #if defined(__SWITCH__) ByteData* FontModule::loadSystemFontByType(SystemFontType type = PlSharedFontType_Standard) { - std::unique_ptr data; - plGetSharedFontByType(data.get(), type); + 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 == nullptr) + 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); + return new ByteData(data.address, data.size, false); } #elif defined(__WIIU__) ByteData* FontModule::loadSystemFontByType(SystemFontType type = OS_SHAREDDATATYPE_FONT_STANDARD) @@ -45,7 +46,7 @@ namespace love if (FT_Init_FreeType(&this->library) != 0) throw love::Exception("Error initializing FreeType library."); - this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); + // this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); } FontModule::~FontModule() From 00733f10eae68244dae69dd38a351e84c610846e Mon Sep 17 00:00:00 2001 From: TurtleP Date: Wed, 6 Nov 2024 23:41:11 -0500 Subject: [PATCH 44/49] switch can render points --- include/driver/graphics/DrawCommand.hpp | 13 ++- log.txt | 42 ------- .../cafe/include/modules/graphics/Texture.hpp | 2 +- .../cafe/source/modules/graphics/Graphics.cpp | 11 +- platform/ctr/source/modules/graphics/Font.cpp | 2 +- platform/hac/include/driver/display/deko.hpp | 5 +- .../include/driver/graphics/Attributes.hpp | 28 +++-- .../include/driver/graphics/StreamBuffer.hpp | 3 +- .../hac/source/driver/display/Framebuffer.cpp | 3 +- platform/hac/source/driver/display/deko.cpp | 37 ++++-- .../hac/source/modules/graphics/Graphics.cpp | 27 +++-- .../hac/source/modules/graphics/Shader.cpp | 13 ++- source/modules/font/freetype/Font.cpp | 2 +- source/modules/graphics/Graphics.cpp | 108 +++++++++--------- source/modules/graphics/Mesh.cpp | 20 ++-- 15 files changed, 164 insertions(+), 152 deletions(-) delete mode 100644 log.txt diff --git a/include/driver/graphics/DrawCommand.hpp b/include/driver/graphics/DrawCommand.hpp index 7a887716..347f35b4 100644 --- a/include/driver/graphics/DrawCommand.hpp +++ b/include/driver/graphics/DrawCommand.hpp @@ -13,12 +13,11 @@ namespace love struct BatchedDrawCommand { PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; - ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; - TriangleIndexMode indexMode = TRIANGLEINDEX_NONE; CommonFormat format = CommonFormat::NONE; - - TextureBase* texture = nullptr; - int vertexCount = 0; + TriangleIndexMode indexMode = TRIANGLEINDEX_NONE; + int vertexCount = 0; + TextureBase* texture = nullptr; + ShaderBase::StandardShader shaderType = ShaderBase::STANDARD_DEFAULT; bool isFont = false; bool pushTransform = true; @@ -53,6 +52,9 @@ namespace love struct DrawCommand { + DrawCommand(const BufferBindings* buffers) : buffers(buffers) + {} + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; int vertexStart = 0; @@ -61,6 +63,7 @@ namespace love TextureBase* texture = nullptr; CullMode cullMode = CULL_NONE; + const BufferBindings* buffers; bool isFont = false; }; diff --git a/log.txt b/log.txt deleted file mode 100644 index 66482b45..00000000 --- a/log.txt +++ /dev/null @@ -1,42 +0,0 @@ --- Updating Catnip cache --- Entering /d/lovepotion/build/main.release -[1/6] Building CXX object CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/deko.o -D:/lovepotion/platform/hac/source/driver/display/deko.cpp: In constructor 'love::deko3d::deko3d()': -D:/lovepotion/platform/hac/source/driver/display/deko.cpp:17:20: warning: 'void* memset(void*, int, size_t)' clearing an object of non-trivial type 'struct love::deko3d::Context'; use assignment or value-initialization instead [-Wclass-memaccess] - 17 | std::memset(&this->context, 0, sizeof(this->context)); - | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In file included from D:/lovepotion/platform/hac/source/driver/display/deko.cpp:1: -D:/lovepotion/platform/hac/include/driver/display/deko.hpp:225:16: note: 'struct love::deko3d::Context' declared here - 225 | struct Context : public ContextBase - | ^~~~~~~ -D:/lovepotion/platform/hac/source/driver/display/deko.cpp: In member function 'virtual void love::deko3d::prepareDraw(love::GraphicsBase*)': -D:/lovepotion/platform/hac/source/driver/display/deko.cpp:158:44: warning: unused parameter 'graphics' [-Wunused-parameter] - 158 | void deko3d::prepareDraw(GraphicsBase* graphics) - | ~~~~~~~~~~~~~~^~~~~~~~ -[2/6] Building CXX object CMakeFiles/lovepotion.dir/source/modules/love/love.o -[3/6] Building CXX object CMakeFiles/lovepotion.dir/source/modules/window/wrap_Window.o -[4/6] Linking CXX executable lovepotion.elf -FAILED: lovepotion.elf lovepotion.lst lovepotion.map /d/lovepotion/build/main.release/lovepotion.lst /d/lovepotion/build/main.release/lovepotion.map -: && /opt/devkitpro/devkitA64/bin/aarch64-none-elf-g++.exe -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec -ffunction-sections -fdata-sections -D__SWITCH__ -g -O2 -DNDEBUG -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec -L/opt/devkitpro/libnx/lib -L/opt/devkitpro/portlibs/switch/lib -fPIE -specs=/opt/devkitpro/libnx/switch.specs -Wl,-Map,/d/lovepotion/build/main.release/lovepotion.map CMakeFiles/lovepotion.dir/platform/hac/source/boot.o CMakeFiles/lovepotion.dir/platform/hac/source/common/screen.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/audio/DigitalSound.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/audio/MemoryPool.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/deko3d/CIntrusiveTree.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/deko3d/CMemPool.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/Framebuffer.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/display/deko.o CMakeFiles/lovepotion.dir/platform/hac/source/driver/EventQueue.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/audio/Source.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Shader.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/joystick/Joystick.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/joystick/JoystickModule.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/keyboard/Keyboard.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/system/System.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/timer/Timer.o CMakeFiles/lovepotion.dir/platform/hac/source/modules/window/Window.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/ASTCHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/ddsHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/JPGHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/KTXHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/PKMHandler.o CMakeFiles/lovepotion.dir/source/modules/image/magpie/PNGHandler.o CMakeFiles/lovepotion.dir/source/modules/font/freetype/Font.o CMakeFiles/lovepotion.dir/source/modules/font/freetype/TrueTypeRasterizer.o CMakeFiles/lovepotion.dir/source/modules/graphics/freetype/Font.o CMakeFiles/lovepotion.dir/source/main.o CMakeFiles/lovepotion.dir/source/common/b64.o CMakeFiles/lovepotion.dir/source/common/Data.o CMakeFiles/lovepotion.dir/source/common/float.o CMakeFiles/lovepotion.dir/source/common/luax.o CMakeFiles/lovepotion.dir/source/common/Matrix.o CMakeFiles/lovepotion.dir/source/common/Message.o CMakeFiles/lovepotion.dir/source/common/Module.o CMakeFiles/lovepotion.dir/source/common/Object.o CMakeFiles/lovepotion.dir/source/common/pixelformat.o CMakeFiles/lovepotion.dir/source/common/Reference.o CMakeFiles/lovepotion.dir/source/common/Stream.o CMakeFiles/lovepotion.dir/source/common/types.o CMakeFiles/lovepotion.dir/source/common/Variant.o CMakeFiles/lovepotion.dir/source/utility/guid.o CMakeFiles/lovepotion.dir/source/modules/audio/Audio.o CMakeFiles/lovepotion.dir/source/modules/audio/Pool.o CMakeFiles/lovepotion.dir/source/modules/audio/Source.o CMakeFiles/lovepotion.dir/source/modules/audio/wrap_Audio.o CMakeFiles/lovepotion.dir/source/modules/audio/wrap_Source.o CMakeFiles/lovepotion.dir/source/modules/data/ByteData.o CMakeFiles/lovepotion.dir/source/modules/data/CompressedData.o CMakeFiles/lovepotion.dir/source/modules/data/DataModule.o CMakeFiles/lovepotion.dir/source/modules/data/DataStream.o CMakeFiles/lovepotion.dir/source/modules/data/DataView.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_ByteData.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_CompressedData.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_Data.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_DataModule.o CMakeFiles/lovepotion.dir/source/modules/data/wrap_DataView.o CMakeFiles/lovepotion.dir/source/modules/event/Event.o CMakeFiles/lovepotion.dir/source/modules/event/wrap_Event.o CMakeFiles/lovepotion.dir/source/modules/filesystem/FileData.o CMakeFiles/lovepotion.dir/source/modules/filesystem/wrap_File.o CMakeFiles/lovepotion.dir/source/modules/filesystem/wrap_FileData.o CMakeFiles/lovepotion.dir/source/modules/filesystem/wrap_Filesystem.o CMakeFiles/lovepotion.dir/source/modules/font/Font.o CMakeFiles/lovepotion.dir/source/modules/font/GlyphData.o CMakeFiles/lovepotion.dir/source/modules/font/Rasterizer.o CMakeFiles/lovepotion.dir/source/modules/font/TextShaper.o CMakeFiles/lovepotion.dir/source/modules/font/GenericShaper.o CMakeFiles/lovepotion.dir/source/modules/font/wrap_Font.o CMakeFiles/lovepotion.dir/source/modules/font/wrap_Rasterizer.o CMakeFiles/lovepotion.dir/source/modules/font/wrap_GlyphData.o CMakeFiles/lovepotion.dir/source/modules/graphics/Buffer.o CMakeFiles/lovepotion.dir/source/modules/graphics/Graphics.o CMakeFiles/lovepotion.dir/source/modules/graphics/FontBase.o CMakeFiles/lovepotion.dir/source/modules/graphics/Mesh.o CMakeFiles/lovepotion.dir/source/modules/graphics/Polyline.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Mesh.o CMakeFiles/lovepotion.dir/source/modules/graphics/TextBatch.o CMakeFiles/lovepotion.dir/source/modules/graphics/renderstate.o CMakeFiles/lovepotion.dir/source/modules/graphics/samplerstate.o CMakeFiles/lovepotion.dir/source/modules/graphics/Shader.o CMakeFiles/lovepotion.dir/source/modules/graphics/Quad.o CMakeFiles/lovepotion.dir/source/modules/graphics/Volatile.o CMakeFiles/lovepotion.dir/source/modules/graphics/vertex.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Graphics.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_TextBatch.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Font.o CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Quad.o CMakeFiles/lovepotion.dir/source/modules/graphics/Texture.o CMakeFiles/lovepotion.dir/source/modules/image/CompressedImageData.o CMakeFiles/lovepotion.dir/source/modules/image/CompressedSlice.o CMakeFiles/lovepotion.dir/source/modules/image/FormatHandler.o CMakeFiles/lovepotion.dir/source/modules/image/Image.o CMakeFiles/lovepotion.dir/source/modules/image/ImageData.o CMakeFiles/lovepotion.dir/source/modules/image/ImageDataBase.o CMakeFiles/lovepotion.dir/source/modules/image/wrap_CompressedImageData.o CMakeFiles/lovepotion.dir/source/modules/image/wrap_Image.o CMakeFiles/lovepotion.dir/source/modules/image/wrap_ImageData.o CMakeFiles/lovepotion.dir/source/modules/joystick/JoystickModule.o CMakeFiles/lovepotion.dir/source/modules/joystick/wrap_Joystick.o CMakeFiles/lovepotion.dir/source/modules/joystick/wrap_JoystickModule.o CMakeFiles/lovepotion.dir/source/modules/keyboard/wrap_Keyboard.o CMakeFiles/lovepotion.dir/source/modules/love/love.o CMakeFiles/lovepotion.dir/source/modules/math/BezierCurve.o CMakeFiles/lovepotion.dir/source/modules/math/MathModule.o CMakeFiles/lovepotion.dir/source/modules/math/RandomGenerator.o CMakeFiles/lovepotion.dir/source/modules/math/Transform.o CMakeFiles/lovepotion.dir/source/modules/math/wrap_BezierCurve.o CMakeFiles/lovepotion.dir/source/modules/math/wrap_MathModule.o CMakeFiles/lovepotion.dir/source/modules/math/wrap_RandomGenerator.o CMakeFiles/lovepotion.dir/source/modules/math/wrap_Transform.o CMakeFiles/lovepotion.dir/source/modules/sensor/Sensor.o CMakeFiles/lovepotion.dir/source/modules/sensor/wrap_Sensor.o CMakeFiles/lovepotion.dir/source/modules/sound/Decoder.o CMakeFiles/lovepotion.dir/source/modules/sound/Sound.o CMakeFiles/lovepotion.dir/source/modules/sound/SoundData.o CMakeFiles/lovepotion.dir/source/modules/sound/wrap_Decoder.o CMakeFiles/lovepotion.dir/source/modules/sound/wrap_Sound.o CMakeFiles/lovepotion.dir/source/modules/sound/wrap_SoundData.o CMakeFiles/lovepotion.dir/source/modules/system/wrap_System.o CMakeFiles/lovepotion.dir/source/modules/thread/Channel.o CMakeFiles/lovepotion.dir/source/modules/thread/LuaThread.o CMakeFiles/lovepotion.dir/source/modules/thread/Thread.o CMakeFiles/lovepotion.dir/source/modules/thread/Threadable.o CMakeFiles/lovepotion.dir/source/modules/thread/ThreadModule.o CMakeFiles/lovepotion.dir/source/modules/thread/wrap_Channel.o CMakeFiles/lovepotion.dir/source/modules/thread/wrap_LuaThread.o CMakeFiles/lovepotion.dir/source/modules/thread/wrap_Thread.o CMakeFiles/lovepotion.dir/source/modules/timer/wrap_Timer.o CMakeFiles/lovepotion.dir/source/modules/touch/Touch.o CMakeFiles/lovepotion.dir/source/modules/touch/wrap_Touch.o CMakeFiles/lovepotion.dir/source/modules/window/wrap_Window.o CMakeFiles/lovepotion.dir/source/modules/data/misc/Compressor.o CMakeFiles/lovepotion.dir/source/modules/data/misc/HashFunction.o CMakeFiles/lovepotion.dir/source/modules/filesystem/physfs/File.o CMakeFiles/lovepotion.dir/source/modules/filesystem/physfs/Filesystem.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/FLACDecoder.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/ModPlugDecoder.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/MP3Decoder.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/VorbisDecoder.o CMakeFiles/lovepotion.dir/source/modules/sound/lullaby/WaveDecoder.o -o lovepotion.elf -ldeko3d /opt/devkitpro/portlibs/switch/lib/libfreetype.a -lbz2 -lpng -lturbojpeg libddsparse.a liblove_physfs.a libwuff.a libnoise1234.a -lvorbisidec -lz libluabit.a liblua53.a -logg -lmodplug liblua-https.a libluasocket.a /opt/devkitpro/portlibs/switch/lib/liblz4.a /opt/devkitpro/portlibs/switch/lib/libcurl.a /opt/devkitpro/libnx/lib/libnx.a /opt/devkitpro/portlibs/switch/lib/libz.a /opt/devkitpro/libnx/lib/libnx.a /opt/devkitpro/portlibs/switch/lib/libz.a /opt/devkitpro/portlibs/switch/lib/liblua5.1.a /opt/devkitpro/devkitA64/aarch64-none-elf/lib/pic/libm.a -lnx -lm && /bin/sh CMakeFiles/lovepotion.dir/post-build.sh 62f60396062a37ea -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0x40): undefined reference to `love::Graphics::newTexture(love::TextureBase::Settings const&, love::TextureBase::Slices const*)' -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0x48): undefined reference to `love::Graphics::newFont(love::Rasterizer*)' -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0x50): undefined reference to `love::Graphics::newDefaultFont(int, love::Rasterizer::Settings const&)' -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0xb0): undefined reference to `love::Graphics::setRenderTargetsInternal(love::GraphicsBase::RenderTargets const&, int, int, bool)' -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/platform/hac/source/modules/graphics/Graphics.o:(.data.rel.ro._ZTVN4love8GraphicsE[_ZTVN4love8GraphicsE]+0xb8): undefined reference to `love::Graphics::isPixelFormatSupported(love::PixelFormat, unsigned int)' -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o: in function `Wrap_Texture::setDepthSampleMode(lua_State*)': -D:/lovepotion/source/modules/graphics/wrap_Texture.cpp:370:(.text._ZN12Wrap_Texture18setDepthSampleModeEP9lua_State+0xf4): undefined reference to `love::Texture::setSamplerState(love::SamplerState const&)' -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o: in function `Wrap_Texture::setWrap(lua_State*)': -D:/lovepotion/source/modules/graphics/wrap_Texture.cpp:261:(.text._ZN12Wrap_Texture7setWrapEP9lua_State+0x1bc): undefined reference to `love::Texture::setSamplerState(love::SamplerState const&)' -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o: in function `Wrap_Texture::setMipmapFilter(lua_State*)': -D:/lovepotion/source/modules/graphics/wrap_Texture.cpp:219:(.text._ZN12Wrap_Texture15setMipmapFilterEP9lua_State+0x7c): undefined reference to `love::Texture::setSamplerState(love::SamplerState const&)' -C:/msys64/opt/devkitpro/devkitA64/bin/../lib/gcc/aarch64-none-elf/14.2.0/../../../../aarch64-none-elf/bin/ld.exe: CMakeFiles/lovepotion.dir/source/modules/graphics/wrap_Texture.o: in function `Wrap_Texture::setFilter(lua_State*)': -D:/lovepotion/source/modules/graphics/wrap_Texture.cpp:175:(.text._ZN12Wrap_Texture9setFilterEP9lua_State+0x170): undefined reference to `love::Texture::setSamplerState(love::SamplerState const&)' -collect2.exe: error: ld returned 1 exit status -ninja: build stopped: subcommand failed. -CMake Error at /opt/devkitpro/cmake/Catnip/Verb-Build.cmake:91 (message): - Failed to build main.release -Call Stack (most recent call first): - /opt/devkitpro/cmake/Catnip/Verb-Build.cmake:102 (__catnip_build) - /opt/devkitpro/cmake/catnip-main.cmake:11 (include) - - diff --git a/platform/cafe/include/modules/graphics/Texture.hpp b/platform/cafe/include/modules/graphics/Texture.hpp index 3a4c25e8..73f85113 100644 --- a/platform/cafe/include/modules/graphics/Texture.hpp +++ b/platform/cafe/include/modules/graphics/Texture.hpp @@ -31,7 +31,7 @@ namespace love void generateMipmapsInternal() override; - void setHandleData(void* data) override + void setHandleData(ptrdiff_t data) override {} private: diff --git a/platform/cafe/source/modules/graphics/Graphics.cpp b/platform/cafe/source/modules/graphics/Graphics.cpp index e7f08896..fe4c9c18 100644 --- a/platform/cafe/source/modules/graphics/Graphics.cpp +++ b/platform/cafe/source/modules/graphics/Graphics.cpp @@ -402,5 +402,14 @@ namespace love } 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/ctr/source/modules/graphics/Font.cpp b/platform/ctr/source/modules/graphics/Font.cpp index 93d411d1..3285c0f2 100644 --- a/platform/ctr/source/modules/graphics/Font.cpp +++ b/platform/ctr/source/modules/graphics/Font.cpp @@ -39,7 +39,7 @@ namespace love auto* texture = graphics->newTexture(settings, nullptr); auto* data = fontGetGlyphSheetTex(font, index); - texture->setHandleData(data); + texture->setHandleData((ptrdiff_t)data); texture->setSamplerState(this->samplerState); this->textures.emplace_back(texture, Acquire::NO_RETAIN); diff --git a/platform/hac/include/driver/display/deko.hpp b/platform/hac/include/driver/display/deko.hpp index 217f3f74..01823775 100644 --- a/platform/hac/include/driver/display/deko.hpp +++ b/platform/hac/include/driver/display/deko.hpp @@ -122,7 +122,9 @@ namespace love void ensureInFrame(); void drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, - uint32_t instanceCount); + uint32_t instanceCount, bool isTexture); + + void draw(DkPrimitive primitive, uint32_t vertexCount, uint32_t firstVertex); // clang-format off ENUMMAP_DECLARE(BlendOperations, BlendOperation, DkBlendOp, @@ -238,6 +240,7 @@ namespace love dk::ColorWriteState colorWrite; dk::BlendState blend; dk::ColorState color; + dk::DepthStencilState depthStencil; dk::Image* boundFramebuffer; } context; diff --git a/platform/hac/include/driver/graphics/Attributes.hpp b/platform/hac/include/driver/graphics/Attributes.hpp index 57c93d41..885252c3 100644 --- a/platform/hac/include/driver/graphics/Attributes.hpp +++ b/platform/hac/include/driver/graphics/Attributes.hpp @@ -29,22 +29,30 @@ namespace love 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, TEXCOORD_OFFSET, DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0 }, + DkVtxAttribState { 0, 0, COLOR_OFFSET, DkVtxAttribSize_4x32, DkVtxAttribType_Float, 0 } + }; // clang-format on - static bool getAttributes(CommonFormat format, Attributes& out) + static void getAttributes(const bool isTexture, Attributes& out) { - switch (format) + if (!isTexture) { - case CommonFormat::XYf_RGBAf: - default: - { - out.attributeState = PrimitiveAttribState; - out.bufferState = PrimitiveBufferState; - return true; - } + out.attributeState = PrimitiveAttribState; + out.bufferState = PrimitiveBufferState; + return; } - return false; + out.attributeState = TextureAttribState; + out.bufferState = TextureBufferState; } } // namespace vertex } // namespace love diff --git a/platform/hac/include/driver/graphics/StreamBuffer.hpp b/platform/hac/include/driver/graphics/StreamBuffer.hpp index 84fa19bc..753f2715 100644 --- a/platform/hac/include/driver/graphics/StreamBuffer.hpp +++ b/platform/hac/include/driver/graphics/StreamBuffer.hpp @@ -28,7 +28,8 @@ namespace love ~StreamBuffer() { - this->memory.destroy(); + if (this->memory) + this->memory.destroy(); } MapInfo map(size_t) diff --git a/platform/hac/source/driver/display/Framebuffer.cpp b/platform/hac/source/driver/display/Framebuffer.cpp index 8aca4675..97ba9928 100644 --- a/platform/hac/source/driver/display/Framebuffer.cpp +++ b/platform/hac/source/driver/display/Framebuffer.cpp @@ -32,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/deko.cpp b/platform/hac/source/driver/display/deko.cpp index 12ff633e..adb28e41 100644 --- a/platform/hac/source/driver/display/deko.cpp +++ b/platform/hac/source/driver/display/deko.cpp @@ -31,7 +31,10 @@ namespace love this->ensureInFrame(); this->context.rasterizer.setCullMode(DkFace_None); - this->context.rasterizer.setFrontFace(DkFrontFace_CCW); + this->context.depthStencil.setDepthTestEnable(true); + this->context.depthStencil.setDepthWriteEnable(true); + this->context.depthStencil.setDepthCompareOp(DkCompareOp_Always); + this->context.color.setBlendEnable(0, true); this->initialized = true; @@ -40,7 +43,9 @@ namespace love deko3d::~deko3d() { this->destroyFramebuffers(); - this->uniformBuffer.destroy(); + + if (this->uniformBuffer) + this->uniformBuffer.destroy(); } void deko3d::createFramebuffers() @@ -95,7 +100,7 @@ namespace love void deko3d::clear(const Color& color) { - if (!this->context.boundFramebuffer) + if (!this->inFrame) return; this->commandBuffer.clearColor(0, DkColorMask_RGBA, color.r, color.g, color.b, color.a); @@ -153,7 +158,7 @@ namespace love dk::ImageView depth { this->depthbuffer.getImage() }; dk::ImageView target { framebuffer }; - this->commandBuffer.bindRenderTargets(&target, &depth); + this->commandBuffer.bindRenderTargets(&target); } } @@ -165,13 +170,10 @@ namespace love this->commandBuffer.bindIdxBuffer(DkIdxFormat_Uint16, buffer); } - void deko3d::drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, uint32_t instanceCount) + void deko3d::drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, uint32_t instanceCount, bool isTexture) { vertex::Attributes attributes {}; - bool success = getAttributes(CommonFormat::XYf_RGBAf, attributes); - - if (!success) - return; + vertex::getAttributes(isTexture, attributes); this->commandBuffer.bindVtxAttribState(attributes.attributeState); this->commandBuffer.bindVtxBufferState(attributes.bufferState); @@ -179,13 +181,24 @@ namespace love this->commandBuffer.drawIndexed(primitive, indexCount, instanceCount, indexOffset, 0, 0); } + void deko3d::draw(DkPrimitive primitive, uint32_t vertexCount, uint32_t firstVertex) + { + vertex::Attributes attributes {}; + vertex::getAttributes(false, attributes); + + this->commandBuffer.bindVtxAttribState(attributes.attributeState); + this->commandBuffer.bindVtxBufferState(attributes.bufferState); + + 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); - // this->commandBuffer.bindColorState(this->context.color); - this->commandBuffer.pushConstants(this->uniformBuffer.getGpuAddr(), this->uniformBuffer.getSize(), 0, TRANSFORM_SIZE, &this->transform); @@ -240,7 +253,7 @@ namespace love const auto blue = (DkColorMask_B * mask.b); const auto alpha = (DkColorMask_A * mask.a); - this->context.colorWrite.setMask(0, (red + green + blue + alpha)); + this->context.colorWrite.setMask(0, uint32_t(red + green + blue + alpha)); } void deko3d::setBlendState(const BlendState& state) diff --git a/platform/hac/source/modules/graphics/Graphics.cpp b/platform/hac/source/modules/graphics/Graphics.cpp index 3a30ffa1..12640dae 100644 --- a/platform/hac/source/modules/graphics/Graphics.cpp +++ b/platform/hac/source/modules/graphics/Graphics.cpp @@ -386,10 +386,8 @@ namespace love if (!Shader::current) Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach(); - return true; - - this->backbufferChanged(width, height, pixelWidth, pixelHeight, backBufferStencil, backBufferDepth, - msaa); + // this->backbufferChanged(width, height, pixelWidth, pixelHeight, backBufferStencil, backBufferDepth, + // msaa); return true; } @@ -408,10 +406,25 @@ namespace love d3d.prepareDraw(this); DkPrimitive primitive; - bool success = deko3d::getConstant(command.primitiveType, primitive); - d3d.drawIndexed(primitive, command.indexCount, command.indexBufferOffset, command.instanceCount); + deko3d::getConstant(command.primitiveType, primitive); + + const auto indexCount = command.indexCount; + const auto offset = command.indexBufferOffset; + const auto instanceCount = command.instanceCount; + const auto isTexture = command.texture != nullptr; + + d3d.drawIndexed(primitive, indexCount, offset, instanceCount, isTexture); + ++drawCalls; } void Graphics::draw(const DrawCommand& command) - {} + { + d3d.prepareDraw(this); + + 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 index 56699f17..4ee93fba 100644 --- a/platform/hac/source/modules/graphics/Shader.cpp +++ b/platform/hac/source/modules/graphics/Shader.cpp @@ -1,8 +1,6 @@ #include "modules/graphics/Shader.hpp" #include "driver/display/deko.hpp" -#include "driver/graphics/Attributes.hpp" - #include #define SHADERS_DIR "romfs:/shaders/" @@ -58,8 +56,7 @@ namespace love Shader::~Shader() { - this->program.vertex.memory.destroy(); - this->program.fragment.memory.destroy(); + this->unloadVolatile(); } void Shader::attach() @@ -82,7 +79,13 @@ namespace love } void Shader::unloadVolatile() - {} + { + if (this->program.vertex.memory) + this->program.vertex.memory.destroy(); + + if (this->program.fragment.memory) + this->program.fragment.memory.destroy(); + } void Shader::updateBuiltinUniforms(GraphicsBase* graphics) {} diff --git a/source/modules/font/freetype/Font.cpp b/source/modules/font/freetype/Font.cpp index 0975fa90..029a14d7 100644 --- a/source/modules/font/freetype/Font.cpp +++ b/source/modules/font/freetype/Font.cpp @@ -46,7 +46,7 @@ namespace love if (FT_Init_FreeType(&this->library) != 0) throw love::Exception("Error initializing FreeType library."); - // this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); + this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); } FontModule::~FontModule() diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 61b19b40..d3a8318f 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -401,58 +401,6 @@ namespace love return data; } - 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::flushBatchedDraws() { BatchedDrawState& state = this->batchedDrawState; @@ -492,7 +440,7 @@ namespace love if (state.pushTransform) this->pushIdentityTransform(); - if (state.indexCount > 0) + if (state.lastIndexCount > 0) { usedSizes[1] = state.lastIndexCount; @@ -510,7 +458,7 @@ namespace love } else { - DrawCommand command {}; + DrawCommand command(&buffers); command.primitiveType = state.primitiveMode; command.vertexStart = 0; command.vertexCount = state.lastVertexCount; @@ -536,6 +484,58 @@ namespace love 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); diff --git a/source/modules/graphics/Mesh.cpp b/source/modules/graphics/Mesh.cpp index 8d192840..f86ee96e 100644 --- a/source/modules/graphics/Mesh.cpp +++ b/source/modules/graphics/Mesh.cpp @@ -340,16 +340,16 @@ namespace love 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); + // 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 From f06000dfc220a85de761f24c1d0d06da82527bb5 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Fri, 8 Nov 2024 12:56:43 -0500 Subject: [PATCH 45/49] switch texture work --- platform/hac/include/driver/display/deko.hpp | 18 +++-- .../include/driver/graphics/Attributes.hpp | 2 +- .../hac/include/driver/graphics/BitAlloc.hpp | 2 +- platform/hac/source/driver/EventQueue.cpp | 41 ++++++------ platform/hac/source/driver/display/deko.cpp | 65 ++++++++++++++----- .../hac/source/modules/graphics/Graphics.cpp | 9 ++- .../hac/source/modules/graphics/Texture.cpp | 65 ++++++++++++------- source/modules/font/freetype/Font.cpp | 2 +- source/modules/graphics/Graphics.cpp | 1 + 9 files changed, 133 insertions(+), 72 deletions(-) diff --git a/platform/hac/include/driver/display/deko.hpp b/platform/hac/include/driver/display/deko.hpp index 01823775..0b48fcda 100644 --- a/platform/hac/include/driver/display/deko.hpp +++ b/platform/hac/include/driver/display/deko.hpp @@ -100,12 +100,12 @@ namespace love } } - dk::Device& getDevice() + dk::Device getDevice() { return this->device; } - dk::Queue& getQueue(QueueType type) + dk::Queue getQueue(QueueType type) { switch (type) { @@ -121,8 +121,14 @@ namespace love 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, bool isTexture); + uint32_t instanceCount); void draw(DkPrimitive primitive, uint32_t vertexCount, uint32_t firstVertex); @@ -227,8 +233,7 @@ namespace love 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; + static constexpr int COMMAND_SIZE = 0x100000; void createFramebuffers(); @@ -243,12 +248,13 @@ namespace love dk::DepthStencilState depthStencil; dk::Image* boundFramebuffer; + bool descriptorsDirty; } context; struct Transform { - glm::mat4 projection; glm::mat4 modelView; + glm::mat4 projection; } transform; static constexpr auto TRANSFORM_SIZE = sizeof(Transform); diff --git a/platform/hac/include/driver/graphics/Attributes.hpp b/platform/hac/include/driver/graphics/Attributes.hpp index 885252c3..850f99f9 100644 --- a/platform/hac/include/driver/graphics/Attributes.hpp +++ b/platform/hac/include/driver/graphics/Attributes.hpp @@ -42,7 +42,7 @@ namespace love }; // clang-format on - static void getAttributes(const bool isTexture, Attributes& out) + static void getAttributes(bool isTexture, Attributes& out) { if (!isTexture) { diff --git a/platform/hac/include/driver/graphics/BitAlloc.hpp b/platform/hac/include/driver/graphics/BitAlloc.hpp index 939af9e0..f879d08f 100644 --- a/platform/hac/include/driver/graphics/BitAlloc.hpp +++ b/platform/hac/include/driver/graphics/BitAlloc.hpp @@ -18,7 +18,7 @@ namespace love return index; } - void reset(uint32_t handle) + void deallocate(uint32_t handle) { auto index = 0; diff --git a/platform/hac/source/driver/EventQueue.cpp b/platform/hac/source/driver/EventQueue.cpp index df412713..a854c212 100644 --- a/platform/hac/source/driver/EventQueue.cpp +++ b/platform/hac/source/driver/EventQueue.cpp @@ -8,17 +8,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() @@ -74,16 +74,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,6 +92,7 @@ namespace love if (joystick == nullptr) continue; + const auto id = joystick->getID(); joystick->update(); for (int input = 0; input < JoystickBase::GAMEPAD_BUTTON_MAX_ENUM; input++) @@ -99,10 +100,10 @@ namespace love 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 < JoystickBase::GAMEPAD_AXIS_MAX_ENUM; input++) @@ -110,7 +111,7 @@ namespace love if (joystick->isAxisChanged(JoystickBase::GamepadAxis(input))) { float value = joystick->getAxis(JoystickBase::GamepadAxis(input)); - this->sendGamepadAxisEvent(0, input, value); + this->sendGamepadAxisEvent(id, input, value); } } @@ -120,7 +121,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/deko.cpp b/platform/hac/source/driver/display/deko.cpp index adb28e41..7ab36ddd 100644 --- a/platform/hac/source/driver/display/deko.cpp +++ b/platform/hac/source/driver/display/deko.cpp @@ -1,6 +1,8 @@ #include "driver/display/deko.hpp" #include "driver/graphics/Attributes.hpp" +#include "modules/graphics/Texture.hpp" + namespace love { deko3d::deko3d() : @@ -12,8 +14,8 @@ namespace love 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), - context {} + context {}, + framebufferSlot(-1) {} void deko3d::initialize() @@ -37,6 +39,12 @@ namespace love this->context.color.setBlendEnable(0, true); + this->imageSet.allocate(this->data); + this->imageSet.bindForImages(this->commandBuffer); + + this->samplerSet.allocate(this->data); + this->samplerSet.bindForSamplers(this->commandBuffer); + this->initialized = true; } @@ -80,6 +88,7 @@ namespace love this->commandBuffer.clear(); this->swapchain.destroy(); + this->context.boundFramebuffer = nullptr; for (auto& framebuffer : this->framebuffers) framebuffer.destroy(); @@ -90,6 +99,9 @@ namespace love { if (!this->inFrame) { + if (!this->swapchain) + return; + if (this->framebufferSlot < 0) this->framebufferSlot = this->mainQueue.acquireImage(this->swapchain); @@ -132,12 +144,14 @@ namespace love { if (registering) { - const auto handle = this->textureHandles.allocate(); + const auto index = this->textureHandles.allocate(); + const auto handle = dkMakeTextureHandle(index, index); texture->setHandleData(handle); + return; } - this->textureHandles.reset((DkResHandle)texture->getHandle()); + this->textureHandles.deallocate((uint32_t)texture->getHandle()); } void deko3d::bindFramebuffer(dk::Image& framebuffer) @@ -170,25 +184,42 @@ namespace love this->commandBuffer.bindIdxBuffer(DkIdxFormat_Uint16, buffer); } - void deko3d::drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, uint32_t instanceCount, bool isTexture) + void deko3d::setVertexAttributes(bool isTexture) { vertex::Attributes attributes {}; vertex::getAttributes(isTexture, attributes); this->commandBuffer.bindVtxAttribState(attributes.attributeState); this->commandBuffer.bindVtxBufferState(attributes.bufferState); + } - this->commandBuffer.drawIndexed(primitive, indexCount, instanceCount, indexOffset, 0, 0); + void deko3d::bindTextureToUnit(TextureBase* texture, int unit) + { + if (!texture) + return; + + const auto handle = (DkResHandle)texture->getHandle(); + this->bindTextureToUnit(handle, unit); } - void deko3d::draw(DkPrimitive primitive, uint32_t vertexCount, uint32_t firstVertex) + void deko3d::bindTextureToUnit(DkResHandle texture, int unit) { - vertex::Attributes attributes {}; - vertex::getAttributes(false, attributes); + if (this->context.descriptorsDirty) + { + this->commandBuffer.barrier(DkBarrier_Primitives, DkInvalidateFlags_Descriptors); + this->context.descriptorsDirty = false; + } - this->commandBuffer.bindVtxAttribState(attributes.attributeState); - this->commandBuffer.bindVtxBufferState(attributes.bufferState); + 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); } @@ -299,6 +330,9 @@ namespace love void deko3d::setSamplerState(TextureBase* texture, const SamplerState&state) { auto* sampler = (dk::Sampler*)texture->getSamplerHandle(); + auto descriptor = ((Texture*)texture)->getDescriptorHandle(); + auto samplerDescriptor = ((Texture*)texture)->getSamplerDescriptorHandle(); + auto index = -1; if (!this->textureHandles.find((uint32_t)texture->getHandle(), index)) @@ -328,13 +362,12 @@ namespace love sampler->setWrapMode(wrapU, wrapV, wrapW); - // const auto descriptor = ((Texture*)texture)->getDescriptorHandle(); - // this->imageSet.update(this->commandBuffer, index, descriptor); + this->imageSet.update(this->commandBuffer, index, descriptor); - // auto samplerDescriptor = ((Texture*)texture)->getSamplerDescriptorHandle(); - // samplerDescriptor.initialize(*sampler); + samplerDescriptor.initialize(*sampler); - // this->samplerSet.update(this->commandBuffer, index, samplerDescriptor); + this->samplerSet.update(this->commandBuffer, index, samplerDescriptor); + this->context.descriptorsDirty = true; } static DkScissor dkScissorFromRect(const Rect& rect) diff --git a/platform/hac/source/modules/graphics/Graphics.cpp b/platform/hac/source/modules/graphics/Graphics.cpp index 12640dae..46fd78b6 100644 --- a/platform/hac/source/modules/graphics/Graphics.cpp +++ b/platform/hac/source/modules/graphics/Graphics.cpp @@ -404,6 +404,9 @@ namespace love 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); @@ -411,15 +414,17 @@ namespace love const auto indexCount = command.indexCount; const auto offset = command.indexBufferOffset; const auto instanceCount = command.instanceCount; - const auto isTexture = command.texture != nullptr; - d3d.drawIndexed(primitive, indexCount, offset, instanceCount, isTexture); + 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); diff --git a/platform/hac/source/modules/graphics/Texture.cpp b/platform/hac/source/modules/graphics/Texture.cpp index 3bb2fecc..b59a3bfb 100644 --- a/platform/hac/source/modules/graphics/Texture.cpp +++ b/platform/hac/source/modules/graphics/Texture.cpp @@ -1,6 +1,7 @@ #include "driver/display/deko.hpp" #include "modules/graphics/Texture.hpp" +#include namespace love { @@ -8,11 +9,11 @@ namespace love { out.x = (uint32_t)rectangle.x; out.y = (uint32_t)rectangle.y; - out.z = (uint32_t)0; + out.z = 0u; out.width = (uint32_t)rectangle.w; out.height = (uint32_t)rectangle.h; - out.depth = (uint32_t)1; + out.depth = 1u; } static void updateTextureObject(dk::Image& image, CMemPool::Handle& memory, @@ -23,43 +24,50 @@ namespace love if (!deko3d::getConstant(format, gpuFormat)) throw love::Exception("Invalid image format."); - auto& pool = d3d.getMemoryPool(deko3d::MEMORYPOOL_DATA); - auto tempMemory = pool.allocate(size, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); + auto& scratchPool = d3d.getMemoryPool(deko3d::MEMORYPOOL_DATA); + auto tempImageMem = scratchPool.allocate(size, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); - if (!tempMemory) - throw love::Exception("Failed to allocate temporary Texture memory."); + if (!tempImageMem) + throw love::Exception("Failed to allocate temporary image memory"); - std::memcpy(tempMemory.getCpuAddr(), data, size); + std::memcpy(tempImageMem.getCpuAddr(), data, size); - const auto& device = d3d.getDevice(); - auto tempCommandBuffer = dk::CmdBufMaker { device }.create(); - auto tempCommandMemory = pool.allocate(DK_MEMBLOCK_ALIGNMENT); + auto device = d3d.getDevice(); + auto tempCmdBuff = dk::CmdBufMaker { device }.create(); - tempCommandBuffer.addMemory(tempCommandMemory.getMemBlock(), tempCommandMemory.getOffset(), - tempCommandMemory.getSize()); - /* set the image layout */ - dk::ImageLayout layout; + auto tempCmdMem = scratchPool.allocate(DK_MEMBLOCK_ALIGNMENT); + tempCmdBuff.addMemory(tempCmdMem.getMemBlock(), tempCmdMem.getOffset(), tempCmdMem.getSize()); + + dk::ImageLayout layout {}; dk::ImageLayoutMaker { device } .setFlags(0) .setFormat(gpuFormat) .setDimensions(rect.w, rect.h) .initialize(layout); + auto& imagePool = d3d.getMemoryPool(deko3d::MEMORYPOOL_IMAGE); + memory = imagePool.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); dk::ImageView view { image }; DkImageRect dkRect {}; dkImageRectFromRect(rect, dkRect); - tempCommandBuffer.copyBufferToImage({ tempMemory.getGpuAddr() }, view, dkRect); + tempCmdBuff.copyBufferToImage({ tempImageMem.getGpuAddr() }, view, dkRect); + + auto imageQueue = d3d.getQueue(deko3d::QUEUE_TYPE_IMAGES); - auto& queue = d3d.getQueue(deko3d::QUEUE_TYPE_IMAGES); - queue.submitCommands(tempCommandBuffer.finishList()); - queue.waitIdle(); + imageQueue.submitCommands(tempCmdBuff.finishList()); + imageQueue.waitIdle(); - tempCommandMemory.destroy(); - tempMemory.destroy(); + tempCmdMem.destroy(); + tempImageMem.destroy(); } static void createFramebufferObject() @@ -67,7 +75,13 @@ namespace love Texture::Texture(GraphicsBase* graphics, const Settings& settings, const Slices* data) : TextureBase(graphics, settings, data), - slices(settings.type) + slices(settings.type), + image {}, + descriptor {}, + sampler {}, + samplerDescriptor {}, + handle(0), + memory {} { if (data != nullptr) this->slices = *data; @@ -75,7 +89,7 @@ namespace love if (!this->loadVolatile()) throw love::Exception("Failed to create texture."); - this->slices.clear(); + slices.clear(); } Texture::~Texture() @@ -85,9 +99,9 @@ namespace love bool Texture::loadVolatile() { - const auto& layout = this->image.getLayout(); - if (layout.getSize() != 0) - return true; + // const auto& layout = this->image.getLayout(); + // if (layout.getSize() != 0) + // return true; if (this->parentView.texture != this) { @@ -168,6 +182,7 @@ namespace love } } + d3d.registerTexture(this, true); this->setSamplerState(this->samplerState); if (this->slices.getMipmapCount() <= 1 && this->getMipmapsMode() != MIPMAPS_NONE) diff --git a/source/modules/font/freetype/Font.cpp b/source/modules/font/freetype/Font.cpp index 029a14d7..0975fa90 100644 --- a/source/modules/font/freetype/Font.cpp +++ b/source/modules/font/freetype/Font.cpp @@ -46,7 +46,7 @@ namespace love if (FT_Init_FreeType(&this->library) != 0) throw love::Exception("Error initializing FreeType library."); - this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); + // this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); } FontModule::~FontModule() diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index d3a8318f..437f0d33 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -47,6 +47,7 @@ namespace love } this->states.clear(); + this->defaultFont.set(nullptr); if (this->batchedDrawState.vertexBuffer) this->batchedDrawState.vertexBuffer->release(); From 1539ad5c4812c4e681cc3fa6981c0f93043d9cee Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 14 Nov 2024 10:16:52 -0500 Subject: [PATCH 46/49] =?UTF-8?q?(=20=CD=A1=C2=B0=20=CD=9C=CA=96=20=CD=A1?= =?UTF-8?q?=C2=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/driver/graphics/StreamBuffer.tcc | 5 - include/modules/graphics/vertex.hpp | 12 +- .../include/driver/display/Framebuffer.hpp | 4 +- platform/hac/include/driver/display/deko.hpp | 21 ++- .../include/driver/graphics/Attributes.hpp | 4 +- .../include/driver/graphics/StreamBuffer.hpp | 8 +- .../hac/include/modules/graphics/Texture.hpp | 4 +- platform/hac/source/driver/display/deko.cpp | 161 ++++++++---------- .../source/driver/graphics/StreamBuffer.cpp | 4 +- .../hac/source/modules/graphics/Graphics.cpp | 15 +- .../hac/source/modules/graphics/Shader.cpp | 20 ++- .../hac/source/modules/graphics/Texture.cpp | 100 +++++------ source/modules/graphics/Buffer.cpp | 8 +- source/modules/graphics/Graphics.cpp | 6 +- source/modules/graphics/Texture.cpp | 12 +- source/modules/graphics/vertex.cpp | 24 +++ source/modules/graphics/wrap_Graphics.cpp | 28 +-- 17 files changed, 224 insertions(+), 212 deletions(-) diff --git a/include/driver/graphics/StreamBuffer.tcc b/include/driver/graphics/StreamBuffer.tcc index 3749d63c..7c4955bd 100644 --- a/include/driver/graphics/StreamBuffer.tcc +++ b/include/driver/graphics/StreamBuffer.tcc @@ -75,11 +75,6 @@ namespace love this->frameGPUReadOffset = 0; } - /* - ** Advance to the next frame. - */ - // virtual void nextFrame() = 0; - protected: StreamBufferBase(BufferUsage usage, size_t size) : bufferSize(size), diff --git a/include/modules/graphics/vertex.hpp b/include/modules/graphics/vertex.hpp index 82244c15..719cfe9a 100644 --- a/include/modules/graphics/vertex.hpp +++ b/include/modules/graphics/vertex.hpp @@ -242,17 +242,9 @@ namespace love static constexpr size_t TEXCOORD_OFFSET = offsetof(Vertex, s); static constexpr size_t COLOR_OFFSET = offsetof(Vertex, color); - inline void DEBUG_VERTEX(const Vertex& v) - { - 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 debugVertices(Vertex* vertices, size_t count); - inline void DEBUG_VERTEX(const Vertex* v) - { - DEBUG_VERTEX(*v); - } + void debugIndices(uint16_t* indices, size_t count); inline CommonFormat getSinglePositionFormat(bool is2D) { diff --git a/platform/hac/include/driver/display/Framebuffer.hpp b/platform/hac/include/driver/display/Framebuffer.hpp index 92d32e0e..6fb4e9cd 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/deko.hpp b/platform/hac/include/driver/display/deko.hpp index 0b48fcda..bafd2ab9 100644 --- a/platform/hac/include/driver/display/deko.hpp +++ b/platform/hac/include/driver/display/deko.hpp @@ -22,7 +22,8 @@ #include #include -#define MAX_OBJECTS 0x400 +#define MAX_OBJECTS 0x400 +#define MAX_RENDERTARGETS 2 namespace love { @@ -54,7 +55,7 @@ namespace love dk::Image& getInternalBackbuffer(); - void bindFramebuffer(dk::Image& target); + void bindFramebuffer(dk::Image* target = nullptr); void present(); @@ -78,7 +79,7 @@ namespace love void useProgram(const dk::Shader& vertex, const dk::Shader& fragment); - void bindBuffer(BufferUsage usage, DkGpuAddr buffer, size_t size); + void bindBuffer(BufferUsage usage, CMemPool::Handle& memory); void onModeChanged() { @@ -171,6 +172,12 @@ namespace love { 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 }, @@ -184,7 +191,7 @@ namespace love { 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_RGBA8_sRGB, DkImageFormat_RGBA8_Unorm_sRGB }, { PIXELFORMAT_DXT1_UNORM, DkImageFormat_RGBA_BC1 }, { PIXELFORMAT_DXT3_UNORM, DkImageFormat_RGBA_BC2 }, { PIXELFORMAT_DXT5_UNORM, DkImageFormat_RGBA_BC3 }, @@ -259,7 +266,7 @@ namespace love static constexpr auto TRANSFORM_SIZE = sizeof(Transform); - CMemPool::Handle uniformBuffer; + CMemPool::Handle uniform; dk::UniqueDevice device; @@ -275,11 +282,11 @@ namespace love int framebufferSlot; - Framebuffer framebuffers[0x02]; + Framebuffer framebuffers[MAX_RENDERTARGETS]; Framebuffer depthbuffer; CCmdMemRing<2> commands; - std::array targets; + std::array targets; BitwiseAlloc textureHandles; CDescriptorSet imageSet; diff --git a/platform/hac/include/driver/graphics/Attributes.hpp b/platform/hac/include/driver/graphics/Attributes.hpp index 850f99f9..887fdbb5 100644 --- a/platform/hac/include/driver/graphics/Attributes.hpp +++ b/platform/hac/include/driver/graphics/Attributes.hpp @@ -37,8 +37,8 @@ namespace love constexpr std::array TextureAttribState = { DkVtxAttribState { 0, 0, POSITION_OFFSET, DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0 }, - DkVtxAttribState { 0, 0, TEXCOORD_OFFSET, DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0 }, - DkVtxAttribState { 0, 0, COLOR_OFFSET, DkVtxAttribSize_4x32, 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 diff --git a/platform/hac/include/driver/graphics/StreamBuffer.hpp b/platform/hac/include/driver/graphics/StreamBuffer.hpp index 753f2715..0b52d77b 100644 --- a/platform/hac/include/driver/graphics/StreamBuffer.hpp +++ b/platform/hac/include/driver/graphics/StreamBuffer.hpp @@ -28,8 +28,7 @@ namespace love ~StreamBuffer() { - if (this->memory) - this->memory.destroy(); + this->memory.destroy(); } MapInfo map(size_t) @@ -43,6 +42,11 @@ namespace love size_t unmap(size_t); + void* getData() const + { + return this->memory.getCpuAddr(); + } + ptrdiff_t getHandle() const { return (ptrdiff_t)this->memory.getGpuAddr(); diff --git a/platform/hac/include/modules/graphics/Texture.hpp b/platform/hac/include/modules/graphics/Texture.hpp index dbf4d2b2..3880e8fd 100644 --- a/platform/hac/include/modules/graphics/Texture.hpp +++ b/platform/hac/include/modules/graphics/Texture.hpp @@ -25,12 +25,12 @@ namespace love ptrdiff_t getSamplerHandle() const override; - dk::ImageDescriptor getDescriptorHandle() + dk::ImageDescriptor& getDescriptorHandle() { return this->descriptor; } - dk::SamplerDescriptor getSamplerDescriptorHandle() + dk::SamplerDescriptor& getSamplerDescriptor() { return this->samplerDescriptor; } diff --git a/platform/hac/source/driver/display/deko.cpp b/platform/hac/source/driver/display/deko.cpp index 7ab36ddd..c86d96ef 100644 --- a/platform/hac/source/driver/display/deko.cpp +++ b/platform/hac/source/driver/display/deko.cpp @@ -23,16 +23,16 @@ namespace love if (this->initialized) return; - this->uniformBuffer = this->data.allocate(TRANSFORM_SIZE, DK_UNIFORM_BUF_ALIGNMENT); + 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->ensureInFrame(); this->context.rasterizer.setCullMode(DkFace_None); + this->context.depthStencil.setDepthTestEnable(true); this->context.depthStencil.setDepthWriteEnable(true); this->context.depthStencil.setDepthCompareOp(DkCompareOp_Always); @@ -40,9 +40,11 @@ namespace love this->context.color.setBlendEnable(0, true); this->imageSet.allocate(this->data); - this->imageSet.bindForImages(this->commandBuffer); - this->samplerSet.allocate(this->data); + + this->ensureInFrame(); + + this->imageSet.bindForImages(this->commandBuffer); this->samplerSet.bindForSamplers(this->commandBuffer); this->initialized = true; @@ -51,21 +53,19 @@ namespace love deko3d::~deko3d() { this->destroyFramebuffers(); - - if (this->uniformBuffer) - this->uniformBuffer.destroy(); + this->uniform.destroy(); } void deko3d::createFramebuffers() { - const auto& info = getScreenInfo()[0]; + 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->targets[index] = this->framebuffers[index].getImage(); } this->swapchain = dk::SwapchainMaker { this->device, nwindowGetDefault(), this->targets }.create(); @@ -99,12 +99,7 @@ namespace love { if (!this->inFrame) { - if (!this->swapchain) - return; - - if (this->framebufferSlot < 0) - this->framebufferSlot = this->mainQueue.acquireImage(this->swapchain); - + this->context.descriptorsDirty = false; this->commands.begin(this->commandBuffer); this->inFrame = true; } @@ -126,18 +121,20 @@ namespace love this->commandBuffer.clearDepthStencil(true, depth, mask, stencil); } - dk::Image& deko3d::getInternalBackbuffer() - { - this->ensureInFrame(); - return this->framebuffers[this->framebufferSlot].getImage(); - } + // 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->uniformBuffer.getGpuAddr(), this->uniformBuffer.getSize()); - // clang-format off + this->commandBuffer.bindUniformBuffer(DkStage_Vertex, 0, this->uniform.getGpuAddr(), this->uniform.getSize()); + // clang-format on } void deko3d::registerTexture(TextureBase* texture, bool registering) @@ -145,43 +142,47 @@ namespace love if (registering) { const auto index = this->textureHandles.allocate(); - const auto handle = dkMakeTextureHandle(index, index); - texture->setHandleData(handle); - + texture->setHandleData(dkMakeTextureHandle(index, index)); return; } this->textureHandles.deallocate((uint32_t)texture->getHandle()); } - void deko3d::bindFramebuffer(dk::Image& framebuffer) + 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) + if (this->context.boundFramebuffer != framebuffer) { bindingModified = true; - this->context.boundFramebuffer = &framebuffer; + this->context.boundFramebuffer = framebuffer; } if (bindingModified) { - dk::ImageView depth { this->depthbuffer.getImage() }; - dk::ImageView target { framebuffer }; + dk::ImageView depth { *this->depthbuffer.getImage() }; + dk::ImageView target { *framebuffer }; - this->commandBuffer.bindRenderTargets(&target); + this->commandBuffer.bindRenderTargets(&target, &depth); } } - void deko3d::bindBuffer(BufferUsage usage, DkGpuAddr buffer, size_t size) + void deko3d::bindBuffer(BufferUsage usage, CMemPool::Handle& handle) { if (usage == BUFFERUSAGE_VERTEX) - this->commandBuffer.bindVtxBuffer(0, buffer, size); + this->commandBuffer.bindVtxBuffer(0, handle.getGpuAddr(), handle.getSize()); else if (usage == BUFFERUSAGE_INDEX) - this->commandBuffer.bindIdxBuffer(DkIdxFormat_Uint16, buffer); + this->commandBuffer.bindIdxBuffer(DkIdxFormat_Uint16, handle.getGpuAddr()); } void deko3d::setVertexAttributes(bool isTexture) @@ -209,11 +210,12 @@ namespace love this->commandBuffer.barrier(DkBarrier_Primitives, DkInvalidateFlags_Descriptors); this->context.descriptorsDirty = false; } - - this->commandBuffer.bindTextures(DkStage_Fragment, 0, { texture }); + std::printf("Texture Handle id: %u\n", texture); + this->commandBuffer.bindTextures(DkStage_Fragment, 0, texture); } - void deko3d::drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, uint32_t instanceCount) + void deko3d::drawIndexed(DkPrimitive primitive, uint32_t indexCount, uint32_t indexOffset, + uint32_t instanceCount) { this->commandBuffer.drawIndexed(primitive, indexCount, instanceCount, indexOffset, 0, 0); } @@ -230,9 +232,8 @@ namespace love this->commandBuffer.bindColorWriteState(this->context.colorWrite); this->commandBuffer.bindBlendStates(0, this->context.blend); - this->commandBuffer.pushConstants(this->uniformBuffer.getGpuAddr(), - this->uniformBuffer.getSize(), 0, TRANSFORM_SIZE, - &this->transform); + this->commandBuffer.pushConstants(this->uniform.getGpuAddr(), this->uniform.getSize(), 0, + TRANSFORM_SIZE, &this->transform); } void deko3d::present() @@ -319,7 +320,6 @@ namespace love this->context.blend.setColorBlendOp(operationRGB); this->context.blend.setAlphaBlendOp(operationA); - // Blend factors this->context.blend.setSrcColorBlendFactor(sourceColor); this->context.blend.setSrcAlphaBlendFactor(sourceAlpha); @@ -327,11 +327,13 @@ namespace love this->context.blend.setDstAlphaBlendFactor(destAlpha); } - void deko3d::setSamplerState(TextureBase* texture, const SamplerState&state) + void deko3d::setSamplerState(TextureBase* texture, const SamplerState& state) { - auto* sampler = (dk::Sampler*)texture->getSamplerHandle(); - auto descriptor = ((Texture*)texture)->getDescriptorHandle(); - auto samplerDescriptor = ((Texture*)texture)->getSamplerDescriptorHandle(); + this->ensureInFrame(); + + auto* sampler = (dk::Sampler*)texture->getSamplerHandle(); + auto& imageDescriptor = ((Texture*)texture)->getDescriptorHandle(); + // auto& samplerDescriptor = ((Texture*)texture)->getSamplerDescriptor(); auto index = -1; @@ -346,7 +348,11 @@ namespace love if (!deko3d::getConstant(state.minFilter, minFilter)) return; - sampler->setFilter(minFilter, magFilter); + DkMipFilter mipFilter; + if (!deko3d::getConstant(state.mipmapFilter, mipFilter)) + return; + + sampler->setFilter(minFilter, magFilter, mipFilter); DkWrapMode wrapU; if (!deko3d::getConstant(state.wrapU, wrapU)) @@ -362,67 +368,48 @@ namespace love sampler->setWrapMode(wrapU, wrapV, wrapW); - this->imageSet.update(this->commandBuffer, index, descriptor); - + dk::SamplerDescriptor samplerDescriptor {}; samplerDescriptor.initialize(*sampler); - + std::printf("updating index %u\n", index); + this->imageSet.update(this->commandBuffer, index, imageDescriptor); this->samplerSet.update(this->commandBuffer, index, samplerDescriptor); + this->context.descriptorsDirty = true; } - static DkScissor dkScissorFromRect(const Rect& rect) + template + T dkRectFromRect(const Rect& rect) { - DkScissor scissor {}; + T value {}; + value.x = rect.x; + value.y = rect.y; + value.width = rect.w; + value.height = rect.h; - 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; + return value; } - void deko3d::setScissor(const Rect& scissor) + void deko3d::setScissor(const Rect& rect) { 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; + const auto scissor = rect == Rect::EMPTY ? this->context.scissor : rect; + DkScissor dkScissor = dkRectFromRect(scissor); - return viewport; + this->commandBuffer.setScissors(0, dkScissor); } - void deko3d::setViewport(const Rect& viewport) + void deko3d::setViewport(const Rect& rect) { this->ensureInFrame(); - DkViewport _viewport {}; - if (viewport == Rect::EMPTY) - _viewport = dkViewportFromRect(this->context.viewport); - else - _viewport = dkViewportFromRect(viewport); + const auto viewport = rect == Rect::EMPTY ? this->context.viewport : rect; + DkViewport dkViewport = dkRectFromRect(viewport); - this->commandBuffer.setViewports(0, { _viewport }); + this->commandBuffer.setViewports(0, dkViewport); - const auto ortho = glm::ortho(0.0f, (float)viewport.w, (float)viewport.h, 0.0f, -10.0f, 10.0f); - this->transform.projection = ortho; + this->transform.projection = + glm::ortho(0.0f, (float)viewport.w, (float)viewport.h, 0.0f, -10.0f, 10.0f); } deko3d d3d; diff --git a/platform/hac/source/driver/graphics/StreamBuffer.cpp b/platform/hac/source/driver/graphics/StreamBuffer.cpp index ad7f2a89..23e82828 100644 --- a/platform/hac/source/driver/graphics/StreamBuffer.cpp +++ b/platform/hac/source/driver/graphics/StreamBuffer.cpp @@ -7,14 +7,14 @@ namespace love template<> size_t StreamBuffer::unmap(size_t) { - d3d.bindBuffer(this->usage, this->memory.getGpuAddr(), this->memory.getSize()); + d3d.bindBuffer(this->usage, this->memory); return this->index; } template<> size_t StreamBuffer::unmap(size_t) { - d3d.bindBuffer(this->usage, this->memory.getGpuAddr(), this->memory.getSize()); + 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 46fd78b6..80930b00 100644 --- a/platform/hac/source/modules/graphics/Graphics.cpp +++ b/platform/hac/source/modules/graphics/Graphics.cpp @@ -122,8 +122,6 @@ namespace love void Graphics::clear(OptionalColor color, OptionalInt stencil, OptionalDouble depth) { - d3d.bindFramebuffer(d3d.getInternalBackbuffer()); - if (color.hasValue) { bool hasIntegerFormat = false; @@ -147,6 +145,8 @@ namespace love } } + d3d.bindFramebuffer(); + if (color.hasValue || stencil.hasValue || depth.hasValue) this->flushBatchedDraws(); @@ -162,8 +162,6 @@ namespace love if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue) return; - d3d.bindFramebuffer(d3d.getInternalBackbuffer()); - int numColors = (int)colors.size(); const auto& targets = this->states.back().renderTargets.colors; @@ -177,6 +175,8 @@ namespace love return; } + d3d.bindFramebuffer(); + this->flushBatchedDraws(); numColors = std::min(numColors, numColorTargets); @@ -306,9 +306,9 @@ namespace love bool isWindow = targets.getFirstTarget().texture == nullptr; if (isWindow) - d3d.bindFramebuffer(d3d.getInternalBackbuffer()); + d3d.bindFramebuffer(); else - d3d.bindFramebuffer(*(dk::Image*)targets.getFirstTarget().texture->getRenderTargetHandle()); + d3d.bindFramebuffer((dk::Image*)targets.getFirstTarget().texture->getRenderTargetHandle()); d3d.setViewport({ 0, 0, pixelWidth, pixelHeight }); @@ -386,9 +386,6 @@ namespace love if (!Shader::current) Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach(); - // this->backbufferChanged(width, height, pixelWidth, pixelHeight, backBufferStencil, backBufferDepth, - // msaa); - return true; } diff --git a/platform/hac/source/modules/graphics/Shader.cpp b/platform/hac/source/modules/graphics/Shader.cpp index 4ee93fba..bce1184b 100644 --- a/platform/hac/source/modules/graphics/Shader.cpp +++ b/platform/hac/source/modules/graphics/Shader.cpp @@ -25,7 +25,7 @@ namespace love Shader::Shader() {} - Shader::Shader(StandardShader shader) + Shader::Shader(StandardShader shader) : ShaderBase(shader) { std::string error; const char* fragmentStage = nullptr; @@ -33,25 +33,31 @@ namespace love 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.c_str()); + 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.c_str()); + throw love::Exception("Invalid fragment shader: {:s}", error); } Shader::~Shader() @@ -69,7 +75,6 @@ namespace love ++shaderSwitches; Shader::current = this; - shaderSwitches++; } } @@ -80,11 +85,8 @@ namespace love void Shader::unloadVolatile() { - if (this->program.vertex.memory) - this->program.vertex.memory.destroy(); - - if (this->program.fragment.memory) - this->program.fragment.memory.destroy(); + this->program.vertex.memory.destroy(); + this->program.fragment.memory.destroy(); } void Shader::updateBuiltinUniforms(GraphicsBase* graphics) diff --git a/platform/hac/source/modules/graphics/Texture.cpp b/platform/hac/source/modules/graphics/Texture.cpp index b59a3bfb..9ecc66d7 100644 --- a/platform/hac/source/modules/graphics/Texture.cpp +++ b/platform/hac/source/modules/graphics/Texture.cpp @@ -5,69 +5,32 @@ namespace love { - static void dkImageRectFromRect(const Rect& rectangle, DkImageRect& out) - { - out.x = (uint32_t)rectangle.x; - out.y = (uint32_t)rectangle.y; - out.z = 0u; - - out.width = (uint32_t)rectangle.w; - out.height = (uint32_t)rectangle.h; - out.depth = 1u; - } - - static void updateTextureObject(dk::Image& image, CMemPool::Handle& memory, - dk::ImageDescriptor& descriptor, const Rect& rect, const void* data, - const size_t size, PixelFormat format) + 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& scratchPool = d3d.getMemoryPool(deko3d::MEMORYPOOL_DATA); - auto tempImageMem = scratchPool.allocate(size, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); - - if (!tempImageMem) - throw love::Exception("Failed to allocate temporary image memory"); - - std::memcpy(tempImageMem.getCpuAddr(), data, size); - - auto device = d3d.getDevice(); - auto tempCmdBuff = dk::CmdBufMaker { device }.create(); - - auto tempCmdMem = scratchPool.allocate(DK_MEMBLOCK_ALIGNMENT); - tempCmdBuff.addMemory(tempCmdMem.getMemBlock(), tempCmdMem.getOffset(), tempCmdMem.getSize()); + auto device = d3d.getDevice(); dk::ImageLayout layout {}; dk::ImageLayoutMaker { device } .setFlags(0) .setFormat(gpuFormat) - .setDimensions(rect.w, rect.h) + .setDimensions(width, height) .initialize(layout); - auto& imagePool = d3d.getMemoryPool(deko3d::MEMORYPOOL_IMAGE); - memory = imagePool.allocate(layout.getSize(), layout.getAlignment()); + auto& pool = d3d.getMemoryPool(deko3d::MEMORYPOOL_IMAGE); + + memory = pool.allocate(layout.getSize(), layout.getAlignment()); if (!memory) - throw love::Exception("Failed to allocate CMemPool::Handle"); + throw love::Exception("Failed to allocate CMemPool::Handle!"); image.initialize(layout, memory.getMemBlock(), memory.getOffset()); descriptor.initialize(image); - - dk::ImageView view { image }; - - DkImageRect dkRect {}; - dkImageRectFromRect(rect, dkRect); - - tempCmdBuff.copyBufferToImage({ tempImageMem.getGpuAddr() }, view, dkRect); - - auto imageQueue = d3d.getQueue(deko3d::QUEUE_TYPE_IMAGES); - - imageQueue.submitCommands(tempCmdBuff.finishList()); - imageQueue.waitIdle(); - - tempCmdMem.destroy(); - tempImageMem.destroy(); } static void createFramebufferObject() @@ -134,9 +97,7 @@ namespace love { d3d.registerTexture(this, false); - if (this->memory) - this->memory.destroy(); - + this->memory.destroy(); this->setGraphicsMemorySize(0); } @@ -144,6 +105,16 @@ namespace love { 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; @@ -197,7 +168,36 @@ namespace love void Texture::uploadByteData(const void* data, size_t size, int level, int slice, const Rect& rect) { - updateTextureObject(this->image, this->memory, this->descriptor, rect, data, size, this->format); + 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() diff --git a/source/modules/graphics/Buffer.cpp b/source/modules/graphics/Buffer.cpp index 26b14b19..b42d3234 100644 --- a/source/modules/graphics/Buffer.cpp +++ b/source/modules/graphics/Buffer.cpp @@ -27,8 +27,8 @@ namespace love const auto& caps = graphics->getCapabilities(); - bool indexBuffer = usageFlags & BUFFERUSAGE_INDEX; - bool vertexBuffer = usageFlags & BUFFERUSAGE_VERTEX; + bool indexBuffer = usageFlags & BUFFERUSAGEFLAG_INDEX; + bool vertexBuffer = usageFlags & BUFFERUSAGEFLAG_VERTEX; size_t offset = 0; size_t stride = 0; @@ -78,7 +78,7 @@ namespace love this->dataMembers.push_back(member); } - stride = alignUp(offset, structureAlignment); + // stride = alignUp(offset, structureAlignment); if (size != 0) { @@ -183,6 +183,8 @@ namespace love { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, }; + default: + break; } return {}; diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 437f0d33..18d97afb 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -409,9 +409,6 @@ namespace love if ((state.lastIndexCount == 0 && state.lastVertexCount == 0) || state.flushing) return; - if ((state.indexCount == 0 && state.vertexCount == 0) || state.flushing) - return; - VertexAttributes attributes {}; BufferBindings buffers {}; @@ -453,6 +450,9 @@ namespace love command.texture = state.texture; command.isFont = state.isFont; + debugVertices((Vertex*)state.vertexBuffer->getData(), state.lastVertexCount); + debugIndices((uint16_t*)state.indexBuffer->getData(), state.lastIndexCount); + this->draw(command); state.indexBufferMap = MapInfo(); diff --git a/source/modules/graphics/Texture.cpp b/source/modules/graphics/Texture.cpp index edce4e7c..f876ac54 100644 --- a/source/modules/graphics/Texture.cpp +++ b/source/modules/graphics/Texture.cpp @@ -411,14 +411,10 @@ namespace love } } - static constexpr bool pow2 = (Console::is(Console::CTR) ? true : false); - void TextureBase::uploadImageData(ImageDataBase* data, int level, int slice, int x, int y) { Rect rectangle = { x, y, data->getWidth(), data->getHeight() }; - - const auto size = getPixelFormatSliceSize(this->format, data->getWidth(), data->getHeight(), pow2); - this->uploadByteData(data->getData(), size, level, slice, rectangle); + this->uploadByteData(data->getData(), data->getSize(), level, slice, rectangle); } void TextureBase::replacePixels(ImageDataBase* data, int slice, int mipmap, int x, int y, @@ -459,6 +455,8 @@ namespace love 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) @@ -476,6 +474,8 @@ namespace love if (graphics != nullptr && graphics->isRenderTargetActive(this)) return; + GraphicsBase::flushBatchedDrawsGlobal(); + this->uploadByteData(data, size, mipmap, slice, rect); if (reloadMipmaps && mipmap == 0 && this->getMipmapCount() > 1) @@ -509,6 +509,8 @@ namespace love } } + GraphicsBase::flushBatchedDrawsGlobal(); + return state; } diff --git a/source/modules/graphics/vertex.cpp b/source/modules/graphics/vertex.cpp index 3250f427..37680e87 100644 --- a/source/modules/graphics/vertex.cpp +++ b/source/modules/graphics/vertex.cpp @@ -1,7 +1,31 @@ #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) diff --git a/source/modules/graphics/wrap_Graphics.cpp b/source/modules/graphics/wrap_Graphics.cpp index e3f99466..730f769f 100644 --- a/source/modules/graphics/wrap_Graphics.cpp +++ b/source/modules/graphics/wrap_Graphics.cpp @@ -1161,25 +1161,25 @@ 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."); + // 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; + // Mesh* mesh = nullptr; - int secondArgType = lua_type(L, 2); + // int secondArgType = lua_type(L, 2); - if (firstArgType == LUA_TTABLE && - (secondArgType == LUA_TTABLE || secondArgType == LUA_TNUMBER || secondArgType == LUA_TUSERDATA)) - { - } - else - mesh = newStandardMesh(L); + // if (firstArgType == LUA_TTABLE && + // (secondArgType == LUA_TTABLE || secondArgType == LUA_TNUMBER || secondArgType == LUA_TUSERDATA)) + // { + // } + // else + // mesh = newStandardMesh(L); - luax_pushtype(L, mesh); - mesh->release(); + // luax_pushtype(L, mesh); + // mesh->release(); - return 1; + return 0; } int Wrap_Graphics::newTextBatch(lua_State* L) From 2aede949aa51f859727a48bf18be643301e49584 Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 14 Nov 2024 10:32:44 -0500 Subject: [PATCH 47/49] re-add sysfont loading --- source/modules/font/freetype/Font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/modules/font/freetype/Font.cpp b/source/modules/font/freetype/Font.cpp index 0975fa90..029a14d7 100644 --- a/source/modules/font/freetype/Font.cpp +++ b/source/modules/font/freetype/Font.cpp @@ -46,7 +46,7 @@ namespace love if (FT_Init_FreeType(&this->library) != 0) throw love::Exception("Error initializing FreeType library."); - // this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); + this->defaultFontData.set(loadSystemFontByType(), Acquire::NO_RETAIN); } FontModule::~FontModule() From 6090002f1f9e37c5af5f0e40e4cd6a10a0e4e65b Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 14 Nov 2024 11:37:25 -0500 Subject: [PATCH 48/49] commit some fixes --- platform/hac/include/driver/display/deko.hpp | 2 +- platform/hac/source/boot.cpp | 17 +++++------ platform/hac/source/driver/EventQueue.cpp | 8 ++++++ platform/hac/source/driver/display/deko.cpp | 9 ++++-- source/main.cpp | 3 +- source/modules/event/Event.cpp | 4 +++ source/modules/graphics/Graphics.cpp | 15 +++++----- source/modules/love/love.cpp | 30 ++++++++++---------- 8 files changed, 53 insertions(+), 35 deletions(-) diff --git a/platform/hac/include/driver/display/deko.hpp b/platform/hac/include/driver/display/deko.hpp index bafd2ab9..94375dac 100644 --- a/platform/hac/include/driver/display/deko.hpp +++ b/platform/hac/include/driver/display/deko.hpp @@ -285,7 +285,7 @@ namespace love Framebuffer framebuffers[MAX_RENDERTARGETS]; Framebuffer depthbuffer; - CCmdMemRing<2> commands; + CCmdMemRing commands; std::array targets; BitwiseAlloc textureHandles; diff --git a/platform/hac/source/boot.cpp b/platform/hac/source/boot.cpp index c96ab92a..7f60ef81 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 a854c212..d7f375a8 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" @@ -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; } } } diff --git a/platform/hac/source/driver/display/deko.cpp b/platform/hac/source/driver/display/deko.cpp index c86d96ef..d8dfc063 100644 --- a/platform/hac/source/driver/display/deko.cpp +++ b/platform/hac/source/driver/display/deko.cpp @@ -6,6 +6,7 @@ namespace love { deko3d::deko3d() : + context {}, transform {}, device(dk::DeviceMaker {}.setFlags(DkDeviceFlags_DepthMinusOneToOne).create()), mainQueue(dk::QueueMaker { this->device }.setFlags(DkQueueFlags_Graphics).create()), @@ -14,7 +15,6 @@ namespace love 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)), - context {}, framebufferSlot(-1) {} @@ -210,7 +210,7 @@ namespace love this->commandBuffer.barrier(DkBarrier_Primitives, DkInvalidateFlags_Descriptors); this->context.descriptorsDirty = false; } - std::printf("Texture Handle id: %u\n", texture); + this->commandBuffer.bindTextures(DkStage_Fragment, 0, texture); } @@ -232,6 +232,9 @@ namespace love 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); } @@ -370,7 +373,7 @@ namespace love dk::SamplerDescriptor samplerDescriptor {}; samplerDescriptor.initialize(*sampler); - std::printf("updating index %u\n", index); + this->imageSet.update(this->commandBuffer, index, imageDescriptor); this->samplerSet.update(this->commandBuffer, index, samplerDescriptor); diff --git a/source/main.cpp b/source/main.cpp index 88cddb2a..b03153f7 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/event/Event.cpp b/source/modules/event/Event.cpp index 2e5bffad..f0b36724 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(event.resize.width); + args.emplace_back(event.resize.height); + result = new Message("resize", args); default: break; } diff --git a/source/modules/graphics/Graphics.cpp b/source/modules/graphics/Graphics.cpp index 18d97afb..87f3561d 100644 --- a/source/modules/graphics/Graphics.cpp +++ b/source/modules/graphics/Graphics.cpp @@ -54,6 +54,8 @@ namespace love if (this->batchedDrawState.indexBuffer) this->batchedDrawState.indexBuffer->release(); + + Volatile::unloadAll(); } void GraphicsBase::resetProjection() @@ -222,10 +224,12 @@ namespace love if (this->batchedDrawState.vertexCount > 0) stats.drawCalls++; - stats.drawCallsBatched = this->drawCallsBatched; - stats.textures = TextureBase::textureCount; - stats.textureMemory = TextureBase::totalGraphicsMemory; - stats.shaderSwitches = ShaderBase::shaderSwitches; + 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; } @@ -450,9 +454,6 @@ namespace love command.texture = state.texture; command.isFont = state.isFont; - debugVertices((Vertex*)state.vertexBuffer->getData(), state.lastVertexCount); - debugIndices((uint16_t*)state.indexBuffer->getData(), state.lastIndexCount); - this->draw(command); state.indexBufferMap = MapInfo(); diff --git a/source/modules/love/love.cpp b/source/modules/love/love.cpp index 278d462f..2f035780 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -164,20 +164,20 @@ static int love_atpanic(lua_State* L) 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 +// #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) { @@ -269,7 +269,7 @@ int love_initialize(lua_State* L) lua_atpanic(L, love_atpanic); #if __DEBUG__ - std::set_terminate(love_atcpanic); + // std::set_terminate(love_atcpanic); #endif return 1; From a879673e38c0bcb45e27d25575013b6de592823f Mon Sep 17 00:00:00 2001 From: TurtleP Date: Thu, 14 Nov 2024 11:41:57 -0500 Subject: [PATCH 49/49] fix compilation woops --- source/modules/event/Event.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/modules/event/Event.cpp b/source/modules/event/Event.cpp index f0b36724..33dd2d74 100644 --- a/source/modules/event/Event.cpp +++ b/source/modules/event/Event.cpp @@ -54,8 +54,8 @@ namespace love break; } case SUBTYPE_RESIZE: - args.emplace_back(event.resize.width); - args.emplace_back(event.resize.height); + args.emplace_back((double)event.resize.width); + args.emplace_back((double)event.resize.height); result = new Message("resize", args); default: break; @@ -260,6 +260,9 @@ namespace love case TYPE_GAMEPAD: message = convertJoystickEvent(event, args); break; + case TYPE_WINDOW: + message = convertWindowEvent(event, args); + break; default: break; }