// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. #if GRAPHICS_API_OPENGL #include "GPUShaderOGL.h" #include "Engine/Core/Memory/Allocator.h" #include "Engine/Serialization/MemoryReadStream.h" #include "GPUConstantBufferOGL.h" #include "GPUShaderProgramOGL.h" #include "Engine/Graphics/GPULimitsOGL.h" #include GPUShaderOGL::GPUShaderOGL(GPUDeviceOGL* device, const String& name) : GPUResourceOGL(device, name) { } GPUShaderOGL::~GPUShaderOGL() { } void GPUShaderOGL::ReleaseGPU() { // Base GPUResourceOGL::ReleaseGPU(); } static bool SupportsSeparateShaderObjects() { return ((GPULimitsOGL*)GPUDevice::Instance->Limits)->SupportsSeparateShaderObjects; } // Verify that an OpenGL program has linked successfully. static bool VerifyLinkedProgram(GLuint program) { #if BUILD_DEBUG || GPU_OGL_DEBUG_SHADERS GLint linkStatus = 0; glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); if (linkStatus != GL_TRUE) { GLint logLength; char defaultLog[] = "No log"; char* compileLog = defaultLog; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 1) { compileLog = (char*)Allocator::Allocate(logLength); glGetProgramInfoLog(program, logLength, NULL, compileLog); } LOG(Error, "Failed to link program. Compile log: \n{0}", compileLog); if (logLength > 1) { Allocator::Free(compileLog); } return false; } #endif return true; } // Verify that an OpenGL shader has compiled successfully. static bool VerifyCompiledShader(GLuint shader, const char* glslCode) { #if BUILD_DEBUG || GPU_OGL_DEBUG_SHADERS if (SupportsSeparateShaderObjects() && glIsProgram(shader)) { bool const compiledOK = VerifyLinkedProgram(shader); #if GPU_OGL_DEBUG_SHADERS if (!compiledOK && glslCode) { LOG(Warning, "Shader: \n{0}", glslCode); } #endif return compiledOK; } else { GLint compileStatus; glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); if (compileStatus != GL_TRUE) { GLint logLength; char defaultLog[] = "No log"; char* compileLog = defaultLog; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); #if PLATFORM_ANDROID if (LogLength == 0) { // Make it big anyway // There was a bug in android 2.2 where glGetShaderiv would return 0 even though there was a error message // https://code.google.com/p/android/issues/detail?id=9953 LogLength = 4096; } #endif if (logLength > 1) { compileLog = (char*)Allocator::Allocate(logLength); glGetShaderInfoLog(shader, logLength, NULL, compileLog); } #if GPU_OGL_DEBUG_SHADERS if (glslCode) { LOG(Warning, "Shader: \n{0}", glslCode); } #endif LOG(Fatal, "Failed to compile shader. Compile log: \n{0}", compileLog); if (logLength > 1) { Allocator::Free(compileLog); } return false; } } #endif return true; } static bool VerifyProgramPipeline(GLuint Program) { bool result = true; // Don't try and validate SSOs here - the draw state matters to SSOs and it definitely can't be guaranteed to be valid at this stage if (SupportsSeparateShaderObjects()) { #if GPU_OGL_DEBUG_SHADERS result = glIsProgramPipeline(Program) == GL_TRUE; #endif } else { result = VerifyLinkedProgram(Program); } return result; } bool GPUShaderOGL::Create(MemoryReadStream& stream) { ASSERT(_device); ReleaseGPU(); // Version int32 version; stream.ReadInt32(&version); if (version != GPU_SHADER_CACHE_VERSION) { LOG(Warning, "Unsupported OpenGL shader version {0}.", version); return true; } Array cache; Array cache2; // Shaders count int32 shadersCount; stream.ReadInt32(&shadersCount); for (int32 i = 0; i < shadersCount; i++) { uint32 cacheSize; ShaderStage type = static_cast(stream.ReadByte()); int32 permutationsCount = stream.ReadByte(); ASSERT(Math::IsInRange(permutationsCount, 1, SHADER_PERMUTATIONS_MAX_COUNT)); // Load shader name StringAnsi name; stream.ReadStringAnsi(&name, 11); for (int32 permutationIndex = 0; permutationIndex < permutationsCount; permutationIndex++) { // Load cache int32 cacheType; stream.ReadInt32(&cacheType); uint32 cacheOriginalSize; stream.ReadUint32(&cacheOriginalSize); stream.ReadUint32(&cacheSize); ASSERT(Math::IsInRange(cacheSize, 1, 1024 * 1024)); cache.EnsureCapacity(cacheSize); stream.ReadBytes(cache.Get(), cacheSize); // Check if unpack the data byte* shaderBytes = cache.Get(); if (cacheType == SHADER_DATA_FORMAT_RAW) { ASSERT(cacheOriginalSize == cacheSize); } else if (cacheType == SHADER_DATA_FORMAT_LZ4) { // Decompress LZ4 data cache2.EnsureCapacity(cacheOriginalSize); int32 res = LZ4_decompress_safe((const char*)cache.Get(), (char*)cache2.Get(), cacheSize, cacheOriginalSize); if (res <= 0) { LOG(Warning, "Failed to decompress OpenGL shader data. Result: {0}.", res); return true; } cacheSize = cacheOriginalSize; shaderBytes = cache2.Get(); } else { LOG(Warning, "Unknown OpenGL shader data format."); return true; } // Create Flax shader object GPUShaderProgram* shader; switch (type) { case ShaderStage::Vertex: shader = New(_device, shaderBytes, cacheSize, stream, name); break; case ShaderStage::Hull: shader = New(_device, shaderBytes, cacheSize, stream, name); break; case ShaderStage::Domain: shader = New(_device, shaderBytes, cacheSize, stream, name); break; case ShaderStage::Geometry: shader = New(shaderBytes, cacheSize, stream, name); break; case ShaderStage::Pixel: shader = New(shaderBytes, cacheSize, stream, name); break; case ShaderStage::Compute: shader = New(shaderBytes, cacheSize, stream, name); break; default: return true; } // Add to collection _shaders.Add(shader, permutationIndex); } } // Constant Buffers byte constantBuffersCount = stream.ReadByte(); byte maximumConstantBufferSlot = stream.ReadByte(); if (constantBuffersCount > 0) { ASSERT(maximumConstantBufferSlot < MAX_CONSTANT_BUFFER_SLOTS); for (int32 i = 0; i < constantBuffersCount; i++) { // Load info byte slotIndex = stream.ReadByte(); uint32 size; stream.ReadUint32(&size); // Create CB #if GPU_ENABLE_RESOURCE_NAMING String name = ToString() + TEXT(".CB") + i; #else String name; #endif auto cb = New(_device, name, size); ASSERT(_constantBuffers[slotIndex] == nullptr); _constantBuffers[slotIndex] = cb; } } return false; } #endif