diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..eb238b52 --- /dev/null +++ b/.clang-format @@ -0,0 +1,21 @@ +BasedOnStyle: Microsoft +Language: Cpp + +IndentWidth: 4 +TabWidth: 4 +UseTab: Never + +ColumnLimit: 100 +PointerAlignment: Left + +SortIncludes: true +IncludeBlocks: Preserve + +NamespaceIndentation: All +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false + +ReflowComments: false +BreakBeforeBraces: Attach + diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 00000000..3f1b8ee3 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,32 @@ +name: Clang Format + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + clang-format: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install clang-format + run: | + sudo apt-get update + sudo apt-get install -y clang-format + + - name: Run clang-format check + run: | + git ls-files | grep -E '\.(h|hpp|c|cpp)$' | xargs clang-format -i + if ! git diff --quiet; then + echo "clang-format produced changes. Please run 'cmake --build build --target format' or the equivalent clang-format command locally and commit the results." + git diff + exit 1 + fi + diff --git a/CMakeLists.txt b/CMakeLists.txt index 37c543e2..2daf29a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,3 +193,19 @@ if(APPLE) target_link_options(${EXEC_NAME} PRIVATE "LINKER:-no_warn_duplicate_libraries") endif() +# ---------------------------------------------------------------------- +# clang-format helper: cmake --build build --target format +# ---------------------------------------------------------------------- +find_program(CLANG_FORMAT_EXECUTABLE NAMES clang-format) + +if(CLANG_FORMAT_EXECUTABLE) + add_custom_target(format + COMMAND git ls-files | grep -E "\\.(h|hpp|c|cpp)$" | xargs ${CLANG_FORMAT_EXECUTABLE} -i + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running clang-format on tracked C/C++ files" + VERBATIM + ) +else() + message(STATUS "clang-format not found; 'format' target will not be available.") +endif() + diff --git a/include/app/BufferUtils.hpp b/include/app/BufferUtils.hpp index 23d9a976..5c94ef02 100644 --- a/include/app/BufferUtils.hpp +++ b/include/app/BufferUtils.hpp @@ -9,77 +9,70 @@ namespace sauce { -struct BufferUtils { - - static void createBuffer( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice, - vk::DeviceSize size, - vk::BufferUsageFlags usage, - vk::MemoryPropertyFlags props, - vk::raii::Buffer& buffer, - vk::raii::DeviceMemory& bufferMemory - ) { - vk::BufferCreateInfo bufferCreateInfo { - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - }; - - buffer = vk::raii::Buffer { *logicalDevice, bufferCreateInfo }; - - vk::MemoryRequirements memoryRequirements = buffer.getMemoryRequirements(); - - vk::MemoryAllocateInfo memoryAllocateInfo { - .allocationSize = memoryRequirements.size, - .memoryTypeIndex = findMemoryType(physicalDevice, memoryRequirements.memoryTypeBits, props), - }; - - bufferMemory = vk::raii::DeviceMemory{ *logicalDevice, memoryAllocateInfo }; - buffer.bindMemory(*bufferMemory, 0); - } - - static uint32_t findMemoryType( - const sauce::PhysicalDevice& physicalDevice, - uint32_t typeFilter, - vk::MemoryPropertyFlags properties - ) { - vk::PhysicalDeviceMemoryProperties deviceMemoryProps = physicalDevice->getMemoryProperties(); - - for (uint32_t i = 0; i < deviceMemoryProps.memoryTypeCount; ++i) { - if ((typeFilter & (1 << i)) && (deviceMemoryProps.memoryTypes[i].propertyFlags & properties) == properties) - return i; - } - - throw std::runtime_error("Failed to find suitable memory type!"); - } - - static void copyBuffer( - const sauce::LogicalDevice& logicalDevice, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue, - vk::raii::Buffer& src, - vk::raii::Buffer& dst, - vk::DeviceSize size - ) { - vk::CommandBufferAllocateInfo copyCommandBufferAllocInfo { - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1, + struct BufferUtils { + static void createBuffer(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice, vk::DeviceSize size, + vk::BufferUsageFlags usage, vk::MemoryPropertyFlags props, + vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { + vk::BufferCreateInfo bufferCreateInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + }; + + buffer = vk::raii::Buffer{*logicalDevice, bufferCreateInfo}; + + vk::MemoryRequirements memoryRequirements = buffer.getMemoryRequirements(); + + vk::MemoryAllocateInfo memoryAllocateInfo{ + .allocationSize = memoryRequirements.size, + .memoryTypeIndex = + findMemoryType(physicalDevice, memoryRequirements.memoryTypeBits, props), + }; + + bufferMemory = vk::raii::DeviceMemory{*logicalDevice, memoryAllocateInfo}; + buffer.bindMemory(*bufferMemory, 0); + } + + static uint32_t findMemoryType(const sauce::PhysicalDevice& physicalDevice, + uint32_t typeFilter, vk::MemoryPropertyFlags properties) { + vk::PhysicalDeviceMemoryProperties deviceMemoryProps = + physicalDevice->getMemoryProperties(); + + for (uint32_t i = 0; i < deviceMemoryProps.memoryTypeCount; ++i) { + if ((typeFilter & (1 << i)) && + (deviceMemoryProps.memoryTypes[i].propertyFlags & properties) == properties) + return i; + } + + throw std::runtime_error("Failed to find suitable memory type!"); + } + + static void copyBuffer(const sauce::LogicalDevice& logicalDevice, + const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue, vk::raii::Buffer& src, + vk::raii::Buffer& dst, vk::DeviceSize size) { + vk::CommandBufferAllocateInfo copyCommandBufferAllocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1, + }; + vk::raii::CommandBuffer copyCommandBuffer = std::move( + logicalDevice->allocateCommandBuffers(copyCommandBufferAllocInfo).front()); + + copyCommandBuffer.begin(vk::CommandBufferBeginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + copyCommandBuffer.copyBuffer(src, dst, vk::BufferCopy(0, 0, size)); + copyCommandBuffer.end(); + + queue.submit( + vk::SubmitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*copyCommandBuffer, + }, + nullptr); + queue.waitIdle(); + } }; - vk::raii::CommandBuffer copyCommandBuffer = std::move(logicalDevice->allocateCommandBuffers(copyCommandBufferAllocInfo).front()); - - copyCommandBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - copyCommandBuffer.copyBuffer(src, dst, vk::BufferCopy(0, 0, size)); - copyCommandBuffer.end(); - - queue.submit(vk::SubmitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*copyCommandBuffer, - }, nullptr); - queue.waitIdle(); - } - -}; -} +} // namespace sauce diff --git a/include/app/Camera.hpp b/include/app/Camera.hpp index 77881045..9b5aeb2f 100644 --- a/include/app/Camera.hpp +++ b/include/app/Camera.hpp @@ -5,155 +5,166 @@ namespace sauce { -struct CameraCreateInfo { - float scrWidth; - float scrHeight; - glm::vec3 pos = { 2.0f, 2.0f, 2.0f }; - glm::vec3 worldUp = { 0.0f, 0.0f, 1.0f }; - float yaw = YAW_DEFAULT; - float pitch = PITCH_DEFAULT; - float fov = FOV_DEFAULT; - float movementSpeed = SPEED_DEFAULT; - float mouseSensitivity = SENSITIVITY_DEFAULT; - - static constexpr float YAW_DEFAULT = -90.0f; - static constexpr float PITCH_DEFAULT = 0.0f; - static constexpr float SPEED_DEFAULT = 2.5f; - static constexpr float SENSITIVITY_DEFAULT = 0.1f; - static constexpr float FOV_DEFAULT = 90.0f; - - static constexpr float PITCH_MIN = -89.0f; - static constexpr float PITCH_MAX = 89.0f; -}; - -class Camera { -public: - enum class Movement { - FORWARD, - BACKWARD, - LEFT, - RIGHT + struct CameraCreateInfo { + float scrWidth; + float scrHeight; + glm::vec3 pos = {2.0f, 2.0f, 2.0f}; + glm::vec3 worldUp = {0.0f, 0.0f, 1.0f}; + float yaw = YAW_DEFAULT; + float pitch = PITCH_DEFAULT; + float fov = FOV_DEFAULT; + float movementSpeed = SPEED_DEFAULT; + float mouseSensitivity = SENSITIVITY_DEFAULT; + + static constexpr float YAW_DEFAULT = -90.0f; + static constexpr float PITCH_DEFAULT = 0.0f; + static constexpr float SPEED_DEFAULT = 2.5f; + static constexpr float SENSITIVITY_DEFAULT = 0.1f; + static constexpr float FOV_DEFAULT = 90.0f; + + static constexpr float PITCH_MIN = -89.0f; + static constexpr float PITCH_MAX = 89.0f; }; - /** + class Camera { + public: + enum class Movement { + FORWARD, + BACKWARD, + LEFT, + RIGHT + }; + + /** * Camera constructor */ - Camera(const sauce::CameraCreateInfo& createInfo) - : pos(createInfo.pos), worldUp(createInfo.worldUp), - yaw(createInfo.yaw), pitch(createInfo.pitch), fov(createInfo.fov), - movementSpeed(createInfo.movementSpeed), mouseSensitivity(createInfo.mouseSensitivity), - scrWidth(createInfo.scrWidth), scrHeight(createInfo.scrHeight) - { - updateView(); - } - - /** + Camera(const sauce::CameraCreateInfo& createInfo) + : pos(createInfo.pos), worldUp(createInfo.worldUp), yaw(createInfo.yaw), + pitch(createInfo.pitch), fov(createInfo.fov), movementSpeed(createInfo.movementSpeed), + mouseSensitivity(createInfo.mouseSensitivity), scrWidth(createInfo.scrWidth), + scrHeight(createInfo.scrHeight) { + updateView(); + } + + /** * Sets camera position * * @param pos - position to move the camera to */ - void setPos(glm::vec3 pos) { - this->pos=pos; - updateView(); - } + void setPos(glm::vec3 pos) { + this->pos = pos; + updateView(); + } - glm::vec3 getPos() const { - return this->pos; - } + glm::vec3 getPos() const { + return this->pos; + } - /** + /** * Sets camera to look at target from position */ - void lookAt(const glm::vec3& position, const glm::vec3& target, const glm::vec3& upVec) { - pos = position; - worldUp = upVec; - glm::vec3 direction = glm::normalize(target - position); - yaw = glm::degrees(atan2(direction.z, direction.x)); - pitch = glm::degrees(asin(direction.y)); - updateView(); - } - - /** + void lookAt(const glm::vec3& position, const glm::vec3& target, const glm::vec3& upVec) { + pos = position; + worldUp = upVec; + glm::vec3 direction = glm::normalize(target - position); + yaw = glm::degrees(atan2(direction.z, direction.x)); + pitch = glm::degrees(asin(direction.y)); + updateView(); + } + + /** * Translates the camera position by offset * * @param offs - offset by which to translate the camera */ - void translate(glm::vec3 offs) { - translate(offs.x, offs.y, offs.z); - } + void translate(glm::vec3 offs) { + translate(offs.x, offs.y, offs.z); + } - /** + /** * Translates the camera position by (x, y, z) */ - void translate(float x, float y, float z) { - this->pos.x+=x; - this->pos.y+=y; - this->pos.z+=z; - updateView(); - } - - /** + void translate(float x, float y, float z) { + this->pos.x += x; + this->pos.y += y; + this->pos.z += z; + updateView(); + } + + /** * Get current FOV */ - float getFOV() const { return fov; } + float getFOV() const { + return fov; + } - /** + /** * Set camera FOV */ - void setFOV(float newFov) { this->fov = newFov; } - - void setMovementSpeed(float speed) { this->movementSpeed = speed; } - float getMovementSpeed() const { return movementSpeed; } - - void setMouseSensitivity(float sensitivity) { this->mouseSensitivity = sensitivity; } - float getMouseSensitivity() const { return mouseSensitivity; } - - /** + void setFOV(float newFov) { + this->fov = newFov; + } + + void setMovementSpeed(float speed) { + this->movementSpeed = speed; + } + float getMovementSpeed() const { + return movementSpeed; + } + + void setMouseSensitivity(float sensitivity) { + this->mouseSensitivity = sensitivity; + } + float getMouseSensitivity() const { + return mouseSensitivity; + } + + /** * Get view matrix from the current view vectors * * @return the view matrix for this camera */ - glm::mat4 getViewMatrix() const { - return glm::lookAt(this->pos, this->pos + this->front, this->worldUp); - } + glm::mat4 getViewMatrix() const { + return glm::lookAt(this->pos, this->pos + this->front, this->worldUp); + } - /** + /** * Get the projection matrix for this camera * * @return projection matrix for this camera */ - glm::mat4 getProjectionMatrix() const { - return glm::perspective(glm::radians(this->fov), scrWidth/scrHeight, 0.1f, 100.f); - } + glm::mat4 getProjectionMatrix() const { + return glm::perspective(glm::radians(this->fov), scrWidth / scrHeight, 0.1f, 100.f); + } - /** + /** * Processes direction input, normalized by deltatime and camera velocity * * @param direction - direction in which to move the camera * @param deltatime - the time passed */ - void processDirection(Movement direction, double deltatime) { - glm::vec3 move; - - switch (direction) { - case Movement::FORWARD: - move=this->front; - break; - case Movement::BACKWARD: - move=-1.f*this->front; - break; - case Movement::LEFT: - move=-1.f*this->right; - break; - case Movement::RIGHT: - move=this->right; - break; - } - move*=deltatime*this->movementSpeed; - this->translate(move); - } - - /** + void processDirection(Movement direction, double deltatime) { + glm::vec3 move; + + switch (direction) { + case Movement::FORWARD: + move = this->front; + break; + case Movement::BACKWARD: + move = -1.f * this->front; + break; + case Movement::LEFT: + move = -1.f * this->right; + break; + case Movement::RIGHT: + move = this->right; + break; + } + move *= deltatime * this->movementSpeed; + this->translate(move); + } + + /** * Processes mouse movements by offsetting yaw and pitch by xoffset and yoffset, resp. * If constrainPitch is true, pitch gets clamped to PITCH_MIN or PITCH_MAX when out of bounds. * @@ -161,50 +172,50 @@ class Camera { * @param yoffset - offset for pitch * @param constrainPitch - whether or not to clamp pitch when out of bounds */ - void processMouseMovement(float xoffset, float yoffset, bool constrainPitch = true) { - yaw += xoffset * mouseSensitivity; - pitch += yoffset * mouseSensitivity; + void processMouseMovement(float xoffset, float yoffset, bool constrainPitch = true) { + yaw += xoffset * mouseSensitivity; + pitch += yoffset * mouseSensitivity; - if (constrainPitch) { - pitch = std::max(std::min(pitch, CameraCreateInfo::PITCH_MAX), CameraCreateInfo::PITCH_MIN); - } + if (constrainPitch) { + pitch = std::max(std::min(pitch, CameraCreateInfo::PITCH_MAX), + CameraCreateInfo::PITCH_MIN); + } - updateView(); - } + updateView(); + } -private: - glm::vec3 pos; + private: + glm::vec3 pos; - /* view orientation vectors */ - glm::vec3 front, up, right; + /* view orientation vectors */ + glm::vec3 front, up, right; - /* Used to get the right vector from front vector */ - glm::vec3 worldUp; + /* Used to get the right vector from front vector */ + glm::vec3 worldUp; - float movementSpeed, mouseSensitivity; + float movementSpeed, mouseSensitivity; - // euler Angles - float yaw, pitch; + // euler Angles + float yaw, pitch; - float fov; - float scrWidth, scrHeight; + float fov; + float scrWidth, scrHeight; - /** + /** * Sets the front, right, and up vectors using the current values for yaw and pitch. * This should be called any time the camera position or angle is changed. */ - void updateView() { - float pitchRad = glm::radians(pitch); - float yawRad = glm::radians(yaw); - - front.x = cos(pitchRad) * sin(yawRad); - front.y = cos(pitchRad) * cos(yawRad); - front.z = sin(pitchRad); + void updateView() { + float pitchRad = glm::radians(pitch); + float yawRad = glm::radians(yaw); - right=glm::normalize(glm::cross(front, worldUp)); - up=glm::normalize(glm::cross(right, front)); - } -}; + front.x = cos(pitchRad) * sin(yawRad); + front.y = cos(pitchRad) * cos(yawRad); + front.z = sin(pitchRad); -} + right = glm::normalize(glm::cross(front, worldUp)); + up = glm::normalize(glm::cross(right, front)); + } + }; +} // namespace sauce diff --git a/include/app/Component.hpp b/include/app/Component.hpp index 2c2fe1a4..c50e7b2c 100644 --- a/include/app/Component.hpp +++ b/include/app/Component.hpp @@ -2,32 +2,39 @@ #include -namespace sauce -{ - -class Entity; - -class Component -{ -public: - const std::string name; - - Component(std::string n = "Component") : name(n) {} - virtual ~Component() {} - - virtual bool getActive() { return this->active; } - virtual void setActive(bool newState) { this->active = newState; } - - virtual void setOwner(Entity *newOwner) { this->owner = newOwner; } - virtual Entity *getOwner() { return this->owner; } - - virtual void update(float deltaT) {}; - virtual void render() {}; - -private: - sauce::Entity *owner = nullptr; - bool active; -}; - -} - +namespace sauce { + + class Entity; + + class Component { + public: + const std::string name; + + Component(std::string n = "Component") : name(n) { + } + virtual ~Component() { + } + + virtual bool getActive() { + return this->active; + } + virtual void setActive(bool newState) { + this->active = newState; + } + + virtual void setOwner(Entity* newOwner) { + this->owner = newOwner; + } + virtual Entity* getOwner() { + return this->owner; + } + + virtual void update(float deltaT) {}; + virtual void render() {}; + + private: + sauce::Entity* owner = nullptr; + bool active; + }; + +} // namespace sauce diff --git a/include/app/Entity.hpp b/include/app/Entity.hpp index 2e7251ef..fc9d9ea8 100644 --- a/include/app/Entity.hpp +++ b/include/app/Entity.hpp @@ -1,146 +1,147 @@ #pragma once -#include -#include #include +#include #include +#include #include - namespace sauce { -class Entity { -public: - Entity(const std::string& name) : name(name) {} - - std::string get_name() const { return name; } - void set_name(const std::string& newName) { name = newName; } - bool getActive() const { return active; } - void setActive(bool active) { this->active = active; } - - - /** + class Entity { + public: + Entity(const std::string& name) : name(name) { + } + + std::string get_name() const { + return name; + } + void set_name(const std::string& newName) { + name = newName; + } + bool getActive() const { + return active; + } + void setActive(bool active) { + this->active = active; + } + + /** * Initializes and adds a component of type with name * * @param name Name for the new component */ - template - void addComponent(Args &&...args) { - auto component = std::make_unique(std::forward(args)...); - component->setOwner(this); - components.push_back(std::move(component)); - } - - /** + template void addComponent(Args&&... args) { + auto component = std::make_unique(std::forward(args)...); + component->setOwner(this); + components.push_back(std::move(component)); + } + + /** * Removes the most recently added component of a specified type */ - template - void removeComponent() { - for (auto it = components.rbegin(); it != components.rend(); ++it) { - if (dynamic_cast(it->get()) != nullptr) { - components.erase(std::next(it).base()); - return; - } - } - } - - /** + template void removeComponent() { + for (auto it = components.rbegin(); it != components.rend(); ++it) { + if (dynamic_cast(it->get()) != nullptr) { + components.erase(std::next(it).base()); + return; + } + } + } + + /** * Removes the most recently added component of a specified type and name * * @param name Name of the component to remove */ - template - void removeComponent(const std::string& name) { - for (auto it = components.rbegin(); it != components.rend(); ++it) { - auto* component = dynamic_cast(it->get()); - if (component != nullptr && component->name == name) { - components.erase(std::next(it).base()); - return; - } - } - } - - /** + template void removeComponent(const std::string& name) { + for (auto it = components.rbegin(); it != components.rend(); ++it) { + auto* component = dynamic_cast(it->get()); + if (component != nullptr && component->name == name) { + components.erase(std::next(it).base()); + return; + } + } + } + + /** * Removes a specific component by pointer */ - void removeComponentByPointer(Component* target) { - for (auto it = components.begin(); it != components.end(); ++it) { - if (it->get() == target) { - components.erase(it); - return; - } - } - } - - /** + void removeComponentByPointer(Component* target) { + for (auto it = components.begin(); it != components.end(); ++it) { + if (it->get() == target) { + components.erase(it); + return; + } + } + } + + /** * Returns a raw pointer to the most recently added component of a specified type */ - template - T* getComponent() { - for (auto it = components.rbegin(); it != components.rend(); ++it) { - auto* component = dynamic_cast(it->get()); - if (component != nullptr) { - return component; - } - } - return nullptr; - } - - template - const T* getComponent() const { - for (auto it = components.rbegin(); it != components.rend(); ++it) { - auto* component = dynamic_cast(it->get()); - if (component != nullptr) { - return component; - } - } - return nullptr; - } - - /** + template T* getComponent() { + for (auto it = components.rbegin(); it != components.rend(); ++it) { + auto* component = dynamic_cast(it->get()); + if (component != nullptr) { + return component; + } + } + return nullptr; + } + + template const T* getComponent() const { + for (auto it = components.rbegin(); it != components.rend(); ++it) { + auto* component = dynamic_cast(it->get()); + if (component != nullptr) { + return component; + } + } + return nullptr; + } + + /** * Returns raw pointers to all components of a specified type */ - template - std::vector getComponents() { - std::vector result; - for (auto& comp : components) { - auto* c = dynamic_cast(comp.get()); - if (c) result.push_back(c); - } - return result; - } - - template - std::vector getComponents() const { - std::vector result; - for (const auto& comp : components) { - auto* c = dynamic_cast(comp.get()); - if (c) result.push_back(c); - } - return result; - } - - /* + template std::vector getComponents() { + std::vector result; + for (auto& comp : components) { + auto* c = dynamic_cast(comp.get()); + if (c) + result.push_back(c); + } + return result; + } + + template std::vector getComponents() const { + std::vector result; + for (const auto& comp : components) { + auto* c = dynamic_cast(comp.get()); + if (c) + result.push_back(c); + } + return result; + } + + /* * Returns a raw pointer to the most recently added component of a specified type and name * * @param name Name of the component to find */ - template - T* getComponent(const std::string& name) { - for (auto it = components.rbegin(); it != components.rend(); ++it) { - auto* component = dynamic_cast(it->get()); - if (component != nullptr && component->name == name) { - return component; - } - } - return nullptr; - } - -private: - std::string name; - bool active = true; - std::vector> components; -}; - -} + template T* getComponent(const std::string& name) { + for (auto it = components.rbegin(); it != components.rend(); ++it) { + auto* component = dynamic_cast(it->get()); + if (component != nullptr && component->name == name) { + return component; + } + } + return nullptr; + } + + private: + std::string name; + bool active = true; + std::vector> components; + }; + +} // namespace sauce diff --git a/include/app/GraphicsPipeline.hpp b/include/app/GraphicsPipeline.hpp index ab55838b..151467e1 100644 --- a/include/app/GraphicsPipeline.hpp +++ b/include/app/GraphicsPipeline.hpp @@ -7,419 +7,421 @@ #include -#include #include #include +#include namespace sauce { -struct GraphicsPipelineConfig { - const sauce::PhysicalDevice& physicalDevice; - const sauce::LogicalDevice& logicalDevice; - std::vector descriptorSetLayouts; - vk::Format colorFormat; - std::string shaderPath; - const std::string vertEntryPoint = "vertMain"; - const std::string fragEntryPoint = "fragMain"; - bool hasVertexInput = true; - bool enableBlending = false; - bool enableCulling = true; - bool depthWrite = true; - bool depthTestEnable = true; - bool hasPushConstants = false; - uint32_t pushConstantSize = 0; -}; - -struct GraphicsPipeline { - GraphicsPipelineConfig config; - - GraphicsPipeline( - const sauce::GraphicsPipelineConfig& config - ) : config(config) { - vk::raii::ShaderModule shaderModule = createShaderModule(config.logicalDevice, readBinaryFile(config.shaderPath)); - vk::PipelineShaderStageCreateInfo vertShaderCreateInfo { - .stage = vk::ShaderStageFlagBits::eVertex, - .module = shaderModule, - .pName = config.vertEntryPoint.c_str(), - }; - vk::PipelineShaderStageCreateInfo fragShaderCreateInfo { - .stage = vk::ShaderStageFlagBits::eFragment, - .module = shaderModule, - .pName = config.fragEntryPoint.c_str(), - }; - vk::PipelineShaderStageCreateInfo shaderStages[] = { - vertShaderCreateInfo, - fragShaderCreateInfo, - }; - - initPipeline(shaderStages); - } - - // Constructor for separate GLSL vertex and fragment shaders - GraphicsPipeline(const sauce::GraphicsPipelineConfig& config, const std::string& vertShaderPath, const std::string& fragShaderPath) : config(config) { - vertShaderModule = createShaderModule(config.logicalDevice, readBinaryFile(vertShaderPath)); - fragShaderModule = createShaderModule(config.logicalDevice, readBinaryFile(fragShaderPath)); - - vk::PipelineShaderStageCreateInfo vertShaderCreateInfo { - .stage = vk::ShaderStageFlagBits::eVertex, - .module = vertShaderModule, - .pName = config.vertEntryPoint.c_str(), - }; - vk::PipelineShaderStageCreateInfo fragShaderCreateInfo { - .stage = vk::ShaderStageFlagBits::eFragment, - .module = fragShaderModule, - .pName = config.fragEntryPoint.c_str(), - }; - vk::PipelineShaderStageCreateInfo shaderStages[] = { - vertShaderCreateInfo, - fragShaderCreateInfo, - }; - - initPipeline(shaderStages); - } - - // Constructor for editor pipelines with config (offscreen rendering) - GraphicsPipeline( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice, - const std::vector& descriptorSetLayouts, - vk::Format colorFormat, - const std::string& vertShaderPath, - const std::string& fragShaderPath, - const GraphicsPipelineConfig& config - ) : config(config) { - vertShaderModule = createShaderModule(logicalDevice, readBinaryFile(vertShaderPath)); - fragShaderModule = createShaderModule(logicalDevice, readBinaryFile(fragShaderPath)); - - vk::PipelineShaderStageCreateInfo vertShaderCreateInfo { - .stage = vk::ShaderStageFlagBits::eVertex, - .module = vertShaderModule, - .pName = "main", - }; - vk::PipelineShaderStageCreateInfo fragShaderCreateInfo { - .stage = vk::ShaderStageFlagBits::eFragment, - .module = fragShaderModule, - .pName = "main", - }; - vk::PipelineShaderStageCreateInfo shaderStages[] = { - vertShaderCreateInfo, - fragShaderCreateInfo, - }; - - initPipelineConfigurable(physicalDevice, logicalDevice, descriptorSetLayouts, colorFormat, shaderStages, config); - } - - -private: - vk::raii::PipelineLayout layout = nullptr; - vk::raii::Pipeline pipeline = nullptr; - vk::raii::ShaderModule vertShaderModule = nullptr; - vk::raii::ShaderModule fragShaderModule = nullptr; - - void initPipeline(vk::PipelineShaderStageCreateInfo* shaderStages) { - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescription(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { - .vertexBindingDescriptionCount = config.hasVertexInput ? 1u : 0u, - .pVertexBindingDescriptions = config.hasVertexInput ? &bindingDescription : nullptr, - .vertexAttributeDescriptionCount = config.hasVertexInput ? static_cast(attributeDescriptions.size()) : 0u, - .pVertexAttributeDescriptions = config.hasVertexInput ? attributeDescriptions.data() : nullptr, - }; - - vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo { - .topology = vk::PrimitiveTopology::eTriangleList, - }; - - vk::PipelineViewportStateCreateInfo viewportStateInfo { - .viewportCount = 1, - .scissorCount = 1, - }; - - vk::PipelineRasterizationStateCreateInfo rasterizerInfo { - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, - .lineWidth = 1.0f, - }; - - vk::PipelineMultisampleStateCreateInfo multisamplingInfo { - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False, - }; - - - vk::PipelineDepthStencilStateCreateInfo depthStencil { - .depthTestEnable = config.depthTestEnable ? vk::True : vk::False, - .depthWriteEnable = config.depthWrite ? vk::True : vk::False, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False, - }; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment { - .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, - }; - - vk::PipelineColorBlendStateCreateInfo colorBlendInfo { - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment, - }; - - std::vector dynamicStates { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor, - }; - - vk::PipelineDynamicStateCreateInfo dynamicStateInfo { - .dynamicStateCount = static_cast(dynamicStates.size()), - .pDynamicStates = dynamicStates.data(), - }; - - vk::PushConstantRange pushConstantRange { - .stageFlags = vk::ShaderStageFlagBits::eFragment, - .offset = 0, - .size = sizeof(uint32_t), - }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo { - .setLayoutCount = static_cast(config.descriptorSetLayouts.size()), - .pSetLayouts = config.descriptorSetLayouts.data(), - .pushConstantRangeCount = 1, - .pPushConstantRanges = &pushConstantRange, - }; - - layout = vk::raii::PipelineLayout { *config.logicalDevice, pipelineLayoutInfo }; - - - vk::Format depthFormat = findDepthFormat(config.physicalDevice); - - vk::PipelineRenderingCreateInfo renderingCreateInfo { - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &config.colorFormat, - .depthAttachmentFormat = config.depthTestEnable ? depthFormat : vk::Format::eUndefined, - }; - - vk::GraphicsPipelineCreateInfo pipelineInfo { - .pNext = &renderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssemblyInfo, - .pViewportState = &viewportStateInfo, - .pRasterizationState = &rasterizerInfo, - .pMultisampleState = &multisamplingInfo, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlendInfo, - .pDynamicState = &dynamicStateInfo, - .layout = layout, - .renderPass = nullptr, - }; - - pipeline = vk::raii::Pipeline { *config.logicalDevice, nullptr, pipelineInfo }; - } - - void initPipelineConfigurable( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice, - const std::vector& descriptorSetLayouts, - vk::Format colorFormat, - vk::PipelineShaderStageCreateInfo* shaderStages, - const GraphicsPipelineConfig& config - ) { - // Vertex input - empty if no vertex input (e.g. grid fullscreen triangle) - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescription(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo {}; - if (config.hasVertexInput) { - vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); - vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - } - - vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo { - .topology = vk::PrimitiveTopology::eTriangleList, - }; - - vk::PipelineViewportStateCreateInfo viewportStateInfo { - .viewportCount = 1, - .scissorCount = 1, - }; - - vk::PipelineRasterizationStateCreateInfo rasterizerInfo { - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = config.enableCulling ? vk::CullModeFlagBits::eBack : vk::CullModeFlagBits::eNone, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False, - .lineWidth = 1.0f, - }; - - vk::PipelineMultisampleStateCreateInfo multisamplingInfo { - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False, - }; - - vk::PipelineDepthStencilStateCreateInfo depthStencil { - .depthTestEnable = config.depthTestEnable ? vk::True : vk::False, - .depthWriteEnable = config.depthWrite ? vk::True : vk::False, - .depthCompareOp = vk::CompareOp::eLessOrEqual, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False, - }; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment {}; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | - vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - if (config.enableBlending) { - colorBlendAttachment.blendEnable = vk::True; - colorBlendAttachment.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha; - colorBlendAttachment.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha; - colorBlendAttachment.colorBlendOp = vk::BlendOp::eAdd; - colorBlendAttachment.srcAlphaBlendFactor = vk::BlendFactor::eOne; - colorBlendAttachment.dstAlphaBlendFactor = vk::BlendFactor::eZero; - colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd; - } else { - colorBlendAttachment.blendEnable = vk::False; - } - - vk::PipelineColorBlendStateCreateInfo colorBlendInfo { - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment, - }; - - std::vector dynamicStates { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor, - }; - - vk::PipelineDynamicStateCreateInfo dynamicStateInfo { - .dynamicStateCount = static_cast(dynamicStates.size()), - .pDynamicStates = dynamicStates.data(), - }; - - vk::PushConstantRange pushRange { - .stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, - .offset = 0, - .size = config.pushConstantSize, - }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo { - .setLayoutCount = static_cast(descriptorSetLayouts.size()), - .pSetLayouts = descriptorSetLayouts.data(), - .pushConstantRangeCount = config.hasPushConstants ? 1u : 0u, - .pPushConstantRanges = config.hasPushConstants ? &pushRange : nullptr, - }; - - layout = vk::raii::PipelineLayout { *logicalDevice, pipelineLayoutInfo }; - - vk::Format depthFormat = findDepthFormat(physicalDevice); - - vk::PipelineRenderingCreateInfo renderingCreateInfo { - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &colorFormat, - .depthAttachmentFormat = depthFormat, - }; - - vk::GraphicsPipelineCreateInfo pipelineInfo { - .pNext = &renderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssemblyInfo, - .pViewportState = &viewportStateInfo, - .pRasterizationState = &rasterizerInfo, - .pMultisampleState = &multisamplingInfo, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlendInfo, - .pDynamicState = &dynamicStateInfo, - .layout = layout, - .renderPass = nullptr, - }; - - pipeline = vk::raii::Pipeline { *logicalDevice, nullptr, pipelineInfo }; - } - - -public: - const vk::raii::Pipeline& operator*() const & noexcept { - return pipeline; - } - - const vk::raii::Pipeline* operator->() const & noexcept { - return &pipeline; - } - - const vk::raii::PipelineLayout& getLayout() const noexcept { - return layout; - } - - static vk::Format findDepthFormat(const sauce::PhysicalDevice& physicalDevice) { - return findSupportedFormat( - physicalDevice, - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - -private: - static std::vector readBinaryFile(const std::string filename) { - std::ifstream file { filename, std::ios::binary | std::ios::ate }; - - if (!file.is_open()) { - throw std::runtime_error("Failed to open file: " + filename); - } - - std::vector buf ( file.tellg() ); - - file.seekg(0, std::ios::beg); - file.read(buf.data(), static_cast(buf.size())); - - file.close(); - - return buf; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const sauce::LogicalDevice& logicalDevice, const std::vector& code) const { - vk::ShaderModuleCreateInfo shaderModuleCreateInfo { - .codeSize = code.size() * sizeof(char), - .pCode = reinterpret_cast(code.data()), - }; - - return { *logicalDevice, shaderModuleCreateInfo }; - } - - - static vk::Format findSupportedFormat(const sauce::PhysicalDevice& physicalDevice, const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { - for (const auto format: candidates) { - vk::FormatProperties props = physicalDevice->getFormatProperties(format); - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features){ - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("Failed to find supported format!"); - } - - bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - -}; - -} + struct GraphicsPipelineConfig { + const sauce::PhysicalDevice& physicalDevice; + const sauce::LogicalDevice& logicalDevice; + std::vector descriptorSetLayouts; + vk::Format colorFormat; + std::string shaderPath; + const std::string vertEntryPoint = "vertMain"; + const std::string fragEntryPoint = "fragMain"; + bool hasVertexInput = true; + bool enableBlending = false; + bool enableCulling = true; + bool depthWrite = true; + bool depthTestEnable = true; + bool hasPushConstants = false; + uint32_t pushConstantSize = 0; + }; + + struct GraphicsPipeline { + GraphicsPipelineConfig config; + + GraphicsPipeline(const sauce::GraphicsPipelineConfig& config) : config(config) { + vk::raii::ShaderModule shaderModule = + createShaderModule(config.logicalDevice, readBinaryFile(config.shaderPath)); + vk::PipelineShaderStageCreateInfo vertShaderCreateInfo{ + .stage = vk::ShaderStageFlagBits::eVertex, + .module = shaderModule, + .pName = config.vertEntryPoint.c_str(), + }; + vk::PipelineShaderStageCreateInfo fragShaderCreateInfo{ + .stage = vk::ShaderStageFlagBits::eFragment, + .module = shaderModule, + .pName = config.fragEntryPoint.c_str(), + }; + vk::PipelineShaderStageCreateInfo shaderStages[] = { + vertShaderCreateInfo, + fragShaderCreateInfo, + }; + + initPipeline(shaderStages); + } + + // Constructor for separate GLSL vertex and fragment shaders + GraphicsPipeline(const sauce::GraphicsPipelineConfig& config, + const std::string& vertShaderPath, const std::string& fragShaderPath) + : config(config) { + vertShaderModule = + createShaderModule(config.logicalDevice, readBinaryFile(vertShaderPath)); + fragShaderModule = + createShaderModule(config.logicalDevice, readBinaryFile(fragShaderPath)); + + vk::PipelineShaderStageCreateInfo vertShaderCreateInfo{ + .stage = vk::ShaderStageFlagBits::eVertex, + .module = vertShaderModule, + .pName = config.vertEntryPoint.c_str(), + }; + vk::PipelineShaderStageCreateInfo fragShaderCreateInfo{ + .stage = vk::ShaderStageFlagBits::eFragment, + .module = fragShaderModule, + .pName = config.fragEntryPoint.c_str(), + }; + vk::PipelineShaderStageCreateInfo shaderStages[] = { + vertShaderCreateInfo, + fragShaderCreateInfo, + }; + + initPipeline(shaderStages); + } + + // Constructor for editor pipelines with config (offscreen rendering) + GraphicsPipeline(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice, + const std::vector& descriptorSetLayouts, + vk::Format colorFormat, const std::string& vertShaderPath, + const std::string& fragShaderPath, const GraphicsPipelineConfig& config) + : config(config) { + vertShaderModule = createShaderModule(logicalDevice, readBinaryFile(vertShaderPath)); + fragShaderModule = createShaderModule(logicalDevice, readBinaryFile(fragShaderPath)); + + vk::PipelineShaderStageCreateInfo vertShaderCreateInfo{ + .stage = vk::ShaderStageFlagBits::eVertex, + .module = vertShaderModule, + .pName = "main", + }; + vk::PipelineShaderStageCreateInfo fragShaderCreateInfo{ + .stage = vk::ShaderStageFlagBits::eFragment, + .module = fragShaderModule, + .pName = "main", + }; + vk::PipelineShaderStageCreateInfo shaderStages[] = { + vertShaderCreateInfo, + fragShaderCreateInfo, + }; + + initPipelineConfigurable(physicalDevice, logicalDevice, descriptorSetLayouts, + colorFormat, shaderStages, config); + } + + private: + vk::raii::PipelineLayout layout = nullptr; + vk::raii::Pipeline pipeline = nullptr; + vk::raii::ShaderModule vertShaderModule = nullptr; + vk::raii::ShaderModule fragShaderModule = nullptr; + + void initPipeline(vk::PipelineShaderStageCreateInfo* shaderStages) { + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescription(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = config.hasVertexInput ? 1u : 0u, + .pVertexBindingDescriptions = config.hasVertexInput ? &bindingDescription : nullptr, + .vertexAttributeDescriptionCount = + config.hasVertexInput ? static_cast(attributeDescriptions.size()) + : 0u, + .pVertexAttributeDescriptions = + config.hasVertexInput ? attributeDescriptions.data() : nullptr, + }; + + vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo{ + .topology = vk::PrimitiveTopology::eTriangleList, + }; + + vk::PipelineViewportStateCreateInfo viewportStateInfo{ + .viewportCount = 1, + .scissorCount = 1, + }; + + vk::PipelineRasterizationStateCreateInfo rasterizerInfo{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .depthBiasSlopeFactor = 1.0f, + .lineWidth = 1.0f, + }; + + vk::PipelineMultisampleStateCreateInfo multisamplingInfo{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False, + }; + + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = config.depthTestEnable ? vk::True : vk::False, + .depthWriteEnable = config.depthWrite ? vk::True : vk::False, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False, + }; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }; + + vk::PipelineColorBlendStateCreateInfo colorBlendInfo{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment, + }; + + std::vector dynamicStates{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + + vk::PipelineDynamicStateCreateInfo dynamicStateInfo{ + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data(), + }; + + vk::PushConstantRange pushConstantRange{ + .stageFlags = vk::ShaderStageFlagBits::eFragment, + .offset = 0, + .size = sizeof(uint32_t), + }; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ + .setLayoutCount = static_cast(config.descriptorSetLayouts.size()), + .pSetLayouts = config.descriptorSetLayouts.data(), + .pushConstantRangeCount = 1, + .pPushConstantRanges = &pushConstantRange, + }; + + layout = vk::raii::PipelineLayout{*config.logicalDevice, pipelineLayoutInfo}; + + vk::Format depthFormat = findDepthFormat(config.physicalDevice); + + vk::PipelineRenderingCreateInfo renderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &config.colorFormat, + .depthAttachmentFormat = + config.depthTestEnable ? depthFormat : vk::Format::eUndefined, + }; + + vk::GraphicsPipelineCreateInfo pipelineInfo{ + .pNext = &renderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssemblyInfo, + .pViewportState = &viewportStateInfo, + .pRasterizationState = &rasterizerInfo, + .pMultisampleState = &multisamplingInfo, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlendInfo, + .pDynamicState = &dynamicStateInfo, + .layout = layout, + .renderPass = nullptr, + }; + + pipeline = vk::raii::Pipeline{*config.logicalDevice, nullptr, pipelineInfo}; + } + + void initPipelineConfigurable( + const sauce::PhysicalDevice& physicalDevice, const sauce::LogicalDevice& logicalDevice, + const std::vector& descriptorSetLayouts, + vk::Format colorFormat, vk::PipelineShaderStageCreateInfo* shaderStages, + const GraphicsPipelineConfig& config) { + // Vertex input - empty if no vertex input (e.g. grid fullscreen triangle) + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescription(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; + if (config.hasVertexInput) { + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + } + + vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo{ + .topology = vk::PrimitiveTopology::eTriangleList, + }; + + vk::PipelineViewportStateCreateInfo viewportStateInfo{ + .viewportCount = 1, + .scissorCount = 1, + }; + + vk::PipelineRasterizationStateCreateInfo rasterizerInfo{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = config.enableCulling ? vk::CullModeFlagBits::eBack + : vk::CullModeFlagBits::eNone, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .lineWidth = 1.0f, + }; + + vk::PipelineMultisampleStateCreateInfo multisamplingInfo{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False, + }; + + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = config.depthTestEnable ? vk::True : vk::False, + .depthWriteEnable = config.depthWrite ? vk::True : vk::False, + .depthCompareOp = vk::CompareOp::eLessOrEqual, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False, + }; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + if (config.enableBlending) { + colorBlendAttachment.blendEnable = vk::True; + colorBlendAttachment.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha; + colorBlendAttachment.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha; + colorBlendAttachment.colorBlendOp = vk::BlendOp::eAdd; + colorBlendAttachment.srcAlphaBlendFactor = vk::BlendFactor::eOne; + colorBlendAttachment.dstAlphaBlendFactor = vk::BlendFactor::eZero; + colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd; + } else { + colorBlendAttachment.blendEnable = vk::False; + } + + vk::PipelineColorBlendStateCreateInfo colorBlendInfo{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment, + }; + + std::vector dynamicStates{ + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + + vk::PipelineDynamicStateCreateInfo dynamicStateInfo{ + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data(), + }; + + vk::PushConstantRange pushRange{ + .stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, + .offset = 0, + .size = config.pushConstantSize, + }; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ + .setLayoutCount = static_cast(descriptorSetLayouts.size()), + .pSetLayouts = descriptorSetLayouts.data(), + .pushConstantRangeCount = config.hasPushConstants ? 1u : 0u, + .pPushConstantRanges = config.hasPushConstants ? &pushRange : nullptr, + }; + + layout = vk::raii::PipelineLayout{*logicalDevice, pipelineLayoutInfo}; + + vk::Format depthFormat = findDepthFormat(physicalDevice); + + vk::PipelineRenderingCreateInfo renderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &colorFormat, + .depthAttachmentFormat = depthFormat, + }; + + vk::GraphicsPipelineCreateInfo pipelineInfo{ + .pNext = &renderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssemblyInfo, + .pViewportState = &viewportStateInfo, + .pRasterizationState = &rasterizerInfo, + .pMultisampleState = &multisamplingInfo, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlendInfo, + .pDynamicState = &dynamicStateInfo, + .layout = layout, + .renderPass = nullptr, + }; + + pipeline = vk::raii::Pipeline{*logicalDevice, nullptr, pipelineInfo}; + } + + public: + const vk::raii::Pipeline& operator*() const& noexcept { + return pipeline; + } + + const vk::raii::Pipeline* operator->() const& noexcept { + return &pipeline; + } + + const vk::raii::PipelineLayout& getLayout() const noexcept { + return layout; + } + + static vk::Format findDepthFormat(const sauce::PhysicalDevice& physicalDevice) { + return findSupportedFormat( + physicalDevice, + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + private: + static std::vector readBinaryFile(const std::string filename) { + std::ifstream file{filename, std::ios::binary | std::ios::ate}; + + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + filename); + } + + std::vector buf(file.tellg()); + + file.seekg(0, std::ios::beg); + file.read(buf.data(), static_cast(buf.size())); + + file.close(); + + return buf; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule( + const sauce::LogicalDevice& logicalDevice, const std::vector& code) const { + vk::ShaderModuleCreateInfo shaderModuleCreateInfo{ + .codeSize = code.size() * sizeof(char), + .pCode = reinterpret_cast(code.data()), + }; + + return {*logicalDevice, shaderModuleCreateInfo}; + } + + static vk::Format findSupportedFormat(const sauce::PhysicalDevice& physicalDevice, + const std::vector& candidates, + vk::ImageTiling tiling, + vk::FormatFeatureFlags features) { + for (const auto format : candidates) { + vk::FormatProperties props = physicalDevice->getFormatProperties(format); + if (tiling == vk::ImageTiling::eLinear && + (props.linearTilingFeatures & features) == features) { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && + (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("Failed to find supported format!"); + } + + bool hasStencilComponent(vk::Format format) { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + }; + +} // namespace sauce diff --git a/include/app/IBLGenerator.hpp b/include/app/IBLGenerator.hpp index a07de47b..580c4ae0 100644 --- a/include/app/IBLGenerator.hpp +++ b/include/app/IBLGenerator.hpp @@ -13,74 +13,61 @@ namespace sauce { -struct IBLMaps { - vk::raii::Image envCubemap = nullptr; - vk::raii::DeviceMemory envCubemapMemory = nullptr; - vk::raii::ImageView envCubemapView = nullptr; - - vk::raii::Image irradianceMap = nullptr; - vk::raii::DeviceMemory irradianceMapMemory = nullptr; - vk::raii::ImageView irradianceMapView = nullptr; - - vk::raii::Image prefilterMap = nullptr; - vk::raii::DeviceMemory prefilterMapMemory = nullptr; - vk::raii::ImageView prefilterMapView = nullptr; - - vk::raii::Image brdfLUT = nullptr; - vk::raii::DeviceMemory brdfLUTMemory = nullptr; - vk::raii::ImageView brdfLUTView = nullptr; - - vk::raii::Sampler sampler = nullptr; -}; - -class IBLGenerator { -public: - IBLGenerator(const sauce::PhysicalDevice& physicalDevice, const sauce::LogicalDevice& logicalDevice); - - std::unique_ptr generateIBLMaps( - const std::string& hdrPath, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue - ); - -private: - const sauce::PhysicalDevice& physicalDevice; - const sauce::LogicalDevice& logicalDevice; - - void convertEquirectangularToCubemap( - const vk::raii::ImageView& hdrView, - IBLMaps& maps, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue - ); - - void generateIrradianceMap( - IBLMaps& maps, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue - ); - - void generatePrefilterMap( - IBLMaps& maps, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue - ); - - void generateBRDFLUT( - IBLMaps& maps, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue - ); - - // Helpers - vk::raii::ShaderModule createShaderModule(const std::string& filename); - void createCubeMesh(const vk::raii::CommandPool& commandPool, const vk::raii::Queue& queue); - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - uint32_t indexCount = 0; -}; + struct IBLMaps { + vk::raii::Image envCubemap = nullptr; + vk::raii::DeviceMemory envCubemapMemory = nullptr; + vk::raii::ImageView envCubemapView = nullptr; + + vk::raii::Image irradianceMap = nullptr; + vk::raii::DeviceMemory irradianceMapMemory = nullptr; + vk::raii::ImageView irradianceMapView = nullptr; + + vk::raii::Image prefilterMap = nullptr; + vk::raii::DeviceMemory prefilterMapMemory = nullptr; + vk::raii::ImageView prefilterMapView = nullptr; + + vk::raii::Image brdfLUT = nullptr; + vk::raii::DeviceMemory brdfLUTMemory = nullptr; + vk::raii::ImageView brdfLUTView = nullptr; + + vk::raii::Sampler sampler = nullptr; + }; + + class IBLGenerator { + public: + IBLGenerator(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice); + + std::unique_ptr generateIBLMaps(const std::string& hdrPath, + const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue); + + private: + const sauce::PhysicalDevice& physicalDevice; + const sauce::LogicalDevice& logicalDevice; + + void convertEquirectangularToCubemap(const vk::raii::ImageView& hdrView, IBLMaps& maps, + const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue); + + void generateIrradianceMap(IBLMaps& maps, const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue); + + void generatePrefilterMap(IBLMaps& maps, const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue); + + void generateBRDFLUT(IBLMaps& maps, const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue); + + // Helpers + vk::raii::ShaderModule createShaderModule(const std::string& filename); + void createCubeMesh(const vk::raii::CommandPool& commandPool, const vk::raii::Queue& queue); + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + uint32_t indexCount = 0; + }; } // namespace sauce diff --git a/include/app/ImGuiRenderer.hpp b/include/app/ImGuiRenderer.hpp index b853ccdd..5d414746 100644 --- a/include/app/ImGuiRenderer.hpp +++ b/include/app/ImGuiRenderer.hpp @@ -1,9 +1,9 @@ #pragma once #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS +#include #include #include -#include #include #include @@ -12,39 +12,39 @@ struct ImGuiContext; namespace sauce { -class SwapChain; - -struct ImGuiRendererCreateInfo { - const vk::raii::Instance& instance; - const sauce::PhysicalDevice& physicalDevice; - const sauce::LogicalDevice& logicalDevice; - uint32_t queueFamilyIndex; - GLFWwindow* window; - const vk::raii::Queue& queue; - const vk::raii::CommandPool& commandPool; - const sauce::SwapChain& swapChain; - uint32_t imageCount; - vk::Format swapChainFormat; - vk::Format depthFormat; -}; - -class ImGuiRenderer { -public: - explicit ImGuiRenderer(const ImGuiRendererCreateInfo& createInfo); - ~ImGuiRenderer(); - - void newFrame(); - void render(const vk::raii::CommandBuffer& commandBuffer, uint32_t imageIndex); - -private: - void createDescriptorPool(const sauce::LogicalDevice& logicalDevice); - void initImGui(const ImGuiRendererCreateInfo& createInfo); - void uploadFonts(const ImGuiRendererCreateInfo& createInfo); - - ImGuiContext* imguiContext = nullptr; - vk::raii::DescriptorPool imguiDescriptorPool = nullptr; - GLFWwindow* window = nullptr; - const vk::raii::Device* device = nullptr; -}; + class SwapChain; + + struct ImGuiRendererCreateInfo { + const vk::raii::Instance& instance; + const sauce::PhysicalDevice& physicalDevice; + const sauce::LogicalDevice& logicalDevice; + uint32_t queueFamilyIndex; + GLFWwindow* window; + const vk::raii::Queue& queue; + const vk::raii::CommandPool& commandPool; + const sauce::SwapChain& swapChain; + uint32_t imageCount; + vk::Format swapChainFormat; + vk::Format depthFormat; + }; + + class ImGuiRenderer { + public: + explicit ImGuiRenderer(const ImGuiRendererCreateInfo& createInfo); + ~ImGuiRenderer(); + + void newFrame(); + void render(const vk::raii::CommandBuffer& commandBuffer, uint32_t imageIndex); + + private: + void createDescriptorPool(const sauce::LogicalDevice& logicalDevice); + void initImGui(const ImGuiRendererCreateInfo& createInfo); + void uploadFonts(const ImGuiRendererCreateInfo& createInfo); + + ImGuiContext* imguiContext = nullptr; + vk::raii::DescriptorPool imguiDescriptorPool = nullptr; + GLFWwindow* window = nullptr; + const vk::raii::Device* device = nullptr; + }; } // namespace sauce diff --git a/include/app/ImageUtils.hpp b/include/app/ImageUtils.hpp index df2c8289..4433f6a7 100644 --- a/include/app/ImageUtils.hpp +++ b/include/app/ImageUtils.hpp @@ -9,173 +9,164 @@ namespace sauce { -struct ImageUtils { - static void createImage( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice, - uint32_t width, - uint32_t height, - vk::Format format, - vk::ImageTiling tiling, - vk::ImageUsageFlags usage, - vk::MemoryPropertyFlags properties, - vk::raii::Image& image, - vk::raii::DeviceMemory& imageMemory, - uint32_t mipLevels = 1, - uint32_t arrayLayers = 1, - vk::ImageCreateFlags flags = {} - ) { - vk::ImageCreateInfo imageInfo{ - .flags = flags, - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = mipLevels, - .arrayLayers = arrayLayers, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, + struct ImageUtils { + static void createImage(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice, uint32_t width, + uint32_t height, vk::Format format, vk::ImageTiling tiling, + vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, + vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory, + uint32_t mipLevels = 1, uint32_t arrayLayers = 1, + vk::ImageCreateFlags flags = {}) { + vk::ImageCreateInfo imageInfo{ + .flags = flags, + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = arrayLayers, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + }; + + image = vk::raii::Image(*logicalDevice, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = + findMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(*logicalDevice, allocInfo); + image.bindMemory(imageMemory, 0); + } + + static vk::raii::ImageView createImageView( + const sauce::LogicalDevice& logicalDevice, vk::raii::Image& image, vk::Format format, + vk::ImageAspectFlags aspectFlags, vk::ImageViewType viewType = vk::ImageViewType::e2D, + uint32_t mipLevels = 1, uint32_t arrayLayers = 1, uint32_t baseMipLevel = 0, + uint32_t baseArrayLayer = 0) { + vk::ImageViewCreateInfo viewInfo{.image = image, + .viewType = viewType, + .format = format, + .subresourceRange = {aspectFlags, baseMipLevel, + mipLevels, baseArrayLayer, + arrayLayers}}; + return vk::raii::ImageView{*logicalDevice, viewInfo}; + } + + static uint32_t findMemoryType(const sauce::PhysicalDevice& physicalDevice, + uint32_t typeFilter, vk::MemoryPropertyFlags properties) { + vk::PhysicalDeviceMemoryProperties deviceMemoryProps = + physicalDevice->getMemoryProperties(); + + for (uint32_t i = 0; i < deviceMemoryProps.memoryTypeCount; ++i) { + if ((typeFilter & (1 << i)) && + (deviceMemoryProps.memoryTypes[i].propertyFlags & properties) == properties) + return i; + } + + throw std::runtime_error("Failed to find suitable memory type!"); + } + + static void copyBufferToImage(const sauce::LogicalDevice& logicalDevice, + const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue, + const vk::raii::Buffer& srcBuffer, vk::raii::Image& dstImage, + uint32_t width, uint32_t height) { + vk::CommandBufferAllocateInfo copyCommandBufferAllocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1, + }; + vk::raii::CommandBuffer copyCommandBuffer = std::move( + logicalDevice->allocateCommandBuffers(copyCommandBufferAllocInfo).front()); + + copyCommandBuffer.begin(vk::CommandBufferBeginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}, + }; + + copyCommandBuffer.copyBufferToImage(srcBuffer, dstImage, + vk::ImageLayout::eTransferDstOptimal, region); + copyCommandBuffer.end(); + + queue.submit( + vk::SubmitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*copyCommandBuffer, + }, + nullptr); + queue.waitIdle(); + } + + static void transitionImageLayout( + const sauce::LogicalDevice& logicalDevice, const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue, vk::raii::Image& image, vk::ImageLayout oldLayout, + vk::ImageLayout newLayout, vk::AccessFlags2 srcAccessMask, + vk::AccessFlags2 dstAccessMask, vk::PipelineStageFlags2 srcStageMask, + vk::PipelineStageFlags2 dstStageMask, uint32_t mipLevels = 1, uint32_t arrayLayers = 1, + uint32_t baseMipLevel = 0, uint32_t baseArrayLayer = 0) { + vk::CommandBufferAllocateInfo transitionCommandBufferAllocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1, + }; + vk::raii::CommandBuffer transitionCommandBuffer = std::move( + logicalDevice->allocateCommandBuffers(transitionCommandBufferAllocInfo).front()); + + transitionCommandBuffer.begin(vk::CommandBufferBeginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + + vk::ImageMemoryBarrier2 barrier{ + .srcStageMask = srcStageMask, + .srcAccessMask = srcAccessMask, + .dstStageMask = dstStageMask, + .dstAccessMask = dstAccessMask, + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = baseMipLevel, + .levelCount = mipLevels, + .baseArrayLayer = baseArrayLayer, + .layerCount = arrayLayers, + }, + }; + + vk::DependencyInfo dependencyInfo{ + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier, + }; + + transitionCommandBuffer.pipelineBarrier2(dependencyInfo); + transitionCommandBuffer.end(); + + queue.submit( + vk::SubmitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*transitionCommandBuffer, + }, + nullptr); + queue.waitIdle(); + } }; - image = vk::raii::Image(*logicalDevice, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(*logicalDevice, allocInfo); - image.bindMemory(imageMemory, 0); - } - - static vk::raii::ImageView createImageView( - const sauce::LogicalDevice& logicalDevice, - vk::raii::Image& image, vk::Format format, - vk::ImageAspectFlags aspectFlags, - vk::ImageViewType viewType = vk::ImageViewType::e2D, - uint32_t mipLevels = 1, - uint32_t arrayLayers = 1, - uint32_t baseMipLevel = 0, - uint32_t baseArrayLayer = 0 - ) { - vk::ImageViewCreateInfo viewInfo{ .image = image, .viewType = viewType, - .format = format, .subresourceRange = { aspectFlags, baseMipLevel, mipLevels, baseArrayLayer, arrayLayers } }; - return vk::raii::ImageView { *logicalDevice, viewInfo }; - } - - static uint32_t findMemoryType(const sauce::PhysicalDevice& physicalDevice, uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties deviceMemoryProps = physicalDevice->getMemoryProperties(); - - for (uint32_t i = 0; i < deviceMemoryProps.memoryTypeCount; ++i) { - if ((typeFilter & (1 << i)) && (deviceMemoryProps.memoryTypes[i].propertyFlags & properties) == properties) - return i; - } - - throw std::runtime_error("Failed to find suitable memory type!"); - } - - static void copyBufferToImage( - const sauce::LogicalDevice& logicalDevice, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue, - const vk::raii::Buffer& srcBuffer, - vk::raii::Image& dstImage, - uint32_t width, - uint32_t height - ) { - vk::CommandBufferAllocateInfo copyCommandBufferAllocInfo { - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1, - }; - vk::raii::CommandBuffer copyCommandBuffer = std::move(logicalDevice->allocateCommandBuffers(copyCommandBufferAllocInfo).front()); - - copyCommandBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - - vk::BufferImageCopy region { - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1}, - }; - - copyCommandBuffer.copyBufferToImage(srcBuffer, dstImage, vk::ImageLayout::eTransferDstOptimal, region); - copyCommandBuffer.end(); - - queue.submit(vk::SubmitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*copyCommandBuffer, - }, nullptr); - queue.waitIdle(); - } - - static void transitionImageLayout( - const sauce::LogicalDevice& logicalDevice, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue, - vk::raii::Image& image, - vk::ImageLayout oldLayout, - vk::ImageLayout newLayout, - vk::AccessFlags2 srcAccessMask, - vk::AccessFlags2 dstAccessMask, - vk::PipelineStageFlags2 srcStageMask, - vk::PipelineStageFlags2 dstStageMask, - uint32_t mipLevels = 1, - uint32_t arrayLayers = 1, - uint32_t baseMipLevel = 0, - uint32_t baseArrayLayer = 0 - ) { - vk::CommandBufferAllocateInfo transitionCommandBufferAllocInfo { - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1, - }; - vk::raii::CommandBuffer transitionCommandBuffer = std::move(logicalDevice->allocateCommandBuffers(transitionCommandBufferAllocInfo).front()); - - transitionCommandBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - - vk::ImageMemoryBarrier2 barrier { - .srcStageMask = srcStageMask, - .srcAccessMask = srcAccessMask, - .dstStageMask = dstStageMask, - .dstAccessMask = dstAccessMask, - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = baseMipLevel, - .levelCount = mipLevels, - .baseArrayLayer = baseArrayLayer, - .layerCount = arrayLayers, - }, - }; - - vk::DependencyInfo dependencyInfo { - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier, - }; - - transitionCommandBuffer.pipelineBarrier2(dependencyInfo); - transitionCommandBuffer.end(); - - queue.submit(vk::SubmitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*transitionCommandBuffer, - }, nullptr); - queue.waitIdle(); - } - -}; - -} +} // namespace sauce diff --git a/include/app/Instance.hpp b/include/app/Instance.hpp index 96458a9f..87f25963 100644 --- a/include/app/Instance.hpp +++ b/include/app/Instance.hpp @@ -9,135 +9,139 @@ namespace sauce { -struct Instance { - Instance(const char** glfwExtensions, uint32_t glfwExtensionCount) { - constexpr vk::ApplicationInfo appInfo { - .pApplicationName = "Vulkan Playground", - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = vk::ApiVersion14 - }; - - auto extensions = getRequiredExtensions(glfwExtensions, glfwExtensionCount); - checkRequiredExtensions(extensions); - - auto validationLayers = getValidationLayers(); - if (!validationLayers.empty()) { - checkRequiredLayers(validationLayers); - } + struct Instance { + Instance(const char** glfwExtensions, uint32_t glfwExtensionCount) { + constexpr vk::ApplicationInfo appInfo{.pApplicationName = "Vulkan Playground", + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14}; + + auto extensions = getRequiredExtensions(glfwExtensions, glfwExtensionCount); + checkRequiredExtensions(extensions); + + auto validationLayers = getValidationLayers(); + if (!validationLayers.empty()) { + checkRequiredLayers(validationLayers); + } - vk::InstanceCreateInfo createInfo { + vk::InstanceCreateInfo createInfo{ #ifdef __APPLE__ - .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, + .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, #endif - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(validationLayers.size()), - .ppEnabledLayerNames = validationLayers.empty() ? nullptr : validationLayers.data(), - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data(), - }; + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(validationLayers.size()), + .ppEnabledLayerNames = validationLayers.empty() ? nullptr : validationLayers.data(), + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data(), + }; - instance = { context, createInfo }; + instance = {context, createInfo}; - setupDebugMessenger(); - } + setupDebugMessenger(); + } - const vk::raii::Instance& operator*() const & noexcept { - return instance; - } + const vk::raii::Instance& operator*() const& noexcept { + return instance; + } - const vk::raii::Instance* operator->() const & noexcept { - return &instance; - } + const vk::raii::Instance* operator->() const& noexcept { + return &instance; + } -private: - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + private: + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; #ifdef NDEBUG - constexpr static bool enableValidationLayers = false; + constexpr static bool enableValidationLayers = false; #else - constexpr static bool enableValidationLayers = true; + constexpr static bool enableValidationLayers = true; #endif - std::vector getValidationLayers() const { - if (enableValidationLayers) { - return { "VK_LAYER_KHRONOS_validation" }; - } - return {}; - } - - static std::vector getRequiredExtensions(const char** glfwExtensions, uint32_t glfwExtensionCount) { - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName); - } + std::vector getValidationLayers() const { + if (enableValidationLayers) { + return {"VK_LAYER_KHRONOS_validation"}; + } + return {}; + } + + static std::vector getRequiredExtensions(const char** glfwExtensions, + uint32_t glfwExtensionCount) { + std::vector extensions(glfwExtensions, + glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } #ifdef __APPLE__ - extensions.push_back(vk::KHRPortabilityEnumerationExtensionName); + extensions.push_back(vk::KHRPortabilityEnumerationExtensionName); #endif - return extensions; - } - - void checkRequiredExtensions(const std::vector& requiredExtensions) { - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (size_t i = 0; i < requiredExtensions.size(); ++i) { - if (std::ranges::none_of(extensionProperties.begin(), extensionProperties.end(), - [&extension = requiredExtensions[i]](auto const& extensionProperty) { - return strcmp(extensionProperty.extensionName, extension) == 0; + return extensions; + } + + void checkRequiredExtensions(const std::vector& requiredExtensions) { + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (size_t i = 0; i < requiredExtensions.size(); ++i) { + if (std::ranges::none_of( + extensionProperties.begin(), extensionProperties.end(), + [&extension = requiredExtensions[i]](auto const& extensionProperty) { + return strcmp(extensionProperty.extensionName, extension) == 0; + })) { + throw std::runtime_error("Required extension not supported: " + + std::string(requiredExtensions[i])); + } } - )) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtensions[i])); - } - } - } - - void checkRequiredLayers(const std::vector requiredLayers) { - auto layerProps = context.enumerateInstanceLayerProperties(); - for (size_t i = 0; i < requiredLayers.size(); ++i) { - if (std::ranges::none_of(layerProps.begin(), layerProps.end(), - [&requiredLayer = requiredLayers[i]](auto const& layerProp) { - return strcmp(requiredLayer, layerProp.layerName) == 0; + } + + void checkRequiredLayers(const std::vector requiredLayers) { + auto layerProps = context.enumerateInstanceLayerProperties(); + for (size_t i = 0; i < requiredLayers.size(); ++i) { + if (std::ranges::none_of( + layerProps.begin(), layerProps.end(), + [&requiredLayer = requiredLayers[i]](auto const& layerProp) { + return strcmp(requiredLayer, layerProp.layerName) == 0; + })) { + throw std::runtime_error("Required layer not supported: " + + std::string(requiredLayers[i])); + } + } + } + + void setupDebugMessenger() { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags{ + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError}; + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags{ + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation}; + + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback, + }; + + debugMessenger = {instance, debugUtilsMessengerCreateInfoEXT}; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL + debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, + vk::DebugUtilsMessageTypeFlagsEXT type, + const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { + if (severity >= vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { + std::cerr << "Validation layer: type " << to_string(type) + << " msg: " << pCallbackData->pMessage << std::endl; } - )) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayers[i])); - } - } - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags { vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eError }; - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags { vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | - vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation }; - - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT { - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback, - }; - - debugMessenger = { instance, debugUtilsMessengerCreateInfoEXT }; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback( - vk::DebugUtilsMessageSeverityFlagBitsEXT severity, - vk::DebugUtilsMessageTypeFlagsEXT type, - const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, - void* - ) { - if (severity >= vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "Validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - return vk::False; - } -}; + return vk::False; + } + }; -} +} // namespace sauce diff --git a/include/app/Log.hpp b/include/app/Log.hpp index 45fd3075..42a3ca5e 100644 --- a/include/app/Log.hpp +++ b/include/app/Log.hpp @@ -1,50 +1,51 @@ #pragma once -#include +#include +#include #include #include -#include -#include +#include #include namespace sauce { -class Log { -public: - static void init(const std::string& filepath = "sauceengine.log"); - static void shutdown(); - - static void setPalantirMode(bool enabled); - static bool isPalantirMode(); - - template - static void info(const std::string& category, std::format_string fmt, Args&&... args) { - write(category, std::format(fmt, std::forward(args)...)); - } - - template - static void verbose(const std::string& category, std::format_string fmt, Args&&... args) { - if (!palantirMode) return; - write(category, std::format(fmt, std::forward(args)...)); - } - -private: - static void write(const std::string& category, const std::string& message); - - static std::ofstream logFile; - static std::mutex logMutex; - static bool palantirMode; - static bool initialized; -}; + class Log { + public: + static void init(const std::string& filepath = "sauceengine.log"); + static void shutdown(); + + static void setPalantirMode(bool enabled); + static bool isPalantirMode(); + + template + static void info(const std::string& category, std::format_string fmt, + Args&&... args) { + write(category, std::format(fmt, std::forward(args)...)); + } + + template + static void verbose(const std::string& category, std::format_string fmt, + Args&&... args) { + if (!palantirMode) + return; + write(category, std::format(fmt, std::forward(args)...)); + } + + private: + static void write(const std::string& category, const std::string& message); + + static std::ofstream logFile; + static std::mutex logMutex; + static bool palantirMode; + static bool initialized; + }; } // namespace sauce #ifdef NDEBUG - #define SAUCE_LOG(category, ...) - #define SAUCE_LOG_VERBOSE(category, ...) +#define SAUCE_LOG(category, ...) +#define SAUCE_LOG_VERBOSE(category, ...) #else - #define SAUCE_LOG(category, ...) \ - sauce::Log::info(category, __VA_ARGS__) - #define SAUCE_LOG_VERBOSE(category, ...) \ - sauce::Log::verbose(category, __VA_ARGS__) +#define SAUCE_LOG(category, ...) sauce::Log::info(category, __VA_ARGS__) +#define SAUCE_LOG_VERBOSE(category, ...) sauce::Log::verbose(category, __VA_ARGS__) #endif diff --git a/include/app/LogicalDevice.hpp b/include/app/LogicalDevice.hpp index 7d837ae0..034b7015 100644 --- a/include/app/LogicalDevice.hpp +++ b/include/app/LogicalDevice.hpp @@ -10,71 +10,77 @@ namespace sauce { -struct LogicalDevice { - - LogicalDevice(std::nullptr_t) {} - - LogicalDevice(const sauce::PhysicalDevice& physicalDevice, const sauce::RenderSurface& surface) { - std::vector queueFamilyProps = (*physicalDevice).getQueueFamilyProperties(); - - auto graphicsQueueFamilyProp = std::ranges::find_if(queueFamilyProps, [&](const vk::QueueFamilyProperties& prop){ - return (prop.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast(0); - }); - - assert(graphicsQueueFamilyProp != queueFamilyProps.end()); - - queueIndex = static_cast(std::distance(queueFamilyProps.begin(), graphicsQueueFamilyProp)); - - - if (!(*physicalDevice).getSurfaceSupportKHR(queueIndex, **surface)) { - throw std::runtime_error("Presentation not supported in the chosen queue!"); - } - - vk::StructureChain< - vk::PhysicalDeviceFeatures2, - vk::PhysicalDeviceVulkan11Features, - vk::PhysicalDeviceVulkan13Features, - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - > featureChain - { - {.features = {.samplerAnisotropy = true }}, - { .shaderDrawParameters = true }, - { .synchronization2 = true, .dynamicRendering = true, }, - { .extendedDynamicState = true }, + struct LogicalDevice { + + LogicalDevice(std::nullptr_t) { + } + + LogicalDevice(const sauce::PhysicalDevice& physicalDevice, + const sauce::RenderSurface& surface) { + std::vector queueFamilyProps = + (*physicalDevice).getQueueFamilyProperties(); + + auto graphicsQueueFamilyProp = + std::ranges::find_if(queueFamilyProps, [&](const vk::QueueFamilyProperties& prop) { + return (prop.queueFlags & vk::QueueFlagBits::eGraphics) != + static_cast(0); + }); + + assert(graphicsQueueFamilyProp != queueFamilyProps.end()); + + queueIndex = static_cast( + std::distance(queueFamilyProps.begin(), graphicsQueueFamilyProp)); + + if (!(*physicalDevice).getSurfaceSupportKHR(queueIndex, **surface)) { + throw std::runtime_error("Presentation not supported in the chosen queue!"); + } + + vk::StructureChain + featureChain{ + {.features = {.samplerAnisotropy = true}}, + {.shaderDrawParameters = true}, + { + .synchronization2 = true, + .dynamicRendering = true, + }, + {.extendedDynamicState = true}, + }; + + float queuePriority = 0.5f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ + .queueFamilyIndex = queueIndex, + .queueCount = 1, + .pQueuePriorities = &queuePriority, + }; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = + static_cast(physicalDevice.requiredExtensions.size()), + .ppEnabledExtensionNames = physicalDevice.requiredExtensions.data(), + }; + + device = vk::raii::Device{*physicalDevice, deviceCreateInfo}; + } + + const vk::raii::Device& operator*() const& noexcept { + return device; + } + + const vk::raii::Device* operator->() const& noexcept { + return &device; + } + + uint32_t getQueueIndex() const noexcept { + return queueIndex; + } + + private: + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; }; - float queuePriority = 0.5f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo { - .queueFamilyIndex = queueIndex, - .queueCount = 1, - .pQueuePriorities = &queuePriority, - }; - vk::DeviceCreateInfo deviceCreateInfo { - .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(physicalDevice.requiredExtensions.size()), - .ppEnabledExtensionNames = physicalDevice.requiredExtensions.data(), - }; - - device = vk::raii::Device { *physicalDevice, deviceCreateInfo }; - } - - const vk::raii::Device& operator*() const & noexcept { - return device; - } - - const vk::raii::Device* operator->() const & noexcept { - return &device; - } - - uint32_t getQueueIndex() const noexcept { - return queueIndex; - } - -private: - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; -}; - -} +} // namespace sauce diff --git a/include/app/ModelViewerRenderer.hpp b/include/app/ModelViewerRenderer.hpp index f60e65c0..a90134c1 100644 --- a/include/app/ModelViewerRenderer.hpp +++ b/include/app/ModelViewerRenderer.hpp @@ -17,90 +17,89 @@ #include #include #include -#include #include +#include namespace sauce { -struct MeshGPUData { - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - uint32_t indexCount = 0; -}; + struct MeshGPUData { + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + uint32_t indexCount = 0; + }; -struct RendererCreateInfo; + struct RendererCreateInfo; -class ModelViewerRenderer { -public: - static constexpr int MAX_FRAMES_IN_FLIGHT = 2; + class ModelViewerRenderer { + public: + static constexpr int MAX_FRAMES_IN_FLIGHT = 2; - ModelViewerRenderer(const RendererCreateInfo& createInfo); + ModelViewerRenderer(const RendererCreateInfo& createInfo); - void loadModel(const std::string& modelPath, - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice); + void loadModel(const std::string& modelPath, const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice); - void drawFrame(const sauce::LogicalDevice& logicalDevice, - const sauce::Camera& camera, - float modelRotationAngle, - sauce::ImGuiRenderer* imguiRenderer = nullptr); + void drawFrame(const sauce::LogicalDevice& logicalDevice, const sauce::Camera& camera, + float modelRotationAngle, sauce::ImGuiRenderer* imguiRenderer = nullptr); - const vk::raii::Queue& getQueue() const { return *pQueue; } - const sauce::SwapChain& getSwapChain() const { return *pSwapChain; } - const vk::raii::CommandPool& getCommandPool() const { return commandPool; } + const vk::raii::Queue& getQueue() const { + return *pQueue; + } + const sauce::SwapChain& getSwapChain() const { + return *pSwapChain; + } + const vk::raii::CommandPool& getCommandPool() const { + return commandPool; + } -private: - std::vector meshBuffers; - std::shared_ptr pModel; + private: + std::vector meshBuffers; + std::shared_ptr pModel; - void uploadMeshToGPU( - const std::shared_ptr& mesh, - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice); + void uploadMeshToGPU(const std::shared_ptr& mesh, + const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice); - void createDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice); - void createDescriptorSets(const sauce::LogicalDevice& logicalDevice); - void createUniformBuffers( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice); + void createDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice); + void createDescriptorSets(const sauce::LogicalDevice& logicalDevice); + void createUniformBuffers(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice); - void transitionImageLayout( - uint32_t imageIndex, - vk::ImageLayout oldLayout, - vk::ImageLayout newLayout, - vk::AccessFlags2 srcAccessMask, - vk::AccessFlags2 dstAccessMask, - vk::PipelineStageFlags2 srcStageMask, - vk::PipelineStageFlags2 dstStageMask); + void transitionImageLayout(uint32_t imageIndex, vk::ImageLayout oldLayout, + vk::ImageLayout newLayout, vk::AccessFlags2 srcAccessMask, + vk::AccessFlags2 dstAccessMask, + vk::PipelineStageFlags2 srcStageMask, + vk::PipelineStageFlags2 dstStageMask); - void recordCommandBuffer(uint32_t imageIndex, sauce::ImGuiRenderer* imguiRenderer); + void recordCommandBuffer(uint32_t imageIndex, sauce::ImGuiRenderer* imguiRenderer); - void updateUniformBuffer(uint32_t curImage, const sauce::Camera& camera, float modelRotationAngle); + void updateUniformBuffer(uint32_t curImage, const sauce::Camera& camera, + float modelRotationAngle); - std::unique_ptr pQueue; - std::unique_ptr pSwapChain; + std::unique_ptr pQueue; + std::unique_ptr pSwapChain; - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; - std::vector presentCompleteSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; - uint32_t frameIndex = 0; - uint32_t queueIndex = ~0; + uint32_t frameIndex = 0; + uint32_t queueIndex = ~0; - std::unique_ptr pPipeline; + std::unique_ptr pPipeline; - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; -}; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + }; -} +} // namespace sauce diff --git a/include/app/PhysicalDevice.hpp b/include/app/PhysicalDevice.hpp index 46b0e35e..a51d8fce 100644 --- a/include/app/PhysicalDevice.hpp +++ b/include/app/PhysicalDevice.hpp @@ -6,89 +6,97 @@ namespace sauce { -struct PhysicalDevice { - - PhysicalDevice(std::nullptr_t) {} - - PhysicalDevice(const sauce::Instance& instance) { - std::vector devices = (*instance).enumeratePhysicalDevices(); - - const auto devIter = std::ranges::find_if(devices, [&](const vk::raii::PhysicalDevice& device) { - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - std::vector queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphicsAndPresent = checkQueueFamilySupport(device.getQueueFamilyProperties(), vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eGraphics); - bool supportsGraphics = std::ranges::any_of(queueFamilies, [](const vk::QueueFamilyProperties& qf) { - return !!(qf.queueFlags & vk::QueueFlagBits::eGraphics); - }); - - bool supportsAllRequiredDeviceExtensions = checkRequiredExtensions(device, requiredExtensions); - bool supportsRequiredFeatures = checkRequiredFeatures(device); - - return supportsVulkan1_3 && supportsGraphicsAndPresent && supportsAllRequiredDeviceExtensions && supportsRequiredFeatures; - }); - - if (devIter != devices.end()) { - physicalDevice = *devIter; - } else { - throw std::runtime_error("Failed to find suitable GPU!"); - } - } - - PhysicalDevice(vk::raii::PhysicalDevice& device) { - physicalDevice = device; - } - - static bool checkRequiredExtensions(const vk::raii::PhysicalDevice& device, const std::vector requiredExtensions) { - auto availableExtensionProps = device.enumerateDeviceExtensionProperties(); - return std::ranges::all_of(requiredExtensions, [&availableExtensionProps](const char* ext) { - return std::ranges::any_of(availableExtensionProps, [&ext](const vk::ExtensionProperties& extProp){ - return strcmp(ext, extProp.extensionName) == 0; - }); - }); - } - - static bool checkRequiredFeatures(const vk::raii::PhysicalDevice& device) { - auto availableFeatures = device.getFeatures2< - vk::PhysicalDeviceFeatures2, - vk::PhysicalDeviceVulkan11Features, - vk::PhysicalDeviceVulkan13Features, - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - >(); - - return - availableFeatures.get().shaderDrawParameters && - availableFeatures.get().synchronization2 && - availableFeatures.get().dynamicRendering && - availableFeatures.get().extendedDynamicState; - } - - static bool checkQueueFamilySupport(const std::vector& qfProps, vk::QueueFlags mask) { - return std::ranges::any_of(qfProps, [&mask](const vk::QueueFamilyProperties& qfProp){ - return !!(qfProp.queueFlags & mask); - }); - } - - const vk::raii::PhysicalDevice& operator*() const & noexcept{ - return physicalDevice; - } - - const vk::raii::PhysicalDevice* operator->() const & noexcept { - return &physicalDevice; - } - - std::vector requiredExtensions = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName, - #ifdef __APPLE__ - "VK_KHR_portability_subset", - #endif - }; - - -private: - vk::raii::PhysicalDevice physicalDevice = nullptr; -}; - -} + struct PhysicalDevice { + PhysicalDevice(std::nullptr_t) { + } + + PhysicalDevice(const sauce::Instance& instance) { + std::vector devices = (*instance).enumeratePhysicalDevices(); + + const auto devIter = + std::ranges::find_if(devices, [&](const vk::raii::PhysicalDevice& device) { + bool supportsVulkan1_3 = + device.getProperties().apiVersion >= VK_API_VERSION_1_3; + std::vector queueFamilies = + device.getQueueFamilyProperties(); + bool supportsGraphicsAndPresent = checkQueueFamilySupport( + device.getQueueFamilyProperties(), + vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eGraphics); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](const vk::QueueFamilyProperties& qf) { + return !!(qf.queueFlags & vk::QueueFlagBits::eGraphics); + }); + + bool supportsAllRequiredDeviceExtensions = + checkRequiredExtensions(device, requiredExtensions); + bool supportsRequiredFeatures = checkRequiredFeatures(device); + + return supportsVulkan1_3 && supportsGraphicsAndPresent && + supportsAllRequiredDeviceExtensions && supportsRequiredFeatures; + }); + + if (devIter != devices.end()) { + physicalDevice = *devIter; + } else { + throw std::runtime_error("Failed to find suitable GPU!"); + } + } + + PhysicalDevice(vk::raii::PhysicalDevice& device) { + physicalDevice = device; + } + + static bool checkRequiredExtensions(const vk::raii::PhysicalDevice& device, + const std::vector requiredExtensions) { + auto availableExtensionProps = device.enumerateDeviceExtensionProperties(); + return std::ranges::all_of( + requiredExtensions, [&availableExtensionProps](const char* ext) { + return std::ranges::any_of(availableExtensionProps, + [&ext](const vk::ExtensionProperties& extProp) { + return strcmp(ext, extProp.extensionName) == 0; + }); + }); + } + + static bool checkRequiredFeatures(const vk::raii::PhysicalDevice& device) { + auto availableFeatures = + device.getFeatures2(); + + return availableFeatures.get() + .shaderDrawParameters && + availableFeatures.get().synchronization2 && + availableFeatures.get().dynamicRendering && + availableFeatures.get() + .extendedDynamicState; + } + + static bool checkQueueFamilySupport(const std::vector& qfProps, + vk::QueueFlags mask) { + return std::ranges::any_of(qfProps, [&mask](const vk::QueueFamilyProperties& qfProp) { + return !!(qfProp.queueFlags & mask); + }); + } + + const vk::raii::PhysicalDevice& operator*() const& noexcept { + return physicalDevice; + } + + const vk::raii::PhysicalDevice* operator->() const& noexcept { + return &physicalDevice; + } + + std::vector requiredExtensions = { + vk::KHRSwapchainExtensionName, vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, vk::KHRCreateRenderpass2ExtensionName, +#ifdef __APPLE__ + "VK_KHR_portability_subset", +#endif + }; + + private: + vk::raii::PhysicalDevice physicalDevice = nullptr; + }; + +} // namespace sauce diff --git a/include/app/RenderSurface.hpp b/include/app/RenderSurface.hpp index ceb28ee1..0c0f43f0 100644 --- a/include/app/RenderSurface.hpp +++ b/include/app/RenderSurface.hpp @@ -11,42 +11,44 @@ namespace sauce { -constexpr uint32_t WIDTH = 1280; -constexpr uint32_t HEIGHT = 720; + constexpr uint32_t WIDTH = 1280; + constexpr uint32_t HEIGHT = 720; -/** + /** * Bridge between Vulkan and the window system (GLFW in this case). * A surface represents a platform-specific window or display that Vulkan can render to. * GLFW handles the platform-specific creation (Win32, X11, Wayland, etc.). */ -struct RenderSurface { - - /** + struct RenderSurface { + /** * Creates a Vulkan surface for the given window. Will immediately throw on failure. */ - RenderSurface(const sauce::Instance& instance, GLFWwindow* window) { - VkSurfaceKHR cSurface; - - // glfw abstracts away all the platform-specific stuff for us here - if (VkResult res = glfwCreateWindowSurface(**instance, window, nullptr, &cSurface); res != 0) { - throw std::runtime_error(std::string("Failed to create window surface! Error code: ") + std::to_string(res)); - } - surface = { *instance, cSurface }; // to automatically clean up - } - - /** + RenderSurface(const sauce::Instance& instance, GLFWwindow* window) { + VkSurfaceKHR cSurface; + + // glfw abstracts away all the platform-specific stuff for us here + if (VkResult res = glfwCreateWindowSurface(**instance, window, nullptr, &cSurface); + res != 0) { + throw std::runtime_error( + std::string("Failed to create window surface! Error code: ") + + std::to_string(res)); + } + surface = {*instance, cSurface}; // to automatically clean up + } + + /** * Access the underlying SurfaceKHR (the Vulkan handle type for surfaces) */ - const vk::raii::SurfaceKHR& operator*() const & noexcept { - return surface; - } + const vk::raii::SurfaceKHR& operator*() const& noexcept { + return surface; + } - const vk::raii::SurfaceKHR* operator->() const & noexcept { - return &surface; - } + const vk::raii::SurfaceKHR* operator->() const& noexcept { + return &surface; + } -private: - vk::raii::SurfaceKHR surface = nullptr; -}; + private: + vk::raii::SurfaceKHR surface = nullptr; + }; -} +} // namespace sauce diff --git a/include/app/Renderer.hpp b/include/app/Renderer.hpp index d87c6291..a60a9712 100644 --- a/include/app/Renderer.hpp +++ b/include/app/Renderer.hpp @@ -15,982 +15,1638 @@ #include #include -#include #include #include #include #include #include #include +#include #include #include namespace sauce { - -const std::vector vertices { - {{ -0.5f, -0.5f, -0.5f, }, { 0.0f, 0.0f, -1.0f, }, { 0.0f, 0.0f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, -0.5f, -0.5f, }, { 0.0f, 0.0f, -1.0f, }, { 0.0f, 0.0f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, 0.5f, -0.5f, }, { 0.0f, 0.0f, -1.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ -0.5f, 0.5f, -0.5f, }, { 0.0f, 0.0f, -1.0f, }, { 0.0f, 0.0f, }, { 1.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - - {{ -0.5f, -0.5f, 0.5f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, -0.5f, 0.5f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, 0.5f, 0.5f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ -0.5f, 0.5f, 0.5f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - - {{ -0.5f, 0.5f, 0.5f, }, {-1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ -0.5f, 0.5f, -0.5f, }, {-1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ -0.5f, -0.5f, -0.5f, }, {-1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ -0.5f, -0.5f, 0.5f, }, {-1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - - {{ 0.5f, 0.5f, 0.5f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, 0.5f, -0.5f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, -0.5f, -0.5f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, -0.5f, 0.5f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - - {{ -0.5f, -0.5f, -0.5f, }, { 0.0f, -1.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, -0.5f, -0.5f, }, { 0.0f, -1.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, -0.5f, 0.5f, }, { 0.0f, -1.0f, 0.0f, }, { 0.0f, 0.0f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ -0.5f, -0.5f, 0.5f, }, { 0.0f, -1.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - - {{ -0.5f, 0.5f, -0.5f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, 0.5f, -0.5f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, }, { 1.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ 0.5f, 0.5f, 0.5f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, - {{ -0.5f, 0.5f, 0.5f, }, { 0.0f, 1.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, 1.0f, }, { 0.0f, 0.0f, 0.0f, 0.0f, }, }, -}; - -const std::vector indices { - 1, 0, 2, 2, 0, 3, - 4, 5, 6, 6, 7, 4, - 8, 9, 10, 10, 11, 8, - 13, 12, 14, 15, 14, 12, - 16, 17, 18, 18, 19, 16, - 21, 20, 22, 23, 22, 20, -}; - -struct MaterialData { - glm::vec4 baseColorFactor{1.0f, 1.0f, 1.0f, 1.0f}; // offset 0 - float metallicFactor{0.0f}; // offset 16 (dielectric) - float roughnessFactor{0.3f}; // offset 20 (glossy) - float normalScale{1.0f}; // offset 24 - float occlusionStrength{1.0f}; // offset 28 - alignas(16) glm::vec3 emissiveFactor{0.0f}; // offset 32 - float _pad0{0.0f}; // offset 44 -}; - -struct RendererCreateInfo { - const sauce::PhysicalDevice& physicalDevice; - const sauce::LogicalDevice& logicalDevice; - const sauce::RenderSurface& renderSurface; - GLFWwindow* window; - bool vsync = true; -}; - -// Callback type for custom command buffer recording -using CommandBufferRecorder = std::function; - -class Renderer { -public: - static constexpr int MAX_FRAMES_IN_FLIGHT = 2; - static constexpr uint32_t MAX_LIGHTS = 64; - - Renderer(const RendererCreateInfo& createInfo) - : pPhysicalDevice(&createInfo.physicalDevice), - pLogicalDevice(&createInfo.logicalDevice), - pRenderSurface(&createInfo.renderSurface), - pWindow(createInfo.window) - { - queueIndex = createInfo.logicalDevice.getQueueIndex(); - pQueue = std::make_unique(*createInfo.logicalDevice, queueIndex, 0); - - pSwapChain = std::make_unique( - createInfo.physicalDevice, - createInfo.logicalDevice, - createInfo.renderSurface, - createInfo.window, - createInfo.vsync - ); - - - createDescriptorSetLayout(createInfo.logicalDevice); - - sauce::GraphicsPipelineConfig mainPipelineConfig { - .physicalDevice = createInfo.physicalDevice, - .logicalDevice = createInfo.logicalDevice, - .descriptorSetLayouts = { *descriptorSetLayout0, *descriptorSetLayout1, *modeling::Material::getDescriptorSetLayout() }, - .colorFormat = pSwapChain->getSurfaceFormat().format, - .shaderPath = "shaders/shader_pbr.spv", - }; - pPipeline = std::make_unique(mainPipelineConfig); - - sauce::GraphicsPipelineConfig postProcessPipelineConfig { - .physicalDevice = createInfo.physicalDevice, - .logicalDevice = createInfo.logicalDevice, - .descriptorSetLayouts = { *postProcessDescriptorSetLayout }, - .colorFormat = pSwapChain->getSurfaceFormat().format, - .shaderPath = "shaders/postprocess.spv", - .hasVertexInput = false, - .depthTestEnable = false, - }; - pPostProcessPipeline = std::make_unique(postProcessPipelineConfig); - - vk::CommandPoolCreateInfo commandPoolCreateInfo { - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex, - }; - - commandPool = vk::raii::CommandPool { *createInfo.logicalDevice, commandPoolCreateInfo }; - - createDepthResources(createInfo.physicalDevice, createInfo.logicalDevice); - createOffscreenResources(createInfo.physicalDevice, createInfo.logicalDevice); - createDefaultTextures(createInfo.physicalDevice, createInfo.logicalDevice); - createMaterialBuffer(createInfo.physicalDevice, createInfo.logicalDevice); - createLightSSBO(createInfo.physicalDevice, createInfo.logicalDevice); - - - vk::CommandBufferAllocateInfo allocInfo { - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT, - }; - - commandBuffers = vk::raii::CommandBuffers(*createInfo.logicalDevice, allocInfo); - - createUniformBuffers(createInfo.physicalDevice, createInfo.logicalDevice); - createVertexBuffer(createInfo.physicalDevice, createInfo.logicalDevice); - createIndexBuffer(createInfo.physicalDevice, createInfo.logicalDevice); - - createDescriptorSets(createInfo.logicalDevice); - createPostProcessDescriptorSets(createInfo.logicalDevice); - - for (size_t i = 0; i < pSwapChain->getImages().size(); ++i) { - renderFinishedSemaphores.emplace_back(*createInfo.logicalDevice, vk::SemaphoreCreateInfo{}); - } - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { - presentCompleteSemaphores.emplace_back(*createInfo.logicalDevice, vk::SemaphoreCreateInfo{}); - inFlightFences.emplace_back(*createInfo.logicalDevice, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - ~Renderer() { - modeling::Material::cleanup(); - } - - const vk::raii::Queue& getQueue() const { return *pQueue; } - const sauce::SwapChain& getSwapChain() const { return *pSwapChain; } - const vk::raii::CommandPool& getCommandPool() const { return commandPool; } - const vk::raii::DescriptorSetLayout& getDescriptorSetLayout0() const { return descriptorSetLayout0; } - const vk::raii::DescriptorSetLayout& getDescriptorSetLayout1() const { return descriptorSetLayout1; } - uint32_t getFrameIndex() const { return frameIndex; } - const vk::raii::DescriptorSet& getCurrentDescriptorSet() const { return descriptorSets[frameIndex]; } - const vk::raii::DescriptorSet& getEnvironmentDescriptorSet() const { - assert(!environmentDescriptorSets.empty() && "Environment descriptor sets not initialized!"); - return environmentDescriptorSets[0]; - } - const vk::raii::DescriptorSet& getDefaultMaterialDescriptorSet() const { - assert(!defaultMaterialDescriptorSets.empty() && "Default material descriptor sets not initialized!"); - return defaultMaterialDescriptorSets[0]; - } - void* getCurrentUniformBufferMapped() const { return uniformBuffersMapped[frameIndex]; } - - void setFramebufferResized() { framebufferResized = true; } - - const vk::raii::Image& getDepthImage() const { return depthImage; } - const vk::raii::ImageView& getDepthImageView() const { return depthImageView; } - const GraphicsPipeline& getPipeline() const { return *pPipeline; } - const vk::raii::Buffer& getCurrentUniformBuffer() const { return uniformBuffers[frameIndex]; } - const vk::raii::DescriptorPool& getDescriptorPool() const { return descriptorPool; } - const vk::raii::ImageView& getDefaultImageView() const { return defaultImageView; } - const vk::raii::Sampler& getDefaultSampler() const { return defaultSampler; } - - void setCommandBufferRecorder(CommandBufferRecorder recorder) { - customRecorder = std::move(recorder); - } - - void recreateSwapChain() { - // Handle minimized windows - int width = 0, height = 0; - glfwGetFramebufferSize(pWindow, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(pWindow, &width, &height); - glfwWaitEvents(); - } - - (*pLogicalDevice)->waitIdle(); - - // Destroy old resources in correct order - depthImageView = nullptr; - depthImageMemory = nullptr; - depthImage = nullptr; - renderFinishedSemaphores.clear(); - pSwapChain.reset(); - - // Recreate swapchain and dependent resources - pSwapChain = std::make_unique( - *pPhysicalDevice, *pLogicalDevice, *pRenderSurface, pWindow - ); - - createDepthResources(*pPhysicalDevice, *pLogicalDevice); - - for (size_t i = 0; i < pSwapChain->getImages().size(); ++i) { - renderFinishedSemaphores.emplace_back(**pLogicalDevice, vk::SemaphoreCreateInfo{}); - } - } - - void createDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice) { - // Set 0: Per-Frame Layout - std::array perFrameBindings; - // Camera UBO - perFrameBindings[0] = { .binding = 0, .descriptorType = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment }; - // Light SSBO - perFrameBindings[1] = { .binding = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment }; - - vk::DescriptorSetLayoutCreateInfo perFrameDsLayoutInfo { - .bindingCount = static_cast(perFrameBindings.size()), - .pBindings = perFrameBindings.data(), - }; - descriptorSetLayout0 = vk::raii::DescriptorSetLayout{ *logicalDevice, perFrameDsLayoutInfo }; - - // Set 1: Environment Layout (IBL Maps) - std::array environmentBindings; - // IBL Maps: Irradiance, Prefilter, BRDF LUT - environmentBindings[0] = { .binding = 0, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment }; - environmentBindings[1] = { .binding = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment }; - environmentBindings[2] = { .binding = 2, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment }; - - vk::DescriptorSetLayoutCreateInfo environmentDsLayoutInfo { - .bindingCount = static_cast(environmentBindings.size()), - .pBindings = environmentBindings.data(), - }; - descriptorSetLayout1 = vk::raii::DescriptorSetLayout{ *logicalDevice, environmentDsLayoutInfo }; - - modeling::Material::initDescriptorSetLayout(logicalDevice); - - vk::DescriptorSetLayoutBinding samplerLayoutBinding { - .binding = 0, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eFragment, - }; - vk::DescriptorSetLayoutCreateInfo ppDsLayoutInfo { - .bindingCount = 1, - .pBindings = &samplerLayoutBinding, - }; - postProcessDescriptorSetLayout = vk::raii::DescriptorSetLayout{ *logicalDevice, ppDsLayoutInfo }; - } - - void createDescriptorSets(const sauce::LogicalDevice& logicalDevice) { - std::array poolSizes {{ - { vk::DescriptorType::eUniformBuffer, 1024u }, - { vk::DescriptorType::eStorageBuffer, 1024u }, - { vk::DescriptorType::eCombinedImageSampler, 1024u }, - }}; - - vk::DescriptorPoolCreateInfo poolCreateInfo { - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = 1024u, - .poolSizeCount = static_cast(poolSizes.size()), - .pPoolSizes = poolSizes.data(), - }; - - descriptorPool = vk::raii::DescriptorPool{ *logicalDevice, poolCreateInfo }; - - // Per-frame sets - std::vector layouts{ MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout0 }; - vk::DescriptorSetAllocateInfo dsAllocInfo { - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - descriptorSets = logicalDevice->allocateDescriptorSets(dsAllocInfo); - - // Environment set - vk::DescriptorSetAllocateInfo envAllocInfo { - .descriptorPool = descriptorPool, - .descriptorSetCount = 1, - .pSetLayouts = &*descriptorSetLayout1 - }; - environmentDescriptorSets = logicalDevice->allocateDescriptorSets(envAllocInfo); - - // Default material set - vk::DescriptorSetLayout materialLayout = *modeling::Material::getDescriptorSetLayout(); - vk::DescriptorSetAllocateInfo matAllocInfo { - .descriptorPool = descriptorPool, - .descriptorSetCount = 1, - .pSetLayouts = &materialLayout - }; - defaultMaterialDescriptorSets = logicalDevice->allocateDescriptorSets(matAllocInfo); - - vk::DescriptorBufferInfo lightSSBOInfo { .buffer = *lightSSBO, .offset = 0, .range = lightSSBOSize }; - - // Fallback for IBL maps - vk::DescriptorImageInfo iblInfo { .sampler = *defaultSampler, .imageView = *defaultImageView, .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal }; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { - vk::DescriptorBufferInfo uboInfo { .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject) }; - - std::array writes; - writes[0] = { .dstSet = descriptorSets[i], .dstBinding = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &uboInfo }; - writes[1] = { .dstSet = descriptorSets[i], .dstBinding = 1, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pBufferInfo = &lightSSBOInfo }; - - logicalDevice->updateDescriptorSets(writes, {}); - } - - std::array envWrites; - envWrites[0] = { .dstSet = environmentDescriptorSets[0], .dstBinding = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .pImageInfo = &iblInfo }; - envWrites[1] = { .dstSet = environmentDescriptorSets[0], .dstBinding = 1, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .pImageInfo = &iblInfo }; - envWrites[2] = { .dstSet = environmentDescriptorSets[0], .dstBinding = 2, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .pImageInfo = &iblInfo }; - logicalDevice->updateDescriptorSets(envWrites, {}); - - vk::DescriptorBufferInfo matUboInfo { .buffer = *materialBuffer, .offset = 0, .range = sizeof(MaterialData) }; - vk::DescriptorImageInfo defaultImageInfo { .sampler = *defaultSampler, .imageView = *defaultImageView, .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal }; - - std::array matWrites; - for (uint32_t i = 0; i < 5; ++i) { - matWrites[i] = { .dstSet = defaultMaterialDescriptorSets[0], .dstBinding = i, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .pImageInfo = &defaultImageInfo }; - } - matWrites[5] = { .dstSet = defaultMaterialDescriptorSets[0], .dstBinding = 5, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &matUboInfo }; - logicalDevice->updateDescriptorSets(matWrites, {}); - } - - void createPostProcessDescriptorSets(const sauce::LogicalDevice& logicalDevice) { - std::vector layouts{ 1, *postProcessDescriptorSetLayout }; - vk::DescriptorSetAllocateInfo dsAllocInfo { - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - postProcessDescriptorSets = logicalDevice->allocateDescriptorSets(dsAllocInfo); - - vk::DescriptorImageInfo imageInfo { - .sampler = *offscreenSampler, - .imageView = *offscreenImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + const std::vector vertices{ + { + { + -0.5f, + -0.5f, + -0.5f, + }, + { + 0.0f, + 0.0f, + -1.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + -0.5f, + -0.5f, + }, + { + 0.0f, + 0.0f, + -1.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + 0.5f, + -0.5f, + }, + { + 0.0f, + 0.0f, + -1.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + -0.5f, + 0.5f, + -0.5f, + }, + { + 0.0f, + 0.0f, + -1.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 1.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + + { + { + -0.5f, + -0.5f, + 0.5f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + -0.5f, + 0.5f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + 0.5f, + 0.5f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + -0.5f, + 0.5f, + 0.5f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + + { + { + -0.5f, + 0.5f, + 0.5f, + }, + { + -1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + -0.5f, + 0.5f, + -0.5f, + }, + { + -1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + -0.5f, + -0.5f, + -0.5f, + }, + { + -1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + -0.5f, + -0.5f, + 0.5f, + }, + { + -1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + + { + { + 0.5f, + 0.5f, + 0.5f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + 0.5f, + -0.5f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + -0.5f, + -0.5f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + -0.5f, + 0.5f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + + { + { + -0.5f, + -0.5f, + -0.5f, + }, + { + 0.0f, + -1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + -0.5f, + -0.5f, + }, + { + 0.0f, + -1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + -0.5f, + 0.5f, + }, + { + 0.0f, + -1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + -0.5f, + -0.5f, + 0.5f, + }, + { + 0.0f, + -1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + + { + { + -0.5f, + 0.5f, + -0.5f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + 0.5f, + -0.5f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 1.0f, + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + 0.5f, + 0.5f, + 0.5f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, + { + { + -0.5f, + 0.5f, + 0.5f, + }, + { + 0.0f, + 1.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + }, + { + 0.0f, + 0.0f, + 1.0f, + }, + { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + }, + }, }; - vk::WriteDescriptorSet descriptorWrite { - .dstSet = postProcessDescriptorSets[0], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo, + const std::vector indices{ + 1, 0, 2, 2, 0, 3, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, + 13, 12, 14, 15, 14, 12, 16, 17, 18, 18, 19, 16, 21, 20, 22, 23, 22, 20, }; - logicalDevice->updateDescriptorSets(descriptorWrite, {}); - } - - - void createDepthResources(const sauce::PhysicalDevice& physicalDevice, const sauce::LogicalDevice& logicalDevice) { - vk::Format depthFormat = GraphicsPipeline::findDepthFormat(physicalDevice); - ImageUtils::createImage( - physicalDevice, - logicalDevice, - pSwapChain->getExtent().width, - pSwapChain->getExtent().height, - depthFormat, - vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eDepthStencilAttachment, - vk::MemoryPropertyFlagBits::eDeviceLocal, - depthImage, - depthImageMemory - ); - depthImageView = ImageUtils::createImageView(logicalDevice, depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - void createOffscreenResources(const sauce::PhysicalDevice& physicalDevice, const sauce::LogicalDevice& logicalDevice) { - vk::Format colorFormat = pSwapChain->getSurfaceFormat().format; - ImageUtils::createImage( - physicalDevice, - logicalDevice, - pSwapChain->getExtent().width, - pSwapChain->getExtent().height, - colorFormat, - vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, - offscreenImage, - offscreenImageMemory - ); - offscreenImageView = ImageUtils::createImageView(logicalDevice, offscreenImage, colorFormat, vk::ImageAspectFlagBits::eColor); - - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eClampToEdge, - .addressModeV = vk::SamplerAddressMode::eClampToEdge, - .addressModeW = vk::SamplerAddressMode::eClampToEdge, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::False, - .maxAnisotropy = 1.0f, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways, - .minLod = 0.0f, - .maxLod = 0.0f, - .borderColor = vk::BorderColor::eIntOpaqueBlack, - .unnormalizedCoordinates = vk::False, - }; - offscreenSampler = vk::raii::Sampler(*logicalDevice, samplerInfo); - } - - void createUniformBuffers( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice - ) { - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { - vk::DeviceSize size = sizeof(UniformBufferObject); - vk::raii::Buffer buf = nullptr; - vk::raii::DeviceMemory mem = nullptr; - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - size, - vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eTransferDst, - vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible, - buf, - mem - ); - - uniformBuffers.emplace_back(std::move(buf)); - uniformBuffersMemory.emplace_back(std::move(mem)); - uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, size)); - } - } - - void createVertexBuffer( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice - ) { - vk::DeviceSize bufferSize = sizeof(Vertex) * vertices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - bufferSize, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, - stagingBufferMemory - ); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - bufferSize, - vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, - vk::MemoryPropertyFlagBits::eDeviceLocal, - vertexBuffer, - vertexBufferMemory - ); - - sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, *pQueue, stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice - ) { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - bufferSize, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, - stagingBufferMemory - ); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - bufferSize, - vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, - vk::MemoryPropertyFlagBits::eDeviceLocal, - indexBuffer, - indexBufferMemory - ); - - sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, *pQueue, stagingBuffer, indexBuffer, bufferSize); - } - - void transitionImageLayout( - const vk::raii::CommandBuffer& cmdBuf, - vk::Image image, - vk::ImageLayout oldLayout, - vk::ImageLayout newLayout, - vk::AccessFlags2 srcAccessMask, - vk::AccessFlags2 dstAccessMask, - vk::PipelineStageFlags2 srcStageMask, - vk::PipelineStageFlags2 dstStageMask, - vk::ImageAspectFlags imageAspectFlags - ) { - vk::ImageMemoryBarrier2 barrier { - .srcStageMask = srcStageMask, - .srcAccessMask = srcAccessMask, - .dstStageMask = dstStageMask, - .dstAccessMask = dstAccessMask, - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = vk::QueueFamilyIgnored, - .dstQueueFamilyIndex = vk::QueueFamilyIgnored, - .image = image, - .subresourceRange = { - .aspectMask = imageAspectFlags, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }; - - vk::DependencyInfo dependencyInfo { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier, + struct MaterialData { + glm::vec4 baseColorFactor{1.0f, 1.0f, 1.0f, 1.0f}; // offset 0 + float metallicFactor{0.0f}; // offset 16 (dielectric) + float roughnessFactor{0.3f}; // offset 20 (glossy) + float normalScale{1.0f}; // offset 24 + float occlusionStrength{1.0f}; // offset 28 + alignas(16) glm::vec3 emissiveFactor{0.0f}; // offset 32 + float _pad0{0.0f}; // offset 44 }; - cmdBuf.pipelineBarrier2(dependencyInfo); - } - - void recordCommandBuffer(uint32_t imageIndex, sauce::ImGuiRenderer* imguiRenderer){ - commandBuffers[frameIndex].begin({}); - - // Transition offscreen image for rendering - transitionImageLayout( - commandBuffers[frameIndex], - *offscreenImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - - transitionImageLayout( - commandBuffers[frameIndex], - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - vk::ClearValue clearColor = vk::ClearColorValue { 0.0f, 0.0f, 0.0f, 1.0f }; - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - // Pass 1: Render Scene to Offscreen Image - vk::RenderingAttachmentInfo colorAttachmentInfo = { - .imageView = *offscreenImageView, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor, + struct RendererCreateInfo { + const sauce::PhysicalDevice& physicalDevice; + const sauce::LogicalDevice& logicalDevice; + const sauce::RenderSurface& renderSurface; + GLFWwindow* window; + bool vsync = true; }; - vk::RenderingAttachmentInfo depthAttachmentInfo { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth, + // Callback type for custom command buffer recording + using CommandBufferRecorder = std::function; + + class Renderer { + public: + static constexpr int MAX_FRAMES_IN_FLIGHT = 2; + static constexpr uint32_t MAX_LIGHTS = 64; + + Renderer(const RendererCreateInfo& createInfo) + : pPhysicalDevice(&createInfo.physicalDevice), + pLogicalDevice(&createInfo.logicalDevice), pRenderSurface(&createInfo.renderSurface), + pWindow(createInfo.window) { + queueIndex = createInfo.logicalDevice.getQueueIndex(); + pQueue = std::make_unique(*createInfo.logicalDevice, queueIndex, 0); + + pSwapChain = std::make_unique( + createInfo.physicalDevice, createInfo.logicalDevice, createInfo.renderSurface, + createInfo.window, createInfo.vsync); + + createDescriptorSetLayout(createInfo.logicalDevice); + + sauce::GraphicsPipelineConfig mainPipelineConfig{ + .physicalDevice = createInfo.physicalDevice, + .logicalDevice = createInfo.logicalDevice, + .descriptorSetLayouts = {*descriptorSetLayout0, *descriptorSetLayout1, + *modeling::Material::getDescriptorSetLayout()}, + .colorFormat = pSwapChain->getSurfaceFormat().format, + .shaderPath = "shaders/shader_pbr.spv", + }; + pPipeline = std::make_unique(mainPipelineConfig); + + sauce::GraphicsPipelineConfig postProcessPipelineConfig{ + .physicalDevice = createInfo.physicalDevice, + .logicalDevice = createInfo.logicalDevice, + .descriptorSetLayouts = {*postProcessDescriptorSetLayout}, + .colorFormat = pSwapChain->getSurfaceFormat().format, + .shaderPath = "shaders/postprocess.spv", + .hasVertexInput = false, + .depthTestEnable = false, + }; + pPostProcessPipeline = + std::make_unique(postProcessPipelineConfig); + + vk::CommandPoolCreateInfo commandPoolCreateInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex, + }; + + commandPool = vk::raii::CommandPool{*createInfo.logicalDevice, commandPoolCreateInfo}; + + createDepthResources(createInfo.physicalDevice, createInfo.logicalDevice); + createOffscreenResources(createInfo.physicalDevice, createInfo.logicalDevice); + createDefaultTextures(createInfo.physicalDevice, createInfo.logicalDevice); + createMaterialBuffer(createInfo.physicalDevice, createInfo.logicalDevice); + createLightSSBO(createInfo.physicalDevice, createInfo.logicalDevice); + + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = MAX_FRAMES_IN_FLIGHT, + }; + + commandBuffers = vk::raii::CommandBuffers(*createInfo.logicalDevice, allocInfo); + + createUniformBuffers(createInfo.physicalDevice, createInfo.logicalDevice); + createVertexBuffer(createInfo.physicalDevice, createInfo.logicalDevice); + createIndexBuffer(createInfo.physicalDevice, createInfo.logicalDevice); + + createDescriptorSets(createInfo.logicalDevice); + createPostProcessDescriptorSets(createInfo.logicalDevice); + + for (size_t i = 0; i < pSwapChain->getImages().size(); ++i) { + renderFinishedSemaphores.emplace_back(*createInfo.logicalDevice, + vk::SemaphoreCreateInfo{}); + } + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + presentCompleteSemaphores.emplace_back(*createInfo.logicalDevice, + vk::SemaphoreCreateInfo{}); + inFlightFences.emplace_back( + *createInfo.logicalDevice, + vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + ~Renderer() { + modeling::Material::cleanup(); + } + + const vk::raii::Queue& getQueue() const { + return *pQueue; + } + const sauce::SwapChain& getSwapChain() const { + return *pSwapChain; + } + const vk::raii::CommandPool& getCommandPool() const { + return commandPool; + } + const vk::raii::DescriptorSetLayout& getDescriptorSetLayout0() const { + return descriptorSetLayout0; + } + const vk::raii::DescriptorSetLayout& getDescriptorSetLayout1() const { + return descriptorSetLayout1; + } + uint32_t getFrameIndex() const { + return frameIndex; + } + const vk::raii::DescriptorSet& getCurrentDescriptorSet() const { + return descriptorSets[frameIndex]; + } + const vk::raii::DescriptorSet& getEnvironmentDescriptorSet() const { + assert(!environmentDescriptorSets.empty() && + "Environment descriptor sets not initialized!"); + return environmentDescriptorSets[0]; + } + const vk::raii::DescriptorSet& getDefaultMaterialDescriptorSet() const { + assert(!defaultMaterialDescriptorSets.empty() && + "Default material descriptor sets not initialized!"); + return defaultMaterialDescriptorSets[0]; + } + void* getCurrentUniformBufferMapped() const { + return uniformBuffersMapped[frameIndex]; + } + + void setFramebufferResized() { + framebufferResized = true; + } + + const vk::raii::Image& getDepthImage() const { + return depthImage; + } + const vk::raii::ImageView& getDepthImageView() const { + return depthImageView; + } + const GraphicsPipeline& getPipeline() const { + return *pPipeline; + } + const vk::raii::Buffer& getCurrentUniformBuffer() const { + return uniformBuffers[frameIndex]; + } + const vk::raii::DescriptorPool& getDescriptorPool() const { + return descriptorPool; + } + const vk::raii::ImageView& getDefaultImageView() const { + return defaultImageView; + } + const vk::raii::Sampler& getDefaultSampler() const { + return defaultSampler; + } + + void setCommandBufferRecorder(CommandBufferRecorder recorder) { + customRecorder = std::move(recorder); + } + + void recreateSwapChain() { + // Handle minimized windows + int width = 0, height = 0; + glfwGetFramebufferSize(pWindow, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(pWindow, &width, &height); + glfwWaitEvents(); + } + + (*pLogicalDevice)->waitIdle(); + + // Destroy old resources in correct order + depthImageView = nullptr; + depthImageMemory = nullptr; + depthImage = nullptr; + renderFinishedSemaphores.clear(); + pSwapChain.reset(); + + // Recreate swapchain and dependent resources + pSwapChain = std::make_unique(*pPhysicalDevice, *pLogicalDevice, + *pRenderSurface, pWindow); + + createDepthResources(*pPhysicalDevice, *pLogicalDevice); + + for (size_t i = 0; i < pSwapChain->getImages().size(); ++i) { + renderFinishedSemaphores.emplace_back(**pLogicalDevice, vk::SemaphoreCreateInfo{}); + } + } + + void createDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice) { + // Set 0: Per-Frame Layout + std::array perFrameBindings; + // Camera UBO + perFrameBindings[0] = {.binding = 0, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eVertex | + vk::ShaderStageFlagBits::eFragment}; + // Light SSBO + perFrameBindings[1] = {.binding = 1, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + + vk::DescriptorSetLayoutCreateInfo perFrameDsLayoutInfo{ + .bindingCount = static_cast(perFrameBindings.size()), + .pBindings = perFrameBindings.data(), + }; + descriptorSetLayout0 = + vk::raii::DescriptorSetLayout{*logicalDevice, perFrameDsLayoutInfo}; + + // Set 1: Environment Layout (IBL Maps) + std::array environmentBindings; + // IBL Maps: Irradiance, Prefilter, BRDF LUT + environmentBindings[0] = {.binding = 0, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + environmentBindings[1] = {.binding = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + environmentBindings[2] = {.binding = 2, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + + vk::DescriptorSetLayoutCreateInfo environmentDsLayoutInfo{ + .bindingCount = static_cast(environmentBindings.size()), + .pBindings = environmentBindings.data(), + }; + descriptorSetLayout1 = + vk::raii::DescriptorSetLayout{*logicalDevice, environmentDsLayoutInfo}; + + modeling::Material::initDescriptorSetLayout(logicalDevice); + + vk::DescriptorSetLayoutBinding samplerLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + }; + vk::DescriptorSetLayoutCreateInfo ppDsLayoutInfo{ + .bindingCount = 1, + .pBindings = &samplerLayoutBinding, + }; + postProcessDescriptorSetLayout = + vk::raii::DescriptorSetLayout{*logicalDevice, ppDsLayoutInfo}; + } + + void createDescriptorSets(const sauce::LogicalDevice& logicalDevice) { + std::array poolSizes{{ + {vk::DescriptorType::eUniformBuffer, 1024u}, + {vk::DescriptorType::eStorageBuffer, 1024u}, + {vk::DescriptorType::eCombinedImageSampler, 1024u}, + }}; + + vk::DescriptorPoolCreateInfo poolCreateInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = 1024u, + .poolSizeCount = static_cast(poolSizes.size()), + .pPoolSizes = poolSizes.data(), + }; + + descriptorPool = vk::raii::DescriptorPool{*logicalDevice, poolCreateInfo}; + + // Per-frame sets + std::vector layouts{MAX_FRAMES_IN_FLIGHT, + *descriptorSetLayout0}; + vk::DescriptorSetAllocateInfo dsAllocInfo{.descriptorPool = descriptorPool, + .descriptorSetCount = + static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + descriptorSets = logicalDevice->allocateDescriptorSets(dsAllocInfo); + + // Environment set + vk::DescriptorSetAllocateInfo envAllocInfo{.descriptorPool = descriptorPool, + .descriptorSetCount = 1, + .pSetLayouts = &*descriptorSetLayout1}; + environmentDescriptorSets = logicalDevice->allocateDescriptorSets(envAllocInfo); + + // Default material set + vk::DescriptorSetLayout materialLayout = *modeling::Material::getDescriptorSetLayout(); + vk::DescriptorSetAllocateInfo matAllocInfo{.descriptorPool = descriptorPool, + .descriptorSetCount = 1, + .pSetLayouts = &materialLayout}; + defaultMaterialDescriptorSets = logicalDevice->allocateDescriptorSets(matAllocInfo); + + vk::DescriptorBufferInfo lightSSBOInfo{ + .buffer = *lightSSBO, .offset = 0, .range = lightSSBOSize}; + + // Fallback for IBL maps + vk::DescriptorImageInfo iblInfo{.sampler = *defaultSampler, + .imageView = *defaultImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + vk::DescriptorBufferInfo uboInfo{ + .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + + std::array writes; + writes[0] = {.dstSet = descriptorSets[i], + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &uboInfo}; + writes[1] = {.dstSet = descriptorSets[i], + .dstBinding = 1, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .pBufferInfo = &lightSSBOInfo}; + + logicalDevice->updateDescriptorSets(writes, {}); + } + + std::array envWrites; + envWrites[0] = {.dstSet = environmentDescriptorSets[0], + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &iblInfo}; + envWrites[1] = {.dstSet = environmentDescriptorSets[0], + .dstBinding = 1, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &iblInfo}; + envWrites[2] = {.dstSet = environmentDescriptorSets[0], + .dstBinding = 2, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &iblInfo}; + logicalDevice->updateDescriptorSets(envWrites, {}); + + vk::DescriptorBufferInfo matUboInfo{ + .buffer = *materialBuffer, .offset = 0, .range = sizeof(MaterialData)}; + vk::DescriptorImageInfo defaultImageInfo{.sampler = *defaultSampler, + .imageView = *defaultImageView, + .imageLayout = + vk::ImageLayout::eShaderReadOnlyOptimal}; + + std::array matWrites; + for (uint32_t i = 0; i < 5; ++i) { + matWrites[i] = {.dstSet = defaultMaterialDescriptorSets[0], + .dstBinding = i, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &defaultImageInfo}; + } + matWrites[5] = {.dstSet = defaultMaterialDescriptorSets[0], + .dstBinding = 5, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &matUboInfo}; + logicalDevice->updateDescriptorSets(matWrites, {}); + } + + void createPostProcessDescriptorSets(const sauce::LogicalDevice& logicalDevice) { + std::vector layouts{1, *postProcessDescriptorSetLayout}; + vk::DescriptorSetAllocateInfo dsAllocInfo{.descriptorPool = descriptorPool, + .descriptorSetCount = + static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + postProcessDescriptorSets = logicalDevice->allocateDescriptorSets(dsAllocInfo); + + vk::DescriptorImageInfo imageInfo{ + .sampler = *offscreenSampler, + .imageView = *offscreenImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }; + + vk::WriteDescriptorSet descriptorWrite{ + .dstSet = postProcessDescriptorSets[0], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo, + }; + + logicalDevice->updateDescriptorSets(descriptorWrite, {}); + } + + void createDepthResources(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + vk::Format depthFormat = GraphicsPipeline::findDepthFormat(physicalDevice); + ImageUtils::createImage( + physicalDevice, logicalDevice, pSwapChain->getExtent().width, + pSwapChain->getExtent().height, depthFormat, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eDepthStencilAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = ImageUtils::createImageView(logicalDevice, depthImage, depthFormat, + vk::ImageAspectFlagBits::eDepth); + } + + void createOffscreenResources(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + vk::Format colorFormat = pSwapChain->getSurfaceFormat().format; + ImageUtils::createImage( + physicalDevice, logicalDevice, pSwapChain->getExtent().width, + pSwapChain->getExtent().height, colorFormat, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, offscreenImage, offscreenImageMemory); + offscreenImageView = ImageUtils::createImageView( + logicalDevice, offscreenImage, colorFormat, vk::ImageAspectFlagBits::eColor); + + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eClampToEdge, + .addressModeV = vk::SamplerAddressMode::eClampToEdge, + .addressModeW = vk::SamplerAddressMode::eClampToEdge, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::False, + .maxAnisotropy = 1.0f, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = vk::BorderColor::eIntOpaqueBlack, + .unnormalizedCoordinates = vk::False, + }; + offscreenSampler = vk::raii::Sampler(*logicalDevice, samplerInfo); + } + + void createUniformBuffers(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + vk::DeviceSize size = sizeof(UniformBufferObject); + vk::raii::Buffer buf = nullptr; + vk::raii::DeviceMemory mem = nullptr; + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, size, + vk::BufferUsageFlagBits::eUniformBuffer | + vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eHostCoherent | + vk::MemoryPropertyFlagBits::eHostVisible, + buf, mem); + + uniformBuffers.emplace_back(std::move(buf)); + uniformBuffersMemory.emplace_back(std::move(mem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, size)); + } + } + + void createVertexBuffer(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + vk::DeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, bufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, stagingBufferMemory); + + void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, bufferSize, + vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, *pQueue, stagingBuffer, + vertexBuffer, bufferSize); + } + + void createIndexBuffer(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, bufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, stagingBufferMemory); + + void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, bufferSize, + vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, *pQueue, stagingBuffer, + indexBuffer, bufferSize); + } + + void transitionImageLayout(const vk::raii::CommandBuffer& cmdBuf, vk::Image image, + vk::ImageLayout oldLayout, vk::ImageLayout newLayout, + vk::AccessFlags2 srcAccessMask, vk::AccessFlags2 dstAccessMask, + vk::PipelineStageFlags2 srcStageMask, + vk::PipelineStageFlags2 dstStageMask, + vk::ImageAspectFlags imageAspectFlags) { + vk::ImageMemoryBarrier2 barrier{ + .srcStageMask = srcStageMask, + .srcAccessMask = srcAccessMask, + .dstStageMask = dstStageMask, + .dstAccessMask = dstAccessMask, + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = image, + .subresourceRange = + { + .aspectMask = imageAspectFlags, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + vk::DependencyInfo dependencyInfo{ + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier, + }; + + cmdBuf.pipelineBarrier2(dependencyInfo); + } + + void recordCommandBuffer(uint32_t imageIndex, sauce::ImGuiRenderer* imguiRenderer) { + commandBuffers[frameIndex].begin({}); + + // Transition offscreen image for rendering + transitionImageLayout(commandBuffers[frameIndex], *offscreenImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::ImageAspectFlagBits::eColor); + + transitionImageLayout(commandBuffers[frameIndex], *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + vk::ClearValue clearColor = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}; + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + // Pass 1: Render Scene to Offscreen Image + vk::RenderingAttachmentInfo colorAttachmentInfo = { + .imageView = *offscreenImageView, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor, + }; + + vk::RenderingAttachmentInfo depthAttachmentInfo{ + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth, + }; + + vk::RenderingInfo renderingInfo{ + .renderArea = + { + .offset = {0, 0}, + .extent = pSwapChain->getExtent(), + }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo, + }; + + commandBuffers[frameIndex].beginRendering(renderingInfo); + + commandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, **pPipeline); + commandBuffers[frameIndex].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[frameIndex].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + pPipeline->getLayout(), 0, + *descriptorSets[frameIndex], nullptr); + commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + pPipeline->getLayout(), 1, + *environmentDescriptorSets[0], nullptr); + commandBuffers[frameIndex].bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, pPipeline->getLayout(), 2, + *defaultMaterialDescriptorSets[0], nullptr); + + commandBuffers[frameIndex].setViewport( + 0, vk::Viewport(0.0f, 0.0f, static_cast(pSwapChain->getExtent().width), + static_cast(pSwapChain->getExtent().height), 0.0f, 1.0f)); + commandBuffers[frameIndex].setScissor( + 0, vk::Rect2D(vk::Offset2D(0, 0), pSwapChain->getExtent())); + + const uint32_t lightCount = 0; + commandBuffers[frameIndex].pushConstants( + *pPipeline->getLayout(), vk::ShaderStageFlagBits::eFragment, 0u, {lightCount}); + + commandBuffers[frameIndex].drawIndexed(static_cast(indices.size()), 1, 0, 0, + 0); + + commandBuffers[frameIndex].endRendering(); + + transitionImageLayout( + commandBuffers[frameIndex], *offscreenImage, + vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::AccessFlagBits2::eColorAttachmentWrite, vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eFragmentShader, vk::ImageAspectFlagBits::eColor); + + transitionImageLayout(commandBuffers[frameIndex], pSwapChain->getImages()[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::ImageAspectFlagBits::eColor); + + vk::RenderingAttachmentInfo ppColorAttachmentInfo = { + .imageView = pSwapChain->getImageViews()[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor, + }; + + vk::RenderingInfo ppRenderingInfo{ + .renderArea = + { + .offset = {0, 0}, + .extent = pSwapChain->getExtent(), + }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &ppColorAttachmentInfo, + }; + + commandBuffers[frameIndex].beginRendering(ppRenderingInfo); + + commandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, + **pPostProcessPipeline); + commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + pPostProcessPipeline->getLayout(), 0, + *postProcessDescriptorSets[0], nullptr); + + commandBuffers[frameIndex].setViewport( + 0, vk::Viewport(0.0f, 0.0f, static_cast(pSwapChain->getExtent().width), + static_cast(pSwapChain->getExtent().height), 0.0f, 1.0f)); + commandBuffers[frameIndex].setScissor( + 0, vk::Rect2D(vk::Offset2D(0, 0), pSwapChain->getExtent())); + + commandBuffers[frameIndex].draw(3, 1, 0, 0); + + // Render ImGui overlay + if (imguiRenderer) { + imguiRenderer->render(commandBuffers[frameIndex], imageIndex); + } + + commandBuffers[frameIndex].endRendering(); + + transitionImageLayout( + commandBuffers[frameIndex], pSwapChain->getImages()[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe, vk::ImageAspectFlagBits::eColor); + + commandBuffers[frameIndex].end(); + } + + void drawFrame(const sauce::LogicalDevice& logicalDevice, const sauce::Scene& scene, + sauce::ImGuiRenderer* imguiRenderer = nullptr) { + // Wait for the previous frame to finish rendering before submitting the next frame + auto fenceResult = + logicalDevice->waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX); + if (fenceResult != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence!"); + } + + // Request the next available image from the swap chain + uint32_t imageIndex; + try { + auto [result, idx] = + (*pSwapChain) + ->acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], + nullptr); + if (result == vk::Result::eErrorOutOfDateKHR) { + recreateSwapChain(); + return; + } + imageIndex = idx; + } catch (const vk::OutOfDateKHRError&) { + recreateSwapChain(); + return; + } + + // Reset the fence for the next frame + logicalDevice->resetFences(*inFlightFences[frameIndex]); + + // Reset and record the command buffer with rendering commands + commandBuffers[frameIndex].reset(); + + if (customRecorder) { + customRecorder(commandBuffers[frameIndex], imageIndex); + } else { + recordCommandBuffer(imageIndex, imguiRenderer); + updateUniformBuffer(frameIndex, scene); + } + + // Prepare submission: wait for image to be available before starting color attachment output + vk::PipelineStageFlags waitDestinationStageMask{ + vk::PipelineStageFlagBits::eColorAttachmentOutput}; + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex], + }; + + // Submit the command buffer to the queue for execution, signaling the fence when complete + pQueue->submit(submitInfo, *inFlightFences[frameIndex]); + + // Prepare presentation: wait for rendering to finish before presenting + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &***pSwapChain, + .pImageIndices = &imageIndex, + }; + + // Present the rendered image to the screen, handling resize + try { + auto result = pQueue->presentKHR(presentInfoKHR); + if (result == vk::Result::eSuboptimalKHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + } catch (const vk::OutOfDateKHRError&) { + framebufferResized = false; + recreateSwapChain(); + } + + // Advance to the next frame in the circular buffer + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t curImage, const sauce::Scene& scene) { + // Record the start time on first call (static initialization) + static auto startTime = std::chrono::high_resolution_clock::now(); + + // Get the current time and calculate elapsed time in seconds + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + // Create uniform buffer object with transformation matrices + sauce::UniformBufferObject ubo{ + // Model matrix: rotates the object 90 degrees per second around the Z axis + .model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), + glm::vec3(0.0f, 0.0f, 1.0f)), + .view = scene.getCameraRO().getViewMatrix(), + .proj = scene.getCameraRO().getProjectionMatrix(), + .cameraPos = scene.getCameraRO().getPos(), + }; + + // Flip Y coordinate of projection matrix (Vulkan uses inverted Y compared to OpenGL) + ubo.proj[1][1] *= -1; + + // Copy the uniform buffer data to GPU-mapped memory for the current frame + memcpy(uniformBuffersMapped[curImage], &ubo, sizeof(ubo)); + } + + // Creates a 1x1 white pixel image + sampler used as fallbacks for all 5 PBR texture slots. + void createDefaultTextures(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + uint8_t whitePixel[4] = {255, 255, 255, 255}; + vk::DeviceSize pixelSize = sizeof(whitePixel); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingMemory = nullptr; + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, pixelSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, stagingMemory); + void* mapped = stagingMemory.mapMemory(0, pixelSize); + memcpy(mapped, whitePixel, pixelSize); + stagingMemory.unmapMemory(); + + ImageUtils::createImage( + physicalDevice, logicalDevice, 1, 1, vk::Format::eR8G8B8A8Unorm, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, defaultImage, defaultImageMemory); + + ImageUtils::transitionImageLayout( + logicalDevice, commandPool, *pQueue, defaultImage, vk::ImageLayout::eUndefined, + vk::ImageLayout::eTransferDstOptimal, {}, vk::AccessFlagBits2::eTransferWrite, + vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer); + ImageUtils::copyBufferToImage(logicalDevice, commandPool, *pQueue, stagingBuffer, + defaultImage, 1, 1); + ImageUtils::transitionImageLayout( + logicalDevice, commandPool, *pQueue, defaultImage, + vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::AccessFlagBits2::eTransferWrite, vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eTransfer, vk::PipelineStageFlagBits2::eFragmentShader); + + defaultImageView = + ImageUtils::createImageView(logicalDevice, defaultImage, vk::Format::eR8G8B8A8Unorm, + vk::ImageAspectFlagBits::eColor); + + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .minLod = 0.0f, + .maxLod = 0.0f, + }; + defaultSampler = vk::raii::Sampler{*logicalDevice, samplerInfo}; + } + + // Creates a host-visible uniform buffer holding default PBR material properties. + void createMaterialBuffer(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + MaterialData defaults{}; + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, sizeof(MaterialData), + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + materialBuffer, materialBufferMemory); + void* data = materialBufferMemory.mapMemory(0, sizeof(MaterialData)); + memcpy(data, &defaults, sizeof(MaterialData)); + materialBufferMemory.unmapMemory(); + } + + // Pre-allocates a persistently-mapped storage buffer for up to MAX_LIGHTS. + void createLightSSBO(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + lightSSBOSize = static_cast(MAX_LIGHTS) * sizeof(GPULight); + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, lightSSBOSize, + vk::BufferUsageFlagBits::eStorageBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + lightSSBO, lightSSBOMemory); + lightSSBOMapped = lightSSBOMemory.mapMemory(0, lightSSBOSize); + } + + // Writes lights into the persistently-mapped SSBO. Returns count written (clamped to MAX_LIGHTS). + uint32_t updateLightSSBO(const GPULight* data, uint32_t count) { + count = std::min(count, MAX_LIGHTS); + if (count > 0) { + std::memcpy(lightSSBOMapped, data, count * sizeof(GPULight)); + } + return count; + } + + const vk::raii::Buffer& getMaterialBuffer() const { + return materialBuffer; + } + + private: + // Stored references for swapchain recreation + const sauce::PhysicalDevice* pPhysicalDevice; + const sauce::LogicalDevice* pLogicalDevice; + const sauce::RenderSurface* pRenderSurface; + GLFWwindow* pWindow; + + std::unique_ptr pQueue; + std::unique_ptr pSwapChain; + + vk::raii::CommandPool commandPool = nullptr; + + std::vector commandBuffers; + + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + + uint32_t frameIndex = 0; + uint32_t queueIndex = ~0; + bool framebufferResized = false; + + std::unique_ptr pPipeline; + std::unique_ptr pPostProcessPipeline; + + vk::raii::DescriptorSetLayout descriptorSetLayout0 = nullptr; + vk::raii::DescriptorSetLayout descriptorSetLayout1 = nullptr; + vk::raii::DescriptorSetLayout postProcessDescriptorSetLayout = nullptr; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + std::vector environmentDescriptorSets; + std::vector defaultMaterialDescriptorSets; + std::vector postProcessDescriptorSets; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + CommandBufferRecorder customRecorder; + + // PBR resources + vk::raii::Buffer materialBuffer = nullptr; + vk::raii::DeviceMemory materialBufferMemory = nullptr; + + vk::DeviceSize lightSSBOSize{0}; + vk::raii::Buffer lightSSBO = nullptr; + vk::raii::DeviceMemory lightSSBOMemory = nullptr; + void* lightSSBOMapped = nullptr; + + vk::raii::Image defaultImage = nullptr; + vk::raii::DeviceMemory defaultImageMemory = nullptr; + vk::raii::ImageView defaultImageView = nullptr; + vk::raii::Sampler defaultSampler = nullptr; + + vk::raii::Image offscreenImage = nullptr; + vk::raii::DeviceMemory offscreenImageMemory = nullptr; + vk::raii::ImageView offscreenImageView = nullptr; + vk::raii::Sampler offscreenSampler = nullptr; + + std::unique_ptr pIBLMaps; }; - vk::RenderingInfo renderingInfo { - .renderArea = { - .offset = { 0, 0 }, - .extent = pSwapChain->getExtent(), - }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentInfo, - .pDepthAttachment = &depthAttachmentInfo, - }; - - commandBuffers[frameIndex].beginRendering(renderingInfo); - - commandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, **pPipeline); - commandBuffers[frameIndex].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[frameIndex].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pPipeline->getLayout(), 0, *descriptorSets[frameIndex], nullptr); - commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pPipeline->getLayout(), 1, *environmentDescriptorSets[0], nullptr); - commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pPipeline->getLayout(), 2, *defaultMaterialDescriptorSets[0], nullptr); - - commandBuffers[frameIndex].setViewport( - 0, vk::Viewport(0.0f, 0.0f, static_cast(pSwapChain->getExtent().width), - static_cast(pSwapChain->getExtent().height), 0.0f, 1.0f)); - commandBuffers[frameIndex].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), pSwapChain->getExtent())); - - const uint32_t lightCount = 0; - commandBuffers[frameIndex].pushConstants( - *pPipeline->getLayout(), - vk::ShaderStageFlagBits::eFragment, - 0u, { lightCount } - ); - - commandBuffers[frameIndex].drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); - - commandBuffers[frameIndex].endRendering(); - - transitionImageLayout( - commandBuffers[frameIndex], - *offscreenImage, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::AccessFlagBits2::eShaderRead, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eFragmentShader, - vk::ImageAspectFlagBits::eColor - ); - - transitionImageLayout( - commandBuffers[frameIndex], - pSwapChain->getImages()[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - - vk::RenderingAttachmentInfo ppColorAttachmentInfo = { - .imageView = pSwapChain->getImageViews()[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor, - }; - - vk::RenderingInfo ppRenderingInfo { - .renderArea = { - .offset = { 0, 0 }, - .extent = pSwapChain->getExtent(), - }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &ppColorAttachmentInfo, - }; - - commandBuffers[frameIndex].beginRendering(ppRenderingInfo); - - commandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, **pPostProcessPipeline); - commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pPostProcessPipeline->getLayout(), 0, *postProcessDescriptorSets[0], nullptr); - - commandBuffers[frameIndex].setViewport( - 0, vk::Viewport(0.0f, 0.0f, static_cast(pSwapChain->getExtent().width), - static_cast(pSwapChain->getExtent().height), 0.0f, 1.0f)); - commandBuffers[frameIndex].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), pSwapChain->getExtent())); - - commandBuffers[frameIndex].draw(3, 1, 0, 0); - - // Render ImGui overlay - if (imguiRenderer) { - imguiRenderer->render(commandBuffers[frameIndex], imageIndex); - } - - commandBuffers[frameIndex].endRendering(); - - transitionImageLayout( - commandBuffers[frameIndex], - pSwapChain->getImages()[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe, - vk::ImageAspectFlagBits::eColor - ); - - commandBuffers[frameIndex].end(); - } - - void drawFrame(const sauce::LogicalDevice& logicalDevice, const sauce::Scene& scene, sauce::ImGuiRenderer* imguiRenderer = nullptr){ - // Wait for the previous frame to finish rendering before submitting the next frame - auto fenceResult = logicalDevice->waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX); - if (fenceResult != vk::Result::eSuccess) { - throw std::runtime_error("Failed to wait for fence!"); - } - - // Request the next available image from the swap chain - uint32_t imageIndex; - try { - auto [result, idx] = (*pSwapChain)->acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - imageIndex = idx; - } catch (const vk::OutOfDateKHRError&) { - recreateSwapChain(); - return; - } - - // Reset the fence for the next frame - logicalDevice->resetFences(*inFlightFences[frameIndex]); - - // Reset and record the command buffer with rendering commands - commandBuffers[frameIndex].reset(); - - if (customRecorder) { - customRecorder(commandBuffers[frameIndex], imageIndex); - } else { - recordCommandBuffer(imageIndex, imguiRenderer); - updateUniformBuffer(frameIndex, scene); - } - - // Prepare submission: wait for image to be available before starting color attachment output - vk::PipelineStageFlags waitDestinationStageMask { vk::PipelineStageFlagBits::eColorAttachmentOutput }; - const vk::SubmitInfo submitInfo { - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[frameIndex], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex], - }; - - // Submit the command buffer to the queue for execution, signaling the fence when complete - pQueue->submit(submitInfo, *inFlightFences[frameIndex]); - - // Prepare presentation: wait for rendering to finish before presenting - const vk::PresentInfoKHR presentInfoKHR { - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], - .swapchainCount = 1, - .pSwapchains = &***pSwapChain, - .pImageIndices = &imageIndex, - }; - - // Present the rendered image to the screen, handling resize - try { - auto result = pQueue->presentKHR(presentInfoKHR); - if (result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } - } catch (const vk::OutOfDateKHRError&) { - framebufferResized = false; - recreateSwapChain(); - } - - // Advance to the next frame in the circular buffer - frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; - } - - void updateUniformBuffer(uint32_t curImage, const sauce::Scene& scene) { - // Record the start time on first call (static initialization) - static auto startTime = std::chrono::high_resolution_clock::now(); - - // Get the current time and calculate elapsed time in seconds - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - // Create uniform buffer object with transformation matrices - sauce::UniformBufferObject ubo { - // Model matrix: rotates the object 90 degrees per second around the Z axis - .model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)), - .view = scene.getCameraRO().getViewMatrix(), - .proj = scene.getCameraRO().getProjectionMatrix(), - .cameraPos = scene.getCameraRO().getPos(), - }; - - // Flip Y coordinate of projection matrix (Vulkan uses inverted Y compared to OpenGL) - ubo.proj[1][1] *= -1; - - // Copy the uniform buffer data to GPU-mapped memory for the current frame - memcpy(uniformBuffersMapped[curImage], &ubo, sizeof(ubo)); - } - - - // Creates a 1x1 white pixel image + sampler used as fallbacks for all 5 PBR texture slots. - void createDefaultTextures(const sauce::PhysicalDevice& physicalDevice, const sauce::LogicalDevice& logicalDevice) { - uint8_t whitePixel[4] = {255, 255, 255, 255}; - vk::DeviceSize pixelSize = sizeof(whitePixel); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingMemory = nullptr; - sauce::BufferUtils::createBuffer( - physicalDevice, logicalDevice, pixelSize, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, stagingMemory - ); - void* mapped = stagingMemory.mapMemory(0, pixelSize); - memcpy(mapped, whitePixel, pixelSize); - stagingMemory.unmapMemory(); - - ImageUtils::createImage( - physicalDevice, logicalDevice, 1, 1, - vk::Format::eR8G8B8A8Unorm, vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, - defaultImage, defaultImageMemory - ); - - ImageUtils::transitionImageLayout( - logicalDevice, commandPool, *pQueue, defaultImage, - vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, - {}, vk::AccessFlagBits2::eTransferWrite, - vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer - ); - ImageUtils::copyBufferToImage(logicalDevice, commandPool, *pQueue, stagingBuffer, defaultImage, 1, 1); - ImageUtils::transitionImageLayout( - logicalDevice, commandPool, *pQueue, defaultImage, - vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, - vk::AccessFlagBits2::eTransferWrite, vk::AccessFlagBits2::eShaderRead, - vk::PipelineStageFlagBits2::eTransfer, vk::PipelineStageFlagBits2::eFragmentShader - ); - - defaultImageView = ImageUtils::createImageView(logicalDevice, defaultImage, vk::Format::eR8G8B8A8Unorm, vk::ImageAspectFlagBits::eColor); - - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .minLod = 0.0f, - .maxLod = 0.0f, - }; - defaultSampler = vk::raii::Sampler{ *logicalDevice, samplerInfo }; - } - - // Creates a host-visible uniform buffer holding default PBR material properties. - void createMaterialBuffer(const sauce::PhysicalDevice& physicalDevice, const sauce::LogicalDevice& logicalDevice) { - MaterialData defaults{}; - sauce::BufferUtils::createBuffer( - physicalDevice, logicalDevice, sizeof(MaterialData), - vk::BufferUsageFlagBits::eUniformBuffer, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - materialBuffer, materialBufferMemory - ); - void* data = materialBufferMemory.mapMemory(0, sizeof(MaterialData)); - memcpy(data, &defaults, sizeof(MaterialData)); - materialBufferMemory.unmapMemory(); - } - - // Pre-allocates a persistently-mapped storage buffer for up to MAX_LIGHTS. - void createLightSSBO(const sauce::PhysicalDevice& physicalDevice, const sauce::LogicalDevice& logicalDevice) { - lightSSBOSize = static_cast(MAX_LIGHTS) * sizeof(GPULight); - sauce::BufferUtils::createBuffer( - physicalDevice, logicalDevice, lightSSBOSize, - vk::BufferUsageFlagBits::eStorageBuffer, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - lightSSBO, lightSSBOMemory - ); - lightSSBOMapped = lightSSBOMemory.mapMemory(0, lightSSBOSize); - } - - // Writes lights into the persistently-mapped SSBO. Returns count written (clamped to MAX_LIGHTS). - uint32_t updateLightSSBO(const GPULight* data, uint32_t count) { - count = std::min(count, MAX_LIGHTS); - if (count > 0) { - std::memcpy(lightSSBOMapped, data, count * sizeof(GPULight)); - } - return count; - } - - const vk::raii::Buffer& getMaterialBuffer() const { return materialBuffer; } - -private: - // Stored references for swapchain recreation - const sauce::PhysicalDevice* pPhysicalDevice; - const sauce::LogicalDevice* pLogicalDevice; - const sauce::RenderSurface* pRenderSurface; - GLFWwindow* pWindow; - - std::unique_ptr pQueue; - std::unique_ptr pSwapChain; - - vk::raii::CommandPool commandPool = nullptr; - - std::vector commandBuffers; - - std::vector presentCompleteSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; - - uint32_t frameIndex = 0; - uint32_t queueIndex = ~0; - bool framebufferResized = false; - - std::unique_ptr pPipeline; - std::unique_ptr pPostProcessPipeline; - - vk::raii::DescriptorSetLayout descriptorSetLayout0 = nullptr; - vk::raii::DescriptorSetLayout descriptorSetLayout1 = nullptr; - vk::raii::DescriptorSetLayout postProcessDescriptorSetLayout = nullptr; - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - std::vector environmentDescriptorSets; - std::vector defaultMaterialDescriptorSets; - std::vector postProcessDescriptorSets; - - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - CommandBufferRecorder customRecorder; - - // PBR resources - vk::raii::Buffer materialBuffer = nullptr; - vk::raii::DeviceMemory materialBufferMemory = nullptr; - - vk::DeviceSize lightSSBOSize{0}; - vk::raii::Buffer lightSSBO = nullptr; - vk::raii::DeviceMemory lightSSBOMemory = nullptr; - void* lightSSBOMapped = nullptr; - - vk::raii::Image defaultImage = nullptr; - vk::raii::DeviceMemory defaultImageMemory = nullptr; - vk::raii::ImageView defaultImageView = nullptr; - vk::raii::Sampler defaultSampler = nullptr; - - vk::raii::Image offscreenImage = nullptr; - vk::raii::DeviceMemory offscreenImageMemory = nullptr; - vk::raii::ImageView offscreenImageView = nullptr; - vk::raii::Sampler offscreenSampler = nullptr; - - std::unique_ptr pIBLMaps; -}; - -} +} // namespace sauce diff --git a/include/app/SauceEngineApp.hpp b/include/app/SauceEngineApp.hpp index 4f086f48..077ee25c 100644 --- a/include/app/SauceEngineApp.hpp +++ b/include/app/SauceEngineApp.hpp @@ -15,20 +15,20 @@ #include #include -#include +#include #include -#include #include -#include +#include #include +#include +#include #include -#include #include -#include -#include #include #include #include +#include +#include #include #include #include @@ -48,62 +48,65 @@ constexpr uint32_t HEIGHT = 720; namespace sauce { -class SauceEngineApp { -public: - SauceEngineApp(); // Constructor to initialize pImGuiComponentManager - void run(); - - ~SauceEngineApp(); + class SauceEngineApp { + public: + SauceEngineApp(); // Constructor to initialize pImGuiComponentManager + void run(); -private: - GLFWwindow *window; + ~SauceEngineApp(); - std::chrono::steady_clock::time_point lastFrameTime = std::chrono::steady_clock::now(); - float deltaTime = 0.0f; + private: + GLFWwindow* window; - float lastX = 0.0f; - float lastY = 0.0f; - bool firstMouse = true; - bool cursorCaptured = true; - bool gravePressedLastFrame = false; + std::chrono::steady_clock::time_point lastFrameTime = std::chrono::steady_clock::now(); + float deltaTime = 0.0f; - std::unique_ptr pInstance; + float lastX = 0.0f; + float lastY = 0.0f; + bool firstMouse = true; + bool cursorCaptured = true; + bool gravePressedLastFrame = false; - std::unique_ptr pRenderSurface; + std::unique_ptr pInstance; - sauce::PhysicalDevice physicalDevice = nullptr; - sauce::LogicalDevice logicalDevice = nullptr; + std::unique_ptr pRenderSurface; - std::unique_ptr pRenderer; + sauce::PhysicalDevice physicalDevice = nullptr; + sauce::LogicalDevice logicalDevice = nullptr; - std::unique_ptr pScene; + std::unique_ptr pRenderer; - std::unique_ptr pImGuiRenderer; + std::unique_ptr pScene; - std::unique_ptr pImGuiComponentManager; - std::function pCustomUIBuilder; + std::unique_ptr pImGuiRenderer; - void initVulkan(); - void initWindow(); - void mainLoop(); - void processInput(float deltaTime); - static void mouseCallback(GLFWwindow* window, double xposIn, double yposIn); + std::unique_ptr pImGuiComponentManager; + std::function pCustomUIBuilder; - void buildExampleUI(); + void initVulkan(); + void initWindow(); + void mainLoop(); + void processInput(float deltaTime); + static void mouseCallback(GLFWwindow* window, double xposIn, double yposIn); - void uploadMeshGPUResources(); - void setupSceneRenderer(); - void syncRigidBodiesToTransforms(); - void recordSceneCommandBuffer(vk::raii::CommandBuffer& cmd, uint32_t imageIndex); + void buildExampleUI(); -public: - sauce::ui::ImGuiComponentManager& getImGuiManager() { return *pImGuiComponentManager; } - void setCustomUIBuilder(std::function builder); - void setSceneFile(const std::string& path) { sceneFile = path; } + void uploadMeshGPUResources(); + void setupSceneRenderer(); + void syncRigidBodiesToTransforms(); + void recordSceneCommandBuffer(vk::raii::CommandBuffer& cmd, uint32_t imageIndex); -private: - std::string sceneFile; -}; + public: + sauce::ui::ImGuiComponentManager& getImGuiManager() { + return *pImGuiComponentManager; + } + void setCustomUIBuilder(std::function builder); + void setSceneFile(const std::string& path) { + sceneFile = path; + } -} + private: + std::string sceneFile; + }; +} // namespace sauce diff --git a/include/app/Scene.hpp b/include/app/Scene.hpp index 33c23fa9..6844a55b 100644 --- a/include/app/Scene.hpp +++ b/include/app/Scene.hpp @@ -5,114 +5,121 @@ #include #include #include -#include #include +#include #include namespace sauce { -namespace modeling { - class ModelNode; - class Model; -} + namespace modeling { + class ModelNode; + class Model; + } // namespace modeling -class SauceEngineApp; + class SauceEngineApp; -namespace editor { - class EditorApp; -} + namespace editor { + class EditorApp; + } -class Scene { -public: - /** + class Scene { + public: + /** * Loads scene from the file if provided. Otherwise, creates an empty scene * @param cameraCreateInfo - arguments for camera creation * @param filename - file to load from */ - Scene(const sauce::CameraCreateInfo& cameraCreateInfo, const std::string& filename = "") { - pCamera = std::make_unique( cameraCreateInfo ); - } + Scene(const sauce::CameraCreateInfo& cameraCreateInfo, const std::string& filename = "") { + pCamera = std::make_unique(cameraCreateInfo); + } - const std::vector& getEntities() const { - return entities; - } + const std::vector& getEntities() const { + return entities; + } - /** + /** * Adds an entity to the scene */ - void addEntity(sauce::Entity&& entity); + void addEntity(sauce::Entity&& entity); - /** + /** * Gets an entity by name (returns nullptr if not found) */ - sauce::Entity* getEntity(const std::string& name); + sauce::Entity* getEntity(const std::string& name); - /** + /** * Loads a GLTF model and creates entities with components * @param filePath - path to the GLTF file * @param preserveHierarchy - if true, creates entity tree; if false, flattens to single level */ - void loadGLTFModel(const std::string& filePath, bool preserveHierarchy = true); + void loadGLTFModel(const std::string& filePath, bool preserveHierarchy = true); - /** + /** * Saves the entire scene to a GLTF/GLB file * @return true on success */ - bool saveToFile(const std::string& filePath) const; + bool saveToFile(const std::string& filePath) const; - /** + /** * Loads a GLTF/GLB file as the entire scene (clears existing entities) * @return true on success */ - bool loadFromFile(const std::string& filePath); - - const std::string& getCurrentFilePath() const { return currentFilePath; } - void setCurrentFilePath(const std::string& path) { currentFilePath = path; } - bool hasFilePath() const { return !currentFilePath.empty(); } - - /** + bool loadFromFile(const std::string& filePath); + + const std::string& getCurrentFilePath() const { + return currentFilePath; + } + void setCurrentFilePath(const std::string& path) { + currentFilePath = path; + } + bool hasFilePath() const { + return !currentFilePath.empty(); + } + + /** * Returns a const ref to camera */ - const sauce::Camera& getCameraRO() const noexcept { - return *pCamera; - } + const sauce::Camera& getCameraRO() const noexcept { + return *pCamera; + } - std::vector& getEntitiesMut() { - return entities; - } + std::vector& getEntitiesMut() { + return entities; + } - /** + /** * Collects GPULight data from all active entities that have a LightComponent. * Each light's world position is taken from the entity's TransformComponent. * Returns a reference to an internal buffer (valid until the next call). */ - const std::vector& collectGPULights(); + const std::vector& collectGPULights(); -private: - std::vector entities; + private: + std::vector entities; - std::unique_ptr pCamera; + std::unique_ptr pCamera; - std::string currentFilePath; - std::vector gpuLightBuffer; + std::string currentFilePath; + std::vector gpuLightBuffer; - // Helper functions for GLTF loading - void loadGLTFNodeHierarchy(std::shared_ptr node, - Entity* parentEntity, - std::unordered_map& nodeToEntityMap, - const std::string& filePath); - void loadGLTFFlattened(std::shared_ptr model, const std::string& filePath); + // Helper functions for GLTF loading + void loadGLTFNodeHierarchy( + std::shared_ptr node, Entity* parentEntity, + std::unordered_map& nodeToEntityMap, + const std::string& filePath); + void loadGLTFFlattened(std::shared_ptr model, const std::string& filePath); - /** + /** * Returns a non-const ref to camera */ - sauce::Camera& getCameraRW() noexcept { - return *pCamera; - } + sauce::Camera& getCameraRW() noexcept { + return *pCamera; + } - friend class sauce::SauceEngineApp; // This gives the app class full access to entities and camera for user interaction - friend class sauce::editor::EditorApp; -}; + friend class sauce:: + SauceEngineApp; // This gives the app class full access to entities and camera for user interaction + friend class sauce::editor::EditorApp; + }; -} +} // namespace sauce diff --git a/include/app/Settings.hpp b/include/app/Settings.hpp index f099f62f..fcb327c1 100644 --- a/include/app/Settings.hpp +++ b/include/app/Settings.hpp @@ -1,40 +1,46 @@ #pragma once -#include #include +#include namespace sauce { -struct EditorSettings { - float imguiScale = 1.0f; - bool vsync = true; - std::string workingDirectory = "."; - bool palantirMode = true; - bool showDebugStats = true; - float mouseSensitivity = 0.1f; - float cameraSpeed = 2.5f; - float fieldOfView = 90.0f; -}; - -class SettingsManager { -public: - using ChangeCallback = std::function; - - void load(const std::string& path = "sauceengine_settings.json"); - void save() const; - void save(const std::string& path); - - EditorSettings& get() { return settings; } - const EditorSettings& get() const { return settings; } - - void markDirtyAndSave(); - - void setOnChangeCallback(ChangeCallback cb) { onChange = std::move(cb); } - -private: - EditorSettings settings; - std::string filePath = "sauceengine_settings.json"; - ChangeCallback onChange; -}; + struct EditorSettings { + float imguiScale = 1.0f; + bool vsync = true; + std::string workingDirectory = "."; + bool palantirMode = true; + bool showDebugStats = true; + float mouseSensitivity = 0.1f; + float cameraSpeed = 2.5f; + float fieldOfView = 90.0f; + }; + + class SettingsManager { + public: + using ChangeCallback = std::function; + + void load(const std::string& path = "sauceengine_settings.json"); + void save() const; + void save(const std::string& path); + + EditorSettings& get() { + return settings; + } + const EditorSettings& get() const { + return settings; + } + + void markDirtyAndSave(); + + void setOnChangeCallback(ChangeCallback cb) { + onChange = std::move(cb); + } + + private: + EditorSettings settings; + std::string filePath = "sauceengine_settings.json"; + ChangeCallback onChange; + }; } // namespace sauce diff --git a/include/app/SwapChain.hpp b/include/app/SwapChain.hpp index 00c6ee36..bcd0a66e 100644 --- a/include/app/SwapChain.hpp +++ b/include/app/SwapChain.hpp @@ -11,181 +11,186 @@ namespace sauce { -/** + /** * Manages the swap chain: a queue of images waiting to be presented to the screen. * The swap chain is essentially Vulkan's frame buffer infrastructure. It holds multiple * images that cycle between being rendered to and being displayed. */ -struct SwapChain { - - /** + struct SwapChain { + /** * Creates a swap chain with sensible settings for what we want to do. We've chosen to optimize * for picture smoothness over input latency by using triple buffering (mailbox present mode). */ - SwapChain( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice, - const sauce::RenderSurface& renderSurface, - GLFWwindow* window, - bool vsync = true - ) { - vk::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice->getSurfaceCapabilitiesKHR(**renderSurface); - extent = chooseSwapExtent(surfaceCapabilities, window); - surfaceFormat = chooseSwapSurfaceFormat(physicalDevice->getSurfaceFormatsKHR(**renderSurface)); - - vk::SwapchainCreateInfoKHR swapChainCreateInfo { - .surface = **renderSurface, - .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), - .imageFormat = surfaceFormat.format, - .imageColorSpace = surfaceFormat.colorSpace, - .imageExtent = extent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive, - .preTransform = surfaceCapabilities.currentTransform, - .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, - .presentMode = chooseSwapPresentMode(physicalDevice->getSurfacePresentModesKHR(**renderSurface), vsync), - .clipped = vk::True - }; - - swapChain = vk::raii::SwapchainKHR { *logicalDevice, swapChainCreateInfo }; - images = swapChain.getImages(); // grab handles to the images vulkan created for us - - // image views describe how to access the image (which part, what format, etc) - vk::ImageViewCreateInfo imageViewCreateInfo { - .viewType = vk::ImageViewType::e2D, - .format = surfaceFormat.format, - .subresourceRange = { - vk::ImageAspectFlagBits::eColor, // we care about color, not depth or stencil - 0, 1, 0, 1, // base mip level, mip count, base array layer, layer count - }, - }; - - // create a view for each swap chain image - for (const auto& image: images) { - imageViewCreateInfo.image = image; - imageViews.emplace_back( *logicalDevice, imageViewCreateInfo ); - } - } - - /** + SwapChain(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice, + const sauce::RenderSurface& renderSurface, GLFWwindow* window, + bool vsync = true) { + vk::SurfaceCapabilitiesKHR surfaceCapabilities = + physicalDevice->getSurfaceCapabilitiesKHR(**renderSurface); + extent = chooseSwapExtent(surfaceCapabilities, window); + surfaceFormat = + chooseSwapSurfaceFormat(physicalDevice->getSurfaceFormatsKHR(**renderSurface)); + + vk::SwapchainCreateInfoKHR swapChainCreateInfo{ + .surface = **renderSurface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .imageFormat = surfaceFormat.format, + .imageColorSpace = surfaceFormat.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode( + physicalDevice->getSurfacePresentModesKHR(**renderSurface), vsync), + .clipped = vk::True}; + + swapChain = vk::raii::SwapchainKHR{*logicalDevice, swapChainCreateInfo}; + images = swapChain.getImages(); // grab handles to the images vulkan created for us + + // image views describe how to access the image (which part, what format, etc) + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = surfaceFormat.format, + .subresourceRange = + { + vk::ImageAspectFlagBits:: + eColor, // we care about color, not depth or stencil + 0, 1, 0, 1, // base mip level, mip count, base array layer, layer count + }, + }; + + // create a view for each swap chain image + for (const auto& image : images) { + imageViewCreateInfo.image = image; + imageViews.emplace_back(*logicalDevice, imageViewCreateInfo); + } + } + + /** * Access the underlying SwapchainKHR. */ - const vk::raii::SwapchainKHR& operator*() const & noexcept { - return swapChain; - } + const vk::raii::SwapchainKHR& operator*() const& noexcept { + return swapChain; + } - const vk::raii::SwapchainKHR* operator->() const & noexcept { - return &swapChain; - } + const vk::raii::SwapchainKHR* operator->() const& noexcept { + return &swapChain; + } - /** + /** * Returns handles to the swap chain images (the actual render targets). */ - const std::vector& getImages() const noexcept { - return images; - } + const std::vector& getImages() const noexcept { + return images; + } - /** + /** * Returns image views - describes how to interpret each image for rendering. */ - const std::vector& getImageViews() const noexcept { - return imageViews; - } + const std::vector& getImageViews() const noexcept { + return imageViews; + } - /** + /** * Returns the chosen surface format (pixel format + color space). */ - const vk::SurfaceFormatKHR& getSurfaceFormat() const noexcept { - return surfaceFormat; - } + const vk::SurfaceFormatKHR& getSurfaceFormat() const noexcept { + return surfaceFormat; + } - /** + /** * Returns the swap chain image dimensions in pixels. */ - const vk::Extent2D& getExtent() const noexcept { - return extent; - } - -private: - vk::Extent2D extent; - vk::SurfaceFormatKHR surfaceFormat; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector images; - std::vector imageViews; - - /** + const vk::Extent2D& getExtent() const noexcept { + return extent; + } + + private: + vk::Extent2D extent; + vk::SurfaceFormatKHR surfaceFormat; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector images; + std::vector imageViews; + + /** * Selects the best available surface format. * Prefers 8-bit BGRA with sRGB color space for accurate color representation. */ - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - for (const auto& format: availableFormats) { - // bgra is the most common format, srgb gives us correct gamma - if (format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) - return format; - } - - return availableFormats[0]; // fallback to whatever's available - } - - /** + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat( + const std::vector& availableFormats) { + for (const auto& format : availableFormats) { + // bgra is the most common format, srgb gives us correct gamma + if (format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) + return format; + } + + return availableFormats[0]; // fallback to whatever's available + } + + /** * Selects the presentation mode based on the vsync setting. * When vsync is true, returns FIFO (vertical sync, guaranteed available). * When vsync is false, prefers Mailbox (triple buffering), then Immediate * (uncapped framerate), falling back to FIFO if neither is available. */ - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes, bool vsync = true) { - if (vsync) { - return vk::PresentModeKHR::eFifo; - } - - for (const auto& mode: availablePresentModes) { - if (mode == vk::PresentModeKHR::eMailbox) { - return mode; - } - } - - for (const auto& mode: availablePresentModes) { - if (mode == vk::PresentModeKHR::eImmediate) { - return mode; - } - } - - return vk::PresentModeKHR::eFifo; - } - - /** + static vk::PresentModeKHR chooseSwapPresentMode( + const std::vector& availablePresentModes, bool vsync = true) { + if (vsync) { + return vk::PresentModeKHR::eFifo; + } + + for (const auto& mode : availablePresentModes) { + if (mode == vk::PresentModeKHR::eMailbox) { + return mode; + } + } + + for (const auto& mode : availablePresentModes) { + if (mode == vk::PresentModeKHR::eImmediate) { + return mode; + } + } + + return vk::PresentModeKHR::eFifo; + } + + /** * Determines the swap chain image resolution * This should usually match window size, clamped to the surface's supported range * High-DPI (like apple retina) displays may have different framebuffer vs window coordinates */ - static vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, GLFWwindow* window) { - // if not max uint32, window manager already set the extent for us - if (capabilities.currentExtent.width != std::numeric_limits::max()) - return capabilities.currentExtent; - - // query actual framebuffer size - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height), - }; - } - - /** + static vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, + GLFWwindow* window) { + // if not max uint32, window manager already set the extent for us + if (capabilities.currentExtent.width != std::numeric_limits::max()) + return capabilities.currentExtent; + + // query actual framebuffer size + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, + capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, + capabilities.maxImageExtent.height), + }; + } + + /** * Determines how many images the swap chain should hold * Requests at least 3 images (triple buffering) when possible */ - static uint32_t chooseSwapMinImageCount(const vk::SurfaceCapabilitiesKHR& capabilities) { - auto minImageCount = std::max(3u, capabilities.minImageCount); - if ((0 < capabilities.maxImageCount) && (capabilities.maxImageCount < minImageCount)) { - minImageCount = capabilities.maxImageCount; - } - return minImageCount; - } - -}; + static uint32_t chooseSwapMinImageCount(const vk::SurfaceCapabilitiesKHR& capabilities) { + auto minImageCount = std::max(3u, capabilities.minImageCount); + if ((0 < capabilities.maxImageCount) && (capabilities.maxImageCount < minImageCount)) { + minImageCount = capabilities.maxImageCount; + } + return minImageCount; + } + }; -} +} // namespace sauce diff --git a/include/app/Vertex.hpp b/include/app/Vertex.hpp index bb1c2c4b..68448104 100644 --- a/include/app/Vertex.hpp +++ b/include/app/Vertex.hpp @@ -9,34 +9,38 @@ namespace sauce { -struct Vertex { - glm::vec3 position; // 3D position - glm::vec3 normal; // Surface normal - glm::vec2 texCoords; // UV coordinates - glm::vec3 color; // Vertex color - glm::vec4 tangent; // Tangent (w = handedness) - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescription() { - return { - vk::VertexInputAttributeDescription { 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, position) }, - vk::VertexInputAttributeDescription { 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal) }, - vk::VertexInputAttributeDescription { 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoords) }, - vk::VertexInputAttributeDescription { 3, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) }, - vk::VertexInputAttributeDescription { 4, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Vertex, tangent) }, + struct Vertex { + glm::vec3 position; // 3D position + glm::vec3 normal; // Surface normal + glm::vec2 texCoords; // UV coordinates + glm::vec3 color; // Vertex color + glm::vec4 tangent; // Tangent (w = handedness) + + static vk::VertexInputBindingDescription getBindingDescription() { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescription() { + return { + vk::VertexInputAttributeDescription{0, 0, vk::Format::eR32G32B32Sfloat, + offsetof(Vertex, position)}, + vk::VertexInputAttributeDescription{1, 0, vk::Format::eR32G32B32Sfloat, + offsetof(Vertex, normal)}, + vk::VertexInputAttributeDescription{2, 0, vk::Format::eR32G32Sfloat, + offsetof(Vertex, texCoords)}, + vk::VertexInputAttributeDescription{3, 0, vk::Format::eR32G32B32Sfloat, + offsetof(Vertex, color)}, + vk::VertexInputAttributeDescription{4, 0, vk::Format::eR32G32B32A32Sfloat, + offsetof(Vertex, tangent)}, + }; + } }; - } -}; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; - alignas(16) glm::vec3 cameraPos; -}; - -} + struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; + alignas(16) glm::vec3 cameraPos; + }; +} // namespace sauce diff --git a/include/app/components/DirectionalLightComponent.hpp b/include/app/components/DirectionalLightComponent.hpp index a58c17f8..19f60545 100644 --- a/include/app/components/DirectionalLightComponent.hpp +++ b/include/app/components/DirectionalLightComponent.hpp @@ -5,26 +5,38 @@ namespace sauce { -class DirectionalLightComponent : public LightComponent { -public: - DirectionalLightComponent(); - DirectionalLightComponent(const glm::vec3& color, float intensity); - ~DirectionalLightComponent() override = default; - - const glm::vec3& getAmbient() const { return ambient; } - const glm::vec3& getDiffuse() const { return diffuse; } - const glm::vec3& getSpecular() const { return specular; } - - void setAmbient(const glm::vec3& a) { ambient = a; } - void setDiffuse(const glm::vec3& d) { diffuse = d; } - void setSpecular(const glm::vec3& s) { specular = s; } - - GPULight toGPULight(const glm::vec3& worldPosition) const override; - -private: - glm::vec3 ambient{0.1f}; - glm::vec3 diffuse{1.0f}; - glm::vec3 specular{1.0f}; -}; + class DirectionalLightComponent : public LightComponent { + public: + DirectionalLightComponent(); + DirectionalLightComponent(const glm::vec3& color, float intensity); + ~DirectionalLightComponent() override = default; + + const glm::vec3& getAmbient() const { + return ambient; + } + const glm::vec3& getDiffuse() const { + return diffuse; + } + const glm::vec3& getSpecular() const { + return specular; + } + + void setAmbient(const glm::vec3& a) { + ambient = a; + } + void setDiffuse(const glm::vec3& d) { + diffuse = d; + } + void setSpecular(const glm::vec3& s) { + specular = s; + } + + GPULight toGPULight(const glm::vec3& worldPosition) const override; + + private: + glm::vec3 ambient{0.1f}; + glm::vec3 diffuse{1.0f}; + glm::vec3 specular{1.0f}; + }; } // namespace sauce diff --git a/include/app/components/LightComponent.hpp b/include/app/components/LightComponent.hpp index 2220f414..393d802d 100644 --- a/include/app/components/LightComponent.hpp +++ b/include/app/components/LightComponent.hpp @@ -1,56 +1,67 @@ #pragma once #include "app/Component.hpp" -#include #include +#include namespace sauce { -enum class LightType : uint32_t { - Directional = 0, - Point = 1, - Spot = 2, -}; - -// Matches the Light struct in shader_pbr.slang using scalar (tightly-packed) layout. -// Used to populate the StructuredBuffer SSBO. -// Note: using std430 we can tightly pack a float3 and a float together -// and we use that here to avoid explicit padding between members. -// That also means the order in which we define these attributes is important. -struct GPULight { - glm::vec3 position{0.0f}; - uint32_t type{0}; - glm::vec3 direction{0.0f, -1.0f, 0.0f}; - float intensity{1.0f}; - glm::vec3 color{1.0f}; - float range{0.0f}; - float innerConeAngle{0.0f}; - float outerConeAngle{0.0f}; - float _pad0[2]; // struct size must be a multiple of 16 bytes -}; -static_assert(sizeof(GPULight) == 64, "GPULight must be 64 bytes to match shader_pbr.slang"); - -class LightComponent : public Component { -public: - explicit LightComponent(LightType type, const std::string& name = "LightComponent") - : Component(name), lightType(type) {} - - virtual ~LightComponent() = default; - - LightType getLightType() const { return lightType; } - - const glm::vec3& getColor() const { return color; } - void setColor(const glm::vec3& c) { color = c; } - - float getIntensity() const { return intensity; } - void setIntensity(float i) { intensity = i; } - - virtual GPULight toGPULight(const glm::vec3& worldPosition) const = 0; - -protected: - LightType lightType; - glm::vec3 color{1.0f}; - float intensity{1.0f}; -}; + enum class LightType : uint32_t { + Directional = 0, + Point = 1, + Spot = 2, + }; + + // Matches the Light struct in shader_pbr.slang using scalar (tightly-packed) layout. + // Used to populate the StructuredBuffer SSBO. + // Note: using std430 we can tightly pack a float3 and a float together + // and we use that here to avoid explicit padding between members. + // That also means the order in which we define these attributes is important. + struct GPULight { + glm::vec3 position{0.0f}; + uint32_t type{0}; + glm::vec3 direction{0.0f, -1.0f, 0.0f}; + float intensity{1.0f}; + glm::vec3 color{1.0f}; + float range{0.0f}; + float innerConeAngle{0.0f}; + float outerConeAngle{0.0f}; + float _pad0[2]; // struct size must be a multiple of 16 bytes + }; + static_assert(sizeof(GPULight) == 64, "GPULight must be 64 bytes to match shader_pbr.slang"); + + class LightComponent : public Component { + public: + explicit LightComponent(LightType type, const std::string& name = "LightComponent") + : Component(name), lightType(type) { + } + + virtual ~LightComponent() = default; + + LightType getLightType() const { + return lightType; + } + + const glm::vec3& getColor() const { + return color; + } + void setColor(const glm::vec3& c) { + color = c; + } + + float getIntensity() const { + return intensity; + } + void setIntensity(float i) { + intensity = i; + } + + virtual GPULight toGPULight(const glm::vec3& worldPosition) const = 0; + + protected: + LightType lightType; + glm::vec3 color{1.0f}; + float intensity{1.0f}; + }; } // namespace sauce diff --git a/include/app/components/MeshRendererComponent.hpp b/include/app/components/MeshRendererComponent.hpp index 07b64358..95d75f5b 100644 --- a/include/app/components/MeshRendererComponent.hpp +++ b/include/app/components/MeshRendererComponent.hpp @@ -1,36 +1,48 @@ #pragma once #include "app/Component.hpp" -#include "app/modeling/Mesh.hpp" #include "app/modeling/Material.hpp" +#include "app/modeling/Mesh.hpp" #include namespace sauce { -class MeshRendererComponent : public Component { -public: - MeshRendererComponent(); - MeshRendererComponent(std::shared_ptr mesh, - std::shared_ptr material); - - // Getters - std::shared_ptr getMesh() const { return mesh; } - std::shared_ptr getMaterial() const { return material; } - const std::string& getModelPath() const { return modelPath; } - - // Setters - void setMesh(std::shared_ptr mesh) { this->mesh = mesh; } - void setMaterial(std::shared_ptr material) { this->material = material; } - void setModelPath(const std::string& path) { modelPath = path; } - - // Component interface - virtual void render() override; - virtual ~MeshRendererComponent() = default; - -private: - std::shared_ptr mesh; - std::shared_ptr material; - std::string modelPath; -}; + class MeshRendererComponent : public Component { + public: + MeshRendererComponent(); + MeshRendererComponent(std::shared_ptr mesh, + std::shared_ptr material); + + // Getters + std::shared_ptr getMesh() const { + return mesh; + } + std::shared_ptr getMaterial() const { + return material; + } + const std::string& getModelPath() const { + return modelPath; + } + + // Setters + void setMesh(std::shared_ptr mesh) { + this->mesh = mesh; + } + void setMaterial(std::shared_ptr material) { + this->material = material; + } + void setModelPath(const std::string& path) { + modelPath = path; + } + + // Component interface + virtual void render() override; + virtual ~MeshRendererComponent() = default; + + private: + std::shared_ptr mesh; + std::shared_ptr material; + std::string modelPath; + }; } // namespace sauce diff --git a/include/app/components/PointLightComponent.hpp b/include/app/components/PointLightComponent.hpp index b7d76c5c..d73484bb 100644 --- a/include/app/components/PointLightComponent.hpp +++ b/include/app/components/PointLightComponent.hpp @@ -5,66 +5,97 @@ namespace sauce { -// Matches the PointLight struct in shader_lights.slang (std140/ConstantBuffer layout, 96 bytes). -// Used to populate ConstantBuffer UBOs for the Blinn-Phong lighting path. -struct alignas(16) GPUPointLight { - glm::vec3 position{0.0f}; // offset 0 - float _pad0{0}; // offset 12 - glm::vec3 color{1.0f}; // offset 16 - float _pad1{0}; // offset 28 - glm::vec3 ambient{0.05f}; // offset 32 - float _pad2{0}; // offset 44 - glm::vec3 diffuse{0.8f}; // offset 48 - float _pad3{0}; // offset 60 - glm::vec3 specular{1.0f}; // offset 64 - float constant{1.0f}; // offset 76 - float linear{0.09f}; // offset 80 - float quadratic{0.032f}; // offset 84 - float _pad4{0}; // offset 88 -}; -static_assert(sizeof(GPUPointLight) == 96, "GPUPointLight must be 96 bytes to match shader_lights.slang"); + // Matches the PointLight struct in shader_lights.slang (std140/ConstantBuffer layout, 96 bytes). + // Used to populate ConstantBuffer UBOs for the Blinn-Phong lighting path. + struct alignas(16) GPUPointLight { + glm::vec3 position{0.0f}; // offset 0 + float _pad0{0}; // offset 12 + glm::vec3 color{1.0f}; // offset 16 + float _pad1{0}; // offset 28 + glm::vec3 ambient{0.05f}; // offset 32 + float _pad2{0}; // offset 44 + glm::vec3 diffuse{0.8f}; // offset 48 + float _pad3{0}; // offset 60 + glm::vec3 specular{1.0f}; // offset 64 + float constant{1.0f}; // offset 76 + float linear{0.09f}; // offset 80 + float quadratic{0.032f}; // offset 84 + float _pad4{0}; // offset 88 + }; + static_assert(sizeof(GPUPointLight) == 96, + "GPUPointLight must be 96 bytes to match shader_lights.slang"); -class PointLightComponent : public LightComponent { -public: - PointLightComponent(); - PointLightComponent(const glm::vec3& color, float intensity, float range = 0.0f); - ~PointLightComponent() override = default; + class PointLightComponent : public LightComponent { + public: + PointLightComponent(); + PointLightComponent(const glm::vec3& color, float intensity, float range = 0.0f); + ~PointLightComponent() override = default; - // PBR properties (shader_pbr.slang path) - float getRange() const { return range; } - void setRange(float r) { range = r; } + // PBR properties (shader_pbr.slang path) + float getRange() const { + return range; + } + void setRange(float r) { + range = r; + } - // Blinn-Phong properties (shader_lights.slang path) - const glm::vec3& getAmbient() const { return ambient; } - const glm::vec3& getDiffuse() const { return diffuse; } - const glm::vec3& getSpecular() const { return specular; } - float getConstant() const { return constant; } - float getLinear() const { return linear; } - float getQuadratic() const { return quadratic; } + // Blinn-Phong properties (shader_lights.slang path) + const glm::vec3& getAmbient() const { + return ambient; + } + const glm::vec3& getDiffuse() const { + return diffuse; + } + const glm::vec3& getSpecular() const { + return specular; + } + float getConstant() const { + return constant; + } + float getLinear() const { + return linear; + } + float getQuadratic() const { + return quadratic; + } - void setAmbient(const glm::vec3& a) { ambient = a; } - void setDiffuse(const glm::vec3& d) { diffuse = d; } - void setSpecular(const glm::vec3& s) { specular = s; } - void setConstant(float c) { constant = c; } - void setLinear(float l) { linear = l; } - void setQuadratic(float q) { quadratic = q; } + void setAmbient(const glm::vec3& a) { + ambient = a; + } + void setDiffuse(const glm::vec3& d) { + diffuse = d; + } + void setSpecular(const glm::vec3& s) { + specular = s; + } + void setConstant(float c) { + constant = c; + } + void setLinear(float l) { + linear = l; + } + void setQuadratic(float q) { + quadratic = q; + } - void setAttenuation(float c, float l, float q) { - constant = c; linear = l; quadratic = q; - } + void setAttenuation(float c, float l, float q) { + constant = c; + linear = l; + quadratic = q; + } - GPUPointLight toGPUPointLight(const glm::vec3& worldPosition) const; - GPULight toGPULight(const glm::vec3& worldPosition) const override; + GPUPointLight toGPUPointLight(const glm::vec3& worldPosition) const; + GPULight toGPULight(const glm::vec3& worldPosition) const override; -private: - float range{0.0f}; + private: + float range{0.0f}; - glm::vec3 ambient{0.05f}; - glm::vec3 diffuse{0.8f}; - glm::vec3 specular{1.0f}; - float constant{1.0f}; - float linear{0.09f}; - float quadratic{0.032f}; -}; + glm::vec3 ambient{0.05f}; + glm::vec3 diffuse{0.8f}; + glm::vec3 specular{1.0f}; + float constant{1.0f}; + float linear{0.09f}; + float quadratic{0.032f}; + }; } // namespace sauce diff --git a/include/app/components/RigidBodyComponent.hpp b/include/app/components/RigidBodyComponent.hpp index 0f8d646e..9356abf9 100644 --- a/include/app/components/RigidBodyComponent.hpp +++ b/include/app/components/RigidBodyComponent.hpp @@ -9,69 +9,97 @@ namespace sauce { -class RigidBodyComponent : public Component { -public: - - RigidBodyComponent( - glm::vec3 initPosition, - glm::vec3 initVelocity, - glm::quat initOrientation, - glm::vec3 initAngularVelocity, - glm::vec3 externalForces = glm::vec3(0.0f, 0.0f, 0.0f), - float invMass = 1.0f, - glm::mat3 invInertiaTensor = glm::mat3(1.0f) - ) : - position(initPosition), velocity(initVelocity), - orientation(initOrientation), angularVelocity(initAngularVelocity), externalForces(externalForces), - invMass(invMass), invInertiaTensor(invInertiaTensor) {} + class RigidBodyComponent : public Component { + public: + RigidBodyComponent(glm::vec3 initPosition, glm::vec3 initVelocity, + glm::quat initOrientation, glm::vec3 initAngularVelocity, + glm::vec3 externalForces = glm::vec3(0.0f, 0.0f, 0.0f), + float invMass = 1.0f, glm::mat3 invInertiaTensor = glm::mat3(1.0f)) + : position(initPosition), velocity(initVelocity), orientation(initOrientation), + angularVelocity(initAngularVelocity), externalForces(externalForces), + invMass(invMass), invInertiaTensor(invInertiaTensor) { + } - void offsetExternalForce(glm::vec3 force) { - externalForces += force; - } + void offsetExternalForce(glm::vec3 force) { + externalForces += force; + } - const glm::vec3& getPosition() const { return position; } - const glm::vec3& getCenterOfMass() const { return centerOfMass; } - const glm::vec3& getVelocity() const { return velocity; } - const glm::quat& getOrientation() const { return orientation; } - const glm::vec3& getAngularVelocity() const { return angularVelocity; } - const glm::vec3& getExternalForces() const { return externalForces; } - float getInvMass() const { return invMass; } - const glm::mat3& getInvInertiaTensor() const { return invInertiaTensor; } + const glm::vec3& getPosition() const { + return position; + } + const glm::vec3& getCenterOfMass() const { + return centerOfMass; + } + const glm::vec3& getVelocity() const { + return velocity; + } + const glm::quat& getOrientation() const { + return orientation; + } + const glm::vec3& getAngularVelocity() const { + return angularVelocity; + } + const glm::vec3& getExternalForces() const { + return externalForces; + } + float getInvMass() const { + return invMass; + } + const glm::mat3& getInvInertiaTensor() const { + return invInertiaTensor; + } - void setPosition(const glm::vec3& p) { position = p; } - void setCenterOfMass(const glm::vec3& p) { centerOfMass = p; } - void setVelocity(const glm::vec3& v) { velocity = v; } - void setOrientation(const glm::quat& q) { orientation = q; } - void setAngularVelocity(const glm::vec3& w) { angularVelocity = w; } - void setExternalForces(const glm::vec3& f) { externalForces = f; } - void setInvMass(float w) { invMass = w; } - void setInvInertiaTensor(const glm::mat3& I) { invInertiaTensor = I; } - void clearExternalForces() { externalForces = glm::vec3(0.0f); } + void setPosition(const glm::vec3& p) { + position = p; + } + void setCenterOfMass(const glm::vec3& p) { + centerOfMass = p; + } + void setVelocity(const glm::vec3& v) { + velocity = v; + } + void setOrientation(const glm::quat& q) { + orientation = q; + } + void setAngularVelocity(const glm::vec3& w) { + angularVelocity = w; + } + void setExternalForces(const glm::vec3& f) { + externalForces = f; + } + void setInvMass(float w) { + invMass = w; + } + void setInvInertiaTensor(const glm::mat3& I) { + invInertiaTensor = I; + } + void clearExternalForces() { + externalForces = glm::vec3(0.0f); + } - // approximate center of mass given a mesh - static glm::vec3 meshCenterOfMass(std::shared_ptr m); - // approximate inverse mass given a mesh - static float meshInvMass(std::shared_ptr m); + // approximate center of mass given a mesh + static glm::vec3 meshCenterOfMass(std::shared_ptr m); + // approximate inverse mass given a mesh + static float meshInvMass(std::shared_ptr m); - // No implementation for this for now - virtual void render() override {}; + // No implementation for this for now + virtual void render() override {}; - // Moves the object using its external forces with no regard for constraints. - virtual void update(float deltatime) override {}; - virtual ~RigidBodyComponent() = default; + // Moves the object using its external forces with no regard for constraints. + virtual void update(float deltatime) override {}; + virtual ~RigidBodyComponent() = default; -private: - glm::vec3 position; - glm::vec3 centerOfMass; - glm::vec3 velocity; - glm::quat orientation; - glm::vec3 angularVelocity; + private: + glm::vec3 position; + glm::vec3 centerOfMass; + glm::vec3 velocity; + glm::quat orientation; + glm::vec3 angularVelocity; - glm::vec3 externalForces; + glm::vec3 externalForces; - float invMass; - glm::mat3 invInertiaTensor; -}; - -} + float invMass; + glm::mat3 invInertiaTensor; + }; +} // namespace sauce diff --git a/include/app/components/TransformComponent.hpp b/include/app/components/TransformComponent.hpp index 9fefb944..849c98a8 100644 --- a/include/app/components/TransformComponent.hpp +++ b/include/app/components/TransformComponent.hpp @@ -5,32 +5,50 @@ namespace sauce { -class TransformComponent : public Component { -public: - TransformComponent(); - explicit TransformComponent(const modeling::Transform& transform); - - // Get the underlying transform - modeling::Transform& getTransform() { return transform; } - const modeling::Transform& getTransform() const { return transform; } - - // Convenience accessors - glm::vec3 getTranslation() const { return transform.getTranslation(); } - glm::quat getRotation() const { return transform.getRotation(); } - glm::vec3 getScale() const { return transform.getScale(); } - - void setTranslation(const glm::vec3& translation) { transform.setTranslation(translation); } - void setRotation(const glm::quat& rotation) { transform.setRotation(rotation); } - void setScale(const glm::vec3& scale) { transform.setScale(scale); } - - // Get the local transformation matrix - glm::mat4 getLocalMatrix() const { return transform.getLocalMatrix(); } - - // Component interface - virtual ~TransformComponent() = default; - -private: - modeling::Transform transform; -}; + class TransformComponent : public Component { + public: + TransformComponent(); + explicit TransformComponent(const modeling::Transform& transform); + + // Get the underlying transform + modeling::Transform& getTransform() { + return transform; + } + const modeling::Transform& getTransform() const { + return transform; + } + + // Convenience accessors + glm::vec3 getTranslation() const { + return transform.getTranslation(); + } + glm::quat getRotation() const { + return transform.getRotation(); + } + glm::vec3 getScale() const { + return transform.getScale(); + } + + void setTranslation(const glm::vec3& translation) { + transform.setTranslation(translation); + } + void setRotation(const glm::quat& rotation) { + transform.setRotation(rotation); + } + void setScale(const glm::vec3& scale) { + transform.setScale(scale); + } + + // Get the local transformation matrix + glm::mat4 getLocalMatrix() const { + return transform.getLocalMatrix(); + } + + // Component interface + virtual ~TransformComponent() = default; + + private: + modeling::Transform transform; + }; } // namespace sauce diff --git a/include/app/modeling/GLTFExporter.hpp b/include/app/modeling/GLTFExporter.hpp index a7aafe97..9b85b49b 100644 --- a/include/app/modeling/GLTFExporter.hpp +++ b/include/app/modeling/GLTFExporter.hpp @@ -1,69 +1,71 @@ #pragma once #include -#include #include +#include namespace tinygltf { class Model; } namespace sauce { -class Scene; -class Entity; - -namespace modeling { -class Mesh; -class Material; -class Texture; - -struct ExportOptions { - bool embedImages = false; - bool embedBuffers = true; - bool prettyPrint = true; - bool writeBinary = false; -}; - -class GLTFExporter { -public: - GLTFExporter(); - explicit GLTFExporter(const ExportOptions& options); - - bool exportScene(const Scene& scene, const std::string& filePath); - - const ExportOptions& getOptions() const { return options; } - void setOptions(const ExportOptions& opts) { options = opts; } - -private: - ExportOptions options; - - // Deduplication maps (keyed on raw pointer from shared_ptr) - std::unordered_map meshMap; - std::unordered_map materialMap; - std::unordered_map textureMap; - std::unordered_map imageMap; - - // Single buffer for all vertex/index data - std::vector bufferData; - - std::string outputDirectory; - - void buildModel(tinygltf::Model& model, const Scene& scene); - int processEntity(tinygltf::Model& model, const Entity& entity); - int getOrCreateMesh(tinygltf::Model& model, const Entity& entity); - int getOrCreateMaterial(tinygltf::Model& model, const Material& material); - int getOrCreateTexture(tinygltf::Model& model, const Texture& texture); - int getOrCreateImage(tinygltf::Model& model, const Texture& texture); - - int addAccessor(tinygltf::Model& model, - const void* data, size_t count, - int componentType, int type, - size_t byteStride, - const std::vector& minValues = {}, - const std::vector& maxValues = {}); - - void padBuffer(size_t alignment = 4); -}; - -} // namespace modeling + class Scene; + class Entity; + + namespace modeling { + class Mesh; + class Material; + class Texture; + + struct ExportOptions { + bool embedImages = false; + bool embedBuffers = true; + bool prettyPrint = true; + bool writeBinary = false; + }; + + class GLTFExporter { + public: + GLTFExporter(); + explicit GLTFExporter(const ExportOptions& options); + + bool exportScene(const Scene& scene, const std::string& filePath); + + const ExportOptions& getOptions() const { + return options; + } + void setOptions(const ExportOptions& opts) { + options = opts; + } + + private: + ExportOptions options; + + // Deduplication maps (keyed on raw pointer from shared_ptr) + std::unordered_map meshMap; + std::unordered_map materialMap; + std::unordered_map textureMap; + std::unordered_map imageMap; + + // Single buffer for all vertex/index data + std::vector bufferData; + + std::string outputDirectory; + + void buildModel(tinygltf::Model& model, const Scene& scene); + int processEntity(tinygltf::Model& model, const Entity& entity); + int getOrCreateMesh(tinygltf::Model& model, const Entity& entity); + int getOrCreateMaterial(tinygltf::Model& model, const Material& material); + int getOrCreateTexture(tinygltf::Model& model, const Texture& texture); + int getOrCreateImage(tinygltf::Model& model, const Texture& texture); + + int addAccessor(tinygltf::Model& model, const void* data, size_t count, + int componentType, int type, size_t byteStride, + const std::vector& minValues = {}, + const std::vector& maxValues = {}); + + void padBuffer(size_t alignment = 4); + }; + + } // namespace modeling } // namespace sauce diff --git a/include/app/modeling/GLTFLoader.hpp b/include/app/modeling/GLTFLoader.hpp index 38d2d87e..91081bdf 100644 --- a/include/app/modeling/GLTFLoader.hpp +++ b/include/app/modeling/GLTFLoader.hpp @@ -2,9 +2,9 @@ #include "app/modeling/Model.hpp" #include "app/modeling/TextureCache.hpp" +#include #include #include -#include // Forward declaration to avoid including tinygltf in header namespace tinygltf { @@ -15,91 +15,96 @@ namespace tinygltf { class Material; class Texture; class Image; -} +} // namespace tinygltf namespace sauce { -namespace modeling { - -struct LoadOptions { - bool generateNormals = true; // Generate normals if missing - bool generateTangents = true; // Generate tangents if missing - bool validateMeshes = true; // Validate mesh data - bool loadTextures = true; // Load texture data -}; - -class GLTFLoader { -public: - GLTFLoader(); - explicit GLTFLoader(const LoadOptions& options); - - // Load all scenes from a GLTF file - std::vector> loadModels(const std::string& filePath); - - // Load a specific scene from a GLTF file (default: scene 0) - std::shared_ptr loadModel(const std::string& filePath, size_t sceneIndex = 0); - - // Get load options - const LoadOptions& getOptions() const { return options; } - void setOptions(const LoadOptions& options) { this->options = options; } - - // Get texture cache - TextureCache& getTextureCache() { return textureCache; } - const TextureCache& getTextureCache() const { return textureCache; } - -private: - LoadOptions options; - TextureCache textureCache; - - // Internal GLTF data during loading - std::string baseDirectory; - const tinygltf::Model* currentGltfModel; - - // Main processing functions - bool loadGltfFile(const std::string& filePath, tinygltf::Model& gltfModel); - std::shared_ptr processScene(const tinygltf::Model& gltfModel, int sceneIndex); - - // Node processing - std::shared_ptr processNode(const tinygltf::Model& gltfModel, int nodeIndex); - void processNodeChildren(const tinygltf::Model& gltfModel, - const tinygltf::Node& gltfNode, - std::shared_ptr node); - - // Mesh processing - std::shared_ptr processPrimitive(const tinygltf::Model& gltfModel, - const tinygltf::Primitive& primitive); - void extractVertexAttribute(const tinygltf::Model& gltfModel, + namespace modeling { + + struct LoadOptions { + bool generateNormals = true; // Generate normals if missing + bool generateTangents = true; // Generate tangents if missing + bool validateMeshes = true; // Validate mesh data + bool loadTextures = true; // Load texture data + }; + + class GLTFLoader { + public: + GLTFLoader(); + explicit GLTFLoader(const LoadOptions& options); + + // Load all scenes from a GLTF file + std::vector> loadModels(const std::string& filePath); + + // Load a specific scene from a GLTF file (default: scene 0) + std::shared_ptr loadModel(const std::string& filePath, size_t sceneIndex = 0); + + // Get load options + const LoadOptions& getOptions() const { + return options; + } + void setOptions(const LoadOptions& options) { + this->options = options; + } + + // Get texture cache + TextureCache& getTextureCache() { + return textureCache; + } + const TextureCache& getTextureCache() const { + return textureCache; + } + + private: + LoadOptions options; + TextureCache textureCache; + + // Internal GLTF data during loading + std::string baseDirectory; + const tinygltf::Model* currentGltfModel; + + // Main processing functions + bool loadGltfFile(const std::string& filePath, tinygltf::Model& gltfModel); + std::shared_ptr processScene(const tinygltf::Model& gltfModel, int sceneIndex); + + // Node processing + std::shared_ptr processNode(const tinygltf::Model& gltfModel, int nodeIndex); + void processNodeChildren(const tinygltf::Model& gltfModel, + const tinygltf::Node& gltfNode, + std::shared_ptr node); + + // Mesh processing + std::shared_ptr processPrimitive(const tinygltf::Model& gltfModel, + const tinygltf::Primitive& primitive); + void extractVertexAttribute(const tinygltf::Model& gltfModel, + const tinygltf::Primitive& primitive, + const std::string& attributeName, + std::vector& vertices, int componentIndex); + void extractIndices(const tinygltf::Model& gltfModel, const tinygltf::Primitive& primitive, - const std::string& attributeName, - std::vector& vertices, - int componentIndex); - void extractIndices(const tinygltf::Model& gltfModel, - const tinygltf::Primitive& primitive, - std::vector& indices); - - // Material processing - std::shared_ptr processMaterial(const tinygltf::Model& gltfModel, int materialIndex); - - // Texture processing - std::shared_ptr processTexture(const tinygltf::Model& gltfModel, - int textureIndex, - TextureType type, - bool sRGB); - std::shared_ptr processImage(const tinygltf::Model& gltfModel, - int imageIndex, - TextureType type, - bool sRGB); - - // KHR_lights_punctual - void parseLightsExtension(const tinygltf::Model& gltfModel); - void applyNodeLight(const tinygltf::Node& gltfNode, std::shared_ptr node); - - std::vector parsedLights; // populated by parseLightsExtension - - // Helper functions - Transform extractTransform(const tinygltf::Node& gltfNode); - template - const T* getAccessorData(const tinygltf::Model& gltfModel, int accessorIndex, size_t& count); -}; - -} // namespace modeling + std::vector& indices); + + // Material processing + std::shared_ptr processMaterial(const tinygltf::Model& gltfModel, + int materialIndex); + + // Texture processing + std::shared_ptr processTexture(const tinygltf::Model& gltfModel, + int textureIndex, TextureType type, bool sRGB); + std::shared_ptr processImage(const tinygltf::Model& gltfModel, int imageIndex, + TextureType type, bool sRGB); + + // KHR_lights_punctual + void parseLightsExtension(const tinygltf::Model& gltfModel); + void applyNodeLight(const tinygltf::Node& gltfNode, std::shared_ptr node); + + std::vector parsedLights; // populated by parseLightsExtension + + // Helper functions + Transform extractTransform(const tinygltf::Node& gltfNode); + template + const T* getAccessorData(const tinygltf::Model& gltfModel, int accessorIndex, + size_t& count); + }; + + } // namespace modeling } // namespace sauce diff --git a/include/app/modeling/Material.hpp b/include/app/modeling/Material.hpp index 8d7a273c..56385bb8 100644 --- a/include/app/modeling/Material.hpp +++ b/include/app/modeling/Material.hpp @@ -1,111 +1,120 @@ #pragma once -#include "app/modeling/Texture.hpp" #include "app/modeling/PropertyValue.hpp" +#include "app/modeling/Texture.hpp" #include #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS -#include +#include #include #include #include -#include - +#include namespace sauce { struct LogicalDevice; } namespace sauce { -namespace modeling { - -struct MaterialProperties { - glm::vec4 baseColorFactor = glm::vec4(1.0f); - float metallicFactor = 1.0f; - float roughnessFactor = 1.0f; - - glm::vec3 emissiveFactor = glm::vec3(0.0f); - float normalScale = 1.0f; - float occlusionStrength = 1.0f; - - enum class AlphaMode { - Opaque, - Mask, - Blend - }; - - AlphaMode alphaMode = AlphaMode::Opaque; - float alphaCutoff = 0.5f; - bool doubleSided = false; -}; - -// GPU-side UBO layout (std140-friendly) -struct MaterialUBO { - glm::vec4 baseColorFactor; - float metallicFactor; - float roughnessFactor; - float normalScale; - float occlusionStrength; - glm::vec4 emissiveFactor_alphaCutoff; // xyz = emissive, w = alphaCutoff -}; - -class Material { -public: - Material(const std::string& name = ""); - - const std::string& getName() const { return name; } - const MaterialProperties& getProperties() const { return properties; } - MaterialProperties& getProperties() { return properties; } - - std::vector getDescriptorBufferInfos() const; - std::vector getDescriptorImageInfos( - const vk::raii::ImageView& defaultView, - const vk::raii::Sampler& defaultSampler) const; - - std::shared_ptr getTexture(TextureType type) const; - void setTexture(TextureType type, std::shared_ptr texture); - bool hasTexture(TextureType type) const; - - const std::unordered_map>& - getTextures() const { return textures; } - - const std::unordered_map& getMetadata() const { return metadata; } - void setMetadata(const std::string& key, const PropertyValue& value); - bool hasMetadata(const std::string& key) const; - - // Vulkan resource management - static void initDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice); - static const vk::raii::DescriptorSetLayout& getDescriptorSetLayout(); - static void cleanup(); - - const vk::raii::DescriptorSet& getDescriptorSet() const { return *descriptorSet; } - bool hasDescriptorSet() const { return (bool)descriptorSet; } - - void initVulkanResources( - const sauce::LogicalDevice& logicalDevice, - vk::raii::PhysicalDevice& physicalDevice, - vk::raii::CommandPool& commandPool, - vk::raii::Queue& queue, - const vk::raii::DescriptorPool& descriptorPool, - const vk::raii::ImageView& defaultView, - const vk::raii::Sampler& defaultSampler - ); - -private: - std::string name; - MaterialProperties properties; - - std::unordered_map> textures; - std::unordered_map metadata; - - // Vulkan resources - static std::unique_ptr descriptorSetLayout; - std::unique_ptr descriptorSet; - std::unique_ptr uniformBuffer; - std::unique_ptr uniformBufferMemory; - - // Helpers - void updateUniformBuffer(const sauce::LogicalDevice& logicalDevice) const; -}; - -} // namespace modeling + namespace modeling { + + struct MaterialProperties { + glm::vec4 baseColorFactor = glm::vec4(1.0f); + float metallicFactor = 1.0f; + float roughnessFactor = 1.0f; + + glm::vec3 emissiveFactor = glm::vec3(0.0f); + float normalScale = 1.0f; + float occlusionStrength = 1.0f; + + enum class AlphaMode { + Opaque, + Mask, + Blend + }; + + AlphaMode alphaMode = AlphaMode::Opaque; + float alphaCutoff = 0.5f; + bool doubleSided = false; + }; + + // GPU-side UBO layout (std140-friendly) + struct MaterialUBO { + glm::vec4 baseColorFactor; + float metallicFactor; + float roughnessFactor; + float normalScale; + float occlusionStrength; + glm::vec4 emissiveFactor_alphaCutoff; // xyz = emissive, w = alphaCutoff + }; + + class Material { + public: + Material(const std::string& name = ""); + + const std::string& getName() const { + return name; + } + const MaterialProperties& getProperties() const { + return properties; + } + MaterialProperties& getProperties() { + return properties; + } + + std::vector getDescriptorBufferInfos() const; + std::vector getDescriptorImageInfos( + const vk::raii::ImageView& defaultView, + const vk::raii::Sampler& defaultSampler) const; + + std::shared_ptr getTexture(TextureType type) const; + void setTexture(TextureType type, std::shared_ptr texture); + bool hasTexture(TextureType type) const; + + const std::unordered_map>& getTextures() const { + return textures; + } + + const std::unordered_map& getMetadata() const { + return metadata; + } + void setMetadata(const std::string& key, const PropertyValue& value); + bool hasMetadata(const std::string& key) const; + + // Vulkan resource management + static void initDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice); + static const vk::raii::DescriptorSetLayout& getDescriptorSetLayout(); + static void cleanup(); + + const vk::raii::DescriptorSet& getDescriptorSet() const { + return *descriptorSet; + } + bool hasDescriptorSet() const { + return (bool)descriptorSet; + } + + void initVulkanResources(const sauce::LogicalDevice& logicalDevice, + vk::raii::PhysicalDevice& physicalDevice, + vk::raii::CommandPool& commandPool, vk::raii::Queue& queue, + const vk::raii::DescriptorPool& descriptorPool, + const vk::raii::ImageView& defaultView, + const vk::raii::Sampler& defaultSampler); + + private: + std::string name; + MaterialProperties properties; + + std::unordered_map> textures; + std::unordered_map metadata; + + // Vulkan resources + static std::unique_ptr descriptorSetLayout; + std::unique_ptr descriptorSet; + std::unique_ptr uniformBuffer; + std::unique_ptr uniformBufferMemory; + + // Helpers + void updateUniformBuffer(const sauce::LogicalDevice& logicalDevice) const; + }; + + } // namespace modeling } // namespace sauce \ No newline at end of file diff --git a/include/app/modeling/Mesh.hpp b/include/app/modeling/Mesh.hpp index 62018c44..7559e250 100644 --- a/include/app/modeling/Mesh.hpp +++ b/include/app/modeling/Mesh.hpp @@ -2,76 +2,86 @@ #include "app/Vertex.hpp" #include "app/modeling/PropertyValue.hpp" -#include #include #include +#include #include namespace sauce { -struct LogicalDevice; -namespace modeling { - -class Mesh { -public: - Mesh() = default; - Mesh(const std::vector& vertices, - const std::vector& indices); - - const std::vector& getVertices() const { return vertices; } - const std::vector& getIndices() const { return indices; } - - size_t getVertexCount() const { return vertices.size(); } - size_t getIndexCount() const { return indices.size(); } - - bool hasGPUData() const { return vertexBuffer != nullptr; } - - bool isValid() const; - - void initVulkanResources( - vk::raii::Device& device, - vk::raii::PhysicalDevice& physicalDevice); - - void bind(vk::raii::CommandBuffer& cmd); - - void draw(vk::raii::CommandBuffer& cmd); - - void setPipelineLayout(vk::raii::PipelineLayout* layout) { - pipelineLayout = layout; - } - - vk::PipelineLayout getPipelineLayout() const { - return pipelineLayout ? **pipelineLayout : VK_NULL_HANDLE; - } - - const std::unordered_map& getMetadata() const { return metadata; } - void setMetadata(const std::string& key, const PropertyValue& value); - bool hasMetadata(const std::string& key) const; - - void generateNormals(); - void generateTangents(); - - // Optional GPU upload (Phase 6) - void initVulkanResources(const sauce::LogicalDevice& logicalDevice, vk::raii::PhysicalDevice& physicalDevice, vk::raii::CommandPool& commandPool, vk::raii::Queue& queue); - -private: - // CPU data - std::vector vertices; - std::vector indices; - std::unordered_map metadata; - - // GPU resources (optional, for Phase 6) - std::unique_ptr vertexBuffer; - std::unique_ptr indexBuffer; - std::unique_ptr vertexBufferMemory; - std::unique_ptr indexBufferMemory; - - vk::raii::PipelineLayout* pipelineLayout = nullptr; - - uint32_t findMemoryType( - vk::PhysicalDeviceMemoryProperties memProperties, - uint32_t typeFilter, - vk::MemoryPropertyFlags properties); -}; - -} // namespace modeling + struct LogicalDevice; + namespace modeling { + + class Mesh { + public: + Mesh() = default; + Mesh(const std::vector& vertices, const std::vector& indices); + + const std::vector& getVertices() const { + return vertices; + } + const std::vector& getIndices() const { + return indices; + } + + size_t getVertexCount() const { + return vertices.size(); + } + size_t getIndexCount() const { + return indices.size(); + } + + bool hasGPUData() const { + return vertexBuffer != nullptr; + } + + bool isValid() const; + + void initVulkanResources(vk::raii::Device& device, + vk::raii::PhysicalDevice& physicalDevice); + + void bind(vk::raii::CommandBuffer& cmd); + + void draw(vk::raii::CommandBuffer& cmd); + + void setPipelineLayout(vk::raii::PipelineLayout* layout) { + pipelineLayout = layout; + } + + vk::PipelineLayout getPipelineLayout() const { + return pipelineLayout ? **pipelineLayout : VK_NULL_HANDLE; + } + + const std::unordered_map& getMetadata() const { + return metadata; + } + void setMetadata(const std::string& key, const PropertyValue& value); + bool hasMetadata(const std::string& key) const; + + void generateNormals(); + void generateTangents(); + + // Optional GPU upload (Phase 6) + void initVulkanResources(const sauce::LogicalDevice& logicalDevice, + vk::raii::PhysicalDevice& physicalDevice, + vk::raii::CommandPool& commandPool, vk::raii::Queue& queue); + + private: + // CPU data + std::vector vertices; + std::vector indices; + std::unordered_map metadata; + + // GPU resources (optional, for Phase 6) + std::unique_ptr vertexBuffer; + std::unique_ptr indexBuffer; + std::unique_ptr vertexBufferMemory; + std::unique_ptr indexBufferMemory; + + vk::raii::PipelineLayout* pipelineLayout = nullptr; + + uint32_t findMemoryType(vk::PhysicalDeviceMemoryProperties memProperties, + uint32_t typeFilter, vk::MemoryPropertyFlags properties); + }; + + } // namespace modeling } // namespace sauce diff --git a/include/app/modeling/Model.hpp b/include/app/modeling/Model.hpp index 86bfd942..4b4f05e1 100644 --- a/include/app/modeling/Model.hpp +++ b/include/app/modeling/Model.hpp @@ -1,151 +1,165 @@ #pragma once -#include "app/modeling/ModelNode.hpp" -#include "app/modeling/Mesh.hpp" +#include "app/LogicalDevice.hpp" #include "app/modeling/Material.hpp" +#include "app/modeling/Mesh.hpp" +#include "app/modeling/ModelNode.hpp" #include "app/modeling/PropertyValue.hpp" -#include "app/LogicalDevice.hpp" -#include #include -#include #include +#include +#include namespace sauce { -namespace modeling { - -class Model { -public: - Model() = default; - - std::shared_ptr getRootNode() const { return rootNode; } - void setRootNode(std::shared_ptr root) { rootNode = root; rebuildFlatLists(); } - - // Flat lists of all meshes and materials in the model - const std::vector>& getAllMeshes() const { return allMeshes; } - const std::vector>& getAllMaterials() const { return allMaterials; } - - // Get all mesh-material pairs by traversing the scene graph - std::vector getAllMeshMaterialPairs() const; - - // Metadata access (for scene-level GLTF extensions) - const std::unordered_map& getMetadata() const { return metadata; } - void setMetadata(const std::string& key, const PropertyValue& value) { metadata[key] = value; } - bool hasMetadata(const std::string& key) const { return metadata.find(key) != metadata.end(); } - - void rebuildFlatLists() { - allMeshes.clear(); - allMaterials.clear(); - descriptorSets.clear(); - if (rootNode) traverseNode(rootNode); - } - - void initVulkanResources( - const sauce::LogicalDevice& logicalDevice, - vk::raii::PhysicalDevice& physicalDevice, - vk::raii::CommandPool& commandPool, - vk::raii::Queue& queue, - vk::raii::DescriptorPool& pool, - const vk::raii::ImageView& defaultView, - const vk::raii::Sampler& defaultSampler) - { - for (auto& mesh : allMeshes) { - mesh->initVulkanResources(logicalDevice, physicalDevice, commandPool, queue); - } - - for (auto& material : allMaterials) { - material->initVulkanResources(logicalDevice, physicalDevice, commandPool, queue, pool, defaultView, defaultSampler); - } - - if (allMaterials.empty()) return; - - // Model-level descriptor sets are now redundant as Materials manage their own, - // but we'll keep the allocation logic if it's expected by other parts of the engine. - // However, we should use Material::getDescriptorSetLayout() for consistency. - - std::vector layouts(allMaterials.size(), *Material::getDescriptorSetLayout()); - - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *pool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data(), - }; + namespace modeling { - descriptorSets = logicalDevice->allocateDescriptorSets(allocInfo); + class Model { + public: + Model() = default; - for (size_t i = 0; i < allMaterials.size(); ++i) { - auto& material = allMaterials[i]; + std::shared_ptr getRootNode() const { + return rootNode; + } + void setRootNode(std::shared_ptr root) { + rootNode = root; + rebuildFlatLists(); + } + + // Flat lists of all meshes and materials in the model + const std::vector>& getAllMeshes() const { + return allMeshes; + } + const std::vector>& getAllMaterials() const { + return allMaterials; + } - std::vector writes; + // Get all mesh-material pairs by traversing the scene graph + std::vector getAllMeshMaterialPairs() const; - auto bufferInfos = material->getDescriptorBufferInfos(); - auto imageInfos = material->getDescriptorImageInfos(defaultView, defaultSampler); + // Metadata access (for scene-level GLTF extensions) + const std::unordered_map& getMetadata() const { + return metadata; + } + void setMetadata(const std::string& key, const PropertyValue& value) { + metadata[key] = value; + } + bool hasMetadata(const std::string& key) const { + return metadata.find(key) != metadata.end(); + } - if (!bufferInfos.empty()) { - writes.emplace_back(vk::WriteDescriptorSet { - .dstSet = *descriptorSets[i], - .dstBinding = 5, // Material Properties UBO is binding 5 - .dstArrayElement = 0, - .descriptorCount = static_cast(bufferInfos.size()), - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = bufferInfos.data(), - }); + void rebuildFlatLists() { + allMeshes.clear(); + allMaterials.clear(); + descriptorSets.clear(); + if (rootNode) + traverseNode(rootNode); } - if (!imageInfos.empty()) { - writes.emplace_back(vk::WriteDescriptorSet { - .dstSet = *descriptorSets[i], - .dstBinding = 0, // Albedo, Normal, etc. start at binding 0 - .dstArrayElement = 0, - .descriptorCount = static_cast(imageInfos.size()), - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = imageInfos.data(), - }); + void initVulkanResources(const sauce::LogicalDevice& logicalDevice, + vk::raii::PhysicalDevice& physicalDevice, + vk::raii::CommandPool& commandPool, vk::raii::Queue& queue, + vk::raii::DescriptorPool& pool, + const vk::raii::ImageView& defaultView, + const vk::raii::Sampler& defaultSampler) { + for (auto& mesh : allMeshes) { + mesh->initVulkanResources(logicalDevice, physicalDevice, commandPool, queue); + } + + for (auto& material : allMaterials) { + material->initVulkanResources(logicalDevice, physicalDevice, commandPool, queue, + pool, defaultView, defaultSampler); + } + + if (allMaterials.empty()) + return; + + // Model-level descriptor sets are now redundant as Materials manage their own, + // but we'll keep the allocation logic if it's expected by other parts of the engine. + // However, we should use Material::getDescriptorSetLayout() for consistency. + + std::vector layouts(allMaterials.size(), + *Material::getDescriptorSetLayout()); + + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *pool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data(), + }; + + descriptorSets = logicalDevice->allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < allMaterials.size(); ++i) { + auto& material = allMaterials[i]; + + std::vector writes; + + auto bufferInfos = material->getDescriptorBufferInfos(); + auto imageInfos = + material->getDescriptorImageInfos(defaultView, defaultSampler); + + if (!bufferInfos.empty()) { + writes.emplace_back(vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 5, // Material Properties UBO is binding 5 + .dstArrayElement = 0, + .descriptorCount = static_cast(bufferInfos.size()), + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = bufferInfos.data(), + }); + } + + if (!imageInfos.empty()) { + writes.emplace_back(vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 0, // Albedo, Normal, etc. start at binding 0 + .dstArrayElement = 0, + .descriptorCount = static_cast(imageInfos.size()), + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = imageInfos.data(), + }); + } + + logicalDevice->updateDescriptorSets(writes, nullptr); + } } - logicalDevice->updateDescriptorSets(writes, nullptr); - } - } - - void draw(vk::raii::CommandBuffer& buffer, const vk::raii::PipelineLayout& pipelineLayout) - { - // Model::draw is currently only used if we draw all materials. - // But in recordSceneCommandBuffer, we draw per-entity. - // If we use this draw, we bind Set Index 1. - for (size_t i = 0; i < allMaterials.size(); ++i) { - // This is a bit problematic because Model doesn't know which mesh belongs to which material here - // unless they are 1:1. - // Let's assume they are 1:1 for now as per traverseNode. - if (i < allMeshes.size()) { - auto& mesh = allMeshes[i]; - mesh->bind(buffer); - - buffer.bindDescriptorSets( - vk::PipelineBindPoint::eGraphics, - pipelineLayout, - 2, // Set Index 2: Material - *descriptorSets[i], - nullptr - ); - - mesh->draw(buffer); + void draw(vk::raii::CommandBuffer& buffer, + const vk::raii::PipelineLayout& pipelineLayout) { + // Model::draw is currently only used if we draw all materials. + // But in recordSceneCommandBuffer, we draw per-entity. + // If we use this draw, we bind Set Index 1. + for (size_t i = 0; i < allMaterials.size(); ++i) { + // This is a bit problematic because Model doesn't know which mesh belongs to which material here + // unless they are 1:1. + // Let's assume they are 1:1 for now as per traverseNode. + if (i < allMeshes.size()) { + auto& mesh = allMeshes[i]; + mesh->bind(buffer); + + buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, + 2, // Set Index 2: Material + *descriptorSets[i], nullptr); + + mesh->draw(buffer); + } + } } - } - } - const vk::raii::DescriptorSet& getDescriptorSet(size_t index) const { - return descriptorSets[index]; - } + const vk::raii::DescriptorSet& getDescriptorSet(size_t index) const { + return descriptorSets[index]; + } -private: - std::shared_ptr rootNode; - std::vector> allMeshes; - std::vector> allMaterials; - std::vector descriptorSets; - std::unordered_map metadata; + private: + std::shared_ptr rootNode; + std::vector> allMeshes; + std::vector> allMaterials; + std::vector descriptorSets; + std::unordered_map metadata; - void traverseNode(std::shared_ptr node); - void collectPairsFromNode(std::shared_ptr node, std::vector& pairs) const; -}; + void traverseNode(std::shared_ptr node); + void collectPairsFromNode(std::shared_ptr node, + std::vector& pairs) const; + }; -} // namespace modeling + } // namespace modeling } // namespace sauce diff --git a/include/app/modeling/ModelNode.hpp b/include/app/modeling/ModelNode.hpp index 390fc51a..fa1592be 100644 --- a/include/app/modeling/ModelNode.hpp +++ b/include/app/modeling/ModelNode.hpp @@ -1,78 +1,107 @@ #pragma once -#include "app/modeling/Transform.hpp" -#include "app/modeling/Mesh.hpp" #include "app/modeling/Material.hpp" +#include "app/modeling/Mesh.hpp" #include "app/modeling/PropertyValue.hpp" -#include -#include +#include "app/modeling/Transform.hpp" #include #include +#include #include +#include #include namespace sauce { -namespace modeling { - -struct MeshMaterialPair { - std::shared_ptr mesh; - std::shared_ptr material; -}; - -struct LightInfo { - enum class Type { Directional = 0, Point = 1, Spot = 2 }; - Type type{Type::Point}; - glm::vec3 color{1.0f}; - float intensity{1.0f}; - float range{0.0f}; - float innerConeAngle{0.0f}; - float outerConeAngle{0.7853981f}; // pi/4 - std::string name; -}; - -class ModelNode { -public: - ModelNode(const std::string& name = ""); - - // Getters - const std::string& getName() const { return name; } - void setName(const std::string& name) { this->name = name; } - - Transform& getTransform() { return transform; } - const Transform& getTransform() const { return transform; } - - ModelNode* getParent() const { return parent; } - void setParent(ModelNode* parent) { this->parent = parent; } - - const std::vector>& getChildren() const { return children; } - void addChild(std::shared_ptr child); - void removeChild(std::shared_ptr child); - - const std::vector& getMeshMaterialPairs() const { return meshMaterialPairs; } - void addMeshMaterialPair(std::shared_ptr mesh, std::shared_ptr material); - - const std::optional& getLightInfo() const { return lightInfo; } - void setLightInfo(const LightInfo& info) { lightInfo = info; } - bool hasLight() const { return lightInfo.has_value(); } - - // Metadata access (for GLTF extensions) - const std::unordered_map& getMetadata() const { return metadata; } - void setMetadata(const std::string& key, const PropertyValue& value); - bool hasMetadata(const std::string& key) const; - - // World transform computation - glm::mat4 getWorldMatrix() const; - -private: - std::string name; - Transform transform; - ModelNode* parent; // Raw pointer to avoid circular shared_ptr - std::vector> children; - std::vector meshMaterialPairs; - std::optional lightInfo; - std::unordered_map metadata; -}; - -} // namespace modeling + namespace modeling { + + struct MeshMaterialPair { + std::shared_ptr mesh; + std::shared_ptr material; + }; + + struct LightInfo { + enum class Type { + Directional = 0, + Point = 1, + Spot = 2 + }; + Type type{Type::Point}; + glm::vec3 color{1.0f}; + float intensity{1.0f}; + float range{0.0f}; + float innerConeAngle{0.0f}; + float outerConeAngle{0.7853981f}; // pi/4 + std::string name; + }; + + class ModelNode { + public: + ModelNode(const std::string& name = ""); + + // Getters + const std::string& getName() const { + return name; + } + void setName(const std::string& name) { + this->name = name; + } + + Transform& getTransform() { + return transform; + } + const Transform& getTransform() const { + return transform; + } + + ModelNode* getParent() const { + return parent; + } + void setParent(ModelNode* parent) { + this->parent = parent; + } + + const std::vector>& getChildren() const { + return children; + } + void addChild(std::shared_ptr child); + void removeChild(std::shared_ptr child); + + const std::vector& getMeshMaterialPairs() const { + return meshMaterialPairs; + } + void addMeshMaterialPair(std::shared_ptr mesh, + std::shared_ptr material); + + const std::optional& getLightInfo() const { + return lightInfo; + } + void setLightInfo(const LightInfo& info) { + lightInfo = info; + } + bool hasLight() const { + return lightInfo.has_value(); + } + + // Metadata access (for GLTF extensions) + const std::unordered_map& getMetadata() const { + return metadata; + } + void setMetadata(const std::string& key, const PropertyValue& value); + bool hasMetadata(const std::string& key) const; + + // World transform computation + glm::mat4 getWorldMatrix() const; + + private: + std::string name; + Transform transform; + ModelNode* parent; // Raw pointer to avoid circular shared_ptr + std::vector> children; + std::vector meshMaterialPairs; + std::optional lightInfo; + std::unordered_map metadata; + }; + + } // namespace modeling } // namespace sauce diff --git a/include/app/modeling/PropertyValue.hpp b/include/app/modeling/PropertyValue.hpp index 8f7555d6..d2203017 100644 --- a/include/app/modeling/PropertyValue.hpp +++ b/include/app/modeling/PropertyValue.hpp @@ -1,13 +1,13 @@ #pragma once -#include #include +#include namespace sauce { -namespace modeling { + namespace modeling { -// Variant type for storing metadata from GLTF extensions and extras -using PropertyValue = std::variant; + // Variant type for storing metadata from GLTF extensions and extras + using PropertyValue = std::variant; -} // namespace modeling + } // namespace modeling } // namespace sauce diff --git a/include/app/modeling/Texture.hpp b/include/app/modeling/Texture.hpp index 72bef7fd..17ddb4e0 100644 --- a/include/app/modeling/Texture.hpp +++ b/include/app/modeling/Texture.hpp @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS #include @@ -12,78 +12,93 @@ namespace sauce { } namespace sauce { -namespace modeling { - -enum class TextureType { - BaseColor, - Normal, - MetallicRoughness, - Occlusion, - Emissive, - EnvironmentMapHDR, - Unknown -}; - -class Texture { -public: - // Constructor for external file path - Texture(const std::string& path, TextureType type, bool sRGB); - - // Constructor for embedded texture data - Texture(const std::vector& data, int width, int height, int channels, - TextureType type, bool sRGB, const std::string& name = "embedded"); - - // Getters - const std::string& getPath() const { return path; } - TextureType getType() const { return type; } - bool isSRGB() const { return sRGB; } - bool isHDR() const { return hdr; } - int getWidth() const { return width; } - int getHeight() const { return height; } - int getChannels() const { return channels; } - bool isEmbedded() const { return embedded; } - bool hasGPUData() const { return image != nullptr; } - - // Get CPU-side texture data (loads from file if necessary) - const std::vector& getData(); - const std::vector& getHDRData(); - - // Initialize Vulkan GPU resources - void initVulkanResources( - const sauce::LogicalDevice& logicalDevice, - vk::raii::PhysicalDevice& physicalDevice, - vk::raii::CommandPool& commandPool, - vk::raii::Queue& queue - ); - - // Get descriptor info - vk::DescriptorImageInfo getDescriptorInfo() const; - -private: - std::string path; - TextureType type; - bool sRGB; - bool hdr; - bool embedded; - - // CPU-side data - int width; - int height; - int channels; - std::vector data; - std::vector hdrData; - bool dataLoaded; - - // GPU resources (optional, for Phase 6) - std::unique_ptr image; - std::unique_ptr imageView; - std::unique_ptr imageMemory; - std::unique_ptr sampler; - - void loadFromFile(); - void loadFromFileHDR(); - void createDefaultPixels(); -}; - -} // namespace modeling + namespace modeling { + + enum class TextureType { + BaseColor, + Normal, + MetallicRoughness, + Occlusion, + Emissive, + EnvironmentMapHDR, + Unknown + }; + + class Texture { + public: + // Constructor for external file path + Texture(const std::string& path, TextureType type, bool sRGB); + + // Constructor for embedded texture data + Texture(const std::vector& data, int width, int height, int channels, + TextureType type, bool sRGB, const std::string& name = "embedded"); + + // Getters + const std::string& getPath() const { + return path; + } + TextureType getType() const { + return type; + } + bool isSRGB() const { + return sRGB; + } + bool isHDR() const { + return hdr; + } + int getWidth() const { + return width; + } + int getHeight() const { + return height; + } + int getChannels() const { + return channels; + } + bool isEmbedded() const { + return embedded; + } + bool hasGPUData() const { + return image != nullptr; + } + + // Get CPU-side texture data (loads from file if necessary) + const std::vector& getData(); + const std::vector& getHDRData(); + + // Initialize Vulkan GPU resources + void initVulkanResources(const sauce::LogicalDevice& logicalDevice, + vk::raii::PhysicalDevice& physicalDevice, + vk::raii::CommandPool& commandPool, vk::raii::Queue& queue); + + // Get descriptor info + vk::DescriptorImageInfo getDescriptorInfo() const; + + private: + std::string path; + TextureType type; + bool sRGB; + bool hdr; + bool embedded; + + // CPU-side data + int width; + int height; + int channels; + std::vector data; + std::vector hdrData; + bool dataLoaded; + + // GPU resources (optional, for Phase 6) + std::unique_ptr image; + std::unique_ptr imageView; + std::unique_ptr imageMemory; + std::unique_ptr sampler; + + void loadFromFile(); + void loadFromFileHDR(); + void createDefaultPixels(); + }; + + } // namespace modeling } // namespace sauce diff --git a/include/app/modeling/TextureCache.hpp b/include/app/modeling/TextureCache.hpp index e31d41d1..08de0828 100644 --- a/include/app/modeling/TextureCache.hpp +++ b/include/app/modeling/TextureCache.hpp @@ -1,48 +1,51 @@ #pragma once #include "app/modeling/Texture.hpp" -#include #include #include +#include namespace sauce { -namespace modeling { + namespace modeling { -class TextureCache { -public: - TextureCache(); + class TextureCache { + public: + TextureCache(); - // Get or create texture from file path - std::shared_ptr getTexture(const std::string& path, TextureType type, bool sRGB); + // Get or create texture from file path + std::shared_ptr getTexture(const std::string& path, TextureType type, + bool sRGB); - // Load an HDR environment map (.hdr file). HDR detection and stbi_loadf - // happen inside Texture::loadFromFile; this method sets the correct type. - std::shared_ptr getHDRTexture(const std::string& path); + // Load an HDR environment map (.hdr file). HDR detection and stbi_loadf + // happen inside Texture::loadFromFile; this method sets the correct type. + std::shared_ptr getHDRTexture(const std::string& path); - // Get or create texture from embedded data (uses hash-based key) - std::shared_ptr getEmbeddedTexture(const std::vector& data, - int width, int height, int channels, - TextureType type, bool sRGB); + // Get or create texture from embedded data (uses hash-based key) + std::shared_ptr getEmbeddedTexture(const std::vector& data, + int width, int height, int channels, + TextureType type, bool sRGB); - // Get default textures (1x1 pixels) - std::shared_ptr getDefaultTexture(TextureType type); + // Get default textures (1x1 pixels) + std::shared_ptr getDefaultTexture(TextureType type); - // Clear the cache - void clear(); + // Clear the cache + void clear(); - // Get cache statistics - size_t getCacheSize() const { return cache.size(); } + // Get cache statistics + size_t getCacheSize() const { + return cache.size(); + } -private: - std::unordered_map> cache; - std::unordered_map> defaultTextures; + private: + std::unordered_map> cache; + std::unordered_map> defaultTextures; - // Generate a hash key for embedded textures - std::string generateEmbeddedKey(const std::vector& data); + // Generate a hash key for embedded textures + std::string generateEmbeddedKey(const std::vector& data); - // Initialize default textures - void initializeDefaultTextures(); -}; + // Initialize default textures + void initializeDefaultTextures(); + }; -} // namespace modeling + } // namespace modeling } // namespace sauce diff --git a/include/app/modeling/Transform.hpp b/include/app/modeling/Transform.hpp index 8137320c..36b78ff9 100644 --- a/include/app/modeling/Transform.hpp +++ b/include/app/modeling/Transform.hpp @@ -5,38 +5,45 @@ #include namespace sauce { -namespace modeling { - -// Forward declaration -class ModelNode; - -class Transform { -public: - Transform(); - Transform(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale); - - // Getters - const glm::vec3& getTranslation() const { return translation; } - const glm::quat& getRotation() const { return rotation; } - const glm::vec3& getScale() const { return scale; } - - // Setters - void setTranslation(const glm::vec3& translation); - void setRotation(const glm::quat& rotation); - void setScale(const glm::vec3& scale); - - // Matrix computation - glm::mat4 getLocalMatrix() const; - glm::mat4 getWorldMatrix(const ModelNode* node) const; - - // Static utility for matrix decomposition - static Transform fromMatrix(const glm::mat4& matrix); - -private: - glm::vec3 translation; - glm::quat rotation; - glm::vec3 scale; -}; - -} // namespace modeling + namespace modeling { + + // Forward declaration + class ModelNode; + + class Transform { + public: + Transform(); + Transform(const glm::vec3& translation, const glm::quat& rotation, + const glm::vec3& scale); + + // Getters + const glm::vec3& getTranslation() const { + return translation; + } + const glm::quat& getRotation() const { + return rotation; + } + const glm::vec3& getScale() const { + return scale; + } + + // Setters + void setTranslation(const glm::vec3& translation); + void setRotation(const glm::quat& rotation); + void setScale(const glm::vec3& scale); + + // Matrix computation + glm::mat4 getLocalMatrix() const; + glm::mat4 getWorldMatrix(const ModelNode* node) const; + + // Static utility for matrix decomposition + static Transform fromMatrix(const glm::mat4& matrix); + + private: + glm::vec3 translation; + glm::quat rotation; + glm::vec3 scale; + }; + + } // namespace modeling } // namespace sauce diff --git a/include/app/ui/ImGuiComponent.hpp b/include/app/ui/ImGuiComponent.hpp index a69bc572..0855c6bf 100644 --- a/include/app/ui/ImGuiComponent.hpp +++ b/include/app/ui/ImGuiComponent.hpp @@ -4,22 +4,29 @@ namespace sauce::ui { -class ImGuiComponent { -public: - explicit ImGuiComponent(const std::string& componentName, bool startEnabled = true) - : name(componentName), enabled(startEnabled) {} - - virtual ~ImGuiComponent() = default; - - virtual void render() = 0; // Override to define UI - - void setEnabled(bool state) { enabled = state; } - bool isEnabled() const { return enabled; } - const std::string& getName() const { return name; } - -protected: - std::string name; - bool enabled; -}; - -} + class ImGuiComponent { + public: + explicit ImGuiComponent(const std::string& componentName, bool startEnabled = true) + : name(componentName), enabled(startEnabled) { + } + + virtual ~ImGuiComponent() = default; + + virtual void render() = 0; // Override to define UI + + void setEnabled(bool state) { + enabled = state; + } + bool isEnabled() const { + return enabled; + } + const std::string& getName() const { + return name; + } + + protected: + std::string name; + bool enabled; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/ImGuiComponentManager.hpp b/include/app/ui/ImGuiComponentManager.hpp index a8d5641b..c3e21fee 100644 --- a/include/app/ui/ImGuiComponentManager.hpp +++ b/include/app/ui/ImGuiComponentManager.hpp @@ -2,22 +2,22 @@ #include #include -#include #include +#include namespace sauce::ui { -class ImGuiComponentManager { -public: - void addComponent(std::unique_ptr component); - bool removeComponent(const std::string& name); - ImGuiComponent* getComponent(const std::string& name); - void renderAll(); // Renders all enabled components - bool setComponentEnabled(const std::string& name, bool enabled); - size_t getComponentCount() const; + class ImGuiComponentManager { + public: + void addComponent(std::unique_ptr component); + bool removeComponent(const std::string& name); + ImGuiComponent* getComponent(const std::string& name); + void renderAll(); // Renders all enabled components + bool setComponentEnabled(const std::string& name, bool enabled); + size_t getComponentCount() const; -private: - std::vector> components; -}; + private: + std::vector> components; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/BulletText.hpp b/include/app/ui/components/BulletText.hpp index 5f69e493..40ad0dc9 100644 --- a/include/app/ui/components/BulletText.hpp +++ b/include/app/ui/components/BulletText.hpp @@ -1,14 +1,13 @@ -#pragma once +#pragma once #include namespace sauce::ui { -class BulletText : public Text { -public: + class BulletText : public Text { + public: + BulletText(const std::string& name, const std::string& text); - BulletText(const std::string& name, const std::string& text); - - void render() override; -}; + void render() override; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/Button.hpp b/include/app/ui/components/Button.hpp index 4a4118ca..58d5b514 100644 --- a/include/app/ui/components/Button.hpp +++ b/include/app/ui/components/Button.hpp @@ -1,23 +1,22 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -class Button : public ImGuiComponent { -public: - using Callback = std::function; - - Button(const std::string& name, const std::string& label, Callback onClick = nullptr); + class Button : public ImGuiComponent { + public: + using Callback = std::function; - void render() override; + Button(const std::string& name, const std::string& label, Callback onClick = nullptr); -private: - std::string label; - Callback onClick; + void render() override; -}; + private: + std::string label; + Callback onClick; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/Checkbox.hpp b/include/app/ui/components/Checkbox.hpp index 81b6cde2..719b644b 100644 --- a/include/app/ui/components/Checkbox.hpp +++ b/include/app/ui/components/Checkbox.hpp @@ -1,23 +1,24 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -class Checkbox : public ImGuiComponent { -public: - using Callback = std::function; + class Checkbox : public ImGuiComponent { + public: + using Callback = std::function; - Checkbox(const std::string& name, const std::string& label, bool* checked, Callback onChanged = nullptr); + Checkbox(const std::string& name, const std::string& label, bool* checked, + Callback onChanged = nullptr); - void render() override; + void render() override; -private: - std::string label; - bool* checked; - Callback onChanged; -}; + private: + std::string label; + bool* checked; + Callback onChanged; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/ChildWindow.hpp b/include/app/ui/components/ChildWindow.hpp index 7af284c8..6db9d8ba 100644 --- a/include/app/ui/components/ChildWindow.hpp +++ b/include/app/ui/components/ChildWindow.hpp @@ -1,38 +1,48 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -/** + /** * ChildWindow - Wraps ImGui::BeginChild() / ImGui::EndChild() * Creates a scrollable region within a parent window */ -class ChildWindow : public ImGuiComponent { -public: - using ContentCallback = std::function; - - ChildWindow(const std::string& name, const ImVec2& size = ImVec2(0, 0), - ImGuiChildFlags childFlags = 0, ImGuiWindowFlags windowFlags = 0, - ContentCallback content = nullptr); - - void render() override; - - void setSize(const ImVec2& newSize) { size = newSize; } - ImVec2 getSize() const { return size; } - - void setContent(ContentCallback newContent) { content = newContent; } - void setChildFlags(ImGuiChildFlags newFlags) { childFlags = newFlags; } - void setWindowFlags(ImGuiWindowFlags newFlags) { windowFlags = newFlags; } - -private: - ImVec2 size; - ImGuiChildFlags childFlags; - ImGuiWindowFlags windowFlags; - ContentCallback content; -}; - -} + class ChildWindow : public ImGuiComponent { + public: + using ContentCallback = std::function; + + ChildWindow(const std::string& name, const ImVec2& size = ImVec2(0, 0), + ImGuiChildFlags childFlags = 0, ImGuiWindowFlags windowFlags = 0, + ContentCallback content = nullptr); + + void render() override; + + void setSize(const ImVec2& newSize) { + size = newSize; + } + ImVec2 getSize() const { + return size; + } + + void setContent(ContentCallback newContent) { + content = newContent; + } + void setChildFlags(ImGuiChildFlags newFlags) { + childFlags = newFlags; + } + void setWindowFlags(ImGuiWindowFlags newFlags) { + windowFlags = newFlags; + } + + private: + ImVec2 size; + ImGuiChildFlags childFlags; + ImGuiWindowFlags windowFlags; + ContentCallback content; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/components/CollapsingHeader.hpp b/include/app/ui/components/CollapsingHeader.hpp index 3253ce94..9ad2e3f9 100644 --- a/include/app/ui/components/CollapsingHeader.hpp +++ b/include/app/ui/components/CollapsingHeader.hpp @@ -1,44 +1,60 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -/** + /** * CollapsingHeader - Wraps ImGui::CollapsingHeader() * Creates a collapsible section header */ -class CollapsingHeader : public ImGuiComponent { -public: - using ContentCallback = std::function; - - CollapsingHeader(const std::string& name, const std::string& label, - ContentCallback content = nullptr, ImGuiTreeNodeFlags flags = 0); - - void render() override; - - void setLabel(const std::string& newLabel) { label = newLabel; } - const std::string& getLabel() const { return label; } - - void setContent(ContentCallback newContent) { content = newContent; } - void setFlags(ImGuiTreeNodeFlags newFlags) { flags = newFlags; } - - bool isHeaderOpen() const { return headerOpen; } - - // Optional: track visibility with close button - bool* getVisiblePtr() { return &visible; } - bool isVisible() const { return visible; } - void setVisible(bool vis) { visible = vis; } - -private: - std::string label; - ContentCallback content; - ImGuiTreeNodeFlags flags; - bool headerOpen = false; - bool visible = true; -}; - -} + class CollapsingHeader : public ImGuiComponent { + public: + using ContentCallback = std::function; + + CollapsingHeader(const std::string& name, const std::string& label, + ContentCallback content = nullptr, ImGuiTreeNodeFlags flags = 0); + + void render() override; + + void setLabel(const std::string& newLabel) { + label = newLabel; + } + const std::string& getLabel() const { + return label; + } + + void setContent(ContentCallback newContent) { + content = newContent; + } + void setFlags(ImGuiTreeNodeFlags newFlags) { + flags = newFlags; + } + + bool isHeaderOpen() const { + return headerOpen; + } + + // Optional: track visibility with close button + bool* getVisiblePtr() { + return &visible; + } + bool isVisible() const { + return visible; + } + void setVisible(bool vis) { + visible = vis; + } + + private: + std::string label; + ContentCallback content; + ImGuiTreeNodeFlags flags; + bool headerOpen = false; + bool visible = true; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/components/Columns.hpp b/include/app/ui/components/Columns.hpp index ff8150bd..546f0fda 100644 --- a/include/app/ui/components/Columns.hpp +++ b/include/app/ui/components/Columns.hpp @@ -1,44 +1,60 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -/** + /** * Columns - Wraps ImGui::Columns() / ImGui::BeginColumns() / ImGui::EndColumns() * Creates a multi-column layout */ -class Columns : public ImGuiComponent { -public: - using ContentCallback = std::function; - - Columns(const std::string& name, int count = 1, const std::string& id = "", - bool border = true, ContentCallback content = nullptr); - - void render() override; - - void setCount(int newCount) { count = newCount; } - int getCount() const { return count; } - - void setId(const std::string& newId) { id = newId; } - const std::string& getId() const { return id; } - - void setBorder(bool newBorder) { border = newBorder; } - bool hasBorder() const { return border; } - - void setContent(ContentCallback newContent) { content = newContent; } - - // Static helper to advance to next column - static void nextColumn() { ImGui::NextColumn(); } - -private: - int count; - std::string id; - bool border; - ContentCallback content; -}; - -} + class Columns : public ImGuiComponent { + public: + using ContentCallback = std::function; + + Columns(const std::string& name, int count = 1, const std::string& id = "", + bool border = true, ContentCallback content = nullptr); + + void render() override; + + void setCount(int newCount) { + count = newCount; + } + int getCount() const { + return count; + } + + void setId(const std::string& newId) { + id = newId; + } + const std::string& getId() const { + return id; + } + + void setBorder(bool newBorder) { + border = newBorder; + } + bool hasBorder() const { + return border; + } + + void setContent(ContentCallback newContent) { + content = newContent; + } + + // Static helper to advance to next column + static void nextColumn() { + ImGui::NextColumn(); + } + + private: + int count; + std::string id; + bool border; + ContentCallback content; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/components/CustomTooltip.hpp b/include/app/ui/components/CustomTooltip.hpp index ac8edec6..06617e7c 100644 --- a/include/app/ui/components/CustomTooltip.hpp +++ b/include/app/ui/components/CustomTooltip.hpp @@ -1,20 +1,18 @@ #pragma once +#include #include #include -#include -namespace sauce::ui -{ - class CustomTooltip : public ImGuiComponent - { - public: - explicit CustomTooltip(const std::string& name, std::function renderFn); - ~CustomTooltip() override; +namespace sauce::ui { + class CustomTooltip : public ImGuiComponent { + public: + explicit CustomTooltip(const std::string& name, std::function renderFn); + ~CustomTooltip() override; - void render() override; + void render() override; - private: - std::function renderFn; - }; -} + private: + std::function renderFn; + }; +} // namespace sauce::ui diff --git a/include/app/ui/components/DebugStatsWindow.hpp b/include/app/ui/components/DebugStatsWindow.hpp index 547949f5..a2191d69 100644 --- a/include/app/ui/components/DebugStatsWindow.hpp +++ b/include/app/ui/components/DebugStatsWindow.hpp @@ -5,16 +5,17 @@ namespace sauce::ui { -class DebugStatsWindow : public ImGuiComponent { -public: - DebugStatsWindow() : ImGuiComponent("DebugStatsWindow") {} + class DebugStatsWindow : public ImGuiComponent { + public: + DebugStatsWindow() : ImGuiComponent("DebugStatsWindow") { + } - void render() override { - ImGui::Begin("SauceEngine Debug"); - ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); - ImGui::Text("Frame Time: %.3f ms", 1000.0f / ImGui::GetIO().Framerate); - ImGui::End(); - } -}; + void render() override { + ImGui::Begin("SauceEngine Debug"); + ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); + ImGui::Text("Frame Time: %.3f ms", 1000.0f / ImGui::GetIO().Framerate); + ImGui::End(); + } + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/DragFloat.hpp b/include/app/ui/components/DragFloat.hpp index df7cbeeb..70c7c47c 100644 --- a/include/app/ui/components/DragFloat.hpp +++ b/include/app/ui/components/DragFloat.hpp @@ -1,58 +1,70 @@ #pragma once #include -#include +#include #include +#include #include -#include namespace sauce::ui { -template -class DragFloat : public ImGuiComponent { - static_assert(N >= 1 && N <= 4, "DragFloat component count must be between 1 and 4"); + template class DragFloat : public ImGuiComponent { + static_assert(N >= 1 && N <= 4, "DragFloat component count must be between 1 and 4"); + + public: + using Callback = std::function&)>; -public: - using Callback = std::function&)>; + DragFloat(const std::string& name, const std::string& label, std::array& value, + float speed = 1.0f, float min = 0.0f, float max = 0.0f, + const std::string& format = "%.3f", Callback onChanged = nullptr) + : ImGuiComponent(name), label(label), value(value), speed(speed), min(min), max(max), + format(format), onChanged(onChanged) { + } - DragFloat(const std::string& name, const std::string& label, std::array& value, - float speed = 1.0f, float min = 0.0f, float max = 0.0f, - const std::string& format = "%.3f", Callback onChanged = nullptr) - : ImGuiComponent(name), label(label), value(value), speed(speed), min(min), max(max), - format(format), onChanged(onChanged) {} + void render() override { + if (!enabled) + return; - void render() override { - if (!enabled) return; + bool changed = false; - bool changed = false; + if constexpr (N == 1) { + changed = + ImGui::DragFloat(label.c_str(), value.data(), speed, min, max, format.c_str()); + } else if constexpr (N == 2) { + changed = + ImGui::DragFloat2(label.c_str(), value.data(), speed, min, max, format.c_str()); + } else if constexpr (N == 3) { + changed = + ImGui::DragFloat3(label.c_str(), value.data(), speed, min, max, format.c_str()); + } else if constexpr (N == 4) { + changed = + ImGui::DragFloat4(label.c_str(), value.data(), speed, min, max, format.c_str()); + } - if constexpr (N == 1) { - changed = ImGui::DragFloat(label.c_str(), value.data(), speed, min, max, format.c_str()); - } else if constexpr (N == 2) { - changed = ImGui::DragFloat2(label.c_str(), value.data(), speed, min, max, format.c_str()); - } else if constexpr (N == 3) { - changed = ImGui::DragFloat3(label.c_str(), value.data(), speed, min, max, format.c_str()); - } else if constexpr (N == 4) { - changed = ImGui::DragFloat4(label.c_str(), value.data(), speed, min, max, format.c_str()); + if (changed && onChanged) { + onChanged(value); + } } - if (changed && onChanged) { - onChanged(value); + void setSpeed(float spd) { + speed = spd; + } + void setRange(float newMin, float newMax) { + min = newMin; + max = newMax; + } + void setFormat(const std::string& fmt) { + format = fmt; } - } - - void setSpeed(float spd) { speed = spd; } - void setRange(float newMin, float newMax) { min = newMin; max = newMax; } - void setFormat(const std::string& fmt) { format = fmt; } - -protected: - std::string label; - std::array& value; - float speed; - float min; - float max; - std::string format; - Callback onChanged; -}; - -} + + protected: + std::string label; + std::array& value; + float speed; + float min; + float max; + std::string format; + Callback onChanged; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/components/DragInt.hpp b/include/app/ui/components/DragInt.hpp index 53bb1f39..85dd3fa9 100644 --- a/include/app/ui/components/DragInt.hpp +++ b/include/app/ui/components/DragInt.hpp @@ -1,58 +1,70 @@ #pragma once #include -#include +#include #include +#include #include -#include namespace sauce::ui { -template -class DragInt : public ImGuiComponent { - static_assert(N >= 1 && N <= 4, "DragInt component count must be between 1 and 4"); + template class DragInt : public ImGuiComponent { + static_assert(N >= 1 && N <= 4, "DragInt component count must be between 1 and 4"); + + public: + using Callback = std::function&)>; -public: - using Callback = std::function&)>; + DragInt(const std::string& name, const std::string& label, std::array& value, + float speed = 1.0f, int min = 0, int max = 0, const std::string& format = "%d", + Callback onChanged = nullptr) + : ImGuiComponent(name), label(label), value(value), speed(speed), min(min), max(max), + format(format), onChanged(onChanged) { + } - DragInt(const std::string& name, const std::string& label, std::array& value, - float speed = 1.0f, int min = 0, int max = 0, - const std::string& format = "%d", Callback onChanged = nullptr) - : ImGuiComponent(name), label(label), value(value), speed(speed), min(min), max(max), - format(format), onChanged(onChanged) {} + void render() override { + if (!enabled) + return; - void render() override { - if (!enabled) return; + bool changed = false; - bool changed = false; + if constexpr (N == 1) { + changed = + ImGui::DragInt(label.c_str(), value.data(), speed, min, max, format.c_str()); + } else if constexpr (N == 2) { + changed = + ImGui::DragInt2(label.c_str(), value.data(), speed, min, max, format.c_str()); + } else if constexpr (N == 3) { + changed = + ImGui::DragInt3(label.c_str(), value.data(), speed, min, max, format.c_str()); + } else if constexpr (N == 4) { + changed = + ImGui::DragInt4(label.c_str(), value.data(), speed, min, max, format.c_str()); + } - if constexpr (N == 1) { - changed = ImGui::DragInt(label.c_str(), value.data(), speed, min, max, format.c_str()); - } else if constexpr (N == 2) { - changed = ImGui::DragInt2(label.c_str(), value.data(), speed, min, max, format.c_str()); - } else if constexpr (N == 3) { - changed = ImGui::DragInt3(label.c_str(), value.data(), speed, min, max, format.c_str()); - } else if constexpr (N == 4) { - changed = ImGui::DragInt4(label.c_str(), value.data(), speed, min, max, format.c_str()); + if (changed && onChanged) { + onChanged(value); + } } - if (changed && onChanged) { - onChanged(value); + void setSpeed(float spd) { + speed = spd; + } + void setRange(int newMin, int newMax) { + min = newMin; + max = newMax; + } + void setFormat(const std::string& fmt) { + format = fmt; } - } - - void setSpeed(float spd) { speed = spd; } - void setRange(int newMin, int newMax) { min = newMin; max = newMax; } - void setFormat(const std::string& fmt) { format = fmt; } - -protected: - std::string label; - std::array& value; - float speed; - int min; - int max; - std::string format; - Callback onChanged; -}; - -} + + protected: + std::string label; + std::array& value; + float speed; + int min; + int max; + std::string format; + Callback onChanged; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/components/Group.hpp b/include/app/ui/components/Group.hpp index e0055365..3f372699 100644 --- a/include/app/ui/components/Group.hpp +++ b/include/app/ui/components/Group.hpp @@ -1,28 +1,30 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -/** + /** * Group - Wraps ImGui::BeginGroup() / ImGui::EndGroup() * Groups widgets together (useful for alignment, drag and drop, etc.) */ -class Group : public ImGuiComponent { -public: - using ContentCallback = std::function; + class Group : public ImGuiComponent { + public: + using ContentCallback = std::function; - Group(const std::string& name, ContentCallback content = nullptr); + Group(const std::string& name, ContentCallback content = nullptr); - void render() override; + void render() override; - void setContent(ContentCallback newContent) { content = newContent; } + void setContent(ContentCallback newContent) { + content = newContent; + } -private: - ContentCallback content; -}; + private: + ContentCallback content; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/HelloWorldWindow.hpp b/include/app/ui/components/HelloWorldWindow.hpp index 8ca5b598..3819de8b 100644 --- a/include/app/ui/components/HelloWorldWindow.hpp +++ b/include/app/ui/components/HelloWorldWindow.hpp @@ -5,18 +5,19 @@ namespace sauce::ui { -class HelloWorldWindow : public ImGuiComponent { -public: - HelloWorldWindow() : ImGuiComponent("HelloWorldWindow") {} + class HelloWorldWindow : public ImGuiComponent { + public: + HelloWorldWindow() : ImGuiComponent("HelloWorldWindow") { + } - void render() override { - ImGui::Begin("Hello World"); - ImGui::Text("Hello World from SauceEngine UI System!"); - ImGui::Separator(); - ImGui::Text("This is an extensible ImGui component."); - ImGui::Text("Create your own by inheriting from ImGuiComponent!"); - ImGui::End(); - } -}; + void render() override { + ImGui::Begin("Hello World"); + ImGui::Text("Hello World from SauceEngine UI System!"); + ImGui::Separator(); + ImGui::Text("This is an extensible ImGui component."); + ImGui::Text("Create your own by inheriting from ImGuiComponent!"); + ImGui::End(); + } + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/Image.hpp b/include/app/ui/components/Image.hpp index d93da43c..b4d4d4fe 100644 --- a/include/app/ui/components/Image.hpp +++ b/include/app/ui/components/Image.hpp @@ -1,23 +1,26 @@ -#pragma once +#pragma once #include #include #include namespace sauce::ui { -class Image : public ImGuiComponent { -public: + class Image : public ImGuiComponent { + public: + Image(const std::string& name, ImTextureID texture, const ImVec2& size); - Image(const std::string& name, ImTextureID texture, const ImVec2& size); + void render() override; - void render() override; + void setTexture(ImTextureID newTexture) { + texture = newTexture; + } + void setSize(const ImVec2& newSize) { + size = newSize; + } - void setTexture(ImTextureID newTexture) { texture = newTexture; } - void setSize(const ImVec2& newSize) { size = newSize; } + protected: + ImTextureID texture; + ImVec2 size; + }; -protected: - ImTextureID texture; - ImVec2 size; -}; - -} +} // namespace sauce::ui diff --git a/include/app/ui/components/ImageButton.hpp b/include/app/ui/components/ImageButton.hpp index d838648e..b1b1853b 100644 --- a/include/app/ui/components/ImageButton.hpp +++ b/include/app/ui/components/ImageButton.hpp @@ -1,19 +1,22 @@ -#pragma once +#pragma once #include #include namespace sauce::ui { -class ImageButton : public Image { -public: - using Callback = std::function; - ImageButton(const std::string& name, ImTextureID texture, const ImVec2& size, Callback onClick = nullptr); + class ImageButton : public Image { + public: + using Callback = std::function; + ImageButton(const std::string& name, ImTextureID texture, const ImVec2& size, + Callback onClick = nullptr); - void render() override; - void setCallback(Callback cb) { onClick = cb; } + void render() override; + void setCallback(Callback cb) { + onClick = cb; + } -private: - Callback onClick; -}; + private: + Callback onClick; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/Input.hpp b/include/app/ui/components/Input.hpp index f445a676..632404b8 100644 --- a/include/app/ui/components/Input.hpp +++ b/include/app/ui/components/Input.hpp @@ -10,141 +10,162 @@ // Make these helpers private to the file namespace sauce::ui { -namespace { -template -using OnChange = std::function; - -template -concept InputOneType = (std::is_same_v || std::is_same_v || std::is_same_v); - -template -concept InputManyType = (std::is_same_v || std::is_same_v) && 1 < N && N <= 4; -} - - -template -class InputBase : public ImGuiComponent { -private: - OnChange func = [](auto arg) {}; - -protected: - const char *label = " "; - T data; - T *ref; - bool check(); - - InputBase(const char *name, const char *label, T data, T *ref, OnChange func) : ImGuiComponent(name), label(label), data(data), ref(ref), func(func) {} - -public: - T &get_data() { - return this->ref ? *this->ref : this->data; - } - - void render() override { - if (!this->enabled) return; - if (static_cast(this)->check()) - func(this->get_data()); - } - - template - static std::unique_ptr make(const char *name, typename T::Args args) { - return std::make_unique(Derived(name, args)); - } -}; - - -template - requires InputOneType -class InputOne : public InputBase, T> { -public: - struct Args { - const char *label = " "; - T init = (T)0.0; - T step = (T)1.0; - T step_fast = (T)1.0; - const char *format = "%.3f"; - ImGuiInputTextFlags flags = 0; - T *ref = nullptr; + namespace { + template using OnChange = std::function; + + template + concept InputOneType = + (std::is_same_v || std::is_same_v || std::is_same_v); + + template + concept InputManyType = + (std::is_same_v || std::is_same_v) && 1 < N && N <= 4; + } // namespace + + template class InputBase : public ImGuiComponent { + private: OnChange func = [](auto arg) {}; + + protected: + const char* label = " "; + T data; + T* ref; + bool check(); + + InputBase(const char* name, const char* label, T data, T* ref, OnChange func) + : ImGuiComponent(name), label(label), data(data), ref(ref), func(func) { + } + + public: + T& get_data() { + return this->ref ? *this->ref : this->data; + } + + void render() override { + if (!this->enabled) + return; + if (static_cast(this)->check()) + func(this->get_data()); + } + + template + static std::unique_ptr make(const char* name, typename T::Args args) { + return std::make_unique(Derived(name, args)); + } }; - InputOne(const char *name, Args args) - : InputBase, T>(name, args.label, args.init, args.ref, args.func), step(args.step), step_fast(args.step_fast), flags(args.flags) - {} - - bool check() { - if constexpr (std::is_same_v) - return ImGui::InputInt(this->label, &this->get_data(), this->step, this->step_fast, this->flags); - else if constexpr (std::is_same_v) - return ImGui::InputFloat(this->label, &this->get_data(), this->step, this->step_fast, this->format, this->flags); - else if constexpr (std::is_same_v) - return ImGui::InputDouble(this->label, &this->get_data(), this->step, this->step_fast, this->format, this->flags); - } - -private: - T step; - T step_fast; - ImGuiInputTextFlags flags; -}; - - -template - requires InputManyType -class InputMany : public InputBase, std::array> { -public: - struct Args { - const char *label = " "; - std::array init = {0}; - ImGuiInputTextFlags flags = 0; - std::array *ref = nullptr; - OnChange> func = [](auto arg) {}; + template + requires InputOneType + class InputOne : public InputBase, T> { + public: + struct Args { + const char* label = " "; + T init = (T)0.0; + T step = (T)1.0; + T step_fast = (T)1.0; + const char* format = "%.3f"; + ImGuiInputTextFlags flags = 0; + T* ref = nullptr; + OnChange func = [](auto arg) {}; + }; + + InputOne(const char* name, Args args) + : InputBase, T>(name, args.label, args.init, args.ref, args.func), + step(args.step), step_fast(args.step_fast), flags(args.flags) { + } + + bool check() { + if constexpr (std::is_same_v) + return ImGui::InputInt(this->label, &this->get_data(), this->step, this->step_fast, + this->flags); + else if constexpr (std::is_same_v) + return ImGui::InputFloat(this->label, &this->get_data(), this->step, + this->step_fast, this->format, this->flags); + else if constexpr (std::is_same_v) + return ImGui::InputDouble(this->label, &this->get_data(), this->step, + this->step_fast, this->format, this->flags); + } + + private: + T step; + T step_fast; + ImGuiInputTextFlags flags; + }; + + template + requires InputManyType + class InputMany : public InputBase, std::array> { + public: + struct Args { + const char* label = " "; + std::array init = {0}; + ImGuiInputTextFlags flags = 0; + std::array* ref = nullptr; + OnChange> func = [](auto arg) {}; + }; + + InputMany(const char* name, Args args = {}) + : InputBase, std::array>(name, args.label, args.init, args.ref, + args.func), + flags(args.flags) { + } + + bool check() { + if constexpr (std::is_same_v) { + if constexpr (N == 2) + return ImGui::InputInt2(this->label, this->get_data().data(), this->flags); + if constexpr (N == 3) + return ImGui::InputInt3(this->label, this->get_data().data(), this->flags); + if constexpr (N == 4) + return ImGui::InputInt4(this->label, this->get_data().data(), this->flags); + } else if constexpr (std::is_same_v) { + if constexpr (N == 2) + return ImGui::InputFloat2(this->label, this->get_data().data(), "%.3f", + this->flags); + if constexpr (N == 3) + return ImGui::InputFloat3(this->label, this->get_data().data(), "%.3f", + this->flags); + if constexpr (N == 4) + return ImGui::InputFloat4(this->label, this->get_data().data(), "%.3f", + this->flags); + } + } + + private: + ImGuiInputTextFlags flags; + std::array* ref; }; - InputMany(const char *name, Args args = {}) - : InputBase, std::array>(name, args.label, args.init, args.ref, args.func), flags(args.flags) {} - - bool check() { - if constexpr (std::is_same_v) { - if constexpr (N == 2) return ImGui::InputInt2(this->label, this->get_data().data(), this->flags); - if constexpr (N == 3) return ImGui::InputInt3(this->label, this->get_data().data(), this->flags); - if constexpr (N == 4) return ImGui::InputInt4(this->label, this->get_data().data(), this->flags); - } else if constexpr (std::is_same_v) { - if constexpr (N == 2) return ImGui::InputFloat2(this->label, this->get_data().data(), "%.3f", this->flags); - if constexpr (N == 3) return ImGui::InputFloat3(this->label, this->get_data().data(), "%.3f", this->flags); - if constexpr (N == 4) return ImGui::InputFloat4(this->label, this->get_data().data(), "%.3f", this->flags); + class InputTxt : public InputBase { + public: + struct Args { + const char* label = " "; + std::string init = ""; + std::optional shape = std::nullopt; + ImGuiInputTextFlags flags = 0; + ImGuiInputTextCallback callback = nullptr; + void* user_data = nullptr; + OnChange func = [](auto arg) {}; + }; + InputTxt(const char* name, Args args = {}) + : InputBase(name, args.label, args.init, nullptr, args.func), + shape(args.shape), flags(args.flags), callback(args.callback), + user_data(args.user_data) { } - } - -private: - ImGuiInputTextFlags flags; - std::array *ref; -}; - -class InputTxt : public InputBase { -public: - struct Args { - const char *label = " "; - std::string init = ""; - std::optional shape = std::nullopt; - ImGuiInputTextFlags flags = 0; - ImGuiInputTextCallback callback = nullptr; - void *user_data = nullptr; - OnChange func = [](auto arg) {}; + + bool check() { + if (this->shape.has_value()) + return ImGui::InputTextMultiline(this->label, &this->data, *this->shape, + this->flags, this->callback, this->user_data); + else + return ImGui::InputText(this->label, &this->data, this->flags, this->callback, + this->user_data); + } + + private: + std::optional shape; + ImGuiInputTextFlags flags; + ImGuiInputTextCallback callback; + void* user_data; }; - InputTxt(const char *name, Args args = {}) - : InputBase(name, args.label, args.init, nullptr, args.func), shape(args.shape), flags(args.flags), callback(args.callback), user_data(args.user_data) {} - - bool check() { - if (this->shape.has_value()) - return ImGui::InputTextMultiline(this->label, &this->data, *this->shape, this->flags, this->callback, this->user_data); - else - return ImGui::InputText(this->label, &this->data, this->flags, this->callback, this->user_data); - } - -private: - std::optional shape; - ImGuiInputTextFlags flags; - ImGuiInputTextCallback callback; - void *user_data; -}; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/LabelText.hpp b/include/app/ui/components/LabelText.hpp index 6f1b3181..f8198f2b 100644 --- a/include/app/ui/components/LabelText.hpp +++ b/include/app/ui/components/LabelText.hpp @@ -1,23 +1,22 @@ -#pragma once +#pragma once #include #include #include namespace sauce::ui { -class LabelText : public Text { -public: + class LabelText : public Text { + public: + LabelText(const std::string& name, const std::string& label, const std::string& text); - LabelText(const std::string& name, const std::string& label, const std::string& text); + void render() override; - void render() override; + void setLabel(const std::string& newLabel) { + label = newLabel; + } - void setLabel(const std::string& newLabel){ - label = newLabel; - } + private: + std::string label; + }; -private: - std::string label; -}; - -} +} // namespace sauce::ui diff --git a/include/app/ui/components/PlotHistogram.hpp b/include/app/ui/components/PlotHistogram.hpp index d8296317..fd8da4cf 100644 --- a/include/app/ui/components/PlotHistogram.hpp +++ b/include/app/ui/components/PlotHistogram.hpp @@ -1,20 +1,18 @@ #pragma once +#include #include #include -#include -namespace sauce::ui -{ - class PlotHistogram : public ImGuiComponent - { - public: - explicit PlotHistogram(const std::string& name, std::vector values); - ~PlotHistogram() override; +namespace sauce::ui { + class PlotHistogram : public ImGuiComponent { + public: + explicit PlotHistogram(const std::string& name, std::vector values); + ~PlotHistogram() override; - void render() override; + void render() override; - private: - std::vector values; - }; -} + private: + std::vector values; + }; +} // namespace sauce::ui diff --git a/include/app/ui/components/PlotLines.hpp b/include/app/ui/components/PlotLines.hpp index 4b5d74f6..e260c8a1 100644 --- a/include/app/ui/components/PlotLines.hpp +++ b/include/app/ui/components/PlotLines.hpp @@ -1,20 +1,18 @@ #pragma once +#include #include #include -#include -namespace sauce::ui -{ - class PlotLines : public ImGuiComponent - { - public: - explicit PlotLines(const std::string& name, std::vector values); - ~PlotLines() override; +namespace sauce::ui { + class PlotLines : public ImGuiComponent { + public: + explicit PlotLines(const std::string& name, std::vector values); + ~PlotLines() override; - void render() override; + void render() override; - private: - std::vector values; - }; -} + private: + std::vector values; + }; +} // namespace sauce::ui diff --git a/include/app/ui/components/ProgressBar.hpp b/include/app/ui/components/ProgressBar.hpp index c1a1e539..6dee0af6 100644 --- a/include/app/ui/components/ProgressBar.hpp +++ b/include/app/ui/components/ProgressBar.hpp @@ -1,19 +1,17 @@ #pragma once -#include #include +#include -namespace sauce::ui -{ - class ProgressBar : public ImGuiComponent - { - public: - explicit ProgressBar(const std::string& name, float fraction); - ~ProgressBar() override; +namespace sauce::ui { + class ProgressBar : public ImGuiComponent { + public: + explicit ProgressBar(const std::string& name, float fraction); + ~ProgressBar() override; - void render() override; + void render() override; - private: - float fraction; - }; -} + private: + float fraction; + }; +} // namespace sauce::ui diff --git a/include/app/ui/components/RadioButton.hpp b/include/app/ui/components/RadioButton.hpp index 121c8bda..ff3126e7 100644 --- a/include/app/ui/components/RadioButton.hpp +++ b/include/app/ui/components/RadioButton.hpp @@ -1,25 +1,25 @@ -#pragma once +#pragma once #include -#include #include +#include #include namespace sauce::ui { -class RadioButton : public ImGuiComponent { -public: - using Callback = std::function; - - RadioButton(const std::string& name, const std::string& label, int* selected, int value, Callback onChanged = nullptr); + class RadioButton : public ImGuiComponent { + public: + using Callback = std::function; - void render() override; + RadioButton(const std::string& name, const std::string& label, int* selected, int value, + Callback onChanged = nullptr); -private: - std::string label; - int* selected; - int value; - Callback onChanged; -}; + void render() override; -} + private: + std::string label; + int* selected; + int value; + Callback onChanged; + }; +} // namespace sauce::ui diff --git a/include/app/ui/components/SameLine.hpp b/include/app/ui/components/SameLine.hpp index 23c0d5f4..8d5f6af0 100644 --- a/include/app/ui/components/SameLine.hpp +++ b/include/app/ui/components/SameLine.hpp @@ -6,25 +6,34 @@ namespace sauce::ui { -/** + /** * SameLine - Wraps ImGui::SameLine() * Places the next element on the same line (horizontal layout) */ -class SameLine : public ImGuiComponent { -public: - explicit SameLine(const std::string& name, float offsetFromStartX = 0.0f, float spacing = -1.0f); + class SameLine : public ImGuiComponent { + public: + explicit SameLine(const std::string& name, float offsetFromStartX = 0.0f, + float spacing = -1.0f); - void render() override; + void render() override; - void setOffsetFromStartX(float offset) { offsetFromStartX = offset; } - float getOffsetFromStartX() const { return offsetFromStartX; } - - void setSpacing(float newSpacing) { spacing = newSpacing; } - float getSpacing() const { return spacing; } + void setOffsetFromStartX(float offset) { + offsetFromStartX = offset; + } + float getOffsetFromStartX() const { + return offsetFromStartX; + } -private: - float offsetFromStartX; - float spacing; -}; + void setSpacing(float newSpacing) { + spacing = newSpacing; + } + float getSpacing() const { + return spacing; + } -} + private: + float offsetFromStartX; + float spacing; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/components/Separator.hpp b/include/app/ui/components/Separator.hpp index b53d2191..e5117a1c 100644 --- a/include/app/ui/components/Separator.hpp +++ b/include/app/ui/components/Separator.hpp @@ -6,15 +6,15 @@ namespace sauce::ui { -/** + /** * Separator - Wraps ImGui::Separator() * Creates a horizontal line separator */ -class Separator : public ImGuiComponent { -public: - explicit Separator(const std::string& name); + class Separator : public ImGuiComponent { + public: + explicit Separator(const std::string& name); - void render() override; -}; + void render() override; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/SettingsWindow.hpp b/include/app/ui/components/SettingsWindow.hpp index a1a037aa..6de249ee 100644 --- a/include/app/ui/components/SettingsWindow.hpp +++ b/include/app/ui/components/SettingsWindow.hpp @@ -1,100 +1,101 @@ #pragma once -#include +#include #include +#include #include #include -#include namespace sauce::ui { -class SettingsWindow : public ImGuiComponent { -public: - explicit SettingsWindow(sauce::SettingsManager& settingsManager) - : ImGuiComponent("SettingsWindow"), settings(settingsManager) - { - auto& s = settings.get(); - const std::size_t copyLen = std::min(s.workingDirectory.size(), sizeof(workingDirBuf) - 1); - std::copy_n(s.workingDirectory.data(), copyLen, workingDirBuf); - workingDirBuf[copyLen] = '\0'; - } - - void render() override { - if (!enabled) return; - - ImGui::SetNextWindowSize(ImVec2(420, 0), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Settings", &enabled)) { - ImGui::End(); - return; - } - - auto& s = settings.get(); - bool changed = false; - - if (ImGui::CollapsingHeader("Display", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderFloat("UI Scale", &s.imguiScale, 0.25f, 4.0f, "%.2fx"); - changed |= ImGui::IsItemDeactivatedAfterEdit(); - - if (ImGui::Checkbox("V-Sync", &s.vsync)) { - changed = true; - } - ImGui::SameLine(); - ImGui::TextDisabled("(requires restart)"); - } - - if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderFloat("Mouse Sensitivity", &s.mouseSensitivity, 0.01f, 1.0f, "%.2f"); - changed |= ImGui::IsItemDeactivatedAfterEdit(); - ImGui::SliderFloat("Movement Speed", &s.cameraSpeed, 0.5f, 20.0f, "%.1f"); - changed |= ImGui::IsItemDeactivatedAfterEdit(); - ImGui::SliderFloat("Field of View", &s.fieldOfView, 30.0f, 120.0f, "%.0f deg"); - changed |= ImGui::IsItemDeactivatedAfterEdit(); - } - - if (ImGui::CollapsingHeader("General", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::InputText("Working Directory", workingDirBuf, sizeof(workingDirBuf), - ImGuiInputTextFlags_EnterReturnsTrue)) { - s.workingDirectory = workingDirBuf; - changed = true; - } - - changed |= ImGui::Checkbox("Show Debug Stats", &s.showDebugStats); - } - - if (ImGui::CollapsingHeader("Diagnostics", ImGuiTreeNodeFlags_DefaultOpen)) { - changed |= ImGui::Checkbox("Palantir Mode (verbose logging)", &s.palantirMode); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Enables detailed diagnostic logging to sauceengine.log"); - } - } - - if (changed) { - settings.markDirtyAndSave(); - } - - ImGui::Separator(); - ImGui::Spacing(); - if (ImGui::SmallButton("store.palantir.com")) { - #ifdef _WIN32 - system("start https://store.palantir.com"); - #elif __APPLE__ - system("open https://store.palantir.com"); - #else - system("xdg-open https://store.palantir.com"); - #endif - } - if (ImGui::IsItemHovered()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - } - - ImGui::End(); - } - -private: - sauce::SettingsManager& settings; - char workingDirBuf[512] = {}; -}; + class SettingsWindow : public ImGuiComponent { + public: + explicit SettingsWindow(sauce::SettingsManager& settingsManager) + : ImGuiComponent("SettingsWindow"), settings(settingsManager) { + auto& s = settings.get(); + const std::size_t copyLen = + std::min(s.workingDirectory.size(), sizeof(workingDirBuf) - 1); + std::copy_n(s.workingDirectory.data(), copyLen, workingDirBuf); + workingDirBuf[copyLen] = '\0'; + } + + void render() override { + if (!enabled) + return; + + ImGui::SetNextWindowSize(ImVec2(420, 0), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Settings", &enabled)) { + ImGui::End(); + return; + } + + auto& s = settings.get(); + bool changed = false; + + if (ImGui::CollapsingHeader("Display", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::SliderFloat("UI Scale", &s.imguiScale, 0.25f, 4.0f, "%.2fx"); + changed |= ImGui::IsItemDeactivatedAfterEdit(); + + if (ImGui::Checkbox("V-Sync", &s.vsync)) { + changed = true; + } + ImGui::SameLine(); + ImGui::TextDisabled("(requires restart)"); + } + + if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::SliderFloat("Mouse Sensitivity", &s.mouseSensitivity, 0.01f, 1.0f, "%.2f"); + changed |= ImGui::IsItemDeactivatedAfterEdit(); + ImGui::SliderFloat("Movement Speed", &s.cameraSpeed, 0.5f, 20.0f, "%.1f"); + changed |= ImGui::IsItemDeactivatedAfterEdit(); + ImGui::SliderFloat("Field of View", &s.fieldOfView, 30.0f, 120.0f, "%.0f deg"); + changed |= ImGui::IsItemDeactivatedAfterEdit(); + } + + if (ImGui::CollapsingHeader("General", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::InputText("Working Directory", workingDirBuf, sizeof(workingDirBuf), + ImGuiInputTextFlags_EnterReturnsTrue)) { + s.workingDirectory = workingDirBuf; + changed = true; + } + + changed |= ImGui::Checkbox("Show Debug Stats", &s.showDebugStats); + } + + if (ImGui::CollapsingHeader("Diagnostics", ImGuiTreeNodeFlags_DefaultOpen)) { + changed |= ImGui::Checkbox("Palantir Mode (verbose logging)", &s.palantirMode); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Enables detailed diagnostic logging to sauceengine.log"); + } + } + + if (changed) { + settings.markDirtyAndSave(); + } + + ImGui::Separator(); + ImGui::Spacing(); + if (ImGui::SmallButton("store.palantir.com")) { +#ifdef _WIN32 + system("start https://store.palantir.com"); +#elif __APPLE__ + system("open https://store.palantir.com"); +#else + system("xdg-open https://store.palantir.com"); +#endif + } + if (ImGui::IsItemHovered()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + + ImGui::End(); + } + + private: + sauce::SettingsManager& settings; + char workingDirBuf[512] = {}; + }; } // namespace sauce::ui diff --git a/include/app/ui/components/SliderAngle.hpp b/include/app/ui/components/SliderAngle.hpp index 617c6b1c..d0675a34 100644 --- a/include/app/ui/components/SliderAngle.hpp +++ b/include/app/ui/components/SliderAngle.hpp @@ -1,35 +1,37 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -class SliderAngle : public ImGuiComponent { -public: - using Callback = std::function; - - SliderAngle(const std::string& name, const std::string& label, float* valueRadians, - float minDegrees = -360.0f, float maxDegrees = 360.0f, - const std::string& format = "%.0f deg", Callback onChanged = nullptr); - - void render() override; - - void setRange(float minDegrees, float maxDegrees) { - this->minDegrees = minDegrees; - this->maxDegrees = maxDegrees; - } - void setFormat(const std::string& fmt) { format = fmt; } - -private: - std::string label; - float* valueRadians; - float minDegrees; - float maxDegrees; - std::string format; - Callback onChanged; -}; - -} + class SliderAngle : public ImGuiComponent { + public: + using Callback = std::function; + + SliderAngle(const std::string& name, const std::string& label, float* valueRadians, + float minDegrees = -360.0f, float maxDegrees = 360.0f, + const std::string& format = "%.0f deg", Callback onChanged = nullptr); + + void render() override; + + void setRange(float minDegrees, float maxDegrees) { + this->minDegrees = minDegrees; + this->maxDegrees = maxDegrees; + } + void setFormat(const std::string& fmt) { + format = fmt; + } + + private: + std::string label; + float* valueRadians; + float minDegrees; + float maxDegrees; + std::string format; + Callback onChanged; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/components/SliderFloat.hpp b/include/app/ui/components/SliderFloat.hpp index 82b54dd1..d03b3d8e 100644 --- a/include/app/ui/components/SliderFloat.hpp +++ b/include/app/ui/components/SliderFloat.hpp @@ -1,56 +1,65 @@ #pragma once #include -#include +#include #include +#include #include -#include namespace sauce::ui { -template -class SliderFloat : public ImGuiComponent { - static_assert(N >= 1 && N <= 4, "SliderFloat component count must be between 1 and 4"); + template class SliderFloat : public ImGuiComponent { + static_assert(N >= 1 && N <= 4, "SliderFloat component count must be between 1 and 4"); -public: - using Callback = std::function&)>; + public: + using Callback = std::function&)>; - SliderFloat(const std::string& name, const std::string& label, std::array& value, - float min = 0.0f, float max = 1.0f, - const std::string& format = "%.3f", Callback onChanged = nullptr) - : ImGuiComponent(name), label(label), value(value), min(min), max(max), - format(format), onChanged(onChanged) {} + SliderFloat(const std::string& name, const std::string& label, std::array& value, + float min = 0.0f, float max = 1.0f, const std::string& format = "%.3f", + Callback onChanged = nullptr) + : ImGuiComponent(name), label(label), value(value), min(min), max(max), format(format), + onChanged(onChanged) { + } - void render() override { - if (!enabled) return; + void render() override { + if (!enabled) + return; - bool changed = false; + bool changed = false; - if constexpr (N == 1) { - changed = ImGui::SliderFloat(label.c_str(), value.data(), min, max, format.c_str()); - } else if constexpr (N == 2) { - changed = ImGui::SliderFloat2(label.c_str(), value.data(), min, max, format.c_str()); - } else if constexpr (N == 3) { - changed = ImGui::SliderFloat3(label.c_str(), value.data(), min, max, format.c_str()); - } else if constexpr (N == 4) { - changed = ImGui::SliderFloat4(label.c_str(), value.data(), min, max, format.c_str()); - } + if constexpr (N == 1) { + changed = ImGui::SliderFloat(label.c_str(), value.data(), min, max, format.c_str()); + } else if constexpr (N == 2) { + changed = + ImGui::SliderFloat2(label.c_str(), value.data(), min, max, format.c_str()); + } else if constexpr (N == 3) { + changed = + ImGui::SliderFloat3(label.c_str(), value.data(), min, max, format.c_str()); + } else if constexpr (N == 4) { + changed = + ImGui::SliderFloat4(label.c_str(), value.data(), min, max, format.c_str()); + } - if (changed && onChanged) { - onChanged(value); + if (changed && onChanged) { + onChanged(value); + } } - } - void setRange(float newMin, float newMax) { min = newMin; max = newMax; } - void setFormat(const std::string& fmt) { format = fmt; } + void setRange(float newMin, float newMax) { + min = newMin; + max = newMax; + } + void setFormat(const std::string& fmt) { + format = fmt; + } -protected: - std::string label; - std::array& value; - float min; - float max; - std::string format; - Callback onChanged; -}; + protected: + std::string label; + std::array& value; + float min; + float max; + std::string format; + Callback onChanged; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/SliderInt.hpp b/include/app/ui/components/SliderInt.hpp index bba0dd53..df2aca9f 100644 --- a/include/app/ui/components/SliderInt.hpp +++ b/include/app/ui/components/SliderInt.hpp @@ -1,56 +1,62 @@ #pragma once #include -#include +#include #include +#include #include -#include namespace sauce::ui { -template -class SliderInt : public ImGuiComponent { - static_assert(N >= 1 && N <= 4, "SliderInt component count must be between 1 and 4"); - -public: - using Callback = std::function&)>; + template class SliderInt : public ImGuiComponent { + static_assert(N >= 1 && N <= 4, "SliderInt component count must be between 1 and 4"); - SliderInt(const std::string& name, const std::string& label, std::array& value, - int min = 0, int max = 100, - const std::string& format = "%d", Callback onChanged = nullptr) - : ImGuiComponent(name), label(label), value(value), min(min), max(max), - format(format), onChanged(onChanged) {} + public: + using Callback = std::function&)>; - void render() override { - if (!enabled) return; - - bool changed = false; - - if constexpr (N == 1) { - changed = ImGui::SliderInt(label.c_str(), value.data(), min, max, format.c_str()); - } else if constexpr (N == 2) { - changed = ImGui::SliderInt2(label.c_str(), value.data(), min, max, format.c_str()); - } else if constexpr (N == 3) { - changed = ImGui::SliderInt3(label.c_str(), value.data(), min, max, format.c_str()); - } else if constexpr (N == 4) { - changed = ImGui::SliderInt4(label.c_str(), value.data(), min, max, format.c_str()); + SliderInt(const std::string& name, const std::string& label, std::array& value, + int min = 0, int max = 100, const std::string& format = "%d", + Callback onChanged = nullptr) + : ImGuiComponent(name), label(label), value(value), min(min), max(max), format(format), + onChanged(onChanged) { } - if (changed && onChanged) { - onChanged(value); + void render() override { + if (!enabled) + return; + + bool changed = false; + + if constexpr (N == 1) { + changed = ImGui::SliderInt(label.c_str(), value.data(), min, max, format.c_str()); + } else if constexpr (N == 2) { + changed = ImGui::SliderInt2(label.c_str(), value.data(), min, max, format.c_str()); + } else if constexpr (N == 3) { + changed = ImGui::SliderInt3(label.c_str(), value.data(), min, max, format.c_str()); + } else if constexpr (N == 4) { + changed = ImGui::SliderInt4(label.c_str(), value.data(), min, max, format.c_str()); + } + + if (changed && onChanged) { + onChanged(value); + } } - } - void setRange(int newMin, int newMax) { min = newMin; max = newMax; } - void setFormat(const std::string& fmt) { format = fmt; } + void setRange(int newMin, int newMax) { + min = newMin; + max = newMax; + } + void setFormat(const std::string& fmt) { + format = fmt; + } -protected: - std::string label; - std::array& value; - int min; - int max; - std::string format; - Callback onChanged; -}; + protected: + std::string label; + std::array& value; + int min; + int max; + std::string format; + Callback onChanged; + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/Spacing.hpp b/include/app/ui/components/Spacing.hpp index 5553c9c0..f6a8a643 100644 --- a/include/app/ui/components/Spacing.hpp +++ b/include/app/ui/components/Spacing.hpp @@ -6,21 +6,25 @@ namespace sauce::ui { -/** + /** * Spacing - Wraps ImGui::Spacing() * Adds vertical spacing between elements */ -class Spacing : public ImGuiComponent { -public: - explicit Spacing(const std::string& name, int count = 1); + class Spacing : public ImGuiComponent { + public: + explicit Spacing(const std::string& name, int count = 1); - void render() override; + void render() override; - void setCount(int newCount) { count = newCount; } - int getCount() const { return count; } + void setCount(int newCount) { + count = newCount; + } + int getCount() const { + return count; + } -private: - int count; // Number of spacing calls to make -}; + private: + int count; // Number of spacing calls to make + }; -} +} // namespace sauce::ui diff --git a/include/app/ui/components/Text.hpp b/include/app/ui/components/Text.hpp index 0488675a..c7d1e85c 100644 --- a/include/app/ui/components/Text.hpp +++ b/include/app/ui/components/Text.hpp @@ -1,23 +1,22 @@ -#pragma once +#pragma once #include #include #include namespace sauce::ui { -class Text : public ImGuiComponent { -public: + class Text : public ImGuiComponent { + public: + Text(const std::string& name, const std::string& text); - Text(const std::string& name, const std::string& text); + void render() override; - void render() override; + void setText(const std::string& newText) { + text = newText; + } - void setText(const std::string& newText){ - text = newText; - } + protected: + std::string text; + }; -protected: - std::string text; -}; - -} +} // namespace sauce::ui diff --git a/include/app/ui/components/TextColored.hpp b/include/app/ui/components/TextColored.hpp index 7f53747c..3a0abf0b 100644 --- a/include/app/ui/components/TextColored.hpp +++ b/include/app/ui/components/TextColored.hpp @@ -1,21 +1,20 @@ -#pragma once +#pragma once #include namespace sauce::ui { -class TextColored : public Text { -public: + class TextColored : public Text { + public: + TextColored(const std::string& name, const std::string& text, const ImVec4& color); - TextColored(const std::string& name, const std::string& text, const ImVec4& color); + void changeColor(const ImVec4& newColor) { + color = newColor; + } - void changeColor(const ImVec4& newColor){ - color = newColor; - } + void render() override; - void render() override; + private: + ImVec4 color; + }; -private: - ImVec4 color; -}; - -} +} // namespace sauce::ui diff --git a/include/app/ui/components/TextWrapped.hpp b/include/app/ui/components/TextWrapped.hpp index c7d8d10b..b7a56731 100644 --- a/include/app/ui/components/TextWrapped.hpp +++ b/include/app/ui/components/TextWrapped.hpp @@ -1,14 +1,13 @@ -#pragma once +#pragma once #include namespace sauce::ui { -class TextWrapped : public Text { -public: + class TextWrapped : public Text { + public: + TextWrapped(const std::string& name, const std::string& text); - TextWrapped(const std::string& name, const std::string& text); + void render() override; + }; - void render() override; -}; - -} +} // namespace sauce::ui diff --git a/include/app/ui/components/Tooltip.hpp b/include/app/ui/components/Tooltip.hpp index 37ee6f69..28f14185 100644 --- a/include/app/ui/components/Tooltip.hpp +++ b/include/app/ui/components/Tooltip.hpp @@ -1,19 +1,17 @@ #pragma once -#include #include +#include -namespace sauce::ui -{ - class Tooltip : public ImGuiComponent - { - public: - explicit Tooltip(const std::string& name, std::string text); - ~Tooltip() override; +namespace sauce::ui { + class Tooltip : public ImGuiComponent { + public: + explicit Tooltip(const std::string& name, std::string text); + ~Tooltip() override; - void render() override; + void render() override; - private: - std::string text; - }; -} + private: + std::string text; + }; +} // namespace sauce::ui diff --git a/include/app/ui/components/TreeNode.hpp b/include/app/ui/components/TreeNode.hpp index 12d55438..86ace226 100644 --- a/include/app/ui/components/TreeNode.hpp +++ b/include/app/ui/components/TreeNode.hpp @@ -1,38 +1,48 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -/** + /** * TreeNode - Wraps ImGui::TreeNode() / ImGui::TreePop() * Creates a collapsible tree hierarchy element */ -class TreeNode : public ImGuiComponent { -public: - using ContentCallback = std::function; - - TreeNode(const std::string& name, const std::string& label, - ContentCallback content = nullptr, ImGuiTreeNodeFlags flags = 0); - - void render() override; - - void setLabel(const std::string& newLabel) { label = newLabel; } - const std::string& getLabel() const { return label; } - - void setContent(ContentCallback newContent) { content = newContent; } - void setFlags(ImGuiTreeNodeFlags newFlags) { flags = newFlags; } - - bool isNodeOpen() const { return nodeOpen; } - -private: - std::string label; - ContentCallback content; - ImGuiTreeNodeFlags flags; - bool nodeOpen = false; -}; - -} + class TreeNode : public ImGuiComponent { + public: + using ContentCallback = std::function; + + TreeNode(const std::string& name, const std::string& label, + ContentCallback content = nullptr, ImGuiTreeNodeFlags flags = 0); + + void render() override; + + void setLabel(const std::string& newLabel) { + label = newLabel; + } + const std::string& getLabel() const { + return label; + } + + void setContent(ContentCallback newContent) { + content = newContent; + } + void setFlags(ImGuiTreeNodeFlags newFlags) { + flags = newFlags; + } + + bool isNodeOpen() const { + return nodeOpen; + } + + private: + std::string label; + ContentCallback content; + ImGuiTreeNodeFlags flags; + bool nodeOpen = false; + }; + +} // namespace sauce::ui diff --git a/include/app/ui/components/Window.hpp b/include/app/ui/components/Window.hpp index 2606881a..6af4724f 100644 --- a/include/app/ui/components/Window.hpp +++ b/include/app/ui/components/Window.hpp @@ -1,40 +1,54 @@ #pragma once #include -#include #include +#include #include namespace sauce::ui { -/** + /** * Window - Wraps ImGui::Begin() / ImGui::End() * Creates a window container that can hold other UI elements */ -class Window : public ImGuiComponent { -public: - using ContentCallback = std::function; - - Window(const std::string& name, const std::string& title, ContentCallback content = nullptr, - ImGuiWindowFlags flags = 0); - - void render() override; - - void setTitle(const std::string& newTitle) { title = newTitle; } - const std::string& getTitle() const { return title; } - - void setContent(ContentCallback newContent) { content = newContent; } - void setFlags(ImGuiWindowFlags newFlags) { flags = newFlags; } - - bool* getOpenPtr() { return &isOpen; } - bool isWindowOpen() const { return isOpen; } - void setOpen(bool open) { isOpen = open; } - -private: - std::string title; - ContentCallback content; - ImGuiWindowFlags flags; - bool isOpen = true; -}; - -} + class Window : public ImGuiComponent { + public: + using ContentCallback = std::function; + + Window(const std::string& name, const std::string& title, ContentCallback content = nullptr, + ImGuiWindowFlags flags = 0); + + void render() override; + + void setTitle(const std::string& newTitle) { + title = newTitle; + } + const std::string& getTitle() const { + return title; + } + + void setContent(ContentCallback newContent) { + content = newContent; + } + void setFlags(ImGuiWindowFlags newFlags) { + flags = newFlags; + } + + bool* getOpenPtr() { + return &isOpen; + } + bool isWindowOpen() const { + return isOpen; + } + void setOpen(bool open) { + isOpen = open; + } + + private: + std::string title; + ContentCallback content; + ImGuiWindowFlags flags; + bool isOpen = true; + }; + +} // namespace sauce::ui diff --git a/include/editor/AABB.hpp b/include/editor/AABB.hpp index d2b6202f..45caf4de 100644 --- a/include/editor/AABB.hpp +++ b/include/editor/AABB.hpp @@ -1,74 +1,71 @@ #pragma once -#include -#include -#include #include -#include +#include #include +#include +#include +#include namespace sauce::editor { -struct AABB { - glm::vec3 min; - glm::vec3 max; + struct AABB { + glm::vec3 min; + glm::vec3 max; - static AABB fromVertices(const std::vector& vertices) { - glm::vec3 lo(std::numeric_limits::max()); - glm::vec3 hi(std::numeric_limits::lowest()); - for (const auto& v : vertices) { - lo = glm::min(lo, v.position); - hi = glm::max(hi, v.position); - } - return { lo, hi }; - } + static AABB fromVertices(const std::vector& vertices) { + glm::vec3 lo(std::numeric_limits::max()); + glm::vec3 hi(std::numeric_limits::lowest()); + for (const auto& v : vertices) { + lo = glm::min(lo, v.position); + hi = glm::max(hi, v.position); + } + return {lo, hi}; + } - AABB transformed(const glm::mat4& m) const { - // Transform all 8 corners and recompute bounds - glm::vec3 corners[8] = { - { min.x, min.y, min.z }, - { max.x, min.y, min.z }, - { min.x, max.y, min.z }, - { max.x, max.y, min.z }, - { min.x, min.y, max.z }, - { max.x, min.y, max.z }, - { min.x, max.y, max.z }, - { max.x, max.y, max.z }, + AABB transformed(const glm::mat4& m) const { + // Transform all 8 corners and recompute bounds + glm::vec3 corners[8] = { + {min.x, min.y, min.z}, {max.x, min.y, min.z}, {min.x, max.y, min.z}, + {max.x, max.y, min.z}, {min.x, min.y, max.z}, {max.x, min.y, max.z}, + {min.x, max.y, max.z}, {max.x, max.y, max.z}, + }; + glm::vec3 newMin(std::numeric_limits::max()); + glm::vec3 newMax(std::numeric_limits::lowest()); + for (const auto& c : corners) { + glm::vec3 t = glm::vec3(m * glm::vec4(c, 1.0f)); + newMin = glm::min(newMin, t); + newMax = glm::max(newMax, t); + } + return {newMin, newMax}; + } }; - glm::vec3 newMin(std::numeric_limits::max()); - glm::vec3 newMax(std::numeric_limits::lowest()); - for (const auto& c : corners) { - glm::vec3 t = glm::vec3(m * glm::vec4(c, 1.0f)); - newMin = glm::min(newMin, t); - newMax = glm::max(newMax, t); - } - return { newMin, newMax }; - } -}; -// Slab method ray-AABB intersection. Returns true if hit; tOut = distance along ray. -inline bool rayIntersectsAABB(const glm::vec3& rayOrigin, const glm::vec3& rayDir, - const AABB& box, float& tOut) { - float tmin = 0.0f; - float tmax = std::numeric_limits::max(); + // Slab method ray-AABB intersection. Returns true if hit; tOut = distance along ray. + inline bool rayIntersectsAABB(const glm::vec3& rayOrigin, const glm::vec3& rayDir, + const AABB& box, float& tOut) { + float tmin = 0.0f; + float tmax = std::numeric_limits::max(); - for (int i = 0; i < 3; ++i) { - if (std::abs(rayDir[i]) < 1e-8f) { - // Ray is parallel to slab - if (rayOrigin[i] < box.min[i] || rayOrigin[i] > box.max[i]) - return false; - } else { - float invD = 1.0f / rayDir[i]; - float t1 = (box.min[i] - rayOrigin[i]) * invD; - float t2 = (box.max[i] - rayOrigin[i]) * invD; - if (t1 > t2) std::swap(t1, t2); - tmin = std::max(tmin, t1); - tmax = std::min(tmax, t2); - if (tmin > tmax) return false; + for (int i = 0; i < 3; ++i) { + if (std::abs(rayDir[i]) < 1e-8f) { + // Ray is parallel to slab + if (rayOrigin[i] < box.min[i] || rayOrigin[i] > box.max[i]) + return false; + } else { + float invD = 1.0f / rayDir[i]; + float t1 = (box.min[i] - rayOrigin[i]) * invD; + float t2 = (box.max[i] - rayOrigin[i]) * invD; + if (t1 > t2) + std::swap(t1, t2); + tmin = std::max(tmin, t1); + tmax = std::min(tmax, t2); + if (tmin > tmax) + return false; + } + } + tOut = tmin; + return true; } - } - tOut = tmin; - return true; -} } // namespace sauce::editor diff --git a/include/editor/EditorApp.hpp b/include/editor/EditorApp.hpp index 9d73450c..8d338cc7 100644 --- a/include/editor/EditorApp.hpp +++ b/include/editor/EditorApp.hpp @@ -11,213 +11,245 @@ #include #include -#include #include #include +#include #include #include - #include #include +#include #include -#include #include +#include #include #include #include -#include -#include #include #include +#include #include -#include #include +#include -#include #include #include +#include #include namespace sauce { -class MeshRendererComponent; + class MeshRendererComponent; } // namespace sauce namespace sauce::ui { -class SettingsWindow; + class SettingsWindow; } // namespace sauce::ui namespace sauce::editor { -struct MeshPushConstants { - glm::mat4 model; // 64 bytes - glm::vec4 baseColor; // 16 bytes - float metallic; // 4 bytes - float roughness; // 4 bytes - float pad[2]; // 8 bytes alignment -}; -// Total: 96 bytes (within 128-byte minimum guarantee) - -enum class ViewportMode { Unlit, Lit }; - -class EditorPanel; -class SceneHierarchyPanel; -class InspectorPanel; -class ViewportPanel; -class AssetBrowserPanel; -class GizmoRenderer; - -class EditorApp { -public: - EditorApp(); - ~EditorApp(); - - void run(); - - sauce::Scene& getScene() { return *pScene; } - const sauce::Scene& getScene() const { return *pScene; } - SelectionManager& getSelectionManager() { return selectionManager; } - EditorCamera& getEditorCamera() { return editorCamera; } - GLFWwindow* getWindow() { return window; } - float getDeltaTime() const { return deltaTime; } - - void createEmptyEntity(); - void createBoxEntity(); - void createBallEntity(); - - const sauce::PhysicalDevice& getPhysicalDevice() const { return physicalDevice; } - const sauce::LogicalDevice& getLogicalDevice() const { return logicalDevice; } - sauce::Renderer& getRenderer() { return *pRenderer; } - - OffscreenFramebuffer* getOffscreenFramebuffer() { return pOffscreenFB.get(); } - - ViewportMode getViewportMode() const { return viewportMode; } - void setViewportMode(ViewportMode mode) { viewportMode = mode; } - - void setStatusMessage(const std::string& msg) { statusMessage = msg; statusTimer = 5.0f; } - - void importGLTFToScene(const std::string& path); - void replaceModelOnComponent(MeshRendererComponent& mrc, const std::string& path); - void clearModelOnComponent(MeshRendererComponent& mrc); - - void openScene(const std::string& path); - void saveScene(); - void saveSceneAs(const std::string& path); - - void setInitialSceneFile(const std::string& path) { initialSceneFile = path; } - - // Play mode - void startPlayMode(); - void stopPlayMode(); - bool isInPlayMode() const { return playModeActive; } - -private: - void initWindow(); - void initVulkan(); - void initEditor(); - void setupEditorTheme(); - void mainLoop(); - void buildEditorUI(); - void setupDefaultDockLayout(ImGuiID dockspaceId); - void processInput(); - - bool saveSceneToZip(const std::string& zipPath, - const std::string& scenePath, - const std::vector& assetPaths); - std::string loadFileToString(const std::string& path); - bool zipFolder(const std::filesystem::path& src, const std::filesystem::path& dst); - bool unzipToFolder(const std::filesystem::path& zipPath, const std::filesystem::path& outDir); - void loadSceneFromZip(const std::string& zipPath); - - bool showExportZipDialog = false; - bool showImportZipDialog = false; - - - void recordEditorCommandBuffer(vk::raii::CommandBuffer& cmd, uint32_t imageIndex); - void uploadMeshGPUResources(); - void pickEntityAtScreen(float windowX, float windowY); - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height); - static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods); - static void cursorPosCallback(GLFWwindow* window, double xpos, double ypos); - static void scrollCallback(GLFWwindow* window, double xoffset, double yoffset); - static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); - static void dropCallback(GLFWwindow* window, int count, const char** paths); - - GLFWwindow* window = nullptr; - - std::chrono::steady_clock::time_point lastFrameTime = std::chrono::steady_clock::now(); - float deltaTime = 0.0f; - - float lastMouseX = 0.0f; - float lastMouseY = 0.0f; - float mousePressX = 0.0f; - float mousePressY = 0.0f; - bool rightMouseDown = false; - bool middleMouseDown = false; - bool leftMouseDown = false; - - std::unique_ptr pInstance; - std::unique_ptr pRenderSurface; - sauce::PhysicalDevice physicalDevice = nullptr; - sauce::LogicalDevice logicalDevice = nullptr; - - std::unique_ptr pScene; - std::unique_ptr pRenderer; - std::unique_ptr pImGuiRenderer; - - // Editor rendering resources - std::unique_ptr pOffscreenFB; - std::unique_ptr pGridPipeline; - std::unique_ptr pUnlitPipeline; - std::unique_ptr pLitPipeline; - std::unique_ptr pGizmoRenderer; - - SelectionManager selectionManager; - EditorCamera editorCamera; - - std::unique_ptr hierarchyPanel; - std::unique_ptr inspectorPanel; - std::unique_ptr viewportPanel; - std::unique_ptr assetBrowserPanel; - - bool showHierarchy = true; - bool showInspector = true; - bool showViewport = true; - bool showAssetBrowser = true; - bool showSettings = false; - - bool firstFrame = true; - bool viewportHovered = false; - bool viewportFocused = false; - - ViewportMode viewportMode = ViewportMode::Unlit; - GizmoType activeGizmoMode = GizmoType::Translate; - bool gizmoInteracting = false; - - std::string statusMessage; - float statusTimer = 0.0f; - - // File dialog state - bool showOpenDialog = false; - bool showSaveAsDialog = false; - char dialogPathBuf[512] = {}; - - std::string initialSceneFile; - - // Play mode state - bool playModeActive = false; - pid_t playProcessPid = -1; - std::string playModeTempFile; - - sauce::SettingsManager settingsManager; - std::string lastWorkingDirectory; - std::unique_ptr settingsWindow; - - void applySettings(const sauce::EditorSettings& s); -}; + struct MeshPushConstants { + glm::mat4 model; // 64 bytes + glm::vec4 baseColor; // 16 bytes + float metallic; // 4 bytes + float roughness; // 4 bytes + float pad[2]; // 8 bytes alignment + }; + // Total: 96 bytes (within 128-byte minimum guarantee) + + enum class ViewportMode { + Unlit, + Lit + }; + + class EditorPanel; + class SceneHierarchyPanel; + class InspectorPanel; + class ViewportPanel; + class AssetBrowserPanel; + class GizmoRenderer; + + class EditorApp { + public: + EditorApp(); + ~EditorApp(); + + void run(); + + sauce::Scene& getScene() { + return *pScene; + } + const sauce::Scene& getScene() const { + return *pScene; + } + SelectionManager& getSelectionManager() { + return selectionManager; + } + EditorCamera& getEditorCamera() { + return editorCamera; + } + GLFWwindow* getWindow() { + return window; + } + float getDeltaTime() const { + return deltaTime; + } + + void createEmptyEntity(); + void createBoxEntity(); + void createBallEntity(); + + const sauce::PhysicalDevice& getPhysicalDevice() const { + return physicalDevice; + } + const sauce::LogicalDevice& getLogicalDevice() const { + return logicalDevice; + } + sauce::Renderer& getRenderer() { + return *pRenderer; + } + + OffscreenFramebuffer* getOffscreenFramebuffer() { + return pOffscreenFB.get(); + } + + ViewportMode getViewportMode() const { + return viewportMode; + } + void setViewportMode(ViewportMode mode) { + viewportMode = mode; + } + + void setStatusMessage(const std::string& msg) { + statusMessage = msg; + statusTimer = 5.0f; + } + + void importGLTFToScene(const std::string& path); + void replaceModelOnComponent(MeshRendererComponent& mrc, const std::string& path); + void clearModelOnComponent(MeshRendererComponent& mrc); + + void openScene(const std::string& path); + void saveScene(); + void saveSceneAs(const std::string& path); + + void setInitialSceneFile(const std::string& path) { + initialSceneFile = path; + } + + // Play mode + void startPlayMode(); + void stopPlayMode(); + bool isInPlayMode() const { + return playModeActive; + } + + private: + void initWindow(); + void initVulkan(); + void initEditor(); + void setupEditorTheme(); + void mainLoop(); + void buildEditorUI(); + void setupDefaultDockLayout(ImGuiID dockspaceId); + void processInput(); + + bool saveSceneToZip(const std::string& zipPath, const std::string& scenePath, + const std::vector& assetPaths); + std::string loadFileToString(const std::string& path); + bool zipFolder(const std::filesystem::path& src, const std::filesystem::path& dst); + bool unzipToFolder(const std::filesystem::path& zipPath, + const std::filesystem::path& outDir); + void loadSceneFromZip(const std::string& zipPath); + + bool showExportZipDialog = false; + bool showImportZipDialog = false; + + void recordEditorCommandBuffer(vk::raii::CommandBuffer& cmd, uint32_t imageIndex); + void uploadMeshGPUResources(); + void pickEntityAtScreen(float windowX, float windowY); + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height); + static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods); + static void cursorPosCallback(GLFWwindow* window, double xpos, double ypos); + static void scrollCallback(GLFWwindow* window, double xoffset, double yoffset); + static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); + static void dropCallback(GLFWwindow* window, int count, const char** paths); + + GLFWwindow* window = nullptr; + + std::chrono::steady_clock::time_point lastFrameTime = std::chrono::steady_clock::now(); + float deltaTime = 0.0f; + + float lastMouseX = 0.0f; + float lastMouseY = 0.0f; + float mousePressX = 0.0f; + float mousePressY = 0.0f; + bool rightMouseDown = false; + bool middleMouseDown = false; + bool leftMouseDown = false; + + std::unique_ptr pInstance; + std::unique_ptr pRenderSurface; + sauce::PhysicalDevice physicalDevice = nullptr; + sauce::LogicalDevice logicalDevice = nullptr; + + std::unique_ptr pScene; + std::unique_ptr pRenderer; + std::unique_ptr pImGuiRenderer; + + // Editor rendering resources + std::unique_ptr pOffscreenFB; + std::unique_ptr pGridPipeline; + std::unique_ptr pUnlitPipeline; + std::unique_ptr pLitPipeline; + std::unique_ptr pGizmoRenderer; + + SelectionManager selectionManager; + EditorCamera editorCamera; + + std::unique_ptr hierarchyPanel; + std::unique_ptr inspectorPanel; + std::unique_ptr viewportPanel; + std::unique_ptr assetBrowserPanel; + + bool showHierarchy = true; + bool showInspector = true; + bool showViewport = true; + bool showAssetBrowser = true; + bool showSettings = false; + + bool firstFrame = true; + bool viewportHovered = false; + bool viewportFocused = false; + + ViewportMode viewportMode = ViewportMode::Unlit; + GizmoType activeGizmoMode = GizmoType::Translate; + bool gizmoInteracting = false; + + std::string statusMessage; + float statusTimer = 0.0f; + + // File dialog state + bool showOpenDialog = false; + bool showSaveAsDialog = false; + char dialogPathBuf[512] = {}; + + std::string initialSceneFile; + + // Play mode state + bool playModeActive = false; + pid_t playProcessPid = -1; + std::string playModeTempFile; + + sauce::SettingsManager settingsManager; + std::string lastWorkingDirectory; + std::unique_ptr settingsWindow; + + void applySettings(const sauce::EditorSettings& s); + }; } // namespace sauce::editor diff --git a/include/editor/EditorCamera.hpp b/include/editor/EditorCamera.hpp index 166a5206..8274e354 100644 --- a/include/editor/EditorCamera.hpp +++ b/include/editor/EditorCamera.hpp @@ -4,84 +4,102 @@ #include namespace sauce { -class Camera; -class Scene; -} + class Camera; + class Scene; +} // namespace sauce namespace sauce::editor { -struct Ray { - glm::vec3 origin; - glm::vec3 direction; -}; - -class EditorCamera { -public: - enum class Mode { Orbit, Fly }; - - EditorCamera(); - - void update(float deltaTime); - void syncToSceneCamera(sauce::Camera& camera); - - // Orbit controls - void orbit(float deltaYaw, float deltaPitch); - void pan(float deltaX, float deltaY); - void zoom(float delta); - void focusOn(const glm::vec3& target); - - // Fly mode controls - void beginFlyMode(); - void endFlyMode(); - void flyMoveForward(float deltaTime); - void flyMoveBackward(float deltaTime); - void flyMoveLeft(float deltaTime); - void flyMoveRight(float deltaTime); - void flyMouseLook(float deltaX, float deltaY); - - Mode getMode() const { return mode; } - glm::vec3 getPosition() const; - glm::mat4 getViewMatrix() const; - glm::mat4 getProjectionMatrix(float aspectRatio) const; - - Ray screenToWorldRay(float localX, float localY, float vpWidth, float vpHeight) const; - - glm::vec3 getOrbitTarget() const { return orbitTarget; } - float getFOV() const { return fov; } - - void setScreenSize(float width, float height) { scrWidth = width; scrHeight = height; } - - void setFlySpeed(float s) { flySpeed = s; } - void setMouseSensitivity(float s) { mouseSensitivity = s; } - void setFOV(float f) { fov = f; } - -private: - void updateOrbitPosition(); - - Mode mode = Mode::Orbit; - - // Orbit parameters - glm::vec3 orbitTarget = { 0.0f, 0.0f, 0.0f }; - float orbitDistance = 5.0f; - float orbitYaw = -90.0f; - float orbitPitch = 30.0f; - glm::vec3 orbitPosition; - - // Fly mode parameters - glm::vec3 flyPosition = { 0.0f, 0.0f, 0.0f }; - float flyYaw = -90.0f; - float flyPitch = 0.0f; - glm::vec3 flyFront; - glm::vec3 flyRight; - glm::vec3 flyUp; - float flySpeed = 5.0f; - float mouseSensitivity = 0.1f; - - float fov = 60.0f; - float scrWidth = 1920.0f; - float scrHeight = 1080.0f; - - static constexpr glm::vec3 WORLD_UP = { 0.0f, 0.0f, 1.0f }; -}; + struct Ray { + glm::vec3 origin; + glm::vec3 direction; + }; + + class EditorCamera { + public: + enum class Mode { + Orbit, + Fly + }; + + EditorCamera(); + + void update(float deltaTime); + void syncToSceneCamera(sauce::Camera& camera); + + // Orbit controls + void orbit(float deltaYaw, float deltaPitch); + void pan(float deltaX, float deltaY); + void zoom(float delta); + void focusOn(const glm::vec3& target); + + // Fly mode controls + void beginFlyMode(); + void endFlyMode(); + void flyMoveForward(float deltaTime); + void flyMoveBackward(float deltaTime); + void flyMoveLeft(float deltaTime); + void flyMoveRight(float deltaTime); + void flyMouseLook(float deltaX, float deltaY); + + Mode getMode() const { + return mode; + } + glm::vec3 getPosition() const; + glm::mat4 getViewMatrix() const; + glm::mat4 getProjectionMatrix(float aspectRatio) const; + + Ray screenToWorldRay(float localX, float localY, float vpWidth, float vpHeight) const; + + glm::vec3 getOrbitTarget() const { + return orbitTarget; + } + float getFOV() const { + return fov; + } + + void setScreenSize(float width, float height) { + scrWidth = width; + scrHeight = height; + } + + void setFlySpeed(float s) { + flySpeed = s; + } + void setMouseSensitivity(float s) { + mouseSensitivity = s; + } + void setFOV(float f) { + fov = f; + } + + private: + void updateOrbitPosition(); + + Mode mode = Mode::Orbit; + + // Orbit parameters + glm::vec3 orbitTarget = {0.0f, 0.0f, 0.0f}; + float orbitDistance = 5.0f; + float orbitYaw = -90.0f; + float orbitPitch = 30.0f; + glm::vec3 orbitPosition; + + // Fly mode parameters + glm::vec3 flyPosition = {0.0f, 0.0f, 0.0f}; + float flyYaw = -90.0f; + float flyPitch = 0.0f; + glm::vec3 flyFront; + glm::vec3 flyRight; + glm::vec3 flyUp; + float flySpeed = 5.0f; + float mouseSensitivity = 0.1f; + + float fov = 60.0f; + float scrWidth = 1920.0f; + float scrHeight = 1080.0f; + + static constexpr glm::vec3 WORLD_UP = {0.0f, 0.0f, 1.0f}; + }; } // namespace sauce::editor diff --git a/include/editor/OffscreenFramebuffer.hpp b/include/editor/OffscreenFramebuffer.hpp index d31c3331..33211c64 100644 --- a/include/editor/OffscreenFramebuffer.hpp +++ b/include/editor/OffscreenFramebuffer.hpp @@ -4,126 +4,132 @@ #include #include -#include #include +#include #include namespace sauce::editor { -class OffscreenFramebuffer { -public: - static constexpr vk::Format COLOR_FORMAT = vk::Format::eR8G8B8A8Unorm; - - OffscreenFramebuffer( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice, - uint32_t initialWidth, - uint32_t initialHeight - ) : pPhysicalDevice(&physicalDevice), pLogicalDevice(&logicalDevice) { - // Create sampler for the offscreen color image - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eClampToEdge, - .addressModeV = vk::SamplerAddressMode::eClampToEdge, - .addressModeW = vk::SamplerAddressMode::eClampToEdge, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::False, - .compareEnable = vk::False, - .minLod = 0.0f, - .maxLod = 1.0f, - .borderColor = vk::BorderColor::eFloatOpaqueBlack, + class OffscreenFramebuffer { + public: + static constexpr vk::Format COLOR_FORMAT = vk::Format::eR8G8B8A8Unorm; + + OffscreenFramebuffer(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice, uint32_t initialWidth, + uint32_t initialHeight) + : pPhysicalDevice(&physicalDevice), pLogicalDevice(&logicalDevice) { + // Create sampler for the offscreen color image + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eClampToEdge, + .addressModeV = vk::SamplerAddressMode::eClampToEdge, + .addressModeW = vk::SamplerAddressMode::eClampToEdge, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::False, + .compareEnable = vk::False, + .minLod = 0.0f, + .maxLod = 1.0f, + .borderColor = vk::BorderColor::eFloatOpaqueBlack, + }; + sampler = vk::raii::Sampler{*logicalDevice, samplerInfo}; + + resize(initialWidth, initialHeight); + } + + void resize(uint32_t newWidth, uint32_t newHeight) { + if (newWidth == 0 || newHeight == 0) + return; + if (newWidth == width && newHeight == height && colorImage != nullptr) + return; + + width = newWidth; + height = newHeight; + + // Remove old ImGui descriptor if it exists + if (imguiDescriptorSet != VK_NULL_HANDLE) { + ImGui_ImplVulkan_RemoveTexture(imguiDescriptorSet); + imguiDescriptorSet = VK_NULL_HANDLE; + } + + // Recreate color image + ImageUtils::createImage( + *pPhysicalDevice, *pLogicalDevice, width, height, COLOR_FORMAT, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); + colorImageView = ImageUtils::createImageView(*pLogicalDevice, colorImage, COLOR_FORMAT, + vk::ImageAspectFlagBits::eColor); + + // Recreate depth image + vk::Format depthFormat = GraphicsPipeline::findDepthFormat(*pPhysicalDevice); + ImageUtils::createImage( + *pPhysicalDevice, *pLogicalDevice, width, height, depthFormat, + vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = ImageUtils::createImageView(*pLogicalDevice, depthImage, depthFormat, + vk::ImageAspectFlagBits::eDepth); + + // Create ImGui descriptor set for displaying the offscreen texture + imguiDescriptorSet = ImGui_ImplVulkan_AddTexture( + *sampler, *colorImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + + VkDescriptorSet getImGuiDescriptorSet() const { + return imguiDescriptorSet; + } + + const vk::raii::Image& getColorImage() const { + return colorImage; + } + const vk::raii::ImageView& getColorImageView() const { + return colorImageView; + } + const vk::raii::Image& getDepthImage() const { + return depthImage; + } + const vk::raii::ImageView& getDepthImageView() const { + return depthImageView; + } + + uint32_t getWidth() const { + return width; + } + uint32_t getHeight() const { + return height; + } + + ~OffscreenFramebuffer() { + if (imguiDescriptorSet != VK_NULL_HANDLE) { + ImGui_ImplVulkan_RemoveTexture(imguiDescriptorSet); + imguiDescriptorSet = VK_NULL_HANDLE; + } + } + + // Non-copyable + OffscreenFramebuffer(const OffscreenFramebuffer&) = delete; + OffscreenFramebuffer& operator=(const OffscreenFramebuffer&) = delete; + + private: + const sauce::PhysicalDevice* pPhysicalDevice; + const sauce::LogicalDevice* pLogicalDevice; + + uint32_t width = 0; + uint32_t height = 0; + + vk::raii::Image colorImage = nullptr; + vk::raii::DeviceMemory colorImageMemory = nullptr; + vk::raii::ImageView colorImageView = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Sampler sampler = nullptr; + + VkDescriptorSet imguiDescriptorSet = VK_NULL_HANDLE; }; - sampler = vk::raii::Sampler { *logicalDevice, samplerInfo }; - - resize(initialWidth, initialHeight); - } - - void resize(uint32_t newWidth, uint32_t newHeight) { - if (newWidth == 0 || newHeight == 0) return; - if (newWidth == width && newHeight == height && colorImage != nullptr) return; - - width = newWidth; - height = newHeight; - - // Remove old ImGui descriptor if it exists - if (imguiDescriptorSet != VK_NULL_HANDLE) { - ImGui_ImplVulkan_RemoveTexture(imguiDescriptorSet); - imguiDescriptorSet = VK_NULL_HANDLE; - } - - // Recreate color image - ImageUtils::createImage( - *pPhysicalDevice, *pLogicalDevice, - width, height, - COLOR_FORMAT, - vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, - colorImage, colorImageMemory - ); - colorImageView = ImageUtils::createImageView(*pLogicalDevice, colorImage, COLOR_FORMAT, vk::ImageAspectFlagBits::eColor); - - // Recreate depth image - vk::Format depthFormat = GraphicsPipeline::findDepthFormat(*pPhysicalDevice); - ImageUtils::createImage( - *pPhysicalDevice, *pLogicalDevice, - width, height, - depthFormat, - vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eDepthStencilAttachment, - vk::MemoryPropertyFlagBits::eDeviceLocal, - depthImage, depthImageMemory - ); - depthImageView = ImageUtils::createImageView(*pLogicalDevice, depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - - // Create ImGui descriptor set for displaying the offscreen texture - imguiDescriptorSet = ImGui_ImplVulkan_AddTexture( - *sampler, *colorImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL - ); - } - - VkDescriptorSet getImGuiDescriptorSet() const { return imguiDescriptorSet; } - - const vk::raii::Image& getColorImage() const { return colorImage; } - const vk::raii::ImageView& getColorImageView() const { return colorImageView; } - const vk::raii::Image& getDepthImage() const { return depthImage; } - const vk::raii::ImageView& getDepthImageView() const { return depthImageView; } - - uint32_t getWidth() const { return width; } - uint32_t getHeight() const { return height; } - - ~OffscreenFramebuffer() { - if (imguiDescriptorSet != VK_NULL_HANDLE) { - ImGui_ImplVulkan_RemoveTexture(imguiDescriptorSet); - imguiDescriptorSet = VK_NULL_HANDLE; - } - } - - // Non-copyable - OffscreenFramebuffer(const OffscreenFramebuffer&) = delete; - OffscreenFramebuffer& operator=(const OffscreenFramebuffer&) = delete; - -private: - const sauce::PhysicalDevice* pPhysicalDevice; - const sauce::LogicalDevice* pLogicalDevice; - - uint32_t width = 0; - uint32_t height = 0; - - vk::raii::Image colorImage = nullptr; - vk::raii::DeviceMemory colorImageMemory = nullptr; - vk::raii::ImageView colorImageView = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Sampler sampler = nullptr; - - VkDescriptorSet imguiDescriptorSet = VK_NULL_HANDLE; -}; } // namespace sauce::editor diff --git a/include/editor/SelectionManager.hpp b/include/editor/SelectionManager.hpp index 6ce44136..1071e8d9 100644 --- a/include/editor/SelectionManager.hpp +++ b/include/editor/SelectionManager.hpp @@ -3,22 +3,30 @@ #include namespace sauce { -class Scene; + class Scene; } namespace sauce::editor { -class SelectionManager { -public: - void select(int index) { selectedIndex = index; } - void deselect() { selectedIndex = -1; } - int getSelectedIndex() const { return selectedIndex; } - bool hasSelection() const { return selectedIndex >= 0; } + class SelectionManager { + public: + void select(int index) { + selectedIndex = index; + } + void deselect() { + selectedIndex = -1; + } + int getSelectedIndex() const { + return selectedIndex; + } + bool hasSelection() const { + return selectedIndex >= 0; + } - sauce::Entity* getSelectedEntity(sauce::Scene& scene); + sauce::Entity* getSelectedEntity(sauce::Scene& scene); -private: - int selectedIndex = -1; -}; + private: + int selectedIndex = -1; + }; } // namespace sauce::editor diff --git a/include/editor/gizmos/Gizmo.hpp b/include/editor/gizmos/Gizmo.hpp index a32c78d8..2ccbd133 100644 --- a/include/editor/gizmos/Gizmo.hpp +++ b/include/editor/gizmos/Gizmo.hpp @@ -1,94 +1,119 @@ #pragma once -#include #include -#include -#include #include +#include +#include #include +#include namespace sauce::editor { -enum class GizmoType { Translate, Rotate, Scale }; -enum class GizmoAxis { None, X, Y, Z }; - -struct GizmoMeshData { - std::vector vertices; - std::vector indices; -}; - -class Gizmo { -public: - virtual ~Gizmo() = default; - virtual GizmoType getType() const = 0; - - virtual GizmoMeshData generateMesh() const = 0; - - virtual GizmoAxis hitTest(const Ray& ray, - const glm::vec3& position, const glm::quat& rotation, float scale) const = 0; - - virtual void beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) = 0; - virtual glm::vec3 updateInteraction(const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) = 0; - virtual void endInteraction() = 0; - - bool isInteracting() const { return interacting; } - GizmoAxis getActiveAxis() const { return activeAxis; } - -protected: - bool interacting = false; - GizmoAxis activeAxis = GizmoAxis::None; - - static glm::vec3 axisDirection(GizmoAxis axis) { - switch (axis) { - case GizmoAxis::X: return { 1, 0, 0 }; - case GizmoAxis::Y: return { 0, 1, 0 }; - case GizmoAxis::Z: return { 0, 0, 1 }; - default: return { 0, 0, 0 }; - } - } - - static glm::vec3 axisColor(GizmoAxis axis) { - switch (axis) { - case GizmoAxis::X: return { 1, 0, 0 }; - case GizmoAxis::Y: return { 0, 1, 0 }; - case GizmoAxis::Z: return { 0, 0, 1 }; - default: return { 1, 1, 1 }; - } - } - - // Project a ray onto an axis line through origin, return parameter t along the axis - static float projectRayOntoAxis(const Ray& ray, const glm::vec3& axisOrigin, - const glm::vec3& axisDir) { - // Closest point between two lines: - // Line 1: axisOrigin + t * axisDir - // Line 2: ray.origin + s * ray.direction - glm::vec3 w = axisOrigin - ray.origin; - float a = glm::dot(axisDir, axisDir); - float b = glm::dot(axisDir, ray.direction); - float c = glm::dot(ray.direction, ray.direction); - float d = glm::dot(axisDir, w); - float e = glm::dot(ray.direction, w); - float denom = a * c - b * b; - if (std::abs(denom) < 1e-8f) return 0.0f; - return (b * e - c * d) / denom; - } - - // Distance from ray to axis line (for hit testing) - static float distanceRayToLine(const Ray& ray, const glm::vec3& lineOrigin, - const glm::vec3& lineDir, float maxT = 1.0f) { - // Clamp parameter t on axis line to [0, maxT] (shaft length) - float t = projectRayOntoAxis(ray, lineOrigin, lineDir); - t = glm::clamp(t, 0.0f, maxT); - - glm::vec3 pointOnAxis = lineOrigin + t * lineDir; - - // Find closest point on ray to pointOnAxis - float s = glm::dot(pointOnAxis - ray.origin, ray.direction); - if (s < 0.0f) s = 0.0f; - glm::vec3 pointOnRay = ray.origin + s * ray.direction; - - return glm::length(pointOnAxis - pointOnRay); - } -}; + enum class GizmoType { + Translate, + Rotate, + Scale + }; + enum class GizmoAxis { + None, + X, + Y, + Z + }; + + struct GizmoMeshData { + std::vector vertices; + std::vector indices; + }; + + class Gizmo { + public: + virtual ~Gizmo() = default; + virtual GizmoType getType() const = 0; + + virtual GizmoMeshData generateMesh() const = 0; + + virtual GizmoAxis hitTest(const Ray& ray, const glm::vec3& position, + const glm::quat& rotation, float scale) const = 0; + + virtual void beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) = 0; + virtual glm::vec3 updateInteraction(const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) = 0; + virtual void endInteraction() = 0; + + bool isInteracting() const { + return interacting; + } + GizmoAxis getActiveAxis() const { + return activeAxis; + } + + protected: + bool interacting = false; + GizmoAxis activeAxis = GizmoAxis::None; + + static glm::vec3 axisDirection(GizmoAxis axis) { + switch (axis) { + case GizmoAxis::X: + return {1, 0, 0}; + case GizmoAxis::Y: + return {0, 1, 0}; + case GizmoAxis::Z: + return {0, 0, 1}; + default: + return {0, 0, 0}; + } + } + + static glm::vec3 axisColor(GizmoAxis axis) { + switch (axis) { + case GizmoAxis::X: + return {1, 0, 0}; + case GizmoAxis::Y: + return {0, 1, 0}; + case GizmoAxis::Z: + return {0, 0, 1}; + default: + return {1, 1, 1}; + } + } + + // Project a ray onto an axis line through origin, return parameter t along the axis + static float projectRayOntoAxis(const Ray& ray, const glm::vec3& axisOrigin, + const glm::vec3& axisDir) { + // Closest point between two lines: + // Line 1: axisOrigin + t * axisDir + // Line 2: ray.origin + s * ray.direction + glm::vec3 w = axisOrigin - ray.origin; + float a = glm::dot(axisDir, axisDir); + float b = glm::dot(axisDir, ray.direction); + float c = glm::dot(ray.direction, ray.direction); + float d = glm::dot(axisDir, w); + float e = glm::dot(ray.direction, w); + float denom = a * c - b * b; + if (std::abs(denom) < 1e-8f) + return 0.0f; + return (b * e - c * d) / denom; + } + + // Distance from ray to axis line (for hit testing) + static float distanceRayToLine(const Ray& ray, const glm::vec3& lineOrigin, + const glm::vec3& lineDir, float maxT = 1.0f) { + // Clamp parameter t on axis line to [0, maxT] (shaft length) + float t = projectRayOntoAxis(ray, lineOrigin, lineDir); + t = glm::clamp(t, 0.0f, maxT); + + glm::vec3 pointOnAxis = lineOrigin + t * lineDir; + + // Find closest point on ray to pointOnAxis + float s = glm::dot(pointOnAxis - ray.origin, ray.direction); + if (s < 0.0f) + s = 0.0f; + glm::vec3 pointOnRay = ray.origin + s * ray.direction; + + return glm::length(pointOnAxis - pointOnRay); + } + }; } // namespace sauce::editor diff --git a/include/editor/gizmos/GizmoRenderer.hpp b/include/editor/gizmos/GizmoRenderer.hpp index dee4a50e..6275a315 100644 --- a/include/editor/gizmos/GizmoRenderer.hpp +++ b/include/editor/gizmos/GizmoRenderer.hpp @@ -1,55 +1,55 @@ #pragma once +#include #include -#include #include #include -#include +#include #include -#include -#include #include +#include #include +#include #include namespace sauce::editor { -class GizmoRenderer { -public: - GizmoRenderer(const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice, - const vk::raii::DescriptorSetLayout& descriptorSetLayout, - vk::Format colorFormat, - sauce::Renderer& renderer); + class GizmoRenderer { + public: + GizmoRenderer(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice, + const vk::raii::DescriptorSetLayout& descriptorSetLayout, + vk::Format colorFormat, sauce::Renderer& renderer); - void setActiveGizmo(GizmoType type); - GizmoType getActiveGizmoType() const { return activeType; } - Gizmo* getActiveGizmo() { return activeGizmo.get(); } + void setActiveGizmo(GizmoType type); + GizmoType getActiveGizmoType() const { + return activeType; + } + Gizmo* getActiveGizmo() { + return activeGizmo.get(); + } - void render(vk::raii::CommandBuffer& cmd, - const vk::raii::DescriptorSet& descriptorSet, - const glm::vec3& entityPosition, - const EditorCamera& camera, - float aspect); + void render(vk::raii::CommandBuffer& cmd, const vk::raii::DescriptorSet& descriptorSet, + const glm::vec3& entityPosition, const EditorCamera& camera, float aspect); - static constexpr float SCALE_FACTOR = 0.15f; + static constexpr float SCALE_FACTOR = 0.15f; -private: - void uploadMesh(); + private: + void uploadMesh(); - const sauce::PhysicalDevice* pPhysicalDevice; - const sauce::LogicalDevice* pLogicalDevice; - sauce::Renderer* pRenderer; + const sauce::PhysicalDevice* pPhysicalDevice; + const sauce::LogicalDevice* pLogicalDevice; + sauce::Renderer* pRenderer; - std::unique_ptr pipeline; - std::unique_ptr mesh; + std::unique_ptr pipeline; + std::unique_ptr mesh; - GizmoType activeType = GizmoType::Translate; - std::unique_ptr activeGizmo; + GizmoType activeType = GizmoType::Translate; + std::unique_ptr activeGizmo; - bool meshDirty = true; -}; + bool meshDirty = true; + }; } // namespace sauce::editor diff --git a/include/editor/gizmos/RotateGizmo.hpp b/include/editor/gizmos/RotateGizmo.hpp index f2c5beae..976fa13e 100644 --- a/include/editor/gizmos/RotateGizmo.hpp +++ b/include/editor/gizmos/RotateGizmo.hpp @@ -4,25 +4,30 @@ namespace sauce::editor { -class RotateGizmo : public Gizmo { -public: - GizmoType getType() const override { return GizmoType::Rotate; } - GizmoMeshData generateMesh() const override; - GizmoAxis hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& rotation, float scale) const override; - void beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) override; - glm::vec3 updateInteraction(const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) override; - void endInteraction() override; + class RotateGizmo : public Gizmo { + public: + GizmoType getType() const override { + return GizmoType::Rotate; + } + GizmoMeshData generateMesh() const override; + GizmoAxis hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& rotation, + float scale) const override; + void beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) override; + glm::vec3 updateInteraction(const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) override; + void endInteraction() override; -private: - float lastAngle = 0.0f; - glm::vec3 lastEntityPos{0.0f}; + private: + float lastAngle = 0.0f; + glm::vec3 lastEntityPos{0.0f}; - static constexpr float RING_RADIUS = 1.0f; - static constexpr float TUBE_RADIUS = 0.02f; - static constexpr int RING_SEGMENTS = 48; - static constexpr int TUBE_SEGMENTS = 8; + static constexpr float RING_RADIUS = 1.0f; + static constexpr float TUBE_RADIUS = 0.02f; + static constexpr int RING_SEGMENTS = 48; + static constexpr int TUBE_SEGMENTS = 8; - float angleOnPlane(const Ray& ray, const glm::vec3& center, const glm::vec3& normal) const; -}; + float angleOnPlane(const Ray& ray, const glm::vec3& center, const glm::vec3& normal) const; + }; } // namespace sauce::editor diff --git a/include/editor/gizmos/ScaleGizmo.hpp b/include/editor/gizmos/ScaleGizmo.hpp index 297d1f9d..38769638 100644 --- a/include/editor/gizmos/ScaleGizmo.hpp +++ b/include/editor/gizmos/ScaleGizmo.hpp @@ -4,23 +4,28 @@ namespace sauce::editor { -class ScaleGizmo : public Gizmo { -public: - GizmoType getType() const override { return GizmoType::Scale; } - GizmoMeshData generateMesh() const override; - GizmoAxis hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& rotation, float scale) const override; - void beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) override; - glm::vec3 updateInteraction(const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) override; - void endInteraction() override; + class ScaleGizmo : public Gizmo { + public: + GizmoType getType() const override { + return GizmoType::Scale; + } + GizmoMeshData generateMesh() const override; + GizmoAxis hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& rotation, + float scale) const override; + void beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) override; + glm::vec3 updateInteraction(const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) override; + void endInteraction() override; -private: - float initialT = 0.0f; - glm::vec3 lastEntityPos{0.0f}; + private: + float initialT = 0.0f; + glm::vec3 lastEntityPos{0.0f}; - static constexpr float SHAFT_LENGTH = 1.0f; - static constexpr float SHAFT_RADIUS = 0.02f; - static constexpr float CUBE_SIZE = 0.06f; - static constexpr int SEGMENTS = 12; -}; + static constexpr float SHAFT_LENGTH = 1.0f; + static constexpr float SHAFT_RADIUS = 0.02f; + static constexpr float CUBE_SIZE = 0.06f; + static constexpr int SEGMENTS = 12; + }; } // namespace sauce::editor diff --git a/include/editor/gizmos/TranslateGizmo.hpp b/include/editor/gizmos/TranslateGizmo.hpp index 2f73db45..6163cddd 100644 --- a/include/editor/gizmos/TranslateGizmo.hpp +++ b/include/editor/gizmos/TranslateGizmo.hpp @@ -4,24 +4,29 @@ namespace sauce::editor { -class TranslateGizmo : public Gizmo { -public: - GizmoType getType() const override { return GizmoType::Translate; } - GizmoMeshData generateMesh() const override; - GizmoAxis hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& rotation, float scale) const override; - void beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) override; - glm::vec3 updateInteraction(const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) override; - void endInteraction() override; + class TranslateGizmo : public Gizmo { + public: + GizmoType getType() const override { + return GizmoType::Translate; + } + GizmoMeshData generateMesh() const override; + GizmoAxis hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& rotation, + float scale) const override; + void beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) override; + glm::vec3 updateInteraction(const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) override; + void endInteraction() override; -private: - float initialT = 0.0f; - glm::vec3 lastEntityPos{0.0f}; + private: + float initialT = 0.0f; + glm::vec3 lastEntityPos{0.0f}; - static constexpr float SHAFT_LENGTH = 1.0f; - static constexpr float SHAFT_RADIUS = 0.02f; - static constexpr float CONE_LENGTH = 0.2f; - static constexpr float CONE_RADIUS = 0.06f; - static constexpr int SEGMENTS = 12; -}; + static constexpr float SHAFT_LENGTH = 1.0f; + static constexpr float SHAFT_RADIUS = 0.02f; + static constexpr float CONE_LENGTH = 0.2f; + static constexpr float CONE_RADIUS = 0.06f; + static constexpr int SEGMENTS = 12; + }; } // namespace sauce::editor diff --git a/include/editor/panels/AssetBrowserPanel.hpp b/include/editor/panels/AssetBrowserPanel.hpp index 3e4d9cf8..9058ffd3 100644 --- a/include/editor/panels/AssetBrowserPanel.hpp +++ b/include/editor/panels/AssetBrowserPanel.hpp @@ -6,21 +6,21 @@ namespace sauce::editor { -class AssetBrowserPanel : public EditorPanel { -public: - AssetBrowserPanel(EditorApp& app); - void render() override; + class AssetBrowserPanel : public EditorPanel { + public: + AssetBrowserPanel(EditorApp& app); + void render() override; - void handleFileDrop(const std::string& path); + void handleFileDrop(const std::string& path); -private: - void drawDirectoryTree(const std::filesystem::path& dir); - void drawFileList(); - void importGLTF(const std::filesystem::path& path); + private: + void drawDirectoryTree(const std::filesystem::path& dir); + void drawFileList(); + void importGLTF(const std::filesystem::path& path); - std::filesystem::path rootPath; - std::filesystem::path currentPath; - std::filesystem::path selectedFile; -}; + std::filesystem::path rootPath; + std::filesystem::path currentPath; + std::filesystem::path selectedFile; + }; } // namespace sauce::editor diff --git a/include/editor/panels/EditorPanel.hpp b/include/editor/panels/EditorPanel.hpp index f4dcf84d..a3f650bd 100644 --- a/include/editor/panels/EditorPanel.hpp +++ b/include/editor/panels/EditorPanel.hpp @@ -4,26 +4,34 @@ namespace sauce::editor { -class EditorApp; - -class EditorPanel { -public: - EditorPanel(const std::string& title, EditorApp& app) - : title(title), app(app) {} - - virtual ~EditorPanel() = default; - - virtual void render() = 0; - - const std::string& getTitle() const { return title; } - bool& getOpenRef() { return isOpen; } - bool getIsOpen() const { return isOpen; } - void setOpen(bool open) { isOpen = open; } - -protected: - std::string title; - EditorApp& app; - bool isOpen = true; -}; + class EditorApp; + + class EditorPanel { + public: + EditorPanel(const std::string& title, EditorApp& app) : title(title), app(app) { + } + + virtual ~EditorPanel() = default; + + virtual void render() = 0; + + const std::string& getTitle() const { + return title; + } + bool& getOpenRef() { + return isOpen; + } + bool getIsOpen() const { + return isOpen; + } + void setOpen(bool open) { + isOpen = open; + } + + protected: + std::string title; + EditorApp& app; + bool isOpen = true; + }; } // namespace sauce::editor diff --git a/include/editor/panels/InspectorPanel.hpp b/include/editor/panels/InspectorPanel.hpp index b415c1b6..078bf7b4 100644 --- a/include/editor/panels/InspectorPanel.hpp +++ b/include/editor/panels/InspectorPanel.hpp @@ -1,32 +1,33 @@ #pragma once -#include #include +#include #include namespace sauce { -class Entity; + class Entity; -namespace modeling { -class Mesh; -class Material; -} + namespace modeling { + class Mesh; + class Material; + } // namespace modeling } // namespace sauce namespace sauce::editor { -class InspectorPanel : public EditorPanel { -public: - InspectorPanel(EditorApp& app); - void render() override; - -private: - void drawTransformSection(sauce::Entity& entity); - void drawMeshRendererSection(sauce::Entity& entity); - void drawMetadataSection(const std::string& label, - const std::unordered_map& metadata); -}; + class InspectorPanel : public EditorPanel { + public: + InspectorPanel(EditorApp& app); + void render() override; + + private: + void drawTransformSection(sauce::Entity& entity); + void drawMeshRendererSection(sauce::Entity& entity); + void drawMetadataSection( + const std::string& label, + const std::unordered_map& metadata); + }; } // namespace sauce::editor diff --git a/include/editor/panels/SceneHierarchyPanel.hpp b/include/editor/panels/SceneHierarchyPanel.hpp index a7f54958..c5e6770a 100644 --- a/include/editor/panels/SceneHierarchyPanel.hpp +++ b/include/editor/panels/SceneHierarchyPanel.hpp @@ -4,10 +4,10 @@ namespace sauce::editor { -class SceneHierarchyPanel : public EditorPanel { -public: - SceneHierarchyPanel(EditorApp& app); - void render() override; -}; + class SceneHierarchyPanel : public EditorPanel { + public: + SceneHierarchyPanel(EditorApp& app); + void render() override; + }; } // namespace sauce::editor diff --git a/include/editor/panels/ViewportPanel.hpp b/include/editor/panels/ViewportPanel.hpp index edf87028..51482ba4 100644 --- a/include/editor/panels/ViewportPanel.hpp +++ b/include/editor/panels/ViewportPanel.hpp @@ -5,25 +5,37 @@ namespace sauce::editor { -class ViewportPanel : public EditorPanel { -public: - ViewportPanel(EditorApp& app); - void render() override; + class ViewportPanel : public EditorPanel { + public: + ViewportPanel(EditorApp& app); + void render() override; - bool isViewportHovered() const { return hovered; } - bool isViewportFocused() const { return focused; } + bool isViewportHovered() const { + return hovered; + } + bool isViewportFocused() const { + return focused; + } - ImVec2 getViewportSize() const { return viewportSize; } - ImVec2 getViewportScreenPos() const { return viewportScreenPos; } - bool viewportSizeChanged() const { return sizeChanged; } - void clearSizeChanged() { sizeChanged = false; } + ImVec2 getViewportSize() const { + return viewportSize; + } + ImVec2 getViewportScreenPos() const { + return viewportScreenPos; + } + bool viewportSizeChanged() const { + return sizeChanged; + } + void clearSizeChanged() { + sizeChanged = false; + } -private: - bool hovered = false; - bool focused = false; - ImVec2 viewportSize = { 0, 0 }; - ImVec2 viewportScreenPos = { 0, 0 }; - bool sizeChanged = false; -}; + private: + bool hovered = false; + bool focused = false; + ImVec2 viewportSize = {0, 0}; + ImVec2 viewportScreenPos = {0, 0}; + bool sizeChanged = false; + }; } // namespace sauce::editor diff --git a/include/editor/zip_file.hpp b/include/editor/zip_file.hpp index 0943fb9d..3f70679a 100644 --- a/include/editor/zip_file.hpp +++ b/include/editor/zip_file.hpp @@ -24,8 +24,8 @@ #include #include -#include #include +#include #include #include #include @@ -226,20 +226,22 @@ //#define MINIZ_NO_MALLOC #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) - // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux - #define MINIZ_NO_TIME +// TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux +#define MINIZ_NO_TIME #endif #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) - #include +#include #endif -#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || \ + defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || \ + defined(__x86_64__) // MINIZ_X86_OR_X64_CPU is only used to help set the below macros. #define MINIZ_X86_OR_X64_CPU 1 #endif -#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU // Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. #define MINIZ_LITTLE_ENDIAN 1 #endif @@ -249,7 +251,8 @@ #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #endif -#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || \ + defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) // Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). #define MINIZ_HAS_64BIT_REGISTERS 1 #endif @@ -272,18 +275,24 @@ extern "C" { typedef unsigned long mz_ulong; // mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. -void mz_free(void *p); +void mz_free(void* p); #define MZ_ADLER32_INIT (1) // mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. -mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); +mz_ulong mz_adler32(mz_ulong adler, const unsigned char* ptr, size_t buf_len); #define MZ_CRC32_INIT (0) // mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. -mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); +mz_ulong mz_crc32(mz_ulong crc, const unsigned char* ptr, size_t buf_len); // Compression strategies. -enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; +enum { + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; // Method #define MZ_DEFLATED 8 @@ -292,25 +301,50 @@ enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3 // Heap allocation callbacks. // Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. -typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); -typedef void (*mz_free_func)(void *opaque, void *address); -typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); +typedef void* (*mz_alloc_func)(void* opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void* opaque, void* address); +typedef void* (*mz_realloc_func)(void* opaque, void* address, size_t items, size_t size); -#define MZ_VERSION "9.1.15" -#define MZ_VERNUM 0x91F0 -#define MZ_VER_MAJOR 9 -#define MZ_VER_MINOR 1 -#define MZ_VER_REVISION 15 -#define MZ_VER_SUBREVISION 0 +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 // Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). -enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; +enum { + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; // Return status codes. MZ_PARAM_ERROR is non-standard. -enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; +enum { + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; // Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. -enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; +enum { + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; // Window bits #define MZ_DEFAULT_WINDOW_BITS 15 @@ -318,32 +352,31 @@ enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBE struct mz_internal_state; // Compression/decompression stream struct. -typedef struct mz_stream_s -{ - const unsigned char *next_in; // pointer to next byte to read - unsigned int avail_in; // number of bytes available at next_in - mz_ulong total_in; // total number of bytes consumed so far - - unsigned char *next_out; // pointer to next byte to write - unsigned int avail_out; // number of bytes that can be written to next_out - mz_ulong total_out; // total number of bytes produced so far - - char *msg; // error msg (unused) - struct mz_internal_state *state; // internal state, allocated by zalloc/zfree - - mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) - mz_free_func zfree; // optional heap free function (defaults to free) - void *opaque; // heap alloc function user pointer - - int data_type; // data_type (unused) - mz_ulong adler; // adler32 of the source or uncompressed data - mz_ulong reserved; // not used +typedef struct mz_stream_s { + const unsigned char* next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char* next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char* msg; // error msg (unused) + struct mz_internal_state* state; // internal state, allocated by zalloc/zfree + + mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void* opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used } mz_stream; -typedef mz_stream *mz_streamp; +typedef mz_stream* mz_streamp; // Returns the version string of miniz.c. -const char *mz_version(void); +const char* mz_version(void); // mz_deflateInit() initializes a compressor with default options: // Parameters: @@ -363,7 +396,8 @@ int mz_deflateInit(mz_streamp pStream, int level); // method must be MZ_DEFLATED // window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) // mem_level must be between [1, 9] (it's checked but ignored by miniz.c) -int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, + int strategy); // Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). int mz_deflateReset(mz_streamp pStream); @@ -391,8 +425,10 @@ mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); // Single-call compression functions mz_compress() and mz_compress2(): // Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. -int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); -int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); +int mz_compress(unsigned char* pDest, mz_ulong* pDest_len, const unsigned char* pSource, + mz_ulong source_len); +int mz_compress2(unsigned char* pDest, mz_ulong* pDest_len, const unsigned char* pSource, + mz_ulong source_len, int level); // mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). mz_ulong mz_compressBound(mz_ulong source_len); @@ -425,84 +461,85 @@ int mz_inflateEnd(mz_streamp pStream); // Single-call decompression. // Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_uncompress(unsigned char* pDest, mz_ulong* pDest_len, const unsigned char* pSource, + mz_ulong source_len); // Returns a string description of the specified error code, or NULL if the error code is invalid. -const char *mz_error(int err); +const char* mz_error(int err); // Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. // Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES - typedef unsigned char Byte; - typedef unsigned int uInt; - typedef mz_ulong uLong; - typedef Byte Bytef; - typedef uInt uIntf; - typedef char charf; - typedef int intf; - typedef void *voidpf; - typedef uLong uLongf; - typedef void *voidp; - typedef void *const voidpc; - #define Z_NULL 0 - #define Z_NO_FLUSH MZ_NO_FLUSH - #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH - #define Z_SYNC_FLUSH MZ_SYNC_FLUSH - #define Z_FULL_FLUSH MZ_FULL_FLUSH - #define Z_FINISH MZ_FINISH - #define Z_BLOCK MZ_BLOCK - #define Z_OK MZ_OK - #define Z_STREAM_END MZ_STREAM_END - #define Z_NEED_DICT MZ_NEED_DICT - #define Z_ERRNO MZ_ERRNO - #define Z_STREAM_ERROR MZ_STREAM_ERROR - #define Z_DATA_ERROR MZ_DATA_ERROR - #define Z_MEM_ERROR MZ_MEM_ERROR - #define Z_BUF_ERROR MZ_BUF_ERROR - #define Z_VERSION_ERROR MZ_VERSION_ERROR - #define Z_PARAM_ERROR MZ_PARAM_ERROR - #define Z_NO_COMPRESSION MZ_NO_COMPRESSION - #define Z_BEST_SPEED MZ_BEST_SPEED - #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION - #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION - #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY - #define Z_FILTERED MZ_FILTERED - #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY - #define Z_RLE MZ_RLE - #define Z_FIXED MZ_FIXED - #define Z_DEFLATED MZ_DEFLATED - #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS - #define alloc_func mz_alloc_func - #define free_func mz_free_func - #define internal_state mz_internal_state - #define z_stream mz_stream - #define deflateInit mz_deflateInit - #define deflateInit2 mz_deflateInit2 - #define deflateReset mz_deflateReset - #define deflate mz_deflate - #define deflateEnd mz_deflateEnd - #define deflateBound mz_deflateBound - #define compress mz_compress - #define compress2 mz_compress2 - #define compressBound mz_compressBound - #define inflateInit mz_inflateInit - #define inflateInit2 mz_inflateInit2 - #define inflate mz_inflate - #define inflateEnd mz_inflateEnd - #define uncompress mz_uncompress - #define crc32 mz_crc32 - #define adler32 mz_adler32 - #define MAX_WBITS 15 - #define MAX_MEM_LEVEL 9 - #define zError mz_error - #define ZLIB_VERSION MZ_VERSION - #define ZLIB_VERNUM MZ_VERNUM - #define ZLIB_VER_MAJOR MZ_VER_MAJOR - #define ZLIB_VER_MINOR MZ_VER_MINOR - #define ZLIB_VER_REVISION MZ_VER_REVISION - #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION - #define zlibVersion mz_version - #define zlib_version mz_version() +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void* voidpf; +typedef uLong uLongf; +typedef void* voidp; +typedef void* const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() #endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES #endif // MINIZ_NO_ZLIB_APIS @@ -523,154 +560,169 @@ typedef int mz_bool; // An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. #ifdef _MSC_VER - #define MZ_MACRO_END while (0, 0) +#define MZ_MACRO_END while (0, 0) #else - #define MZ_MACRO_END while (0) +#define MZ_MACRO_END while (0) #endif // ------------------- ZIP archive reading/writing #ifndef MINIZ_NO_ARCHIVE_APIS -enum -{ - MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, - MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, - MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +enum { + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 }; -typedef struct -{ - mz_uint32 m_file_index; - mz_uint32 m_central_dir_ofs; - mz_uint16 m_version_made_by; - mz_uint16 m_version_needed; - mz_uint16 m_bit_flag; - mz_uint16 m_method; +typedef struct { + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; #ifndef MINIZ_NO_TIME - time_t m_time; + time_t m_time; #endif - mz_uint32 m_crc32; - mz_uint64 m_comp_size; - mz_uint64 m_uncomp_size; - mz_uint16 m_internal_attr; - mz_uint32 m_external_attr; - mz_uint64 m_local_header_ofs; - mz_uint32 m_comment_size; - char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; - char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; } mz_zip_archive_file_stat; -typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); -typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef size_t (*mz_file_read_func)(void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n); struct mz_zip_internal_state_tag; typedef struct mz_zip_internal_state_tag mz_zip_internal_state; -typedef enum -{ - MZ_ZIP_MODE_INVALID = 0, - MZ_ZIP_MODE_READING = 1, - MZ_ZIP_MODE_WRITING = 2, - MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 } mz_zip_mode; -typedef struct mz_zip_archive_tag -{ - mz_uint64 m_archive_size; - mz_uint64 m_central_directory_file_ofs; - mz_uint m_total_files; - mz_zip_mode m_zip_mode; +typedef struct mz_zip_archive_tag { + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + mz_uint m_total_files; + mz_zip_mode m_zip_mode; - mz_uint m_file_offset_alignment; + mz_uint m_file_offset_alignment; - mz_alloc_func m_pAlloc; - mz_free_func m_pFree; - mz_realloc_func m_pRealloc; - void *m_pAlloc_opaque; + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void* m_pAlloc_opaque; - mz_file_read_func m_pRead; - mz_file_write_func m_pWrite; - void *m_pIO_opaque; + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + void* m_pIO_opaque; - mz_zip_internal_state *m_pState; + mz_zip_internal_state* m_pState; } mz_zip_archive; -typedef enum -{ - MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, - MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, - MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, - MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 } mz_zip_flags; // ZIP archive reading // Inits a ZIP archive reader. // These functions read and validate the archive's central directory. -mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); -mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); +mz_bool mz_zip_reader_init(mz_zip_archive* pZip, mz_uint64 size, mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive* pZip, const void* pMem, size_t size, + mz_uint32 flags); #ifndef MINIZ_NO_STDIO -mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +mz_bool mz_zip_reader_init_file(mz_zip_archive* pZip, const char* pFilename, mz_uint32 flags); #endif // Returns the total number of files in the archive. -mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); +mz_uint mz_zip_reader_get_num_files(mz_zip_archive* pZip); // Returns detailed information about an archive file entry. -mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); +mz_bool mz_zip_reader_file_stat(mz_zip_archive* pZip, mz_uint file_index, + mz_zip_archive_file_stat* pStat); // Determines if an archive file entry is a directory entry. -mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); -mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive* pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive* pZip, mz_uint file_index); // Retrieves the filename of an archive file entry. // Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. -mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); +mz_uint mz_zip_reader_get_filename(mz_zip_archive* pZip, mz_uint file_index, char* pFilename, + mz_uint filename_buf_size); // Attempts to locates a file in the archive's central directory. // Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH // Returns -1 if the file cannot be found. -int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +int mz_zip_reader_locate_file(mz_zip_archive* pZip, const char* pName, const char* pComment, + mz_uint flags); // Extracts a archive file to a memory buffer using no memory allocation. -mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); -mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive* pZip, mz_uint file_index, void* pBuf, + size_t buf_size, mz_uint flags, void* pUser_read_buf, + size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive* pZip, const char* pFilename, + void* pBuf, size_t buf_size, mz_uint flags, + void* pUser_read_buf, size_t user_read_buf_size); // Extracts a archive file to a memory buffer. -mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive* pZip, mz_uint file_index, void* pBuf, + size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive* pZip, const char* pFilename, void* pBuf, + size_t buf_size, mz_uint flags); // Extracts a archive file to a dynamically allocated heap buffer. -void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); -void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); +void* mz_zip_reader_extract_to_heap(mz_zip_archive* pZip, mz_uint file_index, size_t* pSize, + mz_uint flags); +void* mz_zip_reader_extract_file_to_heap(mz_zip_archive* pZip, const char* pFilename, size_t* pSize, + mz_uint flags); // Extracts a archive file using a callback function to output the file's data. -mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive* pZip, mz_uint file_index, + mz_file_write_func pCallback, void* pOpaque, + mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive* pZip, const char* pFilename, + mz_file_write_func pCallback, void* pOpaque, + mz_uint flags); #ifndef MINIZ_NO_STDIO // Extracts a archive file to a disk file and sets its last accessed and modified times. // This function only extracts files, not archive directory records. -mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive* pZip, mz_uint file_index, + const char* pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive* pZip, const char* pArchive_filename, + const char* pDst_filename, mz_uint flags); #endif // Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. -mz_bool mz_zip_reader_end(mz_zip_archive *pZip); +mz_bool mz_zip_reader_end(mz_zip_archive* pZip); // ZIP archive writing #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS // Inits a ZIP archive writer. -mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); -mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); +mz_bool mz_zip_writer_init(mz_zip_archive* pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive* pZip, size_t size_to_reserve_at_beginning, + size_t initial_allocation_size); #ifndef MINIZ_NO_STDIO -mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +mz_bool mz_zip_writer_init_file(mz_zip_archive* pZip, const char* pFilename, + mz_uint64 size_to_reserve_at_beginning); #endif // Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. @@ -679,43 +731,54 @@ mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_ // Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. // Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before // the archive is finalized the file's central directory will be hosed. -mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive* pZip, const char* pFilename); // Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. // To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. -mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); -mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); +mz_bool mz_zip_writer_add_mem(mz_zip_archive* pZip, const char* pArchive_name, const void* pBuf, + size_t buf_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive* pZip, const char* pArchive_name, const void* pBuf, + size_t buf_size, const void* pComment, mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, + mz_uint32 uncomp_crc32); #ifndef MINIZ_NO_STDIO // Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. -mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_file(mz_zip_archive* pZip, const char* pArchive_name, + const char* pSrc_filename, const void* pComment, + mz_uint16 comment_size, mz_uint level_and_flags); #endif // Adds a file to an archive by fully cloning the data from another archive. // This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. -mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive* pZip, mz_zip_archive* pSource_zip, + mz_uint file_index); // Finalizes the archive by writing the central directory records followed by the end of central directory record. // After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). // An archive must be manually finalized by calling this function for it to be valid. -mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); -mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive* pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive* pZip, void** pBuf, size_t* pSize); // Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. // Note for the archive to be valid, it must have been finalized before ending. -mz_bool mz_zip_writer_end(mz_zip_archive *pZip); +mz_bool mz_zip_writer_end(mz_zip_archive* pZip); // Misc. high-level helper functions: // mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. -mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char* pZip_filename, + const char* pArchive_name, const void* pBuf, + size_t buf_size, const void* pComment, + mz_uint16 comment_size, mz_uint level_and_flags); // Reads a single file from an archive into a heap block. // Returns NULL on failure. -void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); +void* mz_zip_extract_archive_file_to_heap(const char* pZip_filename, const char* pArchive_name, + size_t* pSize, mz_uint zip_flags); #endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS @@ -728,12 +791,11 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char // TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. // TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). // TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. -enum -{ - TINFL_FLAG_PARSE_ZLIB_HEADER = 1, - TINFL_FLAG_HAS_MORE_INPUT = 2, - TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, - TINFL_FLAG_COMPUTE_ADLER32 = 8 +enum { + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 }; // High level decompression functions: @@ -744,74 +806,88 @@ enum // Function returns a pointer to the decompressed data, or NULL on failure. // *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. // The caller must call mz_free() on the returned block when it's no longer needed. -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); +void* tinfl_decompress_mem_to_heap(const void* pSrc_buf, size_t src_buf_len, size_t* pOut_len, + int flags); // tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. // Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) -size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); +size_t tinfl_decompress_mem_to_mem(void* pOut_buf, size_t out_buf_len, const void* pSrc_buf, + size_t src_buf_len, int flags); // tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. // Returns 1 on success or 0 on failure. -typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); -int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void* pUser); +int tinfl_decompress_mem_to_callback(const void* pIn_buf, size_t* pIn_buf_size, + tinfl_put_buf_func_ptr pPut_buf_func, void* pPut_buf_user, + int flags); -struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; // Max size of LZ dictionary. #define TINFL_LZ_DICT_SIZE 32768 // Return status. -typedef enum -{ - TINFL_STATUS_BAD_PARAM = -3, - TINFL_STATUS_ADLER32_MISMATCH = -2, - TINFL_STATUS_FAILED = -1, - TINFL_STATUS_DONE = 0, - TINFL_STATUS_NEEDS_MORE_INPUT = 1, - TINFL_STATUS_HAS_MORE_OUTPUT = 2 +typedef enum { + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 } tinfl_status; // Initializes the decompressor to its initial state. -#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_init(r) \ + do { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END #define tinfl_get_adler32(r) (r)->m_check_adler32 // Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. // This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. -tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); +tinfl_status tinfl_decompress(tinfl_decompressor* r, const mz_uint8* pIn_buf_next, + size_t* pIn_buf_size, mz_uint8* pOut_buf_start, + mz_uint8* pOut_buf_next, size_t* pOut_buf_size, + const mz_uint32 decomp_flags); // Internal/private bits follow. -enum -{ - TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, - TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +enum { + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS }; -typedef struct -{ - mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; - mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +typedef struct { + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; } tinfl_huff_table; #if MINIZ_HAS_64BIT_REGISTERS - #define TINFL_USE_64BIT_BITBUF 1 +#define TINFL_USE_64BIT_BITBUF 1 #endif #if TINFL_USE_64BIT_BITBUF - typedef mz_uint64 tinfl_bit_buf_t; - #define TINFL_BITBUF_SIZE (64) +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) #else - typedef mz_uint32 tinfl_bit_buf_t; - #define TINFL_BITBUF_SIZE (32) +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) #endif -struct tinfl_decompressor_tag -{ - mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; - tinfl_bit_buf_t m_bit_buf; - size_t m_dist_from_out_buf_start; - tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; - mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +struct tinfl_decompressor_tag { + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, + m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], + m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; // ------------------- Low-level Compression API Definitions @@ -821,9 +897,10 @@ struct tinfl_decompressor_tag // tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): // TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). -enum -{ - TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF +enum { + TDEFL_HUFFMAN_ONLY = 0, + TDEFL_DEFAULT_MAX_PROBES = 128, + TDEFL_MAX_PROBES_MASK = 0xFFF }; // TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. @@ -835,16 +912,15 @@ enum // TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. // TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. // The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). -enum -{ - TDEFL_WRITE_ZLIB_HEADER = 0x01000, - TDEFL_COMPUTE_ADLER32 = 0x02000, - TDEFL_GREEDY_PARSING_FLAG = 0x04000, - TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, - TDEFL_RLE_MATCHES = 0x10000, - TDEFL_FILTER_MATCHES = 0x20000, - TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, - TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +enum { + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 }; // High level compression functions: @@ -856,15 +932,17 @@ enum // Function returns a pointer to the compressed data, or NULL on failure. // *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. // The caller must free() the returned block when it's no longer needed. -void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); +void* tdefl_compress_mem_to_heap(const void* pSrc_buf, size_t src_buf_len, size_t* pOut_len, + int flags); // tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. // Returns 0 on failure. -size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); +size_t tdefl_compress_mem_to_mem(void* pOut_buf, size_t out_buf_len, const void* pSrc_buf, + size_t src_buf_len, int flags); // Compresses an image to a compressed PNG file in memory. // On entry: -// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. +// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. // The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. // level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL // If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). @@ -872,68 +950,95 @@ size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void // Function returns a pointer to the compressed data, or NULL on failure. // *pLen_out will be set to the size of the PNG image file. // The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. -void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); -void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); +void* tdefl_write_image_to_png_file_in_memory_ex(const void* pImage, int w, int h, int num_chans, + size_t* pLen_out, mz_uint level, mz_bool flip); +void* tdefl_write_image_to_png_file_in_memory(const void* pImage, int w, int h, int num_chans, + size_t* pLen_out); // Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. -typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void* pUser); // tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. -mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); - -enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; +mz_bool tdefl_compress_mem_to_output(const void* pBuf, size_t buf_len, + tdefl_put_buf_func_ptr pPut_buf_func, void* pPut_buf_user, + int flags); + +enum { + TDEFL_MAX_HUFF_TABLES = 3, + TDEFL_MAX_HUFF_SYMBOLS_0 = 288, + TDEFL_MAX_HUFF_SYMBOLS_1 = 32, + TDEFL_MAX_HUFF_SYMBOLS_2 = 19, + TDEFL_LZ_DICT_SIZE = 32768, + TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, + TDEFL_MIN_MATCH_LEN = 3, + TDEFL_MAX_MATCH_LEN = 258 +}; // TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). #if TDEFL_LESS_MEMORY -enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +enum { + TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 12, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; #else -enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +enum { + TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 15, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; #endif // The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. -typedef enum -{ - TDEFL_STATUS_BAD_PARAM = -2, - TDEFL_STATUS_PUT_BUF_FAILED = -1, - TDEFL_STATUS_OKAY = 0, - TDEFL_STATUS_DONE = 1, +typedef enum { + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, } tdefl_status; // Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums -typedef enum -{ - TDEFL_NO_FLUSH = 0, - TDEFL_SYNC_FLUSH = 2, - TDEFL_FULL_FLUSH = 3, - TDEFL_FINISH = 4 +typedef enum { + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 } tdefl_flush; // tdefl's compression state structure. -typedef struct -{ - tdefl_put_buf_func_ptr m_pPut_buf_func; - void *m_pPut_buf_user; - mz_uint m_flags, m_max_probes[2]; - int m_greedy_parsing; - mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; - mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; - mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; - mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; - tdefl_status m_prev_return_status; - const void *m_pIn_buf; - void *m_pOut_buf; - size_t *m_pIn_buf_size, *m_pOut_buf_size; - tdefl_flush m_flush; - const mz_uint8 *m_pSrc; - size_t m_src_buf_left, m_out_buf_ofs; - mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; - mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; - mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; - mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; - mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; - mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; - mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; - mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +typedef struct { + tdefl_put_buf_func_ptr m_pPut_buf_func; + void* m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, + m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void* m_pIn_buf; + void* m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8* m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; } tdefl_compressor; // Initializes the compressor. @@ -941,17 +1046,20 @@ typedef struct // pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. // If pBut_buf_func is NULL the user should always call the tdefl_compress() API. // flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) -tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +tdefl_status tdefl_init(tdefl_compressor* d, tdefl_put_buf_func_ptr pPut_buf_func, + void* pPut_buf_user, int flags); // Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. -tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); +tdefl_status tdefl_compress(tdefl_compressor* d, const void* pIn_buf, size_t* pIn_buf_size, + void* pOut_buf, size_t* pOut_buf_size, tdefl_flush flush); // tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. // tdefl_compress_buffer() always consumes the entire input buffer. -tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); +tdefl_status tdefl_compress_buffer(tdefl_compressor* d, const void* pIn_buf, size_t in_buf_size, + tdefl_flush flush); -tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); -mz_uint32 tdefl_get_adler32(tdefl_compressor *d); +tdefl_status tdefl_get_prev_return_status(tdefl_compressor* d); +mz_uint32 tdefl_get_adler32(tdefl_compressor* d); // Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. #ifndef MINIZ_NO_ZLIB_APIS @@ -972,428 +1080,495 @@ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int #ifndef MINIZ_HEADER_FILE_ONLY -typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1]; -typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1]; -typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1]; +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; -#include #include +#include #define MZ_ASSERT(x) assert(x) #ifdef MINIZ_NO_MALLOC - #define MZ_MALLOC(x) NULL - #define MZ_FREE(x) (void)x, ((void)0) - #define MZ_REALLOC(p, x) NULL +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL #else - #define MZ_MALLOC(x) malloc(x) - #define MZ_FREE(x) free(x) - #define MZ_REALLOC(p, x) realloc(p, x) +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) #endif -#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) -#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN - #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) - #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#define MZ_READ_LE16(p) *((const mz_uint16*)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32*)(p)) #else - #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) - #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#define MZ_READ_LE16(p) \ + ((mz_uint32)(((const mz_uint8*)(p))[0]) | ((mz_uint32)(((const mz_uint8*)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) \ + ((mz_uint32)(((const mz_uint8*)(p))[0]) | ((mz_uint32)(((const mz_uint8*)(p))[1]) << 8U) | \ + ((mz_uint32)(((const mz_uint8*)(p))[2]) << 16U) | \ + ((mz_uint32)(((const mz_uint8*)(p))[3]) << 24U)) #endif #ifdef _MSC_VER - #define MZ_FORCEINLINE __forceinline +#define MZ_FORCEINLINE __forceinline #elif defined(__GNUC__) - #define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#define MZ_FORCEINLINE inline __attribute__((__always_inline__)) #else - #define MZ_FORCEINLINE inline +#define MZ_FORCEINLINE inline #endif #ifdef __cplusplus - extern "C" { +extern "C" { #endif // ------------------- zlib-style API's -mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) -{ - mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; - if (!ptr) return MZ_ADLER32_INIT; - while (buf_len) { - for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { - s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; - s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; +mz_ulong mz_adler32(mz_ulong adler, const unsigned char* ptr, size_t buf_len) { + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; } - for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; - s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; - } - return (s2 << 16) + s1; + return (s2 << 16) + s1; } // Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ -mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) -{ - static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; - mz_uint32 crcu32 = (mz_uint32)crc; - if (!ptr) return MZ_CRC32_INIT; - crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } - return ~crcu32; +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8* ptr, size_t buf_len) { + static const mz_uint32 s_crc32[16] = {0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c}; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; } -void mz_free(void *p) -{ - MZ_FREE(p); +void mz_free(void* p) { + MZ_FREE(p); } #ifndef MINIZ_NO_ZLIB_APIS -static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } -static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } -static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } - -const char *mz_version(void) -{ - return MZ_VERSION; +static void* def_alloc_func(void* opaque, size_t items, size_t size) { + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); } - -int mz_deflateInit(mz_streamp pStream, int level) -{ - return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +static void def_free_func(void* opaque, void* address) { + (void)opaque, (void)address; + MZ_FREE(address); } - -int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) -{ - tdefl_compressor *pComp; - mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); - - if (!pStream) return MZ_STREAM_ERROR; - if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; - - pStream->data_type = 0; - pStream->adler = MZ_ADLER32_INIT; - pStream->msg = NULL; - pStream->reserved = 0; - pStream->total_in = 0; - pStream->total_out = 0; - if (!pStream->zalloc) pStream->zalloc = def_alloc_func; - if (!pStream->zfree) pStream->zfree = def_free_func; - - pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); - if (!pComp) - return MZ_MEM_ERROR; - - pStream->state = (struct mz_internal_state *)pComp; - - if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) - { - mz_deflateEnd(pStream); - return MZ_PARAM_ERROR; - } - - return MZ_OK; +static void* def_realloc_func(void* opaque, void* address, size_t items, size_t size) { + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); } -int mz_deflateReset(mz_streamp pStream) -{ - if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; - pStream->total_in = pStream->total_out = 0; - tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); - return MZ_OK; +const char* mz_version(void) { + return MZ_VERSION; } -int mz_deflate(mz_streamp pStream, int flush) -{ - size_t in_bytes, out_bytes; - mz_ulong orig_total_in, orig_total_out; - int mz_status = MZ_OK; - - if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; - if (!pStream->avail_out) return MZ_BUF_ERROR; - - if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; - - if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) - return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; +int mz_deflateInit(mz_streamp pStream, int level) { + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, + MZ_DEFAULT_STRATEGY); +} - orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; - for ( ; ; ) - { - tdefl_status defl_status; - in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, + int strategy) { + tdefl_compressor* pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | + tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || + ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = def_alloc_func; + if (!pStream->zfree) + pStream->zfree = def_free_func; + + pComp = (tdefl_compressor*)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state*)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } - defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); - pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; - pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); + return MZ_OK; +} - pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; - pStream->total_out += (mz_uint)out_bytes; +int mz_deflateReset(mz_streamp pStream) { + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, + ((tdefl_compressor*)pStream->state)->m_flags); + return MZ_OK; +} - if (defl_status < 0) - { - mz_status = MZ_STREAM_ERROR; - break; - } - else if (defl_status == TDEFL_STATUS_DONE) - { - mz_status = MZ_STREAM_END; - break; - } - else if (!pStream->avail_out) - break; - else if ((!pStream->avail_in) && (flush != MZ_FINISH)) - { - if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) - break; - return MZ_BUF_ERROR; // Can't make forward progress without some input. +int mz_deflate(mz_streamp pStream, int flush) { + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || + (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, + pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) { + mz_status = MZ_STREAM_ERROR; + break; + } else if (defl_status == TDEFL_STATUS_DONE) { + mz_status = MZ_STREAM_END; + break; + } else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { + if ((flush) || (pStream->total_in != orig_total_in) || + (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } } - } - return mz_status; + return mz_status; } -int mz_deflateEnd(mz_streamp pStream) -{ - if (!pStream) return MZ_STREAM_ERROR; - if (pStream->state) - { - pStream->zfree(pStream->opaque, pStream->state); - pStream->state = NULL; - } - return MZ_OK; +int mz_deflateEnd(mz_streamp pStream) { + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; } -mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) -{ - (void)pStream; - // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) - return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, + 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); } -int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) -{ - int status; - mz_stream stream; - memset(&stream, 0, sizeof(stream)); - - // In case mz_ulong is 64-bits (argh I hate longs). - if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; - - stream.next_in = pSource; - stream.avail_in = (mz_uint32)source_len; - stream.next_out = pDest; - stream.avail_out = (mz_uint32)*pDest_len; - - status = mz_deflateInit(&stream, level); - if (status != MZ_OK) return status; - - status = mz_deflate(&stream, MZ_FINISH); - if (status != MZ_STREAM_END) - { - mz_deflateEnd(&stream); - return (status == MZ_OK) ? MZ_BUF_ERROR : status; - } +int mz_compress2(unsigned char* pDest, mz_ulong* pDest_len, const unsigned char* pSource, + mz_ulong source_len, int level) { + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } - *pDest_len = stream.total_out; - return mz_deflateEnd(&stream); + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); } -int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) -{ - return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +int mz_compress(unsigned char* pDest, mz_ulong* pDest_len, const unsigned char* pSource, + mz_ulong source_len) { + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); } -mz_ulong mz_compressBound(mz_ulong source_len) -{ - return mz_deflateBound(NULL, source_len); +mz_ulong mz_compressBound(mz_ulong source_len) { + return mz_deflateBound(NULL, source_len); } -typedef struct -{ - tinfl_decompressor m_decomp; - mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; - mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; - tinfl_status m_last_status; +typedef struct { + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; } inflate_state; -int mz_inflateInit2(mz_streamp pStream, int window_bits) -{ - inflate_state *pDecomp; - if (!pStream) return MZ_STREAM_ERROR; - if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; - - pStream->data_type = 0; - pStream->adler = 0; - pStream->msg = NULL; - pStream->total_in = 0; - pStream->total_out = 0; - pStream->reserved = 0; - if (!pStream->zalloc) pStream->zalloc = def_alloc_func; - if (!pStream->zfree) pStream->zfree = def_free_func; - - pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); - if (!pDecomp) return MZ_MEM_ERROR; - - pStream->state = (struct mz_internal_state *)pDecomp; - - tinfl_init(&pDecomp->m_decomp); - pDecomp->m_dict_ofs = 0; - pDecomp->m_dict_avail = 0; - pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; - pDecomp->m_first_call = 1; - pDecomp->m_has_flushed = 0; - pDecomp->m_window_bits = window_bits; - - return MZ_OK; +int mz_inflateInit2(mz_streamp pStream, int window_bits) { + inflate_state* pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = def_alloc_func; + if (!pStream->zfree) + pStream->zfree = def_free_func; + + pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state*)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; } -int mz_inflateInit(mz_streamp pStream) -{ - return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +int mz_inflateInit(mz_streamp pStream) { + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); } -int mz_inflate(mz_streamp pStream, int flush) -{ - inflate_state* pState; - mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; - size_t in_bytes, out_bytes, orig_avail_in; - tinfl_status status; - - if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; - if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; - if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; - - pState = (inflate_state*)pStream->state; - if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; - orig_avail_in = pStream->avail_in; - - first_call = pState->m_first_call; pState->m_first_call = 0; - if (pState->m_last_status < 0) return MZ_DATA_ERROR; - - if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; - pState->m_has_flushed |= (flush == MZ_FINISH); - - if ((flush == MZ_FINISH) && (first_call)) - { - // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. - decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; - in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; - status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); - pState->m_last_status = status; - pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; - pStream->adler = tinfl_get_adler32(&pState->m_decomp); - pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; - - if (status < 0) - return MZ_DATA_ERROR; - else if (status != TINFL_STATUS_DONE) - { - pState->m_last_status = TINFL_STATUS_FAILED; - return MZ_BUF_ERROR; +int mz_inflate(mz_streamp pStream, int flush) { + inflate_state* pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state*)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) { + // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, + pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; } - return MZ_STREAM_END; - } - // flush != MZ_FINISH then we must assume there's more input. - if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; - - if (pState->m_dict_avail) - { - n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); - memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); - pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; - pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); - return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; - } - - for ( ; ; ) - { - in_bytes = pStream->avail_in; - out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; - - status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); - pState->m_last_status = status; - - pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; - pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); - - pState->m_dict_avail = (mz_uint)out_bytes; - - n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); - memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); - pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; - pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); - - if (status < 0) - return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). - else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) - return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. - else if (flush == MZ_FINISH) - { - // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. - if (status == TINFL_STATUS_DONE) - return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; - // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. - else if (!pStream->avail_out) - return MZ_BUF_ERROR; + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) + ? MZ_STREAM_END + : MZ_OK; } - else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) - break; - } - return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; -} + for (;;) { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, + pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. + else if (flush == MZ_FINISH) { + // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || + (pState->m_dict_avail)) + break; + } -int mz_inflateEnd(mz_streamp pStream) -{ - if (!pStream) - return MZ_STREAM_ERROR; - if (pStream->state) - { - pStream->zfree(pStream->opaque, pStream->state); - pStream->state = NULL; - } - return MZ_OK; + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) -{ - mz_stream stream; - int status; - memset(&stream, 0, sizeof(stream)); - - // In case mz_ulong is 64-bits (argh I hate longs). - if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; - - stream.next_in = pSource; - stream.avail_in = (mz_uint32)source_len; - stream.next_out = pDest; - stream.avail_out = (mz_uint32)*pDest_len; - - status = mz_inflateInit(&stream); - if (status != MZ_OK) - return status; +int mz_inflateEnd(mz_streamp pStream) { + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} - status = mz_inflate(&stream, MZ_FINISH); - if (status != MZ_STREAM_END) - { - mz_inflateEnd(&stream); - return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; - } - *pDest_len = stream.total_out; +int mz_uncompress(unsigned char* pDest, mz_ulong* pDest_len, const unsigned char* pSource, + mz_ulong source_len) { + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; - return mz_inflateEnd(&stream); + return mz_inflateEnd(&stream); } -const char *mz_error(int err) -{ - static struct { int m_err; const char *m_pDesc; } s_error_descs[] = - { - { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, - { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } - }; - mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; - return NULL; +const char* mz_error(int err) { + static struct { + int m_err; + const char* m_pDesc; + } s_error_descs[] = {{MZ_OK, ""}, + {MZ_STREAM_END, "stream end"}, + {MZ_NEED_DICT, "need dictionary"}, + {MZ_ERRNO, "file error"}, + {MZ_STREAM_ERROR, "stream error"}, + {MZ_DATA_ERROR, "data error"}, + {MZ_MEM_ERROR, "out of memory"}, + {MZ_BUF_ERROR, "buf error"}, + {MZ_VERSION_ERROR, "version error"}, + {MZ_PARAM_ERROR, "parameter error"}}; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; } #endif //MINIZ_NO_ZLIB_APIS @@ -1403,346 +1578,543 @@ const char *mz_error(int err) #define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) #define TINFL_MEMSET(p, c, l) memset(p, c, l) -#define TINFL_CR_BEGIN switch(r->m_state) { case 0: -#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END -#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END +#define TINFL_CR_BEGIN \ + switch (r->m_state) { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do { \ + for (;;) { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END #define TINFL_CR_FINISH } // TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never // reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. -#define TINFL_GET_BYTE(state_index, c) do { \ - if (pIn_buf_cur >= pIn_buf_end) { \ - for ( ; ; ) { \ - if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ - TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ - if (pIn_buf_cur < pIn_buf_end) { \ - c = *pIn_buf_cur++; \ - break; \ - } \ - } else { \ - c = 0; \ - break; \ - } \ - } \ - } else c = *pIn_buf_cur++; } MZ_MACRO_END - -#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) -#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END -#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END +#define TINFL_GET_BYTE(state_index, c) \ + do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for (;;) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do { \ + if (num_bits < (mz_uint)(n)) { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do { \ + if (num_bits < (mz_uint)(n)) { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END // TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. // It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a // Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the // bit buffer contains >=15 bits (deflate's max. Huffman code size). -#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ - do { \ - temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ - if (temp >= 0) { \ - code_len = temp >> 9; \ - if ((code_len) && (num_bits >= code_len)) \ - break; \ - } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ - code_len = TINFL_FAST_LOOKUP_BITS; \ - do { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ - } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ - } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ - } while (num_bits < 15); +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); // TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read // beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully // decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. // The slow path is only executed at the very end of the input buffer. -#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ - int temp; mz_uint code_len, c; \ - if (num_bits < 15) { \ - if ((pIn_buf_end - pIn_buf_cur) < 2) { \ - TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ - } else { \ - bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ - } \ - } \ - if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ - code_len = temp >> 9, temp &= 511; \ - else { \ - code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ - } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | \ + (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4334) #endif -tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) -{ - static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; - static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - static const int s_min_table_sizes[3] = { 257, 1, 4 }; - - tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; - const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; - mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; - size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; - - // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). - if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } - - num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; - TINFL_CR_BEGIN - - bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; - if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) - { - TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); - counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); - if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); - if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } - } - - do - { - TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; - if (r->m_type == 0) - { - TINFL_SKIP_BITS(5, num_bits & 7); - for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } - if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } - while ((counter) && (num_bits)) - { - TINFL_GET_BITS(51, dist, 8); - while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } - *pOut_buf_cur++ = (mz_uint8)dist; - counter--; - } - while (counter) - { - size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } - while (pIn_buf_cur >= pIn_buf_end) - { - if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) - { - TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); - } - else - { - TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); - } - } - n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); - TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; - } - } - else if (r->m_type == 3) - { - TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); +tinfl_status tinfl_decompress(tinfl_decompressor* r, const mz_uint8* pIn_buf_next, + size_t* pIn_buf_size, mz_uint8* pOut_buf_start, + mz_uint8* pOut_buf_next, size_t* pOut_buf_size, + const mz_uint32 decomp_flags) { + static const int s_length_base[31] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, + 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, + 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const int s_length_extra[31] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0}; + static const int s_dist_base[32] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0}; + static const int s_dist_extra[32] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, + 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + static const mz_uint8 s_length_dezigzag[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, + 11, 4, 12, 3, 13, 2, 14, 1, 15}; + static const int s_min_table_sizes[3] = {257, 1, 4}; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) + ? (size_t)-1 + : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, + dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; } - else - { - if (r->m_type == 1) - { - mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; - r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); - for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; - } - else - { - for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } - MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } - r->m_table_sizes[2] = 19; - } - for ( ; (int)r->m_type >= 0; r->m_type--) - { - int tree_next, tree_cur; tinfl_huff_table *pTable; - mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); - for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; - used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; - for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } - if ((65536 != total) && (used_syms > 1)) - { - TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); - } - for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) - { - mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; - cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); - if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } - if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } - rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); - for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) - { - tree_cur -= ((rev_code >>= 1) & 1); - if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; - } - tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || + ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || + ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } - if (r->m_type == 2) - { - for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) - { - mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } - if ((dist == 16) && (!counter)) - { - TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + + do { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); } - num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; - TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; - } - if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) - { - TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); - } - TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); - } - } - for ( ; ; ) - { - mz_uint8 *pSrc; - for ( ; ; ) - { - if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) - { - TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); - if (counter >= 256) - break; - while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } - *pOut_buf_cur++ = (mz_uint8)counter; - } - else - { - int sym2; mz_uint code_len; + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != + (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } else { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), + (size_t)(pIn_buf_end - pIn_buf_cur)), + counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } else if (r->m_type == 3) { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } else { + if (r->m_type == 1) { + mz_uint8* p = r->m_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } else { + for (counter = 0; counter < 3; counter++) { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) { + int tree_next, tree_cur; + tinfl_huff_table* pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; + ++sym_index) { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == + (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = + (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, + (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], + r->m_table_sizes[1]); + } + } + for (;;) { + mz_uint8* pSrc; + for (;;) { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } else { + int sym2; + mz_uint code_len; #if TINFL_USE_64BIT_BITBUF - if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } + if (num_bits < 30) { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } #else - if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } + if (num_bits < 15) { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } #endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) - code_len = sym2 >> 9; - else - { - code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); - } - counter = sym2; bit_buf >>= code_len; num_bits -= code_len; - if (counter & 256) - break; + if ((sym2 = r->m_tables[0] + .m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else { + code_len = TINFL_FAST_LOOKUP_BITS; + do { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; #if !TINFL_USE_64BIT_BITBUF - if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } + if (num_bits < 15) { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } #endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) - code_len = sym2 >> 9; - else - { - code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); - } - bit_buf >>= code_len; num_bits -= code_len; - - pOut_buf_cur[0] = (mz_uint8)counter; - if (sym2 & 256) - { - pOut_buf_cur++; - counter = sym2; - break; - } - pOut_buf_cur[1] = (mz_uint8)sym2; - pOut_buf_cur += 2; - } - } - if ((counter &= 511) == 256) break; - - num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; - if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + if ((sym2 = r->m_tables[0] + .m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else { + code_len = TINFL_FAST_LOOKUP_BITS; + do { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } - TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); - num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; - if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } - dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; - if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) - { - TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); - } + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && + (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } - pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); - if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) - { - while (counter--) - { - while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } - *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; - } - continue; - } + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) { + while (counter--) { + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = + pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES - else if ((counter >= 9) && (counter <= dist)) - { - const mz_uint8 *pSrc_end = pSrc + (counter & ~7); - do - { - ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; - ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; - pOut_buf_cur += 8; - } while ((pSrc += 8) < pSrc_end); - if ((counter &= 7) < 3) - { - if (counter) - { - pOut_buf_cur[0] = pSrc[0]; - if (counter > 1) - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur += counter; + else if ((counter >= 9) && (counter <= dist)) { + const mz_uint8* pSrc_end = pSrc + (counter & ~7); + do { + ((mz_uint32*)pOut_buf_cur)[0] = ((const mz_uint32*)pSrc)[0]; + ((mz_uint32*)pOut_buf_cur)[1] = ((const mz_uint32*)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) { + if (counter) { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } } - continue; - } } -#endif - do - { - pOut_buf_cur[0] = pSrc[0]; - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur[2] = pSrc[2]; - pOut_buf_cur += 3; pSrc += 3; - } while ((int)(counter -= 3) > 2); - if ((int)counter > 0) - { - pOut_buf_cur[0] = pSrc[0]; - if ((int)counter > 1) - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur += counter; + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { + TINFL_SKIP_BITS(32, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } - } } - } while (!(r->m_final & 1)); - if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) - { - TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } - } - TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); - TINFL_CR_FINISH + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH common_exit: - r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; - *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; - if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) - { - const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; - mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; - while (buf_len) - { - for (i = 0; i + 7 < block_len; i += 8, ptr += 8) - { - s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; - s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; - } - for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; - s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf; + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && + (status >= 0)) { + const mz_uint8* ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && + (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; } - r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; - } - return status; + return status; } #ifdef _MSC_VER @@ -1750,1144 +2122,1545 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex #endif // Higher level helper functions. -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) -{ - tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; - *pOut_len = 0; - tinfl_init(&decomp); - for ( ; ; ) - { - size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; - tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, - (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); - if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) - { - MZ_FREE(pBuf); *pOut_len = 0; return NULL; - } - src_buf_ofs += src_buf_size; - *pOut_len += dst_buf_size; - if (status == TINFL_STATUS_DONE) break; - new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; - pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); - if (!pNew_buf) - { - MZ_FREE(pBuf); *pOut_len = 0; return NULL; +void* tinfl_decompress_mem_to_heap(const void* pSrc_buf, size_t src_buf_len, size_t* pOut_len, + int flags) { + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) { + size_t src_buf_size = src_buf_len - src_buf_ofs, + dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress( + &decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, + pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; } - pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; - } - return pBuf; + return pBuf; } -size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) -{ - tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); - status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); - return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +size_t tinfl_decompress_mem_to_mem(void* pOut_buf, size_t out_buf_len, const void* pSrc_buf, + size_t src_buf_len, int flags) { + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, + (mz_uint8*)pOut_buf, &out_buf_len, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; } -int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) -{ - int result = 0; - tinfl_decompressor decomp; - mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; - if (!pDict) - return TINFL_STATUS_FAILED; - tinfl_init(&decomp); - for ( ; ; ) - { - size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; - tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, - (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); - in_buf_ofs += in_buf_size; - if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) - break; - if (status != TINFL_STATUS_HAS_MORE_OUTPUT) - { - result = (status == TINFL_STATUS_DONE); - break; +int tinfl_decompress_mem_to_callback(const void* pIn_buf, size_t* pIn_buf_size, + tinfl_put_buf_func_ptr pPut_buf_func, void* pPut_buf_user, + int flags) { + int result = 0; + tinfl_decompressor decomp; + mz_uint8* pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, + dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress( + &decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, + &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && + (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); } - dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); - } - MZ_FREE(pDict); - *pIn_buf_size = in_buf_ofs; - return result; + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; } // ------------------- Low-level Compression (independent from all decompression API's) // Purposely making these tables static for faster init and thread safety. static const mz_uint16 s_tdefl_len_sym[256] = { - 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, - 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, - 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, - 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, - 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, - 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, - 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, - 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; + 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, + 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, 273, 273, 273, 273, 273, 273, + 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, + 276, 276, 276, 276, 276, 276, 276, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, + 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, + 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, + 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 282, 282, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, 284, 284, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 285}; static const mz_uint8 s_tdefl_len_extra[256] = { - 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, - 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0}; static const mz_uint8 s_tdefl_small_dist_sym[512] = { - 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, - 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, - 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, - 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, - 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, - 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, - 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, - 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, - 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, - 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, - 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, - 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17}; static const mz_uint8 s_tdefl_small_dist_extra[512] = { - 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7 }; + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; static const mz_uint8 s_tdefl_large_dist_sym[128] = { - 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, - 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, - 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; + 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, + 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29}; static const mz_uint8 s_tdefl_large_dist_extra[128] = { - 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, - 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, - 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13}; // Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. -typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; -static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) -{ - mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); - for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } - while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; - for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) - { - const mz_uint32* pHist = &hist[pass << 8]; - mz_uint offsets[256], cur_ofs = 0; - for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } - for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; - { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } - } - return pCur_syms; +typedef struct { + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, + tdefl_sym_freq* pSyms1) { + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { + const mz_uint32* pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { + tdefl_sym_freq* t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; } // tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. -static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) -{ - int root, leaf, next, avbl, used, dpth; - if (n==0) return; else if (n==1) { A[0].m_key = 1; return; } - A[0].m_key += A[1].m_key; root = 0; leaf = 2; - for (next=1; next < n-1; next++) - { - if (leaf>=n || A[root].m_key=n || (root=0; next--) A[next].m_key = A[A[next].m_key].m_key+1; - avbl = 1; used = dpth = 0; root = n-2; next = n-1; - while (avbl>0) - { - while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; } - while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } - avbl = 2*used; dpth++; used = 0; - } +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq* A, int n) { + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) { + if (leaf >= n || A[root].m_key < A[leaf].m_key) { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) { + while (root >= 0 && (int)A[root].m_key == dpth) { + used++; + root--; + } + while (avbl > used) { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } } // Limits canonical Huffman code table's max code size. -enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; -static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) -{ - int i; mz_uint32 total = 0; if (code_list_len <= 1) return; - for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; - for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); - while (total != (1UL << max_code_size)) - { - pNum_codes[max_code_size]--; - for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } - total--; - } +enum { + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 +}; +static void tdefl_huffman_enforce_max_code_size(int* pNum_codes, int code_list_len, + int max_code_size) { + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } } -static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) -{ - int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); - if (static_table) - { - for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; - } - else - { - tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; - int num_used_syms = 0; - const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; - for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } - - pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); - - for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; - - tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); - - MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); - for (i = 1, j = num_used_syms; i <= code_size_limit; i++) - for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); - } - - next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); - - for (i = 0; i < table_len; i++) - { - mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; - code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); - d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; - } +static void tdefl_optimize_huffman_table(tdefl_compressor* d, int table_num, int table_len, + int code_size_limit, int static_table) { + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_OBJ(num_codes); + if (static_table) { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } else { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16* pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } } -#define TDEFL_PUT_BITS(b, l) do { \ - mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ - d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ - while (d->m_bits_in >= 8) { \ - if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ - *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ - d->m_bit_buffer >>= 8; \ - d->m_bits_in -= 8; \ - } \ -} MZ_MACRO_END - -#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ - if (rle_repeat_count < 3) { \ - d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ - while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ - } else { \ - d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ -} rle_repeat_count = 0; } } - -#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ - if (rle_z_count < 3) { \ - d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ - } else if (rle_z_count <= 10) { \ - d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ - } else { \ - d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ -} rle_z_count = 0; } } - -static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - -static void tdefl_start_dynamic_block(tdefl_compressor *d) -{ - int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; - mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; - - d->m_huff_count[0][256] = 1; - - tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); - tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); - - for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; - for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; - - memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); - memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); - total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; - - memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); - for (i = 0; i < total_code_sizes_to_pack; i++) - { - mz_uint8 code_size = code_sizes_to_pack[i]; - if (!code_size) - { - TDEFL_RLE_PREV_CODE_SIZE(); - if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } +#define TDEFL_PUT_BITS(b, l) \ + do { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = \ + (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ } - else - { - TDEFL_RLE_ZERO_CODE_SIZE(); - if (code_size != prev_code_size) - { - TDEFL_RLE_PREV_CODE_SIZE(); - d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; - } - else if (++rle_repeat_count == 6) - { - TDEFL_RLE_PREV_CODE_SIZE(); - } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ } - prev_code_size = code_size; - } - if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } - tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, + 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +static void tdefl_start_dynamic_block(tdefl_compressor* d) { + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, + packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], + packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], + prev_code_size = 0xFF; - TDEFL_PUT_BITS(2, 2); + d->m_huff_count[0][256] = 1; - TDEFL_PUT_BITS(num_lit_codes - 257, 5); - TDEFL_PUT_BITS(num_dist_codes - 1, 5); + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); - for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; - num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); - for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } else { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } else if (++rle_repeat_count == 6) { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { + TDEFL_RLE_PREV_CODE_SIZE(); + } else { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); - for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) - { - mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); - TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); - if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); - } + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } } -static void tdefl_start_static_block(tdefl_compressor *d) -{ - mz_uint i; - mz_uint8 *p = &d->m_huff_code_sizes[0][0]; +static void tdefl_start_static_block(tdefl_compressor* d) { + mz_uint i; + mz_uint8* p = &d->m_huff_code_sizes[0][0]; - for (i = 0; i <= 143; ++i) *p++ = 8; - for ( ; i <= 255; ++i) *p++ = 9; - for ( ; i <= 279; ++i) *p++ = 7; - for ( ; i <= 287; ++i) *p++ = 8; + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; - memset(d->m_huff_code_sizes[1], 5, 32); + memset(d->m_huff_code_sizes[1], 5, 32); - tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); - tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); - TDEFL_PUT_BITS(1, 2); + TDEFL_PUT_BITS(1, 2); } -static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; +static const mz_uint mz_bitmasks[17] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, + 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, + 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}; #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS -static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) -{ - mz_uint flags; - mz_uint8 *pLZ_codes; - mz_uint8 *pOutput_buf = d->m_pOutput_buf; - mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; - mz_uint64 bit_buffer = d->m_bit_buffer; - mz_uint bits_in = d->m_bits_in; - -#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } - - flags = 1; - for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) - { - if (flags == 1) - flags = *pLZ_codes++ | 0x100; - - if (flags & 1) - { - mz_uint s0, s1, n0, n1, sym, num_extra_bits; - mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; - - MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); - - // This sequence coaxes MSVC into using cmov's vs. jmp's. - s0 = s_tdefl_small_dist_sym[match_dist & 511]; - n0 = s_tdefl_small_dist_extra[match_dist & 511]; - s1 = s_tdefl_large_dist_sym[match_dist >> 8]; - n1 = s_tdefl_large_dist_extra[match_dist >> 8]; - sym = (match_dist < 512) ? s0 : s1; - num_extra_bits = (match_dist < 512) ? n0 : n1; - - MZ_ASSERT(d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); +static mz_bool tdefl_compress_lz_codes(tdefl_compressor* d) { + mz_uint flags; + mz_uint8* pLZ_codes; + mz_uint8* pOutput_buf = d->m_pOutput_buf; + mz_uint8* pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ } - else - { - mz_uint lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - - if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) - { - flags >>= 1; - lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - - if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) - { - flags >>= 1; - lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16*)(pLZ_codes + 1); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], + d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], + s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } else { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } } - } - } - if (pOutput_buf >= d->m_pOutput_buf_end) - return MZ_FALSE; + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; - *(mz_uint64*)pOutput_buf = bit_buffer; - pOutput_buf += (bits_in >> 3); - bit_buffer >>= (bits_in & ~7); - bits_in &= 7; - } + *(mz_uint64*)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } #undef TDEFL_PUT_BITS_FAST - d->m_pOutput_buf = pOutput_buf; - d->m_bits_in = 0; - d->m_bit_buffer = 0; + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; - while (bits_in) - { - mz_uint32 n = MZ_MIN(bits_in, 16); - TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); - bit_buffer >>= n; - bits_in -= n; - } + while (bits_in) { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } - TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); - return (d->m_pOutput_buf < d->m_pOutput_buf_end); + return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #else -static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) -{ - mz_uint flags; - mz_uint8 *pLZ_codes; - - flags = 1; - for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) - { - if (flags == 1) - flags = *pLZ_codes++ | 0x100; - if (flags & 1) - { - mz_uint sym, num_extra_bits; - mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; - - MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); - - if (match_dist < 512) - { - sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; - } - else - { - sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; - } - MZ_ASSERT(d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); - } - else - { - mz_uint lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); +static mz_bool tdefl_compress_lz_codes(tdefl_compressor* d) { + mz_uint flags; + mz_uint8* pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], + d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], + s_tdefl_len_extra[match_len]); + + if (match_dist < 512) { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } else { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } else { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } } - } - TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); - return (d->m_pOutput_buf < d->m_pOutput_buf_end); + return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS -static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) -{ - if (static_block) - tdefl_start_static_block(d); - else - tdefl_start_dynamic_block(d); - return tdefl_compress_lz_codes(d); +static mz_bool tdefl_compress_block(tdefl_compressor* d, mz_bool static_block) { + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); } -static int tdefl_flush_block(tdefl_compressor *d, int flush) -{ - mz_uint saved_bit_buf, saved_bits_in; - mz_uint8 *pSaved_output_buf; - mz_bool comp_block_succeeded = MZ_FALSE; - int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; - mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; - - d->m_pOutput_buf = pOutput_buf_start; - d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; - - MZ_ASSERT(!d->m_output_flush_remaining); - d->m_output_flush_ofs = 0; - d->m_output_flush_remaining = 0; - - *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); - d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); - - if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) - { - TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); - } - - TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); - - pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; - - if (!use_raw_block) - comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); - - // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. - if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && - ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) ) - { - mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; - TDEFL_PUT_BITS(0, 2); - if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } - for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) - { - TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); +static int tdefl_flush_block(tdefl_compressor* d, int flush) { + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8* pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && + (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8* pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && + ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) + ? ((mz_uint8*)d->m_pOut_buf + d->m_out_buf_ofs) + : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { + TDEFL_PUT_BITS(0x78, 8); + TDEFL_PUT_BITS(0x01, 8); } - for (i = 0; i < d->m_total_lz_bytes; ++i) - { - TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); - } - } - // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. - else if (!comp_block_succeeded) - { - d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; - tdefl_compress_block(d, MZ_TRUE); - } - - if (flush) - { - if (flush == TDEFL_FINISH) - { - if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } - if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block( + d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. + if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } } - else - { - mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } + // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); } - } - - MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); - - memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); - memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); - - d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; - if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) - { - if (d->m_pPut_buf_func) - { - *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; - if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) - return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); - } - else if (pOutput_buf_start == d->m_output_buf) - { - int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); - memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); - d->m_out_buf_ofs += bytes_to_copy; - if ((n -= bytes_to_copy) != 0) - { - d->m_output_flush_ofs = bytes_to_copy; - d->m_output_flush_remaining = n; - } + if (flush) { + if (flush == TDEFL_FINISH) { + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } else { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } } - else - { - d->m_out_buf_ofs += n; + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { + if (d->m_pPut_buf_func) { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8*)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } else if (pOutput_buf_start == d->m_output_buf) { + int bytes_to_copy = + (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8*)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } else { + d->m_out_buf_ofs += n; + } } - } - return d->m_output_flush_remaining; + return d->m_output_flush_remaining; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) -static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) -{ - mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; - mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; - const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; - mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); - MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; - for ( ; ; ) - { - for ( ; ; ) - { - if (--num_probes_left == 0) return; - #define TDEFL_PROBE \ - next_probe_pos = d->m_next[probe_pos]; \ - if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ - probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ - if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; - TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; - } - if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; - do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && - (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); - if (!probe_len) - { - *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; - } - else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) - { - *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; - c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor* d, mz_uint lookahead_pos, + mz_uint max_dist, mz_uint max_match_len, + mz_uint* pMatch_dist, mz_uint* pMatch_len) { + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, + probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), + s01 = TDEFL_READ_UNALIGNED_WORD(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) { + for (;;) { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16*)(d->m_dict + probe_pos); + if (TDEFL_READ_UNALIGNED_WORD(q) != s01) + continue; + p = s; + probe_len = 32; + do { + } while ((TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (--probe_len > 0)); + if (!probe_len) { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); + break; + } else if ((probe_len = ((mz_uint)(p - s) * 2) + + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > + match_len) { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } } - } } #else -static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) -{ - mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; - mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; - const mz_uint8 *s = d->m_dict + pos, *p, *q; - mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; - MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; - for ( ; ; ) - { - for ( ; ; ) - { - if (--num_probes_left == 0) return; - #define TDEFL_PROBE \ - next_probe_pos = d->m_next[probe_pos]; \ - if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ - probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ - if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; - TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; - } - if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; - if (probe_len > match_len) - { - *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; - c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor* d, mz_uint lookahead_pos, + mz_uint max_dist, mz_uint max_match_len, + mz_uint* pMatch_dist, mz_uint* pMatch_len) { + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, + probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) { + for (;;) { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } } - } } #endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN -static mz_bool tdefl_compress_fast(tdefl_compressor *d) -{ - // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. - mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; - mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; - mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; - - while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) - { - const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; - mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; - mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); - d->m_src_buf_left -= num_bytes_to_process; - lookahead_size += num_bytes_to_process; - - while (num_bytes_to_process) - { - mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); - memcpy(d->m_dict + dst_pos, d->m_pSrc, n); - if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) - memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); - d->m_pSrc += n; - dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; - num_bytes_to_process -= n; - } +static mz_bool tdefl_compress_fast(tdefl_compressor* d) { + // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, + dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, + num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = + (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, + MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } - dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); - if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; - while (lookahead_size >= 4) - { - mz_uint cur_match_dist, cur_match_len = 1; - mz_uint8 *pCur_dict = d->m_dict + cur_pos; - mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; - mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; - mz_uint probe_pos = d->m_hash[hash]; - d->m_hash[hash] = (mz_uint16)lookahead_pos; - - if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) - { - const mz_uint16 *p = (const mz_uint16 *)pCur_dict; - const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); - mz_uint32 probe_len = 32; - do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && - (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); - cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); - if (!probe_len) - cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; - - if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U))) - { - cur_match_len = 1; - *pLZ_code_buf++ = (mz_uint8)first_trigram; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - d->m_huff_count[0][(mz_uint8)first_trigram]++; - } - else - { - mz_uint32 s0, s1; - cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + while (lookahead_size >= 4) { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8* pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32*)pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & + TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && + ((*(const mz_uint32*)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & + 0xFFFFFF) == first_trigram)) { + const mz_uint16* p = (const mz_uint16*)pCur_dict; + const mz_uint16* q = (const mz_uint16*)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do { + } while ((TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16*)pCur_dict) * 2) + + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || + ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } else { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && + (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16*)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } else { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } - MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + if (--num_flags_left == 0) { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } - cur_match_dist--; + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } - pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); - *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; - pLZ_code_buf += 3; - *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + while (lookahead_size) { + mz_uint8 lit = d->m_dict[cur_pos]; - s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; - s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; - d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } - d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } } - } - else - { - *pLZ_code_buf++ = (mz_uint8)first_trigram; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - d->m_huff_count[0][(mz_uint8)first_trigram]++; - } - - if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } - - total_lz_bytes += cur_match_len; - lookahead_pos += cur_match_len; - dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); - cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; - MZ_ASSERT(lookahead_size >= cur_match_len); - lookahead_size -= cur_match_len; - - if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) - { - int n; - d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; - total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; - } - } - - while (lookahead_size) - { - mz_uint8 lit = d->m_dict[cur_pos]; - - total_lz_bytes++; - *pLZ_code_buf++ = lit; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } - - d->m_huff_count[0][lit]++; - - lookahead_pos++; - dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); - cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; - lookahead_size--; - - if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) - { - int n; - d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; - total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; - } } - } - d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; - return MZ_TRUE; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; } #endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN -static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) -{ - d->m_total_lz_bytes++; - *d->m_pLZ_code_buf++ = lit; - *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } - d->m_huff_count[0][lit]++; +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor* d, mz_uint8 lit) { + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; } -static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) -{ - mz_uint32 s0, s1; +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor* d, mz_uint match_len, + mz_uint match_dist) { + mz_uint32 s0, s1; - MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && + (match_dist <= TDEFL_LZ_DICT_SIZE)); - d->m_total_lz_bytes += match_len; + d->m_total_lz_bytes += match_len; - d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); - match_dist -= 1; - d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); - d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; - *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } - s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; - d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; - if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; + if (match_len >= TDEFL_MIN_MATCH_LEN) + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; } -static mz_bool tdefl_compress_normal(tdefl_compressor *d) -{ - const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; - tdefl_flush flush = d->m_flush; - - while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) - { - mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; - // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. - if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) - { - mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; - mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; - mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); - const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; - src_buf_left -= num_bytes_to_process; - d->m_lookahead_size += num_bytes_to_process; - while (pSrc != pSrc_end) - { - mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; - hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); - d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); - dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; - } - } - else - { - while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) - { - mz_uint8 c = *pSrc++; - mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; - src_buf_left--; - d->m_dict[dst_pos] = c; - if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) - d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; - if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) - { - mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; - mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); - d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); +static mz_bool tdefl_compress_normal(tdefl_compressor* d) { + const mz_uint8* pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, + ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ + d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = + (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8* pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } else { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = + (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] + << (TDEFL_LZ_HASH_SHIFT * 2)) ^ + (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] + << TDEFL_LZ_HASH_SHIFT) ^ + c) & + (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } } - } - } - d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); - if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) - break; + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; - // Simple lazy/greedy parsing state machine. - len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; - if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) - { - if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) - { - mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; - cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } - if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; - } - } - else - { - tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); - } - if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) - { - cur_match_dist = cur_match_len = 0; - } - if (d->m_saved_match_len) - { - if (cur_match_len > d->m_saved_match_len) - { - tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); - if (cur_match_len >= 128) - { - tdefl_record_match(d, cur_match_len, cur_match_dist); - d->m_saved_match_len = 0; len_to_move = cur_match_len; + // Simple lazy/greedy parsing state machine. + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } else { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, + &cur_match_dist, &cur_match_len); } - else - { - d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || + (cur_pos == cur_match_dist) || + ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) { + if (cur_match_len > d->m_saved_match_len) { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } else { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } else { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || + (cur_match_len >= 128)) { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } else { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output buffer. + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && + (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= + d->m_total_lz_bytes) || + (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; } - } - else - { - tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); - len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; - } - } - else if (!cur_match_dist) - tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); - else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) - { - tdefl_record_match(d, cur_match_len, cur_match_dist); - len_to_move = cur_match_len; } - else - { - d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor* d) { + if (d->m_pIn_buf_size) { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8*)d->m_pIn_buf; } - // Move the lookahead forward by len_to_move bytes. - d->m_lookahead_pos += len_to_move; - MZ_ASSERT(d->m_lookahead_size >= len_to_move); - d->m_lookahead_size -= len_to_move; - d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); - // Check if it's time to flush the current LZ codes to the internal output buffer. - if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || - ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) ) - { - int n; - d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; + + if (d->m_pOut_buf_size) { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8*)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, + n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; } - } - d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; - return MZ_TRUE; + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; } -static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) -{ - if (d->m_pIn_buf_size) - { - *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; - } - - if (d->m_pOut_buf_size) - { - size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); - memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); - d->m_output_flush_ofs += (mz_uint)n; - d->m_output_flush_remaining -= (mz_uint)n; - d->m_out_buf_ofs += n; - - *d->m_pOut_buf_size = d->m_out_buf_ofs; - } - - return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; -} +tdefl_status tdefl_compress(tdefl_compressor* d, const void* pIn_buf, size_t* pIn_buf_size, + void* pOut_buf, size_t* pOut_buf_size, tdefl_flush flush) { + if (!d) { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } -tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) -{ - if (!d) - { - if (pIn_buf_size) *pIn_buf_size = 0; - if (pOut_buf_size) *pOut_buf_size = 0; - return TDEFL_STATUS_BAD_PARAM; - } - - d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; - d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; - d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; - d->m_out_buf_ofs = 0; - d->m_flush = flush; - - if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || - (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) ) - { - if (pIn_buf_size) *pIn_buf_size = 0; - if (pOut_buf_size) *pOut_buf_size = 0; - return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); - } - d->m_wants_to_finish |= (flush == TDEFL_FINISH); - - if ((d->m_output_flush_remaining) || (d->m_finished)) - return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8*)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || + (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || + (pIn_buf_size && *pIn_buf_size && !pIn_buf) || + (pOut_buf_size && *pOut_buf_size && !pOut_buf)) { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN - if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && - ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && - ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) - { - if (!tdefl_compress_fast(d)) - return d->m_prev_return_status; - } - else + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == + 0)) { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } else #endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN - { - if (!tdefl_compress_normal(d)) - return d->m_prev_return_status; - } - - if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) - d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); - - if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) - { - if (tdefl_flush_block(d, flush) < 0) - return d->m_prev_return_status; - d->m_finished = (flush == TDEFL_FINISH); - if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } - } - - return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8*)pIn_buf, + d->m_pSrc - (const mz_uint8*)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && + (!d->m_output_flush_remaining)) { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); } -tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) -{ - MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +tdefl_status tdefl_compress_buffer(tdefl_compressor* d, const void* pIn_buf, size_t in_buf_size, + tdefl_flush flush) { + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); } -tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) -{ - d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; - d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; - d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; - if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); - d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; - d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; - d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; - d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; - d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; - d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; - d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; - d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; - memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); - memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); - return TDEFL_STATUS_OKAY; +tdefl_status tdefl_init(tdefl_compressor* d, tdefl_put_buf_func_ptr pPut_buf_func, + void* pPut_buf_user, int flags) { + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = + d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = + d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; } -tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) -{ - return d->m_prev_return_status; +tdefl_status tdefl_get_prev_return_status(tdefl_compressor* d) { + return d->m_prev_return_status; } -mz_uint32 tdefl_get_adler32(tdefl_compressor *d) -{ - return d->m_adler32; +mz_uint32 tdefl_get_adler32(tdefl_compressor* d) { + return d->m_adler32; } -mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) -{ - tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; - pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; - succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); - succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); - MZ_FREE(pComp); return succeeded; +mz_bool tdefl_compress_mem_to_output(const void* pBuf, size_t buf_len, + tdefl_put_buf_func_ptr pPut_buf_func, void* pPut_buf_user, + int flags) { + tdefl_compressor* pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && + (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; } -typedef struct -{ - size_t m_size, m_capacity; - mz_uint8 *m_pBuf; - mz_bool m_expandable; +typedef struct { + size_t m_size, m_capacity; + mz_uint8* m_pBuf; + mz_bool m_expandable; } tdefl_output_buffer; -static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) -{ - tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; - size_t new_size = p->m_size + len; - if (new_size > p->m_capacity) - { - size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; - do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); - pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; - p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; - } - memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; - return MZ_TRUE; +static mz_bool tdefl_output_buffer_putter(const void* pBuf, int len, void* pUser) { + tdefl_output_buffer* p = (tdefl_output_buffer*)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) { + size_t new_capacity = p->m_capacity; + mz_uint8* pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; } -void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) -{ - tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); - if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; - out_buf.m_expandable = MZ_TRUE; - if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; - *pOut_len = out_buf.m_size; return out_buf.m_pBuf; +void* tdefl_compress_mem_to_heap(const void* pSrc_buf, size_t src_buf_len, size_t* pOut_len, + int flags) { + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, + flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; } -size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) -{ - tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); - if (!pOut_buf) return 0; - out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; - if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; - return out_buf.m_size; +size_t tdefl_compress_mem_to_mem(void* pOut_buf, size_t out_buf_len, const void* pSrc_buf, + size_t src_buf_len, int flags) { + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8*)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, + flags)) + return 0; + return out_buf.m_size; } #ifndef MINIZ_NO_ZLIB_APIS -static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; +static const mz_uint s_tdefl_num_probes[11] = {0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500}; // level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). -mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) -{ - mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); - if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; - - if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; - else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; - else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; - else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; - else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; - - return comp_flags; +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) { + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | + ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; } #endif //MINIZ_NO_ZLIB_APIS #ifdef _MSC_VER -#pragma warning (push) -#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) +#pragma warning(push) +#pragma warning( \ + disable : 4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) #endif // Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at // http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. // This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. -void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) -{ - // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. - static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; - tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; - if (!pComp) return NULL; - MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } - // write dummy header - for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); - // compress image data - tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); - for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } - if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } - // write real header - *pLen_out = out_buf.m_size-41; - { - static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; - mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, - 0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, - (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54}; - c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24); - memcpy(out_buf.m_pBuf, pnghdr, 41); - } - // write footer (IDAT CRC-32, followed by IEND chunk) - if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } - c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); - // compute final size of file, grab compressed data buffer and return - *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; +void* tdefl_write_image_to_png_file_in_memory_ex(const void* pImage, int w, int h, int num_chans, + size_t* pLen_out, mz_uint level, mz_bool flip) { + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. + static const mz_uint s_tdefl_png_num_probes[11] = {0, 1, 6, 32, 16, 32, + 128, 256, 512, 768, 1500}; + tdefl_compressor* pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { + MZ_FREE(pComp); + return NULL; + } + // write dummy header + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, + s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, + TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + // write real header + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41] = {0x89, + 0x50, + 0x4e, + 0x47, + 0x0d, + 0x0a, + 0x1a, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x49, + 0x48, + 0x44, + 0x52, + 0, + 0, + (mz_uint8)(w >> 8), + (mz_uint8)w, + 0, + 0, + (mz_uint8)(h >> 8), + (mz_uint8)h, + 8, + chans[num_chans], + 0, + 0, + 0, + 0, + 0, + 0, + 0, + (mz_uint8)(*pLen_out >> 24), + (mz_uint8)(*pLen_out >> 16), + (mz_uint8)(*pLen_out >> 8), + (mz_uint8)*pLen_out, + 0x49, + 0x44, + 0x41, + 0x54}; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8*)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, + &out_buf)) { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; } -void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) -{ - // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) - return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +void* tdefl_write_image_to_png_file_in_memory(const void* pImage, int w, int h, int num_chans, + size_t* pLen_out) { + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, + MZ_FALSE); } #ifdef _MSC_VER -#pragma warning (pop) +#pragma warning(pop) #endif // ------------------- .ZIP archive reading @@ -2895,1120 +3668,1244 @@ void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef MINIZ_NO_STDIO - #define MZ_FILE void * +#define MZ_FILE void* #else - #include - #include - - #if defined(_MSC_VER) || defined(__MINGW64__) - static FILE *mz_fopen(const char *pFilename, const char *pMode) - { - FILE* pFile = NULL; - fopen_s(&pFile, pFilename, pMode); - return pFile; - } - static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) - { - FILE* pFile = NULL; - if (freopen_s(&pFile, pPath, pMode, pStream)) +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW64__) +static FILE* mz_fopen(const char* pFilename, const char* pMode) { + FILE* pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE* mz_freopen(const char* pPath, const char* pMode, FILE* pStream) { + FILE* pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) return NULL; - return pFile; - } - #ifndef MINIZ_NO_TIME - #include - #endif - #define MZ_FILE FILE - #define MZ_FOPEN mz_fopen - #define MZ_FCLOSE fclose - #define MZ_FREAD fread - #define MZ_FWRITE fwrite - #define MZ_FTELL64 _ftelli64 - #define MZ_FSEEK64 _fseeki64 - #define MZ_FILE_STAT_STRUCT _stat - #define MZ_FILE_STAT _stat - #define MZ_FFLUSH fflush - #define MZ_FREOPEN mz_freopen - #define MZ_DELETE_FILE remove - #elif defined(__MINGW32__) - #ifndef MINIZ_NO_TIME - #include - #endif - #define MZ_FILE FILE - #define MZ_FOPEN(f, m) fopen(f, m) - #define MZ_FCLOSE fclose - #define MZ_FREAD fread - #define MZ_FWRITE fwrite - #define MZ_FTELL64 ftello64 - #define MZ_FSEEK64 fseeko64 - #define MZ_FILE_STAT_STRUCT _stat - #define MZ_FILE_STAT _stat - #define MZ_FFLUSH fflush - #define MZ_FREOPEN(f, m, s) freopen(f, m, s) - #define MZ_DELETE_FILE remove - #elif defined(__TINYC__) - #ifndef MINIZ_NO_TIME - #include - #endif - #define MZ_FILE FILE - #define MZ_FOPEN(f, m) fopen(f, m) - #define MZ_FCLOSE fclose - #define MZ_FREAD fread - #define MZ_FWRITE fwrite - #define MZ_FTELL64 ftell - #define MZ_FSEEK64 fseek - #define MZ_FILE_STAT_STRUCT stat - #define MZ_FILE_STAT stat - #define MZ_FFLUSH fflush - #define MZ_FREOPEN(f, m, s) freopen(f, m, s) - #define MZ_DELETE_FILE remove - #elif defined(__GNUC__) && _LARGEFILE64_SOURCE - #ifndef MINIZ_NO_TIME - #include - #endif - #define MZ_FILE FILE - #define MZ_FOPEN(f, m) fopen64(f, m) - #define MZ_FCLOSE fclose - #define MZ_FREAD fread - #define MZ_FWRITE fwrite - #define MZ_FTELL64 ftello64 - #define MZ_FSEEK64 fseeko64 - #define MZ_FILE_STAT_STRUCT stat64 - #define MZ_FILE_STAT stat64 - #define MZ_FFLUSH fflush - #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) - #define MZ_DELETE_FILE remove - #else - #ifndef MINIZ_NO_TIME - #include - #endif - #define MZ_FILE FILE - #define MZ_FOPEN(f, m) fopen(f, m) - #define MZ_FCLOSE fclose - #define MZ_FREAD fread - #define MZ_FWRITE fwrite - #define MZ_FTELL64 ftello - #define MZ_FSEEK64 fseeko - #define MZ_FILE_STAT_STRUCT stat - #define MZ_FILE_STAT stat - #define MZ_FFLUSH fflush - #define MZ_FREOPEN(f, m, s) freopen(f, m, s) - #define MZ_DELETE_FILE remove - #endif // #ifdef _MSC_VER + return pFile; +} +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__GNUC__) && _LARGEFILE64_SOURCE +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#else +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif // #ifdef _MSC_VER #endif // #ifdef MINIZ_NO_STDIO #define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) // Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. -enum -{ - // ZIP archive identifiers and record sizes - MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, - MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, - // Central directory header record offsets - MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, - MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, - MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, - MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, - // Local directory header offsets - MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, - MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, - MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, - // End of central directory offsets - MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, - MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, +enum { + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, }; -typedef struct -{ - void *m_p; - size_t m_size, m_capacity; - mz_uint m_element_size; +typedef struct { + void* m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; } mz_zip_array; -struct mz_zip_internal_state_tag -{ - mz_zip_array m_central_dir; - mz_zip_array m_central_dir_offsets; - mz_zip_array m_sorted_central_dir_offsets; - MZ_FILE *m_pFile; - void *m_pMem; - size_t m_mem_size; - size_t m_mem_capacity; +struct mz_zip_internal_state_tag { + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + MZ_FILE* m_pFile; + void* m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; }; -#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size -#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) \ + (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) \ + ((element_type*)((array_ptr)->m_p))[index] -static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) -{ - pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); - memset(pArray, 0, sizeof(mz_zip_array)); +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive* pZip, mz_zip_array* pArray) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); } -static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) -{ - void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; - if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } - if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; - pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; - return MZ_TRUE; +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive* pZip, mz_zip_array* pArray, + size_t min_new_capacity, mz_uint growing) { + void* pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, + pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; } -static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) -{ - if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } - return MZ_TRUE; +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive* pZip, mz_zip_array* pArray, + size_t new_capacity, mz_uint growing) { + if (new_capacity > pArray->m_capacity) { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; } -static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) -{ - if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } - pArray->m_size = new_size; - return MZ_TRUE; +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive* pZip, mz_zip_array* pArray, + size_t new_size, mz_uint growing) { + if (new_size > pArray->m_capacity) { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; } -static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) -{ - return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive* pZip, mz_zip_array* pArray, + size_t n) { + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); } -static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) -{ - size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; - memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); - return MZ_TRUE; +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive* pZip, mz_zip_array* pArray, + const void* pElements, size_t n) { + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, + n * pArray->m_element_size); + return MZ_TRUE; } #ifndef MINIZ_NO_TIME -static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) -{ - struct tm tm; - memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; - tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; - tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; - return mktime(&tm); +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); } -static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) -{ +static void mz_zip_time_to_dos_time(time_t time, mz_uint16* pDOS_time, mz_uint16* pDOS_date) { #ifdef _MSC_VER - struct tm tm_struct; - struct tm *tm = &tm_struct; - errno_t err = localtime_s(tm, &time); - if (err) - { - *pDOS_date = 0; *pDOS_time = 0; - return; - } + struct tm tm_struct; + struct tm* tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } #else - struct tm *tm = localtime(&time); + struct tm* tm = localtime(&time); #endif - *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); - *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = + (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); } #endif #ifndef MINIZ_NO_STDIO -static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) -{ +static mz_bool mz_zip_get_file_modified_time(const char* pFilename, mz_uint16* pDOS_time, + mz_uint16* pDOS_date) { #ifdef MINIZ_NO_TIME - (void)pFilename; *pDOS_date = *pDOS_time = 0; + (void)pFilename; + *pDOS_date = *pDOS_time = 0; #else - struct MZ_FILE_STAT_STRUCT file_stat; - // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. - if (MZ_FILE_STAT(pFilename, &file_stat) != 0) - return MZ_FALSE; - mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); + struct MZ_FILE_STAT_STRUCT file_stat; + // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); #endif // #ifdef MINIZ_NO_TIME - return MZ_TRUE; + return MZ_TRUE; } #ifndef MINIZ_NO_TIME -static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) -{ - struct utimbuf t; t.actime = access_time; t.modtime = modified_time; - return !utime(pFilename, &t); +static mz_bool mz_zip_set_file_times(const char* pFilename, time_t access_time, + time_t modified_time) { + struct utimbuf t; + t.actime = access_time; + t.modtime = modified_time; + return !utime(pFilename, &t); } #endif // #ifndef MINIZ_NO_TIME #endif // #ifndef MINIZ_NO_STDIO -static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) -{ - (void)flags; - if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) - return MZ_FALSE; +static mz_bool mz_zip_reader_init_internal(mz_zip_archive* pZip, mz_uint32 flags) { + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; - if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; - if (!pZip->m_pFree) pZip->m_pFree = def_free_func; - if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + if (!pZip->m_pAlloc) + pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = def_realloc_func; - pZip->m_zip_mode = MZ_ZIP_MODE_READING; - pZip->m_archive_size = 0; - pZip->m_central_directory_file_ofs = 0; - pZip->m_total_files = 0; + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; - if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) - return MZ_FALSE; - memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); - return MZ_TRUE; + if (NULL == (pZip->m_pState = (mz_zip_internal_state*)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; } -static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) -{ - const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; - const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); - mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); - mz_uint8 l = 0, r = 0; - pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; - pE = pL + MZ_MIN(l_len, r_len); - while (pL < pE) - { - if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) - break; - pL++; pR++; - } - return (pL == pE) ? (l_len < r_len) : (l < r); +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array* pCentral_dir_array, + const mz_zip_array* pCentral_dir_offsets, + mz_uint l_index, mz_uint r_index) { + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), + *pE; + const mz_uint8* pR = + &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), + r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); } -#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END +#define MZ_SWAP_UINT32(a, b) \ + do { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END // Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) -static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) -{ - mz_zip_internal_state *pState = pZip->m_pState; - const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; - const mz_zip_array *pCentral_dir = &pState->m_central_dir; - mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); - const int size = pZip->m_total_files; - int start = (size - 2) >> 1, end; - while (start >= 0) - { - int child, root = start; - for ( ; ; ) - { - if ((child = (root << 1) + 1) >= size) - break; - child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); - if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) - break; - MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive* pZip) { + mz_zip_internal_state* pState = pZip->m_pState; + const mz_zip_array* pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array* pCentral_dir = &pState->m_central_dir; + mz_uint32* pIndices = + &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) { + int child, root = start; + for (;;) { + if ((child = (root << 1) + 1) >= size) + break; + child += (((child + 1) < size) && + (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], + pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + start--; } - start--; - } - - end = size - 1; - while (end > 0) - { - int child, root = 0; - MZ_SWAP_UINT32(pIndices[end], pIndices[0]); - for ( ; ; ) - { - if ((child = (root << 1) + 1) >= end) - break; - child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); - if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) - break; - MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + + end = size - 1; + while (end > 0) { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) { + if ((child = (root << 1) + 1) >= end) + break; + child += (((child + 1) < end) && + mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], + pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; } - end--; - } } -static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) -{ - mz_uint cdir_size, num_this_disk, cdir_disk_index; - mz_uint64 cdir_ofs; - mz_int64 cur_file_ofs; - const mz_uint8 *p; - mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; - mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); - // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. - if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return MZ_FALSE; - // Find the end of central directory record by scanning the file from the end towards the beginning. - cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); - for ( ; ; ) - { - int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) - return MZ_FALSE; - for (i = n - 4; i >= 0; --i) - if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) - break; - if (i >= 0) - { - cur_file_ofs += i; - break; +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive* pZip, mz_uint32 flags) { + mz_uint cdir_size, num_this_disk, cdir_disk_index; + mz_uint64 cdir_ofs; + mz_int64 cur_file_ofs; + const mz_uint8* p; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8* pBuf = (mz_uint8*)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + // Find the end of central directory record by scanning the file from the end towards the beginning. + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + for (i = n - 4; i >= 0; --i) + if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + break; + if (i >= 0) { + cur_file_ofs += i; + break; + } + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= + (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); } - if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) - return MZ_FALSE; - cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); - } - // Read and verify the end of central directory record. - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return MZ_FALSE; - if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || - ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) - return MZ_FALSE; + // Read and verify the end of central directory record. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || + ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != + MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) + return MZ_FALSE; - num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); - cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); - if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) - return MZ_FALSE; + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + if (((num_this_disk | cdir_disk_index) != 0) && + ((num_this_disk != 1) || (cdir_disk_index != 1))) + return MZ_FALSE; - if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) - return MZ_FALSE; + if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < + pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; - cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); - if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) - return MZ_FALSE; + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return MZ_FALSE; - pZip->m_central_directory_file_ofs = cdir_ofs; + pZip->m_central_directory_file_ofs = cdir_ofs; - if (pZip->m_total_files) - { - mz_uint i, n; + if (pZip->m_total_files) { + mz_uint i, n; - // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. - if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || - (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) - return MZ_FALSE; + // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, + MZ_FALSE))) + return MZ_FALSE; - if (sort_central_dir) - { - if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) - return MZ_FALSE; + if (sort_central_dir) { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, + pZip->m_total_files, MZ_FALSE)) + return MZ_FALSE; + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, + cdir_size) != cdir_size) + return MZ_FALSE; + + // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). + p = (const mz_uint8*)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { + mz_uint total_header_size, comp_size, decomp_size, disk_index; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || + (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return MZ_FALSE; + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = + (mz_uint32)(p - (const mz_uint8*)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = + i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || + (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || + (comp_size == 0xFFFFFFFF)) + return MZ_FALSE; + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index != num_this_disk) && (disk_index != 1)) + return MZ_FALSE; + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return MZ_FALSE; + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return MZ_FALSE; + n -= total_header_size; + p += total_header_size; + } } - if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) - return MZ_FALSE; + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); - // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). - p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; - for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) - { - mz_uint total_header_size, comp_size, decomp_size, disk_index; - if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) - return MZ_FALSE; - MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); - if (sort_central_dir) - MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; - comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); - decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) - return MZ_FALSE; - disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); - if ((disk_index != num_this_disk) && (disk_index != 1)) + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive* pZip, mz_uint64 size, mz_uint32 flags) { + if ((!pZip) || (!pZip->m_pRead)) return MZ_FALSE; - if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; - if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); return MZ_FALSE; - n -= total_header_size; p += total_header_size; } - } - - if (sort_central_dir) - mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); - - return MZ_TRUE; -} - -mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) -{ - if ((!pZip) || (!pZip->m_pRead)) - return MZ_FALSE; - if (!mz_zip_reader_init_internal(pZip, flags)) - return MZ_FALSE; - pZip->m_archive_size = size; - if (!mz_zip_reader_read_central_dir(pZip, flags)) - { - mz_zip_reader_end(pZip); - return MZ_FALSE; - } - return MZ_TRUE; + return MZ_TRUE; } -static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) -{ - mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; - size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); - memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); - return s; +static size_t mz_zip_mem_read_func(void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) { + mz_zip_archive* pZip = (mz_zip_archive*)pOpaque; + size_t s = + (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8*)pZip->m_pState->m_pMem + file_ofs, s); + return s; } -mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) -{ - if (!mz_zip_reader_init_internal(pZip, flags)) - return MZ_FALSE; - pZip->m_archive_size = size; - pZip->m_pRead = mz_zip_mem_read_func; - pZip->m_pIO_opaque = pZip; +mz_bool mz_zip_reader_init_mem(mz_zip_archive* pZip, const void* pMem, size_t size, + mz_uint32 flags) { + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; #ifdef __cplusplus - pZip->m_pState->m_pMem = const_cast(pMem); + pZip->m_pState->m_pMem = const_cast(pMem); #else - pZip->m_pState->m_pMem = (void *)pMem; + pZip->m_pState->m_pMem = (void*)pMem; #endif - pZip->m_pState->m_mem_size = size; - if (!mz_zip_reader_read_central_dir(pZip, flags)) - { - mz_zip_reader_end(pZip); - return MZ_FALSE; - } - return MZ_TRUE; + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; } #ifndef MINIZ_NO_STDIO -static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) -{ - mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; - mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); - if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) - return 0; - return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +static size_t mz_zip_file_read_func(void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) { + mz_zip_archive* pZip = (mz_zip_archive*)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || + (((cur_ofs != (mz_int64)file_ofs)) && + (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); } -mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) -{ - mz_uint64 file_size; - MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); - if (!pFile) - return MZ_FALSE; - if (MZ_FSEEK64(pFile, 0, SEEK_END)) - { - MZ_FCLOSE(pFile); - return MZ_FALSE; - } - file_size = MZ_FTELL64(pFile); - if (!mz_zip_reader_init_internal(pZip, flags)) - { - MZ_FCLOSE(pFile); - return MZ_FALSE; - } - pZip->m_pRead = mz_zip_file_read_func; - pZip->m_pIO_opaque = pZip; - pZip->m_pState->m_pFile = pFile; - pZip->m_archive_size = file_size; - if (!mz_zip_reader_read_central_dir(pZip, flags)) - { - mz_zip_reader_end(pZip); - return MZ_FALSE; - } - return MZ_TRUE; +mz_bool mz_zip_reader_init_file(mz_zip_archive* pZip, const char* pFilename, mz_uint32 flags) { + mz_uint64 file_size; + MZ_FILE* pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO -mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) -{ - return pZip ? pZip->m_total_files : 0; +mz_uint mz_zip_reader_get_num_files(mz_zip_archive* pZip) { + return pZip ? pZip->m_total_files : 0; } -static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) -{ - if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) - return NULL; - return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +static MZ_FORCEINLINE const mz_uint8* mz_zip_reader_get_cdh(mz_zip_archive* pZip, + mz_uint file_index) { + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); } -mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) -{ - mz_uint m_bit_flag; - const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); - if (!p) - return MZ_FALSE; - m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); - return (m_bit_flag & 1); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive* pZip, mz_uint file_index) { + mz_uint m_bit_flag; + const mz_uint8* p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); } -mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) -{ - mz_uint filename_len, external_attr; - const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); - if (!p) - return MZ_FALSE; +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive* pZip, mz_uint file_index) { + mz_uint filename_len, external_attr; + const mz_uint8* p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; - // First see if the filename ends with a '/' character. - filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); - if (filename_len) - { - if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') - return MZ_TRUE; - } - - // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. - // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. - // FIXME: Remove this check? Is it necessary - we already check the filename. - external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); - if ((external_attr & 0x10) != 0) - return MZ_TRUE; + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } - return MZ_FALSE; -} + // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. + // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; -mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) -{ - mz_uint n; - const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); - if ((!p) || (!pStat)) return MZ_FALSE; +} - // Unpack the central directory record. - pStat->m_file_index = file_index; - pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); - pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); - pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); - pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); - pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +mz_bool mz_zip_reader_file_stat(mz_zip_archive* pZip, mz_uint file_index, + mz_zip_archive_file_stat* pStat) { + mz_uint n; + const mz_uint8* p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); #ifndef MINIZ_NO_TIME - pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), + MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); #endif - pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); - pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); - pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); - pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); - pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); - - // Copy as much of the filename and comment as possible. - n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); - memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; - - n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); - pStat->m_comment_size = n; - memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; - - return MZ_TRUE; -} + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, + p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), + n); + pStat->m_comment[n] = '\0'; -mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) -{ - mz_uint n; - const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); - if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } - n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); - if (filename_buf_size) - { - n = MZ_MIN(n, filename_buf_size - 1); - memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); - pFilename[n] = '\0'; - } - return n + 1; + return MZ_TRUE; } -static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) -{ - mz_uint i; - if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) - return 0 == memcmp(pA, pB, len); - for (i = 0; i < len; ++i) - if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) - return MZ_FALSE; - return MZ_TRUE; +mz_uint mz_zip_reader_get_filename(mz_zip_archive* pZip, mz_uint file_index, char* pFilename, + mz_uint filename_buf_size) { + mz_uint n; + const mz_uint8* p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { + if (filename_buf_size) + pFilename[0] = '\0'; + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; } -static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) -{ - const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; - mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); - mz_uint8 l = 0, r = 0; - pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; - pE = pL + MZ_MIN(l_len, r_len); - while (pL < pE) - { - if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) - break; - pL++; pR++; - } - return (pL == pE) ? (int)(l_len - r_len) : (l - r); +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char* pA, const char* pB, + mz_uint len, mz_uint flags) { + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; } -static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) -{ - mz_zip_internal_state *pState = pZip->m_pState; - const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; - const mz_zip_array *pCentral_dir = &pState->m_central_dir; - mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); - const int size = pZip->m_total_files; - const mz_uint filename_len = (mz_uint)strlen(pFilename); - int l = 0, h = size - 1; - while (l <= h) - { - int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); - if (!comp) - return file_index; - else if (comp < 0) - l = m + 1; - else - h = m - 1; - } - return -1; +static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array* pCentral_dir_array, + const mz_zip_array* pCentral_dir_offsets, + mz_uint l_index, const char* pR, + mz_uint r_len) { + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), + *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); } -int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) -{ - mz_uint file_index; size_t name_len, comment_len; - if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) - return -1; - if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) - return mz_zip_reader_locate_file_binary_search(pZip, pName); - name_len = strlen(pName); if (name_len > 0xFFFF) return -1; - comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; - for (file_index = 0; file_index < pZip->m_total_files; file_index++) - { - const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); - mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); - const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; - if (filename_len < name_len) - continue; - if (comment_len) - { - mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); - const char *pFile_comment = pFilename + filename_len + file_extra_len; - if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) - continue; +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive* pZip, const char* pFilename) { + mz_zip_internal_state* pState = pZip->m_pState; + const mz_zip_array* pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array* pCentral_dir = &pState->m_central_dir; + mz_uint32* pIndices = + &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) { + int m = (l + h) >> 1, file_index = pIndices[m], + comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, + pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; } - if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) - { - int ofs = filename_len - 1; - do - { - if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) - break; - } while (--ofs >= 0); - ofs++; - pFilename += ofs; filename_len -= ofs; + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive* pZip, const char* pName, const char* pComment, + mz_uint flags) { + mz_uint file_index; + size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && + (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); + if (name_len > 0xFFFF) + return -1; + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > 0xFFFF) + return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) { + const mz_uint8* pHeader = &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char* pFilename = (const char*)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), + file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char* pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || + (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { + int ofs = filename_len - 1; + do { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && + (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; } - if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) - return file_index; - } - return -1; + return -1; } -mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) -{ - int status = TINFL_STATUS_DONE; - mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; - mz_zip_archive_file_stat file_stat; - void *pRead_buf; - mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; - tinfl_decompressor inflator; +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive* pZip, mz_uint file_index, void* pBuf, + size_t buf_size, mz_uint flags, void* pUser_read_buf, + size_t user_read_buf_size) { + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, + read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void* pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8* pLocal_header = (mz_uint8*)local_header_u32; + tinfl_decompressor inflator; - if ((buf_size) && (!pBuf)) - return MZ_FALSE; + if ((buf_size) && (!pBuf)) + return MZ_FALSE; - if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) - return MZ_FALSE; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; - // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) - if (!file_stat.m_comp_size) - return MZ_TRUE; + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; - // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). - // I'm torn how to handle this case - should it fail instead? - if (mz_zip_reader_is_file_a_directory(pZip, file_index)) - return MZ_TRUE; + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; - // Encryption and patch files are not supported. - if (file_stat.m_bit_flag & (1 | 32)) - return MZ_FALSE; + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; - // This function only supports stored and deflate. - if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) - return MZ_FALSE; + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && + (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; - // Ensure supplied output buffer is large enough. - needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; - if (buf_size < needed_size) - return MZ_FALSE; + // Ensure supplied output buffer is large enough. + needed_size = + (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; - // Read and parse the local directory entry. - cur_file_ofs = file_stat.m_local_header_ofs; - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return MZ_FALSE; - if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) - return MZ_FALSE; + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; - cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); - if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) - return MZ_FALSE; + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != + needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || + (mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, (size_t)file_stat.m_uncomp_size) == + file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input buffer. + tinfl_init(&inflator); - if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) - { - // The file is stored or the caller has requested the compressed data. - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) - return MZ_FALSE; - return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); - } - - // Decompress the file either directly from memory or from a file input buffer. - tinfl_init(&inflator); - - if (pZip->m_pState->m_pMem) - { - // Read directly from the archive in memory. - pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; - read_buf_size = read_buf_avail = file_stat.m_comp_size; - comp_remaining = 0; - } - else if (pUser_read_buf) - { - // Use a user provided read buffer. - if (!user_read_buf_size) - return MZ_FALSE; - pRead_buf = (mz_uint8 *)pUser_read_buf; - read_buf_size = user_read_buf_size; - read_buf_avail = 0; - comp_remaining = file_stat.m_comp_size; - } - else - { - // Temporarily allocate a read buffer. - read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (pZip->m_pState->m_pMem) { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8*)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } else if (pUser_read_buf) { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8*)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } else { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); #ifdef _MSC_VER - if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) #else - if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) #endif - return MZ_FALSE; - if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) - return MZ_FALSE; - read_buf_avail = 0; - comp_remaining = file_stat.m_comp_size; - } - - do - { - size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); - if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) - { - read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) - { - status = TINFL_STATUS_FAILED; - break; - } - cur_file_ofs += read_buf_avail; - comp_remaining -= read_buf_avail; - read_buf_ofs = 0; + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do { + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8*)pRead_buf + read_buf_ofs, &in_buf_size, + (mz_uint8*)pBuf, (mz_uint8*)pBuf + out_buf_ofs, &out_buf_size, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | + (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || + (mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, (size_t)file_stat.m_uncomp_size) != + file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; } - in_buf_size = (size_t)read_buf_avail; - status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); - read_buf_avail -= in_buf_size; - read_buf_ofs += in_buf_size; - out_buf_ofs += out_buf_size; - } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); - - if (status == TINFL_STATUS_DONE) - { - // Make sure the entire file was decompressed, and check its CRC. - if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) - status = TINFL_STATUS_FAILED; - } - - if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - - return status == TINFL_STATUS_DONE; + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; } -mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) -{ - int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); - if (file_index < 0) - return MZ_FALSE; - return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive* pZip, const char* pFilename, + void* pBuf, size_t buf_size, mz_uint flags, + void* pUser_read_buf, + size_t user_read_buf_size) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, + pUser_read_buf, user_read_buf_size); } -mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) -{ - return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive* pZip, mz_uint file_index, void* pBuf, + size_t buf_size, mz_uint flags) { + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); } -mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) -{ - return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive* pZip, const char* pFilename, void* pBuf, + size_t buf_size, mz_uint flags) { + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, + 0); } -void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) -{ - mz_uint64 comp_size, uncomp_size, alloc_size; - const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); - void *pBuf; +void* mz_zip_reader_extract_to_heap(mz_zip_archive* pZip, mz_uint file_index, size_t* pSize, + mz_uint flags) { + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8* p = mz_zip_reader_get_cdh(pZip, file_index); + void* pBuf; - if (pSize) - *pSize = 0; - if (!p) - return NULL; + if (pSize) + *pSize = 0; + if (!p) + return NULL; - comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); - uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; #ifdef _MSC_VER - if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) #else - if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) #endif - return NULL; - if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) - return NULL; + return NULL; + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; - if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return NULL; - } + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } - if (pSize) *pSize = (size_t)alloc_size; - return pBuf; + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; } -void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) -{ - int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); - if (file_index < 0) - { - if (pSize) *pSize = 0; - return MZ_FALSE; - } - return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +void* mz_zip_reader_extract_file_to_heap(mz_zip_archive* pZip, const char* pFilename, size_t* pSize, + mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); } -mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) -{ - int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; - mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; - mz_zip_archive_file_stat file_stat; - void *pRead_buf = NULL; void *pWrite_buf = NULL; - mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive* pZip, mz_uint file_index, + mz_file_write_func pCallback, void* pOpaque, + mz_uint flags) { + int status = TINFL_STATUS_DONE; + mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, + cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void* pRead_buf = NULL; + void* pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8* pLocal_header = (mz_uint8*)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; - if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) - return MZ_FALSE; + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; - // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) - if (!file_stat.m_comp_size) - return MZ_TRUE; + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; - // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). - // I'm torn how to handle this case - should it fail instead? - if (mz_zip_reader_is_file_a_directory(pZip, file_index)) - return MZ_TRUE; + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; - // Encryption and patch files are not supported. - if (file_stat.m_bit_flag & (1 | 32)) - return MZ_FALSE; + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && + (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; - // This function only supports stored and deflate. - if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) - return MZ_FALSE; + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; - // Read and parse the local directory entry. - cur_file_ofs = file_stat.m_local_header_ofs; - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return MZ_FALSE; - if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) - return MZ_FALSE; + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; - cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); - if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) - return MZ_FALSE; + // Decompress the file either directly from memory or from a file input buffer. + if (pZip->m_pState->m_pMem) { + pRead_buf = (mz_uint8*)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } else { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } - // Decompress the file either directly from memory or from a file input buffer. - if (pZip->m_pState->m_pMem) - { - pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; - read_buf_size = read_buf_avail = file_stat.m_comp_size; - comp_remaining = 0; - } - else - { - read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); - if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) - return MZ_FALSE; - read_buf_avail = 0; - comp_remaining = file_stat.m_comp_size; - } - - if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) - { - // The file is stored or the caller has requested the compressed data. - if (pZip->m_pState->m_pMem) - { + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) { #ifdef _MSC_VER - if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) #else - if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) #endif - return MZ_FALSE; - if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) - status = TINFL_STATUS_FAILED; - else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) - file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); - cur_file_ofs += file_stat.m_comp_size; - out_buf_ofs += file_stat.m_comp_size; - comp_remaining = 0; - } - else - { - while (comp_remaining) - { - read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) - { - status = TINFL_STATUS_FAILED; - break; - } + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != + file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8*)pRead_buf, + (size_t)file_stat.m_comp_size); + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } else { + while (comp_remaining) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } - if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) - file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8*)pRead_buf, + (size_t)read_buf_avail); - if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) - { - status = TINFL_STATUS_FAILED; - break; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != + read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } } - cur_file_ofs += read_buf_avail; - out_buf_ofs += read_buf_avail; - comp_remaining -= read_buf_avail; - } - } - } - else - { - tinfl_decompressor inflator; - tinfl_init(&inflator); + } else { + tinfl_decompressor inflator; + tinfl_init(&inflator); - if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) - status = TINFL_STATUS_FAILED; - else - { - do - { - mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); - size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); - if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) - { - read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); - if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) - { + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) status = TINFL_STATUS_FAILED; - break; - } - cur_file_ofs += read_buf_avail; - comp_remaining -= read_buf_avail; - read_buf_ofs = 0; - } - - in_buf_size = (size_t)read_buf_avail; - status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); - read_buf_avail -= in_buf_size; - read_buf_ofs += in_buf_size; + else { + do { + mz_uint8* pWrite_buf_cur = + (mz_uint8*)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, + out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } - if (out_buf_size) - { - if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) - { - status = TINFL_STATUS_FAILED; - break; - } - file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); - if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) - { - status = TINFL_STATUS_FAILED; - break; - } + in_buf_size = (size_t)read_buf_avail; + status = + tinfl_decompress(&inflator, (const mz_uint8*)pRead_buf + read_buf_ofs, + &in_buf_size, (mz_uint8*)pWrite_buf, pWrite_buf_cur, + &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != + out_buf_size) { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || + (status == TINFL_STATUS_HAS_MORE_OUTPUT)); } - } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); } - } - if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) - { - // Make sure the entire file was decompressed, and check its CRC. - if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) - status = TINFL_STATUS_FAILED; - } + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } - if (!pZip->m_pState->m_pMem) - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - if (pWrite_buf) - pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); - return status == TINFL_STATUS_DONE; + return status == TINFL_STATUS_DONE; } -mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) -{ - int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); - if (file_index < 0) - return MZ_FALSE; - return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive* pZip, const char* pFilename, + mz_file_write_func pCallback, void* pOpaque, + mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); } #ifndef MINIZ_NO_STDIO -static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) -{ - (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); +static size_t mz_zip_file_write_callback(void* pOpaque, mz_uint64 ofs, const void* pBuf, size_t n) { + (void)ofs; + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); } -mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) -{ - mz_bool status; - mz_zip_archive_file_stat file_stat; - MZ_FILE *pFile; - if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) - return MZ_FALSE; - pFile = MZ_FOPEN(pDst_filename, "wb"); - if (!pFile) - return MZ_FALSE; - status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); - if (MZ_FCLOSE(pFile) == EOF) - return MZ_FALSE; +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive* pZip, mz_uint file_index, + const char* pDst_filename, mz_uint flags) { + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE* pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, + flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; #ifndef MINIZ_NO_TIME - if (status) - mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); #endif - return status; + return status; } #endif // #ifndef MINIZ_NO_STDIO -mz_bool mz_zip_reader_end(mz_zip_archive *pZip) -{ - if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) - return MZ_FALSE; +mz_bool mz_zip_reader_end(mz_zip_archive* pZip) { + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; - if (pZip->m_pState) - { - mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; - mz_zip_array_clear(pZip, &pState->m_central_dir); - mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); - mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + if (pZip->m_pState) { + mz_zip_internal_state* pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO - if (pState->m_pFile) - { - MZ_FCLOSE(pState->m_pFile); - pState->m_pFile = NULL; - } + if (pState->m_pFile) { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } #endif // #ifndef MINIZ_NO_STDIO - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - } - pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; - return MZ_TRUE; + return MZ_TRUE; } #ifndef MINIZ_NO_STDIO -mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) -{ - int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); - if (file_index < 0) - return MZ_FALSE; - return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive* pZip, const char* pArchive_filename, + const char* pDst_filename, mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); } #endif @@ -4016,916 +4913,968 @@ mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pAr #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS -static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } -static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } -#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) -#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) +static void mz_write_le16(mz_uint8* p, mz_uint16 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static void mz_write_le32(mz_uint8* p, mz_uint32 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8*)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8*)(p), (mz_uint32)(v)) -mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) -{ - if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) - return MZ_FALSE; +mz_bool mz_zip_writer_init(mz_zip_archive* pZip, mz_uint64 existing_size) { + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || + (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; - if (pZip->m_file_offset_alignment) - { - // Ensure user specified file offset alignment is a power of 2. - if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) - return MZ_FALSE; - } + if (pZip->m_file_offset_alignment) { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } - if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; - if (!pZip->m_pFree) pZip->m_pFree = def_free_func; - if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + if (!pZip->m_pAlloc) + pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = def_realloc_func; - pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; - pZip->m_archive_size = existing_size; - pZip->m_central_directory_file_ofs = 0; - pZip->m_total_files = 0; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; - if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) - return MZ_FALSE; - memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); - MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); - return MZ_TRUE; + if (NULL == (pZip->m_pState = (mz_zip_internal_state*)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; } -static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) -{ - mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; - mz_zip_internal_state *pState = pZip->m_pState; - mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); +static size_t mz_zip_heap_write_func(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, + size_t n) { + mz_zip_archive* pZip = (mz_zip_archive*)pOpaque; + mz_zip_internal_state* pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); #ifdef _MSC_VER - if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) + if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) #else - if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) + if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) #endif - return 0; - if (new_size > pState->m_mem_capacity) - { - void *pNew_block; - size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; - if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) - return 0; - pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; - } - memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); - pState->m_mem_size = (size_t)new_size; - return n; + return 0; + if (new_size > pState->m_mem_capacity) { + void* pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + while (new_capacity < new_size) + new_capacity *= 2; + if (NULL == + (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8*)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; } -mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) -{ - pZip->m_pWrite = mz_zip_heap_write_func; - pZip->m_pIO_opaque = pZip; - if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) - return MZ_FALSE; - if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) - { - if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) - { - mz_zip_writer_end(pZip); - return MZ_FALSE; +mz_bool mz_zip_writer_init_heap(mz_zip_archive* pZip, size_t size_to_reserve_at_beginning, + size_t initial_allocation_size) { + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != + (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) { + if (NULL == (pZip->m_pState->m_pMem = + pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; } - pZip->m_pState->m_mem_capacity = initial_allocation_size; - } - return MZ_TRUE; + return MZ_TRUE; } #ifndef MINIZ_NO_STDIO -static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) -{ - mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; - mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); - if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) - return 0; - return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +static size_t mz_zip_file_write_func(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, + size_t n) { + mz_zip_archive* pZip = (mz_zip_archive*)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || + (((cur_ofs != (mz_int64)file_ofs)) && + (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); } -mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) -{ - MZ_FILE *pFile; - pZip->m_pWrite = mz_zip_file_write_func; - pZip->m_pIO_opaque = pZip; - if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) - return MZ_FALSE; - if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) - { - mz_zip_writer_end(pZip); - return MZ_FALSE; - } - pZip->m_pState->m_pFile = pFile; - if (size_to_reserve_at_beginning) - { - mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); - do - { - size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) - { +mz_bool mz_zip_writer_init_file(mz_zip_archive* pZip, const char* pFilename, + mz_uint64 size_to_reserve_at_beginning) { + MZ_FILE* pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) { mz_zip_writer_end(pZip); return MZ_FALSE; - } - cur_ofs += n; size_to_reserve_at_beginning -= n; - } while (size_to_reserve_at_beginning); - } - return MZ_TRUE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) { + mz_uint64 cur_ofs = 0; + char buf[4096]; + MZ_CLEAR_OBJ(buf); + do { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO -mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) -{ - mz_zip_internal_state *pState; - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) - return MZ_FALSE; - // No sense in trying to write to an archive that's already at the support max size - if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) - return MZ_FALSE; +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive* pZip, const char* pFilename) { + mz_zip_internal_state* pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max size + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; - pState = pZip->m_pState; + pState = pZip->m_pState; - if (pState->m_pFile) - { + if (pState->m_pFile) { #ifdef MINIZ_NO_STDIO - pFilename; return MZ_FALSE; + pFilename; + return MZ_FALSE; #else - // Archive is being read from stdio - try to reopen as writable. - if (pZip->m_pIO_opaque != pZip) - return MZ_FALSE; - if (!pFilename) - return MZ_FALSE; - pZip->m_pWrite = mz_zip_file_write_func; - if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) - { - // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. - mz_zip_reader_end(pZip); - return MZ_FALSE; - } + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } #endif // #ifdef MINIZ_NO_STDIO - } - else if (pState->m_pMem) - { - // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. - if (pZip->m_pIO_opaque != pZip) - return MZ_FALSE; - pState->m_mem_capacity = pState->m_mem_size; - pZip->m_pWrite = mz_zip_heap_write_func; - } - // Archive is being read via a user provided read function - make sure the user has specified a write function too. - else if (!pZip->m_pWrite) - return MZ_FALSE; + } else if (pState->m_pMem) { + // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; - // Start writing new files at the archive's current central directory location. - pZip->m_archive_size = pZip->m_central_directory_file_ofs; - pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; - pZip->m_central_directory_file_ofs = 0; + // Start writing new files at the archive's current central directory location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; - return MZ_TRUE; + return MZ_TRUE; } -mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) -{ - return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +mz_bool mz_zip_writer_add_mem(mz_zip_archive* pZip, const char* pArchive_name, const void* pBuf, + size_t buf_size, mz_uint level_and_flags) { + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, + 0, 0); } -typedef struct -{ - mz_zip_archive *m_pZip; - mz_uint64 m_cur_archive_file_ofs; - mz_uint64 m_comp_size; +typedef struct { + mz_zip_archive* m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; } mz_zip_writer_add_state; -static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) -{ - mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; - if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) - return MZ_FALSE; - pState->m_cur_archive_file_ofs += len; - pState->m_comp_size += len; - return MZ_TRUE; +static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void* pUser) { + mz_zip_writer_add_state* pState = (mz_zip_writer_add_state*)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, + pBuf, len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; } -static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) -{ - (void)pZip; - memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); - MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); - MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); - MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); - MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); - MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); - return MZ_TRUE; +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive* pZip, mz_uint8* pDst, + mz_uint16 filename_size, mz_uint16 extra_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, + mz_uint32 uncomp_crc32, mz_uint16 method, + mz_uint16 bit_flags, mz_uint16 dos_time, + mz_uint16 dos_date) { + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; } -static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) -{ - (void)pZip; - memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); - MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); - MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); - return MZ_TRUE; +static mz_bool mz_zip_writer_create_central_dir_header( + mz_zip_archive* pZip, mz_uint8* pDst, mz_uint16 filename_size, mz_uint16 extra_size, + mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; } -static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) -{ - mz_zip_internal_state *pState = pZip->m_pState; - mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; - size_t orig_central_dir_size = pState->m_central_dir.m_size; - mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; +static mz_bool mz_zip_writer_add_to_central_dir( + mz_zip_archive* pZip, const char* pFilename, mz_uint16 filename_size, const void* pExtra, + mz_uint16 extra_size, const void* pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, + mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, + mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { + mz_zip_internal_state* pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; - // No zip64 support yet - if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) - return MZ_FALSE; + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || + (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + + extra_size + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; - if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) - return MZ_FALSE; + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, + extra_size, comment_size, uncomp_size, comp_size, + uncomp_crc32, method, bit_flags, dos_time, + dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; - if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || - (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) - { - // Try to push the central directory array back into its original state. - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return MZ_FALSE; - } + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } - return MZ_TRUE; + return MZ_TRUE; } -static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) -{ - // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. - if (*pArchive_name == '/') - return MZ_FALSE; - while (*pArchive_name) - { - if ((*pArchive_name == '\\') || (*pArchive_name == ':')) - return MZ_FALSE; - pArchive_name++; - } - return MZ_TRUE; +static mz_bool mz_zip_writer_validate_archive_name(const char* pArchive_name) { + // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; } -static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) -{ - mz_uint32 n; - if (!pZip->m_file_offset_alignment) - return 0; - n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); - return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive* pZip) { + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); } -static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) -{ - char buf[4096]; - memset(buf, 0, MZ_MIN(sizeof(buf), n)); - while (n) - { - mz_uint32 s = MZ_MIN(sizeof(buf), n); - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) - return MZ_FALSE; - cur_file_ofs += s; n -= s; - } - return MZ_TRUE; +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive* pZip, mz_uint64 cur_file_ofs, + mz_uint32 n) { + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; } -mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) -{ - mz_uint16 method = 0, dos_time = 0, dos_date = 0; - mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; - mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; - size_t archive_name_size; - mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; - tdefl_compressor *pComp = NULL; - mz_bool store_data_uncompressed; - mz_zip_internal_state *pState; - - if ((int)level_and_flags < 0) - level_and_flags = MZ_DEFAULT_LEVEL; - level = level_and_flags & 0xF; - store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); - - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) - return MZ_FALSE; +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive* pZip, const char* pArchive_name, const void* pBuf, + size_t buf_size, const void* pComment, mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, + mz_uint32 uncomp_crc32) { + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, + cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor* pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state* pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || + ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || + (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; - pState = pZip->m_pState; + pState = pZip->m_pState; - if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) - return MZ_FALSE; - // No zip64 support yet - if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) - return MZ_FALSE; - if (!mz_zip_writer_validate_archive_name(pArchive_name)) - return MZ_FALSE; + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; #ifndef MINIZ_NO_TIME - { - time_t cur_time; time(&cur_time); - mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); - } + { + time_t cur_time; + time(&cur_time); + mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); + } #endif // #ifndef MINIZ_NO_TIME - archive_name_size = strlen(pArchive_name); - if (archive_name_size > 0xFFFF) - return MZ_FALSE; + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; - num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); - // no zip64 support yet - if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) - return MZ_FALSE; + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; - if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) - { - // Set DOS Subdirectory attribute bit. - ext_attributes |= 0x10; - // Subdirectories cannot contain data. - if ((buf_size) || (uncomp_size)) - return MZ_FALSE; - } - - // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) - if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) - return MZ_FALSE; + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } - if ((!store_data_uncompressed) && (buf_size)) - { - if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) - return MZ_FALSE; - } + // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + + comment_size)) || + (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; - if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return MZ_FALSE; - } - local_dir_header_ofs += num_alignment_padding_bytes; - if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } - cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); - - MZ_CLEAR_OBJ(local_dir_header); - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return MZ_FALSE; - } - cur_archive_file_ofs += archive_name_size; + if ((!store_data_uncompressed) && (buf_size)) { + if (NULL == (pComp = (tdefl_compressor*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + sizeof(tdefl_compressor)))) + return MZ_FALSE; + } - if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) - { - uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); - uncomp_size = buf_size; - if (uncomp_size <= 3) - { - level = 0; - store_data_uncompressed = MZ_TRUE; + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, + num_alignment_padding_bytes + sizeof(local_dir_header))) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; } - } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); - if (store_data_uncompressed) - { - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return MZ_FALSE; + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, + archive_name_size) != archive_name_size) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) { + level = 0; + store_data_uncompressed = MZ_TRUE; + } } - cur_archive_file_ofs += buf_size; - comp_size = buf_size; + if (store_data_uncompressed) { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } - if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) - method = MZ_DEFLATED; - } - else if (buf_size) - { - mz_zip_writer_add_state state; - - state.m_pZip = pZip; - state.m_cur_archive_file_ofs = cur_archive_file_ofs; - state.m_comp_size = 0; - - if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || - (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - return MZ_FALSE; - } + cur_archive_file_ofs += buf_size; + comp_size = buf_size; - comp_size = state.m_comp_size; - cur_archive_file_ofs = state.m_cur_archive_file_ofs; + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } else if (buf_size) { + mz_zip_writer_add_state state; - method = MZ_DEFLATED; - } + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - pComp = NULL; + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, + tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } - // no zip64 support yet - if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) - return MZ_FALSE; + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; - if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) - return MZ_FALSE; + method = MZ_DEFLATED; + } - if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) - return MZ_FALSE; + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; - if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) - return MZ_FALSE; + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; - pZip->m_total_files++; - pZip->m_archive_size = cur_archive_file_ofs; + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, + 0, uncomp_size, comp_size, uncomp_crc32, method, 0, + dos_time, dos_date)) + return MZ_FALSE; - return MZ_TRUE; -} + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, + sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; -#ifndef MINIZ_NO_STDIO -mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) -{ - mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; - mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; - mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; - size_t archive_name_size; - mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; - MZ_FILE *pSrc_file = NULL; - - if ((int)level_and_flags < 0) - level_and_flags = MZ_DEFAULT_LEVEL; - level = level_and_flags & 0xF; - - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) - return MZ_FALSE; - if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) - return MZ_FALSE; - if (!mz_zip_writer_validate_archive_name(pArchive_name)) - return MZ_FALSE; + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, + 0, pComment, comment_size, uncomp_size, comp_size, + uncomp_crc32, method, 0, dos_time, dos_date, + local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; - archive_name_size = strlen(pArchive_name); - if (archive_name_size > 0xFFFF) - return MZ_FALSE; + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; - num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + return MZ_TRUE; +} - // no zip64 support yet - if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) - return MZ_FALSE; +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive* pZip, const char* pArchive_name, + const char* pSrc_filename, const void* pComment, + mz_uint16 comment_size, mz_uint level_and_flags) { + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, + cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE* pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || + (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; - if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) - return MZ_FALSE; - - pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); - if (!pSrc_file) - return MZ_FALSE; - MZ_FSEEK64(pSrc_file, 0, SEEK_END); - uncomp_size = MZ_FTELL64(pSrc_file); - MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; - if (uncomp_size > 0xFFFFFFFF) - { - // No zip64 support yet - MZ_FCLOSE(pSrc_file); - return MZ_FALSE; - } - if (uncomp_size <= 3) - level = 0; + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); - if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) - { - MZ_FCLOSE(pSrc_file); - return MZ_FALSE; - } - local_dir_header_ofs += num_alignment_padding_bytes; - if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } - cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); - - MZ_CLEAR_OBJ(local_dir_header); - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) - { - MZ_FCLOSE(pSrc_file); - return MZ_FALSE; - } - cur_archive_file_ofs += archive_name_size; - - if (uncomp_size) - { - mz_uint64 uncomp_remaining = uncomp_size; - void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); - if (!pRead_buf) - { - MZ_FCLOSE(pSrc_file); - return MZ_FALSE; - } + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; - if (!level) - { - while (uncomp_remaining) - { - mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); - if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - MZ_FCLOSE(pSrc_file); - return MZ_FALSE; - } - uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); - uncomp_remaining -= n; - cur_archive_file_ofs += n; - } - comp_size = uncomp_size; - } - else - { - mz_bool result = MZ_FALSE; - mz_zip_writer_add_state state; - tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); - if (!pComp) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - MZ_FCLOSE(pSrc_file); + if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) return MZ_FALSE; - } - state.m_pZip = pZip; - state.m_cur_archive_file_ofs = cur_archive_file_ofs; - state.m_comp_size = 0; + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); - if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (uncomp_size > 0xFFFFFFFF) { + // No zip64 support yet MZ_FCLOSE(pSrc_file); return MZ_FALSE; - } + } + if (uncomp_size <= 3) + level = 0; - for ( ; ; ) - { - size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); - tdefl_status status; + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, + num_alignment_padding_bytes + sizeof(local_dir_header))) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); - if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) - break; + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, + archive_name_size) != archive_name_size) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) { + mz_uint64 uncomp_remaining = uncomp_size; + void* pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } - uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); - uncomp_remaining -= in_buf_size; + if (!level) { + while (uncomp_remaining) { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || + (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8*)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } else { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor* pComp = (tdefl_compressor*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + sizeof(tdefl_compressor)); + if (!pComp) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } - status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); - if (status == TDEFL_STATUS_DONE) - { - result = MZ_TRUE; - break; - } - else if (status != TDEFL_STATUS_OKAY) - break; - } + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, + tdefl_create_comp_flags_from_zip_params( + level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } - pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + for (;;) { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; - if (!result) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - MZ_FCLOSE(pSrc_file); - return MZ_FALSE; - } + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; - comp_size = state.m_comp_size; - cur_archive_file_ofs = state.m_cur_archive_file_ofs; + uncomp_crc32 = + (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8*)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; - method = MZ_DEFLATED; - } + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, + uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) { + result = MZ_TRUE; + break; + } else if (status != TDEFL_STATUS_OKAY) + break; + } - pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); - } + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); - MZ_FCLOSE(pSrc_file); pSrc_file = NULL; + if (!result) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } - // no zip64 support yet - if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) - return MZ_FALSE; + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; - if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) - return MZ_FALSE; + method = MZ_DEFLATED; + } - if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) - return MZ_FALSE; + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } - if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) - return MZ_FALSE; + MZ_FCLOSE(pSrc_file); + pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, + 0, uncomp_size, comp_size, uncomp_crc32, method, 0, + dos_time, dos_date)) + return MZ_FALSE; - pZip->m_total_files++; - pZip->m_archive_size = cur_archive_file_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, + sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, + 0, pComment, comment_size, uncomp_size, comp_size, + uncomp_crc32, method, 0, dos_time, dos_date, + local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; - return MZ_TRUE; + return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO -mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) -{ - mz_uint n, bit_flags, num_alignment_padding_bytes; - mz_uint64 comp_bytes_remaining, local_dir_header_ofs; - mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; - mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; - mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; - size_t orig_central_dir_size; - mz_zip_internal_state *pState; - void *pBuf; const mz_uint8 *pSrc_central_header; - - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) - return MZ_FALSE; - if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) - return MZ_FALSE; - pState = pZip->m_pState; +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive* pZip, mz_zip_archive* pSource_zip, + mz_uint file_index) { + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8* pLocal_header = (mz_uint8*)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state* pState; + void* pBuf; + const mz_uint8* pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; - num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); - // no zip64 support yet - if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) - return MZ_FALSE; + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; - cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); - cur_dst_file_ofs = pZip->m_archive_size; + cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; - if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return MZ_FALSE; - if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) - return MZ_FALSE; - cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; - if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) - return MZ_FALSE; - cur_dst_file_ofs += num_alignment_padding_bytes; - local_dir_header_ofs = cur_dst_file_ofs; - if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) - return MZ_FALSE; - cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; - n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); - comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); - if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) - return MZ_FALSE; + if (NULL == (pBuf = pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, + (size_t)MZ_MAX(sizeof(mz_uint32) * 4, + MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) + return MZ_FALSE; - while (comp_bytes_remaining) - { - n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); - if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return MZ_FALSE; - } - cur_src_file_ofs += n; + while (comp_bytes_remaining) { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return MZ_FALSE; + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; } - cur_dst_file_ofs += n; - comp_bytes_remaining -= n; - } + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, + sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } - bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); - if (bit_flags & 8) - { - // Copy data descriptor - if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return MZ_FALSE; - } + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } - n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); - if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - return MZ_FALSE; + cur_src_file_ofs += n; + cur_dst_file_ofs += n; } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - cur_src_file_ofs += n; - cur_dst_file_ofs += n; - } - pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); - - // no zip64 support yet - if (cur_dst_file_ofs > 0xFFFFFFFF) - return MZ_FALSE; + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; - orig_central_dir_size = pState->m_central_dir.m_size; + orig_central_dir_size = pState->m_central_dir.m_size; - memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); - MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) - return MZ_FALSE; + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; - n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) - { - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return MZ_FALSE; - } + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, + pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } - if (pState->m_central_dir.m_size > 0xFFFFFFFF) - return MZ_FALSE; - n = (mz_uint32)orig_central_dir_size; - if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) - { - mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); - return MZ_FALSE; - } + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } - pZip->m_total_files++; - pZip->m_archive_size = cur_dst_file_ofs; + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; - return MZ_TRUE; + return MZ_TRUE; } -mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) -{ - mz_zip_internal_state *pState; - mz_uint64 central_dir_ofs, central_dir_size; - mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive* pZip) { + mz_zip_internal_state* pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) - return MZ_FALSE; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; - pState = pZip->m_pState; + pState = pZip->m_pState; - // no zip64 support yet - if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) - return MZ_FALSE; + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; - central_dir_ofs = 0; - central_dir_size = 0; - if (pZip->m_total_files) - { - // Write central directory - central_dir_ofs = pZip->m_archive_size; - central_dir_size = pState->m_central_dir.m_size; - pZip->m_central_directory_file_ofs = central_dir_ofs; - if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) - return MZ_FALSE; - pZip->m_archive_size += central_dir_size; - } - - // Write end of central directory record - MZ_CLEAR_OBJ(hdr); - MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); - MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); - MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); - MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); - MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); - - if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) - return MZ_FALSE; + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, + (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; #ifndef MINIZ_NO_STDIO - if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) - return MZ_FALSE; + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; #endif // #ifndef MINIZ_NO_STDIO - pZip->m_archive_size += sizeof(hdr); + pZip->m_archive_size += sizeof(hdr); - pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; - return MZ_TRUE; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; } -mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) -{ - if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) - return MZ_FALSE; - if (pZip->m_pWrite != mz_zip_heap_write_func) - return MZ_FALSE; - if (!mz_zip_writer_finalize_archive(pZip)) - return MZ_FALSE; +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive* pZip, void** pBuf, size_t* pSize) { + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; - *pBuf = pZip->m_pState->m_pMem; - *pSize = pZip->m_pState->m_mem_size; - pZip->m_pState->m_pMem = NULL; - pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; - return MZ_TRUE; + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; } -mz_bool mz_zip_writer_end(mz_zip_archive *pZip) -{ - mz_zip_internal_state *pState; - mz_bool status = MZ_TRUE; - if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) - return MZ_FALSE; +mz_bool mz_zip_writer_end(mz_zip_archive* pZip) { + mz_zip_internal_state* pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || + ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; - pState = pZip->m_pState; - pZip->m_pState = NULL; - mz_zip_array_clear(pZip, &pState->m_central_dir); - mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); - mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO - if (pState->m_pFile) - { - MZ_FCLOSE(pState->m_pFile); - pState->m_pFile = NULL; - } + if (pState->m_pFile) { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } #endif // #ifndef MINIZ_NO_STDIO - if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) - { - pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); - pState->m_pMem = NULL; - } + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } - pZip->m_pFree(pZip->m_pAlloc_opaque, pState); - pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; - return status; + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; } #ifndef MINIZ_NO_STDIO -mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) -{ - mz_bool status, created_new_archive = MZ_FALSE; - mz_zip_archive zip_archive; - struct MZ_FILE_STAT_STRUCT file_stat; - MZ_CLEAR_OBJ(zip_archive); - if ((int)level_and_flags < 0) - level_and_flags = MZ_DEFAULT_LEVEL; - if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) - return MZ_FALSE; - if (!mz_zip_writer_validate_archive_name(pArchive_name)) - return MZ_FALSE; - if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) - { - // Create a new archive. - if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) - return MZ_FALSE; - created_new_archive = MZ_TRUE; - } - else - { - // Append to an existing archive. - if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) - return MZ_FALSE; - if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) - { - mz_zip_reader_end(&zip_archive); - return MZ_FALSE; +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char* pZip_filename, + const char* pArchive_name, const void* pBuf, + size_t buf_size, const void* pComment, + mz_uint16 comment_size, mz_uint level_and_flags) { + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || + ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } else { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, + level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } } - } - status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); - // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) - if (!mz_zip_writer_finalize_archive(&zip_archive)) - status = MZ_FALSE; - if (!mz_zip_writer_end(&zip_archive)) - status = MZ_FALSE; - if ((!status) && (created_new_archive)) - { - // It's a new archive and something went wrong, so just delete it. - int ignoredStatus = MZ_DELETE_FILE(pZip_filename); - (void)ignoredStatus; - } - return status; + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, + comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; } -void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) -{ - int file_index; - mz_zip_archive zip_archive; - void *p = NULL; +void* mz_zip_extract_archive_file_to_heap(const char* pZip_filename, const char* pArchive_name, + size_t* pSize, mz_uint flags) { + int file_index; + mz_zip_archive zip_archive; + void* p = NULL; - if (pSize) - *pSize = 0; + if (pSize) + *pSize = 0; - if ((!pZip_filename) || (!pArchive_name)) - return NULL; + if ((!pZip_filename) || (!pArchive_name)) + return NULL; - MZ_CLEAR_OBJ(zip_archive); - if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) - return NULL; + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, + flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; - if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) - p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); - mz_zip_reader_end(&zip_archive); - return p; + mz_zip_reader_end(&zip_archive); + return p; } #endif // #ifndef MINIZ_NO_STDIO @@ -4968,635 +5917,555 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char */ namespace miniz_cpp { -namespace detail { + namespace detail { #ifdef _WIN32 -char directory_separator = '\\'; -char alt_directory_separator = '/'; + char directory_separator = '\\'; + char alt_directory_separator = '/'; #else -char directory_separator = '/'; -char alt_directory_separator = '\\'; + char directory_separator = '/'; + char alt_directory_separator = '\\'; #endif -std::string join_path(const std::vector &parts) -{ - std::string joined; - std::size_t i = 0; - for(auto part : parts) - { - joined.append(part); - - if(i++ != parts.size() - 1) - { - joined.append(1, '/'); - } - } - return joined; -} - -std::vector split_path(const std::string &path, char delim = directory_separator) -{ - std::vector split; - std::string::size_type previous_index = 0; - auto separator_index = path.find(delim); - - while(separator_index != std::string::npos) - { - auto part = path.substr(previous_index, separator_index - previous_index); - if(part != "..") - { - split.push_back(part); - } - else - { - split.pop_back(); - } - previous_index = separator_index + 1; - separator_index = path.find(delim, previous_index); - } - - split.push_back(path.substr(previous_index)); + std::string join_path(const std::vector& parts) { + std::string joined; + std::size_t i = 0; + for (auto part : parts) { + joined.append(part); - if(split.size() == 1 && delim == directory_separator) - { - auto alternative = split_path(path, alt_directory_separator); - if(alternative.size() > 1) - { - return alternative; + if (i++ != parts.size() - 1) { + joined.append(1, '/'); + } + } + return joined; } - } - - return split; -} - -uint32_t crc32buf(const char *buf, std::size_t len) -{ - uint32_t oldcrc32 = 0xFFFFFFFF; - - uint32_t crc_32_tab[] = { /* CRC polynomial 0xedb88320 */ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, - 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, - 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, - 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, - 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, - 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, - 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, - 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, - 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, - 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, - 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, - 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, - 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, - 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, - 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, - 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, - 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, - 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, - 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, - 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, - 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, - 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, - 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, - 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, - 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, - 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, - 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, - 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, - 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, - 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, - 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d - }; - -#define UPDC32(octet,crc) (crc_32_tab[((crc)\ -^ static_cast(octet)) & 0xff] ^ ((crc) >> 8)) - - for ( ; len; --len, ++buf) - { - oldcrc32 = UPDC32(*buf, oldcrc32); - } - - return ~oldcrc32; -} -tm safe_localtime(const time_t &t) -{ -#ifdef _WIN32 - tm time; - localtime_s(&time, &t); - return time; -#else - tm *time = localtime(&t); - assert(time != nullptr); - return *time; -#endif -} - -std::size_t write_callback(void *opaque, mz_uint64 file_ofs, const void *pBuf, std::size_t n) -{ - auto buffer = static_cast *>(opaque); - - if(file_ofs + n > buffer->size()) - { - auto new_size = static_cast::size_type>(file_ofs + n); - buffer->resize(new_size); - } + std::vector split_path(const std::string& path, + char delim = directory_separator) { + std::vector split; + std::string::size_type previous_index = 0; + auto separator_index = path.find(delim); + + while (separator_index != std::string::npos) { + auto part = path.substr(previous_index, separator_index - previous_index); + if (part != "..") { + split.push_back(part); + } else { + split.pop_back(); + } + previous_index = separator_index + 1; + separator_index = path.find(delim, previous_index); + } - for(std::size_t i = 0; i < n; i++) - { - (*buffer)[static_cast(file_ofs + i)] = (static_cast(pBuf))[i]; - } + split.push_back(path.substr(previous_index)); - return n; -} + if (split.size() == 1 && delim == directory_separator) { + auto alternative = split_path(path, alt_directory_separator); + if (alternative.size() > 1) { + return alternative; + } + } -} // namespace detail + return split; + } -struct zip_info -{ - std::string filename; + uint32_t crc32buf(const char* buf, std::size_t len) { + uint32_t oldcrc32 = 0xFFFFFFFF; + + uint32_t crc_32_tab[] = { + /* CRC polynomial 0xedb88320 */ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, + 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, + 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, + 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, + 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, + 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, + 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, + 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, + 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, + 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, + 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, + 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, + 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, + 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, + 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, + 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, + 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, + 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, + 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, + 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + +#define UPDC32(octet, crc) (crc_32_tab[((crc) ^ static_cast(octet)) & 0xff] ^ ((crc) >> 8)) + + for (; len; --len, ++buf) { + oldcrc32 = UPDC32(*buf, oldcrc32); + } - struct - { - int year = 1980; - int month = 0; - int day = 0; - int hours = 0; - int minutes = 0; - int seconds = 0; - } date_time; - - std::string comment; - std::string extra; - uint16_t create_system = 0; - uint16_t create_version = 0; - uint16_t extract_version = 0; - uint16_t flag_bits = 0; - std::size_t volume = 0; - uint32_t internal_attr = 0; - uint32_t external_attr = 0; - std::size_t header_offset = 0; - uint32_t crc = 0; - std::size_t compress_size = 0; - std::size_t file_size = 0; -}; + return ~oldcrc32; + } -class zip_file -{ -public: - zip_file() : archive_(new mz_zip_archive()) - { - reset(); - } + tm safe_localtime(const time_t& t) { +#ifdef _WIN32 + tm time; + localtime_s(&time, &t); + return time; +#else + tm* time = localtime(&t); + assert(time != nullptr); + return *time; +#endif + } - zip_file(const std::string &filename) : zip_file() - { - load(filename); - } + std::size_t write_callback(void* opaque, mz_uint64 file_ofs, const void* pBuf, + std::size_t n) { + auto buffer = static_cast*>(opaque); - zip_file(std::istream &stream) : zip_file() - { - load(stream); - } + if (file_ofs + n > buffer->size()) { + auto new_size = static_cast::size_type>(file_ofs + n); + buffer->resize(new_size); + } - zip_file(const std::vector &bytes) : zip_file() - { - load(bytes); - } + for (std::size_t i = 0; i < n; i++) { + (*buffer)[static_cast(file_ofs + i)] = + (static_cast(pBuf))[i]; + } - ~zip_file() - { - reset(); - } + return n; + } - void load(std::istream &stream) - { - reset(); - buffer_.assign(std::istreambuf_iterator(stream), std::istreambuf_iterator()); - remove_comment(); - start_read(); - } + } // namespace detail + + struct zip_info { + std::string filename; + + struct { + int year = 1980; + int month = 0; + int day = 0; + int hours = 0; + int minutes = 0; + int seconds = 0; + } date_time; + + std::string comment; + std::string extra; + uint16_t create_system = 0; + uint16_t create_version = 0; + uint16_t extract_version = 0; + uint16_t flag_bits = 0; + std::size_t volume = 0; + uint32_t internal_attr = 0; + uint32_t external_attr = 0; + std::size_t header_offset = 0; + uint32_t crc = 0; + std::size_t compress_size = 0; + std::size_t file_size = 0; + }; - void load(const std::string &filename) - { - filename_ = filename; - std::ifstream stream(filename, std::ios::binary); - load(stream); - } + class zip_file { + public: + zip_file() : archive_(new mz_zip_archive()) { + reset(); + } - void load(const std::vector &bytes) - { - reset(); - buffer_.assign(bytes.begin(), bytes.end()); - remove_comment(); - start_read(); - } + zip_file(const std::string& filename) : zip_file() { + load(filename); + } - void save(const std::string &filename) - { - filename_ = filename; - std::ofstream stream(filename, std::ios::binary); - save(stream); - } + zip_file(std::istream& stream) : zip_file() { + load(stream); + } - void save(std::ostream &stream) - { - if(archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) - { - mz_zip_writer_finalize_archive(archive_.get()); + zip_file(const std::vector& bytes) : zip_file() { + load(bytes); } - - if(archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) - { - mz_zip_writer_end(archive_.get()); + + ~zip_file() { + reset(); } - - if(archive_->m_zip_mode == MZ_ZIP_MODE_INVALID) - { + + void load(std::istream& stream) { + reset(); + buffer_.assign(std::istreambuf_iterator(stream), + std::istreambuf_iterator()); + remove_comment(); start_read(); } - - append_comment(); - stream.write(buffer_.data(), static_cast(buffer_.size())); - } - void save(std::vector &bytes) - { - if(archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) - { - mz_zip_writer_finalize_archive(archive_.get()); - } - - if(archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) - { - mz_zip_writer_end(archive_.get()); + void load(const std::string& filename) { + filename_ = filename; + std::ifstream stream(filename, std::ios::binary); + load(stream); } - - if(archive_->m_zip_mode == MZ_ZIP_MODE_INVALID) - { + + void load(const std::vector& bytes) { + reset(); + buffer_.assign(bytes.begin(), bytes.end()); + remove_comment(); start_read(); } - - append_comment(); - bytes.assign(buffer_.begin(), buffer_.end()); - } - void reset() - { - switch(archive_->m_zip_mode) - { - case MZ_ZIP_MODE_READING: - mz_zip_reader_end(archive_.get()); - break; - case MZ_ZIP_MODE_WRITING: - mz_zip_writer_finalize_archive(archive_.get()); - mz_zip_writer_end(archive_.get()); - break; - case MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED: - mz_zip_writer_end(archive_.get()); - break; - case MZ_ZIP_MODE_INVALID: - break; + void save(const std::string& filename) { + filename_ = filename; + std::ofstream stream(filename, std::ios::binary); + save(stream); } - - if(archive_->m_zip_mode != MZ_ZIP_MODE_INVALID) - { - throw std::runtime_error(""); + + void save(std::ostream& stream) { + if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) { + mz_zip_writer_finalize_archive(archive_.get()); + } + + if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) { + mz_zip_writer_end(archive_.get()); + } + + if (archive_->m_zip_mode == MZ_ZIP_MODE_INVALID) { + start_read(); + } + + append_comment(); + stream.write(buffer_.data(), static_cast(buffer_.size())); } - buffer_.clear(); - comment.clear(); - - start_write(); - mz_zip_writer_finalize_archive(archive_.get()); - mz_zip_writer_end(archive_.get()); - } + void save(std::vector& bytes) { + if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) { + mz_zip_writer_finalize_archive(archive_.get()); + } - bool has_file(const std::string &name) - { - if(archive_->m_zip_mode != MZ_ZIP_MODE_READING) - { - start_read(); + if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) { + mz_zip_writer_end(archive_.get()); + } + + if (archive_->m_zip_mode == MZ_ZIP_MODE_INVALID) { + start_read(); + } + + append_comment(); + bytes.assign(buffer_.begin(), buffer_.end()); } - int index = mz_zip_reader_locate_file(archive_.get(), name.c_str(), nullptr, 0); + void reset() { + switch (archive_->m_zip_mode) { + case MZ_ZIP_MODE_READING: + mz_zip_reader_end(archive_.get()); + break; + case MZ_ZIP_MODE_WRITING: + mz_zip_writer_finalize_archive(archive_.get()); + mz_zip_writer_end(archive_.get()); + break; + case MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED: + mz_zip_writer_end(archive_.get()); + break; + case MZ_ZIP_MODE_INVALID: + break; + } - return index != -1; - } + if (archive_->m_zip_mode != MZ_ZIP_MODE_INVALID) { + throw std::runtime_error(""); + } - bool has_file(const zip_info &name) - { - return has_file(name.filename); - } + buffer_.clear(); + comment.clear(); - zip_info getinfo(const std::string &name) - { - if(archive_->m_zip_mode != MZ_ZIP_MODE_READING) - { - start_read(); + start_write(); + mz_zip_writer_finalize_archive(archive_.get()); + mz_zip_writer_end(archive_.get()); } - int index = mz_zip_reader_locate_file(archive_.get(), name.c_str(), nullptr, 0); + bool has_file(const std::string& name) { + if (archive_->m_zip_mode != MZ_ZIP_MODE_READING) { + start_read(); + } - if(index == -1) - { - throw std::runtime_error("not found"); + int index = mz_zip_reader_locate_file(archive_.get(), name.c_str(), nullptr, 0); + + return index != -1; } - return getinfo(index); - } - - std::vector infolist() - { - if(archive_->m_zip_mode != MZ_ZIP_MODE_READING) - { - start_read(); + bool has_file(const zip_info& name) { + return has_file(name.filename); } - std::vector info; + zip_info getinfo(const std::string& name) { + if (archive_->m_zip_mode != MZ_ZIP_MODE_READING) { + start_read(); + } - for(std::size_t i = 0; i < mz_zip_reader_get_num_files(archive_.get()); i++) - { - info.push_back(getinfo(static_cast(i))); + int index = mz_zip_reader_locate_file(archive_.get(), name.c_str(), nullptr, 0); + + if (index == -1) { + throw std::runtime_error("not found"); + } + + return getinfo(index); } - return info; - } + std::vector infolist() { + if (archive_->m_zip_mode != MZ_ZIP_MODE_READING) { + start_read(); + } - std::vector namelist() - { - std::vector names; + std::vector info; - for(auto &info : infolist()) - { - names.push_back(info.filename); + for (std::size_t i = 0; i < mz_zip_reader_get_num_files(archive_.get()); i++) { + info.push_back(getinfo(static_cast(i))); + } + + return info; } - return names; - } + std::vector namelist() { + std::vector names; - std::ostream &open(const std::string &name) - { - return open(getinfo(name)); - } - - std::ostream &open(const zip_info &name) - { - auto data = read(name); - std::string data_string(data.begin(), data.end()); - open_stream_ << data_string; - return open_stream_; - } - - void extract(const std::string &member, const std::string &path) - { - std::fstream stream(detail::join_path({path, member}), std::ios::binary | std::ios::out); - stream << open(member).rdbuf(); - } + for (auto& info : infolist()) { + names.push_back(info.filename); + } - void extract(const zip_info &member, const std::string &path) - { - std::fstream stream(detail::join_path({path, member.filename}), std::ios::binary | std::ios::out); - stream << open(member).rdbuf(); - } + return names; + } - void extractall(const std::string &path) - { - extractall(path, infolist()); - } + std::ostream& open(const std::string& name) { + return open(getinfo(name)); + } - void extractall(const std::string &path, const std::vector &members) - { - for(auto &member : members) - { - extract(member, path); + std::ostream& open(const zip_info& name) { + auto data = read(name); + std::string data_string(data.begin(), data.end()); + open_stream_ << data_string; + return open_stream_; } - } - void extractall(const std::string &path, const std::vector &members) - { - for(auto &member : members) - { - extract(member, path); + void extract(const std::string& member, const std::string& path) { + std::fstream stream(detail::join_path({path, member}), + std::ios::binary | std::ios::out); + stream << open(member).rdbuf(); } - } - - void printdir() - { - printdir(std::cout); - } - void printdir(std::ostream &stream) - { - stream << " Length " << " " << " " << "Date" << " " << " " << "Time " << " " << "Name" << std::endl; - stream << "--------- ---------- ----- ----" << std::endl; - - std::size_t sum_length = 0; - std::size_t file_count = 0; + void extract(const zip_info& member, const std::string& path) { + std::fstream stream(detail::join_path({path, member.filename}), + std::ios::binary | std::ios::out); + stream << open(member).rdbuf(); + } - for(auto &member : infolist()) - { - sum_length += member.file_size; - file_count++; + void extractall(const std::string& path) { + extractall(path, infolist()); + } - std::string length_string = std::to_string(member.file_size); - while(length_string.length() < 9) - { - length_string = " " + length_string; + void extractall(const std::string& path, const std::vector& members) { + for (auto& member : members) { + extract(member, path); } - stream << length_string; - - stream << " "; - stream << (member.date_time.month < 10 ? "0" : "") << member.date_time.month; - stream << "/"; - stream << (member.date_time.day < 10 ? "0" : "") << member.date_time.day; - stream << "/"; - stream << member.date_time.year; - stream << " "; - stream << (member.date_time.hours < 10 ? "0" : "") << member.date_time.hours; - stream << ":"; - stream << (member.date_time.minutes < 10 ? "0" : "") << member.date_time.minutes; - stream << " "; - stream << member.filename; - stream << std::endl; } - stream << "--------- -------" << std::endl; - - std::string length_string = std::to_string(sum_length); - while(length_string.length() < 9) - { - length_string = " " + length_string; + void extractall(const std::string& path, const std::vector& members) { + for (auto& member : members) { + extract(member, path); + } } - stream << length_string << " " << file_count << " " << (file_count == 1 ? "file" : "files"); - stream << std::endl; - } - std::string read(const zip_info &info) - { - std::size_t size; - char *data = static_cast(mz_zip_reader_extract_file_to_heap(archive_.get(), info.filename.c_str(), &size, 0)); - if(data == nullptr) - { - throw std::runtime_error("file couldn't be read"); + void printdir() { + printdir(std::cout); } - std::string extracted(data, data + size); - mz_free(data); - return extracted; - } - std::string read(const std::string &name) - { - return read(getinfo(name)); - } - - std::pair testzip() - { - if(archive_->m_zip_mode == MZ_ZIP_MODE_INVALID) - { - throw std::runtime_error("not open"); + void printdir(std::ostream& stream) { + stream << " Length " << " " << " " << "Date" << " " << " " << "Time " << " " + << "Name" << std::endl; + stream << "--------- ---------- ----- ----" << std::endl; + + std::size_t sum_length = 0; + std::size_t file_count = 0; + + for (auto& member : infolist()) { + sum_length += member.file_size; + file_count++; + + std::string length_string = std::to_string(member.file_size); + while (length_string.length() < 9) { + length_string = " " + length_string; + } + stream << length_string; + + stream << " "; + stream << (member.date_time.month < 10 ? "0" : "") << member.date_time.month; + stream << "/"; + stream << (member.date_time.day < 10 ? "0" : "") << member.date_time.day; + stream << "/"; + stream << member.date_time.year; + stream << " "; + stream << (member.date_time.hours < 10 ? "0" : "") << member.date_time.hours; + stream << ":"; + stream << (member.date_time.minutes < 10 ? "0" : "") << member.date_time.minutes; + stream << " "; + stream << member.filename; + stream << std::endl; + } + + stream << "--------- -------" << std::endl; + + std::string length_string = std::to_string(sum_length); + while (length_string.length() < 9) { + length_string = " " + length_string; + } + stream << length_string << " " << file_count << " " + << (file_count == 1 ? "file" : "files"); + stream << std::endl; } - for(auto &file : infolist()) - { - auto content = read(file); - auto crc = detail::crc32buf(content.c_str(), content.size()); - - if(crc != file.crc) - { - return {false, file.filename}; + std::string read(const zip_info& info) { + std::size_t size; + char* data = static_cast(mz_zip_reader_extract_file_to_heap( + archive_.get(), info.filename.c_str(), &size, 0)); + if (data == nullptr) { + throw std::runtime_error("file couldn't be read"); } + std::string extracted(data, data + size); + mz_free(data); + return extracted; } - return {true, ""}; - } - - void write(const std::string &filename) - { - auto split = detail::split_path(filename); - if(split.size() > 1) - { - split.erase(split.begin()); + std::string read(const std::string& name) { + return read(getinfo(name)); } - auto arcname = detail::join_path(split); - write(filename, arcname); - } - void write(const std::string &filename, const std::string &arcname) - { - std::fstream file(filename, std::ios::binary | std::ios::in); - std::stringstream ss; - ss << file.rdbuf(); - std::string bytes = ss.str(); + std::pair testzip() { + if (archive_->m_zip_mode == MZ_ZIP_MODE_INVALID) { + throw std::runtime_error("not open"); + } - writestr(arcname, bytes); - } + for (auto& file : infolist()) { + auto content = read(file); + auto crc = detail::crc32buf(content.c_str(), content.size()); - void writestr(const std::string &arcname, const std::string &bytes) - { - if(archive_->m_zip_mode != MZ_ZIP_MODE_WRITING) - { - start_write(); - } + if (crc != file.crc) { + return {false, file.filename}; + } + } - if(!mz_zip_writer_add_mem(archive_.get(), arcname.c_str(), bytes.data(), bytes.size(), MZ_BEST_COMPRESSION)) - { - throw std::runtime_error("write error"); + return {true, ""}; } - } - void writestr(const zip_info &info, const std::string &bytes) - { - if(info.filename.empty() || info.date_time.year < 1980) - { - throw std::runtime_error("must specify a filename and valid date (year >= 1980"); + void write(const std::string& filename) { + auto split = detail::split_path(filename); + if (split.size() > 1) { + split.erase(split.begin()); + } + auto arcname = detail::join_path(split); + write(filename, arcname); } - - if(archive_->m_zip_mode != MZ_ZIP_MODE_WRITING) - { - start_write(); + + void write(const std::string& filename, const std::string& arcname) { + std::fstream file(filename, std::ios::binary | std::ios::in); + std::stringstream ss; + ss << file.rdbuf(); + std::string bytes = ss.str(); + + writestr(arcname, bytes); } - - auto crc = detail::crc32buf(bytes.c_str(), bytes.size()); - - if(!mz_zip_writer_add_mem_ex(archive_.get(), info.filename.c_str(), bytes.data(), bytes.size(), info.comment.c_str(), static_cast(info.comment.size()), MZ_BEST_COMPRESSION, 0, crc)) - { - throw std::runtime_error("write error"); + + void writestr(const std::string& arcname, const std::string& bytes) { + if (archive_->m_zip_mode != MZ_ZIP_MODE_WRITING) { + start_write(); + } + + if (!mz_zip_writer_add_mem(archive_.get(), arcname.c_str(), bytes.data(), bytes.size(), + MZ_BEST_COMPRESSION)) { + throw std::runtime_error("write error"); + } } - } - std::string get_filename() const { return filename_; } - - std::string comment; - -private: - void start_read() - { - if(archive_->m_zip_mode == MZ_ZIP_MODE_READING) return; - - if(archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) - { - mz_zip_writer_finalize_archive(archive_.get()); + void writestr(const zip_info& info, const std::string& bytes) { + if (info.filename.empty() || info.date_time.year < 1980) { + throw std::runtime_error("must specify a filename and valid date (year >= 1980"); + } + + if (archive_->m_zip_mode != MZ_ZIP_MODE_WRITING) { + start_write(); + } + + auto crc = detail::crc32buf(bytes.c_str(), bytes.size()); + + if (!mz_zip_writer_add_mem_ex(archive_.get(), info.filename.c_str(), bytes.data(), + bytes.size(), info.comment.c_str(), + static_cast(info.comment.size()), + MZ_BEST_COMPRESSION, 0, crc)) { + throw std::runtime_error("write error"); + } } - - if(archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) - { - mz_zip_writer_end(archive_.get()); + + std::string get_filename() const { + return filename_; } - - if(!mz_zip_reader_init_mem(archive_.get(), buffer_.data(), buffer_.size(), 0)) - { - throw std::runtime_error("bad zip"); + + std::string comment; + + private: + void start_read() { + if (archive_->m_zip_mode == MZ_ZIP_MODE_READING) + return; + + if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) { + mz_zip_writer_finalize_archive(archive_.get()); + } + + if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) { + mz_zip_writer_end(archive_.get()); + } + + if (!mz_zip_reader_init_mem(archive_.get(), buffer_.data(), buffer_.size(), 0)) { + throw std::runtime_error("bad zip"); + } } - } - void start_write() - { - if(archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) return; - - switch(archive_->m_zip_mode) - { - case MZ_ZIP_MODE_READING: - { + void start_write() { + if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) + return; + + switch (archive_->m_zip_mode) { + case MZ_ZIP_MODE_READING: { mz_zip_archive archive_copy; - std::memset(&archive_copy, 0, sizeof(mz_zip_archive)); + std::memset(&archive_copy, 0, sizeof(mz_zip_archive)); std::vector buffer_copy(buffer_.begin(), buffer_.end()); - - if(!mz_zip_reader_init_mem(&archive_copy, buffer_copy.data(), buffer_copy.size(), 0)) - { + + if (!mz_zip_reader_init_mem(&archive_copy, buffer_copy.data(), buffer_copy.size(), + 0)) { throw std::runtime_error("bad zip"); } - + mz_zip_reader_end(archive_.get()); - + archive_->m_pWrite = &detail::write_callback; archive_->m_pIO_opaque = &buffer_; buffer_ = std::vector(); - - if(!mz_zip_writer_init(archive_.get(), 0)) - { + + if (!mz_zip_writer_init(archive_.get(), 0)) { throw std::runtime_error("bad zip"); } - - for(unsigned int i = 0; i < static_cast(archive_copy.m_total_files); i++) - { - if(!mz_zip_writer_add_from_zip_reader(archive_.get(), &archive_copy, i)) - { + + for (unsigned int i = 0; i < static_cast(archive_copy.m_total_files); + i++) { + if (!mz_zip_writer_add_from_zip_reader(archive_.get(), &archive_copy, i)) { throw std::runtime_error("fail"); } } - + mz_zip_reader_end(&archive_copy); return; } @@ -5606,104 +6475,96 @@ class zip_file case MZ_ZIP_MODE_INVALID: case MZ_ZIP_MODE_WRITING: break; - } + } - archive_->m_pWrite = &detail::write_callback; - archive_->m_pIO_opaque = &buffer_; + archive_->m_pWrite = &detail::write_callback; + archive_->m_pIO_opaque = &buffer_; - if(!mz_zip_writer_init(archive_.get(), 0)) - { - throw std::runtime_error("bad zip"); + if (!mz_zip_writer_init(archive_.get(), 0)) { + throw std::runtime_error("bad zip"); + } } - } - void append_comment() - { - if(!comment.empty()) - { - auto comment_length = std::min(static_cast(comment.length()), std::numeric_limits::max()); - buffer_[buffer_.size() - 2] = static_cast(comment_length); - buffer_[buffer_.size() - 1] = static_cast(comment_length >> 8); - std::copy(comment.begin(), comment.end(), std::back_inserter(buffer_)); + void append_comment() { + if (!comment.empty()) { + auto comment_length = std::min(static_cast(comment.length()), + std::numeric_limits::max()); + buffer_[buffer_.size() - 2] = static_cast(comment_length); + buffer_[buffer_.size() - 1] = static_cast(comment_length >> 8); + std::copy(comment.begin(), comment.end(), std::back_inserter(buffer_)); + } } - } - void remove_comment() - { - if(buffer_.empty()) return; - - std::size_t position = buffer_.size() - 1; - - for(; position >= 3; position--) - { - if(buffer_[position - 3] == 'P' - && buffer_[position - 2] == 'K' - && buffer_[position - 1] == '\x05' - && buffer_[position] == '\x06') - { - position = position + 17; - break; + void remove_comment() { + if (buffer_.empty()) + return; + + std::size_t position = buffer_.size() - 1; + + for (; position >= 3; position--) { + if (buffer_[position - 3] == 'P' && buffer_[position - 2] == 'K' && + buffer_[position - 1] == '\x05' && buffer_[position] == '\x06') { + position = position + 17; + break; + } } - } - - if(position == 3) - { - throw std::runtime_error("didn't find end of central directory signature"); - } - - uint16_t length = static_cast(buffer_[position + 1]); - length = static_cast(length << 8) + static_cast(buffer_[position]); - position += 2; - - if(length != 0) - { - comment = std::string(buffer_.data() + position, buffer_.data() + position + length); - buffer_.resize(buffer_.size() - length); - buffer_[buffer_.size() - 1] = 0; - buffer_[buffer_.size() - 2] = 0; - } - } - zip_info getinfo(int index) - { - if(archive_->m_zip_mode != MZ_ZIP_MODE_READING) - { - start_read(); + if (position == 3) { + throw std::runtime_error("didn't find end of central directory signature"); + } + + uint16_t length = static_cast(buffer_[position + 1]); + length = static_cast(length << 8) + static_cast(buffer_[position]); + position += 2; + + if (length != 0) { + comment = + std::string(buffer_.data() + position, buffer_.data() + position + length); + buffer_.resize(buffer_.size() - length); + buffer_[buffer_.size() - 1] = 0; + buffer_[buffer_.size() - 2] = 0; + } } - mz_zip_archive_file_stat stat; - mz_zip_reader_file_stat(archive_.get(), static_cast(index), &stat); - - zip_info result; - - result.filename = std::string(stat.m_filename, stat.m_filename + std::strlen(stat.m_filename)); - result.comment = std::string(stat.m_comment, stat.m_comment + stat.m_comment_size); - result.compress_size = static_cast(stat.m_comp_size); - result.file_size = static_cast(stat.m_uncomp_size); - result.header_offset = static_cast(stat.m_local_header_ofs); - result.crc = stat.m_crc32; - auto time = detail::safe_localtime(stat.m_time); - result.date_time.year = 1900 + time.tm_year; - result.date_time.month = 1 + time.tm_mon; - result.date_time.day = time.tm_mday; - result.date_time.hours = time.tm_hour; - result.date_time.minutes = time.tm_min; - result.date_time.seconds = time.tm_sec; - result.flag_bits = stat.m_bit_flag; - result.internal_attr = stat.m_internal_attr; - result.external_attr = stat.m_external_attr; - result.extract_version = stat.m_version_needed; - result.create_version = stat.m_version_made_by; - result.volume = stat.m_file_index; - result.create_system = stat.m_method; - - return result; - } + zip_info getinfo(int index) { + if (archive_->m_zip_mode != MZ_ZIP_MODE_READING) { + start_read(); + } - std::unique_ptr archive_; - std::vector buffer_; - std::stringstream open_stream_; - std::string filename_; -}; + mz_zip_archive_file_stat stat; + mz_zip_reader_file_stat(archive_.get(), static_cast(index), &stat); + + zip_info result; + + result.filename = + std::string(stat.m_filename, stat.m_filename + std::strlen(stat.m_filename)); + result.comment = std::string(stat.m_comment, stat.m_comment + stat.m_comment_size); + result.compress_size = static_cast(stat.m_comp_size); + result.file_size = static_cast(stat.m_uncomp_size); + result.header_offset = static_cast(stat.m_local_header_ofs); + result.crc = stat.m_crc32; + auto time = detail::safe_localtime(stat.m_time); + result.date_time.year = 1900 + time.tm_year; + result.date_time.month = 1 + time.tm_mon; + result.date_time.day = time.tm_mday; + result.date_time.hours = time.tm_hour; + result.date_time.minutes = time.tm_min; + result.date_time.seconds = time.tm_sec; + result.flag_bits = stat.m_bit_flag; + result.internal_attr = stat.m_internal_attr; + result.external_attr = stat.m_external_attr; + result.extract_version = stat.m_version_needed; + result.create_version = stat.m_version_made_by; + result.volume = stat.m_file_index; + result.create_system = stat.m_method; + + return result; + } + + std::unique_ptr archive_; + std::vector buffer_; + std::stringstream open_stream_; + std::string filename_; + }; } // namespace miniz_cpp diff --git a/include/launcher/optionParser.hpp b/include/launcher/optionParser.hpp index 933ca1a7..a173257a 100644 --- a/include/launcher/optionParser.hpp +++ b/include/launcher/optionParser.hpp @@ -5,9 +5,9 @@ // Program options imports #include -#include -#include #include +#include +#include struct AppOptions { static constexpr unsigned int DEFAULT_SCR_WIDTH = 800; @@ -19,12 +19,15 @@ struct AppOptions { std::string scene_file; bool help; - AppOptions(int argc, const char *argv[]); - AppOptions(): scr_width(DEFAULT_SCR_WIDTH), scr_height(DEFAULT_SCR_HEIGHT), tickrate(DEFAULT_TICKRATE), scene_file(), help(false) {} + AppOptions(int argc, const char* argv[]); + AppOptions() + : scr_width(DEFAULT_SCR_WIDTH), scr_height(DEFAULT_SCR_HEIGHT), tickrate(DEFAULT_TICKRATE), + scene_file(), help(false) { + } boost::program_options::options_description getHelpMessage() const; -private: + private: boost::program_options::options_description desc; }; diff --git a/include/physics/BVH.hpp b/include/physics/BVH.hpp index bc2435a4..b9b839e0 100644 --- a/include/physics/BVH.hpp +++ b/include/physics/BVH.hpp @@ -2,13 +2,12 @@ namespace physics { -struct BVH : public Collider { - - virtual bool checkCollision(const Collider& collider, std::vector& info) const override; + struct BVH : public Collider { + virtual bool checkCollision(const Collider& collider, + std::vector& info) const override; -private: - std::shared_ptr left, right; + private: + std::shared_ptr left, right; + }; -}; - -} +} // namespace physics diff --git a/include/physics/Collider.hpp b/include/physics/Collider.hpp index ca8235c8..65dfd0ee 100644 --- a/include/physics/Collider.hpp +++ b/include/physics/Collider.hpp @@ -6,14 +6,10 @@ namespace physics { -struct Collider { - - // Stores contact info if sphere intersects collider. If no intersection, info is not modified - virtual bool checkCollision(const Collider& collider, std::vector& info) const = 0; - -}; - - - -} + struct Collider { + // Stores contact info if sphere intersects collider. If no intersection, info is not modified + virtual bool checkCollision(const Collider& collider, + std::vector& info) const = 0; + }; +} // namespace physics diff --git a/include/physics/ContactInfo.hpp b/include/physics/ContactInfo.hpp index b4fca2a8..d2d7fb4b 100644 --- a/include/physics/ContactInfo.hpp +++ b/include/physics/ContactInfo.hpp @@ -4,32 +4,24 @@ namespace physics { -struct Collider; + struct Collider; -struct ContactInfo { + struct ContactInfo { + ContactInfo(glm::vec3 contactPoint, glm::vec3 contactNormal, + std::shared_ptr pCollider1, std::shared_ptr pCollider2, + float depth, float restitution = 1.0f, float friction = 0.0f) + : contactPoint(contactPoint), contactNormal(contactNormal), pCollider1(pCollider1), + pCollider2(pCollider2), depth(depth), restitution(restitution), friction(friction) { + } - ContactInfo( - glm::vec3 contactPoint, - glm::vec3 contactNormal, - std::shared_ptr pCollider1, - std::shared_ptr pCollider2, - float depth, - float restitution = 1.0f, - float friction = 0.0f - ) : - contactPoint(contactPoint), contactNormal(contactNormal), - pCollider1(pCollider1), pCollider2(pCollider2), depth(depth), restitution(restitution), friction(friction) - {} + glm::vec3 contactPoint; + glm::vec3 contactNormal; - glm::vec3 contactPoint; - glm::vec3 contactNormal; + std::shared_ptr pCollider1, pCollider2; - std::shared_ptr pCollider1, pCollider2; + float depth; + float restitution; + float friction; + }; - float depth; - float restitution; - float friction; - -}; - -} +} // namespace physics diff --git a/include/physics/SphereBVH.hpp b/include/physics/SphereBVH.hpp index 4d438ead..658dea47 100644 --- a/include/physics/SphereBVH.hpp +++ b/include/physics/SphereBVH.hpp @@ -1,40 +1,41 @@ #pragma once -#include #include -#include +#include #include +#include +#include #include #include -#include -#include +#include namespace physics { -struct SphereBVHNode: public Collider { - SphereCollider sphere; - std::unique_ptr left; - std::unique_ptr right; - - std::vector triangleIndices; + struct SphereBVHNode : public Collider { + SphereCollider sphere; + std::unique_ptr left; + std::unique_ptr right; + + std::vector triangleIndices; + + bool checkCollision(const Collider& collider, std::vector& info) const; - bool checkCollision(const Collider& collider, std::vector& info) const; + static std::unique_ptr fromMesh(sauce::modeling::Mesh& mesh); + bool isLeaf() const; + }; - static std::unique_ptr fromMesh(sauce::modeling::Mesh& mesh); - bool isLeaf() const; -}; + class SphereBVH : public Collider { + public: + static SphereBVH fromScene(const sauce::Scene& scene); -class SphereBVH: public Collider { -public: - static SphereBVH fromScene(const sauce::Scene& scene); + bool checkCollision(const Collider& collider, std::vector& info) const; - bool checkCollision(const Collider& collider, std::vector& info) const; + SphereBVHNode* getRoot() const; - SphereBVHNode *getRoot() const; + private: + std::unique_ptr root; -private: - std::unique_ptr root; - - SphereBVH(std::unique_ptr root) : root(std::move(root)) {} -}; -} + SphereBVH(std::unique_ptr root) : root(std::move(root)) { + } + }; +} // namespace physics diff --git a/include/physics/SphereCollider.hpp b/include/physics/SphereCollider.hpp index 2da23f14..7ea66b98 100644 --- a/include/physics/SphereCollider.hpp +++ b/include/physics/SphereCollider.hpp @@ -4,15 +4,13 @@ namespace physics { -struct SphereCollider : public Collider { + struct SphereCollider : public Collider { + // Stores contact info if spheres intersect. If no intersection, info is not modified + virtual bool checkCollision(const Collider& collider, + std::vector& info) const override; - // Stores contact info if spheres intersect. If no intersection, info is not modified - virtual bool checkCollision(const Collider& collider, std::vector& info) const override; - - float radius = 1.0f; - glm::vec3 center = glm::vec3(0.0f); - -}; - -} + float radius = 1.0f; + glm::vec3 center = glm::vec3(0.0f); + }; +} // namespace physics diff --git a/include/physics/Vertex.hpp b/include/physics/Vertex.hpp index 7dc61c3c..0317f89b 100644 --- a/include/physics/Vertex.hpp +++ b/include/physics/Vertex.hpp @@ -5,15 +5,13 @@ namespace physics { -struct Vertex { + struct Vertex { + glm::vec3 position, velocity; + float invMass; - glm::vec3 position, velocity; - float invMass; + glm::quat orientation = glm::quat(glm::vec3(0.0f)); + glm::vec3 angularVelocity = glm::vec3(0.0f); + glm::mat3 invInertiaTensor = glm::mat3(1.0f); + }; - glm::quat orientation = glm::quat(glm::vec3(0.0f)); - glm::vec3 angularVelocity = glm::vec3(0.0f); - glm::mat3 invInertiaTensor = glm::mat3(1.0f); - -}; - -} +} // namespace physics diff --git a/include/physics/XPBD.hpp b/include/physics/XPBD.hpp index ed8b9611..b0838113 100644 --- a/include/physics/XPBD.hpp +++ b/include/physics/XPBD.hpp @@ -11,68 +11,69 @@ namespace physics { -struct XPBDSolver { - - // Number of Gauss-Seidel iterations per substep (default 10) - // we can probably hook up imgui to this at some point for tuning - int solverIterations = 10; - - void solvePositions(std::vector& rigidBodies, std::vector>& constraints, float deltatime) { - /* + struct XPBDSolver { + // Number of Gauss-Seidel iterations per substep (default 10) + // we can probably hook up imgui to this at some point for tuning + int solverIterations = 10; + + void solvePositions(std::vector& rigidBodies, + std::vector>& constraints, + float deltatime) { + /* * adapted from https://matthias-research.github.io/pages/publications/posBasedDyn.pdf */ - glm::vec3 velocity; - float w; - std::vector centers; // centers of mass for all rigid bodies - - centers.reserve(rigidBodies.size()); - for (auto& r : rigidBodies) { - /* get vertices of r */ - auto o = r.getOwner(); - auto m = o->getComponent(); - /* skip rigid bodies with no mesh, we cannot generate their constraints */ - if (m == nullptr) - continue; - auto v=m->getMesh()->getVertices(); - - /* do physics */ - w=r.getInvMass(); - velocity=r.getVelocity() + deltatime*(w*r.getExternalForces()); - /* + glm::vec3 velocity; + float w; + std::vector centers; // centers of mass for all rigid bodies + + centers.reserve(rigidBodies.size()); + for (auto& r : rigidBodies) { + /* get vertices of r */ + auto o = r.getOwner(); + auto m = o->getComponent(); + /* skip rigid bodies with no mesh, we cannot generate their constraints */ + if (m == nullptr) + continue; + auto v = m->getMesh()->getVertices(); + + /* do physics */ + w = r.getInvMass(); + velocity = r.getVelocity() + deltatime * (w * r.getExternalForces()); + /* * you could damp velocities here */ - r.setPosition(r.getPosition() + velocity*deltatime); - auto constraints=generateCollisionConstraints(rigidBodies); + r.setPosition(r.getPosition() + velocity * deltatime); + auto constraints = generateCollisionConstraints(rigidBodies); - centers.push_back({r.getCenterOfMass(), glm::vec3(0.f,0.f,0.f), r.getInvMass()}); - } + centers.push_back({r.getCenterOfMass(), glm::vec3(0.f, 0.f, 0.f), r.getInvMass()}); + } - for (int i=0; i& vertices, - std::vector>& constraints, - float deltatime - ) { - if (vertices.empty() || constraints.empty()) return; - - for (int iter = 0; iter < solverIterations; ++iter) { - if (iter == 0) { - for (auto& c : constraints) { - c->resetLambda(); + for (int i = 0; i < solverIterations; i++) { + projectConstraints(centers, constraints, deltatime); + } } - } - for (auto& c : constraints) { - c->solve(vertices, deltatime); - } - } - } + void projectConstraints(std::vector& vertices, + std::vector>& constraints, + float deltatime) { + if (vertices.empty() || constraints.empty()) + return; + + for (int iter = 0; iter < solverIterations; ++iter) { + if (iter == 0) { + for (auto& c : constraints) { + c->resetLambda(); + } + } + + for (auto& c : constraints) { + c->solve(vertices, deltatime); + } + } + } - std::vector generateCollisionConstraints(std::vector& rigidBodies); -}; + std::vector generateCollisionConstraints( + std::vector& rigidBodies); + }; -} +} // namespace physics diff --git a/include/physics/constraints/BendConstraint.hpp b/include/physics/constraints/BendConstraint.hpp index 123eacf6..971b104d 100644 --- a/include/physics/constraints/BendConstraint.hpp +++ b/include/physics/constraints/BendConstraint.hpp @@ -4,12 +4,12 @@ namespace physics { -struct BendConstraint : public Constraint { + struct BendConstraint : public Constraint { + virtual void solve(std::vector& vertices, + std::vector& lagrangeMultipliers, + float deltatime) const override; - virtual void solve(std::vector& vertices, std::vector& lagrangeMultipliers, float deltatime) const override; + float restAngle; + }; - float restAngle; - -}; - -} +} // namespace physics diff --git a/include/physics/constraints/CollisionConstraint.hpp b/include/physics/constraints/CollisionConstraint.hpp index edce9bef..060e80ec 100644 --- a/include/physics/constraints/CollisionConstraint.hpp +++ b/include/physics/constraints/CollisionConstraint.hpp @@ -9,99 +9,110 @@ namespace physics { -// Position Based Dynamics Collision constraint -struct CollisionConstraint : public Constraint { - CollisionConstraint() = default; - - // Construct from two vertex indices plus collision geometry. - CollisionConstraint(uint32_t a, uint32_t b, glm::vec3 normal, float depth, - float comp = 0.0f) - : Constraint(comp), indexA(a), indexB(b), contactNormal(normal), penetrationDepth(depth) {} - - // Construct from single vertex colliding with a static surface. - CollisionConstraint(uint32_t a, glm::vec3 contactPt, glm::vec3 normal, - float comp = 0.0f) - : Constraint(comp), indexA(a), indexB(UINT32_MAX), contactPoint(contactPt), - contactNormal(normal), isStaticCollision(true) {} - - void solve(std::vector& vertices, float deltatime) override { - if (isStaticCollision) { - solveStatic(vertices, deltatime); - } else { - solveDynamic(vertices, deltatime); - } - } - - uint32_t indexA = 0; - uint32_t indexB = 0; - glm::vec3 contactPoint = glm::vec3(0.0f); - glm::vec3 contactNormal = glm::vec3(0.0f, 1.0f, 0.0f); - float penetrationDepth = 0.0f; - bool isStaticCollision = false; - -private: - // Dynamic collision, vertex to vertex - void solveDynamic(std::vector& vertices, float deltatime) { - if (indexA >= vertices.size() || indexB >= vertices.size()) return; - - physics::Vertex& va = vertices[indexA]; - physics::Vertex& vb = vertices[indexB]; - - const float w1 = va.invMass; - const float w2 = vb.invMass; - if (w1 + w2 <= 1e-8f) return; - - const float C = glm::dot(va.position - vb.position, contactNormal) - penetrationDepth; - - if (C >= -1e-8f) return; - - const float alphaTilde = compliance / (deltatime * deltatime); - - const float denom = w1 + w2 + alphaTilde; - const float deltaLambda = (-C - alphaTilde * lambda) / denom; - - const glm::vec3 deltaP_a = (w1 * deltaLambda) * contactNormal; - const glm::vec3 deltaP_b = (w2 * deltaLambda) * contactNormal; - - va.position += deltaP_a; - vb.position -= deltaP_b; - - // Orientation corrections - const glm::vec3 dOmega_a = va.invInertiaTensor * glm::cross(contactNormal, deltaP_a); - const glm::vec3 dOmega_b = vb.invInertiaTensor * glm::cross(contactNormal, deltaP_b); - va.orientation = glm::normalize(va.orientation + 0.5f * glm::quat(0.0f, dOmega_a) * va.orientation); - vb.orientation = glm::normalize(vb.orientation - 0.5f * glm::quat(0.0f, dOmega_b) * vb.orientation); - - lambda += deltaLambda; - } - - // Static collision, a single vertex against a fixed surface point - void solveStatic(std::vector& vertices, float deltatime) { - if (indexA >= vertices.size()) return; - - physics::Vertex& va = vertices[indexA]; - const float w = va.invMass; - if (w <= 1e-8f) return; - - const float C = glm::dot(va.position - contactPoint, contactNormal); - - if (C >= -1e-8f) return; - - const float alphaTilde = compliance / (deltatime * deltatime); - - const float denom = w + alphaTilde; - const float deltaLambda = (-C - alphaTilde * lambda) / denom; - - const glm::vec3 deltaP = (w * deltaLambda) * contactNormal; - va.position += deltaP; - - // Orientation correction - const glm::vec3 r = contactPoint - va.position; - const glm::vec3 dOmega = va.invInertiaTensor * glm::cross(r, deltaP); - va.orientation = glm::normalize(va.orientation + 0.5f * glm::quat(0.0f, dOmega) * va.orientation); - - lambda += deltaLambda; - } -}; - -} + // Position Based Dynamics Collision constraint + struct CollisionConstraint : public Constraint { + CollisionConstraint() = default; + + // Construct from two vertex indices plus collision geometry. + CollisionConstraint(uint32_t a, uint32_t b, glm::vec3 normal, float depth, + float comp = 0.0f) + : Constraint(comp), indexA(a), indexB(b), contactNormal(normal), + penetrationDepth(depth) { + } + + // Construct from single vertex colliding with a static surface. + CollisionConstraint(uint32_t a, glm::vec3 contactPt, glm::vec3 normal, float comp = 0.0f) + : Constraint(comp), indexA(a), indexB(UINT32_MAX), contactPoint(contactPt), + contactNormal(normal), isStaticCollision(true) { + } + + void solve(std::vector& vertices, float deltatime) override { + if (isStaticCollision) { + solveStatic(vertices, deltatime); + } else { + solveDynamic(vertices, deltatime); + } + } + + uint32_t indexA = 0; + uint32_t indexB = 0; + glm::vec3 contactPoint = glm::vec3(0.0f); + glm::vec3 contactNormal = glm::vec3(0.0f, 1.0f, 0.0f); + float penetrationDepth = 0.0f; + bool isStaticCollision = false; + + private: + // Dynamic collision, vertex to vertex + void solveDynamic(std::vector& vertices, float deltatime) { + if (indexA >= vertices.size() || indexB >= vertices.size()) + return; + + physics::Vertex& va = vertices[indexA]; + physics::Vertex& vb = vertices[indexB]; + + const float w1 = va.invMass; + const float w2 = vb.invMass; + if (w1 + w2 <= 1e-8f) + return; + + const float C = glm::dot(va.position - vb.position, contactNormal) - penetrationDepth; + + if (C >= -1e-8f) + return; + + const float alphaTilde = compliance / (deltatime * deltatime); + + const float denom = w1 + w2 + alphaTilde; + const float deltaLambda = (-C - alphaTilde * lambda) / denom; + + const glm::vec3 deltaP_a = (w1 * deltaLambda) * contactNormal; + const glm::vec3 deltaP_b = (w2 * deltaLambda) * contactNormal; + + va.position += deltaP_a; + vb.position -= deltaP_b; + + // Orientation corrections + const glm::vec3 dOmega_a = va.invInertiaTensor * glm::cross(contactNormal, deltaP_a); + const glm::vec3 dOmega_b = vb.invInertiaTensor * glm::cross(contactNormal, deltaP_b); + va.orientation = + glm::normalize(va.orientation + 0.5f * glm::quat(0.0f, dOmega_a) * va.orientation); + vb.orientation = + glm::normalize(vb.orientation - 0.5f * glm::quat(0.0f, dOmega_b) * vb.orientation); + + lambda += deltaLambda; + } + + // Static collision, a single vertex against a fixed surface point + void solveStatic(std::vector& vertices, float deltatime) { + if (indexA >= vertices.size()) + return; + + physics::Vertex& va = vertices[indexA]; + const float w = va.invMass; + if (w <= 1e-8f) + return; + + const float C = glm::dot(va.position - contactPoint, contactNormal); + + if (C >= -1e-8f) + return; + + const float alphaTilde = compliance / (deltatime * deltatime); + + const float denom = w + alphaTilde; + const float deltaLambda = (-C - alphaTilde * lambda) / denom; + + const glm::vec3 deltaP = (w * deltaLambda) * contactNormal; + va.position += deltaP; + + // Orientation correction + const glm::vec3 r = contactPoint - va.position; + const glm::vec3 dOmega = va.invInertiaTensor * glm::cross(r, deltaP); + va.orientation = + glm::normalize(va.orientation + 0.5f * glm::quat(0.0f, dOmega) * va.orientation); + + lambda += deltaLambda; + } + }; + +} // namespace physics diff --git a/include/physics/constraints/Constraint.hpp b/include/physics/constraints/Constraint.hpp index b1a607a4..020c92d4 100644 --- a/include/physics/constraints/Constraint.hpp +++ b/include/physics/constraints/Constraint.hpp @@ -6,16 +6,19 @@ namespace physics { -struct Constraint { - Constraint() = default; - explicit Constraint(float comp) : compliance(comp) {} - virtual ~Constraint() = default; - virtual void solve(std::vector& vertices, float deltatime) = 0; - void resetLambda() { lambda = 0.0f; } - float compliance = 0.0f; + struct Constraint { + Constraint() = default; + explicit Constraint(float comp) : compliance(comp) { + } + virtual ~Constraint() = default; + virtual void solve(std::vector& vertices, float deltatime) = 0; + void resetLambda() { + lambda = 0.0f; + } + float compliance = 0.0f; -protected: - float lambda = 0.0f; -}; + protected: + float lambda = 0.0f; + }; -} +} // namespace physics diff --git a/include/physics/constraints/StretchConstraint.hpp b/include/physics/constraints/StretchConstraint.hpp index 94a86623..3d5344ce 100644 --- a/include/physics/constraints/StretchConstraint.hpp +++ b/include/physics/constraints/StretchConstraint.hpp @@ -9,49 +9,53 @@ namespace physics { -struct StretchConstraint : public Constraint { - StretchConstraint() = default; + struct StretchConstraint : public Constraint { + StretchConstraint() = default; - StretchConstraint(uint32_t a, uint32_t b, float rest, float comp = 0.0f) - : Constraint(comp), indexA(a), indexB(b), restLength(rest) {} + StretchConstraint(uint32_t a, uint32_t b, float rest, float comp = 0.0f) + : Constraint(comp), indexA(a), indexB(b), restLength(rest) { + } - void solve(std::vector& vertices, float deltatime) override { - if (indexA >= vertices.size() || indexB >= vertices.size()) return; + void solve(std::vector& vertices, float deltatime) override { + if (indexA >= vertices.size() || indexB >= vertices.size()) + return; - physics::Vertex& va = vertices[indexA]; - physics::Vertex& vb = vertices[indexB]; + physics::Vertex& va = vertices[indexA]; + physics::Vertex& vb = vertices[indexB]; - const float w1 = va.invMass; - const float w2 = vb.invMass; - if (w1 + w2 <= 0.0f) return; // both static + const float w1 = va.invMass; + const float w2 = vb.invMass; + if (w1 + w2 <= 0.0f) + return; // both static - const glm::vec3 diff = va.position - vb.position; - const float dist = glm::length(diff); - if (dist <= std::numeric_limits::epsilon()) return; + const glm::vec3 diff = va.position - vb.position; + const float dist = glm::length(diff); + if (dist <= std::numeric_limits::epsilon()) + return; - // Constraint value - const float C = dist - restLength; + // Constraint value + const float C = dist - restLength; - // Regularisation α̃ = α / h² - const float alphaTilde = compliance / (deltatime * deltatime); + // Regularisation α̃ = α / h² + const float alphaTilde = compliance / (deltatime * deltatime); - // Newton–Raphson Δλ - const float denom = w1 + w2 + alphaTilde; - const float deltaLambda = (-C - alphaTilde * lambda) / denom; + // Newton–Raphson Δλ + const float denom = w1 + w2 + alphaTilde; + const float deltaLambda = (-C - alphaTilde * lambda) / denom; - // Gradient direction (unit) - const glm::vec3 n = diff / dist; + // Gradient direction (unit) + const glm::vec3 n = diff / dist; - // Apply position corrections Δx = w Δλ ∇C - va.position += (w1 * deltaLambda) * n; - vb.position -= (w2 * deltaLambda) * n; + // Apply position corrections Δx = w Δλ ∇C + va.position += (w1 * deltaLambda) * n; + vb.position -= (w2 * deltaLambda) * n; - lambda += deltaLambda; - } + lambda += deltaLambda; + } - uint32_t indexA = 0; - uint32_t indexB = 0; - float restLength = 0.0f; -}; + uint32_t indexA = 0; + uint32_t indexB = 0; + float restLength = 0.0f; + }; -} +} // namespace physics diff --git a/src/app/IBLGenerator.cpp b/src/app/IBLGenerator.cpp index 378b43ae..7eb212de 100644 --- a/src/app/IBLGenerator.cpp +++ b/src/app/IBLGenerator.cpp @@ -1,204 +1,289 @@ +#include #include #include -#include #include -#include -#include +#include #include +#include #include -#include +#include namespace sauce { -struct IBLPushConstants { - glm::mat4 mvp; - float roughness; -}; - -IBLGenerator::IBLGenerator(const sauce::PhysicalDevice& physicalDevice, const sauce::LogicalDevice& logicalDevice) - : physicalDevice(physicalDevice), logicalDevice(logicalDevice) { -} - -std::unique_ptr IBLGenerator::generateIBLMaps( - const std::string& hdrPath, - const vk::raii::CommandPool& commandPool, - const vk::raii::Queue& queue -) { - auto maps = std::make_unique(); - - // loading HDR image - int width, height, channels; - float* pixels = stbi_loadf(hdrPath.c_str(), &width, &height, &channels, STBI_rgb_alpha); - if (!pixels) { - throw std::runtime_error("Failed to load HDR image: " + hdrPath); + struct IBLPushConstants { + glm::mat4 mvp; + float roughness; + }; + + IBLGenerator::IBLGenerator(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) + : physicalDevice(physicalDevice), logicalDevice(logicalDevice) { } - vk::DeviceSize imageSize = width * height * 4 * sizeof(float); - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingMemory = nullptr; - sauce::BufferUtils::createBuffer( - physicalDevice, logicalDevice, imageSize, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, stagingMemory - ); - void* data = stagingMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingMemory.unmapMemory(); - stbi_image_free(pixels); - - vk::raii::Image hdrImage = nullptr; - vk::raii::DeviceMemory hdrImageMemory = nullptr; - sauce::ImageUtils::createImage( - physicalDevice, logicalDevice, width, height, - vk::Format::eR32G32B32A32Sfloat, vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, - hdrImage, hdrImageMemory - ); - - sauce::ImageUtils::transitionImageLayout( - logicalDevice, commandPool, queue, hdrImage, - vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, - {}, vk::AccessFlagBits2::eTransferWrite, - vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer - ); - sauce::ImageUtils::copyBufferToImage(logicalDevice, commandPool, queue, stagingBuffer, hdrImage, width, height); - sauce::ImageUtils::transitionImageLayout( - logicalDevice, commandPool, queue, hdrImage, - vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, - vk::AccessFlagBits2::eTransferWrite, vk::AccessFlagBits2::eShaderRead, - vk::PipelineStageFlagBits2::eTransfer, vk::PipelineStageFlagBits2::eFragmentShader - ); - - vk::raii::ImageView hdrView = sauce::ImageUtils::createImageView( - logicalDevice, hdrImage, vk::Format::eR32G32B32A32Sfloat, vk::ImageAspectFlagBits::eColor - ); - - // map sampler - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eClampToEdge, - .addressModeV = vk::SamplerAddressMode::eClampToEdge, - .addressModeW = vk::SamplerAddressMode::eClampToEdge, - .minLod = 0.0f, - .maxLod = 16.0f, - }; - maps->sampler = vk::raii::Sampler(*logicalDevice, samplerInfo); - - createCubeMesh(commandPool, queue); - - convertEquirectangularToCubemap(hdrView, *maps, commandPool, queue); - generateIrradianceMap(*maps, commandPool, queue); - generatePrefilterMap(*maps, commandPool, queue); - generateBRDFLUT(*maps, commandPool, queue); - - return maps; -} - -void IBLGenerator::createCubeMesh(const vk::raii::CommandPool& commandPool, const vk::raii::Queue& queue) { - std::vector cubeVertices = { - {{-1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, - {{-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, - {{ 1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, - {{ 1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, - {{-1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, - {{-1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, - {{ 1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, - {{ 1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, - }; + std::unique_ptr IBLGenerator::generateIBLMaps(const std::string& hdrPath, + const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue) { + auto maps = std::make_unique(); - std::vector cubeIndices = { - 0, 1, 2, 2, 3, 0, - 4, 5, 6, 6, 7, 4, - 0, 1, 5, 5, 4, 0, - 2, 3, 7, 7, 6, 2, - 0, 3, 7, 7, 4, 0, - 1, 2, 6, 6, 5, 1, - }; - indexCount = static_cast(cubeIndices.size()); - - vk::DeviceSize vertexBufferSize = cubeVertices.size() * sizeof(Vertex); - vk::raii::Buffer vertexStagingBuffer = nullptr; - vk::raii::DeviceMemory vertexStagingMemory = nullptr; - sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, vertexBufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, vertexStagingBuffer, vertexStagingMemory); - void* vertexData = vertexStagingMemory.mapMemory(0, vertexBufferSize); - memcpy(vertexData, cubeVertices.data(), vertexBufferSize); - vertexStagingMemory.unmapMemory(); - sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, vertexBufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, queue, vertexStagingBuffer, vertexBuffer, vertexBufferSize); - - vk::DeviceSize indexBufferSize = cubeIndices.size() * sizeof(uint16_t); - vk::raii::Buffer indexStagingBuffer = nullptr; - vk::raii::DeviceMemory indexStagingMemory = nullptr; - sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, indexBufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, indexStagingBuffer, indexStagingMemory); - void* indexData = indexStagingMemory.mapMemory(0, indexBufferSize); - memcpy(indexData, cubeIndices.data(), indexBufferSize); - indexStagingMemory.unmapMemory(); - sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, indexBufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, queue, indexStagingBuffer, indexBuffer, indexBufferSize); -} - -vk::raii::ShaderModule IBLGenerator::createShaderModule(const std::string& filename) { - std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) throw std::runtime_error("Failed to open shader file: " + filename); - size_t fileSize = (size_t)file.tellg(); - std::vector buffer(fileSize / sizeof(uint32_t)); - file.seekg(0); - file.read(reinterpret_cast(buffer.data()), fileSize); - file.close(); - vk::ShaderModuleCreateInfo createInfo{.codeSize = buffer.size() * sizeof(uint32_t), .pCode = buffer.data()}; - return vk::raii::ShaderModule(*logicalDevice, createInfo); -} - -void IBLGenerator::convertEquirectangularToCubemap(const vk::raii::ImageView& hdrView, IBLMaps& maps, const vk::raii::CommandPool& commandPool, const vk::raii::Queue& queue) { - uint32_t resolution = 512; - vk::Format format = vk::Format::eR16G16B16A16Sfloat; - sauce::ImageUtils::createImage(physicalDevice, logicalDevice, resolution, resolution, format, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, maps.envCubemap, maps.envCubemapMemory, 1, 6, vk::ImageCreateFlagBits::eCubeCompatible); - maps.envCubemapView = sauce::ImageUtils::createImageView(logicalDevice, maps.envCubemap, format, vk::ImageAspectFlagBits::eColor, vk::ImageViewType::eCube, 1, 6); - - // TODO: rendering logic for 6 faces would go here i think, using dynamic rendering - - // transition to ShaderReadOnlyOptimal for subsequent passes - sauce::ImageUtils::transitionImageLayout(logicalDevice, commandPool, queue, maps.envCubemap, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal, {}, vk::AccessFlagBits2::eShaderRead, vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eFragmentShader, 1, 6); -} - -void IBLGenerator::generateIrradianceMap(IBLMaps& maps, const vk::raii::CommandPool& commandPool, const vk::raii::Queue& queue) { - uint32_t resolution = 32; - vk::Format format = vk::Format::eR16G16B16A16Sfloat; - sauce::ImageUtils::createImage(physicalDevice, logicalDevice, resolution, resolution, format, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, maps.irradianceMap, maps.irradianceMapMemory, 1, 6, vk::ImageCreateFlagBits::eCubeCompatible); - maps.irradianceMapView = sauce::ImageUtils::createImageView(logicalDevice, maps.irradianceMap, format, vk::ImageAspectFlagBits::eColor, vk::ImageViewType::eCube, 1, 6); - - sauce::ImageUtils::transitionImageLayout(logicalDevice, commandPool, queue, maps.irradianceMap, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal, {}, vk::AccessFlagBits2::eShaderRead, vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eFragmentShader, 1, 6); -} - -void IBLGenerator::generatePrefilterMap(IBLMaps& maps, const vk::raii::CommandPool& commandPool, const vk::raii::Queue& queue) { - uint32_t resolution = 128; - uint32_t mipLevels = 5; - vk::Format format = vk::Format::eR16G16B16A16Sfloat; - sauce::ImageUtils::createImage(physicalDevice, logicalDevice, resolution, resolution, format, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, maps.prefilterMap, maps.prefilterMapMemory, mipLevels, 6, vk::ImageCreateFlagBits::eCubeCompatible); - maps.prefilterMapView = sauce::ImageUtils::createImageView(logicalDevice, maps.prefilterMap, format, vk::ImageAspectFlagBits::eColor, vk::ImageViewType::eCube, mipLevels, 6); - - for (uint32_t mip = 0; mip < mipLevels; ++mip) { - float roughness = (float)mip / (float)(mipLevels - 1); - for (uint32_t face = 0; face < 6; ++face) { - // TODO: rendering each face for each mip level with specific roughness + // loading HDR image + int width, height, channels; + float* pixels = stbi_loadf(hdrPath.c_str(), &width, &height, &channels, STBI_rgb_alpha); + if (!pixels) { + throw std::runtime_error("Failed to load HDR image: " + hdrPath); } + + vk::DeviceSize imageSize = width * height * 4 * sizeof(float); + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingMemory = nullptr; + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, imageSize, vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, stagingMemory); + void* data = stagingMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingMemory.unmapMemory(); + stbi_image_free(pixels); + + vk::raii::Image hdrImage = nullptr; + vk::raii::DeviceMemory hdrImageMemory = nullptr; + sauce::ImageUtils::createImage( + physicalDevice, logicalDevice, width, height, vk::Format::eR32G32B32A32Sfloat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, hdrImage, hdrImageMemory); + + sauce::ImageUtils::transitionImageLayout( + logicalDevice, commandPool, queue, hdrImage, vk::ImageLayout::eUndefined, + vk::ImageLayout::eTransferDstOptimal, {}, vk::AccessFlagBits2::eTransferWrite, + vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer); + sauce::ImageUtils::copyBufferToImage(logicalDevice, commandPool, queue, stagingBuffer, + hdrImage, width, height); + sauce::ImageUtils::transitionImageLayout( + logicalDevice, commandPool, queue, hdrImage, vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eTransferWrite, + vk::AccessFlagBits2::eShaderRead, vk::PipelineStageFlagBits2::eTransfer, + vk::PipelineStageFlagBits2::eFragmentShader); + + vk::raii::ImageView hdrView = sauce::ImageUtils::createImageView( + logicalDevice, hdrImage, vk::Format::eR32G32B32A32Sfloat, + vk::ImageAspectFlagBits::eColor); + + // map sampler + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eClampToEdge, + .addressModeV = vk::SamplerAddressMode::eClampToEdge, + .addressModeW = vk::SamplerAddressMode::eClampToEdge, + .minLod = 0.0f, + .maxLod = 16.0f, + }; + maps->sampler = vk::raii::Sampler(*logicalDevice, samplerInfo); + + createCubeMesh(commandPool, queue); + + convertEquirectangularToCubemap(hdrView, *maps, commandPool, queue); + generateIrradianceMap(*maps, commandPool, queue); + generatePrefilterMap(*maps, commandPool, queue); + generateBRDFLUT(*maps, commandPool, queue); + + return maps; + } + + void IBLGenerator::createCubeMesh(const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue) { + std::vector cubeVertices = { + {{-1.0f, 1.0f, -1.0f}, + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 1.0f}}, + {{-1.0f, -1.0f, -1.0f}, + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 1.0f}}, + {{1.0f, -1.0f, -1.0f}, + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 1.0f}}, + {{1.0f, 1.0f, -1.0f}, + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 1.0f}}, + {{-1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 1.0f}}, + {{-1.0f, -1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 1.0f}}, + {{1.0f, -1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 1.0f}}, + {{1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f}, + {1.0f, 1.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 1.0f}}, + }; + + std::vector cubeIndices = { + 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 0, 1, 5, 5, 4, 0, + 2, 3, 7, 7, 6, 2, 0, 3, 7, 7, 4, 0, 1, 2, 6, 6, 5, 1, + }; + indexCount = static_cast(cubeIndices.size()); + + vk::DeviceSize vertexBufferSize = cubeVertices.size() * sizeof(Vertex); + vk::raii::Buffer vertexStagingBuffer = nullptr; + vk::raii::DeviceMemory vertexStagingMemory = nullptr; + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, vertexBufferSize, vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + vertexStagingBuffer, vertexStagingMemory); + void* vertexData = vertexStagingMemory.mapMemory(0, vertexBufferSize); + memcpy(vertexData, cubeVertices.data(), vertexBufferSize); + vertexStagingMemory.unmapMemory(); + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, vertexBufferSize, + vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, queue, vertexStagingBuffer, + vertexBuffer, vertexBufferSize); + + vk::DeviceSize indexBufferSize = cubeIndices.size() * sizeof(uint16_t); + vk::raii::Buffer indexStagingBuffer = nullptr; + vk::raii::DeviceMemory indexStagingMemory = nullptr; + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, indexBufferSize, vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + indexStagingBuffer, indexStagingMemory); + void* indexData = indexStagingMemory.mapMemory(0, indexBufferSize); + memcpy(indexData, cubeIndices.data(), indexBufferSize); + indexStagingMemory.unmapMemory(); + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, indexBufferSize, + vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, queue, indexStagingBuffer, + indexBuffer, indexBufferSize); + } + + vk::raii::ShaderModule IBLGenerator::createShaderModule(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) + throw std::runtime_error("Failed to open shader file: " + filename); + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize / sizeof(uint32_t)); + file.seekg(0); + file.read(reinterpret_cast(buffer.data()), fileSize); + file.close(); + vk::ShaderModuleCreateInfo createInfo{.codeSize = buffer.size() * sizeof(uint32_t), + .pCode = buffer.data()}; + return vk::raii::ShaderModule(*logicalDevice, createInfo); + } + + void IBLGenerator::convertEquirectangularToCubemap(const vk::raii::ImageView& hdrView, + IBLMaps& maps, + const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue) { + uint32_t resolution = 512; + vk::Format format = vk::Format::eR16G16B16A16Sfloat; + sauce::ImageUtils::createImage( + physicalDevice, logicalDevice, resolution, resolution, format, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eDeviceLocal, maps.envCubemap, maps.envCubemapMemory, 1, 6, + vk::ImageCreateFlagBits::eCubeCompatible); + maps.envCubemapView = sauce::ImageUtils::createImageView( + logicalDevice, maps.envCubemap, format, vk::ImageAspectFlagBits::eColor, + vk::ImageViewType::eCube, 1, 6); + + // TODO: rendering logic for 6 faces would go here i think, using dynamic rendering + + // transition to ShaderReadOnlyOptimal for subsequent passes + sauce::ImageUtils::transitionImageLayout( + logicalDevice, commandPool, queue, maps.envCubemap, vk::ImageLayout::eUndefined, + vk::ImageLayout::eShaderReadOnlyOptimal, {}, vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eFragmentShader, 1, 6); + } + + void IBLGenerator::generateIrradianceMap(IBLMaps& maps, + const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue) { + uint32_t resolution = 32; + vk::Format format = vk::Format::eR16G16B16A16Sfloat; + sauce::ImageUtils::createImage( + physicalDevice, logicalDevice, resolution, resolution, format, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, maps.irradianceMap, maps.irradianceMapMemory, + 1, 6, vk::ImageCreateFlagBits::eCubeCompatible); + maps.irradianceMapView = sauce::ImageUtils::createImageView( + logicalDevice, maps.irradianceMap, format, vk::ImageAspectFlagBits::eColor, + vk::ImageViewType::eCube, 1, 6); + + sauce::ImageUtils::transitionImageLayout( + logicalDevice, commandPool, queue, maps.irradianceMap, vk::ImageLayout::eUndefined, + vk::ImageLayout::eShaderReadOnlyOptimal, {}, vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eFragmentShader, 1, 6); } - sauce::ImageUtils::transitionImageLayout(logicalDevice, commandPool, queue, maps.prefilterMap, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal, {}, vk::AccessFlagBits2::eShaderRead, vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eFragmentShader, mipLevels, 6); -} + void IBLGenerator::generatePrefilterMap(IBLMaps& maps, const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue) { + uint32_t resolution = 128; + uint32_t mipLevels = 5; + vk::Format format = vk::Format::eR16G16B16A16Sfloat; + sauce::ImageUtils::createImage( + physicalDevice, logicalDevice, resolution, resolution, format, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, maps.prefilterMap, maps.prefilterMapMemory, + mipLevels, 6, vk::ImageCreateFlagBits::eCubeCompatible); + maps.prefilterMapView = sauce::ImageUtils::createImageView( + logicalDevice, maps.prefilterMap, format, vk::ImageAspectFlagBits::eColor, + vk::ImageViewType::eCube, mipLevels, 6); -void IBLGenerator::generateBRDFLUT(IBLMaps& maps, const vk::raii::CommandPool& commandPool, const vk::raii::Queue& queue) { - uint32_t resolution = 512; - vk::Format format = vk::Format::eR16G16Sfloat; - sauce::ImageUtils::createImage(physicalDevice, logicalDevice, resolution, resolution, format, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, maps.brdfLUT, maps.brdfLUTMemory); - maps.brdfLUTView = sauce::ImageUtils::createImageView(logicalDevice, maps.brdfLUT, format, vk::ImageAspectFlagBits::eColor); + for (uint32_t mip = 0; mip < mipLevels; ++mip) { + float roughness = (float)mip / (float)(mipLevels - 1); + for (uint32_t face = 0; face < 6; ++face) { + // TODO: rendering each face for each mip level with specific roughness + } + } + + sauce::ImageUtils::transitionImageLayout( + logicalDevice, commandPool, queue, maps.prefilterMap, vk::ImageLayout::eUndefined, + vk::ImageLayout::eShaderReadOnlyOptimal, {}, vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eFragmentShader, + mipLevels, 6); + } - sauce::ImageUtils::transitionImageLayout(logicalDevice, commandPool, queue, maps.brdfLUT, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal, {}, vk::AccessFlagBits2::eShaderRead, vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eFragmentShader); -} + void IBLGenerator::generateBRDFLUT(IBLMaps& maps, const vk::raii::CommandPool& commandPool, + const vk::raii::Queue& queue) { + uint32_t resolution = 512; + vk::Format format = vk::Format::eR16G16Sfloat; + sauce::ImageUtils::createImage( + physicalDevice, logicalDevice, resolution, resolution, format, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, maps.brdfLUT, maps.brdfLUTMemory); + maps.brdfLUTView = sauce::ImageUtils::createImageView(logicalDevice, maps.brdfLUT, format, + vk::ImageAspectFlagBits::eColor); + + sauce::ImageUtils::transitionImageLayout( + logicalDevice, commandPool, queue, maps.brdfLUT, vk::ImageLayout::eUndefined, + vk::ImageLayout::eShaderReadOnlyOptimal, {}, vk::AccessFlagBits2::eShaderRead, + vk::PipelineStageFlagBits2::eNone, vk::PipelineStageFlagBits2::eFragmentShader); + } } // namespace sauce diff --git a/src/app/ImGuiRenderer.cpp b/src/app/ImGuiRenderer.cpp index 16a3d469..10338b34 100644 --- a/src/app/ImGuiRenderer.cpp +++ b/src/app/ImGuiRenderer.cpp @@ -9,128 +9,121 @@ namespace sauce { -ImGuiRenderer::ImGuiRenderer(const ImGuiRendererCreateInfo& createInfo) { - window = createInfo.window; - device = &*createInfo.logicalDevice; - - createDescriptorPool(createInfo.logicalDevice); - initImGui(createInfo); - uploadFonts(createInfo); -} - -ImGuiRenderer::~ImGuiRenderer() { - if (imguiContext) { - ImGui_ImplVulkan_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(imguiContext); - } -} - -void ImGuiRenderer::createDescriptorPool(const sauce::LogicalDevice& logicalDevice) { - // Descriptor pool with eFreeDescriptorSet flag for dynamic descriptor allocation - std::array poolSizes = {{ - { vk::DescriptorType::eSampler, 1000 }, - { vk::DescriptorType::eCombinedImageSampler, 1000 }, - { vk::DescriptorType::eSampledImage, 1000 }, - { vk::DescriptorType::eStorageImage, 1000 }, - { vk::DescriptorType::eUniformTexelBuffer, 1000 }, - { vk::DescriptorType::eStorageTexelBuffer, 1000 }, - { vk::DescriptorType::eUniformBuffer, 1000 }, - { vk::DescriptorType::eStorageBuffer, 1000 }, - { vk::DescriptorType::eUniformBufferDynamic, 1000 }, - { vk::DescriptorType::eStorageBufferDynamic, 1000 }, - { vk::DescriptorType::eInputAttachment, 1000 } - }}; - - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = 1000, - .poolSizeCount = static_cast(poolSizes.size()), - .pPoolSizes = poolSizes.data() - }; - - imguiDescriptorPool = vk::raii::DescriptorPool{ *logicalDevice, poolInfo }; -} - -void ImGuiRenderer::initImGui(const ImGuiRendererCreateInfo& createInfo) { - // Create ImGui context - imguiContext = ImGui::CreateContext(); - ImGui::SetCurrentContext(imguiContext); - - // Setup ImGui style - ImGui::StyleColorsDark(); - - // Initialize GLFW backend - ImGui_ImplGlfw_InitForVulkan(window, true); - - // Initialize Vulkan backend with dynamic rendering - VkFormat colorAttachmentFormat = static_cast(createInfo.swapChainFormat); - - VkFormat depthFormat = static_cast(createInfo.depthFormat); - - VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo{}; - pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; - pipelineRenderingCreateInfo.colorAttachmentCount = 1; - pipelineRenderingCreateInfo.pColorAttachmentFormats = &colorAttachmentFormat; - pipelineRenderingCreateInfo.depthAttachmentFormat = depthFormat; - - ImGui_ImplVulkan_InitInfo initInfo{}; - initInfo.Instance = *createInfo.instance; - initInfo.PhysicalDevice = **createInfo.physicalDevice; - initInfo.Device = **createInfo.logicalDevice; - initInfo.QueueFamily = createInfo.queueFamilyIndex; - initInfo.Queue = *createInfo.queue; - initInfo.DescriptorPool = *imguiDescriptorPool; - initInfo.MinImageCount = createInfo.imageCount; - initInfo.ImageCount = createInfo.imageCount; - initInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT; - initInfo.UseDynamicRendering = true; // Enable dynamic rendering - initInfo.PipelineRenderingCreateInfo = pipelineRenderingCreateInfo; - - if (!ImGui_ImplVulkan_Init(&initInfo)) { - throw std::runtime_error("Failed to initialize ImGui Vulkan backend"); - } -} - -void ImGuiRenderer::uploadFonts(const ImGuiRendererCreateInfo& createInfo) { - // Upload fonts to GPU using a one-time command buffer - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *createInfo.commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = - std::move(createInfo.logicalDevice->allocateCommandBuffers(allocInfo).front()); - - commandBuffer.begin(vk::CommandBufferBeginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }); - - ImGui_ImplVulkan_CreateFontsTexture(); - - commandBuffer.end(); - - createInfo.queue.submit(vk::SubmitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }, nullptr); - - createInfo.queue.waitIdle(); - - // Clean up font upload resources - ImGui_ImplVulkan_DestroyFontsTexture(); -} - -void ImGuiRenderer::newFrame() { - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); -} - -void ImGuiRenderer::render(const vk::raii::CommandBuffer& commandBuffer, uint32_t imageIndex) { - ImGui::Render(); - ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), *commandBuffer); -} + ImGuiRenderer::ImGuiRenderer(const ImGuiRendererCreateInfo& createInfo) { + window = createInfo.window; + device = &*createInfo.logicalDevice; + + createDescriptorPool(createInfo.logicalDevice); + initImGui(createInfo); + uploadFonts(createInfo); + } + + ImGuiRenderer::~ImGuiRenderer() { + if (imguiContext) { + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(imguiContext); + } + } + + void ImGuiRenderer::createDescriptorPool(const sauce::LogicalDevice& logicalDevice) { + // Descriptor pool with eFreeDescriptorSet flag for dynamic descriptor allocation + std::array poolSizes = { + {{vk::DescriptorType::eSampler, 1000}, + {vk::DescriptorType::eCombinedImageSampler, 1000}, + {vk::DescriptorType::eSampledImage, 1000}, + {vk::DescriptorType::eStorageImage, 1000}, + {vk::DescriptorType::eUniformTexelBuffer, 1000}, + {vk::DescriptorType::eStorageTexelBuffer, 1000}, + {vk::DescriptorType::eUniformBuffer, 1000}, + {vk::DescriptorType::eStorageBuffer, 1000}, + {vk::DescriptorType::eUniformBufferDynamic, 1000}, + {vk::DescriptorType::eStorageBufferDynamic, 1000}, + {vk::DescriptorType::eInputAttachment, 1000}}}; + + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = 1000, + .poolSizeCount = static_cast(poolSizes.size()), + .pPoolSizes = poolSizes.data()}; + + imguiDescriptorPool = vk::raii::DescriptorPool{*logicalDevice, poolInfo}; + } + + void ImGuiRenderer::initImGui(const ImGuiRendererCreateInfo& createInfo) { + // Create ImGui context + imguiContext = ImGui::CreateContext(); + ImGui::SetCurrentContext(imguiContext); + + // Setup ImGui style + ImGui::StyleColorsDark(); + + // Initialize GLFW backend + ImGui_ImplGlfw_InitForVulkan(window, true); + + // Initialize Vulkan backend with dynamic rendering + VkFormat colorAttachmentFormat = static_cast(createInfo.swapChainFormat); + + VkFormat depthFormat = static_cast(createInfo.depthFormat); + + VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo{}; + pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; + pipelineRenderingCreateInfo.colorAttachmentCount = 1; + pipelineRenderingCreateInfo.pColorAttachmentFormats = &colorAttachmentFormat; + pipelineRenderingCreateInfo.depthAttachmentFormat = depthFormat; + + ImGui_ImplVulkan_InitInfo initInfo{}; + initInfo.Instance = *createInfo.instance; + initInfo.PhysicalDevice = **createInfo.physicalDevice; + initInfo.Device = **createInfo.logicalDevice; + initInfo.QueueFamily = createInfo.queueFamilyIndex; + initInfo.Queue = *createInfo.queue; + initInfo.DescriptorPool = *imguiDescriptorPool; + initInfo.MinImageCount = createInfo.imageCount; + initInfo.ImageCount = createInfo.imageCount; + initInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + initInfo.UseDynamicRendering = true; // Enable dynamic rendering + initInfo.PipelineRenderingCreateInfo = pipelineRenderingCreateInfo; + + if (!ImGui_ImplVulkan_Init(&initInfo)) { + throw std::runtime_error("Failed to initialize ImGui Vulkan backend"); + } + } + + void ImGuiRenderer::uploadFonts(const ImGuiRendererCreateInfo& createInfo) { + // Upload fonts to GPU using a one-time command buffer + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *createInfo.commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = + std::move(createInfo.logicalDevice->allocateCommandBuffers(allocInfo).front()); + + commandBuffer.begin( + vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + + ImGui_ImplVulkan_CreateFontsTexture(); + + commandBuffer.end(); + + createInfo.queue.submit( + vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}, nullptr); + + createInfo.queue.waitIdle(); + + // Clean up font upload resources + ImGui_ImplVulkan_DestroyFontsTexture(); + } + + void ImGuiRenderer::newFrame() { + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + } + + void ImGuiRenderer::render(const vk::raii::CommandBuffer& commandBuffer, uint32_t imageIndex) { + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), *commandBuffer); + } } // namespace sauce diff --git a/src/app/Log.cpp b/src/app/Log.cpp index 49a38aaf..449f21ac 100644 --- a/src/app/Log.cpp +++ b/src/app/Log.cpp @@ -2,51 +2,53 @@ namespace sauce { -std::ofstream Log::logFile; -std::mutex Log::logMutex; -bool Log::palantirMode = true; -bool Log::initialized = false; + std::ofstream Log::logFile; + std::mutex Log::logMutex; + bool Log::palantirMode = true; + bool Log::initialized = false; -void Log::init(const std::string& filepath) { + void Log::init(const std::string& filepath) { #ifdef NDEBUG - (void)filepath; + (void)filepath; #else - std::lock_guard lock(logMutex); - if (initialized) return; + std::lock_guard lock(logMutex); + if (initialized) + return; - logFile.open(filepath, std::ios::out | std::ios::trunc); - initialized = logFile.is_open(); + logFile.open(filepath, std::ios::out | std::ios::trunc); + initialized = logFile.is_open(); #endif -} + } -void Log::shutdown() { + void Log::shutdown() { #ifndef NDEBUG - std::lock_guard lock(logMutex); - if (logFile.is_open()) { - logFile.flush(); - logFile.close(); - } - initialized = false; + std::lock_guard lock(logMutex); + if (logFile.is_open()) { + logFile.flush(); + logFile.close(); + } + initialized = false; #endif -} + } -void Log::setPalantirMode(bool enabled) { - palantirMode = enabled; -} + void Log::setPalantirMode(bool enabled) { + palantirMode = enabled; + } -bool Log::isPalantirMode() { - return palantirMode; -} + bool Log::isPalantirMode() { + return palantirMode; + } -void Log::write(const std::string& category, const std::string& message) { - std::lock_guard lock(logMutex); - if (!initialized) return; + void Log::write(const std::string& category, const std::string& message) { + std::lock_guard lock(logMutex); + if (!initialized) + return; - auto now = std::chrono::system_clock::now(); - auto time = std::chrono::floor(now); + auto now = std::chrono::system_clock::now(); + auto time = std::chrono::floor(now); - logFile << std::format("[{:%H:%M:%S}] [{}] {}\n", time, category, message); - logFile.flush(); -} + logFile << std::format("[{:%H:%M:%S}] [{}] {}\n", time, category, message); + logFile.flush(); + } } // namespace sauce diff --git a/src/app/ModelViewerRenderer.cpp b/src/app/ModelViewerRenderer.cpp index 8ba67902..fa2e664e 100644 --- a/src/app/ModelViewerRenderer.cpp +++ b/src/app/ModelViewerRenderer.cpp @@ -1,421 +1,395 @@ #include #include -#include #include +#include namespace sauce { -ModelViewerRenderer::ModelViewerRenderer(const RendererCreateInfo& createInfo) { - queueIndex = createInfo.logicalDevice.getQueueIndex(); - pQueue = std::make_unique(*createInfo.logicalDevice, queueIndex, 0); - - pSwapChain = std::make_unique( - createInfo.physicalDevice, - createInfo.logicalDevice, - createInfo.renderSurface, - createInfo.window - ); - - createDescriptorSetLayout(createInfo.logicalDevice); - sauce::GraphicsPipelineConfig mainPipelineConfig { - .physicalDevice = createInfo.physicalDevice, - .logicalDevice = createInfo.logicalDevice, - .descriptorSetLayouts = { *descriptorSetLayout }, - .colorFormat = pSwapChain->getSurfaceFormat().format, - }; - pPipeline = std::make_unique(mainPipelineConfig, "shaders/shader_3d.vert.spv", "shaders/shader_3d.frag.spv"); - - vk::CommandPoolCreateInfo commandPoolCreateInfo { - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex, - }; - - commandPool = vk::raii::CommandPool { *createInfo.logicalDevice, commandPoolCreateInfo }; - - vk::CommandBufferAllocateInfo allocInfo { - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT, - }; - - commandBuffers = vk::raii::CommandBuffers(*createInfo.logicalDevice, allocInfo); - - createUniformBuffers(createInfo.physicalDevice, createInfo.logicalDevice); - createDescriptorSets(createInfo.logicalDevice); - - for (size_t i = 0; i < pSwapChain->getImages().size(); ++i) { - renderFinishedSemaphores.emplace_back(*createInfo.logicalDevice, vk::SemaphoreCreateInfo{}); - } - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { - presentCompleteSemaphores.emplace_back(*createInfo.logicalDevice, vk::SemaphoreCreateInfo{}); - inFlightFences.emplace_back(*createInfo.logicalDevice, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } -} - -void ModelViewerRenderer::loadModel( - const std::string& modelPath, - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice) { - - std::cout << "Loading model: " << modelPath << std::endl; - - sauce::modeling::GLTFLoader loader; - pModel = loader.loadModel(modelPath); - - if (!pModel) { - throw std::runtime_error("Failed to load model: " + modelPath); - } - - const auto& meshes = pModel->getAllMeshes(); - std::cout << "Model loaded with " << meshes.size() << " mesh(es)" << std::endl; - - if (meshes.empty()) { - throw std::runtime_error("Model has no meshes: " + modelPath); - } - - for (const auto& mesh : meshes) { - uploadMeshToGPU(mesh, physicalDevice, logicalDevice); - } - - std::cout << "All meshes uploaded to GPU" << std::endl; -} - -void ModelViewerRenderer::uploadMeshToGPU( - const std::shared_ptr& mesh, - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice) { - - MeshGPUData gpuData; - - const auto& vertices = mesh->getVertices(); - const auto& indices = mesh->getIndices(); - - // Upload vertex buffer - vk::DeviceSize vertexBufferSize = sizeof(Vertex) * vertices.size(); - - vk::raii::Buffer stagingVertexBuffer = nullptr; - vk::raii::DeviceMemory stagingVertexBufferMemory = nullptr; - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - vertexBufferSize, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingVertexBuffer, - stagingVertexBufferMemory - ); - - void* dataStaging = stagingVertexBufferMemory.mapMemory(0, vertexBufferSize); - memcpy(dataStaging, vertices.data(), vertexBufferSize); - stagingVertexBufferMemory.unmapMemory(); - - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - vertexBufferSize, - vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, - vk::MemoryPropertyFlagBits::eDeviceLocal, - gpuData.vertexBuffer, - gpuData.vertexBufferMemory - ); - - sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, *pQueue, stagingVertexBuffer, gpuData.vertexBuffer, vertexBufferSize); - - // Upload index buffer - vk::DeviceSize indexBufferSize = sizeof(uint32_t) * indices.size(); - - vk::raii::Buffer stagingIndexBuffer = nullptr; - vk::raii::DeviceMemory stagingIndexBufferMemory = nullptr; - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - indexBufferSize, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingIndexBuffer, - stagingIndexBufferMemory - ); - - void* indexDataStaging = stagingIndexBufferMemory.mapMemory(0, indexBufferSize); - memcpy(indexDataStaging, indices.data(), indexBufferSize); - stagingIndexBufferMemory.unmapMemory(); - - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - indexBufferSize, - vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, - vk::MemoryPropertyFlagBits::eDeviceLocal, - gpuData.indexBuffer, - gpuData.indexBufferMemory - ); - - sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, *pQueue, stagingIndexBuffer, gpuData.indexBuffer, indexBufferSize); - - gpuData.indexCount = static_cast(indices.size()); - - meshBuffers.push_back(std::move(gpuData)); -} - -void ModelViewerRenderer::createDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice) { - vk::DescriptorSetLayoutBinding uboLayoutBinding { - .binding = 0, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eVertex, - }; - vk::DescriptorSetLayoutCreateInfo dsLayoutInfo { - .bindingCount = 1, - .pBindings = &uboLayoutBinding, - }; - - descriptorSetLayout = vk::raii::DescriptorSetLayout{ *logicalDevice, dsLayoutInfo }; -} - -void ModelViewerRenderer::createDescriptorSets(const sauce::LogicalDevice& logicalDevice) { - vk::DescriptorPoolSize poolSize { - .type = vk::DescriptorType::eUniformBuffer, - .descriptorCount = MAX_FRAMES_IN_FLIGHT, - }; - - vk::DescriptorPoolCreateInfo poolCreateInfo { - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = 1, - .pPoolSizes = &poolSize, - }; - - descriptorPool = vk::raii::DescriptorPool{ *logicalDevice, poolCreateInfo }; - - std::vector layouts{ MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout }; - vk::DescriptorSetAllocateInfo dsAllocInfo { - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets = logicalDevice->allocateDescriptorSets(dsAllocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { - vk::DescriptorBufferInfo bufferInfo { - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject), - }; - - vk::WriteDescriptorSet descriptorWrite { - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo, - }; - - logicalDevice->updateDescriptorSets(descriptorWrite, {}); - } -} - -void ModelViewerRenderer::createUniformBuffers( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice) { - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { - vk::DeviceSize size = sizeof(UniformBufferObject); - vk::raii::Buffer buf = nullptr; - vk::raii::DeviceMemory mem = nullptr; - sauce::BufferUtils::createBuffer( - physicalDevice, - logicalDevice, - size, - vk::BufferUsageFlagBits::eUniformBuffer, - vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible, - buf, - mem - ); - - uniformBuffers.emplace_back(std::move(buf)); - uniformBuffersMemory.emplace_back(std::move(mem)); - uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, size)); - } -} - -void ModelViewerRenderer::transitionImageLayout( - uint32_t imageIndex, - vk::ImageLayout oldLayout, - vk::ImageLayout newLayout, - vk::AccessFlags2 srcAccessMask, - vk::AccessFlags2 dstAccessMask, - vk::PipelineStageFlags2 srcStageMask, - vk::PipelineStageFlags2 dstStageMask) { - - vk::ImageMemoryBarrier2 barrier { - .srcStageMask = srcStageMask, - .srcAccessMask = srcAccessMask, - .dstStageMask = dstStageMask, - .dstAccessMask = dstAccessMask, - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = vk::QueueFamilyIgnored, - .dstQueueFamilyIndex = vk::QueueFamilyIgnored, - .image = pSwapChain->getImages()[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }; - - vk::DependencyInfo dependencyInfo { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier, - }; - - commandBuffers[frameIndex].pipelineBarrier2(dependencyInfo); -} - -void ModelViewerRenderer::recordCommandBuffer(uint32_t imageIndex, sauce::ImGuiRenderer* imguiRenderer) { - commandBuffers[frameIndex].begin({}); - - transitionImageLayout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eColorAttachmentOutput - ); - - vk::ClearValue clearColor = vk::ClearColorValue { 0.1f, 0.1f, 0.1f, 1.0f }; - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = pSwapChain->getImageViews()[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor, - }; - - vk::RenderingInfo renderingInfo { - .renderArea = { - .offset = { 0, 0 }, - .extent = pSwapChain->getExtent(), - }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo, - }; - - commandBuffers[frameIndex].beginRendering(renderingInfo); - - commandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, **pPipeline); - commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pPipeline->getLayout(), 0, *descriptorSets[frameIndex], nullptr); - - commandBuffers[frameIndex].setViewport( - 0, vk::Viewport(0.0f, 0.0f, static_cast(pSwapChain->getExtent().width), - static_cast(pSwapChain->getExtent().height), 0.0f, 1.0f)); - commandBuffers[frameIndex].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), pSwapChain->getExtent())); - - // Render all meshes - for (const auto& meshData : meshBuffers) { - commandBuffers[frameIndex].bindVertexBuffers(0, *meshData.vertexBuffer, {0}); - commandBuffers[frameIndex].bindIndexBuffer(*meshData.indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[frameIndex].drawIndexed(meshData.indexCount, 1, 0, 0, 0); - } - - // Render ImGui overlay - if (imguiRenderer) { - imguiRenderer->render(commandBuffers[frameIndex], imageIndex); - } - - commandBuffers[frameIndex].endRendering(); - - transitionImageLayout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe - ); - - commandBuffers[frameIndex].end(); -} - -void ModelViewerRenderer::updateUniformBuffer(uint32_t curImage, const sauce::Camera& camera, float modelRotationAngle) { - sauce::UniformBufferObject ubo { - .model = glm::rotate(glm::mat4(1.0f), modelRotationAngle, glm::vec3(0.0f, 1.0f, 0.0f)), - .view = camera.getViewMatrix(), - .proj = camera.getProjectionMatrix(), - }; - - // Flip Y coordinate of projection matrix (Vulkan uses inverted Y compared to OpenGL) - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[curImage], &ubo, sizeof(ubo)); -} - -void ModelViewerRenderer::drawFrame( - const sauce::LogicalDevice& logicalDevice, - const sauce::Camera& camera, - float modelRotationAngle, - sauce::ImGuiRenderer* imguiRenderer) { - - // Wait for the previous frame to finish rendering - auto fenceResult = logicalDevice->waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX); - if (fenceResult != vk::Result::eSuccess) { - throw std::runtime_error("Failed to wait for fence!"); - } - - // Request the next available image from the swap chain - auto [result, imageIndex] = (*pSwapChain)->acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); - - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("Failed to acquire swap chain image"); - } - - // Reset the fence for the next frame - logicalDevice->resetFences(*inFlightFences[frameIndex]); - - // Reset and record the command buffer - commandBuffers[frameIndex].reset(); - recordCommandBuffer(imageIndex, imguiRenderer); - - // Update the uniform buffer - updateUniformBuffer(frameIndex, camera, modelRotationAngle); - - // Submit command buffer - vk::PipelineStageFlags waitDestinationStageMask { vk::PipelineStageFlagBits::eColorAttachmentOutput }; - const vk::SubmitInfo submitInfo { - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[frameIndex], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex], - }; - - pQueue->submit(submitInfo, *inFlightFences[frameIndex]); - - // Present the image - const vk::PresentInfoKHR presentInfoKHR { - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], - .swapchainCount = 1, - .pSwapchains = &***pSwapChain, - .pImageIndices = &imageIndex, - }; - - result = pQueue->presentKHR(presentInfoKHR); - if (result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to present swap chain image!"); - } - - frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; -} - -} + ModelViewerRenderer::ModelViewerRenderer(const RendererCreateInfo& createInfo) { + queueIndex = createInfo.logicalDevice.getQueueIndex(); + pQueue = std::make_unique(*createInfo.logicalDevice, queueIndex, 0); + + pSwapChain = + std::make_unique(createInfo.physicalDevice, createInfo.logicalDevice, + createInfo.renderSurface, createInfo.window); + + createDescriptorSetLayout(createInfo.logicalDevice); + sauce::GraphicsPipelineConfig mainPipelineConfig{ + .physicalDevice = createInfo.physicalDevice, + .logicalDevice = createInfo.logicalDevice, + .descriptorSetLayouts = {*descriptorSetLayout}, + .colorFormat = pSwapChain->getSurfaceFormat().format, + }; + pPipeline = std::make_unique( + mainPipelineConfig, "shaders/shader_3d.vert.spv", "shaders/shader_3d.frag.spv"); + + vk::CommandPoolCreateInfo commandPoolCreateInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex, + }; + + commandPool = vk::raii::CommandPool{*createInfo.logicalDevice, commandPoolCreateInfo}; + + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = MAX_FRAMES_IN_FLIGHT, + }; + + commandBuffers = vk::raii::CommandBuffers(*createInfo.logicalDevice, allocInfo); + + createUniformBuffers(createInfo.physicalDevice, createInfo.logicalDevice); + createDescriptorSets(createInfo.logicalDevice); + + for (size_t i = 0; i < pSwapChain->getImages().size(); ++i) { + renderFinishedSemaphores.emplace_back(*createInfo.logicalDevice, + vk::SemaphoreCreateInfo{}); + } + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + presentCompleteSemaphores.emplace_back(*createInfo.logicalDevice, + vk::SemaphoreCreateInfo{}); + inFlightFences.emplace_back( + *createInfo.logicalDevice, + vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void ModelViewerRenderer::loadModel(const std::string& modelPath, + const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + std::cout << "Loading model: " << modelPath << std::endl; + + sauce::modeling::GLTFLoader loader; + pModel = loader.loadModel(modelPath); + + if (!pModel) { + throw std::runtime_error("Failed to load model: " + modelPath); + } + + const auto& meshes = pModel->getAllMeshes(); + std::cout << "Model loaded with " << meshes.size() << " mesh(es)" << std::endl; + + if (meshes.empty()) { + throw std::runtime_error("Model has no meshes: " + modelPath); + } + + for (const auto& mesh : meshes) { + uploadMeshToGPU(mesh, physicalDevice, logicalDevice); + } + + std::cout << "All meshes uploaded to GPU" << std::endl; + } + + void ModelViewerRenderer::uploadMeshToGPU(const std::shared_ptr& mesh, + const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + MeshGPUData gpuData; + + const auto& vertices = mesh->getVertices(); + const auto& indices = mesh->getIndices(); + + // Upload vertex buffer + vk::DeviceSize vertexBufferSize = sizeof(Vertex) * vertices.size(); + + vk::raii::Buffer stagingVertexBuffer = nullptr; + vk::raii::DeviceMemory stagingVertexBufferMemory = nullptr; + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, vertexBufferSize, vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + stagingVertexBuffer, stagingVertexBufferMemory); + + void* dataStaging = stagingVertexBufferMemory.mapMemory(0, vertexBufferSize); + memcpy(dataStaging, vertices.data(), vertexBufferSize); + stagingVertexBufferMemory.unmapMemory(); + + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, vertexBufferSize, + vk::BufferUsageFlagBits::eVertexBuffer | + vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eDeviceLocal, + gpuData.vertexBuffer, gpuData.vertexBufferMemory); + + sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, *pQueue, stagingVertexBuffer, + gpuData.vertexBuffer, vertexBufferSize); + + // Upload index buffer + vk::DeviceSize indexBufferSize = sizeof(uint32_t) * indices.size(); + + vk::raii::Buffer stagingIndexBuffer = nullptr; + vk::raii::DeviceMemory stagingIndexBufferMemory = nullptr; + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, indexBufferSize, vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + stagingIndexBuffer, stagingIndexBufferMemory); + + void* indexDataStaging = stagingIndexBufferMemory.mapMemory(0, indexBufferSize); + memcpy(indexDataStaging, indices.data(), indexBufferSize); + stagingIndexBufferMemory.unmapMemory(); + + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, indexBufferSize, + vk::BufferUsageFlagBits::eIndexBuffer | + vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eDeviceLocal, + gpuData.indexBuffer, gpuData.indexBufferMemory); + + sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, *pQueue, stagingIndexBuffer, + gpuData.indexBuffer, indexBufferSize); + + gpuData.indexCount = static_cast(indices.size()); + + meshBuffers.push_back(std::move(gpuData)); + } + + void ModelViewerRenderer::createDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice) { + vk::DescriptorSetLayoutBinding uboLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eVertex, + }; + vk::DescriptorSetLayoutCreateInfo dsLayoutInfo{ + .bindingCount = 1, + .pBindings = &uboLayoutBinding, + }; + + descriptorSetLayout = vk::raii::DescriptorSetLayout{*logicalDevice, dsLayoutInfo}; + } + + void ModelViewerRenderer::createDescriptorSets(const sauce::LogicalDevice& logicalDevice) { + vk::DescriptorPoolSize poolSize{ + .type = vk::DescriptorType::eUniformBuffer, + .descriptorCount = MAX_FRAMES_IN_FLIGHT, + }; + + vk::DescriptorPoolCreateInfo poolCreateInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = 1, + .pPoolSizes = &poolSize, + }; + + descriptorPool = vk::raii::DescriptorPool{*logicalDevice, poolCreateInfo}; + + std::vector layouts{MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout}; + vk::DescriptorSetAllocateInfo dsAllocInfo{.descriptorPool = descriptorPool, + .descriptorSetCount = + static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets = logicalDevice->allocateDescriptorSets(dsAllocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject), + }; + + vk::WriteDescriptorSet descriptorWrite{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo, + }; + + logicalDevice->updateDescriptorSets(descriptorWrite, {}); + } + } + + void ModelViewerRenderer::createUniformBuffers(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice) { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + vk::DeviceSize size = sizeof(UniformBufferObject); + vk::raii::Buffer buf = nullptr; + vk::raii::DeviceMemory mem = nullptr; + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, size, + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostCoherent | + vk::MemoryPropertyFlagBits::eHostVisible, + buf, mem); + + uniformBuffers.emplace_back(std::move(buf)); + uniformBuffersMemory.emplace_back(std::move(mem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, size)); + } + } + + void ModelViewerRenderer::transitionImageLayout(uint32_t imageIndex, vk::ImageLayout oldLayout, + vk::ImageLayout newLayout, + vk::AccessFlags2 srcAccessMask, + vk::AccessFlags2 dstAccessMask, + vk::PipelineStageFlags2 srcStageMask, + vk::PipelineStageFlags2 dstStageMask) { + vk::ImageMemoryBarrier2 barrier{ + .srcStageMask = srcStageMask, + .srcAccessMask = srcAccessMask, + .dstStageMask = dstStageMask, + .dstAccessMask = dstAccessMask, + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = pSwapChain->getImages()[imageIndex], + .subresourceRange = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + vk::DependencyInfo dependencyInfo{ + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier, + }; + + commandBuffers[frameIndex].pipelineBarrier2(dependencyInfo); + } + + void ModelViewerRenderer::recordCommandBuffer(uint32_t imageIndex, + sauce::ImGuiRenderer* imguiRenderer) { + commandBuffers[frameIndex].begin({}); + + transitionImageLayout(imageIndex, vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput); + + vk::ClearValue clearColor = vk::ClearColorValue{0.1f, 0.1f, 0.1f, 1.0f}; + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = pSwapChain->getImageViews()[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor, + }; + + vk::RenderingInfo renderingInfo{ + .renderArea = + { + .offset = {0, 0}, + .extent = pSwapChain->getExtent(), + }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo, + }; + + commandBuffers[frameIndex].beginRendering(renderingInfo); + + commandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, **pPipeline); + commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + pPipeline->getLayout(), 0, + *descriptorSets[frameIndex], nullptr); + + commandBuffers[frameIndex].setViewport( + 0, vk::Viewport(0.0f, 0.0f, static_cast(pSwapChain->getExtent().width), + static_cast(pSwapChain->getExtent().height), 0.0f, 1.0f)); + commandBuffers[frameIndex].setScissor( + 0, vk::Rect2D(vk::Offset2D(0, 0), pSwapChain->getExtent())); + + // Render all meshes + for (const auto& meshData : meshBuffers) { + commandBuffers[frameIndex].bindVertexBuffers(0, *meshData.vertexBuffer, {0}); + commandBuffers[frameIndex].bindIndexBuffer(*meshData.indexBuffer, 0, + vk::IndexType::eUint32); + commandBuffers[frameIndex].drawIndexed(meshData.indexCount, 1, 0, 0, 0); + } + + // Render ImGui overlay + if (imguiRenderer) { + imguiRenderer->render(commandBuffers[frameIndex], imageIndex); + } + + commandBuffers[frameIndex].endRendering(); + + transitionImageLayout(imageIndex, vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe); + + commandBuffers[frameIndex].end(); + } + + void ModelViewerRenderer::updateUniformBuffer(uint32_t curImage, const sauce::Camera& camera, + float modelRotationAngle) { + sauce::UniformBufferObject ubo{ + .model = glm::rotate(glm::mat4(1.0f), modelRotationAngle, glm::vec3(0.0f, 1.0f, 0.0f)), + .view = camera.getViewMatrix(), + .proj = camera.getProjectionMatrix(), + }; + + // Flip Y coordinate of projection matrix (Vulkan uses inverted Y compared to OpenGL) + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[curImage], &ubo, sizeof(ubo)); + } + + void ModelViewerRenderer::drawFrame(const sauce::LogicalDevice& logicalDevice, + const sauce::Camera& camera, float modelRotationAngle, + sauce::ImGuiRenderer* imguiRenderer) { + // Wait for the previous frame to finish rendering + auto fenceResult = + logicalDevice->waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX); + if (fenceResult != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence!"); + } + + // Request the next available image from the swap chain + auto [result, imageIndex] = + (*pSwapChain) + ->acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); + + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { + throw std::runtime_error("Failed to acquire swap chain image"); + } + + // Reset the fence for the next frame + logicalDevice->resetFences(*inFlightFences[frameIndex]); + + // Reset and record the command buffer + commandBuffers[frameIndex].reset(); + recordCommandBuffer(imageIndex, imguiRenderer); + + // Update the uniform buffer + updateUniformBuffer(frameIndex, camera, modelRotationAngle); + + // Submit command buffer + vk::PipelineStageFlags waitDestinationStageMask{ + vk::PipelineStageFlagBits::eColorAttachmentOutput}; + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex], + }; + + pQueue->submit(submitInfo, *inFlightFences[frameIndex]); + + // Present the image + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &***pSwapChain, + .pImageIndices = &imageIndex, + }; + + result = pQueue->presentKHR(presentInfoKHR); + if (result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to present swap chain image!"); + } + + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; + } + +} // namespace sauce diff --git a/src/app/SauceEngineApp.cpp b/src/app/SauceEngineApp.cpp index 281fa529..8c27c9b5 100644 --- a/src/app/SauceEngineApp.cpp +++ b/src/app/SauceEngineApp.cpp @@ -1,461 +1,452 @@ #include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include #include +#include namespace sauce { -SauceEngineApp::SauceEngineApp() { - pImGuiComponentManager = std::make_unique(); -} + SauceEngineApp::SauceEngineApp() { + pImGuiComponentManager = std::make_unique(); + } -SauceEngineApp::~SauceEngineApp() { - glfwDestroyWindow(window); - glfwTerminate(); -} + SauceEngineApp::~SauceEngineApp() { + glfwDestroyWindow(window); + glfwTerminate(); + } -void SauceEngineApp::run() { - initWindow(); - initVulkan(); + void SauceEngineApp::run() { + initWindow(); + initVulkan(); - // Load scene file if specified - if (!sceneFile.empty() && pScene) { - if (pScene->loadFromFile(sceneFile) && !pScene->getEntities().empty()) { - uploadMeshGPUResources(); - setupSceneRenderer(); - } - } - - - // Call custom UI builder after ImGui is initialized - if (pCustomUIBuilder) { - pCustomUIBuilder(*pImGuiComponentManager); - } - - mainLoop(); -} - -void SauceEngineApp::setCustomUIBuilder(std::function builder) { - pCustomUIBuilder = std::move(builder); -} - -void SauceEngineApp::initVulkan() { - uint32_t glfwExtensionsCount = 0; - const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionsCount); - pInstance = std::make_unique(glfwExtensions, glfwExtensionsCount); - - pRenderSurface = std::make_unique(*pInstance, window); - - physicalDevice = { *pInstance }; - logicalDevice = { physicalDevice, *pRenderSurface }; - - sauce::CameraCreateInfo cameraCreateInfo { - .scrWidth = WIDTH, - .scrHeight = HEIGHT, - }; - - pScene = std::make_unique( cameraCreateInfo ); - - sauce::RendererCreateInfo rendererCreateInfo { - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .renderSurface = *pRenderSurface, - .window = window, - }; - - pRenderer = std::make_unique(rendererCreateInfo); - - // Initialize ImGui - sauce::ImGuiRendererCreateInfo imguiCreateInfo{ - .instance = **pInstance, - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .queueFamilyIndex = logicalDevice.getQueueIndex(), - .window = window, - .queue = pRenderer->getQueue(), - .commandPool = pRenderer->getCommandPool(), - .swapChain = pRenderer->getSwapChain(), - .imageCount = static_cast(pRenderer->getSwapChain().getImages().size()), - .swapChainFormat = pRenderer->getSwapChain().getSurfaceFormat().format, - .depthFormat = sauce::GraphicsPipeline::findDepthFormat(physicalDevice), - }; - - pImGuiRenderer = std::make_unique(imguiCreateInfo); - - // Add default UI components - pImGuiComponentManager->addComponent(std::make_unique()); - pImGuiComponentManager->addComponent(std::make_unique()); - } - -void SauceEngineApp::initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Playground", nullptr, nullptr); - - glfwSetWindowUserPointer(window, this); - glfwSetCursorPosCallback(window, mouseCallback); - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - } - -void SauceEngineApp::processInput(float deltaTime) { - if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) - glfwSetWindowShouldClose(window, true); - - if (glfwGetKey(window, GLFW_KEY_GRAVE_ACCENT) == GLFW_PRESS && !gravePressedLastFrame) { - cursorCaptured = !cursorCaptured; - glfwSetInputMode(window, GLFW_CURSOR, cursorCaptured ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); - firstMouse = true; + // Load scene file if specified + if (!sceneFile.empty() && pScene) { + if (pScene->loadFromFile(sceneFile) && !pScene->getEntities().empty()) { + uploadMeshGPUResources(); + setupSceneRenderer(); + } + } + + // Call custom UI builder after ImGui is initialized + if (pCustomUIBuilder) { + pCustomUIBuilder(*pImGuiComponentManager); + } + + mainLoop(); } - gravePressedLastFrame = glfwGetKey(window, GLFW_KEY_GRAVE_ACCENT) == GLFW_PRESS; - if (!cursorCaptured || !pScene) { - return; + void SauceEngineApp::setCustomUIBuilder( + std::function builder) { + pCustomUIBuilder = std::move(builder); } - auto& camera = pScene->getCameraRW(); - - if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) - camera.processDirection(Camera::Movement::FORWARD, deltaTime); - if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) - camera.processDirection(Camera::Movement::BACKWARD, deltaTime); - if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) - camera.processDirection(Camera::Movement::LEFT, deltaTime); - if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) - camera.processDirection(Camera::Movement::RIGHT, deltaTime); - } - -void SauceEngineApp::mouseCallback(GLFWwindow* window, double xposIn, double yposIn) { - auto* app = static_cast(glfwGetWindowUserPointer(window)); - if (!app || !app->cursorCaptured) { - return; + void SauceEngineApp::initVulkan() { + uint32_t glfwExtensionsCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionsCount); + pInstance = std::make_unique(glfwExtensions, glfwExtensionsCount); + + pRenderSurface = std::make_unique(*pInstance, window); + + physicalDevice = {*pInstance}; + logicalDevice = {physicalDevice, *pRenderSurface}; + + sauce::CameraCreateInfo cameraCreateInfo{ + .scrWidth = WIDTH, + .scrHeight = HEIGHT, + }; + + pScene = std::make_unique(cameraCreateInfo); + + sauce::RendererCreateInfo rendererCreateInfo{ + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .renderSurface = *pRenderSurface, + .window = window, + }; + + pRenderer = std::make_unique(rendererCreateInfo); + + // Initialize ImGui + sauce::ImGuiRendererCreateInfo imguiCreateInfo{ + .instance = **pInstance, + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .queueFamilyIndex = logicalDevice.getQueueIndex(), + .window = window, + .queue = pRenderer->getQueue(), + .commandPool = pRenderer->getCommandPool(), + .swapChain = pRenderer->getSwapChain(), + .imageCount = static_cast(pRenderer->getSwapChain().getImages().size()), + .swapChainFormat = pRenderer->getSwapChain().getSurfaceFormat().format, + .depthFormat = sauce::GraphicsPipeline::findDepthFormat(physicalDevice), + }; + + pImGuiRenderer = std::make_unique(imguiCreateInfo); + + // Add default UI components + pImGuiComponentManager->addComponent(std::make_unique()); + pImGuiComponentManager->addComponent(std::make_unique()); } - float xpos = static_cast(xposIn); - float ypos = static_cast(yposIn); + void SauceEngineApp::initWindow() { + glfwInit(); - if (app->firstMouse) { - app->lastX = xpos; - app->lastY = ypos; - app->firstMouse = false; - } + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - float xoffset = xpos - app->lastX; - float yoffset = app->lastY - ypos; + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Playground", nullptr, nullptr); - app->lastX = xpos; - app->lastY = ypos; + glfwSetWindowUserPointer(window, this); + glfwSetCursorPosCallback(window, mouseCallback); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + } - if (!app->pScene) { - return; + void SauceEngineApp::processInput(float deltaTime) { + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) + glfwSetWindowShouldClose(window, true); + + if (glfwGetKey(window, GLFW_KEY_GRAVE_ACCENT) == GLFW_PRESS && !gravePressedLastFrame) { + cursorCaptured = !cursorCaptured; + glfwSetInputMode(window, GLFW_CURSOR, + cursorCaptured ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); + firstMouse = true; + } + gravePressedLastFrame = glfwGetKey(window, GLFW_KEY_GRAVE_ACCENT) == GLFW_PRESS; + + if (!cursorCaptured || !pScene) { + return; + } + + auto& camera = pScene->getCameraRW(); + + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) + camera.processDirection(Camera::Movement::FORWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) + camera.processDirection(Camera::Movement::BACKWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) + camera.processDirection(Camera::Movement::LEFT, deltaTime); + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + camera.processDirection(Camera::Movement::RIGHT, deltaTime); } - app->pScene->getCameraRW().processMouseMovement(xoffset, yoffset); - } + void SauceEngineApp::mouseCallback(GLFWwindow* window, double xposIn, double yposIn) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + if (!app || !app->cursorCaptured) { + return; + } + + float xpos = static_cast(xposIn); + float ypos = static_cast(yposIn); + + if (app->firstMouse) { + app->lastX = xpos; + app->lastY = ypos; + app->firstMouse = false; + } -void SauceEngineApp::mainLoop() { - while (!glfwWindowShouldClose(window)) { - auto currentFrameTime = std::chrono::steady_clock::now(); - deltaTime = std::chrono::duration(currentFrameTime - lastFrameTime).count(); - lastFrameTime = currentFrameTime; + float xoffset = xpos - app->lastX; + float yoffset = app->lastY - ypos; - glfwPollEvents(); - processInput(deltaTime); - syncRigidBodiesToTransforms(); + app->lastX = xpos; + app->lastY = ypos; - pImGuiRenderer->newFrame(); - buildExampleUI(); + if (!app->pScene) { + return; + } - pRenderer->drawFrame(logicalDevice, *pScene, pImGuiRenderer.get()); + app->pScene->getCameraRW().processMouseMovement(xoffset, yoffset); } - logicalDevice->waitIdle(); - } + void SauceEngineApp::mainLoop() { + while (!glfwWindowShouldClose(window)) { + auto currentFrameTime = std::chrono::steady_clock::now(); + deltaTime = std::chrono::duration(currentFrameTime - lastFrameTime).count(); + lastFrameTime = currentFrameTime; -void SauceEngineApp::buildExampleUI() { - pImGuiComponentManager->renderAll(); - } + glfwPollEvents(); + processInput(deltaTime); + syncRigidBodiesToTransforms(); -void SauceEngineApp::syncRigidBodiesToTransforms() { - if (!pScene) { - return; - } + pImGuiRenderer->newFrame(); + buildExampleUI(); - for (auto& entity : pScene->getEntitiesMut()) { - auto* rigidBody = entity.getComponent(); - auto* transform = entity.getComponent(); - if (!rigidBody || !transform) { - continue; - } + pRenderer->drawFrame(logicalDevice, *pScene, pImGuiRenderer.get()); + } - transform->setTranslation(rigidBody->getPosition()); - transform->setRotation(rigidBody->getOrientation()); + logicalDevice->waitIdle(); } - } - -void SauceEngineApp::uploadMeshGPUResources() { - for (auto& entity : pScene->getEntitiesMut()) { - auto mrcs = entity.getComponents(); - for (auto* mrc : mrcs) { - auto mesh = mrc->getMesh(); - if (mesh && mesh->isValid() && !mesh->hasGPUData()) { - auto& physDev = const_cast(*physicalDevice); - auto& cmdPool = const_cast(pRenderer->getCommandPool()); - auto& queue = const_cast(pRenderer->getQueue()); - mesh->initVulkanResources(logicalDevice, physDev, cmdPool, queue); - } - - auto material = mrc->getMaterial(); - if (material && !material->hasDescriptorSet()) { - auto& physDev = const_cast(*physicalDevice); - auto& cmdPool = const_cast(pRenderer->getCommandPool()); - auto& queue = const_cast(pRenderer->getQueue()); - material->initVulkanResources( - logicalDevice, physDev, cmdPool, queue, - pRenderer->getDescriptorPool(), - pRenderer->getDefaultImageView(), - pRenderer->getDefaultSampler() - ); - } + + void SauceEngineApp::buildExampleUI() { + pImGuiComponentManager->renderAll(); } - } -} -void SauceEngineApp::setupSceneRenderer() { - pRenderer->setCommandBufferRecorder( - [this](vk::raii::CommandBuffer& cmd, uint32_t imageIndex) { - recordSceneCommandBuffer(cmd, imageIndex); + void SauceEngineApp::syncRigidBodiesToTransforms() { + if (!pScene) { + return; + } + + for (auto& entity : pScene->getEntitiesMut()) { + auto* rigidBody = entity.getComponent(); + auto* transform = entity.getComponent(); + if (!rigidBody || !transform) { + continue; + } + + transform->setTranslation(rigidBody->getPosition()); + transform->setRotation(rigidBody->getOrientation()); + } } - ); -} - -void SauceEngineApp::recordSceneCommandBuffer(vk::raii::CommandBuffer& cmd, uint32_t imageIndex) { - // Write camera matrices to UBO (host side, before GPU execution) - sauce::UniformBufferObject ubo { - .model = glm::mat4(1.0f), - .view = pScene->getCameraRO().getViewMatrix(), - .proj = pScene->getCameraRO().getProjectionMatrix(), - .cameraPos = pScene->getCameraRO().getPos(), - }; - ubo.proj[1][1] *= -1; // Vulkan Y-flip - std::memcpy(pRenderer->getCurrentUniformBufferMapped(), &ubo, sizeof(ubo)); - - const auto& gpuLights = pScene->collectGPULights(); - uint32_t lightCount = pRenderer->updateLightSSBO( - gpuLights.data(), static_cast(gpuLights.size())); - - cmd.begin({}); - - // Transition swapchain image -> ColorAttachmentOptimal - pRenderer->transitionImageLayout( - cmd, - pRenderer->getSwapChain().getImages()[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - - // Transition depth image -> DepthAttachmentOptimal - pRenderer->transitionImageLayout( - cmd, - *pRenderer->getDepthImage(), - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - auto& swapChain = pRenderer->getSwapChain(); - vk::Extent2D extent = swapChain.getExtent(); - - vk::ClearValue clearColor = vk::ClearColorValue { 0.0f, 0.0f, 0.0f, 1.0f }; - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - bool firstDraw = true; - - for (auto& entity : pScene->getEntitiesMut()) { - if (!entity.getActive()) continue; - - auto mrcs = entity.getComponents(); - if (mrcs.empty()) continue; - - // Get model matrix from transform - glm::mat4 modelMatrix = glm::mat4(1.0f); - auto* tc = entity.getComponent(); - if (tc) { - modelMatrix = tc->getLocalMatrix(); + + void SauceEngineApp::uploadMeshGPUResources() { + for (auto& entity : pScene->getEntitiesMut()) { + auto mrcs = entity.getComponents(); + for (auto* mrc : mrcs) { + auto mesh = mrc->getMesh(); + if (mesh && mesh->isValid() && !mesh->hasGPUData()) { + auto& physDev = const_cast(*physicalDevice); + auto& cmdPool = const_cast(pRenderer->getCommandPool()); + auto& queue = const_cast(pRenderer->getQueue()); + mesh->initVulkanResources(logicalDevice, physDev, cmdPool, queue); + } + + auto material = mrc->getMaterial(); + if (material && !material->hasDescriptorSet()) { + auto& physDev = const_cast(*physicalDevice); + auto& cmdPool = const_cast(pRenderer->getCommandPool()); + auto& queue = const_cast(pRenderer->getQueue()); + material->initVulkanResources( + logicalDevice, physDev, cmdPool, queue, pRenderer->getDescriptorPool(), + pRenderer->getDefaultImageView(), pRenderer->getDefaultSampler()); + } + } + } } - // Update model matrix in UBO via vkCmdUpdateBuffer (GPU-side, outside render pass) - cmd.updateBuffer(*pRenderer->getCurrentUniformBuffer(), 0, modelMatrix); - - // Barrier: transfer write -> vertex shader uniform read - vk::BufferMemoryBarrier2 bufBarrier { - .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, - .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, - .dstStageMask = vk::PipelineStageFlagBits2::eVertexShader, - .dstAccessMask = vk::AccessFlagBits2::eUniformRead, - .srcQueueFamilyIndex = vk::QueueFamilyIgnored, - .dstQueueFamilyIndex = vk::QueueFamilyIgnored, - .buffer = *pRenderer->getCurrentUniformBuffer(), - .offset = 0, - .size = sizeof(glm::mat4), - }; - vk::DependencyInfo depInfo { - .bufferMemoryBarrierCount = 1, - .pBufferMemoryBarriers = &bufBarrier, - }; - cmd.pipelineBarrier2(depInfo); - - // Begin rendering (clear on first, load on subsequent) - vk::RenderingAttachmentInfo colorAttachment { - .imageView = swapChain.getImageViews()[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = firstDraw ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor, - }; - vk::RenderingAttachmentInfo depthAttachment { - .imageView = pRenderer->getDepthImageView(), - .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, - .loadOp = firstDraw ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearDepth, - }; - vk::RenderingInfo renderingInfo { - .renderArea = { .offset = { 0, 0 }, .extent = extent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment, - }; - - cmd.beginRendering(renderingInfo); - - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, *pRenderer->getPipeline()); - cmd.setViewport(0, vk::Viewport(0.0f, 0.0f, - static_cast(extent.width), static_cast(extent.height), 0.0f, 1.0f)); - cmd.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), extent)); - - // Bind Set 0: Per-frame - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - pRenderer->getPipeline().getLayout(), 0, { *pRenderer->getCurrentDescriptorSet() }, nullptr); - - // Bind Set 1: Environment - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - pRenderer->getPipeline().getLayout(), 1, { pRenderer->getEnvironmentDescriptorSet() }, nullptr); - - cmd.pushConstants( - *pRenderer->getPipeline().getLayout(), - vk::ShaderStageFlagBits::eFragment, - 0u, { lightCount } - ); - - for (auto* mrc : mrcs) { - auto mesh = mrc->getMesh(); - auto material = mrc->getMaterial(); - if (!mesh || !mesh->hasGPUData()) continue; - mesh->bind(cmd); - - // Bind Set 2: Material - if (material && material->hasDescriptorSet()) { - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - pRenderer->getPipeline().getLayout(), 2, { material->getDescriptorSet() }, nullptr); - } else { - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - pRenderer->getPipeline().getLayout(), 2, { pRenderer->getDefaultMaterialDescriptorSet() }, nullptr); - } - - mesh->draw(cmd); + void SauceEngineApp::setupSceneRenderer() { + pRenderer->setCommandBufferRecorder( + [this](vk::raii::CommandBuffer& cmd, uint32_t imageIndex) { + recordSceneCommandBuffer(cmd, imageIndex); + }); } - cmd.endRendering(); - firstDraw = false; - } - - // If no entities were drawn, still clear the screen - if (firstDraw) { - vk::RenderingAttachmentInfo colorAttachment { - .imageView = swapChain.getImageViews()[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor, - }; - vk::RenderingAttachmentInfo depthAttachment { - .imageView = pRenderer->getDepthImageView(), - .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth, - }; - vk::RenderingInfo renderingInfo { - .renderArea = { .offset = { 0, 0 }, .extent = extent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment, - }; - cmd.beginRendering(renderingInfo); - cmd.endRendering(); - } - - // ImGui pass (load existing framebuffer contents) - { - vk::RenderingAttachmentInfo colorAttachment { - .imageView = swapChain.getImageViews()[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eLoad, - .storeOp = vk::AttachmentStoreOp::eStore, - }; - vk::RenderingAttachmentInfo depthAttachment { - .imageView = pRenderer->getDepthImageView(), - .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eLoad, - .storeOp = vk::AttachmentStoreOp::eDontCare, - }; - vk::RenderingInfo renderingInfo { - .renderArea = { .offset = { 0, 0 }, .extent = extent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment, - }; - cmd.beginRendering(renderingInfo); - if (pImGuiRenderer) { - pImGuiRenderer->render(cmd, imageIndex); + void SauceEngineApp::recordSceneCommandBuffer(vk::raii::CommandBuffer& cmd, + uint32_t imageIndex) { + // Write camera matrices to UBO (host side, before GPU execution) + sauce::UniformBufferObject ubo{ + .model = glm::mat4(1.0f), + .view = pScene->getCameraRO().getViewMatrix(), + .proj = pScene->getCameraRO().getProjectionMatrix(), + .cameraPos = pScene->getCameraRO().getPos(), + }; + ubo.proj[1][1] *= -1; // Vulkan Y-flip + std::memcpy(pRenderer->getCurrentUniformBufferMapped(), &ubo, sizeof(ubo)); + + const auto& gpuLights = pScene->collectGPULights(); + uint32_t lightCount = + pRenderer->updateLightSSBO(gpuLights.data(), static_cast(gpuLights.size())); + + cmd.begin({}); + + // Transition swapchain image -> ColorAttachmentOptimal + pRenderer->transitionImageLayout( + cmd, pRenderer->getSwapChain().getImages()[imageIndex], vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::ImageAspectFlagBits::eColor); + + // Transition depth image -> DepthAttachmentOptimal + pRenderer->transitionImageLayout(cmd, *pRenderer->getDepthImage(), + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + auto& swapChain = pRenderer->getSwapChain(); + vk::Extent2D extent = swapChain.getExtent(); + + vk::ClearValue clearColor = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}; + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + bool firstDraw = true; + + for (auto& entity : pScene->getEntitiesMut()) { + if (!entity.getActive()) + continue; + + auto mrcs = entity.getComponents(); + if (mrcs.empty()) + continue; + + // Get model matrix from transform + glm::mat4 modelMatrix = glm::mat4(1.0f); + auto* tc = entity.getComponent(); + if (tc) { + modelMatrix = tc->getLocalMatrix(); + } + + // Update model matrix in UBO via vkCmdUpdateBuffer (GPU-side, outside render pass) + cmd.updateBuffer(*pRenderer->getCurrentUniformBuffer(), 0, modelMatrix); + + // Barrier: transfer write -> vertex shader uniform read + vk::BufferMemoryBarrier2 bufBarrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eVertexShader, + .dstAccessMask = vk::AccessFlagBits2::eUniformRead, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .buffer = *pRenderer->getCurrentUniformBuffer(), + .offset = 0, + .size = sizeof(glm::mat4), + }; + vk::DependencyInfo depInfo{ + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &bufBarrier, + }; + cmd.pipelineBarrier2(depInfo); + + // Begin rendering (clear on first, load on subsequent) + vk::RenderingAttachmentInfo colorAttachment{ + .imageView = swapChain.getImageViews()[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = firstDraw ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor, + }; + vk::RenderingAttachmentInfo depthAttachment{ + .imageView = pRenderer->getDepthImageView(), + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = firstDraw ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearDepth, + }; + vk::RenderingInfo renderingInfo{ + .renderArea = {.offset = {0, 0}, .extent = extent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment, + }; + + cmd.beginRendering(renderingInfo); + + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, *pRenderer->getPipeline()); + cmd.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(extent.width), + static_cast(extent.height), 0.0f, 1.0f)); + cmd.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), extent)); + + // Bind Set 0: Per-frame + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + pRenderer->getPipeline().getLayout(), 0, + {*pRenderer->getCurrentDescriptorSet()}, nullptr); + + // Bind Set 1: Environment + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + pRenderer->getPipeline().getLayout(), 1, + {pRenderer->getEnvironmentDescriptorSet()}, nullptr); + + cmd.pushConstants(*pRenderer->getPipeline().getLayout(), + vk::ShaderStageFlagBits::eFragment, 0u, {lightCount}); + + for (auto* mrc : mrcs) { + auto mesh = mrc->getMesh(); + auto material = mrc->getMaterial(); + if (!mesh || !mesh->hasGPUData()) + continue; + mesh->bind(cmd); + + // Bind Set 2: Material + if (material && material->hasDescriptorSet()) { + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + pRenderer->getPipeline().getLayout(), 2, + {material->getDescriptorSet()}, nullptr); + } else { + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + pRenderer->getPipeline().getLayout(), 2, + {pRenderer->getDefaultMaterialDescriptorSet()}, nullptr); + } + + mesh->draw(cmd); + } + + cmd.endRendering(); + firstDraw = false; + } + + // If no entities were drawn, still clear the screen + if (firstDraw) { + vk::RenderingAttachmentInfo colorAttachment{ + .imageView = swapChain.getImageViews()[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor, + }; + vk::RenderingAttachmentInfo depthAttachment{ + .imageView = pRenderer->getDepthImageView(), + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth, + }; + vk::RenderingInfo renderingInfo{ + .renderArea = {.offset = {0, 0}, .extent = extent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment, + }; + cmd.beginRendering(renderingInfo); + cmd.endRendering(); + } + + // ImGui pass (load existing framebuffer contents) + { + vk::RenderingAttachmentInfo colorAttachment{ + .imageView = swapChain.getImageViews()[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + }; + vk::RenderingAttachmentInfo depthAttachment{ + .imageView = pRenderer->getDepthImageView(), + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eDontCare, + }; + vk::RenderingInfo renderingInfo{ + .renderArea = {.offset = {0, 0}, .extent = extent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment, + }; + cmd.beginRendering(renderingInfo); + if (pImGuiRenderer) { + pImGuiRenderer->render(cmd, imageIndex); + } + cmd.endRendering(); + } + + // Transition to present + pRenderer->transitionImageLayout( + cmd, pRenderer->getSwapChain().getImages()[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe, vk::ImageAspectFlagBits::eColor); + + cmd.end(); } - cmd.endRendering(); - } - - // Transition to present - pRenderer->transitionImageLayout( - cmd, - pRenderer->getSwapChain().getImages()[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe, - vk::ImageAspectFlagBits::eColor - ); - - cmd.end(); -} } // namespace sauce diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index 25ea25fc..ff64cc9a 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -1,226 +1,226 @@ #include "app/Scene.hpp" -#include "app/modeling/GLTFLoader.hpp" -#include "app/modeling/GLTFExporter.hpp" -#include "app/components/TransformComponent.hpp" +#include "app/components/DirectionalLightComponent.hpp" #include "app/components/MeshRendererComponent.hpp" -#include "app/components/RigidBodyComponent.hpp" #include "app/components/PointLightComponent.hpp" -#include "app/components/DirectionalLightComponent.hpp" -#include +#include "app/components/RigidBodyComponent.hpp" +#include "app/components/TransformComponent.hpp" +#include "app/modeling/GLTFExporter.hpp" +#include "app/modeling/GLTFLoader.hpp" #include +#include namespace sauce { -void Scene::addEntity(sauce::Entity&& entity) { - entities.push_back(std::move(entity)); -} - -sauce::Entity* Scene::getEntity(const std::string& name) { - for (auto& entity : entities) { - if (entity.get_name() == name) { - return &entity; - } - } - return nullptr; -} - -bool Scene::saveToFile(const std::string& filePath) const { - using namespace sauce::modeling; - - ExportOptions opts; - // Determine binary mode from extension - if (filePath.size() >= 4 && filePath.substr(filePath.size() - 4) == ".glb") { - opts.writeBinary = true; - opts.embedImages = true; - opts.embedBuffers = true; + void Scene::addEntity(sauce::Entity&& entity) { + entities.push_back(std::move(entity)); } - GLTFExporter exporter(opts); - bool success = exporter.exportScene(*this, filePath); - if (success) { - const_cast(this)->currentFilePath = filePath; + sauce::Entity* Scene::getEntity(const std::string& name) { + for (auto& entity : entities) { + if (entity.get_name() == name) { + return &entity; + } + } + return nullptr; } - return success; -} -bool Scene::loadFromFile(const std::string& filePath) { - using namespace sauce::modeling; + bool Scene::saveToFile(const std::string& filePath) const { + using namespace sauce::modeling; - GLTFLoader loader; - auto model = loader.loadModel(filePath); + ExportOptions opts; + // Determine binary mode from extension + if (filePath.size() >= 4 && filePath.substr(filePath.size() - 4) == ".glb") { + opts.writeBinary = true; + opts.embedImages = true; + opts.embedBuffers = true; + } - if (!model || !model->getRootNode()) { - std::cerr << "Scene::loadFromFile: Failed to load " << filePath << std::endl; - return false; + GLTFExporter exporter(opts); + bool success = exporter.exportScene(*this, filePath); + if (success) { + const_cast(this)->currentFilePath = filePath; + } + return success; } - // Clear existing entities - entities.clear(); + bool Scene::loadFromFile(const std::string& filePath) { + using namespace sauce::modeling; - // Load entities from model - std::unordered_map nodeToEntityMap; - loadGLTFNodeHierarchy(model->getRootNode(), nullptr, nodeToEntityMap, filePath); + GLTFLoader loader; + auto model = loader.loadModel(filePath); - currentFilePath = filePath; - return true; -} + if (!model || !model->getRootNode()) { + std::cerr << "Scene::loadFromFile: Failed to load " << filePath << std::endl; + return false; + } -void Scene::loadGLTFModel(const std::string& filePath, bool preserveHierarchy) { - using namespace sauce::modeling; + // Clear existing entities + entities.clear(); - // Create loader and load model - GLTFLoader loader; - auto model = loader.loadModel(filePath); + // Load entities from model + std::unordered_map nodeToEntityMap; + loadGLTFNodeHierarchy(model->getRootNode(), nullptr, nodeToEntityMap, filePath); - if (!model) { - return; + currentFilePath = filePath; + return true; } - if (!model->getRootNode()) { - return; - } + void Scene::loadGLTFModel(const std::string& filePath, bool preserveHierarchy) { + using namespace sauce::modeling; - if (preserveHierarchy) { - // Create entity tree preserving hierarchy - std::unordered_map nodeToEntityMap; - loadGLTFNodeHierarchy(model->getRootNode(), nullptr, nodeToEntityMap, filePath); - } else { - // Flatten all meshes into individual entities - loadGLTFFlattened(model, filePath); - } -} - -void Scene::loadGLTFNodeHierarchy(std::shared_ptr node, - Entity* parentEntity, - std::unordered_map& nodeToEntityMap, - const std::string& filePath) { - if (!node) { - return; - } + // Create loader and load model + GLTFLoader loader; + auto model = loader.loadModel(filePath); - // Skip the artificial root node - if (node->getName() == "__root__") { - for (const auto& child : node->getChildren()) { - loadGLTFNodeHierarchy(child, nullptr, nodeToEntityMap, filePath); + if (!model) { + return; } - return; - } - // Create entity for this node - std::string entityName = node->getName().empty() ? "Node" : node->getName(); - Entity entity(entityName); - - // Add TransformComponent - entity.addComponent(node->getTransform()); - - // Add MeshRendererComponents and RigidBodyComponent for each mesh-material pair - for (const auto& pair : node->getMeshMaterialPairs()) { - entity.addComponent(pair.mesh, pair.material); - entity.getComponents().back()->setModelPath(filePath); - - const auto& nodeTransform = node->getTransform(); - entity.addComponent( - nodeTransform.getTranslation(), - glm::vec3(0.f,0.f,0.f), - nodeTransform.getRotation(), - glm::vec3(0.f,0.f,0.f) - ); - // calculate center of mass - glm::vec3 com=RigidBodyComponent::meshCenterOfMass(pair.mesh); - entity.getComponents().back()->setCenterOfMass(com); - // get inverse mass from tags, or compute one - float invmass=1.f; - if (pair.mesh->hasMetadata("InvMass")) { - sauce::modeling::PropertyValue propval=pair.mesh->getMetadata().at("InvMass"); - float *invmassTag=std::get_if(&propval); - if (invmassTag!=nullptr) - invmass=*invmassTag; - else - invmass=RigidBodyComponent::meshInvMass(pair.mesh); - } - else - invmass=RigidBodyComponent::meshInvMass(pair.mesh); - entity.getComponents().back()->setInvMass(invmass); - } + if (!model->getRootNode()) { + return; + } - if (node->hasLight()) { - const auto& info = node->getLightInfo().value(); - if (info.type == modeling::LightInfo::Type::Point) { - entity.addComponent(info.color, info.intensity, info.range); - } else if (info.type == modeling::LightInfo::Type::Directional) { - entity.addComponent(info.color, info.intensity); + if (preserveHierarchy) { + // Create entity tree preserving hierarchy + std::unordered_map nodeToEntityMap; + loadGLTFNodeHierarchy(model->getRootNode(), nullptr, nodeToEntityMap, filePath); + } else { + // Flatten all meshes into individual entities + loadGLTFFlattened(model, filePath); } } - // Add entity to scene - entities.push_back(std::move(entity)); - Entity* entityPtr = &entities.back(); - - // Track node -> entity mapping - nodeToEntityMap[node.get()] = entityPtr; - - // Process children - for (const auto& child : node->getChildren()) { - loadGLTFNodeHierarchy(child, entityPtr, nodeToEntityMap, filePath); - } -} + void Scene::loadGLTFNodeHierarchy( + std::shared_ptr node, Entity* parentEntity, + std::unordered_map& nodeToEntityMap, + const std::string& filePath) { + if (!node) { + return; + } -void Scene::loadGLTFFlattened(std::shared_ptr model, const std::string& filePath) { - const auto& allMeshes = model->getAllMeshes(); - const auto& allMaterials = model->getAllMaterials(); + // Skip the artificial root node + if (node->getName() == "__root__") { + for (const auto& child : node->getChildren()) { + loadGLTFNodeHierarchy(child, nullptr, nodeToEntityMap, filePath); + } + return; + } - // Create one entity per mesh - for (size_t i = 0; i < allMeshes.size(); ++i) { - std::string entityName = "Mesh_" + std::to_string(i); + // Create entity for this node + std::string entityName = node->getName().empty() ? "Node" : node->getName(); Entity entity(entityName); - // Add TransformComponent with default transform - entity.addComponent(); - - // Add MeshRendererComponent - // Use corresponding material if available, otherwise use first material or create default - std::shared_ptr material; - if (i < allMaterials.size()) { - material = allMaterials[i]; - } else if (!allMaterials.empty()) { - material = allMaterials[0]; - } else { - material = std::make_shared("default"); + // Add TransformComponent + entity.addComponent(node->getTransform()); + + // Add MeshRendererComponents and RigidBodyComponent for each mesh-material pair + for (const auto& pair : node->getMeshMaterialPairs()) { + entity.addComponent(pair.mesh, pair.material); + entity.getComponents().back()->setModelPath(filePath); + + const auto& nodeTransform = node->getTransform(); + entity.addComponent( + nodeTransform.getTranslation(), glm::vec3(0.f, 0.f, 0.f), + nodeTransform.getRotation(), glm::vec3(0.f, 0.f, 0.f)); + // calculate center of mass + glm::vec3 com = RigidBodyComponent::meshCenterOfMass(pair.mesh); + entity.getComponents().back()->setCenterOfMass(com); + // get inverse mass from tags, or compute one + float invmass = 1.f; + if (pair.mesh->hasMetadata("InvMass")) { + sauce::modeling::PropertyValue propval = pair.mesh->getMetadata().at("InvMass"); + float* invmassTag = std::get_if(&propval); + if (invmassTag != nullptr) + invmass = *invmassTag; + else + invmass = RigidBodyComponent::meshInvMass(pair.mesh); + } else + invmass = RigidBodyComponent::meshInvMass(pair.mesh); + entity.getComponents().back()->setInvMass(invmass); } - entity.addComponent(allMeshes[i], material); - entity.getComponents().back()->setModelPath(filePath); + if (node->hasLight()) { + const auto& info = node->getLightInfo().value(); + if (info.type == modeling::LightInfo::Type::Point) { + entity.addComponent(info.color, info.intensity, info.range); + } else if (info.type == modeling::LightInfo::Type::Directional) { + entity.addComponent(info.color, info.intensity); + } + } + // Add entity to scene entities.push_back(std::move(entity)); + Entity* entityPtr = &entities.back(); + + // Track node -> entity mapping + nodeToEntityMap[node.get()] = entityPtr; + + // Process children + for (const auto& child : node->getChildren()) { + loadGLTFNodeHierarchy(child, entityPtr, nodeToEntityMap, filePath); + } } -} - -const std::vector& Scene::collectGPULights() { - gpuLightBuffer.clear(); - for (auto& entity : entities) { - if (!entity.getActive()) continue; - - auto* lc = entity.getComponent(); - if (!lc || !lc->getActive()) continue; - - glm::vec3 worldPos{0.0f}; - glm::vec3 direction{0.0f, 0.0f, -1.0f}; - - auto* tc = entity.getComponent(); - if (tc) { - worldPos = tc->getTranslation(); - direction = tc->getRotation() * direction; + + void Scene::loadGLTFFlattened(std::shared_ptr model, + const std::string& filePath) { + const auto& allMeshes = model->getAllMeshes(); + const auto& allMaterials = model->getAllMaterials(); + + // Create one entity per mesh + for (size_t i = 0; i < allMeshes.size(); ++i) { + std::string entityName = "Mesh_" + std::to_string(i); + Entity entity(entityName); + + // Add TransformComponent with default transform + entity.addComponent(); + + // Add MeshRendererComponent + // Use corresponding material if available, otherwise use first material or create default + std::shared_ptr material; + if (i < allMaterials.size()) { + material = allMaterials[i]; + } else if (!allMaterials.empty()) { + material = allMaterials[0]; + } else { + material = std::make_shared("default"); + } + + entity.addComponent(allMeshes[i], material); + entity.getComponents().back()->setModelPath(filePath); + + entities.push_back(std::move(entity)); } + } - GPULight gpuLight = lc->toGPULight(worldPos); + const std::vector& Scene::collectGPULights() { + gpuLightBuffer.clear(); + for (auto& entity : entities) { + if (!entity.getActive()) + continue; - if (lc->getLightType() == LightType::Directional || lc->getLightType() == LightType::Spot) { - gpuLight.direction = direction; - } + auto* lc = entity.getComponent(); + if (!lc || !lc->getActive()) + continue; + + glm::vec3 worldPos{0.0f}; + glm::vec3 direction{0.0f, 0.0f, -1.0f}; + + auto* tc = entity.getComponent(); + if (tc) { + worldPos = tc->getTranslation(); + direction = tc->getRotation() * direction; + } - gpuLightBuffer.push_back(gpuLight); + GPULight gpuLight = lc->toGPULight(worldPos); + + if (lc->getLightType() == LightType::Directional || + lc->getLightType() == LightType::Spot) { + gpuLight.direction = direction; + } + + gpuLightBuffer.push_back(gpuLight); + } + return gpuLightBuffer; } - return gpuLightBuffer; -} } // namespace sauce diff --git a/src/app/Settings.cpp b/src/app/Settings.cpp index 8577fb4a..5fc35c88 100644 --- a/src/app/Settings.cpp +++ b/src/app/Settings.cpp @@ -1,76 +1,84 @@ -#include #include -#include -#include +#include #include +#include +#include namespace sauce { -void SettingsManager::load(const std::string& path) { - filePath = path; + void SettingsManager::load(const std::string& path) { + filePath = path; - std::ifstream file(filePath); - if (!file.is_open()) { - SAUCE_LOG("Settings", "No config file found at {}, using defaults", filePath); - save(); - return; - } + std::ifstream file(filePath); + if (!file.is_open()) { + SAUCE_LOG("Settings", "No config file found at {}, using defaults", filePath); + save(); + return; + } - try { - nlohmann::json j = nlohmann::json::parse(file); + try { + nlohmann::json j = nlohmann::json::parse(file); - if (j.contains("imgui_scale")) settings.imguiScale = j["imgui_scale"].get(); - if (j.contains("vsync")) settings.vsync = j["vsync"].get(); - if (j.contains("working_directory")) settings.workingDirectory = j["working_directory"].get(); - if (j.contains("palantir_mode")) settings.palantirMode = j["palantir_mode"].get(); - if (j.contains("show_debug_stats")) settings.showDebugStats = j["show_debug_stats"].get(); - if (j.contains("mouse_sensitivity")) settings.mouseSensitivity = j["mouse_sensitivity"].get(); - if (j.contains("camera_speed")) settings.cameraSpeed = j["camera_speed"].get(); - if (j.contains("field_of_view")) settings.fieldOfView = j["field_of_view"].get(); + if (j.contains("imgui_scale")) + settings.imguiScale = j["imgui_scale"].get(); + if (j.contains("vsync")) + settings.vsync = j["vsync"].get(); + if (j.contains("working_directory")) + settings.workingDirectory = j["working_directory"].get(); + if (j.contains("palantir_mode")) + settings.palantirMode = j["palantir_mode"].get(); + if (j.contains("show_debug_stats")) + settings.showDebugStats = j["show_debug_stats"].get(); + if (j.contains("mouse_sensitivity")) + settings.mouseSensitivity = j["mouse_sensitivity"].get(); + if (j.contains("camera_speed")) + settings.cameraSpeed = j["camera_speed"].get(); + if (j.contains("field_of_view")) + settings.fieldOfView = j["field_of_view"].get(); - SAUCE_LOG("Settings", "Loaded settings from {}", filePath); - } catch (const nlohmann::json::exception& e) { - SAUCE_LOG("Settings", "Failed to parse {}: {}, using defaults", filePath, e.what()); - settings = EditorSettings{}; - } -} + SAUCE_LOG("Settings", "Loaded settings from {}", filePath); + } catch (const nlohmann::json::exception& e) { + SAUCE_LOG("Settings", "Failed to parse {}: {}, using defaults", filePath, e.what()); + settings = EditorSettings{}; + } + } -void SettingsManager::save() const { - auto roundFloat = [](float v, int decimals) -> double { - double mul = std::pow(10.0, decimals); - return std::round(static_cast(v) * mul) / mul; - }; + void SettingsManager::save() const { + auto roundFloat = [](float v, int decimals) -> double { + double mul = std::pow(10.0, decimals); + return std::round(static_cast(v) * mul) / mul; + }; - nlohmann::json j = { - {"imgui_scale", roundFloat(settings.imguiScale, 2)}, - {"vsync", settings.vsync}, - {"working_directory", settings.workingDirectory}, - {"palantir_mode", settings.palantirMode}, - {"show_debug_stats", settings.showDebugStats}, - {"mouse_sensitivity", roundFloat(settings.mouseSensitivity, 2)}, - {"camera_speed", roundFloat(settings.cameraSpeed, 1)}, - {"field_of_view", roundFloat(settings.fieldOfView, 1)}, - }; + nlohmann::json j = { + {"imgui_scale", roundFloat(settings.imguiScale, 2)}, + {"vsync", settings.vsync}, + {"working_directory", settings.workingDirectory}, + {"palantir_mode", settings.palantirMode}, + {"show_debug_stats", settings.showDebugStats}, + {"mouse_sensitivity", roundFloat(settings.mouseSensitivity, 2)}, + {"camera_speed", roundFloat(settings.cameraSpeed, 1)}, + {"field_of_view", roundFloat(settings.fieldOfView, 1)}, + }; - std::ofstream file(filePath); - if (file.is_open()) { - file << j.dump(2) << std::endl; - SAUCE_LOG_VERBOSE("Settings", "Settings saved to {}", filePath); - } else { - SAUCE_LOG("Settings", "Failed to save settings to {}", filePath); - } -} + std::ofstream file(filePath); + if (file.is_open()) { + file << j.dump(2) << std::endl; + SAUCE_LOG_VERBOSE("Settings", "Settings saved to {}", filePath); + } else { + SAUCE_LOG("Settings", "Failed to save settings to {}", filePath); + } + } -void SettingsManager::save(const std::string& path) { - filePath = path; - save(); -} + void SettingsManager::save(const std::string& path) { + filePath = path; + save(); + } -void SettingsManager::markDirtyAndSave() { - save(); - if (onChange) { - onChange(settings); - } -} + void SettingsManager::markDirtyAndSave() { + save(); + if (onChange) { + onChange(settings); + } + } } // namespace sauce diff --git a/src/app/components/DirectionalLightComponent.cpp b/src/app/components/DirectionalLightComponent.cpp index 1f50809f..2810019e 100644 --- a/src/app/components/DirectionalLightComponent.cpp +++ b/src/app/components/DirectionalLightComponent.cpp @@ -2,27 +2,28 @@ namespace sauce { -DirectionalLightComponent::DirectionalLightComponent() - : LightComponent(LightType::Directional, "DirectionalLightComponent") {} + DirectionalLightComponent::DirectionalLightComponent() + : LightComponent(LightType::Directional, "DirectionalLightComponent") { + } -DirectionalLightComponent::DirectionalLightComponent(const glm::vec3& color, float intensity) - : LightComponent(LightType::Directional, "DirectionalLightComponent") -{ - this->color = color; - this->intensity = intensity; -} + DirectionalLightComponent::DirectionalLightComponent(const glm::vec3& color, float intensity) + : LightComponent(LightType::Directional, "DirectionalLightComponent") { + this->color = color; + this->intensity = intensity; + } -GPULight DirectionalLightComponent::toGPULight(const glm::vec3& worldPosition) const { - GPULight gpu{}; - gpu.position = worldPosition; - gpu.type = static_cast(LightType::Directional); - gpu.direction = glm::vec3(0.0f, 0.0f, -1.0f); // Default direction, will be overridden by Scene - gpu.intensity = intensity; - gpu.color = color; - gpu.range = 0.0f; - gpu.innerConeAngle = 0.0f; - gpu.outerConeAngle = 0.0f; - return gpu; -} + GPULight DirectionalLightComponent::toGPULight(const glm::vec3& worldPosition) const { + GPULight gpu{}; + gpu.position = worldPosition; + gpu.type = static_cast(LightType::Directional); + gpu.direction = + glm::vec3(0.0f, 0.0f, -1.0f); // Default direction, will be overridden by Scene + gpu.intensity = intensity; + gpu.color = color; + gpu.range = 0.0f; + gpu.innerConeAngle = 0.0f; + gpu.outerConeAngle = 0.0f; + return gpu; + } } // namespace sauce diff --git a/src/app/components/MeshRendererComponent.cpp b/src/app/components/MeshRendererComponent.cpp index 2ca028ee..f81667f0 100644 --- a/src/app/components/MeshRendererComponent.cpp +++ b/src/app/components/MeshRendererComponent.cpp @@ -2,23 +2,20 @@ namespace sauce { -MeshRendererComponent::MeshRendererComponent() - : Component("MeshRendererComponent") { -} - -MeshRendererComponent::MeshRendererComponent(std::shared_ptr mesh, - std::shared_ptr material) - : Component("MeshRendererComponent") - , mesh(mesh) - , material(material) { -} + MeshRendererComponent::MeshRendererComponent() : Component("MeshRendererComponent") { + } -void MeshRendererComponent::render() { - if (!mesh || !material) { - return; + MeshRendererComponent::MeshRendererComponent(std::shared_ptr mesh, + std::shared_ptr material) + : Component("MeshRendererComponent"), mesh(mesh), material(material) { } - // TODO: Bind material, mesh buffers, and issue draw call -} + void MeshRendererComponent::render() { + if (!mesh || !material) { + return; + } + + // TODO: Bind material, mesh buffers, and issue draw call + } } // namespace sauce diff --git a/src/app/components/PointLightComponent.cpp b/src/app/components/PointLightComponent.cpp index a5d9bc01..a0116fb1 100644 --- a/src/app/components/PointLightComponent.cpp +++ b/src/app/components/PointLightComponent.cpp @@ -2,41 +2,40 @@ namespace sauce { -PointLightComponent::PointLightComponent() - : LightComponent(LightType::Point, "PointLightComponent") {} + PointLightComponent::PointLightComponent() + : LightComponent(LightType::Point, "PointLightComponent") { + } -PointLightComponent::PointLightComponent(const glm::vec3& color, float intensity, float range) - : LightComponent(LightType::Point, "PointLightComponent") - , range(range) -{ - this->color = color; - this->intensity = intensity; -} + PointLightComponent::PointLightComponent(const glm::vec3& color, float intensity, float range) + : LightComponent(LightType::Point, "PointLightComponent"), range(range) { + this->color = color; + this->intensity = intensity; + } -GPUPointLight PointLightComponent::toGPUPointLight(const glm::vec3& worldPosition) const { - GPUPointLight gpu{}; - gpu.position = worldPosition; - gpu.color = color; - gpu.ambient = ambient; - gpu.diffuse = diffuse; - gpu.specular = specular; - gpu.constant = constant; - gpu.linear = linear; - gpu.quadratic = quadratic; - return gpu; -} + GPUPointLight PointLightComponent::toGPUPointLight(const glm::vec3& worldPosition) const { + GPUPointLight gpu{}; + gpu.position = worldPosition; + gpu.color = color; + gpu.ambient = ambient; + gpu.diffuse = diffuse; + gpu.specular = specular; + gpu.constant = constant; + gpu.linear = linear; + gpu.quadratic = quadratic; + return gpu; + } -GPULight PointLightComponent::toGPULight(const glm::vec3& worldPosition) const { - GPULight gpu{}; - gpu.position = worldPosition; - gpu.type = static_cast(LightType::Point); - gpu.direction = glm::vec3(0.0f); - gpu.intensity = intensity; - gpu.color = color; - gpu.range = range; - gpu.innerConeAngle = 0.0f; - gpu.outerConeAngle = 0.0f; - return gpu; -} + GPULight PointLightComponent::toGPULight(const glm::vec3& worldPosition) const { + GPULight gpu{}; + gpu.position = worldPosition; + gpu.type = static_cast(LightType::Point); + gpu.direction = glm::vec3(0.0f); + gpu.intensity = intensity; + gpu.color = color; + gpu.range = range; + gpu.innerConeAngle = 0.0f; + gpu.outerConeAngle = 0.0f; + return gpu; + } } // namespace sauce diff --git a/src/app/components/RigidBodyComponent.cpp b/src/app/components/RigidBodyComponent.cpp index fd4e8364..b5fdec06 100644 --- a/src/app/components/RigidBodyComponent.cpp +++ b/src/app/components/RigidBodyComponent.cpp @@ -2,35 +2,34 @@ namespace sauce { -glm::vec3 RigidBodyComponent::meshCenterOfMass(std::shared_ptr m) { - auto ret=glm::vec3(0.f,0.f,0.f); - float n=0.f; - /* + glm::vec3 RigidBodyComponent::meshCenterOfMass(std::shared_ptr m) { + auto ret = glm::vec3(0.f, 0.f, 0.f); + float n = 0.f; + /* * simple center of mass for a 3d mesh - just average the coordinates */ - for (auto &v : m->getVertices()) { - ret.x+=v.position.x; - ret.y+=v.position.y; - ret.z+=v.position.z; - n++; - } - - n=(n==0.f)? 1.f : n; // dont divide by 0 - return ret / n; -} - - -float trianglevolume(glm::vec3 p1, glm::vec3 p2, glm::vec3 p3) { - auto v321 = p3.x*p2.y*p1.z; - auto v231 = p2.x*p3.y*p1.z; - auto v312 = p3.x*p1.y*p2.z; - auto v132 = p1.x*p3.y*p2.z; - auto v213 = p2.x*p1.y*p3.z; - auto v123 = p1.x*p2.y*p3.z; - return (1.0f/6.0f)*(-v321 + v231 + v312 - v132 - v213 + v123); -} -float RigidBodyComponent::meshInvMass(std::shared_ptr m) { - /* + for (auto& v : m->getVertices()) { + ret.x += v.position.x; + ret.y += v.position.y; + ret.z += v.position.z; + n++; + } + + n = (n == 0.f) ? 1.f : n; // dont divide by 0 + return ret / n; + } + + float trianglevolume(glm::vec3 p1, glm::vec3 p2, glm::vec3 p3) { + auto v321 = p3.x * p2.y * p1.z; + auto v231 = p2.x * p3.y * p1.z; + auto v312 = p3.x * p1.y * p2.z; + auto v132 = p1.x * p3.y * p2.z; + auto v213 = p2.x * p1.y * p3.z; + auto v123 = p1.x * p2.y * p3.z; + return (1.0f / 6.0f) * (-v321 + v231 + v312 - v132 - v213 + v123); + } + float RigidBodyComponent::meshInvMass(std::shared_ptr m) { + /* * approximate a volume for the mesh, then assume a * constant density and calculate an (inverse) mass. * @@ -38,23 +37,24 @@ float RigidBodyComponent::meshInvMass(std::shared_ptr m) { * https://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up */ - float volume=0.f; - // iterate vertices in index order - auto vertices=m->getVertices(); - auto indices=m->getIndices(); - - if (indices.size() % 3 != 0) - return volume; - for (size_t i = 0; i < indices.size(); i += 3) { - uint32_t i0 = indices[i + 0]; - uint32_t i1 = indices[i + 1]; - uint32_t i2 = indices[i + 2]; - - volume+=trianglevolume(vertices[i0].position, vertices[i1].position, vertices[i2].position); - } - if (volume<0.f) - volume*=-1.f; - return volume; -} + float volume = 0.f; + // iterate vertices in index order + auto vertices = m->getVertices(); + auto indices = m->getIndices(); + + if (indices.size() % 3 != 0) + return volume; + for (size_t i = 0; i < indices.size(); i += 3) { + uint32_t i0 = indices[i + 0]; + uint32_t i1 = indices[i + 1]; + uint32_t i2 = indices[i + 2]; + + volume += + trianglevolume(vertices[i0].position, vertices[i1].position, vertices[i2].position); + } + if (volume < 0.f) + volume *= -1.f; + return volume; + } } // namespace sauce diff --git a/src/app/components/TransformComponent.cpp b/src/app/components/TransformComponent.cpp index d1c0af29..72d7531e 100644 --- a/src/app/components/TransformComponent.cpp +++ b/src/app/components/TransformComponent.cpp @@ -2,12 +2,11 @@ namespace sauce { -TransformComponent::TransformComponent() - : transform() { -} + TransformComponent::TransformComponent() : transform() { + } -TransformComponent::TransformComponent(const modeling::Transform& transform) - : transform(transform) { -} + TransformComponent::TransformComponent(const modeling::Transform& transform) + : transform(transform) { + } } // namespace sauce diff --git a/src/app/modeling/GLTFExporter.cpp b/src/app/modeling/GLTFExporter.cpp index b82b5691..ccc881a0 100644 --- a/src/app/modeling/GLTFExporter.cpp +++ b/src/app/modeling/GLTFExporter.cpp @@ -1,484 +1,486 @@ #include "app/modeling/GLTFExporter.hpp" -#include "app/Scene.hpp" #include "app/Entity.hpp" -#include "app/components/TransformComponent.hpp" +#include "app/Scene.hpp" #include "app/components/MeshRendererComponent.hpp" -#include "app/modeling/Mesh.hpp" +#include "app/components/TransformComponent.hpp" #include "app/modeling/Material.hpp" +#include "app/modeling/Mesh.hpp" #include "app/modeling/Texture.hpp" -#include -#include -#include -#include #include +#include +#include +#include #include +#include namespace sauce { -namespace modeling { - -GLTFExporter::GLTFExporter() = default; - -GLTFExporter::GLTFExporter(const ExportOptions& options) - : options(options) { -} - -bool GLTFExporter::exportScene(const Scene& scene, const std::string& filePath) { - // Reset state - meshMap.clear(); - materialMap.clear(); - textureMap.clear(); - imageMap.clear(); - bufferData.clear(); - - std::filesystem::path outPath(filePath); - outputDirectory = outPath.parent_path().string(); - - // Ensure output directory exists - if (!outputDirectory.empty()) { - std::filesystem::create_directories(outputDirectory); - } - - tinygltf::Model model; - buildModel(model, scene); - - tinygltf::TinyGLTF writer; - bool success = false; - - if (options.writeBinary) { - success = writer.WriteGltfSceneToFile(&model, filePath, options.embedImages, - options.embedBuffers, options.prettyPrint, true); - } else { - success = writer.WriteGltfSceneToFile(&model, filePath, options.embedImages, - options.embedBuffers, options.prettyPrint, false); - } - - if (!success) { - std::cerr << "GLTFExporter: Failed to write file: " << filePath << std::endl; - } - - return success; -} - -void GLTFExporter::buildModel(tinygltf::Model& model, const Scene& scene) { - model.asset.version = "2.0"; - model.asset.generator = "SauceEngine GLTFExporter"; - - tinygltf::Scene gltfScene; - gltfScene.name = "Scene"; - - const auto& entities = scene.getEntities(); - for (size_t i = 0; i < entities.size(); ++i) { - int nodeIdx = processEntity(model, entities[i]); - if (nodeIdx >= 0) { - gltfScene.nodes.push_back(nodeIdx); - } - } - - model.scenes.push_back(gltfScene); - model.defaultScene = 0; - - // Finalize buffer - if (!bufferData.empty()) { - tinygltf::Buffer buffer; - buffer.data = bufferData; - model.buffers.push_back(buffer); - } -} - -int GLTFExporter::processEntity(tinygltf::Model& model, const Entity& entity) { - tinygltf::Node node; - node.name = entity.get_name(); - - // Export transform - const auto* tc = entity.getComponent(); - if (tc) { - const auto& transform = tc->getTransform(); - glm::vec3 t = transform.getTranslation(); - glm::quat r = transform.getRotation(); - glm::vec3 s = transform.getScale(); - - // Only write non-default values - if (t.x != 0.0f || t.y != 0.0f || t.z != 0.0f) { - node.translation = { - static_cast(t.x), - static_cast(t.y), - static_cast(t.z) - }; - } + namespace modeling { - // GLTF quaternion order: [x, y, z, w] - if (r.x != 0.0f || r.y != 0.0f || r.z != 0.0f || r.w != 1.0f) { - node.rotation = { - static_cast(r.x), - static_cast(r.y), - static_cast(r.z), - static_cast(r.w) - }; - } + GLTFExporter::GLTFExporter() = default; - if (s.x != 1.0f || s.y != 1.0f || s.z != 1.0f) { - node.scale = { - static_cast(s.x), - static_cast(s.y), - static_cast(s.z) - }; - } - } - - // Export mesh if entity has MeshRendererComponents - auto mrcs = entity.getComponents(); - if (!mrcs.empty()) { - int meshIdx = getOrCreateMesh(model, entity); - if (meshIdx >= 0) { - node.mesh = meshIdx; + GLTFExporter::GLTFExporter(const ExportOptions& options) : options(options) { } - } - int nodeIdx = static_cast(model.nodes.size()); - model.nodes.push_back(node); - return nodeIdx; -} + bool GLTFExporter::exportScene(const Scene& scene, const std::string& filePath) { + // Reset state + meshMap.clear(); + materialMap.clear(); + textureMap.clear(); + imageMap.clear(); + bufferData.clear(); -int GLTFExporter::getOrCreateMesh(tinygltf::Model& model, const Entity& entity) { - auto mrcs = entity.getComponents(); - if (mrcs.empty()) return -1; + std::filesystem::path outPath(filePath); + outputDirectory = outPath.parent_path().string(); - // Check if all mesh pointers match a previously exported mesh group - // For simplicity, we always create a new GLTF mesh per entity - // (GLTF meshes contain primitives, one per MRC) - tinygltf::Mesh gltfMesh; - gltfMesh.name = entity.get_name(); + // Ensure output directory exists + if (!outputDirectory.empty()) { + std::filesystem::create_directories(outputDirectory); + } - for (const auto* mrc : mrcs) { - auto mesh = mrc->getMesh(); - if (!mesh || !mesh->isValid()) continue; + tinygltf::Model model; + buildModel(model, scene); - tinygltf::Primitive primitive; - primitive.mode = TINYGLTF_MODE_TRIANGLES; + tinygltf::TinyGLTF writer; + bool success = false; - const auto& vertices = mesh->getVertices(); - const auto& indices = mesh->getIndices(); + if (options.writeBinary) { + success = + writer.WriteGltfSceneToFile(&model, filePath, options.embedImages, + options.embedBuffers, options.prettyPrint, true); + } else { + success = + writer.WriteGltfSceneToFile(&model, filePath, options.embedImages, + options.embedBuffers, options.prettyPrint, false); + } - if (vertices.empty() || indices.empty()) continue; + if (!success) { + std::cerr << "GLTFExporter: Failed to write file: " << filePath << std::endl; + } - // Compute POSITION min/max bounds - glm::vec3 posMin(std::numeric_limits::max()); - glm::vec3 posMax(std::numeric_limits::lowest()); - for (const auto& v : vertices) { - posMin = glm::min(posMin, v.position); - posMax = glm::max(posMax, v.position); + return success; } - // Pack POSITION data - { - std::vector posData(vertices.size() * 3); - for (size_t i = 0; i < vertices.size(); ++i) { - posData[i * 3 + 0] = vertices[i].position.x; - posData[i * 3 + 1] = vertices[i].position.y; - posData[i * 3 + 2] = vertices[i].position.z; + void GLTFExporter::buildModel(tinygltf::Model& model, const Scene& scene) { + model.asset.version = "2.0"; + model.asset.generator = "SauceEngine GLTFExporter"; + + tinygltf::Scene gltfScene; + gltfScene.name = "Scene"; + + const auto& entities = scene.getEntities(); + for (size_t i = 0; i < entities.size(); ++i) { + int nodeIdx = processEntity(model, entities[i]); + if (nodeIdx >= 0) { + gltfScene.nodes.push_back(nodeIdx); + } } - std::vector minVals = { - static_cast(posMin.x), - static_cast(posMin.y), - static_cast(posMin.z) - }; - std::vector maxVals = { - static_cast(posMax.x), - static_cast(posMax.y), - static_cast(posMax.z) - }; - int accIdx = addAccessor(model, posData.data(), vertices.size(), - TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC3, - sizeof(float) * 3, minVals, maxVals); - primitive.attributes["POSITION"] = accIdx; - } - // Pack NORMAL data - { - std::vector normalData(vertices.size() * 3); - for (size_t i = 0; i < vertices.size(); ++i) { - normalData[i * 3 + 0] = vertices[i].normal.x; - normalData[i * 3 + 1] = vertices[i].normal.y; - normalData[i * 3 + 2] = vertices[i].normal.z; + model.scenes.push_back(gltfScene); + model.defaultScene = 0; + + // Finalize buffer + if (!bufferData.empty()) { + tinygltf::Buffer buffer; + buffer.data = bufferData; + model.buffers.push_back(buffer); } - int accIdx = addAccessor(model, normalData.data(), vertices.size(), - TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC3, - sizeof(float) * 3); - primitive.attributes["NORMAL"] = accIdx; } - // Pack TEXCOORD_0 data - { - std::vector texData(vertices.size() * 2); - for (size_t i = 0; i < vertices.size(); ++i) { - texData[i * 2 + 0] = vertices[i].texCoords.x; - texData[i * 2 + 1] = vertices[i].texCoords.y; + int GLTFExporter::processEntity(tinygltf::Model& model, const Entity& entity) { + tinygltf::Node node; + node.name = entity.get_name(); + + // Export transform + const auto* tc = entity.getComponent(); + if (tc) { + const auto& transform = tc->getTransform(); + glm::vec3 t = transform.getTranslation(); + glm::quat r = transform.getRotation(); + glm::vec3 s = transform.getScale(); + + // Only write non-default values + if (t.x != 0.0f || t.y != 0.0f || t.z != 0.0f) { + node.translation = {static_cast(t.x), static_cast(t.y), + static_cast(t.z)}; + } + + // GLTF quaternion order: [x, y, z, w] + if (r.x != 0.0f || r.y != 0.0f || r.z != 0.0f || r.w != 1.0f) { + node.rotation = {static_cast(r.x), static_cast(r.y), + static_cast(r.z), static_cast(r.w)}; + } + + if (s.x != 1.0f || s.y != 1.0f || s.z != 1.0f) { + node.scale = {static_cast(s.x), static_cast(s.y), + static_cast(s.z)}; + } } - int accIdx = addAccessor(model, texData.data(), vertices.size(), - TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC2, - sizeof(float) * 2); - primitive.attributes["TEXCOORD_0"] = accIdx; - } - // Pack TANGENT data - { - std::vector tangentData(vertices.size() * 4); - for (size_t i = 0; i < vertices.size(); ++i) { - tangentData[i * 4 + 0] = vertices[i].tangent.x; - tangentData[i * 4 + 1] = vertices[i].tangent.y; - tangentData[i * 4 + 2] = vertices[i].tangent.z; - tangentData[i * 4 + 3] = vertices[i].tangent.w; + // Export mesh if entity has MeshRendererComponents + auto mrcs = entity.getComponents(); + if (!mrcs.empty()) { + int meshIdx = getOrCreateMesh(model, entity); + if (meshIdx >= 0) { + node.mesh = meshIdx; + } } - int accIdx = addAccessor(model, tangentData.data(), vertices.size(), - TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC4, - sizeof(float) * 4); - primitive.attributes["TANGENT"] = accIdx; + + int nodeIdx = static_cast(model.nodes.size()); + model.nodes.push_back(node); + return nodeIdx; } - // Pack indices - { - int accIdx = addAccessor(model, indices.data(), indices.size(), - TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT, TINYGLTF_TYPE_SCALAR, - sizeof(uint32_t)); - primitive.indices = accIdx; + int GLTFExporter::getOrCreateMesh(tinygltf::Model& model, const Entity& entity) { + auto mrcs = entity.getComponents(); + if (mrcs.empty()) + return -1; + + // Check if all mesh pointers match a previously exported mesh group + // For simplicity, we always create a new GLTF mesh per entity + // (GLTF meshes contain primitives, one per MRC) + tinygltf::Mesh gltfMesh; + gltfMesh.name = entity.get_name(); + + for (const auto* mrc : mrcs) { + auto mesh = mrc->getMesh(); + if (!mesh || !mesh->isValid()) + continue; + + tinygltf::Primitive primitive; + primitive.mode = TINYGLTF_MODE_TRIANGLES; + + const auto& vertices = mesh->getVertices(); + const auto& indices = mesh->getIndices(); + + if (vertices.empty() || indices.empty()) + continue; + + // Compute POSITION min/max bounds + glm::vec3 posMin(std::numeric_limits::max()); + glm::vec3 posMax(std::numeric_limits::lowest()); + for (const auto& v : vertices) { + posMin = glm::min(posMin, v.position); + posMax = glm::max(posMax, v.position); + } + + // Pack POSITION data + { + std::vector posData(vertices.size() * 3); + for (size_t i = 0; i < vertices.size(); ++i) { + posData[i * 3 + 0] = vertices[i].position.x; + posData[i * 3 + 1] = vertices[i].position.y; + posData[i * 3 + 2] = vertices[i].position.z; + } + std::vector minVals = {static_cast(posMin.x), + static_cast(posMin.y), + static_cast(posMin.z)}; + std::vector maxVals = {static_cast(posMax.x), + static_cast(posMax.y), + static_cast(posMax.z)}; + int accIdx = addAccessor(model, posData.data(), vertices.size(), + TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC3, + sizeof(float) * 3, minVals, maxVals); + primitive.attributes["POSITION"] = accIdx; + } + + // Pack NORMAL data + { + std::vector normalData(vertices.size() * 3); + for (size_t i = 0; i < vertices.size(); ++i) { + normalData[i * 3 + 0] = vertices[i].normal.x; + normalData[i * 3 + 1] = vertices[i].normal.y; + normalData[i * 3 + 2] = vertices[i].normal.z; + } + int accIdx = addAccessor(model, normalData.data(), vertices.size(), + TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC3, + sizeof(float) * 3); + primitive.attributes["NORMAL"] = accIdx; + } + + // Pack TEXCOORD_0 data + { + std::vector texData(vertices.size() * 2); + for (size_t i = 0; i < vertices.size(); ++i) { + texData[i * 2 + 0] = vertices[i].texCoords.x; + texData[i * 2 + 1] = vertices[i].texCoords.y; + } + int accIdx = addAccessor(model, texData.data(), vertices.size(), + TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC2, + sizeof(float) * 2); + primitive.attributes["TEXCOORD_0"] = accIdx; + } + + // Pack TANGENT data + { + std::vector tangentData(vertices.size() * 4); + for (size_t i = 0; i < vertices.size(); ++i) { + tangentData[i * 4 + 0] = vertices[i].tangent.x; + tangentData[i * 4 + 1] = vertices[i].tangent.y; + tangentData[i * 4 + 2] = vertices[i].tangent.z; + tangentData[i * 4 + 3] = vertices[i].tangent.w; + } + int accIdx = addAccessor(model, tangentData.data(), vertices.size(), + TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_TYPE_VEC4, + sizeof(float) * 4); + primitive.attributes["TANGENT"] = accIdx; + } + + // Pack indices + { + int accIdx = addAccessor(model, indices.data(), indices.size(), + TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT, + TINYGLTF_TYPE_SCALAR, sizeof(uint32_t)); + primitive.indices = accIdx; + } + + // Material + auto material = mrc->getMaterial(); + if (material) { + int matIdx = getOrCreateMaterial(model, *material); + if (matIdx >= 0) { + primitive.material = matIdx; + } + } + + gltfMesh.primitives.push_back(primitive); + } + + if (gltfMesh.primitives.empty()) + return -1; + + int meshIdx = static_cast(model.meshes.size()); + model.meshes.push_back(gltfMesh); + return meshIdx; } - // Material - auto material = mrc->getMaterial(); - if (material) { - int matIdx = getOrCreateMaterial(model, *material); - if (matIdx >= 0) { - primitive.material = matIdx; + int GLTFExporter::getOrCreateMaterial(tinygltf::Model& model, const Material& material) { + const Material* ptr = &material; + auto it = materialMap.find(ptr); + if (it != materialMap.end()) { + return it->second; + } + + tinygltf::Material gltfMat; + gltfMat.name = material.getName(); + + const auto& props = material.getProperties(); + + // PBR metallic-roughness + gltfMat.pbrMetallicRoughness.baseColorFactor = { + static_cast(props.baseColorFactor.r), + static_cast(props.baseColorFactor.g), + static_cast(props.baseColorFactor.b), + static_cast(props.baseColorFactor.a)}; + gltfMat.pbrMetallicRoughness.metallicFactor = static_cast(props.metallicFactor); + gltfMat.pbrMetallicRoughness.roughnessFactor = + static_cast(props.roughnessFactor); + + // Emissive + gltfMat.emissiveFactor = {static_cast(props.emissiveFactor.r), + static_cast(props.emissiveFactor.g), + static_cast(props.emissiveFactor.b)}; + + // Normal scale + gltfMat.normalTexture.scale = static_cast(props.normalScale); + + // Occlusion strength + gltfMat.occlusionTexture.strength = static_cast(props.occlusionStrength); + + // Alpha mode + switch (props.alphaMode) { + case MaterialProperties::AlphaMode::Opaque: + gltfMat.alphaMode = "OPAQUE"; + break; + case MaterialProperties::AlphaMode::Mask: + gltfMat.alphaMode = "MASK"; + break; + case MaterialProperties::AlphaMode::Blend: + gltfMat.alphaMode = "BLEND"; + break; } + gltfMat.alphaCutoff = static_cast(props.alphaCutoff); + gltfMat.doubleSided = props.doubleSided; + + // Textures + const auto& textures = material.getTextures(); + + auto setTextureInfo = [&](TextureType type, auto& textureInfo) { + auto texIt = textures.find(type); + if (texIt != textures.end() && texIt->second) { + int texIdx = getOrCreateTexture(model, *texIt->second); + if (texIdx >= 0) { + textureInfo.index = texIdx; + } + } + }; + + setTextureInfo(TextureType::BaseColor, gltfMat.pbrMetallicRoughness.baseColorTexture); + setTextureInfo(TextureType::MetallicRoughness, + gltfMat.pbrMetallicRoughness.metallicRoughnessTexture); + setTextureInfo(TextureType::Normal, gltfMat.normalTexture); + setTextureInfo(TextureType::Occlusion, gltfMat.occlusionTexture); + setTextureInfo(TextureType::Emissive, gltfMat.emissiveTexture); + + int matIdx = static_cast(model.materials.size()); + model.materials.push_back(gltfMat); + materialMap[ptr] = matIdx; + return matIdx; } - gltfMesh.primitives.push_back(primitive); - } - - if (gltfMesh.primitives.empty()) return -1; - - int meshIdx = static_cast(model.meshes.size()); - model.meshes.push_back(gltfMesh); - return meshIdx; -} - -int GLTFExporter::getOrCreateMaterial(tinygltf::Model& model, const Material& material) { - const Material* ptr = &material; - auto it = materialMap.find(ptr); - if (it != materialMap.end()) { - return it->second; - } - - tinygltf::Material gltfMat; - gltfMat.name = material.getName(); - - const auto& props = material.getProperties(); - - // PBR metallic-roughness - gltfMat.pbrMetallicRoughness.baseColorFactor = { - static_cast(props.baseColorFactor.r), - static_cast(props.baseColorFactor.g), - static_cast(props.baseColorFactor.b), - static_cast(props.baseColorFactor.a) - }; - gltfMat.pbrMetallicRoughness.metallicFactor = static_cast(props.metallicFactor); - gltfMat.pbrMetallicRoughness.roughnessFactor = static_cast(props.roughnessFactor); - - // Emissive - gltfMat.emissiveFactor = { - static_cast(props.emissiveFactor.r), - static_cast(props.emissiveFactor.g), - static_cast(props.emissiveFactor.b) - }; - - // Normal scale - gltfMat.normalTexture.scale = static_cast(props.normalScale); - - // Occlusion strength - gltfMat.occlusionTexture.strength = static_cast(props.occlusionStrength); - - // Alpha mode - switch (props.alphaMode) { - case MaterialProperties::AlphaMode::Opaque: gltfMat.alphaMode = "OPAQUE"; break; - case MaterialProperties::AlphaMode::Mask: gltfMat.alphaMode = "MASK"; break; - case MaterialProperties::AlphaMode::Blend: gltfMat.alphaMode = "BLEND"; break; - } - gltfMat.alphaCutoff = static_cast(props.alphaCutoff); - gltfMat.doubleSided = props.doubleSided; - - // Textures - const auto& textures = material.getTextures(); - - auto setTextureInfo = [&](TextureType type, auto& textureInfo) { - auto texIt = textures.find(type); - if (texIt != textures.end() && texIt->second) { - int texIdx = getOrCreateTexture(model, *texIt->second); - if (texIdx >= 0) { - textureInfo.index = texIdx; + int GLTFExporter::getOrCreateTexture(tinygltf::Model& model, const Texture& texture) { + const Texture* ptr = &texture; + auto it = textureMap.find(ptr); + if (it != textureMap.end()) { + return it->second; } + + int imageIdx = getOrCreateImage(model, texture); + if (imageIdx < 0) + return -1; + + tinygltf::Texture gltfTex; + gltfTex.source = imageIdx; + + int texIdx = static_cast(model.textures.size()); + model.textures.push_back(gltfTex); + textureMap[ptr] = texIdx; + return texIdx; } - }; - - setTextureInfo(TextureType::BaseColor, gltfMat.pbrMetallicRoughness.baseColorTexture); - setTextureInfo(TextureType::MetallicRoughness, gltfMat.pbrMetallicRoughness.metallicRoughnessTexture); - setTextureInfo(TextureType::Normal, gltfMat.normalTexture); - setTextureInfo(TextureType::Occlusion, gltfMat.occlusionTexture); - setTextureInfo(TextureType::Emissive, gltfMat.emissiveTexture); - - int matIdx = static_cast(model.materials.size()); - model.materials.push_back(gltfMat); - materialMap[ptr] = matIdx; - return matIdx; -} - -int GLTFExporter::getOrCreateTexture(tinygltf::Model& model, const Texture& texture) { - const Texture* ptr = &texture; - auto it = textureMap.find(ptr); - if (it != textureMap.end()) { - return it->second; - } - - int imageIdx = getOrCreateImage(model, texture); - if (imageIdx < 0) return -1; - - tinygltf::Texture gltfTex; - gltfTex.source = imageIdx; - - int texIdx = static_cast(model.textures.size()); - model.textures.push_back(gltfTex); - textureMap[ptr] = texIdx; - return texIdx; -} - -int GLTFExporter::getOrCreateImage(tinygltf::Model& model, const Texture& texture) { - const Texture* ptr = &texture; - auto it = imageMap.find(ptr); - if (it != imageMap.end()) { - return it->second; - } - - tinygltf::Image gltfImage; - - if (!texture.isEmbedded() && !texture.getPath().empty()) { - // External file - use relative URI - std::filesystem::path texPath(texture.getPath()); - if (!outputDirectory.empty()) { - std::filesystem::path outDir(outputDirectory); - auto relPath = std::filesystem::relative(texPath, outDir); - // If the relative path goes too far up, just use the filename - if (relPath.string().starts_with("..")) { - // Copy texture to output directory - std::filesystem::path destPath = outDir / texPath.filename(); - try { - if (texPath != destPath && std::filesystem::exists(texPath)) { - std::filesystem::copy_file(texPath, destPath, - std::filesystem::copy_options::skip_existing); + + int GLTFExporter::getOrCreateImage(tinygltf::Model& model, const Texture& texture) { + const Texture* ptr = &texture; + auto it = imageMap.find(ptr); + if (it != imageMap.end()) { + return it->second; + } + + tinygltf::Image gltfImage; + + if (!texture.isEmbedded() && !texture.getPath().empty()) { + // External file - use relative URI + std::filesystem::path texPath(texture.getPath()); + if (!outputDirectory.empty()) { + std::filesystem::path outDir(outputDirectory); + auto relPath = std::filesystem::relative(texPath, outDir); + // If the relative path goes too far up, just use the filename + if (relPath.string().starts_with("..")) { + // Copy texture to output directory + std::filesystem::path destPath = outDir / texPath.filename(); + try { + if (texPath != destPath && std::filesystem::exists(texPath)) { + std::filesystem::copy_file( + texPath, destPath, + std::filesystem::copy_options::skip_existing); + } + } catch (const std::filesystem::filesystem_error&) { + // If copy fails, just reference original filename + } + gltfImage.uri = texPath.filename().string(); + } else { + gltfImage.uri = relPath.string(); + } + } else { + gltfImage.uri = texPath.filename().string(); + } + } else if (texture.isEmbedded()) { + // For embedded textures, store raw RGBA data in the image + // TinyGLTF will handle embedding in the buffer for .glb + auto& texRef = const_cast(texture); + const auto& data = texRef.getData(); + if (!data.empty()) { + gltfImage.width = texture.getWidth(); + gltfImage.height = texture.getHeight(); + gltfImage.component = 4; // RGBA + gltfImage.bits = 8; + gltfImage.pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + gltfImage.image = data; + + // Ensure we have 4-channel data + if (texture.getChannels() < 4 && + gltfImage.image.size() == + static_cast(texture.getWidth() * texture.getHeight() * + texture.getChannels())) { + // Convert to RGBA + std::vector rgba(texture.getWidth() * texture.getHeight() * + 4); + int ch = texture.getChannels(); + for (int i = 0; i < texture.getWidth() * texture.getHeight(); ++i) { + rgba[i * 4 + 0] = (ch > 0) ? data[i * ch + 0] : 0; + rgba[i * 4 + 1] = (ch > 1) ? data[i * ch + 1] : 0; + rgba[i * 4 + 2] = (ch > 2) ? data[i * ch + 2] : 0; + rgba[i * 4 + 3] = (ch > 3) ? data[i * ch + 3] : 255; + } + gltfImage.image = rgba; } - } catch (const std::filesystem::filesystem_error&) { - // If copy fails, just reference original filename } - gltfImage.uri = texPath.filename().string(); - } else { - gltfImage.uri = relPath.string(); } - } else { - gltfImage.uri = texPath.filename().string(); + + int imgIdx = static_cast(model.images.size()); + model.images.push_back(gltfImage); + imageMap[ptr] = imgIdx; + return imgIdx; } - } else if (texture.isEmbedded()) { - // For embedded textures, store raw RGBA data in the image - // TinyGLTF will handle embedding in the buffer for .glb - auto& texRef = const_cast(texture); - const auto& data = texRef.getData(); - if (!data.empty()) { - gltfImage.width = texture.getWidth(); - gltfImage.height = texture.getHeight(); - gltfImage.component = 4; // RGBA - gltfImage.bits = 8; - gltfImage.pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; - gltfImage.image = data; - - // Ensure we have 4-channel data - if (texture.getChannels() < 4 && gltfImage.image.size() == static_cast(texture.getWidth() * texture.getHeight() * texture.getChannels())) { - // Convert to RGBA - std::vector rgba(texture.getWidth() * texture.getHeight() * 4); - int ch = texture.getChannels(); - for (int i = 0; i < texture.getWidth() * texture.getHeight(); ++i) { - rgba[i * 4 + 0] = (ch > 0) ? data[i * ch + 0] : 0; - rgba[i * 4 + 1] = (ch > 1) ? data[i * ch + 1] : 0; - rgba[i * 4 + 2] = (ch > 2) ? data[i * ch + 2] : 0; - rgba[i * 4 + 3] = (ch > 3) ? data[i * ch + 3] : 255; + + int GLTFExporter::addAccessor(tinygltf::Model& model, const void* data, size_t count, + int componentType, int type, size_t byteStride, + const std::vector& minValues, + const std::vector& maxValues) { + // Pad for alignment + padBuffer(4); + + size_t byteOffset = bufferData.size(); + size_t totalBytes = count * byteStride; + + // Append data to buffer + bufferData.resize(bufferData.size() + totalBytes); + std::memcpy(bufferData.data() + byteOffset, data, totalBytes); + + // Create buffer view + tinygltf::BufferView bufferView; + bufferView.buffer = 0; + bufferView.byteOffset = byteOffset; + bufferView.byteLength = totalBytes; + + // Set target based on type + if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT || + componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT || + componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + if (type == TINYGLTF_TYPE_SCALAR) { + bufferView.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; } - gltfImage.image = rgba; + } else { + bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; } + + int bufferViewIdx = static_cast(model.bufferViews.size()); + model.bufferViews.push_back(bufferView); + + // Create accessor + tinygltf::Accessor accessor; + accessor.bufferView = bufferViewIdx; + accessor.byteOffset = 0; + accessor.componentType = componentType; + accessor.count = count; + accessor.type = type; + + if (!minValues.empty()) + accessor.minValues = minValues; + if (!maxValues.empty()) + accessor.maxValues = maxValues; + + int accessorIdx = static_cast(model.accessors.size()); + model.accessors.push_back(accessor); + return accessorIdx; } - } - - int imgIdx = static_cast(model.images.size()); - model.images.push_back(gltfImage); - imageMap[ptr] = imgIdx; - return imgIdx; -} - -int GLTFExporter::addAccessor(tinygltf::Model& model, - const void* data, size_t count, - int componentType, int type, - size_t byteStride, - const std::vector& minValues, - const std::vector& maxValues) { - // Pad for alignment - padBuffer(4); - - size_t byteOffset = bufferData.size(); - size_t totalBytes = count * byteStride; - - // Append data to buffer - bufferData.resize(bufferData.size() + totalBytes); - std::memcpy(bufferData.data() + byteOffset, data, totalBytes); - - // Create buffer view - tinygltf::BufferView bufferView; - bufferView.buffer = 0; - bufferView.byteOffset = byteOffset; - bufferView.byteLength = totalBytes; - - // Set target based on type - if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT || - componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT || - componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - if (type == TINYGLTF_TYPE_SCALAR) { - bufferView.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; + + void GLTFExporter::padBuffer(size_t alignment) { + size_t remainder = bufferData.size() % alignment; + if (remainder != 0) { + size_t padding = alignment - remainder; + bufferData.resize(bufferData.size() + padding, 0); + } } - } else { - bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; - } - - int bufferViewIdx = static_cast(model.bufferViews.size()); - model.bufferViews.push_back(bufferView); - - // Create accessor - tinygltf::Accessor accessor; - accessor.bufferView = bufferViewIdx; - accessor.byteOffset = 0; - accessor.componentType = componentType; - accessor.count = count; - accessor.type = type; - - if (!minValues.empty()) accessor.minValues = minValues; - if (!maxValues.empty()) accessor.maxValues = maxValues; - - int accessorIdx = static_cast(model.accessors.size()); - model.accessors.push_back(accessor); - return accessorIdx; -} - -void GLTFExporter::padBuffer(size_t alignment) { - size_t remainder = bufferData.size() % alignment; - if (remainder != 0) { - size_t padding = alignment - remainder; - bufferData.resize(bufferData.size() + padding, 0); - } -} - -} // namespace modeling + + } // namespace modeling } // namespace sauce diff --git a/src/app/modeling/GLTFLoader.cpp b/src/app/modeling/GLTFLoader.cpp index 8536827c..a42f76f1 100644 --- a/src/app/modeling/GLTFLoader.cpp +++ b/src/app/modeling/GLTFLoader.cpp @@ -4,559 +4,567 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION #include -#include #include +#include namespace sauce { -namespace modeling { - -GLTFLoader::GLTFLoader() - : currentGltfModel(nullptr) { -} - -GLTFLoader::GLTFLoader(const LoadOptions& options) - : options(options) - , currentGltfModel(nullptr) { -} - -std::vector> GLTFLoader::loadModels(const std::string& filePath) { - tinygltf::Model gltfModel; - if (!loadGltfFile(filePath, gltfModel)) { - return {}; - } - - std::vector> models; - for (size_t i = 0; i < gltfModel.scenes.size(); ++i) { - auto model = processScene(gltfModel, static_cast(i)); - if (model) { - models.push_back(model); + namespace modeling { + + GLTFLoader::GLTFLoader() : currentGltfModel(nullptr) { } - } - - return models; -} - -std::shared_ptr GLTFLoader::loadModel(const std::string& filePath, size_t sceneIndex) { - tinygltf::Model gltfModel; - if (!loadGltfFile(filePath, gltfModel)) { - return nullptr; - } - - if (sceneIndex >= gltfModel.scenes.size()) { - return nullptr; - } - - auto model = processScene(gltfModel, static_cast(sceneIndex)); - return model; -} - -bool GLTFLoader::loadGltfFile(const std::string& filePath, tinygltf::Model& gltfModel) { - tinygltf::TinyGLTF loader; - std::string err; - std::string warn; - - // Extract base directory for relative texture paths - std::filesystem::path path(filePath); - baseDirectory = path.parent_path().string(); - - bool ret = false; - if (filePath.ends_with(".gltf")) { - ret = loader.LoadASCIIFromFile(&gltfModel, &err, &warn, filePath); - } else if (filePath.ends_with(".glb")) { - ret = loader.LoadBinaryFromFile(&gltfModel, &err, &warn, filePath); - } else { - return false; - } - - if (!ret) { - return false; - } - - currentGltfModel = &gltfModel; - return true; -} - -std::shared_ptr GLTFLoader::processScene(const tinygltf::Model& gltfModel, int sceneIndex) { - if (sceneIndex < 0 || sceneIndex >= static_cast(gltfModel.scenes.size())) { - return nullptr; - } - - parseLightsExtension(gltfModel); - - const auto& scene = gltfModel.scenes[sceneIndex]; - auto model = std::make_shared(); - - // Create root node - auto rootNode = std::make_shared("__root__"); - - // Process all root nodes in the scene - for (int nodeIndex : scene.nodes) { - auto childNode = processNode(gltfModel, nodeIndex); - if (childNode) { - rootNode->addChild(childNode); + + GLTFLoader::GLTFLoader(const LoadOptions& options) + : options(options), currentGltfModel(nullptr) { } - } - - model->setRootNode(rootNode); - - return model; -} - -std::shared_ptr GLTFLoader::processNode(const tinygltf::Model& gltfModel, int nodeIndex) { - if (nodeIndex < 0 || nodeIndex >= static_cast(gltfModel.nodes.size())) { - return nullptr; - } - - const auto& gltfNode = gltfModel.nodes[nodeIndex]; - auto node = std::make_shared(gltfNode.name); - - // Extract transform - node->getTransform() = extractTransform(gltfNode); - - // Process mesh if present - if (gltfNode.mesh >= 0 && gltfNode.mesh < static_cast(gltfModel.meshes.size())) { - const auto& gltfMesh = gltfModel.meshes[gltfNode.mesh]; - - for (const auto& primitive : gltfMesh.primitives) { - auto mesh = processPrimitive(gltfModel, primitive); - if (mesh) { - // Get material - std::shared_ptr material; - if (primitive.material >= 0) { - material = processMaterial(gltfModel, primitive.material); - } else { - // Default material - material = std::make_shared("default"); - } - node->addMeshMaterialPair(mesh, material); + std::vector> GLTFLoader::loadModels(const std::string& filePath) { + tinygltf::Model gltfModel; + if (!loadGltfFile(filePath, gltfModel)) { + return {}; + } + + std::vector> models; + for (size_t i = 0; i < gltfModel.scenes.size(); ++i) { + auto model = processScene(gltfModel, static_cast(i)); + if (model) { + models.push_back(model); + } } - } - } - applyNodeLight(gltfNode, node); + return models; + } - // Process children - processNodeChildren(gltfModel, gltfNode, node); + std::shared_ptr GLTFLoader::loadModel(const std::string& filePath, + size_t sceneIndex) { + tinygltf::Model gltfModel; + if (!loadGltfFile(filePath, gltfModel)) { + return nullptr; + } - return node; -} + if (sceneIndex >= gltfModel.scenes.size()) { + return nullptr; + } -void GLTFLoader::processNodeChildren(const tinygltf::Model& gltfModel, - const tinygltf::Node& gltfNode, - std::shared_ptr node) { - for (int childIndex : gltfNode.children) { - auto childNode = processNode(gltfModel, childIndex); - if (childNode) { - node->addChild(childNode); + auto model = processScene(gltfModel, static_cast(sceneIndex)); + return model; } - } -} -Transform GLTFLoader::extractTransform(const tinygltf::Node& gltfNode) { - Transform transform; + bool GLTFLoader::loadGltfFile(const std::string& filePath, tinygltf::Model& gltfModel) { + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + // Extract base directory for relative texture paths + std::filesystem::path path(filePath); + baseDirectory = path.parent_path().string(); + + bool ret = false; + if (filePath.ends_with(".gltf")) { + ret = loader.LoadASCIIFromFile(&gltfModel, &err, &warn, filePath); + } else if (filePath.ends_with(".glb")) { + ret = loader.LoadBinaryFromFile(&gltfModel, &err, &warn, filePath); + } else { + return false; + } + + if (!ret) { + return false; + } - if (!gltfNode.matrix.empty() && gltfNode.matrix.size() == 16) { - // Matrix format - glm::mat4 matrix; - for (int i = 0; i < 16; ++i) { - matrix[i / 4][i % 4] = static_cast(gltfNode.matrix[i]); + currentGltfModel = &gltfModel; + return true; } - transform = Transform::fromMatrix(matrix); - } else { - // TRS format - if (!gltfNode.translation.empty() && gltfNode.translation.size() == 3) { - transform.setTranslation(glm::vec3( - static_cast(gltfNode.translation[0]), - static_cast(gltfNode.translation[1]), - static_cast(gltfNode.translation[2]) - )); + + std::shared_ptr GLTFLoader::processScene(const tinygltf::Model& gltfModel, + int sceneIndex) { + if (sceneIndex < 0 || sceneIndex >= static_cast(gltfModel.scenes.size())) { + return nullptr; + } + + parseLightsExtension(gltfModel); + + const auto& scene = gltfModel.scenes[sceneIndex]; + auto model = std::make_shared(); + + // Create root node + auto rootNode = std::make_shared("__root__"); + + // Process all root nodes in the scene + for (int nodeIndex : scene.nodes) { + auto childNode = processNode(gltfModel, nodeIndex); + if (childNode) { + rootNode->addChild(childNode); + } + } + + model->setRootNode(rootNode); + + return model; } - if (!gltfNode.rotation.empty() && gltfNode.rotation.size() == 4) { - // GLTF quaternion format: [x, y, z, w] - transform.setRotation(glm::quat( - static_cast(gltfNode.rotation[3]), // w - static_cast(gltfNode.rotation[0]), // x - static_cast(gltfNode.rotation[1]), // y - static_cast(gltfNode.rotation[2]) // z - )); + std::shared_ptr GLTFLoader::processNode(const tinygltf::Model& gltfModel, + int nodeIndex) { + if (nodeIndex < 0 || nodeIndex >= static_cast(gltfModel.nodes.size())) { + return nullptr; + } + + const auto& gltfNode = gltfModel.nodes[nodeIndex]; + auto node = std::make_shared(gltfNode.name); + + // Extract transform + node->getTransform() = extractTransform(gltfNode); + + // Process mesh if present + if (gltfNode.mesh >= 0 && gltfNode.mesh < static_cast(gltfModel.meshes.size())) { + const auto& gltfMesh = gltfModel.meshes[gltfNode.mesh]; + + for (const auto& primitive : gltfMesh.primitives) { + auto mesh = processPrimitive(gltfModel, primitive); + if (mesh) { + // Get material + std::shared_ptr material; + if (primitive.material >= 0) { + material = processMaterial(gltfModel, primitive.material); + } else { + // Default material + material = std::make_shared("default"); + } + + node->addMeshMaterialPair(mesh, material); + } + } + } + + applyNodeLight(gltfNode, node); + + // Process children + processNodeChildren(gltfModel, gltfNode, node); + + return node; } - if (!gltfNode.scale.empty() && gltfNode.scale.size() == 3) { - transform.setScale(glm::vec3( - static_cast(gltfNode.scale[0]), - static_cast(gltfNode.scale[1]), - static_cast(gltfNode.scale[2]) - )); + void GLTFLoader::processNodeChildren(const tinygltf::Model& gltfModel, + const tinygltf::Node& gltfNode, + std::shared_ptr node) { + for (int childIndex : gltfNode.children) { + auto childNode = processNode(gltfModel, childIndex); + if (childNode) { + node->addChild(childNode); + } + } } - } - - return transform; -} - -std::shared_ptr GLTFLoader::processPrimitive(const tinygltf::Model& gltfModel, - const tinygltf::Primitive& primitive) { - // Only support triangles for now - if (primitive.mode != TINYGLTF_MODE_TRIANGLES) { - return nullptr; - } - - std::vector vertices; - std::vector indices; - - // Extract indices - extractIndices(gltfModel, primitive, indices); - if (indices.empty()) { - return nullptr; - } - - // Determine vertex count from POSITION attribute - auto posIt = primitive.attributes.find("POSITION"); - if (posIt == primitive.attributes.end()) { - return nullptr; - } - - const auto& posAccessor = gltfModel.accessors[posIt->second]; - size_t vertexCount = posAccessor.count; - vertices.resize(vertexCount); - - // Initialize with defaults - for (auto& vertex : vertices) { - vertex.position = glm::vec3(0.0f); - vertex.normal = glm::vec3(0.0f, 1.0f, 0.0f); - vertex.texCoords = glm::vec2(0.0f); - vertex.color = glm::vec3(1.0f); - vertex.tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); - } - - // Extract vertex attributes - extractVertexAttribute(gltfModel, primitive, "POSITION", vertices, 0); - extractVertexAttribute(gltfModel, primitive, "NORMAL", vertices, 1); - extractVertexAttribute(gltfModel, primitive, "TEXCOORD_0", vertices, 2); - extractVertexAttribute(gltfModel, primitive, "COLOR_0", vertices, 3); - extractVertexAttribute(gltfModel, primitive, "TANGENT", vertices, 4); - - auto mesh = std::make_shared(vertices, indices); - - // Generate missing attributes - bool hasNormals = primitive.attributes.find("NORMAL") != primitive.attributes.end(); - bool hasTangents = primitive.attributes.find("TANGENT") != primitive.attributes.end(); - - if (!hasNormals && options.generateNormals) { - mesh->generateNormals(); - } - - if (!hasTangents && options.generateTangents) { - mesh->generateTangents(); - } - - // Validate mesh - if (options.validateMeshes && !mesh->isValid()) { - return nullptr; - } - - return mesh; -} - -void GLTFLoader::extractVertexAttribute(const tinygltf::Model& gltfModel, - const tinygltf::Primitive& primitive, - const std::string& attributeName, - std::vector& vertices, - int componentIndex) { - auto it = primitive.attributes.find(attributeName); - if (it == primitive.attributes.end()) { - return; - } - - int accessorIndex = it->second; - const auto& accessor = gltfModel.accessors[accessorIndex]; - const auto& bufferView = gltfModel.bufferViews[accessor.bufferView]; - const auto& buffer = gltfModel.buffers[bufferView.buffer]; - - const unsigned char* data = buffer.data.data() + bufferView.byteOffset + accessor.byteOffset; - size_t stride = accessor.ByteStride(bufferView); - - for (size_t i = 0; i < accessor.count && i < vertices.size(); ++i) { - const unsigned char* ptr = data + i * stride; - - if (componentIndex == 0) { // POSITION - if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT && accessor.type == TINYGLTF_TYPE_VEC3) { - const float* fptr = reinterpret_cast(ptr); - vertices[i].position = glm::vec3(fptr[0], fptr[1], fptr[2]); - } - } else if (componentIndex == 1) { // NORMAL - if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT && accessor.type == TINYGLTF_TYPE_VEC3) { - const float* fptr = reinterpret_cast(ptr); - vertices[i].normal = glm::vec3(fptr[0], fptr[1], fptr[2]); - } - } else if (componentIndex == 2) { // TEXCOORD_0 - if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT && accessor.type == TINYGLTF_TYPE_VEC2) { - const float* fptr = reinterpret_cast(ptr); - vertices[i].texCoords = glm::vec2(fptr[0], fptr[1]); - } - } else if (componentIndex == 3) { // COLOR_0 - if (accessor.type == TINYGLTF_TYPE_VEC3) { - if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { - const float* fptr = reinterpret_cast(ptr); - vertices[i].color = glm::vec3(fptr[0], fptr[1], fptr[2]); + + Transform GLTFLoader::extractTransform(const tinygltf::Node& gltfNode) { + Transform transform; + + if (!gltfNode.matrix.empty() && gltfNode.matrix.size() == 16) { + // Matrix format + glm::mat4 matrix; + for (int i = 0; i < 16; ++i) { + matrix[i / 4][i % 4] = static_cast(gltfNode.matrix[i]); } - } else if (accessor.type == TINYGLTF_TYPE_VEC4) { - if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { - const float* fptr = reinterpret_cast(ptr); - vertices[i].color = glm::vec3(fptr[0], fptr[1], fptr[2]); + transform = Transform::fromMatrix(matrix); + } else { + // TRS format + if (!gltfNode.translation.empty() && gltfNode.translation.size() == 3) { + transform.setTranslation( + glm::vec3(static_cast(gltfNode.translation[0]), + static_cast(gltfNode.translation[1]), + static_cast(gltfNode.translation[2]))); + } + + if (!gltfNode.rotation.empty() && gltfNode.rotation.size() == 4) { + // GLTF quaternion format: [x, y, z, w] + transform.setRotation(glm::quat(static_cast(gltfNode.rotation[3]), // w + static_cast(gltfNode.rotation[0]), // x + static_cast(gltfNode.rotation[1]), // y + static_cast(gltfNode.rotation[2]) // z + )); + } + + if (!gltfNode.scale.empty() && gltfNode.scale.size() == 3) { + transform.setScale(glm::vec3(static_cast(gltfNode.scale[0]), + static_cast(gltfNode.scale[1]), + static_cast(gltfNode.scale[2]))); } } - } else if (componentIndex == 4) { // TANGENT - if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT && accessor.type == TINYGLTF_TYPE_VEC4) { - const float* fptr = reinterpret_cast(ptr); - vertices[i].tangent = glm::vec4(fptr[0], fptr[1], fptr[2], fptr[3]); - } + + return transform; } - } -} -void GLTFLoader::extractIndices(const tinygltf::Model& gltfModel, - const tinygltf::Primitive& primitive, - std::vector& indices) { - if (primitive.indices < 0) { - return; - } + std::shared_ptr GLTFLoader::processPrimitive(const tinygltf::Model& gltfModel, + const tinygltf::Primitive& primitive) { + // Only support triangles for now + if (primitive.mode != TINYGLTF_MODE_TRIANGLES) { + return nullptr; + } - const auto& accessor = gltfModel.accessors[primitive.indices]; - const auto& bufferView = gltfModel.bufferViews[accessor.bufferView]; - const auto& buffer = gltfModel.buffers[bufferView.buffer]; + std::vector vertices; + std::vector indices; - const unsigned char* data = buffer.data.data() + bufferView.byteOffset + accessor.byteOffset; + // Extract indices + extractIndices(gltfModel, primitive, indices); + if (indices.empty()) { + return nullptr; + } - indices.resize(accessor.count); + // Determine vertex count from POSITION attribute + auto posIt = primitive.attributes.find("POSITION"); + if (posIt == primitive.attributes.end()) { + return nullptr; + } - if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - const uint16_t* ptr = reinterpret_cast(data); - for (size_t i = 0; i < accessor.count; ++i) { - indices[i] = static_cast(ptr[i]); - } - } else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - const uint32_t* ptr = reinterpret_cast(data); - std::memcpy(indices.data(), ptr, accessor.count * sizeof(uint32_t)); - } else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - const uint8_t* ptr = reinterpret_cast(data); - for (size_t i = 0; i < accessor.count; ++i) { - indices[i] = static_cast(ptr[i]); - } - } -} - -std::shared_ptr GLTFLoader::processMaterial(const tinygltf::Model& gltfModel, int materialIndex) { - if (materialIndex < 0 || materialIndex >= static_cast(gltfModel.materials.size())) { - return nullptr; - } - - const auto& gltfMaterial = gltfModel.materials[materialIndex]; - auto material = std::make_shared(gltfMaterial.name); - - // PBR metallic-roughness - if (gltfMaterial.pbrMetallicRoughness.baseColorTexture.index >= 0) { - auto texture = processTexture(gltfModel, - gltfMaterial.pbrMetallicRoughness.baseColorTexture.index, - TextureType::BaseColor, true); - if (texture) { - material->setTexture(TextureType::BaseColor, texture); - } - } - - if (gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) { - auto texture = processTexture(gltfModel, - gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture.index, - TextureType::MetallicRoughness, false); - if (texture) { - material->setTexture(TextureType::MetallicRoughness, texture); - } - } + const auto& posAccessor = gltfModel.accessors[posIt->second]; + size_t vertexCount = posAccessor.count; + vertices.resize(vertexCount); + + // Initialize with defaults + for (auto& vertex : vertices) { + vertex.position = glm::vec3(0.0f); + vertex.normal = glm::vec3(0.0f, 1.0f, 0.0f); + vertex.texCoords = glm::vec2(0.0f); + vertex.color = glm::vec3(1.0f); + vertex.tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); + } - if (gltfMaterial.normalTexture.index >= 0) { - auto texture = processTexture(gltfModel, gltfMaterial.normalTexture.index, - TextureType::Normal, false); - if (texture) { - material->setTexture(TextureType::Normal, texture); - } - } + // Extract vertex attributes + extractVertexAttribute(gltfModel, primitive, "POSITION", vertices, 0); + extractVertexAttribute(gltfModel, primitive, "NORMAL", vertices, 1); + extractVertexAttribute(gltfModel, primitive, "TEXCOORD_0", vertices, 2); + extractVertexAttribute(gltfModel, primitive, "COLOR_0", vertices, 3); + extractVertexAttribute(gltfModel, primitive, "TANGENT", vertices, 4); - if (gltfMaterial.occlusionTexture.index >= 0) { - auto texture = processTexture(gltfModel, gltfMaterial.occlusionTexture.index, - TextureType::Occlusion, false); - if (texture) { - material->setTexture(TextureType::Occlusion, texture); - } - } + auto mesh = std::make_shared(vertices, indices); - if (gltfMaterial.emissiveTexture.index >= 0) { - auto texture = processTexture(gltfModel, gltfMaterial.emissiveTexture.index, - TextureType::Emissive, true); - if (texture) { - material->setTexture(TextureType::Emissive, texture); - } - } - - // Material properties - auto& props = material->getProperties(); - - const auto& bc = gltfMaterial.pbrMetallicRoughness.baseColorFactor; - props.baseColorFactor = glm::vec4( - static_cast(bc[0]), - static_cast(bc[1]), - static_cast(bc[2]), - static_cast(bc[3]) - ); - - props.metallicFactor = static_cast(gltfMaterial.pbrMetallicRoughness.metallicFactor); - props.roughnessFactor = static_cast(gltfMaterial.pbrMetallicRoughness.roughnessFactor); - - const auto& ef = gltfMaterial.emissiveFactor; - props.emissiveFactor = glm::vec3( - static_cast(ef[0]), - static_cast(ef[1]), - static_cast(ef[2]) - ); - - props.normalScale = static_cast(gltfMaterial.normalTexture.scale); - props.occlusionStrength = static_cast(gltfMaterial.occlusionTexture.strength); - - // Alpha mode - if (gltfMaterial.alphaMode == "OPAQUE") { - props.alphaMode = MaterialProperties::AlphaMode::Opaque; - } else if (gltfMaterial.alphaMode == "MASK") { - props.alphaMode = MaterialProperties::AlphaMode::Mask; - } else if (gltfMaterial.alphaMode == "BLEND") { - props.alphaMode = MaterialProperties::AlphaMode::Blend; - } - - props.alphaCutoff = static_cast(gltfMaterial.alphaCutoff); - props.doubleSided = gltfMaterial.doubleSided; - - return material; -} - -std::shared_ptr GLTFLoader::processTexture(const tinygltf::Model& gltfModel, - int textureIndex, - TextureType type, - bool sRGB) { - if (!options.loadTextures) { - return textureCache.getDefaultTexture(type); - } - - if (textureIndex < 0 || textureIndex >= static_cast(gltfModel.textures.size())) { - return textureCache.getDefaultTexture(type); - } - - const auto& gltfTexture = gltfModel.textures[textureIndex]; - - if (gltfTexture.source < 0) { - return textureCache.getDefaultTexture(type); - } - - return processImage(gltfModel, gltfTexture.source, type, sRGB); -} - -std::shared_ptr GLTFLoader::processImage(const tinygltf::Model& gltfModel, - int imageIndex, - TextureType type, - bool sRGB) { - if (imageIndex < 0 || imageIndex >= static_cast(gltfModel.images.size())) { - return textureCache.getDefaultTexture(type); - } - - const auto& gltfImage = gltfModel.images[imageIndex]; - - if (!gltfImage.uri.empty()) { - // External file - std::filesystem::path imagePath = std::filesystem::path(baseDirectory) / gltfImage.uri; - return textureCache.getTexture(imagePath.string(), type, sRGB); - } else if (!gltfImage.image.empty()) { - // Embedded image - return textureCache.getEmbeddedTexture( - gltfImage.image, - gltfImage.width, - gltfImage.height, - gltfImage.component, - type, - sRGB - ); - } - - return textureCache.getDefaultTexture(type); -} - -void GLTFLoader::parseLightsExtension(const tinygltf::Model& gltfModel) { - parsedLights.clear(); - - auto extIt = gltfModel.extensions.find("KHR_lights_punctual"); - if (extIt == gltfModel.extensions.end()) return; - - const auto& extValue = extIt->second; - if (!extValue.Has("lights") || !extValue.Get("lights").IsArray()) return; - - const auto& lightsArray = extValue.Get("lights"); - for (size_t i = 0; i < lightsArray.ArrayLen(); ++i) { - const auto& lightVal = lightsArray.Get(static_cast(i)); - LightInfo info{}; - - if (lightVal.Has("name") && lightVal.Get("name").IsString()) { - info.name = lightVal.Get("name").Get(); + // Generate missing attributes + bool hasNormals = primitive.attributes.find("NORMAL") != primitive.attributes.end(); + bool hasTangents = primitive.attributes.find("TANGENT") != primitive.attributes.end(); + + if (!hasNormals && options.generateNormals) { + mesh->generateNormals(); + } + + if (!hasTangents && options.generateTangents) { + mesh->generateTangents(); + } + + // Validate mesh + if (options.validateMeshes && !mesh->isValid()) { + return nullptr; + } + + return mesh; } - if (lightVal.Has("type") && lightVal.Get("type").IsString()) { - const auto& typeStr = lightVal.Get("type").Get(); - if (typeStr == "directional") info.type = LightInfo::Type::Directional; - else if (typeStr == "point") info.type = LightInfo::Type::Point; - else if (typeStr == "spot") info.type = LightInfo::Type::Spot; + void GLTFLoader::extractVertexAttribute(const tinygltf::Model& gltfModel, + const tinygltf::Primitive& primitive, + const std::string& attributeName, + std::vector& vertices, + int componentIndex) { + auto it = primitive.attributes.find(attributeName); + if (it == primitive.attributes.end()) { + return; + } + + int accessorIndex = it->second; + const auto& accessor = gltfModel.accessors[accessorIndex]; + const auto& bufferView = gltfModel.bufferViews[accessor.bufferView]; + const auto& buffer = gltfModel.buffers[bufferView.buffer]; + + const unsigned char* data = + buffer.data.data() + bufferView.byteOffset + accessor.byteOffset; + size_t stride = accessor.ByteStride(bufferView); + + for (size_t i = 0; i < accessor.count && i < vertices.size(); ++i) { + const unsigned char* ptr = data + i * stride; + + if (componentIndex == 0) { // POSITION + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT && + accessor.type == TINYGLTF_TYPE_VEC3) { + const float* fptr = reinterpret_cast(ptr); + vertices[i].position = glm::vec3(fptr[0], fptr[1], fptr[2]); + } + } else if (componentIndex == 1) { // NORMAL + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT && + accessor.type == TINYGLTF_TYPE_VEC3) { + const float* fptr = reinterpret_cast(ptr); + vertices[i].normal = glm::vec3(fptr[0], fptr[1], fptr[2]); + } + } else if (componentIndex == 2) { // TEXCOORD_0 + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT && + accessor.type == TINYGLTF_TYPE_VEC2) { + const float* fptr = reinterpret_cast(ptr); + vertices[i].texCoords = glm::vec2(fptr[0], fptr[1]); + } + } else if (componentIndex == 3) { // COLOR_0 + if (accessor.type == TINYGLTF_TYPE_VEC3) { + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + const float* fptr = reinterpret_cast(ptr); + vertices[i].color = glm::vec3(fptr[0], fptr[1], fptr[2]); + } + } else if (accessor.type == TINYGLTF_TYPE_VEC4) { + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + const float* fptr = reinterpret_cast(ptr); + vertices[i].color = glm::vec3(fptr[0], fptr[1], fptr[2]); + } + } + } else if (componentIndex == 4) { // TANGENT + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT && + accessor.type == TINYGLTF_TYPE_VEC4) { + const float* fptr = reinterpret_cast(ptr); + vertices[i].tangent = glm::vec4(fptr[0], fptr[1], fptr[2], fptr[3]); + } + } + } } - if (lightVal.Has("color") && lightVal.Get("color").IsArray()) { - const auto& c = lightVal.Get("color"); - if (c.ArrayLen() >= 3) { - info.color = glm::vec3( - static_cast(c.Get(0).IsNumber() ? c.Get(0).GetNumberAsDouble() : 1.0), - static_cast(c.Get(1).IsNumber() ? c.Get(1).GetNumberAsDouble() : 1.0), - static_cast(c.Get(2).IsNumber() ? c.Get(2).GetNumberAsDouble() : 1.0) - ); + void GLTFLoader::extractIndices(const tinygltf::Model& gltfModel, + const tinygltf::Primitive& primitive, + std::vector& indices) { + if (primitive.indices < 0) { + return; + } + + const auto& accessor = gltfModel.accessors[primitive.indices]; + const auto& bufferView = gltfModel.bufferViews[accessor.bufferView]; + const auto& buffer = gltfModel.buffers[bufferView.buffer]; + + const unsigned char* data = + buffer.data.data() + bufferView.byteOffset + accessor.byteOffset; + + indices.resize(accessor.count); + + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + const uint16_t* ptr = reinterpret_cast(data); + for (size_t i = 0; i < accessor.count; ++i) { + indices[i] = static_cast(ptr[i]); + } + } else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + const uint32_t* ptr = reinterpret_cast(data); + std::memcpy(indices.data(), ptr, accessor.count * sizeof(uint32_t)); + } else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + const uint8_t* ptr = reinterpret_cast(data); + for (size_t i = 0; i < accessor.count; ++i) { + indices[i] = static_cast(ptr[i]); + } } } - if (lightVal.Has("intensity") && lightVal.Get("intensity").IsNumber()) { - info.intensity = static_cast(lightVal.Get("intensity").GetNumberAsDouble()); + std::shared_ptr GLTFLoader::processMaterial(const tinygltf::Model& gltfModel, + int materialIndex) { + if (materialIndex < 0 || + materialIndex >= static_cast(gltfModel.materials.size())) { + return nullptr; + } + + const auto& gltfMaterial = gltfModel.materials[materialIndex]; + auto material = std::make_shared(gltfMaterial.name); + + // PBR metallic-roughness + if (gltfMaterial.pbrMetallicRoughness.baseColorTexture.index >= 0) { + auto texture = processTexture( + gltfModel, gltfMaterial.pbrMetallicRoughness.baseColorTexture.index, + TextureType::BaseColor, true); + if (texture) { + material->setTexture(TextureType::BaseColor, texture); + } + } + + if (gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) { + auto texture = processTexture( + gltfModel, gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture.index, + TextureType::MetallicRoughness, false); + if (texture) { + material->setTexture(TextureType::MetallicRoughness, texture); + } + } + + if (gltfMaterial.normalTexture.index >= 0) { + auto texture = processTexture(gltfModel, gltfMaterial.normalTexture.index, + TextureType::Normal, false); + if (texture) { + material->setTexture(TextureType::Normal, texture); + } + } + + if (gltfMaterial.occlusionTexture.index >= 0) { + auto texture = processTexture(gltfModel, gltfMaterial.occlusionTexture.index, + TextureType::Occlusion, false); + if (texture) { + material->setTexture(TextureType::Occlusion, texture); + } + } + + if (gltfMaterial.emissiveTexture.index >= 0) { + auto texture = processTexture(gltfModel, gltfMaterial.emissiveTexture.index, + TextureType::Emissive, true); + if (texture) { + material->setTexture(TextureType::Emissive, texture); + } + } + + // Material properties + auto& props = material->getProperties(); + + const auto& bc = gltfMaterial.pbrMetallicRoughness.baseColorFactor; + props.baseColorFactor = glm::vec4(static_cast(bc[0]), static_cast(bc[1]), + static_cast(bc[2]), static_cast(bc[3])); + + props.metallicFactor = + static_cast(gltfMaterial.pbrMetallicRoughness.metallicFactor); + props.roughnessFactor = + static_cast(gltfMaterial.pbrMetallicRoughness.roughnessFactor); + + const auto& ef = gltfMaterial.emissiveFactor; + props.emissiveFactor = glm::vec3(static_cast(ef[0]), static_cast(ef[1]), + static_cast(ef[2])); + + props.normalScale = static_cast(gltfMaterial.normalTexture.scale); + props.occlusionStrength = static_cast(gltfMaterial.occlusionTexture.strength); + + // Alpha mode + if (gltfMaterial.alphaMode == "OPAQUE") { + props.alphaMode = MaterialProperties::AlphaMode::Opaque; + } else if (gltfMaterial.alphaMode == "MASK") { + props.alphaMode = MaterialProperties::AlphaMode::Mask; + } else if (gltfMaterial.alphaMode == "BLEND") { + props.alphaMode = MaterialProperties::AlphaMode::Blend; + } + + props.alphaCutoff = static_cast(gltfMaterial.alphaCutoff); + props.doubleSided = gltfMaterial.doubleSided; + + return material; } - if (lightVal.Has("range") && lightVal.Get("range").IsNumber()) { - info.range = static_cast(lightVal.Get("range").GetNumberAsDouble()); + std::shared_ptr GLTFLoader::processTexture(const tinygltf::Model& gltfModel, + int textureIndex, TextureType type, + bool sRGB) { + if (!options.loadTextures) { + return textureCache.getDefaultTexture(type); + } + + if (textureIndex < 0 || textureIndex >= static_cast(gltfModel.textures.size())) { + return textureCache.getDefaultTexture(type); + } + + const auto& gltfTexture = gltfModel.textures[textureIndex]; + + if (gltfTexture.source < 0) { + return textureCache.getDefaultTexture(type); + } + + return processImage(gltfModel, gltfTexture.source, type, sRGB); } - if (lightVal.Has("spot") && lightVal.Get("spot").IsObject()) { - const auto& spot = lightVal.Get("spot"); - if (spot.Has("innerConeAngle") && spot.Get("innerConeAngle").IsNumber()) { - info.innerConeAngle = static_cast(spot.Get("innerConeAngle").GetNumberAsDouble()); + std::shared_ptr GLTFLoader::processImage(const tinygltf::Model& gltfModel, + int imageIndex, TextureType type, + bool sRGB) { + if (imageIndex < 0 || imageIndex >= static_cast(gltfModel.images.size())) { + return textureCache.getDefaultTexture(type); } - if (spot.Has("outerConeAngle") && spot.Get("outerConeAngle").IsNumber()) { - info.outerConeAngle = static_cast(spot.Get("outerConeAngle").GetNumberAsDouble()); + + const auto& gltfImage = gltfModel.images[imageIndex]; + + if (!gltfImage.uri.empty()) { + // External file + std::filesystem::path imagePath = + std::filesystem::path(baseDirectory) / gltfImage.uri; + return textureCache.getTexture(imagePath.string(), type, sRGB); + } else if (!gltfImage.image.empty()) { + // Embedded image + return textureCache.getEmbeddedTexture(gltfImage.image, gltfImage.width, + gltfImage.height, gltfImage.component, type, + sRGB); } + + return textureCache.getDefaultTexture(type); } - parsedLights.push_back(info); - } -} + void GLTFLoader::parseLightsExtension(const tinygltf::Model& gltfModel) { + parsedLights.clear(); -void GLTFLoader::applyNodeLight(const tinygltf::Node& gltfNode, std::shared_ptr node) { - auto extIt = gltfNode.extensions.find("KHR_lights_punctual"); - if (extIt == gltfNode.extensions.end()) return; + auto extIt = gltfModel.extensions.find("KHR_lights_punctual"); + if (extIt == gltfModel.extensions.end()) + return; - const auto& extValue = extIt->second; - if (!extValue.Has("light") || !extValue.Get("light").IsInt()) return; + const auto& extValue = extIt->second; + if (!extValue.Has("lights") || !extValue.Get("lights").IsArray()) + return; - int lightIndex = extValue.Get("light").GetNumberAsInt(); - if (lightIndex < 0 || lightIndex >= static_cast(parsedLights.size())) return; + const auto& lightsArray = extValue.Get("lights"); + for (size_t i = 0; i < lightsArray.ArrayLen(); ++i) { + const auto& lightVal = lightsArray.Get(static_cast(i)); + LightInfo info{}; + + if (lightVal.Has("name") && lightVal.Get("name").IsString()) { + info.name = lightVal.Get("name").Get(); + } + + if (lightVal.Has("type") && lightVal.Get("type").IsString()) { + const auto& typeStr = lightVal.Get("type").Get(); + if (typeStr == "directional") + info.type = LightInfo::Type::Directional; + else if (typeStr == "point") + info.type = LightInfo::Type::Point; + else if (typeStr == "spot") + info.type = LightInfo::Type::Spot; + } - node->setLightInfo(parsedLights[lightIndex]); -} + if (lightVal.Has("color") && lightVal.Get("color").IsArray()) { + const auto& c = lightVal.Get("color"); + if (c.ArrayLen() >= 3) { + info.color = glm::vec3( + static_cast(c.Get(0).IsNumber() ? c.Get(0).GetNumberAsDouble() + : 1.0), + static_cast(c.Get(1).IsNumber() ? c.Get(1).GetNumberAsDouble() + : 1.0), + static_cast(c.Get(2).IsNumber() ? c.Get(2).GetNumberAsDouble() + : 1.0)); + } + } + + if (lightVal.Has("intensity") && lightVal.Get("intensity").IsNumber()) { + info.intensity = + static_cast(lightVal.Get("intensity").GetNumberAsDouble()); + } + + if (lightVal.Has("range") && lightVal.Get("range").IsNumber()) { + info.range = static_cast(lightVal.Get("range").GetNumberAsDouble()); + } + + if (lightVal.Has("spot") && lightVal.Get("spot").IsObject()) { + const auto& spot = lightVal.Get("spot"); + if (spot.Has("innerConeAngle") && spot.Get("innerConeAngle").IsNumber()) { + info.innerConeAngle = + static_cast(spot.Get("innerConeAngle").GetNumberAsDouble()); + } + if (spot.Has("outerConeAngle") && spot.Get("outerConeAngle").IsNumber()) { + info.outerConeAngle = + static_cast(spot.Get("outerConeAngle").GetNumberAsDouble()); + } + } + + parsedLights.push_back(info); + } + } + + void GLTFLoader::applyNodeLight(const tinygltf::Node& gltfNode, + std::shared_ptr node) { + auto extIt = gltfNode.extensions.find("KHR_lights_punctual"); + if (extIt == gltfNode.extensions.end()) + return; + + const auto& extValue = extIt->second; + if (!extValue.Has("light") || !extValue.Get("light").IsInt()) + return; + + int lightIndex = extValue.Get("light").GetNumberAsInt(); + if (lightIndex < 0 || lightIndex >= static_cast(parsedLights.size())) + return; + + node->setLightInfo(parsedLights[lightIndex]); + } -} // namespace modeling + } // namespace modeling } // namespace sauce diff --git a/src/app/modeling/Material.cpp b/src/app/modeling/Material.cpp index dd3b6daa..1ca67ded 100644 --- a/src/app/modeling/Material.cpp +++ b/src/app/modeling/Material.cpp @@ -1,206 +1,187 @@ #include "app/modeling/Material.hpp" -#include #include +#include #include #include namespace sauce { -namespace modeling { - -std::unique_ptr Material::descriptorSetLayout = nullptr; - -Material::Material(const std::string& name) - : name(name) { -} - -std::shared_ptr Material::getTexture(TextureType type) const { - auto it = textures.find(type); - if (it != textures.end()) { - return it->second; - } - return nullptr; -} - -void Material::setTexture(TextureType type, std::shared_ptr texture) { - textures[type] = texture; -} - -bool Material::hasTexture(TextureType type) const { - return textures.find(type) != textures.end(); -} - -void Material::setMetadata(const std::string& key, const PropertyValue& value) { - metadata[key] = value; -} - -bool Material::hasMetadata(const std::string& key) const { - return metadata.find(key) != metadata.end(); -} - -// --- Vulkan resource management --- - -void Material::initDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice) { - if (descriptorSetLayout) return; - - std::array materialBindings; - // Albedo, Normal, MetallicRoughness, Emissive, AO textures - for (uint32_t i = 0; i < 5; ++i) { - materialBindings[i] = { - .binding = i, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eFragment - }; - } - // Material Properties UBO - materialBindings[5] = { - .binding = 5, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eFragment - }; - - vk::DescriptorSetLayoutCreateInfo materialDsLayoutInfo { - .bindingCount = static_cast(materialBindings.size()), - .pBindings = materialBindings.data(), - }; - descriptorSetLayout = std::make_unique(*logicalDevice, materialDsLayoutInfo); -} - -const vk::raii::DescriptorSetLayout& Material::getDescriptorSetLayout() { - if (!descriptorSetLayout) { - throw std::runtime_error("Material descriptor set layout not initialized!"); - } - return *descriptorSetLayout; -} - -void Material::cleanup() { - descriptorSetLayout.reset(); -} - -void Material::updateUniformBuffer(const sauce::LogicalDevice& logicalDevice) const { - if (!uniformBufferMemory) return; - - MaterialUBO ubo{}; - ubo.baseColorFactor = properties.baseColorFactor; - ubo.metallicFactor = properties.metallicFactor; - ubo.roughnessFactor = properties.roughnessFactor; - ubo.normalScale = properties.normalScale; - ubo.occlusionStrength = properties.occlusionStrength; - ubo.emissiveFactor_alphaCutoff = glm::vec4(properties.emissiveFactor, properties.alphaCutoff); - - void* data = uniformBufferMemory->mapMemory(0, sizeof(MaterialUBO)); - std::memcpy(data, &ubo, sizeof(MaterialUBO)); - uniformBufferMemory->unmapMemory(); -} - -void Material::initVulkanResources( - const sauce::LogicalDevice& logicalDevice, - vk::raii::PhysicalDevice& physicalDevice, - vk::raii::CommandPool& commandPool, - vk::raii::Queue& queue, - const vk::raii::DescriptorPool& pool, - const vk::raii::ImageView& defaultView, - const vk::raii::Sampler& defaultSampler -) { - // 1. Initialize Vulkan resources on all child Textures - for (auto& [type, texture] : textures) { - if (texture) { - texture->initVulkanResources(logicalDevice, physicalDevice, commandPool, queue); + namespace modeling { + + std::unique_ptr Material::descriptorSetLayout = nullptr; + + Material::Material(const std::string& name) : name(name) { + } + + std::shared_ptr Material::getTexture(TextureType type) const { + auto it = textures.find(type); + if (it != textures.end()) { + return it->second; + } + return nullptr; + } + + void Material::setTexture(TextureType type, std::shared_ptr texture) { + textures[type] = texture; + } + + bool Material::hasTexture(TextureType type) const { + return textures.find(type) != textures.end(); + } + + void Material::setMetadata(const std::string& key, const PropertyValue& value) { + metadata[key] = value; + } + + bool Material::hasMetadata(const std::string& key) const { + return metadata.find(key) != metadata.end(); } - } - - // 2. Create the uniform buffer for material constants - uniformBuffer = std::make_unique(nullptr); - uniformBufferMemory = std::make_unique(nullptr); - - sauce::BufferUtils::createBuffer( - physicalDevice, logicalDevice, - sizeof(MaterialUBO), - vk::BufferUsageFlagBits::eUniformBuffer, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - *uniformBuffer, *uniformBufferMemory - ); - - // 3. Upload initial material data - updateUniformBuffer(logicalDevice); - - // 4. Allocate descriptor set - vk::DescriptorSetLayout layoutHandle = *getDescriptorSetLayout(); - vk::DescriptorSetAllocateInfo allocInfo { - .descriptorPool = *pool, - .descriptorSetCount = 1, - .pSetLayouts = &layoutHandle, - }; - - auto sets = logicalDevice->allocateDescriptorSets(allocInfo); - descriptorSet = std::make_unique(std::move(sets[0])); - - // 5. Update descriptor set - auto imageInfos = getDescriptorImageInfos(defaultView, defaultSampler); - auto bufferInfos = getDescriptorBufferInfos(); - - std::vector writes; - for (uint32_t i = 0; i < 5; ++i) { - writes.push_back({ - .dstSet = **descriptorSet, - .dstBinding = i, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfos[i] - }); - } - writes.push_back({ - .dstSet = **descriptorSet, - .dstBinding = 5, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfos[0] - }); - - logicalDevice->updateDescriptorSets(writes, {}); -} - -std::vector Material::getDescriptorBufferInfos() const { - std::vector infos; - if (uniformBuffer) { - infos.push_back(vk::DescriptorBufferInfo{ - .buffer = **uniformBuffer, - .offset = 0, - .range = sizeof(MaterialUBO) - }); - } - return infos; -} - -std::vector Material::getDescriptorImageInfos( - const vk::raii::ImageView& defaultView, - const vk::raii::Sampler& defaultSampler) const -{ - std::vector infos; - // Order: Albedo, Normal, MetallicRoughness, Emissive, AO - std::array types = { - TextureType::BaseColor, - TextureType::Normal, - TextureType::MetallicRoughness, - TextureType::Emissive, - TextureType::Occlusion - }; - - for (auto type : types) { - auto it = textures.find(type); - if (it != textures.end() && it->second && it->second->hasGPUData()) { - infos.push_back(it->second->getDescriptorInfo()); - } else { - infos.push_back(vk::DescriptorImageInfo{ - .sampler = *defaultSampler, - .imageView = *defaultView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }); + + // --- Vulkan resource management --- + + void Material::initDescriptorSetLayout(const sauce::LogicalDevice& logicalDevice) { + if (descriptorSetLayout) + return; + + std::array materialBindings; + // Albedo, Normal, MetallicRoughness, Emissive, AO textures + for (uint32_t i = 0; i < 5; ++i) { + materialBindings[i] = {.binding = i, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + } + // Material Properties UBO + materialBindings[5] = {.binding = 5, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + + vk::DescriptorSetLayoutCreateInfo materialDsLayoutInfo{ + .bindingCount = static_cast(materialBindings.size()), + .pBindings = materialBindings.data(), + }; + descriptorSetLayout = std::make_unique( + *logicalDevice, materialDsLayoutInfo); + } + + const vk::raii::DescriptorSetLayout& Material::getDescriptorSetLayout() { + if (!descriptorSetLayout) { + throw std::runtime_error("Material descriptor set layout not initialized!"); + } + return *descriptorSetLayout; + } + + void Material::cleanup() { + descriptorSetLayout.reset(); + } + + void Material::updateUniformBuffer(const sauce::LogicalDevice& logicalDevice) const { + if (!uniformBufferMemory) + return; + + MaterialUBO ubo{}; + ubo.baseColorFactor = properties.baseColorFactor; + ubo.metallicFactor = properties.metallicFactor; + ubo.roughnessFactor = properties.roughnessFactor; + ubo.normalScale = properties.normalScale; + ubo.occlusionStrength = properties.occlusionStrength; + ubo.emissiveFactor_alphaCutoff = + glm::vec4(properties.emissiveFactor, properties.alphaCutoff); + + void* data = uniformBufferMemory->mapMemory(0, sizeof(MaterialUBO)); + std::memcpy(data, &ubo, sizeof(MaterialUBO)); + uniformBufferMemory->unmapMemory(); + } + + void Material::initVulkanResources(const sauce::LogicalDevice& logicalDevice, + vk::raii::PhysicalDevice& physicalDevice, + vk::raii::CommandPool& commandPool, + vk::raii::Queue& queue, + const vk::raii::DescriptorPool& pool, + const vk::raii::ImageView& defaultView, + const vk::raii::Sampler& defaultSampler) { + // 1. Initialize Vulkan resources on all child Textures + for (auto& [type, texture] : textures) { + if (texture) { + texture->initVulkanResources(logicalDevice, physicalDevice, commandPool, queue); + } + } + + // 2. Create the uniform buffer for material constants + uniformBuffer = std::make_unique(nullptr); + uniformBufferMemory = std::make_unique(nullptr); + + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, sizeof(MaterialUBO), + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + *uniformBuffer, *uniformBufferMemory); + + // 3. Upload initial material data + updateUniformBuffer(logicalDevice); + + // 4. Allocate descriptor set + vk::DescriptorSetLayout layoutHandle = *getDescriptorSetLayout(); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *pool, + .descriptorSetCount = 1, + .pSetLayouts = &layoutHandle, + }; + + auto sets = logicalDevice->allocateDescriptorSets(allocInfo); + descriptorSet = std::make_unique(std::move(sets[0])); + + // 5. Update descriptor set + auto imageInfos = getDescriptorImageInfos(defaultView, defaultSampler); + auto bufferInfos = getDescriptorBufferInfos(); + + std::vector writes; + for (uint32_t i = 0; i < 5; ++i) { + writes.push_back({.dstSet = **descriptorSet, + .dstBinding = i, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfos[i]}); + } + writes.push_back({.dstSet = **descriptorSet, + .dstBinding = 5, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfos[0]}); + + logicalDevice->updateDescriptorSets(writes, {}); + } + + std::vector Material::getDescriptorBufferInfos() const { + std::vector infos; + if (uniformBuffer) { + infos.push_back(vk::DescriptorBufferInfo{ + .buffer = **uniformBuffer, .offset = 0, .range = sizeof(MaterialUBO)}); + } + return infos; + } + + std::vector Material::getDescriptorImageInfos( + const vk::raii::ImageView& defaultView, const vk::raii::Sampler& defaultSampler) const { + std::vector infos; + // Order: Albedo, Normal, MetallicRoughness, Emissive, AO + std::array types = {TextureType::BaseColor, TextureType::Normal, + TextureType::MetallicRoughness, + TextureType::Emissive, TextureType::Occlusion}; + + for (auto type : types) { + auto it = textures.find(type); + if (it != textures.end() && it->second && it->second->hasGPUData()) { + infos.push_back(it->second->getDescriptorInfo()); + } else { + infos.push_back(vk::DescriptorImageInfo{ + .sampler = *defaultSampler, + .imageView = *defaultView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}); + } + } + return infos; } - } - return infos; -} -} // namespace modeling + } // namespace modeling } // namespace sauce diff --git a/src/app/modeling/Mesh.cpp b/src/app/modeling/Mesh.cpp index 0a749ceb..6e2e3a12 100644 --- a/src/app/modeling/Mesh.cpp +++ b/src/app/modeling/Mesh.cpp @@ -1,225 +1,242 @@ #include "app/modeling/Mesh.hpp" -#include #include "app/BufferUtils.hpp" #include "app/LogicalDevice.hpp" #include +#include namespace sauce { -namespace modeling { - -Mesh::Mesh(const std::vector& vertices, const std::vector& indices) - : vertices(vertices) - , indices(indices) { -} - -bool Mesh::isValid() const { - if (vertices.empty()) { - return false; - } - - if (indices.empty()) { - return false; - } - - // Check that all indices are in bounds - for (const auto& index : indices) { - if (index >= vertices.size()) { - return false; - } - } - - // Check that indices count is a multiple of 3 (triangles) - if (indices.size() % 3 != 0) { - // TODO: Log warning or handle invalid index count - } + namespace modeling { - return true; -} - -void Mesh::setMetadata(const std::string& key, const PropertyValue& value) { - metadata[key] = value; -} + Mesh::Mesh(const std::vector& vertices, const std::vector& indices) + : vertices(vertices), indices(indices) { + } -bool Mesh::hasMetadata(const std::string& key) const { - return metadata.find(key) != metadata.end(); -} + bool Mesh::isValid() const { + if (vertices.empty()) { + return false; + } -void Mesh::initVulkanResources(const sauce::LogicalDevice& logicalDevice, vk::raii::PhysicalDevice& physicalDevice, vk::raii::CommandPool& commandPool, vk::raii::Queue& queue) { - vk::DeviceSize vertexBufferSize = sizeof(vertices[0]) * vertices.size(); + if (indices.empty()) { + return false; + } - vk::raii::Buffer stagingVertexBuffer(nullptr); - vk::raii::DeviceMemory stagingVertexBufferMemory(nullptr); - sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, vertexBufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingVertexBuffer, stagingVertexBufferMemory); + // Check that all indices are in bounds + for (const auto& index : indices) { + if (index >= vertices.size()) { + return false; + } + } - void* data = stagingVertexBufferMemory.mapMemory(0, vertexBufferSize); - memcpy(data, vertices.data(), (size_t)vertexBufferSize); - stagingVertexBufferMemory.unmapMemory(); + // Check that indices count is a multiple of 3 (triangles) + if (indices.size() % 3 != 0) { + // TODO: Log warning or handle invalid index count + } - vertexBuffer = std::make_unique(nullptr); - vertexBufferMemory = std::make_unique(nullptr); - sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, vertexBufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, *vertexBuffer, *vertexBufferMemory); + return true; + } - sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, queue, stagingVertexBuffer, *vertexBuffer, vertexBufferSize); + void Mesh::setMetadata(const std::string& key, const PropertyValue& value) { + metadata[key] = value; + } - vk::DeviceSize indexBufferSize = sizeof(indices[0]) * indices.size(); + bool Mesh::hasMetadata(const std::string& key) const { + return metadata.find(key) != metadata.end(); + } - vk::raii::Buffer stagingIndexBuffer(nullptr); - vk::raii::DeviceMemory stagingIndexBufferMemory(nullptr); - sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, indexBufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingIndexBuffer, stagingIndexBufferMemory); + void Mesh::initVulkanResources(const sauce::LogicalDevice& logicalDevice, + vk::raii::PhysicalDevice& physicalDevice, + vk::raii::CommandPool& commandPool, vk::raii::Queue& queue) { + vk::DeviceSize vertexBufferSize = sizeof(vertices[0]) * vertices.size(); + + vk::raii::Buffer stagingVertexBuffer(nullptr); + vk::raii::DeviceMemory stagingVertexBufferMemory(nullptr); + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, vertexBufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + stagingVertexBuffer, stagingVertexBufferMemory); + + void* data = stagingVertexBufferMemory.mapMemory(0, vertexBufferSize); + memcpy(data, vertices.data(), (size_t)vertexBufferSize); + stagingVertexBufferMemory.unmapMemory(); + + vertexBuffer = std::make_unique(nullptr); + vertexBufferMemory = std::make_unique(nullptr); + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, vertexBufferSize, + vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal, *vertexBuffer, *vertexBufferMemory); + + sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, queue, stagingVertexBuffer, + *vertexBuffer, vertexBufferSize); + + vk::DeviceSize indexBufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingIndexBuffer(nullptr); + vk::raii::DeviceMemory stagingIndexBufferMemory(nullptr); + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, indexBufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + stagingIndexBuffer, stagingIndexBufferMemory); + + data = stagingIndexBufferMemory.mapMemory(0, indexBufferSize); + memcpy(data, indices.data(), (size_t)indexBufferSize); + stagingIndexBufferMemory.unmapMemory(); + + indexBuffer = std::make_unique(nullptr); + indexBufferMemory = std::make_unique(nullptr); + sauce::BufferUtils::createBuffer( + physicalDevice, logicalDevice, indexBufferSize, + vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal, *indexBuffer, *indexBufferMemory); + + sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, queue, stagingIndexBuffer, + *indexBuffer, indexBufferSize); + } - data = stagingIndexBufferMemory.mapMemory(0, indexBufferSize); - memcpy(data, indices.data(), (size_t)indexBufferSize); - stagingIndexBufferMemory.unmapMemory(); + void Mesh::bind(vk::raii::CommandBuffer& commandBuffer) { + vk::Buffer vertexBuffers[] = {**vertexBuffer}; + commandBuffer.bindVertexBuffers(0, *vertexBuffers, {0}); + commandBuffer.bindIndexBuffer(**indexBuffer, 0, vk::IndexType::eUint32); + } - indexBuffer = std::make_unique(nullptr); - indexBufferMemory = std::make_unique(nullptr); - sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, indexBufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, *indexBuffer, *indexBufferMemory); + void Mesh::draw(vk::raii::CommandBuffer& commandBuffer) { + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + } - sauce::BufferUtils::copyBuffer(logicalDevice, commandPool, queue, stagingIndexBuffer, *indexBuffer, indexBufferSize); -} + void Mesh::generateNormals() { + if (indices.size() % 3 != 0) { + return; + } -void Mesh::bind(vk::raii::CommandBuffer& commandBuffer) { - vk::Buffer vertexBuffers[] = { **vertexBuffer }; - commandBuffer.bindVertexBuffers(0, *vertexBuffers, {0}); - commandBuffer.bindIndexBuffer(**indexBuffer, 0, vk::IndexType::eUint32); -} + // Reset all normals to zero + for (auto& vertex : vertices) { + vertex.normal = glm::vec3(0.0f); + } -void Mesh::draw(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); -} + // Accumulate face normals + for (size_t i = 0; i < indices.size(); i += 3) { + uint32_t i0 = indices[i]; + uint32_t i1 = indices[i + 1]; + uint32_t i2 = indices[i + 2]; -void Mesh::generateNormals() { - if (indices.size() % 3 != 0) { - return; - } + if (i0 >= vertices.size() || i1 >= vertices.size() || i2 >= vertices.size()) { + continue; + } - // Reset all normals to zero - for (auto& vertex : vertices) { - vertex.normal = glm::vec3(0.0f); - } + const glm::vec3& p0 = vertices[i0].position; + const glm::vec3& p1 = vertices[i1].position; + const glm::vec3& p2 = vertices[i2].position; - // Accumulate face normals - for (size_t i = 0; i < indices.size(); i += 3) { - uint32_t i0 = indices[i]; - uint32_t i1 = indices[i + 1]; - uint32_t i2 = indices[i + 2]; + glm::vec3 edge1 = p1 - p0; + glm::vec3 edge2 = p2 - p0; + glm::vec3 faceNormal = glm::cross(edge1, edge2); - if (i0 >= vertices.size() || i1 >= vertices.size() || i2 >= vertices.size()) { - continue; - } + // Accumulate (weighted by triangle area, which is proportional to length of cross product) + vertices[i0].normal += faceNormal; + vertices[i1].normal += faceNormal; + vertices[i2].normal += faceNormal; + } - const glm::vec3& p0 = vertices[i0].position; - const glm::vec3& p1 = vertices[i1].position; - const glm::vec3& p2 = vertices[i2].position; - - glm::vec3 edge1 = p1 - p0; - glm::vec3 edge2 = p2 - p0; - glm::vec3 faceNormal = glm::cross(edge1, edge2); - - // Accumulate (weighted by triangle area, which is proportional to length of cross product) - vertices[i0].normal += faceNormal; - vertices[i1].normal += faceNormal; - vertices[i2].normal += faceNormal; - } - - // Normalize all normals - for (auto& vertex : vertices) { - float length = glm::length(vertex.normal); - if (length > 0.0001f) { - vertex.normal = glm::normalize(vertex.normal); - } else { - // Fallback to up vector - vertex.normal = glm::vec3(0.0f, 1.0f, 0.0f); - } - } -} - -void Mesh::generateTangents() { - if (indices.size() % 3 != 0) { - return; - } - - // Reset all tangents to zero - std::vector tangents(vertices.size(), glm::vec3(0.0f)); - std::vector bitangents(vertices.size(), glm::vec3(0.0f)); - - // Accumulate tangents and bitangents - for (size_t i = 0; i < indices.size(); i += 3) { - uint32_t i0 = indices[i]; - uint32_t i1 = indices[i + 1]; - uint32_t i2 = indices[i + 2]; - - if (i0 >= vertices.size() || i1 >= vertices.size() || i2 >= vertices.size()) { - continue; + // Normalize all normals + for (auto& vertex : vertices) { + float length = glm::length(vertex.normal); + if (length > 0.0001f) { + vertex.normal = glm::normalize(vertex.normal); + } else { + // Fallback to up vector + vertex.normal = glm::vec3(0.0f, 1.0f, 0.0f); + } + } } - const glm::vec3& p0 = vertices[i0].position; - const glm::vec3& p1 = vertices[i1].position; - const glm::vec3& p2 = vertices[i2].position; - - const glm::vec2& uv0 = vertices[i0].texCoords; - const glm::vec2& uv1 = vertices[i1].texCoords; - const glm::vec2& uv2 = vertices[i2].texCoords; - - glm::vec3 edge1 = p1 - p0; - glm::vec3 edge2 = p2 - p0; - glm::vec2 deltaUV1 = uv1 - uv0; - glm::vec2 deltaUV2 = uv2 - uv0; + void Mesh::generateTangents() { + if (indices.size() % 3 != 0) { + return; + } - float denom = deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y; - if (std::abs(denom) < 0.0001f) { - // Degenerate UV, skip this triangle - continue; - } + // Reset all tangents to zero + std::vector tangents(vertices.size(), glm::vec3(0.0f)); + std::vector bitangents(vertices.size(), glm::vec3(0.0f)); + + // Accumulate tangents and bitangents + for (size_t i = 0; i < indices.size(); i += 3) { + uint32_t i0 = indices[i]; + uint32_t i1 = indices[i + 1]; + uint32_t i2 = indices[i + 2]; + + if (i0 >= vertices.size() || i1 >= vertices.size() || i2 >= vertices.size()) { + continue; + } + + const glm::vec3& p0 = vertices[i0].position; + const glm::vec3& p1 = vertices[i1].position; + const glm::vec3& p2 = vertices[i2].position; + + const glm::vec2& uv0 = vertices[i0].texCoords; + const glm::vec2& uv1 = vertices[i1].texCoords; + const glm::vec2& uv2 = vertices[i2].texCoords; + + glm::vec3 edge1 = p1 - p0; + glm::vec3 edge2 = p2 - p0; + glm::vec2 deltaUV1 = uv1 - uv0; + glm::vec2 deltaUV2 = uv2 - uv0; + + float denom = deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y; + if (std::abs(denom) < 0.0001f) { + // Degenerate UV, skip this triangle + continue; + } + + float f = 1.0f / denom; + + glm::vec3 tangent; + tangent.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x); + tangent.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y); + tangent.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z); + + glm::vec3 bitangent; + bitangent.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x); + bitangent.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y); + bitangent.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z); + + tangents[i0] += tangent; + tangents[i1] += tangent; + tangents[i2] += tangent; + + bitangents[i0] += bitangent; + bitangents[i1] += bitangent; + bitangents[i2] += bitangent; + } - float f = 1.0f / denom; - - glm::vec3 tangent; - tangent.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x); - tangent.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y); - tangent.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z); - - glm::vec3 bitangent; - bitangent.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x); - bitangent.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y); - bitangent.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z); - - tangents[i0] += tangent; - tangents[i1] += tangent; - tangents[i2] += tangent; - - bitangents[i0] += bitangent; - bitangents[i1] += bitangent; - bitangents[i2] += bitangent; - } - - // Orthogonalize and normalize tangents - for (size_t i = 0; i < vertices.size(); ++i) { - const glm::vec3& n = vertices[i].normal; - const glm::vec3& t = tangents[i]; - const glm::vec3& b = bitangents[i]; - - // Gram-Schmidt orthogonalize - glm::vec3 tangent = t - n * glm::dot(n, t); - - float tangentLength = glm::length(tangent); - if (tangentLength > 0.0001f) { - tangent = glm::normalize(tangent); - } else { - // Fallback to arbitrary perpendicular vector - if (std::abs(n.x) > 0.9f) { - tangent = glm::normalize(glm::cross(n, glm::vec3(0.0f, 1.0f, 0.0f))); - } else { - tangent = glm::normalize(glm::cross(n, glm::vec3(1.0f, 0.0f, 0.0f))); + // Orthogonalize and normalize tangents + for (size_t i = 0; i < vertices.size(); ++i) { + const glm::vec3& n = vertices[i].normal; + const glm::vec3& t = tangents[i]; + const glm::vec3& b = bitangents[i]; + + // Gram-Schmidt orthogonalize + glm::vec3 tangent = t - n * glm::dot(n, t); + + float tangentLength = glm::length(tangent); + if (tangentLength > 0.0001f) { + tangent = glm::normalize(tangent); + } else { + // Fallback to arbitrary perpendicular vector + if (std::abs(n.x) > 0.9f) { + tangent = glm::normalize(glm::cross(n, glm::vec3(0.0f, 1.0f, 0.0f))); + } else { + tangent = glm::normalize(glm::cross(n, glm::vec3(1.0f, 0.0f, 0.0f))); + } + } + + // Calculate handedness + float handedness = (glm::dot(glm::cross(n, tangent), b) < 0.0f) ? -1.0f : 1.0f; + + vertices[i].tangent = glm::vec4(tangent, handedness); } } - // Calculate handedness - float handedness = (glm::dot(glm::cross(n, tangent), b) < 0.0f) ? -1.0f : 1.0f; - - vertices[i].tangent = glm::vec4(tangent, handedness); - } -} - -} // namespace modeling + } // namespace modeling } // namespace sauce diff --git a/src/app/modeling/Model.cpp b/src/app/modeling/Model.cpp index f2bdcfd9..500d7f6d 100644 --- a/src/app/modeling/Model.cpp +++ b/src/app/modeling/Model.cpp @@ -2,67 +2,67 @@ #include namespace sauce { -namespace modeling { + namespace modeling { -std::vector Model::getAllMeshMaterialPairs() const { - std::vector allPairs; + std::vector Model::getAllMeshMaterialPairs() const { + std::vector allPairs; - if (rootNode) { - collectPairsFromNode(rootNode, allPairs); - } - - return allPairs; -} + if (rootNode) { + collectPairsFromNode(rootNode, allPairs); + } + return allPairs; + } -void Model::traverseNode(std::shared_ptr node) { - if (!node) { - return; - } + void Model::traverseNode(std::shared_ptr node) { + if (!node) { + return; + } - // Use sets to avoid duplicates - static std::unordered_set> meshSet; - static std::unordered_set> materialSet; + // Use sets to avoid duplicates + static std::unordered_set> meshSet; + static std::unordered_set> materialSet; - // Add meshes and materials from this node - for (const auto& pair : node->getMeshMaterialPairs()) { - if (pair.mesh && meshSet.find(pair.mesh) == meshSet.end()) { - allMeshes.push_back(pair.mesh); - meshSet.insert(pair.mesh); - } - if (pair.material && materialSet.find(pair.material) == materialSet.end()) { - allMaterials.push_back(pair.material); - materialSet.insert(pair.material); - } - } + // Add meshes and materials from this node + for (const auto& pair : node->getMeshMaterialPairs()) { + if (pair.mesh && meshSet.find(pair.mesh) == meshSet.end()) { + allMeshes.push_back(pair.mesh); + meshSet.insert(pair.mesh); + } + if (pair.material && materialSet.find(pair.material) == materialSet.end()) { + allMaterials.push_back(pair.material); + materialSet.insert(pair.material); + } + } - // Traverse children - for (const auto& child : node->getChildren()) { - traverseNode(child); - } + // Traverse children + for (const auto& child : node->getChildren()) { + traverseNode(child); + } - // Clear static sets after complete traversal - if (node == rootNode) { - meshSet.clear(); - materialSet.clear(); - } -} + // Clear static sets after complete traversal + if (node == rootNode) { + meshSet.clear(); + materialSet.clear(); + } + } -void Model::collectPairsFromNode(std::shared_ptr node, std::vector& pairs) const { - if (!node) { - return; - } + void Model::collectPairsFromNode(std::shared_ptr node, + std::vector& pairs) const { + if (!node) { + return; + } - // Add all pairs from this node - for (const auto& pair : node->getMeshMaterialPairs()) { - pairs.push_back(pair); - } + // Add all pairs from this node + for (const auto& pair : node->getMeshMaterialPairs()) { + pairs.push_back(pair); + } - // Traverse children - for (const auto& child : node->getChildren()) { - collectPairsFromNode(child, pairs); - } -} + // Traverse children + for (const auto& child : node->getChildren()) { + collectPairsFromNode(child, pairs); + } + } -} // namespace modeling + } // namespace modeling } // namespace sauce diff --git a/src/app/modeling/ModelNode.cpp b/src/app/modeling/ModelNode.cpp index 1311ddc8..8ac0719e 100644 --- a/src/app/modeling/ModelNode.cpp +++ b/src/app/modeling/ModelNode.cpp @@ -1,49 +1,48 @@ #include "app/modeling/ModelNode.hpp" namespace sauce { -namespace modeling { - -ModelNode::ModelNode(const std::string& name) - : name(name) - , parent(nullptr) { -} - -void ModelNode::addChild(std::shared_ptr child) { - if (child) { - children.push_back(child); - child->setParent(this); - } -} - -void ModelNode::removeChild(std::shared_ptr child) { - auto it = std::find(children.begin(), children.end(), child); - if (it != children.end()) { - (*it)->setParent(nullptr); - children.erase(it); - } -} - -void ModelNode::addMeshMaterialPair(std::shared_ptr mesh, std::shared_ptr material) { - meshMaterialPairs.push_back({mesh, material}); -} - -void ModelNode::setMetadata(const std::string& key, const PropertyValue& value) { - metadata[key] = value; -} - -bool ModelNode::hasMetadata(const std::string& key) const { - return metadata.find(key) != metadata.end(); -} - -glm::mat4 ModelNode::getWorldMatrix() const { - glm::mat4 localMatrix = transform.getLocalMatrix(); - - if (parent) { - return parent->getWorldMatrix() * localMatrix; - } - - return localMatrix; -} - -} // namespace modeling + namespace modeling { + + ModelNode::ModelNode(const std::string& name) : name(name), parent(nullptr) { + } + + void ModelNode::addChild(std::shared_ptr child) { + if (child) { + children.push_back(child); + child->setParent(this); + } + } + + void ModelNode::removeChild(std::shared_ptr child) { + auto it = std::find(children.begin(), children.end(), child); + if (it != children.end()) { + (*it)->setParent(nullptr); + children.erase(it); + } + } + + void ModelNode::addMeshMaterialPair(std::shared_ptr mesh, + std::shared_ptr material) { + meshMaterialPairs.push_back({mesh, material}); + } + + void ModelNode::setMetadata(const std::string& key, const PropertyValue& value) { + metadata[key] = value; + } + + bool ModelNode::hasMetadata(const std::string& key) const { + return metadata.find(key) != metadata.end(); + } + + glm::mat4 ModelNode::getWorldMatrix() const { + glm::mat4 localMatrix = transform.getLocalMatrix(); + + if (parent) { + return parent->getWorldMatrix() * localMatrix; + } + + return localMatrix; + } + + } // namespace modeling } // namespace sauce diff --git a/src/app/modeling/Texture.cpp b/src/app/modeling/Texture.cpp index 3f1e9a9e..cf9fbd63 100644 --- a/src/app/modeling/Texture.cpp +++ b/src/app/modeling/Texture.cpp @@ -3,257 +3,226 @@ #define STB_IMAGE_IMPLEMENTATION #include -#include -#include #include #include +#include +#include namespace sauce { -namespace modeling { - -Texture::Texture(const std::string& path, TextureType type, bool sRGB) - : path(path) - , type(type) - , sRGB(sRGB) - , hdr(false) - , embedded(false) - , width(0) - , height(0) - , channels(0) - , dataLoaded(false) { -} - -Texture::Texture(const std::vector& data, int width, int height, int channels, - TextureType type, bool sRGB, const std::string& name) - : path(name) - , type(type) - , sRGB(sRGB) - , hdr(false) - , embedded(true) - , width(width) - , height(height) - , channels(channels) - , data(data) - , dataLoaded(true) { -} - -const std::vector& Texture::getData() { - if (!dataLoaded) { - loadFromFile(); - } - return data; -} - -const std::vector& Texture::getHDRData() { - if (!dataLoaded) { - loadFromFile(); - } - return hdrData; -} - -void Texture::loadFromFile() { - if (dataLoaded) { - return; - } - - if (embedded) { - return; - } - - if (stbi_is_hdr(path.c_str())) { - loadFromFileHDR(); - return; - } - - int w, h, c; - unsigned char* pixels = stbi_load(path.c_str(), &w, &h, &c, STBI_rgb_alpha); - - if (!pixels) { - createDefaultPixels(); - return; - } - - width = w; - height = h; - channels = 4; - - size_t dataSize = width * height * channels; - data.resize(dataSize); - std::memcpy(data.data(), pixels, dataSize); - - stbi_image_free(pixels); - dataLoaded = true; -} - -void Texture::loadFromFileHDR() { - int w, h, c; - float* pixels = stbi_loadf(path.c_str(), &w, &h, &c, STBI_rgb_alpha); - - if (!pixels) { - createDefaultPixels(); - return; - } - - width = w; - height = h; - channels = 4; - hdr = true; - - size_t pixelCount = static_cast(width) * height * channels; - hdrData.resize(pixelCount); - std::memcpy(hdrData.data(), pixels, pixelCount * sizeof(float)); - - stbi_image_free(pixels); - dataLoaded = true; -} - -void Texture::createDefaultPixels() { - width = 1; - height = 1; - channels = 4; - data.resize(4); - - if (type == TextureType::Normal) { - data[0] = 128; data[1] = 128; data[2] = 255; data[3] = 255; - } else if (type == TextureType::MetallicRoughness || type == TextureType::Occlusion) { - data[0] = 0; data[1] = 0; data[2] = 0; data[3] = 255; - } else { - data[0] = 255; data[1] = 255; data[2] = 255; data[3] = 255; - } - - dataLoaded = true; -} - -void Texture::initVulkanResources( - const sauce::LogicalDevice& logicalDevice, - vk::raii::PhysicalDevice& physicalDevice, - vk::raii::CommandPool& commandPool, - vk::raii::Queue& queue -) { - if (hasGPUData()) { - return; - } - - if (!dataLoaded) { - getData(); - } - - bool hasPixels = hdr ? !hdrData.empty() : !data.empty(); - if (!hasPixels || width == 0 || height == 0) { - throw std::runtime_error("No data for " + path); - } - - vk::DeviceSize bytesPerPixel = hdr ? (4 * sizeof(float)) : 4; - vk::DeviceSize imageSize = static_cast(width) * height * bytesPerPixel; - - vk::raii::Buffer stagingBuffer(nullptr); - vk::raii::DeviceMemory stagingMemory(nullptr); - - sauce::BufferUtils::createBuffer( - physicalDevice, logicalDevice, imageSize, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, stagingMemory - ); - - void* mappedData = stagingMemory.mapMemory(0, imageSize); - if (hdr) { - memcpy(mappedData, hdrData.data(), static_cast(imageSize)); - } else { - memcpy(mappedData, data.data(), static_cast(imageSize)); - } - stagingMemory.unmapMemory(); - - vk::Format imageFormat; - if (hdr) { - imageFormat = vk::Format::eR32G32B32A32Sfloat; - } else { - imageFormat = sRGB ? vk::Format::eR8G8B8A8Srgb : vk::Format::eR8G8B8A8Unorm; - } - - image = std::make_unique(nullptr); - imageMemory = std::make_unique(nullptr); - - sauce::ImageUtils::createImage( - physicalDevice, logicalDevice, - width, height, imageFormat, - vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, - *image, *imageMemory - ); - - sauce::ImageUtils::transitionImageLayout( - logicalDevice, commandPool, queue, *image, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eTransferDstOptimal, - {}, - vk::AccessFlagBits2::eTransferWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eTransfer - ); - - sauce::ImageUtils::copyBufferToImage( - logicalDevice, commandPool, queue, - stagingBuffer, *image, (uint32_t)width, (uint32_t)height - ); - - sauce::ImageUtils::transitionImageLayout( - logicalDevice, commandPool, queue, *image, - vk::ImageLayout::eTransferDstOptimal, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::AccessFlagBits2::eTransferWrite, - vk::AccessFlagBits2::eShaderRead, - vk::PipelineStageFlagBits2::eTransfer, - vk::PipelineStageFlagBits2::eFragmentShader - ); - - imageView = std::make_unique( - sauce::ImageUtils::createImageView( - logicalDevice, *image, imageFormat, - vk::ImageAspectFlagBits::eColor - ) - ); - - bool isEnvMap = (type == TextureType::EnvironmentMapHDR); - vk::SamplerAddressMode addressMode = isEnvMap - ? vk::SamplerAddressMode::eClampToEdge - : vk::SamplerAddressMode::eRepeat; - - vk::SamplerCreateInfo samplerInfo{}; - samplerInfo.magFilter = vk::Filter::eLinear; - samplerInfo.minFilter = vk::Filter::eLinear; - samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; - samplerInfo.addressModeU = addressMode; - samplerInfo.addressModeV = addressMode; - samplerInfo.addressModeW = addressMode; - samplerInfo.mipLodBias = 0.0f; - samplerInfo.anisotropyEnable = VK_FALSE; - samplerInfo.maxAnisotropy = 1.0f; - samplerInfo.compareEnable = VK_FALSE; - samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = 0.0f; - samplerInfo.borderColor = hdr - ? vk::BorderColor::eFloatOpaqueBlack - : vk::BorderColor::eIntOpaqueBlack; - samplerInfo.unnormalizedCoordinates = VK_FALSE; - - sampler = std::make_unique(*logicalDevice, samplerInfo); -} - -vk::DescriptorImageInfo Texture::getDescriptorInfo() const { - if (!hasGPUData()) { - throw std::runtime_error( - "GPU resources not initialized for " + path - ); - } - - vk::DescriptorImageInfo info{}; - info.sampler = **sampler; - info.imageView = **imageView; - info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - return info; -} - -} // namespace modeling + namespace modeling { + + Texture::Texture(const std::string& path, TextureType type, bool sRGB) + : path(path), type(type), sRGB(sRGB), hdr(false), embedded(false), width(0), height(0), + channels(0), dataLoaded(false) { + } + + Texture::Texture(const std::vector& data, int width, int height, + int channels, TextureType type, bool sRGB, const std::string& name) + : path(name), type(type), sRGB(sRGB), hdr(false), embedded(true), width(width), + height(height), channels(channels), data(data), dataLoaded(true) { + } + + const std::vector& Texture::getData() { + if (!dataLoaded) { + loadFromFile(); + } + return data; + } + + const std::vector& Texture::getHDRData() { + if (!dataLoaded) { + loadFromFile(); + } + return hdrData; + } + + void Texture::loadFromFile() { + if (dataLoaded) { + return; + } + + if (embedded) { + return; + } + + if (stbi_is_hdr(path.c_str())) { + loadFromFileHDR(); + return; + } + + int w, h, c; + unsigned char* pixels = stbi_load(path.c_str(), &w, &h, &c, STBI_rgb_alpha); + + if (!pixels) { + createDefaultPixels(); + return; + } + + width = w; + height = h; + channels = 4; + + size_t dataSize = width * height * channels; + data.resize(dataSize); + std::memcpy(data.data(), pixels, dataSize); + + stbi_image_free(pixels); + dataLoaded = true; + } + + void Texture::loadFromFileHDR() { + int w, h, c; + float* pixels = stbi_loadf(path.c_str(), &w, &h, &c, STBI_rgb_alpha); + + if (!pixels) { + createDefaultPixels(); + return; + } + + width = w; + height = h; + channels = 4; + hdr = true; + + size_t pixelCount = static_cast(width) * height * channels; + hdrData.resize(pixelCount); + std::memcpy(hdrData.data(), pixels, pixelCount * sizeof(float)); + + stbi_image_free(pixels); + dataLoaded = true; + } + + void Texture::createDefaultPixels() { + width = 1; + height = 1; + channels = 4; + data.resize(4); + + if (type == TextureType::Normal) { + data[0] = 128; + data[1] = 128; + data[2] = 255; + data[3] = 255; + } else if (type == TextureType::MetallicRoughness || type == TextureType::Occlusion) { + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 255; + } else { + data[0] = 255; + data[1] = 255; + data[2] = 255; + data[3] = 255; + } + + dataLoaded = true; + } + + void Texture::initVulkanResources(const sauce::LogicalDevice& logicalDevice, + vk::raii::PhysicalDevice& physicalDevice, + vk::raii::CommandPool& commandPool, + vk::raii::Queue& queue) { + if (hasGPUData()) { + return; + } + + if (!dataLoaded) { + getData(); + } + + bool hasPixels = hdr ? !hdrData.empty() : !data.empty(); + if (!hasPixels || width == 0 || height == 0) { + throw std::runtime_error("No data for " + path); + } + + vk::DeviceSize bytesPerPixel = hdr ? (4 * sizeof(float)) : 4; + vk::DeviceSize imageSize = static_cast(width) * height * bytesPerPixel; + + vk::raii::Buffer stagingBuffer(nullptr); + vk::raii::DeviceMemory stagingMemory(nullptr); + + sauce::BufferUtils::createBuffer(physicalDevice, logicalDevice, imageSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, stagingMemory); + + void* mappedData = stagingMemory.mapMemory(0, imageSize); + if (hdr) { + memcpy(mappedData, hdrData.data(), static_cast(imageSize)); + } else { + memcpy(mappedData, data.data(), static_cast(imageSize)); + } + stagingMemory.unmapMemory(); + + vk::Format imageFormat; + if (hdr) { + imageFormat = vk::Format::eR32G32B32A32Sfloat; + } else { + imageFormat = sRGB ? vk::Format::eR8G8B8A8Srgb : vk::Format::eR8G8B8A8Unorm; + } + + image = std::make_unique(nullptr); + imageMemory = std::make_unique(nullptr); + + sauce::ImageUtils::createImage( + physicalDevice, logicalDevice, width, height, imageFormat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, *image, *imageMemory); + + sauce::ImageUtils::transitionImageLayout( + logicalDevice, commandPool, queue, *image, vk::ImageLayout::eUndefined, + vk::ImageLayout::eTransferDstOptimal, {}, vk::AccessFlagBits2::eTransferWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, vk::PipelineStageFlagBits2::eTransfer); + + sauce::ImageUtils::copyBufferToImage(logicalDevice, commandPool, queue, stagingBuffer, + *image, (uint32_t)width, (uint32_t)height); + + sauce::ImageUtils::transitionImageLayout( + logicalDevice, commandPool, queue, *image, vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eTransferWrite, + vk::AccessFlagBits2::eShaderRead, vk::PipelineStageFlagBits2::eTransfer, + vk::PipelineStageFlagBits2::eFragmentShader); + + imageView = std::make_unique(sauce::ImageUtils::createImageView( + logicalDevice, *image, imageFormat, vk::ImageAspectFlagBits::eColor)); + + bool isEnvMap = (type == TextureType::EnvironmentMapHDR); + vk::SamplerAddressMode addressMode = + isEnvMap ? vk::SamplerAddressMode::eClampToEdge : vk::SamplerAddressMode::eRepeat; + + vk::SamplerCreateInfo samplerInfo{}; + samplerInfo.magFilter = vk::Filter::eLinear; + samplerInfo.minFilter = vk::Filter::eLinear; + samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; + samplerInfo.addressModeU = addressMode; + samplerInfo.addressModeV = addressMode; + samplerInfo.addressModeW = addressMode; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 1.0f; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + samplerInfo.borderColor = + hdr ? vk::BorderColor::eFloatOpaqueBlack : vk::BorderColor::eIntOpaqueBlack; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + sampler = std::make_unique(*logicalDevice, samplerInfo); + } + + vk::DescriptorImageInfo Texture::getDescriptorInfo() const { + if (!hasGPUData()) { + throw std::runtime_error("GPU resources not initialized for " + path); + } + + vk::DescriptorImageInfo info{}; + info.sampler = **sampler; + info.imageView = **imageView; + info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + return info; + } + + } // namespace modeling } // namespace sauce diff --git a/src/app/modeling/TextureCache.cpp b/src/app/modeling/TextureCache.cpp index 04c08d59..55b7a443 100644 --- a/src/app/modeling/TextureCache.cpp +++ b/src/app/modeling/TextureCache.cpp @@ -1,128 +1,126 @@ #include "app/modeling/TextureCache.hpp" -#include #include +#include namespace sauce { -namespace modeling { - -TextureCache::TextureCache() { - initializeDefaultTextures(); -} - -std::shared_ptr TextureCache::getTexture(const std::string& path, TextureType type, bool sRGB) { - // Check if texture is already in cache - auto it = cache.find(path); - if (it != cache.end()) { - return it->second; - } - - // Create new texture and add to cache - auto texture = std::make_shared(path, type, sRGB); - cache[path] = texture; - return texture; -} - -std::shared_ptr TextureCache::getHDRTexture(const std::string& path) { - auto it = cache.find(path); - if (it != cache.end()) { - return it->second; - } - - auto texture = std::make_shared(path, TextureType::EnvironmentMapHDR, false); - cache[path] = texture; - return texture; -} - -std::shared_ptr TextureCache::getEmbeddedTexture(const std::vector& data, - int width, int height, int channels, - TextureType type, bool sRGB) { - // Generate hash-based key for embedded texture - std::string key = generateEmbeddedKey(data); - - // Check if texture is already in cache - auto it = cache.find(key); - if (it != cache.end()) { - return it->second; - } - - // Create new embedded texture and add to cache - auto texture = std::make_shared(data, width, height, channels, type, sRGB, key); - cache[key] = texture; - return texture; -} - -std::shared_ptr TextureCache::getDefaultTexture(TextureType type) { - auto it = defaultTextures.find(type); - if (it != defaultTextures.end()) { - return it->second; - } - - // Fallback to BaseColor default if specific type not found - return defaultTextures[TextureType::BaseColor]; -} - -void TextureCache::clear() { - cache.clear(); - // Don't clear default textures - they're always needed -} - -std::string TextureCache::generateEmbeddedKey(const std::vector& data) { - // Simple hash: use first 16 bytes + size - // For better hash, could use std::hash or a proper hash function - std::stringstream ss; - ss << "embedded_"; - - size_t hashSize = std::min(data.size(), size_t(16)); - for (size_t i = 0; i < hashSize; ++i) { - ss << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; - } - - ss << "_" << std::dec << data.size(); - return ss.str(); -} - -void TextureCache::initializeDefaultTextures() { - // Default white texture for BaseColor - { - std::vector whitePixel = {255, 255, 255, 255}; - defaultTextures[TextureType::BaseColor] = std::make_shared( - whitePixel, 1, 1, 4, TextureType::BaseColor, true, "default_white" - ); - } - - // Default flat normal (pointing up: 0, 0, 1 -> 128, 128, 255 in RGB) - { - std::vector normalPixel = {128, 128, 255, 255}; - defaultTextures[TextureType::Normal] = std::make_shared( - normalPixel, 1, 1, 4, TextureType::Normal, false, "default_normal" - ); - } - - // Default metallic-roughness (non-metallic, medium roughness) - // R=occlusion(1.0), G=roughness(0.5), B=metallic(0.0) - { - std::vector metallicRoughnessPixel = {255, 128, 0, 255}; - defaultTextures[TextureType::MetallicRoughness] = std::make_shared( - metallicRoughnessPixel, 1, 1, 4, TextureType::MetallicRoughness, false, "default_metallic_roughness" - ); - } - - // Default occlusion (no occlusion = white) - { - std::vector occlusionPixel = {255, 255, 255, 255}; - defaultTextures[TextureType::Occlusion] = std::make_shared( - occlusionPixel, 1, 1, 4, TextureType::Occlusion, false, "default_occlusion" - ); - } - - // Default emissive (no emission = black) - { - std::vector emissivePixel = {0, 0, 0, 255}; - defaultTextures[TextureType::Emissive] = std::make_shared( - emissivePixel, 1, 1, 4, TextureType::Emissive, true, "default_emissive" - ); - } -} - -} // namespace modeling + namespace modeling { + + TextureCache::TextureCache() { + initializeDefaultTextures(); + } + + std::shared_ptr TextureCache::getTexture(const std::string& path, TextureType type, + bool sRGB) { + // Check if texture is already in cache + auto it = cache.find(path); + if (it != cache.end()) { + return it->second; + } + + // Create new texture and add to cache + auto texture = std::make_shared(path, type, sRGB); + cache[path] = texture; + return texture; + } + + std::shared_ptr TextureCache::getHDRTexture(const std::string& path) { + auto it = cache.find(path); + if (it != cache.end()) { + return it->second; + } + + auto texture = std::make_shared(path, TextureType::EnvironmentMapHDR, false); + cache[path] = texture; + return texture; + } + + std::shared_ptr TextureCache::getEmbeddedTexture( + const std::vector& data, int width, int height, int channels, + TextureType type, bool sRGB) { + // Generate hash-based key for embedded texture + std::string key = generateEmbeddedKey(data); + + // Check if texture is already in cache + auto it = cache.find(key); + if (it != cache.end()) { + return it->second; + } + + // Create new embedded texture and add to cache + auto texture = + std::make_shared(data, width, height, channels, type, sRGB, key); + cache[key] = texture; + return texture; + } + + std::shared_ptr TextureCache::getDefaultTexture(TextureType type) { + auto it = defaultTextures.find(type); + if (it != defaultTextures.end()) { + return it->second; + } + + // Fallback to BaseColor default if specific type not found + return defaultTextures[TextureType::BaseColor]; + } + + void TextureCache::clear() { + cache.clear(); + // Don't clear default textures - they're always needed + } + + std::string TextureCache::generateEmbeddedKey(const std::vector& data) { + // Simple hash: use first 16 bytes + size + // For better hash, could use std::hash or a proper hash function + std::stringstream ss; + ss << "embedded_"; + + size_t hashSize = std::min(data.size(), size_t(16)); + for (size_t i = 0; i < hashSize; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; + } + + ss << "_" << std::dec << data.size(); + return ss.str(); + } + + void TextureCache::initializeDefaultTextures() { + // Default white texture for BaseColor + { + std::vector whitePixel = {255, 255, 255, 255}; + defaultTextures[TextureType::BaseColor] = std::make_shared( + whitePixel, 1, 1, 4, TextureType::BaseColor, true, "default_white"); + } + + // Default flat normal (pointing up: 0, 0, 1 -> 128, 128, 255 in RGB) + { + std::vector normalPixel = {128, 128, 255, 255}; + defaultTextures[TextureType::Normal] = std::make_shared( + normalPixel, 1, 1, 4, TextureType::Normal, false, "default_normal"); + } + + // Default metallic-roughness (non-metallic, medium roughness) + // R=occlusion(1.0), G=roughness(0.5), B=metallic(0.0) + { + std::vector metallicRoughnessPixel = {255, 128, 0, 255}; + defaultTextures[TextureType::MetallicRoughness] = std::make_shared( + metallicRoughnessPixel, 1, 1, 4, TextureType::MetallicRoughness, false, + "default_metallic_roughness"); + } + + // Default occlusion (no occlusion = white) + { + std::vector occlusionPixel = {255, 255, 255, 255}; + defaultTextures[TextureType::Occlusion] = std::make_shared( + occlusionPixel, 1, 1, 4, TextureType::Occlusion, false, "default_occlusion"); + } + + // Default emissive (no emission = black) + { + std::vector emissivePixel = {0, 0, 0, 255}; + defaultTextures[TextureType::Emissive] = std::make_shared( + emissivePixel, 1, 1, 4, TextureType::Emissive, true, "default_emissive"); + } + } + + } // namespace modeling } // namespace sauce diff --git a/src/app/modeling/Transform.cpp b/src/app/modeling/Transform.cpp index b50a1758..5f64fd1a 100644 --- a/src/app/modeling/Transform.cpp +++ b/src/app/modeling/Transform.cpp @@ -5,59 +5,58 @@ #include namespace sauce { -namespace modeling { - -// Forward declaration for ModelNode - will be fully defined in ModelNode.hpp -class ModelNode; - -Transform::Transform() - : translation(0.0f, 0.0f, 0.0f) - , rotation(1.0f, 0.0f, 0.0f, 0.0f) // Identity quaternion - , scale(1.0f, 1.0f, 1.0f) { -} - -Transform::Transform(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale) - : translation(translation) - , rotation(rotation) - , scale(scale) { -} - -void Transform::setTranslation(const glm::vec3& translation) { - this->translation = translation; -} - -void Transform::setRotation(const glm::quat& rotation) { - this->rotation = rotation; -} - -void Transform::setScale(const glm::vec3& scale) { - this->scale = scale; -} - -glm::mat4 Transform::getLocalMatrix() const { - glm::mat4 matrix(1.0f); - matrix = glm::translate(matrix, translation); - matrix = matrix * glm::mat4_cast(rotation); - matrix = glm::scale(matrix, scale); - return matrix; -} - -glm::mat4 Transform::getWorldMatrix(const ModelNode* node) const { - // TODO: Traverse parent chain to compute world matrix - return getLocalMatrix(); -} - -Transform Transform::fromMatrix(const glm::mat4& matrix) { - glm::vec3 translation; - glm::quat rotation; - glm::vec3 scale; - glm::vec3 skew; - glm::vec4 perspective; - - glm::decompose(matrix, scale, rotation, translation, skew, perspective); - - return Transform(translation, rotation, scale); -} - -} // namespace modeling + namespace modeling { + + // Forward declaration for ModelNode - will be fully defined in ModelNode.hpp + class ModelNode; + + Transform::Transform() + : translation(0.0f, 0.0f, 0.0f), rotation(1.0f, 0.0f, 0.0f, 0.0f) // Identity quaternion + , + scale(1.0f, 1.0f, 1.0f) { + } + + Transform::Transform(const glm::vec3& translation, const glm::quat& rotation, + const glm::vec3& scale) + : translation(translation), rotation(rotation), scale(scale) { + } + + void Transform::setTranslation(const glm::vec3& translation) { + this->translation = translation; + } + + void Transform::setRotation(const glm::quat& rotation) { + this->rotation = rotation; + } + + void Transform::setScale(const glm::vec3& scale) { + this->scale = scale; + } + + glm::mat4 Transform::getLocalMatrix() const { + glm::mat4 matrix(1.0f); + matrix = glm::translate(matrix, translation); + matrix = matrix * glm::mat4_cast(rotation); + matrix = glm::scale(matrix, scale); + return matrix; + } + + glm::mat4 Transform::getWorldMatrix(const ModelNode* node) const { + // TODO: Traverse parent chain to compute world matrix + return getLocalMatrix(); + } + + Transform Transform::fromMatrix(const glm::mat4& matrix) { + glm::vec3 translation; + glm::quat rotation; + glm::vec3 scale; + glm::vec3 skew; + glm::vec4 perspective; + + glm::decompose(matrix, scale, rotation, translation, skew, perspective); + + return Transform(translation, rotation, scale); + } + + } // namespace modeling } // namespace sauce diff --git a/src/app/physics/constraints/BendConstraint.cpp b/src/app/physics/constraints/BendConstraint.cpp index c6842340..0d642f96 100644 --- a/src/app/physics/constraints/BendConstraint.cpp +++ b/src/app/physics/constraints/BendConstraint.cpp @@ -1,6 +1,6 @@ #include "app/physics/constraints/BendConstraint.hpp" -namespace{ +namespace { constexpr float EPS = 1e-8f; } @@ -9,7 +9,8 @@ namespace{ * 1. vertices.length() == 4 (this constraint uses exactly four vertices) * 2. lagrangeMultiplers.length() == 1 () */ -void BendConstraint::solve(std::vector& vertices, std::vector& lagrangeMultipliers, float deltatime) const { +void BendConstraint::solve(std::vector& vertices, + std::vector& lagrangeMultipliers, float deltatime) const { Vertex& v1 = vertices[0]; Vertex& v2 = vertices[1]; Vertex& v3 = vertices[2]; @@ -21,7 +22,8 @@ void BendConstraint::solve(std::vector& vertices, std::vector float w4 = v4.invMass; // No point in calculating anything then - if (w1 + w2 + w3 + w4 < EPS) return; + if (w1 + w2 + w3 + w4 < EPS) + return; glm::vec3 p1 = v1.position; glm::vec3 p2 = v2.position; @@ -29,14 +31,15 @@ void BendConstraint::solve(std::vector& vertices, std::vector glm::vec3 p4 = v4.position; // helper vectors representing triangle edges - glm::vec3 e = p2 - p1; // common edge the two triangles share + glm::vec3 e = p2 - p1; // common edge the two triangles share float eLen = glm::length(e); - if (eLen < EPS) return; + if (eLen < EPS) + return; glm::vec3 e31 = p3 - p1; glm::vec3 e41 = p4 - p1; glm::vec3 n1u = glm::cross(e, e31); - glm::vec3 n2u = glm::cross(e, e41); + glm::vec3 n2u = glm::cross(e, e41); glm::vec3 n1 = n1u; // left term in the acos before dotp glm::vec3 n2 = n2u; // right term in the acos before dotp @@ -44,35 +47,39 @@ void BendConstraint::solve(std::vector& vertices, std::vector // normalize float n1Len = glm::length(n1); float n2Len = glm::length(n2); - if (n1Len < EPS || n2Len < EPS) return; // values would be negligible - n1 = n1/n1Len; - n2 = n2/n2Len; + if (n1Len < EPS || n2Len < EPS) + return; // values would be negligible + n1 = n1 / n1Len; + n2 = n2 / n2Len; float dot = glm::clamp(glm::dot(n1, n2), -1.0f, 1.0f); float c = std::acos(dot) - restAngle; - if (std::abs(c) < 1e-6f) return; // basically zero + if (std::abs(c) < 1e-6f) + return; // basically zero // more helper values float ee = glm::dot(e, e); - float s3 = glm::dot(e31, e)/ee; - float s4 = glm::dot(e41, e)/ee; + float s3 = glm::dot(e31, e) / ee; + float s4 = glm::dot(e41, e) / ee; //compute gradients - glm::vec3 g3 = (eLen/(n1Len*n1Len)) * glm::cross(e, n1); - glm::vec3 g4 = (eLen/(n2Len*n2Len)) * glm::cross(n2, e); - glm::vec3 g1 = -g3 - g4 - s3*g3 - s4 * g4; + glm::vec3 g3 = (eLen / (n1Len * n1Len)) * glm::cross(e, n1); + glm::vec3 g4 = (eLen / (n2Len * n2Len)) * glm::cross(n2, e); + glm::vec3 g1 = -g3 - g4 - s3 * g3 - s4 * g4; glm::vec3 g2 = -g1 - g3 - g4; - float a = compliance/(deltatime*deltatime); + float a = compliance / (deltatime * deltatime); float& lambda = lagrangeMultipliers[0].x; - float denominator = w1*glm::dot(g1, g1) + w2*glm::dot(g2, g2) +w3*glm::dot(g3, g3) + w4*glm::dot(g4, g4) + a; + float denominator = w1 * glm::dot(g1, g1) + w2 * glm::dot(g2, g2) + w3 * glm::dot(g3, g3) + + w4 * glm::dot(g4, g4) + a; // negligible in this case - if (denominator < EPS) return; + if (denominator < EPS) + return; - float dlambda = (-c - a * lambda)/denominator; + float dlambda = (-c - a * lambda) / denominator; lambda += dlambda; diff --git a/src/app/physics/constraints/StretchConstraint.cpp b/src/app/physics/constraints/StretchConstraint.cpp index 31e4267f..fea2023a 100644 --- a/src/app/physics/constraints/StretchConstraint.cpp +++ b/src/app/physics/constraints/StretchConstraint.cpp @@ -1,6 +1,6 @@ #include "app/physics/constraints/StretchConstraint.hpp" -namespace{ +namespace { constexpr float EPS = 1e-8f; } @@ -9,7 +9,8 @@ namespace{ * 1. vertices.length() == 2 (this constraint uses exactly two vertices) * 2. lagrangeMultiplers.length() == 1 () */ -void StretchConstraint::solve(std::vector& vertices, std::vector& lagrangeMultipliers, float deltatime) const { +void StretchConstraint::solve(std::vector& vertices, + std::vector& lagrangeMultipliers, float deltatime) const { Vertex& v1 = vertices[0]; Vertex& v2 = vertices[1]; @@ -17,7 +18,8 @@ void StretchConstraint::solve(std::vector& vertices, std::vector& vertices, std::vector #include +#include #include -namespace sauce::ui{ +namespace sauce::ui { -BulletText::BulletText(const std::string& name, const std::string& text) - : Text(name, text) {} + BulletText::BulletText(const std::string& name, const std::string& text) : Text(name, text) { + } -void BulletText::render() { - if (!enabled) return; - ImGui::BulletText("%s", text.c_str()); -} + void BulletText::render() { + if (!enabled) + return; + ImGui::BulletText("%s", text.c_str()); + } -} +} // namespace sauce::ui diff --git a/src/app/ui/Button.cpp b/src/app/ui/Button.cpp index e9b7db32..2a84f3a2 100644 --- a/src/app/ui/Button.cpp +++ b/src/app/ui/Button.cpp @@ -1,19 +1,21 @@ -#include #include +#include #include -namespace sauce::ui{ +namespace sauce::ui { -Button::Button(const std::string& name, const std::string& label, Callback onClick) - : ImGuiComponent(name), label(label), onClick(onClick) {} + Button::Button(const std::string& name, const std::string& label, Callback onClick) + : ImGuiComponent(name), label(label), onClick(onClick) { + } -void Button::render() { - if (!enabled) return; - if (ImGui::Button(label.c_str())) { - if (onClick){ - onClick(); - } - } -} + void Button::render() { + if (!enabled) + return; + if (ImGui::Button(label.c_str())) { + if (onClick) { + onClick(); + } + } + } -} +} // namespace sauce::ui diff --git a/src/app/ui/Checkbox.cpp b/src/app/ui/Checkbox.cpp index 66e531d8..bd2508e3 100644 --- a/src/app/ui/Checkbox.cpp +++ b/src/app/ui/Checkbox.cpp @@ -1,22 +1,25 @@ -#include #include +#include #include -namespace sauce::ui{ +namespace sauce::ui { -Checkbox::Checkbox(const std::string& name, const std::string& label, bool* checked, Callback onChanged) - : ImGuiComponent(name), label(label), checked(checked), onChanged(onChanged) {} + Checkbox::Checkbox(const std::string& name, const std::string& label, bool* checked, + Callback onChanged) + : ImGuiComponent(name), label(label), checked(checked), onChanged(onChanged) { + } -void Checkbox::render() { - if (!enabled) return; + void Checkbox::render() { + if (!enabled) + return; - bool prev = *checked; + bool prev = *checked; - if (ImGui::Checkbox(label.c_str(), checked)) { - if (prev != *checked && onChanged){ - onChanged(*checked); - } - } -} + if (ImGui::Checkbox(label.c_str(), checked)) { + if (prev != *checked && onChanged) { + onChanged(*checked); + } + } + } -} +} // namespace sauce::ui diff --git a/src/app/ui/ChildWindow.cpp b/src/app/ui/ChildWindow.cpp index ea2b623e..64988376 100644 --- a/src/app/ui/ChildWindow.cpp +++ b/src/app/ui/ChildWindow.cpp @@ -2,21 +2,23 @@ namespace sauce::ui { -ChildWindow::ChildWindow(const std::string& name, const ImVec2& size, - ImGuiChildFlags childFlags, ImGuiWindowFlags windowFlags, - ContentCallback content) - : ImGuiComponent(name), size(size), childFlags(childFlags), - windowFlags(windowFlags), content(content) {} + ChildWindow::ChildWindow(const std::string& name, const ImVec2& size, + ImGuiChildFlags childFlags, ImGuiWindowFlags windowFlags, + ContentCallback content) + : ImGuiComponent(name), size(size), childFlags(childFlags), windowFlags(windowFlags), + content(content) { + } + + void ChildWindow::render() { + if (!enabled) + return; -void ChildWindow::render() { - if (!enabled) return; - - if (ImGui::BeginChild(name.c_str(), size, childFlags, windowFlags)) { - if (content) { - content(); + if (ImGui::BeginChild(name.c_str(), size, childFlags, windowFlags)) { + if (content) { + content(); + } } + ImGui::EndChild(); } - ImGui::EndChild(); -} -} +} // namespace sauce::ui diff --git a/src/app/ui/CollapsingHeader.cpp b/src/app/ui/CollapsingHeader.cpp index eff2a4a3..84fda147 100644 --- a/src/app/ui/CollapsingHeader.cpp +++ b/src/app/ui/CollapsingHeader.cpp @@ -2,19 +2,21 @@ namespace sauce::ui { -CollapsingHeader::CollapsingHeader(const std::string& name, const std::string& label, - ContentCallback content, ImGuiTreeNodeFlags flags) - : ImGuiComponent(name), label(label), content(content), flags(flags) {} + CollapsingHeader::CollapsingHeader(const std::string& name, const std::string& label, + ContentCallback content, ImGuiTreeNodeFlags flags) + : ImGuiComponent(name), label(label), content(content), flags(flags) { + } + + void CollapsingHeader::render() { + if (!enabled || !visible) + return; -void CollapsingHeader::render() { - if (!enabled || !visible) return; - - headerOpen = ImGui::CollapsingHeader(label.c_str(), flags); - if (headerOpen) { - if (content) { - content(); + headerOpen = ImGui::CollapsingHeader(label.c_str(), flags); + if (headerOpen) { + if (content) { + content(); + } } } -} -} +} // namespace sauce::ui diff --git a/src/app/ui/Columns.cpp b/src/app/ui/Columns.cpp index 4beaf060..e68386b1 100644 --- a/src/app/ui/Columns.cpp +++ b/src/app/ui/Columns.cpp @@ -2,22 +2,24 @@ namespace sauce::ui { -Columns::Columns(const std::string& name, int count, const std::string& id, - bool border, ContentCallback content) - : ImGuiComponent(name), count(count), id(id), border(border), content(content) {} + Columns::Columns(const std::string& name, int count, const std::string& id, bool border, + ContentCallback content) + : ImGuiComponent(name), count(count), id(id), border(border), content(content) { + } + + void Columns::render() { + if (!enabled) + return; + + // Use the legacy Columns API for simplicity + ImGui::Columns(count, id.empty() ? nullptr : id.c_str(), border); + + if (content) { + content(); + } -void Columns::render() { - if (!enabled) return; - - // Use the legacy Columns API for simplicity - ImGui::Columns(count, id.empty() ? nullptr : id.c_str(), border); - - if (content) { - content(); + // Reset to single column + ImGui::Columns(1); } - - // Reset to single column - ImGui::Columns(1); -} -} +} // namespace sauce::ui diff --git a/src/app/ui/CustomTooltip.cpp b/src/app/ui/CustomTooltip.cpp index 2d03a310..40193013 100644 --- a/src/app/ui/CustomTooltip.cpp +++ b/src/app/ui/CustomTooltip.cpp @@ -1,22 +1,18 @@ -#include #include +#include -namespace sauce::ui -{ - CustomTooltip::CustomTooltip(const std::string& name, std::function renderFn) - : ImGuiComponent(name), renderFn{std::move(renderFn)} - { - } +namespace sauce::ui { + CustomTooltip::CustomTooltip(const std::string& name, std::function renderFn) + : ImGuiComponent(name), renderFn{std::move(renderFn)} { + } - CustomTooltip::~CustomTooltip() = default; + CustomTooltip::~CustomTooltip() = default; - void CustomTooltip::render() - { - if (enabled && ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - this->renderFn(); - ImGui::EndTooltip(); - } - } -} + void CustomTooltip::render() { + if (enabled && ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + this->renderFn(); + ImGui::EndTooltip(); + } + } +} // namespace sauce::ui diff --git a/src/app/ui/Group.cpp b/src/app/ui/Group.cpp index 6e70f761..35cfa87c 100644 --- a/src/app/ui/Group.cpp +++ b/src/app/ui/Group.cpp @@ -2,17 +2,19 @@ namespace sauce::ui { -Group::Group(const std::string& name, ContentCallback content) - : ImGuiComponent(name), content(content) {} + Group::Group(const std::string& name, ContentCallback content) + : ImGuiComponent(name), content(content) { + } + + void Group::render() { + if (!enabled) + return; -void Group::render() { - if (!enabled) return; - - ImGui::BeginGroup(); - if (content) { - content(); + ImGui::BeginGroup(); + if (content) { + content(); + } + ImGui::EndGroup(); } - ImGui::EndGroup(); -} -} +} // namespace sauce::ui diff --git a/src/app/ui/ImGuiComponentManager.cpp b/src/app/ui/ImGuiComponentManager.cpp index d504a8f0..0869e865 100644 --- a/src/app/ui/ImGuiComponentManager.cpp +++ b/src/app/ui/ImGuiComponentManager.cpp @@ -1,58 +1,58 @@ -#include #include +#include namespace sauce::ui { -void ImGuiComponentManager::addComponent(std::unique_ptr component) { - if (component) { - components.push_back(std::move(component)); + void ImGuiComponentManager::addComponent(std::unique_ptr component) { + if (component) { + components.push_back(std::move(component)); + } } -} -bool ImGuiComponentManager::removeComponent(const std::string& name) { - auto it = std::find_if(components.begin(), components.end(), - [&name](const std::unique_ptr& comp) { - return comp->getName() == name; - }); + bool ImGuiComponentManager::removeComponent(const std::string& name) { + auto it = std::find_if(components.begin(), components.end(), + [&name](const std::unique_ptr& comp) { + return comp->getName() == name; + }); - if (it != components.end()) { - components.erase(it); - return true; + if (it != components.end()) { + components.erase(it); + return true; + } + return false; } - return false; -} -ImGuiComponent* ImGuiComponentManager::getComponent(const std::string& name) { - auto it = std::find_if(components.begin(), components.end(), - [&name](const std::unique_ptr& comp) { - return comp->getName() == name; - }); + ImGuiComponent* ImGuiComponentManager::getComponent(const std::string& name) { + auto it = std::find_if(components.begin(), components.end(), + [&name](const std::unique_ptr& comp) { + return comp->getName() == name; + }); - if (it != components.end()) { - return it->get(); + if (it != components.end()) { + return it->get(); + } + return nullptr; } - return nullptr; -} -void ImGuiComponentManager::renderAll() { - for (const auto& component : components) { - if (component && component->isEnabled()) { - component->render(); + void ImGuiComponentManager::renderAll() { + for (const auto& component : components) { + if (component && component->isEnabled()) { + component->render(); + } } } -} -bool ImGuiComponentManager::setComponentEnabled(const std::string& name, bool enabled) { - ImGuiComponent* component = getComponent(name); - if (component) { - component->setEnabled(enabled); - return true; + bool ImGuiComponentManager::setComponentEnabled(const std::string& name, bool enabled) { + ImGuiComponent* component = getComponent(name); + if (component) { + component->setEnabled(enabled); + return true; + } + return false; } - return false; -} -size_t ImGuiComponentManager::getComponentCount() const { - return components.size(); -} + size_t ImGuiComponentManager::getComponentCount() const { + return components.size(); + } -} +} // namespace sauce::ui diff --git a/src/app/ui/Image.cpp b/src/app/ui/Image.cpp index e99a33f7..46544319 100644 --- a/src/app/ui/Image.cpp +++ b/src/app/ui/Image.cpp @@ -1,15 +1,17 @@ -#include #include +#include #include -namespace sauce::ui{ - -Image::Image(const std::string& name, ImTextureID texture, const ImVec2& size) - : ImGuiComponent(name), texture(texture), size(size) {} +namespace sauce::ui { + + Image::Image(const std::string& name, ImTextureID texture, const ImVec2& size) + : ImGuiComponent(name), texture(texture), size(size) { + } -void Image::render() { - if (!enabled) return; - ImGui::Image(texture, size); -} + void Image::render() { + if (!enabled) + return; + ImGui::Image(texture, size); + } -} +} // namespace sauce::ui diff --git a/src/app/ui/ImageButton.cpp b/src/app/ui/ImageButton.cpp index c1ea056e..b1fbf350 100644 --- a/src/app/ui/ImageButton.cpp +++ b/src/app/ui/ImageButton.cpp @@ -1,19 +1,22 @@ -#include #include +#include #include -namespace sauce::ui{ +namespace sauce::ui { -ImageButton::ImageButton(const std::string& name, ImTextureID texture, const ImVec2& size, Callback onClick) - : Image(name, texture, size), onClick(onClick) {} + ImageButton::ImageButton(const std::string& name, ImTextureID texture, const ImVec2& size, + Callback onClick) + : Image(name, texture, size), onClick(onClick) { + } -void ImageButton::render() { - if (!enabled) return; - if (ImGui::ImageButton(name.c_str(), texture, size)){ - if (onClick){ - onClick(); + void ImageButton::render() { + if (!enabled) + return; + if (ImGui::ImageButton(name.c_str(), texture, size)) { + if (onClick) { + onClick(); + } } } -} -} +} // namespace sauce::ui diff --git a/src/app/ui/LabelText.cpp b/src/app/ui/LabelText.cpp index 5dc3b393..49e57df8 100644 --- a/src/app/ui/LabelText.cpp +++ b/src/app/ui/LabelText.cpp @@ -1,15 +1,17 @@ -#include #include +#include #include -namespace sauce::ui{ +namespace sauce::ui { -LabelText::LabelText(const std::string& name, const std::string& label, const std::string& text) - : Text(name, text), label(label) {} + LabelText::LabelText(const std::string& name, const std::string& label, const std::string& text) + : Text(name, text), label(label) { + } -void LabelText::render() { - if (!enabled) return; - ImGui::LabelText(label.c_str(), "%s", text.c_str()); -} + void LabelText::render() { + if (!enabled) + return; + ImGui::LabelText(label.c_str(), "%s", text.c_str()); + } -} +} // namespace sauce::ui diff --git a/src/app/ui/PlotHistogram.cpp b/src/app/ui/PlotHistogram.cpp index 1abe5b30..fd75036f 100644 --- a/src/app/ui/PlotHistogram.cpp +++ b/src/app/ui/PlotHistogram.cpp @@ -1,20 +1,16 @@ -#include #include +#include -namespace sauce::ui -{ - PlotHistogram::PlotHistogram(const std::string& name, std::vector values) - : ImGuiComponent(name), values{std::move(values)} - { - } +namespace sauce::ui { + PlotHistogram::PlotHistogram(const std::string& name, std::vector values) + : ImGuiComponent(name), values{std::move(values)} { + } - PlotHistogram::~PlotHistogram() = default; + PlotHistogram::~PlotHistogram() = default; - void PlotHistogram::render() - { - if (enabled) - { - ImGui::PlotHistogram(this->name.c_str(), this->values.data(), this->values.size()); - } - } -} + void PlotHistogram::render() { + if (enabled) { + ImGui::PlotHistogram(this->name.c_str(), this->values.data(), this->values.size()); + } + } +} // namespace sauce::ui diff --git a/src/app/ui/PlotLines.cpp b/src/app/ui/PlotLines.cpp index f1b6cffa..d355ec7b 100644 --- a/src/app/ui/PlotLines.cpp +++ b/src/app/ui/PlotLines.cpp @@ -1,20 +1,16 @@ -#include #include +#include -namespace sauce::ui -{ - PlotLines::PlotLines(const std::string& name, std::vector values) - : ImGuiComponent(name), values{std::move(values)} - { - } +namespace sauce::ui { + PlotLines::PlotLines(const std::string& name, std::vector values) + : ImGuiComponent(name), values{std::move(values)} { + } - PlotLines::~PlotLines() = default; + PlotLines::~PlotLines() = default; - void PlotLines::render() - { - if (enabled) - { - ImGui::PlotLines(this->name.c_str(), this->values.data(), this->values.size()); - } - } -} + void PlotLines::render() { + if (enabled) { + ImGui::PlotLines(this->name.c_str(), this->values.data(), this->values.size()); + } + } +} // namespace sauce::ui diff --git a/src/app/ui/ProgressBar.cpp b/src/app/ui/ProgressBar.cpp index 16ee419e..7e89b084 100644 --- a/src/app/ui/ProgressBar.cpp +++ b/src/app/ui/ProgressBar.cpp @@ -1,19 +1,16 @@ -#include #include +#include -namespace sauce::ui -{ - ProgressBar::ProgressBar(const std::string& name, float fraction) - : ImGuiComponent(name), fraction{fraction} - { - } +namespace sauce::ui { + ProgressBar::ProgressBar(const std::string& name, float fraction) + : ImGuiComponent(name), fraction{fraction} { + } - ProgressBar::~ProgressBar() = default; + ProgressBar::~ProgressBar() = default; - void ProgressBar::render() - { - if (enabled) { - ImGui::ProgressBar(this->fraction); - } - } -} + void ProgressBar::render() { + if (enabled) { + ImGui::ProgressBar(this->fraction); + } + } +} // namespace sauce::ui diff --git a/src/app/ui/RadioButton.cpp b/src/app/ui/RadioButton.cpp index b2d8ee7d..6b251b50 100644 --- a/src/app/ui/RadioButton.cpp +++ b/src/app/ui/RadioButton.cpp @@ -1,20 +1,23 @@ -#include #include +#include #include -namespace sauce::ui{ +namespace sauce::ui { -RadioButton::RadioButton(const std::string& name, const std::string& label, int* selected, int value, Callback onChanged) - : ImGuiComponent(name), label(label), selected(selected), value(value), onChanged(onChanged) {} + RadioButton::RadioButton(const std::string& name, const std::string& label, int* selected, + int value, Callback onChanged) + : ImGuiComponent(name), label(label), selected(selected), value(value), + onChanged(onChanged) { + } -void RadioButton::render() { - if (!enabled) return; - if (ImGui::RadioButton(label.c_str(), selected, value)) { - if (onChanged) { - onChanged(value); + void RadioButton::render() { + if (!enabled) + return; + if (ImGui::RadioButton(label.c_str(), selected, value)) { + if (onChanged) { + onChanged(value); + } } } -} - -} +} // namespace sauce::ui diff --git a/src/app/ui/SameLine.cpp b/src/app/ui/SameLine.cpp index f171a68a..04b7a3c6 100644 --- a/src/app/ui/SameLine.cpp +++ b/src/app/ui/SameLine.cpp @@ -2,13 +2,15 @@ namespace sauce::ui { -SameLine::SameLine(const std::string& name, float offsetFromStartX, float spacing) - : ImGuiComponent(name), offsetFromStartX(offsetFromStartX), spacing(spacing) {} + SameLine::SameLine(const std::string& name, float offsetFromStartX, float spacing) + : ImGuiComponent(name), offsetFromStartX(offsetFromStartX), spacing(spacing) { + } -void SameLine::render() { - if (!enabled) return; - - ImGui::SameLine(offsetFromStartX, spacing); -} + void SameLine::render() { + if (!enabled) + return; -} + ImGui::SameLine(offsetFromStartX, spacing); + } + +} // namespace sauce::ui diff --git a/src/app/ui/Separator.cpp b/src/app/ui/Separator.cpp index 65ad1724..c99f54dc 100644 --- a/src/app/ui/Separator.cpp +++ b/src/app/ui/Separator.cpp @@ -2,13 +2,14 @@ namespace sauce::ui { -Separator::Separator(const std::string& name) - : ImGuiComponent(name) {} + Separator::Separator(const std::string& name) : ImGuiComponent(name) { + } -void Separator::render() { - if (!enabled) return; - - ImGui::Separator(); -} + void Separator::render() { + if (!enabled) + return; -} + ImGui::Separator(); + } + +} // namespace sauce::ui diff --git a/src/app/ui/SliderAngle.cpp b/src/app/ui/SliderAngle.cpp index d7957f26..849eddf1 100644 --- a/src/app/ui/SliderAngle.cpp +++ b/src/app/ui/SliderAngle.cpp @@ -3,20 +3,23 @@ namespace sauce::ui { -SliderAngle::SliderAngle(const std::string& name, const std::string& label, float* valueRadians, - float minDegrees, float maxDegrees, - const std::string& format, Callback onChanged) - : ImGuiComponent(name), label(label), valueRadians(valueRadians), - minDegrees(minDegrees), maxDegrees(maxDegrees), format(format), onChanged(onChanged) {} + SliderAngle::SliderAngle(const std::string& name, const std::string& label, float* valueRadians, + float minDegrees, float maxDegrees, const std::string& format, + Callback onChanged) + : ImGuiComponent(name), label(label), valueRadians(valueRadians), minDegrees(minDegrees), + maxDegrees(maxDegrees), format(format), onChanged(onChanged) { + } -void SliderAngle::render() { - if (!enabled) return; + void SliderAngle::render() { + if (!enabled) + return; - if (ImGui::SliderAngle(label.c_str(), valueRadians, minDegrees, maxDegrees, format.c_str())) { - if (onChanged) { - onChanged(*valueRadians); + if (ImGui::SliderAngle(label.c_str(), valueRadians, minDegrees, maxDegrees, + format.c_str())) { + if (onChanged) { + onChanged(*valueRadians); + } } } -} -} +} // namespace sauce::ui diff --git a/src/app/ui/Spacing.cpp b/src/app/ui/Spacing.cpp index 3e2a48f5..31272816 100644 --- a/src/app/ui/Spacing.cpp +++ b/src/app/ui/Spacing.cpp @@ -2,15 +2,16 @@ namespace sauce::ui { -Spacing::Spacing(const std::string& name, int count) - : ImGuiComponent(name), count(count) {} + Spacing::Spacing(const std::string& name, int count) : ImGuiComponent(name), count(count) { + } + + void Spacing::render() { + if (!enabled) + return; -void Spacing::render() { - if (!enabled) return; - - for (int i = 0; i < count; ++i) { - ImGui::Spacing(); + for (int i = 0; i < count; ++i) { + ImGui::Spacing(); + } } -} -} +} // namespace sauce::ui diff --git a/src/app/ui/Text.cpp b/src/app/ui/Text.cpp index 07e2c269..63ed72fe 100644 --- a/src/app/ui/Text.cpp +++ b/src/app/ui/Text.cpp @@ -1,15 +1,17 @@ -#include #include +#include #include -namespace sauce::ui{ +namespace sauce::ui { -Text::Text(const std::string& name, const std::string& text) - : ImGuiComponent(name), text(text) {} + Text::Text(const std::string& name, const std::string& text) + : ImGuiComponent(name), text(text) { + } -void Text::render() { - if (!enabled) return; - ImGui::Text("%s", text.c_str()); -} + void Text::render() { + if (!enabled) + return; + ImGui::Text("%s", text.c_str()); + } -} +} // namespace sauce::ui diff --git a/src/app/ui/TextColored.cpp b/src/app/ui/TextColored.cpp index b12f4cd3..42aae013 100644 --- a/src/app/ui/TextColored.cpp +++ b/src/app/ui/TextColored.cpp @@ -1,14 +1,16 @@ #include #include -namespace sauce::ui{ +namespace sauce::ui { -TextColored::TextColored(const std::string& name, const std::string& text, const ImVec4& color) - : Text(name, text), color(color) {} + TextColored::TextColored(const std::string& name, const std::string& text, const ImVec4& color) + : Text(name, text), color(color) { + } -void TextColored::render() { - if (!enabled) return; - ImGui::TextColored(color, "%s", text.c_str()); -} + void TextColored::render() { + if (!enabled) + return; + ImGui::TextColored(color, "%s", text.c_str()); + } -} +} // namespace sauce::ui diff --git a/src/app/ui/TextWrapped.cpp b/src/app/ui/TextWrapped.cpp index 7b9a5df2..d58e78da 100644 --- a/src/app/ui/TextWrapped.cpp +++ b/src/app/ui/TextWrapped.cpp @@ -1,14 +1,15 @@ #include #include -namespace sauce::ui{ +namespace sauce::ui { -TextWrapped::TextWrapped(const std::string& name, const std::string& text) - : Text(name, text) {} + TextWrapped::TextWrapped(const std::string& name, const std::string& text) : Text(name, text) { + } -void TextWrapped::render() { - if (!enabled) return; - ImGui::TextWrapped("%s", text.c_str()); -} + void TextWrapped::render() { + if (!enabled) + return; + ImGui::TextWrapped("%s", text.c_str()); + } -} +} // namespace sauce::ui diff --git a/src/app/ui/Tooltip.cpp b/src/app/ui/Tooltip.cpp index 8a401123..49d933cb 100644 --- a/src/app/ui/Tooltip.cpp +++ b/src/app/ui/Tooltip.cpp @@ -1,20 +1,16 @@ -#include #include +#include -namespace sauce::ui -{ - Tooltip::Tooltip(const std::string& name, std::string text) - : ImGuiComponent(name), text{std::move(text)} - { - } +namespace sauce::ui { + Tooltip::Tooltip(const std::string& name, std::string text) + : ImGuiComponent(name), text{std::move(text)} { + } - Tooltip::~Tooltip() = default; + Tooltip::~Tooltip() = default; - void Tooltip::render() - { - if (enabled && ImGui::IsItemHovered()) - { - ImGui::SetTooltip(this->text.c_str()); - } - } -} + void Tooltip::render() { + if (enabled && ImGui::IsItemHovered()) { + ImGui::SetTooltip(this->text.c_str()); + } + } +} // namespace sauce::ui diff --git a/src/app/ui/TreeNode.cpp b/src/app/ui/TreeNode.cpp index e5fa00ec..af1e0892 100644 --- a/src/app/ui/TreeNode.cpp +++ b/src/app/ui/TreeNode.cpp @@ -2,20 +2,22 @@ namespace sauce::ui { -TreeNode::TreeNode(const std::string& name, const std::string& label, - ContentCallback content, ImGuiTreeNodeFlags flags) - : ImGuiComponent(name), label(label), content(content), flags(flags) {} + TreeNode::TreeNode(const std::string& name, const std::string& label, ContentCallback content, + ImGuiTreeNodeFlags flags) + : ImGuiComponent(name), label(label), content(content), flags(flags) { + } + + void TreeNode::render() { + if (!enabled) + return; -void TreeNode::render() { - if (!enabled) return; - - nodeOpen = ImGui::TreeNodeEx(label.c_str(), flags); - if (nodeOpen) { - if (content) { - content(); + nodeOpen = ImGui::TreeNodeEx(label.c_str(), flags); + if (nodeOpen) { + if (content) { + content(); + } + ImGui::TreePop(); } - ImGui::TreePop(); } -} -} +} // namespace sauce::ui diff --git a/src/app/ui/Window.cpp b/src/app/ui/Window.cpp index 0d0244cd..ddc95cb2 100644 --- a/src/app/ui/Window.cpp +++ b/src/app/ui/Window.cpp @@ -2,19 +2,21 @@ namespace sauce::ui { -Window::Window(const std::string& name, const std::string& title, ContentCallback content, - ImGuiWindowFlags flags) - : ImGuiComponent(name), title(title), content(content), flags(flags) {} + Window::Window(const std::string& name, const std::string& title, ContentCallback content, + ImGuiWindowFlags flags) + : ImGuiComponent(name), title(title), content(content), flags(flags) { + } + + void Window::render() { + if (!enabled) + return; -void Window::render() { - if (!enabled) return; - - if (ImGui::Begin(title.c_str(), &isOpen, flags)) { - if (content) { - content(); + if (ImGui::Begin(title.c_str(), &isOpen, flags)) { + if (content) { + content(); + } } + ImGui::End(); } - ImGui::End(); -} -} +} // namespace sauce::ui diff --git a/src/editor/EditorApp.cpp b/src/editor/EditorApp.cpp index 31216605..a322a3b0 100644 --- a/src/editor/EditorApp.cpp +++ b/src/editor/EditorApp.cpp @@ -1,1760 +1,1755 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include -#include -#include -#include -#include #include #include #include -#include namespace sauce::editor { -static constexpr uint32_t EDITOR_WIDTH = 1920; -static constexpr uint32_t EDITOR_HEIGHT = 1080; - -EditorApp::EditorApp() { - sauce::Log::init(); - settingsManager.load(); - sauce::Log::setPalantirMode(settingsManager.get().palantirMode); - SAUCE_LOG("Editor", "SauceEditor starting up"); - - settingsManager.setOnChangeCallback([this](const sauce::EditorSettings& s) { - applySettings(s); - }); -} + static constexpr uint32_t EDITOR_WIDTH = 1920; + static constexpr uint32_t EDITOR_HEIGHT = 1080; -EditorApp::~EditorApp() { - SAUCE_LOG("Editor", "SauceEditor shutting down"); - - // Stop play mode if active - if (playModeActive) { - stopPlayMode(); - } - - if (pRenderer) { - logicalDevice->waitIdle(); - } - - settingsWindow.reset(); - hierarchyPanel.reset(); - inspectorPanel.reset(); - viewportPanel.reset(); - assetBrowserPanel.reset(); - - pGizmoRenderer.reset(); - pGridPipeline.reset(); - pUnlitPipeline.reset(); - pLitPipeline.reset(); - pOffscreenFB.reset(); - - pImGuiRenderer.reset(); - pRenderer.reset(); - pScene.reset(); - - if (window) { - glfwDestroyWindow(window); - glfwTerminate(); - } - - sauce::Log::shutdown(); -} - -void EditorApp::run() { - initWindow(); - initVulkan(); - initEditor(); - - settingsWindow = std::make_unique(settingsManager); - applySettings(settingsManager.get()); - - mainLoop(); -} - -void EditorApp::initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - window = glfwCreateWindow(EDITOR_WIDTH, EDITOR_HEIGHT, "SauceEditor", nullptr, nullptr); - - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - glfwSetMouseButtonCallback(window, mouseButtonCallback); - glfwSetCursorPosCallback(window, cursorPosCallback); - glfwSetScrollCallback(window, scrollCallback); - glfwSetKeyCallback(window, keyCallback); - glfwSetDropCallback(window, dropCallback); - - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); -} - -void EditorApp::framebufferResizeCallback(GLFWwindow* window, int /*width*/, int /*height*/) { - auto* app = static_cast(glfwGetWindowUserPointer(window)); - if (app && app->pRenderer) { - app->pRenderer->setFramebufferResized(); - } -} - -void EditorApp::initVulkan() { - uint32_t glfwExtensionsCount = 0; - const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionsCount); - pInstance = std::make_unique(glfwExtensions, glfwExtensionsCount); - - pRenderSurface = std::make_unique(*pInstance, window); - - physicalDevice = { *pInstance }; - logicalDevice = { physicalDevice, *pRenderSurface }; - - sauce::CameraCreateInfo cameraCreateInfo { - .scrWidth = static_cast(EDITOR_WIDTH), - .scrHeight = static_cast(EDITOR_HEIGHT), - }; - - pScene = std::make_unique(cameraCreateInfo); - - sauce::RendererCreateInfo rendererCreateInfo { - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .renderSurface = *pRenderSurface, - .window = window, - }; - - pRenderer = std::make_unique(rendererCreateInfo); - - sauce::ImGuiRendererCreateInfo imguiCreateInfo { - .instance = **pInstance, - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .queueFamilyIndex = logicalDevice.getQueueIndex(), - .window = window, - .queue = pRenderer->getQueue(), - .commandPool = pRenderer->getCommandPool(), - .swapChain = pRenderer->getSwapChain(), - .imageCount = static_cast(pRenderer->getSwapChain().getImages().size()), - .swapChainFormat = pRenderer->getSwapChain().getSurfaceFormat().format, - .depthFormat = sauce::GraphicsPipeline::findDepthFormat(physicalDevice), - }; - - pImGuiRenderer = std::make_unique(imguiCreateInfo); - - // Enable docking - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - setupEditorTheme(); - - // Create offscreen framebuffer for viewport rendering - pOffscreenFB = std::make_unique( - physicalDevice, logicalDevice, 800, 600 - ); - - // Create grid pipeline (no vertex input, alpha blending, no culling, no depth write) - sauce::GraphicsPipelineConfig gridConfig { - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .descriptorSetLayouts = { *pRenderer->getDescriptorSetLayout0() }, - .colorFormat = OffscreenFramebuffer::COLOR_FORMAT, - .hasVertexInput = false, - .enableBlending = true, - .enableCulling = false, - .depthWrite = false, - .hasPushConstants = false, - .pushConstantSize = 0, - }; - pGridPipeline = std::make_unique( - physicalDevice, logicalDevice, - gridConfig.descriptorSetLayouts, - OffscreenFramebuffer::COLOR_FORMAT, - "shaders/editor_grid.vert.spv", - "shaders/editor_grid.frag.spv", - gridConfig - ); - - // Create unlit pipeline (vertex input, no blending, culling, depth write, push constants) - sauce::GraphicsPipelineConfig unlitConfig { - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .descriptorSetLayouts = { *pRenderer->getDescriptorSetLayout0() }, - .colorFormat = OffscreenFramebuffer::COLOR_FORMAT, - .hasVertexInput = true, - .enableBlending = false, - .enableCulling = true, - .depthWrite = true, - .hasPushConstants = true, - .pushConstantSize = sizeof(MeshPushConstants), - }; - pUnlitPipeline = std::make_unique( - physicalDevice, logicalDevice, - unlitConfig.descriptorSetLayouts, - OffscreenFramebuffer::COLOR_FORMAT, - "shaders/editor_unlit.vert.spv", - "shaders/editor_unlit.frag.spv", - unlitConfig - ); - - // Create lit pipeline (same config, PBR shaders) - sauce::GraphicsPipelineConfig litConfig { - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .descriptorSetLayouts = { *pRenderer->getDescriptorSetLayout0() }, - .colorFormat = OffscreenFramebuffer::COLOR_FORMAT, - .hasVertexInput = true, - .enableBlending = false, - .enableCulling = true, - .depthWrite = true, - .hasPushConstants = true, - .pushConstantSize = sizeof(MeshPushConstants), - }; - pLitPipeline = std::make_unique( - physicalDevice, logicalDevice, - litConfig.descriptorSetLayouts, - OffscreenFramebuffer::COLOR_FORMAT, - "shaders/editor_lit.vert.spv", - "shaders/editor_lit.frag.spv", - litConfig - ); - - // Create gizmo renderer - pGizmoRenderer = std::make_unique( - physicalDevice, logicalDevice, - pRenderer->getDescriptorSetLayout0(), - OffscreenFramebuffer::COLOR_FORMAT, - *pRenderer - ); - - // Set up custom command buffer recording for the editor - pRenderer->setCommandBufferRecorder( - [this](vk::raii::CommandBuffer& cmd, uint32_t imageIndex) { - recordEditorCommandBuffer(cmd, imageIndex); - } - ); -} - -void EditorApp::setupEditorTheme() { - ImGuiStyle& style = ImGui::GetStyle(); - - // Rounding - style.WindowRounding = 4.0f; - style.ChildRounding = 2.0f; - style.FrameRounding = 3.0f; - style.PopupRounding = 3.0f; - style.ScrollbarRounding = 3.0f; - style.GrabRounding = 2.0f; - style.TabRounding = 3.0f; - - // Spacing - style.WindowPadding = ImVec2(8, 8); - style.FramePadding = ImVec2(6, 4); - style.ItemSpacing = ImVec2(8, 4); - style.ItemInnerSpacing = ImVec2(4, 4); - style.IndentSpacing = 16.0f; - - // Borders - style.WindowBorderSize = 1.0f; - style.ChildBorderSize = 1.0f; - style.FrameBorderSize = 0.0f; - style.TabBorderSize = 0.0f; - - style.ScrollbarSize = 12.0f; - style.GrabMinSize = 8.0f; - - ImVec4* colors = style.Colors; - - // Background - colors[ImGuiCol_WindowBg] = ImVec4(0.12f, 0.12f, 0.14f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.12f, 0.12f, 0.14f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.10f, 0.10f, 0.12f, 0.96f); - - // Borders - colors[ImGuiCol_Border] = ImVec4(0.22f, 0.22f, 0.26f, 1.00f); - colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - - // Frame (input fields, checkboxes) - colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.22f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.24f, 0.24f, 0.30f, 1.00f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.30f, 0.30f, 0.38f, 1.00f); - - // Title bar - colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.09f, 0.11f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.12f, 0.12f, 0.16f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.09f, 0.09f, 0.11f, 0.75f); - - // Menu bar - colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.16f, 1.00f); - - // Scrollbar - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.28f, 0.28f, 0.32f, 1.00f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.36f, 0.36f, 0.42f, 1.00f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.44f, 0.44f, 0.52f, 1.00f); - - // Buttons - colors[ImGuiCol_Button] = ImVec4(0.22f, 0.22f, 0.28f, 1.00f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.32f, 0.32f, 0.42f, 1.00f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.38f, 0.38f, 0.50f, 1.00f); - - // Headers (collapsing headers, tree nodes) - colors[ImGuiCol_Header] = ImVec4(0.20f, 0.20f, 0.26f, 1.00f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.28f, 0.38f, 1.00f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.34f, 0.34f, 0.46f, 1.00f); - - // Separator - colors[ImGuiCol_Separator] = ImVec4(0.22f, 0.22f, 0.26f, 1.00f); - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.40f, 0.55f, 0.80f, 0.78f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.55f, 0.80f, 1.00f); - - // Resize grip - colors[ImGuiCol_ResizeGrip] = ImVec4(0.30f, 0.30f, 0.40f, 0.30f); - colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.40f, 0.55f, 0.80f, 0.67f); - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.40f, 0.55f, 0.80f, 0.95f); - - // Tabs - colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.14f, 0.18f, 1.00f); - colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.28f, 0.38f, 1.00f); - colors[ImGuiCol_TabSelected] = ImVec4(0.22f, 0.22f, 0.30f, 1.00f); - colors[ImGuiCol_TabDimmed] = ImVec4(0.10f, 0.10f, 0.14f, 1.00f); - colors[ImGuiCol_TabDimmedSelected] = ImVec4(0.16f, 0.16f, 0.22f, 1.00f); - - // Docking - colors[ImGuiCol_DockingPreview] = ImVec4(0.40f, 0.55f, 0.80f, 0.70f); - colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f); - - // Text - colors[ImGuiCol_Text] = ImVec4(0.88f, 0.88f, 0.90f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.46f, 0.46f, 0.50f, 1.00f); - - // Selection / Highlight - colors[ImGuiCol_CheckMark] = ImVec4(0.45f, 0.65f, 0.95f, 1.00f); - colors[ImGuiCol_SliderGrab] = ImVec4(0.40f, 0.55f, 0.80f, 1.00f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.50f, 0.65f, 0.90f, 1.00f); - - // Drag drop - colors[ImGuiCol_DragDropTarget] = ImVec4(0.45f, 0.65f, 0.95f, 0.90f); - - // Nav - colors[ImGuiCol_NavHighlight] = ImVec4(0.40f, 0.55f, 0.80f, 1.00f); -} - -void EditorApp::initEditor() { - editorCamera.setScreenSize(EDITOR_WIDTH, EDITOR_HEIGHT); - - hierarchyPanel = std::make_unique(*this); - inspectorPanel = std::make_unique(*this); - viewportPanel = std::make_unique(*this); - assetBrowserPanel = std::make_unique(*this); - - // Seed scene with a few test entities - sauce::Entity cameraEntity("Main Camera"); - cameraEntity.addComponent(); - pScene->addEntity(std::move(cameraEntity)); - - sauce::Entity lightEntity("Directional Light"); - lightEntity.addComponent(); - pScene->addEntity(std::move(lightEntity)); - - // Load initial scene file if specified via command line - if (!initialSceneFile.empty()) { - openScene(initialSceneFile); - } else { - setStatusMessage("SauceEditor ready"); - } -} - -void EditorApp::importGLTFToScene(const std::string& path) { - try { - size_t before = pScene->getEntities().size(); - pScene->loadGLTFModel(path, true); - size_t added = pScene->getEntities().size() - before; - setStatusMessage("Imported " + std::filesystem::path(path).filename().string() + - " (" + std::to_string(added) + " entities)"); - } catch (const std::exception& e) { - setStatusMessage("Import failed: " + std::string(e.what())); - } -} - -void EditorApp::replaceModelOnComponent(MeshRendererComponent& mrc, const std::string& path) { - try { - sauce::modeling::GLTFLoader loader; - auto model = loader.loadModel(path); - if (!model) { - setStatusMessage("Failed to load: " + std::filesystem::path(path).filename().string()); - return; - } - auto pairs = model->getAllMeshMaterialPairs(); - if (pairs.empty()) { - setStatusMessage("No meshes in: " + std::filesystem::path(path).filename().string()); - return; - } + EditorApp::EditorApp() { + sauce::Log::init(); + settingsManager.load(); + sauce::Log::setPalantirMode(settingsManager.get().palantirMode); + SAUCE_LOG("Editor", "SauceEditor starting up"); - logicalDevice->waitIdle(); - mrc.setMesh(pairs[0].mesh); - mrc.setMaterial(pairs[0].material); - mrc.setModelPath(path); - setStatusMessage("Changed model to: " + std::filesystem::path(path).filename().string()); - } catch (const std::exception& e) { - setStatusMessage("Model change failed: " + std::string(e.what())); - } -} - -void EditorApp::clearModelOnComponent(MeshRendererComponent& mrc) { - logicalDevice->waitIdle(); - mrc.setMesh(nullptr); - mrc.setMaterial(nullptr); - mrc.setModelPath(""); - setStatusMessage("Cleared mesh"); -} - -void EditorApp::openScene(const std::string& path) { - try { - logicalDevice->waitIdle(); - selectionManager.deselect(); - if (pScene->loadFromFile(path)) { - setStatusMessage("Opened: " + std::filesystem::path(path).filename().string()); - } else { - setStatusMessage("Failed to open scene: " + path); - } - } catch (const std::exception& e) { - setStatusMessage("Open failed: " + std::string(e.what())); - } -} - -void EditorApp::saveScene() { - if (pScene->hasFilePath()) { - saveSceneAs(pScene->getCurrentFilePath()); - } else { - // Open Save As dialog - std::string defaultPath = (std::filesystem::current_path() / "assets" / "scene.gltf").string(); - std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); - dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; - showSaveAsDialog = true; - } -} - -void EditorApp::saveSceneAs(const std::string& path) { - try { - if (pScene->saveToFile(path)) { - setStatusMessage("Saved: " + std::filesystem::path(path).filename().string()); - } else { - setStatusMessage("Failed to save scene: " + path); + settingsManager.setOnChangeCallback( + [this](const sauce::EditorSettings& s) { applySettings(s); }); } - } catch (const std::exception& e) { - setStatusMessage("Save failed: " + std::string(e.what())); - } -} - -void EditorApp::uploadMeshGPUResources() { - for (auto& entity : pScene->getEntitiesMut()) { - auto mrcs = entity.getComponents(); - for (auto* mrc : mrcs) { - auto mesh = mrc->getMesh(); - if (!mesh || !mesh->isValid()) continue; - if (!mesh->hasGPUData()) { - auto& physDev = const_cast(*physicalDevice); - auto& cmdPool = const_cast(pRenderer->getCommandPool()); - auto& queue = const_cast(pRenderer->getQueue()); - mesh->initVulkanResources(logicalDevice, physDev, cmdPool, queue); - } - } - } -} - -void EditorApp::pickEntityAtScreen(float windowX, float windowY) { - auto* vp = static_cast(viewportPanel.get()); - if (!vp) return; - ImVec2 vpPos = vp->getViewportScreenPos(); - ImVec2 vpSize = vp->getViewportSize(); - if (vpSize.x <= 0 || vpSize.y <= 0) return; + EditorApp::~EditorApp() { + SAUCE_LOG("Editor", "SauceEditor shutting down"); - float localX = windowX - vpPos.x; - float localY = windowY - vpPos.y; + // Stop play mode if active + if (playModeActive) { + stopPlayMode(); + } - // Check if click is within viewport bounds - if (localX < 0 || localY < 0 || localX > vpSize.x || localY > vpSize.y) return; + if (pRenderer) { + logicalDevice->waitIdle(); + } - Ray ray = editorCamera.screenToWorldRay(localX, localY, vpSize.x, vpSize.y); + settingsWindow.reset(); + hierarchyPanel.reset(); + inspectorPanel.reset(); + viewportPanel.reset(); + assetBrowserPanel.reset(); + + pGizmoRenderer.reset(); + pGridPipeline.reset(); + pUnlitPipeline.reset(); + pLitPipeline.reset(); + pOffscreenFB.reset(); + + pImGuiRenderer.reset(); + pRenderer.reset(); + pScene.reset(); + + if (window) { + glfwDestroyWindow(window); + glfwTerminate(); + } - int bestIdx = -1; - float bestDist = std::numeric_limits::max(); + sauce::Log::shutdown(); + } - auto& entities = pScene->getEntitiesMut(); - for (int i = 0; i < static_cast(entities.size()); ++i) { - auto& entity = entities[i]; - if (!entity.getActive()) continue; + void EditorApp::run() { + initWindow(); + initVulkan(); + initEditor(); - auto mrcs = entity.getComponents(); - if (mrcs.empty()) continue; + settingsWindow = std::make_unique(settingsManager); + applySettings(settingsManager.get()); - glm::mat4 modelMatrix = glm::mat4(1.0f); - auto* tc = entity.getComponent(); - if (tc) { - modelMatrix = tc->getLocalMatrix(); + mainLoop(); } - for (auto* mrc : mrcs) { - auto mesh = mrc->getMesh(); - if (!mesh || !mesh->isValid()) continue; + void EditorApp::initWindow() { + glfwInit(); - AABB localAABB = AABB::fromVertices(mesh->getVertices()); - AABB worldAABB = localAABB.transformed(modelMatrix); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - float t = 0.0f; - if (rayIntersectsAABB(ray.origin, ray.direction, worldAABB, t)) { - if (t < bestDist) { - bestDist = t; - bestIdx = i; - } - } - } - } - - if (bestIdx >= 0) { - selectionManager.select(bestIdx); - setStatusMessage("Selected: " + entities[bestIdx].get_name()); - } else { - selectionManager.deselect(); - } -} - -void EditorApp::recordEditorCommandBuffer(vk::raii::CommandBuffer& cmd, uint32_t imageIndex) { - cmd.begin({}); - - // Update UBO with camera matrices - use EditorCamera directly (bypasses Scene Camera) - float aspect = static_cast(pOffscreenFB->getWidth()) / - static_cast(pOffscreenFB->getHeight()); - sauce::UniformBufferObject ubo { - .model = glm::mat4(1.0f), // identity for grid - .view = editorCamera.getViewMatrix(), - .proj = editorCamera.getProjectionMatrix(aspect), - .cameraPos = editorCamera.getPosition(), - }; - ubo.proj[1][1] *= -1; // Vulkan Y-flip - memcpy(pRenderer->getCurrentUniformBufferMapped(), &ubo, sizeof(ubo)); - - // ======================== - // PASS 1: Render scene to offscreen framebuffer - // ======================== - - // Transition offscreen color image -> ColorAttachmentOptimal - pRenderer->transitionImageLayout( - cmd, - *pOffscreenFB->getColorImage(), - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - - // Transition offscreen depth image -> DepthAttachmentOptimal - pRenderer->transitionImageLayout( - cmd, - *pOffscreenFB->getDepthImage(), - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - {}, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - vk::ClearValue clearColor = vk::ClearColorValue { 0.12f, 0.12f, 0.15f, 1.0f }; - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - vk::RenderingAttachmentInfo offscreenColorAttachment { - .imageView = pOffscreenFB->getColorImageView(), - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor, - }; - - vk::RenderingAttachmentInfo offscreenDepthAttachment { - .imageView = pOffscreenFB->getDepthImageView(), - .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearDepth, - }; - - vk::Extent2D offscreenExtent { pOffscreenFB->getWidth(), pOffscreenFB->getHeight() }; - - vk::RenderingInfo offscreenRenderingInfo { - .renderArea = { .offset = { 0, 0 }, .extent = offscreenExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &offscreenColorAttachment, - .pDepthAttachment = &offscreenDepthAttachment, - }; - - cmd.beginRendering(offscreenRenderingInfo); - - cmd.setViewport(0, vk::Viewport(0.0f, 0.0f, - static_cast(offscreenExtent.width), - static_cast(offscreenExtent.height), - 0.0f, 1.0f)); - cmd.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), offscreenExtent)); - - // Draw grid - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, **pGridPipeline); - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - pGridPipeline->getLayout(), 0, { *pRenderer->getCurrentDescriptorSet() }, nullptr); - cmd.draw(6, 1, 0, 0); // Fullscreen quad (6 vertices, no vertex buffer) - - // Draw scene meshes with active pipeline (unlit or lit based on viewport mode) - auto* activePipeline = (viewportMode == ViewportMode::Lit) ? pLitPipeline.get() : pUnlitPipeline.get(); - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, **activePipeline); - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - activePipeline->getLayout(), 0, { *pRenderer->getCurrentDescriptorSet() }, nullptr); - - for (auto& entity : pScene->getEntitiesMut()) { - if (!entity.getActive()) continue; - - auto mrcs = entity.getComponents(); - if (mrcs.empty()) continue; - - // Get transform for this entity - glm::mat4 modelMatrix = glm::mat4(1.0f); - auto* tc = entity.getComponent(); - if (tc) { - modelMatrix = tc->getLocalMatrix(); - } + window = glfwCreateWindow(EDITOR_WIDTH, EDITOR_HEIGHT, "SauceEditor", nullptr, nullptr); - for (auto* mrc : mrcs) { - auto mesh = mrc->getMesh(); - if (!mesh || !mesh->hasGPUData()) continue; - - // Build push constants with material data - MeshPushConstants pushData {}; - pushData.model = modelMatrix; - pushData.baseColor = glm::vec4(0.7f, 0.7f, 0.7f, 1.0f); // default grey - pushData.metallic = 0.0f; - pushData.roughness = 0.5f; - - auto material = mrc->getMaterial(); - if (material) { - auto& props = material->getProperties(); - pushData.baseColor = props.baseColorFactor; - pushData.metallic = props.metallicFactor; - pushData.roughness = props.roughnessFactor; - } - - cmd.pushConstants(activePipeline->getLayout(), - vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, pushData); - - auto& cmdRef = const_cast(cmd); - mesh->bind(cmdRef); - mesh->draw(cmdRef); - } - } - - // Draw gizmo for selected entity - if (selectionManager.hasSelection() && pGizmoRenderer) { - auto* entity = selectionManager.getSelectedEntity(*pScene); - if (entity) { - auto* tc = entity->getComponent(); - if (tc) { - pGizmoRenderer->render(cmd, pRenderer->getCurrentDescriptorSet(), - tc->getTranslation(), editorCamera, aspect); - } - } - } - - cmd.endRendering(); - - // Transition offscreen color image -> ShaderReadOnlyOptimal for ImGui sampling - pRenderer->transitionImageLayout( - cmd, - *pOffscreenFB->getColorImage(), - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::AccessFlagBits2::eShaderRead, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eFragmentShader, - vk::ImageAspectFlagBits::eColor - ); - - // ======================== - // PASS 2: Render ImGui to swapchain - // ======================== - - // Transition swapchain image -> ColorAttachmentOptimal - pRenderer->transitionImageLayout( - cmd, - pRenderer->getSwapChain().getImages()[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - - // Transition renderer's depth image for the ImGui pass (ImGui pipeline expects depth format) - pRenderer->transitionImageLayout( - cmd, - *pRenderer->getDepthImage(), - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - {}, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - vk::ClearValue swapClearColor = vk::ClearColorValue { 0.0f, 0.0f, 0.0f, 1.0f }; - vk::ClearValue swapClearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - vk::RenderingAttachmentInfo swapColorAttachment { - .imageView = pRenderer->getSwapChain().getImageViews()[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = swapClearColor, - }; - - vk::RenderingAttachmentInfo swapDepthAttachment { - .imageView = pRenderer->getDepthImageView(), - .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = swapClearDepth, - }; - - vk::RenderingInfo swapRenderingInfo { - .renderArea = { - .offset = { 0, 0 }, - .extent = pRenderer->getSwapChain().getExtent(), - }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &swapColorAttachment, - .pDepthAttachment = &swapDepthAttachment, - }; - - cmd.beginRendering(swapRenderingInfo); - - // Render ImGui (which includes the viewport panel showing the offscreen texture) - if (pImGuiRenderer) { - pImGuiRenderer->render(cmd, imageIndex); - } - - cmd.endRendering(); - - // Transition swapchain image -> PresentSrcKHR - pRenderer->transitionImageLayout( - cmd, - pRenderer->getSwapChain().getImages()[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe, - vk::ImageAspectFlagBits::eColor - ); - - cmd.end(); -} - -void EditorApp::mainLoop() { - while (!glfwWindowShouldClose(window)) { - auto currentFrameTime = std::chrono::steady_clock::now(); - deltaTime = std::chrono::duration(currentFrameTime - lastFrameTime).count(); - lastFrameTime = currentFrameTime; - - glfwPollEvents(); - processInput(); - - editorCamera.update(deltaTime); - editorCamera.syncToSceneCamera(pScene->getCameraRW()); - - // Handle viewport resize - if (viewportPanel) { - auto* vp = static_cast(viewportPanel.get()); - if (vp->viewportSizeChanged()) { - ImVec2 size = vp->getViewportSize(); - uint32_t w = static_cast(size.x); - uint32_t h = static_cast(size.y); - if (w > 0 && h > 0) { - logicalDevice->waitIdle(); - pOffscreenFB->resize(w, h); - editorCamera.setScreenSize(static_cast(w), static_cast(h)); - } - vp->clearSizeChanged(); - } + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + glfwSetMouseButtonCallback(window, mouseButtonCallback); + glfwSetCursorPosCallback(window, cursorPosCallback); + glfwSetScrollCallback(window, scrollCallback); + glfwSetKeyCallback(window, keyCallback); + glfwSetDropCallback(window, dropCallback); + + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } - // Check if play mode process has exited on its own - if (playModeActive && playProcessPid > 0) { - int status; - pid_t result = waitpid(playProcessPid, &status, WNOHANG); - if (result != 0) { - // Process has exited - playProcessPid = -1; - playModeActive = false; - if (!playModeTempFile.empty()) { - std::error_code ec2; - std::filesystem::remove(playModeTempFile, ec2); - playModeTempFile.clear(); + void EditorApp::framebufferResizeCallback(GLFWwindow* window, int /*width*/, int /*height*/) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + if (app && app->pRenderer) { + app->pRenderer->setFramebufferResized(); } - setStatusMessage("Play mode ended"); - } } - // Upload mesh GPU resources for any newly imported models - uploadMeshGPUResources(); - - pImGuiRenderer->newFrame(); - buildEditorUI(); - - pRenderer->drawFrame(logicalDevice, *pScene, pImGuiRenderer.get()); - } - - logicalDevice->waitIdle(); -} - -void EditorApp::processInput() { - // Always allow fly mode WASD when in fly mode (cursor is captured) - if (editorCamera.getMode() == EditorCamera::Mode::Fly) { - if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) - editorCamera.flyMoveForward(deltaTime); - if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) - editorCamera.flyMoveBackward(deltaTime); - if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) - editorCamera.flyMoveLeft(deltaTime); - if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) - editorCamera.flyMoveRight(deltaTime); - return; - } - - // In orbit mode, only process input when viewport hovered - if (!viewportHovered || ImGui::GetIO().WantCaptureKeyboard) { - return; - } -} - -void EditorApp::buildEditorUI() { - // Create full-screen dockspace - ImGuiViewport* vp = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(vp->WorkPos); - ImGui::SetNextWindowSize(vp->WorkSize); - ImGui::SetNextWindowViewport(vp->ID); - - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; - windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse; - windowFlags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; - windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; - windowFlags |= ImGuiWindowFlags_NoBackground; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - - ImGui::Begin("DockSpace", nullptr, windowFlags); - ImGui::PopStyleVar(3); - - ImGuiID dockspaceId = ImGui::GetID("EditorDockSpace"); - - if (firstFrame) { - setupDefaultDockLayout(dockspaceId); - firstFrame = false; - } - - ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); - - // Menu bar - if (ImGui::BeginMenuBar()) { - if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("New Scene", "Ctrl+N")) { - logicalDevice->waitIdle(); - pScene->getEntitiesMut().clear(); - pScene->setCurrentFilePath(""); - selectionManager.deselect(); - setStatusMessage("New scene created"); - } - if (ImGui::MenuItem("Open Scene...", "Ctrl+O")) { - std::string defaultPath = pScene->hasFilePath() - ? pScene->getCurrentFilePath() - : (std::filesystem::current_path() / "assets" / "scene.gltf").string(); - std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); - dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; - showOpenDialog = true; - } - ImGui::Separator(); - if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { - saveScene(); - } - if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S")) { - std::string defaultPath = pScene->hasFilePath() - ? pScene->getCurrentFilePath() - : (std::filesystem::current_path() / "assets" / "scene.gltf").string(); - std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); - dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; - showSaveAsDialog = true; - } - - ImGui::Separator(); - - if (ImGui::MenuItem("Export Scene as ZIP...")) { - std::string defaultPath = (std::filesystem::current_path() / "scene_export.zip").string(); - std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); - dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; - showExportZipDialog = true; + void EditorApp::initVulkan() { + uint32_t glfwExtensionsCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionsCount); + pInstance = std::make_unique(glfwExtensions, glfwExtensionsCount); + + pRenderSurface = std::make_unique(*pInstance, window); + + physicalDevice = {*pInstance}; + logicalDevice = {physicalDevice, *pRenderSurface}; + + sauce::CameraCreateInfo cameraCreateInfo{ + .scrWidth = static_cast(EDITOR_WIDTH), + .scrHeight = static_cast(EDITOR_HEIGHT), + }; + + pScene = std::make_unique(cameraCreateInfo); + + sauce::RendererCreateInfo rendererCreateInfo{ + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .renderSurface = *pRenderSurface, + .window = window, + }; + + pRenderer = std::make_unique(rendererCreateInfo); + + sauce::ImGuiRendererCreateInfo imguiCreateInfo{ + .instance = **pInstance, + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .queueFamilyIndex = logicalDevice.getQueueIndex(), + .window = window, + .queue = pRenderer->getQueue(), + .commandPool = pRenderer->getCommandPool(), + .swapChain = pRenderer->getSwapChain(), + .imageCount = static_cast(pRenderer->getSwapChain().getImages().size()), + .swapChainFormat = pRenderer->getSwapChain().getSurfaceFormat().format, + .depthFormat = sauce::GraphicsPipeline::findDepthFormat(physicalDevice), + }; + + pImGuiRenderer = std::make_unique(imguiCreateInfo); + + // Enable docking + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + setupEditorTheme(); + + // Create offscreen framebuffer for viewport rendering + pOffscreenFB = + std::make_unique(physicalDevice, logicalDevice, 800, 600); + + // Create grid pipeline (no vertex input, alpha blending, no culling, no depth write) + sauce::GraphicsPipelineConfig gridConfig{ + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .descriptorSetLayouts = {*pRenderer->getDescriptorSetLayout0()}, + .colorFormat = OffscreenFramebuffer::COLOR_FORMAT, + .hasVertexInput = false, + .enableBlending = true, + .enableCulling = false, + .depthWrite = false, + .hasPushConstants = false, + .pushConstantSize = 0, + }; + pGridPipeline = std::make_unique( + physicalDevice, logicalDevice, gridConfig.descriptorSetLayouts, + OffscreenFramebuffer::COLOR_FORMAT, "shaders/editor_grid.vert.spv", + "shaders/editor_grid.frag.spv", gridConfig); + + // Create unlit pipeline (vertex input, no blending, culling, depth write, push constants) + sauce::GraphicsPipelineConfig unlitConfig{ + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .descriptorSetLayouts = {*pRenderer->getDescriptorSetLayout0()}, + .colorFormat = OffscreenFramebuffer::COLOR_FORMAT, + .hasVertexInput = true, + .enableBlending = false, + .enableCulling = true, + .depthWrite = true, + .hasPushConstants = true, + .pushConstantSize = sizeof(MeshPushConstants), + }; + pUnlitPipeline = std::make_unique( + physicalDevice, logicalDevice, unlitConfig.descriptorSetLayouts, + OffscreenFramebuffer::COLOR_FORMAT, "shaders/editor_unlit.vert.spv", + "shaders/editor_unlit.frag.spv", unlitConfig); + + // Create lit pipeline (same config, PBR shaders) + sauce::GraphicsPipelineConfig litConfig{ + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .descriptorSetLayouts = {*pRenderer->getDescriptorSetLayout0()}, + .colorFormat = OffscreenFramebuffer::COLOR_FORMAT, + .hasVertexInput = true, + .enableBlending = false, + .enableCulling = true, + .depthWrite = true, + .hasPushConstants = true, + .pushConstantSize = sizeof(MeshPushConstants), + }; + pLitPipeline = std::make_unique( + physicalDevice, logicalDevice, litConfig.descriptorSetLayouts, + OffscreenFramebuffer::COLOR_FORMAT, "shaders/editor_lit.vert.spv", + "shaders/editor_lit.frag.spv", litConfig); + + // Create gizmo renderer + pGizmoRenderer = std::make_unique( + physicalDevice, logicalDevice, pRenderer->getDescriptorSetLayout0(), + OffscreenFramebuffer::COLOR_FORMAT, *pRenderer); + + // Set up custom command buffer recording for the editor + pRenderer->setCommandBufferRecorder( + [this](vk::raii::CommandBuffer& cmd, uint32_t imageIndex) { + recordEditorCommandBuffer(cmd, imageIndex); + }); } - if (ImGui::MenuItem("Import Scene from ZIP...")) { - std::string defaultPath = (std::filesystem::current_path() / "scene_import.zip").string(); - std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); - dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; - showImportZipDialog = true; + void EditorApp::setupEditorTheme() { + ImGuiStyle& style = ImGui::GetStyle(); + + // Rounding + style.WindowRounding = 4.0f; + style.ChildRounding = 2.0f; + style.FrameRounding = 3.0f; + style.PopupRounding = 3.0f; + style.ScrollbarRounding = 3.0f; + style.GrabRounding = 2.0f; + style.TabRounding = 3.0f; + + // Spacing + style.WindowPadding = ImVec2(8, 8); + style.FramePadding = ImVec2(6, 4); + style.ItemSpacing = ImVec2(8, 4); + style.ItemInnerSpacing = ImVec2(4, 4); + style.IndentSpacing = 16.0f; + + // Borders + style.WindowBorderSize = 1.0f; + style.ChildBorderSize = 1.0f; + style.FrameBorderSize = 0.0f; + style.TabBorderSize = 0.0f; + + style.ScrollbarSize = 12.0f; + style.GrabMinSize = 8.0f; + + ImVec4* colors = style.Colors; + + // Background + colors[ImGuiCol_WindowBg] = ImVec4(0.12f, 0.12f, 0.14f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.12f, 0.12f, 0.14f, 1.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.10f, 0.10f, 0.12f, 0.96f); + + // Borders + colors[ImGuiCol_Border] = ImVec4(0.22f, 0.22f, 0.26f, 1.00f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + + // Frame (input fields, checkboxes) + colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.22f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.24f, 0.24f, 0.30f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.30f, 0.30f, 0.38f, 1.00f); + + // Title bar + colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.09f, 0.11f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.12f, 0.12f, 0.16f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.09f, 0.09f, 0.11f, 0.75f); + + // Menu bar + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.16f, 1.00f); + + // Scrollbar + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.28f, 0.28f, 0.32f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.36f, 0.36f, 0.42f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.44f, 0.44f, 0.52f, 1.00f); + + // Buttons + colors[ImGuiCol_Button] = ImVec4(0.22f, 0.22f, 0.28f, 1.00f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.32f, 0.32f, 0.42f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.38f, 0.38f, 0.50f, 1.00f); + + // Headers (collapsing headers, tree nodes) + colors[ImGuiCol_Header] = ImVec4(0.20f, 0.20f, 0.26f, 1.00f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.28f, 0.38f, 1.00f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.34f, 0.34f, 0.46f, 1.00f); + + // Separator + colors[ImGuiCol_Separator] = ImVec4(0.22f, 0.22f, 0.26f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.40f, 0.55f, 0.80f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.55f, 0.80f, 1.00f); + + // Resize grip + colors[ImGuiCol_ResizeGrip] = ImVec4(0.30f, 0.30f, 0.40f, 0.30f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.40f, 0.55f, 0.80f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.40f, 0.55f, 0.80f, 0.95f); + + // Tabs + colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.14f, 0.18f, 1.00f); + colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.28f, 0.38f, 1.00f); + colors[ImGuiCol_TabSelected] = ImVec4(0.22f, 0.22f, 0.30f, 1.00f); + colors[ImGuiCol_TabDimmed] = ImVec4(0.10f, 0.10f, 0.14f, 1.00f); + colors[ImGuiCol_TabDimmedSelected] = ImVec4(0.16f, 0.16f, 0.22f, 1.00f); + + // Docking + colors[ImGuiCol_DockingPreview] = ImVec4(0.40f, 0.55f, 0.80f, 0.70f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f); + + // Text + colors[ImGuiCol_Text] = ImVec4(0.88f, 0.88f, 0.90f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.46f, 0.46f, 0.50f, 1.00f); + + // Selection / Highlight + colors[ImGuiCol_CheckMark] = ImVec4(0.45f, 0.65f, 0.95f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.40f, 0.55f, 0.80f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.50f, 0.65f, 0.90f, 1.00f); + + // Drag drop + colors[ImGuiCol_DragDropTarget] = ImVec4(0.45f, 0.65f, 0.95f, 0.90f); + + // Nav + colors[ImGuiCol_NavHighlight] = ImVec4(0.40f, 0.55f, 0.80f, 1.00f); } + void EditorApp::initEditor() { + editorCamera.setScreenSize(EDITOR_WIDTH, EDITOR_HEIGHT); + hierarchyPanel = std::make_unique(*this); + inspectorPanel = std::make_unique(*this); + viewportPanel = std::make_unique(*this); + assetBrowserPanel = std::make_unique(*this); - ImGui::Separator(); + // Seed scene with a few test entities + sauce::Entity cameraEntity("Main Camera"); + cameraEntity.addComponent(); + pScene->addEntity(std::move(cameraEntity)); - if (ImGui::MenuItem("Exit", "Esc")) { - glfwSetWindowShouldClose(window, true); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Edit")) { - if (ImGui::MenuItem("Deselect All", "Ctrl+D")) { - selectionManager.deselect(); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("View")) { - ImGui::MenuItem("Hierarchy", nullptr, &showHierarchy); - ImGui::MenuItem("Inspector", nullptr, &showInspector); - ImGui::MenuItem("Viewport", nullptr, &showViewport); - ImGui::MenuItem("Asset Browser", nullptr, &showAssetBrowser); - ImGui::MenuItem("Settings", nullptr, &showSettings); - ImGui::Separator(); - if (ImGui::MenuItem("Reset Layout")) { - firstFrame = true; // re-trigger layout on next frame - } - ImGui::EndMenu(); + sauce::Entity lightEntity("Directional Light"); + lightEntity.addComponent(); + pScene->addEntity(std::move(lightEntity)); + + // Load initial scene file if specified via command line + if (!initialSceneFile.empty()) { + openScene(initialSceneFile); + } else { + setStatusMessage("SauceEditor ready"); + } } - if (ImGui::BeginMenu("Entity")) { - if (ImGui::BeginMenu("Create")) { - if (ImGui::MenuItem("Empty Entity")) {createEmptyEntity();} - if (ImGui::MenuItem("Box")) {createBoxEntity();} - if (ImGui::MenuItem("Ball")) {createBallEntity();} - ImGui::EndMenu(); - } - ImGui::EndMenu(); + + void EditorApp::importGLTFToScene(const std::string& path) { + try { + size_t before = pScene->getEntities().size(); + pScene->loadGLTFModel(path, true); + size_t added = pScene->getEntities().size() - before; + setStatusMessage("Imported " + std::filesystem::path(path).filename().string() + " (" + + std::to_string(added) + " entities)"); + } catch (const std::exception& e) { + setStatusMessage("Import failed: " + std::string(e.what())); + } } - // Play mode controls - centered in menu bar - { - float menuBarWidth = ImGui::GetWindowWidth(); - float buttonWidth = 80.0f; - float totalWidth = playModeActive ? (buttonWidth * 1 + 8.0f) : buttonWidth; - float cursorX = (menuBarWidth - totalWidth) * 0.5f; - - // Ensure we don't overlap menus - if (cursorX < ImGui::GetCursorPosX()) - cursorX = ImGui::GetCursorPosX() + 20.0f; - - ImGui::SetCursorPosX(cursorX); - - if (!playModeActive) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.55f, 0.15f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.20f, 0.70f, 0.20f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.10f, 0.45f, 0.10f, 1.0f)); - if (ImGui::Button("Play", ImVec2(buttonWidth, 0))) { - startPlayMode(); - } - ImGui::PopStyleColor(3); - } else { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.70f, 0.15f, 0.15f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.85f, 0.20f, 0.20f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.55f, 0.10f, 0.10f, 1.0f)); - if (ImGui::Button("Stop", ImVec2(buttonWidth, 0))) { - stopPlayMode(); - } - ImGui::PopStyleColor(3); - } + void EditorApp::replaceModelOnComponent(MeshRendererComponent& mrc, const std::string& path) { + try { + sauce::modeling::GLTFLoader loader; + auto model = loader.loadModel(path); + if (!model) { + setStatusMessage("Failed to load: " + + std::filesystem::path(path).filename().string()); + return; + } + auto pairs = model->getAllMeshMaterialPairs(); + if (pairs.empty()) { + setStatusMessage("No meshes in: " + + std::filesystem::path(path).filename().string()); + return; + } + + logicalDevice->waitIdle(); + mrc.setMesh(pairs[0].mesh); + mrc.setMaterial(pairs[0].material); + mrc.setModelPath(path); + setStatusMessage("Changed model to: " + + std::filesystem::path(path).filename().string()); + } catch (const std::exception& e) { + setStatusMessage("Model change failed: " + std::string(e.what())); + } } - ImGui::EndMenuBar(); - } - - ImGui::End(); - - // Render panels - if (showHierarchy && hierarchyPanel) { - hierarchyPanel->render(); - } - if (showInspector && inspectorPanel) { - inspectorPanel->render(); - } - if (showViewport && viewportPanel) { - viewportPanel->render(); - viewportHovered = static_cast(viewportPanel.get())->isViewportHovered(); - viewportFocused = static_cast(viewportPanel.get())->isViewportFocused(); - } - if (showAssetBrowser && assetBrowserPanel) { - assetBrowserPanel->render(); - } - if (showSettings && settingsWindow) { - settingsWindow->setEnabled(true); - settingsWindow->render(); - // Sync back: if user closed the window via its X button - if (!settingsWindow->isEnabled()) { - showSettings = false; + void EditorApp::clearModelOnComponent(MeshRendererComponent& mrc) { + logicalDevice->waitIdle(); + mrc.setMesh(nullptr); + mrc.setMaterial(nullptr); + mrc.setModelPath(""); + setStatusMessage("Cleared mesh"); } - } - // Status bar at bottom - { - if (statusTimer > 0.0f) { - statusTimer -= deltaTime; + void EditorApp::openScene(const std::string& path) { + try { + logicalDevice->waitIdle(); + selectionManager.deselect(); + if (pScene->loadFromFile(path)) { + setStatusMessage("Opened: " + std::filesystem::path(path).filename().string()); + } else { + setStatusMessage("Failed to open scene: " + path); + } + } catch (const std::exception& e) { + setStatusMessage("Open failed: " + std::string(e.what())); + } } - float statusBarHeight = 24.0f; - ImGui::SetNextWindowPos(ImVec2(vp->WorkPos.x, vp->WorkPos.y + vp->WorkSize.y - statusBarHeight)); - ImGui::SetNextWindowSize(ImVec2(vp->WorkSize.x, statusBarHeight)); - ImGuiWindowFlags statusFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 3)); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10f, 0.10f, 0.12f, 1.00f)); - ImGui::Begin("##StatusBar", nullptr, statusFlags); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - - if (statusTimer > 0.0f && !statusMessage.empty()) { - ImGui::Text("%s", statusMessage.c_str()); - } else { - int entityCount = static_cast(pScene->getEntities().size()); - auto& cam = editorCamera; - glm::vec3 camPos = cam.getPosition(); - ImGui::Text("Entities: %d | Camera: (%.1f, %.1f, %.1f) | %s", - entityCount, camPos.x, camPos.y, camPos.z, - cam.getMode() == EditorCamera::Mode::Orbit ? "Orbit" : "Fly"); + void EditorApp::saveScene() { + if (pScene->hasFilePath()) { + saveSceneAs(pScene->getCurrentFilePath()); + } else { + // Open Save As dialog + std::string defaultPath = + (std::filesystem::current_path() / "assets" / "scene.gltf").string(); + std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); + dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; + showSaveAsDialog = true; + } } - ImGui::End(); - } - - // Open Scene dialog - if (showOpenDialog) { - ImGui::OpenPopup("Open Scene"); - showOpenDialog = false; - } - if (ImGui::BeginPopupModal("Open Scene", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("File path:"); - ImGui::SetNextItemWidth(400); - ImGui::InputText("##openpath", dialogPathBuf, sizeof(dialogPathBuf)); - ImGui::Spacing(); - if (ImGui::Button("Open", ImVec2(120, 0))) { - openScene(dialogPathBuf); - ImGui::CloseCurrentPopup(); + void EditorApp::saveSceneAs(const std::string& path) { + try { + if (pScene->saveToFile(path)) { + setStatusMessage("Saved: " + std::filesystem::path(path).filename().string()); + } else { + setStatusMessage("Failed to save scene: " + path); + } + } catch (const std::exception& e) { + setStatusMessage("Save failed: " + std::string(e.what())); + } } - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) { - ImGui::CloseCurrentPopup(); + + void EditorApp::uploadMeshGPUResources() { + for (auto& entity : pScene->getEntitiesMut()) { + auto mrcs = entity.getComponents(); + for (auto* mrc : mrcs) { + auto mesh = mrc->getMesh(); + if (!mesh || !mesh->isValid()) + continue; + if (!mesh->hasGPUData()) { + auto& physDev = const_cast(*physicalDevice); + auto& cmdPool = const_cast(pRenderer->getCommandPool()); + auto& queue = const_cast(pRenderer->getQueue()); + mesh->initVulkanResources(logicalDevice, physDev, cmdPool, queue); + } + } + } } - ImGui::EndPopup(); - } + void EditorApp::pickEntityAtScreen(float windowX, float windowY) { + auto* vp = static_cast(viewportPanel.get()); + if (!vp) + return; + + ImVec2 vpPos = vp->getViewportScreenPos(); + ImVec2 vpSize = vp->getViewportSize(); + if (vpSize.x <= 0 || vpSize.y <= 0) + return; + + float localX = windowX - vpPos.x; + float localY = windowY - vpPos.y; -// Export Scene as ZIP dialog -if (showExportZipDialog) { - ImGui::OpenPopup("Export Scene as ZIP"); - showExportZipDialog = false; -} + // Check if click is within viewport bounds + if (localX < 0 || localY < 0 || localX > vpSize.x || localY > vpSize.y) + return; -if (ImGui::BeginPopupModal("Export Scene as ZIP", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("ZIP file path:"); - ImGui::SetNextItemWidth(400); - ImGui::InputText("##exportzip", dialogPathBuf, sizeof(dialogPathBuf)); + Ray ray = editorCamera.screenToWorldRay(localX, localY, vpSize.x, vpSize.y); - ImGui::Spacing(); + int bestIdx = -1; + float bestDist = std::numeric_limits::max(); - if (ImGui::Button("Export", ImVec2(120, 0))) { - std::string zipPath = dialogPathBuf; + auto& entities = pScene->getEntitiesMut(); + for (int i = 0; i < static_cast(entities.size()); ++i) { + auto& entity = entities[i]; + if (!entity.getActive()) + continue; - // Collect asset paths (basic version) - std::vector assets; - for (auto& entity : pScene->getEntities()) { auto mrcs = entity.getComponents(); + if (mrcs.empty()) + continue; + + glm::mat4 modelMatrix = glm::mat4(1.0f); + auto* tc = entity.getComponent(); + if (tc) { + modelMatrix = tc->getLocalMatrix(); + } + for (auto* mrc : mrcs) { - if (!mrc->getModelPath().empty()) { - assets.push_back(mrc->getModelPath()); + auto mesh = mrc->getMesh(); + if (!mesh || !mesh->isValid()) + continue; + + AABB localAABB = AABB::fromVertices(mesh->getVertices()); + AABB worldAABB = localAABB.transformed(modelMatrix); + + float t = 0.0f; + if (rayIntersectsAABB(ray.origin, ray.direction, worldAABB, t)) { + if (t < bestDist) { + bestDist = t; + bestIdx = i; + } } } } - if (saveSceneToZip(zipPath, pScene->getCurrentFilePath(), assets)) { - setStatusMessage("Exported scene to ZIP"); - ImGui::CloseCurrentPopup(); + if (bestIdx >= 0) { + selectionManager.select(bestIdx); + setStatusMessage("Selected: " + entities[bestIdx].get_name()); + } else { + selectionManager.deselect(); } } - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) { - ImGui::CloseCurrentPopup(); - } + void EditorApp::recordEditorCommandBuffer(vk::raii::CommandBuffer& cmd, uint32_t imageIndex) { + cmd.begin({}); + + // Update UBO with camera matrices - use EditorCamera directly (bypasses Scene Camera) + float aspect = static_cast(pOffscreenFB->getWidth()) / + static_cast(pOffscreenFB->getHeight()); + sauce::UniformBufferObject ubo{ + .model = glm::mat4(1.0f), // identity for grid + .view = editorCamera.getViewMatrix(), + .proj = editorCamera.getProjectionMatrix(aspect), + .cameraPos = editorCamera.getPosition(), + }; + ubo.proj[1][1] *= -1; // Vulkan Y-flip + memcpy(pRenderer->getCurrentUniformBufferMapped(), &ubo, sizeof(ubo)); + + // ======================== + // PASS 1: Render scene to offscreen framebuffer + // ======================== + + // Transition offscreen color image -> ColorAttachmentOptimal + pRenderer->transitionImageLayout( + cmd, *pOffscreenFB->getColorImage(), vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::ImageAspectFlagBits::eColor); + + // Transition offscreen depth image -> DepthAttachmentOptimal + pRenderer->transitionImageLayout(cmd, *pOffscreenFB->getDepthImage(), + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, {}, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + vk::ClearValue clearColor = vk::ClearColorValue{0.12f, 0.12f, 0.15f, 1.0f}; + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderingAttachmentInfo offscreenColorAttachment{ + .imageView = pOffscreenFB->getColorImageView(), + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor, + }; + + vk::RenderingAttachmentInfo offscreenDepthAttachment{ + .imageView = pOffscreenFB->getDepthImageView(), + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearDepth, + }; + + vk::Extent2D offscreenExtent{pOffscreenFB->getWidth(), pOffscreenFB->getHeight()}; + + vk::RenderingInfo offscreenRenderingInfo{ + .renderArea = {.offset = {0, 0}, .extent = offscreenExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &offscreenColorAttachment, + .pDepthAttachment = &offscreenDepthAttachment, + }; + + cmd.beginRendering(offscreenRenderingInfo); + + cmd.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(offscreenExtent.width), + static_cast(offscreenExtent.height), 0.0f, 1.0f)); + cmd.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), offscreenExtent)); + + // Draw grid + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, **pGridPipeline); + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pGridPipeline->getLayout(), 0, + {*pRenderer->getCurrentDescriptorSet()}, nullptr); + cmd.draw(6, 1, 0, 0); // Fullscreen quad (6 vertices, no vertex buffer) + + // Draw scene meshes with active pipeline (unlit or lit based on viewport mode) + auto* activePipeline = + (viewportMode == ViewportMode::Lit) ? pLitPipeline.get() : pUnlitPipeline.get(); + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, **activePipeline); + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, activePipeline->getLayout(), 0, + {*pRenderer->getCurrentDescriptorSet()}, nullptr); + + for (auto& entity : pScene->getEntitiesMut()) { + if (!entity.getActive()) + continue; - ImGui::EndPopup(); -} -// Import Scene from ZIP dialog -if (showImportZipDialog) { - ImGui::OpenPopup("Import Scene from ZIP"); - showImportZipDialog = false; -} - -if (ImGui::BeginPopupModal("Import Scene from ZIP", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("ZIP file path:"); - ImGui::SetNextItemWidth(400); - ImGui::InputText("##importzip", dialogPathBuf, sizeof(dialogPathBuf)); - - ImGui::Spacing(); - - if (ImGui::Button("Import", ImVec2(120, 0))) { - loadSceneFromZip(dialogPathBuf); - setStatusMessage("Imported scene from ZIP"); - ImGui::CloseCurrentPopup(); - } + auto mrcs = entity.getComponents(); + if (mrcs.empty()) + continue; + + // Get transform for this entity + glm::mat4 modelMatrix = glm::mat4(1.0f); + auto* tc = entity.getComponent(); + if (tc) { + modelMatrix = tc->getLocalMatrix(); + } - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) { - ImGui::CloseCurrentPopup(); - } + for (auto* mrc : mrcs) { + auto mesh = mrc->getMesh(); + if (!mesh || !mesh->hasGPUData()) + continue; + + // Build push constants with material data + MeshPushConstants pushData{}; + pushData.model = modelMatrix; + pushData.baseColor = glm::vec4(0.7f, 0.7f, 0.7f, 1.0f); // default grey + pushData.metallic = 0.0f; + pushData.roughness = 0.5f; + + auto material = mrc->getMaterial(); + if (material) { + auto& props = material->getProperties(); + pushData.baseColor = props.baseColorFactor; + pushData.metallic = props.metallicFactor; + pushData.roughness = props.roughnessFactor; + } - ImGui::EndPopup(); -} - - - - - // Save Scene As dialog - if (showSaveAsDialog) { - ImGui::OpenPopup("Save Scene As"); - showSaveAsDialog = false; - } - if (ImGui::BeginPopupModal("Save Scene As", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("File path (.gltf or .glb):"); - ImGui::SetNextItemWidth(400); - ImGui::InputText("##savepath", dialogPathBuf, sizeof(dialogPathBuf)); - ImGui::Spacing(); - if (ImGui::Button("Save", ImVec2(120, 0))) { - saveSceneAs(dialogPathBuf); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } -} - -void EditorApp::setupDefaultDockLayout(ImGuiID dockspaceId) { - ImGui::DockBuilderRemoveNode(dockspaceId); - ImGui::DockBuilderAddNode(dockspaceId, ImGuiDockNodeFlags_DockSpace); - - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->WorkSize); - - ImGuiID dockLeft, dockCenter; - ImGui::DockBuilderSplitNode(dockspaceId, ImGuiDir_Left, 0.20f, &dockLeft, &dockCenter); - - ImGuiID dockRight, dockCenterRemaining; - ImGui::DockBuilderSplitNode(dockCenter, ImGuiDir_Right, 0.25f, &dockRight, &dockCenterRemaining); - - ImGuiID dockBottom, dockViewport; - ImGui::DockBuilderSplitNode(dockCenterRemaining, ImGuiDir_Down, 0.28f, &dockBottom, &dockViewport); - - ImGui::DockBuilderDockWindow("Hierarchy", dockLeft); - ImGui::DockBuilderDockWindow("Inspector", dockRight); - ImGui::DockBuilderDockWindow("Asset Browser", dockBottom); - ImGui::DockBuilderDockWindow("Viewport", dockViewport); - - ImGui::DockBuilderFinish(dockspaceId); -} - -void EditorApp::mouseButtonCallback(GLFWwindow* window, int button, int action, int /*mods*/) { - auto* app = static_cast(glfwGetWindowUserPointer(window)); - if (!app) return; - - double xpos, ypos; - glfwGetCursorPos(window, &xpos, &ypos); - - // Only guard PRESS events — releases must always be processed to prevent stuck state - bool guardPress = false; - if (action == GLFW_PRESS) { - bool imguiWants = ImGui::GetIO().WantCaptureMouse; - bool inFlyMode = app->editorCamera.getMode() == EditorCamera::Mode::Fly; - guardPress = imguiWants && !app->viewportHovered && !inFlyMode; - } - - if (button == GLFW_MOUSE_BUTTON_RIGHT) { - if (action == GLFW_PRESS) { - if (guardPress) return; - app->rightMouseDown = true; - app->lastMouseX = static_cast(xpos); - app->lastMouseY = static_cast(ypos); - if (app->viewportHovered) { - app->editorCamera.beginFlyMode(); - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - } - } else if (action == GLFW_RELEASE) { - app->rightMouseDown = false; - if (app->editorCamera.getMode() == EditorCamera::Mode::Fly) { - app->editorCamera.endFlyMode(); - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - } - } - } - - if (button == GLFW_MOUSE_BUTTON_MIDDLE) { - if (action == GLFW_PRESS) { - if (guardPress) return; - app->middleMouseDown = true; - app->lastMouseX = static_cast(xpos); - app->lastMouseY = static_cast(ypos); - } else if (action == GLFW_RELEASE) { - app->middleMouseDown = false; - } - } - - if (button == GLFW_MOUSE_BUTTON_LEFT) { - if (action == GLFW_PRESS) { - if (guardPress) return; - app->leftMouseDown = true; - app->lastMouseX = static_cast(xpos); - app->lastMouseY = static_cast(ypos); - app->mousePressX = static_cast(xpos); - app->mousePressY = static_cast(ypos); - - // Check gizmo hit first - if (app->viewportHovered && app->selectionManager.hasSelection() && app->pGizmoRenderer) { - auto* gizmo = app->pGizmoRenderer->getActiveGizmo(); - auto* entity = app->selectionManager.getSelectedEntity(*app->pScene); - if (gizmo && entity) { - auto* tc = entity->getComponent(); - if (tc) { - auto* vp = static_cast(app->viewportPanel.get()); - ImVec2 vpPos = vp->getViewportScreenPos(); - ImVec2 vpSize = vp->getViewportSize(); - float localX = static_cast(xpos) - vpPos.x; - float localY = static_cast(ypos) - vpPos.y; - Ray ray = app->editorCamera.screenToWorldRay(localX, localY, vpSize.x, vpSize.y); - - float dist = glm::length(app->editorCamera.getPosition() - tc->getTranslation()); - float gizmoScale = dist * GizmoRenderer::SCALE_FACTOR; - GizmoAxis hitAxis = gizmo->hitTest(ray, tc->getTranslation(), tc->getRotation(), gizmoScale); - if (hitAxis != GizmoAxis::None) { - gizmo->beginInteraction(hitAxis, ray, tc->getTranslation(), tc->getRotation()); - app->gizmoInteracting = true; + cmd.pushConstants(activePipeline->getLayout(), + vk::ShaderStageFlagBits::eVertex | + vk::ShaderStageFlagBits::eFragment, + 0, pushData); + + auto& cmdRef = const_cast(cmd); + mesh->bind(cmdRef); + mesh->draw(cmdRef); } - } - } - } - } else if (action == GLFW_RELEASE) { - // End gizmo interaction if active - if (app->gizmoInteracting && app->pGizmoRenderer) { - auto* gizmo = app->pGizmoRenderer->getActiveGizmo(); - if (gizmo) gizmo->endInteraction(); - app->gizmoInteracting = false; - } else { - // Click-to-select: if mouse barely moved, it's a click not a drag - float dx = static_cast(xpos) - app->mousePressX; - float dy = static_cast(ypos) - app->mousePressY; - if (std::sqrt(dx * dx + dy * dy) < 3.0f) { - app->pickEntityAtScreen(static_cast(xpos), static_cast(ypos)); - } - } - app->leftMouseDown = false; - } - } -} - -void EditorApp::cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { - auto* app = static_cast(glfwGetWindowUserPointer(window)); - if (!app) return; - - float xposf = static_cast(xpos); - float yposf = static_cast(ypos); - float deltaX = xposf - app->lastMouseX; - float deltaY = app->lastMouseY - yposf; // Inverted Y - - app->lastMouseX = xposf; - app->lastMouseY = yposf; - - // Always process fly mode mouse look (cursor is captured) - if (app->rightMouseDown && app->editorCamera.getMode() == EditorCamera::Mode::Fly) { - app->editorCamera.flyMouseLook(deltaX, deltaY); - return; - } - - // Handle gizmo interaction (takes priority over orbit/pan) - if (app->gizmoInteracting && app->leftMouseDown && app->pGizmoRenderer) { - auto* gizmo = app->pGizmoRenderer->getActiveGizmo(); - auto* entity = app->selectionManager.getSelectedEntity(*app->pScene); - if (gizmo && entity) { - auto* tc = entity->getComponent(); - if (tc) { - auto* vp = static_cast(app->viewportPanel.get()); - ImVec2 vpPos = vp->getViewportScreenPos(); - ImVec2 vpSize = vp->getViewportSize(); - float localX = xposf - vpPos.x; - float localY = yposf - vpPos.y; - Ray ray = app->editorCamera.screenToWorldRay(localX, localY, vpSize.x, vpSize.y); - - glm::vec3 delta = gizmo->updateInteraction(ray, tc->getTranslation(), tc->getRotation()); - - GizmoType type = app->pGizmoRenderer->getActiveGizmoType(); - if (type == GizmoType::Translate) { - tc->setTranslation(tc->getTranslation() + delta); - } else if (type == GizmoType::Rotate) { - // delta encodes angle * axis - float angle = glm::length(delta); - if (angle > 1e-6f) { - glm::vec3 axis = delta / angle; - glm::quat rot = glm::angleAxis(angle, axis); - tc->setRotation(rot * tc->getRotation()); - } - } else if (type == GizmoType::Scale) { - tc->setScale(tc->getScale() + delta); - } - } - } - return; - } - - // Only orbit/pan when viewport is hovered - if (!app->viewportHovered) return; - - if (app->leftMouseDown && app->editorCamera.getMode() == EditorCamera::Mode::Orbit) { - app->editorCamera.orbit(deltaX * 0.3f, deltaY * 0.3f); - } else if (app->middleMouseDown) { - app->editorCamera.pan(deltaX, deltaY); - } -} - -void EditorApp::scrollCallback(GLFWwindow* window, double /*xoffset*/, double yoffset) { - auto* app = static_cast(glfwGetWindowUserPointer(window)); - if (!app) return; - - if (!app->viewportHovered) return; - - app->editorCamera.zoom(static_cast(yoffset)); -} - -void EditorApp::keyCallback(GLFWwindow* window, int key, int /*scancode*/, int action, int mods) { - auto* app = static_cast(glfwGetWindowUserPointer(window)); - if (!app) return; - - bool ctrl = (mods & GLFW_MOD_CONTROL) != 0; - bool shift = (mods & GLFW_MOD_SHIFT) != 0; - - // Global shortcuts that work even when ImGui wants keyboard - if (action == GLFW_PRESS && ctrl) { - if (key == GLFW_KEY_S && shift) { - // Ctrl+Shift+S = Save As - std::string defaultPath = app->pScene->hasFilePath() - ? app->pScene->getCurrentFilePath() - : (std::filesystem::current_path() / "assets" / "scene.gltf").string(); - std::strncpy(app->dialogPathBuf, defaultPath.c_str(), sizeof(app->dialogPathBuf) - 1); - app->dialogPathBuf[sizeof(app->dialogPathBuf) - 1] = '\0'; - app->showSaveAsDialog = true; - return; - } - if (key == GLFW_KEY_S) { - app->saveScene(); - return; - } - if (key == GLFW_KEY_O) { - std::string defaultPath = app->pScene->hasFilePath() - ? app->pScene->getCurrentFilePath() - : (std::filesystem::current_path() / "assets" / "scene.gltf").string(); - std::strncpy(app->dialogPathBuf, defaultPath.c_str(), sizeof(app->dialogPathBuf) - 1); - app->dialogPathBuf[sizeof(app->dialogPathBuf) - 1] = '\0'; - app->showOpenDialog = true; - return; - } - if (key == GLFW_KEY_N) { - app->logicalDevice->waitIdle(); - app->pScene->getEntitiesMut().clear(); - app->pScene->setCurrentFilePath(""); - app->selectionManager.deselect(); - app->setStatusMessage("New scene created"); - return; - } - if (key == GLFW_KEY_D) { - app->selectionManager.deselect(); - return; - } - if (key == GLFW_KEY_P) { - if (app->playModeActive) - app->stopPlayMode(); - else - app->startPlayMode(); - return; - } - } + } - if (ImGui::GetIO().WantCaptureKeyboard) return; + // Draw gizmo for selected entity + if (selectionManager.hasSelection() && pGizmoRenderer) { + auto* entity = selectionManager.getSelectedEntity(*pScene); + if (entity) { + auto* tc = entity->getComponent(); + if (tc) { + pGizmoRenderer->render(cmd, pRenderer->getCurrentDescriptorSet(), + tc->getTranslation(), editorCamera, aspect); + } + } + } + + cmd.endRendering(); + + // Transition offscreen color image -> ShaderReadOnlyOptimal for ImGui sampling + pRenderer->transitionImageLayout( + cmd, *pOffscreenFB->getColorImage(), vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eColorAttachmentWrite, + vk::AccessFlagBits2::eShaderRead, vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eFragmentShader, vk::ImageAspectFlagBits::eColor); + + // ======================== + // PASS 2: Render ImGui to swapchain + // ======================== + + // Transition swapchain image -> ColorAttachmentOptimal + pRenderer->transitionImageLayout( + cmd, pRenderer->getSwapChain().getImages()[imageIndex], vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::ImageAspectFlagBits::eColor); + + // Transition renderer's depth image for the ImGui pass (ImGui pipeline expects depth format) + pRenderer->transitionImageLayout(cmd, *pRenderer->getDepthImage(), + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, {}, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + vk::ClearValue swapClearColor = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}; + vk::ClearValue swapClearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderingAttachmentInfo swapColorAttachment{ + .imageView = pRenderer->getSwapChain().getImageViews()[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = swapClearColor, + }; + + vk::RenderingAttachmentInfo swapDepthAttachment{ + .imageView = pRenderer->getDepthImageView(), + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = swapClearDepth, + }; + + vk::RenderingInfo swapRenderingInfo{ + .renderArea = + { + .offset = {0, 0}, + .extent = pRenderer->getSwapChain().getExtent(), + }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &swapColorAttachment, + .pDepthAttachment = &swapDepthAttachment, + }; + + cmd.beginRendering(swapRenderingInfo); + + // Render ImGui (which includes the viewport panel showing the offscreen texture) + if (pImGuiRenderer) { + pImGuiRenderer->render(cmd, imageIndex); + } - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - // If in fly mode, exit fly mode first - if (app->editorCamera.getMode() == EditorCamera::Mode::Fly) { - app->editorCamera.endFlyMode(); - app->rightMouseDown = false; - glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - return; + cmd.endRendering(); + + // Transition swapchain image -> PresentSrcKHR + pRenderer->transitionImageLayout( + cmd, pRenderer->getSwapChain().getImages()[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe, vk::ImageAspectFlagBits::eColor); + + cmd.end(); } - glfwSetWindowShouldClose(window, true); - } - - if (key == GLFW_KEY_F && action == GLFW_PRESS) { - auto* entity = app->selectionManager.getSelectedEntity(*app->pScene); - if (entity) { - auto* tc = entity->getComponent(); - if (tc) { - app->editorCamera.focusOn(tc->getTranslation()); - app->setStatusMessage("Focused on: " + entity->get_name()); - } + + void EditorApp::mainLoop() { + while (!glfwWindowShouldClose(window)) { + auto currentFrameTime = std::chrono::steady_clock::now(); + deltaTime = std::chrono::duration(currentFrameTime - lastFrameTime).count(); + lastFrameTime = currentFrameTime; + + glfwPollEvents(); + processInput(); + + editorCamera.update(deltaTime); + editorCamera.syncToSceneCamera(pScene->getCameraRW()); + + // Handle viewport resize + if (viewportPanel) { + auto* vp = static_cast(viewportPanel.get()); + if (vp->viewportSizeChanged()) { + ImVec2 size = vp->getViewportSize(); + uint32_t w = static_cast(size.x); + uint32_t h = static_cast(size.y); + if (w > 0 && h > 0) { + logicalDevice->waitIdle(); + pOffscreenFB->resize(w, h); + editorCamera.setScreenSize(static_cast(w), static_cast(h)); + } + vp->clearSizeChanged(); + } + } + + // Check if play mode process has exited on its own + if (playModeActive && playProcessPid > 0) { + int status; + pid_t result = waitpid(playProcessPid, &status, WNOHANG); + if (result != 0) { + // Process has exited + playProcessPid = -1; + playModeActive = false; + if (!playModeTempFile.empty()) { + std::error_code ec2; + std::filesystem::remove(playModeTempFile, ec2); + playModeTempFile.clear(); + } + setStatusMessage("Play mode ended"); + } + } + + // Upload mesh GPU resources for any newly imported models + uploadMeshGPUResources(); + + pImGuiRenderer->newFrame(); + buildEditorUI(); + + pRenderer->drawFrame(logicalDevice, *pScene, pImGuiRenderer.get()); + } + + logicalDevice->waitIdle(); } - } - - if (key == GLFW_KEY_DELETE && action == GLFW_PRESS) { - int idx = app->selectionManager.getSelectedIndex(); - auto& entities = app->pScene->getEntitiesMut(); - if (idx >= 0 && idx < static_cast(entities.size())) { - std::string name = entities[idx].get_name(); - // Wait for GPU to finish using entity's mesh buffers before destroying - app->logicalDevice->waitIdle(); - entities.erase(entities.begin() + idx); - app->selectionManager.deselect(); - app->setStatusMessage("Deleted: " + name); + + void EditorApp::processInput() { + // Always allow fly mode WASD when in fly mode (cursor is captured) + if (editorCamera.getMode() == EditorCamera::Mode::Fly) { + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) + editorCamera.flyMoveForward(deltaTime); + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) + editorCamera.flyMoveBackward(deltaTime); + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) + editorCamera.flyMoveLeft(deltaTime); + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + editorCamera.flyMoveRight(deltaTime); + return; + } + + // In orbit mode, only process input when viewport hovered + if (!viewportHovered || ImGui::GetIO().WantCaptureKeyboard) { + return; + } } - } - - // W/E/R for gizmo mode switching (only when not in fly mode and no modifier keys) - if (action == GLFW_PRESS && app->editorCamera.getMode() != EditorCamera::Mode::Fly && !ctrl) { - if (key == GLFW_KEY_W) { - app->activeGizmoMode = GizmoType::Translate; - if (app->pGizmoRenderer) app->pGizmoRenderer->setActiveGizmo(GizmoType::Translate); - app->setStatusMessage("Gizmo: Translate"); + + void EditorApp::buildEditorUI() { + // Create full-screen dockspace + ImGuiViewport* vp = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(vp->WorkPos); + ImGui::SetNextWindowSize(vp->WorkSize); + ImGui::SetNextWindowViewport(vp->ID); + + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse; + windowFlags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + windowFlags |= ImGuiWindowFlags_NoBackground; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + + ImGui::Begin("DockSpace", nullptr, windowFlags); + ImGui::PopStyleVar(3); + + ImGuiID dockspaceId = ImGui::GetID("EditorDockSpace"); + + if (firstFrame) { + setupDefaultDockLayout(dockspaceId); + firstFrame = false; + } + + ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); + + // Menu bar + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("New Scene", "Ctrl+N")) { + logicalDevice->waitIdle(); + pScene->getEntitiesMut().clear(); + pScene->setCurrentFilePath(""); + selectionManager.deselect(); + setStatusMessage("New scene created"); + } + if (ImGui::MenuItem("Open Scene...", "Ctrl+O")) { + std::string defaultPath = + pScene->hasFilePath() + ? pScene->getCurrentFilePath() + : (std::filesystem::current_path() / "assets" / "scene.gltf").string(); + std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); + dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; + showOpenDialog = true; + } + ImGui::Separator(); + if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { + saveScene(); + } + if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S")) { + std::string defaultPath = + pScene->hasFilePath() + ? pScene->getCurrentFilePath() + : (std::filesystem::current_path() / "assets" / "scene.gltf").string(); + std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); + dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; + showSaveAsDialog = true; + } + + ImGui::Separator(); + + if (ImGui::MenuItem("Export Scene as ZIP...")) { + std::string defaultPath = + (std::filesystem::current_path() / "scene_export.zip").string(); + std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); + dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; + showExportZipDialog = true; + } + + if (ImGui::MenuItem("Import Scene from ZIP...")) { + std::string defaultPath = + (std::filesystem::current_path() / "scene_import.zip").string(); + std::strncpy(dialogPathBuf, defaultPath.c_str(), sizeof(dialogPathBuf) - 1); + dialogPathBuf[sizeof(dialogPathBuf) - 1] = '\0'; + showImportZipDialog = true; + } + + ImGui::Separator(); + + if (ImGui::MenuItem("Exit", "Esc")) { + glfwSetWindowShouldClose(window, true); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) { + if (ImGui::MenuItem("Deselect All", "Ctrl+D")) { + selectionManager.deselect(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("View")) { + ImGui::MenuItem("Hierarchy", nullptr, &showHierarchy); + ImGui::MenuItem("Inspector", nullptr, &showInspector); + ImGui::MenuItem("Viewport", nullptr, &showViewport); + ImGui::MenuItem("Asset Browser", nullptr, &showAssetBrowser); + ImGui::MenuItem("Settings", nullptr, &showSettings); + ImGui::Separator(); + if (ImGui::MenuItem("Reset Layout")) { + firstFrame = true; // re-trigger layout on next frame + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Entity")) { + if (ImGui::BeginMenu("Create")) { + if (ImGui::MenuItem("Empty Entity")) { + createEmptyEntity(); + } + if (ImGui::MenuItem("Box")) { + createBoxEntity(); + } + if (ImGui::MenuItem("Ball")) { + createBallEntity(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + + // Play mode controls - centered in menu bar + { + float menuBarWidth = ImGui::GetWindowWidth(); + float buttonWidth = 80.0f; + float totalWidth = playModeActive ? (buttonWidth * 1 + 8.0f) : buttonWidth; + float cursorX = (menuBarWidth - totalWidth) * 0.5f; + + // Ensure we don't overlap menus + if (cursorX < ImGui::GetCursorPosX()) + cursorX = ImGui::GetCursorPosX() + 20.0f; + + ImGui::SetCursorPosX(cursorX); + + if (!playModeActive) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.55f, 0.15f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.20f, 0.70f, 0.20f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.10f, 0.45f, 0.10f, 1.0f)); + if (ImGui::Button("Play", ImVec2(buttonWidth, 0))) { + startPlayMode(); + } + ImGui::PopStyleColor(3); + } else { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.70f, 0.15f, 0.15f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.85f, 0.20f, 0.20f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.55f, 0.10f, 0.10f, 1.0f)); + if (ImGui::Button("Stop", ImVec2(buttonWidth, 0))) { + stopPlayMode(); + } + ImGui::PopStyleColor(3); + } + } + + ImGui::EndMenuBar(); + } + + ImGui::End(); + + // Render panels + if (showHierarchy && hierarchyPanel) { + hierarchyPanel->render(); + } + if (showInspector && inspectorPanel) { + inspectorPanel->render(); + } + if (showViewport && viewportPanel) { + viewportPanel->render(); + viewportHovered = static_cast(viewportPanel.get())->isViewportHovered(); + viewportFocused = static_cast(viewportPanel.get())->isViewportFocused(); + } + if (showAssetBrowser && assetBrowserPanel) { + assetBrowserPanel->render(); + } + if (showSettings && settingsWindow) { + settingsWindow->setEnabled(true); + settingsWindow->render(); + // Sync back: if user closed the window via its X button + if (!settingsWindow->isEnabled()) { + showSettings = false; + } + } + + // Status bar at bottom + { + if (statusTimer > 0.0f) { + statusTimer -= deltaTime; + } + + float statusBarHeight = 24.0f; + ImGui::SetNextWindowPos( + ImVec2(vp->WorkPos.x, vp->WorkPos.y + vp->WorkSize.y - statusBarHeight)); + ImGui::SetNextWindowSize(ImVec2(vp->WorkSize.x, statusBarHeight)); + ImGuiWindowFlags statusFlags = + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 3)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10f, 0.10f, 0.12f, 1.00f)); + ImGui::Begin("##StatusBar", nullptr, statusFlags); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + if (statusTimer > 0.0f && !statusMessage.empty()) { + ImGui::Text("%s", statusMessage.c_str()); + } else { + int entityCount = static_cast(pScene->getEntities().size()); + auto& cam = editorCamera; + glm::vec3 camPos = cam.getPosition(); + ImGui::Text("Entities: %d | Camera: (%.1f, %.1f, %.1f) | %s", entityCount, camPos.x, + camPos.y, camPos.z, + cam.getMode() == EditorCamera::Mode::Orbit ? "Orbit" : "Fly"); + } + + ImGui::End(); + } + + // Open Scene dialog + if (showOpenDialog) { + ImGui::OpenPopup("Open Scene"); + showOpenDialog = false; + } + if (ImGui::BeginPopupModal("Open Scene", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("File path:"); + ImGui::SetNextItemWidth(400); + ImGui::InputText("##openpath", dialogPathBuf, sizeof(dialogPathBuf)); + ImGui::Spacing(); + if (ImGui::Button("Open", ImVec2(120, 0))) { + openScene(dialogPathBuf); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + // Export Scene as ZIP dialog + if (showExportZipDialog) { + ImGui::OpenPopup("Export Scene as ZIP"); + showExportZipDialog = false; + } + + if (ImGui::BeginPopupModal("Export Scene as ZIP", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("ZIP file path:"); + ImGui::SetNextItemWidth(400); + ImGui::InputText("##exportzip", dialogPathBuf, sizeof(dialogPathBuf)); + + ImGui::Spacing(); + + if (ImGui::Button("Export", ImVec2(120, 0))) { + std::string zipPath = dialogPathBuf; + + // Collect asset paths (basic version) + std::vector assets; + for (auto& entity : pScene->getEntities()) { + auto mrcs = entity.getComponents(); + for (auto* mrc : mrcs) { + if (!mrc->getModelPath().empty()) { + assets.push_back(mrc->getModelPath()); + } + } + } + + if (saveSceneToZip(zipPath, pScene->getCurrentFilePath(), assets)) { + setStatusMessage("Exported scene to ZIP"); + ImGui::CloseCurrentPopup(); + } + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + // Import Scene from ZIP dialog + if (showImportZipDialog) { + ImGui::OpenPopup("Import Scene from ZIP"); + showImportZipDialog = false; + } + + if (ImGui::BeginPopupModal("Import Scene from ZIP", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("ZIP file path:"); + ImGui::SetNextItemWidth(400); + ImGui::InputText("##importzip", dialogPathBuf, sizeof(dialogPathBuf)); + + ImGui::Spacing(); + + if (ImGui::Button("Import", ImVec2(120, 0))) { + loadSceneFromZip(dialogPathBuf); + setStatusMessage("Imported scene from ZIP"); + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + // Save Scene As dialog + if (showSaveAsDialog) { + ImGui::OpenPopup("Save Scene As"); + showSaveAsDialog = false; + } + if (ImGui::BeginPopupModal("Save Scene As", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("File path (.gltf or .glb):"); + ImGui::SetNextItemWidth(400); + ImGui::InputText("##savepath", dialogPathBuf, sizeof(dialogPathBuf)); + ImGui::Spacing(); + if (ImGui::Button("Save", ImVec2(120, 0))) { + saveSceneAs(dialogPathBuf); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } } - if (key == GLFW_KEY_E) { - app->activeGizmoMode = GizmoType::Rotate; - if (app->pGizmoRenderer) app->pGizmoRenderer->setActiveGizmo(GizmoType::Rotate); - app->setStatusMessage("Gizmo: Rotate"); + + void EditorApp::setupDefaultDockLayout(ImGuiID dockspaceId) { + ImGui::DockBuilderRemoveNode(dockspaceId); + ImGui::DockBuilderAddNode(dockspaceId, ImGuiDockNodeFlags_DockSpace); + + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->WorkSize); + + ImGuiID dockLeft, dockCenter; + ImGui::DockBuilderSplitNode(dockspaceId, ImGuiDir_Left, 0.20f, &dockLeft, &dockCenter); + + ImGuiID dockRight, dockCenterRemaining; + ImGui::DockBuilderSplitNode(dockCenter, ImGuiDir_Right, 0.25f, &dockRight, + &dockCenterRemaining); + + ImGuiID dockBottom, dockViewport; + ImGui::DockBuilderSplitNode(dockCenterRemaining, ImGuiDir_Down, 0.28f, &dockBottom, + &dockViewport); + + ImGui::DockBuilderDockWindow("Hierarchy", dockLeft); + ImGui::DockBuilderDockWindow("Inspector", dockRight); + ImGui::DockBuilderDockWindow("Asset Browser", dockBottom); + ImGui::DockBuilderDockWindow("Viewport", dockViewport); + + ImGui::DockBuilderFinish(dockspaceId); } - if (key == GLFW_KEY_R) { - app->activeGizmoMode = GizmoType::Scale; - if (app->pGizmoRenderer) app->pGizmoRenderer->setActiveGizmo(GizmoType::Scale); - app->setStatusMessage("Gizmo: Scale"); + + void EditorApp::mouseButtonCallback(GLFWwindow* window, int button, int action, int /*mods*/) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + if (!app) + return; + + double xpos, ypos; + glfwGetCursorPos(window, &xpos, &ypos); + + // Only guard PRESS events — releases must always be processed to prevent stuck state + bool guardPress = false; + if (action == GLFW_PRESS) { + bool imguiWants = ImGui::GetIO().WantCaptureMouse; + bool inFlyMode = app->editorCamera.getMode() == EditorCamera::Mode::Fly; + guardPress = imguiWants && !app->viewportHovered && !inFlyMode; + } + + if (button == GLFW_MOUSE_BUTTON_RIGHT) { + if (action == GLFW_PRESS) { + if (guardPress) + return; + app->rightMouseDown = true; + app->lastMouseX = static_cast(xpos); + app->lastMouseY = static_cast(ypos); + if (app->viewportHovered) { + app->editorCamera.beginFlyMode(); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + } + } else if (action == GLFW_RELEASE) { + app->rightMouseDown = false; + if (app->editorCamera.getMode() == EditorCamera::Mode::Fly) { + app->editorCamera.endFlyMode(); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + } + } + + if (button == GLFW_MOUSE_BUTTON_MIDDLE) { + if (action == GLFW_PRESS) { + if (guardPress) + return; + app->middleMouseDown = true; + app->lastMouseX = static_cast(xpos); + app->lastMouseY = static_cast(ypos); + } else if (action == GLFW_RELEASE) { + app->middleMouseDown = false; + } + } + + if (button == GLFW_MOUSE_BUTTON_LEFT) { + if (action == GLFW_PRESS) { + if (guardPress) + return; + app->leftMouseDown = true; + app->lastMouseX = static_cast(xpos); + app->lastMouseY = static_cast(ypos); + app->mousePressX = static_cast(xpos); + app->mousePressY = static_cast(ypos); + + // Check gizmo hit first + if (app->viewportHovered && app->selectionManager.hasSelection() && + app->pGizmoRenderer) { + auto* gizmo = app->pGizmoRenderer->getActiveGizmo(); + auto* entity = app->selectionManager.getSelectedEntity(*app->pScene); + if (gizmo && entity) { + auto* tc = entity->getComponent(); + if (tc) { + auto* vp = static_cast(app->viewportPanel.get()); + ImVec2 vpPos = vp->getViewportScreenPos(); + ImVec2 vpSize = vp->getViewportSize(); + float localX = static_cast(xpos) - vpPos.x; + float localY = static_cast(ypos) - vpPos.y; + Ray ray = app->editorCamera.screenToWorldRay(localX, localY, vpSize.x, + vpSize.y); + + float dist = + glm::length(app->editorCamera.getPosition() - tc->getTranslation()); + float gizmoScale = dist * GizmoRenderer::SCALE_FACTOR; + GizmoAxis hitAxis = gizmo->hitTest(ray, tc->getTranslation(), + tc->getRotation(), gizmoScale); + if (hitAxis != GizmoAxis::None) { + gizmo->beginInteraction(hitAxis, ray, tc->getTranslation(), + tc->getRotation()); + app->gizmoInteracting = true; + } + } + } + } + } else if (action == GLFW_RELEASE) { + // End gizmo interaction if active + if (app->gizmoInteracting && app->pGizmoRenderer) { + auto* gizmo = app->pGizmoRenderer->getActiveGizmo(); + if (gizmo) + gizmo->endInteraction(); + app->gizmoInteracting = false; + } else { + // Click-to-select: if mouse barely moved, it's a click not a drag + float dx = static_cast(xpos) - app->mousePressX; + float dy = static_cast(ypos) - app->mousePressY; + if (std::sqrt(dx * dx + dy * dy) < 3.0f) { + app->pickEntityAtScreen(static_cast(xpos), static_cast(ypos)); + } + } + app->leftMouseDown = false; + } + } } - } -} - -void EditorApp::dropCallback(GLFWwindow* window, int count, const char** paths) { - auto* app = static_cast(glfwGetWindowUserPointer(window)); - if (!app || !app->assetBrowserPanel) return; - - for (int i = 0; i < count; ++i) { - app->assetBrowserPanel->handleFileDrop(paths[i]); - } -} - -void EditorApp::createEmptyEntity() { - sauce::Entity e("Empty Entity"); - e.addComponent(); - pScene->addEntity(std::move(e)); - - selectionManager.select(int(pScene->getEntities().size()) - 1); - setStatusMessage("Created Empty Entity"); -} - -void EditorApp::createBoxEntity() { - size_t before = pScene->getEntities().size(); - pScene->loadGLTFModel("assets/models/Cube.gltf", true); - size_t after = pScene->getEntities().size(); - - if (after > before) { - selectionManager.select(int(after - 1)); - setStatusMessage("Created Box"); + + void EditorApp::cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + if (!app) + return; + + float xposf = static_cast(xpos); + float yposf = static_cast(ypos); + float deltaX = xposf - app->lastMouseX; + float deltaY = app->lastMouseY - yposf; // Inverted Y + + app->lastMouseX = xposf; + app->lastMouseY = yposf; + + // Always process fly mode mouse look (cursor is captured) + if (app->rightMouseDown && app->editorCamera.getMode() == EditorCamera::Mode::Fly) { + app->editorCamera.flyMouseLook(deltaX, deltaY); + return; + } + + // Handle gizmo interaction (takes priority over orbit/pan) + if (app->gizmoInteracting && app->leftMouseDown && app->pGizmoRenderer) { + auto* gizmo = app->pGizmoRenderer->getActiveGizmo(); + auto* entity = app->selectionManager.getSelectedEntity(*app->pScene); + if (gizmo && entity) { + auto* tc = entity->getComponent(); + if (tc) { + auto* vp = static_cast(app->viewportPanel.get()); + ImVec2 vpPos = vp->getViewportScreenPos(); + ImVec2 vpSize = vp->getViewportSize(); + float localX = xposf - vpPos.x; + float localY = yposf - vpPos.y; + Ray ray = + app->editorCamera.screenToWorldRay(localX, localY, vpSize.x, vpSize.y); + + glm::vec3 delta = + gizmo->updateInteraction(ray, tc->getTranslation(), tc->getRotation()); + + GizmoType type = app->pGizmoRenderer->getActiveGizmoType(); + if (type == GizmoType::Translate) { + tc->setTranslation(tc->getTranslation() + delta); + } else if (type == GizmoType::Rotate) { + // delta encodes angle * axis + float angle = glm::length(delta); + if (angle > 1e-6f) { + glm::vec3 axis = delta / angle; + glm::quat rot = glm::angleAxis(angle, axis); + tc->setRotation(rot * tc->getRotation()); + } + } else if (type == GizmoType::Scale) { + tc->setScale(tc->getScale() + delta); + } + } + } + return; + } + + // Only orbit/pan when viewport is hovered + if (!app->viewportHovered) + return; + + if (app->leftMouseDown && app->editorCamera.getMode() == EditorCamera::Mode::Orbit) { + app->editorCamera.orbit(deltaX * 0.3f, deltaY * 0.3f); + } else if (app->middleMouseDown) { + app->editorCamera.pan(deltaX, deltaY); + } } -} -void EditorApp::createBallEntity() { - size_t before = pScene->getEntities().size(); - pScene->loadGLTFModel("assets/models/sphere.gltf", true); + void EditorApp::scrollCallback(GLFWwindow* window, double /*xoffset*/, double yoffset) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + if (!app) + return; - size_t after = pScene->getEntities().size(); + if (!app->viewportHovered) + return; - if (after > before) { - selectionManager.select(int(after - 1)); - setStatusMessage("Created Ball"); + app->editorCamera.zoom(static_cast(yoffset)); } -} -bool EditorApp::zipFolder( - const std::filesystem::path& src, - const std::filesystem::path& dst) -{ - if (!std::filesystem::exists(src)) { - setStatusMessage("Cannot zip folder: source path does not exist."); - return false; - } + void EditorApp::keyCallback(GLFWwindow* window, int key, int /*scancode*/, int action, + int mods) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + if (!app) + return; + + bool ctrl = (mods & GLFW_MOD_CONTROL) != 0; + bool shift = (mods & GLFW_MOD_SHIFT) != 0; + + // Global shortcuts that work even when ImGui wants keyboard + if (action == GLFW_PRESS && ctrl) { + if (key == GLFW_KEY_S && shift) { + // Ctrl+Shift+S = Save As + std::string defaultPath = + app->pScene->hasFilePath() + ? app->pScene->getCurrentFilePath() + : (std::filesystem::current_path() / "assets" / "scene.gltf").string(); + std::strncpy(app->dialogPathBuf, defaultPath.c_str(), + sizeof(app->dialogPathBuf) - 1); + app->dialogPathBuf[sizeof(app->dialogPathBuf) - 1] = '\0'; + app->showSaveAsDialog = true; + return; + } + if (key == GLFW_KEY_S) { + app->saveScene(); + return; + } + if (key == GLFW_KEY_O) { + std::string defaultPath = + app->pScene->hasFilePath() + ? app->pScene->getCurrentFilePath() + : (std::filesystem::current_path() / "assets" / "scene.gltf").string(); + std::strncpy(app->dialogPathBuf, defaultPath.c_str(), + sizeof(app->dialogPathBuf) - 1); + app->dialogPathBuf[sizeof(app->dialogPathBuf) - 1] = '\0'; + app->showOpenDialog = true; + return; + } + if (key == GLFW_KEY_N) { + app->logicalDevice->waitIdle(); + app->pScene->getEntitiesMut().clear(); + app->pScene->setCurrentFilePath(""); + app->selectionManager.deselect(); + app->setStatusMessage("New scene created"); + return; + } + if (key == GLFW_KEY_D) { + app->selectionManager.deselect(); + return; + } + if (key == GLFW_KEY_P) { + if (app->playModeActive) + app->stopPlayMode(); + else + app->startPlayMode(); + return; + } + } + + if (ImGui::GetIO().WantCaptureKeyboard) + return; - miniz_cpp::zip_file zip; + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { + // If in fly mode, exit fly mode first + if (app->editorCamera.getMode() == EditorCamera::Mode::Fly) { + app->editorCamera.endFlyMode(); + app->rightMouseDown = false; + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + return; + } + glfwSetWindowShouldClose(window, true); + } - for (const auto& entry : - std::filesystem::recursive_directory_iterator(src)) - { - if (!entry.is_regular_file()) continue; + if (key == GLFW_KEY_F && action == GLFW_PRESS) { + auto* entity = app->selectionManager.getSelectedEntity(*app->pScene); + if (entity) { + auto* tc = entity->getComponent(); + if (tc) { + app->editorCamera.focusOn(tc->getTranslation()); + app->setStatusMessage("Focused on: " + entity->get_name()); + } + } + } - std::filesystem::path rel = - std::filesystem::relative(entry.path(), src); + if (key == GLFW_KEY_DELETE && action == GLFW_PRESS) { + int idx = app->selectionManager.getSelectedIndex(); + auto& entities = app->pScene->getEntitiesMut(); + if (idx >= 0 && idx < static_cast(entities.size())) { + std::string name = entities[idx].get_name(); + // Wait for GPU to finish using entity's mesh buffers before destroying + app->logicalDevice->waitIdle(); + entities.erase(entities.begin() + idx); + app->selectionManager.deselect(); + app->setStatusMessage("Deleted: " + name); + } + } - std::string data = loadFileToString(entry.path().string()); - if (!data.empty()) { - zip.writestr(rel.string(), data); + // W/E/R for gizmo mode switching (only when not in fly mode and no modifier keys) + if (action == GLFW_PRESS && app->editorCamera.getMode() != EditorCamera::Mode::Fly && + !ctrl) { + if (key == GLFW_KEY_W) { + app->activeGizmoMode = GizmoType::Translate; + if (app->pGizmoRenderer) + app->pGizmoRenderer->setActiveGizmo(GizmoType::Translate); + app->setStatusMessage("Gizmo: Translate"); + } + if (key == GLFW_KEY_E) { + app->activeGizmoMode = GizmoType::Rotate; + if (app->pGizmoRenderer) + app->pGizmoRenderer->setActiveGizmo(GizmoType::Rotate); + app->setStatusMessage("Gizmo: Rotate"); + } + if (key == GLFW_KEY_R) { + app->activeGizmoMode = GizmoType::Scale; + if (app->pGizmoRenderer) + app->pGizmoRenderer->setActiveGizmo(GizmoType::Scale); + app->setStatusMessage("Gizmo: Scale"); + } } } - zip.save(dst.string()); - return true; -} + void EditorApp::dropCallback(GLFWwindow* window, int count, const char** paths) { + auto* app = static_cast(glfwGetWindowUserPointer(window)); + if (!app || !app->assetBrowserPanel) + return; + for (int i = 0; i < count; ++i) { + app->assetBrowserPanel->handleFileDrop(paths[i]); + } + } + void EditorApp::createEmptyEntity() { + sauce::Entity e("Empty Entity"); + e.addComponent(); + pScene->addEntity(std::move(e)); -std::string EditorApp::loadFileToString(const std::string& path) -{ - std::ifstream file(path, std::ios::binary); - if (!file) { - return {}; + selectionManager.select(int(pScene->getEntities().size()) - 1); + setStatusMessage("Created Empty Entity"); } - return std::string( - std::istreambuf_iterator(file), - std::istreambuf_iterator() - ); -} - -bool EditorApp::saveSceneToZip(const std::string& zipPath, - const std::string& scenePath, - const std::vector& assetPaths) -{ - if (zipPath.empty()) { - setStatusMessage("Cannot export: no output path specified."); - return false; + void EditorApp::createBoxEntity() { + size_t before = pScene->getEntities().size(); + pScene->loadGLTFModel("assets/models/Cube.gltf", true); + size_t after = pScene->getEntities().size(); + + if (after > before) { + selectionManager.select(int(after - 1)); + setStatusMessage("Created Box"); + } } - // If the scene has been saved to disk, use that file directly. - // Otherwise, save to a temp file so we can still export. - std::string sceneFile = scenePath; - bool usedTmp = false; + void EditorApp::createBallEntity() { + size_t before = pScene->getEntities().size(); + pScene->loadGLTFModel("assets/models/sphere.gltf", true); - if (sceneFile.empty() || !std::filesystem::exists(sceneFile)) { - auto tmpDir = std::filesystem::temp_directory_path() / "sauce_export"; - std::filesystem::create_directories(tmpDir); - sceneFile = (tmpDir / "scene.gltf").string(); + size_t after = pScene->getEntities().size(); - if (!pScene->saveToFile(sceneFile)) { - setStatusMessage("Cannot export: failed to save scene to temp file."); - return false; + if (after > before) { + selectionManager.select(int(after - 1)); + setStatusMessage("Created Ball"); } - usedTmp = true; } - miniz_cpp::zip_file zip; + bool EditorApp::zipFolder(const std::filesystem::path& src, const std::filesystem::path& dst) { + if (!std::filesystem::exists(src)) { + setStatusMessage("Cannot zip folder: source path does not exist."); + return false; + } + + miniz_cpp::zip_file zip; - std::string sceneData = loadFileToString(sceneFile); - zip.writestr("scene.gltf", sceneData); + for (const auto& entry : std::filesystem::recursive_directory_iterator(src)) { + if (!entry.is_regular_file()) + continue; - for (const auto& asset : assetPaths) { - std::string data = loadFileToString(asset); - if (!data.empty()) { - zip.writestr(asset, data); + std::filesystem::path rel = std::filesystem::relative(entry.path(), src); + + std::string data = loadFileToString(entry.path().string()); + if (!data.empty()) { + zip.writestr(rel.string(), data); + } } + + zip.save(dst.string()); + return true; } - zip.save(zipPath); + std::string EditorApp::loadFileToString(const std::string& path) { + std::ifstream file(path, std::ios::binary); + if (!file) { + return {}; + } - if (usedTmp) { - std::filesystem::remove_all(std::filesystem::temp_directory_path() / "sauce_export"); + return std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()); } - return true; -} + bool EditorApp::saveSceneToZip(const std::string& zipPath, const std::string& scenePath, + const std::vector& assetPaths) { + if (zipPath.empty()) { + setStatusMessage("Cannot export: no output path specified."); + return false; + } + + // If the scene has been saved to disk, use that file directly. + // Otherwise, save to a temp file so we can still export. + std::string sceneFile = scenePath; + bool usedTmp = false; + + if (sceneFile.empty() || !std::filesystem::exists(sceneFile)) { + auto tmpDir = std::filesystem::temp_directory_path() / "sauce_export"; + std::filesystem::create_directories(tmpDir); + sceneFile = (tmpDir / "scene.gltf").string(); + + if (!pScene->saveToFile(sceneFile)) { + setStatusMessage("Cannot export: failed to save scene to temp file."); + return false; + } + usedTmp = true; + } + + miniz_cpp::zip_file zip; + + std::string sceneData = loadFileToString(sceneFile); + zip.writestr("scene.gltf", sceneData); + + for (const auto& asset : assetPaths) { + std::string data = loadFileToString(asset); + if (!data.empty()) { + zip.writestr(asset, data); + } + } + + zip.save(zipPath); + + if (usedTmp) { + std::filesystem::remove_all(std::filesystem::temp_directory_path() / "sauce_export"); + } + return true; + } + void EditorApp::loadSceneFromZip(const std::string& zipPath) { + miniz_cpp::zip_file zip; + zip.load(zipPath); + std::string extractDir = "temp_scene_extract"; + std::filesystem::create_directories(extractDir); + zip.extractall(extractDir); -void EditorApp::loadSceneFromZip(const std::string& zipPath) -{ - miniz_cpp::zip_file zip; - zip.load(zipPath); + openScene(extractDir + "/scene.gltf"); + } - std::string extractDir = "temp_scene_extract"; - std::filesystem::create_directories(extractDir); + bool EditorApp::unzipToFolder(const std::filesystem::path& zipPath, + const std::filesystem::path& outDir) { + miniz_cpp::zip_file zip; + zip.load(zipPath.string()); + zip.extractall(outDir.string()); + return true; + } - zip.extractall(extractDir); + void EditorApp::applySettings(const sauce::EditorSettings& s) { + ImGui::GetIO().FontGlobalScale = s.imguiScale; + + sauce::Log::setPalantirMode(s.palantirMode); + + editorCamera.setMouseSensitivity(s.mouseSensitivity); + editorCamera.setFlySpeed(s.cameraSpeed); + editorCamera.setFOV(s.fieldOfView); + + if (!s.workingDirectory.empty() && s.workingDirectory != lastWorkingDirectory) { + std::error_code ec; + std::filesystem::current_path(s.workingDirectory, ec); + if (ec) { + SAUCE_LOG("Settings", "Failed to set working directory to '{}': {}", + s.workingDirectory, ec.message()); + } else { + lastWorkingDirectory = s.workingDirectory; + SAUCE_LOG_VERBOSE("Settings", "Working directory set to '{}'", + std::filesystem::current_path().string()); + } + } - openScene(extractDir + "/scene.gltf"); -} + SAUCE_LOG_VERBOSE( + "Settings", + "Settings applied (scale={:.2f}, sensitivity={:.2f}, speed={:.1f}, fov={:.0f})", + s.imguiScale, s.mouseSensitivity, s.cameraSpeed, s.fieldOfView); + } -bool EditorApp::unzipToFolder( - const std::filesystem::path& zipPath, - const std::filesystem::path& outDir) -{ - miniz_cpp::zip_file zip; - zip.load(zipPath.string()); - zip.extractall(outDir.string()); - return true; -} + void EditorApp::startPlayMode() { + if (playModeActive) + return; + + // Save scene to a temporary .glb file + namespace fs = std::filesystem; + fs::path tempDir = fs::temp_directory_path() / "sauceengine_play"; + std::error_code ec; + fs::create_directories(tempDir, ec); + if (ec) { + setStatusMessage("Play: Failed to create temp directory"); + SAUCE_LOG("Play", "Failed to create temp dir: {}", ec.message()); + return; + } + playModeTempFile = (tempDir / "play_scene.glb").string(); + // Save original file path before saveToFile overwrites it + std::string originalPath = pScene->getCurrentFilePath(); -void EditorApp::applySettings(const sauce::EditorSettings& s) { - ImGui::GetIO().FontGlobalScale = s.imguiScale; + if (!pScene->saveToFile(playModeTempFile)) { + setStatusMessage("Play: Failed to save scene"); + SAUCE_LOG("Play", "Failed to save scene to temp file"); + return; + } - sauce::Log::setPalantirMode(s.palantirMode); + // Restore the original scene file path (saveToFile updates it) + pScene->setCurrentFilePath(originalPath); - editorCamera.setMouseSensitivity(s.mouseSensitivity); - editorCamera.setFlySpeed(s.cameraSpeed); - editorCamera.setFOV(s.fieldOfView); + // Find the SauceEngine executable relative to the editor executable + fs::path engineExe = fs::current_path() / "build" / "SauceEngine"; + if (!fs::exists(engineExe)) { + // Try alongside the current executable + engineExe = "SauceEngine"; + } - if (!s.workingDirectory.empty() && s.workingDirectory != lastWorkingDirectory) { - std::error_code ec; - std::filesystem::current_path(s.workingDirectory, ec); - if (ec) { - SAUCE_LOG("Settings", "Failed to set working directory to '{}': {}", s.workingDirectory, ec.message()); - } else { - lastWorkingDirectory = s.workingDirectory; - SAUCE_LOG_VERBOSE("Settings", "Working directory set to '{}'", std::filesystem::current_path().string()); + SAUCE_LOG("Play", "Starting play mode: {} {}", engineExe.string(), playModeTempFile); + + pid_t pid = fork(); + if (pid == 0) { + // Child process - exec SauceEngine with the temp scene file + execlp(engineExe.c_str(), engineExe.c_str(), playModeTempFile.c_str(), nullptr); + // If exec fails + _exit(1); + } else if (pid > 0) { + playProcessPid = pid; + playModeActive = true; + setStatusMessage("Play mode started"); + SAUCE_LOG("Play", "SauceEngine launched with PID {}", pid); + } else { + setStatusMessage("Play: Failed to launch engine"); + SAUCE_LOG("Play", "fork() failed"); + } } - } - - SAUCE_LOG_VERBOSE("Settings", "Settings applied (scale={:.2f}, sensitivity={:.2f}, speed={:.1f}, fov={:.0f})", - s.imguiScale, s.mouseSensitivity, s.cameraSpeed, s.fieldOfView); -} - -void EditorApp::startPlayMode() { - if (playModeActive) return; - - // Save scene to a temporary .glb file - namespace fs = std::filesystem; - fs::path tempDir = fs::temp_directory_path() / "sauceengine_play"; - std::error_code ec; - fs::create_directories(tempDir, ec); - if (ec) { - setStatusMessage("Play: Failed to create temp directory"); - SAUCE_LOG("Play", "Failed to create temp dir: {}", ec.message()); - return; - } - - playModeTempFile = (tempDir / "play_scene.glb").string(); - - // Save original file path before saveToFile overwrites it - std::string originalPath = pScene->getCurrentFilePath(); - - if (!pScene->saveToFile(playModeTempFile)) { - setStatusMessage("Play: Failed to save scene"); - SAUCE_LOG("Play", "Failed to save scene to temp file"); - return; - } - - // Restore the original scene file path (saveToFile updates it) - pScene->setCurrentFilePath(originalPath); - - // Find the SauceEngine executable relative to the editor executable - fs::path engineExe = fs::current_path() / "build" / "SauceEngine"; - if (!fs::exists(engineExe)) { - // Try alongside the current executable - engineExe = "SauceEngine"; - } - - SAUCE_LOG("Play", "Starting play mode: {} {}", engineExe.string(), playModeTempFile); - - pid_t pid = fork(); - if (pid == 0) { - // Child process - exec SauceEngine with the temp scene file - execlp(engineExe.c_str(), engineExe.c_str(), playModeTempFile.c_str(), nullptr); - // If exec fails - _exit(1); - } else if (pid > 0) { - playProcessPid = pid; - playModeActive = true; - setStatusMessage("Play mode started"); - SAUCE_LOG("Play", "SauceEngine launched with PID {}", pid); - } else { - setStatusMessage("Play: Failed to launch engine"); - SAUCE_LOG("Play", "fork() failed"); - } -} - -void EditorApp::stopPlayMode() { - if (!playModeActive) return; - - if (playProcessPid > 0) { - SAUCE_LOG("Play", "Stopping SauceEngine (PID {})", playProcessPid); - kill(playProcessPid, SIGTERM); - - // Wait briefly for clean exit, then force kill - int status; - pid_t result = waitpid(playProcessPid, &status, WNOHANG); - if (result == 0) { - // Process still running, give it a moment - usleep(100000); // 100ms - result = waitpid(playProcessPid, &status, WNOHANG); - if (result == 0) { - kill(playProcessPid, SIGKILL); - waitpid(playProcessPid, &status, 0); - } + + void EditorApp::stopPlayMode() { + if (!playModeActive) + return; + + if (playProcessPid > 0) { + SAUCE_LOG("Play", "Stopping SauceEngine (PID {})", playProcessPid); + kill(playProcessPid, SIGTERM); + + // Wait briefly for clean exit, then force kill + int status; + pid_t result = waitpid(playProcessPid, &status, WNOHANG); + if (result == 0) { + // Process still running, give it a moment + usleep(100000); // 100ms + result = waitpid(playProcessPid, &status, WNOHANG); + if (result == 0) { + kill(playProcessPid, SIGKILL); + waitpid(playProcessPid, &status, 0); + } + } + playProcessPid = -1; + } + + // Clean up temp file + if (!playModeTempFile.empty()) { + std::error_code ec; + std::filesystem::remove(playModeTempFile, ec); + playModeTempFile.clear(); + } + + playModeActive = false; + setStatusMessage("Play mode stopped"); } - playProcessPid = -1; - } - - // Clean up temp file - if (!playModeTempFile.empty()) { - std::error_code ec; - std::filesystem::remove(playModeTempFile, ec); - playModeTempFile.clear(); - } - - playModeActive = false; - setStatusMessage("Play mode stopped"); -} } // namespace sauce::editor diff --git a/src/editor/EditorCamera.cpp b/src/editor/EditorCamera.cpp index 1de96262..d9c80c66 100644 --- a/src/editor/EditorCamera.cpp +++ b/src/editor/EditorCamera.cpp @@ -1,165 +1,166 @@ -#include #include -#include #include +#include +#include namespace sauce::editor { -EditorCamera::EditorCamera() { - updateOrbitPosition(); -} - -void EditorCamera::updateOrbitPosition() { - float pitchRad = glm::radians(orbitPitch); - float yawRad = glm::radians(orbitYaw); - - orbitPosition.x = orbitTarget.x + orbitDistance * cos(pitchRad) * sin(yawRad); - orbitPosition.y = orbitTarget.y + orbitDistance * cos(pitchRad) * cos(yawRad); - orbitPosition.z = orbitTarget.z + orbitDistance * sin(pitchRad); -} - -void EditorCamera::update(float /*deltaTime*/) { - if (mode == Mode::Orbit) { - updateOrbitPosition(); - } else { - // Update fly vectors from yaw/pitch - float pitchRad = glm::radians(flyPitch); - float yawRad = glm::radians(flyYaw); - - flyFront.x = cos(pitchRad) * sin(yawRad); - flyFront.y = cos(pitchRad) * cos(yawRad); - flyFront.z = sin(pitchRad); - flyFront = glm::normalize(flyFront); - - flyRight = glm::normalize(glm::cross(flyFront, WORLD_UP)); - flyUp = glm::normalize(glm::cross(flyRight, flyFront)); - } -} - -void EditorCamera::syncToSceneCamera(sauce::Camera& cam) { - glm::vec3 pos = getPosition(); - - glm::vec3 dir; - if (mode == Mode::Orbit) { - dir = glm::normalize(orbitTarget - orbitPosition); - } else { - dir = flyFront; - } - - cam.lookAt(pos, pos + dir, WORLD_UP); -} - -void EditorCamera::orbit(float deltaYaw, float deltaPitch) { - orbitYaw += deltaYaw; - orbitPitch += deltaPitch; - orbitPitch = glm::clamp(orbitPitch, -89.0f, 89.0f); - updateOrbitPosition(); -} - -void EditorCamera::pan(float deltaX, float deltaY) { - glm::vec3 forward = glm::normalize(orbitTarget - orbitPosition); - glm::vec3 right = glm::normalize(glm::cross(forward, WORLD_UP)); - glm::vec3 up = glm::normalize(glm::cross(right, forward)); - - float panSpeed = orbitDistance * 0.002f; - orbitTarget += right * (-deltaX * panSpeed) + up * (deltaY * panSpeed); - updateOrbitPosition(); -} - -void EditorCamera::zoom(float delta) { - orbitDistance -= delta * orbitDistance * 0.1f; - orbitDistance = glm::clamp(orbitDistance, 0.1f, 500.0f); - updateOrbitPosition(); -} - -void EditorCamera::focusOn(const glm::vec3& target) { - orbitTarget = target; - orbitDistance = 5.0f; - updateOrbitPosition(); -} - -void EditorCamera::beginFlyMode() { - mode = Mode::Fly; - flyPosition = orbitPosition; - - glm::vec3 dir = glm::normalize(orbitTarget - orbitPosition); - flyPitch = glm::degrees(asin(glm::clamp(dir.z, -1.0f, 1.0f))); - flyYaw = glm::degrees(atan2(dir.x, dir.y)); - - flyFront = dir; - flyRight = glm::normalize(glm::cross(flyFront, WORLD_UP)); - flyUp = glm::normalize(glm::cross(flyRight, flyFront)); -} - -void EditorCamera::endFlyMode() { - mode = Mode::Orbit; - // Update orbit params from fly camera state - orbitTarget = flyPosition + flyFront * orbitDistance; - orbitYaw = flyYaw + 180.0f; - orbitPitch = -flyPitch; - updateOrbitPosition(); -} - -void EditorCamera::flyMoveForward(float deltaTime) { - flyPosition += flyFront * flySpeed * deltaTime; -} - -void EditorCamera::flyMoveBackward(float deltaTime) { - flyPosition -= flyFront * flySpeed * deltaTime; -} - -void EditorCamera::flyMoveLeft(float deltaTime) { - flyPosition -= flyRight * flySpeed * deltaTime; -} - -void EditorCamera::flyMoveRight(float deltaTime) { - flyPosition += flyRight * flySpeed * deltaTime; -} - -void EditorCamera::flyMouseLook(float deltaX, float deltaY) { - flyYaw += deltaX * mouseSensitivity; - flyPitch += deltaY * mouseSensitivity; - flyPitch = glm::clamp(flyPitch, -89.0f, 89.0f); -} - -glm::vec3 EditorCamera::getPosition() const { - return mode == Mode::Orbit ? orbitPosition : flyPosition; -} - -glm::mat4 EditorCamera::getViewMatrix() const { - if (mode == Mode::Orbit) { - return glm::lookAt(orbitPosition, orbitTarget, WORLD_UP); - } else { - return glm::lookAt(flyPosition, flyPosition + flyFront, WORLD_UP); - } -} - -glm::mat4 EditorCamera::getProjectionMatrix(float aspectRatio) const { - return glm::perspective(glm::radians(fov), aspectRatio, 0.1f, 1000.0f); -} - -Ray EditorCamera::screenToWorldRay(float localX, float localY, float vpWidth, float vpHeight) const { - // Convert pixel coords to NDC [-1, 1] - // Screen Y increases downward; OpenGL NDC Y increases upward, so flip Y - float ndcX = (2.0f * localX) / vpWidth - 1.0f; - float ndcY = 1.0f - (2.0f * localY) / vpHeight; - - float aspect = vpWidth / vpHeight; - glm::mat4 proj = getProjectionMatrix(aspect); - glm::mat4 view = getViewMatrix(); - - glm::mat4 invProjView = glm::inverse(proj * view); - - glm::vec4 nearPoint = invProjView * glm::vec4(ndcX, ndcY, 0.0f, 1.0f); - glm::vec4 farPoint = invProjView * glm::vec4(ndcX, ndcY, 1.0f, 1.0f); - - nearPoint /= nearPoint.w; - farPoint /= farPoint.w; - - glm::vec3 origin = glm::vec3(nearPoint); - glm::vec3 direction = glm::normalize(glm::vec3(farPoint) - glm::vec3(nearPoint)); - - return { origin, direction }; -} + EditorCamera::EditorCamera() { + updateOrbitPosition(); + } + + void EditorCamera::updateOrbitPosition() { + float pitchRad = glm::radians(orbitPitch); + float yawRad = glm::radians(orbitYaw); + + orbitPosition.x = orbitTarget.x + orbitDistance * cos(pitchRad) * sin(yawRad); + orbitPosition.y = orbitTarget.y + orbitDistance * cos(pitchRad) * cos(yawRad); + orbitPosition.z = orbitTarget.z + orbitDistance * sin(pitchRad); + } + + void EditorCamera::update(float /*deltaTime*/) { + if (mode == Mode::Orbit) { + updateOrbitPosition(); + } else { + // Update fly vectors from yaw/pitch + float pitchRad = glm::radians(flyPitch); + float yawRad = glm::radians(flyYaw); + + flyFront.x = cos(pitchRad) * sin(yawRad); + flyFront.y = cos(pitchRad) * cos(yawRad); + flyFront.z = sin(pitchRad); + flyFront = glm::normalize(flyFront); + + flyRight = glm::normalize(glm::cross(flyFront, WORLD_UP)); + flyUp = glm::normalize(glm::cross(flyRight, flyFront)); + } + } + + void EditorCamera::syncToSceneCamera(sauce::Camera& cam) { + glm::vec3 pos = getPosition(); + + glm::vec3 dir; + if (mode == Mode::Orbit) { + dir = glm::normalize(orbitTarget - orbitPosition); + } else { + dir = flyFront; + } + + cam.lookAt(pos, pos + dir, WORLD_UP); + } + + void EditorCamera::orbit(float deltaYaw, float deltaPitch) { + orbitYaw += deltaYaw; + orbitPitch += deltaPitch; + orbitPitch = glm::clamp(orbitPitch, -89.0f, 89.0f); + updateOrbitPosition(); + } + + void EditorCamera::pan(float deltaX, float deltaY) { + glm::vec3 forward = glm::normalize(orbitTarget - orbitPosition); + glm::vec3 right = glm::normalize(glm::cross(forward, WORLD_UP)); + glm::vec3 up = glm::normalize(glm::cross(right, forward)); + + float panSpeed = orbitDistance * 0.002f; + orbitTarget += right * (-deltaX * panSpeed) + up * (deltaY * panSpeed); + updateOrbitPosition(); + } + + void EditorCamera::zoom(float delta) { + orbitDistance -= delta * orbitDistance * 0.1f; + orbitDistance = glm::clamp(orbitDistance, 0.1f, 500.0f); + updateOrbitPosition(); + } + + void EditorCamera::focusOn(const glm::vec3& target) { + orbitTarget = target; + orbitDistance = 5.0f; + updateOrbitPosition(); + } + + void EditorCamera::beginFlyMode() { + mode = Mode::Fly; + flyPosition = orbitPosition; + + glm::vec3 dir = glm::normalize(orbitTarget - orbitPosition); + flyPitch = glm::degrees(asin(glm::clamp(dir.z, -1.0f, 1.0f))); + flyYaw = glm::degrees(atan2(dir.x, dir.y)); + + flyFront = dir; + flyRight = glm::normalize(glm::cross(flyFront, WORLD_UP)); + flyUp = glm::normalize(glm::cross(flyRight, flyFront)); + } + + void EditorCamera::endFlyMode() { + mode = Mode::Orbit; + // Update orbit params from fly camera state + orbitTarget = flyPosition + flyFront * orbitDistance; + orbitYaw = flyYaw + 180.0f; + orbitPitch = -flyPitch; + updateOrbitPosition(); + } + + void EditorCamera::flyMoveForward(float deltaTime) { + flyPosition += flyFront * flySpeed * deltaTime; + } + + void EditorCamera::flyMoveBackward(float deltaTime) { + flyPosition -= flyFront * flySpeed * deltaTime; + } + + void EditorCamera::flyMoveLeft(float deltaTime) { + flyPosition -= flyRight * flySpeed * deltaTime; + } + + void EditorCamera::flyMoveRight(float deltaTime) { + flyPosition += flyRight * flySpeed * deltaTime; + } + + void EditorCamera::flyMouseLook(float deltaX, float deltaY) { + flyYaw += deltaX * mouseSensitivity; + flyPitch += deltaY * mouseSensitivity; + flyPitch = glm::clamp(flyPitch, -89.0f, 89.0f); + } + + glm::vec3 EditorCamera::getPosition() const { + return mode == Mode::Orbit ? orbitPosition : flyPosition; + } + + glm::mat4 EditorCamera::getViewMatrix() const { + if (mode == Mode::Orbit) { + return glm::lookAt(orbitPosition, orbitTarget, WORLD_UP); + } else { + return glm::lookAt(flyPosition, flyPosition + flyFront, WORLD_UP); + } + } + + glm::mat4 EditorCamera::getProjectionMatrix(float aspectRatio) const { + return glm::perspective(glm::radians(fov), aspectRatio, 0.1f, 1000.0f); + } + + Ray EditorCamera::screenToWorldRay(float localX, float localY, float vpWidth, + float vpHeight) const { + // Convert pixel coords to NDC [-1, 1] + // Screen Y increases downward; OpenGL NDC Y increases upward, so flip Y + float ndcX = (2.0f * localX) / vpWidth - 1.0f; + float ndcY = 1.0f - (2.0f * localY) / vpHeight; + + float aspect = vpWidth / vpHeight; + glm::mat4 proj = getProjectionMatrix(aspect); + glm::mat4 view = getViewMatrix(); + + glm::mat4 invProjView = glm::inverse(proj * view); + + glm::vec4 nearPoint = invProjView * glm::vec4(ndcX, ndcY, 0.0f, 1.0f); + glm::vec4 farPoint = invProjView * glm::vec4(ndcX, ndcY, 1.0f, 1.0f); + + nearPoint /= nearPoint.w; + farPoint /= farPoint.w; + + glm::vec3 origin = glm::vec3(nearPoint); + glm::vec3 direction = glm::normalize(glm::vec3(farPoint) - glm::vec3(nearPoint)); + + return {origin, direction}; + } } // namespace sauce::editor diff --git a/src/editor/SelectionManager.cpp b/src/editor/SelectionManager.cpp index e88045e7..b2241076 100644 --- a/src/editor/SelectionManager.cpp +++ b/src/editor/SelectionManager.cpp @@ -1,14 +1,14 @@ -#include #include +#include namespace sauce::editor { -sauce::Entity* SelectionManager::getSelectedEntity(sauce::Scene& scene) { - auto& entities = scene.getEntitiesMut(); - if (selectedIndex < 0 || selectedIndex >= static_cast(entities.size())) { - return nullptr; - } - return &entities[selectedIndex]; -} + sauce::Entity* SelectionManager::getSelectedEntity(sauce::Scene& scene) { + auto& entities = scene.getEntitiesMut(); + if (selectedIndex < 0 || selectedIndex >= static_cast(entities.size())) { + return nullptr; + } + return &entities[selectedIndex]; + } } // namespace sauce::editor diff --git a/src/editor/gizmos/GizmoRenderer.cpp b/src/editor/gizmos/GizmoRenderer.cpp index 6c8cfde9..39dfc0a9 100644 --- a/src/editor/gizmos/GizmoRenderer.cpp +++ b/src/editor/gizmos/GizmoRenderer.cpp @@ -1,116 +1,109 @@ -#include #include #include +#include #include namespace sauce::editor { -GizmoRenderer::GizmoRenderer( - const sauce::PhysicalDevice& physicalDevice, - const sauce::LogicalDevice& logicalDevice, - const vk::raii::DescriptorSetLayout& descriptorSetLayout, - vk::Format colorFormat, - sauce::Renderer& renderer) - : pPhysicalDevice(&physicalDevice) - , pLogicalDevice(&logicalDevice) - , pRenderer(&renderer) -{ - sauce::GraphicsPipelineConfig gizmoConfig { - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .descriptorSetLayouts = { *descriptorSetLayout }, - .colorFormat = sauce::editor::OffscreenFramebuffer::COLOR_FORMAT, - .hasVertexInput = true, - .enableBlending = false, - .enableCulling = false, - .depthWrite = false, - .depthTestEnable = false, - .hasPushConstants = true, - .pushConstantSize = sizeof(MeshPushConstants), - }; - - pipeline = std::make_unique( - physicalDevice, logicalDevice, - gizmoConfig.descriptorSetLayouts, - colorFormat, - "shaders/editor_unlit.vert.spv", - "shaders/editor_unlit.frag.spv", - gizmoConfig - ); - - activeGizmo = std::make_unique(); - meshDirty = true; -} - -void GizmoRenderer::setActiveGizmo(GizmoType type) { - if (type == activeType && activeGizmo) return; - activeType = type; - - switch (type) { - case GizmoType::Translate: - activeGizmo = std::make_unique(); - break; - case GizmoType::Rotate: - activeGizmo = std::make_unique(); - break; - case GizmoType::Scale: - activeGizmo = std::make_unique(); - break; - } - meshDirty = true; -} - -void GizmoRenderer::uploadMesh() { - if (!activeGizmo) return; - - // Wait for GPU to finish using the old mesh before destroying it - if (mesh) { - (*pLogicalDevice)->waitIdle(); - } - - GizmoMeshData meshData = activeGizmo->generateMesh(); - mesh = std::make_unique(meshData.vertices, meshData.indices); - - auto& physDev = const_cast(**pPhysicalDevice); - auto& cmdPool = const_cast(pRenderer->getCommandPool()); - auto& queue = const_cast(pRenderer->getQueue()); - mesh->initVulkanResources(*pLogicalDevice, physDev, cmdPool, queue); - - meshDirty = false; -} - -void GizmoRenderer::render( - vk::raii::CommandBuffer& cmd, - const vk::raii::DescriptorSet& descriptorSet, - const glm::vec3& entityPosition, - const EditorCamera& camera, - float aspect) -{ - if (!activeGizmo) return; - if (meshDirty) uploadMesh(); - if (!mesh || !mesh->hasGPUData()) return; - - // Constant screen size: scale by distance to camera - float dist = glm::length(camera.getPosition() - entityPosition); - float gizmoScale = dist * SCALE_FACTOR; - - glm::mat4 model = glm::translate(glm::mat4(1.0f), entityPosition); - model = glm::scale(model, glm::vec3(gizmoScale)); - - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, **pipeline); - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - pipeline->getLayout(), 0, *descriptorSet, nullptr); - MeshPushConstants pushData {}; - pushData.model = model; - pushData.baseColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); - pushData.metallic = 0.0f; - pushData.roughness = 0.0f; - cmd.pushConstants(pipeline->getLayout(), - vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, pushData); - - auto& cmdRef = const_cast(cmd); - mesh->bind(cmdRef); - mesh->draw(cmdRef); -} + GizmoRenderer::GizmoRenderer(const sauce::PhysicalDevice& physicalDevice, + const sauce::LogicalDevice& logicalDevice, + const vk::raii::DescriptorSetLayout& descriptorSetLayout, + vk::Format colorFormat, sauce::Renderer& renderer) + : pPhysicalDevice(&physicalDevice), pLogicalDevice(&logicalDevice), pRenderer(&renderer) { + sauce::GraphicsPipelineConfig gizmoConfig{ + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .descriptorSetLayouts = {*descriptorSetLayout}, + .colorFormat = sauce::editor::OffscreenFramebuffer::COLOR_FORMAT, + .hasVertexInput = true, + .enableBlending = false, + .enableCulling = false, + .depthWrite = false, + .depthTestEnable = false, + .hasPushConstants = true, + .pushConstantSize = sizeof(MeshPushConstants), + }; + + pipeline = std::make_unique( + physicalDevice, logicalDevice, gizmoConfig.descriptorSetLayouts, colorFormat, + "shaders/editor_unlit.vert.spv", "shaders/editor_unlit.frag.spv", gizmoConfig); + + activeGizmo = std::make_unique(); + meshDirty = true; + } + + void GizmoRenderer::setActiveGizmo(GizmoType type) { + if (type == activeType && activeGizmo) + return; + activeType = type; + + switch (type) { + case GizmoType::Translate: + activeGizmo = std::make_unique(); + break; + case GizmoType::Rotate: + activeGizmo = std::make_unique(); + break; + case GizmoType::Scale: + activeGizmo = std::make_unique(); + break; + } + meshDirty = true; + } + + void GizmoRenderer::uploadMesh() { + if (!activeGizmo) + return; + + // Wait for GPU to finish using the old mesh before destroying it + if (mesh) { + (*pLogicalDevice)->waitIdle(); + } + + GizmoMeshData meshData = activeGizmo->generateMesh(); + mesh = std::make_unique(meshData.vertices, meshData.indices); + + auto& physDev = const_cast(**pPhysicalDevice); + auto& cmdPool = const_cast(pRenderer->getCommandPool()); + auto& queue = const_cast(pRenderer->getQueue()); + mesh->initVulkanResources(*pLogicalDevice, physDev, cmdPool, queue); + + meshDirty = false; + } + + void GizmoRenderer::render(vk::raii::CommandBuffer& cmd, + const vk::raii::DescriptorSet& descriptorSet, + const glm::vec3& entityPosition, const EditorCamera& camera, + float aspect) { + if (!activeGizmo) + return; + if (meshDirty) + uploadMesh(); + if (!mesh || !mesh->hasGPUData()) + return; + + // Constant screen size: scale by distance to camera + float dist = glm::length(camera.getPosition() - entityPosition); + float gizmoScale = dist * SCALE_FACTOR; + + glm::mat4 model = glm::translate(glm::mat4(1.0f), entityPosition); + model = glm::scale(model, glm::vec3(gizmoScale)); + + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, **pipeline); + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline->getLayout(), 0, + *descriptorSet, nullptr); + MeshPushConstants pushData{}; + pushData.model = model; + pushData.baseColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + pushData.metallic = 0.0f; + pushData.roughness = 0.0f; + cmd.pushConstants( + pipeline->getLayout(), + vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, pushData); + + auto& cmdRef = const_cast(cmd); + mesh->bind(cmdRef); + mesh->draw(cmdRef); + } } // namespace sauce::editor diff --git a/src/editor/gizmos/RotateGizmo.cpp b/src/editor/gizmos/RotateGizmo.cpp index 3542af93..c14e6464 100644 --- a/src/editor/gizmos/RotateGizmo.cpp +++ b/src/editor/gizmos/RotateGizmo.cpp @@ -1,142 +1,159 @@ -#include #include +#include namespace sauce::editor { -GizmoMeshData RotateGizmo::generateMesh() const { - GizmoMeshData data; - constexpr float PI = 3.14159265f; - - auto addRing = [&](GizmoAxis axis) { - glm::vec3 normal = axisDirection(axis); - glm::vec3 color = axisColor(axis); - - // Build tangent/bitangent for the ring plane - glm::vec3 tangent = (std::abs(normal.z) < 0.9f) ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0); - tangent = glm::normalize(tangent - normal * glm::dot(tangent, normal)); - glm::vec3 bitangent = glm::cross(normal, tangent); - - uint32_t base = static_cast(data.vertices.size()); - - for (int i = 0; i <= RING_SEGMENTS; ++i) { - float ringAngle = 2.0f * PI * static_cast(i) / static_cast(RING_SEGMENTS); - // Center of tube cross-section on the ring - glm::vec3 ringCenter = (tangent * std::cos(ringAngle) + bitangent * std::sin(ringAngle)) * RING_RADIUS; - glm::vec3 ringNormal = glm::normalize(ringCenter); - - // Build tube cross-section - glm::vec3 tubeUp = normal; - glm::vec3 tubeRight = ringNormal; - - for (int j = 0; j <= TUBE_SEGMENTS; ++j) { - float tubeAngle = 2.0f * PI * static_cast(j) / static_cast(TUBE_SEGMENTS); - glm::vec3 offset = (tubeRight * std::cos(tubeAngle) + tubeUp * std::sin(tubeAngle)) * TUBE_RADIUS; - - sauce::Vertex v{}; - v.position = ringCenter + offset; - v.normal = glm::normalize(offset); - v.color = color; - data.vertices.push_back(v); - } + GizmoMeshData RotateGizmo::generateMesh() const { + GizmoMeshData data; + constexpr float PI = 3.14159265f; + + auto addRing = [&](GizmoAxis axis) { + glm::vec3 normal = axisDirection(axis); + glm::vec3 color = axisColor(axis); + + // Build tangent/bitangent for the ring plane + glm::vec3 tangent = + (std::abs(normal.z) < 0.9f) ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0); + tangent = glm::normalize(tangent - normal * glm::dot(tangent, normal)); + glm::vec3 bitangent = glm::cross(normal, tangent); + + uint32_t base = static_cast(data.vertices.size()); + + for (int i = 0; i <= RING_SEGMENTS; ++i) { + float ringAngle = + 2.0f * PI * static_cast(i) / static_cast(RING_SEGMENTS); + // Center of tube cross-section on the ring + glm::vec3 ringCenter = + (tangent * std::cos(ringAngle) + bitangent * std::sin(ringAngle)) * RING_RADIUS; + glm::vec3 ringNormal = glm::normalize(ringCenter); + + // Build tube cross-section + glm::vec3 tubeUp = normal; + glm::vec3 tubeRight = ringNormal; + + for (int j = 0; j <= TUBE_SEGMENTS; ++j) { + float tubeAngle = + 2.0f * PI * static_cast(j) / static_cast(TUBE_SEGMENTS); + glm::vec3 offset = + (tubeRight * std::cos(tubeAngle) + tubeUp * std::sin(tubeAngle)) * + TUBE_RADIUS; + + sauce::Vertex v{}; + v.position = ringCenter + offset; + v.normal = glm::normalize(offset); + v.color = color; + data.vertices.push_back(v); + } + } + + // Indices: connect adjacent ring segments + uint32_t stride = TUBE_SEGMENTS + 1; + for (int i = 0; i < RING_SEGMENTS; ++i) { + for (int j = 0; j < TUBE_SEGMENTS; ++j) { + uint32_t a = + base + static_cast(i) * stride + static_cast(j); + uint32_t b = a + stride; + uint32_t c = a + 1; + uint32_t d = b + 1; + data.indices.push_back(a); + data.indices.push_back(b); + data.indices.push_back(c); + data.indices.push_back(c); + data.indices.push_back(b); + data.indices.push_back(d); + } + } + }; + + addRing(GizmoAxis::X); + addRing(GizmoAxis::Y); + addRing(GizmoAxis::Z); + + return data; } - // Indices: connect adjacent ring segments - uint32_t stride = TUBE_SEGMENTS + 1; - for (int i = 0; i < RING_SEGMENTS; ++i) { - for (int j = 0; j < TUBE_SEGMENTS; ++j) { - uint32_t a = base + static_cast(i) * stride + static_cast(j); - uint32_t b = a + stride; - uint32_t c = a + 1; - uint32_t d = b + 1; - data.indices.push_back(a); - data.indices.push_back(b); - data.indices.push_back(c); - data.indices.push_back(c); - data.indices.push_back(b); - data.indices.push_back(d); - } + GizmoAxis RotateGizmo::hitTest(const Ray& ray, const glm::vec3& position, + const glm::quat& /*rotation*/, float scale) const { + float hitThreshold = 0.1f * scale; + float bestDist = hitThreshold; + GizmoAxis bestAxis = GizmoAxis::None; + + for (auto axis : {GizmoAxis::X, GizmoAxis::Y, GizmoAxis::Z}) { + glm::vec3 normal = axisDirection(axis); + float radius = RING_RADIUS * scale; + + // Intersect ray with the ring's plane + float denom = glm::dot(normal, ray.direction); + if (std::abs(denom) < 1e-6f) + continue; + + float t = glm::dot(position - ray.origin, normal) / denom; + if (t < 0.0f) + continue; + + glm::vec3 hitPoint = ray.origin + t * ray.direction; + float distFromCenter = glm::length(hitPoint - position); + float ringDist = std::abs(distFromCenter - radius); + + if (ringDist < bestDist) { + bestDist = ringDist; + bestAxis = axis; + } + } + return bestAxis; } - }; - addRing(GizmoAxis::X); - addRing(GizmoAxis::Y); - addRing(GizmoAxis::Z); + float RotateGizmo::angleOnPlane(const Ray& ray, const glm::vec3& center, + const glm::vec3& normal) const { + float denom = glm::dot(normal, ray.direction); + if (std::abs(denom) < 1e-6f) + return 0.0f; + + float t = glm::dot(center - ray.origin, normal) / denom; + glm::vec3 hitPoint = ray.origin + t * ray.direction; + glm::vec3 dir = hitPoint - center; + + // Build local 2D axes on the plane + glm::vec3 tangent = (std::abs(normal.z) < 0.9f) ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0); + tangent = glm::normalize(tangent - normal * glm::dot(tangent, normal)); + glm::vec3 bitangent = glm::cross(normal, tangent); - return data; -} + return std::atan2(glm::dot(dir, bitangent), glm::dot(dir, tangent)); + } + + void RotateGizmo::beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, + const glm::quat& /*rotation*/) { + activeAxis = axis; + interacting = true; + lastEntityPos = entityPos; + lastAngle = angleOnPlane(ray, entityPos, axisDirection(axis)); + } -GizmoAxis RotateGizmo::hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& /*rotation*/, float scale) const { - float hitThreshold = 0.1f * scale; - float bestDist = hitThreshold; - GizmoAxis bestAxis = GizmoAxis::None; + glm::vec3 RotateGizmo::updateInteraction(const Ray& ray, const glm::vec3& entityPos, + const glm::quat& /*rotation*/) { + if (!interacting || activeAxis == GizmoAxis::None) + return glm::vec3(0.0f); - for (auto axis : { GizmoAxis::X, GizmoAxis::Y, GizmoAxis::Z }) { - glm::vec3 normal = axisDirection(axis); - float radius = RING_RADIUS * scale; + glm::vec3 normal = axisDirection(activeAxis); + float currentAngle = angleOnPlane(ray, lastEntityPos, normal); + float delta = currentAngle - lastAngle; - // Intersect ray with the ring's plane - float denom = glm::dot(normal, ray.direction); - if (std::abs(denom) < 1e-6f) continue; + // Wrap around + if (delta > 3.14159f) + delta -= 2.0f * 3.14159f; + if (delta < -3.14159f) + delta += 2.0f * 3.14159f; - float t = glm::dot(position - ray.origin, normal) / denom; - if (t < 0.0f) continue; + lastAngle = currentAngle; + lastEntityPos = entityPos; - glm::vec3 hitPoint = ray.origin + t * ray.direction; - float distFromCenter = glm::length(hitPoint - position); - float ringDist = std::abs(distFromCenter - radius); + // Return angle delta packed into the axis component + return normal * delta; + } - if (ringDist < bestDist) { - bestDist = ringDist; - bestAxis = axis; + void RotateGizmo::endInteraction() { + interacting = false; + activeAxis = GizmoAxis::None; } - } - return bestAxis; -} - -float RotateGizmo::angleOnPlane(const Ray& ray, const glm::vec3& center, const glm::vec3& normal) const { - float denom = glm::dot(normal, ray.direction); - if (std::abs(denom) < 1e-6f) return 0.0f; - - float t = glm::dot(center - ray.origin, normal) / denom; - glm::vec3 hitPoint = ray.origin + t * ray.direction; - glm::vec3 dir = hitPoint - center; - - // Build local 2D axes on the plane - glm::vec3 tangent = (std::abs(normal.z) < 0.9f) ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0); - tangent = glm::normalize(tangent - normal * glm::dot(tangent, normal)); - glm::vec3 bitangent = glm::cross(normal, tangent); - - return std::atan2(glm::dot(dir, bitangent), glm::dot(dir, tangent)); -} - -void RotateGizmo::beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, const glm::quat& /*rotation*/) { - activeAxis = axis; - interacting = true; - lastEntityPos = entityPos; - lastAngle = angleOnPlane(ray, entityPos, axisDirection(axis)); -} - -glm::vec3 RotateGizmo::updateInteraction(const Ray& ray, const glm::vec3& entityPos, const glm::quat& /*rotation*/) { - if (!interacting || activeAxis == GizmoAxis::None) return glm::vec3(0.0f); - - glm::vec3 normal = axisDirection(activeAxis); - float currentAngle = angleOnPlane(ray, lastEntityPos, normal); - float delta = currentAngle - lastAngle; - - // Wrap around - if (delta > 3.14159f) delta -= 2.0f * 3.14159f; - if (delta < -3.14159f) delta += 2.0f * 3.14159f; - - lastAngle = currentAngle; - lastEntityPos = entityPos; - - // Return angle delta packed into the axis component - return normal * delta; -} - -void RotateGizmo::endInteraction() { - interacting = false; - activeAxis = GizmoAxis::None; -} } // namespace sauce::editor diff --git a/src/editor/gizmos/ScaleGizmo.cpp b/src/editor/gizmos/ScaleGizmo.cpp index 084100dc..312a89cb 100644 --- a/src/editor/gizmos/ScaleGizmo.cpp +++ b/src/editor/gizmos/ScaleGizmo.cpp @@ -1,151 +1,154 @@ -#include #include +#include #define GLM_ENABLE_EXPERIMENTAL #include namespace sauce::editor { -GizmoMeshData ScaleGizmo::generateMesh() const { - GizmoMeshData data; - constexpr float PI = 3.14159265f; - - auto addHandle = [&](GizmoAxis axis) { - glm::vec3 dir = axisDirection(axis); - glm::vec3 color = axisColor(axis); - - glm::vec3 up = (std::abs(dir.z) < 0.9f) ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0); - glm::vec3 right = glm::normalize(glm::cross(dir, up)); - up = glm::normalize(glm::cross(right, dir)); - - uint32_t base = static_cast(data.vertices.size()); - - // --- Shaft (cylinder) --- - for (int i = 0; i <= SEGMENTS; ++i) { - float angle = 2.0f * PI * static_cast(i) / static_cast(SEGMENTS); - float c = std::cos(angle); - float s = std::sin(angle); - glm::vec3 offset = (right * c + up * s) * SHAFT_RADIUS; - glm::vec3 normal = glm::normalize(right * c + up * s); - - sauce::Vertex v0{}; - v0.position = offset; - v0.normal = normal; - v0.color = color; - data.vertices.push_back(v0); - - sauce::Vertex v1{}; - v1.position = dir * SHAFT_LENGTH + offset; - v1.normal = normal; - v1.color = color; - data.vertices.push_back(v1); - } - - for (int i = 0; i < SEGMENTS; ++i) { - uint32_t b = base + static_cast(i) * 2; - data.indices.push_back(b); - data.indices.push_back(b + 1); - data.indices.push_back(b + 2); - data.indices.push_back(b + 1); - data.indices.push_back(b + 3); - data.indices.push_back(b + 2); + GizmoMeshData ScaleGizmo::generateMesh() const { + GizmoMeshData data; + constexpr float PI = 3.14159265f; + + auto addHandle = [&](GizmoAxis axis) { + glm::vec3 dir = axisDirection(axis); + glm::vec3 color = axisColor(axis); + + glm::vec3 up = (std::abs(dir.z) < 0.9f) ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0); + glm::vec3 right = glm::normalize(glm::cross(dir, up)); + up = glm::normalize(glm::cross(right, dir)); + + uint32_t base = static_cast(data.vertices.size()); + + // --- Shaft (cylinder) --- + for (int i = 0; i <= SEGMENTS; ++i) { + float angle = 2.0f * PI * static_cast(i) / static_cast(SEGMENTS); + float c = std::cos(angle); + float s = std::sin(angle); + glm::vec3 offset = (right * c + up * s) * SHAFT_RADIUS; + glm::vec3 normal = glm::normalize(right * c + up * s); + + sauce::Vertex v0{}; + v0.position = offset; + v0.normal = normal; + v0.color = color; + data.vertices.push_back(v0); + + sauce::Vertex v1{}; + v1.position = dir * SHAFT_LENGTH + offset; + v1.normal = normal; + v1.color = color; + data.vertices.push_back(v1); + } + + for (int i = 0; i < SEGMENTS; ++i) { + uint32_t b = base + static_cast(i) * 2; + data.indices.push_back(b); + data.indices.push_back(b + 1); + data.indices.push_back(b + 2); + data.indices.push_back(b + 1); + data.indices.push_back(b + 3); + data.indices.push_back(b + 2); + } + + // --- Cube endpoint --- + glm::vec3 cubeCenter = dir * SHAFT_LENGTH; + float hs = CUBE_SIZE; // half-size + + // 8 corners of the cube + glm::vec3 corners[8]; + int idx = 0; + for (int x = -1; x <= 1; x += 2) { + for (int y = -1; y <= 1; y += 2) { + for (int z = -1; z <= 1; z += 2) { + corners[idx++] = cubeCenter + glm::vec3(x, y, z) * hs; + } + } + } + + // 6 faces (each as 2 triangles) + // Corner indices: 0(-,-,-) 1(-,-,+) 2(-,+,-) 3(-,+,+) 4(+,-,-) 5(+,-,+) 6(+,+,-) 7(+,+,+) + static const int faces[6][4] = { + {0, 1, 3, 2}, // -X + {4, 6, 7, 5}, // +X + {0, 4, 5, 1}, // -Y + {2, 3, 7, 6}, // +Y + {0, 2, 6, 4}, // -Z + {1, 5, 7, 3}, // +Z + }; + static const glm::vec3 faceNormals[6] = {{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, + {0, 1, 0}, {0, 0, -1}, {0, 0, 1}}; + + for (int f = 0; f < 6; ++f) { + uint32_t faceBase = static_cast(data.vertices.size()); + for (int v = 0; v < 4; ++v) { + sauce::Vertex vert{}; + vert.position = corners[faces[f][v]]; + vert.normal = faceNormals[f]; + vert.color = color; + data.vertices.push_back(vert); + } + data.indices.push_back(faceBase); + data.indices.push_back(faceBase + 1); + data.indices.push_back(faceBase + 2); + data.indices.push_back(faceBase); + data.indices.push_back(faceBase + 2); + data.indices.push_back(faceBase + 3); + } + }; + + addHandle(GizmoAxis::X); + addHandle(GizmoAxis::Y); + addHandle(GizmoAxis::Z); + + return data; } - // --- Cube endpoint --- - glm::vec3 cubeCenter = dir * SHAFT_LENGTH; - float hs = CUBE_SIZE; // half-size - - // 8 corners of the cube - glm::vec3 corners[8]; - int idx = 0; - for (int x = -1; x <= 1; x += 2) { - for (int y = -1; y <= 1; y += 2) { - for (int z = -1; z <= 1; z += 2) { - corners[idx++] = cubeCenter + glm::vec3(x, y, z) * hs; + GizmoAxis ScaleGizmo::hitTest(const Ray& ray, const glm::vec3& position, + const glm::quat& rotation, float scale) const { + float hitThreshold = 0.08f * scale; + float shaftLen = SHAFT_LENGTH * scale; + + float bestDist = hitThreshold; + GizmoAxis bestAxis = GizmoAxis::None; + + for (auto axis : {GizmoAxis::X, GizmoAxis::Y, GizmoAxis::Z}) { + glm::vec3 dir = rotation * axisDirection(axis); + float dist = distanceRayToLine(ray, position, dir, shaftLen); + if (dist < bestDist) { + bestDist = dist; + bestAxis = axis; + } } - } + return bestAxis; } - // 6 faces (each as 2 triangles) - // Corner indices: 0(-,-,-) 1(-,-,+) 2(-,+,-) 3(-,+,+) 4(+,-,-) 5(+,-,+) 6(+,+,-) 7(+,+,+) - static const int faces[6][4] = { - {0, 1, 3, 2}, // -X - {4, 6, 7, 5}, // +X - {0, 4, 5, 1}, // -Y - {2, 3, 7, 6}, // +Y - {0, 2, 6, 4}, // -Z - {1, 5, 7, 3}, // +Z - }; - static const glm::vec3 faceNormals[6] = { - {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} - }; - - for (int f = 0; f < 6; ++f) { - uint32_t faceBase = static_cast(data.vertices.size()); - for (int v = 0; v < 4; ++v) { - sauce::Vertex vert{}; - vert.position = corners[faces[f][v]]; - vert.normal = faceNormals[f]; - vert.color = color; - data.vertices.push_back(vert); - } - data.indices.push_back(faceBase); - data.indices.push_back(faceBase + 1); - data.indices.push_back(faceBase + 2); - data.indices.push_back(faceBase); - data.indices.push_back(faceBase + 2); - data.indices.push_back(faceBase + 3); + void ScaleGizmo::beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) { + activeAxis = axis; + interacting = true; + lastEntityPos = entityPos; + glm::vec3 dir = rotation * axisDirection(axis); + initialT = projectRayOntoAxis(ray, entityPos, dir); } - }; - - addHandle(GizmoAxis::X); - addHandle(GizmoAxis::Y); - addHandle(GizmoAxis::Z); - return data; -} + glm::vec3 ScaleGizmo::updateInteraction(const Ray& ray, const glm::vec3& entityPos, + const glm::quat& rotation) { + if (!interacting || activeAxis == GizmoAxis::None) + return glm::vec3(0.0f); -GizmoAxis ScaleGizmo::hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& rotation, float scale) const { - float hitThreshold = 0.08f * scale; - float shaftLen = SHAFT_LENGTH * scale; + glm::vec3 dir = rotation * axisDirection(activeAxis); + float currentT = projectRayOntoAxis(ray, lastEntityPos, dir); + float deltaT = currentT - initialT; + initialT = currentT; + lastEntityPos = entityPos; - float bestDist = hitThreshold; - GizmoAxis bestAxis = GizmoAxis::None; + // Return scale delta along the active axis + return dir * deltaT; + } - for (auto axis : { GizmoAxis::X, GizmoAxis::Y, GizmoAxis::Z }) { - glm::vec3 dir = rotation * axisDirection(axis); - float dist = distanceRayToLine(ray, position, dir, shaftLen); - if (dist < bestDist) { - bestDist = dist; - bestAxis = axis; + void ScaleGizmo::endInteraction() { + interacting = false; + activeAxis = GizmoAxis::None; } - } - return bestAxis; -} - -void ScaleGizmo::beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) { - activeAxis = axis; - interacting = true; - lastEntityPos = entityPos; - glm::vec3 dir = rotation * axisDirection(axis); - initialT = projectRayOntoAxis(ray, entityPos, dir); -} - -glm::vec3 ScaleGizmo::updateInteraction(const Ray& ray, const glm::vec3& entityPos, const glm::quat& rotation) { - if (!interacting || activeAxis == GizmoAxis::None) return glm::vec3(0.0f); - - glm::vec3 dir = rotation * axisDirection(activeAxis); - float currentT = projectRayOntoAxis(ray, lastEntityPos, dir); - float deltaT = currentT - initialT; - initialT = currentT; - lastEntityPos = entityPos; - - // Return scale delta along the active axis - return dir * deltaT; -} - -void ScaleGizmo::endInteraction() { - interacting = false; - activeAxis = GizmoAxis::None; -} } // namespace sauce::editor diff --git a/src/editor/gizmos/TranslateGizmo.cpp b/src/editor/gizmos/TranslateGizmo.cpp index ad582d1c..ae813eae 100644 --- a/src/editor/gizmos/TranslateGizmo.cpp +++ b/src/editor/gizmos/TranslateGizmo.cpp @@ -1,155 +1,160 @@ -#include #include +#include namespace sauce::editor { -GizmoMeshData TranslateGizmo::generateMesh() const { - GizmoMeshData data; - constexpr float PI = 3.14159265f; - - auto addArrow = [&](GizmoAxis axis) { - glm::vec3 dir = axisDirection(axis); - glm::vec3 color = axisColor(axis); - - // Build a local coordinate frame for the cylinder cross-section - glm::vec3 up = (std::abs(dir.z) < 0.9f) ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0); - glm::vec3 right = glm::normalize(glm::cross(dir, up)); - up = glm::normalize(glm::cross(right, dir)); - - uint32_t base = static_cast(data.vertices.size()); - - // --- Shaft (cylinder) --- - for (int i = 0; i <= SEGMENTS; ++i) { - float angle = 2.0f * PI * static_cast(i) / static_cast(SEGMENTS); - float c = std::cos(angle); - float s = std::sin(angle); - glm::vec3 offset = (right * c + up * s) * SHAFT_RADIUS; - glm::vec3 normal = glm::normalize(right * c + up * s); - - // Bottom ring vertex - sauce::Vertex v0{}; - v0.position = offset; - v0.normal = normal; - v0.color = color; - data.vertices.push_back(v0); - - // Top ring vertex - sauce::Vertex v1{}; - v1.position = dir * SHAFT_LENGTH + offset; - v1.normal = normal; - v1.color = color; - data.vertices.push_back(v1); - } - - // Shaft indices - for (int i = 0; i < SEGMENTS; ++i) { - uint32_t b = base + static_cast(i) * 2; - data.indices.push_back(b); - data.indices.push_back(b + 1); - data.indices.push_back(b + 2); - data.indices.push_back(b + 1); - data.indices.push_back(b + 3); - data.indices.push_back(b + 2); + GizmoMeshData TranslateGizmo::generateMesh() const { + GizmoMeshData data; + constexpr float PI = 3.14159265f; + + auto addArrow = [&](GizmoAxis axis) { + glm::vec3 dir = axisDirection(axis); + glm::vec3 color = axisColor(axis); + + // Build a local coordinate frame for the cylinder cross-section + glm::vec3 up = (std::abs(dir.z) < 0.9f) ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0); + glm::vec3 right = glm::normalize(glm::cross(dir, up)); + up = glm::normalize(glm::cross(right, dir)); + + uint32_t base = static_cast(data.vertices.size()); + + // --- Shaft (cylinder) --- + for (int i = 0; i <= SEGMENTS; ++i) { + float angle = 2.0f * PI * static_cast(i) / static_cast(SEGMENTS); + float c = std::cos(angle); + float s = std::sin(angle); + glm::vec3 offset = (right * c + up * s) * SHAFT_RADIUS; + glm::vec3 normal = glm::normalize(right * c + up * s); + + // Bottom ring vertex + sauce::Vertex v0{}; + v0.position = offset; + v0.normal = normal; + v0.color = color; + data.vertices.push_back(v0); + + // Top ring vertex + sauce::Vertex v1{}; + v1.position = dir * SHAFT_LENGTH + offset; + v1.normal = normal; + v1.color = color; + data.vertices.push_back(v1); + } + + // Shaft indices + for (int i = 0; i < SEGMENTS; ++i) { + uint32_t b = base + static_cast(i) * 2; + data.indices.push_back(b); + data.indices.push_back(b + 1); + data.indices.push_back(b + 2); + data.indices.push_back(b + 1); + data.indices.push_back(b + 3); + data.indices.push_back(b + 2); + } + + // --- Cone tip --- + uint32_t coneBase = static_cast(data.vertices.size()); + glm::vec3 coneStart = dir * SHAFT_LENGTH; + glm::vec3 coneTip = dir * (SHAFT_LENGTH + CONE_LENGTH); + + // Cone ring vertices + for (int i = 0; i <= SEGMENTS; ++i) { + float angle = 2.0f * PI * static_cast(i) / static_cast(SEGMENTS); + float c = std::cos(angle); + float s = std::sin(angle); + glm::vec3 offset = (right * c + up * s) * CONE_RADIUS; + // Approximate cone normal + glm::vec3 sideDir = glm::normalize(offset); + glm::vec3 normal = glm::normalize(sideDir * CONE_LENGTH + dir * CONE_RADIUS); + + sauce::Vertex v{}; + v.position = coneStart + offset; + v.normal = normal; + v.color = color; + data.vertices.push_back(v); + } + + // Tip vertex + uint32_t tipIdx = static_cast(data.vertices.size()); + sauce::Vertex tipV{}; + tipV.position = coneTip; + tipV.normal = dir; + tipV.color = color; + data.vertices.push_back(tipV); + + // Cone side indices + for (int i = 0; i < SEGMENTS; ++i) { + data.indices.push_back(coneBase + static_cast(i)); + data.indices.push_back(tipIdx); + data.indices.push_back(coneBase + static_cast(i) + 1); + } + + // Cone base cap + uint32_t capCenter = static_cast(data.vertices.size()); + sauce::Vertex centerV{}; + centerV.position = coneStart; + centerV.normal = -dir; + centerV.color = color; + data.vertices.push_back(centerV); + + for (int i = 0; i < SEGMENTS; ++i) { + data.indices.push_back(capCenter); + data.indices.push_back(coneBase + static_cast(i) + 1); + data.indices.push_back(coneBase + static_cast(i)); + } + }; + + addArrow(GizmoAxis::X); + addArrow(GizmoAxis::Y); + addArrow(GizmoAxis::Z); + + return data; } - // --- Cone tip --- - uint32_t coneBase = static_cast(data.vertices.size()); - glm::vec3 coneStart = dir * SHAFT_LENGTH; - glm::vec3 coneTip = dir * (SHAFT_LENGTH + CONE_LENGTH); - - // Cone ring vertices - for (int i = 0; i <= SEGMENTS; ++i) { - float angle = 2.0f * PI * static_cast(i) / static_cast(SEGMENTS); - float c = std::cos(angle); - float s = std::sin(angle); - glm::vec3 offset = (right * c + up * s) * CONE_RADIUS; - // Approximate cone normal - glm::vec3 sideDir = glm::normalize(offset); - glm::vec3 normal = glm::normalize(sideDir * CONE_LENGTH + dir * CONE_RADIUS); - - sauce::Vertex v{}; - v.position = coneStart + offset; - v.normal = normal; - v.color = color; - data.vertices.push_back(v); + GizmoAxis TranslateGizmo::hitTest(const Ray& ray, const glm::vec3& position, + const glm::quat& /*rotation*/, float scale) const { + float hitThreshold = 0.08f * scale; + float shaftLen = (SHAFT_LENGTH + CONE_LENGTH) * scale; + + float bestDist = hitThreshold; + GizmoAxis bestAxis = GizmoAxis::None; + + for (auto axis : {GizmoAxis::X, GizmoAxis::Y, GizmoAxis::Z}) { + glm::vec3 dir = axisDirection(axis); + float dist = distanceRayToLine(ray, position, dir, shaftLen); + if (dist < bestDist) { + bestDist = dist; + bestAxis = axis; + } + } + return bestAxis; } - // Tip vertex - uint32_t tipIdx = static_cast(data.vertices.size()); - sauce::Vertex tipV{}; - tipV.position = coneTip; - tipV.normal = dir; - tipV.color = color; - data.vertices.push_back(tipV); - - // Cone side indices - for (int i = 0; i < SEGMENTS; ++i) { - data.indices.push_back(coneBase + static_cast(i)); - data.indices.push_back(tipIdx); - data.indices.push_back(coneBase + static_cast(i) + 1); + void TranslateGizmo::beginInteraction(GizmoAxis axis, const Ray& ray, + const glm::vec3& entityPos, + const glm::quat& /*rotation*/) { + activeAxis = axis; + interacting = true; + lastEntityPos = entityPos; + initialT = projectRayOntoAxis(ray, entityPos, axisDirection(axis)); } - // Cone base cap - uint32_t capCenter = static_cast(data.vertices.size()); - sauce::Vertex centerV{}; - centerV.position = coneStart; - centerV.normal = -dir; - centerV.color = color; - data.vertices.push_back(centerV); - - for (int i = 0; i < SEGMENTS; ++i) { - data.indices.push_back(capCenter); - data.indices.push_back(coneBase + static_cast(i) + 1); - data.indices.push_back(coneBase + static_cast(i)); + glm::vec3 TranslateGizmo::updateInteraction(const Ray& ray, const glm::vec3& entityPos, + const glm::quat& /*rotation*/) { + if (!interacting || activeAxis == GizmoAxis::None) + return glm::vec3(0.0f); + + glm::vec3 dir = axisDirection(activeAxis); + float currentT = projectRayOntoAxis(ray, lastEntityPos, dir); + float deltaT = currentT - initialT; + initialT = currentT; + lastEntityPos = entityPos; + return dir * deltaT; } - }; - - addArrow(GizmoAxis::X); - addArrow(GizmoAxis::Y); - addArrow(GizmoAxis::Z); - - return data; -} - -GizmoAxis TranslateGizmo::hitTest(const Ray& ray, const glm::vec3& position, const glm::quat& /*rotation*/, float scale) const { - float hitThreshold = 0.08f * scale; - float shaftLen = (SHAFT_LENGTH + CONE_LENGTH) * scale; - - float bestDist = hitThreshold; - GizmoAxis bestAxis = GizmoAxis::None; - for (auto axis : { GizmoAxis::X, GizmoAxis::Y, GizmoAxis::Z }) { - glm::vec3 dir = axisDirection(axis); - float dist = distanceRayToLine(ray, position, dir, shaftLen); - if (dist < bestDist) { - bestDist = dist; - bestAxis = axis; + void TranslateGizmo::endInteraction() { + interacting = false; + activeAxis = GizmoAxis::None; } - } - return bestAxis; -} - -void TranslateGizmo::beginInteraction(GizmoAxis axis, const Ray& ray, const glm::vec3& entityPos, const glm::quat& /*rotation*/) { - activeAxis = axis; - interacting = true; - lastEntityPos = entityPos; - initialT = projectRayOntoAxis(ray, entityPos, axisDirection(axis)); -} - -glm::vec3 TranslateGizmo::updateInteraction(const Ray& ray, const glm::vec3& entityPos, const glm::quat& /*rotation*/) { - if (!interacting || activeAxis == GizmoAxis::None) return glm::vec3(0.0f); - - glm::vec3 dir = axisDirection(activeAxis); - float currentT = projectRayOntoAxis(ray, lastEntityPos, dir); - float deltaT = currentT - initialT; - initialT = currentT; - lastEntityPos = entityPos; - return dir * deltaT; -} - -void TranslateGizmo::endInteraction() { - interacting = false; - activeAxis = GizmoAxis::None; -} } // namespace sauce::editor diff --git a/src/editor/main.cpp b/src/editor/main.cpp index ca1ed6aa..f6088d86 100644 --- a/src/editor/main.cpp +++ b/src/editor/main.cpp @@ -2,26 +2,26 @@ #include int main(int argc, char** argv) { - try { - sauce::editor::EditorApp app; + try { + sauce::editor::EditorApp app; - // Pass scene file path if provided as first argument - std::string sceneFile; - for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - if (arg.ends_with(".gltf") || arg.ends_with(".glb")) { - sceneFile = arg; - break; - } - } - if (!sceneFile.empty()) { - app.setInitialSceneFile(sceneFile); - } + // Pass scene file path if provided as first argument + std::string sceneFile; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg.ends_with(".gltf") || arg.ends_with(".glb")) { + sceneFile = arg; + break; + } + } + if (!sceneFile.empty()) { + app.setInitialSceneFile(sceneFile); + } - app.run(); - } catch (const std::exception& e) { - std::cerr << "Fatal error: " << e.what() << std::endl; - return EXIT_FAILURE; - } - return EXIT_SUCCESS; + app.run(); + } catch (const std::exception& e) { + std::cerr << "Fatal error: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } diff --git a/src/editor/panels/AssetBrowserPanel.cpp b/src/editor/panels/AssetBrowserPanel.cpp index 2d8aa594..7943a08a 100644 --- a/src/editor/panels/AssetBrowserPanel.cpp +++ b/src/editor/panels/AssetBrowserPanel.cpp @@ -1,269 +1,277 @@ -#include -#include #include +#include +#include +#include #include #include -#include namespace sauce::editor { -static std::string formatFileSize(uintmax_t size) { - if (size < 1024) return std::to_string(size) + " B"; - if (size < 1024 * 1024) return std::to_string(size / 1024) + " KB"; - return std::to_string(size / (1024 * 1024)) + " MB"; -} - -AssetBrowserPanel::AssetBrowserPanel(EditorApp& app) - : EditorPanel("Asset Browser", app) { - - rootPath = std::filesystem::current_path() / "assets"; - if (!std::filesystem::exists(rootPath)) { - rootPath = std::filesystem::current_path(); - } - currentPath = rootPath; -} - -void AssetBrowserPanel::render() { - ImGui::Begin(title.c_str(), &isOpen); - - // Breadcrumb navigation - { - if (currentPath != rootPath) { - if (ImGui::Button("Up")) { - currentPath = currentPath.parent_path(); - if (!currentPath.string().starts_with(rootPath.string())) { - currentPath = rootPath; - } - } - ImGui::SameLine(); + static std::string formatFileSize(uintmax_t size) { + if (size < 1024) + return std::to_string(size) + " B"; + if (size < 1024 * 1024) + return std::to_string(size / 1024) + " KB"; + return std::to_string(size / (1024 * 1024)) + " MB"; } - if (ImGui::Button("Refresh")) { - // Just re-entering the directory triggers re-read - } - ImGui::SameLine(); - - auto relativePath = std::filesystem::relative(currentPath, rootPath); - ImGui::TextDisabled("Root /"); - ImGui::SameLine(0, 0); - ImGui::Text(" %s", relativePath.string().c_str()); - } - - ImGui::Separator(); - - // Two-column layout - float treeWidth = ImGui::GetContentRegionAvail().x * 0.28f; - - // Directory tree (left) - ImGui::BeginChild("DirTree", ImVec2(treeWidth, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX); - if (std::filesystem::exists(rootPath)) { - drawDirectoryTree(rootPath); - } - ImGui::EndChild(); - - ImGui::SameLine(); - - // File list (right) - ImGui::BeginChild("FileList", ImVec2(0, 0), ImGuiChildFlags_Borders); - drawFileList(); - ImGui::EndChild(); - - ImGui::End(); -} - -void AssetBrowserPanel::drawDirectoryTree(const std::filesystem::path& dir) { - try { - // Collect and sort directories - std::vector dirs; - for (const auto& entry : std::filesystem::directory_iterator(dir)) { - if (entry.is_directory() && entry.path().filename().string()[0] != '.') { - dirs.push_back(entry); - } - } - std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) { - return a.path().filename() < b.path().filename(); - }); - - for (const auto& entry : dirs) { - std::string name = entry.path().filename().string(); - - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth; - - bool hasSubDirs = false; - try { - for (const auto& sub : std::filesystem::directory_iterator(entry.path())) { - if (sub.is_directory() && sub.path().filename().string()[0] != '.') { - hasSubDirs = true; - break; - } + AssetBrowserPanel::AssetBrowserPanel(EditorApp& app) : EditorPanel("Asset Browser", app) { + rootPath = std::filesystem::current_path() / "assets"; + if (!std::filesystem::exists(rootPath)) { + rootPath = std::filesystem::current_path(); } - } catch (...) {} + currentPath = rootPath; + } - if (!hasSubDirs) { - flags |= ImGuiTreeNodeFlags_Leaf; - } + void AssetBrowserPanel::render() { + ImGui::Begin(title.c_str(), &isOpen); + + // Breadcrumb navigation + { + if (currentPath != rootPath) { + if (ImGui::Button("Up")) { + currentPath = currentPath.parent_path(); + if (!currentPath.string().starts_with(rootPath.string())) { + currentPath = rootPath; + } + } + ImGui::SameLine(); + } - if (currentPath == entry.path()) { - flags |= ImGuiTreeNodeFlags_Selected; - } + if (ImGui::Button("Refresh")) { + // Just re-entering the directory triggers re-read + } + ImGui::SameLine(); - bool nodeOpen = ImGui::TreeNodeEx(name.c_str(), flags); + auto relativePath = std::filesystem::relative(currentPath, rootPath); + ImGui::TextDisabled("Root /"); + ImGui::SameLine(0, 0); + ImGui::Text(" %s", relativePath.string().c_str()); + } - if (ImGui::IsItemClicked()) { - currentPath = entry.path(); - } + ImGui::Separator(); - if (nodeOpen) { - drawDirectoryTree(entry.path()); - ImGui::TreePop(); - } - } - } catch (const std::filesystem::filesystem_error&) { - ImGui::TextDisabled("Access denied"); - } -} - -void AssetBrowserPanel::drawFileList() { - if (!std::filesystem::exists(currentPath)) { - ImGui::TextDisabled("Directory not found"); - return; - } - - try { - // Collect entries - std::vector dirs; - std::vector files; - - for (const auto& entry : std::filesystem::directory_iterator(currentPath)) { - if (entry.path().filename().string()[0] == '.') continue; // skip hidden - if (entry.is_directory()) { - dirs.push_back(entry); - } else if (entry.is_regular_file()) { - files.push_back(entry); - } - } + // Two-column layout + float treeWidth = ImGui::GetContentRegionAvail().x * 0.28f; - std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) { - return a.path().filename() < b.path().filename(); - }); - std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) { - return a.path().filename() < b.path().filename(); - }); - - // Table layout - if (ImGui::BeginTable("FileTable", 2, - ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBody)) { - - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 70.0f); - ImGui::TableHeadersRow(); - - // Directories first - for (const auto& entry : dirs) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - - std::string name = entry.path().filename().string(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.9f, 1.0f)); - if (ImGui::Selectable(name.c_str(), false, - ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) { - if (ImGui::IsMouseDoubleClicked(0)) { - currentPath = entry.path(); - } - } - ImGui::PopStyleColor(); - - ImGui::TableSetColumnIndex(1); - ImGui::TextDisabled("DIR"); - } - - // Then files - for (const auto& entry : files) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - - std::string name = entry.path().filename().string(); - std::string ext = entry.path().extension().string(); - bool isGltf = (ext == ".gltf" || ext == ".glb"); - bool isImage = (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" || ext == ".tga"); - bool isShader = (ext == ".slang" || ext == ".spv" || ext == ".glsl" || ext == ".vert" || ext == ".frag"); - - if (isGltf) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 0.85f, 0.3f, 1.0f)); - } else if (isImage) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.85f, 0.7f, 0.3f, 1.0f)); - } else if (isShader) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.3f, 0.85f, 1.0f)); + // Directory tree (left) + ImGui::BeginChild("DirTree", ImVec2(treeWidth, 0), + ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX); + if (std::filesystem::exists(rootPath)) { + drawDirectoryTree(rootPath); } + ImGui::EndChild(); - bool selected = (selectedFile == entry.path()); - if (ImGui::Selectable(name.c_str(), selected, - ImGuiSelectableFlags_SpanAllColumns)) { - selectedFile = entry.path(); - } + ImGui::SameLine(); - if (isGltf || isImage || isShader) { - ImGui::PopStyleColor(); - } + // File list (right) + ImGui::BeginChild("FileList", ImVec2(0, 0), ImGuiChildFlags_Borders); + drawFileList(); + ImGui::EndChild(); - // Drag source - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { - std::string pathStr = entry.path().string(); - ImGui::SetDragDropPayload("ASSET_FILE", pathStr.c_str(), pathStr.size() + 1); - ImGui::Text("%s", name.c_str()); - ImGui::EndDragDropSource(); - } + ImGui::End(); + } - // Context menu for GLTF files - if (isGltf && ImGui::BeginPopupContextItem()) { - if (ImGui::MenuItem("Open as Scene")) { - app.openScene(entry.path().string()); - } - ImGui::Separator(); - if (ImGui::MenuItem("Import to Scene")) { - importGLTF(entry.path()); - } - if (ImGui::MenuItem("Import (Flattened)")) { - try { - app.getScene().loadGLTFModel(entry.path().string(), false); - app.setStatusMessage("Imported (flat): " + name); - } catch (const std::exception& e) { - app.setStatusMessage(std::string("Import failed: ") + e.what()); + void AssetBrowserPanel::drawDirectoryTree(const std::filesystem::path& dir) { + try { + // Collect and sort directories + std::vector dirs; + for (const auto& entry : std::filesystem::directory_iterator(dir)) { + if (entry.is_directory() && entry.path().filename().string()[0] != '.') { + dirs.push_back(entry); + } } - } - ImGui::EndPopup(); + std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) { + return a.path().filename() < b.path().filename(); + }); + + for (const auto& entry : dirs) { + std::string name = entry.path().filename().string(); + + ImGuiTreeNodeFlags flags = + ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth; + + bool hasSubDirs = false; + try { + for (const auto& sub : std::filesystem::directory_iterator(entry.path())) { + if (sub.is_directory() && sub.path().filename().string()[0] != '.') { + hasSubDirs = true; + break; + } + } + } catch (...) { + } + + if (!hasSubDirs) { + flags |= ImGuiTreeNodeFlags_Leaf; + } + + if (currentPath == entry.path()) { + flags |= ImGuiTreeNodeFlags_Selected; + } + + bool nodeOpen = ImGui::TreeNodeEx(name.c_str(), flags); + + if (ImGui::IsItemClicked()) { + currentPath = entry.path(); + } + + if (nodeOpen) { + drawDirectoryTree(entry.path()); + ImGui::TreePop(); + } + } + } catch (const std::filesystem::filesystem_error&) { + ImGui::TextDisabled("Access denied"); + } + } + + void AssetBrowserPanel::drawFileList() { + if (!std::filesystem::exists(currentPath)) { + ImGui::TextDisabled("Directory not found"); + return; } - // File size column - ImGui::TableSetColumnIndex(1); try { - ImGui::TextDisabled("%s", formatFileSize(entry.file_size()).c_str()); - } catch (...) { - ImGui::TextDisabled("?"); + // Collect entries + std::vector dirs; + std::vector files; + + for (const auto& entry : std::filesystem::directory_iterator(currentPath)) { + if (entry.path().filename().string()[0] == '.') + continue; // skip hidden + if (entry.is_directory()) { + dirs.push_back(entry); + } else if (entry.is_regular_file()) { + files.push_back(entry); + } + } + + std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) { + return a.path().filename() < b.path().filename(); + }); + std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) { + return a.path().filename() < b.path().filename(); + }); + + // Table layout + if (ImGui::BeginTable("FileTable", 2, + ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | + ImGuiTableFlags_NoBordersInBody)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 70.0f); + ImGui::TableHeadersRow(); + + // Directories first + for (const auto& entry : dirs) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + + std::string name = entry.path().filename().string(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.9f, 1.0f)); + if (ImGui::Selectable(name.c_str(), false, + ImGuiSelectableFlags_SpanAllColumns | + ImGuiSelectableFlags_AllowDoubleClick)) { + if (ImGui::IsMouseDoubleClicked(0)) { + currentPath = entry.path(); + } + } + ImGui::PopStyleColor(); + + ImGui::TableSetColumnIndex(1); + ImGui::TextDisabled("DIR"); + } + + // Then files + for (const auto& entry : files) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + + std::string name = entry.path().filename().string(); + std::string ext = entry.path().extension().string(); + bool isGltf = (ext == ".gltf" || ext == ".glb"); + bool isImage = (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || + ext == ".bmp" || ext == ".tga"); + bool isShader = (ext == ".slang" || ext == ".spv" || ext == ".glsl" || + ext == ".vert" || ext == ".frag"); + + if (isGltf) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 0.85f, 0.3f, 1.0f)); + } else if (isImage) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.85f, 0.7f, 0.3f, 1.0f)); + } else if (isShader) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.3f, 0.85f, 1.0f)); + } + + bool selected = (selectedFile == entry.path()); + if (ImGui::Selectable(name.c_str(), selected, + ImGuiSelectableFlags_SpanAllColumns)) { + selectedFile = entry.path(); + } + + if (isGltf || isImage || isShader) { + ImGui::PopStyleColor(); + } + + // Drag source + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { + std::string pathStr = entry.path().string(); + ImGui::SetDragDropPayload("ASSET_FILE", pathStr.c_str(), + pathStr.size() + 1); + ImGui::Text("%s", name.c_str()); + ImGui::EndDragDropSource(); + } + + // Context menu for GLTF files + if (isGltf && ImGui::BeginPopupContextItem()) { + if (ImGui::MenuItem("Open as Scene")) { + app.openScene(entry.path().string()); + } + ImGui::Separator(); + if (ImGui::MenuItem("Import to Scene")) { + importGLTF(entry.path()); + } + if (ImGui::MenuItem("Import (Flattened)")) { + try { + app.getScene().loadGLTFModel(entry.path().string(), false); + app.setStatusMessage("Imported (flat): " + name); + } catch (const std::exception& e) { + app.setStatusMessage(std::string("Import failed: ") + e.what()); + } + } + ImGui::EndPopup(); + } + + // File size column + ImGui::TableSetColumnIndex(1); + try { + ImGui::TextDisabled("%s", formatFileSize(entry.file_size()).c_str()); + } catch (...) { + ImGui::TextDisabled("?"); + } + } + + ImGui::EndTable(); + } + } catch (const std::filesystem::filesystem_error&) { + ImGui::TextDisabled("Error reading directory"); } - } + } - ImGui::EndTable(); + void AssetBrowserPanel::importGLTF(const std::filesystem::path& path) { + app.importGLTFToScene(path.string()); + } + + void AssetBrowserPanel::handleFileDrop(const std::string& path) { + std::filesystem::path filePath(path); + std::string ext = filePath.extension().string(); + + if (ext == ".gltf" || ext == ".glb") { + importGLTF(filePath); + } else { + app.setStatusMessage("Unsupported file type: " + ext); + } } - } catch (const std::filesystem::filesystem_error&) { - ImGui::TextDisabled("Error reading directory"); - } -} - -void AssetBrowserPanel::importGLTF(const std::filesystem::path& path) { - app.importGLTFToScene(path.string()); -} - -void AssetBrowserPanel::handleFileDrop(const std::string& path) { - std::filesystem::path filePath(path); - std::string ext = filePath.extension().string(); - - if (ext == ".gltf" || ext == ".glb") { - importGLTF(filePath); - } else { - app.setStatusMessage("Unsupported file type: " + ext); - } -} } // namespace sauce::editor diff --git a/src/editor/panels/InspectorPanel.cpp b/src/editor/panels/InspectorPanel.cpp index 84676f7e..3870ca5b 100644 --- a/src/editor/panels/InspectorPanel.cpp +++ b/src/editor/panels/InspectorPanel.cpp @@ -1,324 +1,339 @@ -#include -#include -#include #include -#include +#include #include -#include +#include #include +#include #include +#include +#include #include #include -#include -#include #include +#include +#include -#include #include #include +#include namespace sauce::editor { + InspectorPanel::InspectorPanel(EditorApp& app) : EditorPanel("Inspector", app) { + } -InspectorPanel::InspectorPanel(EditorApp& app) - : EditorPanel("Inspector", app) {} - -void InspectorPanel::render() { - ImGui::Begin(title.c_str(), &isOpen); - - auto& selection = app.getSelectionManager(); - auto* entity = selection.getSelectedEntity(app.getScene()); - - if (!entity) { - ImGui::TextDisabled("No entity selected"); - ImGui::End(); - return; - } + void InspectorPanel::render() { + ImGui::Begin(title.c_str(), &isOpen); - // Editable entity name - { - char nameBuf[256]; - std::string currentName = entity->get_name(); - strncpy(nameBuf, currentName.c_str(), sizeof(nameBuf) - 1); - nameBuf[sizeof(nameBuf) - 1] = '\0'; + auto& selection = app.getSelectionManager(); + auto* entity = selection.getSelectedEntity(app.getScene()); - ImGui::SetNextItemWidth(-60); - if (ImGui::InputText("##EntityName", nameBuf, sizeof(nameBuf), - ImGuiInputTextFlags_EnterReturnsTrue)) { - entity->set_name(nameBuf); - app.setStatusMessage("Renamed to: " + std::string(nameBuf)); - } - ImGui::SameLine(); + if (!entity) { + ImGui::TextDisabled("No entity selected"); + ImGui::End(); + return; + } - // Active checkbox - bool active = entity->getActive(); - if (ImGui::Checkbox("##Active", &active)) { - entity->setActive(active); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Active"); - } - } - - ImGui::Separator(); - - drawTransformSection(*entity); - drawMeshRendererSection(*entity); - - ImGui::Spacing(); - ImGui::Spacing(); - - // Add Component button - float width = ImGui::GetContentRegionAvail().x; - if (ImGui::Button("Add Component", ImVec2(width, 0))) { - ImGui::OpenPopup("AddComponentPopup"); - } - - if (ImGui::BeginPopup("AddComponentPopup")) { - if (ImGui::MenuItem("Transform")) { - if (!entity->getComponent()) { - entity->addComponent(); - app.setStatusMessage("Added TransformComponent"); - } else { - app.setStatusMessage("Entity already has a TransformComponent"); - } - } - if (ImGui::MenuItem("Mesh Renderer")) { - entity->addComponent(); - app.setStatusMessage("Added MeshRendererComponent"); - } - ImGui::EndPopup(); - } + // Editable entity name + { + char nameBuf[256]; + std::string currentName = entity->get_name(); + strncpy(nameBuf, currentName.c_str(), sizeof(nameBuf) - 1); + nameBuf[sizeof(nameBuf) - 1] = '\0'; + + ImGui::SetNextItemWidth(-60); + if (ImGui::InputText("##EntityName", nameBuf, sizeof(nameBuf), + ImGuiInputTextFlags_EnterReturnsTrue)) { + entity->set_name(nameBuf); + app.setStatusMessage("Renamed to: " + std::string(nameBuf)); + } + ImGui::SameLine(); + + // Active checkbox + bool active = entity->getActive(); + if (ImGui::Checkbox("##Active", &active)) { + entity->setActive(active); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Active"); + } + } - ImGui::End(); -} + ImGui::Separator(); -static bool drawVec3Control(const std::string& label, glm::vec3& values, float resetValue, float speed) { - bool modified = false; + drawTransformSection(*entity); + drawMeshRendererSection(*entity); - ImGui::PushID(label.c_str()); + ImGui::Spacing(); + ImGui::Spacing(); - float labelWidth = 70.0f; - float totalWidth = ImGui::GetContentRegionAvail().x; - float fieldWidth = (totalWidth - labelWidth) / 3.0f - 4.0f; + // Add Component button + float width = ImGui::GetContentRegionAvail().x; + if (ImGui::Button("Add Component", ImVec2(width, 0))) { + ImGui::OpenPopup("AddComponentPopup"); + } - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s", label.c_str()); - ImGui::SameLine(labelWidth); + if (ImGui::BeginPopup("AddComponentPopup")) { + if (ImGui::MenuItem("Transform")) { + if (!entity->getComponent()) { + entity->addComponent(); + app.setStatusMessage("Added TransformComponent"); + } else { + app.setStatusMessage("Entity already has a TransformComponent"); + } + } + if (ImGui::MenuItem("Mesh Renderer")) { + entity->addComponent(); + app.setStatusMessage("Added MeshRendererComponent"); + } + ImGui::EndPopup(); + } - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 0)); + ImGui::End(); + } - const ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags_AutoSelectAll; + static bool drawVec3Control(const std::string& label, glm::vec3& values, float resetValue, + float speed) { + bool modified = false; - // X - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.35f, 0.15f, 0.15f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.45f, 0.20f, 0.20f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.55f, 0.25f, 0.25f, 1.0f)); - ImGui::SetNextItemWidth(fieldWidth); - if (ImGui::InputFloat("##X", &values.x, 0.0f, 0.0f, "%.2f", inputFlags)) modified = true; - ImGui::PopStyleColor(3); + ImGui::PushID(label.c_str()); - ImGui::SameLine(); + float labelWidth = 70.0f; + float totalWidth = ImGui::GetContentRegionAvail().x; + float fieldWidth = (totalWidth - labelWidth) / 3.0f - 4.0f; - // Y - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.35f, 0.15f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.20f, 0.45f, 0.20f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.25f, 0.55f, 0.25f, 1.0f)); - ImGui::SetNextItemWidth(fieldWidth); - if (ImGui::InputFloat("##Y", &values.y, 0.0f, 0.0f, "%.2f", inputFlags)) modified = true; - ImGui::PopStyleColor(3); + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", label.c_str()); + ImGui::SameLine(labelWidth); - ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 0)); - // Z - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.15f, 0.35f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.20f, 0.20f, 0.45f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.25f, 0.25f, 0.55f, 1.0f)); - ImGui::SetNextItemWidth(fieldWidth); - if (ImGui::InputFloat("##Z", &values.z, 0.0f, 0.0f, "%.2f", inputFlags)) modified = true; - ImGui::PopStyleColor(3); + const ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags_AutoSelectAll; - ImGui::PopStyleVar(); + // X + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.35f, 0.15f, 0.15f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.45f, 0.20f, 0.20f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.55f, 0.25f, 0.25f, 1.0f)); + ImGui::SetNextItemWidth(fieldWidth); + if (ImGui::InputFloat("##X", &values.x, 0.0f, 0.0f, "%.2f", inputFlags)) + modified = true; + ImGui::PopStyleColor(3); - // Normalize negative zero to positive zero - if (values.x == 0.0f) values.x = 0.0f; - if (values.y == 0.0f) values.y = 0.0f; - if (values.z == 0.0f) values.z = 0.0f; + ImGui::SameLine(); - ImGui::PopID(); + // Y + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.35f, 0.15f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.20f, 0.45f, 0.20f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.25f, 0.55f, 0.25f, 1.0f)); + ImGui::SetNextItemWidth(fieldWidth); + if (ImGui::InputFloat("##Y", &values.y, 0.0f, 0.0f, "%.2f", inputFlags)) + modified = true; + ImGui::PopStyleColor(3); - return modified; -} + ImGui::SameLine(); -void InspectorPanel::drawTransformSection(sauce::Entity& entity) { - auto* tc = entity.getComponent(); - if (!tc) return; + // Z + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.15f, 0.35f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.20f, 0.20f, 0.45f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.25f, 0.25f, 0.55f, 1.0f)); + ImGui::SetNextItemWidth(fieldWidth); + if (ImGui::InputFloat("##Z", &values.z, 0.0f, 0.0f, "%.2f", inputFlags)) + modified = true; + ImGui::PopStyleColor(3); - if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { - auto& transform = tc->getTransform(); + ImGui::PopStyleVar(); - // Position - glm::vec3 pos = transform.getTranslation(); - if (drawVec3Control("Position", pos, 0.0f, 0.1f)) { - transform.setTranslation(pos); - } + // Normalize negative zero to positive zero + if (values.x == 0.0f) + values.x = 0.0f; + if (values.y == 0.0f) + values.y = 0.0f; + if (values.z == 0.0f) + values.z = 0.0f; - // Rotation (euler degrees from quaternion) - glm::quat rot = transform.getRotation(); - glm::vec3 euler = glm::degrees(glm::eulerAngles(rot)); - if (drawVec3Control("Rotation", euler, 0.0f, 0.5f)) { - transform.setRotation(glm::quat(glm::radians(euler))); - } + ImGui::PopID(); - // Scale - glm::vec3 scale = transform.getScale(); - if (drawVec3Control("Scale", scale, 1.0f, 0.01f)) { - transform.setScale(scale); + return modified; } - } -} - -void InspectorPanel::drawMeshRendererSection(sauce::Entity& entity) { - auto mrcs = entity.getComponents(); - if (mrcs.empty()) return; - - for (size_t i = 0; i < mrcs.size(); ++i) { - auto* mrc = mrcs[i]; - ImGui::PushID(static_cast(i)); - - std::string headerLabel = (mrcs.size() == 1) - ? "Mesh Renderer" - : "Mesh " + std::to_string(i); - - bool removeRequested = false; - if (ImGui::CollapsingHeader(headerLabel.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { - auto mesh = mrc->getMesh(); - auto material = mrc->getMaterial(); - - const std::string& modelPath = mrc->getModelPath(); - std::string displayName = modelPath.empty() - ? "None" - : std::filesystem::path(modelPath).filename().string(); - - float clearBtnWidth = 24.0f; - float labelWidth = 50.0f; - float slotWidth = ImGui::GetContentRegionAvail().x - clearBtnWidth - ImGui::GetStyle().ItemSpacing.x - labelWidth; - - ImGui::AlignTextToFramePadding(); - ImGui::Text("Model"); - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered)); - ImGui::Button(displayName.c_str(), ImVec2(slotWidth, 0)); - ImGui::PopStyleColor(2); - - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Drag a .gltf/.glb from the Asset Browser to assign or replace"); - - if (ImGui::BeginDragDropTarget()) { - if (auto* payload = ImGui::AcceptDragDropPayload("ASSET_FILE")) { - std::string path(static_cast(payload->Data)); - std::string ext = std::filesystem::path(path).extension().string(); - if (ext == ".gltf" || ext == ".glb") { - app.replaceModelOnComponent(*mrc, path); - } - } - ImGui::EndDragDropTarget(); - } - - ImGui::SameLine(); - if (ImGui::Button("x##clearMesh", ImVec2(clearBtnWidth, 0))) { - app.clearModelOnComponent(*mrc); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Clear mesh"); - - if (mesh) { - ImGui::Text("Vertices: %zu", mesh->getVertexCount()); - ImGui::SameLine(0, 20); - ImGui::Text("Indices: %zu", mesh->getIndexCount()); - const auto& meshMeta = mesh->getMetadata(); - if (!meshMeta.empty()) - drawMetadataSection("Mesh Metadata", meshMeta); - } - - ImGui::Spacing(); - - if (material) { - ImGui::Text("Material: %s", material->getName().c_str()); - auto& props = material->getProperties(); - - glm::vec4 color = props.baseColorFactor; - if (ImGui::ColorEdit4("Base Color", glm::value_ptr(color), ImGuiColorEditFlags_Float)) { - props.baseColorFactor = color; - } - if (ImGui::SliderFloat("Metallic", &props.metallicFactor, 0.0f, 1.0f, "%.2f")) {} - if (ImGui::SliderFloat("Roughness", &props.roughnessFactor, 0.0f, 1.0f, "%.2f")) {} - - if (ImGui::TreeNode("Emissive")) { - glm::vec3 emissive = props.emissiveFactor; - if (ImGui::ColorEdit3("Emissive", glm::value_ptr(emissive))) { - props.emissiveFactor = emissive; - } - ImGui::TreePop(); - } - - const auto& matMeta = material->getMetadata(); - if (!matMeta.empty()) { - drawMetadataSection("Material Metadata", matMeta); + void InspectorPanel::drawTransformSection(sauce::Entity& entity) { + auto* tc = entity.getComponent(); + if (!tc) + return; + + if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { + auto& transform = tc->getTransform(); + + // Position + glm::vec3 pos = transform.getTranslation(); + if (drawVec3Control("Position", pos, 0.0f, 0.1f)) { + transform.setTranslation(pos); + } + + // Rotation (euler degrees from quaternion) + glm::quat rot = transform.getRotation(); + glm::vec3 euler = glm::degrees(glm::eulerAngles(rot)); + if (drawVec3Control("Rotation", euler, 0.0f, 0.5f)) { + transform.setRotation(glm::quat(glm::radians(euler))); + } + + // Scale + glm::vec3 scale = transform.getScale(); + if (drawVec3Control("Scale", scale, 1.0f, 0.01f)) { + transform.setScale(scale); + } } - } else { - ImGui::TextDisabled("No material assigned"); - } - - ImGui::Spacing(); - float removeWidth = ImGui::GetContentRegionAvail().x; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.15f, 0.15f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.2f, 0.2f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.8f, 0.25f, 0.25f, 1.0f)); - if (ImGui::Button("Remove Component", ImVec2(removeWidth, 0))) { - removeRequested = true; - } - ImGui::PopStyleColor(3); } - if (removeRequested) { - entity.removeComponentByPointer(mrc); - app.setStatusMessage("Removed MeshRendererComponent"); - ImGui::PopID(); - break; + void InspectorPanel::drawMeshRendererSection(sauce::Entity& entity) { + auto mrcs = entity.getComponents(); + if (mrcs.empty()) + return; + + for (size_t i = 0; i < mrcs.size(); ++i) { + auto* mrc = mrcs[i]; + ImGui::PushID(static_cast(i)); + + std::string headerLabel = + (mrcs.size() == 1) ? "Mesh Renderer" : "Mesh " + std::to_string(i); + + bool removeRequested = false; + if (ImGui::CollapsingHeader(headerLabel.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + auto mesh = mrc->getMesh(); + auto material = mrc->getMaterial(); + + const std::string& modelPath = mrc->getModelPath(); + std::string displayName = + modelPath.empty() ? "None" + : std::filesystem::path(modelPath).filename().string(); + + float clearBtnWidth = 24.0f; + float labelWidth = 50.0f; + float slotWidth = ImGui::GetContentRegionAvail().x - clearBtnWidth - + ImGui::GetStyle().ItemSpacing.x - labelWidth; + + ImGui::AlignTextToFramePadding(); + ImGui::Text("Model"); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered)); + ImGui::Button(displayName.c_str(), ImVec2(slotWidth, 0)); + ImGui::PopStyleColor(2); + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip( + "Drag a .gltf/.glb from the Asset Browser to assign or replace"); + + if (ImGui::BeginDragDropTarget()) { + if (auto* payload = ImGui::AcceptDragDropPayload("ASSET_FILE")) { + std::string path(static_cast(payload->Data)); + std::string ext = std::filesystem::path(path).extension().string(); + if (ext == ".gltf" || ext == ".glb") { + app.replaceModelOnComponent(*mrc, path); + } + } + ImGui::EndDragDropTarget(); + } + + ImGui::SameLine(); + if (ImGui::Button("x##clearMesh", ImVec2(clearBtnWidth, 0))) { + app.clearModelOnComponent(*mrc); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Clear mesh"); + + if (mesh) { + ImGui::Text("Vertices: %zu", mesh->getVertexCount()); + ImGui::SameLine(0, 20); + ImGui::Text("Indices: %zu", mesh->getIndexCount()); + const auto& meshMeta = mesh->getMetadata(); + if (!meshMeta.empty()) + drawMetadataSection("Mesh Metadata", meshMeta); + } + + ImGui::Spacing(); + + if (material) { + ImGui::Text("Material: %s", material->getName().c_str()); + auto& props = material->getProperties(); + + glm::vec4 color = props.baseColorFactor; + if (ImGui::ColorEdit4("Base Color", glm::value_ptr(color), + ImGuiColorEditFlags_Float)) { + props.baseColorFactor = color; + } + + if (ImGui::SliderFloat("Metallic", &props.metallicFactor, 0.0f, 1.0f, "%.2f")) { + } + if (ImGui::SliderFloat("Roughness", &props.roughnessFactor, 0.0f, 1.0f, + "%.2f")) { + } + + if (ImGui::TreeNode("Emissive")) { + glm::vec3 emissive = props.emissiveFactor; + if (ImGui::ColorEdit3("Emissive", glm::value_ptr(emissive))) { + props.emissiveFactor = emissive; + } + ImGui::TreePop(); + } + + const auto& matMeta = material->getMetadata(); + if (!matMeta.empty()) { + drawMetadataSection("Material Metadata", matMeta); + } + } else { + ImGui::TextDisabled("No material assigned"); + } + + ImGui::Spacing(); + float removeWidth = ImGui::GetContentRegionAvail().x; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.15f, 0.15f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.2f, 0.2f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.8f, 0.25f, 0.25f, 1.0f)); + if (ImGui::Button("Remove Component", ImVec2(removeWidth, 0))) { + removeRequested = true; + } + ImGui::PopStyleColor(3); + } + + if (removeRequested) { + entity.removeComponentByPointer(mrc); + app.setStatusMessage("Removed MeshRendererComponent"); + ImGui::PopID(); + break; + } + + ImGui::PopID(); + } } - ImGui::PopID(); - } -} - -void InspectorPanel::drawMetadataSection( - const std::string& label, - const std::unordered_map& metadata) { - - if (ImGui::TreeNode(label.c_str())) { - for (const auto& [key, value] : metadata) { - std::visit([&key](const auto& val) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - ImGui::Text("%s: %d", key.c_str(), val); - } else if constexpr (std::is_same_v) { - ImGui::Text("%s: %s", key.c_str(), val ? "true" : "false"); - } else if constexpr (std::is_same_v) { - ImGui::Text("%s: %.3f", key.c_str(), val); - } else if constexpr (std::is_same_v) { - ImGui::Text("%s: %.3f", key.c_str(), val); - } else if constexpr (std::is_same_v) { - ImGui::Text("%s: %s", key.c_str(), val.c_str()); + void InspectorPanel::drawMetadataSection( + const std::string& label, + const std::unordered_map& metadata) { + if (ImGui::TreeNode(label.c_str())) { + for (const auto& [key, value] : metadata) { + std::visit( + [&key](const auto& val) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + ImGui::Text("%s: %d", key.c_str(), val); + } else if constexpr (std::is_same_v) { + ImGui::Text("%s: %s", key.c_str(), val ? "true" : "false"); + } else if constexpr (std::is_same_v) { + ImGui::Text("%s: %.3f", key.c_str(), val); + } else if constexpr (std::is_same_v) { + ImGui::Text("%s: %.3f", key.c_str(), val); + } else if constexpr (std::is_same_v) { + ImGui::Text("%s: %s", key.c_str(), val.c_str()); + } + }, + value); + } + ImGui::TreePop(); } - }, value); } - ImGui::TreePop(); - } -} } // namespace sauce::editor diff --git a/src/editor/panels/SceneHierarchyPanel.cpp b/src/editor/panels/SceneHierarchyPanel.cpp index c8194715..0ed7246f 100644 --- a/src/editor/panels/SceneHierarchyPanel.cpp +++ b/src/editor/panels/SceneHierarchyPanel.cpp @@ -1,160 +1,168 @@ -#include -#include -#include #include -#include +#include #include +#include +#include +#include -#include #include #include +#include namespace sauce::editor { -SceneHierarchyPanel::SceneHierarchyPanel(EditorApp& app) - : EditorPanel("Hierarchy", app) {} - -void SceneHierarchyPanel::render() { - ImGui::Begin(title.c_str(), &isOpen); - - if (ImGui::Button("Create")) { - ImGui::OpenPopup("CreateEntityPopup"); - } - - if (ImGui::BeginPopup("CreateEntityPopup")) { - if (ImGui::MenuItem("Empty Entity")) {app.createEmptyEntity();} - if (ImGui::MenuItem("Box")){app.createBoxEntity();} - if (ImGui::MenuItem("Ball")){app.createBallEntity();} - ImGui::EndPopup(); - } - - ImGui::Separator(); - - - auto& scene = app.getScene(); - auto& entities = scene.getEntitiesMut(); - auto& selection = app.getSelectionManager(); - - // Search/filter bar - static char searchBuf[128] = ""; - ImGui::SetNextItemWidth(-1); - ImGui::InputTextWithHint("##Search", "Filter entities...", searchBuf, sizeof(searchBuf)); - - ImGui::Separator(); - - std::string filter(searchBuf); - - for (int i = 0; i < static_cast(entities.size()); ++i) { - auto& entity = entities[i]; - std::string name = entity.get_name(); - - // Filter - if (!filter.empty()) { - std::string lowerName = name; - std::string lowerFilter = filter; - std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); - std::transform(lowerFilter.begin(), lowerFilter.end(), lowerFilter.begin(), ::tolower); - if (lowerName.find(lowerFilter) == std::string::npos) { - continue; - } - } - - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | - ImGuiTreeNodeFlags_NoTreePushOnOpen | - ImGuiTreeNodeFlags_SpanAvailWidth; - - if (selection.getSelectedIndex() == i) { - flags |= ImGuiTreeNodeFlags_Selected; + SceneHierarchyPanel::SceneHierarchyPanel(EditorApp& app) : EditorPanel("Hierarchy", app) { } - bool active = entity.getActive(); - if (!active) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); - } - - // Show component indicators - bool hasMesh = entity.getComponent() != nullptr; - const char* icon = hasMesh ? "[M] " : " "; - char label[256]; - snprintf(label, sizeof(label), "%s%s", icon, name.c_str()); - - ImGui::TreeNodeEx(reinterpret_cast(static_cast(i)), flags, "%s", label); + void SceneHierarchyPanel::render() { + ImGui::Begin(title.c_str(), &isOpen); - if (!active) { - ImGui::PopStyleColor(); - } + if (ImGui::Button("Create")) { + ImGui::OpenPopup("CreateEntityPopup"); + } - if (ImGui::IsItemClicked()) { - selection.select(i); - } + if (ImGui::BeginPopup("CreateEntityPopup")) { + if (ImGui::MenuItem("Empty Entity")) { + app.createEmptyEntity(); + } + if (ImGui::MenuItem("Box")) { + app.createBoxEntity(); + } + if (ImGui::MenuItem("Ball")) { + app.createBallEntity(); + } + ImGui::EndPopup(); + } - // Double-click to focus - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { - auto* tc = entity.getComponent(); - if (tc) { - app.getEditorCamera().focusOn(tc->getTranslation()); - app.setStatusMessage("Focused on: " + name); - } - } + ImGui::Separator(); + + auto& scene = app.getScene(); + auto& entities = scene.getEntitiesMut(); + auto& selection = app.getSelectionManager(); + + // Search/filter bar + static char searchBuf[128] = ""; + ImGui::SetNextItemWidth(-1); + ImGui::InputTextWithHint("##Search", "Filter entities...", searchBuf, sizeof(searchBuf)); + + ImGui::Separator(); + + std::string filter(searchBuf); + + for (int i = 0; i < static_cast(entities.size()); ++i) { + auto& entity = entities[i]; + std::string name = entity.get_name(); + + // Filter + if (!filter.empty()) { + std::string lowerName = name; + std::string lowerFilter = filter; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + std::transform(lowerFilter.begin(), lowerFilter.end(), lowerFilter.begin(), + ::tolower); + if (lowerName.find(lowerFilter) == std::string::npos) { + continue; + } + } + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | + ImGuiTreeNodeFlags_NoTreePushOnOpen | + ImGuiTreeNodeFlags_SpanAvailWidth; + + if (selection.getSelectedIndex() == i) { + flags |= ImGuiTreeNodeFlags_Selected; + } + + bool active = entity.getActive(); + if (!active) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); + } + + // Show component indicators + bool hasMesh = entity.getComponent() != nullptr; + const char* icon = hasMesh ? "[M] " : " "; + char label[256]; + snprintf(label, sizeof(label), "%s%s", icon, name.c_str()); + + ImGui::TreeNodeEx(reinterpret_cast(static_cast(i)), flags, "%s", + label); + + if (!active) { + ImGui::PopStyleColor(); + } + + if (ImGui::IsItemClicked()) { + selection.select(i); + } + + // Double-click to focus + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { + auto* tc = entity.getComponent(); + if (tc) { + app.getEditorCamera().focusOn(tc->getTranslation()); + app.setStatusMessage("Focused on: " + name); + } + } + + // Right-click context menu + if (ImGui::BeginPopupContextItem()) { + if (ImGui::MenuItem("Duplicate")) { + sauce::Entity copy(name + " (Copy)"); + auto* tc = entity.getComponent(); + if (tc) { + copy.addComponent(tc->getTransform()); + } + scene.addEntity(std::move(copy)); + app.setStatusMessage("Duplicated: " + name); + } + if (ImGui::MenuItem("Delete")) { + std::string deletedName = name; + if (selection.getSelectedIndex() == i) { + selection.deselect(); + } else if (selection.getSelectedIndex() > i) { + selection.select(selection.getSelectedIndex() - 1); + } + // Wait for GPU to finish using entity's mesh buffers before destroying + app.getLogicalDevice()->waitIdle(); + entities.erase(entities.begin() + i); + app.setStatusMessage("Deleted: " + deletedName); + ImGui::EndPopup(); + break; + } + ImGui::Separator(); + if (ImGui::MenuItem("Toggle Active")) { + entity.setActive(!entity.getActive()); + } + ImGui::EndPopup(); + } + } - // Right-click context menu - if (ImGui::BeginPopupContextItem()) { - if (ImGui::MenuItem("Duplicate")) { - sauce::Entity copy(name + " (Copy)"); - auto* tc = entity.getComponent(); - if (tc) { - copy.addComponent(tc->getTransform()); + // Drop target for GLTF files in empty area + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_FILE")) { + std::string path(static_cast(payload->Data)); + std::string ext = std::filesystem::path(path).extension().string(); + if (ext == ".gltf" || ext == ".glb") { + app.importGLTFToScene(path); + } + } + ImGui::EndDragDropTarget(); } - scene.addEntity(std::move(copy)); - app.setStatusMessage("Duplicated: " + name); - } - if (ImGui::MenuItem("Delete")) { - std::string deletedName = name; - if (selection.getSelectedIndex() == i) { - selection.deselect(); - } else if (selection.getSelectedIndex() > i) { - selection.select(selection.getSelectedIndex() - 1); + + // Right-click in empty space + if (ImGui::BeginPopupContextWindow(nullptr, ImGuiPopupFlags_NoOpenOverItems | + ImGuiPopupFlags_MouseButtonRight)) { + if (ImGui::MenuItem("Add Empty Entity")) { + sauce::Entity newEntity("New Entity"); + newEntity.addComponent(); + scene.addEntity(std::move(newEntity)); + selection.select(static_cast(entities.size()) - 1); + app.setStatusMessage("Created new entity"); + } + ImGui::EndPopup(); } - // Wait for GPU to finish using entity's mesh buffers before destroying - app.getLogicalDevice()->waitIdle(); - entities.erase(entities.begin() + i); - app.setStatusMessage("Deleted: " + deletedName); - ImGui::EndPopup(); - break; - } - ImGui::Separator(); - if (ImGui::MenuItem("Toggle Active")) { - entity.setActive(!entity.getActive()); - } - ImGui::EndPopup(); - } - } - - // Drop target for GLTF files in empty area - if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_FILE")) { - std::string path(static_cast(payload->Data)); - std::string ext = std::filesystem::path(path).extension().string(); - if (ext == ".gltf" || ext == ".glb") { - app.importGLTFToScene(path); - } - } - ImGui::EndDragDropTarget(); - } - - // Right-click in empty space - if (ImGui::BeginPopupContextWindow(nullptr, ImGuiPopupFlags_NoOpenOverItems | ImGuiPopupFlags_MouseButtonRight)) { - if (ImGui::MenuItem("Add Empty Entity")) { - sauce::Entity newEntity("New Entity"); - newEntity.addComponent(); - scene.addEntity(std::move(newEntity)); - selection.select(static_cast(entities.size()) - 1); - app.setStatusMessage("Created new entity"); - } - ImGui::EndPopup(); - } - ImGui::End(); -} + ImGui::End(); + } } // namespace sauce::editor diff --git a/src/editor/panels/ViewportPanel.cpp b/src/editor/panels/ViewportPanel.cpp index 7be49236..060521bc 100644 --- a/src/editor/panels/ViewportPanel.cpp +++ b/src/editor/panels/ViewportPanel.cpp @@ -1,133 +1,130 @@ -#include #include #include +#include -#include -#include #include #include +#include +#include namespace sauce::editor { -ViewportPanel::ViewportPanel(EditorApp& app) - : EditorPanel("Viewport", app) {} - -void ViewportPanel::render() { - ImGuiWindowFlags flags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::Begin(title.c_str(), &isOpen, flags); - ImGui::PopStyleVar(); - - hovered = ImGui::IsWindowHovered(); - focused = ImGui::IsWindowFocused(); - - ImVec2 size = ImGui::GetContentRegionAvail(); - - // Detect viewport size changes (clamped to min 1x1) - size.x = std::max(size.x, 1.0f); - size.y = std::max(size.y, 1.0f); - - if (std::abs(size.x - viewportSize.x) > 0.5f || std::abs(size.y - viewportSize.y) > 0.5f) { - viewportSize = size; - sizeChanged = true; - } - - // Display the offscreen framebuffer texture using DrawList directly - // (bypasses ImGui::Image layout/clipping which can reject the draw after dock resize) - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - viewportScreenPos = cursorPos; - auto* offscreenFB = app.getOffscreenFramebuffer(); - if (offscreenFB) { - VkDescriptorSet ds = offscreenFB->getImGuiDescriptorSet(); - if (ds != VK_NULL_HANDLE) { - ImGui::GetWindowDrawList()->AddImage( - reinterpret_cast(ds), - cursorPos, - ImVec2(cursorPos.x + size.x, cursorPos.y + size.y) - ); - } - } - - ImVec2 windowPos = ImGui::GetWindowPos(); - ImVec2 contentMin = ImGui::GetWindowContentRegionMin(); - - // Viewport mode toggle button (top-right corner) - // Drawn before InvisibleButton so it gets click priority in its rect - { - ViewportMode currentMode = app.getViewportMode(); - const char* vpModeStr = (currentMode == ViewportMode::Lit) ? "Lit" : "Unlit"; - float btnWidth = 60.0f; - float btnHeight = 22.0f; - float btnX = windowPos.x + contentMin.x + size.x - btnWidth - 10; - float btnY = windowPos.y + contentMin.y + 5; - - ImGui::SetCursorScreenPos(ImVec2(btnX, btnY)); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.18f, 0.85f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.25f, 0.32f, 0.90f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.35f, 0.35f, 0.45f, 0.95f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); - if (ImGui::Button(vpModeStr, ImVec2(btnWidth, btnHeight))) { - if (currentMode == ViewportMode::Unlit) { - app.setViewportMode(ViewportMode::Lit); - } else { - app.setViewportMode(ViewportMode::Unlit); - } + ViewportPanel::ViewportPanel(EditorApp& app) : EditorPanel("Viewport", app) { } - ImGui::PopStyleVar(); - ImGui::PopStyleColor(3); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Toggle viewport shading mode (Unlit / Lit)"); - } - } - - // Make the viewport area a drop target for GLTF files - ImGui::SetCursorScreenPos(cursorPos); - ImGui::InvisibleButton("##ViewportDropTarget", size); - if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_FILE")) { - std::string path(static_cast(payload->Data)); - std::string ext = std::filesystem::path(path).extension().string(); - if (ext == ".gltf" || ext == ".glb") { - app.importGLTFToScene(path); - } + + void ViewportPanel::render() { + ImGuiWindowFlags flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::Begin(title.c_str(), &isOpen, flags); + ImGui::PopStyleVar(); + + hovered = ImGui::IsWindowHovered(); + focused = ImGui::IsWindowFocused(); + + ImVec2 size = ImGui::GetContentRegionAvail(); + + // Detect viewport size changes (clamped to min 1x1) + size.x = std::max(size.x, 1.0f); + size.y = std::max(size.y, 1.0f); + + if (std::abs(size.x - viewportSize.x) > 0.5f || std::abs(size.y - viewportSize.y) > 0.5f) { + viewportSize = size; + sizeChanged = true; + } + + // Display the offscreen framebuffer texture using DrawList directly + // (bypasses ImGui::Image layout/clipping which can reject the draw after dock resize) + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + viewportScreenPos = cursorPos; + auto* offscreenFB = app.getOffscreenFramebuffer(); + if (offscreenFB) { + VkDescriptorSet ds = offscreenFB->getImGuiDescriptorSet(); + if (ds != VK_NULL_HANDLE) { + ImGui::GetWindowDrawList()->AddImage( + reinterpret_cast(ds), cursorPos, + ImVec2(cursorPos.x + size.x, cursorPos.y + size.y)); + } + } + + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 contentMin = ImGui::GetWindowContentRegionMin(); + + // Viewport mode toggle button (top-right corner) + // Drawn before InvisibleButton so it gets click priority in its rect + { + ViewportMode currentMode = app.getViewportMode(); + const char* vpModeStr = (currentMode == ViewportMode::Lit) ? "Lit" : "Unlit"; + float btnWidth = 60.0f; + float btnHeight = 22.0f; + float btnX = windowPos.x + contentMin.x + size.x - btnWidth - 10; + float btnY = windowPos.y + contentMin.y + 5; + + ImGui::SetCursorScreenPos(ImVec2(btnX, btnY)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.18f, 0.85f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.25f, 0.32f, 0.90f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.35f, 0.35f, 0.45f, 0.95f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + if (ImGui::Button(vpModeStr, ImVec2(btnWidth, btnHeight))) { + if (currentMode == ViewportMode::Unlit) { + app.setViewportMode(ViewportMode::Lit); + } else { + app.setViewportMode(ViewportMode::Unlit); + } + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Toggle viewport shading mode (Unlit / Lit)"); + } + } + + // Make the viewport area a drop target for GLTF files + ImGui::SetCursorScreenPos(cursorPos); + ImGui::InvisibleButton("##ViewportDropTarget", size); + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET_FILE")) { + std::string path(static_cast(payload->Data)); + std::string ext = std::filesystem::path(path).extension().string(); + if (ext == ".gltf" || ext == ".glb") { + app.importGLTFToScene(path); + } + } + ImGui::EndDragDropTarget(); + } + + // Draw overlay info on top + ImVec2 overlayPos = ImVec2(windowPos.x + contentMin.x + 10, windowPos.y + contentMin.y + 5); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + auto& editorCamera = app.getEditorCamera(); + const char* modeStr = editorCamera.getMode() == EditorCamera::Mode::Orbit ? "Orbit" : "Fly"; + + // Semi-transparent background for readability + float lineHeight = ImGui::GetTextLineHeightWithSpacing(); + ImVec2 bgMin = ImVec2(overlayPos.x - 4, overlayPos.y - 2); + ImVec2 bgMax = ImVec2(overlayPos.x + 420, overlayPos.y + lineHeight * 3 + 4); + drawList->AddRectFilled(bgMin, bgMax, IM_COL32(0, 0, 0, 140), 4.0f); + + // Camera mode + char buf[128]; + snprintf(buf, sizeof(buf), "Camera: %s", modeStr); + drawList->AddText(overlayPos, IM_COL32(200, 200, 200, 255), buf); + + // FPS + float dt = app.getDeltaTime(); + float fps = dt > 0.0001f ? 1.0f / dt : 0.0f; + snprintf(buf, sizeof(buf), "FPS: %.0f (%.2f ms)", fps, dt * 1000.0f); + drawList->AddText(ImVec2(overlayPos.x, overlayPos.y + lineHeight), + IM_COL32(200, 200, 200, 255), buf); + + // Controls hint + drawList->AddText( + ImVec2(overlayPos.x, overlayPos.y + lineHeight * 2), IM_COL32(140, 140, 140, 255), + "LMB: Select | MMB: Pan | Scroll: Zoom | RMB: Fly | F: Focus | W/E/R: Gizmo"); + + ImGui::End(); } - ImGui::EndDragDropTarget(); - } - - // Draw overlay info on top - ImVec2 overlayPos = ImVec2(windowPos.x + contentMin.x + 10, - windowPos.y + contentMin.y + 5); - - ImDrawList* drawList = ImGui::GetWindowDrawList(); - - auto& editorCamera = app.getEditorCamera(); - const char* modeStr = editorCamera.getMode() == EditorCamera::Mode::Orbit ? "Orbit" : "Fly"; - - // Semi-transparent background for readability - float lineHeight = ImGui::GetTextLineHeightWithSpacing(); - ImVec2 bgMin = ImVec2(overlayPos.x - 4, overlayPos.y - 2); - ImVec2 bgMax = ImVec2(overlayPos.x + 420, overlayPos.y + lineHeight * 3 + 4); - drawList->AddRectFilled(bgMin, bgMax, IM_COL32(0, 0, 0, 140), 4.0f); - - // Camera mode - char buf[128]; - snprintf(buf, sizeof(buf), "Camera: %s", modeStr); - drawList->AddText(overlayPos, IM_COL32(200, 200, 200, 255), buf); - - // FPS - float dt = app.getDeltaTime(); - float fps = dt > 0.0001f ? 1.0f / dt : 0.0f; - snprintf(buf, sizeof(buf), "FPS: %.0f (%.2f ms)", fps, dt * 1000.0f); - drawList->AddText(ImVec2(overlayPos.x, overlayPos.y + lineHeight), IM_COL32(200, 200, 200, 255), buf); - - // Controls hint - drawList->AddText(ImVec2(overlayPos.x, overlayPos.y + lineHeight * 2), - IM_COL32(140, 140, 140, 255), - "LMB: Select | MMB: Pan | Scroll: Zoom | RMB: Fly | F: Focus | W/E/R: Gizmo"); - - ImGui::End(); -} } // namespace sauce::editor diff --git a/src/launcher/optionParser.cpp b/src/launcher/optionParser.cpp index eefe21f3..7d28a487 100644 --- a/src/launcher/optionParser.cpp +++ b/src/launcher/optionParser.cpp @@ -8,16 +8,19 @@ * -t --tickrate Tickrate * -f --input-file Scene file */ -AppOptions::AppOptions(int argc, char const **argv): desc("Allowed options") { +AppOptions::AppOptions(int argc, char const** argv) : desc("Allowed options") { namespace po = boost::program_options; - desc.add_options() - ("help", "produce help message") - ("skip-launcher", "start the engine immediately") - ("width,w", po::value(&(this->scr_width))->default_value(DEFAULT_SCR_WIDTH), "screen width") - ("height,h", po::value(&(this->scr_height))->default_value(DEFAULT_SCR_HEIGHT), "screen height") - ("tickrate,t", po::value(&(this->tickrate))->default_value(DEFAULT_TICKRATE), "animation tickrate") - ("input-file,f", po::value(&(this->scene_file))->default_value(""), "scene file to load"); + desc.add_options()("help", "produce help message")("skip-launcher", + "start the engine immediately")( + "width,w", po::value(&(this->scr_width))->default_value(DEFAULT_SCR_WIDTH), + "screen width")( + "height,h", po::value(&(this->scr_height))->default_value(DEFAULT_SCR_HEIGHT), + "screen height")("tickrate,t", + po::value(&(this->tickrate))->default_value(DEFAULT_TICKRATE), + "animation tickrate")( + "input-file,f", po::value(&(this->scene_file))->default_value(""), + "scene file to load"); po::positional_options_description p; p.add("input-file", 1); diff --git a/src/main.cpp b/src/main.cpp index ff4acf55..ef135c94 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,7 @@ -#include #include +#include #include -#include #include #include #include @@ -11,25 +10,26 @@ #include #include #include +#include -int main(int argc, const char *argv[]) { - const AppOptions ops(argc, argv); +int main(int argc, const char* argv[]) { + const AppOptions ops(argc, argv); - if (ops.help) { - std::cout << "Usage: " << argv[0] << " [scene_file]" << std::endl; - std::cout << ops.getHelpMessage() << std::endl; - return 1; - } + if (ops.help) { + std::cout << "Usage: " << argv[0] << " [scene_file]" << std::endl; + std::cout << ops.getHelpMessage() << std::endl; + return 1; + } - sauce::SauceEngineApp mainApp; - try { - if (!ops.scene_file.empty()) { - mainApp.setSceneFile(ops.scene_file); + sauce::SauceEngineApp mainApp; + try { + if (!ops.scene_file.empty()) { + mainApp.setSceneFile(ops.scene_file); + } + mainApp.run(); + } catch (std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; } - mainApp.run(); - } catch (std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - return EXIT_SUCCESS; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/modeltest.cpp b/src/modeltest.cpp index 945b7848..fb5290b8 100644 --- a/src/modeltest.cpp +++ b/src/modeltest.cpp @@ -1,33 +1,44 @@ -#include "app/modeling/GLTFLoader.hpp" #include "app/modeling/Model.hpp" -#include "app/modeling/ModelNode.hpp" -#include "app/modeling/Mesh.hpp" +#include "app/modeling/GLTFLoader.hpp" #include "app/modeling/Material.hpp" +#include "app/modeling/Mesh.hpp" +#include "app/modeling/ModelNode.hpp" #include "app/modeling/Texture.hpp" -#include #include +#include #include using namespace sauce::modeling; std::string textureTypeToString(TextureType type) { switch (type) { - case TextureType::BaseColor: return "BaseColor"; - case TextureType::Normal: return "Normal"; - case TextureType::MetallicRoughness: return "MetallicRoughness"; - case TextureType::Occlusion: return "Occlusion"; - case TextureType::Emissive: return "Emissive"; - case TextureType::Unknown: return "Unknown"; - default: return "Invalid"; + case TextureType::BaseColor: + return "BaseColor"; + case TextureType::Normal: + return "Normal"; + case TextureType::MetallicRoughness: + return "MetallicRoughness"; + case TextureType::Occlusion: + return "Occlusion"; + case TextureType::Emissive: + return "Emissive"; + case TextureType::Unknown: + return "Unknown"; + default: + return "Invalid"; } } std::string alphaModeToString(MaterialProperties::AlphaMode mode) { switch (mode) { - case MaterialProperties::AlphaMode::Opaque: return "Opaque"; - case MaterialProperties::AlphaMode::Mask: return "Mask"; - case MaterialProperties::AlphaMode::Blend: return "Blend"; - default: return "Unknown"; + case MaterialProperties::AlphaMode::Opaque: + return "Opaque"; + case MaterialProperties::AlphaMode::Mask: + return "Mask"; + case MaterialProperties::AlphaMode::Blend: + return "Blend"; + default: + return "Unknown"; } } @@ -41,10 +52,8 @@ void printMeshInfo(const std::shared_ptr& mesh, int index) { if (mesh->getVertexCount() > 0) { const auto& vertices = mesh->getVertices(); - std::cout << " First vertex position: (" - << vertices[0].position.x << ", " - << vertices[0].position.y << ", " - << vertices[0].position.z << ")\n"; + std::cout << " First vertex position: (" << vertices[0].position.x << ", " + << vertices[0].position.y << ", " << vertices[0].position.z << ")\n"; } } @@ -53,17 +62,12 @@ void printMaterialInfo(const std::shared_ptr& material, int index) { std::cout << " Name: " << material->getName() << "\n"; const auto& props = material->getProperties(); - std::cout << " Base Color: (" - << props.baseColorFactor.r << ", " - << props.baseColorFactor.g << ", " - << props.baseColorFactor.b << ", " - << props.baseColorFactor.a << ")\n"; + std::cout << " Base Color: (" << props.baseColorFactor.r << ", " << props.baseColorFactor.g + << ", " << props.baseColorFactor.b << ", " << props.baseColorFactor.a << ")\n"; std::cout << " Metallic: " << props.metallicFactor << "\n"; std::cout << " Roughness: " << props.roughnessFactor << "\n"; - std::cout << " Emissive: (" - << props.emissiveFactor.r << ", " - << props.emissiveFactor.g << ", " - << props.emissiveFactor.b << ")\n"; + std::cout << " Emissive: (" << props.emissiveFactor.r << ", " << props.emissiveFactor.g + << ", " << props.emissiveFactor.b << ")\n"; std::cout << " Alpha Mode: " << alphaModeToString(props.alphaMode) << "\n"; std::cout << " Double Sided: " << (props.doubleSided ? "Yes" : "No") << "\n"; @@ -90,14 +94,10 @@ void printNodeHierarchy(const std::shared_ptr& node, int depth = 0) { std::cout << indent << "Node: " << node->getName() << "\n"; const auto& transform = node->getTransform(); - std::cout << indent << " Translation: (" - << transform.getTranslation().x << ", " - << transform.getTranslation().y << ", " - << transform.getTranslation().z << ")\n"; - std::cout << indent << " Scale: (" - << transform.getScale().x << ", " - << transform.getScale().y << ", " - << transform.getScale().z << ")\n"; + std::cout << indent << " Translation: (" << transform.getTranslation().x << ", " + << transform.getTranslation().y << ", " << transform.getTranslation().z << ")\n"; + std::cout << indent << " Scale: (" << transform.getScale().x << ", " << transform.getScale().y + << ", " << transform.getScale().z << ")\n"; const auto& pairs = node->getMeshMaterialPairs(); if (!pairs.empty()) { diff --git a/src/modelviewer.cpp b/src/modelviewer.cpp index 96cc10e8..94820bcc 100644 --- a/src/modelviewer.cpp +++ b/src/modelviewer.cpp @@ -1,8 +1,8 @@ -#include #include +#include +#include #include #include -#include #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS #include @@ -14,14 +14,14 @@ #include #include +#include +#include #include -#include #include +#include +#include #include -#include #include -#include -#include // Disable validation layers (requires VK_LAYER_KHRONOS_validation to be installed) constexpr bool enableValidationLayers = false; @@ -30,177 +30,179 @@ constexpr uint32_t WIDTH = 1280; constexpr uint32_t HEIGHT = 720; class ModelViewerApp { -public: - ModelViewerApp(const std::string& modelPath) : modelPath(modelPath) {} - - void run() { - initWindow(); - initVulkan(); - loadModel(); - mainLoop(); - } - - ~ModelViewerApp() { - logicalDevice->waitIdle(); - glfwDestroyWindow(window); - glfwTerminate(); - } - -private: - GLFWwindow* window; - std::string modelPath; - - std::unique_ptr pInstance; - std::unique_ptr pRenderSurface; - - sauce::PhysicalDevice physicalDevice = nullptr; - sauce::LogicalDevice logicalDevice = nullptr; - - std::unique_ptr pCamera; - std::unique_ptr pRenderer; - std::unique_ptr pImGuiRenderer; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Model Viewer", nullptr, nullptr); - } - - void initVulkan() { - uint32_t glfwExtensionsCount = 0; - const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionsCount); - pInstance = std::make_unique(glfwExtensions, glfwExtensionsCount); - - pRenderSurface = std::make_unique(*pInstance, window); - - physicalDevice = { *pInstance }; - logicalDevice = { physicalDevice, *pRenderSurface }; - - // Create camera positioned to view the model - sauce::CameraCreateInfo cameraCreateInfo { - .scrWidth = static_cast(WIDTH), - .scrHeight = static_cast(HEIGHT), - .pos = { 0.0f, 2.0f, 5.0f }, - .fov = 60.0f, - }; - pCamera = std::make_unique(cameraCreateInfo); - - // Initialize camera to look at origin - pCamera->lookAt(glm::vec3(0.0f, 2.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); - - // Create renderer - sauce::RendererCreateInfo rendererCreateInfo { - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .renderSurface = *pRenderSurface, - .window = window, - }; - pRenderer = std::make_unique(rendererCreateInfo); - - // Initialize ImGui - sauce::ImGuiRendererCreateInfo imguiCreateInfo { - .instance = **pInstance, - .physicalDevice = physicalDevice, - .logicalDevice = logicalDevice, - .queueFamilyIndex = logicalDevice.getQueueIndex(), - .window = window, - .queue = pRenderer->getQueue(), - .commandPool = pRenderer->getCommandPool(), - .swapChain = pRenderer->getSwapChain(), - .imageCount = static_cast(pRenderer->getSwapChain().getImages().size()), - .swapChainFormat = pRenderer->getSwapChain().getSurfaceFormat().format, - .depthFormat = sauce::GraphicsPipeline::findDepthFormat(physicalDevice), - }; - pImGuiRenderer = std::make_unique(imguiCreateInfo); - } - - void loadModel() { - std::cout << "Loading model: " << modelPath << std::endl; - pRenderer->loadModel(modelPath, physicalDevice, logicalDevice); - } - - void updateCamera(float time) { - // Orbital camera: rotate around the origin - float orbitAngle = time * glm::radians(30.0f); // 30 degrees per second - float distance = 5.0f; - - float camX = distance * sin(orbitAngle); - float camZ = distance * cos(orbitAngle); - float camY = 2.0f; - - glm::vec3 cameraPos(camX, camY, camZ); - glm::vec3 target(0.0f, 0.0f, 0.0f); - glm::vec3 up(0.0f, 1.0f, 0.0f); - - pCamera->lookAt(cameraPos, target, up); - } - - void buildUI(float fps) { - ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(200, 80), ImGuiCond_FirstUseEver); - - ImGui::Begin("Model Viewer Stats"); - ImGui::Text("FPS: %.1f", fps); - ImGui::Text("Frame Time: %.2f ms", 1000.0f / fps); - ImGui::End(); - } - - void mainLoop() { - auto startTime = std::chrono::high_resolution_clock::now(); - auto lastFrameTime = startTime; - float fps = 0.0f; - - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - // Calculate FPS - float frameDelta = std::chrono::duration(currentTime - lastFrameTime).count(); - if (frameDelta > 0.0f) { - fps = 1.0f / frameDelta; - } - lastFrameTime = currentTime; - - // Update orbital camera - updateCamera(time); - - // Model rotation: 45 degrees per second - float modelRotation = time * glm::radians(45.0f); - - // Build UI - pImGuiRenderer->newFrame(); - buildUI(fps); - - // Render frame - pRenderer->drawFrame(logicalDevice, *pCamera, modelRotation, pImGuiRenderer.get()); + public: + ModelViewerApp(const std::string& modelPath) : modelPath(modelPath) { + } + + void run() { + initWindow(); + initVulkan(); + loadModel(); + mainLoop(); + } + + ~ModelViewerApp() { + logicalDevice->waitIdle(); + glfwDestroyWindow(window); + glfwTerminate(); + } + + private: + GLFWwindow* window; + std::string modelPath; + + std::unique_ptr pInstance; + std::unique_ptr pRenderSurface; + + sauce::PhysicalDevice physicalDevice = nullptr; + sauce::LogicalDevice logicalDevice = nullptr; + + std::unique_ptr pCamera; + std::unique_ptr pRenderer; + std::unique_ptr pImGuiRenderer; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Model Viewer", nullptr, nullptr); } - logicalDevice->waitIdle(); - } + void initVulkan() { + uint32_t glfwExtensionsCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionsCount); + pInstance = std::make_unique(glfwExtensions, glfwExtensionsCount); + + pRenderSurface = std::make_unique(*pInstance, window); + + physicalDevice = {*pInstance}; + logicalDevice = {physicalDevice, *pRenderSurface}; + + // Create camera positioned to view the model + sauce::CameraCreateInfo cameraCreateInfo{ + .scrWidth = static_cast(WIDTH), + .scrHeight = static_cast(HEIGHT), + .pos = {0.0f, 2.0f, 5.0f}, + .fov = 60.0f, + }; + pCamera = std::make_unique(cameraCreateInfo); + + // Initialize camera to look at origin + pCamera->lookAt(glm::vec3(0.0f, 2.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 1.0f, 0.0f)); + + // Create renderer + sauce::RendererCreateInfo rendererCreateInfo{ + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .renderSurface = *pRenderSurface, + .window = window, + }; + pRenderer = std::make_unique(rendererCreateInfo); + + // Initialize ImGui + sauce::ImGuiRendererCreateInfo imguiCreateInfo{ + .instance = **pInstance, + .physicalDevice = physicalDevice, + .logicalDevice = logicalDevice, + .queueFamilyIndex = logicalDevice.getQueueIndex(), + .window = window, + .queue = pRenderer->getQueue(), + .commandPool = pRenderer->getCommandPool(), + .swapChain = pRenderer->getSwapChain(), + .imageCount = static_cast(pRenderer->getSwapChain().getImages().size()), + .swapChainFormat = pRenderer->getSwapChain().getSurfaceFormat().format, + .depthFormat = sauce::GraphicsPipeline::findDepthFormat(physicalDevice), + }; + pImGuiRenderer = std::make_unique(imguiCreateInfo); + } + + void loadModel() { + std::cout << "Loading model: " << modelPath << std::endl; + pRenderer->loadModel(modelPath, physicalDevice, logicalDevice); + } + + void updateCamera(float time) { + // Orbital camera: rotate around the origin + float orbitAngle = time * glm::radians(30.0f); // 30 degrees per second + float distance = 5.0f; + + float camX = distance * sin(orbitAngle); + float camZ = distance * cos(orbitAngle); + float camY = 2.0f; + + glm::vec3 cameraPos(camX, camY, camZ); + glm::vec3 target(0.0f, 0.0f, 0.0f); + glm::vec3 up(0.0f, 1.0f, 0.0f); + + pCamera->lookAt(cameraPos, target, up); + } + + void buildUI(float fps) { + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(200, 80), ImGuiCond_FirstUseEver); + + ImGui::Begin("Model Viewer Stats"); + ImGui::Text("FPS: %.1f", fps); + ImGui::Text("Frame Time: %.2f ms", 1000.0f / fps); + ImGui::End(); + } + + void mainLoop() { + auto startTime = std::chrono::high_resolution_clock::now(); + auto lastFrameTime = startTime; + float fps = 0.0f; + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + // Calculate FPS + float frameDelta = std::chrono::duration(currentTime - lastFrameTime).count(); + if (frameDelta > 0.0f) { + fps = 1.0f / frameDelta; + } + lastFrameTime = currentTime; + + // Update orbital camera + updateCamera(time); + + // Model rotation: 45 degrees per second + float modelRotation = time * glm::radians(45.0f); + + // Build UI + pImGuiRenderer->newFrame(); + buildUI(fps); + + // Render frame + pRenderer->drawFrame(logicalDevice, *pCamera, modelRotation, pImGuiRenderer.get()); + } + + logicalDevice->waitIdle(); + } }; int main(int argc, char* argv[]) { - std::string modelPath = "assets/models/monkey.gltf"; + std::string modelPath = "assets/models/monkey.gltf"; - if (argc > 1) { - modelPath = argv[1]; - } + if (argc > 1) { + modelPath = argv[1]; + } - std::cout << "Model Viewer" << std::endl; - std::cout << "Loading: " << modelPath << std::endl; + std::cout << "Model Viewer" << std::endl; + std::cout << "Loading: " << modelPath << std::endl; - ModelViewerApp app(modelPath); - try { - app.run(); - } catch (std::exception& e) { - std::cerr << "Error: " << e.what() << std::endl; - return EXIT_FAILURE; - } + ModelViewerApp app(modelPath); + try { + app.run(); + } catch (std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } - return EXIT_SUCCESS; + return EXIT_SUCCESS; } diff --git a/src/physics/SphereBVH.cpp b/src/physics/SphereBVH.cpp index d9e26bc2..423f0757 100644 --- a/src/physics/SphereBVH.cpp +++ b/src/physics/SphereBVH.cpp @@ -6,353 +6,355 @@ namespace physics { -// helper functions -namespace { - struct TriangleInfo { - uint32_t idx; - glm::vec3 v0, v1, v2; - glm::vec3 centroid; - }; - std::unique_ptr buildNode(std::vector &triangles, size_t start, size_t end) { - auto node = std::make_unique(); - size_t count = end - start; - glm::vec3 minExt(std::numeric_limits::max()); - glm::vec3 maxExt(std::numeric_limits::lowest()); - - for (size_t i = start; i < end; ++i) { - const auto &t = triangles[i]; - minExt = glm::min(minExt, glm::min(t.v0, glm::min(t.v1, t.v2))); - maxExt = glm::max(maxExt, glm::max(t.v0, glm::max(t.v1, t.v2))); - } + // helper functions + namespace { + struct TriangleInfo { + uint32_t idx; + glm::vec3 v0, v1, v2; + glm::vec3 centroid; + }; + std::unique_ptr buildNode(std::vector& triangles, size_t start, + size_t end) { + auto node = std::make_unique(); + size_t count = end - start; + glm::vec3 minExt(std::numeric_limits::max()); + glm::vec3 maxExt(std::numeric_limits::lowest()); - node->sphere.center = (minExt + maxExt) / 2.0f; - float maxRadiusSq = 0.0f; - for (size_t i = start; i < end; ++i) { - maxRadiusSq = std::max(maxRadiusSq, glm::length2(node->sphere.center - triangles[i].v0)); - maxRadiusSq = std::max(maxRadiusSq, glm::length2(node->sphere.center - triangles[i].v1)); - maxRadiusSq = std::max(maxRadiusSq, glm::length2(node->sphere.center - triangles[i].v2)); - } - node->sphere.radius = std::sqrt(maxRadiusSq); + for (size_t i = start; i < end; ++i) { + const auto& t = triangles[i]; + minExt = glm::min(minExt, glm::min(t.v0, glm::min(t.v1, t.v2))); + maxExt = glm::max(maxExt, glm::max(t.v0, glm::max(t.v1, t.v2))); + } + + node->sphere.center = (minExt + maxExt) / 2.0f; + float maxRadiusSq = 0.0f; + for (size_t i = start; i < end; ++i) { + maxRadiusSq = + std::max(maxRadiusSq, glm::length2(node->sphere.center - triangles[i].v0)); + maxRadiusSq = + std::max(maxRadiusSq, glm::length2(node->sphere.center - triangles[i].v1)); + maxRadiusSq = + std::max(maxRadiusSq, glm::length2(node->sphere.center - triangles[i].v2)); + } + node->sphere.radius = std::sqrt(maxRadiusSq); + const size_t MAX_TRIANGLES_PER_LEAF = 4; + if (count <= MAX_TRIANGLES_PER_LEAF) { + for (size_t i = start; i < end; ++i) { + node->triangleIndices.push_back(triangles[i].idx); + } + return node; + } - const size_t MAX_TRIANGLES_PER_LEAF = 4; - if (count <= MAX_TRIANGLES_PER_LEAF) { + glm::vec3 centroidMin(std::numeric_limits::max()); + glm::vec3 centroidMax(std::numeric_limits::lowest()); for (size_t i = start; i < end; ++i) { - node->triangleIndices.push_back(triangles[i].idx); + centroidMin = glm::min(centroidMin, triangles[i].centroid); + centroidMax = glm::max(centroidMax, triangles[i].centroid); } - return node; - } + glm::vec3 extent = centroidMax - centroidMin; + int axis = 0; + if (extent.y > extent.x) + axis = 1; + if (extent.z > extent[axis]) + axis = 2; - glm::vec3 centroidMin(std::numeric_limits::max()); - glm::vec3 centroidMax(std::numeric_limits::lowest()); - for (size_t i = start; i < end; ++i) { - centroidMin = glm::min(centroidMin, triangles[i].centroid); - centroidMax = glm::max(centroidMax, triangles[i].centroid); - } + size_t mid = start + count / 2; + std::nth_element(triangles.begin() + start, triangles.begin() + mid, + triangles.begin() + end, + [axis](const TriangleInfo& a, const TriangleInfo& b) { + return a.centroid[axis] < b.centroid[axis]; + }); - glm::vec3 extent = centroidMax - centroidMin; - int axis = 0; - if (extent.y > extent.x) axis = 1; - if (extent.z > extent[axis]) axis = 2; + node->left = buildNode(triangles, start, mid); + node->right = buildNode(triangles, mid, end); - size_t mid = start + count / 2; - std::nth_element(triangles.begin() + start, - triangles.begin() + mid, - triangles.begin() + end, - [axis](const TriangleInfo &a, const TriangleInfo &b) { - return a.centroid[axis] < b.centroid[axis]; - }); + return node; + } - node->left = buildNode(triangles, start, mid); - node->right = buildNode(triangles, mid, end); + SphereCollider mergeSpheres(const SphereCollider& s1, const SphereCollider& s2) { + float dist = glm::distance(s1.center, s2.center); - return node; - } + if (dist + s1.radius <= s2.radius) { + return s2; + } + if (dist + s2.radius <= s1.radius) { + return s1; + } - SphereCollider mergeSpheres(const SphereCollider& s1, const SphereCollider& s2) { - float dist = glm::distance(s1.center, s2.center); + float newRadius = (dist + s1.radius + s2.radius) / 2.0; - if (dist + s1.radius <= s2.radius) { - return s2; - } - if (dist + s2.radius <= s1.radius) { - return s1; + SphereCollider res; + res.center = glm::mix(s1.center, s2.center, (newRadius - s1.radius) / dist); + res.radius = newRadius; + return res; } - float newRadius = (dist + s1.radius + s2.radius) / 2.0; - - SphereCollider res; - res.center = glm::mix(s1.center, s2.center, (newRadius - s1.radius) / dist); - res.radius = newRadius; - return res; - } + std::unique_ptr buildBVH( + std::vector>& spheres, int start, int end) { + int count = end - start; + if (count <= 0) { + return nullptr; + } - std::unique_ptr buildBVH(std::vector>& spheres, int start, int end) { - int count = end - start; - if (count <= 0) { - return nullptr; - } + if (count == 1) { + return std::move(spheres[start]); + } - if (count == 1) { - return std::move(spheres[start]); - } - - glm::vec3 minCentroid(std::numeric_limits::max()); - glm::vec3 maxCentroid(std::numeric_limits::lowest()); - - for (int i = start; i < end; ++i) { - glm::vec3 c = spheres[i]->sphere.center; - minCentroid = glm::min(minCentroid, c); - maxCentroid = glm::max(maxCentroid, c); - } + glm::vec3 minCentroid(std::numeric_limits::max()); + glm::vec3 maxCentroid(std::numeric_limits::lowest()); - glm::vec3 extent = maxCentroid - minCentroid; - int axis = 0; - if (extent.y > extent.x) { - axis = 1; - } else if (extent.z > extent[axis]) { - axis = 2; - } + for (int i = start; i < end; ++i) { + glm::vec3 c = spheres[i]->sphere.center; + minCentroid = glm::min(minCentroid, c); + maxCentroid = glm::max(maxCentroid, c); + } - int mid = start + count / 2; - std::nth_element( - spheres.begin() + start, - spheres.begin() + mid, - spheres.begin() + end, - [axis](const std::unique_ptr& a, const std::unique_ptr& b) { - return a->sphere.center[axis] < b->sphere.center[axis]; + glm::vec3 extent = maxCentroid - minCentroid; + int axis = 0; + if (extent.y > extent.x) { + axis = 1; + } else if (extent.z > extent[axis]) { + axis = 2; } - ); - auto node = std::make_unique(); - node->left = buildBVH(spheres, start, mid); - node->right = buildBVH(spheres, mid, end); - node->sphere = mergeSpheres(node->left->sphere, node->right->sphere); + int mid = start + count / 2; + std::nth_element(spheres.begin() + start, spheres.begin() + mid, spheres.begin() + end, + [axis](const std::unique_ptr& a, + const std::unique_ptr& b) { + return a->sphere.center[axis] < b->sphere.center[axis]; + }); - return node; - } -} + auto node = std::make_unique(); + node->left = buildBVH(spheres, start, mid); + node->right = buildBVH(spheres, mid, end); + node->sphere = mergeSpheres(node->left->sphere, node->right->sphere); -// SphereBVHNode -bool SphereBVHNode::checkCollision(const Collider& collider, std::vector& info) const { - if (!this->sphere.checkCollision(collider, info)) { - return false; + return node; + } } - return this->left->sphere.checkCollision(collider, info) - || this->right->sphere.checkCollision(collider, info); -} + // SphereBVHNode + bool SphereBVHNode::checkCollision(const Collider& collider, + std::vector& info) const { + if (!this->sphere.checkCollision(collider, info)) { + return false; + } -static constexpr size_t MAX_MANIFOLD_CONTACTS = 4; + return this->left->sphere.checkCollision(collider, info) || + this->right->sphere.checkCollision(collider, info); + } -// ── helpers ────────────────────────────────────────────────────────── + static constexpr size_t MAX_MANIFOLD_CONTACTS = 4; -static bool spheresOverlap(const SphereCollider& a, const SphereCollider& b) { - float radiusSum = a.radius + b.radius; - return glm::length2(b.center - a.center) < radiusSum * radiusSum; -} + // ── helpers ────────────────────────────────────────────────────────── -// Reduces a set of contacts down to at most MAX_MANIFOLD_CONTACTS that -// best represent the contact surface. Strategy: -// 1. Keep the deepest penetration (most important for stability) -// 2. Keep the point farthest from #1 (maximise spread) -// 3. Keep the point that maximises triangle area with #1 and #2 -// 4. Keep the point that maximises quadrilateral area with #1, #2, #3 -static void reduceManifold(std::vector& contacts) { - if (contacts.size() <= MAX_MANIFOLD_CONTACTS) return; - - std::vector reduced; - reduced.reserve(MAX_MANIFOLD_CONTACTS); - - // 1. Deepest penetration - size_t deepestIdx = 0; - for (size_t i = 1; i < contacts.size(); ++i) { - if (contacts[i].depth > contacts[deepestIdx].depth) { - deepestIdx = i; - } - } - reduced.push_back(contacts[deepestIdx]); - - // 2. Farthest from the deepest - size_t farthestIdx = 0; - float maxDistSq = -1.0f; - for (size_t i = 0; i < contacts.size(); ++i) { - float dSq = glm::length2(contacts[i].contactPoint - reduced[0].contactPoint); - if (dSq > maxDistSq) { - maxDistSq = dSq; - farthestIdx = i; - } + static bool spheresOverlap(const SphereCollider& a, const SphereCollider& b) { + float radiusSum = a.radius + b.radius; + return glm::length2(b.center - a.center) < radiusSum * radiusSum; } - reduced.push_back(contacts[farthestIdx]); - - // 3. Maximise triangle area with the first two - size_t thirdIdx = 0; - float maxArea = -1.0f; - glm::vec3 edge = reduced[1].contactPoint - reduced[0].contactPoint; - for (size_t i = 0; i < contacts.size(); ++i) { - glm::vec3 cross = glm::cross(edge, contacts[i].contactPoint - reduced[0].contactPoint); - float area = glm::length2(cross); - if (area > maxArea) { - maxArea = area; - thirdIdx = i; + + // Reduces a set of contacts down to at most MAX_MANIFOLD_CONTACTS that + // best represent the contact surface. Strategy: + // 1. Keep the deepest penetration (most important for stability) + // 2. Keep the point farthest from #1 (maximise spread) + // 3. Keep the point that maximises triangle area with #1 and #2 + // 4. Keep the point that maximises quadrilateral area with #1, #2, #3 + static void reduceManifold(std::vector& contacts) { + if (contacts.size() <= MAX_MANIFOLD_CONTACTS) + return; + + std::vector reduced; + reduced.reserve(MAX_MANIFOLD_CONTACTS); + + // 1. Deepest penetration + size_t deepestIdx = 0; + for (size_t i = 1; i < contacts.size(); ++i) { + if (contacts[i].depth > contacts[deepestIdx].depth) { + deepestIdx = i; + } } - } - reduced.push_back(contacts[thirdIdx]); - - // 4. Maximise quadrilateral area — pick the point farthest from the - // plane formed by the first three - if (MAX_MANIFOLD_CONTACTS >= 4) { - glm::vec3 triNormal = glm::cross( - reduced[1].contactPoint - reduced[0].contactPoint, - reduced[2].contactPoint - reduced[0].contactPoint - ); - float triNormalLen = glm::length(triNormal); - if (triNormalLen > 1e-8f) { - triNormal /= triNormalLen; + reduced.push_back(contacts[deepestIdx]); + + // 2. Farthest from the deepest + size_t farthestIdx = 0; + float maxDistSq = -1.0f; + for (size_t i = 0; i < contacts.size(); ++i) { + float dSq = glm::length2(contacts[i].contactPoint - reduced[0].contactPoint); + if (dSq > maxDistSq) { + maxDistSq = dSq; + farthestIdx = i; + } } + reduced.push_back(contacts[farthestIdx]); - size_t fourthIdx = 0; - float maxDist = -1.0f; + // 3. Maximise triangle area with the first two + size_t thirdIdx = 0; + float maxArea = -1.0f; + glm::vec3 edge = reduced[1].contactPoint - reduced[0].contactPoint; for (size_t i = 0; i < contacts.size(); ++i) { - float d = std::abs(glm::dot(contacts[i].contactPoint - reduced[0].contactPoint, triNormal)); - if (d > maxDist) { - maxDist = d; - fourthIdx = i; + glm::vec3 cross = glm::cross(edge, contacts[i].contactPoint - reduced[0].contactPoint); + float area = glm::length2(cross); + if (area > maxArea) { + maxArea = area; + thirdIdx = i; } } + reduced.push_back(contacts[thirdIdx]); + + // 4. Maximise quadrilateral area — pick the point farthest from the + // plane formed by the first three + if (MAX_MANIFOLD_CONTACTS >= 4) { + glm::vec3 triNormal = glm::cross(reduced[1].contactPoint - reduced[0].contactPoint, + reduced[2].contactPoint - reduced[0].contactPoint); + float triNormalLen = glm::length(triNormal); + if (triNormalLen > 1e-8f) { + triNormal /= triNormalLen; + } - // Only add if it's meaningfully off-plane; otherwise pick farthest - // from centroid of the existing three - if (maxDist < 1e-6f) { - glm::vec3 centroid = (reduced[0].contactPoint + reduced[1].contactPoint + reduced[2].contactPoint) / 3.0f; - float maxCentroidDistSq = -1.0f; + size_t fourthIdx = 0; + float maxDist = -1.0f; for (size_t i = 0; i < contacts.size(); ++i) { - float dSq = glm::length2(contacts[i].contactPoint - centroid); - if (dSq > maxCentroidDistSq) { - maxCentroidDistSq = dSq; + float d = std::abs( + glm::dot(contacts[i].contactPoint - reduced[0].contactPoint, triNormal)); + if (d > maxDist) { + maxDist = d; fourthIdx = i; } } + + // Only add if it's meaningfully off-plane; otherwise pick farthest + // from centroid of the existing three + if (maxDist < 1e-6f) { + glm::vec3 centroid = + (reduced[0].contactPoint + reduced[1].contactPoint + reduced[2].contactPoint) / + 3.0f; + float maxCentroidDistSq = -1.0f; + for (size_t i = 0; i < contacts.size(); ++i) { + float dSq = glm::length2(contacts[i].contactPoint - centroid); + if (dSq > maxCentroidDistSq) { + maxCentroidDistSq = dSq; + fourthIdx = i; + } + } + } + reduced.push_back(contacts[fourthIdx]); } - reduced.push_back(contacts[fourthIdx]); + + contacts = std::move(reduced); } - contacts = std::move(reduced); -} + // ── SphereBVHNode ──────────────────────────────────────────────────── -// ── SphereBVHNode ──────────────────────────────────────────────────── + bool SphereBVHNode::isLeaf() const { + return left == nullptr && right == nullptr; + } -bool SphereBVHNode::isLeaf() const { - return left == nullptr && right == nullptr; -} + bool SphereBVHNode::checkCollision(const Collider& collider, std::vector& info) { + const auto* otherSphere = dynamic_cast(&collider); + if (!otherSphere) + return false; -bool SphereBVHNode::checkCollision(const Collider& collider, std::vector& info) { - const auto* otherSphere = dynamic_cast(&collider); - if (!otherSphere) return false; + if (!spheresOverlap(sphere, *otherSphere)) { + return false; + } - if (!spheresOverlap(sphere, *otherSphere)) { - return false; - } + if (isLeaf()) { + return sphere.checkCollision(*otherSphere, info); + } - if (isLeaf()) { - return sphere.checkCollision(*otherSphere, info); + bool hitLeft = left ? left->checkCollision(collider, info) : false; + bool hitRight = right ? right->checkCollision(collider, info) : false; + return hitLeft || hitRight; } - bool hitLeft = left ? left->checkCollision(collider, info) : false; - bool hitRight = right ? right->checkCollision(collider, info) : false; - return hitLeft || hitRight; -} - -std::unique_ptr SphereBVHNode::fromMesh(sauce::modeling::Mesh& mesh) { - const auto &vertices = mesh.getVertices(); - const auto &indices = mesh.getIndices(); + std::unique_ptr SphereBVHNode::fromMesh(sauce::modeling::Mesh& mesh) { + const auto& vertices = mesh.getVertices(); + const auto& indices = mesh.getIndices(); - if (indices.empty() || vertices.empty()) { - return; - } + if (indices.empty() || vertices.empty()) { + return; + } - std::vector triangles; - size_t numTriangles = indices.size() / 3; - triangles.reserve(numTriangles); - - for (size_t i = 0; i < numTriangles; ++i) { - glm::vec3 p0 = vertices[indices[i * 3 + 0]].position; - glm::vec3 p1 = vertices[indices[i * 3 + 1]].position; - glm::vec3 p2 = vertices[indices[i * 3 + 2]].position; - - triangles.push_back({ - .idx = static_cast(i), - .v0 = p0, - .v1 = p1, - .v2 = p2, - .centroid = (p0 + p1 + p2) / 3.0f - }); - } + std::vector triangles; + size_t numTriangles = indices.size() / 3; + triangles.reserve(numTriangles); - return buildNode(triangles, 0, triangles.size()); -} + for (size_t i = 0; i < numTriangles; ++i) { + glm::vec3 p0 = vertices[indices[i * 3 + 0]].position; + glm::vec3 p1 = vertices[indices[i * 3 + 1]].position; + glm::vec3 p2 = vertices[indices[i * 3 + 2]].position; -bool SphereBVHNode::isLeaf() const { - return left == nullptr && right == nullptr; -} + triangles.push_back({.idx = static_cast(i), + .v0 = p0, + .v1 = p1, + .v2 = p2, + .centroid = (p0 + p1 + p2) / 3.0f}); + } + return buildNode(triangles, 0, triangles.size()); + } + bool SphereBVHNode::isLeaf() const { + return left == nullptr && right == nullptr; + } -// SphereBVH -SphereBVH SphereBVH::fromScene(const sauce::Scene& scene) { - - // make BVH for every mesh - std::vector> spheres; - auto &entities = scene.getEntities(); - for (auto entity: entities) { - auto mesh_renderer = entity.getComponent(); - if (mesh_renderer != nullptr) { - spheres.push_back(SphereBVHNode::fromMesh(*mesh_renderer->getMesh())); + // SphereBVH + SphereBVH SphereBVH::fromScene(const sauce::Scene& scene) { + // make BVH for every mesh + std::vector> spheres; + auto& entities = scene.getEntities(); + for (auto entity : entities) { + auto mesh_renderer = entity.getComponent(); + if (mesh_renderer != nullptr) { + spheres.push_back(SphereBVHNode::fromMesh(*mesh_renderer->getMesh())); + } } + + // Combine every BVH into a large one or the scene + return SphereBVH(buildBVH(spheres, 0, entities.size())); } - - // Combine every BVH into a large one or the scene - return SphereBVH(buildBVH(spheres, 0, entities.size())); -} -bool SphereBVH::checkCollision(const Collider& collider, std::vector& info) const { - if (!root) return false; + bool SphereBVH::checkCollision(const Collider& collider, std::vector& info) const { + if (!root) + return false; - // Collect all raw contacts from the tree - std::vector rawContacts; - bool hit = root->checkCollision(collider, rawContacts); + // Collect all raw contacts from the tree + std::vector rawContacts; + bool hit = root->checkCollision(collider, rawContacts); - if (hit && !rawContacts.empty()) { - reduceManifold(rawContacts); - info.insert(info.end(), rawContacts.begin(), rawContacts.end()); - } + if (hit && !rawContacts.empty()) { + reduceManifold(rawContacts); + info.insert(info.end(), rawContacts.begin(), rawContacts.end()); + } - return hit; -} + return hit; + } -const SphereBVHNode *SphereBVH::getRoot() const { - return this->root.get(); -} + const SphereBVHNode* SphereBVH::getRoot() const { + return this->root.get(); + } }; - glm::vec3 extent = centroidMax - centroidMin; - int axis = 0; - if (extent.y > extent.x) axis = 1; - if (extent.z > extent[axis]) axis = 2; +glm::vec3 extent = centroidMax - centroidMin; +int axis = 0; +if (extent.y > extent.x) + axis = 1; +if (extent.z > extent[axis]) + axis = 2; - size_t mid = start + count / 2; - std::nth_element(triangles.begin() + start, - triangles.begin() + mid, - triangles.begin() + end, - [axis](const TriangleInfo &a, const TriangleInfo &b) { - return a.centroid[axis] < b.centroid[axis]; - }); +size_t mid = start + count / 2; +std::nth_element(triangles.begin() + start, triangles.begin() + mid, triangles.begin() + end, + [axis](const TriangleInfo& a, const TriangleInfo& b) { + return a.centroid[axis] < b.centroid[axis]; + }); - node->left = buildRecursive(triangles, start, mid); - node->right = buildRecursive(triangles, mid, end); +node->left = buildRecursive(triangles, start, mid); +node->right = buildRecursive(triangles, mid, end); - return node; +return node; } -}; +} +; diff --git a/src/physics/SphereCollider.cpp b/src/physics/SphereCollider.cpp index b0d5e1ad..ceebd7a9 100644 --- a/src/physics/SphereCollider.cpp +++ b/src/physics/SphereCollider.cpp @@ -4,40 +4,41 @@ namespace physics { -const float eps = 1e-8f; - -bool SphereCollider::checkCollision(const Collider& collider, std::vector& info) const { - const auto* other = dynamic_cast(&collider); - if (!other) { - // Delegate to the other collider's checkCollision (double dispatch) - return collider.checkCollision(*this, info); - } - - glm::vec3 delta = other->center - center; - float distSq = glm::dot(delta, delta); - float radiusSum = radius + other->radius; - - if (distSq > (radiusSum * radiusSum) + eps) { - return false; - } - - float dist = glm::sqrt(distSq); - - glm::vec3 normal; - glm::vec3 contactPoint; - float penetrationDepth = radiusSum - dist; - - if (dist < eps) { - // Spheres are nearly coincident — pick an arbitrary normal - normal = glm::vec3(0.0f, 1.0f, 0.0f); - contactPoint = center; - } else { - normal = delta / dist; // Points from this toward other - contactPoint = center + normal * (radius - penetrationDepth * 0.5f); - } - - info.emplace_back(contactPoint, normal, this, other, penetrationDepth); - return true; -} + const float eps = 1e-8f; + + bool SphereCollider::checkCollision(const Collider& collider, + std::vector& info) const { + const auto* other = dynamic_cast(&collider); + if (!other) { + // Delegate to the other collider's checkCollision (double dispatch) + return collider.checkCollision(*this, info); + } + + glm::vec3 delta = other->center - center; + float distSq = glm::dot(delta, delta); + float radiusSum = radius + other->radius; + + if (distSq > (radiusSum * radiusSum) + eps) { + return false; + } + + float dist = glm::sqrt(distSq); + + glm::vec3 normal; + glm::vec3 contactPoint; + float penetrationDepth = radiusSum - dist; + + if (dist < eps) { + // Spheres are nearly coincident — pick an arbitrary normal + normal = glm::vec3(0.0f, 1.0f, 0.0f); + contactPoint = center; + } else { + normal = delta / dist; // Points from this toward other + contactPoint = center + normal * (radius - penetrationDepth * 0.5f); + } + + info.emplace_back(contactPoint, normal, this, other, penetrationDepth); + return true; + } } // namespace physics \ No newline at end of file