//------------------------------------------------------------------------------------- // DirectXTexImage.cpp // // DirectX Texture Library - Image container // // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // // http://go.microsoft.com/fwlink/?LinkId=248926 //------------------------------------------------------------------------------------- #include "DirectXTexP.h" namespace DirectX { extern bool _CalculateMipLevels(_In_ size_t width, _In_ size_t height, _Inout_ size_t& mipLevels); extern bool _CalculateMipLevels3D(_In_ size_t width, _In_ size_t height, _In_ size_t depth, _Inout_ size_t& mipLevels); extern bool _IsAlphaAllOpaqueBC(_In_ const Image& cImage); } using namespace DirectX; //------------------------------------------------------------------------------------- // Determines number of image array entries and pixel size //------------------------------------------------------------------------------------- _Use_decl_annotations_ bool DirectX::_DetermineImageArray( const TexMetadata& metadata, DWORD cpFlags, size_t& nImages, size_t& pixelSize) { assert(metadata.width > 0 && metadata.height > 0 && metadata.depth > 0); assert(metadata.arraySize > 0); assert(metadata.mipLevels > 0); uint64_t totalPixelSize = 0; size_t nimages = 0; switch (metadata.dimension) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: for (size_t item = 0; item < metadata.arraySize; ++item) { size_t w = metadata.width; size_t h = metadata.height; for (size_t level = 0; level < metadata.mipLevels; ++level) { size_t rowPitch, slicePitch; if (FAILED(ComputePitch(metadata.format, w, h, rowPitch, slicePitch, cpFlags))) { nImages = pixelSize = 0; return false; } totalPixelSize += uint64_t(slicePitch); ++nimages; if (h > 1) h >>= 1; if (w > 1) w >>= 1; } } break; case TEX_DIMENSION_TEXTURE3D: { size_t w = metadata.width; size_t h = metadata.height; size_t d = metadata.depth; for (size_t level = 0; level < metadata.mipLevels; ++level) { size_t rowPitch, slicePitch; if (FAILED(ComputePitch(metadata.format, w, h, rowPitch, slicePitch, cpFlags))) { nImages = pixelSize = 0; return false; } for (size_t slice = 0; slice < d; ++slice) { totalPixelSize += uint64_t(slicePitch); ++nimages; } if (h > 1) h >>= 1; if (w > 1) w >>= 1; if (d > 1) d >>= 1; } } break; default: nImages = pixelSize = 0; return false; } #if defined(_M_IX86) || defined(_M_ARM) || defined(_M_HYBRID_X86_ARM64) static_assert(sizeof(size_t) == 4, "Not a 32-bit platform!"); if (totalPixelSize > UINT32_MAX) { nImages = pixelSize = 0; return false; } #else static_assert(sizeof(size_t) == 8, "Not a 64-bit platform!"); #endif nImages = nimages; pixelSize = static_cast(totalPixelSize); return true; } //------------------------------------------------------------------------------------- // Fills in the image array entries //------------------------------------------------------------------------------------- _Use_decl_annotations_ bool DirectX::_SetupImageArray( uint8_t *pMemory, size_t pixelSize, const TexMetadata& metadata, DWORD cpFlags, Image* images, size_t nImages) { assert(pMemory); assert(pixelSize > 0); assert(nImages > 0); if (!images) return false; size_t index = 0; uint8_t* pixels = pMemory; const uint8_t* pEndBits = pMemory + pixelSize; switch (metadata.dimension) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: if (metadata.arraySize == 0 || metadata.mipLevels == 0) { return false; } for (size_t item = 0; item < metadata.arraySize; ++item) { size_t w = metadata.width; size_t h = metadata.height; for (size_t level = 0; level < metadata.mipLevels; ++level) { if (index >= nImages) { return false; } size_t rowPitch, slicePitch; if (FAILED(ComputePitch(metadata.format, w, h, rowPitch, slicePitch, cpFlags))) return false; images[index].width = w; images[index].height = h; images[index].format = metadata.format; images[index].rowPitch = rowPitch; images[index].slicePitch = slicePitch; images[index].pixels = pixels; ++index; pixels += slicePitch; if (pixels > pEndBits) { return false; } if (h > 1) h >>= 1; if (w > 1) w >>= 1; } } return true; case TEX_DIMENSION_TEXTURE3D: { if (metadata.mipLevels == 0 || metadata.depth == 0) { return false; } size_t w = metadata.width; size_t h = metadata.height; size_t d = metadata.depth; for (size_t level = 0; level < metadata.mipLevels; ++level) { size_t rowPitch, slicePitch; if (FAILED(ComputePitch(metadata.format, w, h, rowPitch, slicePitch, cpFlags))) return false; for (size_t slice = 0; slice < d; ++slice) { if (index >= nImages) { return false; } // We use the same memory organization that Direct3D 11 needs for D3D11_SUBRESOURCE_DATA // with all slices of a given miplevel being continuous in memory images[index].width = w; images[index].height = h; images[index].format = metadata.format; images[index].rowPitch = rowPitch; images[index].slicePitch = slicePitch; images[index].pixels = pixels; ++index; pixels += slicePitch; if (pixels > pEndBits) { return false; } } if (h > 1) h >>= 1; if (w > 1) w >>= 1; if (d > 1) d >>= 1; } } return true; default: return false; } } //===================================================================================== // ScratchImage - Bitmap image container //===================================================================================== ScratchImage& ScratchImage::operator= (ScratchImage&& moveFrom) noexcept { if (this != &moveFrom) { Release(); m_nimages = moveFrom.m_nimages; m_size = moveFrom.m_size; m_metadata = moveFrom.m_metadata; m_image = moveFrom.m_image; m_memory = moveFrom.m_memory; moveFrom.m_nimages = 0; moveFrom.m_size = 0; moveFrom.m_image = nullptr; moveFrom.m_memory = nullptr; } return *this; } //------------------------------------------------------------------------------------- // Methods //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT ScratchImage::Initialize(const TexMetadata& mdata, DWORD flags) { if (!IsValid(mdata.format)) return E_INVALIDARG; if (IsPalettized(mdata.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); size_t mipLevels = mdata.mipLevels; switch (mdata.dimension) { case TEX_DIMENSION_TEXTURE1D: if (!mdata.width || mdata.height != 1 || mdata.depth != 1 || !mdata.arraySize) return E_INVALIDARG; if (!_CalculateMipLevels(mdata.width, 1, mipLevels)) return E_INVALIDARG; break; case TEX_DIMENSION_TEXTURE2D: if (!mdata.width || !mdata.height || mdata.depth != 1 || !mdata.arraySize) return E_INVALIDARG; if (mdata.IsCubemap()) { if ((mdata.arraySize % 6) != 0) return E_INVALIDARG; } if (!_CalculateMipLevels(mdata.width, mdata.height, mipLevels)) return E_INVALIDARG; break; case TEX_DIMENSION_TEXTURE3D: if (!mdata.width || !mdata.height || !mdata.depth || mdata.arraySize != 1) return E_INVALIDARG; if (!_CalculateMipLevels3D(mdata.width, mdata.height, mdata.depth, mipLevels)) return E_INVALIDARG; break; default: return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); } Release(); m_metadata.width = mdata.width; m_metadata.height = mdata.height; m_metadata.depth = mdata.depth; m_metadata.arraySize = mdata.arraySize; m_metadata.mipLevels = mipLevels; m_metadata.miscFlags = mdata.miscFlags; m_metadata.miscFlags2 = mdata.miscFlags2; m_metadata.format = mdata.format; m_metadata.dimension = mdata.dimension; size_t pixelSize, nimages; if (!_DetermineImageArray(m_metadata, flags, nimages, pixelSize)) return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); m_image = new (std::nothrow) Image[nimages]; if (!m_image) return E_OUTOFMEMORY; m_nimages = nimages; memset(m_image, 0, sizeof(Image) * nimages); m_memory = static_cast(_aligned_malloc(pixelSize, 16)); if (!m_memory) { Release(); return E_OUTOFMEMORY; } m_size = pixelSize; if (!_SetupImageArray(m_memory, pixelSize, m_metadata, flags, m_image, nimages)) { Release(); return E_FAIL; } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::Initialize1D(DXGI_FORMAT fmt, size_t length, size_t arraySize, size_t mipLevels, DWORD flags) { if (!length || !arraySize) return E_INVALIDARG; // 1D is a special case of the 2D case HRESULT hr = Initialize2D(fmt, length, 1, arraySize, mipLevels, flags); if (FAILED(hr)) return hr; m_metadata.dimension = TEX_DIMENSION_TEXTURE1D; return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::Initialize2D(DXGI_FORMAT fmt, size_t width, size_t height, size_t arraySize, size_t mipLevels, DWORD flags) { if (!IsValid(fmt) || !width || !height || !arraySize) return E_INVALIDARG; if (IsPalettized(fmt)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); if (!_CalculateMipLevels(width, height, mipLevels)) return E_INVALIDARG; Release(); m_metadata.width = width; m_metadata.height = height; m_metadata.depth = 1; m_metadata.arraySize = arraySize; m_metadata.mipLevels = mipLevels; m_metadata.miscFlags = 0; m_metadata.miscFlags2 = 0; m_metadata.format = fmt; m_metadata.dimension = TEX_DIMENSION_TEXTURE2D; size_t pixelSize, nimages; if (!_DetermineImageArray(m_metadata, flags, nimages, pixelSize)) return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); m_image = new (std::nothrow) Image[nimages]; if (!m_image) return E_OUTOFMEMORY; m_nimages = nimages; memset(m_image, 0, sizeof(Image) * nimages); m_memory = static_cast(_aligned_malloc(pixelSize, 16)); if (!m_memory) { Release(); return E_OUTOFMEMORY; } m_size = pixelSize; if (!_SetupImageArray(m_memory, pixelSize, m_metadata, flags, m_image, nimages)) { Release(); return E_FAIL; } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::Initialize3D(DXGI_FORMAT fmt, size_t width, size_t height, size_t depth, size_t mipLevels, DWORD flags) { if (!IsValid(fmt) || !width || !height || !depth) return E_INVALIDARG; if (IsPalettized(fmt)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); if (!_CalculateMipLevels3D(width, height, depth, mipLevels)) return E_INVALIDARG; Release(); m_metadata.width = width; m_metadata.height = height; m_metadata.depth = depth; m_metadata.arraySize = 1; // Direct3D 10.x/11 does not support arrays of 3D textures m_metadata.mipLevels = mipLevels; m_metadata.miscFlags = 0; m_metadata.miscFlags2 = 0; m_metadata.format = fmt; m_metadata.dimension = TEX_DIMENSION_TEXTURE3D; size_t pixelSize, nimages; if (!_DetermineImageArray(m_metadata, flags, nimages, pixelSize)) return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); m_image = new (std::nothrow) Image[nimages]; if (!m_image) { Release(); return E_OUTOFMEMORY; } m_nimages = nimages; memset(m_image, 0, sizeof(Image) * nimages); m_memory = static_cast(_aligned_malloc(pixelSize, 16)); if (!m_memory) { Release(); return E_OUTOFMEMORY; } m_size = pixelSize; if (!_SetupImageArray(m_memory, pixelSize, m_metadata, flags, m_image, nimages)) { Release(); return E_FAIL; } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::InitializeCube(DXGI_FORMAT fmt, size_t width, size_t height, size_t nCubes, size_t mipLevels, DWORD flags) { if (!width || !height || !nCubes) return E_INVALIDARG; // A DirectX11 cubemap is just a 2D texture array that is a multiple of 6 for each cube HRESULT hr = Initialize2D(fmt, width, height, nCubes * 6, mipLevels, flags); if (FAILED(hr)) return hr; m_metadata.miscFlags |= TEX_MISC_TEXTURECUBE; return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::InitializeFromImage(const Image& srcImage, bool allow1D, DWORD flags) { HRESULT hr = (srcImage.height > 1 || !allow1D) ? Initialize2D(srcImage.format, srcImage.width, srcImage.height, 1, 1, flags) : Initialize1D(srcImage.format, srcImage.width, 1, 1, flags); if (FAILED(hr)) return hr; size_t rowCount = ComputeScanlines(srcImage.format, srcImage.height); if (!rowCount) return E_UNEXPECTED; const uint8_t* sptr = srcImage.pixels; if (!sptr) return E_POINTER; uint8_t* dptr = m_image[0].pixels; if (!dptr) return E_POINTER; size_t spitch = srcImage.rowPitch; size_t dpitch = m_image[0].rowPitch; size_t size = std::min(dpitch, spitch); for (size_t y = 0; y < rowCount; ++y) { memcpy_s(dptr, dpitch, sptr, size); sptr += spitch; dptr += dpitch; } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::InitializeArrayFromImages(const Image* images, size_t nImages, bool allow1D, DWORD flags) { if (!images || !nImages) return E_INVALIDARG; DXGI_FORMAT format = images[0].format; size_t width = images[0].width; size_t height = images[0].height; for (size_t index = 0; index < nImages; ++index) { if (!images[index].pixels) return E_POINTER; if (images[index].format != format || images[index].width != width || images[index].height != height) { // All images must be the same format, width, and height return E_FAIL; } } HRESULT hr = (height > 1 || !allow1D) ? Initialize2D(format, width, height, nImages, 1, flags) : Initialize1D(format, width, nImages, 1, flags); if (FAILED(hr)) return hr; size_t rowCount = ComputeScanlines(format, height); if (!rowCount) return E_UNEXPECTED; for (size_t index = 0; index < nImages; ++index) { const uint8_t* sptr = images[index].pixels; if (!sptr) return E_POINTER; assert(index < m_nimages); uint8_t* dptr = m_image[index].pixels; if (!dptr) return E_POINTER; size_t spitch = images[index].rowPitch; size_t dpitch = m_image[index].rowPitch; size_t size = std::min(dpitch, spitch); for (size_t y = 0; y < rowCount; ++y) { memcpy_s(dptr, dpitch, sptr, size); sptr += spitch; dptr += dpitch; } } return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::InitializeCubeFromImages(const Image* images, size_t nImages, DWORD flags) { if (!images || !nImages) return E_INVALIDARG; // A DirectX11 cubemap is just a 2D texture array that is a multiple of 6 for each cube if ((nImages % 6) != 0) return E_INVALIDARG; HRESULT hr = InitializeArrayFromImages(images, nImages, false, flags); if (FAILED(hr)) return hr; m_metadata.miscFlags |= TEX_MISC_TEXTURECUBE; return S_OK; } _Use_decl_annotations_ HRESULT ScratchImage::Initialize3DFromImages(const Image* images, size_t depth, DWORD flags) { if (!images || !depth) return E_INVALIDARG; DXGI_FORMAT format = images[0].format; size_t width = images[0].width; size_t height = images[0].height; for (size_t slice = 0; slice < depth; ++slice) { if (!images[slice].pixels) return E_POINTER; if (images[slice].format != format || images[slice].width != width || images[slice].height != height) { // All images must be the same format, width, and height return E_FAIL; } } HRESULT hr = Initialize3D(format, width, height, depth, 1, flags); if (FAILED(hr)) return hr; size_t rowCount = ComputeScanlines(format, height); if (!rowCount) return E_UNEXPECTED; for (size_t slice = 0; slice < depth; ++slice) { const uint8_t* sptr = images[slice].pixels; if (!sptr) return E_POINTER; assert(slice < m_nimages); uint8_t* dptr = m_image[slice].pixels; if (!dptr) return E_POINTER; size_t spitch = images[slice].rowPitch; size_t dpitch = m_image[slice].rowPitch; size_t size = std::min(dpitch, spitch); for (size_t y = 0; y < rowCount; ++y) { memcpy_s(dptr, dpitch, sptr, size); sptr += spitch; dptr += dpitch; } } return S_OK; } void ScratchImage::Release() { m_nimages = 0; m_size = 0; if (m_image) { delete[] m_image; m_image = nullptr; } if (m_memory) { _aligned_free(m_memory); m_memory = nullptr; } memset(&m_metadata, 0, sizeof(m_metadata)); } _Use_decl_annotations_ bool ScratchImage::OverrideFormat(DXGI_FORMAT f) { if (!m_image) return false; if (!IsValid(f) || IsPlanar(f) || IsPalettized(f)) return false; for (size_t index = 0; index < m_nimages; ++index) { m_image[index].format = f; } m_metadata.format = f; return true; } _Use_decl_annotations_ const Image* ScratchImage::GetImage(size_t mip, size_t item, size_t slice) const { if (mip >= m_metadata.mipLevels) return nullptr; size_t index = 0; switch (m_metadata.dimension) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: if (slice > 0) return nullptr; if (item >= m_metadata.arraySize) return nullptr; index = item*(m_metadata.mipLevels) + mip; break; case TEX_DIMENSION_TEXTURE3D: if (item > 0) { // No support for arrays of volumes return nullptr; } else { size_t d = m_metadata.depth; for (size_t level = 0; level < mip; ++level) { index += d; if (d > 1) d >>= 1; } if (slice >= d) return nullptr; index += slice; } break; default: return nullptr; } return &m_image[index]; } bool ScratchImage::IsAlphaAllOpaque() const { if (!m_image) return false; if (!HasAlpha(m_metadata.format)) return true; if (IsCompressed(m_metadata.format)) { for (size_t index = 0; index < m_nimages; ++index) { if (!_IsAlphaAllOpaqueBC(m_image[index])) return false; } } else { ScopedAlignedArrayXMVECTOR scanline(static_cast(_aligned_malloc((sizeof(XMVECTOR)*m_metadata.width), 16))); if (!scanline) return false; static const XMVECTORF32 threshold = { { { 0.997f, 0.997f, 0.997f, 0.997f } } }; for (size_t index = 0; index < m_nimages; ++index) { #pragma warning( suppress : 6011 ) const Image& img = m_image[index]; const uint8_t *pPixels = img.pixels; assert(pPixels); for (size_t h = 0; h < img.height; ++h) { if (!_LoadScanline(scanline.get(), img.width, pPixels, img.rowPitch, img.format)) return false; const XMVECTOR* ptr = scanline.get(); for (size_t w = 0; w < img.width; ++w) { XMVECTOR alpha = XMVectorSplatW(*ptr); if (XMVector4Less(alpha, threshold)) return false; ++ptr; } pPixels += img.rowPitch; } } } return true; }