#include "fixtures/OpenGLTestFixture.h" #include "XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h" #include "XCEngine/RHI/OpenGL/OpenGLPipelineState.h" #include "XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h" #include "XCEngine/RHI/OpenGL/OpenGLResourceView.h" #include "XCEngine/RHI/OpenGL/OpenGLSampler.h" #include "XCEngine/RHI/RHIBuffer.h" #include "XCEngine/RHI/RHICommandList.h" #include "XCEngine/RHI/RHICommandQueue.h" #include "XCEngine/RHI/RHIDescriptorPool.h" #include "XCEngine/RHI/RHIEnums.h" #include "XCEngine/RHI/RHIPipelineLayout.h" #include "XCEngine/RHI/RHIPipelineState.h" #include "XCEngine/RHI/RHIResourceView.h" #include "XCEngine/RHI/RHIShader.h" #include "XCEngine/RHI/RHISampler.h" #include "XCEngine/RHI/RHISwapChain.h" #include "XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h" #include "XCEngine/RHI/RHITexture.h" #include #include #include #include #include #include #include #include #include using namespace XCEngine::RHI; namespace { bool FindToolFromVulkanSdkOrPath(const wchar_t* fileName) { wchar_t sdkBuffer[32767] = {}; const DWORD sdkBufferCount = static_cast(sizeof(sdkBuffer) / sizeof(sdkBuffer[0])); const DWORD sdkLength = GetEnvironmentVariableW(L"VULKAN_SDK", sdkBuffer, sdkBufferCount); if (sdkLength > 0 && sdkLength < sdkBufferCount) { const std::filesystem::path candidate = std::filesystem::path(sdkBuffer) / L"Bin" / fileName; if (std::filesystem::exists(candidate)) { return true; } } wchar_t pathBuffer[MAX_PATH] = {}; const DWORD pathLength = SearchPathW(nullptr, fileName, nullptr, MAX_PATH, pathBuffer, nullptr); return pathLength > 0 && pathLength < MAX_PATH; } bool SupportsOpenGLHlslToolchainForTests() { return FindToolFromVulkanSdkOrPath(L"glslangValidator.exe") && FindToolFromVulkanSdkOrPath(L"spirv-cross.exe"); } } // namespace TEST_F(OpenGLTestFixture, Device_Initialize_UnifiedPath_CanCreateSwapChain) { auto* device = new OpenGLDevice(); ASSERT_NE(device, nullptr); RHIDeviceDesc deviceDesc = {}; ASSERT_TRUE(device->Initialize(deviceDesc)); CommandQueueDesc queueDesc = {}; queueDesc.queueType = static_cast(CommandQueueType::Direct); RHICommandQueue* queue = device->CreateCommandQueue(queueDesc); ASSERT_NE(queue, nullptr); SwapChainDesc swapDesc = {}; swapDesc.windowHandle = GetWindow(); swapDesc.width = 320; swapDesc.height = 180; RHISwapChain* swapChain = device->CreateSwapChain(swapDesc, queue); ASSERT_NE(swapChain, nullptr); swapChain->Present(0, 0); swapChain->Shutdown(); delete swapChain; queue->Shutdown(); delete queue; device->Shutdown(); delete device; } TEST_F(OpenGLTestFixture, Device_CreateShader_HlslVertex_UsesTranspiledHlslPath) { ASSERT_TRUE(GetDevice()->MakeContextCurrent()); if (!SupportsOpenGLHlslToolchainForTests()) { GTEST_SKIP() << "glslangValidator.exe or spirv-cross.exe was not found."; } static const char* vertexSource = R"( struct VSInput { float4 position : POSITION; }; struct VSOutput { float4 position : SV_POSITION; }; VSOutput MainVS(VSInput input) { VSOutput output; output.position = input.position; return output; } )"; ShaderCompileDesc shaderDesc = {}; shaderDesc.source.assign(vertexSource, vertexSource + std::strlen(vertexSource)); shaderDesc.sourceLanguage = ShaderLanguage::HLSL; shaderDesc.entryPoint = L"MainVS"; shaderDesc.profile = L"vs_5_0"; RHIShader* shader = GetDevice()->CreateShader(shaderDesc); ASSERT_NE(shader, nullptr); EXPECT_TRUE(shader->IsValid()); EXPECT_EQ(shader->GetType(), ShaderType::Vertex); shader->Shutdown(); delete shader; } TEST(OpenGLShaderCompiler_Test, ParallelSpirvToolInvocationsDoNotCollideOnTemporaryFiles) { if (!SupportsOpenGLHlslToolchainForTests()) { GTEST_SKIP() << "glslangValidator.exe or spirv-cross.exe was not found."; } static const char* vertexSource = R"( struct VSInput { float4 position : POSITION; }; struct VSOutput { float4 position : SV_POSITION; }; VSOutput MainVS(VSInput input) { VSOutput output; output.position = input.position; return output; } )"; ShaderCompileDesc shaderDesc = {}; shaderDesc.source.assign(vertexSource, vertexSource + std::strlen(vertexSource)); shaderDesc.sourceLanguage = ShaderLanguage::HLSL; shaderDesc.entryPoint = L"MainVS"; shaderDesc.profile = L"vs_5_0"; constexpr int kWorkerCount = 4; constexpr int kIterationsPerWorker = 4; std::mutex failureMutex; std::vector failures; std::vector workers; workers.reserve(kWorkerCount); for (int workerIndex = 0; workerIndex < kWorkerCount; ++workerIndex) { workers.emplace_back([&, workerIndex]() { for (int iteration = 0; iteration < kIterationsPerWorker; ++iteration) { CompiledSpirvShader compiledShader = {}; std::string errorMessage; if (!CompileSpirvShader( shaderDesc, SpirvTargetEnvironment::Vulkan, compiledShader, &errorMessage)) { std::lock_guard lock(failureMutex); failures.push_back( "CompileSpirvShader failed for worker " + std::to_string(workerIndex) + ", iteration " + std::to_string(iteration) + ": " + errorMessage); return; } std::string glslSource; errorMessage.clear(); if (!TranspileSpirvToOpenGLGLSL(compiledShader, glslSource, &errorMessage) || glslSource.empty()) { std::lock_guard lock(failureMutex); failures.push_back( "TranspileSpirvToOpenGLGLSL failed for worker " + std::to_string(workerIndex) + ", iteration " + std::to_string(iteration) + ": " + errorMessage); return; } } }); } for (std::thread& worker : workers) { worker.join(); } ASSERT_TRUE(failures.empty()) << failures.front(); } TEST_F(OpenGLTestFixture, Device_CreatePipelineState_HlslGraphicsShaders_UsesTranspiledHlslPath) { ASSERT_TRUE(GetDevice()->MakeContextCurrent()); if (!SupportsOpenGLHlslToolchainForTests()) { GTEST_SKIP() << "glslangValidator.exe or spirv-cross.exe was not found."; } static const char* hlslSource = R"( struct VSInput { float4 position : POSITION; }; struct PSInput { float4 position : SV_POSITION; }; PSInput MainVS(VSInput input) { PSInput output; output.position = input.position; return output; } float4 MainPS(PSInput input) : SV_TARGET { return float4(1.0f, 0.25f, 0.0f, 1.0f); } )"; GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.topologyType = static_cast(PrimitiveTopologyType::Triangle); pipelineDesc.renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); pipelineDesc.depthStencilFormat = static_cast(Format::Unknown); InputElementDesc position = {}; position.semanticName = "POSITION"; position.semanticIndex = 0; position.format = static_cast(Format::R32G32B32A32_Float); position.inputSlot = 0; position.alignedByteOffset = 0; pipelineDesc.inputLayout.elements.push_back(position); pipelineDesc.vertexShader.source.assign(hlslSource, hlslSource + std::strlen(hlslSource)); pipelineDesc.vertexShader.sourceLanguage = ShaderLanguage::HLSL; pipelineDesc.vertexShader.entryPoint = L"MainVS"; pipelineDesc.vertexShader.profile = L"vs_5_0"; pipelineDesc.fragmentShader.source.assign(hlslSource, hlslSource + std::strlen(hlslSource)); pipelineDesc.fragmentShader.sourceLanguage = ShaderLanguage::HLSL; pipelineDesc.fragmentShader.entryPoint = L"MainPS"; pipelineDesc.fragmentShader.profile = L"ps_5_0"; RHIPipelineState* pipelineState = GetDevice()->CreatePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); EXPECT_NE(pipelineState->GetNativeHandle(), nullptr); pipelineState->Shutdown(); delete pipelineState; } TEST_F(OpenGLTestFixture, Device_CreatePipelineState_HlslGraphicsShaders_AssignsCombinedSamplerUniformUnits) { ASSERT_TRUE(GetDevice()->MakeContextCurrent()); if (!SupportsOpenGLHlslToolchainForTests()) { GTEST_SKIP() << "glslangValidator.exe or spirv-cross.exe was not found."; } static const char* hlslSource = R"( Texture2D BaseColorTexture : register(t0); SamplerState LinearClampSampler : register(s0); Texture2D ShadowMapTexture : register(t1); SamplerState ShadowMapSampler : register(s1); struct VSInput { float4 position : POSITION; float2 texcoord : TEXCOORD0; }; struct PSInput { float4 position : SV_POSITION; float2 texcoord : TEXCOORD0; }; PSInput MainVS(VSInput input) { PSInput output; output.position = input.position; output.texcoord = input.texcoord; return output; } float4 MainPS(PSInput input) : SV_TARGET { float4 baseColor = BaseColorTexture.Sample(LinearClampSampler, input.texcoord); float shadow = ShadowMapTexture.Sample(ShadowMapSampler, input.texcoord).r; return float4(baseColor.rgb * shadow, baseColor.a); } )"; GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.topologyType = static_cast(PrimitiveTopologyType::Triangle); pipelineDesc.renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); InputElementDesc position = {}; position.semanticName = "POSITION"; position.semanticIndex = 0; position.format = static_cast(Format::R32G32B32A32_Float); position.inputSlot = 0; position.alignedByteOffset = 0; pipelineDesc.inputLayout.elements.push_back(position); InputElementDesc texcoord = {}; texcoord.semanticName = "TEXCOORD"; texcoord.semanticIndex = 0; texcoord.format = static_cast(Format::R32G32_Float); texcoord.inputSlot = 0; texcoord.alignedByteOffset = sizeof(float) * 4; pipelineDesc.inputLayout.elements.push_back(texcoord); pipelineDesc.vertexShader.source.assign(hlslSource, hlslSource + std::strlen(hlslSource)); pipelineDesc.vertexShader.sourceLanguage = ShaderLanguage::HLSL; pipelineDesc.vertexShader.entryPoint = L"MainVS"; pipelineDesc.vertexShader.profile = L"vs_5_0"; pipelineDesc.fragmentShader.source.assign(hlslSource, hlslSource + std::strlen(hlslSource)); pipelineDesc.fragmentShader.sourceLanguage = ShaderLanguage::HLSL; pipelineDesc.fragmentShader.entryPoint = L"MainPS"; pipelineDesc.fragmentShader.profile = L"ps_5_0"; RHIPipelineState* pipelineState = GetDevice()->CreatePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); const GLuint program = static_cast(pipelineState)->GetProgram(); ASSERT_NE(program, 0u); const GLint baseColorLocation = glGetUniformLocation(program, "SPIRV_Cross_CombinedBaseColorTextureLinearClampSampler"); const GLint shadowLocation = glGetUniformLocation(program, "SPIRV_Cross_CombinedShadowMapTextureShadowMapSampler"); ASSERT_GE(baseColorLocation, 0); ASSERT_GE(shadowLocation, 0); GLint baseColorUnit = -1; GLint shadowUnit = -1; glGetUniformiv(program, baseColorLocation, &baseColorUnit); glGetUniformiv(program, shadowLocation, &shadowUnit); GLint activeUniformCount = 0; glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniformCount); for (GLint uniformIndex = 0; uniformIndex < activeUniformCount; ++uniformIndex) { GLsizei nameLength = 0; GLint arraySize = 0; GLenum type = 0; char nameBuffer[256] = {}; glGetActiveUniform( program, static_cast(uniformIndex), static_cast(sizeof(nameBuffer)), &nameLength, &arraySize, &type, nameBuffer); if (nameLength <= 0) { continue; } GLint location = glGetUniformLocation(program, nameBuffer); GLint value = -1; if (location >= 0) { glGetUniformiv(program, location, &value); } std::cout << "uniform[" << uniformIndex << "] name=" << nameBuffer << " location=" << location << " value=" << value << " type=" << type << " arraySize=" << arraySize << std::endl; } EXPECT_EQ(baseColorUnit, 0); EXPECT_EQ(shadowUnit, 1); pipelineState->Shutdown(); delete pipelineState; } TEST_F(OpenGLTestFixture, CommandList_SetRenderTargets_BindsColorAndDepthAttachments) { TextureDesc colorDesc = {}; colorDesc.width = 128; colorDesc.height = 128; colorDesc.format = static_cast(Format::R8G8B8A8_UNorm); colorDesc.textureType = static_cast(TextureType::Texture2D); TextureDesc depthDesc = {}; depthDesc.width = 128; depthDesc.height = 128; depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); depthDesc.textureType = static_cast(TextureType::Texture2D); RHITexture* colorTexture = GetDevice()->CreateTexture(colorDesc); RHITexture* depthTexture = GetDevice()->CreateTexture(depthDesc); ASSERT_NE(colorTexture, nullptr); ASSERT_NE(depthTexture, nullptr); ResourceViewDesc colorViewDesc = {}; colorViewDesc.format = colorDesc.format; RHIResourceView* rtv = GetDevice()->CreateRenderTargetView(colorTexture, colorViewDesc); ResourceViewDesc depthViewDesc = {}; depthViewDesc.format = depthDesc.format; RHIResourceView* dsv = GetDevice()->CreateDepthStencilView(depthTexture, depthViewDesc); ASSERT_NE(rtv, nullptr); ASSERT_NE(dsv, nullptr); CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); ASSERT_NE(cmdList, nullptr); cmdList->Reset(); cmdList->SetRenderTargets(1, &rtv, dsv); GLint framebuffer = 0; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &framebuffer); EXPECT_NE(framebuffer, 0); EXPECT_EQ(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE); GLint colorAttachment = 0; glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &colorAttachment); EXPECT_EQ(static_cast(colorAttachment), static_cast(rtv)->GetTexture()); GLint depthAttachment = 0; glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthAttachment); EXPECT_EQ(static_cast(depthAttachment), static_cast(dsv)->GetTexture()); cmdList->Close(); cmdList->Shutdown(); delete cmdList; delete dsv; delete rtv; depthTexture->Shutdown(); delete depthTexture; colorTexture->Shutdown(); delete colorTexture; } TEST_F(OpenGLTestFixture, CommandList_SetRenderTargets_BindsMultipleColorAttachments) { TextureDesc texDesc = {}; texDesc.width = 128; texDesc.height = 128; texDesc.format = static_cast(Format::R8G8B8A8_UNorm); texDesc.textureType = static_cast(TextureType::Texture2D); RHITexture* texture0 = GetDevice()->CreateTexture(texDesc); RHITexture* texture1 = GetDevice()->CreateTexture(texDesc); ASSERT_NE(texture0, nullptr); ASSERT_NE(texture1, nullptr); ResourceViewDesc viewDesc = {}; viewDesc.format = texDesc.format; RHIResourceView* rtv0 = GetDevice()->CreateRenderTargetView(texture0, viewDesc); RHIResourceView* rtv1 = GetDevice()->CreateRenderTargetView(texture1, viewDesc); ASSERT_NE(rtv0, nullptr); ASSERT_NE(rtv1, nullptr); RHIResourceView* rtvs[] = { rtv0, rtv1 }; CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); ASSERT_NE(cmdList, nullptr); cmdList->Reset(); cmdList->SetRenderTargets(2, rtvs, nullptr); GLint framebuffer = 0; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &framebuffer); EXPECT_NE(framebuffer, 0); EXPECT_EQ(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE); GLint colorAttachment0 = 0; glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &colorAttachment0); EXPECT_EQ(static_cast(colorAttachment0), static_cast(rtv0)->GetTexture()); GLint colorAttachment1 = 0; glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &colorAttachment1); EXPECT_EQ(static_cast(colorAttachment1), static_cast(rtv1)->GetTexture()); cmdList->Close(); cmdList->Shutdown(); delete cmdList; delete rtv1; delete rtv0; texture1->Shutdown(); delete texture1; texture0->Shutdown(); delete texture0; } TEST_F(OpenGLTestFixture, CommandList_CopyResource_UsesActualTextureDimensions) { constexpr uint32_t kWidth = 64; constexpr uint32_t kHeight = 32; std::vector srcPixels(kWidth * kHeight * 4); for (uint32_t y = 0; y < kHeight; ++y) { for (uint32_t x = 0; x < kWidth; ++x) { const size_t index = static_cast((y * kWidth + x) * 4); srcPixels[index + 0] = static_cast(x * 3); srcPixels[index + 1] = static_cast(y * 5); srcPixels[index + 2] = static_cast((x + y) * 7); srcPixels[index + 3] = 255; } } TextureDesc texDesc = {}; texDesc.width = kWidth; texDesc.height = kHeight; texDesc.depth = 1; texDesc.mipLevels = 1; texDesc.arraySize = 1; texDesc.format = static_cast(Format::R8G8B8A8_UNorm); texDesc.textureType = static_cast(TextureType::Texture2D); texDesc.sampleCount = 1; texDesc.sampleQuality = 0; RHITexture* srcTexture = GetDevice()->CreateTexture(texDesc, srcPixels.data(), srcPixels.size(), kWidth * 4); ASSERT_NE(srcTexture, nullptr); RHITexture* dstTexture = GetDevice()->CreateTexture(texDesc); ASSERT_NE(dstTexture, nullptr); RHIResourceView* srcView = GetDevice()->CreateShaderResourceView(srcTexture, {}); RHIResourceView* dstView = GetDevice()->CreateShaderResourceView(dstTexture, {}); ASSERT_NE(srcView, nullptr); ASSERT_NE(dstView, nullptr); CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); ASSERT_NE(cmdList, nullptr); cmdList->Reset(); cmdList->CopyResource(dstView, srcView); glFinish(); cmdList->Close(); std::vector dstPixels(kWidth * kHeight * 4, 0); const GLuint dstTextureId = static_cast(dstView)->GetTexture(); glBindTexture(GL_TEXTURE_2D, dstTextureId); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, dstPixels.data()); glBindTexture(GL_TEXTURE_2D, 0); EXPECT_EQ(dstPixels, srcPixels); cmdList->Shutdown(); delete cmdList; delete srcView; delete dstView; srcTexture->Shutdown(); delete srcTexture; dstTexture->Shutdown(); delete dstTexture; } TEST_F(OpenGLTestFixture, CommandList_SetVertexBuffers_InterleavedInputLayoutUsesPipelineLayout) { ASSERT_TRUE(GetDevice()->MakeContextCurrent()); GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); InputElementDesc position = {}; position.semanticName = "POSITION"; position.semanticIndex = 0; position.format = static_cast(Format::R32G32B32A32_Float); position.inputSlot = 0; position.alignedByteOffset = 0; pipelineDesc.inputLayout.elements.push_back(position); InputElementDesc color = {}; color.semanticName = "COLOR"; color.semanticIndex = 0; color.format = static_cast(Format::R32G32B32A32_Float); color.inputSlot = 0; color.alignedByteOffset = sizeof(float) * 4; pipelineDesc.inputLayout.elements.push_back(color); static const char* vertexSource = R"(#version 430 layout(location = 0) in vec4 aPosition; layout(location = 1) in vec4 aColor; out vec4 vColor; void main() { gl_Position = aPosition; vColor = aColor; } )"; static const char* fragmentSource = R"(#version 430 in vec4 vColor; layout(location = 0) out vec4 fragColor; void main() { fragColor = vColor; } )"; pipelineDesc.vertexShader.source.assign(vertexSource, vertexSource + std::strlen(vertexSource)); pipelineDesc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; pipelineDesc.vertexShader.profile = L"vs_4_30"; pipelineDesc.fragmentShader.source.assign(fragmentSource, fragmentSource + std::strlen(fragmentSource)); pipelineDesc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL; pipelineDesc.fragmentShader.profile = L"fs_4_30"; RHIPipelineState* pipelineState = GetDevice()->CreatePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); BufferDesc bufferDesc = {}; bufferDesc.size = sizeof(float) * 8 * 3; bufferDesc.stride = sizeof(float) * 8; bufferDesc.bufferType = static_cast(BufferType::Vertex); RHIBuffer* buffer = GetDevice()->CreateBuffer(bufferDesc); ASSERT_NE(buffer, nullptr); ResourceViewDesc viewDesc = {}; viewDesc.dimension = ResourceViewDimension::Buffer; viewDesc.structureByteStride = bufferDesc.stride; RHIResourceView* vbv = GetDevice()->CreateVertexBufferView(buffer, viewDesc); ASSERT_NE(vbv, nullptr); CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); ASSERT_NE(cmdList, nullptr); RHIResourceView* views[] = { vbv }; uint64_t offsets[] = { 0 }; uint32_t strides[] = { bufferDesc.stride }; cmdList->Reset(); cmdList->SetPipelineState(pipelineState); cmdList->SetVertexBuffers(0, 1, views, offsets, strides); GLint enabled0 = 0; GLint enabled1 = 0; GLint size0 = 0; GLint size1 = 0; GLint stride0 = 0; GLint stride1 = 0; void* pointer0 = nullptr; void* pointer1 = nullptr; glGetVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled0); glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled1); glGetVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_SIZE, &size0); glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_ARRAY_SIZE, &size1); glGetVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &stride0); glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &stride1); glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &pointer0); glGetVertexAttribPointerv(1, GL_VERTEX_ATTRIB_ARRAY_POINTER, &pointer1); EXPECT_EQ(enabled0, GL_TRUE); EXPECT_EQ(enabled1, GL_TRUE); EXPECT_EQ(size0, 4); EXPECT_EQ(size1, 4); EXPECT_EQ(stride0, static_cast(bufferDesc.stride)); EXPECT_EQ(stride1, static_cast(bufferDesc.stride)); EXPECT_EQ(reinterpret_cast(pointer0), 0u); EXPECT_EQ(reinterpret_cast(pointer1), sizeof(float) * 4u); cmdList->Close(); cmdList->Shutdown(); delete cmdList; vbv->Shutdown(); delete vbv; buffer->Shutdown(); delete buffer; pipelineState->Shutdown(); delete pipelineState; } TEST_F(OpenGLTestFixture, CommandList_SetComputeDescriptorSets_UsesSetAwareImageBindings) { ASSERT_TRUE(GetDevice()->MakeContextCurrent()); const uint8_t initialPixel[4] = { 0, 0, 0, 0 }; TextureDesc textureDesc = {}; textureDesc.width = 1; textureDesc.height = 1; textureDesc.depth = 1; textureDesc.mipLevels = 1; textureDesc.arraySize = 1; textureDesc.format = static_cast(Format::R8G8B8A8_UNorm); textureDesc.textureType = static_cast(TextureType::Texture2D); textureDesc.sampleCount = 1; RHITexture* texture = GetDevice()->CreateTexture(textureDesc, initialPixel, sizeof(initialPixel), 4); ASSERT_NE(texture, nullptr); ResourceViewDesc uavDesc = {}; uavDesc.format = textureDesc.format; uavDesc.dimension = ResourceViewDimension::Texture2D; RHIResourceView* uav = GetDevice()->CreateUnorderedAccessView(texture, uavDesc); ASSERT_NE(uav, nullptr); DescriptorPoolDesc poolDesc = {}; poolDesc.type = DescriptorHeapType::CBV_SRV_UAV; poolDesc.descriptorCount = 1; poolDesc.shaderVisible = true; RHIDescriptorPool* pool = GetDevice()->CreateDescriptorPool(poolDesc); ASSERT_NE(pool, nullptr); DescriptorSetLayoutBinding reservedBinding = {}; reservedBinding.binding = 0; reservedBinding.type = static_cast(DescriptorType::UAV); reservedBinding.count = 1; DescriptorSetLayoutBinding actualBinding = {}; actualBinding.binding = 0; actualBinding.type = static_cast(DescriptorType::UAV); actualBinding.count = 1; DescriptorSetLayoutDesc reservedLayout = {}; reservedLayout.bindings = &reservedBinding; reservedLayout.bindingCount = 1; DescriptorSetLayoutDesc actualLayout = {}; actualLayout.bindings = &actualBinding; actualLayout.bindingCount = 1; DescriptorSetLayoutDesc setLayouts[2] = {}; setLayouts[0] = reservedLayout; setLayouts[1] = actualLayout; RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = setLayouts; pipelineLayoutDesc.setLayoutCount = 2; RHIPipelineLayout* pipelineLayout = GetDevice()->CreatePipelineLayout(pipelineLayoutDesc); ASSERT_NE(pipelineLayout, nullptr); RHIDescriptorSet* descriptorSet = pool->AllocateSet(actualLayout); ASSERT_NE(descriptorSet, nullptr); descriptorSet->Update(0, uav); GraphicsPipelineDesc pipelineDesc = {}; RHIPipelineState* pipelineState = GetDevice()->CreatePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); ShaderCompileDesc shaderDesc = {}; shaderDesc.sourceLanguage = ShaderLanguage::GLSL; static const char* computeSource = R"( #version 430 layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; layout(binding = 1, rgba8) uniform writeonly image2D uImage; void main() { imageStore(uImage, ivec2(0, 0), vec4(1.0, 0.0, 0.0, 1.0)); } )"; shaderDesc.source.assign(computeSource, computeSource + std::strlen(computeSource)); shaderDesc.profile = L"cs_4_30"; RHIShader* computeShader = GetDevice()->CreateShader(shaderDesc); ASSERT_NE(computeShader, nullptr); pipelineState->SetComputeShader(computeShader); CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); ASSERT_NE(cmdList, nullptr); glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8); glBindImageTexture(1, 0, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8); cmdList->Reset(); cmdList->SetPipelineState(pipelineState); RHIDescriptorSet* descriptorSets[] = { descriptorSet }; cmdList->SetComputeDescriptorSets(1, 1, descriptorSets, pipelineLayout); GLint boundImageAtZero = -1; GLint boundImageAtOne = -1; glGetIntegeri_v(GL_IMAGE_BINDING_NAME, 0, &boundImageAtZero); glGetIntegeri_v(GL_IMAGE_BINDING_NAME, 1, &boundImageAtOne); EXPECT_EQ(boundImageAtZero, 0); EXPECT_EQ( static_cast(boundImageAtOne), static_cast(reinterpret_cast(texture->GetNativeHandle()))); cmdList->Dispatch(1, 1, 1); glMemoryBarrier(GL_ALL_BARRIER_BITS); uint8_t pixel[4] = {}; glBindTexture(GL_TEXTURE_2D, static_cast(reinterpret_cast(texture->GetNativeHandle()))); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel); glBindTexture(GL_TEXTURE_2D, 0); EXPECT_EQ(pixel[0], 255u); EXPECT_EQ(pixel[1], 0u); EXPECT_EQ(pixel[2], 0u); EXPECT_EQ(pixel[3], 255u); cmdList->Close(); cmdList->Shutdown(); delete cmdList; computeShader->Shutdown(); delete computeShader; pipelineState->Shutdown(); delete pipelineState; descriptorSet->Shutdown(); delete descriptorSet; pipelineLayout->Shutdown(); delete pipelineLayout; pool->Shutdown(); delete pool; uav->Shutdown(); delete uav; texture->Shutdown(); delete texture; } TEST_F(OpenGLTestFixture, DescriptorSet_Update_UsesBindingNumber) { ASSERT_TRUE(GetDevice()->MakeContextCurrent()); DescriptorPoolDesc poolDesc = {}; poolDesc.type = DescriptorHeapType::CBV_SRV_UAV; poolDesc.descriptorCount = 8; poolDesc.shaderVisible = true; RHIDescriptorPool* pool = GetDevice()->CreateDescriptorPool(poolDesc); ASSERT_NE(pool, nullptr); TextureDesc textureDesc = {}; textureDesc.width = 1; textureDesc.height = 1; textureDesc.depth = 1; textureDesc.mipLevels = 1; textureDesc.arraySize = 1; textureDesc.format = static_cast(Format::R8G8B8A8_UNorm); textureDesc.textureType = static_cast(TextureType::Texture2D); textureDesc.sampleCount = 1; const uint8_t pixel[4] = { 255, 255, 255, 255 }; RHITexture* texture = GetDevice()->CreateTexture(textureDesc, pixel, sizeof(pixel), 4); ASSERT_NE(texture, nullptr); ResourceViewDesc srvDesc = {}; srvDesc.format = textureDesc.format; srvDesc.dimension = ResourceViewDimension::Texture2D; RHIResourceView* srv = GetDevice()->CreateShaderResourceView(texture, srvDesc); ASSERT_NE(srv, nullptr); DescriptorSetLayoutBinding bindings[2] = {}; bindings[0].binding = 3; bindings[0].type = static_cast(DescriptorType::SRV); bindings[0].count = 1; bindings[1].binding = 7; bindings[1].type = static_cast(DescriptorType::SRV); bindings[1].count = 1; DescriptorSetLayoutDesc layoutDesc = {}; layoutDesc.bindings = bindings; layoutDesc.bindingCount = 2; RHIDescriptorSet* set = pool->AllocateSet(layoutDesc); ASSERT_NE(set, nullptr); auto* openGLSet = static_cast(set); const uint32_t untouchedUnit = openGLSet->GetBindingPoint(3); const uint32_t updatedUnit = openGLSet->GetBindingPoint(7); ASSERT_NE(untouchedUnit, updatedUnit); set->Update(7, srv); set->Bind(); GLint updatedTexture = 0; glActiveTexture(GL_TEXTURE0 + updatedUnit); glGetIntegerv(GL_TEXTURE_BINDING_2D, &updatedTexture); EXPECT_EQ(static_cast(updatedTexture), static_cast(reinterpret_cast(srv->GetNativeHandle()))); GLint untouchedTexture = 0; glActiveTexture(GL_TEXTURE0 + untouchedUnit); glGetIntegerv(GL_TEXTURE_BINDING_2D, &untouchedTexture); EXPECT_EQ(untouchedTexture, 0); set->Unbind(); GLint unboundTexture = 0; glActiveTexture(GL_TEXTURE0 + updatedUnit); glGetIntegerv(GL_TEXTURE_BINDING_2D, &unboundTexture); EXPECT_EQ(unboundTexture, 0); set->Shutdown(); delete set; srv->Shutdown(); delete srv; texture->Shutdown(); delete texture; pool->Shutdown(); delete pool; } TEST_F(OpenGLTestFixture, DescriptorSet_BindConstantBuffer_UsesBindingNumber) { ASSERT_TRUE(GetDevice()->MakeContextCurrent()); DescriptorPoolDesc poolDesc = {}; poolDesc.type = DescriptorHeapType::CBV_SRV_UAV; poolDesc.descriptorCount = 1; poolDesc.shaderVisible = false; RHIDescriptorPool* pool = GetDevice()->CreateDescriptorPool(poolDesc); ASSERT_NE(pool, nullptr); DescriptorSetLayoutBinding binding = {}; binding.binding = 3; binding.type = static_cast(DescriptorType::CBV); binding.count = 1; DescriptorSetLayoutDesc layoutDesc = {}; layoutDesc.bindings = &binding; layoutDesc.bindingCount = 1; RHIDescriptorSet* set = pool->AllocateSet(layoutDesc); ASSERT_NE(set, nullptr); const float matrixData[16] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; set->WriteConstant(3, matrixData, sizeof(matrixData)); set->Bind(); GLint boundBuffer = 0; glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, 3, &boundBuffer); EXPECT_NE(boundBuffer, 0); set->Unbind(); GLint unboundBuffer = -1; glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, 3, &unboundBuffer); EXPECT_EQ(unboundBuffer, 0); set->Shutdown(); delete set; pool->Shutdown(); delete pool; } TEST_F(OpenGLTestFixture, PipelineLayout_SeparatesOverlappingBindingsAcrossSetSlots) { DescriptorSetLayoutBinding set0Bindings[4] = {}; set0Bindings[0].binding = 0; set0Bindings[0].type = static_cast(DescriptorType::CBV); set0Bindings[0].count = 1; set0Bindings[1].binding = 0; set0Bindings[1].type = static_cast(DescriptorType::SRV); set0Bindings[1].count = 1; set0Bindings[2].binding = 0; set0Bindings[2].type = static_cast(DescriptorType::UAV); set0Bindings[2].count = 1; set0Bindings[3].binding = 0; set0Bindings[3].type = static_cast(DescriptorType::Sampler); set0Bindings[3].count = 1; DescriptorSetLayoutBinding set1Bindings[4] = {}; set1Bindings[0].binding = 0; set1Bindings[0].type = static_cast(DescriptorType::CBV); set1Bindings[0].count = 1; set1Bindings[1].binding = 0; set1Bindings[1].type = static_cast(DescriptorType::SRV); set1Bindings[1].count = 1; set1Bindings[2].binding = 0; set1Bindings[2].type = static_cast(DescriptorType::UAV); set1Bindings[2].count = 1; set1Bindings[3].binding = 0; set1Bindings[3].type = static_cast(DescriptorType::Sampler); set1Bindings[3].count = 1; DescriptorSetLayoutDesc setLayouts[2] = {}; setLayouts[0].bindings = set0Bindings; setLayouts[0].bindingCount = 4; setLayouts[1].bindings = set1Bindings; setLayouts[1].bindingCount = 4; RHIPipelineLayoutDesc desc = {}; desc.setLayouts = setLayouts; desc.setLayoutCount = 2; RHIPipelineLayout* layout = GetDevice()->CreatePipelineLayout(desc); ASSERT_NE(layout, nullptr); auto* openGLLayout = static_cast(layout); EXPECT_TRUE(openGLLayout->UsesSetLayouts()); EXPECT_EQ(openGLLayout->GetSetLayoutCount(), 2u); EXPECT_TRUE(openGLLayout->HasConstantBufferBinding(0, 0)); EXPECT_TRUE(openGLLayout->HasConstantBufferBinding(1, 0)); EXPECT_EQ(openGLLayout->GetConstantBufferBindingPoint(0, 0), 0u); EXPECT_EQ(openGLLayout->GetConstantBufferBindingPoint(1, 0), 1u); EXPECT_EQ(openGLLayout->GetShaderResourceBindingPoint(0, 0), 0u); EXPECT_EQ(openGLLayout->GetShaderResourceBindingPoint(1, 0), 1u); EXPECT_EQ(openGLLayout->GetUnorderedAccessBindingPoint(0, 0), 0u); EXPECT_EQ(openGLLayout->GetUnorderedAccessBindingPoint(1, 0), 1u); EXPECT_EQ(openGLLayout->GetSamplerBindingPoint(0, 0), 0u); EXPECT_EQ(openGLLayout->GetSamplerBindingPoint(1, 0), 1u); layout->Shutdown(); delete layout; } TEST_F(OpenGLTestFixture, Sampler_HonorsSamplerDesc) { ASSERT_TRUE(GetDevice()->MakeContextCurrent()); SamplerDesc desc = {}; desc.filter = static_cast(FilterMode::ComparisonAnisotropic); desc.addressU = static_cast(TextureAddressMode::Clamp); desc.addressV = static_cast(TextureAddressMode::Mirror); desc.addressW = static_cast(TextureAddressMode::Border); desc.maxAnisotropy = 8; desc.comparisonFunc = static_cast(ComparisonFunc::GreaterEqual); desc.minLod = 1.0f; desc.maxLod = 4.0f; RHISampler* sampler = GetDevice()->CreateSampler(desc); ASSERT_NE(sampler, nullptr); const GLuint samplerId = static_cast(sampler)->GetID(); GLint value = 0; glGetSamplerParameteriv(samplerId, GL_TEXTURE_WRAP_S, &value); EXPECT_EQ(value, GL_CLAMP_TO_EDGE); glGetSamplerParameteriv(samplerId, GL_TEXTURE_WRAP_T, &value); EXPECT_EQ(value, GL_MIRRORED_REPEAT); glGetSamplerParameteriv(samplerId, GL_TEXTURE_WRAP_R, &value); EXPECT_EQ(value, GL_CLAMP_TO_BORDER); glGetSamplerParameteriv(samplerId, GL_TEXTURE_MIN_FILTER, &value); EXPECT_EQ(value, GL_LINEAR_MIPMAP_LINEAR); glGetSamplerParameteriv(samplerId, GL_TEXTURE_MAG_FILTER, &value); EXPECT_EQ(value, GL_LINEAR); glGetSamplerParameteriv(samplerId, GL_TEXTURE_COMPARE_MODE, &value); EXPECT_EQ(value, GL_COMPARE_REF_TO_TEXTURE); glGetSamplerParameteriv(samplerId, GL_TEXTURE_COMPARE_FUNC, &value); EXPECT_EQ(value, GL_GEQUAL); GLfloat floatValue = 0.0f; glGetSamplerParameterfv(samplerId, GL_TEXTURE_MAX_ANISOTROPY, &floatValue); EXPECT_GE(floatValue, 8.0f); glGetSamplerParameterfv(samplerId, GL_TEXTURE_MIN_LOD, &floatValue); EXPECT_FLOAT_EQ(floatValue, 1.0f); glGetSamplerParameterfv(samplerId, GL_TEXTURE_MAX_LOD, &floatValue); EXPECT_FLOAT_EQ(floatValue, 4.0f); sampler->Shutdown(); delete sampler; }