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 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);
}
}

View File

@ -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::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
_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;
}
@ -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,
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;
}
}
{
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_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<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
@ -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;

View File

@ -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<gs::texture> _mipmap_texture;

View File

@ -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 <Windows.h>
#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 <atlutil.h>
#include <d3d11.h>
#include <dxgi.h>
#include <util/windows/ComPtr.hpp>
#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<IDXGIFactory1> factory;
ComPtr<IDXGIAdapter1> adapter;
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
// No other fields required.
};
#endif
gs::mipmapper::~mipmapper()
@ -73,59 +45,47 @@ gs::mipmapper::~mipmapper()
gs::mipmapper::mipmapper()
{
_vb = std::make_unique<gs::vertex_buffer>(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<gs::vertex_buffer>(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);
}
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.
char* path = obs_module_file("effects/mipgen.effect");
_effect = gs::effect::create(path);
bfree(path);
}
}
// Do nothing if there is no texture given.
if (!source) {
#ifdef _DEBUG
assert(!source);
#endif
return;
}
if (!target) {
#ifdef _DEBUG
assert(!target);
#endif
return;
}
void gs::mipmapper::rebuild(std::shared_ptr<gs::texture> source, std::shared_ptr<gs::texture> target)
{
{ // Validate arguments and structure.
if (!source || !target)
return; // Do nothing if source or target are missing.
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())) {
@ -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())) {
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<gs::rendertarget>(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<gs_d3d11_device*>(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<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
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<ID3D11Texture2D*>(gs_texture_get_obj(target->get_object()));
source_t2 = reinterpret_cast<ID3D11Texture2D*>(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<ID3D11Texture2D*>(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 (gs_get_device_type() == GS_DEVICE_OPENGL) {
// FixMe! Implement OpenGL
}
}
// If we do not have any miplevels, just stop now.
if (mip_levels == 1) {
return;
}
// Do we even need to do anything here?
if (max_mip_level == 1)
break;
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;
}
// 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);
texel_width = 1.0f / texture_width;
texel_height = 1.0f / texture_height;
uint32_t cwidth = std::max<uint32_t>(width >> mip, 1);
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
try {
auto op = _rt->render(uint32_t(texture_width), uint32_t(texture_height));
gs_set_cull_mode(GS_NEITHER);
// 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);
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));
_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())) {
while (gs_effect_loop(_effect.get_object(), "Draw")) {
gs_draw(gs_draw_mode::GS_TRIS, 0, _vb->size());
}
} 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_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-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<gs::vertex_buffer> _vb;
std::unique_ptr<gs::rendertarget> _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<gs::texture> source, std::shared_ptr<gs::texture> target,
gs::mipmapper::generator generator, float_t strength);
void rebuild(std::shared_ptr<gs::texture> source, std::shared_ptr<gs::texture> target);
};
} // namespace gs

View File

@ -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"