Skip to content

Commit

Permalink
Move object HSV calculations to GPU
Browse files Browse the repository at this point in the history
  • Loading branch information
opstic committed Jun 16, 2024
1 parent da8cdc2 commit 10964cd
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 76 deletions.
29 changes: 1 addition & 28 deletions src/level/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ pub(crate) fn update_object_color(
object_groups.archetype_entity,
object_color.object_color_kind,
object_color.channel_entity,
object_color.hsv,
)) {
let Some(cached_calculated): &Option<ObjectColorCalculated> =
cached_calculated
Expand Down Expand Up @@ -530,7 +529,6 @@ pub(crate) fn update_object_color(
object_groups.archetype_entity,
object_color.object_color_kind,
object_color.channel_entity,
object_color.hsv,
),
Some(ObjectColorCalculated {
enabled: false,
Expand Down Expand Up @@ -576,15 +574,6 @@ pub(crate) fn update_object_color(
ColorMod::apply_color_mods(iter, &mut color);
}

match object_color.object_color_kind {
ObjectColorKind::None | ObjectColorKind::Black => (),
_ => {
if let Some(hsv) = object_color.hsv {
hsv.apply_rgba(&mut color);
}
}
}

if calculated.blending != blending {
par_commands.command_scope(|mut commands| {
commands.entity(entity).insert(if !blending {
Expand All @@ -603,7 +592,6 @@ pub(crate) fn update_object_color(
object_groups.archetype_entity,
object_color.object_color_kind,
object_color.channel_entity,
object_color.hsv,
),
Some(*calculated),
);
Expand Down Expand Up @@ -669,7 +657,7 @@ impl ColorMod {
}
}

#[derive(Component, Debug, Deserialize, Copy, Clone, Reflect, PartialEq)]
#[derive(Component, Debug, Deserialize, Copy, Clone, Reflect)]
pub(crate) struct HsvMod {
pub(crate) h: f32,
pub(crate) s: f32,
Expand All @@ -678,21 +666,6 @@ pub(crate) struct HsvMod {
pub(crate) v_absolute: bool,
}

impl Hash for HsvMod {
fn hash<H: Hasher>(&self, state: &mut H) {
(
self.h.to_bits(),
self.s.to_bits(),
self.v.to_bits(),
self.s_absolute,
self.v_absolute,
)
.hash(state)
}
}

impl Eq for HsvMod {}

impl HsvMod {
pub(crate) fn parse(hsv_string: &str) -> Result<HsvMod, anyhow::Error> {
let hsv_data: [&str; 5] = de::from_str(hsv_string, 'a')?;
Expand Down
116 changes: 74 additions & 42 deletions src/render/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use bevy::tasks::ComputeTaskPool;
use bevy::utils::{syncunsafecell::SyncUnsafeCell, FloatOrd};
use indexmap::IndexMap;

use crate::level::color::ObjectColorCalculated;
use crate::level::color::{HsvMod, ObjectColor, ObjectColorCalculated, ObjectColorKind};
use crate::level::transform::GlobalTransform2d;
use crate::level::{object::Object, section::GlobalSections, LevelWorld};
use crate::state::level::Options;
Expand Down Expand Up @@ -249,6 +249,10 @@ impl SpecializedRenderPipeline for ObjectPipeline {
VertexFormat::Float32x4,
// @location(5) i_texture_index: u32
VertexFormat::Uint32,
// @location(6) i_hsv: vec3<f32>
VertexFormat::Float32x3,
// @location(7) i_hsv_flags: u32
VertexFormat::Uint32,
],
);

Expand Down Expand Up @@ -305,15 +309,14 @@ impl SpecializedRenderPipeline for ObjectPipeline {
pub struct ExtractedObject {
transform: GlobalTransform2d,
color: Vec4,
hsv: Option<HsvMod>,
/// Select an area of the texture
rect: Option<Rect>,
/// Change the on-screen size of the sprite
custom_size: Option<Vec2>,
rect: Rect,
/// Asset ID of the [`Image`] of this sprite
/// PERF: storing an `AssetId` instead of `Handle<Image>` enables some optimizations (`ExtractedSprite` becomes `Copy` and doesn't need to be dropped)
image_handle_id: AssetId<Image>,
flip_x: bool,
flip_y: bool,
// flip_x: bool,
// flip_y: bool,
anchor: Vec2,
rotated: bool,
entity: Entity,
Expand Down Expand Up @@ -350,6 +353,7 @@ pub(crate) struct ExtractSystemStateCache {
Entity,
&'static GlobalTransform2d,
&'static Object,
&'static ObjectColor,
&'static ObjectColorCalculated,
&'static Handle<Image>,
),
Expand Down Expand Up @@ -399,6 +403,7 @@ pub(crate) fn extract_objects(
Entity,
&GlobalTransform2d,
&Object,
&ObjectColor,
&ObjectColorCalculated,
&Handle<Image>,
)>,
Expand All @@ -420,8 +425,10 @@ pub(crate) fn extract_objects(
}

for section in &global_sections.sections[global_sections.visible.clone()] {
for (entity, transform, object, object_color, image_handle) in objects.iter_many(section) {
if !object_color.enabled {
for (entity, transform, object, object_color, object_color_calc, image_handle) in
objects.iter_many(section)
{
if !object_color_calc.enabled {
continue;
}

Expand All @@ -441,7 +448,7 @@ pub(crate) fn extract_objects(
}

let z_layer = object.z_layer
- if object_color.blending ^ (object.z_layer % 2 == 0) {
- if object_color_calc.blending ^ (object.z_layer % 2 == 0) {
1
} else {
0
Expand All @@ -462,14 +469,17 @@ pub(crate) fn extract_objects(
&mut extracted_layers.layers[layer_index].1
};

let hsv = match object_color.object_color_kind {
ObjectColorKind::None | ObjectColorKind::Black => None,
_ => object_color.hsv,
};

extracted_layer.get_mut().push(ExtractedObject {
transform: *transform,
color: object_color.color,
rect: Some(object.frame.rect),
custom_size: None,
color: object_color_calc.color,
hsv,
rect: object.frame.rect,
image_handle_id: image_handle.id(),
flip_x: false,
flip_y: false,
anchor: object.frame.anchor + object.anchor,
rotated: object.frame.rotated,
entity,
Expand All @@ -485,11 +495,23 @@ struct ObjectInstance {
i_color: [f32; 4],
i_uv_offset_scale: [f32; 4],
i_texture_index: u32,
i_hsv: [f32; 3],
i_hsv_flags: u32,
}

impl ObjectInstance {
#[inline]
fn from(transform: &Affine2, color: Vec4, uv_offset_scale: &Vec4, texture_index: u32) -> Self {
fn from(
transform: &Affine2,
color: Vec4,
uv_offset_scale: &Vec4,
texture_index: u32,
hsv_mod: Option<HsvMod>,
) -> Self {
let (i_hsv, i_hsv_flags) = match hsv_mod {
Some(hsv_mod) => hsv_mod.into(),
None => ([0.; 3], HSV_FLAGS_DISABLED),
};
Self {
i_model: [
transform.matrix2.x_axis,
Expand All @@ -499,10 +521,29 @@ impl ObjectInstance {
i_color: color.to_array(),
i_uv_offset_scale: uv_offset_scale.to_array(),
i_texture_index: texture_index,
i_hsv,
i_hsv_flags,
}
}
}

const HSV_FLAGS_DISABLED: u32 = 1 << 0;
const HSV_FLAGS_S_ABSOLUTE: u32 = 1 << 1;
const HSV_FLAGS_V_ABSOLUTE: u32 = 1 << 2;

impl From<HsvMod> for ([f32; 3], u32) {
fn from(hsv: HsvMod) -> Self {
let mut flags = 0;
if hsv.s_absolute {
flags |= HSV_FLAGS_S_ABSOLUTE;
}
if hsv.v_absolute {
flags |= HSV_FLAGS_V_ABSOLUTE;
}
([hsv.h, hsv.s, hsv.v], flags)
}
}

#[derive(Resource)]
pub struct ObjectMeta {
view_bind_group: Option<BindGroup>,
Expand Down Expand Up @@ -700,7 +741,7 @@ pub(crate) fn prepare_objects(
instance_mut_ref.split_at_mut(extracted_layer.len());
instance_mut_ref = other_chunk;

assert_eq!(extracted_layer.len(), this_chunk.len());
debug_assert_eq!(extracted_layer.len(), this_chunk.len());

let layer_batches = unsafe { &mut *layers_batches.get() }
.entry(item_index)
Expand Down Expand Up @@ -763,33 +804,23 @@ pub(crate) fn prepare_objects(
// Calculate vertex data for this item
let mut uv_offset_scale: Vec4;

// If a rect is specified, adjust UVs and the size of the quad
if let Some(rect) = extracted_object.rect {
let rect_size = rect.size();
uv_offset_scale = Vec4::new(
rect.min.x / current_image_size.x,
rect.max.y / current_image_size.y,
rect_size.x / current_image_size.x,
-rect_size.y / current_image_size.y,
);
quad_size = rect_size;
} else {
uv_offset_scale = Vec4::new(0.0, 1.0, 1.0, -1.0);
}

if extracted_object.flip_x {
uv_offset_scale.x += uv_offset_scale.z;
uv_offset_scale.z *= -1.0;
}
if extracted_object.flip_y {
uv_offset_scale.y += uv_offset_scale.w;
uv_offset_scale.w *= -1.0;
}
let rect_size = extracted_object.rect.size();
uv_offset_scale = Vec4::new(
extracted_object.rect.min.x / current_image_size.x,
extracted_object.rect.max.y / current_image_size.y,
rect_size.x / current_image_size.x,
-rect_size.y / current_image_size.y,
);
quad_size = rect_size;

// Override the size if a custom one is specified
if let Some(custom_size) = extracted_object.custom_size {
quad_size = custom_size;
}
// if extracted_object.flip_x {
// uv_offset_scale.x += uv_offset_scale.z;
// uv_offset_scale.z *= -1.0;
// }
// if extracted_object.flip_y {
// uv_offset_scale.y += uv_offset_scale.w;
// uv_offset_scale.w *= -1.0;
// }

// Texture atlas scale factor
quad_size /= 4.;
Expand All @@ -814,6 +845,7 @@ pub(crate) fn prepare_objects(
extracted_object.color,
&uv_offset_scale,
texture_index as u32,
extracted_object.hsv,
);

batch_range.end += 1;
Expand Down
55 changes: 50 additions & 5 deletions src/render/object.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ struct VertexInput {
@location(3) i_color: vec4<f32>,
@location(4) i_uv_offset_scale: vec4<f32>,
@location(5) i_texture_index: u32,
@location(6) i_hsv: vec3<f32>,
@location(7) i_hsv_flags: u32,
@builtin(vertex_index) index: u32,
}

Expand All @@ -33,13 +35,31 @@ struct VertexOutput {
#endif
};

// From https://github.com/lolengine/lolengine/blob/3c26/doc/legacy/front_camera_sprite.lolfx#L56
fn rgb2hsv(rgb: vec3<f32>) -> vec3<f32> {
let K = vec4<f32>(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
let p = mix(vec4<f32>(rgb.bg, K.wz), vec4<f32>(rgb.gb, K.xy), step(rgb.b, rgb.g));
let q = mix(vec4<f32>(p.xyw, rgb.r), vec4<f32>(rgb.r, p.yzx), step(p.x, rgb.r));

let d = q.x - min(q.w, q.y);
let e = 1.0e-10;
return vec3<f32>(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

// From https://github.com/lolengine/lolengine/blob/3c26/doc/legacy/front_camera_sprite.lolfx#L67
fn hsv2rgb(hsv: vec3<f32>) -> vec3<f32> {
let K = vec4<f32>(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
let p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www);
return hsv.z * mix(K.xxx, clamp(p - K.xxx, vec3<f32>(0.0), vec3<f32>(1.0)), hsv.y);
}

@vertex
fn vertex(in: VertexInput) -> VertexOutput {
var out: VertexOutput;

let vertex_position = vec2<f32>(
f32(in.index & 0x1u),
f32((in.index & 0x2u) >> 1u),
f32(in.index & 1),
f32((in.index & 2) >> 1),
);

out.clip_position = vec4<f32>(vertex_position, 0.0, 1.0)
Expand All @@ -51,11 +71,36 @@ fn vertex(in: VertexInput) -> VertexOutput {

out.uv = vertex_position * in.i_uv_offset_scale.zw + in.i_uv_offset_scale.xy;

var rgb = in.i_color.rgb;

var hsv = rgb2hsv(in.i_color.rgb);
var h = hsv.x + in.i_hsv.x;
let normal_sv = vec2<f32>(
hsv.y * in.i_hsv.y,
hsv.z * in.i_hsv.z,
);
let absolute_sv = vec2<f32>(
hsv.y + in.i_hsv.y,
hsv.z + in.i_hsv.z,
);
let absolute_flags = vec2<f32>(
f32((in.i_hsv_flags >> 1) & 1),
f32((in.i_hsv_flags >> 2) & 1),
);

let sv = mix(normal_sv, absolute_sv, absolute_flags);

hsv = vec3<f32>(fract(h + 1.0), clamp(sv, vec2<f32>(0.0), vec2<f32>(1.0)));

let hsv_rgb = hsv2rgb(hsv);

rgb = mix(hsv_rgb, rgb, vec3<f32>(f32((in.i_hsv_flags >> 0) & 1)));

#ifndef ADDITIVE_BLENDING
out.color = vec4<f32>(in.i_color.rgb * in.i_color.a, in.i_color.a);
out.color = vec4<f32>(rgb * in.i_color.a, in.i_color.a);
#else
var alpha = in.i_color.a * in.i_color.a;
out.color = vec4<f32>(in.i_color.rgb * alpha, 0.0);
let alpha = in.i_color.a * in.i_color.a;
out.color = vec4<f32>(rgb * alpha, 0.0);
#endif

#ifndef NO_TEXTURE_ARRAY
Expand Down
3 changes: 2 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ pub(crate) fn rgb_to_hsv(rgb: [f32; 3]) -> [f32; 3] {

#[inline]
pub(crate) fn hsv_to_rgb([h, s, v]: [f32; 3]) -> [f32; 3] {
let h = (h.fract() + if h < 0. { 1. } else { 0. }) * 6.;
debug_assert!(h >= 0.);
let h = (h + 1.).fract() * 6.;
let h_fract = h.fract();
let s = s.clamp(0., 1.);
let v = v.clamp(0., 1.);
Expand Down

0 comments on commit 10964cd

Please sign in to comment.