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.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2020-04-25 08:15:20 +02:00
parent 47a22ce462
commit 4947d46aa1
6 changed files with 195 additions and 425 deletions

View file

@ -1,172 +1,35 @@
uniform float4x4 ViewProj; uniform float4x4 ViewProj;
uniform texture2d image; uniform texture2d image;
uniform int level;
uniform float2 imageTexel; uniform float2 imageTexel;
uniform float strength; uniform int level;
sampler_state pointSampler { sampler_state def_sampler {
Filter = Point;
AddressU = Clamp;
AddressV = Clamp;
};
sampler_state linearSampler {
Filter = Linear; Filter = Linear;
AddressU = Clamp; AddressU = Clamp;
AddressV = Clamp; AddressV = Clamp;
}; };
struct VertDataIn { struct VertexData {
float4 pos : POSITION; float4 pos : POSITION;
float2 uv : TEXCOORD0; float2 uv : TEXCOORD0;
}; };
struct VertDataOut { VertexData VSDefault(VertexData vtx)
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
VertDataOut VSDefault(VertDataIn v_in)
{ {
VertDataOut vert_out; vtx.pos = mul(float4(vtx.pos.xyz, 1.0), ViewProj);
vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); return vtx;
vert_out.uv = v_in.uv;
return vert_out;
} }
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 technique Draw
{
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
{ {
pass pass
{ {
vertex_shader = VSDefault(v_in); vertex_shader = VSDefault(vtx);
pixel_shader = PSPoint(v_in); pixel_shader = PSDefault(vtx);
}
}
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);
} }
} }

View file

@ -81,9 +81,8 @@ enum RotationOrder : int64_t {
}; };
transform_instance::transform_instance(obs_data_t* data, obs_source_t* context) transform_instance::transform_instance(obs_data_t* data, obs_source_t* context)
: obs::source_instance(data, context), _cache_rendered(), _mipmap_enabled(), _mipmap_strength(), : obs::source_instance(data, context), _cache_rendered(), _mipmap_enabled(), _source_rendered(), _source_size(),
_mipmap_generator(), _source_rendered(), _source_size(), _update_mesh(), _rotation_order(), _update_mesh(), _rotation_order(), _camera_orthographic(), _camera_fov()
_camera_orthographic(), _camera_fov()
{ {
_cache_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE); _cache_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
_source_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE); _source_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
@ -150,8 +149,6 @@ void transform_instance::update(obs_data_t* settings)
// Mipmapping // Mipmapping
_mipmap_enabled = obs_data_get_bool(settings, ST_MIPMAPPING); _mipmap_enabled = obs_data_get_bool(settings, ST_MIPMAPPING);
_mipmap_strength = obs_data_get_double(settings, S_MIPGENERATOR_INTENSITY);
_mipmap_generator = static_cast<gs::mipmapper::generator>(obs_data_get_int(settings, S_MIPGENERATOR));
_update_mesh = true; _update_mesh = true;
} }
@ -360,16 +357,14 @@ void transform_instance::video_render(gs_effect_t* effect)
_mipmap_texture = std::make_shared<gs::texture>(cache_width, cache_height, GS_RGBA, mip_levels, nullptr, _mipmap_texture = std::make_shared<gs::texture>(cache_width, cache_height, GS_RGBA, mip_levels, nullptr,
gs::texture::flags::None); 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; _mipmap_rendered = true;
} else {
_mipmap_texture = _cache_texture;
}
if (!_mipmap_texture) { if (!_mipmap_texture) {
obs_source_skip_video_filter(_self); obs_source_skip_video_filter(_self);
return; return;
} }
}
{ {
gs::debug_marker _marker_draw{gs::debug_color_cache_render, "Geometry"}; gs::debug_marker _marker_draw{gs::debug_color_cache_render, "Geometry"};
@ -400,7 +395,9 @@ void transform_instance::video_render(gs_effect_t* effect)
gs_load_vertexbuffer(_vertex_buffer->update(false)); gs_load_vertexbuffer(_vertex_buffer->update(false));
gs_load_indexbuffer(nullptr); gs_load_indexbuffer(nullptr);
gs_effect_set_texture(gs_effect_get_param_by_name(default_effect, "image"), 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")) { while (gs_effect_loop(default_effect, "Draw")) {
gs_draw(GS_TRISTRIP, 0, 4); 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_SCALE_Y, 100);
obs_data_set_default_double(settings, ST_SHEAR_X, 0); obs_data_set_default_double(settings, ST_SHEAR_X, 0);
obs_data_set_default_double(settings, ST_SHEAR_Y, 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_bool(settings, ST_MIPMAPPING, false);
obs_data_set_default_int(settings, S_MIPGENERATOR, static_cast<int64_t>(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 static bool modified_properties(obs_properties_t* pr, obs_property_t*, obs_data_t* d) noexcept
@ -474,10 +468,6 @@ try {
break; 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; return true;
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); 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); obs_properties_add_group(pr, ST_ROTATION, D_TRANSLATE(ST_ROTATION), OBS_GROUP_NORMAL, grp);
} }
{ // Scale { // 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); 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(); auto grp = obs_properties_create();
obs_properties_add_group(pr, S_ADVANCED, D_TRANSLATE(S_ADVANCED), OBS_GROUP_NORMAL, grp);
{ { // Mipmapping
auto p = obs_properties_add_list(grp, S_MIPGENERATOR, D_TRANSLATE(S_MIPGENERATOR), OBS_COMBO_TYPE_LIST, auto p = obs_properties_add_bool(grp, ST_MIPMAPPING, D_TRANSLATE(ST_MIPMAPPING));
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);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(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; return pr;

View file

@ -36,8 +36,6 @@ namespace streamfx::filter::transform {
// Mip-mapping // Mip-mapping
bool _mipmap_enabled; bool _mipmap_enabled;
bool _mipmap_rendered; bool _mipmap_rendered;
double_t _mipmap_strength;
gs::mipmapper::generator _mipmap_generator;
gs::mipmapper _mipmapper; gs::mipmapper _mipmapper;
std::shared_ptr<gs::texture> _mipmap_texture; std::shared_ptr<gs::texture> _mipmap_texture;

View file

@ -22,46 +22,18 @@
#include "obs/gs/gs-helper.hpp" #include "obs/gs/gs-helper.hpp"
#include "plugin.hpp" #include "plugin.hpp"
#if defined(WIN32) || defined(WIN64) #ifdef _WIN32
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 5039) #pragma warning(disable : 4201 4365 5039)
#endif #endif
#include <Windows.h> #include <Windows.h>
#ifdef _MSC_VER #include <atlutil.h>
#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 <d3d11.h> #include <d3d11.h>
#include <dxgi.h> #include <dxgi.h>
#include <util/windows/ComPtr.hpp>
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(pop) #pragma warning(pop)
#endif #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<IDXGIFactory1> factory;
ComPtr<IDXGIAdapter1> adapter;
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
// No other fields required.
};
#endif #endif
gs::mipmapper::~mipmapper() gs::mipmapper::~mipmapper()
@ -73,59 +45,47 @@ gs::mipmapper::~mipmapper()
gs::mipmapper::mipmapper() gs::mipmapper::mipmapper()
{ {
_vb = std::make_unique<gs::vertex_buffer>(uint32_t(6u), std::uint8_t(1u)); _vb = std::make_unique<gs::vertex_buffer>(uint32_t(3u), 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;
auto v1 = _vb->at(1); {
auto v4 = _vb->at(4); auto vtx = _vb->at(0);
v4.position->x = v1.position->x = 1.0; vtx.position->x = 0;
v4.position->y = v1.position->y = 0; vtx.position->y = 0;
v4.uv[0]->x = v1.uv[0]->x = 1.0; vtx.uv[0]->x = 0;
v4.uv[0]->y = v1.uv[0]->y = 0; vtx.uv[0]->y = 0;
}
auto v2 = _vb->at(2); {
auto v3 = _vb->at(3); auto vtx = _vb->at(1);
v3.position->x = v2.position->x = 0; vtx.position->x = 0.;
v3.position->y = v2.position->y = 1.0; vtx.position->y = 2.;
v3.uv[0]->x = v2.uv[0]->x = 0; vtx.uv[0]->x = 0.;
v3.uv[0]->y = v2.uv[0]->y = 1.0; vtx.uv[0]->y = 2.;
}
auto v5 = _vb->at(5); {
v5.position->x = 1.0; auto vtx = _vb->at(2);
v5.position->y = 1.0; vtx.position->x = 2.;
v5.uv[0]->x = 1.0; vtx.position->y = 0.;
v5.uv[0]->y = 1.0; vtx.uv[0]->x = 2.;
vtx.uv[0]->y = 0.;
}
_vb->update(); _vb->update();
char* effect_file = obs_module_file("effects/mipgen.effect"); {
_effect = gs::effect::create(effect_file); char* path = obs_module_file("effects/mipgen.effect");
bfree(effect_file); _effect = gs::effect::create(path);
bfree(path);
}
} }
void gs::mipmapper::rebuild(std::shared_ptr<gs::texture> source, std::shared_ptr<gs::texture> target, void gs::mipmapper::rebuild(std::shared_ptr<gs::texture> source, std::shared_ptr<gs::texture> target)
gs::mipmapper::generator generator = gs::mipmapper::generator::Linear,
float_t strength = 1.0)
{ {
// 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 (!_vb || !_effect)
if (!source) { return; // Do nothing if the necessary data failed to load.
#ifdef _DEBUG
assert(!source);
#endif
return;
}
if (!target) {
#ifdef _DEBUG
assert(!target);
#endif
return;
}
// Ensure texture sizes match // Ensure texture sizes match
if ((source->get_width() != target->get_width()) || (source->get_height() != target->get_height())) { if ((source->get_width() != target->get_width()) || (source->get_height() != target->get_height())) {
@ -141,132 +101,126 @@ void gs::mipmapper::rebuild(std::shared_ptr<gs::texture> source, std::shared_ptr
if ((source->get_color_format() != target->get_color_format())) { if ((source->get_color_format() != target->get_color_format())) {
throw std::invalid_argument("source and target must have same format"); throw std::invalid_argument("source and target must have same format");
} }
}
// Get a unique lock on the graphics context.
auto gctx = gs::context(); auto gctx = gs::context();
// Copy original texture // Do we need to recreate the render target for a different format?
//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.
if ((!_rt) || (source->get_color_format() != _rt->get_color_format())) { if ((!_rt) || (source->get_color_format() != _rt->get_color_format())) {
_rt = std::make_unique<gs::rendertarget>(source->get_color_format(), GS_ZS_NONE); _rt = std::make_unique<gs::rendertarget>(source->get_color_format(), GS_ZS_NONE);
} }
// Render // Grab API related information.
#if defined(WIN32) || defined(WIN64) #ifdef _WIN32
graphics_t* ctx = gs_get_context(); ID3D11Device* d3d_device = nullptr;
gs_d3d11_device* dev = reinterpret_cast<gs_d3d11_device*>(ctx->device); 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<ID3D11Resource*>(gs_texture_get_obj(source->get_object()));
d3d_target = reinterpret_cast<ID3D11Resource*>(gs_texture_get_obj(target->get_object()));
d3d_device = reinterpret_cast<ID3D11Device*>(gs_get_device_obj());
d3d_device->GetImmediateContext(&d3d_context);
}
#endif #endif
int device_type = gs_get_device_type(); if (gs_get_device_type() == GS_DEVICE_OPENGL) {
std::string technique = "Draw"; // FixMe! Implement OpenGL
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;
} }
gs_load_vertexbuffer(_vb->update()); // Use different methods for different types of textures.
gs_load_indexbuffer(nullptr);
if (source->get_type() == gs::texture::type::Normal) { if (source->get_type() == gs::texture::type::Normal) {
std::size_t texture_width = source->get_width(); while (true) {
std::size_t texture_height = source->get_height(); uint32_t width = source->get_width();
float_t texel_width = 1.0f / texture_width; uint32_t height = source->get_height();
float_t texel_height = 1.0f / texture_height; size_t max_mip_level = 1;
std::size_t mip_levels = 1;
#if defined(WIN32) || defined(WIN64) {
ID3D11Texture2D* target_t2 = nullptr; auto cctr = gs::debug_marker(gs::debug_color_azure_radiance, "Mip Level %lld", 0);
ID3D11Texture2D* source_t2 = nullptr; #ifdef _WIN32
if (device_type == GS_DEVICE_DIRECT3D_11) { if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) {
// We definitely have a Direct3D11 resource. { // Retrieve maximum mip map level.
D3D11_TEXTURE2D_DESC target_t2desc; D3D11_TEXTURE2D_DESC td;
target_t2 = reinterpret_cast<ID3D11Texture2D*>(gs_texture_get_obj(target->get_object())); reinterpret_cast<ID3D11Texture2D*>(d3d_target)->GetDesc(&td);
source_t2 = reinterpret_cast<ID3D11Texture2D*>(gs_texture_get_obj(source->get_object())); max_mip_level = td.MipLevels;
target_t2->GetDesc(&target_t2desc); }
dev->context->CopySubresourceRegion(target_t2, 0, 0, 0, 0, source_t2, 0, nullptr);
mip_levels = target_t2desc.MipLevels; // Copy mip level 0 across textures.
d3d_context->CopySubresourceRegion(d3d_target, 0, 0, 0, 0, d3d_source, 0, nullptr);
} }
#endif #endif
if (device_type == GS_DEVICE_OPENGL) { if (gs_get_device_type() == GS_DEVICE_OPENGL) {
// This is an OpenGL resource. // FixMe! Implement OpenGL
}
} }
// If we do not have any miplevels, just stop now. // Do we even need to do anything here?
if (mip_levels == 1) { if (max_mip_level == 1)
return; break;
}
for (std::size_t mip = 1; mip < mip_levels; mip++) { // Render each mip map level.
texture_width /= 2; for (size_t mip = 1; mip < max_mip_level; mip++) {
texture_height /= 2; auto cctr = gs::debug_marker(gs::debug_color_azure_radiance, "Mip Level %lld", mip);
if (texture_width == 0) {
texture_width = 1;
}
if (texture_height == 0) {
texture_height = 1;
}
texel_width = 1.0f / texture_width; uint32_t cwidth = std::max<uint32_t>(width >> mip, 1);
texel_height = 1.0f / texture_height; uint32_t cheight = std::max<uint32_t>(height >> mip, 1);
float_t iwidth = 1. / static_cast<float_t>(cwidth);
float_t iheight = 1. / static_cast<float_t>(cheight);
// Draw mipmap layer // Set up rendering state.
try { gs_load_vertexbuffer(_vb->update(false));
auto op = _rt->render(uint32_t(texture_width), uint32_t(texture_height)); gs_load_indexbuffer(nullptr);
gs_blend_state_push();
gs_set_cull_mode(GS_NEITHER);
gs_reset_blend_state(); gs_reset_blend_state();
gs_blend_function_separate(gs_blend_type::GS_BLEND_ONE, gs_blend_type::GS_BLEND_ZERO, gs_enable_blending(false);
gs_blend_type::GS_BLEND_ONE, gs_blend_type::GS_BLEND_ZERO); gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
gs_enable_color(true, true, true, true);
gs_enable_depth_test(false); gs_enable_depth_test(false);
gs_enable_stencil_test(false); gs_enable_stencil_test(false);
gs_enable_stencil_write(false); gs_enable_stencil_write(false);
gs_enable_color(true, true, true, true); gs_set_cull_mode(GS_NEITHER);
gs_ortho(0, 1, 0, 1, -1, 1); 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 black = {1., 1., 1., 1};
vec4_zero(&black);
gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0);
_effect.get_parameter("image").set_texture(target); _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)); _effect.get_parameter("level").set_int(int32_t(mip - 1));
_effect.get_parameter("imageTexel").set_float2(texel_width, texel_height); while (gs_effect_loop(_effect.get_object(), "Draw")) {
_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()); 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<ID3D11Texture2D*>(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);
}
#endif
}
} }
// Clean up rendering state.
gs_load_indexbuffer(nullptr); gs_load_indexbuffer(nullptr);
gs_load_vertexbuffer(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<ID3D11Texture2D*>(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
}
}
break;
}
} else {
throw std::runtime_error("Texture type is not supported by mipmapping yet.");
}
} }

View file

@ -24,27 +24,28 @@
#include "gs-texture.hpp" #include "gs-texture.hpp"
#include "gs-vertexbuffer.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 { namespace gs {
class mipmapper { class mipmapper {
std::unique_ptr<gs::vertex_buffer> _vb; std::unique_ptr<gs::vertex_buffer> _vb;
std::unique_ptr<gs::rendertarget> _rt; std::unique_ptr<gs::rendertarget> _rt;
gs::effect _effect; gs::effect _effect;
public:
enum class generator : std::uint8_t {
Point,
Linear,
Sharpen,
Smoothen,
Bicubic,
Lanczos,
};
public: public:
~mipmapper(); ~mipmapper();
mipmapper(); mipmapper();
void rebuild(std::shared_ptr<gs::texture> source, std::shared_ptr<gs::texture> target, void rebuild(std::shared_ptr<gs::texture> source, std::shared_ptr<gs::texture> target);
gs::mipmapper::generator generator, float_t strength);
}; };
} // namespace gs } // namespace gs

View file

@ -72,15 +72,6 @@
#define S_BLUR_SUBTYPE_ROTATIONAL "Blur.Subtype.Rotational" #define S_BLUR_SUBTYPE_ROTATIONAL "Blur.Subtype.Rotational"
#define S_BLUR_SUBTYPE_ZOOM "Blur.Subtype.Zoom" #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_RED "Channel.Red"
#define S_CHANNEL_GREEN "Channel.Green" #define S_CHANNEL_GREEN "Channel.Green"
#define S_CHANNEL_BLUE "Channel.Blue" #define S_CHANNEL_BLUE "Channel.Blue"