/*
* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gles/swapchain_gles.h"
#include
#include
#include
#include "gles/device_gles.h"
#include "gles/gl_functions.h"
#include "gles/gpu_image_gles.h"
#include "gles/surface_information.h"
#include "util/log.h"
using namespace BASE_NS;
// NOTE: think more about the initialization. (context creation etc..)
RENDER_BEGIN_NAMESPACE()
namespace {
struct FormatInfo {
Format format;
uint32_t r, g, b, a;
GLenum glFormat; // texture
GLenum glFormat2; // renderable
};
// NOTE: add more mappings if needed.
static constexpr FormatInfo FORMATS[] = { { BASE_FORMAT_R8G8B8_UNORM, 8, 8, 8, 0, GL_RGB8, GL_RGB8 },
{ BASE_FORMAT_R8G8B8A8_UNORM, 8, 8, 8, 8, GL_RGBA8, GL_RGBA8 },
#if RENDER_HAS_GL_BACKEND
{ BASE_FORMAT_R16G16B16_UNORM, 16, 16, 16, 0, GL_RGB16, GL_RGB16 },
{ BASE_FORMAT_R16G16B16A16_UNORM, 16, 16, 16, 16, GL_RGBA16, GL_RGBA16 },
#endif
{ BASE_FORMAT_UNDEFINED, 0, 0, 0, 0, GL_NONE, GL_NONE } };
static constexpr FormatInfo FORMATS_SRGB[] = { { BASE_FORMAT_R8G8B8_SRGB, 8, 8, 8, 0, GL_SRGB8, GL_SRGB8_ALPHA8 },
{ BASE_FORMAT_R8G8B8A8_SRGB, 8, 8, 8, 8, GL_SRGB8_ALPHA8, GL_SRGB8_ALPHA8 },
{ BASE_FORMAT_UNDEFINED, 0, 0, 0, 0, GL_NONE, GL_NONE } };
SampleCountFlags SamplesToSampleCountFlags(uint32_t samples)
{
SampleCountFlags sampleCount = 0;
const SampleCountFlagBits bits[] = { CORE_SAMPLE_COUNT_1_BIT, CORE_SAMPLE_COUNT_2_BIT, CORE_SAMPLE_COUNT_4_BIT,
CORE_SAMPLE_COUNT_8_BIT, CORE_SAMPLE_COUNT_16_BIT, CORE_SAMPLE_COUNT_32_BIT, CORE_SAMPLE_COUNT_64_BIT };
for (int id = 0;; id++) {
if (bits[id] == 0) {
break;
}
if (samples == 0) {
break;
}
if (samples & 1) {
sampleCount |= (SampleCountFlags)bits[id];
}
samples >>= 1;
}
if (sampleCount == 0) {
sampleCount = CORE_SAMPLE_COUNT_1_BIT;
}
return sampleCount;
}
Format GetDepthFormat(uint32_t depthSize, uint32_t stencilSize)
{
auto depthFormat = BASE_FORMAT_UNDEFINED;
// This mapping might not be exact.
if (stencilSize == 0) {
if (depthSize == 16) {
depthFormat = BASE_FORMAT_D16_UNORM;
} else if (depthSize == 24) {
depthFormat = BASE_FORMAT_X8_D24_UNORM_PACK32;
} else if (depthSize == 32) {
depthFormat = BASE_FORMAT_D32_SFLOAT;
}
} else {
if (depthSize == 16) {
PLUGIN_LOG_E("unsupported depth stencil format D16_UNORM_S8_UINT fallback to D24_UNORM_S8_UINT");
depthFormat = BASE_FORMAT_D24_UNORM_S8_UINT;
} else if (depthSize == 24) {
depthFormat = BASE_FORMAT_D24_UNORM_S8_UINT;
} else if (depthSize == 32) {
PLUGIN_LOG_E("unsupported depth stencil format D32_SFLOAT_S8_UINT fallback to D24_UNORM_S8_UINT");
depthFormat = BASE_FORMAT_D24_UNORM_S8_UINT;
}
}
return depthFormat;
}
Format RgbToFormat(uint32_t r, uint32_t g, uint32_t b, uint32_t a, bool srgb)
{
const FormatInfo* format = nullptr;
if (!srgb) {
format = FORMATS;
} else {
format = FORMATS_SRGB;
}
for (int i = 0;; i++) {
if (format[i].format == BASE_FORMAT_UNDEFINED) {
// no match.
break;
}
if ((format[i].r == r) && (format[i].g == g) && (format[i].b == b) && (format[i].a == a)) {
return format[i].format;
}
}
PLUGIN_LOG_E("Unsupported swapchain color channels");
return BASE_FORMAT_UNDEFINED;
}
#if RENDER_GL_FLIP_Y_SWAPCHAIN
GLenum FormatToGlFormat(Format colorFormat)
{
const FormatInfo* steps[] = { FORMATS, FORMATS_SRGB, nullptr };
for (int step = 0;; step++) {
const FormatInfo* format = steps[step];
if (steps[step] == nullptr) {
// no match in any lists..
break;
}
for (int i = 0;; i++) {
if (format[i].format == BASE_FORMAT_MAX_ENUM) {
// no match.
break;
}
if (format[i].format == colorFormat) {
return format[i].glFormat2;
}
}
}
PLUGIN_LOG_E("Unsupported swapchain format");
return GL_NONE;
}
#endif
GlesImplementation::SurfaceInfo ExtractInfo(DeviceGLES& device, const uint64_t surfaceHandle) noexcept
{
GlesImplementation::SurfaceInfo info;
const auto& EGLState = device.GetEglState();
#if RENDER_HAS_GLES_BACKEND
if (device.GetBackendType() == DeviceBackendType::OPENGLES) {
const auto& devicePlatformData = (const DevicePlatformDataGLES&)device.GetPlatformData();
EGLSurface surface = (EGLSurface)surfaceHandle;
if (!EGLState.GetSurfaceInformation(devicePlatformData, surface, info)) {
PLUGIN_LOG_E("Could not query surface information");
}
if (!surface) {
PLUGIN_LOG_E("Surface is null");
}
}
#endif
#if RENDER_HAS_GL_BACKEND
if (device.GetBackendType() == DeviceBackendType::OPENGL) {
#if _WIN32
HDC surface = (HDC)surfaceHandle;
if (!EGLState.GetSurfaceInformation(surface, info)) {
PLUGIN_LOG_E("Could not query surface information");
}
if (!surface) {
PLUGIN_LOG_E("Surface is null");
}
#else
#error Core::DeviceBackendType::OPENGL not implemented for this platform yet.
#endif
}
#endif
return info;
}
GpuImageDesc GenerateDescriptor(bool depth, Format format, uint32_t width, uint32_t height, uint32_t samples) noexcept
{
GpuImageDesc res = {
ImageType::CORE_IMAGE_TYPE_2D, // imageType
ImageViewType::CORE_IMAGE_VIEW_TYPE_2D, // imageViewType
format, // format
ImageTiling::CORE_IMAGE_TILING_OPTIMAL, // imageTiling
0, // usageFlags
MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, // memoryPropertyFlags
0, // createFlags
EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_DYNAMIC_BARRIERS, // engineCreationFlags
width, // width
height, // height
1, // depth
1, // mipCount
1, // layerCount
SamplesToSampleCountFlags(samples), // sampleCountFlags
{} // componentMapping
};
if (depth) {
res.usageFlags = ImageUsageFlagBits::CORE_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
ImageUsageFlagBits::CORE_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT |
ImageUsageFlagBits::CORE_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
res.memoryPropertyFlags |= MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
} else {
res.usageFlags = ImageUsageFlagBits::CORE_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
ImageUsageFlagBits::CORE_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
res.engineCreationFlags |= EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_RESET_STATE_ON_FRAME_BORDERS;
}
return res;
}
#if RENDER_GL_FLIP_Y_SWAPCHAIN
void GenerateTextures(
DeviceGLES& device, uint32_t count, GLenum colorf, uint32_t sampleCount, SwapchainPlatformDataGL& plat)
{
constexpr const uint32_t TEMP_BIND_UNIT = 15; // Use texture unit 15 as a temporary bind spot.
uint32_t binding;
if (sampleCount > 1) {
binding = device.BoundTexture(TEMP_BIND_UNIT, GL_TEXTURE_2D_MULTISAMPLE);
} else {
binding = device.BoundTexture(TEMP_BIND_UNIT, GL_TEXTURE_2D);
}
Math::UVec2 size { plat.swapchainImages.width, plat.swapchainImages.height };
plat.swapchainImages.images.resize(1); // Supports multiple images, see if it helps performance.
glGenTextures(static_cast(plat.swapchainImages.images.size()), plat.swapchainImages.images.data());
for (size_t i = 0; i < plat.swapchainImages.images.size(); i++) {
GLuint tid = plat.swapchainImages.images[i];
if (sampleCount > 1) {
device.TexStorage2DMultisample(tid, GL_TEXTURE_2D_MULTISAMPLE, sampleCount, colorf, size, false);
} else {
device.TexStorage2D(tid, GL_TEXTURE_2D, 1, colorf, size);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
// Currently missing pseudo depth buffer for "CORE_DEFAULT_BACKBUFFER_DEPTH"
if (sampleCount > 1) {
device.BindTexture(TEMP_BIND_UNIT, GL_TEXTURE_2D_MULTISAMPLE, binding);
} else {
device.BindTexture(TEMP_BIND_UNIT, GL_TEXTURE_2D, binding);
}
}
void GenerateFBO(DeviceGLES& device, SwapchainPlatformDataGL& plat, bool msaa)
{
// create fbos to be used during present (blit from this fbo to backbuffer)
plat.fbos.resize(plat.swapchainImages.images.size());
glGenFramebuffers(static_cast(plat.fbos.size()), plat.fbos.data());
for (size_t i = 0; i < plat.fbos.size(); i++) {
device.BindFrameBuffer(plat.fbos[i]);
GLenum texType;
if (msaa) {
texType = GL_TEXTURE_2D_MULTISAMPLE;
} else {
texType = GL_TEXTURE_2D;
}
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texType, plat.swapchainImages.images[i], 0);
GLenum status;
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
PLUGIN_LOG_E("Framebuffer incomplete (status: %x)", status);
}
}
device.BindFrameBuffer(0);
}
#endif
} // namespace
SwapchainGLES::SwapchainGLES(Device& device, const SwapchainCreateInfo& swapchainCreateInfo)
: device_((DeviceGLES&)device), flags_(swapchainCreateInfo.swapchainFlags)
{
// check for surface creation automatically
if ((swapchainCreateInfo.surfaceHandle == 0) && swapchainCreateInfo.window.window) {
plat_.surface =
device_.GetEglState().CreateSurface(swapchainCreateInfo.window.window, swapchainCreateInfo.window.instance);
ownsSurface_ = true;
} else {
plat_.surface = (uintptr_t)swapchainCreateInfo.surfaceHandle;
}
// Fetch information about the backbuffer.
// Create pseudo handles (or actual handles, depending if direct backbuffer rendering is enabled or not)
GlesImplementation::SurfaceInfo info = ExtractInfo(device_, plat_.surface);
const Format colorFormat = RgbToFormat(info.red_size, info.green_size, info.blue_size, info.alpha_size, info.srgb);
PLUGIN_LOG_I("Input surface for swapchain is [%x] %dx%d R:%d G:%d B:%d A:%d D:%d S:%d samples:%d srgb:%s",
info.configId, info.width, info.height, info.red_size, info.green_size, info.blue_size, info.alpha_size,
info.depth_size, info.stencil_size, info.samples, info.srgb ? "true" : "false");
info.width = Math::max(info.width, 1u);
info.height = Math::max(info.height, 1u);
plat_.swapchainImageIndex = 0;
plat_.vsync = (flags_ & SwapchainFlagBits::CORE_SWAPCHAIN_VSYNC_BIT);
plat_.swapchainImages.width = info.width;
plat_.swapchainImages.height = info.height;
#if RENDER_GL_FLIP_Y_SWAPCHAIN
// Create "swapchain" images to be used.
// These are actual textures to be used during rendering. these will be blitted/flipped to backbuffer during
// present.
GenerateTextures(device_, 1, FormatToGlFormat(colorFormat), info.samples, plat_);
GenerateFBO(device_, plat_, (info.samples > 1));
#else
// Initialize one "swapchain" texture with GL name '0'
// It will be identified based on that in nodecontextpoolmanager which creates (or in this case just uses default)
// FBO.
plat_.swapchainImages.images.resize(1);
#endif
desc_ = GenerateDescriptor(false, colorFormat, info.width, info.height, info.samples);
if (flags_ & SwapchainFlagBits::CORE_SWAPCHAIN_DEPTH_BUFFER_BIT) {
if (info.depth_size == 0) {
PLUGIN_LOG_V("Default backbuffer has no depth, but depth requested!");
} else {
const auto depthFormat = GetDepthFormat(info.depth_size, info.stencil_size);
plat_.depthPlatformData = GpuImageGLES::GetPlatformData(device_, depthFormat);
descDepthBuffer_ = GenerateDescriptor(true, depthFormat, info.width, info.height, info.samples);
}
}
}
SwapchainGLES::~SwapchainGLES()
{
#if RENDER_GL_FLIP_Y_SWAPCHAIN
device_.Activate();
for (auto id : plat_.fbos) {
device_.DeleteFrameBuffer(id);
}
for (size_t i = 0; i < plat_.swapchainImages.images.size(); i++) {
device_.DeleteTexture(plat_.swapchainImages.images[i]);
}
device_.Deactivate();
#endif
if (ownsSurface_) {
device_.GetEglState().DestroySurface(plat_.surface);
}
}
const SwapchainPlatformDataGL& SwapchainGLES::GetPlatformData() const
{
return plat_;
}
const GpuImageDesc& SwapchainGLES::GetDesc() const
{
return desc_;
}
const GpuImageDesc& SwapchainGLES::GetDescDepthBuffer() const
{
return descDepthBuffer_;
}
uint32_t SwapchainGLES::GetFlags() const
{
return flags_;
}
uint32_t SwapchainGLES::GetNextImage() const
{
PLUGIN_ASSERT(plat_.swapchainImages.images.size() <= 1U);
// NOTE: does not flip images
return plat_.swapchainImageIndex;
}
uint64_t SwapchainGLES::GetSurfaceHandle() const
{
return static_cast(plat_.surface);
}
RENDER_END_NAMESPACE()