diff --git a/config.m4 b/config.m4 index d749690e..f01da6b2 100644 --- a/config.m4 +++ b/config.m4 @@ -125,9 +125,10 @@ if test "$PHP_GLFW" != "no"; then phpglfw_texture.c \ phpglfw_objparser.c \ phpglfw_voxparser.c \ - ogt_vox_c_wrapper.cpp \ + src/ogt_vox_c_wrapper.cpp \ phpglfw_vg.c \ phpglfw_audio.c \ + phpglfw_drawcall_assembler.c \ vendor/fastobj/fast_obj.c \ vendor/glad/src/glad.c \ vendor/nanovg/src/nanovg.c" @@ -148,6 +149,8 @@ if test "$PHP_GLFW" != "no"; then PHP_ADD_INCLUDE([$ext_srcdir]) + PHP_ADD_INCLUDE([$ext_srcdir/include]) + PHP_ADD_INCLUDE([$ext_srcdir/src]) PHP_ADD_INCLUDE([$ext_srcdir/vendor/glad/include]) PHP_ADD_INCLUDE([$ext_srcdir/vendor/cvector]) PHP_ADD_INCLUDE([$ext_srcdir/vendor/stb]) @@ -158,6 +161,7 @@ if test "$PHP_GLFW" != "no"; then PHP_ADD_INCLUDE([$GLFW_DIR/include]) PHP_INSTALL_HEADERS([ext/glfw], [*.h \ + include/*.h \ vendor/glad/include/glad/*.h \ vendor/glad/include/KHR/*.h \ vendor/cvector/*.h \ diff --git a/config.w32 b/config.w32 index c4c437ed..fa036da5 100644 --- a/config.w32 +++ b/config.w32 @@ -7,6 +7,7 @@ if (PHP_GLFW != "no") { if ( CHECK_HEADER_ADD_INCLUDE("glad/glad.h", "CFLAGS_GLFW", configure_module_dirname + "\\vendor\\glad\\include;" + PHP_EXTRA_INCLUDES) && CHECK_HEADER_ADD_INCLUDE("GLFW/glfw3.h", "CFLAGS_GLFW", configure_module_dirname + "\\vendor\\glfw\\include;" + PHP_EXTRA_INCLUDES) && + CHECK_HEADER_ADD_INCLUDE("ogt_vox_c_wrapper.h", "CFLAGS_GLFW", configure_module_dirname + "\\include;" + PHP_EXTRA_INCLUDES) && CHECK_HEADER_ADD_INCLUDE("cvector.h", "CFLAGS_GLFW", configure_module_dirname + "\\vendor\\cvector;" + PHP_EXTRA_INCLUDES) && CHECK_HEADER_ADD_INCLUDE("stb_image.h", "CFLAGS_GLFW", configure_module_dirname + "\\vendor\\stb;" + PHP_EXTRA_INCLUDES) && CHECK_HEADER_ADD_INCLUDE("stb_perlin.h", "CFLAGS_GLFW", configure_module_dirname + "\\vendor\\stb;" + PHP_EXTRA_INCLUDES) && @@ -19,7 +20,8 @@ if (PHP_GLFW != "no") { CHECK_HEADER_ADD_INCLUDE("ogt_vox.h", "CFLAGS_GLFW", configure_module_dirname + "\\vendor\\opengametools\\src;" + PHP_EXTRA_INCLUDES) ) { - var sources = "phpglfw.c phpglfw_constants.c phpglfw_functions.c phpglfw_math.c phpglfw_buffer.c phpglfw_texture.c phpglfw_objparser.c phpglfw_voxparser.c ogt_vox_c_wrapper.cpp phpglfw_vg.c phpglfw_audio.c"; + var sources = "phpglfw.c phpglfw_constants.c phpglfw_functions.c phpglfw_math.c phpglfw_buffer.c phpglfw_texture.c phpglfw_objparser.c phpglfw_voxparser.c phpglfw_vg.c phpglfw_audio.c phpglfw_drawcall_assembler.c"; + var src_sources = "ogt_vox_c_wrapper.cpp"; var glad_sources = "glad.c"; var fastobj_sources = "fast_obj.c"; @@ -30,6 +32,7 @@ if (PHP_GLFW != "no") { EXTENSION("glfw", sources); + ADD_SOURCES(configure_module_dirname + "\\src", src_sources, "glfw"); ADD_SOURCES(configure_module_dirname + "\\vendor\\glad\\src", glad_sources, "glfw"); ADD_SOURCES(configure_module_dirname + "\\vendor\\glfw\\src", glfw_sources, "glfw"); ADD_SOURCES(configure_module_dirname + "\\vendor\\fastobj", fastobj_sources, "glfw"); diff --git a/docs/API/Geometry/ObjFileParserMaterial.md b/docs/API/Geometry/ObjFileParserMaterial.md index 3fa47ba2..dfb37a95 100644 --- a/docs/API/Geometry/ObjFileParserMaterial.md +++ b/docs/API/Geometry/ObjFileParserMaterial.md @@ -62,16 +62,16 @@ This property is often also used for reflection color, shininess or highlights c public readonly Vec3 $specular; ``` -### $emmisive +### $emissive -The emmisive color of the material. (marked as "Ke") +The emissive color of the material. (marked as "Ke") This property is often also used for illumination, self glow or light emission. ```php /* * @var \GL\Math\Vec3 */ -public readonly Vec3 $emmisive; +public readonly Vec3 $emissive; ``` ### $transmittance diff --git a/docs/API/Texture/Texture2D.md b/docs/API/Texture/Texture2D.md index d3d50ea4..9d66e519 100644 --- a/docs/API/Texture/Texture2D.md +++ b/docs/API/Texture/Texture2D.md @@ -37,12 +37,17 @@ Read more about the [`glTexImage2D`](/API/OpenGL/glTexImage2D.html) function to Loads a texture / image from a file on disk and returns a Texture2D object. ```php -static function fromDisk(string $path) : \GL\Texture\Texture2D +static function fromDisk(string $path, int $requestedChannelCount = 0, bool $flipVertically = true) : \GL\Texture\Texture2D ``` +This method automatically detects HDR files (.hdr) and loads them into a FloatBuffer. +All other supported formats (PNG, JPG, GIF, BMP, TGA, etc.) are loaded into a UByteBuffer. + arguments -: 1. `string` `$file` The path to the image file to load. +: 1. `string` `$path` The path to the image file to load. + 2. `int` `$requestedChannelCount` The number of channels to force. 0 means use the original channel count. + 3. `bool` `$flipVertically` Whether to flip the image vertically on load (default: true). returns @@ -52,7 +57,7 @@ returns ### `fromBuffer` -Loads a texture / image from a buffer and returns a Texture2D object. +Loads a texture / image from a UByteBuffer and returns a Texture2D object. ```php static function fromBuffer(int $width, int $height, \GL\Buffer\UByteBuffer $buffer, int $channels = \GL\Texture\Texture2D::CHANNEL_RGBA) : \GL\Texture\Texture2D @@ -60,6 +65,17 @@ static function fromBuffer(int $width, int $height, \GL\Buffer\UByteBuffer $buff The buffer is not copied, the Texture2D object will hold a reference to the buffer given. +arguments + +: 1. `int` `$width` The width of the image. + 2. `int` `$height` The height of the image. + 3. `\GL\Buffer\UByteBuffer` `$buffer` The buffer containing the image data. + 4. `int` `$channels` The number of channels in the image data. + +returns + +: `\GL\Texture\Texture2D` The created texture object. + --- ### `width` @@ -106,15 +122,18 @@ returns ### `buffer` -Returns a reference to the internal `UByteBuffer` instance of the current texture. +Returns a reference to the internal buffer instance of the current texture. ```php -function buffer() : \GL\Buffer\UByteBuffer +function buffer() : \GL\Buffer\UByteBuffer|\GL\Buffer\FloatBuffer ``` +For LDR images, this returns a `UByteBuffer`. For HDR images, this returns a `FloatBuffer`. +Use `isHDR()` to check which type of buffer is returned. + returns -: `\GL\UByteBuffer` The loaded image data. +: `\GL\Buffer\UByteBuffer|\GL\Buffer\FloatBuffer` The loaded image data. --- @@ -126,9 +145,11 @@ Writes the image data to a file on disk. (JPEG) function writeJPG(string $path, int $quality = 100) : void ``` +Note: This method only works with LDR textures. For HDR textures, use writeHDR(). + arguments -: 1. `string` `$file` The path to the file to write to. +: 1. `string` `$path` The path to the file to write to. 2. `int` `$quality` The quality of the image. (0 - 100) returns @@ -145,9 +166,11 @@ Writes the image data to a file on disk. (PNG) function writePNG(string $path) : void ``` +Note: This method only works with LDR textures. For HDR textures, use writeHDR(). + arguments -: 1. `string` `$file` The path to the file to write to. +: 1. `string` `$path` The path to the file to write to. --- @@ -159,9 +182,11 @@ Writes the image data to a file on disk. (BMP) function writeBMP(string $path) : void ``` +Note: This method only works with LDR textures. For HDR textures, use writeHDR(). + arguments -: 1. `string` `$file` The path to the file to write to. +: 1. `string` `$path` The path to the file to write to. --- @@ -173,9 +198,11 @@ Writes the image data to a file on disk. (TGA) function writeTGA(string $path) : void ``` +Note: This method only works with LDR textures. For HDR textures, use writeHDR(). + arguments -: 1. `string` `$file` The path to the file to write to. +: 1. `string` `$path` The path to the file to write to. --- diff --git a/generator/templates/phpglfw.stub.php.php b/generator/templates/phpglfw.stub.php.php index 14b5c95a..1339390d 100644 --- a/generator/templates/phpglfw.stub.php.php +++ b/generator/templates/phpglfw.stub.php.php @@ -166,16 +166,19 @@ class Texture2D { // public const CHANNEL_RGBA = 4; public static function fromDisk(string $path, int $requestedChannelCount = 0, bool $flipVertically = true) : Texture2D {} - public static function fromBuffer(int $width, int $height, \GL\Buffer\UByteBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} - public function buffer() : \GL\Buffer\UByteBuffer {} + public static function fromBuffer(int $width, int $height, \GL\Buffer\UByteBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} + public static function fromBufferHDR(int $width, int $height, \GL\Buffer\FloatBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} + public function buffer() : \GL\Buffer\UByteBuffer|\GL\Buffer\FloatBuffer {} public function width() : int {} public function height() : int {} public function channels() : int {} + public function isHDR() : bool {} public function writeJPG(string $path, int $quality = 100) : void {} public function writePNG(string $path) : void {} public function writeBMP(string $path) : void {} public function writeTGA(string $path) : void {} + public function writeHDR(string $path) : void {} } } @@ -305,6 +308,120 @@ public function dump() : string {} }; +namespace GL\Rendering +{ + class DrawCallAssembler + { + /** @var int */ + public const SORT_NONE = 0; + /** @var int */ + public const SORT_FRONT_TO_BACK = 1; + /** @var int */ + public const SORT_BACK_TO_FRONT = 2; + + /** @var int */ + public const PASS_OPAQUE = 0; + /** @var int */ + public const PASS_TRANSPARENT = 1; + /** @var int */ + public const PASS_DEPTH = 2; + /** @var int */ + public const PASS_USER = 3; + + /** @var int */ + public const FLAG_IGNORE_CULLING = 2; + /** @var int */ + public const FLAG_DISABLE_INSTANCING = 4; + /** @var int */ + public const FLAG_CUSTOM_SORT_KEY = 8; + + public readonly \GL\Buffer\UIntBuffer $commandBuffer; + public readonly \GL\Buffer\FloatBuffer $instanceTransformBuffer; + public readonly \GL\Buffer\UIntBuffer $instanceMetaBuffer; + public readonly \GL\Buffer\FloatBuffer $instancePayloadBuffer; + + public readonly int $commandStride; + public readonly int $transformStride; + public readonly int $instanceMetaStride; + + public function __construct( + int $initialMeshCapacity = 256, + int $initialInstanceCapacity = 2048, + int $initialCommandCapacity = 512 + ) {} + + public function setAutoInstancing(bool $enabled) : void {} + public function setSortMode(int $mode) : void {} + + public function setCameraData( + ?\GL\Math\Vec3 $cameraPosition = null, + ?\GL\Math\Mat4 $viewMatrix = null, + ?\GL\Math\Mat4 $projectionMatrix = null + ) : void {} + + public function setFrustumPlanes( + \GL\Math\Vec4 $left, + \GL\Math\Vec4 $right, + \GL\Math\Vec4 $bottom, + \GL\Math\Vec4 $top, + \GL\Math\Vec4 $near, + \GL\Math\Vec4 $far + ) : void {} + + public function registerMesh( + int $vao, + int $vertexOffset = 0, + int $vertexCount = 0, + int $indexOffset = 0, + int $indexCount = 0, + ?\GL\Math\Vec3 $aabbMin = null, + ?\GL\Math\Vec3 $aabbMax = null, + int $materialHint = 0, + int $primitive = 0x0004 + ) : int {} + + public function setMeshMaterial(int $meshHandle, int $materialId) : void {} + + public function setLodTable( + int $meshHandle, + \GL\Buffer\FloatBuffer $distanceThresholds, + \GL\Buffer\UIntBuffer $meshHandles + ) : void {} + + public function bindTransformBuffer(int $vao, int $offset = 1) : int {} + + public function bindPayloadData(\GL\Buffer\FloatBuffer $payloadBuffer, int $stride) : void {} + public function bindPayloadBuffer(int $vao, int $offset, int $stride = 0) : int {} + + public function clearInstances() : void {} + + public function submit( + int $meshHandle, + \GL\Math\Mat4 $transform, + int $materialId, + int $pass = 0, + int $programId = 0, + int $flags = 0, + float $sortBias = 0.0, + int $userId = 0 + ) : void {} + + public function build() : int {} + + public function execute(callable $drawCallback) : void {} + + public function commandCount() : int {} + + public function instanceCount() : int {} + + public function builtInstanceCount() : int {} + + public function clearMeshes() : void {} + + public function reset() : void {} + } +} + namespace GL\VectorGraphics { class VGColor { diff --git a/generator/templates/phpglfw_buffer.c.php b/generator/templates/phpglfw_buffer.c.php index fe41c84c..29aa3beb 100644 --- a/generator/templates/phpglfw_buffer.c.php +++ b/generator/templates/phpglfw_buffer.c.php @@ -145,9 +145,10 @@ return NULL; } - iterator = emalloc(sizeof(getIteratorObjectName(); ?>)); + iterator = ecalloc(1, sizeof(getIteratorObjectName(); ?>)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &getIteratorHandlersVarName(); ?>; diff --git a/generator/templates/phpglfw_functions.c.php b/generator/templates/phpglfw_functions.c.php index 4dbaee4f..1eb85a9a 100644 --- a/generator/templates/phpglfw_functions.c.php +++ b/generator/templates/phpglfw_functions.c.php @@ -134,21 +134,20 @@ class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES; zval *getObjectReadPropertyFunctionName(); ?>(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv) { getObjectStructName(); ?> *intern = getObjectStructPtrFromZObjPtrFunctionName(); ?>(object); - zval *retval = &EG(uninitialized_zval); if (intern->getObjectStructPointerVar(); ?>) { structAccessMembers as $i => $member) : ?> if (zend_string_equals_literal(member, "")) { - ZVAL_LONG(retval, intern->getObjectStructPointerVar(); ?>->); + ZVAL_LONG(rv, intern->getObjectStructPointerVar(); ?>->); - ZVAL_DOUBLE(retval, intern->getObjectStructPointerVar(); ?>->); + ZVAL_DOUBLE(rv, intern->getObjectStructPointerVar(); ?>->); - ZVAL_STRING(retval, intern->getObjectStructPointerVar(); ?>->); + ZVAL_STRING(rv, intern->getObjectStructPointerVar(); ?>->); - ZVAL_BOOL(retval, intern->getObjectStructPointerVar(); ?>->); + ZVAL_BOOL(rv, intern->getObjectStructPointerVar(); ?>->); - return retval; + return rv; } else { @@ -156,7 +155,7 @@ class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES; } } - return retval; + return &EG(uninitialized_zval); } diff --git a/generator/templates/stubs/phpglfw.php.php b/generator/templates/stubs/phpglfw.php.php index c0bb42c0..b9375dfa 100644 --- a/generator/templates/stubs/phpglfw.php.php +++ b/generator/templates/stubs/phpglfw.php.php @@ -874,6 +874,346 @@ public function dump() : string {} }; +namespace GL\Rendering +{ + /** + * The draw call assembler will help you to generate efficient draw call batches for your meshes. + * + * This particular workload is quite costly in user-land PHP, this class takes care of this common problem + * in a efficient manner specifically designed for PHP-GLFW. + * + * What it can do: + * - Batch meshes that share the same material. + * - Sort meshes based on distance to camera, what shader they use etc. + * - Generate optimal draw call lists to minimize state changes in OpenGL. + * - Auto instance meshes that are used multiple times. + * - Frustum cull meshes based on the camera. + * - LOD support based on distance to camera. + */ + class DrawCallAssembler + { + /** @var int */ + public const SORT_NONE = 0; + /** @var int */ + public const SORT_FRONT_TO_BACK = 1; + /** @var int */ + public const SORT_BACK_TO_FRONT = 2; + + /** @var int */ + public const PASS_OPAQUE = 0; + /** @var int */ + public const PASS_TRANSPARENT = 1; + /** @var int */ + public const PASS_DEPTH = 2; + /** @var int */ + public const PASS_USER = 3; + + /** @var int */ + public const FLAG_IGNORE_CULLING = 2; + /** @var int */ + public const FLAG_DISABLE_INSTANCING = 4; + /** @var int */ + public const FLAG_CUSTOM_SORT_KEY = 8; + + /** + * Packed draw commands represented as uint32 values. Filled after calling "build". + * + * Layout per command (uint32 stride = 8): + * 0 => mesh handle + * 1 => VAO id for the draw + * 2 => index offset (0 when drawing arrays) + * 3 => index or vertex count + * 4 => base vertex offset + * 5 => instance offset inside the transform buffer + * 6 => instance count (1 for non-instanced draws) + * 7 => material identifier used for batching + */ + public readonly \GL\Buffer\UIntBuffer $commandBuffer; + + /** + * Packed transform buffer storing Mat4 entries per instance. + * + * Note: This only contains visible instances, determined by the last "build" call. + */ + public readonly \GL\Buffer\FloatBuffer $instanceTransformBuffer; + + /** + * Packed per-instance metadata. + * Layout per instance (uint32 stride = 4): + * 0 => mesh handle + * 1 => material id + * 2 => custom user id (optional) + * 3 => draw flags snapshot + * + * Note: This only contains visible instances, determined by the last "build" call. + */ + public readonly \GL\Buffer\UIntBuffer $instanceMetaBuffer; + + /** + * Optional floating point payload buffer that can store arbitrary per-instance attributes + * (colors, skinning weights, etc.). + * + * Layout per instance (float stride = N): + */ + public readonly \GL\Buffer\FloatBuffer $instancePayloadBuffer; + + /** + * The command buffer stride (in uint32 components) of one draw command entry. + */ + public readonly int $commandStride; + + /** + * The instance transform buffer stride (in float components) of one transform entry. + */ + public readonly int $transformStride; + + /** + * The instance metadata buffer stride (in uint32 components) of one metadata entry. + */ + public readonly int $instanceMetaStride; + + /** + * Create a new assembler instance. + * + * @param int $initialMeshCapacity Number of mesh slots preallocated in the registry. + * @param int $initialInstanceCapacity Number of instance slots backed by the buffers. + * @param int $initialCommandCapacity Number of draw command slots. + */ + public function __construct( + int $initialMeshCapacity = 256, + int $initialInstanceCapacity = 2048, + int $initialCommandCapacity = 512 + ) {} + + /** + * Enable or disable automatic instancing for meshes that are submitted multiple times per frame. + */ + public function setAutoInstancing(bool $enabled) : void {} + + /** + * Select the ordering applied to opaque draw calls. + */ + public function setSortMode(int $mode) : void {} + + /** + * Update the camera state used for sorting, frustum culling and LOD evaluation. + * + * @param ?\GL\Math\Vec3 $cameraPosition Camera position used for LOD selection. + * @param ?\GL\Math\Mat4 $viewMatrix View matrix used for frustum generation. + * @param ?\GL\Math\Mat4 $projectionMatrix Projection matrix used for frustum generation. + */ + public function setCameraData( + ?\GL\Math\Vec3 $cameraPosition = null, + ?\GL\Math\Mat4 $viewMatrix = null, + ?\GL\Math\Mat4 $projectionMatrix = null + ) : void {} + + /** + * Manually configure the frustum to use for culling tasks. + */ + public function setFrustumPlanes( + \GL\Math\Vec4 $left, + \GL\Math\Vec4 $right, + \GL\Math\Vec4 $bottom, + \GL\Math\Vec4 $top, + \GL\Math\Vec4 $near, + \GL\Math\Vec4 $far + ) : void {} + + /** + * Register a mesh in the assembler's registry and receive a numeric handle. + * + * You need to register your meshes so you can submit instances of them later. + * + * You are the owner of any passed references, the assembler will not modify or delete them. + * + * @param int $vao OpenGL vertex array object handle that encapsulates buffers and layout. + * @param int $vertexOffset Starting vertex inside the bound VBO (0 based). + * @param int $vertexCount Number of vertices to draw when the mesh is rendered without indices. + * @param int $indexOffset Starting index inside the EBO (set to 0 to draw arrays instead). + * @param int $indexCount Number of indices used for indexed rendering (0 falls back to $vertexCount arrays). + * @param ?\GL\Math\Vec3 $aabbMin Optional minimum point of the mesh bounds (for frustum culling). + * @param ?\GL\Math\Vec3 $aabbMax Optional maximum point of the mesh bounds. + * @param int $materialHint Optional default material id that is applied when submit() omits a material. + * @param int $primitive GL primitive value (defaults to GL_TRIANGLES = 0x0004). + * + * @return int Numeric handle for reference + */ + public function registerMesh( + int $vao, + int $vertexOffset = 0, + int $vertexCount = 0, + int $indexOffset = 0, + int $indexCount = 0, + ?\GL\Math\Vec3 $aabbMin = null, + ?\GL\Math\Vec3 $aabbMax = null, + int $materialHint = 0, + int $primitive = 0x0004 + ) : int {} + + /** + * Update or override the default material identifier attached to a mesh. + */ + public function setMeshMaterial(int $meshHandle, int $materialId) : void {} + + /** + * Attach LOD alternatives to a mesh. + * + * @param int $meshHandle Base mesh handle (the one that should be replaced) returned by {@see registerMesh}. + * @param \GL\Buffer\FloatBuffer $distanceThresholds Distances in ascending order. + * @param \GL\Buffer\UIntBuffer $meshHandles Mesh handles for each LOD level. + */ + public function setLodTable( + int $meshHandle, + \GL\Buffer\FloatBuffer $distanceThresholds, + \GL\Buffer\UIntBuffer $meshHandles + ) : void {} + + /** + * Binds and sets up the transform buffer for instanced rendering. + * + * This method: + * - creates or reuses an internal VBO for transform data + * - sets up vertex attribute pointers for the transform matrix (4x Vec4 attributes) + * - returns the next available attribute location offset + * + * ```php + * $nextOffset = $assembler->bindTransformBuffer($vao, 2); + * // transform matrix now uses attributes 2, 3, 4, 5 + * // next custom attribute can use location $nextOffset (6 in this case) + * ``` + * + * @param int $vao The vertex array object to configure. + * @param int $offset The starting vertex attribute location (default: 2). + * @return int The next available attribute location after the transform matrix. + */ + public function bindTransformBuffer(int $vao, int $offset = 1) : int {} + + /** + * Binds payload data for per-instance custom attributes. + * + * Don't forget to call {@see bindPayloadBuffer()} to set up the attributes after calling {@see build()}. + * + * This method stores a reference to a FloatBuffer containing custom per-instance data + * (colors, skinning weights, etc.) and specifies how many floats comprise each instance's payload. + * The data will be available in the instancePayloadBuffer after calling build(). + * + * @param \GL\Buffer\FloatBuffer $payloadBuffer Buffer containing per-instance payload data. + * @param int $stride Number of floats per instance in the payload buffer. + */ + public function bindPayloadData(\GL\Buffer\FloatBuffer $payloadBuffer, int $stride) : void {} + + /** + * Binds and sets up the payload buffer for instanced rendering. + * + * This method sets up vertex attribute pointers for the custom payload data buffer. + * Must be called after bindPayloadData() and build() to properly configure the payload attributes. + * + * @param int $vao The vertex array object to configure. + * @param int $offset The starting vertex attribute location. + * @param int $stride Number of floats per payload entry (0 uses bound payload stride). + * @return int The next available attribute location after the payload attributes. + */ + public function bindPayloadBuffer(int $vao, int $offset, int $stride = 0) : int {} + + /** + * Remove all recorded instances without touching registered meshes. + */ + public function clearInstances() : void {} + + /** + * Record a single mesh instance. + * + * @param int $meshHandle Mesh handle from {@see registerMesh} + * @param \GL\Math\Mat4 $transform World transform + * @param int $materialId Material identifier + * @param int $pass Render pass identifier (use PASS_* constants) + * @param int $programId Optional program/shader identifier used for batching and sorting + * @param int $flags Optional state overrides (FLAG_* bitmask) + * @param float $sortBias Extra distance bias applied when sorting draws + * @param int $userId Optional user supplied id mirrored into the instance metadata buffer + */ + public function submit( + int $meshHandle, + \GL\Math\Mat4 $transform, + int $materialId, + int $pass = 0, + int $programId = 0, + int $flags = 0, + float $sortBias = 0.0, + int $userId = 0 + ) : void {} + + /** + * Build the final draw list. Returns the number of commands generated for this frame. + * + * Will fill the instance buffers: + * - {@see commandBuffer} + * - {@see instanceTransformBuffer} + * - {@see instanceMetaBuffer} + * - {@see instancePayloadBuffer} + * + * Use this if you want to handle and execute the draw calls **manually**. + * Otherwise you can use {@see execute()} to perform the draw calls directly. + */ + public function build() : int {} + + /** + * Executes the draw calls by issuing OpenGL commands. + * + * The given callback will be called before each draw call allowing you to set up materials and other GL state. + * Before calling this method, you must call {@see bindTransformBuffer()} to set up the transform data. + * + * The callback receives the following parameters: + * - int $meshHandle: The mesh handle for this draw call + * - int $materialId: The material identifier for this draw call + * - int $instanceOffset: The offset into the instance transform buffer + * - int $instanceCount: The number of instances to draw + * - int $flags: The draw flags (see FLAG_* constants) + * + * Example usage: + * ```php + * // first bind the transform buffer to set up instancing + * $assembler->bindTransformBuffer($vao, $instanceTransformVBO); + * // ... setup draw loop ... + * // then execute the draw calls + * $assembler->execute(function(int $meshHandle, int $materialId, int $instanceOffset, int $instanceCount, int $flags) { + * // use shaders, bind textures, set uniforms etc.. + * // note: the VAO is bound by the assembler internally, and the draw calls are issued automatically. + * }); + * ``` + * + * @param callable(int,int,int,int,int):void $drawCallback Before draw callback function. + */ + public function execute(callable $drawCallback) : void {} + + /** + * Number of draw commands generated by the previous {@see build()} call. + */ + public function commandCount() : int {} + + /** + * Number of instances submitted so far for the current frame. + */ + public function instanceCount() : int {} + + /** + * Number of instances that participated in the last {@see build()} call. + */ + public function builtInstanceCount() : int {} + + /** + * Completely remove all registered meshes and release buffers. + */ + public function clearMeshes() : void {} + + /** + * Reset both registry and frame state. + */ + public function reset() : void {} + } +}; + namespace GL\Geometry\ObjFileParser { /** @@ -951,12 +1291,12 @@ class Material public readonly \GL\Math\Vec3 $specular; /** - * The emmisive color of the material + * The emissive color of the material * This property is often also used for illumination, self glow or light emission. * - * @var \GL\Math\Vec3 $emmisive + * @var \GL\Math\Vec3 $emissive */ - public readonly \GL\Math\Vec3 $emmisive; + public readonly \GL\Math\Vec3 $emissive; /** * The transmittance of the material @@ -1465,7 +1805,8 @@ public function getPaletteColor(int $colorIndex) : ?\GL\Math\Vec4 {} { /** * The Texture2D class is part of the PHP-GLFW OpenGL extension. - * It loads images / textures from common formats like PNG, JPG, GIF, BMP, TGA etc. and converts the raw bitmap to a `GL\UByteBuffer` instance. + * It loads images / textures from common formats like PNG, JPG, GIF, BMP, TGA, HDR etc. and converts the raw bitmap to a buffer instance. + * For LDR (Low Dynamic Range) images, a `GL\Buffer\UByteBuffer` is used. For HDR (High Dynamic Range) images, a `GL\Buffer\FloatBuffer` is used. */ class Texture2D { @@ -1478,25 +1819,52 @@ class Texture2D /** * Loads a texture / image from a file on disk and returns a Texture2D object. + * + * This method automatically detects HDR files (.hdr) and loads them into a FloatBuffer. + * All other supported formats (PNG, JPG, GIF, BMP, TGA, etc.) are loaded into a UByteBuffer. * - * @param string $file The path to the image file to load. + * @param string $path The path to the image file to load. + * @param int $requestedChannelCount The number of channels to force. 0 means use the original channel count. + * @param bool $flipVertically Whether to flip the image vertically on load (default: true). * @return \GL\Texture\Texture2D The loaded texture object. */ - public static function fromDisk(string $path) : Texture2D {} + public static function fromDisk(string $path, int $requestedChannelCount = 0, bool $flipVertically = true) : Texture2D {} /** - * Loads a texture / image from a buffer and returns a Texture2D object. + * Loads a texture / image from a UByteBuffer and returns a Texture2D object. * * The buffer is not copied, the Texture2D object will hold a reference to the buffer given. + * + * @param int $width The width of the image. + * @param int $height The height of the image. + * @param \GL\Buffer\UByteBuffer $buffer The buffer containing the image data. + * @param int $channels The number of channels in the image data. + * @return \GL\Texture\Texture2D The created texture object. */ public static function fromBuffer(int $width, int $height, \GL\Buffer\UByteBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} /** - * Returns a reference to the internal `UByteBuffer` instance of the current texture. + * Loads a HDR texture from a FloatBuffer and returns a Texture2D object. + * + * The buffer is not copied, the Texture2D object will hold a reference to the buffer given. + * + * @param int $width The width of the image. + * @param int $height The height of the image. + * @param \GL\Buffer\FloatBuffer $buffer The buffer containing the HDR image data. + * @param int $channels The number of channels in the image data. + * @return \GL\Texture\Texture2D The created HDR texture object. + */ + public static function fromBufferHDR(int $width, int $height, \GL\Buffer\FloatBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} + + /** + * Returns a reference to the internal buffer instance of the current texture. + * + * For LDR images, this returns a `UByteBuffer`. For HDR images, this returns a `FloatBuffer`. + * Use `isHDR()` to check which type of buffer is returned. * - * @return \GL\UByteBuffer The loaded image data. + * @return \GL\Buffer\UByteBuffer|\GL\Buffer\FloatBuffer The loaded image data. */ - public function buffer() : \GL\Buffer\UByteBuffer {} + public function buffer() : \GL\Buffer\UByteBuffer|\GL\Buffer\FloatBuffer {} /** * Returns the width of the image. @@ -1519,10 +1887,22 @@ public function height() : int {} */ public function channels() : int {} + /** + * Returns whether the texture contains HDR (High Dynamic Range) data. + * + * When true, the buffer() method returns a FloatBuffer. + * When false, the buffer() method returns a UByteBuffer. + * + * @return bool + */ + public function isHDR() : bool {} + /** * Writes the image data to a file on disk. (JPEG) * - * @param string $file The path to the file to write to. + * Note: This method only works with LDR textures. For HDR textures, use writeHDR(). + * + * @param string $path The path to the file to write to. * @param int $quality The quality of the image. (0 - 100) * * @return void @@ -1531,24 +1911,39 @@ public function writeJPG(string $path, int $quality = 100) : void {} /** * Writes the image data to a file on disk. (PNG) + * + * Note: This method only works with LDR textures. For HDR textures, use writeHDR(). * - * @param string $file The path to the file to write to. + * @param string $path The path to the file to write to. */ public function writePNG(string $path) : void {} /** * Writes the image data to a file on disk. (BMP) + * + * Note: This method only works with LDR textures. For HDR textures, use writeHDR(). * - * @param string $file The path to the file to write to. + * @param string $path The path to the file to write to. */ public function writeBMP(string $path) : void {} /** * Writes the image data to a file on disk. (TGA) + * + * Note: This method only works with LDR textures. For HDR textures, use writeHDR(). * - * @param string $file The path to the file to write to. + * @param string $path The path to the file to write to. */ public function writeTGA(string $path) : void {} + + /** + * Writes the HDR image data to a file on disk. (.hdr format) + * + * Note: This method only works with HDR textures. For LDR textures, use writePNG(), writeJPG(), etc. + * + * @param string $path The path to the file to write to. + */ + public function writeHDR(string $path) : void {} } } diff --git a/ogt_vox_c_wrapper.h b/include/ogt_vox_c_wrapper.h similarity index 100% rename from ogt_vox_c_wrapper.h rename to include/ogt_vox_c_wrapper.h diff --git a/phpglfw.c b/phpglfw.c index c2a4d6a9..e9458ae7 100644 --- a/phpglfw.c +++ b/phpglfw.c @@ -40,6 +40,7 @@ #include "phpglfw_voxparser.h" #include "phpglfw_vg.h" #include "phpglfw_audio.h" +#include "phpglfw_drawcall_assembler.h" ZEND_DECLARE_MODULE_GLOBALS(glfw) @@ -108,6 +109,9 @@ PHP_MINIT_FUNCTION(glfw) // audio module phpglfw_register_audio_module(INIT_FUNC_ARGS_PASSTHRU); + // drawcall assembler module + phpglfw_register_drawcall_assembler_module(INIT_FUNC_ARGS_PASSTHRU); + return SUCCESS; } diff --git a/phpglfw.stub.php b/phpglfw.stub.php index 65eb7a96..197edfea 100644 --- a/phpglfw.stub.php +++ b/phpglfw.stub.php @@ -167,16 +167,19 @@ class Texture2D { // public const CHANNEL_RGBA = 4; public static function fromDisk(string $path, int $requestedChannelCount = 0, bool $flipVertically = true) : Texture2D {} - public static function fromBuffer(int $width, int $height, \GL\Buffer\UByteBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} - public function buffer() : \GL\Buffer\UByteBuffer {} + public static function fromBuffer(int $width, int $height, \GL\Buffer\UByteBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} + public static function fromBufferHDR(int $width, int $height, \GL\Buffer\FloatBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} + public function buffer() : \GL\Buffer\UByteBuffer|\GL\Buffer\FloatBuffer {} public function width() : int {} public function height() : int {} public function channels() : int {} + public function isHDR() : bool {} public function writeJPG(string $path, int $quality = 100) : void {} public function writePNG(string $path) : void {} public function writeBMP(string $path) : void {} public function writeTGA(string $path) : void {} + public function writeHDR(string $path) : void {} } } @@ -460,6 +463,120 @@ public function dump() : string {} } }; +namespace GL\Rendering +{ + class DrawCallAssembler + { + /** @var int */ + public const SORT_NONE = 0; + /** @var int */ + public const SORT_FRONT_TO_BACK = 1; + /** @var int */ + public const SORT_BACK_TO_FRONT = 2; + + /** @var int */ + public const PASS_OPAQUE = 0; + /** @var int */ + public const PASS_TRANSPARENT = 1; + /** @var int */ + public const PASS_DEPTH = 2; + /** @var int */ + public const PASS_USER = 3; + + /** @var int */ + public const FLAG_IGNORE_CULLING = 2; + /** @var int */ + public const FLAG_DISABLE_INSTANCING = 4; + /** @var int */ + public const FLAG_CUSTOM_SORT_KEY = 8; + + public readonly \GL\Buffer\UIntBuffer $commandBuffer; + public readonly \GL\Buffer\FloatBuffer $instanceTransformBuffer; + public readonly \GL\Buffer\UIntBuffer $instanceMetaBuffer; + public readonly \GL\Buffer\FloatBuffer $instancePayloadBuffer; + + public readonly int $commandStride; + public readonly int $transformStride; + public readonly int $instanceMetaStride; + + public function __construct( + int $initialMeshCapacity = 256, + int $initialInstanceCapacity = 2048, + int $initialCommandCapacity = 512 + ) {} + + public function setAutoInstancing(bool $enabled) : void {} + public function setSortMode(int $mode) : void {} + + public function setCameraData( + ?\GL\Math\Vec3 $cameraPosition = null, + ?\GL\Math\Mat4 $viewMatrix = null, + ?\GL\Math\Mat4 $projectionMatrix = null + ) : void {} + + public function setFrustumPlanes( + \GL\Math\Vec4 $left, + \GL\Math\Vec4 $right, + \GL\Math\Vec4 $bottom, + \GL\Math\Vec4 $top, + \GL\Math\Vec4 $near, + \GL\Math\Vec4 $far + ) : void {} + + public function registerMesh( + int $vao, + int $vertexOffset = 0, + int $vertexCount = 0, + int $indexOffset = 0, + int $indexCount = 0, + ?\GL\Math\Vec3 $aabbMin = null, + ?\GL\Math\Vec3 $aabbMax = null, + int $materialHint = 0, + int $primitive = 0x0004 + ) : int {} + + public function setMeshMaterial(int $meshHandle, int $materialId) : void {} + + public function setLodTable( + int $meshHandle, + \GL\Buffer\FloatBuffer $distanceThresholds, + \GL\Buffer\UIntBuffer $meshHandles + ) : void {} + + public function bindTransformBuffer(int $vao, int $offset = 1) : int {} + + public function bindPayloadData(\GL\Buffer\FloatBuffer $payloadBuffer, int $stride) : void {} + public function bindPayloadBuffer(int $vao, int $offset, int $stride = 0) : int {} + + public function clearInstances() : void {} + + public function submit( + int $meshHandle, + \GL\Math\Mat4 $transform, + int $materialId, + int $pass = 0, + int $programId = 0, + int $flags = 0, + float $sortBias = 0.0, + int $userId = 0 + ) : void {} + + public function build() : int {} + + public function execute(callable $drawCallback) : void {} + + public function commandCount() : int {} + + public function instanceCount() : int {} + + public function builtInstanceCount() : int {} + + public function clearMeshes() : void {} + + public function reset() : void {} + } +} + namespace GL\VectorGraphics { class VGColor { diff --git a/phpglfw_arginfo.h b/phpglfw_arginfo.h index 8df8de44..aa16d199 100644 --- a/phpglfw_arginfo.h +++ b/phpglfw_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 802b724983d817083ad91edff17f154a94a071be */ + * Stub hash: 0eda5f52dd751acb54f19c93baeb11164c8bd4d5 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_glCullFace, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, mode, IS_LONG, 0) @@ -2383,7 +2383,15 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_GL_Texture_Texture2D_fromBu ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, channels, IS_LONG, 0, "GL\\Texture\\Texture2D::CHANNEL_RGBA") ZEND_END_ARG_INFO() -#define arginfo_class_GL_Texture_Texture2D_buffer arginfo_class_GL_Geometry_VoxFileParser_Palette_getBuffer +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_GL_Texture_Texture2D_fromBufferHDR, 0, 3, GL\\Texture\\Texture2D, 0) + ZEND_ARG_TYPE_INFO(0, width, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, height, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, buffer, GL\\Buffer\\FloatBuffer, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, channels, IS_LONG, 0, "GL\\Texture\\Texture2D::CHANNEL_RGBA") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_GL_Texture_Texture2D_buffer, 0, 0, GL\\Buffer\\\125ByteBuffer|GL\\Buffer\\FloatBuffer, 0) +ZEND_END_ARG_INFO() #define arginfo_class_GL_Texture_Texture2D_width arginfo_glGetError @@ -2391,6 +2399,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_GL_Texture_Texture2D_channels arginfo_glGetError +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Texture_Texture2D_isHDR, 0, 0, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Texture_Texture2D_writeJPG, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, quality, IS_LONG, 0, "100") @@ -2404,6 +2415,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_GL_Texture_Texture2D_writeTGA arginfo_class_GL_Texture_Texture2D_writePNG +#define arginfo_class_GL_Texture_Texture2D_writeHDR arginfo_class_GL_Texture_Texture2D_writePNG + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Noise_perlin, 0, 3, IS_DOUBLE, 0) ZEND_ARG_TYPE_INFO(0, x, IS_DOUBLE, 0) ZEND_ARG_TYPE_INFO(0, y, IS_DOUBLE, 0) @@ -3061,6 +3074,101 @@ ZEND_END_ARG_INFO() #define arginfo_class_GL_Buffer_UByteBuffer_dump arginfo_glfwGetVersionString +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler___construct, 0, 0, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, initialMeshCapacity, IS_LONG, 0, "256") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, initialInstanceCapacity, IS_LONG, 0, "2048") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, initialCommandCapacity, IS_LONG, 0, "512") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_setAutoInstancing, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, enabled, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_GL_Rendering_DrawCallAssembler_setSortMode arginfo_glCullFace + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_setCameraData, 0, 0, IS_VOID, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, cameraPosition, GL\\Math\\Vec3, 1, "null") + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, viewMatrix, GL\\Math\\Mat4, 1, "null") + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, projectionMatrix, GL\\Math\\Mat4, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_setFrustumPlanes, 0, 6, IS_VOID, 0) + ZEND_ARG_OBJ_INFO(0, left, GL\\Math\\Vec4, 0) + ZEND_ARG_OBJ_INFO(0, right, GL\\Math\\Vec4, 0) + ZEND_ARG_OBJ_INFO(0, bottom, GL\\Math\\Vec4, 0) + ZEND_ARG_OBJ_INFO(0, top, GL\\Math\\Vec4, 0) + ZEND_ARG_OBJ_INFO(0, near, GL\\Math\\Vec4, 0) + ZEND_ARG_OBJ_INFO(0, far, GL\\Math\\Vec4, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_registerMesh, 0, 1, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, vao, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, vertexOffset, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, vertexCount, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, indexOffset, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, indexCount, IS_LONG, 0, "0") + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, aabbMin, GL\\Math\\Vec3, 1, "null") + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, aabbMax, GL\\Math\\Vec3, 1, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, materialHint, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, primitive, IS_LONG, 0, "0x4") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_setMeshMaterial, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, meshHandle, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, materialId, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_setLodTable, 0, 3, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, meshHandle, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, distanceThresholds, GL\\Buffer\\FloatBuffer, 0) + ZEND_ARG_OBJ_INFO(0, meshHandles, GL\\Buffer\\\125IntBuffer, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_bindTransformBuffer, 0, 1, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, vao, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "1") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_bindPayloadData, 0, 2, IS_VOID, 0) + ZEND_ARG_OBJ_INFO(0, payloadBuffer, GL\\Buffer\\FloatBuffer, 0) + ZEND_ARG_TYPE_INFO(0, stride, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_bindPayloadBuffer, 0, 2, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, vao, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, stride, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +#define arginfo_class_GL_Rendering_DrawCallAssembler_clearInstances arginfo_glFinish + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_submit, 0, 3, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, meshHandle, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, transform, GL\\Math\\Mat4, 0) + ZEND_ARG_TYPE_INFO(0, materialId, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, pass, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, programId, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, sortBias, IS_DOUBLE, 0, "0.0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, userId, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +#define arginfo_class_GL_Rendering_DrawCallAssembler_build arginfo_glGetError + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Rendering_DrawCallAssembler_execute, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, drawCallback, IS_CALLABLE, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_GL_Rendering_DrawCallAssembler_commandCount arginfo_glGetError + +#define arginfo_class_GL_Rendering_DrawCallAssembler_instanceCount arginfo_glGetError + +#define arginfo_class_GL_Rendering_DrawCallAssembler_builtInstanceCount arginfo_glGetError + +#define arginfo_class_GL_Rendering_DrawCallAssembler_clearMeshes arginfo_glFinish + +#define arginfo_class_GL_Rendering_DrawCallAssembler_reset arginfo_glFinish + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_GL_VectorGraphics_VGColor_rgb, 0, 3, GL\\VectorGraphics\\VGColor, 0) ZEND_ARG_TYPE_INFO(0, r, IS_DOUBLE, 0) ZEND_ARG_TYPE_INFO(0, g, IS_DOUBLE, 0) @@ -3608,8 +3716,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Audio_Sound_setStopMs, ZEND_ARG_TYPE_INFO(0, stop, IS_LONG, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_GL_Audio_Sound_isPlaying, 0, 0, _IS_BOOL, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_GL_Audio_Sound_isPlaying arginfo_class_GL_Texture_Texture2D_isHDR #define arginfo_class_GL_Audio_Sound_getCursor arginfo_glfwGetTime @@ -4263,14 +4370,17 @@ ZEND_METHOD(GL_Geometry_VoxFileParser_Layer, __construct); ZEND_METHOD(GL_Geometry_VoxFileParser_Group, __construct); ZEND_METHOD(GL_Texture_Texture2D, fromDisk); ZEND_METHOD(GL_Texture_Texture2D, fromBuffer); +ZEND_METHOD(GL_Texture_Texture2D, fromBufferHDR); ZEND_METHOD(GL_Texture_Texture2D, buffer); ZEND_METHOD(GL_Texture_Texture2D, width); ZEND_METHOD(GL_Texture_Texture2D, height); ZEND_METHOD(GL_Texture_Texture2D, channels); +ZEND_METHOD(GL_Texture_Texture2D, isHDR); ZEND_METHOD(GL_Texture_Texture2D, writeJPG); ZEND_METHOD(GL_Texture_Texture2D, writePNG); ZEND_METHOD(GL_Texture_Texture2D, writeBMP); ZEND_METHOD(GL_Texture_Texture2D, writeTGA); +ZEND_METHOD(GL_Texture_Texture2D, writeHDR); ZEND_METHOD(GL_Noise, perlin); ZEND_METHOD(GL_Noise, ridge); ZEND_METHOD(GL_Noise, fbm); @@ -4475,6 +4585,26 @@ ZEND_METHOD(GL_Buffer_UByteBuffer, size); ZEND_METHOD(GL_Buffer_UByteBuffer, capacity); ZEND_METHOD(GL_Buffer_UByteBuffer, reserve); ZEND_METHOD(GL_Buffer_UByteBuffer, dump); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, __construct); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, setAutoInstancing); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, setSortMode); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, setCameraData); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, setFrustumPlanes); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, registerMesh); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, setMeshMaterial); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, setLodTable); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, bindTransformBuffer); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, bindPayloadData); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, bindPayloadBuffer); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, clearInstances); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, submit); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, build); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, execute); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, commandCount); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, instanceCount); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, builtInstanceCount); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, clearMeshes); +ZEND_METHOD(GL_Rendering_DrawCallAssembler, reset); ZEND_METHOD(GL_VectorGraphics_VGColor, rgb); ZEND_METHOD(GL_VectorGraphics_VGColor, rgba); ZEND_METHOD(GL_VectorGraphics_VGColor, hsl); @@ -5296,14 +5426,17 @@ static const zend_function_entry class_GL_Geometry_VoxFileParser_Group_methods[] static const zend_function_entry class_GL_Texture_Texture2D_methods[] = { ZEND_ME(GL_Texture_Texture2D, fromDisk, arginfo_class_GL_Texture_Texture2D_fromDisk, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(GL_Texture_Texture2D, fromBuffer, arginfo_class_GL_Texture_Texture2D_fromBuffer, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(GL_Texture_Texture2D, fromBufferHDR, arginfo_class_GL_Texture_Texture2D_fromBufferHDR, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(GL_Texture_Texture2D, buffer, arginfo_class_GL_Texture_Texture2D_buffer, ZEND_ACC_PUBLIC) ZEND_ME(GL_Texture_Texture2D, width, arginfo_class_GL_Texture_Texture2D_width, ZEND_ACC_PUBLIC) ZEND_ME(GL_Texture_Texture2D, height, arginfo_class_GL_Texture_Texture2D_height, ZEND_ACC_PUBLIC) ZEND_ME(GL_Texture_Texture2D, channels, arginfo_class_GL_Texture_Texture2D_channels, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Texture_Texture2D, isHDR, arginfo_class_GL_Texture_Texture2D_isHDR, ZEND_ACC_PUBLIC) ZEND_ME(GL_Texture_Texture2D, writeJPG, arginfo_class_GL_Texture_Texture2D_writeJPG, ZEND_ACC_PUBLIC) ZEND_ME(GL_Texture_Texture2D, writePNG, arginfo_class_GL_Texture_Texture2D_writePNG, ZEND_ACC_PUBLIC) ZEND_ME(GL_Texture_Texture2D, writeBMP, arginfo_class_GL_Texture_Texture2D_writeBMP, ZEND_ACC_PUBLIC) ZEND_ME(GL_Texture_Texture2D, writeTGA, arginfo_class_GL_Texture_Texture2D_writeTGA, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Texture_Texture2D, writeHDR, arginfo_class_GL_Texture_Texture2D_writeHDR, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -5602,6 +5735,31 @@ static const zend_function_entry class_GL_Buffer_UByteBuffer_methods[] = { }; +static const zend_function_entry class_GL_Rendering_DrawCallAssembler_methods[] = { + ZEND_ME(GL_Rendering_DrawCallAssembler, __construct, arginfo_class_GL_Rendering_DrawCallAssembler___construct, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, setAutoInstancing, arginfo_class_GL_Rendering_DrawCallAssembler_setAutoInstancing, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, setSortMode, arginfo_class_GL_Rendering_DrawCallAssembler_setSortMode, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, setCameraData, arginfo_class_GL_Rendering_DrawCallAssembler_setCameraData, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, setFrustumPlanes, arginfo_class_GL_Rendering_DrawCallAssembler_setFrustumPlanes, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, registerMesh, arginfo_class_GL_Rendering_DrawCallAssembler_registerMesh, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, setMeshMaterial, arginfo_class_GL_Rendering_DrawCallAssembler_setMeshMaterial, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, setLodTable, arginfo_class_GL_Rendering_DrawCallAssembler_setLodTable, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, bindTransformBuffer, arginfo_class_GL_Rendering_DrawCallAssembler_bindTransformBuffer, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, bindPayloadData, arginfo_class_GL_Rendering_DrawCallAssembler_bindPayloadData, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, bindPayloadBuffer, arginfo_class_GL_Rendering_DrawCallAssembler_bindPayloadBuffer, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, clearInstances, arginfo_class_GL_Rendering_DrawCallAssembler_clearInstances, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, submit, arginfo_class_GL_Rendering_DrawCallAssembler_submit, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, build, arginfo_class_GL_Rendering_DrawCallAssembler_build, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, execute, arginfo_class_GL_Rendering_DrawCallAssembler_execute, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, commandCount, arginfo_class_GL_Rendering_DrawCallAssembler_commandCount, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, instanceCount, arginfo_class_GL_Rendering_DrawCallAssembler_instanceCount, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, builtInstanceCount, arginfo_class_GL_Rendering_DrawCallAssembler_builtInstanceCount, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, clearMeshes, arginfo_class_GL_Rendering_DrawCallAssembler_clearMeshes, ZEND_ACC_PUBLIC) + ZEND_ME(GL_Rendering_DrawCallAssembler, reset, arginfo_class_GL_Rendering_DrawCallAssembler_reset, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + static const zend_function_entry class_GL_VectorGraphics_VGColor_methods[] = { ZEND_ME(GL_VectorGraphics_VGColor, rgb, arginfo_class_GL_VectorGraphics_VGColor_rgb, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(GL_VectorGraphics_VGColor, rgba, arginfo_class_GL_VectorGraphics_VGColor_rgba, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) @@ -6429,6 +6587,122 @@ static zend_class_entry *register_class_GL_Buffer_UByteBuffer(zend_class_entry * return class_entry; } +static zend_class_entry *register_class_GL_Rendering_DrawCallAssembler(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "GL\\Rendering", "DrawCallAssembler", class_GL_Rendering_DrawCallAssembler_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + + zval const_SORT_NONE_value; + ZVAL_LONG(&const_SORT_NONE_value, 0); + zend_string *const_SORT_NONE_name = zend_string_init_interned("SORT_NONE", sizeof("SORT_NONE") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_SORT_NONE_name, &const_SORT_NONE_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_SORT_NONE_name); + + zval const_SORT_FRONT_TO_BACK_value; + ZVAL_LONG(&const_SORT_FRONT_TO_BACK_value, 1); + zend_string *const_SORT_FRONT_TO_BACK_name = zend_string_init_interned("SORT_FRONT_TO_BACK", sizeof("SORT_FRONT_TO_BACK") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_SORT_FRONT_TO_BACK_name, &const_SORT_FRONT_TO_BACK_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_SORT_FRONT_TO_BACK_name); + + zval const_SORT_BACK_TO_FRONT_value; + ZVAL_LONG(&const_SORT_BACK_TO_FRONT_value, 2); + zend_string *const_SORT_BACK_TO_FRONT_name = zend_string_init_interned("SORT_BACK_TO_FRONT", sizeof("SORT_BACK_TO_FRONT") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_SORT_BACK_TO_FRONT_name, &const_SORT_BACK_TO_FRONT_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_SORT_BACK_TO_FRONT_name); + + zval const_PASS_OPAQUE_value; + ZVAL_LONG(&const_PASS_OPAQUE_value, 0); + zend_string *const_PASS_OPAQUE_name = zend_string_init_interned("PASS_OPAQUE", sizeof("PASS_OPAQUE") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_PASS_OPAQUE_name, &const_PASS_OPAQUE_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_PASS_OPAQUE_name); + + zval const_PASS_TRANSPARENT_value; + ZVAL_LONG(&const_PASS_TRANSPARENT_value, 1); + zend_string *const_PASS_TRANSPARENT_name = zend_string_init_interned("PASS_TRANSPARENT", sizeof("PASS_TRANSPARENT") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_PASS_TRANSPARENT_name, &const_PASS_TRANSPARENT_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_PASS_TRANSPARENT_name); + + zval const_PASS_DEPTH_value; + ZVAL_LONG(&const_PASS_DEPTH_value, 2); + zend_string *const_PASS_DEPTH_name = zend_string_init_interned("PASS_DEPTH", sizeof("PASS_DEPTH") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_PASS_DEPTH_name, &const_PASS_DEPTH_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_PASS_DEPTH_name); + + zval const_PASS_USER_value; + ZVAL_LONG(&const_PASS_USER_value, 3); + zend_string *const_PASS_USER_name = zend_string_init_interned("PASS_USER", sizeof("PASS_USER") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_PASS_USER_name, &const_PASS_USER_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_PASS_USER_name); + + zval const_FLAG_IGNORE_CULLING_value; + ZVAL_LONG(&const_FLAG_IGNORE_CULLING_value, 2); + zend_string *const_FLAG_IGNORE_CULLING_name = zend_string_init_interned("FLAG_IGNORE_CULLING", sizeof("FLAG_IGNORE_CULLING") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_FLAG_IGNORE_CULLING_name, &const_FLAG_IGNORE_CULLING_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_FLAG_IGNORE_CULLING_name); + + zval const_FLAG_DISABLE_INSTANCING_value; + ZVAL_LONG(&const_FLAG_DISABLE_INSTANCING_value, 4); + zend_string *const_FLAG_DISABLE_INSTANCING_name = zend_string_init_interned("FLAG_DISABLE_INSTANCING", sizeof("FLAG_DISABLE_INSTANCING") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_FLAG_DISABLE_INSTANCING_name, &const_FLAG_DISABLE_INSTANCING_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_FLAG_DISABLE_INSTANCING_name); + + zval const_FLAG_CUSTOM_SORT_KEY_value; + ZVAL_LONG(&const_FLAG_CUSTOM_SORT_KEY_value, 8); + zend_string *const_FLAG_CUSTOM_SORT_KEY_name = zend_string_init_interned("FLAG_CUSTOM_SORT_KEY", sizeof("FLAG_CUSTOM_SORT_KEY") - 1, 1); + zend_declare_class_constant_ex(class_entry, const_FLAG_CUSTOM_SORT_KEY_name, &const_FLAG_CUSTOM_SORT_KEY_value, ZEND_ACC_PUBLIC, NULL); + zend_string_release(const_FLAG_CUSTOM_SORT_KEY_name); + + zval property_commandBuffer_default_value; + ZVAL_UNDEF(&property_commandBuffer_default_value); + zend_string *property_commandBuffer_name = zend_string_init("commandBuffer", sizeof("commandBuffer") - 1, 1); + zend_string *property_commandBuffer_class_GL_Buffer_UIntBuffer = zend_string_init("GL\\Buffer\\\125IntBuffer", sizeof("GL\\Buffer\\\125IntBuffer")-1, 1); + zend_declare_typed_property(class_entry, property_commandBuffer_name, &property_commandBuffer_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_commandBuffer_class_GL_Buffer_UIntBuffer, 0, 0)); + zend_string_release(property_commandBuffer_name); + + zval property_instanceTransformBuffer_default_value; + ZVAL_UNDEF(&property_instanceTransformBuffer_default_value); + zend_string *property_instanceTransformBuffer_name = zend_string_init("instanceTransformBuffer", sizeof("instanceTransformBuffer") - 1, 1); + zend_string *property_instanceTransformBuffer_class_GL_Buffer_FloatBuffer = zend_string_init("GL\\Buffer\\FloatBuffer", sizeof("GL\\Buffer\\FloatBuffer")-1, 1); + zend_declare_typed_property(class_entry, property_instanceTransformBuffer_name, &property_instanceTransformBuffer_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_instanceTransformBuffer_class_GL_Buffer_FloatBuffer, 0, 0)); + zend_string_release(property_instanceTransformBuffer_name); + + zval property_instanceMetaBuffer_default_value; + ZVAL_UNDEF(&property_instanceMetaBuffer_default_value); + zend_string *property_instanceMetaBuffer_name = zend_string_init("instanceMetaBuffer", sizeof("instanceMetaBuffer") - 1, 1); + zend_string *property_instanceMetaBuffer_class_GL_Buffer_UIntBuffer = zend_string_init("GL\\Buffer\\\125IntBuffer", sizeof("GL\\Buffer\\\125IntBuffer")-1, 1); + zend_declare_typed_property(class_entry, property_instanceMetaBuffer_name, &property_instanceMetaBuffer_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_instanceMetaBuffer_class_GL_Buffer_UIntBuffer, 0, 0)); + zend_string_release(property_instanceMetaBuffer_name); + + zval property_instancePayloadBuffer_default_value; + ZVAL_UNDEF(&property_instancePayloadBuffer_default_value); + zend_string *property_instancePayloadBuffer_name = zend_string_init("instancePayloadBuffer", sizeof("instancePayloadBuffer") - 1, 1); + zend_string *property_instancePayloadBuffer_class_GL_Buffer_FloatBuffer = zend_string_init("GL\\Buffer\\FloatBuffer", sizeof("GL\\Buffer\\FloatBuffer")-1, 1); + zend_declare_typed_property(class_entry, property_instancePayloadBuffer_name, &property_instancePayloadBuffer_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_instancePayloadBuffer_class_GL_Buffer_FloatBuffer, 0, 0)); + zend_string_release(property_instancePayloadBuffer_name); + + zval property_commandStride_default_value; + ZVAL_UNDEF(&property_commandStride_default_value); + zend_string *property_commandStride_name = zend_string_init("commandStride", sizeof("commandStride") - 1, 1); + zend_declare_typed_property(class_entry, property_commandStride_name, &property_commandStride_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_commandStride_name); + + zval property_transformStride_default_value; + ZVAL_UNDEF(&property_transformStride_default_value); + zend_string *property_transformStride_name = zend_string_init("transformStride", sizeof("transformStride") - 1, 1); + zend_declare_typed_property(class_entry, property_transformStride_name, &property_transformStride_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_transformStride_name); + + zval property_instanceMetaStride_default_value; + ZVAL_UNDEF(&property_instanceMetaStride_default_value); + zend_string *property_instanceMetaStride_name = zend_string_init("instanceMetaStride", sizeof("instanceMetaStride") - 1, 1); + zend_declare_typed_property(class_entry, property_instanceMetaStride_name, &property_instanceMetaStride_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_instanceMetaStride_name); + + return class_entry; +} + static zend_class_entry *register_class_GL_VectorGraphics_VGColor(void) { zend_class_entry ce, *class_entry; diff --git a/phpglfw_buffer.c b/phpglfw_buffer.c index bdccaaaf..40bce43b 100644 --- a/phpglfw_buffer.c +++ b/phpglfw_buffer.c @@ -202,9 +202,10 @@ zend_object_iterator *phpglfw_buffer_glfloat_get_iterator_handler(zend_class_ent return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_glfloat_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_glfloat_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_glfloat_iterator_handlers; @@ -841,9 +842,10 @@ zend_object_iterator *phpglfw_buffer_glhalf_get_iterator_handler(zend_class_entr return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_glhalf_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_glhalf_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_glhalf_iterator_handlers; @@ -1357,9 +1359,10 @@ zend_object_iterator *phpglfw_buffer_gldouble_get_iterator_handler(zend_class_en return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_gldouble_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_gldouble_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_gldouble_iterator_handlers; @@ -1881,9 +1884,10 @@ zend_object_iterator *phpglfw_buffer_glint_get_iterator_handler(zend_class_entry return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_glint_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_glint_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_glint_iterator_handlers; @@ -2397,9 +2401,10 @@ zend_object_iterator *phpglfw_buffer_gluint_get_iterator_handler(zend_class_entr return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_gluint_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_gluint_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_gluint_iterator_handlers; @@ -2913,9 +2918,10 @@ zend_object_iterator *phpglfw_buffer_glshort_get_iterator_handler(zend_class_ent return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_glshort_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_glshort_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_glshort_iterator_handlers; @@ -3429,9 +3435,10 @@ zend_object_iterator *phpglfw_buffer_glushort_get_iterator_handler(zend_class_en return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_glushort_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_glushort_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_glushort_iterator_handlers; @@ -3945,9 +3952,10 @@ zend_object_iterator *phpglfw_buffer_glbyte_get_iterator_handler(zend_class_entr return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_glbyte_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_glbyte_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_glbyte_iterator_handlers; @@ -4461,9 +4469,10 @@ zend_object_iterator *phpglfw_buffer_glubyte_get_iterator_handler(zend_class_ent return NULL; } - iterator = emalloc(sizeof(phpglfw_buffer_glubyte_iterator)); + iterator = ecalloc(1, sizeof(phpglfw_buffer_glubyte_iterator)); - zend_iterator_init((zend_object_iterator*)iterator); + zend_iterator_init((zend_object_iterator*)iterator); + ZVAL_UNDEF(&iterator->current); ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); iterator->intern.funcs = &phpglfw_buffer_glubyte_iterator_handlers; diff --git a/phpglfw_drawcall_assembler.c b/phpglfw_drawcall_assembler.c new file mode 100644 index 00000000..a5d0523c --- /dev/null +++ b/phpglfw_drawcall_assembler.c @@ -0,0 +1,1567 @@ +/** + * PHP-glfw + * + * Extension: DrawCallAssembler + * + * Copyright (c) 2018-2024 Mario Döring + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "phpglfw_drawcall_assembler.h" +#include "phpglfw_arginfo.h" + +#include +#include +#include "cvector.h" + +zend_class_entry *phpglfw_drawcall_assembler_ce; + +zend_class_entry *phpglfw_get_drawcall_assembler_ce() +{ + return phpglfw_drawcall_assembler_ce; +} + +phpglfw_drawcall_assembler_object *phpglfw_drawcall_assembler_objectptr_from_zobj_p(zend_object *obj) +{ + return (phpglfw_drawcall_assembler_object *)((char *)(obj)-XtOffsetOf(phpglfw_drawcall_assembler_object, std)); +} + +static zend_object_handlers phpglfw_drawcall_assembler_object_handlers; + +static float phpglfw_drawcall_distance_to_camera(phpglfw_math_vec3_object *camera_pos, const mat4x4 transform); +static void phpglfw_drawcall_store_plane(vec4 dest, float a, float b, float c, float d); +static void phpglfw_drawcall_extract_frustum(phpglfw_drawcall_assembler_object *intern); +static uint32_t phpglfw_drawcall_collect_visible_indices(phpglfw_drawcall_assembler_object *intern, uint32_t *visible_indices); +static uint32_t phpglfw_drawcall_select_lod(phpglfw_drawcall_assembler_object *intern, phpglfw_drawcall_instance *instance, float distance); +static void phpglfw_drawcall_update_instance_lod(phpglfw_drawcall_assembler_object *intern, phpglfw_drawcall_instance *instance); +static void phpglfw_drawcall_refresh_instance_lods(phpglfw_drawcall_assembler_object *intern); + +static inline void phpglfw_drawcall_release_zval(zval *value) +{ + if (!Z_ISUNDEF(*value)) + { + zval_ptr_dtor(value); + ZVAL_UNDEF(value); + } +} + +static zend_object *phpglfw_drawcall_assembler_create_object(zend_class_entry *class_type) +{ + phpglfw_drawcall_assembler_object *intern = zend_object_alloc(sizeof(phpglfw_drawcall_assembler_object), class_type); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + intern->std.handlers = &phpglfw_drawcall_assembler_object_handlers; + + // initialize all pointers to null + intern->command_buffer = NULL; + intern->instance_transform_buffer = NULL; + intern->instance_meta_buffer = NULL; + intern->instance_payload_buffer = NULL; + intern->meshes = NULL; + intern->instances = NULL; + intern->visible_index_buffer = NULL; + intern->camera_position = NULL; + intern->view_matrix = NULL; + intern->projection_matrix = NULL; + ZVAL_UNDEF(&intern->payload_data_buffer_zv); + ZVAL_UNDEF(&intern->camera_position_zv); + ZVAL_UNDEF(&intern->view_matrix_zv); + ZVAL_UNDEF(&intern->projection_matrix_zv); + + memset(intern->frustum_planes, 0, sizeof(intern->frustum_planes)); + + intern->mesh_capacity = 0; + intern->mesh_count = 0; + intern->instance_capacity = 0; + intern->instance_count = 0; + intern->has_frustum = false; + intern->sort_mode = PHPGLFW_SORT_NONE; + intern->auto_instancing = true; + intern->final_command_count = 0; + intern->final_instance_count = 0; + + // rendering dispatch + intern->rendering_transform_buffer_data = NULL; + intern->rendering_payload_buffer_data = NULL; + + return &intern->std; +} + +static void phpglfw_drawcall_assembler_free_handler(zend_object *object) +{ + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(object); + + // release internal-only zvals + phpglfw_drawcall_release_zval(&intern->payload_data_buffer_zv); + phpglfw_drawcall_release_zval(&intern->camera_position_zv); + phpglfw_drawcall_release_zval(&intern->view_matrix_zv); + phpglfw_drawcall_release_zval(&intern->projection_matrix_zv); + + // free mesh array + if (intern->meshes) { + efree(intern->meshes); + } + + // free instance array + if (intern->instances) { + efree(intern->instances); + } + + if (intern->visible_index_buffer) { + cvector_free(intern->visible_index_buffer); + } + + // rendering dispatch + cvector_free(intern->rendering_transform_buffer_data); + cvector_free(intern->rendering_payload_buffer_data); + + // we do own the internal VBOs, so make sure to delete them + if (intern->internal_transform_vbo != 0) { + glDeleteBuffers(1, &intern->internal_transform_vbo); + } + if (intern->internal_payload_vbo != 0) { + glDeleteBuffers(1, &intern->internal_payload_vbo); + } + + zend_object_std_dtor(&intern->std); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, __construct) +{ + zend_long initial_mesh_capacity = 256; + zend_long initial_instance_capacity = 2048; + zend_long initial_command_capacity = 512; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lll", + &initial_mesh_capacity, + &initial_instance_capacity, + &initial_command_capacity + ) == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + // allocate mesh storage + intern->mesh_capacity = initial_mesh_capacity; + intern->meshes = ecalloc(intern->mesh_capacity, sizeof(phpglfw_drawcall_mesh)); + + // allocate instance storage + intern->instance_capacity = initial_instance_capacity; + intern->instances = ecalloc(intern->instance_capacity, sizeof(phpglfw_drawcall_instance)); + + // for readonly properties, we must write directly to the properties table + // zend_update_property doesn't work with readonly properties declared in stubs + zend_object *this_obj = Z_OBJ_P(ZEND_THIS); + zval *prop; + + // command buffer + prop = OBJ_PROP_NUM(this_obj, 0); + object_init_ex(prop, phpglfw_get_buffer_gluint_ce()); + intern->command_buffer = phpglfw_buffer_gluint_objectptr_from_zobj_p(Z_OBJ_P(prop)); + cvector_reserve(intern->command_buffer->vec, initial_command_capacity * PHPGLFW_COMMAND_STRIDE); + + // instance transform buffer + prop = OBJ_PROP_NUM(this_obj, 1); + object_init_ex(prop, phpglfw_get_buffer_glfloat_ce()); + intern->instance_transform_buffer = phpglfw_buffer_glfloat_objectptr_from_zobj_p(Z_OBJ_P(prop)); + cvector_reserve(intern->instance_transform_buffer->vec, initial_instance_capacity * PHPGLFW_TRANSFORM_STRIDE); + + // instance meta buffer + prop = OBJ_PROP_NUM(this_obj, 2); + object_init_ex(prop, phpglfw_get_buffer_gluint_ce()); + intern->instance_meta_buffer = phpglfw_buffer_gluint_objectptr_from_zobj_p(Z_OBJ_P(prop)); + cvector_reserve(intern->instance_meta_buffer->vec, initial_instance_capacity * PHPGLFW_INSTANCE_META_STRIDE); + + // instance payload buffer + prop = OBJ_PROP_NUM(this_obj, 3); + object_init_ex(prop, phpglfw_get_buffer_glfloat_ce()); + intern->instance_payload_buffer = phpglfw_buffer_glfloat_objectptr_from_zobj_p(Z_OBJ_P(prop)); + + // stride constants + prop = OBJ_PROP_NUM(this_obj, 4); + ZVAL_LONG(prop, PHPGLFW_COMMAND_STRIDE); + + prop = OBJ_PROP_NUM(this_obj, 5); + ZVAL_LONG(prop, PHPGLFW_TRANSFORM_STRIDE); + + prop = OBJ_PROP_NUM(this_obj, 6); + ZVAL_LONG(prop, PHPGLFW_INSTANCE_META_STRIDE); + + // initialize internal VBO (will be created on first use) + intern->internal_transform_vbo = 0; + intern->internal_payload_vbo = 0; +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, setCameraData) +{ + zval *camera_position = NULL; + zval *view_matrix = NULL; + zval *projection_matrix = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|zzz", &camera_position, &view_matrix, &projection_matrix) == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + // store camera data if provided + if (camera_position && Z_TYPE_P(camera_position) == IS_OBJECT) + { + phpglfw_drawcall_release_zval(&intern->camera_position_zv); + ZVAL_OBJ_COPY(&intern->camera_position_zv, Z_OBJ_P(camera_position)); + intern->camera_position = phpglfw_math_vec3_objectptr_from_zobj_p(Z_OBJ(intern->camera_position_zv)); + } + else + { + phpglfw_drawcall_release_zval(&intern->camera_position_zv); + intern->camera_position = NULL; + } + + if (view_matrix && Z_TYPE_P(view_matrix) == IS_OBJECT) + { + phpglfw_drawcall_release_zval(&intern->view_matrix_zv); + ZVAL_OBJ_COPY(&intern->view_matrix_zv, Z_OBJ_P(view_matrix)); + intern->view_matrix = phpglfw_math_mat4_objectptr_from_zobj_p(Z_OBJ(intern->view_matrix_zv)); + } + else + { + phpglfw_drawcall_release_zval(&intern->view_matrix_zv); + intern->view_matrix = NULL; + } + + if (projection_matrix && Z_TYPE_P(projection_matrix) == IS_OBJECT) + { + phpglfw_drawcall_release_zval(&intern->projection_matrix_zv); + ZVAL_OBJ_COPY(&intern->projection_matrix_zv, Z_OBJ_P(projection_matrix)); + intern->projection_matrix = phpglfw_math_mat4_objectptr_from_zobj_p(Z_OBJ(intern->projection_matrix_zv)); + } + else + { + phpglfw_drawcall_release_zval(&intern->projection_matrix_zv); + intern->projection_matrix = NULL; + } + + if (intern->view_matrix && intern->projection_matrix) + { + phpglfw_drawcall_extract_frustum(intern); + } + else + { + intern->has_frustum = false; + } +} + +static void phpglfw_drawcall_store_plane(vec4 dest, float a, float b, float c, float d) +{ + float len = sqrtf((a * a) + (b * b) + (c * c)); + if (len > 0.0f) + { + dest[0] = a / len; + dest[1] = b / len; + dest[2] = c / len; + dest[3] = d / len; + } + else + { + dest[0] = a; + dest[1] = b; + dest[2] = c; + dest[3] = d; + } +} + +static void phpglfw_drawcall_extract_frustum(phpglfw_drawcall_assembler_object *intern) +{ + mat4x4 clip; + mat4x4_mul(clip, intern->projection_matrix->data, intern->view_matrix->data); + + float m[4][4]; + for (int row = 0; row < 4; row++) + { + for (int col = 0; col < 4; col++) + { + m[row][col] = clip[col][row]; + } + } + + // left + phpglfw_drawcall_store_plane( + intern->frustum_planes[0], + m[3][0] + m[0][0], + m[3][1] + m[0][1], + m[3][2] + m[0][2], + m[3][3] + m[0][3] + ); + + // right + phpglfw_drawcall_store_plane( + intern->frustum_planes[1], + m[3][0] - m[0][0], + m[3][1] - m[0][1], + m[3][2] - m[0][2], + m[3][3] - m[0][3] + ); + + // bottom + phpglfw_drawcall_store_plane( + intern->frustum_planes[2], + m[3][0] + m[1][0], + m[3][1] + m[1][1], + m[3][2] + m[1][2], + m[3][3] + m[1][3] + ); + + // top + phpglfw_drawcall_store_plane( + intern->frustum_planes[3], + m[3][0] - m[1][0], + m[3][1] - m[1][1], + m[3][2] - m[1][2], + m[3][3] - m[1][3] + ); + + // near + phpglfw_drawcall_store_plane( + intern->frustum_planes[4], + m[3][0] + m[2][0], + m[3][1] + m[2][1], + m[3][2] + m[2][2], + m[3][3] + m[2][3] + ); + + // far + phpglfw_drawcall_store_plane( + intern->frustum_planes[5], + m[3][0] - m[2][0], + m[3][1] - m[2][1], + m[3][2] - m[2][2], + m[3][3] - m[2][3] + ); + + intern->has_frustum = true; +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, registerMesh) +{ + zend_long vao_id; + zend_long vertex_offset = 0, vertex_count = 0; + zend_long index_offset = 0, index_count = 0; + zval *aabb_min = NULL, *aabb_max = NULL; + zend_long material_hint = 0; + zend_long primitive = 0x0004; // gl_triangles + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "l|llllzzll", + &vao_id, + &vertex_offset, &vertex_count, + &index_offset, &index_count, + &aabb_min, &aabb_max, + &material_hint, + &primitive + ) == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + // resize mesh array if needed + if (intern->mesh_count >= intern->mesh_capacity) { + intern->mesh_capacity *= 2; + intern->meshes = erealloc(intern->meshes, intern->mesh_capacity * sizeof(phpglfw_drawcall_mesh)); + } + + phpglfw_drawcall_mesh *mesh = &intern->meshes[intern->mesh_count]; + mesh->vao_id = vao_id; + mesh->vertex_offset = vertex_offset; + mesh->vertex_count = vertex_count; + mesh->index_offset = index_offset; + mesh->index_count = index_count; + mesh->material_hint = material_hint; + mesh->primitive = primitive; + + // store bounding box if provided + if (aabb_min && Z_TYPE_P(aabb_min) == IS_OBJECT) { + mesh->aabb_min = phpglfw_math_vec3_objectptr_from_zobj_p(Z_OBJ_P(aabb_min)); + } else { + mesh->aabb_min = NULL; + } + + if (aabb_max && Z_TYPE_P(aabb_max) == IS_OBJECT) { + mesh->aabb_max = phpglfw_math_vec3_objectptr_from_zobj_p(Z_OBJ_P(aabb_max)); + } else { + mesh->aabb_max = NULL; + } + + if (mesh->aabb_min && mesh->aabb_max) + { + mesh->has_bounds = true; + float half_extents[3]; + + for (int axis = 0; axis < 3; axis++) { + float min_val = mesh->aabb_min->data[axis]; + float max_val = mesh->aabb_max->data[axis]; + mesh->bounds_center[axis] = (min_val + max_val) * 0.5f; + half_extents[axis] = (max_val - min_val) * 0.5f; + } + + mesh->bounds_radius = sqrtf( + (half_extents[0] * half_extents[0]) + + (half_extents[1] * half_extents[1]) + + (half_extents[2] * half_extents[2]) + ); + } + else + { + mesh->has_bounds = false; + mesh->bounds_radius = 0.0f; + mesh->bounds_center[0] = 0.0f; + mesh->bounds_center[1] = 0.0f; + mesh->bounds_center[2] = 0.0f; + } + + // initialize lod data + mesh->lod_distances = NULL; + mesh->lod_handles = NULL; + + uint32_t handle = intern->mesh_count; + intern->mesh_count++; + + RETURN_LONG(handle); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, setLodTable) +{ + zend_long mesh_handle; + zval *distance_thresholds, *mesh_handles; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "lzz", &mesh_handle, &distance_thresholds, &mesh_handles) == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + if (mesh_handle >= intern->mesh_count) { + zend_throw_error(NULL, "invalid mesh handle"); + RETURN_THROWS(); + } + + phpglfw_drawcall_mesh *mesh = &intern->meshes[mesh_handle]; + + if (Z_TYPE_P(distance_thresholds) == IS_OBJECT) { + mesh->lod_distances = phpglfw_buffer_glfloat_objectptr_from_zobj_p(Z_OBJ_P(distance_thresholds)); + } + else { + mesh->lod_distances = NULL; + } + + if (Z_TYPE_P(mesh_handles) == IS_OBJECT) { + mesh->lod_handles = phpglfw_buffer_gluint_objectptr_from_zobj_p(Z_OBJ_P(mesh_handles)); + } + else { + mesh->lod_handles = NULL; + } +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, setMeshMaterial) +{ + zend_long mesh_handle, material_id; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &mesh_handle, &material_id) == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + if (mesh_handle >= intern->mesh_count) { + zend_throw_error(NULL, "invalid mesh handle"); + RETURN_THROWS(); + } + + intern->meshes[mesh_handle].material_hint = material_id; +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, setAutoInstancing) +{ + zend_bool enabled; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "b", &enabled) == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + intern->auto_instancing = enabled; +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, setFrustumPlanes) +{ + zval *left, *right, *bottom, *top, *near, *far; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzzzzz", &left, &right, &bottom, &top, &near, &far) == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + phpglfw_math_vec4_object *planes[6] = { + phpglfw_math_vec4_objectptr_from_zobj_p(Z_OBJ_P(left)), + phpglfw_math_vec4_objectptr_from_zobj_p(Z_OBJ_P(right)), + phpglfw_math_vec4_objectptr_from_zobj_p(Z_OBJ_P(bottom)), + phpglfw_math_vec4_objectptr_from_zobj_p(Z_OBJ_P(top)), + phpglfw_math_vec4_objectptr_from_zobj_p(Z_OBJ_P(near)), + phpglfw_math_vec4_objectptr_from_zobj_p(Z_OBJ_P(far)), + }; + + for (int i = 0; i < 6; i++) { + memcpy(intern->frustum_planes[i], planes[i]->data, sizeof(vec4)); + } + + intern->has_frustum = true; +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, setSortMode) +{ + zend_long mode; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &mode) == FAILURE) + { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + intern->sort_mode = mode; +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, clearInstances) +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + intern->instance_count = 0; + intern->final_command_count = 0; + intern->final_instance_count = 0; + + cvector_set_size(intern->command_buffer->vec, 0); + cvector_set_size(intern->instance_transform_buffer->vec, 0); + cvector_set_size(intern->instance_meta_buffer->vec, 0); + cvector_set_size(intern->instance_payload_buffer->vec, 0); +} + +static float phpglfw_drawcall_distance_to_camera_sq(phpglfw_math_vec3_object *camera_pos, const mat4x4 transform) +{ + const float dx = transform[3][0] - camera_pos->data[0]; + const float dy = transform[3][1] - camera_pos->data[1]; + const float dz = transform[3][2] - camera_pos->data[2]; + + return dx * dx + dy * dy + dz * dz; +} + +static float phpglfw_drawcall_distance_to_camera(phpglfw_math_vec3_object *camera_pos, const mat4x4 transform) +{ + return sqrtf(phpglfw_drawcall_distance_to_camera_sq(camera_pos, transform)); +} + +static uint32_t phpglfw_drawcall_collect_visible_indices(phpglfw_drawcall_assembler_object *intern, uint32_t *visible_indices) +{ + const uint32_t total_instances = intern->instance_count; + const phpglfw_drawcall_instance * const instances = intern->instances; + const phpglfw_drawcall_mesh * const meshes = intern->meshes; + + // if there is no frustum, we consider all instances to be visible + if (!intern->has_frustum) { + for (uint32_t i = 0; i < total_instances; i++) { + visible_indices[i] = i; + } + + return total_instances; + } + + uint32_t write_index = 0; + + // get the frustum planes into stack memory (i think this is faster, but to be honest, i did not benchmark it..) + const float frustum[6][4] = { + {intern->frustum_planes[0][0], intern->frustum_planes[0][1], intern->frustum_planes[0][2], intern->frustum_planes[0][3]}, + {intern->frustum_planes[1][0], intern->frustum_planes[1][1], intern->frustum_planes[1][2], intern->frustum_planes[1][3]}, + {intern->frustum_planes[2][0], intern->frustum_planes[2][1], intern->frustum_planes[2][2], intern->frustum_planes[2][3]}, + {intern->frustum_planes[3][0], intern->frustum_planes[3][1], intern->frustum_planes[3][2], intern->frustum_planes[3][3]}, + {intern->frustum_planes[4][0], intern->frustum_planes[4][1], intern->frustum_planes[4][2], intern->frustum_planes[4][3]}, + {intern->frustum_planes[5][0], intern->frustum_planes[5][1], intern->frustum_planes[5][2], intern->frustum_planes[5][3]} + }; + + for (uint32_t read_index = 0; read_index < total_instances; read_index++) + { + const phpglfw_drawcall_instance * const instance = &instances[read_index]; + + // fast path for ignore culling flag + if (instance->flags & PHPGLFW_FLAG_IGNORE_CULLING) { + visible_indices[write_index++] = read_index; + continue; + } + + const phpglfw_drawcall_mesh * const mesh = &meshes[instance->mesh_handle]; + + float cx = instance->transform[3][0]; + float cy = instance->transform[3][1]; + float cz = instance->transform[3][2]; + float scaled_radius = mesh->bounds_radius; + + if (mesh->has_bounds) + { + // transform mesh center to world space + const float local_x = mesh->bounds_center[0]; + const float local_y = mesh->bounds_center[1]; + const float local_z = mesh->bounds_center[2]; + + cx = instance->transform[0][0] * local_x + instance->transform[1][0] * local_y + instance->transform[2][0] * local_z + instance->transform[3][0]; + cy = instance->transform[0][1] * local_x + instance->transform[1][1] * local_y + instance->transform[2][1] * local_z + instance->transform[3][1]; + cz = instance->transform[0][2] * local_x + instance->transform[1][2] * local_y + instance->transform[2][2] * local_z + instance->transform[3][2]; + + // compute uniform scale factor + const mat4x4 * const t = &instance->transform; + const float scale_x_sq = (*t)[0][0] * (*t)[0][0] + (*t)[0][1] * (*t)[0][1] + (*t)[0][2] * (*t)[0][2]; + const float scale_y_sq = (*t)[1][0] * (*t)[1][0] + (*t)[1][1] * (*t)[1][1] + (*t)[1][2] * (*t)[1][2]; + const float scale_z_sq = (*t)[2][0] * (*t)[2][0] + (*t)[2][1] * (*t)[2][1] + (*t)[2][2] * (*t)[2][2]; + const float max_scale_sq = fmaxf(scale_x_sq, fmaxf(scale_y_sq, scale_z_sq)); + scaled_radius *= sqrtf(max_scale_sq); + } + + // frustum test + bool is_visible = true; + for (int plane = 0; plane < 6 && is_visible; plane++) + { + const float distance = frustum[plane][0] * cx + frustum[plane][1] * cy + frustum[plane][2] * cz + frustum[plane][3]; + is_visible = distance >= -scaled_radius; + } + + if (is_visible) { + visible_indices[write_index++] = read_index; + } + } + + return write_index; +} + +static zend_always_inline uint32_t phpglfw_drawcall_select_lod( + phpglfw_drawcall_assembler_object *intern, + phpglfw_drawcall_instance *instance, + float distance +) { + if (!intern->camera_position) { + return instance->base_mesh_handle; + } + + // check cache first + const float distance_diff = fabsf(distance - instance->cached_lod_distance); + if (distance_diff < PHPGLFW_LOD_CACHE_EPSILON && instance->cached_lod_handle != 0) { + return instance->cached_lod_handle; + } + + const uint32_t base_handle = instance->base_mesh_handle; + if (base_handle >= intern->mesh_count) { + return base_handle; + } + + const phpglfw_drawcall_mesh *mesh = &intern->meshes[base_handle]; + + if (!mesh->lod_distances || !mesh->lod_handles) + { + instance->cached_lod_distance = distance; + instance->cached_lod_handle = base_handle; + return base_handle; + } + + const uint32_t lod_count = (uint32_t)cvector_size(mesh->lod_distances->vec); + const uint32_t handle_count = (uint32_t)cvector_size(mesh->lod_handles->vec); + if (lod_count == 0 || handle_count == 0) + { + instance->cached_lod_distance = distance; + instance->cached_lod_handle = base_handle; + return base_handle; + } + + const uint32_t limit = lod_count < handle_count ? lod_count : handle_count; + if (limit == 0) + { + instance->cached_lod_distance = distance; + instance->cached_lod_handle = base_handle; + return base_handle; + } + + const float clamped_distance = distance < 0.0f ? 0.0f : distance; + uint32_t selected_handle = base_handle; + + // optimize loop by avoiding bounds checks where possible + const float *distances = mesh->lod_distances->vec; + const uint32_t *handles = mesh->lod_handles->vec; + + for (uint32_t i = 0; i < limit; i++) + { + const float threshold = distances[i]; + const uint32_t alternative = handles[i]; + + if (alternative >= intern->mesh_count) + { + continue; + } + + if (clamped_distance >= threshold) + { + selected_handle = alternative; + } + else + { + break; + } + } + + // cache result + instance->cached_lod_distance = distance; + instance->cached_lod_handle = selected_handle; + + return selected_handle; +} + +static inline uint64_t make_sort_key_opaque( + uint32_t pass, uint32_t program_id, uint32_t material_id, + uint32_t vao_id, uint32_t mesh_id, uint32_t depth_bucket +){ + uint64_t k = 0; + k |= ((uint64_t)(pass & SK_MASK(SK_PASS_BITS))) << SK_PASS_SHIFT; + k |= ((uint64_t)(program_id & SK_MASK(SK_PROG_BITS))) << SK_PROG_SHIFT; + k |= ((uint64_t)(material_id & SK_MASK(SK_MAT_BITS))) << SK_MAT_SHIFT; + k |= ((uint64_t)(vao_id & SK_MASK(SK_VAO_BITS))) << SK_VAO_SHIFT; + k |= ((uint64_t)(mesh_id & SK_MASK(SK_MESH_BITS))) << SK_MESH_SHIFT; + k |= ((uint64_t)(depth_bucket& SK_MASK(SK_DEPTH_BITS))) << SK_DEPTH_SHIFT; + return k; +} + +static inline uint64_t make_sort_key_transparent( + uint32_t pass, uint32_t program_id, uint32_t material_id, + uint32_t vao_id, uint32_t mesh_id, uint32_t depth_bucket +){ + uint32_t depth_rev = (SK_MASK(SK_DEPTH_BITS) - (depth_bucket & SK_MASK(SK_DEPTH_BITS))); + uint64_t k = 0; + k |= ((uint64_t)(pass & SK_MASK(SK_PASS_BITS))) << SK_PASS_SHIFT; + k |= ((uint64_t)(depth_rev & SK_MASK(SK_DEPTH_BITS))) << SK_PROG_SHIFT; + k |= ((uint64_t)(program_id & SK_MASK(SK_PROG_BITS))) << SK_MAT_SHIFT; + k |= ((uint64_t)(material_id & SK_MASK(SK_MAT_BITS))) << SK_VAO_SHIFT; + k |= ((uint64_t)(vao_id & SK_MASK(SK_VAO_BITS))) << SK_MESH_SHIFT; + k |= ((uint64_t)(mesh_id & SK_MASK(SK_MESH_BITS))) << SK_DEPTH_SHIFT; + return k; +} + +static inline uint64_t phpglfw_drawcall_make_group_key( + uint32_t pass, uint32_t program_id, uint32_t material_id, + uint32_t mesh_id, uint32_t flags +){ + uint64_t k = 0; + k |= ((uint64_t)(pass & SK_MASK(SK_PASS_BITS))) << SK_PASS_SHIFT; + k |= ((uint64_t)(program_id & SK_MASK(SK_PROG_BITS))) << SK_PROG_SHIFT; + k |= ((uint64_t)(material_id & SK_MASK(SK_MAT_BITS))) << SK_MAT_SHIFT; + k |= ((uint64_t)(mesh_id & SK_MASK(SK_MESH_BITS))) << SK_MESH_SHIFT; + k |= ((uint64_t)(flags & SK_MASK(SK_DEPTH_BITS))) << SK_DEPTH_SHIFT; + return k; +} + +static inline void phpglfw_drawcall_compute_sort_key(phpglfw_drawcall_assembler_object *intern, phpglfw_drawcall_instance *instance) +{ + phpglfw_drawcall_mesh *mesh = &intern->meshes[instance->mesh_handle]; + + instance->group_key = phpglfw_drawcall_make_group_key( + instance->pass, + instance->program_id, + instance->material_id, + instance->mesh_handle, + instance->flags + ); + + // quantize depth to 0-1023 range (clamped at 1000 units) for 10-bit field + float distance = instance->sort_distance + instance->sort_bias; + uint32_t depth_bucket = (uint32_t)(fminf(distance / 1000.0f, 1.0f) * 1023.0f); + + const bool is_transparent = (instance->pass == PHPGLFW_PASS_TRANSPARENT); + + if (is_transparent) { + instance->sort_key = make_sort_key_transparent( + instance->pass, + instance->program_id, + instance->material_id, + mesh->vao_id, + instance->mesh_handle, + depth_bucket + ); + } else { + instance->sort_key = make_sort_key_opaque( + instance->pass, + instance->program_id, + instance->material_id, + mesh->vao_id, + instance->mesh_handle, + depth_bucket + ); + } +} + +static inline void phpglfw_drawcall_set_instance_distance(phpglfw_drawcall_assembler_object *intern, phpglfw_drawcall_instance *instance, float distance) +{ + instance->sort_distance = distance; +} + +static zend_always_inline void phpglfw_drawcall_update_instance_lod(phpglfw_drawcall_assembler_object *intern, phpglfw_drawcall_instance *instance) +{ + instance->mesh_handle = phpglfw_drawcall_select_lod(intern, instance, instance->sort_distance); + + // rebuild sort key to reflect new mesh handle + phpglfw_drawcall_compute_sort_key(intern, instance); +} + +static void phpglfw_drawcall_refresh_instance_lods(phpglfw_drawcall_assembler_object *intern) +{ + if (intern->instance_count == 0) { + return; + } + + for (uint32_t i = 0; i < intern->instance_count; i++) { + phpglfw_drawcall_update_instance_lod(intern, &intern->instances[i]); + } +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, submit) +{ + zend_long mesh_handle, material_id; + zval *transform; + zend_long pass = PHPGLFW_PASS_OPAQUE; + zend_long program_id = 0; + zend_long flags = 0; + double sort_bias = 0.0; + zend_long user_id = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "lzl|llldl", &mesh_handle, &transform, &material_id, &pass, &program_id, &flags, &sort_bias, &user_id) == FAILURE){ + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + if (mesh_handle >= intern->mesh_count) { + zend_throw_error(NULL, "invalid mesh handle"); + RETURN_THROWS(); + } + + // resize instance array if needed + if (intern->instance_count >= intern->instance_capacity) { + intern->instance_capacity *= 2; + intern->instances = erealloc(intern->instances, intern->instance_capacity * sizeof(phpglfw_drawcall_instance)); + } + + phpglfw_drawcall_instance *instance = &intern->instances[intern->instance_count]; + const phpglfw_drawcall_mesh *mesh = &intern->meshes[mesh_handle]; + instance->base_mesh_handle = (uint32_t)mesh_handle; + instance->mesh_handle = (uint32_t)mesh_handle; + instance->material_id = material_id; + instance->pass = (uint32_t)(pass & SK_MASK(SK_PASS_BITS)); + instance->program_id = program_id; + instance->user_id = user_id; + instance->flags = flags; + instance->sort_bias = sort_bias; + instance->sort_distance = 0.0f; + + instance->cached_lod_distance = -1.0f; + instance->cached_lod_handle = 0; + + phpglfw_math_mat4_object *transform_obj = phpglfw_math_mat4_objectptr_from_zobj_p(Z_OBJ_P(transform)); + mat4x4_dup(instance->transform, transform_obj->data); + + phpglfw_drawcall_compute_sort_key(intern, instance); + + intern->instance_count++; +} + +static const phpglfw_drawcall_instance *instances_global; +static int phpglfw_index_compare_by_sort_key(const void *a, const void *b) +{ + const uint32_t idx_a = *(const uint32_t *)a; + const uint32_t idx_b = *(const uint32_t *)b; + const uint64_t key_a = instances_global[idx_a].sort_key; + const uint64_t key_b = instances_global[idx_b].sort_key; + return (key_a < key_b) ? -1 : ((key_a > key_b) ? 1 : 0); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, build) +{ + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + // clear output buffers + cvector_set_size(intern->command_buffer->vec, 0); + cvector_set_size(intern->instance_transform_buffer->vec, 0); + cvector_set_size(intern->instance_meta_buffer->vec, 0); + + uint32_t command_count = 0; + uint32_t instance_offset = 0; + uint32_t visible_count = intern->instance_count; + uint32_t *visible_indices = NULL; + uint32_t stack_buffer[PHPGLFW_SMALL_BUFFER_THRESHOLD]; + const bool use_frustum_culling = intern->has_frustum && visible_count > 0; + const bool use_stack_allocation = visible_count <= PHPGLFW_SMALL_BUFFER_THRESHOLD; + + if (use_stack_allocation) { + visible_indices = stack_buffer; + } + else + { + // ensure the index buffer is allocated and large enough + if (!intern->visible_index_buffer) { + cvector_reserve(intern->visible_index_buffer, visible_count); + } + else if (cvector_capacity(intern->visible_index_buffer) < visible_count) { + cvector_reserve(intern->visible_index_buffer, visible_count); + } + visible_indices = intern->visible_index_buffer; + } + + // collect the visible instance indices + // aka do frustum culling + visible_count = phpglfw_drawcall_collect_visible_indices(intern, visible_indices); + + // nothing visible = nothing to do + if (visible_count == 0) { + intern->final_command_count = 0; + intern->final_instance_count = 0; + RETURN_LONG(0); + } + + const size_t total_transform_floats = (size_t)visible_count * PHPGLFW_TRANSFORM_STRIDE; + const size_t total_meta_uints = (size_t)visible_count * PHPGLFW_INSTANCE_META_STRIDE; + cvector_ensure(intern->instance_transform_buffer->vec, total_transform_floats); + cvector_ensure(intern->instance_meta_buffer->vec, total_meta_uints); + + // setup payload buffer if bound + float *payload_write_ptr = NULL; + size_t payload_cursor = 0; + if (!Z_ISUNDEF(intern->payload_data_buffer_zv)) { + const size_t total_payload_floats = (size_t)visible_count * intern->payload_data_stride; + cvector_ensure(intern->instance_payload_buffer->vec, total_payload_floats); + payload_write_ptr = intern->instance_payload_buffer->vec; + } + + float *transform_write_ptr = intern->instance_transform_buffer->vec; + uint32_t *meta_write_ptr = intern->instance_meta_buffer->vec; + size_t transform_cursor = 0; + size_t meta_cursor = 0; + + const phpglfw_drawcall_instance * const instances = intern->instances; + const phpglfw_drawcall_mesh * const meshes = intern->meshes; + + // compute the distance to camera for each visible instance + if (intern->camera_position) + { + for (uint32_t i = 0; i < visible_count; i++) + { + const uint32_t instance_index = visible_indices[i]; + phpglfw_drawcall_instance *instance = &instances[instance_index]; + + // calculate distance to camera and apply it to the instance + float distance_sq = phpglfw_drawcall_distance_to_camera_sq(intern->camera_position, instance->transform); + phpglfw_drawcall_set_instance_distance(intern, instance, sqrtf(distance_sq)); + phpglfw_drawcall_compute_sort_key(intern, instance); + } + } + + // eval LOD selection + phpglfw_drawcall_refresh_instance_lods(intern); + + // sort the visible indices by precomputed sort keys + if (visible_count > 1) { + instances_global = instances; + qsort(visible_indices, visible_count, sizeof(uint32_t), phpglfw_index_compare_by_sort_key); + } + + // build draw call commands + for (uint32_t base_pos = 0; base_pos < visible_count;) + { + uint32_t base_index = visible_indices[base_pos]; + const phpglfw_drawcall_instance *base_instance = &instances[base_index]; + const phpglfw_drawcall_mesh *mesh = &meshes[base_instance->mesh_handle]; + uint32_t command_flags = base_instance->flags; + const uint32_t batch_flags = command_flags; + const uint64_t batch_group_key = base_instance->group_key; + bool can_auto_instance = intern->auto_instancing && !(command_flags & PHPGLFW_FLAG_DISABLE_INSTANCING); + uint32_t batch_pos = base_pos + 1; + + if (can_auto_instance) + { + while (batch_pos < visible_count) + { + const phpglfw_drawcall_instance *candidate = &instances[visible_indices[batch_pos]]; + if (candidate->group_key != batch_group_key) { + break; + } + batch_pos++; + } + } + + const uint32_t batch_size = batch_pos - base_pos; + const uint32_t draw_count = mesh->index_count > 0 ? mesh->index_count : mesh->vertex_count; + + cvector_push_back(intern->command_buffer->vec, base_instance->mesh_handle); + cvector_push_back(intern->command_buffer->vec, mesh->vao_id); + cvector_push_back(intern->command_buffer->vec, mesh->index_offset); + cvector_push_back(intern->command_buffer->vec, draw_count); + cvector_push_back(intern->command_buffer->vec, mesh->vertex_offset); + cvector_push_back(intern->command_buffer->vec, instance_offset); + cvector_push_back(intern->command_buffer->vec, batch_size); + cvector_push_back(intern->command_buffer->vec, base_instance->material_id); + + for (uint32_t cursor = base_pos; cursor < batch_pos; cursor++) + { + const phpglfw_drawcall_instance * const instance = &instances[visible_indices[cursor]]; + const mat4x4 * const transform = &instance->transform; + float *transform_dst = transform_write_ptr + transform_cursor; + + // unroll matrix copy + transform_dst[0] = (*transform)[0][0]; + transform_dst[1] = (*transform)[0][1]; + transform_dst[2] = (*transform)[0][2]; + transform_dst[3] = (*transform)[0][3]; + transform_dst[4] = (*transform)[1][0]; + transform_dst[5] = (*transform)[1][1]; + transform_dst[6] = (*transform)[1][2]; + transform_dst[7] = (*transform)[1][3]; + transform_dst[8] = (*transform)[2][0]; + transform_dst[9] = (*transform)[2][1]; + transform_dst[10] = (*transform)[2][2]; + transform_dst[11] = (*transform)[2][3]; + transform_dst[12] = (*transform)[3][0]; + transform_dst[13] = (*transform)[3][1]; + transform_dst[14] = (*transform)[3][2]; + transform_dst[15] = (*transform)[3][3]; + + uint32_t *meta_dst = meta_write_ptr + meta_cursor; + meta_dst[0] = instance->mesh_handle; + meta_dst[1] = instance->material_id; + meta_dst[2] = instance->user_id; + meta_dst[3] = batch_flags; + + transform_cursor += PHPGLFW_TRANSFORM_STRIDE; + meta_cursor += PHPGLFW_INSTANCE_META_STRIDE; + } + + // copy payload data if available + if (payload_write_ptr && !Z_ISUNDEF(intern->payload_data_buffer_zv)) + { + phpglfw_buffer_glfloat_object *payload_source = phpglfw_buffer_glfloat_objectptr_from_zobj_p(Z_OBJ(intern->payload_data_buffer_zv)); + + for (uint32_t cursor = base_pos; cursor < batch_pos; cursor++) + { + uint32_t source_offset = visible_indices[cursor] * intern->payload_data_stride; + + // ensure we don't read beyond the source buffer + if (source_offset + intern->payload_data_stride <= cvector_size(payload_source->vec)) { + float *payload_dst = payload_write_ptr + payload_cursor; + for (uint32_t p = 0; p < intern->payload_data_stride; p++) { + payload_dst[p] = payload_source->vec[source_offset + p]; + } + } + payload_cursor += intern->payload_data_stride; + } + } + + instance_offset += batch_size; + command_count++; + base_pos = batch_pos; + } + + // only store back visible_indices if we used heap allocation + if (use_frustum_culling && !use_stack_allocation) { + intern->visible_index_buffer = visible_indices; + } + + intern->final_command_count = command_count; + intern->final_instance_count = instance_offset; + + RETURN_LONG(command_count); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, bindTransformBuffer) +{ + zend_long vao, offset = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &vao, &offset) == FAILURE) { + RETURN_THROWS(); + } + + // ensure given vao and vbo are valid + if (vao == 0) { + zend_throw_error(NULL, "invalid VAO handle given."); + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + // // check if the VBO was already set, we don't really support that, because + // // it might lead to confusion. The VBO will be constantly updated during execute(). + // // with the expectation that it is the same VBO for every draw call. + // // The VAO on the other hand can be different per draw call as long as it shares the same VBO binding. + // if (intern->internal_transform_vbo != 0 && intern->internal_transform_vbo != (GLuint)vbo) { + // zend_throw_error(NULL, "transform VBO is already bound, you can pass multiple VAOs but they must share the same VBO."); + // RETURN_THROWS(); + // } + + // the top is there is just for reference, i've decided that the VBO is owned by the DrawCallAssembler + // and will be updated automatically during execute(). + if (intern->internal_transform_vbo == 0) { + glGenBuffers(1, &intern->internal_transform_vbo); + } + + // bind the VAO and VBO + glBindVertexArray((GLuint)vao); + glBindBuffer(GL_ARRAY_BUFFER, intern->internal_transform_vbo); + + // transform matrix attribute pointers (4x Vec4 for Mat4) + GLsizei stride = PHPGLFW_TRANSFORM_STRIDE * sizeof(GLfloat); + + for (int i = 0; i < 4; i++) { + GLuint location = (GLuint)offset + i; + glEnableVertexAttribArray(location); + glVertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, stride, (void*)(i * 4 * sizeof(GLfloat))); + glVertexAttribDivisor(location, 1); // this is an instanced attribute + } + + // return next available attribute location + RETURN_LONG(offset + 4); +} + + +PHP_METHOD(GL_Rendering_DrawCallAssembler, bindPayloadData) +{ + zval *payload_buffer; + zend_long stride; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Ol", &payload_buffer, phpglfw_get_buffer_glfloat_ce(), &stride) == FAILURE) { + RETURN_THROWS(); + } + + // validate stride + if (stride <= 0) { + zend_throw_error(NULL, "stride must be greater than 0"); + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + // store reference to the buffer object and stride + ZVAL_COPY(&intern->payload_data_buffer_zv, payload_buffer); + intern->payload_data_stride = (uint32_t)stride; +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, bindPayloadBuffer) +{ + zend_long vao, offset, stride = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll|l", &vao, &offset, &stride) == FAILURE) { + RETURN_THROWS(); + } + + // validate VAO + if (vao == 0) { + zend_throw_error(NULL, "invalid VAO handle given."); + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + // check if payload data was bound + if (Z_ISUNDEF(intern->payload_data_buffer_zv)) { + zend_throw_error(NULL, "bindPayloadData must be called before bindPayloadBuffer."); + RETURN_THROWS(); + } + + // use bound stride if not provided + uint32_t payload_stride = stride > 0 ? (uint32_t)stride : intern->payload_data_stride; + + if (payload_stride == 0) { + zend_throw_error(NULL, "payload stride cannot be 0"); + RETURN_THROWS(); + } + + // create internal payload VBO if needed + if (intern->internal_payload_vbo == 0) { + glGenBuffers(1, &intern->internal_payload_vbo); + } + + // bind VAO and VBO + glBindVertexArray((GLuint)vao); + glBindBuffer(GL_ARRAY_BUFFER, intern->internal_payload_vbo); + + // set up vertex attributes for payload data + // each attribute can hold up to 4 floats (Vec4), so we need to split larger strides + uint32_t remaining_components = payload_stride; + uint32_t current_offset_bytes = 0; + uint32_t attribute_index = 0; + GLsizei stride_bytes = payload_stride * sizeof(GLfloat); + + while (remaining_components > 0) { + uint32_t components_in_this_attr = remaining_components > 4 ? 4 : remaining_components; + GLuint location = (GLuint)offset + attribute_index; + + glEnableVertexAttribArray(location); + glVertexAttribPointer( + location, + components_in_this_attr, + GL_FLOAT, + GL_FALSE, + stride_bytes, + (void*)(current_offset_bytes) + ); + glVertexAttribDivisor(location, 1); // instanced attribute + + remaining_components -= components_in_this_attr; + current_offset_bytes += components_in_this_attr * sizeof(GLfloat); + attribute_index++; + } + + // return next available attribute location + RETURN_LONG(offset + attribute_index); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, execute) +{ + zend_fcall_info fci; + zend_fcall_info_cache fci_cache; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "f", &fci, &fci_cache) == FAILURE) { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + uint32_t command_count = 0; + uint32_t instance_offset = 0; + uint32_t visible_count = intern->instance_count; + uint32_t *visible_indices = NULL; + uint32_t stack_buffer[PHPGLFW_SMALL_BUFFER_THRESHOLD]; + const bool use_frustum_culling = intern->has_frustum && visible_count > 0; + const bool use_stack_allocation = visible_count <= PHPGLFW_SMALL_BUFFER_THRESHOLD; + + if (use_stack_allocation) { + visible_indices = stack_buffer; + } + else + { + // ensure the index buffer is allocated and large enough + if (!intern->visible_index_buffer) { + cvector_reserve(intern->visible_index_buffer, visible_count); + } + else if (cvector_capacity(intern->visible_index_buffer) < visible_count) { + cvector_reserve(intern->visible_index_buffer, visible_count); + } + visible_indices = intern->visible_index_buffer; + } + + // collect the visible instance indices + // aka do frustum culling + visible_count = phpglfw_drawcall_collect_visible_indices(intern, visible_indices); + + // nothing visible = nothing to do + if (visible_count == 0) { + intern->final_command_count = 0; + intern->final_instance_count = 0; + RETURN_LONG(0); + } + + phpglfw_drawcall_instance * const instances = intern->instances; + const phpglfw_drawcall_mesh * const meshes = intern->meshes; + + // compute the distance to camera for each visible instance + if (intern->camera_position) + { + for (uint32_t i = 0; i < visible_count; i++) + { + const uint32_t instance_index = visible_indices[i]; + phpglfw_drawcall_instance *instance = &instances[instance_index]; + + // calculate distance to camera and apply it to the instance + float distance_sq = phpglfw_drawcall_distance_to_camera_sq(intern->camera_position, instance->transform); + phpglfw_drawcall_set_instance_distance(intern, instance, sqrtf(distance_sq)); + phpglfw_drawcall_compute_sort_key(intern, instance); + } + } + + // eval LOD selection + phpglfw_drawcall_refresh_instance_lods(intern); + + // sort the visible indices by precomputed sort keys + if (visible_count > 1) { + instances_global = instances; + qsort(visible_indices, visible_count, sizeof(uint32_t), phpglfw_index_compare_by_sort_key); + } + + // prepare transform buffer data + const size_t total_transform_floats = (size_t)visible_count * PHPGLFW_TRANSFORM_STRIDE; + cvector_ensure(intern->rendering_transform_buffer_data, total_transform_floats); + float *transform_write_ptr = intern->rendering_transform_buffer_data; + size_t transform_cursor = 0; + + // prepare payload buffer data if bound + float *payload_write_ptr = NULL; + size_t payload_cursor = 0; + bool has_payload = !Z_ISUNDEF(intern->payload_data_buffer_zv); + + if (has_payload) { + const size_t total_payload_floats = (size_t)visible_count * intern->payload_data_stride; + cvector_ensure(intern->rendering_payload_buffer_data, total_payload_floats); + payload_write_ptr = intern->rendering_payload_buffer_data; + + // build payload data for visible instances + phpglfw_buffer_glfloat_object *payload_source = phpglfw_buffer_glfloat_objectptr_from_zobj_p(Z_OBJ(intern->payload_data_buffer_zv)); + for (uint32_t i = 0; i < visible_count; i++) { + uint32_t source_offset = visible_indices[i] * intern->payload_data_stride; + + // ensure we don't read beyond the source buffer + if (source_offset + intern->payload_data_stride <= cvector_size(payload_source->vec)) { + for (uint32_t p = 0; p < intern->payload_data_stride; p++) { + payload_write_ptr[payload_cursor + p] = payload_source->vec[source_offset + p]; + } + } + payload_cursor += intern->payload_data_stride; + } + payload_cursor = 0; // reset for batch processing + } + + uint32_t current_vao = 0; + + // build draw call commands and execute them directly + for (uint32_t base_pos = 0; base_pos < visible_count;) + { + uint32_t base_index = visible_indices[base_pos]; + const phpglfw_drawcall_instance *base_instance = &instances[base_index]; + const phpglfw_drawcall_mesh *mesh = &meshes[base_instance->mesh_handle]; + uint32_t command_flags = base_instance->flags; + const uint64_t batch_group_key = base_instance->group_key; + bool can_auto_instance = intern->auto_instancing && !(command_flags & PHPGLFW_FLAG_DISABLE_INSTANCING); + uint32_t batch_pos = base_pos + 1; + + if (can_auto_instance) + { + while (batch_pos < visible_count) + { + const phpglfw_drawcall_instance *candidate = &instances[visible_indices[batch_pos]]; + if (candidate->group_key != batch_group_key) { + break; + } + batch_pos++; + } + } + + const uint32_t batch_size = batch_pos - base_pos; + const uint32_t draw_count = mesh->index_count > 0 ? mesh->index_count : mesh->vertex_count; + + if (mesh->vao_id != current_vao) { + glBindVertexArray(mesh->vao_id); + current_vao = mesh->vao_id; + } + + // populate transform buffer for this batch + for (uint32_t cursor = base_pos; cursor < batch_pos; cursor++) + { + const phpglfw_drawcall_instance * const instance = &instances[visible_indices[cursor]]; + const mat4x4 * const transform = &instance->transform; + float *transform_dst = transform_write_ptr + transform_cursor; + + // unroll matrix copy + transform_dst[0] = (*transform)[0][0]; + transform_dst[1] = (*transform)[0][1]; + transform_dst[2] = (*transform)[0][2]; + transform_dst[3] = (*transform)[0][3]; + transform_dst[4] = (*transform)[1][0]; + transform_dst[5] = (*transform)[1][1]; + transform_dst[6] = (*transform)[1][2]; + transform_dst[7] = (*transform)[1][3]; + transform_dst[8] = (*transform)[2][0]; + transform_dst[9] = (*transform)[2][1]; + transform_dst[10] = (*transform)[2][2]; + transform_dst[11] = (*transform)[2][3]; + transform_dst[12] = (*transform)[3][0]; + transform_dst[13] = (*transform)[3][1]; + transform_dst[14] = (*transform)[3][2]; + transform_dst[15] = (*transform)[3][3]; + + transform_cursor += PHPGLFW_TRANSFORM_STRIDE; + } + + // upload transform data to GPU using internal VBO + if (intern->internal_transform_vbo == 0) { + zend_throw_error(NULL, "bindTransformBuffer must be called before execute, seeing this error means you probably forgot to bind your VAO to the DrawCallAssembler."); + RETURN_THROWS(); + } + glBindBuffer(GL_ARRAY_BUFFER, intern->internal_transform_vbo); + glBufferData(GL_ARRAY_BUFFER, batch_size * PHPGLFW_TRANSFORM_STRIDE * sizeof(float), transform_write_ptr + (instance_offset * PHPGLFW_TRANSFORM_STRIDE), GL_DYNAMIC_DRAW); + + // upload payload data to GPU if bound + if (has_payload && intern->internal_payload_vbo != 0) { + uint32_t payload_data_size = batch_size * intern->payload_data_stride * sizeof(float); + uint32_t payload_offset = instance_offset * intern->payload_data_stride; + + glBindBuffer(GL_ARRAY_BUFFER, intern->internal_payload_vbo); + glBufferData(GL_ARRAY_BUFFER, payload_data_size, payload_write_ptr + payload_offset, GL_DYNAMIC_DRAW); + } + + // prepare callback arguments: mesh_handle, material_id, instance_offset, instance_count, flags + zval args[5]; + ZVAL_LONG(&args[0], base_instance->mesh_handle); + ZVAL_LONG(&args[1], base_instance->material_id); + ZVAL_LONG(&args[2], instance_offset); + ZVAL_LONG(&args[3], batch_size); + ZVAL_LONG(&args[4], command_flags); + + fci.param_count = 5; + fci.params = args; + + zval retval; + fci.retval = &retval; + + // call the user callback + if (zend_call_function(&fci, &fci_cache) == FAILURE) + { + zval_ptr_dtor(&retval); + RETURN_THROWS(); + } + + zval_ptr_dtor(&retval); + + // issue the actual draw call + if (mesh->index_count > 0) + { + // indexed draw + glDrawElementsInstancedBaseVertex( + mesh->primitive, + draw_count, + GL_UNSIGNED_INT, + (void*)(mesh->index_offset * sizeof(uint32_t)), + batch_size, + mesh->vertex_offset + ); + } + else + { + // array draw + glDrawArraysInstanced( + mesh->primitive, + mesh->vertex_offset, + draw_count, + batch_size + ); + } + + instance_offset += batch_size; + command_count++; + base_pos = batch_pos; + } + + // only store back visible_indices if we used heap allocation + if (use_frustum_culling && !use_stack_allocation) { + intern->visible_index_buffer = visible_indices; + } + + intern->final_command_count = command_count; + intern->final_instance_count = instance_offset; + + RETURN_LONG(command_count); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, commandCount) +{ + if (zend_parse_parameters_none() == FAILURE) + { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + RETURN_LONG(intern->final_command_count); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, instanceCount) +{ + if (zend_parse_parameters_none() == FAILURE) + { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + RETURN_LONG(intern->instance_count); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, builtInstanceCount) +{ + if (zend_parse_parameters_none() == FAILURE) + { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + RETURN_LONG(intern->final_instance_count); +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, clearMeshes) +{ + if (zend_parse_parameters_none() == FAILURE) + { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + intern->mesh_count = 0; +} + +PHP_METHOD(GL_Rendering_DrawCallAssembler, reset) +{ + if (zend_parse_parameters_none() == FAILURE) + { + RETURN_THROWS(); + } + + phpglfw_drawcall_assembler_object *intern = phpglfw_drawcall_assembler_objectptr_from_zobj_p(Z_OBJ_P(ZEND_THIS)); + + intern->mesh_count = 0; + intern->instance_count = 0; + intern->final_command_count = 0; + intern->final_instance_count = 0; + if (intern->visible_index_buffer) { + cvector_set_size(intern->visible_index_buffer, 0); + } + + cvector_set_size(intern->command_buffer->vec, 0); + cvector_set_size(intern->instance_transform_buffer->vec, 0); + cvector_set_size(intern->instance_meta_buffer->vec, 0); + cvector_set_size(intern->instance_payload_buffer->vec, 0); +} + +void phpglfw_register_drawcall_assembler_module(INIT_FUNC_ARGS) +{ + // register class using stub-generated function + phpglfw_drawcall_assembler_ce = register_class_GL_Rendering_DrawCallAssembler(); + phpglfw_drawcall_assembler_ce->create_object = phpglfw_drawcall_assembler_create_object; + + // setup object handlers + memcpy(&phpglfw_drawcall_assembler_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + phpglfw_drawcall_assembler_object_handlers.offset = XtOffsetOf(phpglfw_drawcall_assembler_object, std); + phpglfw_drawcall_assembler_object_handlers.free_obj = phpglfw_drawcall_assembler_free_handler; +} \ No newline at end of file diff --git a/phpglfw_drawcall_assembler.h b/phpglfw_drawcall_assembler.h new file mode 100644 index 00000000..b9309864 --- /dev/null +++ b/phpglfw_drawcall_assembler.h @@ -0,0 +1,181 @@ +/** + * PHP-glfw + * + * Extension: DrawCallAssembler + * + * Copyright (c) 2018-2024 Mario Döring + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef PHP_GLFW_DRAWCALL_ASSEMBLER_H +#define PHP_GLFW_DRAWCALL_ASSEMBLER_H + +#include "php.h" +#include "phpglfw_math.h" +#include "phpglfw_buffer.h" + +// sort mode constants +#define PHPGLFW_SORT_NONE 0 +#define PHPGLFW_SORT_FRONT_TO_BACK 1 +#define PHPGLFW_SORT_BACK_TO_FRONT 2 +#define PHPGLFW_SORT_DEPTH_PREPASS 3 + +// sort key bit packing constants +#define SK_PASS_BITS 2 +#define SK_PROG_BITS 12 +#define SK_MAT_BITS 14 +#define SK_VAO_BITS 12 +#define SK_MESH_BITS 14 +#define SK_DEPTH_BITS 10 + +// sort key bit shifts (MSB -> LSB) +#define SK_DEPTH_SHIFT 0 +#define SK_MESH_SHIFT (SK_DEPTH_SHIFT + SK_DEPTH_BITS) // 10 +#define SK_VAO_SHIFT (SK_MESH_SHIFT + SK_MESH_BITS) // 24 +#define SK_MAT_SHIFT (SK_VAO_SHIFT + SK_VAO_BITS) // 36 +#define SK_PROG_SHIFT (SK_MAT_SHIFT + SK_MAT_BITS) // 50 +#define SK_PASS_SHIFT (SK_PROG_SHIFT + SK_PROG_BITS) // 62 + +// sort key bit masks +#define SK_MASK(bits) ((1ULL << (bits)) - 1ULL) + +// render passes (kept to 2 bits via SK_PASS_BITS) +#define PHPGLFW_PASS_OPAQUE 0 +#define PHPGLFW_PASS_TRANSPARENT 1 +#define PHPGLFW_PASS_DEPTH 2 +#define PHPGLFW_PASS_USER 3 + +// draw flags +#define PHPGLFW_FLAG_IGNORE_CULLING 2 +#define PHPGLFW_FLAG_DISABLE_INSTANCING 4 +#define PHPGLFW_FLAG_CUSTOM_SORT_KEY 8 + +// buffer strides +#define PHPGLFW_COMMAND_STRIDE 8 +#define PHPGLFW_TRANSFORM_STRIDE 16 +#define PHPGLFW_INSTANCE_META_STRIDE 4 + +// performance constants +#define PHPGLFW_SMALL_BUFFER_THRESHOLD 256 +#define PHPGLFW_LOD_CACHE_EPSILON 0.01f + +typedef struct _phpglfw_drawcall_mesh { + uint32_t vao_id; + uint32_t vertex_offset; + uint32_t vertex_count; + uint32_t index_offset; + uint32_t index_count; + phpglfw_math_vec3_object *aabb_min; + phpglfw_math_vec3_object *aabb_max; + uint32_t material_hint; + uint32_t primitive; + bool has_bounds; + float bounds_center[3]; + float bounds_radius; + + // lod table + phpglfw_buffer_glfloat_object *lod_distances; + phpglfw_buffer_gluint_object *lod_handles; +} phpglfw_drawcall_mesh; + +typedef struct _phpglfw_drawcall_instance { + uint32_t base_mesh_handle; + uint32_t mesh_handle; + uint32_t material_id; + uint32_t pass; + uint32_t program_id; + uint32_t user_id; + uint32_t flags; + mat4x4 transform; + float sort_distance; + float sort_bias; + // lod caching + float cached_lod_distance; + uint32_t cached_lod_handle; + // precomputed sort key for fast sorting + uint64_t sort_key; + // precomputed group key for batching + uint64_t group_key; +} phpglfw_drawcall_instance; + +typedef struct _phpglfw_drawcall_assembler_object { + // buffers exposed to php + phpglfw_buffer_gluint_object *command_buffer; + phpglfw_buffer_glfloat_object *instance_transform_buffer; + phpglfw_buffer_gluint_object *instance_meta_buffer; + phpglfw_buffer_glfloat_object *instance_payload_buffer; + + // payload data buffer (for user-defined per-instance data) + // this one holds the source data, the instance_payload_buffer is the GPU buffer + // after culling and reordering + zval payload_data_buffer_zv; + uint32_t payload_data_stride; // offset in floats (not bytes, currently only float buffers supported) + + // internal state + phpglfw_drawcall_mesh *meshes; + uint32_t mesh_capacity; + uint32_t mesh_count; + + phpglfw_drawcall_instance *instances; + uint32_t instance_capacity; + uint32_t instance_count; + + // buffer where we store the visible instance indices during culling + // we reuse this buffer each frame to avoid reallocations + uint32_t *visible_index_buffer; + + // frame state + phpglfw_math_vec3_object *camera_position; + phpglfw_math_mat4_object *view_matrix; + phpglfw_math_mat4_object *projection_matrix; + zval camera_position_zv; + zval view_matrix_zv; + zval projection_matrix_zv; + + // frustum planes (left, right, bottom, top, near, far) + vec4 frustum_planes[6]; + bool has_frustum; + + // settings + int sort_mode; + bool auto_instancing; + + // rendering dispatch + cvector_vector_type(float) rendering_transform_buffer_data; + cvector_vector_type(float) rendering_payload_buffer_data; + + // internal vbo for transform data + GLuint internal_transform_vbo; + + // internal vbo for payload data + GLuint internal_payload_vbo; + + // output stats + uint32_t final_command_count; + uint32_t final_instance_count; + + zend_object std; +} phpglfw_drawcall_assembler_object; + +zend_class_entry *phpglfw_get_drawcall_assembler_ce(); +phpglfw_drawcall_assembler_object *phpglfw_drawcall_assembler_objectptr_from_zobj_p(zend_object* obj); + +void phpglfw_register_drawcall_assembler_module(INIT_FUNC_ARGS); + +#endif \ No newline at end of file diff --git a/phpglfw_functions.c b/phpglfw_functions.c index ac38400a..04043bd0 100644 --- a/phpglfw_functions.c +++ b/phpglfw_functions.c @@ -418,39 +418,38 @@ static void phpglfw_glfwvidmode_object_free(zend_object *intern) zval *phpglfw_glfwvidmode_object_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv) { phpglfw_glfwvidmode_object *intern = phpglfw_glfwvidmode_objectptr_from_zobj_p(object); - zval *retval = &EG(uninitialized_zval); if (intern->glfwvidmode) { if (zend_string_equals_literal(member, "width")) { - ZVAL_LONG(retval, intern->glfwvidmode->width); - return retval; + ZVAL_LONG(rv, intern->glfwvidmode->width); + return rv; } else if (zend_string_equals_literal(member, "height")) { - ZVAL_LONG(retval, intern->glfwvidmode->height); - return retval; + ZVAL_LONG(rv, intern->glfwvidmode->height); + return rv; } else if (zend_string_equals_literal(member, "redBits")) { - ZVAL_LONG(retval, intern->glfwvidmode->redBits); - return retval; + ZVAL_LONG(rv, intern->glfwvidmode->redBits); + return rv; } else if (zend_string_equals_literal(member, "greenBits")) { - ZVAL_LONG(retval, intern->glfwvidmode->greenBits); - return retval; + ZVAL_LONG(rv, intern->glfwvidmode->greenBits); + return rv; } else if (zend_string_equals_literal(member, "blueBits")) { - ZVAL_LONG(retval, intern->glfwvidmode->blueBits); - return retval; + ZVAL_LONG(rv, intern->glfwvidmode->blueBits); + return rv; } else if (zend_string_equals_literal(member, "refreshRate")) { - ZVAL_LONG(retval, intern->glfwvidmode->refreshRate); - return retval; + ZVAL_LONG(rv, intern->glfwvidmode->refreshRate); + return rv; } else { zend_throw_error(NULL, "Trying to access invalid property '%s' on GLFWvidmode", ZSTR_VAL(member)); } } - return retval; + return &EG(uninitialized_zval); } diff --git a/phpglfw_texture.c b/phpglfw_texture.c index 9df720f9..9ce445a8 100644 --- a/phpglfw_texture.c +++ b/phpglfw_texture.c @@ -62,7 +62,7 @@ zend_object *phpglfw_texture2d_create_handler(zend_class_entry *class_type) intern->std.handlers = &phpglfw_texture2d_handlers; ZVAL_UNDEF(&intern->buffer_zval); - object_init_ex(&intern->buffer_zval, phpglfw_get_buffer_glubyte_ce()); + intern->is_hdr = 0; return &intern->std; } @@ -81,7 +81,7 @@ static HashTable *phpglfw_texture2d_debug_info_handler(zend_object *object, int zval zv; HashTable *ht; - ht = zend_new_array(3); + ht = zend_new_array(4); *is_temp = 1; ZVAL_LONG(&zv, intern->width); @@ -92,7 +92,9 @@ static HashTable *phpglfw_texture2d_debug_info_handler(zend_object *object, int ZVAL_LONG(&zv, intern->channels); zend_hash_str_update(ht, "channels", sizeof("channels") - 1, &zv); - + + ZVAL_BOOL(&zv, intern->is_hdr); + zend_hash_str_update(ht, "isHdr", sizeof("isHdr") - 1, &zv); return ht; } @@ -114,42 +116,56 @@ PHP_METHOD(GL_Texture_Texture2D, fromDisk) object_init_ex(return_value, phpglfw_get_texture_texture2d_ce()); phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(return_value)); - phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); - stbi_set_flip_vertically_on_load(flip_vertically); + stbi_set_flip_vertically_on_load(flip_vertically); - unsigned char *data = stbi_load(path, &intern->width, &intern->height, &intern->channels, force_channels); - size_t buffersize; - if (data == NULL) { - zend_throw_error(NULL, "Failed to load image from disk '%s'.", path); - return; - } + // we do a quick check if the image is HDR or LDR + // and then either allocate a FloatBuffer or UByteBuffer + if (stbi_is_hdr(path)) { + // HD arrrrrr + object_init_ex(&intern->buffer_zval, phpglfw_get_buffer_glfloat_ce()); + phpglfw_buffer_glfloat_object *buffer = phpglfw_buffer_glfloat_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); + intern->is_hdr = 1; - // in our case we overwrite the channels with the forced channels - // because from userland we care about the final resulting buffer - if (force_channels > 0) { - intern->channels = force_channels; - } + float *data = stbi_loadf(path, &intern->width, &intern->height, &intern->channels, force_channels); + if (data == NULL) { + zend_throw_error(NULL, "Failed to load HDR image from disk '%s'.", path); + return; + } - buffersize = intern->width * intern->height * intern->channels; + if (force_channels > 0) { + intern->channels = force_channels; + } - // php_printf("%s: %i x %i x %i, (%i)", path, intern->width, intern->height, intern->channels, buffersize); + size_t buffersize = intern->width * intern->height * intern->channels; + cvector_reserve(buffer->vec, buffersize); + memcpy(buffer->vec, data, buffersize * sizeof(float)); + cvector_set_size(buffer->vec, buffersize); + + efree(data); + } else { + // lllowwwww + object_init_ex(&intern->buffer_zval, phpglfw_get_buffer_glubyte_ce()); + phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); + intern->is_hdr = 0; + + unsigned char *data = stbi_load(path, &intern->width, &intern->height, &intern->channels, force_channels); + if (data == NULL) { + zend_throw_error(NULL, "Failed to load image from disk '%s'.", path); + return; + } - // prealloc space - cvector_reserve(buffer->vec, buffersize); + if (force_channels > 0) { + intern->channels = force_channels; + } - // copy the data over to the buffer - // please if somebody finds a way to avoid this copy, let me know - memcpy(buffer->vec, data, buffersize * sizeof(unsigned char)); - cvector_set_size(buffer->vec, buffersize); - // for (size_t i = 0; i < buffersize; i++) { - // cvector_push_back(buffer->vec, data[i]); - // } + size_t buffersize = intern->width * intern->height * intern->channels; + cvector_reserve(buffer->vec, buffersize); + memcpy(buffer->vec, data, buffersize * sizeof(unsigned char)); + cvector_set_size(buffer->vec, buffersize); - // free the stb allocated memory - efree(data); - // php_printf("buffer cap %i\n", cvector_capacity(buffer->vec)); - // buffer->vec = data; + efree(data); + } } PHP_METHOD(GL_Texture_Texture2D, fromBuffer) @@ -181,6 +197,39 @@ PHP_METHOD(GL_Texture_Texture2D, fromBuffer) intern->width = width; intern->height = height; intern->channels = channels; + intern->is_hdr = 0; +} + +PHP_METHOD(GL_Texture_Texture2D, fromBufferHDR) +{ + zend_long width, height, channels = 4; + zval *buffer_zval; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "llO|l", &width, &height, &buffer_zval, phpglfw_get_buffer_glfloat_ce(), &channels) == FAILURE) { + RETURN_THROWS(); + } + + if (channels > 4 || channels < 1) { + zend_throw_error(NULL, "Invalid number of channels. Must be between 1 and 4."); + return; + } + + // check if the buffer is big enough, size matters.. + phpglfw_buffer_glfloat_object *buffer = phpglfw_buffer_glfloat_objectptr_from_zobj_p(Z_OBJ_P(buffer_zval)); + + if (cvector_size(buffer->vec) < width * height * channels) { + zend_throw_error(NULL, "Buffer is too small to hold the HDR image data."); + return; + } + + object_init_ex(return_value, phpglfw_get_texture_texture2d_ce()); + phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(return_value)); + + ZVAL_COPY(&intern->buffer_zval, buffer_zval); + + intern->width = width; + intern->height = height; + intern->channels = channels; + intern->is_hdr = 1; } PHP_METHOD(GL_Texture_Texture2D, width) @@ -201,6 +250,12 @@ PHP_METHOD(GL_Texture_Texture2D, channels) RETURN_LONG(intern->channels); } +PHP_METHOD(GL_Texture_Texture2D, isHDR) +{ + phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(getThis())); + RETURN_BOOL(intern->is_hdr); +} + PHP_METHOD(GL_Texture_Texture2D, buffer) { phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(getThis())); @@ -214,12 +269,18 @@ PHP_METHOD(GL_Texture_Texture2D, buffer) PHP_METHOD(GL_Texture_Texture2D, writeJPG) { phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(getThis())); - phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); const char *path; size_t path_size; if (zend_parse_parameters(ZEND_NUM_ARGS() , "s", &path, &path_size) == FAILURE) { RETURN_THROWS(); } + + if (intern->is_hdr) { + zend_throw_error(NULL, "Cannot write HDR texture to JPG format. Use writeHDR() instead or convert to LDR first."); + return; + } + + phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); stbi_flip_vertically_on_write(1); stbi_write_jpg(path, intern->width, intern->height, intern->channels, buffer->vec, 100); } @@ -227,12 +288,18 @@ PHP_METHOD(GL_Texture_Texture2D, writeJPG) PHP_METHOD(GL_Texture_Texture2D, writePNG) { phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(getThis())); - phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); const char *path; size_t path_size; if (zend_parse_parameters(ZEND_NUM_ARGS() , "s", &path, &path_size) == FAILURE) { RETURN_THROWS(); } + + if (intern->is_hdr) { + zend_throw_error(NULL, "Cannot write HDR texture to PNG format. Use writeHDR() instead or convert to LDR first."); + return; + } + + phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); stbi_flip_vertically_on_write(1); stbi_write_png(path, intern->width, intern->height, intern->channels, buffer->vec, 0); } @@ -240,12 +307,18 @@ PHP_METHOD(GL_Texture_Texture2D, writePNG) PHP_METHOD(GL_Texture_Texture2D, writeBMP) { phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(getThis())); - phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); const char *path; size_t path_size; if (zend_parse_parameters(ZEND_NUM_ARGS() , "s", &path, &path_size) == FAILURE) { RETURN_THROWS(); } + + if (intern->is_hdr) { + zend_throw_error(NULL, "Cannot write HDR texture to BMP format. Use writeHDR() instead or convert to LDR first."); + return; + } + + phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); stbi_flip_vertically_on_write(1); stbi_write_bmp(path, intern->width, intern->height, intern->channels, buffer->vec); } @@ -253,16 +326,41 @@ PHP_METHOD(GL_Texture_Texture2D, writeBMP) PHP_METHOD(GL_Texture_Texture2D, writeTGA) { phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(getThis())); - phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); const char *path; size_t path_size; if (zend_parse_parameters(ZEND_NUM_ARGS() , "s", &path, &path_size) == FAILURE) { RETURN_THROWS(); } + + if (intern->is_hdr) { + zend_throw_error(NULL, "Cannot write HDR texture to TGA format. Use writeHDR() instead or convert to LDR first."); + return; + } + + phpglfw_buffer_glubyte_object *buffer = phpglfw_buffer_glubyte_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); stbi_flip_vertically_on_write(1); stbi_write_tga(path, intern->width, intern->height, intern->channels, buffer->vec); } +PHP_METHOD(GL_Texture_Texture2D, writeHDR) +{ + phpglfw_texture2d_object *intern = phpglfw_texture2d_objectptr_from_zobj_p(Z_OBJ_P(getThis())); + const char *path; + size_t path_size; + if (zend_parse_parameters(ZEND_NUM_ARGS() , "s", &path, &path_size) == FAILURE) { + RETURN_THROWS(); + } + + if (!intern->is_hdr) { + zend_throw_error(NULL, "Cannot write LDR texture to HDR format. Use writePNG() or other LDR formats instead."); + return; + } + + phpglfw_buffer_glfloat_object *buffer = phpglfw_buffer_glfloat_objectptr_from_zobj_p(Z_OBJ_P(&intern->buffer_zval)); + stbi_flip_vertically_on_write(1); + stbi_write_hdr(path, intern->width, intern->height, intern->channels, buffer->vec); +} + PHP_METHOD(GL_Noise, perlin) { double x, y, z; diff --git a/phpglfw_texture.h b/phpglfw_texture.h index 5460e694..8cce3114 100644 --- a/phpglfw_texture.h +++ b/phpglfw_texture.h @@ -33,6 +33,7 @@ typedef struct _phpglfw_texture2d_object { int width; int height; int channels; + int is_hdr; // 1 if the texture is HDR (FloatBuffer), 0 if LDR (UByteBuffer) zend_object std; } phpglfw_texture2d_object; diff --git a/ogt_vox_c_wrapper.cpp b/src/ogt_vox_c_wrapper.cpp similarity index 100% rename from ogt_vox_c_wrapper.cpp rename to src/ogt_vox_c_wrapper.cpp diff --git a/stubs/phpglfw.php b/stubs/phpglfw.php index 42a4ba95..b3e6ec13 100644 --- a/stubs/phpglfw.php +++ b/stubs/phpglfw.php @@ -1946,6 +1946,346 @@ public function dump() : string {} } }; +namespace GL\Rendering +{ + /** + * The draw call assembler will help you to generate efficient draw call batches for your meshes. + * + * This particular workload is quite costly in user-land PHP, this class takes care of this common problem + * in a efficient manner specifically designed for PHP-GLFW. + * + * What it can do: + * - Batch meshes that share the same material. + * - Sort meshes based on distance to camera, what shader they use etc. + * - Generate optimal draw call lists to minimize state changes in OpenGL. + * - Auto instance meshes that are used multiple times. + * - Frustum cull meshes based on the camera. + * - LOD support based on distance to camera. + */ + class DrawCallAssembler + { + /** @var int */ + public const SORT_NONE = 0; + /** @var int */ + public const SORT_FRONT_TO_BACK = 1; + /** @var int */ + public const SORT_BACK_TO_FRONT = 2; + + /** @var int */ + public const PASS_OPAQUE = 0; + /** @var int */ + public const PASS_TRANSPARENT = 1; + /** @var int */ + public const PASS_DEPTH = 2; + /** @var int */ + public const PASS_USER = 3; + + /** @var int */ + public const FLAG_IGNORE_CULLING = 2; + /** @var int */ + public const FLAG_DISABLE_INSTANCING = 4; + /** @var int */ + public const FLAG_CUSTOM_SORT_KEY = 8; + + /** + * Packed draw commands represented as uint32 values. Filled after calling "build". + * + * Layout per command (uint32 stride = 8): + * 0 => mesh handle + * 1 => VAO id for the draw + * 2 => index offset (0 when drawing arrays) + * 3 => index or vertex count + * 4 => base vertex offset + * 5 => instance offset inside the transform buffer + * 6 => instance count (1 for non-instanced draws) + * 7 => material identifier used for batching + */ + public readonly \GL\Buffer\UIntBuffer $commandBuffer; + + /** + * Packed transform buffer storing Mat4 entries per instance. + * + * Note: This only contains visible instances, determined by the last "build" call. + */ + public readonly \GL\Buffer\FloatBuffer $instanceTransformBuffer; + + /** + * Packed per-instance metadata. + * Layout per instance (uint32 stride = 4): + * 0 => mesh handle + * 1 => material id + * 2 => custom user id (optional) + * 3 => draw flags snapshot + * + * Note: This only contains visible instances, determined by the last "build" call. + */ + public readonly \GL\Buffer\UIntBuffer $instanceMetaBuffer; + + /** + * Optional floating point payload buffer that can store arbitrary per-instance attributes + * (colors, skinning weights, etc.). + * + * Layout per instance (float stride = N): + */ + public readonly \GL\Buffer\FloatBuffer $instancePayloadBuffer; + + /** + * The command buffer stride (in uint32 components) of one draw command entry. + */ + public readonly int $commandStride; + + /** + * The instance transform buffer stride (in float components) of one transform entry. + */ + public readonly int $transformStride; + + /** + * The instance metadata buffer stride (in uint32 components) of one metadata entry. + */ + public readonly int $instanceMetaStride; + + /** + * Create a new assembler instance. + * + * @param int $initialMeshCapacity Number of mesh slots preallocated in the registry. + * @param int $initialInstanceCapacity Number of instance slots backed by the buffers. + * @param int $initialCommandCapacity Number of draw command slots. + */ + public function __construct( + int $initialMeshCapacity = 256, + int $initialInstanceCapacity = 2048, + int $initialCommandCapacity = 512 + ) {} + + /** + * Enable or disable automatic instancing for meshes that are submitted multiple times per frame. + */ + public function setAutoInstancing(bool $enabled) : void {} + + /** + * Select the ordering applied to opaque draw calls. + */ + public function setSortMode(int $mode) : void {} + + /** + * Update the camera state used for sorting, frustum culling and LOD evaluation. + * + * @param ?\GL\Math\Vec3 $cameraPosition Camera position used for LOD selection. + * @param ?\GL\Math\Mat4 $viewMatrix View matrix used for frustum generation. + * @param ?\GL\Math\Mat4 $projectionMatrix Projection matrix used for frustum generation. + */ + public function setCameraData( + ?\GL\Math\Vec3 $cameraPosition = null, + ?\GL\Math\Mat4 $viewMatrix = null, + ?\GL\Math\Mat4 $projectionMatrix = null + ) : void {} + + /** + * Manually configure the frustum to use for culling tasks. + */ + public function setFrustumPlanes( + \GL\Math\Vec4 $left, + \GL\Math\Vec4 $right, + \GL\Math\Vec4 $bottom, + \GL\Math\Vec4 $top, + \GL\Math\Vec4 $near, + \GL\Math\Vec4 $far + ) : void {} + + /** + * Register a mesh in the assembler's registry and receive a numeric handle. + * + * You need to register your meshes so you can submit instances of them later. + * + * You are the owner of any passed references, the assembler will not modify or delete them. + * + * @param int $vao OpenGL vertex array object handle that encapsulates buffers and layout. + * @param int $vertexOffset Starting vertex inside the bound VBO (0 based). + * @param int $vertexCount Number of vertices to draw when the mesh is rendered without indices. + * @param int $indexOffset Starting index inside the EBO (set to 0 to draw arrays instead). + * @param int $indexCount Number of indices used for indexed rendering (0 falls back to $vertexCount arrays). + * @param ?\GL\Math\Vec3 $aabbMin Optional minimum point of the mesh bounds (for frustum culling). + * @param ?\GL\Math\Vec3 $aabbMax Optional maximum point of the mesh bounds. + * @param int $materialHint Optional default material id that is applied when submit() omits a material. + * @param int $primitive GL primitive value (defaults to GL_TRIANGLES = 0x0004). + * + * @return int Numeric handle for reference + */ + public function registerMesh( + int $vao, + int $vertexOffset = 0, + int $vertexCount = 0, + int $indexOffset = 0, + int $indexCount = 0, + ?\GL\Math\Vec3 $aabbMin = null, + ?\GL\Math\Vec3 $aabbMax = null, + int $materialHint = 0, + int $primitive = 0x0004 + ) : int {} + + /** + * Update or override the default material identifier attached to a mesh. + */ + public function setMeshMaterial(int $meshHandle, int $materialId) : void {} + + /** + * Attach LOD alternatives to a mesh. + * + * @param int $meshHandle Base mesh handle (the one that should be replaced) returned by {@see registerMesh}. + * @param \GL\Buffer\FloatBuffer $distanceThresholds Distances in ascending order. + * @param \GL\Buffer\UIntBuffer $meshHandles Mesh handles for each LOD level. + */ + public function setLodTable( + int $meshHandle, + \GL\Buffer\FloatBuffer $distanceThresholds, + \GL\Buffer\UIntBuffer $meshHandles + ) : void {} + + /** + * Binds and sets up the transform buffer for instanced rendering. + * + * This method: + * - creates or reuses an internal VBO for transform data + * - sets up vertex attribute pointers for the transform matrix (4x Vec4 attributes) + * - returns the next available attribute location offset + * + * ```php + * $nextOffset = $assembler->bindTransformBuffer($vao, 2); + * // transform matrix now uses attributes 2, 3, 4, 5 + * // next custom attribute can use location $nextOffset (6 in this case) + * ``` + * + * @param int $vao The vertex array object to configure. + * @param int $offset The starting vertex attribute location (default: 2). + * @return int The next available attribute location after the transform matrix. + */ + public function bindTransformBuffer(int $vao, int $offset = 1) : int {} + + /** + * Binds payload data for per-instance custom attributes. + * + * Don't forget to call {@see bindPayloadBuffer()} to set up the attributes after calling {@see build()}. + * + * This method stores a reference to a FloatBuffer containing custom per-instance data + * (colors, skinning weights, etc.) and specifies how many floats comprise each instance's payload. + * The data will be available in the instancePayloadBuffer after calling build(). + * + * @param \GL\Buffer\FloatBuffer $payloadBuffer Buffer containing per-instance payload data. + * @param int $stride Number of floats per instance in the payload buffer. + */ + public function bindPayloadData(\GL\Buffer\FloatBuffer $payloadBuffer, int $stride) : void {} + + /** + * Binds and sets up the payload buffer for instanced rendering. + * + * This method sets up vertex attribute pointers for the custom payload data buffer. + * Must be called after bindPayloadData() and build() to properly configure the payload attributes. + * + * @param int $vao The vertex array object to configure. + * @param int $offset The starting vertex attribute location. + * @param int $stride Number of floats per payload entry (0 uses bound payload stride). + * @return int The next available attribute location after the payload attributes. + */ + public function bindPayloadBuffer(int $vao, int $offset, int $stride = 0) : int {} + + /** + * Remove all recorded instances without touching registered meshes. + */ + public function clearInstances() : void {} + + /** + * Record a single mesh instance. + * + * @param int $meshHandle Mesh handle from {@see registerMesh} + * @param \GL\Math\Mat4 $transform World transform + * @param int $materialId Material identifier + * @param int $pass Render pass identifier (use PASS_* constants) + * @param int $programId Optional program/shader identifier used for batching and sorting + * @param int $flags Optional state overrides (FLAG_* bitmask) + * @param float $sortBias Extra distance bias applied when sorting draws + * @param int $userId Optional user supplied id mirrored into the instance metadata buffer + */ + public function submit( + int $meshHandle, + \GL\Math\Mat4 $transform, + int $materialId, + int $pass = 0, + int $programId = 0, + int $flags = 0, + float $sortBias = 0.0, + int $userId = 0 + ) : void {} + + /** + * Build the final draw list. Returns the number of commands generated for this frame. + * + * Will fill the instance buffers: + * - {@see commandBuffer} + * - {@see instanceTransformBuffer} + * - {@see instanceMetaBuffer} + * - {@see instancePayloadBuffer} + * + * Use this if you want to handle and execute the draw calls **manually**. + * Otherwise you can use {@see execute()} to perform the draw calls directly. + */ + public function build() : int {} + + /** + * Executes the draw calls by issuing OpenGL commands. + * + * The given callback will be called before each draw call allowing you to set up materials and other GL state. + * Before calling this method, you must call {@see bindTransformBuffer()} to set up the transform data. + * + * The callback receives the following parameters: + * - int $meshHandle: The mesh handle for this draw call + * - int $materialId: The material identifier for this draw call + * - int $instanceOffset: The offset into the instance transform buffer + * - int $instanceCount: The number of instances to draw + * - int $flags: The draw flags (see FLAG_* constants) + * + * Example usage: + * ```php + * // first bind the transform buffer to set up instancing + * $assembler->bindTransformBuffer($vao, $instanceTransformVBO); + * // ... setup draw loop ... + * // then execute the draw calls + * $assembler->execute(function(int $meshHandle, int $materialId, int $instanceOffset, int $instanceCount, int $flags) { + * // use shaders, bind textures, set uniforms etc.. + * // note: the VAO is bound by the assembler internally, and the draw calls are issued automatically. + * }); + * ``` + * + * @param callable(int,int,int,int,int):void $drawCallback Before draw callback function. + */ + public function execute(callable $drawCallback) : void {} + + /** + * Number of draw commands generated by the previous {@see build()} call. + */ + public function commandCount() : int {} + + /** + * Number of instances submitted so far for the current frame. + */ + public function instanceCount() : int {} + + /** + * Number of instances that participated in the last {@see build()} call. + */ + public function builtInstanceCount() : int {} + + /** + * Completely remove all registered meshes and release buffers. + */ + public function clearMeshes() : void {} + + /** + * Reset both registry and frame state. + */ + public function reset() : void {} + } +}; + namespace GL\Geometry\ObjFileParser { /** @@ -2023,12 +2363,12 @@ class Material public readonly \GL\Math\Vec3 $specular; /** - * The emmisive color of the material + * The emissive color of the material * This property is often also used for illumination, self glow or light emission. * - * @var \GL\Math\Vec3 $emmisive + * @var \GL\Math\Vec3 $emissive */ - public readonly \GL\Math\Vec3 $emmisive; + public readonly \GL\Math\Vec3 $emissive; /** * The transmittance of the material @@ -2537,7 +2877,8 @@ public function getPaletteColor(int $colorIndex) : ?\GL\Math\Vec4 {} { /** * The Texture2D class is part of the PHP-GLFW OpenGL extension. - * It loads images / textures from common formats like PNG, JPG, GIF, BMP, TGA etc. and converts the raw bitmap to a `GL\UByteBuffer` instance. + * It loads images / textures from common formats like PNG, JPG, GIF, BMP, TGA, HDR etc. and converts the raw bitmap to a buffer instance. + * For LDR (Low Dynamic Range) images, a `GL\Buffer\UByteBuffer` is used. For HDR (High Dynamic Range) images, a `GL\Buffer\FloatBuffer` is used. */ class Texture2D { @@ -2550,25 +2891,52 @@ class Texture2D /** * Loads a texture / image from a file on disk and returns a Texture2D object. + * + * This method automatically detects HDR files (.hdr) and loads them into a FloatBuffer. + * All other supported formats (PNG, JPG, GIF, BMP, TGA, etc.) are loaded into a UByteBuffer. * - * @param string $file The path to the image file to load. + * @param string $path The path to the image file to load. + * @param int $requestedChannelCount The number of channels to force. 0 means use the original channel count. + * @param bool $flipVertically Whether to flip the image vertically on load (default: true). * @return \GL\Texture\Texture2D The loaded texture object. */ - public static function fromDisk(string $path) : Texture2D {} + public static function fromDisk(string $path, int $requestedChannelCount = 0, bool $flipVertically = true) : Texture2D {} /** - * Loads a texture / image from a buffer and returns a Texture2D object. + * Loads a texture / image from a UByteBuffer and returns a Texture2D object. * * The buffer is not copied, the Texture2D object will hold a reference to the buffer given. + * + * @param int $width The width of the image. + * @param int $height The height of the image. + * @param \GL\Buffer\UByteBuffer $buffer The buffer containing the image data. + * @param int $channels The number of channels in the image data. + * @return \GL\Texture\Texture2D The created texture object. */ public static function fromBuffer(int $width, int $height, \GL\Buffer\UByteBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} /** - * Returns a reference to the internal `UByteBuffer` instance of the current texture. + * Loads a HDR texture from a FloatBuffer and returns a Texture2D object. + * + * The buffer is not copied, the Texture2D object will hold a reference to the buffer given. + * + * @param int $width The width of the image. + * @param int $height The height of the image. + * @param \GL\Buffer\FloatBuffer $buffer The buffer containing the HDR image data. + * @param int $channels The number of channels in the image data. + * @return \GL\Texture\Texture2D The created HDR texture object. + */ + public static function fromBufferHDR(int $width, int $height, \GL\Buffer\FloatBuffer $buffer, int $channels = Texture2D::CHANNEL_RGBA) : Texture2D {} + + /** + * Returns a reference to the internal buffer instance of the current texture. + * + * For LDR images, this returns a `UByteBuffer`. For HDR images, this returns a `FloatBuffer`. + * Use `isHDR()` to check which type of buffer is returned. * - * @return \GL\UByteBuffer The loaded image data. + * @return \GL\Buffer\UByteBuffer|\GL\Buffer\FloatBuffer The loaded image data. */ - public function buffer() : \GL\Buffer\UByteBuffer {} + public function buffer() : \GL\Buffer\UByteBuffer|\GL\Buffer\FloatBuffer {} /** * Returns the width of the image. @@ -2591,10 +2959,22 @@ public function height() : int {} */ public function channels() : int {} + /** + * Returns whether the texture contains HDR (High Dynamic Range) data. + * + * When true, the buffer() method returns a FloatBuffer. + * When false, the buffer() method returns a UByteBuffer. + * + * @return bool + */ + public function isHDR() : bool {} + /** * Writes the image data to a file on disk. (JPEG) * - * @param string $file The path to the file to write to. + * Note: This method only works with LDR textures. For HDR textures, use writeHDR(). + * + * @param string $path The path to the file to write to. * @param int $quality The quality of the image. (0 - 100) * * @return void @@ -2603,24 +2983,39 @@ public function writeJPG(string $path, int $quality = 100) : void {} /** * Writes the image data to a file on disk. (PNG) + * + * Note: This method only works with LDR textures. For HDR textures, use writeHDR(). * - * @param string $file The path to the file to write to. + * @param string $path The path to the file to write to. */ public function writePNG(string $path) : void {} /** * Writes the image data to a file on disk. (BMP) + * + * Note: This method only works with LDR textures. For HDR textures, use writeHDR(). * - * @param string $file The path to the file to write to. + * @param string $path The path to the file to write to. */ public function writeBMP(string $path) : void {} /** * Writes the image data to a file on disk. (TGA) + * + * Note: This method only works with LDR textures. For HDR textures, use writeHDR(). * - * @param string $file The path to the file to write to. + * @param string $path The path to the file to write to. */ public function writeTGA(string $path) : void {} + + /** + * Writes the HDR image data to a file on disk. (.hdr format) + * + * Note: This method only works with HDR textures. For LDR textures, use writePNG(), writeJPG(), etc. + * + * @param string $path The path to the file to write to. + */ + public function writeHDR(string $path) : void {} } } diff --git a/tests/Buffer/BufferTestCase.php b/tests/Buffer/BufferTestCase.php index da88558e..ed03e136 100644 --- a/tests/Buffer/BufferTestCase.php +++ b/tests/Buffer/BufferTestCase.php @@ -207,6 +207,21 @@ public function testIterator() } } + public function testIteratorUnkeyed() + { + $className = $this->getBufferClass(); + $data = $this->getTestData(); + $buffer = new $className($data); + + $this->assertEquals(count($data), $buffer->size()); + + $i = 0; + foreach($buffer as $value) { + $this->assertEqualBufferValue($data[$i], $value); + $i++; + } + } + public function testSerialisation() { $className = $this->getBufferClass(); diff --git a/tests/Texture/Texture2DTest.php b/tests/Texture/Texture2DTest.php index f793558c..608c2e71 100644 --- a/tests/Texture/Texture2DTest.php +++ b/tests/Texture/Texture2DTest.php @@ -2,6 +2,8 @@ namespace GL\Tests\Texture; +use GL\Buffer\FloatBuffer; +use GL\Buffer\UByteBuffer; use GL\Texture\Texture2D; class Texture2DTest extends \PHPUnit\Framework\TestCase @@ -14,8 +16,11 @@ public function testFromDisk() : void $this->assertEquals(512, $logo->width()); $this->assertEquals(512, $logo->height()); $this->assertEquals(3, $logo->channels()); + $this->assertFalse($logo->isHDR()); $buffer = $logo->buffer(); + $this->assertInstanceOf(UByteBuffer::class, $buffer); + $samples = []; foreach($buffer as $k => $val) { if ($k % 128 === 0) { @@ -25,4 +30,79 @@ public function testFromDisk() : void $this->assertEquals('a0bed380430adca3c3ddaec5e9142516', md5(implode('', $samples))); } + + public function testFromBufferHDRAndWriteHDR() : void + { + $width = 8; + $height = 8; + $channels = 3; + + // we just generate a float buffer to use as our HDR data + $buffer = new FloatBuffer(); + for ($y = 0; $y < $height; $y++) { + for ($x = 0; $x < $width; $x++) { + $r = ($x / $width) * 2.0; // 0.0 -> 2.0 + $g = ($y / $height) * 2.0; // 0.0 -> 2.0 + $b = 0.5; + $buffer->push($r); + $buffer->push($g); + $buffer->push($b); + } + } + + // create texture from our HDR buffer + $texture = Texture2D::fromBufferHDR($width, $height, $buffer, $channels); + + $this->assertEquals($width, $texture->width()); + $this->assertEquals($height, $texture->height()); + $this->assertEquals($channels, $texture->channels()); + $this->assertTrue($texture->isHDR()); + $this->assertInstanceOf(FloatBuffer::class, $texture->buffer()); + + // write to HDR file + $tempFile = sys_get_temp_dir() . '/phpglfw_test_' . uniqid() . '.hdr'; + $texture->writeHDR($tempFile); + + $this->assertFileExists($tempFile); + + // load it back + $loadedTexture = Texture2D::fromDisk($tempFile); + + $this->assertEquals($width, $loadedTexture->width()); + $this->assertEquals($height, $loadedTexture->height()); + $this->assertEquals($channels, $loadedTexture->channels()); + $this->assertTrue($loadedTexture->isHDR()); + + $loadedBuffer = $loadedTexture->buffer(); + $this->assertInstanceOf(FloatBuffer::class, $loadedBuffer); + + // size must match + $this->assertEquals($buffer->size(), $loadedBuffer->size()); + + $tolerance = 0.01; // we allow some tolarance due to file encoding, honestly i did not check the HDR spec... + for ($i = 0; $i < $buffer->size(); $i++) { + $this->assertEqualsWithDelta($buffer[$i], $loadedBuffer[$i], $tolerance); + } + + + unlink($tempFile); + } + + public function testHDRTextureCannotWriteLDRFormats() : void + { + $buffer = new FloatBuffer([1.69, 4.2, 0.69, 4.20, 0.0, 3.14]); + $texture = Texture2D::fromBufferHDR(2, 1, $buffer, 3); + + $this->expectException(\Error::class); + $texture->writePNG(sys_get_temp_dir() . '/test.png'); + } + + public function testLDRTextureCannotWriteHDR() : void + { + $buffer = new UByteBuffer([1, 2, 3, 4, 5, 6]); + $texture = Texture2D::fromBuffer(2, 1, $buffer, 3); + + $this->expectException(\Error::class); + $texture->writeHDR(sys_get_temp_dir() . '/test.hdr'); + } } \ No newline at end of file diff --git a/vendor/cvector/cvector.h b/vendor/cvector/cvector.h index 31bfb61c..cc00c804 100644 --- a/vendor/cvector/cvector.h +++ b/vendor/cvector/cvector.h @@ -198,6 +198,23 @@ cvector_set_size((vec), cvmax(count, cvector_size(vec))); \ } while (0) +/** + * @brief cvector_ensure - make sure the vector has at least the requested size + * @param vec - the vector + * @param size - minimum size to enforce + * @return void + */ +#define cvector_ensure(vec, size) \ + do { \ + size_t cv_req__ = (size); \ + if (cvector_capacity(vec) < cv_req__) { \ + cvector_grow((vec), cv_req__); \ + } \ + if (cvector_size(vec) < cv_req__) { \ + cvector_set_size((vec), cv_req__); \ + } \ + } while (0) + /** * @brief cvector_insert - insert element at position pos to the vector * @param vec - the vector