Optimize B2T and T2B copies with multiple texture layers on D3D12
This patch optimizes the implementation of buffer-to-texture and texture-to-buffer computations on D3D12 backends by computing TextureCopySplits once for all the 2D texture array layers in the copy instead of computing it once per layer. You can see the comments in the function D3D12::ComputeTextureCopySplits() for more details. BUG=dawn:453 TEST=dawn_end2end_tests Change-Id: I1b66d24d2418147957fbe03e2c25144bd043a62e Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/24643 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org> Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
This commit is contained in:
parent
b31f5e717e
commit
3b17f0bde8
|
@ -80,6 +80,67 @@ namespace dawn_native { namespace d3d12 {
|
||||||
copySize.depth == srcSize.depth;
|
copySize.depth == srcSize.depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RecordCopyBufferToTextureFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
|
||||||
|
const Texture2DCopySplit& baseCopySplit,
|
||||||
|
Buffer* buffer,
|
||||||
|
uint64_t baseOffset,
|
||||||
|
uint64_t bufferBytesPerRow,
|
||||||
|
Texture* texture,
|
||||||
|
uint32_t textureMiplevel,
|
||||||
|
uint32_t textureSlice) {
|
||||||
|
const D3D12_TEXTURE_COPY_LOCATION textureLocation =
|
||||||
|
ComputeTextureCopyLocationForTexture(texture, textureMiplevel, textureSlice);
|
||||||
|
|
||||||
|
const uint64_t offset = baseCopySplit.offset + baseOffset;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < baseCopySplit.count; ++i) {
|
||||||
|
const Texture2DCopySplit::CopyInfo& info = baseCopySplit.copies[i];
|
||||||
|
|
||||||
|
// TODO(jiawei.shao@intel.com): pre-compute bufferLocation and sourceRegion as
|
||||||
|
// members in Texture2DCopySplit::CopyInfo.
|
||||||
|
const D3D12_TEXTURE_COPY_LOCATION bufferLocation =
|
||||||
|
ComputeBufferLocationForCopyTextureRegion(texture, buffer->GetD3D12Resource(),
|
||||||
|
info.bufferSize, offset,
|
||||||
|
bufferBytesPerRow);
|
||||||
|
const D3D12_BOX sourceRegion =
|
||||||
|
ComputeD3D12BoxFromOffsetAndSize(info.bufferOffset, info.copySize);
|
||||||
|
|
||||||
|
commandList->CopyTextureRegion(&textureLocation, info.textureOffset.x,
|
||||||
|
info.textureOffset.y, info.textureOffset.z,
|
||||||
|
&bufferLocation, &sourceRegion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecordCopyTextureToBufferFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
|
||||||
|
const Texture2DCopySplit& baseCopySplit,
|
||||||
|
Buffer* buffer,
|
||||||
|
uint64_t baseOffset,
|
||||||
|
uint64_t bufferBytesPerRow,
|
||||||
|
Texture* texture,
|
||||||
|
uint32_t textureMiplevel,
|
||||||
|
uint32_t textureSlice) {
|
||||||
|
const D3D12_TEXTURE_COPY_LOCATION textureLocation =
|
||||||
|
ComputeTextureCopyLocationForTexture(texture, textureMiplevel, textureSlice);
|
||||||
|
|
||||||
|
const uint64_t offset = baseCopySplit.offset + baseOffset;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < baseCopySplit.count; ++i) {
|
||||||
|
const Texture2DCopySplit::CopyInfo& info = baseCopySplit.copies[i];
|
||||||
|
|
||||||
|
// TODO(jiawei.shao@intel.com): pre-compute bufferLocation and sourceRegion as
|
||||||
|
// members in Texture2DCopySplit::CopyInfo.
|
||||||
|
const D3D12_TEXTURE_COPY_LOCATION bufferLocation =
|
||||||
|
ComputeBufferLocationForCopyTextureRegion(texture, buffer->GetD3D12Resource(),
|
||||||
|
info.bufferSize, offset,
|
||||||
|
bufferBytesPerRow);
|
||||||
|
const D3D12_BOX sourceRegion =
|
||||||
|
ComputeD3D12BoxFromOffsetAndSize(info.textureOffset, info.copySize);
|
||||||
|
|
||||||
|
commandList->CopyTextureRegion(&bufferLocation, info.bufferOffset.x,
|
||||||
|
info.bufferOffset.y, info.bufferOffset.z,
|
||||||
|
&textureLocation, &sourceRegion);
|
||||||
|
}
|
||||||
|
}
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
class BindGroupStateTracker : public BindGroupAndStorageBarrierTrackerBase<false, uint64_t> {
|
class BindGroupStateTracker : public BindGroupAndStorageBarrierTrackerBase<false, uint64_t> {
|
||||||
|
@ -617,46 +678,42 @@ namespace dawn_native { namespace d3d12 {
|
||||||
texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst,
|
texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst,
|
||||||
subresources);
|
subresources);
|
||||||
|
|
||||||
|
// See comments in ComputeTextureCopySplits() for more details.
|
||||||
|
const TextureCopySplits copySplits = ComputeTextureCopySplits(
|
||||||
|
copy->destination.origin, copy->copySize, texture->GetFormat(),
|
||||||
|
copy->source.offset, copy->source.bytesPerRow, copy->source.rowsPerImage);
|
||||||
|
|
||||||
const uint64_t bytesPerSlice =
|
const uint64_t bytesPerSlice =
|
||||||
copy->source.bytesPerRow *
|
copy->source.bytesPerRow *
|
||||||
(copy->source.rowsPerImage / texture->GetFormat().blockHeight);
|
(copy->source.rowsPerImage / texture->GetFormat().blockHeight);
|
||||||
|
|
||||||
const dawn_native::Extent3D copyOneLayerSize = {copy->copySize.width,
|
// copySplits.copies2D[1] is always calculated for the second copy slice with
|
||||||
copy->copySize.height, 1};
|
// extra "bytesPerSlice" copy offset compared with the first copy slice. So
|
||||||
uint64_t bufferOffsetForNextSlice = 0;
|
// here we use an array bufferOffsetsForNextSlice to record the extra offsets
|
||||||
for (uint32_t copySlice = copy->destination.origin.z;
|
// for each copy slice: bufferOffsetsForNextSlice[0] is the extra offset for
|
||||||
copySlice < copy->destination.origin.z + copy->copySize.depth;
|
// the next copy slice that uses copySplits.copies2D[0], and
|
||||||
++copySlice) {
|
// bufferOffsetsForNextSlice[1] is the extra offset for the next copy slice
|
||||||
// TODO(jiawei.shao@intel.com): compute copySplit once for all texture array
|
// that uses copySplits.copies2D[1].
|
||||||
// layers when possible.
|
std::array<uint64_t, TextureCopySplits::kMaxTextureCopySplits>
|
||||||
Origin3D destinationOriginInSubresource = copy->destination.origin;
|
bufferOffsetsForNextSlice = {{0u, 0u}};
|
||||||
destinationOriginInSubresource.z = 0;
|
for (uint32_t copySlice = 0; copySlice < copy->copySize.depth; ++copySlice) {
|
||||||
auto copySplit = ComputeTextureCopySplit(
|
const uint32_t splitIndex = copySlice % copySplits.copies2D.size();
|
||||||
destinationOriginInSubresource, copyOneLayerSize, texture->GetFormat(),
|
|
||||||
bufferOffsetForNextSlice + copy->source.offset,
|
|
||||||
copy->source.bytesPerRow, copy->source.rowsPerImage);
|
|
||||||
|
|
||||||
D3D12_TEXTURE_COPY_LOCATION textureLocation =
|
const Texture2DCopySplit& copySplitPerLayerBase =
|
||||||
ComputeTextureCopyLocationForTexture(
|
copySplits.copies2D[splitIndex];
|
||||||
texture, copy->destination.mipLevel, copySlice);
|
const uint64_t bufferOffsetForNextSlice =
|
||||||
|
bufferOffsetsForNextSlice[splitIndex];
|
||||||
|
const uint32_t copyTextureLayer = copySlice + copy->destination.origin.z;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
RecordCopyBufferToTextureFromTextureCopySplit(
|
||||||
const TextureCopySplit::CopyInfo& info = copySplit.copies[i];
|
commandList, copySplitPerLayerBase, buffer, bufferOffsetForNextSlice,
|
||||||
|
copy->source.bytesPerRow, texture, copy->destination.mipLevel,
|
||||||
|
copyTextureLayer);
|
||||||
|
|
||||||
D3D12_TEXTURE_COPY_LOCATION bufferLocation =
|
bufferOffsetsForNextSlice[splitIndex] +=
|
||||||
ComputeBufferLocationForCopyTextureRegion(
|
bytesPerSlice * copySplits.copies2D.size();
|
||||||
texture, buffer->GetD3D12Resource(), info.bufferSize,
|
|
||||||
copySplit.offset, copy->source.bytesPerRow);
|
|
||||||
D3D12_BOX sourceRegion =
|
|
||||||
ComputeD3D12BoxFromOffsetAndSize(info.bufferOffset, info.copySize);
|
|
||||||
|
|
||||||
commandList->CopyTextureRegion(
|
|
||||||
&textureLocation, info.textureOffset.x, info.textureOffset.y,
|
|
||||||
info.textureOffset.z, &bufferLocation, &sourceRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferOffsetForNextSlice += bytesPerSlice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,46 +731,43 @@ namespace dawn_native { namespace d3d12 {
|
||||||
subresources);
|
subresources);
|
||||||
buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopyDst);
|
buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopyDst);
|
||||||
|
|
||||||
|
// See comments around ComputeTextureCopySplits() for more details.
|
||||||
|
const TextureCopySplits copySplits = ComputeTextureCopySplits(
|
||||||
|
copy->source.origin, copy->copySize, texture->GetFormat(),
|
||||||
|
copy->destination.offset, copy->destination.bytesPerRow,
|
||||||
|
copy->destination.rowsPerImage);
|
||||||
|
|
||||||
const uint64_t bytesPerSlice =
|
const uint64_t bytesPerSlice =
|
||||||
copy->destination.bytesPerRow *
|
copy->destination.bytesPerRow *
|
||||||
(copy->destination.rowsPerImage / texture->GetFormat().blockHeight);
|
(copy->destination.rowsPerImage / texture->GetFormat().blockHeight);
|
||||||
|
|
||||||
const dawn_native::Extent3D copyOneLayerSize = {copy->copySize.width,
|
// copySplits.copies2D[1] is always calculated for the second copy slice with
|
||||||
copy->copySize.height, 1};
|
// extra "bytesPerSlice" copy offset compared with the first copy slice. So
|
||||||
uint64_t bufferOffsetForNextSlice = 0;
|
// here we use an array bufferOffsetsForNextSlice to record the extra offsets
|
||||||
for (uint32_t copySlice = copy->source.origin.z;
|
// for each copy slice: bufferOffsetsForNextSlice[0] is the extra offset for
|
||||||
copySlice < copy->source.origin.z + copy->copySize.depth; ++copySlice) {
|
// the next copy slice that uses copySplits.copies2D[0], and
|
||||||
// TODO(jiawei.shao@intel.com): compute copySplit once for all texture array
|
// bufferOffsetsForNextSlice[1] is the extra offset for the next copy slice
|
||||||
// layers when possible.
|
// that uses copySplits.copies2D[1].
|
||||||
Origin3D sourceOriginInSubresource = copy->source.origin;
|
std::array<uint64_t, TextureCopySplits::kMaxTextureCopySplits>
|
||||||
sourceOriginInSubresource.z = 0;
|
bufferOffsetsForNextSlice = {{0u, 0u}};
|
||||||
TextureCopySplit copySplit = ComputeTextureCopySplit(
|
for (uint32_t copySlice = 0; copySlice < copy->copySize.depth; ++copySlice) {
|
||||||
sourceOriginInSubresource, copyOneLayerSize, texture->GetFormat(),
|
const uint32_t splitIndex = copySlice % copySplits.copies2D.size();
|
||||||
bufferOffsetForNextSlice + copy->destination.offset,
|
|
||||||
copy->destination.bytesPerRow, copy->destination.rowsPerImage);
|
|
||||||
|
|
||||||
D3D12_TEXTURE_COPY_LOCATION textureLocation =
|
const Texture2DCopySplit& copySplitPerLayerBase =
|
||||||
ComputeTextureCopyLocationForTexture(texture, copy->source.mipLevel,
|
copySplits.copies2D[splitIndex];
|
||||||
copySlice);
|
const uint64_t bufferOffsetForNextSlice =
|
||||||
|
bufferOffsetsForNextSlice[splitIndex];
|
||||||
|
const uint32_t copyTextureLayer = copySlice + copy->source.origin.z;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
RecordCopyTextureToBufferFromTextureCopySplit(
|
||||||
const TextureCopySplit::CopyInfo& info = copySplit.copies[i];
|
commandList, copySplitPerLayerBase, buffer, bufferOffsetForNextSlice,
|
||||||
|
copy->destination.bytesPerRow, texture, copy->source.mipLevel,
|
||||||
|
copyTextureLayer);
|
||||||
|
|
||||||
D3D12_TEXTURE_COPY_LOCATION bufferLocation =
|
bufferOffsetsForNextSlice[splitIndex] +=
|
||||||
ComputeBufferLocationForCopyTextureRegion(
|
bytesPerSlice * copySplits.copies2D.size();
|
||||||
texture, buffer->GetD3D12Resource(), info.bufferSize,
|
|
||||||
copySplit.offset, copy->destination.bytesPerRow);
|
|
||||||
|
|
||||||
D3D12_BOX sourceRegion =
|
|
||||||
ComputeD3D12BoxFromOffsetAndSize(info.textureOffset, info.copySize);
|
|
||||||
|
|
||||||
commandList->CopyTextureRegion(&bufferLocation, info.bufferOffset.x,
|
|
||||||
info.bufferOffset.y, info.bufferOffset.z,
|
|
||||||
&textureLocation, &sourceRegion);
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferOffsetForNextSlice += bytesPerSlice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,13 +37,13 @@ namespace dawn_native { namespace d3d12 {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TextureCopySplit ComputeTextureCopySplit(Origin3D origin,
|
Texture2DCopySplit ComputeTextureCopySplit(Origin3D origin,
|
||||||
Extent3D copySize,
|
Extent3D copySize,
|
||||||
const Format& format,
|
const Format& format,
|
||||||
uint64_t offset,
|
uint64_t offset,
|
||||||
uint32_t bytesPerRow,
|
uint32_t bytesPerRow,
|
||||||
uint32_t rowsPerImage) {
|
uint32_t rowsPerImage) {
|
||||||
TextureCopySplit copy;
|
Texture2DCopySplit copy;
|
||||||
|
|
||||||
ASSERT(bytesPerRow % format.blockByteSize == 0);
|
ASSERT(bytesPerRow % format.blockByteSize == 0);
|
||||||
|
|
||||||
|
@ -183,4 +183,50 @@ namespace dawn_native { namespace d3d12 {
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextureCopySplits ComputeTextureCopySplits(Origin3D origin,
|
||||||
|
Extent3D copySize,
|
||||||
|
const Format& format,
|
||||||
|
uint64_t offset,
|
||||||
|
uint32_t bytesPerRow,
|
||||||
|
uint32_t rowsPerImage) {
|
||||||
|
TextureCopySplits copies;
|
||||||
|
|
||||||
|
const uint64_t bytesPerSlice = bytesPerRow * (rowsPerImage / format.blockHeight);
|
||||||
|
|
||||||
|
// The function ComputeTextureCopySplit() decides how to split the copy based on:
|
||||||
|
// - the alignment of the buffer offset with D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT (512)
|
||||||
|
// - the alignment of the buffer offset with D3D12_TEXTURE_DATA_PITCH_ALIGNMENT (256)
|
||||||
|
// Each slice of a 2D array or 3D copy might need to be split, but because of the WebGPU
|
||||||
|
// constraint that "bytesPerRow" must be a multiple of 256, all odd (resp. all even) slices
|
||||||
|
// will be at an offset multiple of 512 of each other, which means they will all result in
|
||||||
|
// the same 2D split. Thus we can just compute the copy splits for the first and second
|
||||||
|
// slices, and reuse them for the remaining slices by adding the related offset of each
|
||||||
|
// slice. Moreover, if "rowsPerImage" is even, both the first and second copy layers can
|
||||||
|
// share the same copy split, so in this situation we just need to compute copy split once
|
||||||
|
// and reuse it for all the slices.
|
||||||
|
const dawn_native::Extent3D copyOneLayerSize = {copySize.width, copySize.height, 1};
|
||||||
|
const dawn_native::Origin3D copyFirstLayerOrigin = {origin.x, origin.y, 0};
|
||||||
|
|
||||||
|
copies.copies2D[0] = ComputeTextureCopySplit(copyFirstLayerOrigin, copyOneLayerSize, format,
|
||||||
|
offset, bytesPerRow, rowsPerImage);
|
||||||
|
|
||||||
|
// When the copy only refers one texture 2D array layer copies.copies2D[1] will never be
|
||||||
|
// used so we can safely early return here.
|
||||||
|
if (copySize.depth == 1) {
|
||||||
|
return copies;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesPerSlice % D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT == 0) {
|
||||||
|
copies.copies2D[1] = copies.copies2D[0];
|
||||||
|
copies.copies2D[1].offset += bytesPerSlice;
|
||||||
|
} else {
|
||||||
|
const uint64_t bufferOffsetNextLayer = offset + bytesPerSlice;
|
||||||
|
copies.copies2D[1] =
|
||||||
|
ComputeTextureCopySplit(copyFirstLayerOrigin, copyOneLayerSize, format,
|
||||||
|
bufferOffsetNextLayer, bytesPerRow, rowsPerImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return copies;
|
||||||
|
}
|
||||||
|
|
||||||
}} // namespace dawn_native::d3d12
|
}} // namespace dawn_native::d3d12
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace dawn_native {
|
||||||
|
|
||||||
namespace dawn_native { namespace d3d12 {
|
namespace dawn_native { namespace d3d12 {
|
||||||
|
|
||||||
struct TextureCopySplit {
|
struct Texture2DCopySplit {
|
||||||
static constexpr unsigned int kMaxTextureCopyRegions = 2;
|
static constexpr unsigned int kMaxTextureCopyRegions = 2;
|
||||||
|
|
||||||
struct CopyInfo {
|
struct CopyInfo {
|
||||||
|
@ -43,12 +43,25 @@ namespace dawn_native { namespace d3d12 {
|
||||||
std::array<CopyInfo, kMaxTextureCopyRegions> copies;
|
std::array<CopyInfo, kMaxTextureCopyRegions> copies;
|
||||||
};
|
};
|
||||||
|
|
||||||
TextureCopySplit ComputeTextureCopySplit(Origin3D origin,
|
struct TextureCopySplits {
|
||||||
Extent3D copySize,
|
static constexpr uint32_t kMaxTextureCopySplits = 2;
|
||||||
const Format& format,
|
|
||||||
uint64_t offset,
|
std::array<Texture2DCopySplit, kMaxTextureCopySplits> copies2D;
|
||||||
uint32_t bytesPerRow,
|
};
|
||||||
uint32_t rowsPerImage);
|
|
||||||
|
Texture2DCopySplit ComputeTextureCopySplit(Origin3D origin,
|
||||||
|
Extent3D copySize,
|
||||||
|
const Format& format,
|
||||||
|
uint64_t offset,
|
||||||
|
uint32_t bytesPerRow,
|
||||||
|
uint32_t rowsPerImage);
|
||||||
|
|
||||||
|
TextureCopySplits ComputeTextureCopySplits(Origin3D origin,
|
||||||
|
Extent3D copySize,
|
||||||
|
const Format& format,
|
||||||
|
uint64_t offset,
|
||||||
|
uint32_t bytesPerRow,
|
||||||
|
uint32_t rowsPerImage);
|
||||||
}} // namespace dawn_native::d3d12
|
}} // namespace dawn_native::d3d12
|
||||||
|
|
||||||
#endif // DAWNNATIVE_D3D12_TEXTURECOPYSPLITTER_H_
|
#endif // DAWNNATIVE_D3D12_TEXTURECOPYSPLITTER_H_
|
||||||
|
|
|
@ -926,7 +926,7 @@ namespace dawn_native { namespace d3d12 {
|
||||||
Extent3D copySize = GetMipLevelVirtualSize(level);
|
Extent3D copySize = GetMipLevelVirtualSize(level);
|
||||||
|
|
||||||
uint32_t rowsPerImage = GetHeight();
|
uint32_t rowsPerImage = GetHeight();
|
||||||
TextureCopySplit copySplit =
|
Texture2DCopySplit copySplit =
|
||||||
ComputeTextureCopySplit({0, 0, 0}, copySize, GetFormat(),
|
ComputeTextureCopySplit({0, 0, 0}, copySize, GetFormat(),
|
||||||
uploadHandle.startOffset, bytesPerRow, rowsPerImage);
|
uploadHandle.startOffset, bytesPerRow, rowsPerImage);
|
||||||
|
|
||||||
|
@ -942,7 +942,7 @@ namespace dawn_native { namespace d3d12 {
|
||||||
D3D12_TEXTURE_COPY_LOCATION textureLocation =
|
D3D12_TEXTURE_COPY_LOCATION textureLocation =
|
||||||
ComputeTextureCopyLocationForTexture(this, level, layer);
|
ComputeTextureCopyLocationForTexture(this, level, layer);
|
||||||
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
||||||
TextureCopySplit::CopyInfo& info = copySplit.copies[i];
|
Texture2DCopySplit::CopyInfo& info = copySplit.copies[i];
|
||||||
|
|
||||||
D3D12_TEXTURE_COPY_LOCATION bufferLocation =
|
D3D12_TEXTURE_COPY_LOCATION bufferLocation =
|
||||||
ComputeBufferLocationForCopyTextureRegion(
|
ComputeBufferLocationForCopyTextureRegion(
|
||||||
|
|
|
@ -746,6 +746,60 @@ TEST_P(CopyTests_T2B, Texture2DArrayRegionNonzeroRowsPerImage) {
|
||||||
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
|
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test a special code path in the D3D12 backends when (BytesPerRow * RowsPerImage) is not a
|
||||||
|
// multiple of 512.
|
||||||
|
TEST_P(CopyTests_T2B, Texture2DArrayRegionWithOffsetOddRowsPerImage) {
|
||||||
|
// TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
|
||||||
|
// with swiftshader.
|
||||||
|
DAWN_SKIP_TEST_IF(IsSwiftshader());
|
||||||
|
|
||||||
|
constexpr uint32_t kWidth = 64;
|
||||||
|
constexpr uint32_t kHeight = 128;
|
||||||
|
constexpr uint32_t kLayers = 8u;
|
||||||
|
constexpr uint32_t kBaseLayer = 2u;
|
||||||
|
constexpr uint32_t kCopyLayers = 5u;
|
||||||
|
|
||||||
|
constexpr uint32_t kRowsPerImage = kHeight + 1;
|
||||||
|
|
||||||
|
TextureSpec textureSpec;
|
||||||
|
textureSpec.copyOrigin = {0, 0, kBaseLayer};
|
||||||
|
textureSpec.textureSize = {kWidth, kHeight, kLayers};
|
||||||
|
textureSpec.level = 0;
|
||||||
|
|
||||||
|
BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
|
||||||
|
bufferSpec.offset += 128u;
|
||||||
|
bufferSpec.size += 128u;
|
||||||
|
bufferSpec.rowsPerImage = kRowsPerImage;
|
||||||
|
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test a special code path in the D3D12 backends when (BytesPerRow * RowsPerImage) is a multiple
|
||||||
|
// of 512.
|
||||||
|
TEST_P(CopyTests_T2B, Texture2DArrayRegionWithOffsetEvenRowsPerImage) {
|
||||||
|
// TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
|
||||||
|
// with swiftshader.
|
||||||
|
DAWN_SKIP_TEST_IF(IsSwiftshader());
|
||||||
|
|
||||||
|
constexpr uint32_t kWidth = 64;
|
||||||
|
constexpr uint32_t kHeight = 128;
|
||||||
|
constexpr uint32_t kLayers = 8u;
|
||||||
|
constexpr uint32_t kBaseLayer = 2u;
|
||||||
|
constexpr uint32_t kCopyLayers = 4u;
|
||||||
|
|
||||||
|
constexpr uint32_t kRowsPerImage = kHeight + 2;
|
||||||
|
|
||||||
|
TextureSpec textureSpec;
|
||||||
|
textureSpec.copyOrigin = {0, 0, kBaseLayer};
|
||||||
|
textureSpec.textureSize = {kWidth, kHeight, kLayers};
|
||||||
|
textureSpec.level = 0;
|
||||||
|
|
||||||
|
BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
|
||||||
|
bufferSpec.offset += 128u;
|
||||||
|
bufferSpec.size += 128u;
|
||||||
|
bufferSpec.rowsPerImage = kRowsPerImage;
|
||||||
|
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
|
||||||
|
}
|
||||||
|
|
||||||
DAWN_INSTANTIATE_TEST(CopyTests_T2B,
|
DAWN_INSTANTIATE_TEST(CopyTests_T2B,
|
||||||
D3D12Backend(),
|
D3D12Backend(),
|
||||||
MetalBackend(),
|
MetalBackend(),
|
||||||
|
@ -1094,6 +1148,60 @@ TEST_P(CopyTests_B2T, Texture2DArrayRegionNonzeroRowsPerImage) {
|
||||||
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
|
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test a special code path in the D3D12 backends when (BytesPerRow * RowsPerImage) is not a
|
||||||
|
// multiple of 512.
|
||||||
|
TEST_P(CopyTests_B2T, Texture2DArrayRegionWithOffsetOddRowsPerImage) {
|
||||||
|
// TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
|
||||||
|
// with swiftshader.
|
||||||
|
DAWN_SKIP_TEST_IF(IsSwiftshader());
|
||||||
|
|
||||||
|
constexpr uint32_t kWidth = 64;
|
||||||
|
constexpr uint32_t kHeight = 128;
|
||||||
|
constexpr uint32_t kLayers = 8u;
|
||||||
|
constexpr uint32_t kBaseLayer = 2u;
|
||||||
|
constexpr uint32_t kCopyLayers = 5u;
|
||||||
|
|
||||||
|
constexpr uint32_t kRowsPerImage = kHeight + 1;
|
||||||
|
|
||||||
|
TextureSpec textureSpec;
|
||||||
|
textureSpec.copyOrigin = {0, 0, kBaseLayer};
|
||||||
|
textureSpec.textureSize = {kWidth, kHeight, kLayers};
|
||||||
|
textureSpec.level = 0;
|
||||||
|
|
||||||
|
BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
|
||||||
|
bufferSpec.offset += 128u;
|
||||||
|
bufferSpec.size += 128u;
|
||||||
|
bufferSpec.rowsPerImage = kRowsPerImage;
|
||||||
|
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test a special code path in the D3D12 backends when (BytesPerRow * RowsPerImage) is a multiple
|
||||||
|
// of 512.
|
||||||
|
TEST_P(CopyTests_B2T, Texture2DArrayRegionWithOffsetEvenRowsPerImage) {
|
||||||
|
// TODO(jiawei.shao@intel.com): investigate why copies with multiple texture array layers fail
|
||||||
|
// with swiftshader.
|
||||||
|
DAWN_SKIP_TEST_IF(IsSwiftshader());
|
||||||
|
|
||||||
|
constexpr uint32_t kWidth = 64;
|
||||||
|
constexpr uint32_t kHeight = 128;
|
||||||
|
constexpr uint32_t kLayers = 8u;
|
||||||
|
constexpr uint32_t kBaseLayer = 2u;
|
||||||
|
constexpr uint32_t kCopyLayers = 5u;
|
||||||
|
|
||||||
|
constexpr uint32_t kRowsPerImage = kHeight + 2;
|
||||||
|
|
||||||
|
TextureSpec textureSpec;
|
||||||
|
textureSpec.copyOrigin = {0, 0, kBaseLayer};
|
||||||
|
textureSpec.textureSize = {kWidth, kHeight, kLayers};
|
||||||
|
textureSpec.level = 0;
|
||||||
|
|
||||||
|
BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers, false);
|
||||||
|
bufferSpec.offset += 128u;
|
||||||
|
bufferSpec.size += 128u;
|
||||||
|
bufferSpec.rowsPerImage = kRowsPerImage;
|
||||||
|
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
|
||||||
|
}
|
||||||
|
|
||||||
DAWN_INSTANTIATE_TEST(CopyTests_B2T,
|
DAWN_INSTANTIATE_TEST(CopyTests_B2T,
|
||||||
D3D12Backend(),
|
D3D12Backend(),
|
||||||
MetalBackend(),
|
MetalBackend(),
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check that each copy region fits inside the buffer footprint
|
// Check that each copy region fits inside the buffer footprint
|
||||||
void ValidateFootprints(const TextureCopySplit& copySplit) {
|
void ValidateFootprints(const Texture2DCopySplit& copySplit) {
|
||||||
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
||||||
const auto& copy = copySplit.copies[i];
|
const auto& copy = copySplit.copies[i];
|
||||||
ASSERT_LE(copy.bufferOffset.x + copy.copySize.width, copy.bufferSize.width);
|
ASSERT_LE(copy.bufferOffset.x + copy.copySize.width, copy.bufferSize.width);
|
||||||
|
@ -54,7 +54,7 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the offset is aligned
|
// Check that the offset is aligned
|
||||||
void ValidateOffset(const TextureCopySplit& copySplit) {
|
void ValidateOffset(const Texture2DCopySplit& copySplit) {
|
||||||
ASSERT_TRUE(Align(copySplit.offset, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT) ==
|
ASSERT_TRUE(Align(copySplit.offset, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT) ==
|
||||||
copySplit.offset);
|
copySplit.offset);
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that no pair of copy regions intersect each other
|
// Check that no pair of copy regions intersect each other
|
||||||
void ValidateDisjoint(const TextureCopySplit& copySplit) {
|
void ValidateDisjoint(const Texture2DCopySplit& copySplit) {
|
||||||
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
||||||
const auto& a = copySplit.copies[i];
|
const auto& a = copySplit.copies[i];
|
||||||
for (uint32_t j = i + 1; j < copySplit.count; ++j) {
|
for (uint32_t j = i + 1; j < copySplit.count; ++j) {
|
||||||
|
@ -84,7 +84,8 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the union of the copy regions exactly covers the texture region
|
// Check that the union of the copy regions exactly covers the texture region
|
||||||
void ValidateTextureBounds(const TextureSpec& textureSpec, const TextureCopySplit& copySplit) {
|
void ValidateTextureBounds(const TextureSpec& textureSpec,
|
||||||
|
const Texture2DCopySplit& copySplit) {
|
||||||
ASSERT_TRUE(copySplit.count > 0);
|
ASSERT_TRUE(copySplit.count > 0);
|
||||||
|
|
||||||
uint32_t minX = copySplit.copies[0].textureOffset.x;
|
uint32_t minX = copySplit.copies[0].textureOffset.x;
|
||||||
|
@ -114,7 +115,7 @@ namespace {
|
||||||
|
|
||||||
// Validate that the number of pixels copied is exactly equal to the number of pixels in the
|
// Validate that the number of pixels copied is exactly equal to the number of pixels in the
|
||||||
// texture region
|
// texture region
|
||||||
void ValidatePixelCount(const TextureSpec& textureSpec, const TextureCopySplit& copySplit) {
|
void ValidatePixelCount(const TextureSpec& textureSpec, const Texture2DCopySplit& copySplit) {
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
||||||
const auto& copy = copySplit.copies[i];
|
const auto& copy = copySplit.copies[i];
|
||||||
|
@ -126,7 +127,7 @@ namespace {
|
||||||
// Check that every buffer offset is at the correct pixel location
|
// Check that every buffer offset is at the correct pixel location
|
||||||
void ValidateBufferOffset(const TextureSpec& textureSpec,
|
void ValidateBufferOffset(const TextureSpec& textureSpec,
|
||||||
const BufferSpec& bufferSpec,
|
const BufferSpec& bufferSpec,
|
||||||
const TextureCopySplit& copySplit) {
|
const Texture2DCopySplit& copySplit) {
|
||||||
ASSERT_TRUE(copySplit.count > 0);
|
ASSERT_TRUE(copySplit.count > 0);
|
||||||
|
|
||||||
uint32_t texelsPerBlock = textureSpec.blockWidth * textureSpec.blockHeight;
|
uint32_t texelsPerBlock = textureSpec.blockWidth * textureSpec.blockHeight;
|
||||||
|
@ -161,7 +162,7 @@ namespace {
|
||||||
|
|
||||||
void ValidateCopySplit(const TextureSpec& textureSpec,
|
void ValidateCopySplit(const TextureSpec& textureSpec,
|
||||||
const BufferSpec& bufferSpec,
|
const BufferSpec& bufferSpec,
|
||||||
const TextureCopySplit& copySplit) {
|
const Texture2DCopySplit& copySplit) {
|
||||||
ValidateFootprints(copySplit);
|
ValidateFootprints(copySplit);
|
||||||
ValidateOffset(copySplit);
|
ValidateOffset(copySplit);
|
||||||
ValidateDisjoint(copySplit);
|
ValidateDisjoint(copySplit);
|
||||||
|
@ -184,7 +185,7 @@ namespace {
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const TextureCopySplit& copySplit) {
|
std::ostream& operator<<(std::ostream& os, const Texture2DCopySplit& copySplit) {
|
||||||
os << "CopySplit" << std::endl;
|
os << "CopySplit" << std::endl;
|
||||||
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
for (uint32_t i = 0; i < copySplit.count; ++i) {
|
||||||
const auto& copy = copySplit.copies[i];
|
const auto& copy = copySplit.copies[i];
|
||||||
|
@ -286,14 +287,14 @@ namespace {
|
||||||
|
|
||||||
class CopySplitTest : public testing::Test {
|
class CopySplitTest : public testing::Test {
|
||||||
protected:
|
protected:
|
||||||
TextureCopySplit DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec) {
|
Texture2DCopySplit DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec) {
|
||||||
ASSERT(textureSpec.width % textureSpec.blockWidth == 0 &&
|
ASSERT(textureSpec.width % textureSpec.blockWidth == 0 &&
|
||||||
textureSpec.height % textureSpec.blockHeight == 0);
|
textureSpec.height % textureSpec.blockHeight == 0);
|
||||||
dawn_native::Format fakeFormat = {};
|
dawn_native::Format fakeFormat = {};
|
||||||
fakeFormat.blockWidth = textureSpec.blockWidth;
|
fakeFormat.blockWidth = textureSpec.blockWidth;
|
||||||
fakeFormat.blockHeight = textureSpec.blockHeight;
|
fakeFormat.blockHeight = textureSpec.blockHeight;
|
||||||
fakeFormat.blockByteSize = textureSpec.texelBlockSizeInBytes;
|
fakeFormat.blockByteSize = textureSpec.texelBlockSizeInBytes;
|
||||||
TextureCopySplit copySplit = ComputeTextureCopySplit(
|
Texture2DCopySplit copySplit = ComputeTextureCopySplit(
|
||||||
{textureSpec.x, textureSpec.y, textureSpec.z},
|
{textureSpec.x, textureSpec.y, textureSpec.z},
|
||||||
{textureSpec.width, textureSpec.height, textureSpec.depth}, fakeFormat,
|
{textureSpec.width, textureSpec.height, textureSpec.depth}, fakeFormat,
|
||||||
bufferSpec.offset, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage);
|
bufferSpec.offset, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage);
|
||||||
|
@ -305,7 +306,7 @@ class CopySplitTest : public testing::Test {
|
||||||
TEST_F(CopySplitTest, General) {
|
TEST_F(CopySplitTest, General) {
|
||||||
for (TextureSpec textureSpec : kBaseTextureSpecs) {
|
for (TextureSpec textureSpec : kBaseTextureSpecs) {
|
||||||
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
@ -325,7 +326,7 @@ TEST_F(CopySplitTest, TextureWidth) {
|
||||||
}
|
}
|
||||||
textureSpec.width = val;
|
textureSpec.width = val;
|
||||||
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
@ -346,7 +347,7 @@ TEST_F(CopySplitTest, TextureHeight) {
|
||||||
}
|
}
|
||||||
textureSpec.height = val;
|
textureSpec.height = val;
|
||||||
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
@ -364,7 +365,7 @@ TEST_F(CopySplitTest, TextureX) {
|
||||||
for (uint32_t val : kCheckValues) {
|
for (uint32_t val : kCheckValues) {
|
||||||
textureSpec.x = val;
|
textureSpec.x = val;
|
||||||
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
@ -382,7 +383,7 @@ TEST_F(CopySplitTest, TextureY) {
|
||||||
for (uint32_t val : kCheckValues) {
|
for (uint32_t val : kCheckValues) {
|
||||||
textureSpec.y = val;
|
textureSpec.y = val;
|
||||||
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
@ -400,7 +401,7 @@ TEST_F(CopySplitTest, TexelSize) {
|
||||||
for (uint32_t texelSize : {4, 8, 16, 32, 64}) {
|
for (uint32_t texelSize : {4, 8, 16, 32, 64}) {
|
||||||
textureSpec.texelBlockSizeInBytes = texelSize;
|
textureSpec.texelBlockSizeInBytes = texelSize;
|
||||||
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
@ -419,7 +420,7 @@ TEST_F(CopySplitTest, BufferOffset) {
|
||||||
for (uint32_t val : kCheckValues) {
|
for (uint32_t val : kCheckValues) {
|
||||||
bufferSpec.offset = textureSpec.texelBlockSizeInBytes * val;
|
bufferSpec.offset = textureSpec.texelBlockSizeInBytes * val;
|
||||||
|
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
@ -439,7 +440,7 @@ TEST_F(CopySplitTest, RowPitch) {
|
||||||
for (uint32_t i = 0; i < 5; ++i) {
|
for (uint32_t i = 0; i < 5; ++i) {
|
||||||
bufferSpec.bytesPerRow = baseRowPitch + i * 256;
|
bufferSpec.bytesPerRow = baseRowPitch + i * 256;
|
||||||
|
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
@ -459,7 +460,7 @@ TEST_F(CopySplitTest, ImageHeight) {
|
||||||
for (uint32_t i = 0; i < 5; ++i) {
|
for (uint32_t i = 0; i < 5; ++i) {
|
||||||
bufferSpec.rowsPerImage = baseImageHeight + i * 256;
|
bufferSpec.rowsPerImage = baseImageHeight + i * 256;
|
||||||
|
|
||||||
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
Texture2DCopySplit copySplit = DoTest(textureSpec, bufferSpec);
|
||||||
if (HasFatalFailure()) {
|
if (HasFatalFailure()) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec
|
||||||
|
|
Loading…
Reference in New Issue