obs-StreamFX/source/obs/gs/gs-mipmapper.cpp
Michael Fabian 'Xaymar' Dirks 4947d46aa1 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.
2023-03-28 12:40:40 +02:00

226 lines
6.8 KiB
C++

/*
* Modern effects for a modern Streamer
* Copyright (C) 2017 Michael Fabian Dirks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "gs-mipmapper.hpp"
#include <stdexcept>
#include "obs/gs/gs-helper.hpp"
#include "plugin.hpp"
#ifdef _WIN32
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4201 4365 5039)
#endif
#include <Windows.h>
#include <atlutil.h>
#include <d3d11.h>
#include <dxgi.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif
gs::mipmapper::~mipmapper()
{
_vb.reset();
_rt.reset();
_effect.reset();
}
gs::mipmapper::mipmapper()
{
_vb = std::make_unique<gs::vertex_buffer>(uint32_t(3u), std::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();
{
char* path = obs_module_file("effects/mipgen.effect");
_effect = gs::effect::create(path);
bfree(path);
}
}
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())) {
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();
// 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);
}
// 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
if (gs_get_device_type() == GS_DEVICE_OPENGL) {
// FixMe! Implement OpenGL
}
// Use different methods for different types of textures.
if (source->get_type() == gs::texture::type::Normal) {
while (true) {
uint32_t width = source->get_width();
uint32_t height = source->get_height();
size_t max_mip_level = 1;
{
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 (gs_get_device_type() == GS_DEVICE_OPENGL) {
// FixMe! Implement OpenGL
}
}
// 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++) {
auto cctr = gs::debug_marker(gs::debug_color_azure_radiance, "Mip Level %lld", mip);
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);
// 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, cwidth, 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<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.");
}
}