obs/gs/effect: Preprocess shaders to improve platform compatibility

Improves cross-platform compatibility of Shaders written for StreamFX through the use of preprocessing to make things a bit more compatible. While we don't perform any proper parsing, this will be able to prevent basic issues.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2021-11-07 14:37:05 +01:00
parent 2ccbd76c02
commit 29bbe22bec
14 changed files with 223 additions and 108 deletions

View file

@ -153,11 +153,11 @@ blur_instance::blur_instance(obs_data_t* settings, obs_source_t* self)
// Load Effects // Load Effects
{ {
auto file = streamfx::data_file_path("effects/mask.effect").string(); auto file = streamfx::data_file_path("effects/mask.effect");
try { try {
_effect_mask = streamfx::obs::gs::effect::create(file); _effect_mask = streamfx::obs::gs::effect::create(file);
} catch (std::runtime_error& ex) { } catch (std::exception& ex) {
DLOG_ERROR("<filter-blur> Loading effect '%s' failed with error(s): %s", file.c_str(), ex.what()); DLOG_ERROR("Error loading '%s': %s", file.generic_u8string().c_str(), ex.what());
} }
} }
} }

View file

@ -137,38 +137,39 @@ color_grade_instance::color_grade_instance(obs_data_t* data, obs_source_t* self)
_lut_initialized(false), _lut_dirty(true), _lut_producer(), _lut_consumer() _lut_initialized(false), _lut_dirty(true), _lut_producer(), _lut_consumer()
{ {
// Load the color grading effect. {
auto path = streamfx::data_file_path("effects/color-grade.effect"); auto gctx = streamfx::obs::gs::context();
if (!std::filesystem::exists(path)) {
D_LOG_ERROR("Failed to locate effect file '%s'.", path.u8string().c_str()); // Load the color grading effect.
throw std::runtime_error("Failed to load color grade effect."); {
} else { auto file = streamfx::data_file_path("effects/color-grade.effect");
try {
_effect = streamfx::obs::gs::effect::create(file);
} catch (std::exception& ex) {
D_LOG_ERROR("Error loading '%s': %s", file.u8string().c_str(), ex.what());
throw;
}
}
// Initialize LUT work flow.
try { try {
_effect = streamfx::obs::gs::effect::create(path.u8string()); _lut_producer = std::make_shared<streamfx::gfx::lut::producer>();
_lut_consumer = std::make_shared<streamfx::gfx::lut::consumer>();
_lut_initialized = true;
} catch (std::exception const& ex) { } catch (std::exception const& ex) {
D_LOG_ERROR("Failed to load effect '%s': %s", path.u8string().c_str(), ex.what()); D_LOG_WARNING("Failed to initialize LUT rendering, falling back to direct rendering.\n%s", ex.what());
_lut_initialized = false;
}
// Allocate render target for rendering.
try {
allocate_rendertarget(GS_RGBA);
} catch (std::exception const& ex) {
D_LOG_ERROR("Failed to acquire render target for rendering: %s", ex.what());
throw; throw;
} }
} }
// Initialize LUT work flow.
try {
_lut_producer = std::make_shared<streamfx::gfx::lut::producer>();
_lut_consumer = std::make_shared<streamfx::gfx::lut::consumer>();
_lut_initialized = true;
} catch (std::exception const& ex) {
D_LOG_WARNING("Failed to initialize LUT rendering, falling back to direct rendering.\n%s", ex.what());
_lut_initialized = false;
}
// Allocate render target for rendering.
try {
allocate_rendertarget(GS_RGBA);
} catch (std::exception const& ex) {
D_LOG_ERROR("Failed to acquire render target for rendering: %s", ex.what());
throw;
}
update(data); update(data);
} }

View file

@ -51,7 +51,19 @@ using namespace streamfx::filter::displacement;
displacement_instance::displacement_instance(obs_data_t* data, obs_source_t* context) displacement_instance::displacement_instance(obs_data_t* data, obs_source_t* context)
: obs::source_instance(data, context) : obs::source_instance(data, context)
{ {
_effect = streamfx::obs::gs::effect::create(streamfx::data_file_path("effects/displace.effect").u8string()); {
auto gctx = streamfx::obs::gs::context();
{
auto file = streamfx::data_file_path("effects/displace.effect");
try {
_effect = streamfx::obs::gs::effect::create(file);
} catch (std::exception& ex) {
D_LOG_ERROR("Error loading '%s': %s", file.u8string().c_str(), ex.what());
throw;
}
}
}
update(data); update(data);
} }

View file

@ -77,13 +77,21 @@ dynamic_mask_instance::dynamic_mask_instance(obs_data_t* settings, obs_source_t*
_filter_texture(), _have_input_texture(false), _input(), _input_capture(), _input_texture(), _filter_texture(), _have_input_texture(false), _input(), _input_capture(), _input_texture(),
_have_final_texture(false), _final_rt(), _final_texture(), _channels(), _precalc() _have_final_texture(false), _final_rt(), _final_texture(), _channels(), _precalc()
{ {
_filter_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE); {
_final_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE); auto gctx = streamfx::obs::gs::context();
try { _filter_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
_effect = streamfx::obs::gs::effect::create(streamfx::data_file_path("effects/channel-mask.effect").u8string()); _final_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
} catch (const std::exception& ex) {
DLOG_ERROR("Loading channel mask effect failed with error(s):\n%s", ex.what()); {
auto file = streamfx::data_file_path("effects/channel-mask.effect");
try {
_effect = streamfx::obs::gs::effect::create(file);
} catch (std::exception& ex) {
D_LOG_ERROR("Error loading '%s': %s", file.u8string().c_str(), ex.what());
throw;
}
}
} }
update(settings); update(settings);

View file

@ -145,12 +145,12 @@ sdf_effects_instance::sdf_effects_instance(obs_data_t* settings, obs_source_t* s
{"effects/sdf/sdf-consumer.effect", _sdf_consumer_effect}, {"effects/sdf/sdf-consumer.effect", _sdf_consumer_effect},
}; };
for (auto& kv : load_arr) { for (auto& kv : load_arr) {
auto path = streamfx::data_file_path(kv.first).u8string(); auto file = streamfx::data_file_path(kv.first);
try { try {
kv.second = streamfx::obs::gs::effect::create(path); kv.second = streamfx::obs::gs::effect::create(file);
} catch (const std::exception& ex) { } catch (std::exception& ex) {
D_LOG_ERROR("Failed to load effect '%s' (located at '%s') with error(s): %s", kv.first, path.c_str(), D_LOG_ERROR("Error loading '%s': %s", file.u8string().c_str(), ex.what());
ex.what()); throw;
} }
} }
} }

View file

@ -114,42 +114,46 @@ transform_instance::transform_instance(obs_data_t* data, obs_source_t* context)
_sampler(), _params(), _corners(), _cache_rendered(), _mipmap_enabled(), _source_rendered(), _source_size(), _sampler(), _params(), _corners(), _cache_rendered(), _mipmap_enabled(), _source_rendered(), _source_size(),
_update_mesh(true) _update_mesh(true)
{ {
_cache_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
_source_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
_vertex_buffer = std::make_shared<streamfx::obs::gs::vertex_buffer>(uint32_t(4u), uint8_t(1u));
{ {
auto file = streamfx::data_file_path("effects/standard.effect"); auto gctx = obs::gs::context();
try {
_standard_effect = streamfx::obs::gs::effect::create(file.generic_u8string());
} catch (const std::exception& ex) {
DLOG_ERROR("Error loading '%s' from disk: %s", file.generic_u8string().c_str(), ex.what());
}
}
{
auto file = streamfx::data_file_path("effects/transform.effect");
try {
_transform_effect = streamfx::obs::gs::effect::create(file.generic_u8string());
} catch (const std::exception& ex) {
DLOG_ERROR("Error loading '%s' from disk: %s", file.generic_u8string().c_str(), ex.what());
}
}
{
_sampler.set_address_mode_u(GS_ADDRESS_CLAMP);
_sampler.set_address_mode_v(GS_ADDRESS_CLAMP);
_sampler.set_address_mode_w(GS_ADDRESS_CLAMP);
_sampler.set_filter(GS_FILTER_LINEAR);
_sampler.set_max_anisotropy(8);
}
vec3_set(&_params.position, 0, 0, 0); _cache_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
vec3_set(&_params.rotation, 0, 0, 0); _source_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
vec3_set(&_params.scale, 1, 1, 1); _vertex_buffer = std::make_shared<streamfx::obs::gs::vertex_buffer>(uint32_t(4u), uint8_t(1u));
vec3_set(&_params.shear, 0, 0, 0); {
auto file = streamfx::data_file_path("effects/standard.effect");
try {
_standard_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());
}
}
{
auto file = streamfx::data_file_path("effects/transform.effect");
try {
_transform_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());
}
}
{
_sampler.set_address_mode_u(GS_ADDRESS_CLAMP);
_sampler.set_address_mode_v(GS_ADDRESS_CLAMP);
_sampler.set_address_mode_w(GS_ADDRESS_CLAMP);
_sampler.set_filter(GS_FILTER_LINEAR);
_sampler.set_max_anisotropy(8);
}
vec2_set(&_corners.tl, 0, 0); vec3_set(&_params.position, 0, 0, 0);
vec2_set(&_corners.tr, 1, 0); vec3_set(&_params.rotation, 0, 0, 0);
vec2_set(&_corners.bl, 0, 1); vec3_set(&_params.scale, 1, 1, 1);
vec2_set(&_corners.br, 1, 1); vec3_set(&_params.shear, 0, 0, 0);
vec2_set(&_corners.tl, 0, 0);
vec2_set(&_corners.tr, 1, 0);
vec2_set(&_corners.bl, 0, 1);
vec2_set(&_corners.br, 1, 1);
}
update(data); update(data);
} }

View file

@ -37,11 +37,13 @@
streamfx::gfx::blur::box_linear_data::box_linear_data() streamfx::gfx::blur::box_linear_data::box_linear_data()
{ {
auto gctx = streamfx::obs::gs::context(); auto gctx = streamfx::obs::gs::context();
try { {
_effect = auto file = streamfx::data_file_path("effects/blur/box-linear.effect");
streamfx::obs::gs::effect::create(streamfx::data_file_path("effects/blur/box-linear.effect").u8string()); try {
} catch (...) { _effect = streamfx::obs::gs::effect::create(file);
DLOG_ERROR("<gfx::blur::box_linear> Failed to load _effect."); } catch (const std::exception& ex) {
DLOG_ERROR("Error loading '%s': %s", file.generic_u8string().c_str(), ex.what());
}
} }
} }

View file

@ -37,10 +37,13 @@
streamfx::gfx::blur::box_data::box_data() streamfx::gfx::blur::box_data::box_data()
{ {
auto gctx = streamfx::obs::gs::context(); auto gctx = streamfx::obs::gs::context();
try { {
_effect = streamfx::obs::gs::effect::create(streamfx::data_file_path("effects/blur/box.effect").u8string()); auto file = streamfx::data_file_path("effects/blur/box.effect");
} catch (...) { try {
DLOG_ERROR("<gfx::blur::box> Failed to load _effect."); _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());
}
} }
} }

View file

@ -54,11 +54,13 @@
streamfx::gfx::blur::dual_filtering_data::dual_filtering_data() streamfx::gfx::blur::dual_filtering_data::dual_filtering_data()
{ {
auto gctx = streamfx::obs::gs::context(); auto gctx = streamfx::obs::gs::context();
try { {
_effect = streamfx::obs::gs::effect::create( auto file = streamfx::data_file_path("effects/blur/dual-filtering.effect");
streamfx::data_file_path("effects/blur/dual-filtering.effect").u8string()); try {
} catch (...) { _effect = streamfx::obs::gs::effect::create(file);
DLOG_ERROR("<gfx::blur::box_linear> Failed to load _effect."); } catch (const std::exception& ex) {
DLOG_ERROR("Error loading '%s': %s", file.generic_u8string().c_str(), ex.what());
}
} }
} }

View file

@ -42,9 +42,18 @@
streamfx::gfx::blur::gaussian_linear_data::gaussian_linear_data() streamfx::gfx::blur::gaussian_linear_data::gaussian_linear_data()
{ {
auto gctx = streamfx::obs::gs::context(); {
_effect = auto gctx = streamfx::obs::gs::context();
streamfx::obs::gs::effect::create(streamfx::data_file_path("effects/blur/gaussian-linear.effect").u8string());
{
auto file = streamfx::data_file_path("effects/blur/gaussian-linear.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());
}
}
}
// Precalculate Kernels // Precalculate Kernels
for (std::size_t kernel_size = 1; kernel_size <= ST_MAX_BLUR_SIZE; kernel_size++) { for (std::size_t kernel_size = 1; kernel_size <= ST_MAX_BLUR_SIZE; kernel_size++) {

View file

@ -46,8 +46,15 @@ streamfx::gfx::blur::gaussian_data::gaussian_data()
{ {
auto gctx = streamfx::obs::gs::context(); auto gctx = streamfx::obs::gs::context();
_effect =
streamfx::obs::gs::effect::create(streamfx::data_file_path("effects/blur/gaussian.effect").u8string()); {
auto file = streamfx::data_file_path("effects/blur/gaussian.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());
}
}
} }
//#define ST_USE_PASCAL_TRIANGLE //#define ST_USE_PASCAL_TRIANGLE

View file

@ -19,28 +19,78 @@
#include "gs-effect.hpp" #include "gs-effect.hpp"
#include <fstream> #include <fstream>
#include <sstream>
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
#include "obs/gs/gs-helper.hpp" #include "obs/gs/gs-helper.hpp"
#include "util/util-platform.hpp"
#define MAX_EFFECT_SIZE 32 * 1024 * 1024 #define MAX_EFFECT_SIZE 32 * 1024 * 1024 // 32 MiB, big enough for everything.
static std::string load_file_as_code(std::filesystem::path file) static std::string load_file_as_code(std::filesystem::path shader_file)
{ {
uintmax_t size = std::filesystem::file_size(file); std::stringstream shader_stream;
std::filesystem::path shader_path = std::filesystem::absolute(shader_file);
std::filesystem::path shader_root = shader_path.parent_path();
// Ensure it meets size limits.
uintmax_t size = std::filesystem::file_size(shader_path);
if (size > MAX_EFFECT_SIZE) { if (size > MAX_EFFECT_SIZE) {
throw std::runtime_error("File is too large to be loaded."); throw std::runtime_error("File is too large to be loaded.");
} }
std::ifstream ifs(file, std::ios::binary); // Try to open as-is.
std::ifstream ifs(shader_path, std::ios::in);
if (!ifs.is_open() || ifs.bad()) { if (!ifs.is_open() || ifs.bad()) {
throw std::runtime_error("An unknown error occured trying to open the file."); throw std::runtime_error("Failed to open file.");
} }
std::vector<char> buf(size_t(size + 1), 0); // Push Graphics API to shader.
ifs.read(buf.data(), static_cast<std::streamsize>(size)); switch (gs_get_device_type()) {
case GS_DEVICE_DIRECT3D_11:
shader_stream << "#define GS_DEVICE_DIRECT3D_11" << std::endl;
shader_stream << "#define GS_DEVICE_DIRECT3D" << std::endl;
break;
case GS_DEVICE_OPENGL:
shader_stream << "#define GS_DEVICE_OPENGL" << std::endl;
break;
}
return std::string(buf.data(), buf.data() + size); // Pre-process the shader.
std::string line;
while (std::getline(ifs, line)) {
std::string line_trimmed = line;
{ // Figure out the length of the trim.
size_t trim_length = 0;
for (size_t idx = 0, edx = line_trimmed.length(); idx < edx; idx++) {
char ch = line_trimmed.at(idx);
if ((ch != ' ') && (ch != '\t')) {
trim_length = idx;
break;
}
}
if (trim_length > 0) {
line_trimmed.erase(0, trim_length);
}
}
// Handle '#include'
if (line_trimmed.substr(0, 8) == "#include") {
std::string include_str = line_trimmed.substr(10, line_trimmed.size() - 11); // '#include "'
std::filesystem::path include_path = include_str;
if (!include_path.is_absolute()) {
include_path = shader_root / include_str;
}
line = load_file_as_code(include_path);
}
shader_stream << line << std::endl;
}
return shader_stream.str();
} }
streamfx::obs::gs::effect::effect(const std::string& code, const std::string& name) streamfx::obs::gs::effect::effect(const std::string& code, const std::string& name)
@ -58,7 +108,10 @@ streamfx::obs::gs::effect::effect(const std::string& code, const std::string& na
reset(effect, [](gs_effect_t* ptr) { gs_effect_destroy(ptr); }); reset(effect, [](gs_effect_t* ptr) { gs_effect_destroy(ptr); });
} }
streamfx::obs::gs::effect::effect(std::filesystem::path file) : effect(load_file_as_code(file), file.u8string()) {} streamfx::obs::gs::effect::effect(std::filesystem::path file)
: effect(load_file_as_code(file),
streamfx::util::platform::utf8_to_native(std::filesystem::absolute(file)).generic_u8string())
{}
streamfx::obs::gs::effect::~effect() streamfx::obs::gs::effect::~effect()
{ {

View file

@ -49,14 +49,19 @@ namespace streamfx::obs::gs {
return get(); return get();
} }
static streamfx::obs::gs::effect create(const std::string& file)
{
return streamfx::obs::gs::effect(file);
};
static streamfx::obs::gs::effect create(const std::string& code, const std::string& name) static streamfx::obs::gs::effect create(const std::string& code, const std::string& name)
{ {
return streamfx::obs::gs::effect(code, name); return streamfx::obs::gs::effect(code, name);
}; };
static streamfx::obs::gs::effect create(const std::string& file)
{
return streamfx::obs::gs::effect(std::filesystem::path(file));
};
static streamfx::obs::gs::effect create(const std::filesystem::path& file)
{
return streamfx::obs::gs::effect(file);
};
}; };
} // namespace streamfx::obs::gs } // namespace streamfx::obs::gs

View file

@ -45,6 +45,8 @@ streamfx::obs::gs::mipmapper::~mipmapper()
streamfx::obs::gs::mipmapper::mipmapper() streamfx::obs::gs::mipmapper::mipmapper()
{ {
auto gctx = streamfx::obs::gs::context();
_vb = std::make_unique<streamfx::obs::gs::vertex_buffer>(uint32_t(3u), uint8_t(1u)); _vb = std::make_unique<streamfx::obs::gs::vertex_buffer>(uint32_t(3u), uint8_t(1u));
{ {
@ -71,7 +73,14 @@ streamfx::obs::gs::mipmapper::mipmapper()
_vb->update(); _vb->update();
_effect = streamfx::obs::gs::effect::create(streamfx::data_file_path("effects/mipgen.effect").u8string()); {
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());
}
}
} }
void streamfx::obs::gs::mipmapper::rebuild(std::shared_ptr<streamfx::obs::gs::texture> source, void streamfx::obs::gs::mipmapper::rebuild(std::shared_ptr<streamfx::obs::gs::texture> source,