mirror of
https://github.com/bailwillharr/engine.git
synced 2024-09-21 04:51:18 +00:00
Add textures
This commit is contained in:
parent
7cc09484c1
commit
d71254985b
@ -23,7 +23,7 @@ namespace engine {
|
||||
|
||||
// adds a draw call to the queue
|
||||
// vertexBuffer is required, indexBuffer can be NULL, uniformData is required
|
||||
void draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize);
|
||||
void draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize, const gfx::Texture* texture);
|
||||
|
||||
// Call once per frame. Executes all queued draw calls and renders to the screen.
|
||||
void renderFrame();
|
||||
|
@ -14,6 +14,8 @@ public:
|
||||
Texture(const std::filesystem::path& resPath);
|
||||
~Texture() override;
|
||||
|
||||
gfx::Texture* getHandle();
|
||||
|
||||
private:
|
||||
gfx::Texture* m_gpuTexture;
|
||||
};
|
||||
|
@ -77,7 +77,6 @@ void Camera::usePerspective(float fovDeg)
|
||||
|
||||
float fovRad = glm::radians(fovDeg);
|
||||
m_projMatrix = glm::perspectiveFovRH_ZO(fovRad, viewportDim.x, viewportDim.y, NEAR, FAR);
|
||||
m_projMatrix[1][1] *= -1;
|
||||
}
|
||||
|
||||
void Camera::useOrtho()
|
||||
@ -88,7 +87,6 @@ void Camera::useOrtho()
|
||||
float aspect = viewportDim.x / viewportDim.y;
|
||||
|
||||
m_projMatrix = glm::orthoRH_ZO(-10.0f * aspect, 10.0f * aspect, -10.0f, 10.0f, -100.0f, 100.0f);
|
||||
m_projMatrix[1][1] *= -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace engine::components {
|
||||
Renderer::Renderer(Object* parent) : Component(parent, TypeEnum::RENDERER)
|
||||
{
|
||||
m_shader = this->parent.res.get<resources::Shader>("shader.glsl");
|
||||
// m_texture = this->parent.res.get<resources::Texture>("textures/missing.png");
|
||||
m_texture = this->parent.res.get<resources::Texture>("textures/missing.png");
|
||||
}
|
||||
|
||||
Renderer::~Renderer()
|
||||
@ -32,7 +32,7 @@ void Renderer::render(glm::mat4 transform, glm::mat4 view)
|
||||
gfxdev->updateUniformBuffer(m_shader->getPipeline(), &uniformData.color, sizeof(uniformData.color), offsetof(resources::Shader::UniformBuffer, color));
|
||||
|
||||
glm::mat4 pushConsts[] = { transform, view };
|
||||
gfxdev->draw(m_shader->getPipeline(), m_mesh->vb, m_mesh->ib, m_mesh->m_vertices.size(), pushConsts, sizeof(glm::mat4) * 2);
|
||||
gfxdev->draw(m_shader->getPipeline(), m_mesh->vb, m_mesh->ib, m_mesh->m_indices.size(), pushConsts, sizeof(glm::mat4) * 2, m_texture->getHandle());
|
||||
}
|
||||
|
||||
void Renderer::setMesh(const std::string& name)
|
||||
|
@ -58,7 +58,6 @@ namespace engine {
|
||||
VkQueue handle;
|
||||
};
|
||||
|
||||
|
||||
struct DepthBuffer {
|
||||
VkImage image;
|
||||
VmaAllocation allocation;
|
||||
@ -91,6 +90,7 @@ namespace engine {
|
||||
const gfx::Buffer* indexBuffer = nullptr; // if this is nullptr, don't use indexed
|
||||
uint32_t count = 0;
|
||||
uint8_t pushConstantData[PUSH_CONSTANT_MAX_SIZE];
|
||||
const gfx::Texture* texture = nullptr;
|
||||
};
|
||||
|
||||
enum class QueueFlags : uint32_t {
|
||||
@ -119,6 +119,11 @@ namespace engine {
|
||||
struct gfx::Texture {
|
||||
VkImage image;
|
||||
VmaAllocation alloc;
|
||||
VkImageView imageView;
|
||||
VkSampler sampler;
|
||||
VkDescriptorPool pool;
|
||||
std::array<VkDescriptorSet, FRAMES_IN_FLIGHT> descriptorSets{};
|
||||
uint32_t mipLevels;
|
||||
};
|
||||
|
||||
|
||||
@ -164,7 +169,6 @@ namespace engine {
|
||||
options.SetOptimizationLevel(shaderc_optimization_level_size);
|
||||
options.SetTargetSpirv(shaderc_spirv_version_1_6);
|
||||
options.SetAutoBindUniforms(false);
|
||||
options.SetInvertY(false);
|
||||
|
||||
// preprocess
|
||||
shaderc::PreprocessedSourceCompilationResult preprocessed = compiler.PreprocessGlsl(source, kind, filename, options);
|
||||
@ -774,7 +778,7 @@ namespace engine {
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
|
||||
}
|
||||
|
||||
static void cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImageLayout oldLayout, VkImageLayout newLayout, VkImage image)
|
||||
static void cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels, VkImage image)
|
||||
{
|
||||
|
||||
VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
|
||||
@ -785,7 +789,7 @@ namespace engine {
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.levelCount = mipLevels;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
|
||||
@ -814,6 +818,77 @@ namespace engine {
|
||||
|
||||
}
|
||||
|
||||
static void cmdGenerateMipmaps(VkCommandBuffer commandBuffer, VkImage image, int32_t width, int32_t height, uint32_t mipLevels)
|
||||
{
|
||||
|
||||
VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
|
||||
barrier.image = image;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
|
||||
int32_t mipWidth = width;
|
||||
int32_t mipHeight = height;
|
||||
for (uint32_t i = 1; i < mipLevels; i++) {
|
||||
barrier.subresourceRange.baseMipLevel = i - 1;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &barrier);
|
||||
|
||||
VkImageBlit blit{};
|
||||
blit.srcOffsets[0] = { 0, 0, 0 };
|
||||
blit.srcOffsets[1] = { mipWidth, mipHeight, 1 };
|
||||
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
blit.srcSubresource.mipLevel = i - 1;
|
||||
blit.srcSubresource.baseArrayLayer = 0;
|
||||
blit.srcSubresource.layerCount = 1;
|
||||
blit.dstOffsets[0] = { 0, 0, 0 };
|
||||
blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 };
|
||||
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
blit.dstSubresource.mipLevel = i;
|
||||
blit.dstSubresource.baseArrayLayer = 0;
|
||||
blit.dstSubresource.layerCount = 1;
|
||||
|
||||
vkCmdBlitImage(commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR);
|
||||
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &barrier);
|
||||
|
||||
if (mipWidth > 1) mipWidth /= 2;
|
||||
if (mipHeight > 1) mipHeight /= 2;
|
||||
|
||||
}
|
||||
|
||||
barrier.subresourceRange.baseMipLevel = mipLevels - 1;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(commandBuffer,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &barrier);
|
||||
}
|
||||
|
||||
// class definitions
|
||||
|
||||
struct GFXDevice::Impl {
|
||||
@ -845,7 +920,10 @@ namespace engine {
|
||||
std::map<const gfx::Pipeline*, std::queue<DrawCall>> drawQueues{};
|
||||
|
||||
VkDescriptorSetLayoutBinding uboLayoutBinding{};
|
||||
VkDescriptorSetLayout uboLayout{};
|
||||
VkDescriptorSetLayout descriptorSetLayout{};
|
||||
|
||||
VkDescriptorSetLayoutBinding samplerLayoutBinding{};
|
||||
VkDescriptorSetLayout samplerSetLayout{};
|
||||
|
||||
};
|
||||
|
||||
@ -1032,6 +1110,19 @@ namespace engine {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for some features:
|
||||
VkPhysicalDeviceFeatures devFeatures;
|
||||
vkGetPhysicalDeviceFeatures(dev, &devFeatures);
|
||||
// anisotropic filtering is needed
|
||||
if (devFeatures.samplerAnisotropy == VK_FALSE) continue;
|
||||
|
||||
// check for linear filtering for mipmaps
|
||||
VkFormatProperties formatProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(dev, VK_FORMAT_R8G8B8A8_SRGB, &formatProperties);
|
||||
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pimpl->physicalDevice = dev;
|
||||
break;
|
||||
|
||||
@ -1127,6 +1218,9 @@ namespace engine {
|
||||
throw std::runtime_error("The selected queue family does not support this surface");
|
||||
}
|
||||
|
||||
VkPhysicalDeviceFeatures deviceFeatures{};
|
||||
deviceFeatures.samplerAnisotropy = VK_TRUE;
|
||||
|
||||
VkDeviceCreateInfo deviceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@ -1137,7 +1231,7 @@ namespace engine {
|
||||
// IGNORED: .ppEnabledLayerNames
|
||||
.enabledExtensionCount = (uint32_t)requiredDeviceExtensions.size(),
|
||||
.ppEnabledExtensionNames = requiredDeviceExtensions.data(),
|
||||
.pEnabledFeatures = nullptr,
|
||||
.pEnabledFeatures = &deviceFeatures,
|
||||
};
|
||||
|
||||
res = vkCreateDevice(pimpl->physicalDevice, &deviceCreateInfo, nullptr, &pimpl->device);
|
||||
@ -1244,24 +1338,41 @@ namespace engine {
|
||||
assert(res == VK_SUCCESS);
|
||||
}
|
||||
|
||||
// create uniform buffer stuff
|
||||
// create uniform buffer descriptor set layout
|
||||
pimpl->uboLayoutBinding.binding = 0;
|
||||
pimpl->uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
pimpl->uboLayoutBinding.descriptorCount = 1;
|
||||
pimpl->uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
|
||||
pimpl->uboLayoutBinding.pImmutableSamplers = nullptr;
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||
descriptorSetLayoutInfo.bindingCount = 1;
|
||||
descriptorSetLayoutInfo.pBindings = &pimpl->uboLayoutBinding;
|
||||
res = vkCreateDescriptorSetLayout(pimpl->device, &descriptorSetLayoutInfo, nullptr, &pimpl->uboLayout);
|
||||
res = vkCreateDescriptorSetLayout(pimpl->device, &descriptorSetLayoutInfo, nullptr, &pimpl->descriptorSetLayout);
|
||||
assert(res == VK_SUCCESS);
|
||||
|
||||
// create texture sampler descriptor set layout
|
||||
pimpl->samplerLayoutBinding.binding = 0;
|
||||
pimpl->samplerLayoutBinding.descriptorCount = 1;
|
||||
pimpl->samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
pimpl->samplerLayoutBinding.pImmutableSamplers = nullptr;
|
||||
pimpl->samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo samplerSetLayoutInfo{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||
samplerSetLayoutInfo.bindingCount = 1;
|
||||
samplerSetLayoutInfo.pBindings = &pimpl->samplerLayoutBinding;
|
||||
res = vkCreateDescriptorSetLayout(pimpl->device, &samplerSetLayoutInfo, nullptr, &pimpl->samplerSetLayout);
|
||||
assert(res == VK_SUCCESS);
|
||||
|
||||
|
||||
}
|
||||
|
||||
GFXDevice::~GFXDevice()
|
||||
{
|
||||
TRACE("Destroying GFXDevice...");
|
||||
|
||||
vkDestroyDescriptorSetLayout(pimpl->device, pimpl->uboLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(pimpl->device, pimpl->samplerSetLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(pimpl->device, pimpl->descriptorSetLayout, nullptr);
|
||||
|
||||
for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
|
||||
|
||||
@ -1298,7 +1409,7 @@ namespace engine {
|
||||
*h = (uint32_t)height;
|
||||
}
|
||||
|
||||
void GFXDevice::draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize)
|
||||
void GFXDevice::draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize, const gfx::Texture* texture)
|
||||
{
|
||||
assert(vertexBuffer->type == gfx::BufferType::VERTEX);
|
||||
assert(vertexBuffer != nullptr);
|
||||
@ -1313,6 +1424,8 @@ namespace engine {
|
||||
|
||||
memcpy(call.pushConstantData, pushConstantData, pushConstantSize);
|
||||
|
||||
call.texture = texture; // will be ignored if nullptr
|
||||
|
||||
pimpl->drawQueues[pipeline].push(call);
|
||||
|
||||
}
|
||||
@ -1367,9 +1480,9 @@ namespace engine {
|
||||
|
||||
VkViewport viewport{};
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = 0.0f;
|
||||
viewport.y = (float)pimpl->swapchain.extent.height;
|
||||
viewport.width = (float)pimpl->swapchain.extent.width;
|
||||
viewport.height = (float)pimpl->swapchain.extent.height;
|
||||
viewport.height = -(float)pimpl->swapchain.extent.height;
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
vkCmdSetViewport(pimpl->commandBuffers[frameIndex], 0, 1, &viewport);
|
||||
@ -1390,6 +1503,8 @@ namespace engine {
|
||||
|
||||
DrawCall call = queue.front();
|
||||
|
||||
vkCmdBindDescriptorSets(pimpl->commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->layout, 1, 1, &call.texture->descriptorSets[frameIndex], 0, nullptr);
|
||||
|
||||
vkCmdPushConstants(pimpl->commandBuffers[frameIndex], pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0, PUSH_CONSTANT_MAX_SIZE, call.pushConstantData);
|
||||
|
||||
vkCmdBindVertexBuffers(pimpl->commandBuffers[frameIndex], 0, 1, &call.vertexBuffer->buffer, offsets);
|
||||
@ -1465,7 +1580,7 @@ namespace engine {
|
||||
|
||||
VkShaderModule vertShaderModule = compileShader(pimpl->device, shaderc_vertex_shader, vertShaderCode.data(), vertShaderPath);
|
||||
VkShaderModule fragShaderModule = compileShader(pimpl->device, shaderc_fragment_shader, fragShaderCode.data(), fragShaderPath);
|
||||
|
||||
|
||||
// create uniform buffers
|
||||
pipeline->uniformBuffers.resize(FRAMES_IN_FLIGHT);
|
||||
for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
|
||||
@ -1490,10 +1605,11 @@ namespace engine {
|
||||
pipeline->uniformBuffers[i] = buf;
|
||||
}
|
||||
|
||||
// create descriptor pool for uniform buffers
|
||||
// create descriptor pools
|
||||
VkDescriptorPoolSize poolSize{};
|
||||
poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
poolSize.descriptorCount = FRAMES_IN_FLIGHT;
|
||||
|
||||
VkDescriptorPoolCreateInfo poolInfo{};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
poolInfo.poolSizeCount = 1;
|
||||
@ -1503,19 +1619,21 @@ namespace engine {
|
||||
assert(res == VK_SUCCESS);
|
||||
|
||||
std::array<VkDescriptorSetLayout, FRAMES_IN_FLIGHT> layouts;
|
||||
layouts.fill(pimpl->uboLayout);
|
||||
layouts.fill(pimpl->descriptorSetLayout);
|
||||
VkDescriptorSetAllocateInfo dSetAllocInfo{};
|
||||
dSetAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
dSetAllocInfo.descriptorPool = pipeline->descriptorPool;
|
||||
dSetAllocInfo.descriptorSetCount = FRAMES_IN_FLIGHT;
|
||||
dSetAllocInfo.pSetLayouts = layouts.data();
|
||||
res = vkAllocateDescriptorSets(pimpl->device, &dSetAllocInfo, pipeline->descriptorSets.data());
|
||||
assert(res == VK_SUCCESS);
|
||||
|
||||
for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
|
||||
VkDescriptorBufferInfo bufferInfo{};
|
||||
bufferInfo.buffer = pipeline->uniformBuffers[i]->buffer;
|
||||
bufferInfo.offset = 0;
|
||||
bufferInfo.range = uniformBufferSize;
|
||||
|
||||
VkWriteDescriptorSet descriptorWrite{};
|
||||
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptorWrite.dstSet = pipeline->descriptorSets[i];
|
||||
@ -1576,9 +1694,9 @@ namespace engine {
|
||||
|
||||
VkViewport viewport{};
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = 0.0f;
|
||||
viewport.y = (float)pimpl->swapchain.extent.height;
|
||||
viewport.width = (float)pimpl->swapchain.extent.width;
|
||||
viewport.height = (float)pimpl->swapchain.extent.height;
|
||||
viewport.height = -(float)pimpl->swapchain.extent.height;
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
|
||||
@ -1667,10 +1785,12 @@ namespace engine {
|
||||
pushConstantRange.size = PUSH_CONSTANT_MAX_SIZE;
|
||||
pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
|
||||
std::array<VkDescriptorSetLayout, 2> setLayouts{ pimpl->descriptorSetLayout, pimpl->samplerSetLayout};
|
||||
|
||||
VkPipelineLayoutCreateInfo layoutInfo{};
|
||||
layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
layoutInfo.setLayoutCount = 1;
|
||||
layoutInfo.pSetLayouts = &pimpl->uboLayout;
|
||||
layoutInfo.setLayoutCount = setLayouts.size();
|
||||
layoutInfo.pSetLayouts = setLayouts.data();
|
||||
layoutInfo.pushConstantRangeCount = 1;
|
||||
layoutInfo.pPushConstantRanges = &pushConstantRange;
|
||||
|
||||
@ -1810,6 +1930,8 @@ namespace engine {
|
||||
|
||||
size_t imageSize = w * h * 4;
|
||||
|
||||
out->mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(w, h)))) + 1;
|
||||
|
||||
// first load image into staging buffer
|
||||
VkBuffer stagingBuffer;
|
||||
VmaAllocation stagingAllocation;
|
||||
@ -1841,12 +1963,12 @@ namespace engine {
|
||||
imageInfo.extent.width = w;
|
||||
imageInfo.extent.height = h;
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.mipLevels = out->mipLevels;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.flags = 0;
|
||||
@ -1863,7 +1985,7 @@ namespace engine {
|
||||
|
||||
// begin cmd buffer
|
||||
|
||||
cmdTransitionImageLayout(commandBuffer, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, out->image);
|
||||
cmdTransitionImageLayout(commandBuffer, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, out->mipLevels, out->image);
|
||||
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = 0;
|
||||
@ -1880,7 +2002,8 @@ namespace engine {
|
||||
|
||||
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
|
||||
cmdTransitionImageLayout(commandBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, out->image);
|
||||
// Mipmap generation handles the transition to SHADER_READ_ONLY_OPTIMAL
|
||||
cmdGenerateMipmaps(commandBuffer, out->image, w, h, out->mipLevels);
|
||||
|
||||
// end cmd buffer
|
||||
endOneTimeCommands(pimpl->device, pimpl->commandPool, commandBuffer, pimpl->gfxQueue.handle);
|
||||
@ -1890,11 +2013,101 @@ namespace engine {
|
||||
// destroy staging buffer
|
||||
vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation);
|
||||
|
||||
// create image view
|
||||
VkImageViewCreateInfo imageViewInfo{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
|
||||
imageViewInfo.image = out->image;
|
||||
imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
imageViewInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
|
||||
imageViewInfo.subresourceRange = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = out->mipLevels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
};
|
||||
|
||||
res = vkCreateImageView(pimpl->device, &imageViewInfo, nullptr, &out->imageView);
|
||||
assert(res == VK_SUCCESS);
|
||||
|
||||
// create texture sampler
|
||||
{
|
||||
VkSamplerCreateInfo samplerInfo{ VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
|
||||
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
||||
samplerInfo.minFilter = VK_FILTER_LINEAR;
|
||||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
samplerInfo.anisotropyEnable = VK_TRUE;
|
||||
// Find max anisotropic filtering level
|
||||
{
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
vkGetPhysicalDeviceProperties(pimpl->physicalDevice, &properties);
|
||||
|
||||
INFO("Anisotropic filtering level: {}", properties.limits.maxSamplerAnisotropy);
|
||||
samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;
|
||||
}
|
||||
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
|
||||
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||
samplerInfo.compareEnable = VK_FALSE;
|
||||
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
|
||||
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = static_cast<float>(out->mipLevels);
|
||||
samplerInfo.mipLodBias = 0.0f;
|
||||
|
||||
res = vkCreateSampler(pimpl->device, &samplerInfo, nullptr, &out->sampler);
|
||||
assert(res == VK_SUCCESS);
|
||||
}
|
||||
|
||||
// create descriptor pools
|
||||
VkDescriptorPoolSize poolSize{};
|
||||
poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
poolSize.descriptorCount = FRAMES_IN_FLIGHT;
|
||||
|
||||
VkDescriptorPoolCreateInfo poolInfo{};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
poolInfo.poolSizeCount = 1;
|
||||
poolInfo.pPoolSizes = &poolSize;
|
||||
poolInfo.maxSets = FRAMES_IN_FLIGHT;
|
||||
res = vkCreateDescriptorPool(pimpl->device, &poolInfo, nullptr, &out->pool);
|
||||
assert(res == VK_SUCCESS);
|
||||
|
||||
std::array<VkDescriptorSetLayout, FRAMES_IN_FLIGHT> layouts{};
|
||||
layouts.fill(pimpl->samplerSetLayout);
|
||||
VkDescriptorSetAllocateInfo dSetAllocInfo{};
|
||||
dSetAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
dSetAllocInfo.descriptorPool = out->pool;
|
||||
dSetAllocInfo.descriptorSetCount = FRAMES_IN_FLIGHT;
|
||||
dSetAllocInfo.pSetLayouts = layouts.data();
|
||||
res = vkAllocateDescriptorSets(pimpl->device, &dSetAllocInfo, out->descriptorSets.data());
|
||||
assert(res == VK_SUCCESS);
|
||||
|
||||
for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
|
||||
VkDescriptorImageInfo imageInfo{};
|
||||
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
imageInfo.imageView = out->imageView;
|
||||
imageInfo.sampler = out->sampler;
|
||||
|
||||
VkWriteDescriptorSet descriptorWrite{};
|
||||
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptorWrite.dstSet = out->descriptorSets[i];
|
||||
descriptorWrite.dstBinding = 0;
|
||||
descriptorWrite.dstArrayElement = 0;
|
||||
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
descriptorWrite.descriptorCount = 1;
|
||||
descriptorWrite.pImageInfo = &imageInfo;
|
||||
|
||||
vkUpdateDescriptorSets(pimpl->device, 1, &descriptorWrite, 0, nullptr);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void GFXDevice::destroyTexture(const gfx::Texture* texture)
|
||||
{
|
||||
vkDestroyDescriptorPool(pimpl->device, texture->pool, nullptr);
|
||||
vkDestroySampler(pimpl->device, texture->sampler, nullptr);
|
||||
vkDestroyImageView(pimpl->device, texture->imageView, nullptr);
|
||||
vmaDestroyImage(pimpl->allocator, texture->image, texture->alloc);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ static void loadMeshFromFile(const std::filesystem::path& path, std::vector<Vert
|
||||
vertices->resize(header.vertex_count);
|
||||
|
||||
fread(indices->data(), sizeof(uint32_t) * header.index_count, 1, fp);
|
||||
fread(vertices->data(), sizeof(float) * 8 * header.vertex_count, 1, fp);
|
||||
fread(vertices->data(), sizeof(Vertex) * header.vertex_count, 1, fp);
|
||||
fclose(fp);
|
||||
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
namespace engine::resources {
|
||||
|
||||
// returns false if unable to open file
|
||||
static bool readPNG(const std::string& path, std::vector<uint8_t>& texbuf, int *width, int *height, bool *isRGBA)
|
||||
static bool readPNG(const std::string& path, std::vector<uint8_t>* texbuf, int *width, int *height, bool *isRGBA)
|
||||
{
|
||||
int x, y, n;
|
||||
unsigned char *data = stbi_load(path.c_str(), &x, &y, &n, STBI_rgb_alpha);
|
||||
@ -22,8 +22,8 @@ static bool readPNG(const std::string& path, std::vector<uint8_t>& texbuf, int *
|
||||
|
||||
const size_t size = (size_t)x * (size_t)y * 4;
|
||||
|
||||
texbuf.resize(size);
|
||||
memcpy(texbuf.data(), data, size);
|
||||
texbuf->resize(size);
|
||||
memcpy(texbuf->data(), data, size);
|
||||
|
||||
*width = x;
|
||||
*height = y;
|
||||
@ -35,7 +35,7 @@ static bool readPNG(const std::string& path, std::vector<uint8_t>& texbuf, int *
|
||||
|
||||
}
|
||||
|
||||
static bool readGLRaw(const std::string& path, std::vector<uint8_t>& texbuf, int *width, int *height, bool *isRGBA)
|
||||
static bool readGLRaw(const std::string& path, std::vector<uint8_t>* texbuf, int *width, int *height, bool *isRGBA)
|
||||
{
|
||||
FILE *fp = fopen(path.c_str(), "rb");
|
||||
if (!fp) {
|
||||
@ -49,9 +49,9 @@ static bool readGLRaw(const std::string& path, std::vector<uint8_t>& texbuf, int
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
uint64_t end = ftell(fp);
|
||||
|
||||
texbuf.resize(end);
|
||||
texbuf->resize(end);
|
||||
fseek(fp, (long)tex_data_offset, SEEK_SET);
|
||||
fread(texbuf.data(), 1, end, fp);
|
||||
fread(texbuf->data(), 1, end, fp);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
@ -66,15 +66,15 @@ static bool readGLRaw(const std::string& path, std::vector<uint8_t>& texbuf, int
|
||||
Texture::Texture(const std::filesystem::path& resPath) : Resource(resPath, "texture")
|
||||
{
|
||||
|
||||
std::vector<uint8_t> texbuf;
|
||||
auto texbuf = std::make_unique<std::vector<uint8_t>>();
|
||||
|
||||
int width, height;
|
||||
bool isRGBA, success;
|
||||
|
||||
if (resPath.extension() == ".png") {
|
||||
success = readPNG(resPath.string(), texbuf, &width, &height, &isRGBA);
|
||||
if (resPath.extension() == ".png" || resPath.extension() == ".jpg") {
|
||||
success = readPNG(resPath.string(), texbuf.get(), &width, &height, &isRGBA);
|
||||
} else {
|
||||
success = readGLRaw(resPath.string(), texbuf, &width, &height, &isRGBA);
|
||||
success = readGLRaw(resPath.string(), texbuf.get(), &width, &height, &isRGBA);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
@ -82,12 +82,12 @@ Texture::Texture(const std::filesystem::path& resPath) : Resource(resPath, "text
|
||||
}
|
||||
|
||||
if (isRGBA == false) {
|
||||
throw std::runtime_error("Currently, only RGBA textures are supported. Size: " + std::to_string(texbuf.size()));
|
||||
throw std::runtime_error("Currently, only RGBA textures are supported. Size: " + std::to_string(texbuf->size()));
|
||||
}
|
||||
|
||||
m_gpuTexture = gfxdev->createTexture(texbuf.data(), (uint32_t)width, (uint32_t)height);
|
||||
m_gpuTexture = gfxdev->createTexture(texbuf->data(), (uint32_t)width, (uint32_t)height);
|
||||
|
||||
DEBUG("loaded texture {} width: {} height: {} size: {}", resPath.filename().string(), width, height, texbuf.size());
|
||||
DEBUG("loaded texture {} width: {} height: {} size: {}", resPath.filename().string(), width, height, texbuf->size());
|
||||
|
||||
}
|
||||
|
||||
@ -96,4 +96,9 @@ Texture::~Texture()
|
||||
gfxdev->destroyTexture(m_gpuTexture);
|
||||
}
|
||||
|
||||
gfx::Texture* Texture::getHandle()
|
||||
{
|
||||
return m_gpuTexture;
|
||||
}
|
||||
|
||||
}
|
@ -276,12 +276,19 @@ namespace engine {
|
||||
|
||||
void Window::setFullscreen(bool fullscreen, bool exclusive)
|
||||
{
|
||||
|
||||
if (m_resizable) {
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
SDL_GetDesktopDisplayMode(SDL_GetWindowDisplayIndex(m_handle), &mode);
|
||||
SDL_SetWindowDisplayMode(m_handle, &mode);
|
||||
|
||||
if (SDL_SetWindowFullscreen(m_handle, fullscreen ? (exclusive ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP) : 0) != 0) {
|
||||
throw std::runtime_error("Unable to set window to fullscreen/windowed");
|
||||
}
|
||||
m_fullscreen = fullscreen;
|
||||
if (fullscreen) {
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSize(m_handle, &width, &height);
|
||||
onResize(width, height);
|
||||
@ -291,7 +298,7 @@ namespace engine {
|
||||
|
||||
void Window::toggleFullscreen()
|
||||
{
|
||||
setFullscreen(!m_fullscreen, false);
|
||||
setFullscreen(!m_fullscreen, true);
|
||||
}
|
||||
|
||||
bool Window::isFullscreen() const
|
||||
|
BIN
test/game.aps
Normal file
BIN
test/game.aps
Normal file
Binary file not shown.
BIN
test/res/meshes/castle_0.mesh
Normal file
BIN
test/res/meshes/castle_0.mesh
Normal file
Binary file not shown.
BIN
test/res/meshes/castle_1.mesh
Normal file
BIN
test/res/meshes/castle_1.mesh
Normal file
Binary file not shown.
BIN
test/res/meshes/castle_2.mesh
Normal file
BIN
test/res/meshes/castle_2.mesh
Normal file
Binary file not shown.
BIN
test/res/meshes/castle_3.mesh
Normal file
BIN
test/res/meshes/castle_3.mesh
Normal file
Binary file not shown.
BIN
test/res/meshes/castle_4.mesh
Normal file
BIN
test/res/meshes/castle_4.mesh
Normal file
Binary file not shown.
BIN
test/res/meshes/castle_5.mesh
Normal file
BIN
test/res/meshes/castle_5.mesh
Normal file
Binary file not shown.
@ -8,7 +8,7 @@ layout(location = 4) in vec3 fragColor;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
float snoise(vec2 v);
|
||||
layout(set = 1, binding = 0) uniform sampler2D texSampler;
|
||||
|
||||
void main() {
|
||||
|
||||
@ -16,7 +16,7 @@ void main() {
|
||||
vec3 lightColor = vec3(1.0, 1.0, 1.0);
|
||||
vec3 ambientColor = vec3(1.0, 1.0, 1.0);
|
||||
float ambientStrength = 0.1;
|
||||
vec3 baseColor = vec3(fragColor.x * snoise(fragUV * 10.0), mod(fragUV.x, 1.0), mod(fragUV.y, 1.0));
|
||||
vec3 baseColor = vec3(texture(texSampler, fragUV));
|
||||
vec3 emission = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
// code
|
||||
@ -35,37 +35,4 @@ void main() {
|
||||
|
||||
vec3 lighting = min(diffuse + ambient + specular, 1.0);
|
||||
outColor = min( ( vec4(baseColor, 1.0) ) * vec4(lighting + emission, 1.0), vec4(1.0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Simplex 2D noise
|
||||
//
|
||||
vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
|
||||
|
||||
float snoise(vec2 v){
|
||||
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
|
||||
-0.577350269189626, 0.024390243902439);
|
||||
vec2 i = floor(v + dot(v, C.yy) );
|
||||
vec2 x0 = v - i + dot(i, C.xx);
|
||||
vec2 i1;
|
||||
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||||
vec4 x12 = x0.xyxy + C.xxzz;
|
||||
x12.xy -= i1;
|
||||
i = mod(i, 289.0);
|
||||
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
|
||||
+ i.x + vec3(0.0, i1.x, 1.0 ));
|
||||
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
|
||||
dot(x12.zw,x12.zw)), 0.0);
|
||||
m = m*m ;
|
||||
m = m*m ;
|
||||
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||||
vec3 h = abs(x) - 0.5;
|
||||
vec3 ox = floor(x + 0.5);
|
||||
vec3 a0 = x - ox;
|
||||
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
|
||||
vec3 g;
|
||||
g.x = a0.x * x0.x + h.x * x0.y;
|
||||
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||
return 130.0 * dot(m, g);
|
||||
}
|
Binary file not shown.
Binary file not shown.
BIN
test/res/textures/door.jpg
Normal file
BIN
test/res/textures/door.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 462 KiB |
BIN
test/res/textures/grass.jpg
Normal file
BIN
test/res/textures/grass.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 205 KiB |
BIN
test/res/textures/metal.jpg
Normal file
BIN
test/res/textures/metal.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
BIN
test/res/textures/rock.jpg
Normal file
BIN
test/res/textures/rock.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 476 KiB |
@ -56,8 +56,8 @@ void playGame()
|
||||
auto camCamera = cam->createComponent<engine::components::Camera>();
|
||||
camCamera->usePerspective(70.0f);
|
||||
cam->createComponent<CameraController>();
|
||||
cam->createComponent<engine::components::Renderer>()->m_mesh = genSphereMesh(0.2f, 20);
|
||||
cam->getComponent<engine::components::Renderer>()->setTexture("textures/cobble_stone.png");
|
||||
//cam->createComponent<engine::components::Renderer>()->m_mesh = genSphereMesh(0.2f, 20);
|
||||
//cam->getComponent<engine::components::Renderer>()->setTexture("textures/cobble_stone.png");
|
||||
|
||||
auto gun = cam->createChild("gun");
|
||||
gun->transform.position = glm::vec3{ 0.2f, -0.1f, -0.15f };
|
||||
@ -76,7 +76,7 @@ void playGame()
|
||||
auto floor = app.scene()->createChild("floor");
|
||||
auto floorRenderer = floor->createComponent<engine::components::Renderer>();
|
||||
floor->transform.position = glm::vec3{ 0.0f, 0.0f, 0.0f };
|
||||
floorRenderer->setTexture("textures/stone_bricks.png");
|
||||
floorRenderer->setTexture("textures/grass.jpg");
|
||||
floorRenderer->m_mesh = std::make_unique<engine::resources::Mesh>(std::vector<Vertex>{
|
||||
{ { -16.0f, 0.0f, 16.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, GRASS_DENSITY } },
|
||||
{ { 16.0f, 0.0f, -16.0f }, { 0.0f, 1.0f, 0.0f }, { GRASS_DENSITY, 0.0f } },
|
||||
@ -128,5 +128,30 @@ void playGame()
|
||||
sphereRenderer->m_mesh = genSphereMesh(5.0f, 100, false);
|
||||
sphereRenderer->setTexture("textures/cobble_stone.png");
|
||||
|
||||
/* castle */
|
||||
auto castle = app.scene()->createChild("castle");
|
||||
castle->transform.scale = { 0.01f, 0.01f, 0.01f };
|
||||
std::vector<engine::Object*> castleParts(6);
|
||||
for (int i = 0; i < castleParts.size(); i++) {
|
||||
if (i == 2) continue;
|
||||
castleParts[i] = castle->createChild(std::to_string(i));
|
||||
auto ren = castleParts[i]->createComponent<engine::components::Renderer>();
|
||||
ren->setMesh("meshes/castle_" + std::to_string(i) + ".mesh");
|
||||
ren->setTexture("textures/rock.jpg");
|
||||
|
||||
if (i == 5) {
|
||||
ren->setTexture("textures/metal.jpg");
|
||||
}
|
||||
if (i == 4) {
|
||||
ren->setTexture("textures/door.jpg");
|
||||
}
|
||||
}
|
||||
|
||||
// boundary
|
||||
auto bounds = app.scene()->createChild("bounds");
|
||||
auto boundsRen = bounds->createComponent<engine::components::Renderer>();
|
||||
boundsRen->m_mesh = genSphereMesh(100.0f, 100, true);
|
||||
boundsRen->setTexture("textures/metal.jpg");
|
||||
|
||||
app.gameLoop();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user