From 10ec6a7ad2c337ff33449eacb662f337dffcbd91 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Tue, 9 Nov 2021 11:34:27 +0100 Subject: [PATCH] obs/gs/mipmapper: Add support for OpenGL --- source/obs/gs/gs-mipmapper.cpp | 374 ++++++++++++++++++++++----------- source/obs/gs/gs-mipmapper.hpp | 7 +- 2 files changed, 250 insertions(+), 131 deletions(-) diff --git a/source/obs/gs/gs-mipmapper.cpp b/source/obs/gs/gs-mipmapper.cpp index 8ac59f87..c315500c 100644 --- a/source/obs/gs/gs-mipmapper.cpp +++ b/source/obs/gs/gs-mipmapper.cpp @@ -18,10 +18,12 @@ */ #include "gs-mipmapper.hpp" +#include #include #include "obs/gs/gs-helper.hpp" #include "plugin.hpp" +// Direct3D 11 #ifdef _WIN32 #ifdef _MSC_VER #pragma warning(push) @@ -36,9 +38,163 @@ #endif #endif +// OpenGL +#include "glad/gl.h" + +#ifdef _WIN32 +struct d3d_info { + ID3D11Device* device = nullptr; + ID3D11DeviceContext* context = nullptr; + ID3D11Resource* source = nullptr; + ID3D11Resource* target = nullptr; +}; + +void d3d_initialize(d3d_info& info, std::shared_ptr source, + std::shared_ptr target) +{ + info.source = reinterpret_cast(gs_texture_get_obj(source->get_object())); + info.target = reinterpret_cast(gs_texture_get_obj(target->get_object())); + info.device = reinterpret_cast(gs_get_device_obj()); + info.device->GetImmediateContext(&info.context); +} + +void d3d_copy_subregion(d3d_info& info, std::shared_ptr source, uint32_t mip_level, + uint32_t width, uint32_t height) +{ + D3D11_BOX box = {0, 0, 0, width, height, 1}; + info.context->CopySubresourceRegion(info.target, mip_level, 0, 0, 0, info.source, 0, &box); +} + +#endif + +struct opengl_info { + GLuint source = 0; + GLuint target = 0; + GLuint fbo = 0; +}; + +std::string opengl_translate_error(GLenum error) +{ +#define TRANSLATE_CASE(X) \ + case X: \ + return #X; + + switch (error) { + TRANSLATE_CASE(GL_NO_ERROR); + TRANSLATE_CASE(GL_INVALID_ENUM); + TRANSLATE_CASE(GL_INVALID_VALUE); + TRANSLATE_CASE(GL_INVALID_OPERATION); + TRANSLATE_CASE(GL_STACK_OVERFLOW); + TRANSLATE_CASE(GL_STACK_UNDERFLOW); + TRANSLATE_CASE(GL_OUT_OF_MEMORY); + TRANSLATE_CASE(GL_INVALID_FRAMEBUFFER_OPERATION); + } + + return std::to_string(error); +#undef TRANSLATE_CASE +} + +std::string opengl_translate_framebuffer_status(GLenum error) +{ +#define TRANSLATE_CASE(X) \ + case X: \ + return #X; + + switch (error) { + TRANSLATE_CASE(GL_FRAMEBUFFER_COMPLETE); + TRANSLATE_CASE(GL_FRAMEBUFFER_UNDEFINED); + TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT); + TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT); + TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER); + TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER); + TRANSLATE_CASE(GL_FRAMEBUFFER_UNSUPPORTED); + TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE); + TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS); + } + + return std::to_string(error); +#undef TRANSLATE_CASE +} + +#define D_OPENGL_CHECK_ERROR(FUNCTION) \ + if (auto err = glGetError(); err != GL_NO_ERROR) { \ + std::stringstream sstr; \ + sstr << opengl_translate_error(err) << " = " << FUNCTION; \ + throw std::runtime_error(sstr.str()); \ + } + +#define D_OPENGL_CHECK_FRAMEBUFFERSTATUS(BUFFER, FUNCTION) \ + if (auto err = glCheckFramebufferStatus(BUFFER); err != GL_FRAMEBUFFER_COMPLETE) { \ + std::stringstream sstr; \ + sstr << opengl_translate_framebuffer_status(err) << " = " << FUNCTION; \ + throw std::runtime_error(sstr.str()); \ + } + +void opengl_initialize(opengl_info& info, std::shared_ptr source, + std::shared_ptr target) +{ + info.source = *reinterpret_cast(gs_texture_get_obj(source->get_object())); + info.target = *reinterpret_cast(gs_texture_get_obj(target->get_object())); + + glGenFramebuffers(1, &info.fbo); +} + +void opengl_finalize(opengl_info& info) +{ + glDeleteFramebuffers(1, &info.fbo); +} + +void opengl_copy_subregion(opengl_info& info, std::shared_ptr source_tex, + uint32_t mip_level, uint32_t width, uint32_t height) +{ + GLuint source = *reinterpret_cast(gs_texture_get_obj(source_tex->get_object())); + + // Source -> Texture Unit 0, Read Color Framebuffer + glActiveTexture(GL_TEXTURE0); + D_OPENGL_CHECK_ERROR("glActiveTexture(GL_TEXTURE0);"); + glBindTexture(GL_TEXTURE_2D, source); + D_OPENGL_CHECK_ERROR("glBindTexture(GL_TEXTURE_2D, origin);"); + glBindFramebuffer(GL_READ_FRAMEBUFFER, info.fbo); + D_OPENGL_CHECK_ERROR("glBindFramebuffer(GL_READ_FRAMEBUFFER, info.fbo);"); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, source, + 0); // Origin is a render target, not a texture + D_OPENGL_CHECK_ERROR( + "glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, origin, mip_level);"); + D_OPENGL_CHECK_FRAMEBUFFERSTATUS( + GL_READ_FRAMEBUFFER, + "glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, origin, mip_level);"); + + // Target -> Texture Unit 1 + glActiveTexture(GL_TEXTURE1); + D_OPENGL_CHECK_ERROR("glActiveTexture(GL_TEXTURE1);"); + glBindTexture(GL_TEXTURE_2D, info.target); + D_OPENGL_CHECK_ERROR("glBindTexture(GL_TEXTURE_2D, info.target);"); + + // Copy Data + glCopyTexSubImage2D(GL_TEXTURE_2D, mip_level, 0, 0, 0, 0, width, height); + D_OPENGL_CHECK_ERROR("glCopyTexSubImage2D(GL_TEXTURE_2D, mip_level, 0, 0, 0, 0, width, height);"); + + // Target -/-> Texture Unit 1 + glActiveTexture(GL_TEXTURE1); + D_OPENGL_CHECK_ERROR("glActiveTexture(GL_TEXTURE1);"); + glBindTexture(GL_TEXTURE_2D, 0); + D_OPENGL_CHECK_ERROR("glBindTexture(GL_TEXTURE_2D, 0);"); + + // Source -/-> Texture Unit 0, Read Color Framebuffer + glActiveTexture(GL_TEXTURE0); + D_OPENGL_CHECK_ERROR("glActiveTexture(GL_TEXTURE0);"); + glBindTexture(GL_TEXTURE_2D, 0); + D_OPENGL_CHECK_ERROR("glBindTexture(GL_TEXTURE_2D, 0);"); + glBindFramebuffer(GL_READ_FRAMEBUFFER, info.fbo); + D_OPENGL_CHECK_ERROR("glBindFramebuffer(GL_READ_FRAMEBUFFER, info.fbo);"); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + D_OPENGL_CHECK_ERROR("glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);"); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + D_OPENGL_CHECK_ERROR("glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);"); +} + streamfx::obs::gs::mipmapper::~mipmapper() { - _vb.reset(); _rt.reset(); _effect.reset(); } @@ -47,32 +203,6 @@ streamfx::obs::gs::mipmapper::mipmapper() { auto gctx = streamfx::obs::gs::context(); - _vb = std::make_unique(uint32_t(3u), uint8_t(1u)); - - { - auto vtx = _vb->at(0); - vtx.position->x = 0; - vtx.position->y = 0; - vtx.uv[0]->x = 0; - vtx.uv[0]->y = 0; - } - { - auto vtx = _vb->at(1); - vtx.position->x = 0.; - vtx.position->y = 2.; - vtx.uv[0]->x = 0.; - vtx.uv[0]->y = 2.; - } - { - auto vtx = _vb->at(2); - vtx.position->x = 2.; - vtx.position->y = 0.; - vtx.uv[0]->x = 2.; - vtx.uv[0]->y = 0.; - } - - _vb->update(); - { auto file = streamfx::data_file_path("effects/mipgen.effect"); try { @@ -83,6 +213,11 @@ streamfx::obs::gs::mipmapper::mipmapper() } } +uint32_t streamfx::obs::gs::mipmapper::calculate_max_mip_level(uint32_t width, uint32_t height) +{ + return static_cast(1 + std::lroundl(floor(log2(std::max(width, height))))); +} + void streamfx::obs::gs::mipmapper::rebuild(std::shared_ptr source, std::shared_ptr target) { @@ -90,7 +225,7 @@ void streamfx::obs::gs::mipmapper::rebuild(std::shared_ptr(source->get_color_format(), GS_ZS_NONE); } - // Grab API related information. + // Initialize API Handlers. + opengl_info oglinfo; + if (gs_get_device_type() == GS_DEVICE_OPENGL) { + opengl_initialize(oglinfo, source, target); + } #ifdef _WIN32 - ID3D11Device* d3d_device = nullptr; - ID3D11DeviceContext* d3d_context = nullptr; - ID3D11Resource* d3d_source = nullptr; - ID3D11Resource* d3d_target = nullptr; + d3d_info d3dinfo; if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { - d3d_source = reinterpret_cast(gs_texture_get_obj(source->get_object())); - d3d_target = reinterpret_cast(gs_texture_get_obj(target->get_object())); - d3d_device = reinterpret_cast(gs_get_device_obj()); - d3d_device->GetImmediateContext(&d3d_context); + d3d_initialize(d3dinfo, source, target); } #endif - if (gs_get_device_type() == GS_DEVICE_OPENGL) { - // FixMe! Implement OpenGL - } // Use different methods for different types of textures. if (source->get_type() == streamfx::obs::gs::texture::type::Normal) { - while (true) { - uint32_t width = source->get_width(); - uint32_t height = source->get_height(); - size_t max_mip_level = 1; + uint32_t width = source->get_width(); + uint32_t height = source->get_height(); + size_t max_mip_level = calculate_max_mip_level(width, height); - { + { #ifdef ENABLE_PROFILING - auto cctr = streamfx::obs::gs::debug_marker(streamfx::obs::gs::debug_color_azure_radiance, - "Mip Level %" PRId64 "", 0); + auto cctr = streamfx::obs::gs::debug_marker(streamfx::obs::gs::debug_color_azure_radiance, + "Mip Level %" PRId64 "", 0); #endif + // Retrieve maximum mip map level. #ifdef _WIN32 - if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { - { // Retrieve maximum mip map level. - D3D11_TEXTURE2D_DESC td; - static_cast(d3d_target)->GetDesc(&td); - max_mip_level = td.MipLevels; - } - - // Copy mip level 0 across textures. - d3d_context->CopySubresourceRegion(d3d_target, 0, 0, 0, 0, d3d_source, 0, nullptr); - } -#endif - if (gs_get_device_type() == GS_DEVICE_OPENGL) { - // FixMe! Implement OpenGL - } + if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { + d3d_copy_subregion(d3dinfo, source, 0, width, height); } - - // Do we even need to do anything here? - if (max_mip_level == 1) - break; - - // Render each mip map level. - for (size_t mip = 1; mip < max_mip_level; mip++) { -#ifdef ENABLE_PROFILING - auto cctr = streamfx::obs::gs::debug_marker(streamfx::obs::gs::debug_color_azure_radiance, - "Mip Level %" PRIuMAX, mip); #endif - - uint32_t cwidth = std::max(width >> mip, 1); - uint32_t cheight = std::max(height >> mip, 1); - float_t iwidth = 1.f / static_cast(cwidth); - float_t iheight = 1.f / static_cast(cheight); - - // Set up rendering state. - gs_load_vertexbuffer(_vb->update(false)); - gs_load_indexbuffer(nullptr); - gs_blend_state_push(); - gs_reset_blend_state(); - gs_enable_blending(false); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); - gs_enable_color(true, true, true, true); - gs_enable_depth_test(false); - gs_enable_stencil_test(false); - gs_enable_stencil_write(false); - gs_set_cull_mode(GS_NEITHER); - try { - auto op = _rt->render(width, height); - gs_set_viewport(0, 0, static_cast(cwidth), static_cast(cheight)); - gs_ortho(0, 1, 0, 1, 0, 1); - - vec4 black = {1., 1., 1., 1}; - gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); - - _effect.get_parameter("image").set_texture(target); - _effect.get_parameter("imageTexel").set_float2(iwidth, iheight); - _effect.get_parameter("level").set_int(int32_t(mip - 1)); - while (gs_effect_loop(_effect.get_object(), "Draw")) { - gs_draw(gs_draw_mode::GS_TRIS, 0, _vb->size()); - } - } catch (...) { - } - - // Clean up rendering state. - gs_load_indexbuffer(nullptr); - gs_load_vertexbuffer(nullptr); - gs_blend_state_pop(); - - // Copy from the render target to the target mip level. -#ifdef _WIN32 - if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { - ID3D11Texture2D* rtt = - reinterpret_cast(gs_texture_get_obj(_rt->get_texture()->get_object())); - uint32_t level = uint32_t(D3D11CalcSubresource(UINT(mip), 0, UINT(max_mip_level))); - - D3D11_BOX box = {0, 0, 0, cwidth, cheight, 1}; - d3d_context->CopySubresourceRegion(d3d_target, level, 0, 0, 0, rtt, 0, &box); - } -#endif - if (gs_get_device_type() == GS_DEVICE_OPENGL) { - // FixMe! Implement OpenGL - } + if (gs_get_device_type() == GS_DEVICE_OPENGL) { + opengl_copy_subregion(oglinfo, source, 0, width, height); } - - break; } + + // Render each mip map level. + for (size_t mip = 1; mip < max_mip_level; mip++) { +#ifdef ENABLE_PROFILING + auto cctr = streamfx::obs::gs::debug_marker(streamfx::obs::gs::debug_color_azure_radiance, + "Mip Level %" PRIuMAX, mip); +#endif + + uint32_t cwidth = std::max(width >> mip, 1); + uint32_t cheight = std::max(height >> mip, 1); + float_t iwidth = 1.f / static_cast(cwidth); + float_t iheight = 1.f / static_cast(cheight); + + // Set up rendering state. + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_blending(false); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + gs_enable_color(true, true, true, true); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_set_cull_mode(GS_NEITHER); + try { + auto op = _rt->render(width, height); + gs_set_viewport(0, 0, static_cast(cwidth), static_cast(cheight)); + gs_ortho(0, 1, 0, 1, 0, 1); + + vec4 black = {1., 1., 1., 1}; + gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); + + _effect.get_parameter("image").set_texture(target); + _effect.get_parameter("imageTexel").set_float2(iwidth, iheight); + _effect.get_parameter("level").set_int(int32_t(mip - 1)); + while (gs_effect_loop(_effect.get_object(), "Draw")) { + streamfx::gs_draw_fullscreen_tri(); + } + } catch (...) { + } + + // Clean up rendering state. + gs_blend_state_pop(); + + // Copy from the render target to the target mip level. +#ifdef _WIN32 + if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { + d3d_copy_subregion(d3dinfo, _rt->get_texture(), mip, cwidth, cheight); + } +#endif + if (gs_get_device_type() == GS_DEVICE_OPENGL) { + opengl_copy_subregion(oglinfo, _rt->get_texture(), mip, cwidth, cheight); + } + } + } else { - throw std::runtime_error("Texture type is not supported by mipmapping yet."); + throw std::runtime_error("Only 2D Textures support Mip-mapping."); } + + // Finalize API handlers. + if (gs_get_device_type() == GS_DEVICE_OPENGL) { + opengl_finalize(oglinfo); + } +#ifdef _WIN32 + if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { + //d3d_finalize(d3dinfo); + } +#endif } diff --git a/source/obs/gs/gs-mipmapper.hpp b/source/obs/gs/gs-mipmapper.hpp index 7d482e6c..8f7354f6 100644 --- a/source/obs/gs/gs-mipmapper.hpp +++ b/source/obs/gs/gs-mipmapper.hpp @@ -38,14 +38,15 @@ namespace streamfx::obs::gs { class mipmapper { - std::unique_ptr _vb; - std::unique_ptr _rt; - streamfx::obs::gs::effect _effect; + std::unique_ptr _rt; + streamfx::obs::gs::effect _effect; public: ~mipmapper(); mipmapper(); + uint32_t calculate_max_mip_level(uint32_t width, uint32_t height); + void rebuild(std::shared_ptr source, std::shared_ptr target); };