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
{
auto file = streamfx::data_file_path("effects/mask.effect").string();
auto file = streamfx::data_file_path("effects/mask.effect");
try {
_effect_mask = streamfx::obs::gs::effect::create(file);
} catch (std::runtime_error& ex) {
DLOG_ERROR("<filter-blur> Loading effect '%s' failed with error(s): %s", file.c_str(), ex.what());
} catch (std::exception& ex) {
DLOG_ERROR("Error loading '%s': %s", file.generic_u8string().c_str(), ex.what());
}
}
}

View file

@ -137,16 +137,16 @@ color_grade_instance::color_grade_instance(obs_data_t* data, obs_source_t* self)
_lut_initialized(false), _lut_dirty(true), _lut_producer(), _lut_consumer()
{
{
auto gctx = streamfx::obs::gs::context();
// Load the color grading effect.
auto path = streamfx::data_file_path("effects/color-grade.effect");
if (!std::filesystem::exists(path)) {
D_LOG_ERROR("Failed to locate effect file '%s'.", path.u8string().c_str());
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(path.u8string());
} catch (std::exception const& ex) {
D_LOG_ERROR("Failed to load effect '%s': %s", path.u8string().c_str(), ex.what());
_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;
}
}
@ -168,6 +168,7 @@ color_grade_instance::color_grade_instance(obs_data_t* data, obs_source_t* self)
D_LOG_ERROR("Failed to acquire render target for rendering: %s", ex.what());
throw;
}
}
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)
: 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);
}

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(),
_have_final_texture(false), _final_rt(), _final_texture(), _channels(), _precalc()
{
{
auto gctx = streamfx::obs::gs::context();
_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 file = streamfx::data_file_path("effects/channel-mask.effect");
try {
_effect = streamfx::obs::gs::effect::create(streamfx::data_file_path("effects/channel-mask.effect").u8string());
} catch (const std::exception& ex) {
DLOG_ERROR("Loading channel mask effect failed with error(s):\n%s", ex.what());
_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);

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},
};
for (auto& kv : load_arr) {
auto path = streamfx::data_file_path(kv.first).u8string();
auto file = streamfx::data_file_path(kv.first);
try {
kv.second = streamfx::obs::gs::effect::create(path);
} catch (const std::exception& ex) {
D_LOG_ERROR("Failed to load effect '%s' (located at '%s') with error(s): %s", kv.first, path.c_str(),
ex.what());
kv.second = streamfx::obs::gs::effect::create(file);
} catch (std::exception& ex) {
D_LOG_ERROR("Error loading '%s': %s", file.u8string().c_str(), ex.what());
throw;
}
}
}

View file

@ -114,23 +114,26 @@ transform_instance::transform_instance(obs_data_t* data, obs_source_t* context)
_sampler(), _params(), _corners(), _cache_rendered(), _mipmap_enabled(), _source_rendered(), _source_size(),
_update_mesh(true)
{
{
auto gctx = obs::gs::context();
_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");
try {
_standard_effect = streamfx::obs::gs::effect::create(file.generic_u8string());
_standard_effect = streamfx::obs::gs::effect::create(file);
} catch (const std::exception& ex) {
DLOG_ERROR("Error loading '%s' from disk: %s", file.generic_u8string().c_str(), ex.what());
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.generic_u8string());
_transform_effect = streamfx::obs::gs::effect::create(file);
} catch (const std::exception& ex) {
DLOG_ERROR("Error loading '%s' from disk: %s", file.generic_u8string().c_str(), ex.what());
DLOG_ERROR("Error loading '%s': %s", file.generic_u8string().c_str(), ex.what());
}
}
{
@ -150,6 +153,7 @@ transform_instance::transform_instance(obs_data_t* data, obs_source_t* context)
vec2_set(&_corners.tr, 1, 0);
vec2_set(&_corners.bl, 0, 1);
vec2_set(&_corners.br, 1, 1);
}
update(data);
}

View file

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

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

@ -41,10 +41,19 @@
#define ST_SEARCH_RANGE ST_MAX_KERNEL_SIZE * 2
streamfx::gfx::blur::gaussian_linear_data::gaussian_linear_data()
{
{
auto gctx = streamfx::obs::gs::context();
_effect =
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
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();
_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

View file

@ -19,28 +19,78 @@
#include "gs-effect.hpp"
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <vector>
#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) {
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()) {
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);
ifs.read(buf.data(), static_cast<std::streamsize>(size));
// Push Graphics API to shader.
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)
@ -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); });
}
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()
{

View file

@ -49,14 +49,19 @@ namespace streamfx::obs::gs {
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)
{
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

View file

@ -45,6 +45,8 @@ 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));
{
@ -71,7 +73,14 @@ streamfx::obs::gs::mipmapper::mipmapper()
_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,