// d3d12-helper-functions.cpp #include "d3d12-helper-functions.h" #ifdef GFX_NVAPI #include "../nvapi/nvapi-include.h" #endif #include "../nvapi/nvapi-util.h" #include "d3d12-buffer.h" #include "d3d12-query.h" #include "d3d12-transient-heap.h" namespace gfx { using namespace Slang; namespace d3d12 { bool isSupportedNVAPIOp(ID3D12Device* dev, uint32_t op) { #ifdef GFX_NVAPI { bool isSupported; NvAPI_Status status = NvAPI_D3D12_IsNvShaderExtnOpCodeSupported(dev, NvU32(op), &isSupported); return status == NVAPI_OK && isSupported; } #else return false; #endif } D3D12_RESOURCE_FLAGS calcResourceFlag(ResourceState state) { switch (state) { case ResourceState::RenderTarget: return D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; case ResourceState::DepthRead: case ResourceState::DepthWrite: return D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; case ResourceState::UnorderedAccess: case ResourceState::AccelerationStructure: return D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; default: return D3D12_RESOURCE_FLAG_NONE; } } D3D12_RESOURCE_FLAGS calcResourceFlags(ResourceStateSet states) { int dstFlags = 0; for (uint32_t i = 0; i < (uint32_t)ResourceState::_Count; i++) { auto state = (ResourceState)i; if (states.contains(state)) dstFlags |= calcResourceFlag(state); } return (D3D12_RESOURCE_FLAGS)dstFlags; } D3D12_RESOURCE_DIMENSION calcResourceDimension(IResource::Type type) { switch (type) { case IResource::Type::Buffer: return D3D12_RESOURCE_DIMENSION_BUFFER; case IResource::Type::Texture1D: return D3D12_RESOURCE_DIMENSION_TEXTURE1D; case IResource::Type::TextureCube: case IResource::Type::Texture2D: { return D3D12_RESOURCE_DIMENSION_TEXTURE2D; } case IResource::Type::Texture3D: return D3D12_RESOURCE_DIMENSION_TEXTURE3D; default: return D3D12_RESOURCE_DIMENSION_UNKNOWN; } } DXGI_FORMAT getTypelessFormatFromDepthFormat(Format format) { switch (format) { case Format::D16_UNORM: return DXGI_FORMAT_R16_TYPELESS; case Format::D32_FLOAT: return DXGI_FORMAT_R32_TYPELESS; case Format::D32_FLOAT_S8_UINT: return DXGI_FORMAT_R32G8X24_TYPELESS; // case Format::D24_UNORM_S8_UINT: // return DXGI_FORMAT_R24G8_TYPELESS; default: return D3DUtil::getMapFormat(format); } } bool isTypelessDepthFormat(DXGI_FORMAT format) { switch (format) { case DXGI_FORMAT_R16_TYPELESS: case DXGI_FORMAT_R32_TYPELESS: case DXGI_FORMAT_R32G8X24_TYPELESS: case DXGI_FORMAT_R24G8_TYPELESS: return true; default: return false; } } D3D12_FILTER_TYPE translateFilterMode(TextureFilteringMode mode) { switch (mode) { default: return D3D12_FILTER_TYPE(0); #define CASE(SRC, DST) \ case TextureFilteringMode::SRC: \ return D3D12_FILTER_TYPE_##DST CASE(Point, POINT); CASE(Linear, LINEAR); #undef CASE } } D3D12_FILTER_REDUCTION_TYPE translateFilterReduction(TextureReductionOp op) { switch (op) { default: return D3D12_FILTER_REDUCTION_TYPE(0); #define CASE(SRC, DST) \ case TextureReductionOp::SRC: \ return D3D12_FILTER_REDUCTION_TYPE_##DST CASE(Average, STANDARD); CASE(Comparison, COMPARISON); CASE(Minimum, MINIMUM); CASE(Maximum, MAXIMUM); #undef CASE } } D3D12_TEXTURE_ADDRESS_MODE translateAddressingMode(TextureAddressingMode mode) { switch (mode) { default: return D3D12_TEXTURE_ADDRESS_MODE(0); #define CASE(SRC, DST) \ case TextureAddressingMode::SRC: \ return D3D12_TEXTURE_ADDRESS_MODE_##DST CASE(Wrap, WRAP); CASE(ClampToEdge, CLAMP); CASE(ClampToBorder, BORDER); CASE(MirrorRepeat, MIRROR); CASE(MirrorOnce, MIRROR_ONCE); #undef CASE } } D3D12_COMPARISON_FUNC translateComparisonFunc(ComparisonFunc func) { switch (func) { default: // TODO: need to report failures return D3D12_COMPARISON_FUNC_ALWAYS; #define CASE(FROM, TO) \ case ComparisonFunc::FROM: \ return D3D12_COMPARISON_FUNC_##TO CASE(Never, NEVER); CASE(Less, LESS); CASE(Equal, EQUAL); CASE(LessEqual, LESS_EQUAL); CASE(Greater, GREATER); CASE(NotEqual, NOT_EQUAL); CASE(GreaterEqual, GREATER_EQUAL); CASE(Always, ALWAYS); #undef CASE } } uint32_t getViewDescriptorCount(const ITransientResourceHeap::Desc& desc) { return Math::Max( Math::Max( desc.srvDescriptorCount, desc.uavDescriptorCount, desc.accelerationStructureDescriptorCount), desc.constantBufferDescriptorCount, 2048); } void initSrvDesc( IResource::Type resourceType, const ITextureResource::Desc& textureDesc, const D3D12_RESOURCE_DESC& desc, DXGI_FORMAT pixelFormat, SubresourceRange subresourceRange, D3D12_SHADER_RESOURCE_VIEW_DESC& descOut) { // create SRV descOut = D3D12_SHADER_RESOURCE_VIEW_DESC(); descOut.Format = (pixelFormat == DXGI_FORMAT_UNKNOWN) ? D3DUtil::calcFormat(D3DUtil::USAGE_SRV, desc.Format) : pixelFormat; descOut.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; if (desc.DepthOrArraySize == 1) { switch (desc.Dimension) { case D3D12_RESOURCE_DIMENSION_TEXTURE1D: descOut.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D; descOut.Texture1D.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels - subresourceRange.mipLevel : subresourceRange.mipLevelCount; descOut.Texture1D.MostDetailedMip = subresourceRange.mipLevel; break; case D3D12_RESOURCE_DIMENSION_TEXTURE2D: descOut.ViewDimension = textureDesc.sampleDesc.numSamples > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMS : D3D12_SRV_DIMENSION_TEXTURE2D; descOut.Texture2D.PlaneSlice = D3DUtil::getPlaneSlice(descOut.Format, subresourceRange.aspectMask); descOut.Texture2D.ResourceMinLODClamp = 0.0f; descOut.Texture2D.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels - subresourceRange.mipLevel : subresourceRange.mipLevelCount; descOut.Texture2D.MostDetailedMip = subresourceRange.mipLevel; break; case D3D12_RESOURCE_DIMENSION_TEXTURE3D: descOut.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; descOut.Texture3D.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels - subresourceRange.mipLevel : subresourceRange.mipLevelCount; descOut.Texture3D.MostDetailedMip = subresourceRange.mipLevel; break; default: assert(!"Unknown dimension"); } } else if (resourceType == IResource::Type::TextureCube) { if (textureDesc.arraySize > 1) { descOut.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY; descOut.TextureCubeArray.NumCubes = subresourceRange.layerCount == 0 ? textureDesc.arraySize : subresourceRange.layerCount / 6; descOut.TextureCubeArray.First2DArrayFace = subresourceRange.baseArrayLayer; descOut.TextureCubeArray.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels - subresourceRange.mipLevel : subresourceRange.mipLevelCount; descOut.TextureCubeArray.MostDetailedMip = subresourceRange.mipLevel; descOut.TextureCubeArray.ResourceMinLODClamp = 0; } else { descOut.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; descOut.TextureCube.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels - subresourceRange.mipLevel : subresourceRange.mipLevelCount; descOut.TextureCube.MostDetailedMip = subresourceRange.mipLevel; descOut.TextureCube.ResourceMinLODClamp = 0; } } else { assert(desc.DepthOrArraySize > 1); switch (desc.Dimension) { case D3D12_RESOURCE_DIMENSION_TEXTURE1D: descOut.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY; descOut.Texture1D.MostDetailedMip = subresourceRange.mipLevel; descOut.Texture1D.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels : subresourceRange.mipLevelCount; descOut.Texture1DArray.ArraySize = subresourceRange.layerCount == 0 ? desc.DepthOrArraySize : subresourceRange.layerCount; descOut.Texture1DArray.FirstArraySlice = subresourceRange.baseArrayLayer; descOut.Texture1DArray.ResourceMinLODClamp = 0; descOut.Texture1DArray.MostDetailedMip = subresourceRange.mipLevel; descOut.Texture1DArray.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels - subresourceRange.mipLevel : subresourceRange.mipLevelCount; break; case D3D12_RESOURCE_DIMENSION_TEXTURE2D: descOut.ViewDimension = textureDesc.sampleDesc.numSamples > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D12_SRV_DIMENSION_TEXTURE2DARRAY; if (descOut.ViewDimension == D3D12_SRV_DIMENSION_TEXTURE2DARRAY) { descOut.Texture2DArray.ArraySize = subresourceRange.layerCount == 0 ? desc.DepthOrArraySize : subresourceRange.layerCount; descOut.Texture2DArray.FirstArraySlice = subresourceRange.baseArrayLayer; descOut.Texture2DArray.PlaneSlice = D3DUtil::getPlaneSlice(descOut.Format, subresourceRange.aspectMask); descOut.Texture2DArray.ResourceMinLODClamp = 0; descOut.Texture2DArray.MostDetailedMip = subresourceRange.mipLevel; descOut.Texture2DArray.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels - subresourceRange.mipLevel : subresourceRange.mipLevelCount; } else { assert(descOut.ViewDimension == D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY); descOut.Texture2DMSArray.FirstArraySlice = subresourceRange.baseArrayLayer; descOut.Texture2DMSArray.ArraySize = subresourceRange.layerCount == 0 ? desc.DepthOrArraySize : subresourceRange.layerCount; } break; case D3D12_RESOURCE_DIMENSION_TEXTURE3D: descOut.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; descOut.Texture3D.MostDetailedMip = subresourceRange.mipLevel; descOut.Texture3D.MipLevels = subresourceRange.mipLevelCount == 0 ? desc.MipLevels : subresourceRange.mipLevelCount; break; default: assert(!"Unknown dimension"); } } } Result initTextureResourceDesc( D3D12_RESOURCE_DESC& resourceDesc, const ITextureResource::Desc& srcDesc) { const DXGI_FORMAT pixelFormat = D3DUtil::getMapFormat(srcDesc.format); if (pixelFormat == DXGI_FORMAT_UNKNOWN) { return SLANG_FAIL; } const int arraySize = calcEffectiveArraySize(srcDesc); const D3D12_RESOURCE_DIMENSION dimension = calcResourceDimension(srcDesc.type); if (dimension == D3D12_RESOURCE_DIMENSION_UNKNOWN) { return SLANG_FAIL; } const int numMipMaps = srcDesc.numMipLevels; resourceDesc.Dimension = dimension; resourceDesc.Format = pixelFormat; resourceDesc.Width = srcDesc.size.width; resourceDesc.Height = srcDesc.size.height; resourceDesc.DepthOrArraySize = (srcDesc.size.depth > 1) ? srcDesc.size.depth : arraySize; resourceDesc.MipLevels = numMipMaps; resourceDesc.SampleDesc.Count = srcDesc.sampleDesc.numSamples; resourceDesc.SampleDesc.Quality = srcDesc.sampleDesc.quality; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; resourceDesc.Flags |= calcResourceFlags(srcDesc.allowedStates); resourceDesc.Alignment = 0; if (isDepthFormat(srcDesc.format) && (srcDesc.allowedStates.contains(ResourceState::ShaderResource) || srcDesc.allowedStates.contains(ResourceState::UnorderedAccess))) { resourceDesc.Format = getTypelessFormatFromDepthFormat(srcDesc.format); } return SLANG_OK; } void initBufferResourceDesc(Size bufferSize, D3D12_RESOURCE_DESC& out) { out = {}; out.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; out.Alignment = 0; out.Width = bufferSize; out.Height = 1; out.DepthOrArraySize = 1; out.MipLevels = 1; out.Format = DXGI_FORMAT_UNKNOWN; out.SampleDesc.Count = 1; out.SampleDesc.Quality = 0; out.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; out.Flags = D3D12_RESOURCE_FLAG_NONE; } Result uploadBufferDataImpl( ID3D12Device* device, ID3D12GraphicsCommandList* cmdList, TransientResourceHeapImpl* transientHeap, BufferResourceImpl* buffer, Offset offset, Size size, void* data) { IBufferResource* uploadResource; Offset uploadResourceOffset = 0; if (buffer->getDesc()->memoryType != MemoryType::Upload) { SLANG_RETURN_ON_FAIL(transientHeap->allocateStagingBuffer( size, uploadResource, uploadResourceOffset, MemoryType::Upload)); } else { uploadResourceOffset = offset; } D3D12Resource& uploadResourceRef = (buffer->getDesc()->memoryType == MemoryType::Upload) ? buffer->m_resource : static_cast(uploadResource)->m_resource; D3D12_RANGE readRange = {}; readRange.Begin = 0; readRange.End = 0; void* uploadData; SLANG_RETURN_ON_FAIL( uploadResourceRef.getResource()->Map(0, &readRange, reinterpret_cast(&uploadData))); memcpy((uint8_t*)uploadData + uploadResourceOffset, data, size); D3D12_RANGE writtenRange = {}; writtenRange.Begin = uploadResourceOffset; writtenRange.End = uploadResourceOffset + size; uploadResourceRef.getResource()->Unmap(0, &writtenRange); if (buffer->getDesc()->memoryType != MemoryType::Upload) { cmdList->CopyBufferRegion( buffer->m_resource.getResource(), offset, uploadResourceRef.getResource(), uploadResourceOffset, size); } return SLANG_OK; } Result createNullDescriptor( ID3D12Device* d3dDevice, D3D12_CPU_DESCRIPTOR_HANDLE destDescriptor, const ShaderObjectLayoutImpl::BindingRangeInfo& bindingRange) { switch (bindingRange.bindingType) { case slang::BindingType::ConstantBuffer: { D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {}; cbvDesc.BufferLocation = 0; cbvDesc.SizeInBytes = 0; d3dDevice->CreateConstantBufferView(&cbvDesc, destDescriptor); } break; case slang::BindingType::MutableRawBuffer: { D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW; uavDesc.Format = DXGI_FORMAT_R32_TYPELESS; d3dDevice->CreateUnorderedAccessView(nullptr, nullptr, &uavDesc, destDescriptor); } break; case slang::BindingType::MutableTypedBuffer: { D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; uavDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; d3dDevice->CreateUnorderedAccessView(nullptr, nullptr, &uavDesc, destDescriptor); } break; case slang::BindingType::RawBuffer: { D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW; srvDesc.Format = DXGI_FORMAT_R32_TYPELESS; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; d3dDevice->CreateShaderResourceView(nullptr, &srvDesc, destDescriptor); } break; case slang::BindingType::TypedBuffer: { D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; d3dDevice->CreateShaderResourceView(nullptr, &srvDesc, destDescriptor); } break; case slang::BindingType::Texture: { D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; switch (bindingRange.resourceShape) { case SLANG_TEXTURE_1D: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D; break; case SLANG_TEXTURE_1D_ARRAY: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY; break; case SLANG_TEXTURE_2D: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; break; case SLANG_TEXTURE_2D_ARRAY: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; break; case SLANG_TEXTURE_3D: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; break; case SLANG_TEXTURE_CUBE: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; break; case SLANG_TEXTURE_CUBE_ARRAY: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY; break; case SLANG_TEXTURE_2D_MULTISAMPLE: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS; break; case SLANG_TEXTURE_2D_MULTISAMPLE_ARRAY: srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY; break; default: return SLANG_OK; } d3dDevice->CreateShaderResourceView(nullptr, &srvDesc, destDescriptor); } break; case slang::BindingType::MutableTexture: { D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; uavDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; switch (bindingRange.resourceShape) { case SLANG_TEXTURE_1D: uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D; break; case SLANG_TEXTURE_1D_ARRAY: uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY; break; case SLANG_TEXTURE_2D: uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; break; case SLANG_TEXTURE_2D_ARRAY: uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; break; case SLANG_TEXTURE_3D: uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; break; case SLANG_TEXTURE_CUBE: case SLANG_TEXTURE_CUBE_ARRAY: case SLANG_TEXTURE_2D_MULTISAMPLE: case SLANG_TEXTURE_2D_MULTISAMPLE_ARRAY: default: return SLANG_OK; } d3dDevice->CreateUnorderedAccessView(nullptr, nullptr, &uavDesc, destDescriptor); } break; default: break; } return SLANG_OK; } void translatePostBuildInfoDescs( int propertyQueryCount, AccelerationStructureQueryDesc* queryDescs, List& postBuildInfoDescs) { postBuildInfoDescs.setCount(propertyQueryCount); for (int i = 0; i < propertyQueryCount; i++) { switch (queryDescs[i].queryType) { case QueryType::AccelerationStructureCompactedSize: postBuildInfoDescs[i].InfoType = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_COMPACTED_SIZE; postBuildInfoDescs[i].DestBuffer = static_cast(queryDescs[i].queryPool) ->m_bufferResource->getDeviceAddress() + sizeof(D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_COMPACTED_SIZE_DESC) * queryDescs[i].firstQueryIndex; break; case QueryType::AccelerationStructureCurrentSize: postBuildInfoDescs[i].InfoType = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_CURRENT_SIZE; postBuildInfoDescs[i].DestBuffer = static_cast(queryDescs[i].queryPool) ->m_bufferResource->getDeviceAddress() + sizeof(D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_COMPACTED_SIZE_DESC) * queryDescs[i].firstQueryIndex; break; case QueryType::AccelerationStructureSerializedSize: postBuildInfoDescs[i].InfoType = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_SERIALIZATION; postBuildInfoDescs[i].DestBuffer = static_cast(queryDescs[i].queryPool) ->m_bufferResource->getDeviceAddress() + sizeof(D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_SERIALIZATION_DESC) * queryDescs[i].firstQueryIndex; break; } } } } // namespace d3d12 Result SLANG_MCALL getD3D12Adapters(List& outAdapters) { List> dxgiAdapters; SLANG_RETURN_ON_FAIL( D3DUtil::findAdapters(DeviceCheckFlag::UseHardwareDevice, nullptr, dxgiAdapters)); outAdapters.clear(); for (const auto& dxgiAdapter : dxgiAdapters) { DXGI_ADAPTER_DESC desc; dxgiAdapter->GetDesc(&desc); AdapterInfo info = {}; auto name = String::fromWString(desc.Description); memcpy( info.name, name.getBuffer(), Math::Min(name.getLength(), (Index)sizeof(AdapterInfo::name) - 1)); info.vendorID = desc.VendorId; info.deviceID = desc.DeviceId; info.luid = D3DUtil::getAdapterLUID(dxgiAdapter); outAdapters.add(info); } return SLANG_OK; } Result SLANG_MCALL createD3D12Device(const IDevice::Desc* desc, IDevice** outDevice) { RefPtr result = new d3d12::DeviceImpl(); SLANG_RETURN_ON_FAIL(result->initialize(*desc)); returnComPtr(outDevice, result); return SLANG_OK; } } // namespace gfx