diff --git a/Editor/terrain/bush.wiscene b/Editor/terrain/bush.wiscene index c43fbe9c2..00a232e48 100644 Binary files a/Editor/terrain/bush.wiscene and b/Editor/terrain/bush.wiscene differ diff --git a/Editor/terrain/tree.wiscene b/Editor/terrain/tree.wiscene index 3b55a8717..14f267114 100644 Binary files a/Editor/terrain/tree.wiscene and b/Editor/terrain/tree.wiscene differ diff --git a/WickedEngine/wiGraphics.h b/WickedEngine/wiGraphics.h index 45288d3b8..8d67d43bf 100644 --- a/WickedEngine/wiGraphics.h +++ b/WickedEngine/wiGraphics.h @@ -443,9 +443,9 @@ namespace wi::graphics TEXTURE_2D, TEXTURE_3D, } type = Type::TEXTURE_2D; - uint32_t width = 0; - uint32_t height = 0; - uint32_t depth = 0; + uint32_t width = 1; + uint32_t height = 1; + uint32_t depth = 1; uint32_t array_size = 1; uint32_t mip_levels = 1; Format format = Format::UNKNOWN; @@ -769,9 +769,9 @@ namespace wi::graphics struct SubresourceData { - const void* data_ptr = nullptr; - uint32_t row_pitch = 0; - uint32_t slice_pitch = 0; + const void* data_ptr = nullptr; // pointer to the beginning of the subresource data (pointer to beginning of resource + subresource offset) + uint32_t row_pitch = 0; // bytes between two rows of a texture (2D and 3D textures) + uint32_t slice_pitch = 0; // bytes between two depth slices of a texture (3D textures only) }; struct Rect @@ -816,8 +816,9 @@ namespace wi::graphics constexpr bool IsBuffer() const { return type == Type::BUFFER; } constexpr bool IsAccelerationStructure() const { return type == Type::RAYTRACING_ACCELERATION_STRUCTURE; } - void* mapped_data = nullptr; - uint32_t mapped_rowpitch = 0; + // These are only valid if the resource was created with CPU access (USAGE::UPLOAD or USAGE::READBACK) + void* mapped_data = nullptr; // for buffers, it is a pointer to the buffer data; for textures, it is a pointer to texture data with linear tiling; + size_t mapped_size = 0; // for buffers, it is the full buffer size; for textures it is the full texture size including all subresources; }; struct GPUBuffer : public GPUResource @@ -831,6 +832,10 @@ namespace wi::graphics { TextureDesc desc; + // These are only valid if the texture was created with CPU access (USAGE::UPLOAD or USAGE::READBACK) + const SubresourceData* mapped_subresources = nullptr; // an array of subresource mappings in the following memory layout: slice0|mip0, slice0|mip1, slice0|mip2, ... sliceN|mipN + size_t mapped_subresource_count = 0; // the array size of mapped_subresources (number of slices * number of miplevels) + constexpr const TextureDesc& GetDesc() const { return desc; } }; diff --git a/WickedEngine/wiGraphicsDevice_DX12.cpp b/WickedEngine/wiGraphicsDevice_DX12.cpp index 3784341fc..8c6088b9d 100644 --- a/WickedEngine/wiGraphicsDevice_DX12.cpp +++ b/WickedEngine/wiGraphicsDevice_DX12.cpp @@ -1308,10 +1308,14 @@ namespace dx12_internal wi::vector subresources_uav; SingleDescriptor uav_raw; - D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; - D3D12_GPU_VIRTUAL_ADDRESS gpu_address = 0; + UINT64 total_size = 0; + wi::vector footprints; + wi::vector rowSizesInBytes; + wi::vector numRows; + wi::vector mapped_subresources; + virtual ~Resource_DX12() { allocationhandler->destroylocker.lock(); @@ -2997,7 +3001,7 @@ using namespace dx12_internal; buffer->internal_state = internal_state; buffer->type = GPUResource::Type::BUFFER; buffer->mapped_data = nullptr; - buffer->mapped_rowpitch = 0; + buffer->mapped_size = 0; buffer->desc = *desc; HRESULT hr = E_FAIL; @@ -3047,8 +3051,6 @@ using namespace dx12_internal; resourceState = D3D12_RESOURCE_STATE_GENERIC_READ; } - device->GetCopyableFootprints(&resourceDesc, 0, 1, 0, &internal_state->footprint, nullptr, nullptr, nullptr); - hr = allocationhandler->allocator->CreateResource( &allocationDesc, &resourceDesc, @@ -3065,14 +3067,14 @@ using namespace dx12_internal; { hr = internal_state->resource->Map(0, nullptr, &buffer->mapped_data); assert(SUCCEEDED(hr)); - buffer->mapped_rowpitch = static_cast(desc->size); + buffer->mapped_size = static_cast(desc->size); } else if (desc->usage == Usage::UPLOAD) { D3D12_RANGE read_range = {}; hr = internal_state->resource->Map(0, &read_range, &buffer->mapped_data); assert(SUCCEEDED(hr)); - buffer->mapped_rowpitch = static_cast(desc->size); + buffer->mapped_size = static_cast(desc->size); } // Issue data copy on request: @@ -3123,7 +3125,9 @@ using namespace dx12_internal; texture->internal_state = internal_state; texture->type = GPUResource::Type::TEXTURE; texture->mapped_data = nullptr; - texture->mapped_rowpitch = 0; + texture->mapped_size = 0; + texture->mapped_subresources = nullptr; + texture->mapped_subresource_count = 0; texture->desc = *desc; HRESULT hr = E_FAIL; @@ -3207,14 +3211,33 @@ using namespace dx12_internal; resourceState = D3D12_RESOURCE_STATE_COMMON; } + if (texture->desc.mip_levels == 0) + { + texture->desc.mip_levels = (uint32_t)log2(std::max(texture->desc.width, texture->desc.height)) + 1; + } + + internal_state->total_size = 0; + internal_state->footprints.resize(desc->array_size * std::max(1u, desc->mip_levels)); + internal_state->rowSizesInBytes.resize(internal_state->footprints.size()); + internal_state->numRows.resize(internal_state->footprints.size()); + device->GetCopyableFootprints( + &resourcedesc, + 0, + (UINT)internal_state->footprints.size(), + 0, + internal_state->footprints.data(), + internal_state->numRows.data(), + internal_state->rowSizesInBytes.data(), + &internal_state->total_size + ); + if (texture->desc.usage == Usage::READBACK || texture->desc.usage == Usage::UPLOAD) { - UINT64 RequiredSize = 0; - device->GetCopyableFootprints(&resourcedesc, 0, 1, 0, &internal_state->footprint, nullptr, nullptr, &RequiredSize); resourcedesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - resourcedesc.Width = RequiredSize; + resourcedesc.Width = internal_state->total_size; resourcedesc.Height = 1; resourcedesc.DepthOrArraySize = 1; + resourcedesc.MipLevels = 1; resourcedesc.Format = DXGI_FORMAT_UNKNOWN; resourcedesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourcedesc.Flags = D3D12_RESOURCE_FLAG_NONE; @@ -3245,54 +3268,54 @@ using namespace dx12_internal; { hr = internal_state->resource->Map(0, nullptr, &texture->mapped_data); assert(SUCCEEDED(hr)); - texture->mapped_rowpitch = internal_state->footprint.Footprint.RowPitch; } else if(texture->desc.usage == Usage::UPLOAD) { D3D12_RANGE read_range = {}; hr = internal_state->resource->Map(0, &read_range, &texture->mapped_data); assert(SUCCEEDED(hr)); - texture->mapped_rowpitch = internal_state->footprint.Footprint.RowPitch; } - if (texture->desc.mip_levels == 0) + if (texture->mapped_data != nullptr) { - texture->desc.mip_levels = (uint32_t)log2(std::max(texture->desc.width, texture->desc.height)) + 1; + texture->mapped_size = internal_state->total_size; + internal_state->mapped_subresources.resize(internal_state->footprints.size()); + for (size_t i = 0; i < internal_state->footprints.size(); ++i) + { + internal_state->mapped_subresources[i].data_ptr = (uint8_t*)texture->mapped_data + internal_state->footprints[i].Offset; + internal_state->mapped_subresources[i].row_pitch = internal_state->footprints[i].Footprint.RowPitch; + internal_state->mapped_subresources[i].slice_pitch = internal_state->footprints[i].Footprint.RowPitch * internal_state->footprints[i].Footprint.Height; + } + texture->mapped_subresources = internal_state->mapped_subresources.data(); + texture->mapped_subresource_count = internal_state->mapped_subresources.size(); } // Issue data copy on request: if (initial_data != nullptr) { - uint32_t dataCount = desc->array_size * std::max(1u, desc->mip_levels); - wi::vector data(dataCount); - for (uint32_t slice = 0; slice < dataCount; ++slice) + wi::vector data(internal_state->footprints.size()); + for (size_t i = 0; i < internal_state->footprints.size(); ++i) { - data[slice] = _ConvertSubresourceData(initial_data[slice]); + data[i] = _ConvertSubresourceData(initial_data[i]); } - UINT64 RequiredSize = 0; - wi::vector layouts(dataCount); - wi::vector rowSizesInBytes(dataCount); - wi::vector numRows(dataCount); - device->GetCopyableFootprints(&resourcedesc, 0, dataCount, 0, layouts.data(), numRows.data(), rowSizesInBytes.data(), &RequiredSize); + auto cmd = copyAllocator.allocate(internal_state->total_size); - auto cmd = copyAllocator.allocate(RequiredSize); - - for (uint32_t i = 0; i < dataCount; ++i) + for (size_t i = 0; i < internal_state->footprints.size(); ++i) { - if (rowSizesInBytes[i] > (SIZE_T)-1) + if (internal_state->rowSizesInBytes[i] > (SIZE_T)-1) continue; D3D12_MEMCPY_DEST DestData = {}; - DestData.pData = (void*)((UINT64)cmd.uploadbuffer.mapped_data + layouts[i].Offset); - DestData.RowPitch = (SIZE_T)layouts[i].Footprint.RowPitch; - DestData.SlicePitch = (SIZE_T)layouts[i].Footprint.RowPitch * (SIZE_T)numRows[i]; - MemcpySubresource(&DestData, &data[i], (SIZE_T)rowSizesInBytes[i], numRows[i], layouts[i].Footprint.Depth); + DestData.pData = (void*)((UINT64)cmd.uploadbuffer.mapped_data + internal_state->footprints[i].Offset); + DestData.RowPitch = (SIZE_T)internal_state->footprints[i].Footprint.RowPitch; + DestData.SlicePitch = (SIZE_T)internal_state->footprints[i].Footprint.RowPitch * (SIZE_T)internal_state->numRows[i]; + MemcpySubresource(&DestData, &data[i], (SIZE_T)internal_state->rowSizesInBytes[i], internal_state->numRows[i], internal_state->footprints[i].Footprint.Depth); } - for (UINT i = 0; i < dataCount; ++i) + for (UINT i = 0; i < internal_state->footprints.size(); ++i) { CD3DX12_TEXTURE_COPY_LOCATION Dst(internal_state->resource.Get(), i); - CD3DX12_TEXTURE_COPY_LOCATION Src(to_internal(&cmd.uploadbuffer)->resource.Get(), layouts[i]); + CD3DX12_TEXTURE_COPY_LOCATION Src(to_internal(&cmd.uploadbuffer)->resource.Get(), internal_state->footprints[i]); cmd.commandList->CopyTextureRegion( &Dst, 0, @@ -4992,13 +5015,26 @@ using namespace dx12_internal; internal_state->allocationhandler = allocationhandler; internal_state->resource = swapchain_internal->backBuffers[swapchain_internal->swapChain->GetCurrentBackBufferIndex()]; - D3D12_RESOURCE_DESC desc = internal_state->resource->GetDesc(); - device->GetCopyableFootprints(&desc, 0, 1, 0, &internal_state->footprint, nullptr, nullptr, nullptr); + D3D12_RESOURCE_DESC resourcedesc = internal_state->resource->GetDesc(); + internal_state->total_size = 0; + internal_state->footprints.resize(resourcedesc.DepthOrArraySize * resourcedesc.MipLevels); + internal_state->rowSizesInBytes.resize(internal_state->footprints.size()); + internal_state->numRows.resize(internal_state->footprints.size()); + device->GetCopyableFootprints( + &resourcedesc, + 0, + (UINT)internal_state->footprints.size(), + 0, + internal_state->footprints.data(), + internal_state->numRows.data(), + internal_state->rowSizesInBytes.data(), + &internal_state->total_size + ); Texture result; result.type = GPUResource::Type::TEXTURE; result.internal_state = internal_state; - result.desc = _ConvertTextureDesc_Inv(desc); + result.desc = _ConvertTextureDesc_Inv(resourcedesc); return result; } @@ -5512,19 +5548,35 @@ using namespace dx12_internal; CommandList_DX12& commandlist = GetCommandList(cmd); auto internal_state_src = to_internal(pSrc); auto internal_state_dst = to_internal(pDst); - D3D12_RESOURCE_DESC desc_src = internal_state_src->resource->GetDesc(); - D3D12_RESOURCE_DESC desc_dst = internal_state_dst->resource->GetDesc(); - if (desc_dst.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER && desc_src.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER) + + const TextureDesc& src_desc = ((const Texture*)pSrc)->GetDesc(); + const TextureDesc& dst_desc = ((const Texture*)pDst)->GetDesc(); + + if (src_desc.usage == Usage::UPLOAD) { - CD3DX12_TEXTURE_COPY_LOCATION Src(internal_state_src->resource.Get(), 0); - CD3DX12_TEXTURE_COPY_LOCATION Dst(internal_state_dst->resource.Get(), internal_state_dst->footprint); - commandlist.GetGraphicsCommandList()->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr); + for (uint32_t layer = 0; layer < dst_desc.array_size; ++layer) + { + for (uint32_t mip = 0; mip < dst_desc.mip_levels; ++mip) + { + UINT subresource = D3D12CalcSubresource(mip, layer, 0, dst_desc.mip_levels, dst_desc.array_size); + CD3DX12_TEXTURE_COPY_LOCATION Src(internal_state_src->resource.Get(), internal_state_src->footprints[layer * dst_desc.mip_levels + mip]); + CD3DX12_TEXTURE_COPY_LOCATION Dst(internal_state_dst->resource.Get(), subresource); + commandlist.GetGraphicsCommandList()->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr); + } + } } - else if (desc_src.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER && desc_dst.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER) + else if (dst_desc.usage == Usage::READBACK) { - CD3DX12_TEXTURE_COPY_LOCATION Src(internal_state_src->resource.Get(), internal_state_src->footprint); - CD3DX12_TEXTURE_COPY_LOCATION Dst(internal_state_dst->resource.Get(), 0); - commandlist.GetGraphicsCommandList()->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr); + for (uint32_t layer = 0; layer < dst_desc.array_size; ++layer) + { + for (uint32_t mip = 0; mip < dst_desc.mip_levels; ++mip) + { + UINT subresource = D3D12CalcSubresource(mip, layer, 0, dst_desc.mip_levels, dst_desc.array_size); + CD3DX12_TEXTURE_COPY_LOCATION Src(internal_state_src->resource.Get(), subresource); + CD3DX12_TEXTURE_COPY_LOCATION Dst(internal_state_dst->resource.Get(), internal_state_dst->footprints[layer * dst_desc.mip_levels + mip]); + commandlist.GetGraphicsCommandList()->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr); + } + } } else { diff --git a/WickedEngine/wiGraphicsDevice_Vulkan.cpp b/WickedEngine/wiGraphicsDevice_Vulkan.cpp index 59d905e1d..66733f538 100644 --- a/WickedEngine/wiGraphicsDevice_Vulkan.cpp +++ b/WickedEngine/wiGraphicsDevice_Vulkan.cpp @@ -693,7 +693,7 @@ namespace vulkan_internal wi::vector subresources_dsv; wi::vector subresources_framebuffer_layercount; - VkSubresourceLayout subresourcelayout = {}; + wi::vector mapped_subresources; ~Texture_Vulkan() { @@ -3460,7 +3460,7 @@ using namespace vulkan_internal; buffer->internal_state = internal_state; buffer->type = GPUResource::Type::BUFFER; buffer->mapped_data = nullptr; - buffer->mapped_rowpitch = 0; + buffer->mapped_size = 0; buffer->desc = *desc; VkBufferCreateInfo bufferInfo = {}; @@ -3549,7 +3549,7 @@ using namespace vulkan_internal; if (desc->usage == Usage::READBACK || desc->usage == Usage::UPLOAD) { buffer->mapped_data = internal_state->allocation->GetMappedData(); - buffer->mapped_rowpitch = static_cast(desc->size); + buffer->mapped_size = internal_state->allocation->GetSize(); } if (bufferInfo.usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) @@ -3663,7 +3663,9 @@ using namespace vulkan_internal; texture->internal_state = internal_state; texture->type = GPUResource::Type::TEXTURE; texture->mapped_data = nullptr; - texture->mapped_rowpitch = 0; + texture->mapped_size = 0; + texture->mapped_subresources = nullptr; + texture->mapped_subresource_count = 0; texture->desc = *desc; if (texture->desc.mip_levels == 0) @@ -3678,7 +3680,7 @@ using namespace vulkan_internal; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.extent.width = texture->desc.width; imageInfo.extent.height = texture->desc.height; - imageInfo.extent.depth = 1; + imageInfo.extent.depth = texture->desc.depth; imageInfo.format = _ConvertFormat(texture->desc.format); imageInfo.arrayLayers = texture->desc.array_size; imageInfo.mipLevels = texture->desc.mip_levels; @@ -3742,7 +3744,6 @@ using namespace vulkan_internal; break; case TextureDesc::Type::TEXTURE_3D: imageInfo.imageType = VK_IMAGE_TYPE_3D; - imageInfo.extent.depth = texture->desc.depth; break; default: assert(0); @@ -3755,12 +3756,23 @@ using namespace vulkan_internal; { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = - imageInfo.extent.width * - imageInfo.extent.height * - imageInfo.extent.depth * - imageInfo.arrayLayers * - GetFormatStride(texture->desc.format); + bufferInfo.size = 0; + const uint32_t data_stride = GetFormatStride(texture->desc.format); + const uint32_t block_size = GetFormatBlockSize(texture->desc.format); + const uint32_t num_blocks_x = texture->desc.width / block_size; + const uint32_t num_blocks_y = texture->desc.height / block_size; + uint32_t mip_width = num_blocks_x; + uint32_t mip_height = num_blocks_y; + uint32_t mip_depth = texture->desc.depth; + for (uint32_t mip = 0; mip < texture->desc.mip_levels; ++mip) + { + bufferInfo.size += mip_width * mip_height * mip_depth; + mip_width = std::max(1u, mip_width / 2); + mip_height = std::max(1u, mip_height / 2); + mip_depth = std::max(1u, mip_depth / 2); + } + bufferInfo.size *= imageInfo.arrayLayers; + bufferInfo.size *= data_stride; allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; if (texture->desc.usage == Usage::READBACK) @@ -3778,18 +3790,43 @@ using namespace vulkan_internal; assert(res == VK_SUCCESS); imageInfo.tiling = VK_IMAGE_TILING_LINEAR; + imageInfo.mipLevels = 1; // set miplevels to 1 for linear tiling resource (this resource is just temporary) VkImage image; res = vkCreateImage(device, &imageInfo, nullptr, &image); assert(res == VK_SUCCESS); + VkSubresourceLayout subresourcelayout = {}; VkImageSubresource subresource = {}; subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - vkGetImageSubresourceLayout(device, image, &subresource, &internal_state->subresourcelayout); + vkGetImageSubresourceLayout(device, image, &subresource, &subresourcelayout); if (texture->desc.usage == Usage::READBACK || texture->desc.usage == Usage::UPLOAD) { texture->mapped_data = internal_state->allocation->GetMappedData(); - texture->mapped_rowpitch = (uint32_t)internal_state->subresourcelayout.rowPitch; + texture->mapped_size = internal_state->allocation->GetSize(); + + internal_state->mapped_subresources.resize(texture->desc.array_size* texture->desc.mip_levels); + size_t subresourceIndex = 0; + size_t subresourceDataOffset = 0; + for (uint32_t layer = 0; layer < texture->desc.array_size; ++layer) + { + uint32_t rowpitch = (uint32_t)subresourcelayout.rowPitch; + uint32_t slicepitch = (uint32_t)subresourcelayout.depthPitch; + uint32_t mip_width = num_blocks_x; + for (uint32_t mip = 0; mip < texture->desc.mip_levels; ++mip) + { + SubresourceData& subresourcedata = internal_state->mapped_subresources[subresourceIndex++]; + subresourcedata.data_ptr = (uint8_t*)texture->mapped_data + subresourceDataOffset; + subresourcedata.row_pitch = rowpitch; + subresourcedata.slice_pitch = slicepitch; + subresourceDataOffset += std::max(rowpitch * mip_width, slicepitch); + rowpitch = std::max(data_stride, rowpitch / 2); + slicepitch = std::max(data_stride, slicepitch / 4); // squared reduction + mip_width = std::max(1u, mip_width / 2); + } + } + texture->mapped_subresources = internal_state->mapped_subresources.data(); + texture->mapped_subresource_count = internal_state->mapped_subresources.size(); } vkDestroyImage(device, image, nullptr); @@ -7015,38 +7052,65 @@ using namespace vulkan_internal; if (src_desc.usage == Usage::UPLOAD) { VkBufferImageCopy copy = {}; - copy.imageExtent.width = dst_desc.width; - copy.imageExtent.height = dst_desc.height; - copy.imageExtent.depth = 1; - copy.imageExtent.width = dst_desc.width; + copy.imageSubresource.baseArrayLayer = 0; + copy.imageSubresource.layerCount = dst_desc.array_size; copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copy.imageSubresource.layerCount = 1; - vkCmdCopyBufferToImage( - commandlist.GetCommandBuffer(), - internal_state_src->staging_resource, - internal_state_dst->resource, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, - © - ); + const uint32_t data_stride = GetFormatStride(dst_desc.format); + uint32_t mip_width = dst_desc.width; + uint32_t mip_height = dst_desc.height; + uint32_t mip_depth = dst_desc.depth; + for (uint32_t mip = 0; mip < dst_desc.mip_levels; ++mip) + { + copy.imageExtent.width = mip_width; + copy.imageExtent.height = mip_height; + copy.imageExtent.depth = mip_depth; + copy.imageSubresource.mipLevel = mip; + vkCmdCopyBufferToImage( + commandlist.GetCommandBuffer(), + internal_state_src->staging_resource, + internal_state_dst->resource, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + © + ); + + copy.bufferOffset += mip_width * mip_height * mip_depth * data_stride; + mip_width = std::max(1u, mip_width / 2); + mip_height = std::max(1u, mip_height / 2); + mip_depth = std::max(1u, mip_depth / 2); + } + } else if (dst_desc.usage == Usage::READBACK) { VkBufferImageCopy copy = {}; - copy.imageExtent.width = src_desc.width; - copy.imageExtent.height = src_desc.height; - copy.imageExtent.depth = 1; - copy.imageExtent.width = src_desc.width; + copy.imageSubresource.baseArrayLayer = 0; + copy.imageSubresource.layerCount = dst_desc.array_size; copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copy.imageSubresource.layerCount = 1; - vkCmdCopyImageToBuffer( - commandlist.GetCommandBuffer(), - internal_state_src->resource, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - internal_state_dst->staging_resource, - 1, - © - ); + const uint32_t data_stride = GetFormatStride(dst_desc.format); + uint32_t mip_width = dst_desc.width; + uint32_t mip_height = dst_desc.height; + uint32_t mip_depth = dst_desc.depth; + for (uint32_t mip = 0; mip < dst_desc.mip_levels; ++mip) + { + copy.imageExtent.width = mip_width; + copy.imageExtent.height = mip_height; + copy.imageExtent.depth = mip_depth; + copy.imageSubresource.mipLevel = mip; + vkCmdCopyImageToBuffer( + commandlist.GetCommandBuffer(), + internal_state_src->resource, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + internal_state_dst->staging_resource, + 1, + © + ); + + copy.bufferOffset += mip_width * mip_height * mip_depth * data_stride; + mip_width = std::max(1u, mip_width / 2); + mip_height = std::max(1u, mip_height / 2); + mip_depth = std::max(1u, mip_depth / 2); + } } else { diff --git a/WickedEngine/wiHelper.cpp b/WickedEngine/wiHelper.cpp index af7f826ff..ed56af51b 100644 --- a/WickedEngine/wiHelper.cpp +++ b/WickedEngine/wiHelper.cpp @@ -121,7 +121,6 @@ namespace wi::helper Texture stagingTex; TextureDesc staging_desc = desc; staging_desc.usage = Usage::READBACK; - staging_desc.mip_levels = 1; staging_desc.layout = ResourceState::COPY_DST; staging_desc.bind_flags = BindFlag::NONE; staging_desc.misc_flags = ResourceMiscFlag::NONE; @@ -149,33 +148,48 @@ namespace wi::helper device->SubmitCommandLists(); device->WaitForGPU(); - desc.width /= GetFormatBlockSize(desc.format); - desc.height /= GetFormatBlockSize(desc.format); - uint32_t data_count = desc.width * desc.height; - uint32_t data_stride = GetFormatStride(desc.format); - uint32_t data_size = data_count * data_stride; - texturedata.clear(); - texturedata.resize(data_size); if (stagingTex.mapped_data != nullptr) { - if (stagingTex.mapped_rowpitch / data_stride != desc.width) + texturedata.resize(stagingTex.mapped_size); + + const uint32_t data_stride = GetFormatStride(desc.format); + const uint32_t block_size = GetFormatBlockSize(desc.format); + const uint32_t num_blocks_x = desc.width / block_size; + const uint32_t num_blocks_y = desc.height / block_size; + size_t cpy_offset = 0; + size_t subresourceIndex = 0; + for (uint32_t layer = 0; layer < desc.array_size; ++layer) { - // Copy padded texture row by row: - const uint32_t cpysize = desc.width * data_stride; - for (uint32_t i = 0; i < desc.height; ++i) + uint32_t mip_width = num_blocks_x; + uint32_t mip_height = num_blocks_y; + uint32_t mip_depth = desc.depth; + for (uint32_t mip = 0; mip < desc.mip_levels; ++mip) { - void* src = (void*)((size_t)stagingTex.mapped_data + size_t(i * stagingTex.mapped_rowpitch)); - void* dst = (void*)((size_t)texturedata.data() + size_t(i * cpysize)); - memcpy(dst, src, cpysize); + assert(subresourceIndex < stagingTex.mapped_subresource_count); + const SubresourceData& subresourcedata = stagingTex.mapped_subresources[subresourceIndex++]; + const size_t dst_rowpitch = mip_width * data_stride; + for (uint32_t z = 0; z < mip_depth; ++z) + { + uint8_t* dst_slice = texturedata.data() + cpy_offset; + uint8_t* src_slice = (uint8_t*)subresourcedata.data_ptr + subresourcedata.slice_pitch * z; + for (uint32_t i = 0; i < mip_height; ++i) + { + std::memcpy( + dst_slice + i * dst_rowpitch, + src_slice + i * subresourcedata.row_pitch, + subresourcedata.row_pitch + ); + } + cpy_offset += mip_height * dst_rowpitch; + } + + mip_width = std::max(1u, mip_width / 2); + mip_height = std::max(1u, mip_height / 2); + mip_depth = std::max(1u, mip_depth / 2); } } - else - { - // Copy whole - std::memcpy(texturedata.data(), stagingTex.mapped_data, texturedata.size()); - } } else { @@ -200,12 +214,40 @@ namespace wi::helper bool saveTextureToMemoryFile(const wi::vector& texturedata, const wi::graphics::TextureDesc& desc, const std::string& fileExtension, wi::vector& filedata) { using namespace wi::graphics; - uint32_t data_count = desc.width * desc.height; + const uint32_t data_stride = GetFormatStride(desc.format); + + struct MipDesc + { + const uint8_t* address = nullptr; + uint32_t width = 0; + uint32_t height = 0; + uint32_t depth = 0; + }; + wi::vector mips; + mips.reserve(desc.mip_levels); + + uint32_t data_count = 0; + uint32_t mip_width = desc.width; + uint32_t mip_height = desc.height; + uint32_t mip_depth = desc.depth; + for (uint32_t mip = 0; mip < desc.mip_levels; ++mip) + { + MipDesc& mipdesc = mips.emplace_back(); + mipdesc.address = texturedata.data() + data_count * data_stride; + data_count += mip_width * mip_height * mip_depth; + mipdesc.width = mip_width; + mipdesc.height = mip_height; + mipdesc.depth = mip_depth; + mip_width = std::max(1u, mip_width / 2); + mip_height = std::max(1u, mip_height / 2); + mip_depth = std::max(1u, mip_depth / 2); + } std::string extension = wi::helper::toUpper(fileExtension); bool basis = !extension.compare("BASIS"); bool ktx2 = !extension.compare("KTX2"); basisu::image basis_image; + basisu::vector basis_mipmaps; if (desc.format == Format::R10G10B10A2_UNORM) { @@ -343,9 +385,24 @@ namespace wi::helper if (basis_image.get_total_pixels() == 0) { basis_image.init(texturedata.data(), desc.width, desc.height, 4); + if (desc.mip_levels > 1) + { + basis_mipmaps.reserve(desc.mip_levels - 1); + for (uint32_t mip = 1; mip < desc.mip_levels; ++mip) + { + basisu::image basis_mip; + const MipDesc& mipdesc = mips[mip]; + basis_mip.init(mipdesc.address, mipdesc.width, mipdesc.height, 4); + basis_mipmaps.push_back(basis_mip); + } + } } basisu::basis_compressor_params params; params.m_source_images.push_back(basis_image); + if (desc.mip_levels > 1) + { + params.m_source_mipmap_images.push_back(basis_mipmaps); + } if (ktx2) { params.m_create_ktx2_file = true; @@ -359,7 +416,10 @@ namespace wi::helper #else params.m_compression_level = basisu::BASISU_MAX_COMPRESSION_LEVEL; #endif - params.m_mip_gen = true; + // Disable CPU mipmap generation: + // instead we provide mipmap data that was downloaded from the GPU with m_source_mipmap_images. + // This is better, because engine specific mipgen options will be retained, such as coverage preserving mipmaps + params.m_mip_gen = false; params.m_pSel_codebook = &g_basis_global_codebook; params.m_quality_level = basisu::BASISU_QUALITY_MAX; params.m_multithreading = true; @@ -387,6 +447,11 @@ namespace wi::helper return true; } } + else + { + wi::backlog::post("basisu::basis_compressor::process() failure!", wi::backlog::LogLevel::Error); + assert(0); + } } return false; } @@ -408,21 +473,24 @@ namespace wi::helper src_data = basis_image.get_ptr(); } + static int mip_request = 0; // you can use this while debugging to write specific mip level to file (todo: option param?) + const MipDesc& mip = mips[mip_request]; + if (!extension.compare("JPG") || !extension.compare("JPEG")) { - write_result = stbi_write_jpg_to_func(func, &filedata, (int)desc.width, (int)desc.height, 4, src_data, 100); + write_result = stbi_write_jpg_to_func(func, &filedata, (int)mip.width, (int)mip.height, 4, mip.address, 100); } else if (!extension.compare("PNG")) { - write_result = stbi_write_png_to_func(func, &filedata, (int)desc.width, (int)desc.height, 4, src_data, 0); + write_result = stbi_write_png_to_func(func, &filedata, (int)mip.width, (int)mip.height, 4, mip.address, 0); } else if (!extension.compare("TGA")) { - write_result = stbi_write_tga_to_func(func, &filedata, (int)desc.width, (int)desc.height, 4, src_data); + write_result = stbi_write_tga_to_func(func, &filedata, (int)mip.width, (int)mip.height, 4, mip.address); } else if (!extension.compare("BMP")) { - write_result = stbi_write_bmp_to_func(func, &filedata, (int)desc.width, (int)desc.height, 4, src_data); + write_result = stbi_write_bmp_to_func(func, &filedata, (int)mip.width, (int)mip.height, 4, mip.address); } else { diff --git a/WickedEngine/wiResourceManager.cpp b/WickedEngine/wiResourceManager.cpp index 1a8e96c5f..e99b45fc3 100644 --- a/WickedEngine/wiResourceManager.cpp +++ b/WickedEngine/wiResourceManager.cpp @@ -3,6 +3,7 @@ #include "wiHelper.h" #include "wiTextureHelper.h" #include "wiUnorderedMap.h" +#include "wiBacklog.h" #include "Utility/stb_image.h" #include "Utility/qoi.h" @@ -235,11 +236,14 @@ namespace wi { // all subresources will use one allocation for transcoder destination, so compute combined size: size_t transcoded_data_size = 0; - for (uint32_t layer = 0; layer < std::max(1u, transcoder.get_layers()); ++layer) + const uint32_t layers = std::max(1u, transcoder.get_layers()); + const uint32_t faces = transcoder.get_faces(); + const uint32_t levels = transcoder.get_levels(); + for (uint32_t layer = 0; layer < layers; ++layer) { - for (uint32_t face = 0; face < transcoder.get_faces(); ++face) + for (uint32_t face = 0; face < faces; ++face) { - for (uint32_t mip = 0; mip < transcoder.get_levels(); ++mip) + for (uint32_t mip = 0; mip < levels; ++mip) { basist::ktx2_image_level_info level_info; if (transcoder.get_image_level_info(level_info, mip, layer, face)) @@ -249,15 +253,15 @@ namespace wi } } } - wi::vector transcoded_data(transcoded_data_size); + wi::vector transcoded_data(transcoded_data_size); wi::vector InitData; size_t transcoded_data_offset = 0; - for (uint32_t layer = 0; layer < std::max(1u, transcoder.get_layers()); ++layer) + for (uint32_t layer = 0; layer < layers; ++layer) { - for (uint32_t face = 0; face < transcoder.get_faces(); ++face) + for (uint32_t face = 0; face < faces; ++face) { - for (uint32_t mip = 0; mip < transcoder.get_levels(); ++mip) + for (uint32_t mip = 0; mip < levels; ++mip) { basist::ktx2_image_level_info level_info; if (transcoder.get_image_level_info(level_info, mip, layer, face)) @@ -265,7 +269,9 @@ namespace wi void* data_ptr = transcoded_data.data() + transcoded_data_offset; transcoded_data_offset += level_info.m_total_blocks * bytes_per_block; if (transcoder.transcode_image_level( - mip, layer, face, + mip, + layer, + face, data_ptr, level_info.m_total_blocks, fmt @@ -277,6 +283,16 @@ namespace wi subresourceData.slice_pitch = subresourceData.row_pitch * level_info.m_num_blocks_y; InitData.push_back(subresourceData); } + else + { + wi::backlog::post("KTX2 transcoding error while loading image!", wi::backlog::LogLevel::Error); + assert(0); + } + } + else + { + wi::backlog::post("KTX2 transcoding error while loading image level info!", wi::backlog::LogLevel::Error); + assert(0); } } } @@ -334,7 +350,7 @@ namespace wi transcoded_data_size += level_info.m_total_blocks * bytes_per_block; } } - wi::vector transcoded_data(transcoded_data_size); + wi::vector transcoded_data(transcoded_data_size); wi::vector InitData; size_t transcoded_data_offset = 0; @@ -346,10 +362,12 @@ namespace wi void* data_ptr = transcoded_data.data() + transcoded_data_offset; transcoded_data_offset += level_info.m_total_blocks * bytes_per_block; if (transcoder.transcode_image_level( - filedata, (uint32_t)filesize, image_index, + filedata, + (uint32_t)filesize, + image_index, mip, data_ptr, - info.m_total_blocks, + level_info.m_total_blocks, fmt )) { @@ -359,6 +377,16 @@ namespace wi subresourceData.slice_pitch = subresourceData.row_pitch * level_info.m_num_blocks_y; InitData.push_back(subresourceData); } + else + { + wi::backlog::post("BASIS transcoding error while loading image!", wi::backlog::LogLevel::Error); + assert(0); + } + } + else + { + wi::backlog::post("BASIS transcoding error while loading image level info!", wi::backlog::LogLevel::Error); + assert(0); } } diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 2d0f7e1a1..c03580c83 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 60; // minor bug fixes, alterations, refactors, updates - const int revision = 87; + const int revision = 88; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);