diff options
Diffstat (limited to 'src/blucat')
81 files changed, 9037 insertions, 0 deletions
diff --git a/src/blucat/animation.cpp b/src/blucat/animation.cpp new file mode 100644 index 0000000..b4daeef --- /dev/null +++ b/src/blucat/animation.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "animation.hpp" + +namespace BluCat +{ + +Bone::Bone(glm::mat4 offset_matrix): + offset_matrix{offset_matrix} +{ +} + +} diff --git a/src/blucat/animation.hpp b/src/blucat/animation.hpp new file mode 100644 index 0000000..c789c5e --- /dev/null +++ b/src/blucat/animation.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_ANIMATION_H +#define CANDY_GEAR_BLUCAT_ANIMATION_H 1 + +#include <vector> + +#include "core.hpp" +#include "animation/frame.hpp" + +namespace BluCat +{ + +struct Bone +{ + glm::mat4x4 offset_matrix; + + Bone(glm::mat4 offset_matrix); +}; + +struct BoneTransform +{ + uint32_t bone_id; + Channel<glm::vec3> positions; + Channel<glm::quat> rotations; + Channel<glm::vec3> scales; +}; + +struct Animation +{ + std::vector<BoneTransform> bone_transforms; + float final_time; +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_ANIMATION_H */ diff --git a/src/blucat/animation/frame.hpp b/src/blucat/animation/frame.hpp new file mode 100644 index 0000000..a1d5f39 --- /dev/null +++ b/src/blucat/animation/frame.hpp @@ -0,0 +1,85 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_FRAME_H +#define CANDY_GEAR_BLUCAT_FRAME_H 1 + +#include <vector> + +#include "../core.hpp" + +namespace BluCat +{ + +template<typename T> +struct Frame +{ + const T value; + const float timestamp; + + Frame(T value, float timestamp): + value{value}, + timestamp{timestamp} + { + } + +}; + +template<typename T> +struct Channel +{ + int current_index{0}; + std::vector<Frame<T>> key_frames; + + inline glm::mat4 + interpolate( + float animation_time, + glm::mat4 (*single_frame)(T frame), + glm::mat4 (*multiple_frames)(T current_frame, T next_frame, float scale)) + { + if(this->key_frames.size() == 1) + return single_frame(this->key_frames[0].value); + else + { + while(animation_time > this->key_frames[current_index].timestamp) + this->current_index++; + + float scale_factor; + Frame<T> *previous_frame; + Frame<T> *next_frame{&(this->key_frames[this->current_index])}; + if(this->current_index == 0) + { + previous_frame = &(this->key_frames[this->key_frames.size() - 1]); + float midway_length{animation_time - 0}; + float frames_diff{next_frame->timestamp - 0}; + scale_factor = midway_length / frames_diff; + } + else + { + previous_frame = &(this->key_frames[this->current_index - 1]); + float midway_length{animation_time - previous_frame->timestamp}; + float frames_diff{next_frame->timestamp - previous_frame->timestamp}; + scale_factor = midway_length / frames_diff; + } + + return multiple_frames( + previous_frame->value, next_frame->value, scale_factor); + } + }; +}; + +} +#endif /* CANDY_GEAR_BLUCAT_FRAME_H */ diff --git a/src/blucat/base_buffer.cpp b/src/blucat/base_buffer.cpp new file mode 100644 index 0000000..762c89a --- /dev/null +++ b/src/blucat/base_buffer.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base_buffer.hpp" + +namespace BluCat +{ + +const CommandChain BaseBuffer::loader{ + {&BaseBuffer::load_buffer, &BaseBuffer::unload_buffer}, + {&BaseBuffer::load_memory, &BaseBuffer::unload_memory} +}; + +void +BaseBuffer::load_buffer(void *obj) +{ + auto self = static_cast<BaseBuffer*>(obj); + + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.pNext = nullptr; + buffer_info.flags = 0; + buffer_info.size = self->device_size; + buffer_info.usage = self->buffer_usage; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + buffer_info.queueFamilyIndexCount = 0; + buffer_info.pQueueFamilyIndices = nullptr; + + if(vkCreateBuffer( + self->device->device, &buffer_info, nullptr, &self->buffer) + != VK_SUCCESS) + throw CommandError{"Failed to create vertex buffer."}; +} + +void +BaseBuffer::unload_buffer(void *obj) +{ + auto self = static_cast<BaseBuffer*>(obj); + + if(self->buffer != VK_NULL_HANDLE) + vkDestroyBuffer(self->device->device, self->buffer, nullptr); +} + +void +BaseBuffer::load_memory(void *obj) +{ + auto self = static_cast<BaseBuffer*>(obj); + + VkMemoryRequirements memory_requirements; + vkGetBufferMemoryRequirements( + self->device->device, self->buffer, &memory_requirements); + + VkPhysicalDeviceMemoryProperties memory_properties; + vkGetPhysicalDeviceMemoryProperties( + self->device->physical_device, &memory_properties); + + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.allocationSize = memory_requirements.size; + if(!self->device->select_memory_type( + &alloc_info.memoryTypeIndex, &memory_requirements, + self->memory_properties)) + throw CommandError{"Could not allocate memory for Vulkan vertex buffer."}; + + if(vkAllocateMemory(self->device->device, &alloc_info, nullptr, + &self->device_memory) != VK_SUCCESS) + throw CommandError{"Could not allocate memory for Vulkan vertex buffer."}; + + vkBindBufferMemory( + self->device->device, self->buffer, self->device_memory, 0); +} + +void +BaseBuffer::unload_memory(void *obj) +{ + auto self = static_cast<BaseBuffer*>(obj); + + if(self->device_memory != VK_NULL_HANDLE) + vkFreeMemory(self->device->device, self->device_memory, nullptr); +} + +} diff --git a/src/blucat/base_buffer.hpp b/src/blucat/base_buffer.hpp new file mode 100644 index 0000000..03b838c --- /dev/null +++ b/src/blucat/base_buffer.hpp @@ -0,0 +1,56 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_BASE_BUFFER_H +#define CANDY_GEAR_BLUCAT_BASE_BUFFER_H 1 + +#include "../command.hpp" +#include "core.hpp" +#include "device.hpp" + +namespace BluCat +{ + +class BaseBuffer +{ + public: + virtual ~BaseBuffer(){}; + + VkBuffer buffer; + VkDeviceMemory device_memory; + VkDeviceSize device_size; + VkBufferUsageFlags buffer_usage; + VkMemoryPropertyFlags memory_properties; + + protected: + static const CommandChain loader; + + Device *device; + + static void + load_buffer(void *obj); + static void + unload_buffer(void *obj); + + static void + load_memory(void *obj); + static void + unload_memory(void *obj); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_BASE_BUFFER_H */ diff --git a/src/blucat/character.cpp b/src/blucat/character.cpp new file mode 100644 index 0000000..c13ffef --- /dev/null +++ b/src/blucat/character.cpp @@ -0,0 +1,274 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "character.hpp" + +#include "../command.hpp" +#include "../core.hpp" +#include "font.hpp" +#include "image.hpp" +#include "source_buffer.hpp" + +namespace +{ + +struct CharacterBuilder +{ + BluCat::Character *character; + FT_Face face; + uint32_t character_code; + + CharacterBuilder( + BluCat::Character *character, FT_Face face, uint32_t character_code); +}; + +CharacterBuilder::CharacterBuilder( + BluCat::Character *character, FT_Face face, uint32_t character_code): + character{character}, + face{face}, + character_code{character_code} +{ +} + +// TODO: creating one image with one device memory for each character is +// ineficient +void +load_image(void *obj) +{ + auto self = static_cast<CharacterBuilder*>(obj); + + FT_Error error; + std::vector<uint8_t> source_image_raw; + const int num_channels = 4; // all images are converted to RGBA + auto glyph_index{FT_Get_Char_Index(self->face, self->character_code)}; + + error = FT_Load_Glyph(self->face, glyph_index, FT_LOAD_DEFAULT); + if(error) throw CommandError{"failed to load glyph"}; + + error = FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL); + if(error) throw CommandError{"failed to render glyph"}; + + self->character->bearing_x = self->face->glyph->bitmap_left; + self->character->bearing_y = self->face->glyph->bitmap_top; + self->character->advance = (self->face->glyph->advance.x >> 6); + self->character->width = self->face->glyph->bitmap.width; + self->character->height = self->face->glyph->bitmap.rows; + self->character->mip_levels = 1; + + // Character is a white-space. + if(self->character->width <= 0) + { + self->character->image = VK_NULL_HANDLE; + self->character->device_memory = VK_NULL_HANDLE; + + return; + } + + auto image_size{static_cast<size_t>( + self->face->glyph->bitmap.width * + self->face->glyph->bitmap.rows * num_channels)}; + + { // Create the data for the image buffer + source_image_raw.resize(image_size, 0); + + for(auto y{0}; y < self->face->glyph->bitmap.width; y++) + { + for(auto x{0}; x < self->face->glyph->bitmap.rows; x++) + { + auto image_coord = y * self->face->glyph->bitmap.rows * num_channels + + x * num_channels; + auto glyph_coord = y * self->face->glyph->bitmap.rows + x; + // Red + source_image_raw[image_coord] = 255; + // Green + source_image_raw[image_coord + 1] = 255; + // Blue + source_image_raw[image_coord + 2] = 255; + // Alpha + source_image_raw[image_coord + 3] = + self->face->glyph->bitmap.buffer[glyph_coord]; + } + } + } + + BluCat::SourceBuffer source_image_buffer{ + cg_core.vk_device_with_swapchain, source_image_raw.data(), + image_size}; + + { // Create Vulkan image. + try + { + VkExtent3D vk_extent3d; + vk_extent3d.width = self->face->glyph->bitmap.width; + vk_extent3d.height = self->face->glyph->bitmap.rows; + vk_extent3d.depth = 1; + + BluCat::Image::create( + cg_core.vk_device_with_swapchain, + &self->character->image, + &self->character->device_memory, + VK_FORMAT_R8G8B8A8_UNORM, + vk_extent3d, + self->character->mip_levels, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); + } + catch(BluCat::Image::Error error) + { + throw CommandError{error.what()}; + } + } + + { // Copy image from buffer into image. + auto queue_family{cg_core.vk_device_with_swapchain-> + get_queue_family_with_presentation()}; + auto queue{queue_family->get_queue()}; + BluCat::CommandPool command_pool{queue_family, 1}; + VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; + + queue.submit_one_time_command(vk_command_buffer, [&](){ + BluCat::Image::move_image_state( + vk_command_buffer, self->character->image, VK_FORMAT_R8G8B8A8_UNORM, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferImageCopy image_copy; + image_copy.bufferOffset = 0; + image_copy.bufferRowLength = 0; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_copy.imageSubresource.mipLevel = 0; + image_copy.imageSubresource.baseArrayLayer = 0; + image_copy.imageSubresource.layerCount = 1; + image_copy.imageOffset = {0, 0, 0}; + image_copy.imageExtent = { + self->character->width, self->character->height, 1}; + + vkCmdCopyBufferToImage( + vk_command_buffer, source_image_buffer.buffer, self->character->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); + + BluCat::Image::move_image_state( + vk_command_buffer, self->character->image, VK_FORMAT_R8G8B8A8_UNORM, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + }); + } +} + +void +unload_image(void *obj) +{ + auto self = static_cast<CharacterBuilder*>(obj); + + vkDestroyImage( + cg_core.vk_device_with_swapchain->device, self->character->image, nullptr); + vkFreeMemory( + cg_core.vk_device_with_swapchain->device, self->character->device_memory, + nullptr); +} + +const CommandChain loader{ + {&load_image, &unload_image} +}; + +} + +namespace BluCat +{ + +Character::Character(FT_Face face, uint32_t character_code) +{ + CharacterBuilder character_builder(this, face, character_code); + loader.execute(&character_builder); +} + +Character::~Character() +{ + CharacterBuilder character_builder(this, nullptr, 0); + loader.revert(&character_builder); +} + +std::vector<uint32_t> +Character::str_to_unicode(const char* str) +{ + std::vector<uint32_t> unicode_text; + int text_width{0}; + int text_height{0}; + + { // Reserve memory + int size{0}; + for(auto i{0}; str[i] != '\0'; i++) + if((str[i] & 0b11000000) != 0b10000000) size++; + unicode_text.reserve(size); + } + + for(auto i{0}; str[i] != '\0'; i++) + { + int num_bytes; + uint32_t codepoint{0}; + + if(str[i] >= 0 && str[i] < 127) + { // Normal ASCI character, 1-byte. + num_bytes = 1; + codepoint = str[i]; + } + else if((str[i] & 0xE0) == 0xC0) + { // 2-byte character. + num_bytes = 2; + codepoint += ((str[i] & 0b00011111) << 6); + } + else if((str[i] & 0xF0) == 0xE0) + { // 3-byte character. + num_bytes = 3; + codepoint += ((str[i] & 0b00001111) << 12); + } + else if((str[i] & 0xF8) == 0xF0) + { // 4-byte character. + num_bytes = 4; + codepoint += ((str[i] & 0b00000111) << 18); + } + else + { // FIXME: Add support to 5-byte and 6-byte characters. + } + + switch (num_bytes) + { + case 4: + i++; + codepoint += ((str[i] & 0b00111111) << 12); + case 3: + i++; + codepoint += ((str[i] & 0b00111111) << 6); + case 2: + i++; + codepoint += (str[i] & 0b00111111); + case 1: + default: + break; + } + + unicode_text.push_back(codepoint); + } + + return unicode_text; +} + +} diff --git a/src/blucat/character.hpp b/src/blucat/character.hpp new file mode 100644 index 0000000..43cc765 --- /dev/null +++ b/src/blucat/character.hpp @@ -0,0 +1,49 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_CHARACTER_H +#define CANDY_GEAR_BLUCAT_CHARACTER_H 1 + +#include <ft2build.h> +#include FT_FREETYPE_H + +#include "core.hpp" + +#include <vector> + +namespace BluCat +{ + +struct Character +{ + VkImage image; + VkDeviceMemory device_memory; + int32_t bearing_y, bearing_x; + uint32_t width, height, advance, mip_levels; + + Character(FT_Face face, uint32_t character_code); + ~Character(); + + Character(Character const&) = delete; + Character& operator=(Character const&) = delete; + + static std::vector<uint32_t> + str_to_unicode(const char* str); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_CHARACTER_H */ diff --git a/src/blucat/command_pool.cpp b/src/blucat/command_pool.cpp new file mode 100644 index 0000000..898d7ae --- /dev/null +++ b/src/blucat/command_pool.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "command_pool.hpp" + +namespace BluCat +{ + +const CommandChain CommandPool::loader{ + {&CommandPool::load_command_pool, &CommandPool::unload_command_pool}, + {&CommandPool::load_command_buffers, nullptr} +}; + +CommandPool::CommandPool(QueueFamily *queue_family, uint32_t buffers_quantity): + queue_family{queue_family} +{ + this->command_buffers.resize(buffers_quantity); + + CommandPool::loader.execute(this); +} + +CommandPool::~CommandPool() +{ + CommandPool::loader.revert(this); +} + +void +CommandPool::load_command_pool(void *obj) +{ + auto *self = static_cast<CommandPool*>(obj); + + VkCommandPoolCreateInfo command_pool_create_info; + + command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + command_pool_create_info.pNext = nullptr; + command_pool_create_info.flags = + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + command_pool_create_info.queueFamilyIndex = self->queue_family->family_index; + + if(vkCreateCommandPool( + self->queue_family->device->device, &command_pool_create_info, + nullptr, &self->command_pool) != VK_SUCCESS) + throw CommandError{"Vulkan command pool could not be created."}; +} + +void +CommandPool::unload_command_pool(void *obj) +{ + auto *self = static_cast<CommandPool*>(obj); + + vkDestroyCommandPool( + self->queue_family->device->device, self->command_pool, nullptr); +} + +void +CommandPool::load_command_buffers(void *obj) +{ + auto *self = static_cast<CommandPool*>(obj); + + VkCommandBufferAllocateInfo command_buffer_info; + command_buffer_info.sType = + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + command_buffer_info.pNext = nullptr; + command_buffer_info.commandPool = self->command_pool; + command_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + command_buffer_info.commandBufferCount = self->command_buffers.size(); + + if(vkAllocateCommandBuffers( + self->queue_family->device->device, + &command_buffer_info, self->command_buffers.data()) != VK_SUCCESS) + throw CommandError{"Vulkan command buffers could not be allocated."}; +} + +} diff --git a/src/blucat/command_pool.hpp b/src/blucat/command_pool.hpp new file mode 100644 index 0000000..aa23387 --- /dev/null +++ b/src/blucat/command_pool.hpp @@ -0,0 +1,59 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_COMMAND_POOL_H +#define CANDY_GEAR_BLUCAT_COMMAND_POOL_H 1 + +#include <vector> + +#include "../command.hpp" +#include "core.hpp" +#include "device.hpp" + +namespace BluCat +{ + +class CommandPool +{ + CommandPool(const CommandPool &t) = delete; + CommandPool& operator=(const CommandPool &t) = delete; + CommandPool(const CommandPool &&t) = delete; + CommandPool& operator=(const CommandPool &&t) = delete; + +public: + std::vector<VkCommandBuffer> command_buffers; + + CommandPool(QueueFamily *queue_family, uint32_t buffers_quantity); + ~CommandPool(); + +private: + static const CommandChain loader; + + QueueFamily *queue_family; + VkCommandPool command_pool; + + static void + load_command_pool(void *obj); + static void + unload_command_pool(void *obj); + + static void + load_command_buffers(void *obj); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_COMMAND_POOL_H */ diff --git a/src/blucat/core.hpp b/src/blucat/core.hpp new file mode 100644 index 0000000..169e3bc --- /dev/null +++ b/src/blucat/core.hpp @@ -0,0 +1,34 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_CORE_H +#define CANDY_GEAR_BLUCAT_CORE_H 1 + +// GLM uses some definitions to control their behavior, so you should not +// include it directly. Instead, use this header. +#define GLM_ENABLE_EXPERIMENTAL +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE + +#include <glm/ext/vector_float3.hpp> +#include <glm/ext.hpp> +#include <glm/gtc/matrix_transform.hpp> +#include <glm/gtx/quaternion.hpp> +#include <glm/vec3.hpp> + +#include <vulkan/vulkan.h> + +#endif /* CANDY_GEAR_BLUCAT_CORE_H */ diff --git a/src/blucat/descriptor_set_layout.cpp b/src/blucat/descriptor_set_layout.cpp new file mode 100644 index 0000000..0fc5208 --- /dev/null +++ b/src/blucat/descriptor_set_layout.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "descriptor_set_layout.hpp" + +#include <array> + +#include "../core.hpp" + +namespace +{ + +void +load_world(void *obj) +{ + auto self = static_cast<BluCat::DescriptorSetLayout*>(obj); + + std::array<VkDescriptorSetLayoutBinding, 2> set_layouts{}; + set_layouts[0].binding = 0; + set_layouts[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + set_layouts[0].descriptorCount = 1; + set_layouts[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + set_layouts[0].pImmutableSamplers = nullptr; + + set_layouts[1].binding = 1; + set_layouts[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + set_layouts[1].descriptorCount = 1; + set_layouts[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + set_layouts[1].pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo layout_info{}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.pNext = nullptr; + layout_info.flags = 0; + layout_info.bindingCount = set_layouts.size(); + layout_info.pBindings = set_layouts.data(); + + if(vkCreateDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, &layout_info, nullptr, + &self->world) != VK_SUCCESS) + throw CommandError{ + "Failed to create Vulkan descriptor set layout for world view."}; +} + +void +unload_world(void *obj) +{ + auto self = static_cast<BluCat::DescriptorSetLayout*>(obj); + + vkDestroyDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, self->world, nullptr); +} + +void +load_view(void *obj) +{ + auto self = static_cast<BluCat::DescriptorSetLayout*>(obj); + + std::array<VkDescriptorSetLayoutBinding, 1> layout_bindings{}; + + layout_bindings[0].binding = 0; + layout_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + layout_bindings[0].descriptorCount = 1; + layout_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + layout_bindings[0].pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo layout_info{}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.pNext = nullptr; + layout_info.flags = 0; + layout_info.bindingCount = static_cast<uint32_t>(layout_bindings.size()); + layout_info.pBindings = layout_bindings.data(); + + if(vkCreateDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, &layout_info, nullptr, + &self->view) != VK_SUCCESS) + throw CommandError{ + "Failed to create Vulkan descriptor set layout for view."}; +} + +void +unload_view(void *obj) +{ + auto self = static_cast<BluCat::DescriptorSetLayout*>(obj); + + vkDestroyDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, self->view, nullptr); +} + +void +load_texture(void *obj) +{ + auto self = static_cast<BluCat::DescriptorSetLayout*>(obj); + + std::array<VkDescriptorSetLayoutBinding, 1> layout_bindings{}; + + layout_bindings[0].binding = 0; + layout_bindings[0].descriptorType = + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + layout_bindings[0].descriptorCount = 1; + layout_bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + layout_bindings[0].pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo layout_info{}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.pNext = nullptr; + layout_info.flags = 0; + layout_info.bindingCount = static_cast<uint32_t>(layout_bindings.size()); + layout_info.pBindings = layout_bindings.data(); + + if(vkCreateDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, &layout_info, nullptr, + &self->texture) != VK_SUCCESS) + throw CommandError{ + "Failed to create Vulkan descriptor set layout for textures."}; +} + +void +unload_texture(void *obj) +{ + auto self = static_cast<BluCat::DescriptorSetLayout*>(obj); + + vkDestroyDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, self->texture, nullptr); +} + +void +load_model(void *obj) +{ + auto self = static_cast<BluCat::DescriptorSetLayout*>(obj); + + std::array<VkDescriptorSetLayoutBinding, 1> layout_bindings; + layout_bindings[0].binding = 0; + layout_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + layout_bindings[0].descriptorCount = 1; + layout_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + layout_bindings[0].pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo layout_info{}; + layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_info.pNext = nullptr; + layout_info.flags = 0; + layout_info.bindingCount = static_cast<uint32_t>(layout_bindings.size()); + layout_info.pBindings = layout_bindings.data(); + + if(vkCreateDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, &layout_info, nullptr, + &self->model) != VK_SUCCESS) + throw CommandError{ + "Failed to create Vulkan descriptor set layout for model instance."}; +} + +void +unload_model(void *obj) +{ + auto self = static_cast<BluCat::DescriptorSetLayout*>(obj); + + vkDestroyDescriptorSetLayout( + cg_core.vk_device_with_swapchain->device, self->model, nullptr); +} + +const CommandChain loader{ + {&load_world, &unload_world}, + {&load_view, &unload_view}, + {&load_texture, &unload_texture}, + {&load_model, &unload_model}, +}; + +} + +namespace BluCat +{ + +DescriptorSetLayout::DescriptorSetLayout() +{ + loader.execute(this); +} + +DescriptorSetLayout::~DescriptorSetLayout() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/descriptor_set_layout.hpp b/src/blucat/descriptor_set_layout.hpp new file mode 100644 index 0000000..144a137 --- /dev/null +++ b/src/blucat/descriptor_set_layout.hpp @@ -0,0 +1,38 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_DESCRIPTOR_SET_LAYOUT_H +#define CANDY_GEAR_BLUCAT_DESCRIPTOR_SET_LAYOUT_H 1 + +#include "core.hpp" + +namespace BluCat +{ + +struct DescriptorSetLayout +{ + VkDescriptorSetLayout world; + VkDescriptorSetLayout view; + VkDescriptorSetLayout texture; + VkDescriptorSetLayout model; + + DescriptorSetLayout(); + ~DescriptorSetLayout(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_DESCRIPTOR_SET_LAYOUT_H */ diff --git a/src/blucat/destination_buffer.cpp b/src/blucat/destination_buffer.cpp new file mode 100644 index 0000000..ea5f31e --- /dev/null +++ b/src/blucat/destination_buffer.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "destination_buffer.hpp" + +#include "command_pool.hpp" + +namespace BluCat +{ + +DestinationBuffer::DestinationBuffer( + QueueFamily *queue_family, SourceBuffer *source_buffer, + VkBufferUsageFlags buffer_usage) +{ + this->device = queue_family->device; + this->device_size = source_buffer->device_size; + this->buffer_usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | buffer_usage; + this->memory_properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + this->queue_family = queue_family; + this->source_buffer = source_buffer; + + BaseBuffer::loader.execute(dynamic_cast<BaseBuffer*>(this)); + + this->copy_data(); +} + +DestinationBuffer::DestinationBuffer( + DestinationBuffer &&that) +{ + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; +} + +DestinationBuffer& +DestinationBuffer::operator=(DestinationBuffer &&that) +{ + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; + + return *this; +} + +DestinationBuffer::~DestinationBuffer() +{ + BaseBuffer::loader.revert(dynamic_cast<BaseBuffer*>(this)); +} + +void +DestinationBuffer::copy_data() +{ + CommandPool command_pool(this->queue_family, 1); + Queue transfer_queue{this->queue_family->get_queue()}; + this->device_size = source_buffer->device_size; + + this->vk_command_buffer = command_pool.command_buffers[0]; + + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + VkBufferCopy copy_region = {}; + copy_region.srcOffset = 0; + copy_region.dstOffset = 0; + copy_region.size = this->device_size; + + vkBeginCommandBuffer(this->vk_command_buffer, &begin_info); + + vkCmdCopyBuffer( + this->vk_command_buffer, this->source_buffer->buffer, this->buffer, 1, + ©_region); + + vkEndCommandBuffer(this->vk_command_buffer); + + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &this->vk_command_buffer; + + vkQueueSubmit(transfer_queue.queue, 1, &submit_info, VK_NULL_HANDLE); + + vkQueueWaitIdle(transfer_queue.queue); +} + +} diff --git a/src/blucat/destination_buffer.hpp b/src/blucat/destination_buffer.hpp new file mode 100644 index 0000000..8c098ab --- /dev/null +++ b/src/blucat/destination_buffer.hpp @@ -0,0 +1,54 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_DESTINATION_BUFFER_H +#define CANDY_GEAR_BLUCAT_DESTINATION_BUFFER_H 1 + +#include "base_buffer.hpp" +#include "core.hpp" +#include "source_buffer.hpp" +#include "queue_family.hpp" + +namespace BluCat +{ + +struct DestinationBuffer: public BaseBuffer +{ + DestinationBuffer(const DestinationBuffer &t) = delete; + DestinationBuffer& + operator=(const DestinationBuffer &t) = delete; + + QueueFamily *queue_family; + SourceBuffer *source_buffer; + VkCommandBuffer vk_command_buffer; + + DestinationBuffer( + QueueFamily *queue_family, SourceBuffer *source_buffer, + VkBufferUsageFlags buffer_usage); + + DestinationBuffer(DestinationBuffer &&that); + DestinationBuffer& + operator=(DestinationBuffer &&that); + + ~DestinationBuffer(); + + void + copy_data(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_DESTINATION_BUFFER_H */ diff --git a/src/blucat/device.cpp b/src/blucat/device.cpp new file mode 100644 index 0000000..cf21fa4 --- /dev/null +++ b/src/blucat/device.cpp @@ -0,0 +1,392 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "device.hpp" + +#include <fstream> +#include <new> +#include <vector> +#ifdef DEBUG +#include <sstream> +#endif + +#include "../core.hpp" + +namespace +{ +VkShaderModule +create_shader_module(VkDevice vk_device, const char *filename) +{ + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) + { + throw std::runtime_error("Failed to open shader module file."); + } + + size_t file_size = (size_t) file.tellg(); + std::vector<char> code(file_size); + + file.seekg(0); + file.read(code.data(), file_size); + + file.close(); + + VkShaderModuleCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.codeSize = code.size(); + create_info.pCode = reinterpret_cast<const uint32_t*>(code.data()); + + VkShaderModule shader_module; + if (vkCreateShaderModule(vk_device, &create_info, nullptr, + &shader_module) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create shader module."); + } + + return shader_module; +} +} + +namespace BluCat +{ + +Device::Device(VkPhysicalDevice vk_physical_device, bool with_swapchain) +{ + this->physical_device = vk_physical_device; + + std::vector<VkQueueFamilyProperties> queue_family_properties; + + // Get queue families. + { + vkGetPhysicalDeviceQueueFamilyProperties( + vk_physical_device, &this->queue_families_count, nullptr); + queue_family_properties.resize(this->queue_families_count); + vkGetPhysicalDeviceQueueFamilyProperties( + vk_physical_device, &this->queue_families_count, + queue_family_properties.data()); + } + + // Get information from physical device. + { + VkPhysicalDeviceProperties physical_properties = {}; + vkGetPhysicalDeviceProperties(vk_physical_device, &physical_properties); + VkPhysicalDeviceFeatures supported_features = {}; + vkGetPhysicalDeviceFeatures(vk_physical_device, &supported_features); + +#ifdef DEBUG + std::stringstream message{}; + message << "Name: " << physical_properties.deviceName << std::endl; + message << "API version: " << physical_properties.apiVersion << + std::endl; + message << "Driver version: " << physical_properties.driverVersion << + std::endl; + message << "Vendor ID: " << physical_properties.vendorID << std::endl; + message << "Device ID: " << physical_properties.deviceID << std::endl; + message << "Device type: " << physical_properties.deviceType << + std::endl; + cg_core.log.message(Log::Level::Trace, message.str()); +#endif + + std::vector<VkDeviceQueueCreateInfo> device_queue_create_infos; + std::vector<std::vector<float>> queue_priorities( + queue_family_properties.size()); + device_queue_create_infos.resize(queue_family_properties.size()); + for(auto i{0}; i < queue_family_properties.size(); i++) + { + // Give different priorities to queues. + int queue_count = queue_family_properties[i].queueCount; + queue_priorities[i].resize(queue_count); + float priority_unity = 1.0f/queue_count; + for(auto ii{0}; ii < queue_count; ii++) + queue_priorities[i][ii] = priority_unity * (queue_count - ii); + + device_queue_create_infos[i].sType = + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + device_queue_create_infos[i].pNext = nullptr; + device_queue_create_infos[i].flags = 0; + device_queue_create_infos[i].queueFamilyIndex = i; + device_queue_create_infos[i].queueCount = queue_priorities[i].size(); + device_queue_create_infos[i].pQueuePriorities = + queue_priorities[i].data(); + } + + VkPhysicalDeviceFeatures required_features = {}; + required_features.multiDrawIndirect = supported_features.multiDrawIndirect; + required_features.fillModeNonSolid = VK_TRUE; + required_features.geometryShader = VK_TRUE; + required_features.tessellationShader = VK_TRUE; + required_features.samplerAnisotropy = VK_TRUE; + + std::vector<const char*> device_extensions; + if(with_swapchain) + device_extensions.emplace_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + + VkDeviceCreateInfo device_create_info = {}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = nullptr; + device_create_info.flags = 0; + device_create_info.queueCreateInfoCount = device_queue_create_infos.size(); + device_create_info.pQueueCreateInfos = device_queue_create_infos.data(); + device_create_info.enabledLayerCount = 0; + device_create_info.ppEnabledLayerNames = nullptr; + device_create_info.enabledExtensionCount = device_extensions.size(); + if(device_extensions.size() == 0) + device_create_info.ppEnabledExtensionNames = nullptr; + else + device_create_info.ppEnabledExtensionNames = device_extensions.data(); + + device_create_info.pEnabledFeatures = &required_features; + + if(vkCreateDevice(this->physical_device, &device_create_info, nullptr, + &this->device) != VK_SUCCESS) + throw std::runtime_error("Failed to create Vulkan physical device."); + } + + // Load Shaders + { + std::string data_dir{""}; +#ifdef _WIN64 + HKEY hKey; + LPCSTR lpSubKey = "SOFTWARE\\CandyGear"; + LPCSTR lpValueName = "InstallLocation"; + DWORD dwType = REG_SZ; + DWORD dwSize = 0; + LPBYTE lpData = NULL; + + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_READ, &hKey) == + ERROR_SUCCESS) + { + if(RegQueryValueEx(hKey, lpValueName, NULL, &dwType, NULL, &dwSize) == + ERROR_SUCCESS) + { + lpData = new BYTE[dwSize]; + if(RegQueryValueEx( + hKey, lpValueName, NULL, &dwType, lpData, &dwSize) == + ERROR_SUCCESS) + { + data_dir = reinterpret_cast<char const*>(lpData); + } + delete[] lpData; + } + RegCloseKey(hKey); + } + + if(data_dir == "") + throw std::runtime_error("Failed to read CandyGear registry."); + + std::string vert_sprite_3d_shader_module{data_dir}; + vert_sprite_3d_shader_module.append("\\glsl\\shader_sprite_3d.vert.spv"); + this->vert_sprite_3d_shader_module = create_shader_module( + this->device, vert_sprite_3d_shader_module.c_str()); + + std::string frag_sprite_3d_shader_module{data_dir}; + frag_sprite_3d_shader_module.append("\\glsl\\shader_sprite_3d.frag.spv"); + this->frag_sprite_3d_shader_module = create_shader_module( + this->device, frag_sprite_3d_shader_module.c_str()); + + std::string vert3d_shader_module{data_dir}; + vert3d_shader_module.append("\\glsl\\shader_3d.vert.spv"); + this->vert3d_shader_module = create_shader_module( + this->device, vert3d_shader_module.c_str()); + + std::string vert3d_skeletal_shader_module{data_dir}; + vert3d_skeletal_shader_module.append( + "\\glsl\\shader_3d_skeletal.vert.spv"); + this->vert3d_skeletal_shader_module = create_shader_module( + this->device, vert3d_skeletal_shader_module.c_str()); + + std::string frag3d_shader_module{data_dir}; + frag3d_shader_module.append("\\glsl\\shader_3d.frag.spv"); + this->frag3d_shader_module = create_shader_module( + this->device, frag3d_shader_module.c_str()); + + std::string vert2d_solid_shader_module{data_dir}; + vert2d_solid_shader_module.append("\\glsl\\shader_2d_solid.vert.spv"); + this->vert2d_solid_shader_module = create_shader_module( + this->device, vert2d_solid_shader_module.c_str()); + + std::string frag2d_solid_shader_module{data_dir}; + frag2d_solid_shader_module.append("\\glsl\\shader_2d_solid.frag.spv"); + this->frag2d_solid_shader_module = create_shader_module( + this->device, frag2d_solid_shader_module.c_str()); + + std::string vert2d_wired_shader_module{data_dir}; + vert2d_wired_shader_module.append("\\glsl\\shader_2d_wired.vert.spv"); + this->vert2d_wired_shader_module = create_shader_module( + this->device, vert2d_wired_shader_module.c_str()); + + std::string frag2d_wired_shader_module{data_dir}; + frag2d_wired_shader_module.append("\\glsl\\shader_2d_wired.frag.spv"); + this->frag2d_wired_shader_module = create_shader_module( + this->device, frag2d_wired_shader_module.c_str()); +#else + data_dir = "/usr/local/share/candy_gear"; + + std::string vert_sprite_3d_shader_module{data_dir}; + vert_sprite_3d_shader_module.append("/glsl/shader_sprite_3d.vert.spv"); + this->vert_sprite_3d_shader_module = create_shader_module( + this->device, vert_sprite_3d_shader_module.c_str()); + + std::string frag_sprite_3d_shader_module{data_dir}; + frag_sprite_3d_shader_module.append("/glsl/shader_sprite_3d.frag.spv"); + this->frag_sprite_3d_shader_module = create_shader_module( + this->device, frag_sprite_3d_shader_module.c_str()); + + std::string vert3d_shader_module{data_dir}; + vert3d_shader_module.append("/glsl/shader_3d.vert.spv"); + this->vert3d_shader_module = create_shader_module( + this->device, vert3d_shader_module.c_str()); + + std::string vert3d_skeletal_shader_module{data_dir}; + vert3d_skeletal_shader_module.append("/glsl/shader_3d_skeletal.vert.spv"); + this->vert3d_skeletal_shader_module = create_shader_module( + this->device, vert3d_skeletal_shader_module.c_str()); + + std::string frag3d_shader_module{data_dir}; + frag3d_shader_module.append("/glsl/shader_3d.frag.spv"); + this->frag3d_shader_module = create_shader_module( + this->device, frag3d_shader_module.c_str()); + + std::string vert2d_solid_shader_module{data_dir}; + vert2d_solid_shader_module.append("/glsl/shader_2d_solid.vert.spv"); + this->vert2d_solid_shader_module = create_shader_module( + this->device, vert2d_solid_shader_module.c_str()); + + std::string frag2d_solid_shader_module{data_dir}; + frag2d_solid_shader_module.append("/glsl/shader_2d_solid.frag.spv"); + this->frag2d_solid_shader_module = create_shader_module( + this->device, frag2d_solid_shader_module.c_str()); + + std::string vert2d_wired_shader_module{data_dir}; + vert2d_wired_shader_module.append("/glsl/shader_2d_wired.vert.spv"); + this->vert2d_wired_shader_module = create_shader_module( + this->device, vert2d_wired_shader_module.c_str()); + + std::string frag2d_wired_shader_module{data_dir}; + frag2d_wired_shader_module.append("/glsl/shader_2d_wired.frag.spv"); + this->frag2d_wired_shader_module = create_shader_module( + this->device, frag2d_wired_shader_module.c_str()); +#endif + } + + this->queue_families = static_cast<QueueFamily*>( + std::malloc(this->queue_families_count * sizeof(QueueFamily))); + for(auto i{0}; i < this->queue_families_count; i++) + { + new(&this->queue_families[i])QueueFamily( + this, i, queue_family_properties[i]); + + // Select families with graphics support. + auto &family_properties = this->queue_families[i].family_properties; + if(family_properties.queueCount > 0 && + family_properties.queueFlags & VK_QUEUE_GRAPHICS_BIT) + this->queue_families_with_graphics.push_back( + &this->queue_families[i]); + + // Select families with presentation support. + VkBool32 present_supported; + vkGetPhysicalDeviceSurfaceSupportKHR( + vk_physical_device, i, cg_core.window_surface, &present_supported); + if(present_supported) + this->queue_families_with_presentation.push_back( + &this->queue_families[i]); + } +} + +Device::~Device() +{ + for(auto i{0}; i < this->queue_families_count; i++) + this->queue_families[i].~QueueFamily(); + std::free(this->queue_families); + + // Destroy shaders + vkDestroyShaderModule( + this->device, this->vert_sprite_3d_shader_module, nullptr); + vkDestroyShaderModule( + this->device, this->frag_sprite_3d_shader_module, nullptr); + vkDestroyShaderModule(this->device, this->vert3d_shader_module, nullptr); + vkDestroyShaderModule( + this->device, this->vert3d_skeletal_shader_module, nullptr); + vkDestroyShaderModule(this->device, this->frag3d_shader_module, nullptr); + vkDestroyShaderModule( + this->device, this->vert2d_solid_shader_module, nullptr); + vkDestroyShaderModule( + this->device, this->frag2d_solid_shader_module, nullptr); + vkDestroyShaderModule( + this->device, this->vert2d_wired_shader_module, nullptr); + vkDestroyShaderModule( + this->device, this->frag2d_wired_shader_module, nullptr); + + vkDeviceWaitIdle(this->device); + vkDestroyDevice(this->device, nullptr); +} + +bool +Device::select_memory_type( + uint32_t *memory_type_index, VkMemoryRequirements *vk_memory_requirements, + VkMemoryPropertyFlags vk_property_flags) +{ + VkPhysicalDeviceMemoryProperties vk_memory_properties; + vkGetPhysicalDeviceMemoryProperties( + this->physical_device, &vk_memory_properties); + + for (auto i{0}; i < vk_memory_properties.memoryTypeCount; i++) + { + if(vk_memory_requirements->memoryTypeBits & (1 << i)) + { + const VkMemoryType& type = vk_memory_properties.memoryTypes[i]; + + if ((type.propertyFlags & vk_property_flags) == vk_property_flags) + { + *memory_type_index = i; + return true; + } + } + } + + return false; +} + +QueueFamily* +Device::get_queue_family_with_graphics() const +{ + /* + Returns a random queue family, so not all commands in the engine use the + same queue. + TODO: There must be a better way of doing this. + */ + std::uniform_int_distribution<std::size_t> random_distribution{ + 0, this->queue_families_with_graphics.size() -1}; + auto random = random_distribution(random_number_generator); + return this->queue_families_with_graphics[0]; +} + +QueueFamily* +Device::get_queue_family_with_presentation() const +{ + /* + Returns a random queue family, so not all commands in the engine use the + same queue. + TODO: There must be a better way of doing this. + */ + std::uniform_int_distribution<std::size_t> random_distribution{ + 0, this->queue_families_with_presentation.size() -1}; + auto random = random_distribution(random_number_generator); + return this->queue_families_with_presentation[0]; +} + +} diff --git a/src/blucat/device.hpp b/src/blucat/device.hpp new file mode 100644 index 0000000..bcfca4d --- /dev/null +++ b/src/blucat/device.hpp @@ -0,0 +1,69 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_DEVICE_H +#define CANDY_GEAR_BLUCAT_DEVICE_H 1 + +#include <cstdlib> +#include <vector> + +#include "core.hpp" +#include "queue_family.hpp" + +namespace BluCat +{ + +struct Device +{ + uint32_t queue_families_count; + QueueFamily *queue_families; + std::vector<QueueFamily*> queue_families_with_graphics; + std::vector<QueueFamily*> queue_families_with_presentation; + +public: + VkDevice device; + VkPhysicalDevice physical_device; + + VkShaderModule vert_sprite_3d_shader_module; + VkShaderModule frag_sprite_3d_shader_module; + VkShaderModule vert3d_shader_module; + VkShaderModule vert3d_skeletal_shader_module; + VkShaderModule frag3d_shader_module; + VkShaderModule vert2d_solid_shader_module; + VkShaderModule frag2d_solid_shader_module; + VkShaderModule vert2d_wired_shader_module; + VkShaderModule frag2d_wired_shader_module; + + bool with_swapchain; + + Device(VkPhysicalDevice vk_physical_device, bool with_swapchain); + ~Device(); + + bool + select_memory_type( + uint32_t *memoryTypeIndex, VkMemoryRequirements *vk_memory_requirements, + VkMemoryPropertyFlags vk_property_flags); + + QueueFamily* + get_queue_family_with_graphics() const; + + QueueFamily* + get_queue_family_with_presentation() const; +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_DEVICE_H */ diff --git a/src/blucat/font.cpp b/src/blucat/font.cpp new file mode 100644 index 0000000..4195b57 --- /dev/null +++ b/src/blucat/font.cpp @@ -0,0 +1,53 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "font.hpp" + +#include "../core.hpp" + +namespace BluCat +{ + +Font::Font(const char* font_path, int font_size) +{ + FT_Error error; + error = FT_New_Face(cg_core.font_library, font_path, 0, &this->face); + if(error == FT_Err_Unknown_File_Format) throw std::invalid_argument( + "The font file could be opened and read, but it appears that its font " + "format is unsupported."); + else if(error) throw std::invalid_argument( + "The font file could not be opened or read, or it is broken."); + + error = FT_Set_Pixel_Sizes(this->face, 0, font_size); + if(error) throw std::invalid_argument("Failed to load font size."); +} + +Font::~Font() +{ + FT_Done_Face(this->face); +} + +std::shared_ptr<Character> +Font::character(uint32_t character_code) +{ + if(!this->characters.contains(character_code)) + this->characters.emplace( + character_code, std::make_shared<Character>(this->face, character_code)); + + return this->characters.at(character_code); +} + +} diff --git a/src/blucat/font.hpp b/src/blucat/font.hpp new file mode 100644 index 0000000..1b9be69 --- /dev/null +++ b/src/blucat/font.hpp @@ -0,0 +1,42 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_FONT_H +#define CANDY_GEAR_BLUCAT_FONT_H 1 + +#include <memory> +#include <unordered_map> + +#include "character.hpp" + +namespace BluCat +{ + +struct Font +{ + FT_Face face; + std::unordered_map<uint32_t, std::shared_ptr<Character>> characters; + + Font(const char* font_path, int font_size); + ~Font(); + + std::shared_ptr<Character> + character(uint32_t character_code); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_FONT_H */ diff --git a/src/blucat/framebuffer.cpp b/src/blucat/framebuffer.cpp new file mode 100644 index 0000000..e761ff2 --- /dev/null +++ b/src/blucat/framebuffer.cpp @@ -0,0 +1,198 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "framebuffer.hpp" + +#include "../core.hpp" +#include "image.hpp" + +namespace +{ +void +load_depth_image(void *obj) +{ + auto self = static_cast<BluCat::Framebuffer*>(obj); + + VkExtent3D extent3d{}; + extent3d.width = cg_core.display_width; + extent3d.height = cg_core.display_height; + extent3d.depth = 1; + + try + { + BluCat::Image::create( + cg_core.vk_device_with_swapchain, + &self->depth_image, + &self->depth_image_memory, + VK_FORMAT_D32_SFLOAT, + extent3d, + 1, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + catch(BluCat::Image::Error error) + { + std::string error_message{"Failed to create depth image → "}; + error_message += error.what(); + throw CommandError{error_message}; + } +} + +void +unload_depth_image(void *obj) +{ + auto self = static_cast<BluCat::Framebuffer*>(obj); + + vkDestroyImage( + cg_core.vk_device_with_swapchain->device, self->depth_image, + nullptr); + vkFreeMemory( + cg_core.vk_device_with_swapchain->device, + self->depth_image_memory, nullptr); +} + +void +load_depth_image_view(void *obj) +{ + auto self = static_cast<BluCat::Framebuffer*>(obj); + + try + { + BluCat::Image::create_view( + cg_core.vk_device_with_swapchain, &self->depth_image_view, + self->depth_image, + VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_DEPTH_BIT); + } + catch(BluCat::Image::Error error) + { + std::string error_message{"Failed to create depth image view → "}; + error_message += error.what(); + throw CommandError{error_message}; + } +} + +void +unload_depth_image_view(void *obj) +{ + auto self = static_cast<BluCat::Framebuffer*>(obj); + + vkDestroyImageView( + cg_core.vk_device_with_swapchain->device, self->depth_image_view, nullptr); +} + +void +load_3d(void *obj) +{ + auto self = static_cast<BluCat::Framebuffer*>(obj); + + self->pipeline_3d.resize(cg_core.vk_swapchain->images_count); + for (auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + { + std::array<VkImageView, 2> attachments = { + cg_core.vk_swapchain->image_views[i], + self->depth_image_view + }; + + VkFramebufferCreateInfo framebuffer_info{}; + framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebuffer_info.renderPass = cg_core.vk_render_pass->pipeline_3d; + framebuffer_info.attachmentCount = attachments.size(); + framebuffer_info.pAttachments = attachments.data(); + framebuffer_info.width = cg_core.display_width; + framebuffer_info.height = cg_core.display_height; + + framebuffer_info.layers = 1; + + if(vkCreateFramebuffer( + cg_core.vk_device_with_swapchain->device, &framebuffer_info, nullptr, + &self->pipeline_3d[i]) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan Framebuffer."}; + } +} + +void +unload_3d(void *obj) +{ + auto self = static_cast<BluCat::Framebuffer*>(obj); + + for(auto framebuffer: self->pipeline_3d) + vkDestroyFramebuffer( + cg_core.vk_device_with_swapchain->device, framebuffer, nullptr); +} + +void +load_2d(void *obj) +{ + auto self = static_cast<BluCat::Framebuffer*>(obj); + + self->pipeline_2d.resize(cg_core.vk_swapchain->images_count); + for (auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + { + std::array<VkImageView, 1> attachments = { + cg_core.vk_swapchain->image_views[i] + }; + + VkFramebufferCreateInfo framebuffer_info{}; + framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebuffer_info.renderPass = cg_core.vk_render_pass->pipeline_2d; + framebuffer_info.attachmentCount = attachments.size(); + framebuffer_info.pAttachments = attachments.data(); + framebuffer_info.width = cg_core.display_width; + framebuffer_info.height = cg_core.display_height; + framebuffer_info.layers = 1; + + if(vkCreateFramebuffer( + cg_core.vk_device_with_swapchain->device, &framebuffer_info, nullptr, + &self->pipeline_2d[i]) + != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan Framebuffer."}; + } +} + +void +unload_2d(void *obj) +{ + auto self = static_cast<BluCat::Framebuffer*>(obj); + + for(auto framebuffer: self->pipeline_2d) + vkDestroyFramebuffer( + cg_core.vk_device_with_swapchain->device, framebuffer, nullptr); +} + +const CommandChain loader{ + {&load_depth_image, &unload_depth_image}, + {&load_depth_image_view, &unload_depth_image_view}, + {&load_3d, &unload_3d}, + {&load_2d, &unload_2d} +}; + +} + +namespace BluCat +{ + +Framebuffer::Framebuffer() +{ + loader.execute(this); +} + +Framebuffer::~Framebuffer() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/framebuffer.hpp b/src/blucat/framebuffer.hpp new file mode 100644 index 0000000..32968a6 --- /dev/null +++ b/src/blucat/framebuffer.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_FRAMEBUFFER_H +#define CANDY_GEAR_BLUCAT_FRAMEBUFFER_H 1 + +#include <vector> + +#include "core.hpp" + +namespace BluCat +{ + +struct Framebuffer +{ + // Depth image. + VkImage depth_image; + VkDeviceMemory depth_image_memory; + VkImageView depth_image_view; + + std::vector<VkFramebuffer> pipeline_3d; + std::vector<VkFramebuffer> pipeline_2d; + + Framebuffer(); + ~Framebuffer(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_FRAMEBUFFER_H */ diff --git a/src/blucat/graphics_pipeline_2d_solid.cpp b/src/blucat/graphics_pipeline_2d_solid.cpp new file mode 100644 index 0000000..28b19b6 --- /dev/null +++ b/src/blucat/graphics_pipeline_2d_solid.cpp @@ -0,0 +1,297 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline_2d_solid.hpp" + +#include <array> + +#include "../core.hpp" +#include "sprite.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DSolid*>(obj); + + VkPipelineShaderStageCreateInfo vert_shader_stage_info{}; + vert_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vert_shader_stage_info.pNext = nullptr; + vert_shader_stage_info.flags = 0; + vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_shader_stage_info.module = + cg_core.vk_device_with_swapchain->vert2d_solid_shader_module; + vert_shader_stage_info.pName = "main"; + vert_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo frag_shader_stage_info{}; + frag_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + frag_shader_stage_info.pNext = nullptr; + frag_shader_stage_info.flags = 0; + frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_shader_stage_info.module = + cg_core.vk_device_with_swapchain->frag2d_solid_shader_module; + frag_shader_stage_info.pName = "main"; + frag_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shader_stages[] = { + vert_shader_stage_info, + frag_shader_stage_info + }; + + VkVertexInputBindingDescription vertex_input_binding{}; + vertex_input_binding.binding = 0; + vertex_input_binding.stride = sizeof(glm::vec2); + vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + std::array<VkVertexInputAttributeDescription, 1> vertex_attribute{}; + // Texture coordinate. + vertex_attribute[0].location = 0; + vertex_attribute[0].binding = 0; + vertex_attribute[0].format = VK_FORMAT_R32G32_SFLOAT; + vertex_attribute[0].offset = 0; + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.pNext = nullptr; + vertex_input_info.flags = 0; + vertex_input_info.vertexBindingDescriptionCount = 1; + vertex_input_info.pVertexBindingDescriptions = &vertex_input_binding; + vertex_input_info.vertexAttributeDescriptionCount = + static_cast<uint32_t>(vertex_attribute.size()); + vertex_input_info.pVertexAttributeDescriptions = vertex_attribute.data(); + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.pNext = nullptr; + input_assembly.flags = 0; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + input_assembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0; + viewport.y = 0; + viewport.width = cg_core.display_width; + viewport.height = cg_core.display_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = {cg_core.display_width, cg_core.display_height}; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.pNext = nullptr; + viewport_state.flags = 0; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.pNext = nullptr; + rasterizer.flags = 0; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + rasterizer.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.blendEnable = VK_TRUE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + color_blend_attachment.dstColorBlendFactor = + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.pNext = nullptr; + color_blending.flags = 0; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + color_blending.blendConstants[0] = 0.0f; + color_blending.blendConstants[1] = 0.0f; + color_blending.blendConstants[2] = 0.0f; + color_blending.blendConstants[3] = 0.0f; + + std::array<VkDynamicState, 3> dynamic_states{ + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH, + VK_DYNAMIC_STATE_BLEND_CONSTANTS + }; + + VkPipelineDynamicStateCreateInfo dynamic_state_info = {}; + dynamic_state_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state_info.dynamicStateCount = dynamic_states.size(); + dynamic_state_info.pDynamicStates = dynamic_states.data(); + + VkGraphicsPipelineCreateInfo pipeline_info{}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.pNext = nullptr; + pipeline_info.flags = 0; + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pTessellationState = nullptr; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = nullptr; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state_info; + pipeline_info.layout = + cg_core.vk_graphics_pipeline_2d_solid_layout->pipeline; + pipeline_info.renderPass = cg_core.vk_render_pass->pipeline_2d; + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_info.basePipelineIndex = -1; + + if(vkCreateGraphicsPipelines( + cg_core.vk_device_with_swapchain->device, VK_NULL_HANDLE, 1, + &pipeline_info, nullptr, &self->graphic_pipeline) + != VK_SUCCESS) + throw CommandError{"Failed to create graphics pipeline."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DSolid*>(obj); + + vkDestroyPipeline( + cg_core.vk_device_with_swapchain->device, self->graphic_pipeline, nullptr); +} + +const CommandChain loader{ + {&load_pipeline, &unload_pipeline} +}; + +} + +namespace BluCat +{ + +GraphicsPipeline2DSolid::GraphicsPipeline2DSolid() +{ + loader.execute(this); +} + +GraphicsPipeline2DSolid::~GraphicsPipeline2DSolid() +{ + loader.revert(this); +} + +void +GraphicsPipeline2DSolid::draw( + std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const size_t next_frame, + const uint32_t image_index) +{ + // TODO set viewport just once per view, not once per pipeline. + { // Set viewport + VkViewport vk_viewport{}; + vk_viewport.x = view->region.x; + vk_viewport.y = view->region.y; + vk_viewport.width = view->region.z; + vk_viewport.height = view->region.w; + vk_viewport.minDepth = 0.0f; + vk_viewport.maxDepth = 1.0f; + vkCmdSetViewport(draw_command_buffer, 0, 1, &vk_viewport); + + VkRect2D vk_scissor{}; + vk_scissor.offset.x = static_cast<int32_t>(view->region.x); + vk_scissor.offset.y = static_cast<int32_t>(view->region.y); + vk_scissor.extent.width = static_cast<uint32_t>(view->region.z); + vk_scissor.extent.height = static_cast<uint32_t>(view->region.w); + vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor); + } + + vkCmdBindPipeline( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + this->graphic_pipeline); + + // FIXME: I know sorting is expensive, but I need to figure out better what + // to do here. + std::sort(view->sprites_to_draw[current_frame].begin(), + view->sprites_to_draw[current_frame].end()); + + // Draw sprites + for(auto& sprite_to_draw: view->sprites_to_draw[current_frame]) + { + std::array<VkDescriptorSet, 2> vk_descriptor_sets{ + view->descriptor_sets_2d[image_index], + sprite_to_draw.sprite->texture->descriptor_sets[image_index]}; + VkDeviceSize offsets[]{0}; + + vkCmdBindDescriptorSets( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + cg_core.vk_graphics_pipeline_2d_solid_layout->pipeline, 0, + vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr); + vkCmdBindVertexBuffers( + draw_command_buffer, 0, 1, &sprite_to_draw.sprite->vertex_buffer->buffer, + offsets); + + UDOVector4D position{sprite_to_draw.position}; + vkCmdPushConstants( + draw_command_buffer, + cg_core.vk_graphics_pipeline_2d_solid_layout->pipeline, + VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(UDOVector4D), &position); + vkCmdDraw(draw_command_buffer, Sprite::vertex_count, 1, 0, 0); + } + + // Prepare for the next frame. + view->sprites_to_draw[next_frame].clear(); +} + +} diff --git a/src/blucat/graphics_pipeline_2d_solid.hpp b/src/blucat/graphics_pipeline_2d_solid.hpp new file mode 100644 index 0000000..eae54ae --- /dev/null +++ b/src/blucat/graphics_pipeline_2d_solid.hpp @@ -0,0 +1,44 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_SOLID_H +#define CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_SOLID_H 1 + +#include <memory> + +#include "core.hpp" +#include "command_pool.hpp" +#include "view_2d.hpp" + +namespace BluCat +{ + +struct GraphicsPipeline2DSolid +{ + VkPipeline graphic_pipeline; + + GraphicsPipeline2DSolid(); + ~GraphicsPipeline2DSolid(); + + void + draw(std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const size_t next_frame, + const uint32_t image_index); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_SOLID_H */ diff --git a/src/blucat/graphics_pipeline_2d_solid_layout.cpp b/src/blucat/graphics_pipeline_2d_solid_layout.cpp new file mode 100644 index 0000000..7b537a2 --- /dev/null +++ b/src/blucat/graphics_pipeline_2d_solid_layout.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline_2d_solid_layout.hpp" + +#include <array> + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DSolidLayout*>(obj); + + std::array<VkDescriptorSetLayout, 2> set_layouts{ + cg_core.vk_descriptor_set_layout->view, + cg_core.vk_descriptor_set_layout->texture + }; + + std::array<VkPushConstantRange, 1> push_constants; + push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + push_constants[0].offset = 0; + push_constants[0].size = sizeof(BluCat::UDOVector4D); + + VkPipelineLayoutCreateInfo pipeline_layout_info{}; + pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_info.setLayoutCount = set_layouts.size(); + pipeline_layout_info.pSetLayouts = set_layouts.data(); + pipeline_layout_info.pushConstantRangeCount = push_constants.size(); + pipeline_layout_info.pPushConstantRanges = push_constants.data(); + + if(vkCreatePipelineLayout( + cg_core.vk_device_with_swapchain->device, &pipeline_layout_info, + nullptr, &self->pipeline) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan pipeline layout."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DSolidLayout*>(obj); + + vkDestroyPipelineLayout( + cg_core.vk_device_with_swapchain->device, self->pipeline, nullptr); +} + +const CommandChain loader{ + {&load_pipeline, &unload_pipeline} +}; + +} + +namespace BluCat +{ + +GraphicsPipeline2DSolidLayout::GraphicsPipeline2DSolidLayout() +{ + loader.execute(this); +} + +GraphicsPipeline2DSolidLayout::~GraphicsPipeline2DSolidLayout() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/graphics_pipeline_2d_solid_layout.hpp b/src/blucat/graphics_pipeline_2d_solid_layout.hpp new file mode 100644 index 0000000..77172be --- /dev/null +++ b/src/blucat/graphics_pipeline_2d_solid_layout.hpp @@ -0,0 +1,35 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_LAYOUT_H +#define CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_LAYOUT_H 1 + +#include "core.hpp" + +namespace BluCat +{ + +struct GraphicsPipeline2DSolidLayout +{ + VkPipelineLayout pipeline; + + GraphicsPipeline2DSolidLayout(); + ~GraphicsPipeline2DSolidLayout(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_LAYOUT_H */ diff --git a/src/blucat/graphics_pipeline_2d_wired.cpp b/src/blucat/graphics_pipeline_2d_wired.cpp new file mode 100644 index 0000000..192a9d6 --- /dev/null +++ b/src/blucat/graphics_pipeline_2d_wired.cpp @@ -0,0 +1,313 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline_2d_wired.hpp" + +#include <array> + +#include "../core.hpp" +#include "rectangle.hpp" +#include "sprite.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_indexes(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DWired*>(obj); + + self->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_graphics(); + + std::array<uint32_t, 4> indexes{0, 1, 2, 3}; + void *indexes_data{indexes.data()}; + size_t indexes_size{sizeof(indexes[0]) * indexes.size()}; + BluCat::SourceBuffer source_index_buffer{ + self->queue_family->device, indexes_data, indexes_size}; + self->index_buffer = new BluCat::DestinationBuffer{ + self->queue_family, &source_index_buffer, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT}; +} + +void +unload_indexes(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DWired*>(obj); + + delete self->index_buffer; +} + +void +load_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DWired*>(obj); + + VkPipelineShaderStageCreateInfo vert_shader_stage_info{}; + vert_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vert_shader_stage_info.pNext = nullptr; + vert_shader_stage_info.flags = 0; + vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_shader_stage_info.module = + cg_core.vk_device_with_swapchain->vert2d_wired_shader_module; + vert_shader_stage_info.pName = "main"; + vert_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo frag_shader_stage_info{}; + frag_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + frag_shader_stage_info.pNext = nullptr; + frag_shader_stage_info.flags = 0; + frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_shader_stage_info.module = + cg_core.vk_device_with_swapchain->frag2d_wired_shader_module; + frag_shader_stage_info.pName = "main"; + frag_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shader_stages[] = { + vert_shader_stage_info, + frag_shader_stage_info + }; + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.pNext = nullptr; + vertex_input_info.flags = 0; + vertex_input_info.vertexBindingDescriptionCount = 0; + vertex_input_info.pVertexBindingDescriptions = nullptr; + vertex_input_info.vertexAttributeDescriptionCount = 0; + vertex_input_info.pVertexAttributeDescriptions = nullptr; + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.pNext = nullptr; + input_assembly.flags = 0; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + input_assembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0; + viewport.y = 0; + viewport.width = cg_core.display_width; + viewport.height = cg_core.display_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = {cg_core.display_width, cg_core.display_height}; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.pNext = nullptr; + viewport_state.flags = 0; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.pNext = nullptr; + rasterizer.flags = 0; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_LINE; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + rasterizer.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.blendEnable = VK_FALSE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.pNext = nullptr; + color_blending.flags = 0; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + color_blending.blendConstants[0] = 0.0f; + color_blending.blendConstants[1] = 0.0f; + color_blending.blendConstants[2] = 0.0f; + color_blending.blendConstants[3] = 0.0f; + + VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH + }; + + VkPipelineDynamicStateCreateInfo dynamic_state_info = {}; + dynamic_state_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state_info.dynamicStateCount = 2; + dynamic_state_info.pDynamicStates = dynamic_states; + + VkGraphicsPipelineCreateInfo pipeline_info{}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.pNext = nullptr; + pipeline_info.flags = 0; + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pTessellationState = nullptr; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = nullptr; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state_info; + pipeline_info.layout = + cg_core.vk_graphics_pipeline_2d_wired_layout->pipeline; + pipeline_info.renderPass = cg_core.vk_render_pass->pipeline_2d; + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_info.basePipelineIndex = -1; + + if(vkCreateGraphicsPipelines( + cg_core.vk_device_with_swapchain->device, VK_NULL_HANDLE, 1, + &pipeline_info, nullptr, &self->graphic_pipeline) != VK_SUCCESS) + throw CommandError{"Failed to create graphics pipeline."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DWired*>(obj); + + vkDestroyPipeline( + cg_core.vk_device_with_swapchain->device, self->graphic_pipeline, nullptr); +} + +const CommandChain loader{ + {&load_indexes, &unload_indexes}, + {&load_pipeline, &unload_pipeline} +}; + +} + +namespace BluCat +{ + +GraphicsPipeline2DWired::GraphicsPipeline2DWired() +{ + loader.execute(this); +} + +GraphicsPipeline2DWired::~GraphicsPipeline2DWired() +{ + loader.revert(this); +} + +void +GraphicsPipeline2DWired::draw( + std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const size_t next_frame, + const uint32_t image_index) +{ + // Set viewport + { + VkViewport vk_viewport{}; + vk_viewport.x = view->region.x; + vk_viewport.y = view->region.y; + vk_viewport.width = view->region.z; + vk_viewport.height = view->region.w; + vk_viewport.minDepth = 0.0f; + vk_viewport.maxDepth = 1.0f; + vkCmdSetViewport(draw_command_buffer, 0, 1, &vk_viewport); + + VkRect2D vk_scissor{}; + vk_scissor.offset.x = static_cast<int32_t>(view->region.x); + vk_scissor.offset.y = static_cast<int32_t>(view->region.y); + vk_scissor.extent.width = static_cast<uint32_t>(view->region.z); + vk_scissor.extent.height = static_cast<uint32_t>(view->region.w); + vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor); + } + + // Draw rectangles + { + std::array<VkDescriptorSet, 1> vk_descriptor_sets{ + view->descriptor_sets_2d[image_index]}; + VkDeviceSize offsets[]{0}; + + vkCmdBindDescriptorSets( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + cg_core.vk_graphics_pipeline_2d_wired_layout->pipeline, 0, + vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr); + vkCmdBindPipeline( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + this->graphic_pipeline); + vkCmdBindIndexBuffer( + draw_command_buffer, this->index_buffer->buffer, 0, + VK_INDEX_TYPE_UINT32); + + for(auto i{0}; i < view->rectangles_to_draw[current_frame].size(); i++) + { + auto &rect{view->rectangles_to_draw[current_frame][i]}; + + UDOVector4D position{rect.position}; + UDOVector3D color{rect.color}; + vkCmdPushConstants( + draw_command_buffer, + cg_core.vk_graphics_pipeline_2d_wired_layout->pipeline, + VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(UDOVector4D), &position); + vkCmdPushConstants( + draw_command_buffer, + cg_core.vk_graphics_pipeline_2d_wired_layout->pipeline, + VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(UDOVector4D), sizeof(UDOVector3D), + &color); + vkCmdDrawIndexed( + draw_command_buffer, Rectangle::VertexCount, 1, 0, 0, 0); + } + } + + // Prepare for the next frame. + view->rectangles_to_draw[next_frame].clear(); +} + +} diff --git a/src/blucat/graphics_pipeline_2d_wired.hpp b/src/blucat/graphics_pipeline_2d_wired.hpp new file mode 100644 index 0000000..32d965b --- /dev/null +++ b/src/blucat/graphics_pipeline_2d_wired.hpp @@ -0,0 +1,47 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_WIRED_H +#define CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_WIRED_H 1 + +#include <memory> + +#include "core.hpp" +#include "view_2d.hpp" + +namespace BluCat +{ + +struct GraphicsPipeline2DWired +{ + QueueFamily *queue_family; + + VkPipeline graphic_pipeline; + + DestinationBuffer *index_buffer; + + GraphicsPipeline2DWired(); + ~GraphicsPipeline2DWired(); + + void + draw(std::shared_ptr<View2D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const size_t next_frame, + const uint32_t image_index); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_WIRED_H */ diff --git a/src/blucat/graphics_pipeline_2d_wired_layout.cpp b/src/blucat/graphics_pipeline_2d_wired_layout.cpp new file mode 100644 index 0000000..eaa6af7 --- /dev/null +++ b/src/blucat/graphics_pipeline_2d_wired_layout.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline_2d_wired_layout.hpp" + +#include <array> + +#include "../core.hpp" +#include "graphics_pipeline_2d_solid_layout.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DWiredLayout*>(obj); + + std::array<VkDescriptorSetLayout, 1> set_layouts{ + cg_core.vk_descriptor_set_layout->view + }; + + std::array<VkPushConstantRange, 2> push_constants; + push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + push_constants[0].offset = 0; + push_constants[0].size = sizeof(BluCat::UDOVector4D); + + push_constants[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + push_constants[1].offset = sizeof(BluCat::UDOVector4D); + push_constants[1].size = sizeof(BluCat::UDOVector3D); + + VkPipelineLayoutCreateInfo pipeline_layout_info{}; + pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_info.setLayoutCount = set_layouts.size(); + pipeline_layout_info.pSetLayouts = set_layouts.data(); + pipeline_layout_info.pushConstantRangeCount = push_constants.size(); + pipeline_layout_info.pPushConstantRanges = push_constants.data(); + + if(vkCreatePipelineLayout( + cg_core.vk_device_with_swapchain->device, &pipeline_layout_info, + nullptr, &self->pipeline) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan pipeline layout."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline2DWiredLayout*>(obj); + + vkDestroyPipelineLayout( + cg_core.vk_device_with_swapchain->device, self->pipeline, nullptr); +} + +const CommandChain loader{ + {&load_pipeline, &unload_pipeline} +}; + +} + +namespace BluCat +{ + +GraphicsPipeline2DWiredLayout::GraphicsPipeline2DWiredLayout() +{ + loader.execute(this); +} + +GraphicsPipeline2DWiredLayout::~GraphicsPipeline2DWiredLayout() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/graphics_pipeline_2d_wired_layout.hpp b/src/blucat/graphics_pipeline_2d_wired_layout.hpp new file mode 100644 index 0000000..d447230 --- /dev/null +++ b/src/blucat/graphics_pipeline_2d_wired_layout.hpp @@ -0,0 +1,35 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_WIRED_LAYOUT_H +#define CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_WIRED_LAYOUT_H 1 + +#include "core.hpp" + +namespace BluCat +{ + +struct GraphicsPipeline2DWiredLayout +{ + VkPipelineLayout pipeline; + + GraphicsPipeline2DWiredLayout(); + ~GraphicsPipeline2DWiredLayout(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_2D_LAYOUT_H */ diff --git a/src/blucat/graphics_pipeline_3d.cpp b/src/blucat/graphics_pipeline_3d.cpp new file mode 100644 index 0000000..c1b60a6 --- /dev/null +++ b/src/blucat/graphics_pipeline_3d.cpp @@ -0,0 +1,307 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline_3d.hpp" + +#include <array> +#include <stdexcept> + +#include "../core.hpp" +#include "core.hpp" +#include "static_mesh_vertex.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline3D*>(obj); + + VkPipelineShaderStageCreateInfo vert_shader_stage_info = {}; + vert_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vert_shader_stage_info.pNext = nullptr; + vert_shader_stage_info.flags = 0; + vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_shader_stage_info.module = + cg_core.vk_device_with_swapchain->vert3d_shader_module; + vert_shader_stage_info.pName = "main"; + vert_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo frag_shader_stage_info = {}; + frag_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + frag_shader_stage_info.pNext = nullptr; + frag_shader_stage_info.flags = 0; + frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_shader_stage_info.module = + cg_core.vk_device_with_swapchain->frag3d_shader_module; + frag_shader_stage_info.pName = "main"; + frag_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shader_stages[] = { + vert_shader_stage_info, + frag_shader_stage_info + }; + + VkVertexInputBindingDescription vertex_input_binding{}; + vertex_input_binding.binding = 0; + vertex_input_binding.stride = sizeof(BluCat::StaticMeshVertex); + vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + std::array<VkVertexInputAttributeDescription, 3> vertex_attribute{}; + // Position. + vertex_attribute[0].location = 0; + vertex_attribute[0].binding = 0; + vertex_attribute[0].format = VK_FORMAT_R32G32B32_SFLOAT; + vertex_attribute[0].offset = offsetof(BluCat::StaticMeshVertex, position); + // Normal. + vertex_attribute[1].location = 1; + vertex_attribute[1].binding = 0; + vertex_attribute[1].format = VK_FORMAT_R32G32B32_SFLOAT; + vertex_attribute[1].offset = offsetof(BluCat::StaticMeshVertex, normal); + // Texture coordinate. + vertex_attribute[2].location = 2; + vertex_attribute[2].binding = 0; + vertex_attribute[2].format = VK_FORMAT_R32G32_SFLOAT; + vertex_attribute[2].offset = offsetof(BluCat::StaticMeshVertex, texture_coord); + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.pNext = nullptr; + vertex_input_info.flags = 0; + vertex_input_info.vertexBindingDescriptionCount = 1; + vertex_input_info.pVertexBindingDescriptions = &vertex_input_binding; + vertex_input_info.vertexAttributeDescriptionCount = + static_cast<uint32_t>(vertex_attribute.size()); + vertex_input_info.pVertexAttributeDescriptions = vertex_attribute.data(); + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.pNext = nullptr; + input_assembly.flags = 0; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + input_assembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0; + viewport.y = 0; + viewport.width = cg_core.display_width; + viewport.height = cg_core.display_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = {cg_core.display_width, cg_core.display_height}; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.pNext = nullptr; + viewport_state.flags = 0; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.pNext = nullptr; + rasterizer.flags = 0; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + rasterizer.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + VkPipelineDepthStencilStateCreateInfo depth_stencil = {}; + depth_stencil.sType = + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depth_stencil.depthTestEnable = VK_TRUE; + depth_stencil.depthWriteEnable = VK_TRUE; + depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS; + depth_stencil.depthBoundsTestEnable = VK_FALSE; + depth_stencil.minDepthBounds = 0.0f; + depth_stencil.maxDepthBounds = 1.0f; + depth_stencil.stencilTestEnable = VK_FALSE; + depth_stencil.front = {}; + depth_stencil.back = {}; + + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.blendEnable = VK_FALSE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.pNext = nullptr; + color_blending.flags = 0; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + color_blending.blendConstants[0] = 0.0f; + color_blending.blendConstants[1] = 0.0f; + color_blending.blendConstants[2] = 0.0f; + color_blending.blendConstants[3] = 0.0f; + + VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH + }; + + VkPipelineDynamicStateCreateInfo dynamic_state_info = {}; + dynamic_state_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state_info.dynamicStateCount = 2; + dynamic_state_info.pDynamicStates = dynamic_states; + + VkGraphicsPipelineCreateInfo pipeline_info{}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.pNext = nullptr; + pipeline_info.flags = 0; + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pTessellationState = nullptr; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = &depth_stencil; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state_info; + pipeline_info.layout = cg_core.vk_graphics_pipeline_3d_layout->pipeline; + pipeline_info.renderPass = cg_core.vk_render_pass->pipeline_3d; + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_info.basePipelineIndex = -1; + + if(vkCreateGraphicsPipelines( + cg_core.vk_device_with_swapchain->device, VK_NULL_HANDLE, 1, + &pipeline_info, nullptr, &self->graphic_pipeline) + != VK_SUCCESS) + throw CommandError{"Failed to create graphics pipeline."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline3D*>(obj); + + vkDestroyPipeline( + cg_core.vk_device_with_swapchain->device, self->graphic_pipeline, nullptr); +} + +const CommandChain loader{ + {&load_pipeline, &unload_pipeline} +}; + +} + +namespace BluCat +{ + +GraphicsPipeline3D::GraphicsPipeline3D() +{ + loader.execute(this); +} + +GraphicsPipeline3D::~GraphicsPipeline3D() +{ + loader.revert(this); +} + +void +GraphicsPipeline3D::draw( + std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const uint32_t image_index) +{ + vkCmdBindPipeline( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + this->graphic_pipeline); + + // Draw models + for(auto& [static_mesh, instances]: + cg_core.vk_renderer->static_models_to_draw[current_frame]) + { + VkBuffer vertex_buffers[]{static_mesh->vertex_buffer->buffer}; + VkDeviceSize offsets[]{0}; + + vkCmdBindVertexBuffers( + draw_command_buffer, 0, 1, vertex_buffers, offsets); + vkCmdBindIndexBuffer( + draw_command_buffer, static_mesh->index_buffer->buffer, 0, + VK_INDEX_TYPE_UINT32); + + for(auto &instance: instances) + { // Object matrix. + glm::mat4 translation_matrix{1.0f}; + translation_matrix = glm::translate( + translation_matrix, *instance->position); + glm::mat4 rotation_matrix{glm::toMat4(*instance->orientation)}; + + std::array<VkDescriptorSet, 4> vk_descriptor_sets{ + cg_core.vk_light->descriptor_sets_world[image_index], + view->descriptor_sets_3d[image_index], + instance->descriptor_sets[image_index], + instance->texture->descriptor_sets[image_index]}; + + vkCmdBindDescriptorSets( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + cg_core.vk_graphics_pipeline_3d_layout->pipeline, 0, + vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr); + + vkCmdDrawIndexed( + draw_command_buffer, static_mesh->index_count, 1, 0, 0, 0); + + BluCat::UDOStaticModel udo_static_model{}; + udo_static_model.base_matrix = translation_matrix * rotation_matrix; + instance->uniform_buffers[image_index].copy_data(&udo_static_model); + } + } +} + +} diff --git a/src/blucat/graphics_pipeline_3d.hpp b/src/blucat/graphics_pipeline_3d.hpp new file mode 100644 index 0000000..de0c422 --- /dev/null +++ b/src/blucat/graphics_pipeline_3d.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_3D_H +#define CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_3D_H 1 + +#include <memory> + +#include "core.hpp" +#include "command_pool.hpp" +#include "view_3d.hpp" + +namespace BluCat +{ + +struct GraphicsPipeline3D +{ + VkPipeline graphic_pipeline; + + GraphicsPipeline3D(); + ~GraphicsPipeline3D(); + + void + draw(std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const uint32_t image_index); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_3D_H */ diff --git a/src/blucat/graphics_pipeline_3d_layout.cpp b/src/blucat/graphics_pipeline_3d_layout.cpp new file mode 100644 index 0000000..b2a54d6 --- /dev/null +++ b/src/blucat/graphics_pipeline_3d_layout.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline_3d_layout.hpp" + +#include <array> + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline3DLayout*>(obj); + + std::array<VkDescriptorSetLayout, 4> set_layouts{ + cg_core.vk_descriptor_set_layout->world, + cg_core.vk_descriptor_set_layout->view, + cg_core.vk_descriptor_set_layout->model, + cg_core.vk_descriptor_set_layout->texture}; + + VkPipelineLayoutCreateInfo pipeline_layout_info{}; + pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_info.setLayoutCount = set_layouts.size(); + pipeline_layout_info.pSetLayouts = set_layouts.data(); + pipeline_layout_info.pushConstantRangeCount = 0; + pipeline_layout_info.pPushConstantRanges = nullptr; + + if(vkCreatePipelineLayout( + cg_core.vk_device_with_swapchain->device, + &pipeline_layout_info, nullptr, &self->pipeline) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan pipeline layout."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline3DLayout*>(obj); + + vkDestroyPipelineLayout( + cg_core.vk_device_with_swapchain->device, self->pipeline, nullptr); +} + +const CommandChain loader{ + {&load_pipeline, &unload_pipeline} +}; + +} + +namespace BluCat +{ + +GraphicsPipeline3DLayout::GraphicsPipeline3DLayout() +{ + loader.execute(this); +} + +GraphicsPipeline3DLayout::~GraphicsPipeline3DLayout() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/graphics_pipeline_3d_layout.hpp b/src/blucat/graphics_pipeline_3d_layout.hpp new file mode 100644 index 0000000..2b44dac --- /dev/null +++ b/src/blucat/graphics_pipeline_3d_layout.hpp @@ -0,0 +1,35 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_3D_LAYOUT_H +#define CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_3D_LAYOUT_H 1 + +#include "core.hpp" + +namespace BluCat +{ + +struct GraphicsPipeline3DLayout +{ + VkPipelineLayout pipeline; + + GraphicsPipeline3DLayout(); + ~GraphicsPipeline3DLayout(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_3D_LAYOUT_H */ diff --git a/src/blucat/graphics_pipeline_3d_skeletal.cpp b/src/blucat/graphics_pipeline_3d_skeletal.cpp new file mode 100644 index 0000000..b039c00 --- /dev/null +++ b/src/blucat/graphics_pipeline_3d_skeletal.cpp @@ -0,0 +1,322 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline_3d.hpp" + +#include <array> +#include <stdexcept> + +#include "../core.hpp" +#include "core.hpp" +#include "skeletal_mesh_vertex.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline3DSkeletal*>(obj); + + VkPipelineShaderStageCreateInfo vert_shader_stage_info = {}; + vert_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vert_shader_stage_info.pNext = nullptr; + vert_shader_stage_info.flags = 0; + vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_shader_stage_info.module = + cg_core.vk_device_with_swapchain->vert3d_skeletal_shader_module; + vert_shader_stage_info.pName = "main"; + vert_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo frag_shader_stage_info = {}; + frag_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + frag_shader_stage_info.pNext = nullptr; + frag_shader_stage_info.flags = 0; + frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_shader_stage_info.module = + cg_core.vk_device_with_swapchain->frag3d_shader_module; + frag_shader_stage_info.pName = "main"; + frag_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shader_stages[] = { + vert_shader_stage_info, + frag_shader_stage_info + }; + + VkVertexInputBindingDescription vertex_input_binding{}; + vertex_input_binding.binding = 0; + vertex_input_binding.stride = sizeof(BluCat::SkeletalMeshVertex); + vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + std::array<VkVertexInputAttributeDescription, 5> vertex_attribute{}; + // Position. + vertex_attribute[0].location = 0; + vertex_attribute[0].binding = 0; + vertex_attribute[0].format = VK_FORMAT_R32G32B32_SFLOAT; + vertex_attribute[0].offset = offsetof(BluCat::SkeletalMeshVertex, position); + // Normal. + vertex_attribute[1].location = 1; + vertex_attribute[1].binding = 0; + vertex_attribute[1].format = VK_FORMAT_R32G32B32_SFLOAT; + vertex_attribute[1].offset = offsetof(BluCat::SkeletalMeshVertex, normal); + // Texture coordinate. + vertex_attribute[2].location = 2; + vertex_attribute[2].binding = 0; + vertex_attribute[2].format = VK_FORMAT_R32G32_SFLOAT; + vertex_attribute[2].offset = offsetof(BluCat::SkeletalMeshVertex, texture_coord); + // Bones ids. + vertex_attribute[3].location = 3; + vertex_attribute[3].binding = 0; + vertex_attribute[3].format = VK_FORMAT_R32G32B32A32_SINT; + vertex_attribute[3].offset = offsetof(BluCat::SkeletalMeshVertex, bone_ids); + // Bones weights. + vertex_attribute[4].location = 4; + vertex_attribute[4].binding = 0; + vertex_attribute[4].format = VK_FORMAT_R32G32B32A32_SFLOAT; + vertex_attribute[4].offset = offsetof(BluCat::SkeletalMeshVertex, bone_weights); + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.pNext = nullptr; + vertex_input_info.flags = 0; + vertex_input_info.vertexBindingDescriptionCount = 1; + vertex_input_info.pVertexBindingDescriptions = &vertex_input_binding; + vertex_input_info.vertexAttributeDescriptionCount = + static_cast<uint32_t>(vertex_attribute.size()); + vertex_input_info.pVertexAttributeDescriptions = vertex_attribute.data(); + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.pNext = nullptr; + input_assembly.flags = 0; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + input_assembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0; + viewport.y = 0; + viewport.width = cg_core.display_width; + viewport.height = cg_core.display_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = {cg_core.display_width, cg_core.display_height}; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.pNext = nullptr; + viewport_state.flags = 0; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.pNext = nullptr; + rasterizer.flags = 0; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + rasterizer.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + VkPipelineDepthStencilStateCreateInfo depth_stencil = {}; + depth_stencil.sType = + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depth_stencil.depthTestEnable = VK_TRUE; + depth_stencil.depthWriteEnable = VK_TRUE; + depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS; + depth_stencil.depthBoundsTestEnable = VK_FALSE; + depth_stencil.minDepthBounds = 0.0f; + depth_stencil.maxDepthBounds = 1.0f; + depth_stencil.stencilTestEnable = VK_FALSE; + depth_stencil.front = {}; + depth_stencil.back = {}; + + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.blendEnable = VK_FALSE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.pNext = nullptr; + color_blending.flags = 0; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + color_blending.blendConstants[0] = 0.0f; + color_blending.blendConstants[1] = 0.0f; + color_blending.blendConstants[2] = 0.0f; + color_blending.blendConstants[3] = 0.0f; + + VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH + }; + + VkPipelineDynamicStateCreateInfo dynamic_state_info = {}; + dynamic_state_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state_info.dynamicStateCount = 2; + dynamic_state_info.pDynamicStates = dynamic_states; + + VkGraphicsPipelineCreateInfo pipeline_info{}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.pNext = nullptr; + pipeline_info.flags = 0; + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pTessellationState = nullptr; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = &depth_stencil; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state_info; + pipeline_info.layout = cg_core.vk_graphics_pipeline_3d_layout->pipeline; + pipeline_info.renderPass = cg_core.vk_render_pass->pipeline_3d; + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_info.basePipelineIndex = -1; + + if(vkCreateGraphicsPipelines( + cg_core.vk_device_with_swapchain->device, VK_NULL_HANDLE, 1, + &pipeline_info, nullptr, &self->graphic_pipeline) + != VK_SUCCESS) + throw CommandError{"Failed to create graphics pipeline."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipeline3DSkeletal*>(obj); + + vkDestroyPipeline( + cg_core.vk_device_with_swapchain->device, self->graphic_pipeline, nullptr); +} + +const CommandChain loader{ + {&load_pipeline, &unload_pipeline} +}; + +} + +namespace BluCat +{ + +GraphicsPipeline3DSkeletal::GraphicsPipeline3DSkeletal() +{ + loader.execute(this); +} + +GraphicsPipeline3DSkeletal::~GraphicsPipeline3DSkeletal() +{ + loader.revert(this); +} + +void +GraphicsPipeline3DSkeletal::draw( + std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const uint32_t image_index) +{ + vkCmdBindPipeline( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + this->graphic_pipeline); + + // Draw models + for(auto& [skeletal_mesh, instances]: + cg_core.vk_renderer->skeletal_models_to_draw[current_frame]) + { + VkBuffer vertex_buffers[]{skeletal_mesh->vertex_buffer->buffer}; + VkDeviceSize offsets[]{0}; + + vkCmdBindVertexBuffers( + draw_command_buffer, 0, 1, vertex_buffers, offsets); + vkCmdBindIndexBuffer( + draw_command_buffer, skeletal_mesh->index_buffer->buffer, 0, + VK_INDEX_TYPE_UINT32); + + for(auto &instance: instances) + { // Object matrix. + glm::mat4 translation_matrix{1.0f}; + translation_matrix = glm::translate( + translation_matrix, *instance->position); + glm::mat4 rotation_matrix{glm::toMat4(*instance->orientation)}; + + std::array<VkDescriptorSet, 4> vk_descriptor_sets{ + cg_core.vk_light->descriptor_sets_world[image_index], + view->descriptor_sets_3d[image_index], + instance->descriptor_sets[image_index], + instance->texture->descriptor_sets[image_index]}; + + vkCmdBindDescriptorSets( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + cg_core.vk_graphics_pipeline_3d_layout->pipeline, 0, + vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr); + + vkCmdDrawIndexed( + draw_command_buffer, skeletal_mesh->index_count, 1, 0, 0, 0); + + BluCat::UDOSkeletalModel udo_skeletal_model{}; + instance->tick(cg_core.delta_time); + udo_skeletal_model.base_matrix = translation_matrix * rotation_matrix; + std::copy(instance->bone_transforms.begin(), + instance->bone_transforms.end(), + udo_skeletal_model.bone_matrices); + instance->uniform_buffers[image_index].copy_data(&udo_skeletal_model); + } + } + +} + +} diff --git a/src/blucat/graphics_pipeline_3d_skeletal.hpp b/src/blucat/graphics_pipeline_3d_skeletal.hpp new file mode 100644 index 0000000..430d2ec --- /dev/null +++ b/src/blucat/graphics_pipeline_3d_skeletal.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_3D_SKELETAL_H +#define CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_3D_SKELETAL_H 1 + +#include <memory> + +#include "core.hpp" +#include "command_pool.hpp" +#include "view_3d.hpp" + +namespace BluCat +{ + +struct GraphicsPipeline3DSkeletal +{ + VkPipeline graphic_pipeline; + + GraphicsPipeline3DSkeletal(); + ~GraphicsPipeline3DSkeletal(); + + void + draw(std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const uint32_t image_index); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_SKELETAL_3D_H */ diff --git a/src/blucat/graphics_pipeline_sprite_3d.cpp b/src/blucat/graphics_pipeline_sprite_3d.cpp new file mode 100644 index 0000000..a71e8bc --- /dev/null +++ b/src/blucat/graphics_pipeline_sprite_3d.cpp @@ -0,0 +1,315 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphics_pipeline_sprite_3d.hpp" + +#include <array> + +#include "../core.hpp" +#include "core.hpp" +#include "sprite.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +struct Sprite3DOrder +{ + std::shared_ptr<BluCat::Sprite3D> sprite_3d; + float distance; +}; + +bool +operator<(const Sprite3DOrder &a, const Sprite3DOrder &b) +{ + return a.distance < b.distance; +} + +bool +operator>(const Sprite3DOrder &a, const Sprite3DOrder &b) +{ + return a.distance > b.distance; +} + +void +load_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipelineSprite3D*>(obj); + + VkPipelineShaderStageCreateInfo vert_shader_stage_info{}; + vert_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vert_shader_stage_info.pNext = nullptr; + vert_shader_stage_info.flags = 0; + vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_shader_stage_info.module = + cg_core.vk_device_with_swapchain->vert_sprite_3d_shader_module; + vert_shader_stage_info.pName = "main"; + vert_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo frag_shader_stage_info{}; + frag_shader_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + frag_shader_stage_info.pNext = nullptr; + frag_shader_stage_info.flags = 0; + frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_shader_stage_info.module = + cg_core.vk_device_with_swapchain->frag_sprite_3d_shader_module; + frag_shader_stage_info.pName = "main"; + frag_shader_stage_info.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shader_stages[] = { + vert_shader_stage_info, + frag_shader_stage_info + }; + + VkVertexInputBindingDescription vertex_input_binding{}; + vertex_input_binding.binding = 0; + vertex_input_binding.stride = sizeof(glm::vec2); + vertex_input_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + std::array<VkVertexInputAttributeDescription, 1> vertex_attribute{}; + // Texture coordinate. + vertex_attribute[0].location = 0; + vertex_attribute[0].binding = 0; + vertex_attribute[0].format = VK_FORMAT_R32G32_SFLOAT; + vertex_attribute[0].offset = 0; + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.pNext = nullptr; + vertex_input_info.flags = 0; + vertex_input_info.vertexBindingDescriptionCount = 1; + vertex_input_info.pVertexBindingDescriptions = &vertex_input_binding; + vertex_input_info.vertexAttributeDescriptionCount = + static_cast<uint32_t>(vertex_attribute.size()); + vertex_input_info.pVertexAttributeDescriptions = vertex_attribute.data(); + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.pNext = nullptr; + input_assembly.flags = 0; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + input_assembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0; + viewport.y = 0; + viewport.width = cg_core.display_width; + viewport.height = cg_core.display_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = {cg_core.display_width, cg_core.display_height}; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.pNext = nullptr; + viewport_state.flags = 0; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.pNext = nullptr; + rasterizer.flags = 0; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + rasterizer.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + VkPipelineDepthStencilStateCreateInfo depth_stencil = {}; + depth_stencil.sType = + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depth_stencil.depthTestEnable = VK_TRUE; + depth_stencil.depthWriteEnable = VK_TRUE; + depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS; + depth_stencil.depthBoundsTestEnable = VK_FALSE; + depth_stencil.minDepthBounds = 0.0f; + depth_stencil.maxDepthBounds = 1.0f; + depth_stencil.stencilTestEnable = VK_FALSE; + depth_stencil.front = {}; + depth_stencil.back = {}; + + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.blendEnable = VK_TRUE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + color_blend_attachment.dstColorBlendFactor = + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.pNext = nullptr; + color_blending.flags = 0; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + color_blending.blendConstants[0] = 0.0f; + color_blending.blendConstants[1] = 0.0f; + color_blending.blendConstants[2] = 0.0f; + color_blending.blendConstants[3] = 0.0f; + + std::array<VkDynamicState, 3> dynamic_states{ + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH, + VK_DYNAMIC_STATE_BLEND_CONSTANTS + }; + + VkPipelineDynamicStateCreateInfo dynamic_state_info = {}; + dynamic_state_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state_info.dynamicStateCount = dynamic_states.size(); + dynamic_state_info.pDynamicStates = dynamic_states.data(); + + VkGraphicsPipelineCreateInfo pipeline_info{}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.pNext = nullptr; + pipeline_info.flags = 0; + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pTessellationState = nullptr; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = &depth_stencil; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state_info; + pipeline_info.layout = cg_core.vk_graphics_pipeline_3d_layout->pipeline; + pipeline_info.renderPass = cg_core.vk_render_pass->pipeline_3d; + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_info.basePipelineIndex = -1; + + if(vkCreateGraphicsPipelines( + cg_core.vk_device_with_swapchain->device, VK_NULL_HANDLE, 1, + &pipeline_info, nullptr, &self->graphic_pipeline) + != VK_SUCCESS) + throw CommandError{"Failed to create graphics pipeline sprite 3d."}; +} + +void +unload_pipeline(void *obj) +{ + auto self = static_cast<BluCat::GraphicsPipelineSprite3D*>(obj); + + vkDestroyPipeline( + cg_core.vk_device_with_swapchain->device, self->graphic_pipeline, nullptr); +} + +const CommandChain loader{ + {&load_pipeline, &unload_pipeline} +}; + +} + +namespace BluCat +{ + +GraphicsPipelineSprite3D::GraphicsPipelineSprite3D() +{ + loader.execute(this); +} + +GraphicsPipelineSprite3D::~GraphicsPipelineSprite3D() +{ + loader.revert(this); +} + +void +GraphicsPipelineSprite3D::draw( + std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const uint32_t image_index) +{ + vkCmdBindPipeline( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + this->graphic_pipeline); + + std::vector<Sprite3DOrder> sprite_3d_order; + { // Sort sprites 3D + sprite_3d_order.reserve( + cg_core.vk_renderer->sprites_3d_to_draw[current_frame].size()); + + for(std::shared_ptr<BluCat::Sprite3D> sprite: + cg_core.vk_renderer->sprites_3d_to_draw[current_frame]) + sprite_3d_order.emplace_back( + sprite, glm::distance(*view->camera_position, *sprite->position)); + + std::sort(sprite_3d_order.rbegin(), sprite_3d_order.rend()); + } + + // Draw sprites + for(auto& sprite: sprite_3d_order) + { + std::array<VkDescriptorSet, 4> vk_descriptor_sets{ + cg_core.vk_light->descriptor_sets_world[image_index], + view->descriptor_sets_3d[image_index], + sprite.sprite_3d->descriptor_sets[image_index], + sprite.sprite_3d->sprite->texture->descriptor_sets[image_index]}; + VkDeviceSize offsets[]{0}; + + vkCmdBindVertexBuffers( + draw_command_buffer, 0, 1, + &sprite.sprite_3d->sprite->vertex_buffer->buffer, offsets); + vkCmdBindDescriptorSets( + draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + cg_core.vk_graphics_pipeline_3d_layout->pipeline, 0, + vk_descriptor_sets.size(), vk_descriptor_sets.data(), 0, nullptr); + + UDOSprite3D ubo_sprite_3d{}; + ubo_sprite_3d.position = *sprite.sprite_3d->position; + ubo_sprite_3d.size = sprite.sprite_3d->size; + sprite.sprite_3d->uniform_buffers[image_index].copy_data(&ubo_sprite_3d); + + vkCmdDraw(draw_command_buffer, Sprite::vertex_count, 1, 0, 0); + } +} + +} diff --git a/src/blucat/graphics_pipeline_sprite_3d.hpp b/src/blucat/graphics_pipeline_sprite_3d.hpp new file mode 100644 index 0000000..969f905 --- /dev/null +++ b/src/blucat/graphics_pipeline_sprite_3d.hpp @@ -0,0 +1,44 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_SPRITE_3D_H +#define CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_SPRITE_3D_H 1 + +#include <memory> + +#include "core.hpp" +#include "command_pool.hpp" +#include "sprite_3d.hpp" +#include "view_3d.hpp" + +namespace BluCat +{ + +struct GraphicsPipelineSprite3D +{ + VkPipeline graphic_pipeline; + + GraphicsPipelineSprite3D(); + ~GraphicsPipelineSprite3D(); + + void + draw(std::shared_ptr<View3D> view, const VkCommandBuffer draw_command_buffer, + const size_t current_frame, const uint32_t image_index); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_GRAPHICS_PIPELINE_SPRITE_3D_H */ diff --git a/src/blucat/image.cpp b/src/blucat/image.cpp new file mode 100644 index 0000000..7f8633f --- /dev/null +++ b/src/blucat/image.cpp @@ -0,0 +1,149 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "image.hpp" + +#include "../core.hpp" + +namespace BluCat::Image +{ + +Error::Error(const std::string &m): + error(m) +{ +} + +Error::Error(const char &m): + Error{std::string{m}} +{ +} + +const char* +Error::what() const noexcept +{ + return this->error.c_str(); +} + +void +create( + BluCat::Device *device, + VkImage *image, + VkDeviceMemory *image_memory, + VkFormat format, + const VkExtent3D &extent3d, + uint32_t mip_levels, + VkImageTiling image_tiling, + VkImageUsageFlags usage) +{ + VkImageCreateInfo image_info{}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.pNext = nullptr; + image_info.flags = 0; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = format; + image_info.extent = extent3d; + image_info.mipLevels = mip_levels; + image_info.arrayLayers = 1; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = image_tiling; + image_info.usage = usage; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_info.queueFamilyIndexCount = 0; + image_info.pQueueFamilyIndices = nullptr; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + if(vkCreateImage( + device->device, &image_info, nullptr, image) != VK_SUCCESS) + throw Error{"Failed to create Vulkan image."}; + + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(device->device, *image, &memory_requirements); + + VkMemoryAllocateInfo memory_alloc_info{}; + memory_alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memory_alloc_info.allocationSize = memory_requirements.size; + device->select_memory_type(&memory_alloc_info.memoryTypeIndex, + &memory_requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + if(vkAllocateMemory( + device->device, &memory_alloc_info, nullptr, image_memory) + != VK_SUCCESS) + { + vkDestroyImage(device->device, *image, nullptr); + throw Error{"Failed to allocate memory for Vulkan image."}; + } + + vkBindImageMemory(device->device, *image, *image_memory, 0); +} + +void move_image_state( + VkCommandBuffer vk_command_buffer, VkImage vk_image, VkFormat format, + VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, + VkImageLayout old_layout, VkImageLayout new_layout, + VkPipelineStageFlags source_stage, VkPipelineStageFlags destination_stage) +{ + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.pNext = nullptr; + barrier.srcAccessMask = src_access_mask; + barrier.dstAccessMask = dst_access_mask; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = vk_image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier( + vk_command_buffer, source_stage, destination_stage, 0, 0, nullptr, + 0, nullptr, 1, &barrier); +} + +void create_view( + BluCat::Device *device, + VkImageView *image_view, + const VkImage &image, + VkFormat format, + VkImageAspectFlags image_aspect_flags) +{ + VkImageViewCreateInfo image_view_info{}; + image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + image_view_info.pNext = nullptr; + image_view_info.flags = 0; + image_view_info.image = image; + image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + image_view_info.format = format; + image_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_info.subresourceRange.aspectMask = image_aspect_flags; + image_view_info.subresourceRange.baseMipLevel = 0; + image_view_info.subresourceRange.levelCount = 1; + image_view_info.subresourceRange.baseArrayLayer = 0; + image_view_info.subresourceRange.layerCount = 1; + + if(vkCreateImageView(device->device, &image_view_info, + nullptr, image_view) != VK_SUCCESS) + throw Error{"Failed to create texture view."}; + +} + +} diff --git a/src/blucat/image.hpp b/src/blucat/image.hpp new file mode 100644 index 0000000..cb6cfd4 --- /dev/null +++ b/src/blucat/image.hpp @@ -0,0 +1,73 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BLUE_KITTY_BLUCAT_IMAGE_H +#define BLUE_KITTY_BLUCAT_IMAGE_H 1 + +#include <memory> +#include <string> + +#include "core.hpp" +#include "device.hpp" + +namespace BluCat::Image +{ + +struct Error: public std::exception +{ + Error(const std::string &m); + Error(const char &m); + + const char* + what() const noexcept; + +private: + std::string error; +}; + +void +create( + BluCat::Device *device, + VkImage *image, + VkDeviceMemory *image_memory, + VkFormat format, + const VkExtent3D &extent3d, + uint32_t mip_levels, + VkImageTiling image_tiling, + VkImageUsageFlags usage); + +void +move_image_state( + VkCommandBuffer vk_command_buffer, + VkImage vk_image, + VkFormat format, + VkAccessFlags src_access_mask, + VkAccessFlags dst_access_mask, + VkImageLayout old_layout, + VkImageLayout new_layout, + VkPipelineStageFlags source_stage, + VkPipelineStageFlags destination_stage); + +void +create_view( + BluCat::Device *device, + VkImageView *image_view, + const VkImage &image, + VkFormat format, + VkImageAspectFlags image_aspect_flags); +} + +#endif /* CANDY_GEAR_BLUCAT_IMAGE_H */ diff --git a/src/blucat/light.cpp b/src/blucat/light.cpp new file mode 100644 index 0000000..5efc6ec --- /dev/null +++ b/src/blucat/light.cpp @@ -0,0 +1,205 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "light.hpp" + +#include <array> + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_world_vert_uniform_buffer(void *obj) +{ + auto self = static_cast<BluCat::Light*>(obj); + + try + { + self->ub_world_vert.reserve(cg_core.vk_swapchain->images_count); + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->ub_world_vert.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(BluCat::UDOWorld3D_Vert)); + } + catch(const std::exception& e) + { + throw CommandError{e.what()}; + } +} + +void +unload_world_vert_uniform_buffer(void *obj) +{ + auto self = static_cast<BluCat::Light*>(obj); + + self->ub_world_vert.clear(); +} + +void +load_world_frag_uniform_buffer(void *obj) +{ + auto self = static_cast<BluCat::Light*>(obj); + + try + { + self->ub_world_frag.reserve(cg_core.vk_swapchain->images_count); + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->ub_world_frag.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(BluCat::UDOWorld3D_Frag)); + } + catch(const std::exception& e) + { + throw CommandError{e.what()}; + } +} + +void +unload_world_frag_uniform_buffer(void *obj) +{ + auto self = static_cast<BluCat::Light*>(obj); + + self->ub_world_frag.clear(); +} + +void +load_descriptor_pool(void *obj) +{ + auto self = static_cast<BluCat::Light*>(obj); + + uint32_t uniform_buffers_count = + self->ub_world_vert.size() + self->ub_world_vert.size(); + + VkDescriptorPoolSize descriptor_pool_size{}; + descriptor_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_pool_size.descriptorCount = uniform_buffers_count; + + VkDescriptorPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = uniform_buffers_count; + pool_info.poolSizeCount = 1; + pool_info.pPoolSizes = &descriptor_pool_size; + + if(vkCreateDescriptorPool( + cg_core.vk_device_with_swapchain->device, &pool_info, nullptr, + &self->descriptor_pool) != VK_SUCCESS) + throw CommandError{"Failed to create a Vulkan descriptor pool."}; +} + +void +unload_descriptor_pool(void *obj) +{ + auto self = static_cast<BluCat::Light*>(obj); + + vkDestroyDescriptorPool( + cg_core.vk_device_with_swapchain->device, self->descriptor_pool, + nullptr); +} + +void +load_descriptor_sets_world(void *obj) +{ + auto self = static_cast<BluCat::Light*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + cg_core.vk_descriptor_set_layout->world); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->descriptor_pool; + alloc_info.descriptorSetCount = layouts.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->descriptor_sets_world.resize(layouts.size()); + if(vkAllocateDescriptorSets( + cg_core.vk_device_with_swapchain->device, &alloc_info, + self->descriptor_sets_world.data()) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan world descriptor set."}; +} + +void +load_resources_to_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::Light*>(obj); + + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + { + VkDescriptorBufferInfo world_vert_info{}; + world_vert_info.buffer = self->ub_world_vert[i].buffer; + world_vert_info.offset = 0; + world_vert_info.range = sizeof(BluCat::UDOWorld3D_Vert); + + VkDescriptorBufferInfo world_frag_info{}; + world_frag_info.buffer = self->ub_world_frag[i].buffer; + world_frag_info.offset = 0; + world_frag_info.range = sizeof(BluCat::UDOWorld3D_Frag); + + std::array<VkWriteDescriptorSet, 2> write_descriptors{}; + write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[0].dstSet = self->descriptor_sets_world[i]; + write_descriptors[0].dstBinding = 0; + write_descriptors[0].dstArrayElement = 0; + write_descriptors[0].descriptorCount = 1; + write_descriptors[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptors[0].pBufferInfo = &world_vert_info; + write_descriptors[0].pImageInfo = nullptr; + write_descriptors[0].pTexelBufferView = nullptr; + + write_descriptors[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[1].dstSet = self->descriptor_sets_world[i]; + write_descriptors[1].dstBinding = 1; + write_descriptors[1].dstArrayElement = 0; + write_descriptors[1].descriptorCount = 1; + write_descriptors[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptors[1].pBufferInfo = &world_frag_info; + write_descriptors[1].pImageInfo = nullptr; + write_descriptors[1].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, write_descriptors.size(), + write_descriptors.data(), 0, nullptr); + } +} + +const CommandChain loader{ + {&load_world_vert_uniform_buffer, &unload_world_vert_uniform_buffer}, + {&load_world_frag_uniform_buffer, &unload_world_frag_uniform_buffer}, + {&load_descriptor_pool, &unload_descriptor_pool}, + // By destroying the pool the sets are also destroyed. + {&load_descriptor_sets_world, nullptr}, + {&load_resources_to_descriptor_sets, nullptr} +}; + +} + +namespace BluCat +{ + +Light::Light() +{ + loader.execute(this); +} + +Light::~Light() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/light.hpp b/src/blucat/light.hpp new file mode 100644 index 0000000..7e1d3a1 --- /dev/null +++ b/src/blucat/light.hpp @@ -0,0 +1,41 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_LIGHT_H +#define CANDY_GEAR_BLUCAT_LIGHT_H 1 + +#include "core.hpp" +#include "uniform_buffer.hpp" + +namespace BluCat +{ + +struct Light +{ + // FIXME: if this vector get resized, it will cause a segmentation fault! + std::vector<UniformBuffer> ub_world_vert; + std::vector<UniformBuffer> ub_world_frag; + + VkDescriptorPool descriptor_pool; + std::vector<VkDescriptorSet> descriptor_sets_world; + + Light(); + ~Light(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_LIGHT_H */ diff --git a/src/blucat/qoi.cpp b/src/blucat/qoi.cpp new file mode 100644 index 0000000..663e4e9 --- /dev/null +++ b/src/blucat/qoi.cpp @@ -0,0 +1,205 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "qoi.hpp" + +#include <array> +#include <fstream> + +#include "../binary_reader.hpp" + +namespace +{ + +const char MAGIC[]{"qoif"}; +const int HEADER_SIZE{14}; +const uint8_t PADDING[8]{0,0,0,0,0,0,0,1}; +const unsigned int PIXELS_MAX{400000000}; + +const int OP_INDEX{0b00000000}; +const int OP_DIFF {0b01000000}; +const int OP_LUMA {0b10000000}; +const int OP_RUN {0b11000000}; +const int OP_RGB {0b11111110}; +const int OP_RGBA {0b11111111}; + +const int MASK_2_BITS{0b11000000}; + +struct RGBA +{ + uint8_t red, green, blue, alpha; + + RGBA(); + RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha); +}; + +RGBA::RGBA(): + red{0}, + green{0}, + blue{0}, + alpha{0} +{ +} + +RGBA::RGBA(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha): + red{red}, + green{green}, + blue{blue}, + alpha{alpha} +{ +} + +struct Pixel +{ + union + { + RGBA colors; + uint32_t value; + }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha); +}; + +Pixel::Pixel(): + colors{} +{ +} + +Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha): + colors(red, green, blue, alpha) +{ +} + +inline int +color_hash(const RGBA &colors) +{ + return colors.red*3 + colors.green*5 + colors.blue*7 + colors.alpha*11; +} + +} + +namespace BluCat::QOI +{ + +Image::Image(const char *file_path, uint8_t channels): + channels{channels} +{ + if(this->channels != 0 && this->channels != 3 && this->channels != 4) + { + throw std::invalid_argument{"invalid number of channels"}; + } + + BinaryReader input{file_path}; + if(input.size() < HEADER_SIZE + (int)sizeof(PADDING)) + { + throw std::runtime_error{"invalid QOI file"}; + } + + input.read_chars(this->header.magic, 4); + this->header.width = input.read_ui32(); + this->header.height = input.read_ui32(); + this->header.channels = input.read_ui8(); + this->header.colorspace = input.read_ui8(); + + if(this->header.width == 0 || this->header.height == 0 || + this->header.channels < 3 || this->header.channels > 4 || + this->header.colorspace > 1 || + this->header.height >= PIXELS_MAX / this->header.width) + { + throw std::runtime_error{"QOI file have an invalid header"}; + } + + if(this->channels == 0) this->channels = this->header.channels; + + uint32_t num_pixels{this->header.width * this->header.height}; + this->pixels_len = num_pixels * this->channels; + this->pixels = new uint8_t[this->pixels_len]; + + std::array<Pixel, 64> index; + Pixel pixel(0, 0, 0, 255); + int chunks_len = input.size() - (int)sizeof(PADDING); + + /* + This algorithm is based on the original implementation that is in GitHub: + https://github.com/phoboslab/qoi + + For information about the QOI image format, see: https://qoiformat.org/ + */ + int pixel_p{0}; + int run{0}; + for(uint32_t decoded_pixel{0}; decoded_pixel < num_pixels; decoded_pixel++) + { + if(run > 0) + { + run--; + } + else if(input.pointer() < chunks_len) + { + int b1 = input.read_ui8(); + + if (b1 == OP_RGB) + { + pixel.colors.red = input.read_ui8(); + pixel.colors.green = input.read_ui8(); + pixel.colors.blue = input.read_ui8(); + } + else if (b1 == OP_RGBA) + { + pixel.colors.red = input.read_ui8(); + pixel.colors.green = input.read_ui8(); + pixel.colors.blue = input.read_ui8(); + pixel.colors.alpha = input.read_ui8(); + } + else if ((b1 & MASK_2_BITS) == OP_INDEX) + { + pixel = index[b1]; + } + else if ((b1 & MASK_2_BITS) == OP_DIFF) + { + pixel.colors.red += ((b1 >> 4) & 0x03) - 2; + pixel.colors.green += ((b1 >> 2) & 0x03) - 2; + pixel.colors.blue += (b1 & 0x03) - 2; + } + else if ((b1 & MASK_2_BITS) == OP_LUMA) + { + int b2 = input.read_ui8(); + int vg = (b1 & 0x3f) - 32; + pixel.colors.red += vg - 8 + ((b2 >> 4) & 0x0f); + pixel.colors.green += vg; + pixel.colors.blue += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & MASK_2_BITS) == OP_RUN) + { + run = (b1 & 0x3f); + } + + index[color_hash(pixel.colors) % 64] = pixel; + } + + this->pixels[pixel_p++] = pixel.colors.red; + this->pixels[pixel_p++] = pixel.colors.green; + this->pixels[pixel_p++] = pixel.colors.blue; + if(this->channels == 4) this->pixels[pixel_p++] = pixel.colors.alpha; + } +} + +Image::~Image() +{ + delete[] this->pixels; +} + +} diff --git a/src/blucat/qoi.hpp b/src/blucat/qoi.hpp new file mode 100644 index 0000000..cd33205 --- /dev/null +++ b/src/blucat/qoi.hpp @@ -0,0 +1,41 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdint> + +namespace BluCat::QOI +{ + struct Header + { + char magic[4]; + uint32_t width; + uint32_t height; + uint8_t channels; + uint8_t colorspace; + }; + + struct Image + { + Header header; + uint32_t pixels_len; + uint8_t channels; + uint8_t *pixels; + + Image(const char *file_path, uint8_t channels); + ~Image(); + }; + +} diff --git a/src/blucat/queue.cpp b/src/blucat/queue.cpp new file mode 100644 index 0000000..c00d874 --- /dev/null +++ b/src/blucat/queue.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "queue.hpp" + +#include "queue_family.hpp" + +namespace BluCat +{ + +Queue::Queue( + BluCat::QueueFamily *queue_family, VkQueue queue, int queue_index): + queue_family{queue_family}, + queue{queue}, + queue_index{queue_index} +{ + this->queue_family->queue_states[this->queue_index].busy = true; +} + +Queue::Queue(Queue &&that): + queue{that.queue}, + queue_family{that.queue_family}, + queue_index{that.queue_index} +{ + that.queue_family = nullptr; +} + +Queue& Queue::operator=(Queue &&that) +{ + this->queue = that.queue; + this->queue_family = that.queue_family; + this->queue_index = that.queue_index; + + that.queue_family = nullptr; + + return *this; +} + +Queue::~Queue() +{ + if(this->queue_family) + { + std::unique_lock<std::mutex> lock{this->queue_family->queue_mutex}; + this->queue_family->queue_states[this->queue_index].busy = false; + } +} + +} diff --git a/src/blucat/queue.hpp b/src/blucat/queue.hpp new file mode 100644 index 0000000..a71dc9d --- /dev/null +++ b/src/blucat/queue.hpp @@ -0,0 +1,82 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_QUEUE_H +#define CANDY_GEAR_BLUCAT_QUEUE_H 1 + +#include "core.hpp" + +namespace BluCat +{ +class QueueFamily; + +struct Queue +{ + friend class BluCat::QueueFamily; + + Queue(const Queue &t) = delete; + Queue& operator=(const Queue &t) = delete; + + VkQueue queue; + + template<typename T> + void submit_one_time_command( + const VkCommandBuffer vk_command_buffer, T commands); + + Queue(Queue &&that); + Queue& operator=(Queue &&that); + + ~Queue(); + +private: + BluCat::QueueFamily *queue_family; + int queue_index; + + Queue(BluCat::QueueFamily *queue_family, VkQueue queue, int queue_index); +}; + +template<typename T> void +Queue::submit_one_time_command( + const VkCommandBuffer vk_command_buffer, T commands) +{ + VkCommandBufferBeginInfo buffer_begin_info{}; + buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(vk_command_buffer, &buffer_begin_info); + + commands(); + + vkEndCommandBuffer(vk_command_buffer); + + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = nullptr; + submit_info.waitSemaphoreCount = 0; + submit_info.pWaitSemaphores = nullptr; + submit_info.pWaitDstStageMask = nullptr; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &vk_command_buffer; + submit_info.signalSemaphoreCount = 0; + submit_info.pSignalSemaphores = nullptr; + + vkQueueSubmit(this->queue, 1, &submit_info, VK_NULL_HANDLE); + vkQueueWaitIdle(this->queue); +} + +} + +#endif /* CANDY_GEAR_BLUCAT_QUEUE_H */ diff --git a/src/blucat/queue_family.cpp b/src/blucat/queue_family.cpp new file mode 100644 index 0000000..32aaf4b --- /dev/null +++ b/src/blucat/queue_family.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "queue_family.hpp" + +#ifdef DEBUG +#include <sstream> +#endif + +#include "../core.hpp" + +namespace BluCat +{ + +QueueFamily::QueueFamily( + BluCat::Device *device, uint32_t family_index, + const VkQueueFamilyProperties &queue_family_properties): + queue_mutex{} +{ + +#ifdef DEBUG + std::stringstream message{}; + message << "Queue quantity: " << queue_family_properties.queueCount << + std::endl; + message << "Graphics: " << + (queue_family_properties.queueFlags & VK_QUEUE_GRAPHICS_BIT ? + "true" : "false") << std::endl; + message << "Compute: " << + (queue_family_properties.queueFlags & VK_QUEUE_COMPUTE_BIT ? + "true" : "false") << std::endl; + message << "Transfer: " << + (queue_family_properties.queueFlags & VK_QUEUE_TRANSFER_BIT ? + "true" : "false") << std::endl; + message << "Sparse Binding: " << + (queue_family_properties.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT ? + "true" : "false") << std::endl; + cg_core.log.message(Log::Level::Trace, message.str()); +#endif + + this->device = device; + this->family_index = family_index; + this->family_properties = queue_family_properties; + + // Create queues + { + auto queue_count = this->family_properties.queueCount; + this->queue_states.resize(queue_count); + + for(auto i{0}; i < queue_count; i++) + { + vkGetDeviceQueue(device->device, this->family_index, i, + &this->queue_states[i].queue); + if(this->queue_states[i].queue == VK_NULL_HANDLE) + throw std::runtime_error("Failed to get Vulkan queue."); + } + } +} + +Queue +QueueFamily::get_queue() +{ + std::unique_lock<std::mutex> lock{this->queue_mutex}; + + for(auto i{0}; i < this->queue_states.size(); i++) + if(!this->queue_states[i].busy) + return Queue(this, this->queue_states[i].queue, i); + + throw std::length_error("No free queues found."); +} + +} diff --git a/src/blucat/queue_family.hpp b/src/blucat/queue_family.hpp new file mode 100644 index 0000000..2486316 --- /dev/null +++ b/src/blucat/queue_family.hpp @@ -0,0 +1,58 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_QUEUE_FAMILY_H +#define CANDY_GEAR_BLUCAT_QUEUE_FAMILY_H 1 + +#include <mutex> +#include <vector> + +#include "core.hpp" +#include "queue.hpp" + +namespace BluCat +{ +class Device; + +struct QueueState +{ + VkQueue queue; + bool busy; +}; + +class QueueFamily +{ + friend class Queue; + + std::mutex queue_mutex; + std::vector<QueueState> queue_states; + +public: + BluCat::Device *device; + + uint32_t family_index; + VkQueueFamilyProperties family_properties; + + QueueFamily(BluCat::Device *device, uint32_t family_index, + const VkQueueFamilyProperties &queue_family_properties); + + Queue + get_queue(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_QUEUE_FAMILY_H */ diff --git a/src/blucat/rectangle.cpp b/src/blucat/rectangle.cpp new file mode 100644 index 0000000..34f844d --- /dev/null +++ b/src/blucat/rectangle.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rectangle.hpp" + +namespace BluCat +{ + +const int Rectangle::VertexCount{4}; + +Rectangle::Rectangle(glm::vec4 position, glm::vec3 color): + position{position}, + color{color} +{ +} + +} diff --git a/src/blucat/rectangle.hpp b/src/blucat/rectangle.hpp new file mode 100644 index 0000000..876508c --- /dev/null +++ b/src/blucat/rectangle.hpp @@ -0,0 +1,42 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_RECTANGLE_H +#define CANDY_GEAR_BLUCAT_RECTANGLE_H 1 + +#include <vector> + +#include "core.hpp" +#include "destination_buffer.hpp" +#include "queue_family.hpp" +#include "uniform_buffer.hpp" + +namespace BluCat +{ + +struct Rectangle +{ + static const int VertexCount; + + glm::vec4 position; + glm::vec3 color; + + Rectangle(glm::vec4 position, glm::vec3 color); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_RECTANGLE_H */ diff --git a/src/blucat/render_pass.cpp b/src/blucat/render_pass.cpp new file mode 100644 index 0000000..0eb30e3 --- /dev/null +++ b/src/blucat/render_pass.cpp @@ -0,0 +1,203 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "render_pass.hpp" + +#include <array> + +#include "../core.hpp" + +namespace +{ + +void +load_3d(void *obj) +{ + auto self = static_cast<BluCat::RenderPass*>(obj); + + std::array<VkAttachmentDescription, 2> attachments{}; + // Color attachment. + attachments[0].flags = 0; + attachments[0].format = cg_core.vk_swapchain->image_format; + attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + // Depth attachment. + attachments[1].flags = 0; + attachments[1].format = VK_FORMAT_D32_SFLOAT; + attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[1].finalLayout = + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference color_attachment_ref{}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference depth_attachment_ref{}; + depth_attachment_ref.attachment = 1; + depth_attachment_ref.layout = + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.flags = 0; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = nullptr; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + subpass.pResolveAttachments = nullptr; + subpass.pDepthStencilAttachment = &depth_attachment_ref; + subpass.preserveAttachmentCount = 0; + subpass.pPreserveAttachments = nullptr; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo render_pass_info{}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.pNext = nullptr; + render_pass_info.flags = 0; + render_pass_info.attachmentCount = attachments.size(); + render_pass_info.pAttachments = attachments.data(); + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + render_pass_info.dependencyCount = 1; + render_pass_info.pDependencies = &dependency; + + if(vkCreateRenderPass( + cg_core.vk_device_with_swapchain->device, &render_pass_info, nullptr, + &self->pipeline_3d) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan Render Pass 3D."}; +} + +void +unload_3d(void *obj) +{ + auto self = static_cast<BluCat::RenderPass*>(obj); + + vkDestroyRenderPass( + cg_core.vk_device_with_swapchain->device, self->pipeline_3d, nullptr); +} + +void +load_2d(void *obj) +{ + auto self = static_cast<BluCat::RenderPass*>(obj); + + std::array<VkAttachmentDescription, 1> attachments{}; + // Color attachment. + attachments[0].flags = 0; + attachments[0].format = cg_core.vk_swapchain->image_format; + attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference color_attachment_ref{}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.flags = 0; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = nullptr; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + subpass.pResolveAttachments = nullptr; + subpass.pDepthStencilAttachment = nullptr; + subpass.preserveAttachmentCount = 0; + subpass.pPreserveAttachments = nullptr; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo render_pass_info{}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.pNext = nullptr; + render_pass_info.flags = 0; + render_pass_info.attachmentCount = attachments.size(); + render_pass_info.pAttachments = attachments.data(); + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + render_pass_info.dependencyCount = 1; + render_pass_info.pDependencies = &dependency; + + if(vkCreateRenderPass( + cg_core.vk_device_with_swapchain->device, &render_pass_info, + nullptr, &self->pipeline_2d) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan Render Pass 2D."}; +} + +void +unload_2d(void *obj) +{ + auto self = static_cast<BluCat::RenderPass*>(obj); + + vkDestroyRenderPass( + cg_core.vk_device_with_swapchain->device, self->pipeline_2d, nullptr); +} + +const CommandChain loader{ + {&load_3d, &unload_3d}, + {&load_2d, &unload_2d} +}; + +} + +namespace BluCat +{ + +RenderPass::RenderPass() +{ + loader.execute(this); +} + +RenderPass::~RenderPass() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/render_pass.hpp b/src/blucat/render_pass.hpp new file mode 100644 index 0000000..196ce97 --- /dev/null +++ b/src/blucat/render_pass.hpp @@ -0,0 +1,36 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_RENDER_PASS_H +#define CANDY_GEAR_BLUCAT_RENDER_PASS_H 1 + +#include "core.hpp" + +namespace BluCat +{ + +struct RenderPass +{ + VkRenderPass pipeline_2d; + VkRenderPass pipeline_3d; + + RenderPass(); + ~RenderPass(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_RENDER_PASS_H */ diff --git a/src/blucat/renderer.cpp b/src/blucat/renderer.cpp new file mode 100644 index 0000000..75822e0 --- /dev/null +++ b/src/blucat/renderer.cpp @@ -0,0 +1,413 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "renderer.hpp" + +#include <array> + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_descriptor_pool(void *obj) +{ + auto self = static_cast<BluCat::Renderer*>(obj); + + uint32_t uniform_buffer_count = 0; + for(auto &view : self->views_3d) + uniform_buffer_count += (view->ub_3d.size() + view->ub_2d.size()); + for(auto &view : self->views_2d) + uniform_buffer_count += (view->ub_2d.size()); + + VkDescriptorPoolSize descriptor_pool_size{}; + descriptor_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_pool_size.descriptorCount = uniform_buffer_count; + + VkDescriptorPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = uniform_buffer_count; + pool_info.poolSizeCount = 1; + pool_info.pPoolSizes = &descriptor_pool_size; + + if(vkCreateDescriptorPool( + cg_core.vk_device_with_swapchain->device, &pool_info, nullptr, + &self->descriptor_pool) != VK_SUCCESS) + throw CommandError{"Failed to create a Vulkan descriptor pool."}; + + for(auto &view : self->views_3d) + view->load_descriptor_sets(self->descriptor_pool); + for(auto &view : self->views_2d) + view->load_descriptor_sets(self->descriptor_pool); +} + +void +unload_descriptor_pool(void *obj) +{ + auto self = static_cast<BluCat::Renderer*>(obj); + + for(auto &view : self->views_3d) view->unload_descriptor_sets(); + for(auto &view : self->views_2d) view->unload_descriptor_sets(); + + vkDestroyDescriptorPool( + cg_core.vk_device_with_swapchain->device, self->descriptor_pool, + nullptr); +} + +void +load_queue_family(void *obj) +{ + auto self = static_cast<BluCat::Renderer*>(obj); + + self->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_presentation(); +} + +void +load_command_pool(void *obj) +{ + auto self = static_cast<BluCat::Renderer*>(obj); + + VkCommandPoolCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + create_info.pNext = nullptr; + create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + create_info.queueFamilyIndex = self->queue_family->family_index; + + vkCreateCommandPool( + self->queue_family->device->device, &create_info, nullptr, + &self->command_pool); +} + +void +unload_command_pool(void *obj) +{ + auto self = static_cast<BluCat::Renderer*>(obj); + + vkWaitForFences(cg_core.vk_device_with_swapchain->device, + BluCat::Swapchain::max_frames_in_flight, + cg_core.vk_swapchain->in_flight_fences.data(), VK_TRUE, + std::numeric_limits<uint64_t>::max()); + vkDestroyCommandPool( + self->queue_family->device->device, self->command_pool, nullptr); +} + +void +load_draw_command_buffer(void *obj) +{ + auto self = static_cast<BluCat::Renderer*>(obj); + + // FIXME: 3 is a magical number, triple buffering. + self->draw_command_buffers.resize(3); + + VkCommandBufferAllocateInfo allocate_info{}; + allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocate_info.pNext = nullptr; + allocate_info.commandPool = self->command_pool; + allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocate_info.commandBufferCount = self->draw_command_buffers.size(); + + if(vkAllocateCommandBuffers( + self->queue_family->device->device, &allocate_info, + self->draw_command_buffers.data()) != VK_SUCCESS) + throw CommandError{"Vulkan draw command buffers could not be allocated."}; +} + +const CommandChain loader{ + {&load_descriptor_pool, &unload_descriptor_pool}, + {&load_queue_family, nullptr}, + {&load_command_pool, &unload_command_pool}, + {&load_draw_command_buffer, nullptr} +}; + +} + +namespace BluCat +{ + +Renderer::Renderer(std::vector<std::shared_ptr<View2D>> views_2d, + std::vector<std::shared_ptr<View3D>> views_3d): + skeletal_models_to_draw{cg_core.vk_swapchain->images_count}, + static_models_to_draw{cg_core.vk_swapchain->images_count}, + sprites_3d_to_draw{cg_core.vk_swapchain->images_count}, + views_2d{views_2d}, + views_3d{views_3d} +{ + loader.execute(this); +} + +Renderer::Renderer(std::initializer_list<std::shared_ptr<View2D>> views_2d, + std::initializer_list<std::shared_ptr<View3D>> views_3d): + Renderer(std::vector(views_2d), std::vector(views_3d)) +{ +} + +Renderer::~Renderer() +{ + loader.revert(this); +} + +void +Renderer::draw() +{ + auto fence_status = vkGetFenceStatus( + cg_core.vk_device_with_swapchain->device, + cg_core.vk_swapchain->in_flight_fences[ + cg_core.vk_swapchain->current_frame]); + + if(fence_status == VK_SUCCESS) + { + auto next_frame = cg_core.vk_swapchain->current_frame + 1; + if(next_frame == Swapchain::max_frames_in_flight) next_frame = 0; + + vkResetFences(cg_core.vk_device_with_swapchain->device, 1, + &cg_core.vk_swapchain->in_flight_fences[ + cg_core.vk_swapchain->current_frame]); + + uint32_t image_index; + vkAcquireNextImageKHR( + cg_core.vk_device_with_swapchain->device, + cg_core.vk_swapchain->swapchain, std::numeric_limits<uint64_t>::max(), + cg_core.vk_swapchain->image_available_semaphores[ + cg_core.vk_swapchain->current_frame], VK_NULL_HANDLE, &image_index); + + VkCommandBuffer draw_command_buffer = + this->draw_command_buffers[cg_core.vk_swapchain->current_frame]; + vkResetCommandBuffer(draw_command_buffer, 0); + + // Begin command buffer. + { + VkCommandBufferBeginInfo begin_info{}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = 0; + begin_info.pInheritanceInfo = nullptr; + if (vkBeginCommandBuffer(draw_command_buffer, &begin_info) != VK_SUCCESS) + throw std::runtime_error{"Failed to beggin draw command buffer."}; + } + + // 3D drawing. + { + // Dark gray blue. + std::array<VkClearValue, 2> clear_values{}; + clear_values[0].color = {0.12f, 0.12f, 0.18f, 1.0f}; + clear_values[1].depthStencil = {1.0f, 0}; + + { // Update world uniform buffer + UDOWorld3D_Vert ubo_world_3d_vert{}; + ubo_world_3d_vert.ambient_light_color = + glm::vec4{0.25, 0.25, 0.25, 1.0}; + cg_core.vk_light->ub_world_vert[image_index].copy_data( + &ubo_world_3d_vert); + + UDOWorld3D_Frag ubo_world_3d_frag{}; + ubo_world_3d_frag.directional_light_direction = + glm::vec3{-0.57735, 0.57735, -0.57735}; + ubo_world_3d_frag.directional_light_color = + glm::vec4{0.8, 0.8, 0.8, 1.0}; + cg_core.vk_light->ub_world_frag[image_index].copy_data( + &ubo_world_3d_frag); + } + + VkRenderPassBeginInfo render_pass_begin{}; + render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_begin.pNext = nullptr; + render_pass_begin.renderPass = cg_core.vk_render_pass->pipeline_3d; + render_pass_begin.framebuffer = + cg_core.vk_framebuffer->pipeline_3d[image_index]; + render_pass_begin.renderArea.offset = {0, 0}; + render_pass_begin.renderArea.extent = { + static_cast<uint32_t>(cg_core.display_width), + static_cast<uint32_t>(cg_core.display_height)}; + render_pass_begin.clearValueCount = clear_values.size(); + render_pass_begin.pClearValues = clear_values.data(); + + vkCmdBeginRenderPass( + draw_command_buffer, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); + } + + for(auto &view: this->views_3d) + { + { // Set viewport + VkViewport vk_viewport{}; + vk_viewport.x = view->region.x; + vk_viewport.y = view->region.y; + vk_viewport.width = view->region.z; + vk_viewport.height = view->region.w; + vk_viewport.minDepth = 0.0f; + vk_viewport.maxDepth = 1.0f; + vkCmdSetViewport(draw_command_buffer, 0, 1, &vk_viewport); + + VkRect2D vk_scissor{}; + vk_scissor.offset.x = static_cast<int32_t>(view->region.x); + vk_scissor.offset.y = static_cast<int32_t>(view->region.y); + vk_scissor.extent.width = static_cast<uint32_t>(view->region.z); + vk_scissor.extent.height = static_cast<uint32_t>(view->region.w); + vkCmdSetScissor(draw_command_buffer, 0, 1, &vk_scissor); + } + + cg_core.vk_graphics_pipeline_3d->draw( + view, draw_command_buffer, cg_core.vk_swapchain->current_frame, + image_index); + + cg_core.vk_graphics_pipeline_sprite_3d->draw( + view, draw_command_buffer, cg_core.vk_swapchain->current_frame, + image_index); + + cg_core.vk_graphics_pipeline_3d_skeletal->draw( + view, draw_command_buffer, cg_core.vk_swapchain->current_frame, + image_index); + + { // Update view uniform buffers + BluCat::UDOView3D ubo_view_3d{}; + + // View matrix. + glm::mat4 translation_matrix{1.0f}; + translation_matrix = glm::translate( + translation_matrix, *view->camera_position); + glm::mat4 rotation_matrix{glm::toMat4(*view->camera_orientation)}; + ubo_view_3d.view = glm::inverse(translation_matrix * rotation_matrix); + + // Projection matrix. + ubo_view_3d.proj = glm::perspective( + glm::radians(view->field_of_view), + view->region.z / view->region.w, + 0.1f, 100.0f); + + view->ub_3d[image_index].copy_data(&ubo_view_3d); + } + } + + vkCmdEndRenderPass(draw_command_buffer); + + { // 2D render pass + VkRenderPassBeginInfo render_pass_begin{}; + render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_begin.pNext = nullptr; + render_pass_begin.renderPass = cg_core.vk_render_pass->pipeline_2d; + render_pass_begin.framebuffer = + cg_core.vk_framebuffer->pipeline_2d[image_index]; + render_pass_begin.renderArea.offset = {0, 0}; + render_pass_begin.renderArea.extent = { + static_cast<uint32_t>(cg_core.display_width), + static_cast<uint32_t>(cg_core.display_height)}; + render_pass_begin.clearValueCount = 0; + render_pass_begin.pClearValues = nullptr; + + vkCmdBeginRenderPass( + draw_command_buffer, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); + + } + + { // 2D solid drawing + for(auto &view: this->views_2d) + cg_core.vk_graphics_pipeline_2d_solid->draw( + view, draw_command_buffer, cg_core.vk_swapchain->current_frame, + next_frame, image_index); + + for(auto &view: this->views_3d) + cg_core.vk_graphics_pipeline_2d_solid->draw( + view, draw_command_buffer, cg_core.vk_swapchain->current_frame, + next_frame, image_index); + } + + { // 2D wired drawing + for(auto &view: this->views_2d) + cg_core.vk_graphics_pipeline_2d_wired->draw( + view, draw_command_buffer, cg_core.vk_swapchain->current_frame, + next_frame, image_index); + + for(auto &view: this->views_3d) + cg_core.vk_graphics_pipeline_2d_wired->draw( + view, draw_command_buffer, cg_core.vk_swapchain->current_frame, + next_frame, image_index); + } + + vkCmdEndRenderPass(draw_command_buffer); + + // End command buffer. + if(vkEndCommandBuffer(draw_command_buffer) != VK_SUCCESS) + throw std::runtime_error{"Failed to end draw command buffer."}; + + // Submit drawing command. + { + auto queue{this->queue_family->get_queue()}; + + VkSemaphore wait_semaphores[]{ + cg_core.vk_swapchain->image_available_semaphores[ + cg_core.vk_swapchain->current_frame]}; + VkPipelineStageFlags wait_stages[] = + {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + VkSemaphore signal_semaphores[]{ + cg_core.vk_swapchain->render_finished_semaphores[ + cg_core.vk_swapchain->current_frame]}; + + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = nullptr; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = wait_semaphores; + submit_info.pWaitDstStageMask = wait_stages; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &draw_command_buffer; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = signal_semaphores; + + if(vkQueueSubmit( + queue.queue, 1, &submit_info, cg_core.vk_swapchain->in_flight_fences[ + cg_core.vk_swapchain->current_frame]) != VK_SUCCESS) + throw std::runtime_error{"Failed to submit draw command buffer."}; + + VkSwapchainKHR swap_chains[]{cg_core.vk_swapchain->swapchain}; + + VkPresentInfoKHR present_info{}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.pNext = nullptr; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = signal_semaphores; + present_info.swapchainCount = 1; + present_info.pSwapchains = swap_chains; + present_info.pImageIndices = &image_index; + present_info.pResults = nullptr; + + vkQueuePresentKHR(queue.queue, &present_info); + } + + // Prepare for the next frame. + { + this->skeletal_models_to_draw[next_frame].clear(); + this->static_models_to_draw[next_frame].clear(); + this->sprites_3d_to_draw[next_frame].clear(); + cg_core.vk_swapchain->current_frame = next_frame; + } + } + else + { + // Clear images for the current frame because we are skipping this frame. + this->skeletal_models_to_draw[cg_core.vk_swapchain->current_frame].clear(); + this->static_models_to_draw[cg_core.vk_swapchain->current_frame].clear(); + this->sprites_3d_to_draw[cg_core.vk_swapchain->current_frame].clear(); + for(auto &view: this->views_2d) + view->sprites_to_draw[cg_core.vk_swapchain->current_frame].clear(); + for(auto &view: this->views_3d) + view->sprites_to_draw[cg_core.vk_swapchain->current_frame].clear(); + } +} + +} diff --git a/src/blucat/renderer.hpp b/src/blucat/renderer.hpp new file mode 100644 index 0000000..d23ebe5 --- /dev/null +++ b/src/blucat/renderer.hpp @@ -0,0 +1,72 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_RENDERER_H +#define CANDY_GEAR_BLUCAT_RENDERER_H 1 + +#include <initializer_list> +#include <memory> +#include <vector> + +#include "core.hpp" +#include "skeletal_mesh.hpp" +#include "skeletal_model.hpp" +#include "sprite_3d.hpp" +#include "static_mesh.hpp" +#include "static_model.hpp" +#include "queue_family.hpp" +#include "view_2d.hpp" +#include "view_3d.hpp" + +namespace BluCat +{ + +struct Renderer +{ + std::vector< + std::unordered_map< + std::shared_ptr<SkeletalMesh>, + std::vector<std::shared_ptr<SkeletalModel>>>> + skeletal_models_to_draw; + + std::vector< + std::unordered_map< + std::shared_ptr<StaticMesh>, + std::vector<std::shared_ptr<StaticModel>>>> + static_models_to_draw; + + std::vector<std::vector<std::shared_ptr<Sprite3D>>> sprites_3d_to_draw; + + VkDescriptorPool descriptor_pool; + std::vector<std::shared_ptr<View2D>> views_2d; + std::vector<std::shared_ptr<View3D>> views_3d; + QueueFamily *queue_family; + VkCommandPool command_pool; + std::vector<VkCommandBuffer> draw_command_buffers; + + Renderer(std::vector<std::shared_ptr<View2D>> views_2d, + std::vector<std::shared_ptr<View3D>> views_3d); + Renderer(std::initializer_list<std::shared_ptr<View2D>> views_2d, + std::initializer_list<std::shared_ptr<View3D>> views_3d); + ~Renderer(); + + void + draw(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_RENDERER_H */ diff --git a/src/blucat/skeletal_mesh.cpp b/src/blucat/skeletal_mesh.cpp new file mode 100644 index 0000000..baea635 --- /dev/null +++ b/src/blucat/skeletal_mesh.cpp @@ -0,0 +1,204 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "skeletal_mesh.hpp" + +#include "../binary_reader.hpp" +#include "../command.hpp" +#include "../core.hpp" +#include "skeletal_mesh_vertex.hpp" + +namespace +{ + +// Data that is only needed for the command chain but not for the SkeletalMesh +// goes here. +struct MeshBuilder +{ + std::string mesh_path; + BluCat::SkeletalMesh *mesh; + + MeshBuilder(BluCat::SkeletalMesh *m, std::string mp); + MeshBuilder(BluCat::SkeletalMesh *m, const char* mp); +}; + +MeshBuilder::MeshBuilder(BluCat::SkeletalMesh *m, std::string mp): + mesh{m}, + mesh_path{mp} +{ +} + +MeshBuilder::MeshBuilder(BluCat::SkeletalMesh *m, const char *mp): + MeshBuilder{m, std::string(mp)} +{ +} + +void +load_mesh(void *obj) +{ + auto self = static_cast<MeshBuilder*>(obj); + + BinaryReader input{self->mesh_path}; + + self->mesh->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_graphics(); + + { // Load vertexes. + auto vertex_count{input.read_ui32()}; + std::vector<BluCat::SkeletalMeshVertex> vertexes{vertex_count}; + + for(auto i{0}; i < vertex_count; i++) + { + vertexes[i].position = input.read_vec3(); + vertexes[i].normal = input.read_vec3(); + vertexes[i].texture_coord = input.read_vec2(); + + for(auto ii{0}; ii < BluCat::SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES; + ii++) + vertexes[i].bone_ids[ii] = input.read_ui32(); + + for(auto ii{0}; ii < BluCat::SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES; + ii++) + vertexes[i].bone_weights[ii] = input.read_float(); + } + + void *vertexes_data{vertexes.data()}; + size_t vertexes_size = sizeof(vertexes[0]) * vertexes.size(); + self->mesh->source_vertex_buffer = new BluCat::SourceBuffer{ + self->mesh->queue_family->device, vertexes_data, vertexes_size}; + self->mesh->vertex_buffer = new BluCat::DestinationBuffer{ + self->mesh->queue_family, self->mesh->source_vertex_buffer, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT}; + } + + { // Load indexes. + self->mesh->index_count = input.read_ui32(); + std::vector<uint32_t> indexes(self->mesh->index_count); + + for(auto i{0}; i < self->mesh->index_count; i++) + indexes[i] = input.read_ui32(); + + void *indexes_data{indexes.data()}; + size_t indexes_size{sizeof(indexes[0]) * indexes.size()}; + BluCat::SourceBuffer source_index_buffer{ + self->mesh->queue_family->device, indexes_data, indexes_size}; + self->mesh->index_buffer = new BluCat::DestinationBuffer{ + self->mesh->queue_family, &source_index_buffer, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT}; + } + + { // Load bones + auto bone_count{input.read_ui32()}; + self->mesh->bones.reserve(bone_count); + for(int i{0}; i < bone_count; i++) + self->mesh->bones.emplace_back(input.read_mat4()); + } + + { // Load animations + auto num_animations{input.read_ui32()}; + self->mesh->animations.resize(num_animations); + for(uint32_t i{0}; i < num_animations; i++) + { + auto duration{input.read_double()}; + self->mesh->animations[i].final_time = (float)duration; + + auto ticks_per_second{input.read_double()}; + + auto num_bone_transforms{input.read_ui32()}; + std::vector<BluCat::BoneTransform> *bone_transforms = + &(self->mesh->animations[i].bone_transforms); + bone_transforms->resize(num_bone_transforms); + for(uint32_t bone_transform_index{0}; + bone_transform_index < num_bone_transforms; bone_transform_index++) + { + auto bone_id{input.read_ui32()}; + + auto num_positions{input.read_ui32()}; + BluCat::Channel<glm::vec3> *positions = + &((*bone_transforms)[bone_transform_index].positions); + for(auto position_key_index{0}; position_key_index < num_positions; + position_key_index++) + { + auto vec3{input.read_vec3()}; + auto timestamp{input.read_double()}; + positions->key_frames.emplace_back( + vec3, static_cast<float>(timestamp)); + } + + auto num_rotations{input.read_ui32()}; + BluCat::Channel<glm::quat> *rotations = + &((*bone_transforms)[bone_transform_index].rotations); + for(auto rotation_key_index{0}; rotation_key_index < num_rotations; + rotation_key_index++) + { + auto quat{input.read_quat()}; + auto timestamp{input.read_double()}; + rotations->key_frames.emplace_back( + quat, static_cast<float>(timestamp)); + } + + auto num_scales{input.read_ui32()}; + BluCat::Channel<glm::vec3> *scales = + &((*bone_transforms)[bone_transform_index].scales); + for(auto scaling_key_index{0}; scaling_key_index < num_scales; + scaling_key_index++) + { + auto vec3{input.read_vec3()}; + auto timestamp{input.read_double()}; + scales->key_frames.emplace_back(vec3, static_cast<float>(timestamp)); + } + } + } + } +} + +void +unload_mesh(void *obj) +{ + auto self = static_cast<MeshBuilder*>(obj); + + delete self->mesh->index_buffer; + delete self->mesh->vertex_buffer; + delete self->mesh->source_vertex_buffer; +} + +static const CommandChain loader{ + {&load_mesh, &unload_mesh} +}; + +} + +namespace BluCat +{ + +SkeletalMesh::SkeletalMesh(std::string mesh_path) +{ + MeshBuilder mesh_builder(this, mesh_path); + loader.execute(&mesh_builder); +} + +SkeletalMesh::SkeletalMesh(const char* mesh_path): + SkeletalMesh{std::string(mesh_path)} +{ +} + +SkeletalMesh::~SkeletalMesh() +{ + MeshBuilder mesh_builder(this, ""); + loader.revert(&mesh_builder); +} + +} diff --git a/src/blucat/skeletal_mesh.hpp b/src/blucat/skeletal_mesh.hpp new file mode 100644 index 0000000..8b7d0fe --- /dev/null +++ b/src/blucat/skeletal_mesh.hpp @@ -0,0 +1,52 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_SKELETAL_MESH_H +#define CANDY_GEAR_BLUCAT_SKELETAL_MESH_H 1 + +#include <string> +#include <vector> + +#include "animation.hpp" +#include "core.hpp" +#include "destination_buffer.hpp" +#include "queue_family.hpp" +#include "uniform_buffer.hpp" +#include "texture.hpp" + +namespace BluCat +{ + +struct SkeletalMesh +{ + QueueFamily *queue_family; + + uint32_t index_count; + SourceBuffer *source_vertex_buffer; + DestinationBuffer *index_buffer; + DestinationBuffer *vertex_buffer; + + std::vector<Bone> bones; + std::vector<Animation> animations; + + SkeletalMesh(std::string mesh_path); + SkeletalMesh(const char* mesh_path); + ~SkeletalMesh(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_SKELETAL_MESH_H */ diff --git a/src/blucat/skeletal_mesh_vertex.hpp b/src/blucat/skeletal_mesh_vertex.hpp new file mode 100644 index 0000000..533c2ab --- /dev/null +++ b/src/blucat/skeletal_mesh_vertex.hpp @@ -0,0 +1,42 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_SKELETAL_MESH_VERTEX_H +#define CANDY_GEAR_BLUCAT_SKELETAL_MESH_VERTEX_H 1 + +#include "core.hpp" + +namespace BluCat +{ + +// This variable define the maximum ammount of bones that can influence a +// vertex. +const int SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES{4}; +const int SKELETAL_MESH_MAX_NUM_OF_BONES{50}; + +struct SkeletalMeshVertex +{ + glm::vec3 position; + glm::vec3 normal; + glm::vec2 texture_coord; + + uint32_t bone_ids[SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES]; + float bone_weights[SKELETAL_MESH_MAX_NUM_OF_INFLUENCING_BONES]; +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_SKELETAL_MESH_VERTEX_H */ diff --git a/src/blucat/skeletal_model.cpp b/src/blucat/skeletal_model.cpp new file mode 100644 index 0000000..8761bc8 --- /dev/null +++ b/src/blucat/skeletal_model.cpp @@ -0,0 +1,238 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "skeletal_model.hpp" + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_uniform_buffers(void *obj) +{ + auto self = static_cast<BluCat::SkeletalModel*>(obj); + + try + { + self->uniform_buffers.reserve(cg_core.vk_swapchain->images_count); + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->uniform_buffers.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(BluCat::UDOSkeletalModel)); + } + catch(const std::exception& e) + { + throw CommandError{e.what()}; + } +} + +void +unload_uniform_buffers(void *obj) +{ + auto self = static_cast<BluCat::SkeletalModel*>(obj); + + self->uniform_buffers.clear(); +} + +void +load_descriptor_set_pool(void *obj) +{ + auto self = static_cast<BluCat::SkeletalModel*>(obj); + + std::array<VkDescriptorPoolSize, 1> descriptor_pool_sizes{}; + descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_pool_sizes[0].descriptorCount = + self->uniform_buffers.size(); + + VkDescriptorPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = self->uniform_buffers.size(); + pool_info.poolSizeCount = descriptor_pool_sizes.size(); + pool_info.pPoolSizes = descriptor_pool_sizes.data(); + + if(vkCreateDescriptorPool( + self->skeletal_mesh->queue_family->device->device, &pool_info, nullptr, + &self->descriptor_pool) != VK_SUCCESS) + throw CommandError{"Failed to create a Vulkan descriptor pool."}; +} + +void +unload_descriptor_set_pool(void *obj) +{ + auto self = static_cast<BluCat::SkeletalModel*>(obj); + + vkDestroyDescriptorPool( + self->skeletal_mesh->queue_family->device->device, self->descriptor_pool, + nullptr); +} + +void +load_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::SkeletalModel*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + cg_core.vk_descriptor_set_layout->model); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->descriptor_pool; + alloc_info.descriptorSetCount = layouts.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->descriptor_sets.resize(layouts.size()); + if(vkAllocateDescriptorSets( + self->skeletal_mesh->queue_family->device->device, &alloc_info, + self->descriptor_sets.data()) != VK_SUCCESS) + CommandError{"Failed to create Vulkan descriptor set."}; +} + +void +load_buffers_to_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::SkeletalModel*>(obj); + + for(auto i{0}; i < self->uniform_buffers.size(); i++) + { + VkDescriptorBufferInfo buffer_info{}; + buffer_info.buffer = self->uniform_buffers[i].buffer; + buffer_info.offset = 0; + buffer_info.range = sizeof(BluCat::UDOSkeletalModel); + + std::array<VkWriteDescriptorSet, 1> write_descriptors{}; + write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[0].dstSet = self->descriptor_sets[i]; + write_descriptors[0].dstBinding = 0; + write_descriptors[0].dstArrayElement = 0; + write_descriptors[0].descriptorCount = 1; + write_descriptors[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptors[0].pBufferInfo = &buffer_info; + write_descriptors[0].pImageInfo = nullptr; + write_descriptors[0].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, write_descriptors.size(), + write_descriptors.data(), 0, nullptr); + } +} + +static const CommandChain loader{ + {&load_uniform_buffers, &unload_uniform_buffers}, + {&load_descriptor_set_pool, &unload_descriptor_set_pool}, + {&load_descriptor_sets, nullptr}, + {&load_buffers_to_descriptor_sets, nullptr} +}; + +} + +namespace BluCat +{ + +SkeletalModel::SkeletalModel( + std::shared_ptr<SkeletalMesh> skeletal_mesh, + std::shared_ptr<Texture> texture, std::shared_ptr<glm::vec3> position, + std::shared_ptr<glm::quat> orientation): + skeletal_mesh{skeletal_mesh}, + texture{texture}, + position{position}, + orientation{orientation}, + animation_index{0}, + animation_time{0.0f}, + bone_transforms(SKELETAL_MESH_MAX_NUM_OF_BONES) +{ + loader.execute(this); + + for(int i{0}; i < skeletal_mesh->bones.size(); i++) + this->bone_transforms[i] = skeletal_mesh->bones[i].offset_matrix; +} + +SkeletalModel::~SkeletalModel() +{ + loader.revert(this); +} + +void +SkeletalModel::tick(float delta) +{ + BluCat::Animation *current_animation = + &this->skeletal_mesh->animations[this->animation_index]; + + { // update time + this->animation_time += delta; + if(this->animation_time > current_animation->final_time) + { + this->animation_time -= current_animation->final_time; + for(BluCat::BoneTransform &bone_transform: + current_animation->bone_transforms) + { + bone_transform.positions.current_index = 0; + bone_transform.rotations.current_index = 0; + bone_transform.scales.current_index = 0; + } + } + } + + for(int i{0}; i < current_animation->bone_transforms.size(); i++) + { + BluCat::BoneTransform *bone_transform = ¤t_animation->bone_transforms[i]; + + auto position{bone_transform->positions.interpolate( + this->animation_time, + [](glm::vec3 frame) + { + return glm::translate(glm::mat4(1.0f), frame); + }, + [](glm::vec3 previous_frame, glm::vec3 next_frame, float scale_factor) + { + glm::vec3 final_position{glm::mix( + previous_frame, next_frame, scale_factor)}; + return glm::translate(glm::mat4(1.0f), final_position); + })}; + + auto rotation{bone_transform->rotations.interpolate( + this->animation_time, + [](glm::quat frame) + { + return glm::toMat4(glm::normalize(frame)); + }, + [](glm::quat previous_frame, glm::quat next_frame, float scale_factor) + { + return glm::toMat4(glm::slerp( + previous_frame, next_frame, scale_factor)); + })}; + + auto scale{bone_transform->scales.interpolate( + this->animation_time, + [](glm::vec3 frame) + { + return glm::scale(glm::mat4(1.0f), frame); + }, + [](glm::vec3 previous_frame, glm::vec3 next_frame, float scale_factor) + { + glm::vec3 scale{glm::mix( + previous_frame, next_frame, scale_factor)}; + return glm::scale(glm::mat4(1.0f), scale); + })}; + + this->bone_transforms[i] = position * rotation * scale; + } +} + +} diff --git a/src/blucat/skeletal_model.hpp b/src/blucat/skeletal_model.hpp new file mode 100644 index 0000000..cd3685e --- /dev/null +++ b/src/blucat/skeletal_model.hpp @@ -0,0 +1,55 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_SKELETAL_MODEL_H +#define CANDY_GEAR_BLUCAT_SKELETAL_MODEL_H 1 + +#include <memory> +#include <vector> + +#include "core.hpp" +#include "skeletal_mesh.hpp" + +namespace BluCat +{ + +struct SkeletalModel +{ + std::shared_ptr<SkeletalMesh> skeletal_mesh; + std::shared_ptr<Texture> texture; + std::vector<UniformBuffer> uniform_buffers; + std::shared_ptr<glm::vec3> position; + std::shared_ptr<glm::quat> orientation; + int animation_index; + float animation_time; + std::vector<glm::mat4> bone_transforms; + + VkDescriptorPool descriptor_pool; + std::vector<VkDescriptorSet> descriptor_sets; + + SkeletalModel( + std::shared_ptr<SkeletalMesh> skeletal_mesh, + std::shared_ptr<Texture> texture, std::shared_ptr<glm::vec3> position, + std::shared_ptr<glm::quat> orientation); + ~SkeletalModel(); + + void + tick(float delta); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_SKELETAL_MODEL_H */ diff --git a/src/blucat/source_buffer.cpp b/src/blucat/source_buffer.cpp new file mode 100644 index 0000000..41c40b9 --- /dev/null +++ b/src/blucat/source_buffer.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source_buffer.hpp" + +#include <cstring> + +namespace BluCat +{ + +SourceBuffer::SourceBuffer(Device *device, void *data, size_t data_size) +{ + this->device = device; + this->device_size = data_size; + this->buffer_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + this->memory_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + try + { + BluCat::BaseBuffer::loader.execute(static_cast<BluCat::BaseBuffer*>(this)); + } + catch(const CommandError &command_error) + { + std::string error{"Could not initialize Vulkan source buffer → "}; + error += command_error.what(); + throw std::runtime_error{error}; + } + this->copy_data(data); +} + +SourceBuffer::SourceBuffer(SourceBuffer &&that) +{ + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; +} + +SourceBuffer& +SourceBuffer::operator=(SourceBuffer &&that) +{ + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; + + return *this; +} + +SourceBuffer::~SourceBuffer() +{ + BluCat::BaseBuffer::loader.revert(static_cast<BluCat::BaseBuffer*>(this)); +} + +void +SourceBuffer::copy_data(void *src_data) +{ + void *dst_data; + vkMapMemory(this->device->device, this->device_memory, 0, this->device_size, + 0, &dst_data); + memcpy(dst_data, src_data, static_cast<size_t>(this->device_size)); + vkUnmapMemory(this->device->device, this->device_memory); +} + +} diff --git a/src/blucat/source_buffer.hpp b/src/blucat/source_buffer.hpp new file mode 100644 index 0000000..dd52acc --- /dev/null +++ b/src/blucat/source_buffer.hpp @@ -0,0 +1,45 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_SOURCE_BUFFER_H +#define CANDY_GEAR_BLUCAT_SOURCE_BUFFER_H 1 + +#include "base_buffer.hpp" + +namespace BluCat +{ + +struct SourceBuffer: public BaseBuffer +{ + SourceBuffer(const SourceBuffer &t) = delete; + SourceBuffer& + operator=(const SourceBuffer &t) = delete; + + SourceBuffer(Device *device, void *data, size_t data_size); + + SourceBuffer(SourceBuffer &&that); + SourceBuffer& + operator=(SourceBuffer &&that); + + ~SourceBuffer(); + + void + copy_data(void *src_data); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_SOURCE_BUFFER_H */ diff --git a/src/blucat/sprite.cpp b/src/blucat/sprite.cpp new file mode 100644 index 0000000..a275df8 --- /dev/null +++ b/src/blucat/sprite.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sprite.hpp" + +#include <array> + +#include "../core.hpp" +#include "sprite.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +struct SpriteBuilder +{ + BluCat::Sprite *sprite; + glm::vec4 ▭ + + SpriteBuilder(BluCat::Sprite *sprite, glm::vec4 &rect); +}; + +SpriteBuilder::SpriteBuilder(BluCat::Sprite *sprite, glm::vec4 &rect): + sprite{sprite}, + rect{rect} +{ +} + +void +load_mesh(void *obj) +{ + auto self = static_cast<SpriteBuilder*>(obj); + + self->sprite->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_graphics(); + + glm::vec2 rect[BluCat::Sprite::vertex_count]{ + glm::vec2{self->rect.x, self->rect.y}, + glm::vec2{self->rect.x, self->rect.w}, + glm::vec2{self->rect.z, self->rect.y}, + glm::vec2{self->rect.z, self->rect.w} + }; + + void *vertexes_data{&rect}; + static const size_t vertexes_size = + sizeof(glm::vec2) * BluCat::Sprite::vertex_count; + self->sprite->source_buffer = new BluCat::SourceBuffer{ + self->sprite->queue_family->device, vertexes_data, vertexes_size}; + self->sprite->vertex_buffer = new BluCat::DestinationBuffer{ + self->sprite->queue_family, self->sprite->source_buffer, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT}; +} + +void +unload_mesh(void *obj) +{ + auto self = static_cast<SpriteBuilder*>(obj); + + delete self->sprite->vertex_buffer; + delete self->sprite->source_buffer; +} + +static const CommandChain loader{ + {&load_mesh, &unload_mesh}, +}; + +} + +namespace BluCat +{ + +Sprite::Sprite(std::shared_ptr<Texture> texture, glm::vec4 &rect): + texture{texture} +{ + SpriteBuilder sprite_builder(this, rect); + loader.execute(&sprite_builder); +} + +Sprite::~Sprite() +{ + glm::vec4 vector_4d{}; + SpriteBuilder sprite_builder(this, vector_4d); + loader.revert(&sprite_builder); +} + +} diff --git a/src/blucat/sprite.hpp b/src/blucat/sprite.hpp new file mode 100644 index 0000000..6252d00 --- /dev/null +++ b/src/blucat/sprite.hpp @@ -0,0 +1,50 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_SPRITE_H +#define CANDY_GEAR_BLUCAT_SPRITE_H 1 + +#include <memory> +#include <unordered_map> +#include <vector> + +#include "core.hpp" +#include "destination_buffer.hpp" +#include "queue_family.hpp" +#include "uniform_buffer.hpp" +#include "texture.hpp" + +namespace BluCat +{ + +struct Sprite +{ + static const uint32_t vertex_count{4}; + + QueueFamily *queue_family; + + SourceBuffer *source_buffer; + DestinationBuffer *vertex_buffer; + + std::shared_ptr<Texture> texture; + + Sprite(std::shared_ptr<Texture> texture, glm::vec4 &rect); + ~Sprite(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_SPRITE_H */ diff --git a/src/blucat/sprite_3d.cpp b/src/blucat/sprite_3d.cpp new file mode 100644 index 0000000..6fa6d49 --- /dev/null +++ b/src/blucat/sprite_3d.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sprite_3d.hpp" + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_uniform_buffers(void *obj) +{ + auto self = static_cast<BluCat::Sprite3D*>(obj); + + try + { + self->uniform_buffers.reserve(cg_core.vk_swapchain->images_count); + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->uniform_buffers.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(BluCat::UDOSprite3D)); + } + catch(const std::exception& e) + { + throw CommandError{e.what()}; + } +} + +void +unload_uniform_buffers(void *obj) +{ + auto self = static_cast<BluCat::Sprite3D*>(obj); + + self->uniform_buffers.clear(); +} + +void +load_descriptor_set_pool(void *obj) +{ + auto self = static_cast<BluCat::Sprite3D*>(obj); + + std::array<VkDescriptorPoolSize, 1> descriptor_pool_sizes{}; + descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_pool_sizes[0].descriptorCount = + self->uniform_buffers.size(); + + VkDescriptorPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = self->uniform_buffers.size(); + pool_info.poolSizeCount = descriptor_pool_sizes.size(); + pool_info.pPoolSizes = descriptor_pool_sizes.data(); + + if(vkCreateDescriptorPool( + self->queue_family->device->device, &pool_info, nullptr, + &self->descriptor_pool) != VK_SUCCESS) + throw CommandError{"Failed to create a Vulkan descriptor pool."}; +} + +void +unload_descriptor_set_pool(void *obj) +{ + auto self = static_cast<BluCat::Sprite3D*>(obj); + + vkDestroyDescriptorPool( + self->queue_family->device->device, self->descriptor_pool, nullptr); +} +void +load_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::Sprite3D*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + cg_core.vk_descriptor_set_layout->model); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->descriptor_pool; + alloc_info.descriptorSetCount = layouts.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->descriptor_sets.resize(layouts.size()); + if(vkAllocateDescriptorSets( + self->queue_family->device->device, &alloc_info, + self->descriptor_sets.data()) != VK_SUCCESS) + CommandError{"Failed to create Vulkan descriptor set."}; +} + +void +load_buffers_to_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::Sprite3D*>(obj); + + for(auto i{0}; i < self->uniform_buffers.size(); i++) + { + VkDescriptorBufferInfo buffer_info{}; + buffer_info.buffer = self->uniform_buffers[i].buffer; + buffer_info.offset = 0; + buffer_info.range = sizeof(BluCat::UDOSprite3D); + + VkDescriptorImageInfo image_info{}; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_info.imageView = self->sprite->texture->view; + image_info.sampler = self->sprite->texture->sampler; + + std::array<VkWriteDescriptorSet, 1> write_descriptors{}; + write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[0].dstSet = self->descriptor_sets[i]; + write_descriptors[0].dstBinding = 0; + write_descriptors[0].dstArrayElement = 0; + write_descriptors[0].descriptorCount = 1; + write_descriptors[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptors[0].pBufferInfo = &buffer_info; + write_descriptors[0].pImageInfo = nullptr; + write_descriptors[0].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, write_descriptors.size(), + write_descriptors.data(), 0, nullptr); + } +} + +static const CommandChain loader{ + {&load_uniform_buffers, &unload_uniform_buffers}, + {&load_descriptor_set_pool, &unload_descriptor_set_pool}, + {&load_descriptor_sets, nullptr}, + {&load_buffers_to_descriptor_sets, nullptr} +}; + +} + +namespace BluCat +{ + +Sprite3D::Sprite3D( + std::shared_ptr<BluCat::Sprite> sprite, std::shared_ptr<glm::vec3> position, + glm::vec2 size): + sprite{sprite}, + position{position}, + size{size} +{ + this->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_graphics(); + loader.execute(this); +} + +Sprite3D::~Sprite3D() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/sprite_3d.hpp b/src/blucat/sprite_3d.hpp new file mode 100644 index 0000000..dd0f16e --- /dev/null +++ b/src/blucat/sprite_3d.hpp @@ -0,0 +1,46 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_SPRITE_3D_H +#define CANDY_GEAR_BLUCAT_SPRITE_3D_H 1 + +#include "core.hpp" +#include "sprite.hpp" + +namespace BluCat +{ + +struct Sprite3D +{ + std::shared_ptr<Sprite> sprite; + std::shared_ptr<glm::vec3> position; + glm::vec2 size; + + QueueFamily *queue_family; + + std::vector<UniformBuffer> uniform_buffers; + VkDescriptorPool descriptor_pool; + std::vector<VkDescriptorSet> descriptor_sets; + + Sprite3D( + std::shared_ptr<Sprite> sprite, std::shared_ptr<glm::vec3> position, + glm::vec2 size); + ~Sprite3D(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_SPRITE_3D_H */ diff --git a/src/blucat/sprite_to_draw.cpp b/src/blucat/sprite_to_draw.cpp new file mode 100644 index 0000000..9013adb --- /dev/null +++ b/src/blucat/sprite_to_draw.cpp @@ -0,0 +1,40 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sprite_to_draw.hpp" + +namespace BluCat +{ + SpriteToDraw::SpriteToDraw( + std::shared_ptr<Sprite> sprite, const glm::vec4 &position, float z_index): + sprite{sprite}, + position{position}, + z_index{z_index} + { + } + + bool + operator<(const SpriteToDraw& a, const SpriteToDraw& b) + { + return a.z_index < b.z_index; + } + + bool + operator>(const SpriteToDraw& a, const SpriteToDraw& b) + { + return a.z_index > b.z_index; + } +} diff --git a/src/blucat/sprite_to_draw.hpp b/src/blucat/sprite_to_draw.hpp new file mode 100644 index 0000000..fb46eac --- /dev/null +++ b/src/blucat/sprite_to_draw.hpp @@ -0,0 +1,44 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_SPRITES_TO_DRAW_H +#define CANDY_GEAR_BLUCAT_SPRITES_TO_DRAW_H 1 + +#include <memory> + +#include "core.hpp" +#include "sprite.hpp" + +namespace BluCat +{ + struct SpriteToDraw + { + std::shared_ptr<Sprite> sprite; + glm::vec4 position; + float z_index; + + SpriteToDraw(std::shared_ptr<Sprite> sprite, const glm::vec4 &position, + float z_index); + }; + + bool + operator<(const SpriteToDraw &a, const SpriteToDraw &b); + + bool + operator>(const SpriteToDraw &a, const SpriteToDraw &b); +} + +#endif /* CANDY_GEAR_BLUCAT_SPRITES_TO_DRAW_H */ diff --git a/src/blucat/static_mesh.cpp b/src/blucat/static_mesh.cpp new file mode 100644 index 0000000..ab45ca9 --- /dev/null +++ b/src/blucat/static_mesh.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "static_mesh.hpp" + +#include "../binary_reader.hpp" +#include "../command.hpp" +#include "../core.hpp" +#include "static_mesh_vertex.hpp" + +namespace +{ + +// Data that is only needed for the command chain but not for the StaticMesh +// goes here. +struct MeshBuilder +{ + std::string mesh_path; + BluCat::StaticMesh *mesh; + + MeshBuilder(BluCat::StaticMesh *m, std::string mp); + MeshBuilder(BluCat::StaticMesh *m, const char* mp); +}; + +MeshBuilder::MeshBuilder(BluCat::StaticMesh *m, std::string mp): + mesh{m}, + mesh_path{mp} +{ +} + +MeshBuilder::MeshBuilder(BluCat::StaticMesh *m, const char *mp): + MeshBuilder{m, std::string(mp)} +{ +} + +void +load_mesh(void *obj) +{ + auto self = static_cast<MeshBuilder*>(obj); + + BinaryReader input{self->mesh_path}; + + self->mesh->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_graphics(); + + { // Load vertexes. + auto vertex_count{input.read_ui32()}; + std::vector<BluCat::StaticMeshVertex> vertexes{vertex_count}; + + for(auto i{0}; i < vertex_count; i++) + { + vertexes[i].position = input.read_vec3(); + vertexes[i].normal = input.read_vec3(); + vertexes[i].texture_coord = input.read_vec2(); + } + + void *vertexes_data{vertexes.data()}; + size_t vertexes_size = sizeof(vertexes[0]) * vertexes.size(); + self->mesh->source_vertex_buffer = new BluCat::SourceBuffer{ + self->mesh->queue_family->device, vertexes_data, vertexes_size}; + self->mesh->vertex_buffer = new BluCat::DestinationBuffer{ + self->mesh->queue_family, self->mesh->source_vertex_buffer, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT}; + } + + { // Load indexes + self->mesh->index_count = input.read_ui32(); + std::vector<uint32_t> indexes(self->mesh->index_count); + + for(auto i{0}; i < self->mesh->index_count; i++) + indexes[i] = input.read_ui32(); + + void *indexes_data{indexes.data()}; + size_t indexes_size{sizeof(indexes[0]) * indexes.size()}; + BluCat::SourceBuffer source_index_buffer{ + self->mesh->queue_family->device, indexes_data, indexes_size}; + self->mesh->index_buffer = new BluCat::DestinationBuffer{ + self->mesh->queue_family, &source_index_buffer, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT}; + } +} + +void +unload_mesh(void *obj) +{ + auto self = static_cast<MeshBuilder*>(obj); + + delete self->mesh->index_buffer; + delete self->mesh->vertex_buffer; + delete self->mesh->source_vertex_buffer; +} + +static const CommandChain loader{ + {&load_mesh, &unload_mesh} +}; + +} + +namespace BluCat +{ + +StaticMesh::StaticMesh(std::string mesh_path) +{ + MeshBuilder mesh_builder(this, mesh_path); + loader.execute(&mesh_builder); +} + +StaticMesh::StaticMesh(const char* mesh_path): + StaticMesh{std::string(mesh_path)} +{ +} + +StaticMesh::~StaticMesh() +{ + MeshBuilder mesh_builder(this, ""); + loader.revert(&mesh_builder); +} + +} diff --git a/src/blucat/static_mesh.hpp b/src/blucat/static_mesh.hpp new file mode 100644 index 0000000..0efcfc1 --- /dev/null +++ b/src/blucat/static_mesh.hpp @@ -0,0 +1,48 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_STATIC_MESH_H +#define CANDY_GEAR_BLUCAT_STATIC_MESH_H 1 + +#include <string> +#include <vector> + +#include "core.hpp" +#include "destination_buffer.hpp" +#include "queue_family.hpp" +#include "uniform_buffer.hpp" +#include "texture.hpp" + +namespace BluCat +{ + +struct StaticMesh +{ + QueueFamily *queue_family; + + uint32_t index_count; + SourceBuffer *source_vertex_buffer; + DestinationBuffer *index_buffer; + DestinationBuffer *vertex_buffer; + + StaticMesh(std::string mesh_path); + StaticMesh(const char* mesh_path); + ~StaticMesh(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_STATIC_MESH_H */ diff --git a/src/blucat/static_mesh_vertex.hpp b/src/blucat/static_mesh_vertex.hpp new file mode 100644 index 0000000..025ad63 --- /dev/null +++ b/src/blucat/static_mesh_vertex.hpp @@ -0,0 +1,34 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_STATIC_MESH_VERTEX_H +#define CANDY_GEAR_BLUCAT_STATIC_MESH_VERTEX_H 1 + +#include "core.hpp" + +namespace BluCat +{ + +struct StaticMeshVertex +{ + glm::vec3 position; + glm::vec3 normal; + glm::vec2 texture_coord; +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_STATIC_MESH_VERTEX_H */ diff --git a/src/blucat/static_model.cpp b/src/blucat/static_model.cpp new file mode 100644 index 0000000..e3c6482 --- /dev/null +++ b/src/blucat/static_model.cpp @@ -0,0 +1,165 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "static_model.hpp" + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_uniform_buffers(void *obj) +{ + auto self = static_cast<BluCat::StaticModel*>(obj); + + try + { + self->uniform_buffers.reserve(cg_core.vk_swapchain->images_count); + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->uniform_buffers.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(BluCat::UDOStaticModel)); + } + catch(const std::exception& e) + { + throw CommandError{e.what()}; + } +} + +void +unload_uniform_buffers(void *obj) +{ + auto self = static_cast<BluCat::StaticModel*>(obj); + + self->uniform_buffers.clear(); +} + +void +load_descriptor_set_pool(void *obj) +{ + auto self = static_cast<BluCat::StaticModel*>(obj); + + std::array<VkDescriptorPoolSize, 1> descriptor_pool_sizes{}; + descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_pool_sizes[0].descriptorCount = + self->uniform_buffers.size(); + + VkDescriptorPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = self->uniform_buffers.size(); + pool_info.poolSizeCount = descriptor_pool_sizes.size(); + pool_info.pPoolSizes = descriptor_pool_sizes.data(); + + if(vkCreateDescriptorPool( + self->static_mesh->queue_family->device->device, &pool_info, nullptr, + &self->descriptor_pool) != VK_SUCCESS) + throw CommandError{"Failed to create a Vulkan descriptor pool."}; +} + +void +unload_descriptor_set_pool(void *obj) +{ + auto self = static_cast<BluCat::StaticModel*>(obj); + + vkDestroyDescriptorPool( + self->static_mesh->queue_family->device->device, self->descriptor_pool, + nullptr); +} + +void +load_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::StaticModel*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + cg_core.vk_descriptor_set_layout->model); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->descriptor_pool; + alloc_info.descriptorSetCount = layouts.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->descriptor_sets.resize(layouts.size()); + if(vkAllocateDescriptorSets( + self->static_mesh->queue_family->device->device, &alloc_info, + self->descriptor_sets.data()) != VK_SUCCESS) + CommandError{"Failed to create Vulkan descriptor set."}; +} + +void +load_buffers_to_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::StaticModel*>(obj); + + for(auto i{0}; i < self->uniform_buffers.size(); i++) + { + VkDescriptorBufferInfo buffer_info{}; + buffer_info.buffer = self->uniform_buffers[i].buffer; + buffer_info.offset = 0; + buffer_info.range = sizeof(BluCat::UDOStaticModel); + + std::array<VkWriteDescriptorSet, 1> write_descriptors{}; + write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[0].dstSet = self->descriptor_sets[i]; + write_descriptors[0].dstBinding = 0; + write_descriptors[0].dstArrayElement = 0; + write_descriptors[0].descriptorCount = 1; + write_descriptors[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptors[0].pBufferInfo = &buffer_info; + write_descriptors[0].pImageInfo = nullptr; + write_descriptors[0].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, write_descriptors.size(), + write_descriptors.data(), 0, nullptr); + } +} + +static const CommandChain loader{ + {&load_uniform_buffers, &unload_uniform_buffers}, + {&load_descriptor_set_pool, &unload_descriptor_set_pool}, + {&load_descriptor_sets, nullptr}, + {&load_buffers_to_descriptor_sets, nullptr} +}; + +} + +namespace BluCat +{ + +StaticModel::StaticModel( + std::shared_ptr<StaticMesh> static_mesh, + std::shared_ptr<Texture> texture, std::shared_ptr<glm::vec3> position, + std::shared_ptr<glm::quat> orientation): + static_mesh{static_mesh}, + texture{texture}, + position{position}, + orientation{orientation} +{ + loader.execute(this); +} + +StaticModel::~StaticModel() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/static_model.hpp b/src/blucat/static_model.hpp new file mode 100644 index 0000000..68c5ea1 --- /dev/null +++ b/src/blucat/static_model.hpp @@ -0,0 +1,49 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_STATIC_MODEL_H +#define CANDY_GEAR_BLUCAT_STATIC_MODEL_H 1 + +#include <memory> +#include <vector> + +#include "core.hpp" +#include "static_mesh.hpp" + +namespace BluCat +{ + +struct StaticModel +{ + std::shared_ptr<StaticMesh> static_mesh; + std::shared_ptr<Texture> texture; + std::vector<UniformBuffer> uniform_buffers; + std::shared_ptr<glm::vec3> position; + std::shared_ptr<glm::quat> orientation; + + VkDescriptorPool descriptor_pool; + std::vector<VkDescriptorSet> descriptor_sets; + + StaticModel( + std::shared_ptr<StaticMesh> static_mesh, + std::shared_ptr<Texture> texture, std::shared_ptr<glm::vec3> position, + std::shared_ptr<glm::quat> orientation); + ~StaticModel(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_STATIC_MODEL_H */ diff --git a/src/blucat/swapchain.cpp b/src/blucat/swapchain.cpp new file mode 100644 index 0000000..1e521e4 --- /dev/null +++ b/src/blucat/swapchain.cpp @@ -0,0 +1,204 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "swapchain.hpp" + +#include "../core.hpp" + +#include <vector> + +namespace +{ + +void +load_swapchain(void *obj) +{ + auto self = static_cast<BluCat::Swapchain*>(obj); + + // Surface formats. + uint32_t vk_surface_format_count; + std::vector<VkSurfaceFormatKHR> vk_surface_formats; + vkGetPhysicalDeviceSurfaceFormatsKHR( + cg_core.vk_device_with_swapchain->physical_device, cg_core.window_surface, + &vk_surface_format_count, nullptr); + vk_surface_formats.resize(vk_surface_format_count); + vkGetPhysicalDeviceSurfaceFormatsKHR( + cg_core.vk_device_with_swapchain->physical_device, cg_core.window_surface, + &vk_surface_format_count, vk_surface_formats.data()); + + VkSwapchainCreateInfoKHR swapchain_create_info = {}; + swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchain_create_info.pNext = nullptr; + swapchain_create_info.flags = 0; + swapchain_create_info.surface = cg_core.window_surface; + swapchain_create_info.minImageCount = 3; // triple buffering. + + self->image_format = vk_surface_formats[0].format; + swapchain_create_info.imageFormat = self->image_format; + swapchain_create_info.imageColorSpace = vk_surface_formats[0].colorSpace; + + swapchain_create_info.imageExtent = { + cg_core.display_width, cg_core.display_height}; + swapchain_create_info.imageArrayLayers = 1; + swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchain_create_info.queueFamilyIndexCount = 0; + swapchain_create_info.pQueueFamilyIndices = nullptr; + swapchain_create_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; + swapchain_create_info.clipped = VK_FALSE; + swapchain_create_info.oldSwapchain = VK_NULL_HANDLE; + + if(vkCreateSwapchainKHR( + cg_core.vk_device_with_swapchain->device, &swapchain_create_info, + nullptr, &self->swapchain) != VK_SUCCESS) + throw CommandError{"Vulkan failed to create swapchain."}; + + vkGetSwapchainImagesKHR( + cg_core.vk_device_with_swapchain->device, self->swapchain, + &self->images_count, nullptr); + self->images = new VkImage[self->images_count]; + vkGetSwapchainImagesKHR( + cg_core.vk_device_with_swapchain->device, self->swapchain, + &self->images_count, self->images); +} + +void +unload_swapchain(void *obj) +{ + auto self = static_cast<BluCat::Swapchain*>(obj); + + delete[] self->images; + vkDestroySwapchainKHR( + cg_core.vk_device_with_swapchain->device, self->swapchain, nullptr); +} + +void +load_image_view(void *obj) +{ + auto self = static_cast<BluCat::Swapchain*>(obj); + + self->image_views = new VkImageView[self->images_count]; + for(auto i{0}; i < self->images_count; i++) + { + VkImageViewCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + create_info.image = self->images[i]; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + create_info.format = self->image_format; + create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + create_info.subresourceRange.baseMipLevel = 0; + create_info.subresourceRange.levelCount = 1; + create_info.subresourceRange.baseArrayLayer = 0; + create_info.subresourceRange.layerCount = 1; + + if(vkCreateImageView( + cg_core.vk_device_with_swapchain->device, &create_info, nullptr, + &self->image_views[i])) + throw CommandError{"Could no create Image View for swapchain."}; + } +} + +void +unload_image_view(void *obj) +{ + auto self = static_cast<BluCat::Swapchain*>(obj); + + for(auto i{0}; i < self->images_count; i++) + vkDestroyImageView( + cg_core.vk_device_with_swapchain->device, self->image_views[i], nullptr); +} + +void +load_frame_sync(void *obj) +{ + auto self = static_cast<BluCat::Swapchain*>(obj); + + self->image_available_semaphores.resize(self->max_frames_in_flight); + self->render_finished_semaphores.resize(self->max_frames_in_flight); + self->in_flight_fences.resize(self->max_frames_in_flight); + + VkSemaphoreCreateInfo semaphore_info = {}; + semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphore_info.pNext = nullptr; + semaphore_info.flags = 0; + + VkFenceCreateInfo fence_info = {}; + fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fence_info.pNext = nullptr; + fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + // FIXME: if this loop fails, it will not destroy the semaphores. + for(auto i{0}; i < self->max_frames_in_flight; i++) + { + if(vkCreateSemaphore( + cg_core.vk_device_with_swapchain->device, &semaphore_info, + nullptr, &self->image_available_semaphores[i]) != VK_SUCCESS || + vkCreateSemaphore( + cg_core.vk_device_with_swapchain->device, &semaphore_info, + nullptr, &self->render_finished_semaphores[i]) != VK_SUCCESS || + vkCreateFence(cg_core.vk_device_with_swapchain->device, &fence_info, + nullptr, &self->in_flight_fences[i]) != VK_SUCCESS) + throw CommandError{"Failed to create semaphores."}; + } +} + +void +unload_frame_sync(void *obj) +{ + auto self = static_cast<BluCat::Swapchain*>(obj); + + vkDeviceWaitIdle(cg_core.vk_device_with_swapchain->device); + + for(auto i{0}; i < self->max_frames_in_flight; i++) + { + vkDestroySemaphore(cg_core.vk_device_with_swapchain->device, + self->render_finished_semaphores[i], nullptr); + vkDestroySemaphore(cg_core.vk_device_with_swapchain->device, + self->image_available_semaphores[i], nullptr); + vkDestroyFence(cg_core.vk_device_with_swapchain->device, + self->in_flight_fences[i], nullptr); + } +} + +const CommandChain loader{ + {&load_swapchain, &unload_swapchain}, + {&load_image_view, &unload_image_view}, + {&load_frame_sync, &unload_frame_sync} +}; + +} + +namespace BluCat +{ + +Swapchain::Swapchain(): + current_frame{0} +{ + loader.execute(this); +} + +Swapchain::~Swapchain() +{ + loader.revert(this); +} + +} diff --git a/src/blucat/swapchain.hpp b/src/blucat/swapchain.hpp new file mode 100644 index 0000000..979fe08 --- /dev/null +++ b/src/blucat/swapchain.hpp @@ -0,0 +1,47 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_SWAPCHAIN_H +#define CANDY_GEAR_BLUCAT_SWAPCHAIN_H 1 + +#include "core.hpp" +#include "../command.hpp" + +namespace BluCat +{ + +struct Swapchain +{ + VkSwapchainKHR swapchain; + VkFormat image_format; + + uint32_t images_count; + VkImage *images; + VkImageView *image_views; + + static const int max_frames_in_flight{2}; + size_t current_frame; + std::vector<VkSemaphore> image_available_semaphores; + std::vector<VkSemaphore> render_finished_semaphores; + std::vector<VkFence> in_flight_fences; + + Swapchain(); + ~Swapchain(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_SWAPCHAIN_H */ diff --git a/src/blucat/texture.cpp b/src/blucat/texture.cpp new file mode 100644 index 0000000..1213528 --- /dev/null +++ b/src/blucat/texture.cpp @@ -0,0 +1,559 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "texture.hpp" + +#include "../command.hpp" +#include "../core.hpp" +#include "image.hpp" +#include "qoi.hpp" +#include "source_buffer.hpp" + +namespace +{ + +inline void +create_vulkan_image( + VkImage *image, VkDeviceMemory *device_memory, int width, int height, + uint32_t mip_levels) +{ + VkExtent3D vk_extent3d{}; + vk_extent3d.width = width; + vk_extent3d.height = height; + vk_extent3d.depth = 1; + + BluCat::Image::create( + cg_core.vk_device_with_swapchain, + image, + device_memory, + VK_FORMAT_R8G8B8A8_UNORM, + vk_extent3d, + mip_levels, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); +} + +struct ImageBuilder +{ + BluCat::Texture *texture; +}; + +struct ImageTextureBuilder: public ImageBuilder +{ + std::string texture_path; + + ImageTextureBuilder(BluCat::Texture *t, std::string tp); + ImageTextureBuilder(BluCat::Texture *t, const char* tp); +}; + +ImageTextureBuilder::ImageTextureBuilder(BluCat::Texture *t, std::string tp): + texture_path{tp} +{ + this->texture = t; +} + +ImageTextureBuilder::ImageTextureBuilder(BluCat::Texture *t, const char* tp): + ImageTextureBuilder{t, std::string(tp)} +{ +} + +void +load_image(void *obj) +{ + auto self = static_cast<ImageTextureBuilder*>(obj); + + const int num_channels = 4; // all images are converted to RGBA + BluCat::QOI::Image qoi_image(self->texture_path.c_str(), num_channels); + uint8_t *pixels; + + { // Load file image from file. + self->texture->width = qoi_image.header.width; + self->texture->height = qoi_image.header.height; + self->texture->mip_levels = 1; + + pixels = qoi_image.pixels; + } + + // Load file image into a vulkan buffer. + size_t image_size{static_cast<size_t>( + qoi_image.header.width * qoi_image.header.height * num_channels)}; + BluCat::SourceBuffer source_image_buffer{ + cg_core.vk_device_with_swapchain, pixels, image_size}; + + { // Create vulkan image. + try + { + create_vulkan_image( + &self->texture->image, + &self->texture->device_memory, + self->texture->width, + self->texture->height, + self->texture->mip_levels); + } + catch(BluCat::Image::Error error) + { + throw CommandError{error.what()}; + } + } + + // Copy image from vulkan buffer into vulkan image. + { + auto queue_family = self->texture->queue_family; + auto queue{queue_family->get_queue()}; + BluCat::CommandPool command_pool{queue_family, 1}; + VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; + + queue.submit_one_time_command(vk_command_buffer, [&](){ + BluCat::Image::move_image_state( + vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferImageCopy image_copy{}; + image_copy.bufferOffset = 0; + image_copy.bufferRowLength = 0; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_copy.imageSubresource.mipLevel = 0; + image_copy.imageSubresource.baseArrayLayer = 0; + image_copy.imageSubresource.layerCount = 1; + image_copy.imageOffset = {0, 0, 0}; + image_copy.imageExtent = { + self->texture->width, self->texture->height, 1}; + + vkCmdCopyBufferToImage( + vk_command_buffer, source_image_buffer.buffer, self->texture->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); + + BluCat::Image::move_image_state( + vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + }); + } +} + +void +unload_image(void *obj) +{ + auto self = static_cast<ImageBuilder*>(obj); + + vkDestroyImage( + cg_core.vk_device_with_swapchain->device, self->texture->image, nullptr); + vkFreeMemory( + cg_core.vk_device_with_swapchain->device, self->texture->device_memory, + nullptr); +} + +void +load_sampler(void *obj) +{ + auto self = static_cast<ImageBuilder*>(obj); + + VkSamplerCreateInfo sampler_info{}; + sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler_info.pNext = nullptr; + sampler_info.flags = 0; + sampler_info.magFilter = VK_FILTER_LINEAR; + sampler_info.minFilter = VK_FILTER_LINEAR; + sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.mipLodBias = 0.0f; + sampler_info.anisotropyEnable = VK_TRUE; + sampler_info.maxAnisotropy = 16; + sampler_info.compareEnable = VK_FALSE; + sampler_info.compareOp = VK_COMPARE_OP_NEVER; + sampler_info.minLod = 0.0f; + sampler_info.maxLod = 0.0f; + sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + sampler_info.unnormalizedCoordinates = VK_FALSE; + + if(vkCreateSampler( + cg_core.vk_device_with_swapchain->device, &sampler_info, nullptr, + &self->texture->sampler) != VK_SUCCESS) + throw CommandError{"Failed to create texture sampler."}; +} + +void +unload_sampler(void *obj) +{ + auto self = static_cast<ImageBuilder*>(obj); + + vkDestroySampler( + cg_core.vk_device_with_swapchain->device, self->texture->sampler, nullptr); +} + +void +load_view(void *obj) +{ + auto self = static_cast<ImageBuilder*>(obj); + + try + { + BluCat::Image::create_view( + cg_core.vk_device_with_swapchain, &self->texture->view, + self->texture->image, + VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); + } + catch(BluCat::Image::Error error) + { + throw CommandError{error.what()}; + } +} + +void +unload_view(void *obj) +{ + auto self = static_cast<ImageBuilder*>(obj); + + vkDestroyImageView( + cg_core.vk_device_with_swapchain->device, self->texture->view, nullptr); +} + +const CommandChain image_loader{ + {&load_image, &unload_image}, + {&load_sampler, &unload_sampler}, + {&load_view, &unload_view} +}; + +struct CharacterToDraw +{ + int pos_x; + std::shared_ptr<BluCat::Character> character; + + CharacterToDraw(int x, std::shared_ptr<BluCat::Character> character): + pos_x{x}, + character{character} + {}; +}; + +struct TextTextureBuilder: public ImageBuilder +{ + BluCat::Font *font; + const char* str; + uint32_t max_bearing_y; + std::vector<CharacterToDraw> chars_to_draw; + + TextTextureBuilder(BluCat::Texture *texture, BluCat::Font *font, const char* str): + font{font}, + str{str} + { + this->texture = texture; + } +}; + +void +load_text_proportions(void *obj) +{ + auto self = static_cast<TextTextureBuilder*>(obj); + + uint32_t texture_width{0}, texture_descender{0}; + auto unicode_text{BluCat::Character::str_to_unicode(self->str)}; + + auto first_image{self->font->character(unicode_text[0])}; + if(first_image->bearing_x < 0) texture_width = - first_image->bearing_x; + + self->max_bearing_y = 0; + self->chars_to_draw.reserve(unicode_text.size()); + + // FIXME: I need to test several different fonts to find all bugs in this + // code. + std::shared_ptr<BluCat::Character> char_image{}; + { // Calculate image size + int max_height; + for(auto char_code : unicode_text) + { + char_image = self->font->character(char_code); + uint32_t descender{char_image->height - char_image->bearing_y}; + uint32_t pos_x{texture_width + char_image->bearing_x}; + + if(char_image->image != VK_NULL_HANDLE) + self->chars_to_draw.emplace_back(pos_x, char_image); + + if(char_image->bearing_y > self->max_bearing_y) + self->max_bearing_y = char_image->bearing_y; + if(descender > texture_descender) texture_descender = descender; + + texture_width += char_image->advance; + } + } + + { // Restore image width if last character have a negative bearing. + int bearing_x_pluss_width = char_image->bearing_x + char_image->width; + if(bearing_x_pluss_width > char_image->advance) + texture_width += bearing_x_pluss_width - char_image->advance; + } + + self->texture->width = texture_width; + self->texture->height = self->max_bearing_y + texture_descender; + self->texture->mip_levels = 1; +} + +void +load_text_image(void *obj) +{ + auto self = static_cast<TextTextureBuilder*>(obj); + + const int NumChannels = 4; + + size_t image_size{static_cast<size_t>( + self->texture->width * self->texture->height * NumChannels)}; + std::vector<unsigned char> pixels(image_size); + for(auto x{0}; x < self->texture->width; x++) + { + for(auto y{0}; y < self->texture->height; y++) + { + auto image_coord = y * self->font->face->glyph->bitmap.width + + x * NumChannels; + pixels[image_coord] = 0; // Red + pixels[image_coord + 1] = 0; // Green + pixels[image_coord + 2] = 0; // Blue + pixels[image_coord + 3] = 0; // Alpha + } + } + BluCat::SourceBuffer source_image_buffer{ + cg_core.vk_device_with_swapchain, pixels.data(), image_size}; + + { // Create vulkan image. + try + { + create_vulkan_image( + &self->texture->image, + &self->texture->device_memory, + self->texture->width, + self->texture->height, + self->texture->mip_levels); + } + catch(BluCat::Image::Error error) + { + throw CommandError{error.what()}; + } + } + + { // Render text + auto queue_family{ + cg_core.vk_device_with_swapchain->get_queue_family_with_presentation()}; + auto queue{queue_family->get_queue()}; + BluCat::CommandPool command_pool{queue_family, 1}; + VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; + + queue.submit_one_time_command(vk_command_buffer, [&](){ + BluCat::Image::move_image_state( + vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkBufferImageCopy image_copy{}; + image_copy.bufferOffset = 0; + image_copy.bufferRowLength = 0; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_copy.imageSubresource.mipLevel = 0; + image_copy.imageSubresource.baseArrayLayer = 0; + image_copy.imageSubresource.layerCount = 1; + image_copy.imageOffset = {0, 0, 0}; + image_copy.imageExtent = {self->texture->width, self->texture->height, 1}; + + vkCmdCopyBufferToImage( + vk_command_buffer, source_image_buffer.buffer, self->texture->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); + + for(auto &to_draw: self->chars_to_draw) + { + VkImageSubresourceLayers + source_subresources{}, destination_subresources{}; + source_subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + source_subresources.mipLevel = 0; + source_subresources.baseArrayLayer = 0; + source_subresources.layerCount = 1; + + destination_subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + destination_subresources.mipLevel = 0; + destination_subresources.baseArrayLayer = 0; + destination_subresources.layerCount = 1; + + VkImageCopy image_copy{}; + image_copy.srcSubresource = source_subresources; + image_copy.srcOffset = {0, 0, 0}; + image_copy.dstSubresource = destination_subresources; + image_copy.dstOffset = { + to_draw.pos_x, + (int)(self->max_bearing_y - to_draw.character->bearing_y), + 0}; + image_copy.extent = { + to_draw.character->width, to_draw.character->height, 1}; + + vkCmdCopyImage( + vk_command_buffer, + to_draw.character->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + self->texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &image_copy); + } + + BluCat::Image::move_image_state( + vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + }); + } +} + +const CommandChain text_loader{ + {&load_text_proportions, nullptr}, + {&load_text_image, &unload_image}, + {&load_sampler, &unload_sampler}, + {&load_view, &unload_view} +}; + +void +load_descriptor_set_pool(void *obj) +{ + auto self = static_cast<BluCat::Texture*>(obj); + + std::array<VkDescriptorPoolSize, 1> descriptor_pool_sizes{}; + descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptor_pool_sizes[0].descriptorCount = + cg_core.vk_swapchain->images_count; + + VkDescriptorPoolCreateInfo pool_info{}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = 0; + pool_info.maxSets = cg_core.vk_swapchain->images_count; + pool_info.poolSizeCount = descriptor_pool_sizes.size(); + pool_info.pPoolSizes = descriptor_pool_sizes.data(); + + if(vkCreateDescriptorPool( + self->queue_family->device->device, &pool_info, nullptr, + &self->descriptor_pool) != VK_SUCCESS) + throw CommandError{"Failed to create a Vulkan descriptor pool."}; +} + +void +unload_descriptor_set_pool(void *obj) +{ + auto self = static_cast<BluCat::Texture*>(obj); + + vkDestroyDescriptorPool( + self->queue_family->device->device, self->descriptor_pool, nullptr); +} + +void +load_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::Texture*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + cg_core.vk_descriptor_set_layout->texture); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->descriptor_pool; + alloc_info.descriptorSetCount = layouts.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->descriptor_sets.resize(layouts.size()); + if(vkAllocateDescriptorSets( + self->queue_family->device->device, &alloc_info, + self->descriptor_sets.data()) != VK_SUCCESS) + CommandError{"Failed to create Vulkan descriptor set."}; +} + +void +load_data_to_descriptor_sets(void *obj) +{ + auto self = static_cast<BluCat::Texture*>(obj); + + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + { + VkDescriptorImageInfo image_info{}; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_info.imageView = self->view; + image_info.sampler = self->sampler; + + std::array<VkWriteDescriptorSet, 1> write_descriptors{}; + write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[0].dstSet = self->descriptor_sets[i]; + write_descriptors[0].dstBinding = 0; + write_descriptors[0].dstArrayElement = 0; + write_descriptors[0].descriptorCount = 1; + write_descriptors[0].descriptorType = + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_descriptors[0].pBufferInfo = nullptr; + write_descriptors[0].pImageInfo = &image_info; + write_descriptors[0].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, write_descriptors.size(), + write_descriptors.data(), 0, nullptr); + } +} + +const CommandChain descriptor_loader{ + {&load_descriptor_set_pool, &unload_descriptor_set_pool}, + {&load_descriptor_sets, nullptr}, + {&load_data_to_descriptor_sets, nullptr} +}; + +} + +namespace BluCat +{ + +Texture::Texture(Font *font, const char* str) +{ + this->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_presentation(); + + TextTextureBuilder text_builder(this, font, str); + text_loader.execute(&text_builder); + descriptor_loader.execute(this); +} + +Texture::Texture(std::string texture_path) +{ + this->queue_family = + cg_core.vk_device_with_swapchain->get_queue_family_with_presentation(); + + ImageTextureBuilder texture_builder(this, texture_path); + image_loader.execute(&texture_builder); + descriptor_loader.execute(this); +} + +Texture::Texture(const char* texture_path): + Texture{std::string(texture_path)} +{ +} + +Texture::~Texture() +{ + ImageTextureBuilder texture_builder(this, ""); + image_loader.revert(&texture_builder); + descriptor_loader.revert(this); +} + +} diff --git a/src/blucat/texture.hpp b/src/blucat/texture.hpp new file mode 100644 index 0000000..bee61e6 --- /dev/null +++ b/src/blucat/texture.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_TEXTURE_H +#define CANDY_GEAR_BLUCAT_TEXTURE_H 1 + +#include <string> + +#include "core.hpp" +#include "font.hpp" +#include "queue_family.hpp" + +namespace BluCat +{ + +struct Texture +{ + QueueFamily *queue_family; + + VkImage image; + VkSampler sampler; + VkImageView view; + VkDeviceMemory device_memory; + uint32_t width, height; + uint32_t mip_levels; + + VkDescriptorPool descriptor_pool; + std::vector<VkDescriptorSet> descriptor_sets; + + Texture(Font *font, const char *str); + Texture(std::string texture_path); + Texture(const char* texture_path); + ~Texture(); +}; + +} + +#endif /* CANDY_GEAR_TEXTURE_H */ diff --git a/src/blucat/uniform_buffer.cpp b/src/blucat/uniform_buffer.cpp new file mode 100644 index 0000000..4b6194a --- /dev/null +++ b/src/blucat/uniform_buffer.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uniform_buffer.hpp" + +#include <cstring> +#include <stdexcept> + +namespace BluCat +{ + +UniformBuffer::UniformBuffer(Device *device, VkDeviceSize data_size) +{ + this->device = device; + this->device_size = data_size; + this->buffer_usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + this->memory_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + try + { + BaseBuffer::loader.execute(static_cast<BaseBuffer*>(this)); + } + catch(const CommandError &command_error) + { + std::string error{"Could not initialize Vulkan uniform buffer → "}; + error += command_error.what(); + throw CommandError{error}; + } +} + +UniformBuffer::~UniformBuffer() +{ + BaseBuffer::loader.revert(static_cast<BaseBuffer*>(this)); +} + +UniformBuffer::UniformBuffer(UniformBuffer &&that) +{ + this->device = that.device; + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; +} + +UniformBuffer& +UniformBuffer::operator=(UniformBuffer &&that) +{ + this->device = that.device; + this->buffer = that.buffer; + this->device_memory = that.device_memory; + this->device_size = that.device_size; + this->buffer_usage = that.buffer_usage; + this->memory_properties = that.memory_properties; + + that.buffer = VK_NULL_HANDLE; + that.device_memory = VK_NULL_HANDLE; + + return *this; +} + +void +UniformBuffer::copy_data(void *ubo) +{ + void *data; + vkMapMemory(this->device->device, this->device_memory, 0, + this->device_size, 0, &data); + memcpy(data, ubo, this->device_size); + vkUnmapMemory(this->device->device, this->device_memory); +} + +} diff --git a/src/blucat/uniform_buffer.hpp b/src/blucat/uniform_buffer.hpp new file mode 100644 index 0000000..2131ee1 --- /dev/null +++ b/src/blucat/uniform_buffer.hpp @@ -0,0 +1,49 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_UNIFORM_BUFFER_H +#define CANDY_GEAR_BLUCAT_UNIFORM_BUFFER_H 1 + +#include <memory> + +#include "core.hpp" + +#include "base_buffer.hpp" + +namespace BluCat +{ + +// FIXME: this class need to delete or create custom copy constructors! +class UniformBuffer: public BaseBuffer +{ + UniformBuffer(const UniformBuffer &t) = delete; + UniformBuffer& + operator=(const UniformBuffer &t) = delete; + +public: + UniformBuffer(Device *device, VkDeviceSize data_size); + ~UniformBuffer(); + + UniformBuffer(UniformBuffer &&that); + UniformBuffer& + operator=(UniformBuffer &&that); + + void copy_data(void* ubo); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_UNIFORM_BUFFER_H */ diff --git a/src/blucat/uniform_data_object.hpp b/src/blucat/uniform_data_object.hpp new file mode 100644 index 0000000..d2292b1 --- /dev/null +++ b/src/blucat/uniform_data_object.hpp @@ -0,0 +1,80 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_UNIFORM_DATA_OBJECT_H +#define CANDY_GEAR_BLUCAT_UNIFORM_DATA_OBJECT_H 1 + +#include "core.hpp" +#include "skeletal_mesh_vertex.hpp" + +namespace BluCat +{ + +// UDO = "uniform data object" + +struct UDOView2D +{ + glm::mat4 proj; +}; + +struct UDOView3D +{ + glm::mat4 view; + glm::mat4 proj; +}; + +struct UDOWorld3D_Vert +{ + glm::vec4 ambient_light_color; +}; + +struct UDOWorld3D_Frag +{ + glm::vec3 directional_light_direction; + glm::vec4 directional_light_color; +}; + +struct UDOStaticModel +{ + glm::mat4 base_matrix; +}; + +struct UDOSkeletalModel +{ + glm::mat4 base_matrix; + glm::mat4 bone_matrices[SKELETAL_MESH_MAX_NUM_OF_BONES]; +}; + +struct UDOVector4D +{ + glm::vec4 vector; +}; + +struct UDOVector3D +{ + glm::vec3 vectors; +}; + +struct UDOSprite3D +{ + glm::vec3 position; + uint32_t padding; + glm::vec2 size; +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_UNIFORM_DATA_OBJECT_H */ diff --git a/src/blucat/view_2d.cpp b/src/blucat/view_2d.cpp new file mode 100644 index 0000000..f27b4c4 --- /dev/null +++ b/src/blucat/view_2d.cpp @@ -0,0 +1,160 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "view_2d.hpp" + +#include <array> + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_2d_uniform_buffer(void *obj) +{ + auto self = static_cast<BluCat::View2D*>(obj); + + try + { + self->ub_2d.reserve(cg_core.vk_swapchain->images_count); + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->ub_2d.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(BluCat::UDOView2D)); + } + catch(const std::exception& e) + { + throw CommandError{e.what()}; + } +} + +void +unload_2d_uniform_buffer(void *obj) +{ + auto self = static_cast<BluCat::View2D*>(obj); + + self->ub_2d.clear(); +} + +void +load_descriptor_sets_2d(void *obj) +{ + auto self = static_cast<BluCat::View2D*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + cg_core.vk_descriptor_set_layout->view); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->descriptor_pool; + alloc_info.descriptorSetCount = layouts.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->descriptor_sets_2d.resize(layouts.size()); + if(vkAllocateDescriptorSets( + cg_core.vk_device_with_swapchain->device, &alloc_info, + self->descriptor_sets_2d.data()) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan descriptor sets for view."}; +} + +void +load_resources_to_descriptor_sets_2d(void *obj) +{ + auto self = static_cast<BluCat::View2D*>(obj); + + for(auto i{0}; i < self->ub_2d.size(); i++) + { + VkDescriptorBufferInfo view_2d_info{}; + view_2d_info.buffer = self->ub_2d[i].buffer; + view_2d_info.offset = 0; + view_2d_info.range = sizeof(BluCat::UDOView2D); + + std::array<VkWriteDescriptorSet, 1> write_descriptors{}; + write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[0].dstSet = self->descriptor_sets_2d[i]; + write_descriptors[0].dstBinding = 0; + write_descriptors[0].dstArrayElement = 0; + write_descriptors[0].descriptorCount = 1; + write_descriptors[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptors[0].pBufferInfo = &view_2d_info; + write_descriptors[0].pImageInfo = nullptr; + write_descriptors[0].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, write_descriptors.size(), + write_descriptors.data(), 0, nullptr); + + BluCat::UDOView2D ubo_view_2d; + ubo_view_2d.proj = glm::ortho( + 0.0f, self->projection_width, + 0.0f, self->projection_height, + 0.0f, 100.0f); + self->ub_2d[i].copy_data(&ubo_view_2d); + } +} + +} + +namespace BluCat +{ + +const CommandChain View2D::loader{ + {&load_2d_uniform_buffer, &unload_2d_uniform_buffer} +}; + +const CommandChain View2D::descriptor_sets_loader{ + {&load_descriptor_sets_2d, nullptr}, + {&load_resources_to_descriptor_sets_2d, nullptr} +}; + +View2D::View2D( + glm::vec4 region, float projection_width, float projection_height): + projection_width{projection_width}, + projection_height{projection_height}, + region{region}, + descriptor_pool{VK_NULL_HANDLE}, + rectangles_to_draw{cg_core.vk_swapchain->images_count}, + sprites_to_draw{cg_core.vk_swapchain->images_count} +{ + loader.execute(this); +} + +View2D::~View2D() +{ + loader.revert(this); +} + +void +View2D::load_descriptor_sets(VkDescriptorPool descriptor_pool) +{ + if(this->descriptor_pool != VK_NULL_HANDLE) return; + + this->descriptor_pool = descriptor_pool; + descriptor_sets_loader.execute(this); +} + +void +View2D::unload_descriptor_sets() +{ + if(this->descriptor_pool == VK_NULL_HANDLE) return; + + this->descriptor_pool = VK_NULL_HANDLE; + descriptor_sets_loader.revert(this); +} + +} diff --git a/src/blucat/view_2d.hpp b/src/blucat/view_2d.hpp new file mode 100644 index 0000000..b369334 --- /dev/null +++ b/src/blucat/view_2d.hpp @@ -0,0 +1,60 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_VIEW_2D_H +#define CANDY_GEAR_BLUCAT_VIEW_2D_H 1 + +#include <memory> +#include <unordered_map> +#include <vector> + +#include "core.hpp" +#include "sprite_to_draw.hpp" +#include "rectangle.hpp" + +namespace BluCat +{ + +struct View2D +{ + glm::vec4 region; + float projection_width, projection_height; + + // FIXME: if these vectors get resized, they can cause a segmentation fault! + std::vector<UniformBuffer> ub_2d; + + VkDescriptorPool descriptor_pool; + std::vector<VkDescriptorSet> descriptor_sets_2d; + + std::vector<std::vector<Rectangle>> rectangles_to_draw; + std::vector<std::vector<SpriteToDraw>> sprites_to_draw; + + View2D(glm::vec4 region, float projection_width, float projection_height); + virtual ~View2D(); + + void + virtual load_descriptor_sets(VkDescriptorPool descriptor_pool); + + void + virtual unload_descriptor_sets(); + +protected: + static const CommandChain loader, descriptor_sets_loader; +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_VIEW_2D_H */ diff --git a/src/blucat/view_3d.cpp b/src/blucat/view_3d.cpp new file mode 100644 index 0000000..4b7b959 --- /dev/null +++ b/src/blucat/view_3d.cpp @@ -0,0 +1,155 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "view_3d.hpp" + +#include <array> + +#include "../core.hpp" +#include "uniform_data_object.hpp" + +namespace +{ + +void +load_3d_uniform_buffer(void *obj) +{ + auto self = static_cast<BluCat::View3D*>(obj); + + try + { + self->ub_3d.reserve(cg_core.vk_swapchain->images_count); + for(auto i{0}; i < cg_core.vk_swapchain->images_count; i++) + self->ub_3d.emplace_back( + cg_core.vk_device_with_swapchain, sizeof(BluCat::UDOView3D)); + } + catch(const std::exception& e) + { + throw CommandError{e.what()}; + } +} + +void +unload_3d_uniform_buffer(void *obj) +{ + auto self = static_cast<BluCat::View3D*>(obj); + + self->ub_3d.clear(); +} + +void +load_descriptor_sets_3d(void *obj) +{ + auto self = static_cast<BluCat::View3D*>(obj); + + std::vector<VkDescriptorSetLayout> layouts( + cg_core.vk_swapchain->images_count, + cg_core.vk_descriptor_set_layout->view); + + VkDescriptorSetAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = self->descriptor_pool; + alloc_info.descriptorSetCount = layouts.size(); + alloc_info.pSetLayouts = layouts.data(); + + self->descriptor_sets_3d.resize(layouts.size()); + if(vkAllocateDescriptorSets( + cg_core.vk_device_with_swapchain->device, &alloc_info, + self->descriptor_sets_3d.data()) != VK_SUCCESS) + throw CommandError{"Failed to create Vulkan descriptor sets for view."}; +} + +void +load_resources_to_descriptor_sets_3d(void *obj) +{ + auto self = static_cast<BluCat::View3D*>(obj); + + for(auto i{0}; i < self->ub_3d.size(); i++) + { + VkDescriptorBufferInfo view_3d_info{}; + view_3d_info.buffer = self->ub_3d[i].buffer; + view_3d_info.offset = 0; + view_3d_info.range = sizeof(BluCat::UDOView3D); + + std::array<VkWriteDescriptorSet, 1> write_descriptors{}; + write_descriptors[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptors[0].dstSet = self->descriptor_sets_3d[i]; + write_descriptors[0].dstBinding = 0; + write_descriptors[0].dstArrayElement = 0; + write_descriptors[0].descriptorCount = 1; + write_descriptors[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptors[0].pBufferInfo = &view_3d_info; + write_descriptors[0].pImageInfo = nullptr; + write_descriptors[0].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets( + cg_core.vk_device_with_swapchain->device, write_descriptors.size(), + write_descriptors.data(), 0, nullptr); + } +} + +const CommandChain loader{ + {&load_3d_uniform_buffer, &unload_3d_uniform_buffer} +}; + +const CommandChain descriptor_sets_loader{ + {&load_descriptor_sets_3d, nullptr}, + {&load_resources_to_descriptor_sets_3d, nullptr} +}; + +} + +namespace BluCat +{ + +View3D::View3D( + glm::vec4 region, float projection_width, float projection_height): + View2D{region, projection_width, projection_height}, + field_of_view{45.0f}, + camera_position{std::make_shared<glm::vec3>(0.0f, 0.0f, 0.0f)}, + camera_orientation{std::make_shared<glm::quat>(0.0f, 0.0f, 0.0f, 0.0f)} +{ + ::loader.execute(this); +} + +View3D::~View3D() +{ + ::loader.revert(this); +} + +void +View3D::load_descriptor_sets(VkDescriptorPool descriptor_pool) +{ + if(this->descriptor_pool != VK_NULL_HANDLE) return; + + auto parent = dynamic_cast<BluCat::View2D*>(this); + this->descriptor_pool = descriptor_pool; + View2D::descriptor_sets_loader.execute(parent); + ::descriptor_sets_loader.execute(this); +} + +void +View3D::unload_descriptor_sets() +{ + if(this->descriptor_pool == VK_NULL_HANDLE) return; + + auto parent = dynamic_cast<BluCat::View2D*>(this); + this->descriptor_pool = VK_NULL_HANDLE; + ::descriptor_sets_loader.revert(this); + View2D::descriptor_sets_loader.revert(parent); +} + +} diff --git a/src/blucat/view_3d.hpp b/src/blucat/view_3d.hpp new file mode 100644 index 0000000..05cae6b --- /dev/null +++ b/src/blucat/view_3d.hpp @@ -0,0 +1,48 @@ +/* + * Copyright 2022-2024 Frederico de Oliveira Linhares + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CANDY_GEAR_BLUCAT_VIEW_3D_H +#define CANDY_GEAR_BLUCAT_VIEW_3D_H 1 + +#include "view_2d.hpp" + +namespace BluCat +{ + +struct View3D: public View2D +{ + float field_of_view; + // FIXME: if this vector get resized, it can cause a segmentation fault! + std::vector<UniformBuffer> ub_3d; + + std::vector<VkDescriptorSet> descriptor_sets_3d; + + std::shared_ptr<glm::vec3> camera_position; + std::shared_ptr<glm::quat> camera_orientation; + + View3D(glm::vec4 region, float projection_width, float projection_height); + ~View3D(); + + void + load_descriptor_sets(VkDescriptorPool descriptor_pool); + + void + unload_descriptor_sets(); +}; + +} + +#endif /* CANDY_GEAR_BLUCAT_VIEW_3D_H */ |