From 4947d46aa165b9cdcc4a8fc3f94b3588377159ec Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Sat, 25 Apr 2020 08:15:20 +0200 Subject: [PATCH] gs-mipmapper: Update API usage, remove broken options and optimize The new libOBS API allows us to directly access the underlying API instead of having to mess around in memory. By using it we can avoid crashing in case the compiler for it is different, or in case the actual back end structure changes. Additionally the mostly unimplemented and unused options have also been removed, which streamlines the use of this class even further and reduces both shader and code complexity. Finally by optimizing the use of the internal render target we can achieve a speed up of up to 3000% over the old way, allowing for many more mipmapped filters. --- data/effects/mipgen.effect | 159 +------------ source/filters/filter-transform.cpp | 89 +++----- source/filters/filter-transform.hpp | 2 - source/obs/gs/gs-mipmapper.cpp | 336 ++++++++++++---------------- source/obs/gs/gs-mipmapper.hpp | 25 ++- source/strings.hpp | 9 - 6 files changed, 195 insertions(+), 425 deletions(-) diff --git a/data/effects/mipgen.effect b/data/effects/mipgen.effect index 5cf23446..1852dfac 100644 --- a/data/effects/mipgen.effect +++ b/data/effects/mipgen.effect @@ -1,172 +1,35 @@ uniform float4x4 ViewProj; uniform texture2d image; -uniform int level; uniform float2 imageTexel; -uniform float strength; +uniform int level; -sampler_state pointSampler { - Filter = Point; - AddressU = Clamp; - AddressV = Clamp; -}; - -sampler_state linearSampler { +sampler_state def_sampler { Filter = Linear; AddressU = Clamp; AddressV = Clamp; }; -struct VertDataIn { +struct VertexData { float4 pos : POSITION; float2 uv : TEXCOORD0; }; -struct VertDataOut { - float4 pos : POSITION; - float2 uv : TEXCOORD0; -}; - -VertDataOut VSDefault(VertDataIn v_in) +VertexData VSDefault(VertexData vtx) { - VertDataOut vert_out; - vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); - vert_out.uv = v_in.uv; - return vert_out; + vtx.pos = mul(float4(vtx.pos.xyz, 1.0), ViewProj); + return vtx; } -float4 PSPoint(VertDataOut v_in) : TARGET +float4 PSDefault(VertexData vtx) : TARGET { - return image.SampleLevel(pointSampler, v_in.uv, level); + return image.SampleLevel(def_sampler, vtx.uv, level); } -float4 PSLinear(VertDataOut v_in) : TARGET -{ - return image.SampleLevel(linearSampler, v_in.uv, level); -} - -float4 PSSharpen(VertDataOut v_in) : TARGET -{ - float2 ul, ur, dl, dr, u, d, l, r; - ul = float2(-imageTexel.x, -imageTexel.y); - ur = float2(imageTexel.x, -imageTexel.y); - dl = -ur; - dr = -ul; - u = float2(0, -imageTexel.y); - d = -u; - l = float2(-imageTexel.x, 0); - r = -l; - - float4 tl, tc, tr, cl, cc, cr, bl, bc, br; - tl = image.SampleLevel(pointSampler, v_in.uv + ul, level); - tc = image.SampleLevel(pointSampler, v_in.uv + u, level); - tr = image.SampleLevel(pointSampler, v_in.uv + ur, level); - cl = image.SampleLevel(pointSampler, v_in.uv + l, level); - cc = image.SampleLevel(pointSampler, v_in.uv, level); - cr = image.SampleLevel(pointSampler, v_in.uv + r, level); - bl = image.SampleLevel(pointSampler, v_in.uv + dl, level); - bc = image.SampleLevel(pointSampler, v_in.uv + d, level); - br = image.SampleLevel(pointSampler, v_in.uv + dr, level); - - float kernel1, kernel2, kernel3; - kernel1 = -0.25 * strength; - kernel2 = -0.50 * strength; - kernel3 = abs(kernel1 * 4) + abs(kernel2 * 4) + 1; - - return (tl * kernel1) + (tr * kernel1) + (bl * kernel1) + (br * kernel1) + (cl * kernel2) + (cr * kernel2) + (tc * kernel2) + (bc * kernel2) + (cc * kernel3); -} - -float4 PSSmoothen(VertDataOut v_in) : TARGET -{ - // If we use linear sampling, we can get away with just 4 total sampler queries. - // However this is not a cheap implementation, it's just meant to be accurate so we do each sampler query and rely on the compiler. - - float3 smoothKernel3 = float3(0.0574428, 0.0947072, 0.3914000); - float2 ul, ur, dl, dr, u, d, l, r; - float4 tl, tc, tr, cl, cc, cr, bl, bc, br; - float limitstr = clamp(strength, 0.0, 1.0); - - ul = float2(-imageTexel.x, -imageTexel.y); - ur = float2(imageTexel.x, -imageTexel.y); - dl = -ur; - dr = -ul; - u = float2(0, -imageTexel.y); - d = -u; - l = float2(-imageTexel.x, 0); - r = -l; - - tl = image.SampleLevel(pointSampler, v_in.uv + ul * limitstr, level) * smoothKernel3[0]; - tc = image.SampleLevel(pointSampler, v_in.uv + u * limitstr, level) * smoothKernel3[1]; - tr = image.SampleLevel(pointSampler, v_in.uv + ur * limitstr, level) * smoothKernel3[0]; - cl = image.SampleLevel(pointSampler, v_in.uv + l * limitstr, level) * smoothKernel3[1]; - cc = image.SampleLevel(pointSampler, v_in.uv, level) * smoothKernel3[2]; - cr = image.SampleLevel(pointSampler, v_in.uv + r * limitstr, level) * smoothKernel3[1]; - bl = image.SampleLevel(pointSampler, v_in.uv + dl * limitstr, level) * smoothKernel3[0]; - bc = image.SampleLevel(pointSampler, v_in.uv + d * limitstr, level) * smoothKernel3[1]; - br = image.SampleLevel(pointSampler, v_in.uv + dr * limitstr, level) * smoothKernel3[0]; - - return tl + tc + tr + cl + cc + cr + bl + bc + br; -} - -float4 PSBicubic(VertDataOut v_in) : TARGET -{ - return float4(1.0, 0.0, 1.0, 1.0); -} - -float4 PSLanczos(VertDataOut v_in) : TARGET -{ - return float4(1.0, 0.0, 1.0, 1.0); -} - -technique Point +technique Draw { pass { - vertex_shader = VSDefault(v_in); - pixel_shader = PSPoint(v_in); - } -} - -technique Linear -{ - pass - { - vertex_shader = VSDefault(v_in); - pixel_shader = PSLinear(v_in); - } -} - -technique Sharpen -{ - pass - { - vertex_shader = VSDefault(v_in); - pixel_shader = PSSharpen(v_in); - } -} - -technique Smoothen -{ - pass - { - vertex_shader = VSDefault(v_in); - pixel_shader = PSSmoothen(v_in); - } -} - -technique Bicubic -{ - pass - { - vertex_shader = VSDefault(v_in); - pixel_shader = PSBicubic(v_in); - } -} - -technique Lanczos -{ - pass - { - vertex_shader = VSDefault(v_in); - pixel_shader = PSLanczos(v_in); + vertex_shader = VSDefault(vtx); + pixel_shader = PSDefault(vtx); } } diff --git a/source/filters/filter-transform.cpp b/source/filters/filter-transform.cpp index 7c0cc373..0543a8a1 100644 --- a/source/filters/filter-transform.cpp +++ b/source/filters/filter-transform.cpp @@ -81,9 +81,8 @@ enum RotationOrder : int64_t { }; transform_instance::transform_instance(obs_data_t* data, obs_source_t* context) - : obs::source_instance(data, context), _cache_rendered(), _mipmap_enabled(), _mipmap_strength(), - _mipmap_generator(), _source_rendered(), _source_size(), _update_mesh(), _rotation_order(), - _camera_orthographic(), _camera_fov() + : obs::source_instance(data, context), _cache_rendered(), _mipmap_enabled(), _source_rendered(), _source_size(), + _update_mesh(), _rotation_order(), _camera_orthographic(), _camera_fov() { _cache_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); _source_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); @@ -149,9 +148,7 @@ void transform_instance::update(obs_data_t* settings) _shear->z = 0.0f; // Mipmapping - _mipmap_enabled = obs_data_get_bool(settings, ST_MIPMAPPING); - _mipmap_strength = obs_data_get_double(settings, S_MIPGENERATOR_INTENSITY); - _mipmap_generator = static_cast(obs_data_get_int(settings, S_MIPGENERATOR)); + _mipmap_enabled = obs_data_get_bool(settings, ST_MIPMAPPING); _update_mesh = true; } @@ -360,15 +357,13 @@ void transform_instance::video_render(gs_effect_t* effect) _mipmap_texture = std::make_shared(cache_width, cache_height, GS_RGBA, mip_levels, nullptr, gs::texture::flags::None); } - _mipmapper.rebuild(_cache_texture, _mipmap_texture, _mipmap_generator, float_t(_mipmap_strength)); + _mipmapper.rebuild(_cache_texture, _mipmap_texture); _mipmap_rendered = true; - } else { - _mipmap_texture = _cache_texture; - } - if (!_mipmap_texture) { - obs_source_skip_video_filter(_self); - return; + if (!_mipmap_texture) { + obs_source_skip_video_filter(_self); + return; + } } { @@ -400,7 +395,9 @@ void transform_instance::video_render(gs_effect_t* effect) gs_load_vertexbuffer(_vertex_buffer->update(false)); gs_load_indexbuffer(nullptr); gs_effect_set_texture(gs_effect_get_param_by_name(default_effect, "image"), - _mipmap_enabled ? _mipmap_texture->get_object() : _cache_texture->get_object()); + _mipmap_enabled + ? (_mipmap_texture ? _mipmap_texture->get_object() : _cache_texture->get_object()) + : _cache_texture->get_object()); while (gs_effect_loop(default_effect, "Draw")) { gs_draw(GS_TRISTRIP, 0, 4); } @@ -455,10 +452,7 @@ void transform_factory::get_defaults2(obs_data_t* settings) obs_data_set_default_double(settings, ST_SCALE_Y, 100); obs_data_set_default_double(settings, ST_SHEAR_X, 0); obs_data_set_default_double(settings, ST_SHEAR_Y, 0); - obs_data_set_default_bool(settings, S_ADVANCED, false); obs_data_set_default_bool(settings, ST_MIPMAPPING, false); - obs_data_set_default_int(settings, S_MIPGENERATOR, static_cast(gs::mipmapper::generator::Linear)); - obs_data_set_default_double(settings, S_MIPGENERATOR_INTENSITY, 100.0); } static bool modified_properties(obs_properties_t* pr, obs_property_t*, obs_data_t* d) noexcept @@ -474,10 +468,6 @@ try { break; } - bool advancedVisible = obs_data_get_bool(d, S_ADVANCED); - obs_property_set_visible(obs_properties_get(pr, ST_ROTATION_ORDER), advancedVisible); - obs_property_set_visible(obs_properties_get(pr, ST_MIPMAPPING), advancedVisible); - return true; } catch (const std::exception& ex) { LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); @@ -537,18 +527,6 @@ obs_properties_t* transform_factory::get_properties2(transform_instance* data) } } - { // Order - auto p = obs_properties_add_list(grp, ST_ROTATION_ORDER, D_TRANSLATE(ST_ROTATION_ORDER), - OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); - obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_ROTATION_ORDER))); - obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_XYZ), RotationOrder::XYZ); - obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_XZY), RotationOrder::XZY); - obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_YXZ), RotationOrder::YXZ); - obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_YZX), RotationOrder::YZX); - obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_ZXY), RotationOrder::ZXY); - obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_ZYX), RotationOrder::ZYX); - } - obs_properties_add_group(pr, ST_ROTATION, D_TRANSLATE(ST_ROTATION), OBS_GROUP_NORMAL, grp); } { // Scale @@ -576,41 +554,26 @@ obs_properties_t* transform_factory::get_properties2(transform_instance* data) obs_properties_add_group(pr, ST_SHEAR, D_TRANSLATE(ST_SHEAR), OBS_GROUP_NORMAL, grp); } - { - auto p = obs_properties_add_bool(pr, S_ADVANCED, D_TRANSLATE(S_ADVANCED)); - obs_property_set_modified_callback(p, modified_properties); - } - { auto grp = obs_properties_create(); + obs_properties_add_group(pr, S_ADVANCED, D_TRANSLATE(S_ADVANCED), OBS_GROUP_NORMAL, grp); - { - auto p = obs_properties_add_list(grp, S_MIPGENERATOR, D_TRANSLATE(S_MIPGENERATOR), OBS_COMBO_TYPE_LIST, - OBS_COMBO_FORMAT_INT); - obs_property_set_long_description(p, D_TRANSLATE(D_DESC(S_MIPGENERATOR))); - obs_property_list_add_int(p, D_TRANSLATE(S_MIPGENERATOR_POINT), (long long)gs::mipmapper::generator::Point); - obs_property_list_add_int(p, D_TRANSLATE(S_MIPGENERATOR_LINEAR), - (long long)gs::mipmapper::generator::Linear); - obs_property_list_add_int(p, D_TRANSLATE(S_MIPGENERATOR_SHARPEN), - (long long)gs::mipmapper::generator::Sharpen); - obs_property_list_add_int(p, D_TRANSLATE(S_MIPGENERATOR_SMOOTHEN), - (long long)gs::mipmapper::generator::Smoothen); - obs_property_list_add_int(p, D_TRANSLATE(S_MIPGENERATOR_BICUBIC), - (long long)gs::mipmapper::generator::Bicubic); - obs_property_list_add_int(p, D_TRANSLATE(S_MIPGENERATOR_LANCZOS), - (long long)gs::mipmapper::generator::Lanczos); - } - { - auto p = obs_properties_add_float_slider(grp, S_MIPGENERATOR_INTENSITY, - D_TRANSLATE(S_MIPGENERATOR_INTENSITY), 0.0, 1000.0, 0.01); - obs_property_set_long_description(p, D_TRANSLATE(D_DESC(S_MIPGENERATOR_INTENSITY))); - obs_property_float_set_suffix(p, "%"); - } - - { - auto p = obs_properties_add_group(pr, ST_MIPMAPPING, D_TRANSLATE(ST_MIPMAPPING), OBS_GROUP_CHECKABLE, grp); + { // Mipmapping + auto p = obs_properties_add_bool(grp, ST_MIPMAPPING, D_TRANSLATE(ST_MIPMAPPING)); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MIPMAPPING))); } + + { // Order + auto p = obs_properties_add_list(grp, ST_ROTATION_ORDER, D_TRANSLATE(ST_ROTATION_ORDER), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_ROTATION_ORDER))); + obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_XYZ), RotationOrder::XYZ); + obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_XZY), RotationOrder::XZY); + obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_YXZ), RotationOrder::YXZ); + obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_YZX), RotationOrder::YZX); + obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_ZXY), RotationOrder::ZXY); + obs_property_list_add_int(p, D_TRANSLATE(ST_ROTATION_ORDER_ZYX), RotationOrder::ZYX); + } } return pr; diff --git a/source/filters/filter-transform.hpp b/source/filters/filter-transform.hpp index 9e54a659..b0ff8477 100644 --- a/source/filters/filter-transform.hpp +++ b/source/filters/filter-transform.hpp @@ -36,8 +36,6 @@ namespace streamfx::filter::transform { // Mip-mapping bool _mipmap_enabled; bool _mipmap_rendered; - double_t _mipmap_strength; - gs::mipmapper::generator _mipmap_generator; gs::mipmapper _mipmapper; std::shared_ptr _mipmap_texture; diff --git a/source/obs/gs/gs-mipmapper.cpp b/source/obs/gs/gs-mipmapper.cpp index 7e3773cb..55715aec 100644 --- a/source/obs/gs/gs-mipmapper.cpp +++ b/source/obs/gs/gs-mipmapper.cpp @@ -22,46 +22,18 @@ #include "obs/gs/gs-helper.hpp" #include "plugin.hpp" -#if defined(WIN32) || defined(WIN64) +#ifdef _WIN32 #ifdef _MSC_VER #pragma warning(push) -#pragma warning(disable : 5039) +#pragma warning(disable : 4201 4365 5039) #endif #include -#ifdef _MSC_VER -#pragma warning(pop) -#endif -#endif - -// Here be dragons! -// This is to add support for mipmap generation which is by default not possible with libobs. -// OBS hides a ton of possible things from us, which we'd have to simulate - or just hack around. -struct graphics_subsystem { - void* module; - gs_device_t* device; - // No other fields required. -}; - -#if defined(WIN32) || defined(WIN64) -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4201 4365) -#endif +#include #include #include -#include #ifdef _MSC_VER #pragma warning(pop) #endif - -// Slaughtered copy of d3d11-subsystem.hpp gs_device. We only need up to device and context, the rest is "unknown" to us. -struct gs_d3d11_device { - ComPtr factory; - ComPtr adapter; - ComPtr device; - ComPtr context; - // No other fields required. -}; #endif gs::mipmapper::~mipmapper() @@ -73,200 +45,182 @@ gs::mipmapper::~mipmapper() gs::mipmapper::mipmapper() { - _vb = std::make_unique(uint32_t(6u), std::uint8_t(1u)); - auto v0 = _vb->at(0); - v0.position->x = 0; - v0.position->y = 0; - v0.uv[0]->x = 0; - v0.uv[0]->y = 0; + _vb = std::make_unique(uint32_t(3u), std::uint8_t(1u)); - auto v1 = _vb->at(1); - auto v4 = _vb->at(4); - v4.position->x = v1.position->x = 1.0; - v4.position->y = v1.position->y = 0; - v4.uv[0]->x = v1.uv[0]->x = 1.0; - v4.uv[0]->y = v1.uv[0]->y = 0; - - auto v2 = _vb->at(2); - auto v3 = _vb->at(3); - v3.position->x = v2.position->x = 0; - v3.position->y = v2.position->y = 1.0; - v3.uv[0]->x = v2.uv[0]->x = 0; - v3.uv[0]->y = v2.uv[0]->y = 1.0; - - auto v5 = _vb->at(5); - v5.position->x = 1.0; - v5.position->y = 1.0; - v5.uv[0]->x = 1.0; - v5.uv[0]->y = 1.0; + { + 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(); - char* effect_file = obs_module_file("effects/mipgen.effect"); - _effect = gs::effect::create(effect_file); - bfree(effect_file); + { + char* path = obs_module_file("effects/mipgen.effect"); + _effect = gs::effect::create(path); + bfree(path); + } } -void gs::mipmapper::rebuild(std::shared_ptr source, std::shared_ptr target, - gs::mipmapper::generator generator = gs::mipmapper::generator::Linear, - float_t strength = 1.0) +void gs::mipmapper::rebuild(std::shared_ptr source, std::shared_ptr target) { - // Here be dragons! You have been warned. + { // Validate arguments and structure. + if (!source || !target) + return; // Do nothing if source or target are missing. - // Do nothing if there is no texture given. - if (!source) { -#ifdef _DEBUG - assert(!source); -#endif - return; - } - if (!target) { -#ifdef _DEBUG - assert(!target); -#endif - return; - } - - // Ensure texture sizes match - if ((source->get_width() != target->get_width()) || (source->get_height() != target->get_height())) { - throw std::invalid_argument("source and target must have same size"); - } - - // Ensure texture types match - if ((source->get_type() != target->get_type())) { - throw std::invalid_argument("source and target must have same type"); - } - - // Ensure texture formats match - if ((source->get_color_format() != target->get_color_format())) { - throw std::invalid_argument("source and target must have same format"); + if (!_vb || !_effect) + return; // Do nothing if the necessary data failed to load. + + // Ensure texture sizes match + if ((source->get_width() != target->get_width()) || (source->get_height() != target->get_height())) { + throw std::invalid_argument("source and target must have same size"); + } + + // Ensure texture types match + if ((source->get_type() != target->get_type())) { + throw std::invalid_argument("source and target must have same type"); + } + + // Ensure texture formats match + if ((source->get_color_format() != target->get_color_format())) { + throw std::invalid_argument("source and target must have same format"); + } } + // Get a unique lock on the graphics context. auto gctx = gs::context(); - // Copy original texture - //gs_copy_texture(target->get_object(), source->get_object()); - - // Test if we actually need to recreate the render target for a different format or at all. + // Do we need to recreate the render target for a different format? if ((!_rt) || (source->get_color_format() != _rt->get_color_format())) { _rt = std::make_unique(source->get_color_format(), GS_ZS_NONE); } - // Render -#if defined(WIN32) || defined(WIN64) - graphics_t* ctx = gs_get_context(); - gs_d3d11_device* dev = reinterpret_cast(ctx->device); + // Grab API related information. +#ifdef _WIN32 + ID3D11Device* d3d_device = nullptr; + ID3D11DeviceContext* d3d_context = nullptr; + ID3D11Resource* d3d_source = nullptr; + ID3D11Resource* d3d_target = nullptr; + if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { + D3D11_TEXTURE2D_DESC td; + 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); + } #endif - int device_type = gs_get_device_type(); - std::string technique = "Draw"; - - switch (generator) { - case generator::Point: - technique = "Point"; - break; - case generator::Linear: - technique = "Linear"; - break; - case generator::Sharpen: - technique = "Sharpen"; - break; - case generator::Smoothen: - technique = "Smoothen"; - break; - case generator::Bicubic: - technique = "Bicubic"; - break; - case generator::Lanczos: - technique = "Lanczos"; - break; + if (gs_get_device_type() == GS_DEVICE_OPENGL) { + // FixMe! Implement OpenGL } - gs_load_vertexbuffer(_vb->update()); - gs_load_indexbuffer(nullptr); - + // Use different methods for different types of textures. if (source->get_type() == gs::texture::type::Normal) { - std::size_t texture_width = source->get_width(); - std::size_t texture_height = source->get_height(); - float_t texel_width = 1.0f / texture_width; - float_t texel_height = 1.0f / texture_height; - std::size_t mip_levels = 1; + while (true) { + uint32_t width = source->get_width(); + uint32_t height = source->get_height(); + size_t max_mip_level = 1; -#if defined(WIN32) || defined(WIN64) - ID3D11Texture2D* target_t2 = nullptr; - ID3D11Texture2D* source_t2 = nullptr; - if (device_type == GS_DEVICE_DIRECT3D_11) { - // We definitely have a Direct3D11 resource. - D3D11_TEXTURE2D_DESC target_t2desc; - target_t2 = reinterpret_cast(gs_texture_get_obj(target->get_object())); - source_t2 = reinterpret_cast(gs_texture_get_obj(source->get_object())); - target_t2->GetDesc(&target_t2desc); - dev->context->CopySubresourceRegion(target_t2, 0, 0, 0, 0, source_t2, 0, nullptr); - mip_levels = target_t2desc.MipLevels; - } + { + auto cctr = gs::debug_marker(gs::debug_color_azure_radiance, "Mip Level %lld", 0); +#ifdef _WIN32 + if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) { + { // Retrieve maximum mip map level. + D3D11_TEXTURE2D_DESC td; + reinterpret_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 (device_type == GS_DEVICE_OPENGL) { - // This is an OpenGL resource. - } - - // If we do not have any miplevels, just stop now. - if (mip_levels == 1) { - return; - } - - for (std::size_t mip = 1; mip < mip_levels; mip++) { - texture_width /= 2; - texture_height /= 2; - if (texture_width == 0) { - texture_width = 1; - } - if (texture_height == 0) { - texture_height = 1; + if (gs_get_device_type() == GS_DEVICE_OPENGL) { + // FixMe! Implement OpenGL + } } - texel_width = 1.0f / texture_width; - texel_height = 1.0f / texture_height; + // Do we even need to do anything here? + if (max_mip_level == 1) + break; - // Draw mipmap layer - try { - auto op = _rt->render(uint32_t(texture_width), uint32_t(texture_height)); + // Render each mip map level. + for (size_t mip = 1; mip < max_mip_level; mip++) { + auto cctr = gs::debug_marker(gs::debug_color_azure_radiance, "Mip Level %lld", mip); - gs_set_cull_mode(GS_NEITHER); + uint32_t cwidth = std::max(width >> mip, 1); + uint32_t cheight = std::max(height >> mip, 1); + float_t iwidth = 1. / static_cast(cwidth); + float_t iheight = 1. / 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_blend_function_separate(gs_blend_type::GS_BLEND_ONE, gs_blend_type::GS_BLEND_ZERO, - gs_blend_type::GS_BLEND_ONE, gs_blend_type::GS_BLEND_ZERO); + 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_enable_color(true, true, true, true); - gs_ortho(0, 1, 0, 1, -1, 1); + gs_set_cull_mode(GS_NEITHER); + try { + auto op = _rt->render(width, height); + gs_set_viewport(0, 0, cwidth, cheight); + gs_ortho(0, 1, 0, 1, 0, 1); - vec4 black; - vec4_zero(&black); - gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); + 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("level").set_int(int32_t(mip - 1)); - _effect.get_parameter("imageTexel").set_float2(texel_width, texel_height); - _effect.get_parameter("strength").set_float(strength); - - while (gs_effect_loop(_effect.get_object(), technique.c_str())) { - gs_draw(gs_draw_mode::GS_TRIS, 0, _vb->size()); + _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 (...) { } - } catch (...) { - LOG_ERROR("Failed to render mipmap layer."); - } -#if defined(WIN32) || defined(WIN64) - if (device_type == GS_DEVICE_DIRECT3D_11) { - // Copy - ID3D11Texture2D* rt = reinterpret_cast(gs_texture_get_obj(_rt->get_object())); - std::uint32_t level = uint32_t(D3D11CalcSubresource(UINT(mip), 0, UINT(mip_levels))); - dev->context->CopySubresourceRegion(target_t2, level, 0, 0, 0, rt, 0, NULL); - } + // 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())); + std::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 + } + } - gs_load_indexbuffer(nullptr); - gs_load_vertexbuffer(nullptr); + break; + } + } else { + throw std::runtime_error("Texture type is not supported by mipmapping yet."); + } } diff --git a/source/obs/gs/gs-mipmapper.hpp b/source/obs/gs/gs-mipmapper.hpp index 1509e585..49bd6180 100644 --- a/source/obs/gs/gs-mipmapper.hpp +++ b/source/obs/gs/gs-mipmapper.hpp @@ -24,27 +24,28 @@ #include "gs-texture.hpp" #include "gs-vertexbuffer.hpp" +/* gs::mipmapper is an attempt at adding dynamic mip-map generation to a software + * which only supports static mip-maps. It is effectively an incredibly bad hack + * instead of a proper solution - can break any time and likely already has. + * + * Needless to say, dynamic mip-map generation costs a lot of GPU time, especially + * when things need to be synchronized. In the ideal case we would just render + * straight to the mip level, but this is not possible in DirectX 11 and OpenGL. + * + * So instead we render to a render target and copy from there to the actual + * resource. Super wasteful, but what else can we actually do? + */ + namespace gs { class mipmapper { std::unique_ptr _vb; std::unique_ptr _rt; gs::effect _effect; - public: - enum class generator : std::uint8_t { - Point, - Linear, - Sharpen, - Smoothen, - Bicubic, - Lanczos, - }; - public: ~mipmapper(); mipmapper(); - void rebuild(std::shared_ptr source, std::shared_ptr target, - gs::mipmapper::generator generator, float_t strength); + void rebuild(std::shared_ptr source, std::shared_ptr target); }; } // namespace gs diff --git a/source/strings.hpp b/source/strings.hpp index dbb7cec1..7f5d511c 100644 --- a/source/strings.hpp +++ b/source/strings.hpp @@ -72,15 +72,6 @@ #define S_BLUR_SUBTYPE_ROTATIONAL "Blur.Subtype.Rotational" #define S_BLUR_SUBTYPE_ZOOM "Blur.Subtype.Zoom" -#define S_MIPGENERATOR "MipGenerator" -#define S_MIPGENERATOR_POINT "MipGenerator.Point" -#define S_MIPGENERATOR_LINEAR "MipGenerator.Linear" -#define S_MIPGENERATOR_SHARPEN "MipGenerator.Sharpen" -#define S_MIPGENERATOR_SMOOTHEN "MipGenerator.Smoothen" -#define S_MIPGENERATOR_BICUBIC "MipGenerator.Bicubic" -#define S_MIPGENERATOR_LANCZOS "MipGenerator.Lanczos" -#define S_MIPGENERATOR_INTENSITY "MipGenerator.Intensity" - #define S_CHANNEL_RED "Channel.Red" #define S_CHANNEL_GREEN "Channel.Green" #define S_CHANNEL_BLUE "Channel.Blue"