388 lines
13 KiB
C++
388 lines
13 KiB
C++
// AUTOGENERATED COPYRIGHT HEADER START
|
|
// Copyright (C) 2017-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
|
|
// Copyright (C) 2022 lainon <GermanAizek@yandex.ru>
|
|
// AUTOGENERATED COPYRIGHT HEADER END
|
|
|
|
#include "gfx-mipmapper.hpp"
|
|
#include "obs/gs/gs-helper.hpp"
|
|
#include "plugin.hpp"
|
|
|
|
#include "warning-disable.hpp"
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
// Direct3D 11
|
|
#ifdef _WIN32
|
|
#include <Windows.h>
|
|
#include <atlutil.h>
|
|
#include <d3d11.h>
|
|
#include <dxgi.h>
|
|
#endif
|
|
// OpenGL
|
|
#include "glad/gl.h"
|
|
#include "warning-enable.hpp"
|
|
|
|
#ifdef _WIN32
|
|
struct d3d_info {
|
|
ID3D11Device* device = nullptr;
|
|
ID3D11DeviceContext* context = nullptr;
|
|
ID3D11Resource* target = nullptr;
|
|
};
|
|
|
|
static void d3d_initialize(d3d_info& info, std::shared_ptr<streamfx::obs::gs::texture> source, std::shared_ptr<streamfx::obs::gs::texture> target)
|
|
{
|
|
info.target = reinterpret_cast<ID3D11Resource*>(gs_texture_get_obj(*target));
|
|
info.device = reinterpret_cast<ID3D11Device*>(gs_get_device_obj());
|
|
info.device->GetImmediateContext(&info.context);
|
|
}
|
|
|
|
static void d3d_copy_subregion(d3d_info& info, gs_texture_t* source, uint32_t mip_level, uint32_t width, uint32_t height)
|
|
{
|
|
D3D11_BOX box = {0, 0, 0, width, height, 1};
|
|
auto source_ref = reinterpret_cast<ID3D11Resource*>(gs_texture_get_obj(source));
|
|
info.context->CopySubresourceRegion(info.target, mip_level, 0, 0, 0, source_ref, 0, &box);
|
|
}
|
|
|
|
#endif
|
|
|
|
struct opengl_info {
|
|
GLuint target = 0;
|
|
GLuint fbo = 0;
|
|
};
|
|
|
|
static std::string opengl_translate_error(GLenum error)
|
|
{
|
|
#define TRANSLATE_CASE(X) \
|
|
case X: \
|
|
return #X;
|
|
|
|
switch (error) {
|
|
TRANSLATE_CASE(GL_NO_ERROR) break;
|
|
TRANSLATE_CASE(GL_INVALID_ENUM) break;
|
|
TRANSLATE_CASE(GL_INVALID_VALUE) break;
|
|
TRANSLATE_CASE(GL_INVALID_OPERATION) break;
|
|
TRANSLATE_CASE(GL_STACK_OVERFLOW) break;
|
|
TRANSLATE_CASE(GL_STACK_UNDERFLOW) break;
|
|
TRANSLATE_CASE(GL_OUT_OF_MEMORY) break;
|
|
TRANSLATE_CASE(GL_INVALID_FRAMEBUFFER_OPERATION) break;
|
|
}
|
|
|
|
return std::to_string(error);
|
|
#undef TRANSLATE_CASE
|
|
}
|
|
|
|
static std::string opengl_translate_framebuffer_status(GLenum error)
|
|
{
|
|
#define TRANSLATE_CASE(X) \
|
|
case X: \
|
|
return #X;
|
|
|
|
switch (error) {
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_COMPLETE) break;
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_UNDEFINED) break;
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) break;
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) break;
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER) break;
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER) break;
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_UNSUPPORTED) break;
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE) break;
|
|
TRANSLATE_CASE(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS) break;
|
|
}
|
|
|
|
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()); \
|
|
}
|
|
|
|
static void opengl_initialize(opengl_info& info, std::shared_ptr<streamfx::obs::gs::texture> source, std::shared_ptr<streamfx::obs::gs::texture> target)
|
|
{
|
|
info.target = *reinterpret_cast<GLuint*>(gs_texture_get_obj(*target));
|
|
|
|
glGenFramebuffers(1, &info.fbo);
|
|
}
|
|
|
|
static void opengl_finalize(opengl_info& info)
|
|
{
|
|
glDeleteFramebuffers(1, &info.fbo);
|
|
}
|
|
|
|
static void opengl_copy_subregion(opengl_info& info, gs_texture_t* source, uint32_t mip_level, uint32_t width, uint32_t height)
|
|
{
|
|
GLuint source_ref = *reinterpret_cast<GLuint*>(gs_texture_get_obj(source));
|
|
|
|
// Source -> Texture Unit 0, Read Color Framebuffer
|
|
glActiveTexture(GL_TEXTURE0);
|
|
D_OPENGL_CHECK_ERROR("glActiveTexture(GL_TEXTURE0);");
|
|
glBindTexture(GL_TEXTURE_2D, source_ref);
|
|
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_ref,
|
|
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, static_cast<GLint>(mip_level), 0, 0, 0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(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::gfx::mipmapper::~mipmapper()
|
|
{
|
|
_effect.reset();
|
|
}
|
|
|
|
streamfx::gfx::mipmapper::mipmapper() : _gfx_util(::streamfx::gfx::util::get())
|
|
{
|
|
auto gctx = streamfx::obs::gs::context();
|
|
|
|
{
|
|
auto file = streamfx::data_file_path("effects/mipgen.effect");
|
|
try {
|
|
_effect = streamfx::obs::gs::effect::create(file);
|
|
} catch (const std::exception& ex) {
|
|
DLOG_ERROR("Error loading '%s': %s", file.generic_u8string().c_str(), ex.what());
|
|
throw std::runtime_error("Unexpected error loading required effect file.");
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t streamfx::gfx::mipmapper::calculate_max_mip_level(uint32_t width, uint32_t height)
|
|
{
|
|
return static_cast<uint32_t>(1 + std::lroundl(floor(log2(std::max<GLint>(static_cast<GLint>(width), static_cast<GLint>(height))))));
|
|
}
|
|
|
|
void streamfx::gfx::mipmapper::rebuild(std::shared_ptr<streamfx::obs::gs::texture> source, std::shared_ptr<streamfx::obs::gs::texture> target, mip_generator generator)
|
|
{
|
|
{ // Validate arguments and structure.
|
|
if (!source || !target)
|
|
return; // Do nothing if source or target are missing.
|
|
|
|
if (!_effect)
|
|
return; // Do nothing if the necessary data failed to load.
|
|
|
|
// Ensure texture sizes match
|
|
if ((source->width() != target->width()) || (source->height() != target->height())) {
|
|
throw std::invalid_argument("source and target must have same size");
|
|
}
|
|
|
|
// Ensure texture formats match
|
|
if ((source->color_format() != target->color_format())) {
|
|
throw std::invalid_argument("source and target must have same format");
|
|
}
|
|
}
|
|
|
|
std::string technique = "Linear";
|
|
bool separable_technique = false;
|
|
switch (generator) {
|
|
case mip_generator::MAGIC_KERNEL_SHARP_PLUS_2021:
|
|
separable_technique = true;
|
|
technique = "MagicKernelSharp2021Separable";
|
|
break;
|
|
case mip_generator::MAGIC_KERNEL_2011:
|
|
separable_technique = false;
|
|
technique = "MagicKernel2011";
|
|
break;
|
|
case mip_generator::LINEAR:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Get a unique lock on the graphics context.
|
|
auto gctx = streamfx::obs::gs::context();
|
|
|
|
// Initialize some render targets, and their texture storage.
|
|
auto rt0 = streamfx::obs::gs::texrender::pool::instance()->acquire(source->color_format(), GS_ZS_NONE);
|
|
auto rt1 = streamfx::obs::gs::texrender::pool::instance()->acquire(source->color_format(), GS_ZS_NONE);
|
|
auto rt2 = rt1;
|
|
if (separable_technique) {
|
|
rt2 = streamfx::obs::gs::texrender::pool::instance()->acquire(source->color_format(), GS_ZS_NONE);
|
|
}
|
|
|
|
// Initialize API Handlers.
|
|
opengl_info oglinfo;
|
|
if (gs_get_device_type() == GS_DEVICE_OPENGL) {
|
|
opengl_initialize(oglinfo, source, target);
|
|
}
|
|
#ifdef _WIN32
|
|
d3d_info d3dinfo;
|
|
if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) {
|
|
d3d_initialize(d3dinfo, source, target);
|
|
}
|
|
#endif
|
|
|
|
// Use different methods for different types of textures.
|
|
uint32_t width = source->width();
|
|
uint32_t height = source->height();
|
|
size_t max_mip_level = calculate_max_mip_level(width, height);
|
|
|
|
{ // Copy mip level 0 to output.
|
|
#ifdef _WIN32
|
|
if (gs_get_device_type() == GS_DEVICE_DIRECT3D_11) {
|
|
d3d_copy_subregion(d3dinfo, *source, 0, width, height);
|
|
}
|
|
#endif
|
|
if (gs_get_device_type() == GS_DEVICE_OPENGL) {
|
|
opengl_copy_subregion(oglinfo, *source, 0, width, height);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
// sRGB support.
|
|
bool was_srgb_enabled = gs_framebuffer_srgb_enabled();
|
|
bool enable_srgb = gs_get_linear_srgb();
|
|
gs_enable_framebuffer_srgb(enable_srgb);
|
|
|
|
// Render each mip level.
|
|
for (size_t mip = 1; mip < max_mip_level; mip++) {
|
|
uint32_t cwidth = std::max<uint32_t>(width >> mip, 1);
|
|
uint32_t cheight = std::max<uint32_t>(height >> mip, 1);
|
|
float iwidth = 1.f / static_cast<float>(cwidth);
|
|
float iheight = 1.f / static_cast<float>(cheight);
|
|
|
|
if (!separable_technique) {
|
|
try {
|
|
auto op = rt0->render(cwidth, cheight);
|
|
gs_ortho(0, 1, 0, 1, 0, 1);
|
|
|
|
if (mip == 1) {
|
|
_effect.get_parameter("image").set_texture(source, enable_srgb);
|
|
} else {
|
|
_effect.get_parameter("image").set_texture(rt1->get_object(), enable_srgb);
|
|
}
|
|
_effect.get_parameter("imageTexel").set_float2(iwidth, iheight);
|
|
_effect.get_parameter("separableTexel").set_float2(iwidth, iheight);
|
|
while (gs_effect_loop(_effect.get_object(), technique.c_str())) {
|
|
_gfx_util->draw_fullscreen_triangle();
|
|
}
|
|
} catch (...) {
|
|
}
|
|
} else {
|
|
for (size_t pass = 0; gs_effect_loop(_effect.get_object(), technique.c_str()); pass++) {
|
|
// Flow:
|
|
// Mip-Level 1:
|
|
// - Pass 0:
|
|
// - Swap rt0, rt2
|
|
// - Read from source
|
|
// - Write to rt0
|
|
// - Pass 1:
|
|
// - Swap rt0, rt2
|
|
// - Read from rt2
|
|
// - Write to rt0
|
|
// - Pass 2:
|
|
// - Swap rt0, rt2
|
|
// - Read from rt2
|
|
// - Write to rt0
|
|
// - Copy rt0 to target
|
|
// - Swap rt0, rt1
|
|
//
|
|
// Mip-Level 2:
|
|
// - Pass 0:
|
|
// - Swap rt0, rt2
|
|
// - Read from rt1
|
|
// - Write to rt0
|
|
// - (identical to Mip-Level 1)
|
|
//
|
|
// This works only if we swap {rt0, rt2} first, otherwise we end up only storing the data from the previous pass.
|
|
|
|
// Swap rt0, rt2.
|
|
std::swap(rt0, rt2);
|
|
|
|
if (mip == 1) {
|
|
_effect.get_parameter("image").set_texture(source, enable_srgb);
|
|
} else if (pass == 0) {
|
|
_effect.get_parameter("image").set_texture(rt1->get_object(), enable_srgb);
|
|
} else {
|
|
_effect.get_parameter("image").set_texture(rt2->get_object(), enable_srgb);
|
|
}
|
|
_effect.get_parameter("imageTexel").set_float2(iwidth, iheight);
|
|
if ((pass % 2) == 0) {
|
|
_effect.get_parameter("separableTexel").set_float2(iwidth, 0.);
|
|
} else {
|
|
_effect.get_parameter("separableTexel").set_float2(0., iheight);
|
|
}
|
|
|
|
try {
|
|
auto op = rt0->render(cwidth, cheight);
|
|
gs_ortho(0, 1, 0, 1, 0, 1);
|
|
_gfx_util->draw_fullscreen_triangle();
|
|
} catch (...) {
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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, rt0->get_object(), static_cast<uint32_t>(mip), cwidth, cheight);
|
|
}
|
|
#endif
|
|
if (gs_get_device_type() == GS_DEVICE_OPENGL) {
|
|
opengl_copy_subregion(oglinfo, rt0->get_object(), static_cast<uint32_t>(mip), cwidth, cheight);
|
|
}
|
|
|
|
// Swap the render targets.
|
|
std::swap(rt0, rt1);
|
|
}
|
|
|
|
// Clean up rendering state.
|
|
gs_enable_framebuffer_srgb(was_srgb_enabled);
|
|
gs_blend_state_pop();
|
|
|
|
// 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
|
|
}
|