#include "glsl_emit_context.h"
#include "../graph/proc_node.h"
#include "../graph/proc_node_graph.h"
#include <godot_cpp/variant/utility_functions.hpp>
namespace godot {
String GlslEmitContext::type_name(SocketType p_type) {
switch (p_type) {
case SocketType::Float: return "float";
case SocketType::Int: return "int";
case SocketType::Bool: return "bool";
case SocketType::Vector: return "vec3";
case SocketType::Color: return "vec4";
case SocketType::Rotation: return "vec4";
case SocketType::Matrix: return "mat4";
// String and Geometry deliberately have no spelling.
default: return String();
}
}
String GlslEmitContext::literal(int p_node_idx, int p_socket) const {
if (graph == nullptr) {
return String();
}
Ref<ProcNode> node = graph->get_node(p_node_idx);
if (node.is_null() || p_socket < 0 || p_socket >= node->get_input_count()) {
return String();
}
return literal_value(node->get_input_def(p_socket).type, node->get_input_default(p_socket));
}
String GlslEmitContext::literal_value(SocketType p_type, const Variant &v) const {
switch (p_type) {
case SocketType::Float:
return String::num((double)v, 9);
case SocketType::Int: {
const int i = (int)v;
// The octave clamp is the reason integers are not simply printed:
// a graph evaluated at a coarse LOD must not run more octaves than
// the voxel size can carry, and the consumer supplies the limit.
return clamp_octaves ? (String("int(min(") + itos(i) + ", pn_max_octaves))") : itos(i);
}
case SocketType::Bool:
return ((bool)v) ? "true" : "false";
case SocketType::Vector: {
const Vector3 vec = v;
return String("vec3(") + String::num(vec.x, 9) + ", " + String::num(vec.y, 9) + ", " +
String::num(vec.z, 9) + ")";
}
case SocketType::Color: {
const Color c = v;
return String("vec4(") + String::num(c.r, 9) + ", " + String::num(c.g, 9) + ", " +
String::num(c.b, 9) + ", " + String::num(c.a, 9) + ")";
}
default:
return String();
}
}
String GlslEmitContext::param(int p_node_idx, int p_socket) const {
if (!push_constants || graph == nullptr) {
return in(p_node_idx, p_socket);
}
// ⛔ A PUSH CONSTANT IS ONE VALUE FOR EVERY VERTEX, so a socket driven by a
// per-vertex producer must not read one. When that producer is emitted into
// this same shader its result is a local, and reading the local is both
// correct and free. When it is NOT -- a single-node batch fed from outside
// -- the push constant is all there is, and the executor refuses the batch
// if the value behind it is a field.
const int src = graph->get_input_source_node(p_node_idx, p_socket);
if (src >= 0 && is_emitted(src)) {
return in(p_node_idx, p_socket);
}
Ref<ProcNode> node = graph->get_node(p_node_idx);
if (node.is_null()) {
return in(p_node_idx, p_socket);
}
const PackedInt32Array map = node->get_push_constant_sockets();
// Walk the map summing widths. A Vector takes three slots; everything else
// takes one. This is the same walk GraphExecutor does when it packs, and
// deliberately so -- a second spelling of it is the drift this ends.
int slot = 0;
for (int i = 0; i < map.size(); i++) {
const int sock = map[i];
const int width = (node->get_input_type(sock) == (int)SocketType::Vector) ? 3 : 1;
if (sock == p_socket) {
// The block is element_count + seven floats. A map that overruns it
// would silently read a neighbour's slot, so say so and fall back to
// the literal rather than emit a wrong address.
if (slot + width > 7) {
UtilityFunctions::push_error(
"proc-nodes: node ", p_node_idx, " socket ", p_socket,
" is declared at push-constant slot ", slot, " width ", width,
", past the seven-float block. Emitting its default instead.");
return in(p_node_idx, p_socket);
}
if (width == 3) {
return String("vec3(pc.param") + itos(slot) + ", pc.param" + itos(slot + 1) +
", pc.param" + itos(slot + 2) + ")";
}
return String("pc.param") + itos(slot);
}
slot += width;
}
// Not declared: an ordinary input.
return in(p_node_idx, p_socket);
}
String GlslEmitContext::out(int p_node_idx, int p_socket) const {
return String("n") + itos(p_node_idx) + "_s" + itos(p_socket);
}
String GlslEmitContext::declare(int p_node_idx, int p_socket) const {
if (graph == nullptr) {
return String();
}
Ref<ProcNode> node = graph->get_node(p_node_idx);
if (node.is_null() || p_socket < 0 || p_socket >= node->get_output_count()) {
return String();
}
const String t = type_name(node->get_output_def(p_socket).type);
if (t.is_empty()) {
return String();
}
return t + String(" ") + out(p_node_idx, p_socket);
}
String GlslEmitContext::in(int p_node_idx, int p_socket) const {
if (graph == nullptr) {
// An empty expression splices into the shader as a syntax error at best
// and a silently different field at worst, which is the failure this
// context exists to end. Say so.
UtilityFunctions::push_error(
"GlslEmitContext::in() called without a graph -- node ", p_node_idx,
" socket ", p_socket, ". The emitter cannot resolve an input.");
return String();
}
const int src_node = graph->get_input_source_node(p_node_idx, p_socket);
if (src_node < 0) {
return literal(p_node_idx, p_socket);
}
// ⛔ A LOCAL ONLY EXISTS IF ITS PRODUCER IS IN THIS SHADER.
//
// param() learned this; in() did not, and nodes that read their inputs
// directly -- Combine XYZ among them -- go straight through here. The batch
// for the bridged mountain held nodes 31 and 32 only, and Combine XYZ still
// emitted `vec3 n31_s0 = vec3(0.0, 0.0, n30_s0);` for a producer that was
// never written:
//
// ERROR: 0:46: 'n30_s0' : undeclared identifier
//
// Scoped to the BATCH path by push_constants. compile_to_function emits the
// whole graph and sets no emitted_nodes at all, so checking there would
// reduce every input to its default and silently gut the density shader.
if (push_constants && !is_emitted(src_node)) {
return literal(p_node_idx, p_socket);
}
const int src_socket = graph->get_input_source_socket(p_node_idx, p_socket);
const String expr = out(src_node, src_socket);
Ref<ProcNode> dst = graph->get_node(p_node_idx);
Ref<ProcNode> src = graph->get_node(src_node);
if (dst.is_null() || src.is_null()) {
return expr;
}
if (p_socket >= dst->get_input_count() || src_socket >= src->get_output_count()) {
return expr;
}
const SocketType want = dst->get_input_def(p_socket).type;
const SocketType have = src->get_output_def(src_socket).type;
if (want == have) {
return expr;
}
// COERCIONS MATCH THE CPU, NOT BLENDER. SocketValue::to_float() takes vec.x
// from a vector where Blender AVERAGES x, y and z, and a float read as a
// vector broadcasts. The emitter reproduces the CPU so that the two paths
// agree with each other; the asymmetry with Blender is a real defect, and
// hiding it inside a coercion here is how it would stop being findable.
switch (want) {
case SocketType::Float:
if (have == SocketType::Vector) return expr + String(".x");
if (have == SocketType::Color) return expr + String(".r");
if (have == SocketType::Int || have == SocketType::Bool) return String("float(") + expr + ")";
break;
case SocketType::Int:
if (have == SocketType::Float) return String("int(") + expr + ")";
if (have == SocketType::Bool) return String("int(") + expr + ")";
break;
case SocketType::Bool:
if (have == SocketType::Float) return String("(") + expr + " != 0.0)";
if (have == SocketType::Int) return String("(") + expr + " != 0)";
break;
case SocketType::Vector:
if (have == SocketType::Float) return String("vec3(") + expr + ")";
if (have == SocketType::Int) return String("vec3(float(") + expr + "))";
if (have == SocketType::Color) return expr + String(".rgb");
break;
case SocketType::Color:
if (have == SocketType::Float) return String("vec4(vec3(") + expr + "), 1.0)";
if (have == SocketType::Vector) return String("vec4(") + expr + ", 1.0)";
break;
default:
break;
}
return expr;
}
} // namespace godot