浏览代码

resource: parse conditional render states

Actual expression evaluation will be implemented in a later commit.
Daniele Bartolini 1 年之前
父节点
当前提交
7d5a9c7dc4
共有 1 个文件被更改,包括 434 次插入144 次删除
  1. 434 144
      src/resource/shader_resource.cpp

+ 434 - 144
src/resource/shader_resource.cpp

@@ -10,15 +10,19 @@
 #include "core/json/json_object.inl"
 #include "core/json/sjson.h"
 #include "core/memory/temp_allocator.inl"
+#include "core/option.inl"
 #include "core/process.h"
 #include "core/strings/dynamic_string.inl"
 #include "core/strings/string_stream.inl"
 #include "device/device.h"
+#include "device/log.h"
 #include "resource/compile_options.inl"
 #include "resource/resource_manager.h"
 #include "resource/shader_resource.h"
 #include "world/shader_manager.h"
 
+LOG_SYSTEM(SHADER_RESOURCE, "shader_resource")
+
 namespace crown
 {
 namespace shader_resource_internal
@@ -500,72 +504,141 @@ namespace shader_resource_internal
 
 	struct RenderState
 	{
-		bool _rgb_write_enable;
-		bool _alpha_write_enable;
-		bool _depth_write_enable;
-		bool _depth_enable;
-		bool _blend_enable;
-		DepthFunction::Enum _depth_func;
-		BlendFunction::Enum _blend_src;
-		BlendFunction::Enum _blend_dst;
-		BlendEquation::Enum _blend_equation;
-		CullMode::Enum _cull_mode;
-		PrimitiveType::Enum _primitive_type;
-
-		RenderState()
+		ALLOCATOR_AWARE;
+
+		struct State
 		{
-			reset();
-		}
+			Option<bool> _rgb_write_enable;
+			Option<bool> _alpha_write_enable;
+			Option<bool> _depth_write_enable;
+			Option<bool> _depth_enable;
+			Option<bool> _blend_enable;
+			Option<DepthFunction::Enum> _depth_func;
+			Option<BlendFunction::Enum> _blend_src;
+			Option<BlendFunction::Enum> _blend_dst;
+			Option<BlendEquation::Enum> _blend_equation;
+			Option<CullMode::Enum> _cull_mode;
+			Option<PrimitiveType::Enum> _primitive_type;
+
+			void dump()
+			{
+				logi(SHADER_RESOURCE, "rgb_write_enable %d", _rgb_write_enable.value());
+				logi(SHADER_RESOURCE, "alpha_write_enable %d", _alpha_write_enable.value());
+				logi(SHADER_RESOURCE, "depth_write_enable %d", _depth_write_enable.value());
+				logi(SHADER_RESOURCE, "depth_enable %d", _depth_enable.value());
+				logi(SHADER_RESOURCE, "blend_enable %d", _blend_enable.value());
+				logi(SHADER_RESOURCE, "depth_func %d", _depth_func.value());
+				logi(SHADER_RESOURCE, "blend_src %d", _blend_src.value());
+				logi(SHADER_RESOURCE, "blend_dst %d", _blend_dst.value());
+				logi(SHADER_RESOURCE, "blend_equation %d", _blend_equation.value());
+				logi(SHADER_RESOURCE, "cull_mode %d", _cull_mode.value());
+				logi(SHADER_RESOURCE, "primitive_type %d", _primitive_type.value());
+			}
 
-		void reset()
+			State()
+				: _rgb_write_enable(true)
+				, _alpha_write_enable(true)
+				, _depth_write_enable(true)
+				, _depth_enable(true)
+				, _blend_enable(false)
+				, _depth_func(DepthFunction::LEQUAL)
+				, _blend_src(BlendFunction::SRC_ALPHA)
+				, _blend_dst(BlendFunction::INV_SRC_ALPHA)
+				, _blend_equation(BlendEquation::ADD)
+				, _cull_mode(CullMode::CW)
+				, _primitive_type(PrimitiveType::PT_TRIANGLES)
+			{
+			}
+
+			State(const State &other) = delete;
+
+			State &operator=(const State &other) = delete;
+
+			void overwrite_changed_properties(const State &other)
+			{
+				if (other._rgb_write_enable.has_changed())
+					_rgb_write_enable.set_value(other._rgb_write_enable.value());
+				if (other._alpha_write_enable.has_changed())
+					_alpha_write_enable.set_value(other._alpha_write_enable.value());
+				if (other._depth_write_enable.has_changed())
+					_depth_write_enable.set_value(other._depth_write_enable.value());
+				if (other._depth_enable.has_changed())
+					_depth_enable.set_value(other._depth_enable.value());
+				if (other._blend_enable.has_changed())
+					_blend_enable.set_value(other._blend_enable.value());
+				if (other._depth_func.has_changed())
+					_depth_func.set_value(other._depth_func.value());
+				if (other._blend_src.has_changed())
+					_blend_src.set_value(other._blend_src.value());
+				if (other._blend_dst.has_changed())
+					_blend_dst.set_value(other._blend_dst.value());
+				if (other._blend_equation.has_changed())
+					_blend_equation.set_value(other._blend_equation.value());
+				if (other._cull_mode.has_changed())
+					_cull_mode.set_value(other._cull_mode.value());
+				if (other._primitive_type.has_changed())
+					_primitive_type.set_value(other._primitive_type.value());
+			}
+
+			u64 encode() const
+			{
+				const u64 depth_func = _depth_enable.value()
+					? _bgfx_depth_func_map[_depth_func.value()]
+					: 0
+					;
+				const u64 blend_func = _blend_enable.value() && _blend_src.value() != BlendFunction::COUNT && _blend_dst.value() != BlendFunction::COUNT
+					? BGFX_STATE_BLEND_FUNC(_bgfx_blend_func_map[_blend_src.value()], _bgfx_blend_func_map[_blend_dst.value()])
+					: 0
+					;
+				const u64 blend_eq = _blend_enable.value() && _blend_equation.value() != BlendEquation::COUNT
+					? BGFX_STATE_BLEND_EQUATION(_bgfx_blend_equation_map[_blend_equation.value()])
+					: 0
+					;
+				const u64 cull_mode = _cull_mode.value() != CullMode::COUNT
+					? _bgfx_cull_mode_map[_cull_mode.value()]
+					: 0
+					;
+				const u64 primitive_type = _primitive_type.value() != PrimitiveType::COUNT
+					? _bgfx_primitive_type_map[_primitive_type.value()]
+					: 0
+					;
+
+				u64 state = 0;
+				state |= _rgb_write_enable.value()   ? BGFX_STATE_WRITE_RGB : 0;
+				state |= _alpha_write_enable.value() ? BGFX_STATE_WRITE_A   : 0;
+				state |= _depth_write_enable.value() ? BGFX_STATE_WRITE_Z   : 0;
+				state |= depth_func;
+				state |= blend_func;
+				state |= blend_eq;
+				state |= cull_mode;
+				state |= primitive_type;
+
+				return state;
+			}
+		};
+
+		DynamicString _inherit;
+		Vector<DynamicString> _expressions;
+		Array<State> _states;
+
+		explicit RenderState(Allocator &a)
+			: _inherit(a)
+			, _expressions(a)
+			, _states(a)
 		{
-			_rgb_write_enable = true;
-			_alpha_write_enable = true;
-			_depth_write_enable = true;
-			_depth_enable = true;
-			_blend_enable = false;
-			_depth_func = DepthFunction::LEQUAL;
-			_blend_src = BlendFunction::SRC_ALPHA;
-			_blend_dst = BlendFunction::INV_SRC_ALPHA;
-			_blend_equation = BlendEquation::ADD;
-			_cull_mode = CullMode::CW;
-			_primitive_type = PrimitiveType::PT_TRIANGLES;
 		}
 
-		u64 encode() const
+		void push_back_states(const DynamicString &expr, const RenderState::State &state)
 		{
-			const u64 depth_func = _depth_enable
-				? _bgfx_depth_func_map[_depth_func]
-				: 0
-				;
-			const u64 blend_func = _blend_enable && _blend_src != BlendFunction::COUNT && _blend_dst != BlendFunction::COUNT
-				? BGFX_STATE_BLEND_FUNC(_bgfx_blend_func_map[_blend_src], _bgfx_blend_func_map[_blend_dst])
-				: 0
-				;
-			const u64 blend_eq = _blend_enable && _blend_equation != BlendEquation::COUNT
-				? BGFX_STATE_BLEND_EQUATION(_bgfx_blend_equation_map[_blend_equation])
-				: 0
-				;
-			const u64 cull_mode = _cull_mode != CullMode::COUNT
-				? _bgfx_cull_mode_map[_cull_mode]
-				: 0
-				;
-			const u64 primitive_type = _primitive_type != PrimitiveType::COUNT
-				? _bgfx_primitive_type_map[_primitive_type]
-				: 0
-				;
-
-			u64 state = 0;
-			state |= _rgb_write_enable   ? BGFX_STATE_WRITE_RGB : 0;
-			state |= _alpha_write_enable ? BGFX_STATE_WRITE_A   : 0;
-			state |= _depth_write_enable ? BGFX_STATE_WRITE_Z   : 0;
-			state |= depth_func;
-			state |= blend_func;
-			state |= blend_eq;
-			state |= cull_mode;
-			state |= primitive_type;
+			vector::push_back(_expressions, expr);
+			array::push_back(_states, state);
+		}
 
-			return state;
+		void push_back_states(const char *expr, const RenderState::State &state)
+		{
+			DynamicString expr_str(default_allocator());
+			expr_str = expr;
+			push_back_states(expr_str, state);
 		}
 	};
 
@@ -742,129 +815,319 @@ namespace shader_resource_internal
 			return 0;
 		}
 
-		s32 parse_render_states(const char *json)
+		s32 parse_states_compat(RenderState::State &state, JsonObject &obj)
 		{
+			// gui = {
+			//   ...
+			//   states = { <-- You are here.
+			//   }
+			// }
+			// This function is for backwards compatibility only.
+			// See: parse_states() and parse_conditional_states().
+
 			TempAllocator4096 ta;
-			JsonObject render_states(ta);
-			sjson::parse_object(render_states, json);
+			const char *warn_msg = "RenderState properties are deprecated. Use states = { ... } object.";
 
-			auto cur = json_object::begin(render_states);
-			auto end = json_object::end(render_states);
-			for (; cur != end; ++cur) {
-				JSON_OBJECT_SKIP_HOLE(render_states, cur);
+			if (json_object::has(obj, "rgb_write_enable")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				state._rgb_write_enable.set_value(sjson::parse_bool(obj["rgb_write_enable"]));
+			}
 
-				JsonObject obj(ta);
-				sjson::parse_object(obj, cur->second);
+			if (json_object::has(obj, "alpha_write_enable")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				state._alpha_write_enable.set_value(sjson::parse_bool(obj["alpha_write_enable"]));
+			}
 
-				RenderState rs;
-				rs.reset();
+			if (json_object::has(obj, "depth_write_enable")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				state._depth_write_enable.set_value(sjson::parse_bool(obj["depth_write_enable"]));
+			}
 
-				// Read inherit render state if any.
-				const bool has_inherit = json_object::has(obj, "inherit");
-				if (has_inherit) {
-					DynamicString inherit(ta);
-					sjson::parse_string(inherit, obj["inherit"]);
-					DATA_COMPILER_ASSERT(hash_map::has(_render_states, inherit)
-						, _opts
-						, "Unknown inherit render state: '%s'"
-						, inherit.c_str()
-						);
-					RenderState deffault_rs;
-					rs = hash_map::get(_render_states, inherit, deffault_rs);
-				}
+			if (json_object::has(obj, "depth_enable")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				state._depth_enable.set_value(sjson::parse_bool(obj["depth_enable"]));
+			}
 
-				const bool has_rgb_write_enable   = json_object::has(obj, "rgb_write_enable");
-				const bool has_alpha_write_enable = json_object::has(obj, "alpha_write_enable");
-				const bool has_depth_write_enable = json_object::has(obj, "depth_write_enable");
-				const bool has_depth_enable       = json_object::has(obj, "depth_enable");
-				const bool has_blend_enable       = json_object::has(obj, "blend_enable");
-				const bool has_depth_func         = json_object::has(obj, "depth_func");
-				const bool has_blend_src          = json_object::has(obj, "blend_src");
-				const bool has_blend_dst          = json_object::has(obj, "blend_dst");
-				const bool has_blend_equation     = json_object::has(obj, "blend_equation");
-				const bool has_cull_mode          = json_object::has(obj, "cull_mode");
-				const bool has_primitive_type     = json_object::has(obj, "primitive_type");
+			if (json_object::has(obj, "blend_enable")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				state._blend_enable.set_value(sjson::parse_bool(obj["blend_enable"]));
+			}
 
-				if (has_rgb_write_enable)
-					rs._rgb_write_enable = sjson::parse_bool(obj["rgb_write_enable"]);
+			if (json_object::has(obj, "depth_func")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				DynamicString depth_func(ta);
+				sjson::parse_string(depth_func, obj["depth_func"]);
+				state._depth_func.set_value(name_to_depth_func(depth_func.c_str()));
+				DATA_COMPILER_ASSERT(state._depth_func.value() != DepthFunction::COUNT
+					, _opts
+					, "Unknown depth test: '%s'"
+					, depth_func.c_str()
+					);
+			}
 
-				if (has_alpha_write_enable)
-					rs._alpha_write_enable = sjson::parse_bool(obj["alpha_write_enable"]);
+			if (json_object::has(obj, "blend_src")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				DynamicString blend_src(ta);
+				sjson::parse_string(blend_src, obj["blend_src"]);
+				state._blend_src.set_value(name_to_blend_function(blend_src.c_str()));
+				DATA_COMPILER_ASSERT(state._blend_src.value() != BlendFunction::COUNT
+					, _opts
+					, "Unknown blend function: '%s'"
+					, blend_src.c_str()
+					);
+			}
 
-				if (has_depth_write_enable)
-					rs._depth_write_enable = sjson::parse_bool(obj["depth_write_enable"]);
+			if (json_object::has(obj, "blend_dst")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				DynamicString blend_dst(ta);
+				sjson::parse_string(blend_dst, obj["blend_dst"]);
+				state._blend_dst.set_value(name_to_blend_function(blend_dst.c_str()));
+				DATA_COMPILER_ASSERT(state._blend_dst.value() != BlendFunction::COUNT
+					, _opts
+					, "Unknown blend function: '%s'"
+					, blend_dst.c_str()
+					);
+			}
 
-				if (has_depth_enable)
-					rs._depth_enable = sjson::parse_bool(obj["depth_enable"]);
+			if (json_object::has(obj, "blend_equation")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				DynamicString blend_equation(ta);
+				sjson::parse_string(blend_equation, obj["blend_equation"]);
+				state._blend_equation.set_value(name_to_blend_equation(blend_equation.c_str()));
+				DATA_COMPILER_ASSERT(state._blend_equation.value() != BlendEquation::COUNT
+					, _opts
+					, "Unknown blend equation: '%s'"
+					, blend_equation.c_str()
+					);
+			}
 
-				if (has_blend_enable)
-					rs._blend_enable = sjson::parse_bool(obj["blend_enable"]);
+			if (json_object::has(obj, "cull_mode")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				DynamicString cull_mode(ta);
+				sjson::parse_string(cull_mode, obj["cull_mode"]);
+				state._cull_mode.set_value(name_to_cull_mode(cull_mode.c_str()));
+				DATA_COMPILER_ASSERT(state._cull_mode.value() != CullMode::COUNT
+					, _opts
+					, "Unknown cull mode: '%s'"
+					, cull_mode.c_str()
+					);
+			}
 
-				if (has_depth_func) {
+			if (json_object::has(obj, "primitive_type")) {
+				logw(SHADER_RESOURCE, warn_msg);
+				DynamicString primitive_type(ta);
+				sjson::parse_string(primitive_type, obj["primitive_type"]);
+				state._primitive_type.set_value(name_to_primitive_type(primitive_type.c_str()));
+				DATA_COMPILER_ASSERT(state._primitive_type.value() != PrimitiveType::COUNT
+					, _opts
+					, "Unknown primitive type: '%s'"
+					, primitive_type.c_str()
+					);
+			}
+
+			return 0;
+		}
+
+		s32 parse_states(RenderState::State &state, const char *json)
+		{
+			// gui = {
+			//   states = { <-- You are here.
+			//   }
+			//   ...
+			// }
+			//
+			// Possible cases:
+			// a) "some_expr" = { state_a = b, state_b = c, ... }
+			//   Ignore, this is handled by parse_conditional_states().
+			// b) state_x = y
+
+			TempAllocator4096 ta;
+			JsonObject states(ta);
+			sjson::parse_object(states, json);
+
+			auto cur = json_object::begin(states);
+			auto end = json_object::end(states);
+			for (; cur != end; ++cur) {
+				JSON_OBJECT_SKIP_HOLE(states, cur);
+
+				// It must be a regular key/value state.
+				if (cur->first == "rgb_write_enable") {
+					state._rgb_write_enable.set_value(sjson::parse_bool(states["rgb_write_enable"]));
+				} else if (cur->first == "alpha_write_enable") {
+					state._alpha_write_enable.set_value(sjson::parse_bool(states["alpha_write_enable"]));
+				} else if (cur->first == "depth_write_enable") {
+					state._depth_write_enable.set_value(sjson::parse_bool(states["depth_write_enable"]));
+				} else if (cur->first == "depth_enable") {
+					state._depth_enable.set_value(sjson::parse_bool(states["depth_enable"]));
+				} else if (cur->first == "blend_enable") {
+					state._blend_enable.set_value(sjson::parse_bool(states["blend_enable"]));
+				} else if (cur->first == "depth_func") {
 					DynamicString depth_func(ta);
-					sjson::parse_string(depth_func, obj["depth_func"]);
-					rs._depth_func = name_to_depth_func(depth_func.c_str());
-					DATA_COMPILER_ASSERT(rs._depth_func != DepthFunction::COUNT
+					sjson::parse_string(depth_func, states["depth_func"]);
+					state._depth_func.set_value(name_to_depth_func(depth_func.c_str()));
+					DATA_COMPILER_ASSERT(state._depth_func.value() != DepthFunction::COUNT
 						, _opts
 						, "Unknown depth test: '%s'"
 						, depth_func.c_str()
 						);
-				}
-
-				if (has_blend_src) {
+				} else if (cur->first == "blend_src") {
 					DynamicString blend_src(ta);
-					sjson::parse_string(blend_src, obj["blend_src"]);
-					rs._blend_src = name_to_blend_function(blend_src.c_str());
-					DATA_COMPILER_ASSERT(rs._blend_src != BlendFunction::COUNT
+					sjson::parse_string(blend_src, states["blend_src"]);
+					state._blend_src.set_value(name_to_blend_function(blend_src.c_str()));
+					DATA_COMPILER_ASSERT(state._blend_src.value() != BlendFunction::COUNT
 						, _opts
 						, "Unknown blend function: '%s'"
 						, blend_src.c_str()
 						);
-				}
-
-				if (has_blend_dst) {
+				} else if (cur->first == "blend_dst") {
 					DynamicString blend_dst(ta);
-					sjson::parse_string(blend_dst, obj["blend_dst"]);
-					rs._blend_dst = name_to_blend_function(blend_dst.c_str());
-					DATA_COMPILER_ASSERT(rs._blend_dst != BlendFunction::COUNT
+					sjson::parse_string(blend_dst, states["blend_dst"]);
+					state._blend_dst.set_value(name_to_blend_function(blend_dst.c_str()));
+					DATA_COMPILER_ASSERT(state._blend_dst.value() != BlendFunction::COUNT
 						, _opts
 						, "Unknown blend function: '%s'"
 						, blend_dst.c_str()
 						);
-				}
-
-				if (has_blend_equation) {
+				} else if (cur->first == "blend_equation") {
 					DynamicString blend_equation(ta);
-					sjson::parse_string(blend_equation, obj["blend_equation"]);
-					rs._blend_equation = name_to_blend_equation(blend_equation.c_str());
-					DATA_COMPILER_ASSERT(rs._blend_equation != BlendEquation::COUNT
+					sjson::parse_string(blend_equation, states["blend_equation"]);
+					state._blend_equation.set_value(name_to_blend_equation(blend_equation.c_str()));
+					DATA_COMPILER_ASSERT(state._blend_equation.value() != BlendEquation::COUNT
 						, _opts
 						, "Unknown blend equation: '%s'"
 						, blend_equation.c_str()
 						);
-				}
-
-				if (has_cull_mode) {
+				} else if (cur->first == "cull_mode") {
 					DynamicString cull_mode(ta);
-					sjson::parse_string(cull_mode, obj["cull_mode"]);
-					rs._cull_mode = name_to_cull_mode(cull_mode.c_str());
-					DATA_COMPILER_ASSERT(rs._cull_mode != CullMode::COUNT
+					sjson::parse_string(cull_mode, states["cull_mode"]);
+					state._cull_mode.set_value(name_to_cull_mode(cull_mode.c_str()));
+					DATA_COMPILER_ASSERT(state._cull_mode.value() != CullMode::COUNT
 						, _opts
 						, "Unknown cull mode: '%s'"
 						, cull_mode.c_str()
 						);
-				}
-
-				if (has_primitive_type) {
+				} else if (cur->first == "primitive_type") {
 					DynamicString primitive_type(ta);
-					sjson::parse_string(primitive_type, obj["primitive_type"]);
-					rs._primitive_type = name_to_primitive_type(primitive_type.c_str());
-					DATA_COMPILER_ASSERT(rs._primitive_type != PrimitiveType::COUNT
+					sjson::parse_string(primitive_type, states["primitive_type"]);
+					state._primitive_type.set_value(name_to_primitive_type(primitive_type.c_str()));
+					DATA_COMPILER_ASSERT(state._primitive_type.value() != PrimitiveType::COUNT
 						, _opts
 						, "Unknown primitive type: '%s'"
 						, primitive_type.c_str()
 						);
+				} else {
+					// Skip conditionals state objects, error out on anything else.
+					if (sjson::type(cur->second) != JsonValueType::OBJECT) {
+						DATA_COMPILER_ASSERT(false
+							, _opts
+							, "Unknown state property: '%.*s'"
+							, cur->first.length()
+							, cur->first.data()
+							);
+					}
+				}
+			}
+
+			return 0;
+		}
+
+		s32 parse_conditional_states(RenderState &rs, const char *json)
+		{
+			// gui = {
+			//   states = { <-- You are here.
+			//   }
+			//   ...
+			// }
+			//
+			// Possible cases:
+			// a) "some_expr" = { state_a = b, state_b = c, ... }
+			// b) state_x = y
+			//   Ignore, this is handled by parse_states().
+
+			TempAllocator4096 ta;
+			JsonObject obj(ta);
+			sjson::parse_object(obj, json);
+
+			// Read conditional states.
+			auto cur = json_object::begin(obj);
+			auto end = json_object::end(obj);
+			for (; cur != end; ++cur) {
+				JSON_OBJECT_SKIP_HOLE(obj, cur);
+
+				if (sjson::type(cur->second) == JsonValueType::OBJECT) {
+					// It must be a conditional expression: "expr()" = { ... }
+					RenderState::State states;
+					s32 err = parse_states(states, cur->second);
+					DATA_COMPILER_ENSURE(err == 0, _opts);
+
+					DynamicString expr(default_allocator());
+					expr = cur->first;
+					rs.push_back_states(expr, states);
 				}
+			}
+
+			return 0;
+		}
+
+		s32 parse_render_state(RenderState &rs, const char *json)
+		{
+			// gui = { <-- You are here.
+			//   states = {
+			//   }
+			//   ...
+			// }
+
+			TempAllocator4096 ta;
+			JsonObject obj(ta);
+			sjson::parse_object(obj, json);
+			s32 err = 0;
+
+			// Read inherit render state if any.
+			if (json_object::has(obj, "inherit")) {
+				sjson::parse_string(rs._inherit, obj["inherit"]);
+				DATA_COMPILER_ASSERT(hash_map::has(_render_states, rs._inherit)
+					, _opts
+					, "Unknown inherit render state: '%s'"
+					, rs._inherit.c_str()
+					);
+			}
+
+			// Read states from render state object itself; for backwards compatibility.
+			RenderState::State states_compat;
+			err = parse_states_compat(states_compat, obj);
+			DATA_COMPILER_ENSURE(err == 0, _opts);
+			rs.push_back_states("1", states_compat); // Always applied.
+
+			// Read states from new, dedicated "states" object.
+			if (json_object::has(obj, "states")) {
+				RenderState::State states;
+				err = parse_states(states, obj["states"]);
+				DATA_COMPILER_ENSURE(err == 0, _opts);
+				rs.push_back_states("1", states); // Always applied.
+
+				err = parse_conditional_states(rs, obj["states"]);
+			}
+
+			return 0;
+		}
+
+		s32 parse_render_states(const char *json)
+		{
+			TempAllocator4096 ta;
+			JsonObject render_states(ta);
+			sjson::parse_object(render_states, json);
+
+			auto cur = json_object::begin(render_states);
+			auto end = json_object::end(render_states);
+			for (; cur != end; ++cur) {
+				JSON_OBJECT_SKIP_HOLE(render_states, cur);
+
+				JsonObject obj(ta);
+				sjson::parse_object(obj, cur->second);
+
+				RenderState rs(default_allocator());
+				s32 err = parse_render_state(rs, cur->second);
+				DATA_COMPILER_ENSURE(err == 0, _opts);
 
 				DynamicString key(ta);
 				key = cur->first;
@@ -1167,13 +1430,15 @@ namespace shader_resource_internal
 					, render_state.c_str()
 					);
 
-				const RenderState rs_default;
-				const RenderState &rs = hash_map::get(_render_states, render_state, rs_default);
+				s32 err = 0;
+				RenderState::State state;
+				err = compile_render_state(state, render_state.c_str(), defines);
+				DATA_COMPILER_ENSURE(err == 0, _opts);
 
-				_opts.write(shader_name._id);                                // Shader name
-				_opts.write(rs.encode());                                    // Render state
-				compile_sampler_states(bgfx_shader.c_str());                 // Sampler states
-				s32 err = compile_bgfx_shader(bgfx_shader.c_str(), defines); // Shader code
+				_opts.write(shader_name._id);                            // Shader name
+				_opts.write(state.encode());                             // Render state
+				compile_sampler_states(bgfx_shader.c_str());             // Sampler states
+				err = compile_bgfx_shader(bgfx_shader.c_str(), defines); // Shader code
 				DATA_COMPILER_ENSURE(err == 0, _opts);
 			}
 
@@ -1326,6 +1591,31 @@ namespace shader_resource_internal
 
 			return 0;
 		}
+
+		s32 compile_render_state(RenderState::State &state, const char *render_state, const Vector<DynamicString> &defines)
+		{
+			TempAllocator512 taa;
+			DynamicString key(taa);
+			key = render_state;
+			const RenderState rs_default(default_allocator());
+			const RenderState &rs = hash_map::get(_render_states, key, rs_default);
+
+			// Compile inherited state if any.
+			if (!(rs._inherit == "")) {
+				s32 err = compile_render_state(state, rs._inherit.c_str(), defines);
+				DATA_COMPILER_ENSURE(err == 0, _opts);
+			}
+
+			// Evaluate expressions and apply states.
+			if (vector::size(rs._expressions) > 0) {
+				// Evaluate expressions.
+				for (u32 i = 0; i < vector::size(rs._expressions); ++i) {
+					state.overwrite_changed_properties(rs._states[i]);
+				}
+			}
+
+			return 0;
+		}
 	};
 
 	s32 compile(CompileOptions &opts)