Skip to content

Commit

Permalink
Add BlendEquation and BlendFunction support
Browse files Browse the repository at this point in the history
  • Loading branch information
adamkewley committed Aug 18, 2024
1 parent fd06954 commit b361eb1
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src/oscar/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ add_library(oscar STATIC

Graphics/AntiAliasingLevel.cpp
Graphics/AntiAliasingLevel.h
Graphics/BlendEquation.h
Graphics/BlendFunction.h
Graphics/BlitFlags.h
Graphics/Camera.h
Graphics/CameraClearFlags.h
Expand Down
2 changes: 2 additions & 0 deletions src/oscar/Graphics.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <oscar/Graphics/AntiAliasingLevel.h>
#include <oscar/Graphics/BlendEquation.h>
#include <oscar/Graphics/BlendFunction.h>
#include <oscar/Graphics/BlitFlags.h>
#include <oscar/Graphics/Camera.h>
#include <oscar/Graphics/CameraClearFlags.h>
Expand Down
15 changes: 15 additions & 0 deletions src/oscar/Graphics/BlendEquation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <cstdint>

namespace osc
{
enum class BlendEquation : uint8_t {
Add,
Min,
Max,
NUM_OPTIONS,

Default = Add,
};
}
19 changes: 19 additions & 0 deletions src/oscar/Graphics/BlendFunction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <cstdint>

namespace osc
{
enum class BlendFunction : uint8_t {
One,
Zero,
SourceAlpha,
OneMinusSourceAlpha,
NUM_OPTIONS,

// defaults currently assume blending with associated (in contrast to
// premultiplied) alpha via `BlendEquation::Add`
SourceDefault = SourceAlpha,
DestinationDefault = OneMinusSourceAlpha,
};
}
101 changes: 100 additions & 1 deletion src/oscar/Graphics/GraphicsImplementation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,33 @@ namespace
}
}

// blending functions
namespace
{
GLenum to_opengl_blend_func(BlendFunction f)
{
static_assert(num_options<BlendFunction>() == 4);
switch (f) {
case BlendFunction::One: return GL_ONE;
case BlendFunction::Zero: return GL_ZERO;
case BlendFunction::SourceAlpha: return GL_SRC_ALPHA;
case BlendFunction::OneMinusSourceAlpha: return GL_ONE_MINUS_SRC_ALPHA;
default: return GL_ONE;
}
}

GLenum to_opengl_blend_equation(BlendEquation f)
{
static_assert(num_options<BlendEquation>() == 3);
switch (f) {
case BlendEquation::Add: return GL_FUNC_ADD;
case BlendEquation::Min: return GL_MIN;
case BlendEquation::Max: return GL_MAX;
default: return GL_FUNC_ADD;
}
}
}

// material value storage
//
// materials can store a variety of stuff (colors, positions, offsets, textures, etc.). This
Expand Down Expand Up @@ -2977,6 +3004,15 @@ class osc::Material::Impl final {
bool is_transparent() const { return is_transparent_; }
void set_transparent(bool value) { is_transparent_ = value; }

BlendFunction source_blend_function() const { return source_blend_function_; }
void set_source_blend_function(BlendFunction f) { source_blend_function_ = f; }

BlendFunction destination_blend_function() const { return destination_blend_function_; }
void set_destination_blend_function(BlendFunction f) { destination_blend_function_ = f; }

BlendEquation blend_equation() const { return blend_equation_; }
void set_blend_equation(BlendEquation f) { blend_equation_ = f; }

bool is_depth_tested() const { return is_depth_tested_; }
void set_depth_tested(bool value) { is_depth_tested_ = value; }

Expand All @@ -2996,6 +3032,9 @@ class osc::Material::Impl final {
MaterialPropertyBlock properties_;
DepthFunction depth_function_ = DepthFunction::Default;
CullMode cull_mode_ = CullMode::Default;
BlendFunction source_blend_function_ = BlendFunction::SourceDefault;
BlendFunction destination_blend_function_ = BlendFunction::DestinationDefault;
BlendEquation blend_equation_ = BlendEquation::Default;
bool is_transparent_ = false;
bool is_depth_tested_ = true;
bool is_wireframe_mode_ = false;
Expand All @@ -3020,6 +3059,36 @@ void osc::Material::set_transparent(bool value)
impl_.upd()->set_transparent(value);
}

BlendFunction osc::Material::source_blend_function() const
{
return impl_->source_blend_function();
}

void osc::Material::set_source_blend_function(BlendFunction f)
{
impl_.upd()->set_source_blend_function(f);
}

BlendFunction osc::Material::destination_blend_function() const
{
return impl_->destination_blend_function();
}

void osc::Material::set_destination_blend_function(BlendFunction f)
{
impl_.upd()->set_destination_blend_function(f);
}

BlendEquation osc::Material::blend_equation() const
{
return impl_->blend_equation();
}

void osc::Material::set_blend_equation(BlendEquation f)
{
impl_.upd()->set_blend_equation(f);
}

bool osc::Material::is_depth_tested() const
{
return impl_->is_depth_tested();
Expand Down Expand Up @@ -5718,13 +5787,18 @@ namespace
// reports anything missing to the log at the provided log level
validate_opengl_backend_extension_support(LogLevel::debug);

// enable required capabilities
for (const auto& capability : c_required_opengl_capabilities) {
glEnable(capability.id);
if (not glIsEnabled(capability.id)) {
log_warn("failed to enable %s: this may cause rendering issues", capability.label.c_str());
}
}

// ensure alpha blending functions are defaulted
glBlendFunc(to_opengl_blend_func(BlendFunction::SourceDefault), to_opengl_blend_func(BlendFunction::DestinationDefault));
glBlendEquation(to_opengl_blend_equation(BlendEquation::Default));

// print OpenGL information to console (handy for debugging user's rendering
// issues)
log_info(
Expand Down Expand Up @@ -6760,6 +6834,19 @@ void osc::GraphicsBackend::handle_batch_with_same_material(

gl::use_program(shader_impl.program());

if (material_impl.source_blend_function() != BlendFunction::SourceDefault or
material_impl.destination_blend_function() != BlendFunction::DestinationDefault) {

glBlendFunc(
to_opengl_blend_func(material_impl.source_blend_function()),
to_opengl_blend_func(material_impl.destination_blend_function())
);
}

if (material_impl.blend_equation() != BlendEquation::Default) {
glBlendEquation(to_opengl_blend_equation(material_impl.blend_equation()));
}

#ifndef EMSCRIPTEN
if (material_impl.is_wireframe()) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
Expand Down Expand Up @@ -6836,6 +6923,19 @@ void osc::GraphicsBackend::handle_batch_with_same_material(
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
#endif

if (material_impl.blend_equation() != BlendEquation::Default) {
glBlendEquation(to_opengl_blend_equation(BlendEquation::Default));
}

if (material_impl.source_blend_function() != BlendFunction::SourceDefault or
material_impl.destination_blend_function() != BlendFunction::DestinationDefault) {

glBlendFunc(
to_opengl_blend_func(BlendFunction::SourceDefault),
to_opengl_blend_func(BlendFunction::DestinationDefault)
);
}
}

// helper: draw a sequence of `RenderObject`s
Expand Down Expand Up @@ -6985,7 +7085,6 @@ float osc::GraphicsBackend::setup_top_level_pipeline_state(
{
const auto viewport_geom = calc_viewport_geometry(camera, maybe_custom_render_target);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
gl::viewport(
static_cast<GLsizei>(viewport_geom.bottom_left.x),
static_cast<GLsizei>(viewport_geom.bottom_left.y),
Expand Down
13 changes: 12 additions & 1 deletion src/oscar/Graphics/Material.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <oscar/Graphics/BlendEquation.h>
#include <oscar/Graphics/BlendFunction.h>
#include <oscar/Graphics/Color.h>
#include <oscar/Graphics/Cubemap.h>
#include <oscar/Graphics/CullMode.h>
Expand All @@ -24,7 +26,7 @@

namespace osc
{
class Material final {
class Material {
public:
explicit Material(Shader);

Expand Down Expand Up @@ -63,6 +65,15 @@ namespace osc
bool is_transparent() const;
void set_transparent(bool);

BlendFunction source_blend_function() const;
void set_source_blend_function(BlendFunction);

BlendFunction destination_blend_function() const;
void set_destination_blend_function(BlendFunction);

BlendEquation blend_equation() const;
void set_blend_equation(BlendEquation);

bool is_depth_tested() const;
void set_depth_tested(bool);

Expand Down
2 changes: 1 addition & 1 deletion src/oscar/Graphics/Materials/MeshBasicMaterial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void main()
const StringName osc::MeshBasicMaterial::c_color_propname{"uDiffuseColor"};

osc::MeshBasicMaterial::MeshBasicMaterial(const Color& color) :
material_{Shader{c_vertex_shader_src, c_fragment_shader_src}}
Material{Shader{c_vertex_shader_src, c_fragment_shader_src}}
{
set_color(color);
}
18 changes: 3 additions & 15 deletions src/oscar/Graphics/Materials/MeshBasicMaterial.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace osc
{
// a material for drawing meshes with a simple solid color
class MeshBasicMaterial final {
class MeshBasicMaterial final : public Material {
public:
class PropertyBlock final {
public:
Expand All @@ -30,22 +30,10 @@ namespace osc

explicit MeshBasicMaterial(const Color& = Color::black());

Color color() const { return *material_.get<Color>(c_color_propname); }
void set_color(Color c) { material_.set<Color>(c_color_propname, c); }

bool is_wireframe() const { return material_.is_wireframe(); }
void set_wireframe(bool v) { material_.set_wireframe(v); }

bool is_depth_tested() const { return material_.is_depth_tested(); }
void set_depth_tested(bool v) { material_.set_depth_tested(v); }

bool is_transparent() const { return material_.is_transparent(); }
void set_transparent(bool v) { material_.set_transparent(v); }

operator const Material& () const { return material_; }
Color color() const { return *get<Color>(c_color_propname); }
void set_color(const Color& color) { set(c_color_propname, color); }

private:
static const StringName c_color_propname;
Material material_;
};
}
7 changes: 5 additions & 2 deletions src/oscar/Graphics/Scene/SceneRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,11 @@ class osc::SceneRenderer::Impl final {
wireframe_material_.set_color(Color::black());

// rim materials
rim_filler_material_.set_depth_tested(true);
rim_filler_material_.set_transparent(false);
rim_filler_material_.set_depth_tested(false);
rim_filler_material_.set_transparent(true);
rim_filler_material_.set_source_blend_function(BlendFunction::One);
rim_filler_material_.set_destination_blend_function(BlendFunction::One);
rim_filler_material_.set_blend_equation(BlendEquation::Max);
edge_detection_material_.set_transparent(true);
edge_detection_material_.set_depth_tested(false);
}
Expand Down
2 changes: 1 addition & 1 deletion src/oscar/Graphics/Scene/SceneRendererParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ namespace osc
Color background_color = default_background_color();
std::array<Color, c_num_rim_groups> rim_group_colors = std::to_array<Color>({
Color{0.95f, 0.4f, 0.0f, 1.0f},
Color{0.95f, 0.2f, 0.0f, 0.25f},
Color{0.95f, 0.2f, 0.0f, 0.4f},
});
Vec2 rim_thickness_in_pixels = {1.0f, 1.0f};

Expand Down
45 changes: 45 additions & 0 deletions tests/testoscar/Graphics/TestRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,51 @@ TEST_F(Renderer, MaterialSetTransparentBehavesAsExpected)
ASSERT_TRUE(mat.is_transparent());
}

TEST_F(Renderer, Material_source_blend_function_returns_SourceDefault_when_not_set)
{
const Material mat = GenerateMaterial();
ASSERT_EQ(mat.source_blend_function(), BlendFunction::SourceDefault);
}

TEST_F(Renderer, Material_set_source_blend_function_sets_source_blend_function)
{
static_assert(BlendFunction::SourceDefault != BlendFunction::Zero);

Material mat = GenerateMaterial();
mat.set_source_blend_function(BlendFunction::Zero);
ASSERT_EQ(mat.source_blend_function(), BlendFunction::Zero);
}

TEST_F(Renderer, Material_destination_blend_function_returns_DestinationDefault_when_not_set)
{
const Material mat = GenerateMaterial();
ASSERT_EQ(mat.destination_blend_function(), BlendFunction::DestinationDefault);
}

TEST_F(Renderer, Material_set_destination_blend_function_sets_destination_blend_function)
{
static_assert(BlendFunction::DestinationDefault != BlendFunction::SourceAlpha);

Material mat = GenerateMaterial();
mat.set_source_blend_function(BlendFunction::SourceAlpha);
ASSERT_EQ(mat.source_blend_function(), BlendFunction::SourceAlpha);
}

TEST_F(Renderer, Material_blend_equation_returns_Default_when_not_set)
{
const Material mat = GenerateMaterial();
ASSERT_EQ(mat.blend_equation(), BlendEquation::Default);
}

TEST_F(Renderer, Material_set_blend_equation_sets_blend_equation)
{
static_assert(BlendEquation::Default != BlendEquation::Max);

Material mat = GenerateMaterial();
mat.set_blend_equation(BlendEquation::Max);
ASSERT_EQ(mat.blend_equation(), BlendEquation::Max);
}

TEST_F(Renderer, MaterialGetDepthTestedIsInitiallyTrue)
{
Material mat = GenerateMaterial();
Expand Down

0 comments on commit b361eb1

Please sign in to comment.