192 lines
7.1 KiB
Python
192 lines
7.1 KiB
Python
"""
|
|
|
|
RenderPipeline
|
|
|
|
Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
|
|
"""
|
|
from rplibs.six import iteritems
|
|
|
|
from panda3d.core import PTAFloat, PTALVecBase3f, PTALMatrix4f, PTALVecBase2f
|
|
from panda3d.core import PTALVecBase4f, PTALMatrix3f, PTAInt, TypeRegistry, PTALVecBase2i
|
|
|
|
from rpcore.rpobject import RPObject
|
|
|
|
|
|
class SimpleInputBlock(RPObject):
|
|
|
|
""" Simplest possible uniform buffer which just stores a set of values.
|
|
This does not use any fancy uniform buffer objects under the hood, and
|
|
instead just sets every value as a shader input. """
|
|
|
|
def __init__(self, name):
|
|
""" Creates the ubo with the given name """
|
|
RPObject.__init__(self)
|
|
self._inputs = {}
|
|
self.name = name
|
|
|
|
def add_input(self, name, value):
|
|
""" Adds a new input to the UBO """
|
|
self._inputs[self.name + "." + name] = value
|
|
|
|
def bind_to(self, target):
|
|
""" Binds the UBO to a target """
|
|
target.set_shader_inputs(**self._inputs)
|
|
|
|
|
|
class GroupedInputBlock(RPObject):
|
|
|
|
""" Grouped uniform buffer which either uses PointerToArray's to efficiently
|
|
store and update the shader inputs, or in case of uniform buffer object (UBO)
|
|
support, uses these to pass the inputs to the shaders. """
|
|
|
|
# Keeps track of the global allocated input blocks to be able to assign
|
|
# a unique binding to all of them
|
|
UBO_BINDING_INDEX = 0
|
|
|
|
# Mapping of the pta-types to glsl types, and vice versa
|
|
PTA_MAPPINGS = {
|
|
PTAInt: "int",
|
|
PTAFloat: "float",
|
|
PTALVecBase2f: "vec2",
|
|
PTALVecBase2i: "ivec2",
|
|
PTALVecBase3f: "vec3",
|
|
PTALVecBase4f: "vec4",
|
|
PTALMatrix3f: "mat3",
|
|
PTALMatrix4f: "mat4",
|
|
}
|
|
|
|
def __init__(self, name):
|
|
""" Constructs the input block with a given name """
|
|
RPObject.__init__(self)
|
|
self.ptas = {}
|
|
self._inputs = {}
|
|
self.name = name
|
|
self.use_ubo = bool(TypeRegistry.ptr().find_type("GLUniformBufferContext"))
|
|
|
|
# Acquire a unique index for each UBO to store its binding
|
|
self.bind_id = GroupedInputBlock.UBO_BINDING_INDEX
|
|
GroupedInputBlock.UBO_BINDING_INDEX += 1
|
|
|
|
if self.bind_id == 0:
|
|
# Only output the bind support debug output once (for the first ubo)
|
|
self.debug("Native UBO support =", self.use_ubo)
|
|
|
|
def register_pta(self, name, input_type):
|
|
""" Registers a new input, type should be a glsl type """
|
|
pta = self.glsl_type_to_pta(input_type).empty_array(1)
|
|
self.ptas[name] = pta
|
|
if self.use_ubo:
|
|
self._inputs[self.name + "_UBO." + name] = pta
|
|
else:
|
|
self._inputs[self.name + "." + name] = pta
|
|
|
|
def pta_to_glsl_type(self, pta_handle):
|
|
""" Converts a PtaXXX to a glsl type """
|
|
for pta_type, glsl_type in iteritems(GroupedInputBlock.PTA_MAPPINGS):
|
|
if isinstance(pta_handle, pta_type):
|
|
return glsl_type
|
|
self.error("Unrecognized PTA type:", pta_handle)
|
|
|
|
def glsl_type_to_pta(self, glsl_type):
|
|
""" Converts a glsl type to a PtaXXX type """
|
|
for key, val in iteritems(GroupedInputBlock.PTA_MAPPINGS):
|
|
if val == glsl_type:
|
|
return key
|
|
self.error("Could not resolve GLSL type:", glsl_type)
|
|
|
|
def bind_to(self, target):
|
|
""" Binds all inputs of this UBO to the given target, which may be
|
|
either a RenderTarget or a NodePath """
|
|
|
|
target.set_shader_inputs(**self._inputs)
|
|
|
|
def update_input(self, name, value):
|
|
""" Updates an existing input """
|
|
self.ptas[name][0] = value
|
|
|
|
def get_input(self, name):
|
|
""" Returns the value of an existing input """
|
|
return self.ptas[name][0]
|
|
|
|
def generate_shader_code(self): # pylint: disable=too-many-branches
|
|
""" Generates the GLSL shader code to use the UBO """
|
|
|
|
content = "#pragma once\n\n"
|
|
content += "// Autogenerated by the render pipeline\n"
|
|
content += "// Do not edit! Your changes will be lost.\n\n"
|
|
|
|
structs = {}
|
|
inputs = []
|
|
|
|
for input_name, handle in iteritems(self.ptas):
|
|
parts = input_name.split(".")
|
|
|
|
# Single input, simply add it to the input list
|
|
if len(parts) == 1:
|
|
inputs.append(self.pta_to_glsl_type(handle) + " " + input_name + ";")
|
|
|
|
# Nested input, like scattering.sun_color
|
|
elif len(parts) == 2:
|
|
struct_name = parts[0]
|
|
actual_input_name = parts[1]
|
|
if struct_name in structs:
|
|
# Struct is already defined, add member definition
|
|
structs[struct_name].append(
|
|
self.pta_to_glsl_type(handle) + " " + actual_input_name + ";")
|
|
else:
|
|
# Construct a new struct and add it to the list of inputs
|
|
inputs.append(struct_name + "_UBOSTRUCT " + struct_name + ";")
|
|
structs[struct_name] = [
|
|
self.pta_to_glsl_type(handle) + " " + actual_input_name + ";"
|
|
]
|
|
|
|
# Nested input, like scattering.some_setting.sun_color, not supported yet
|
|
else:
|
|
self.warn("Structure definition too nested, not supported (yet):", input_name)
|
|
|
|
# Add structures
|
|
for struct_name, members in iteritems(structs):
|
|
content += "struct " + struct_name + "_UBOSTRUCT {\n"
|
|
for member in members:
|
|
content += " " * 4 + member + "\n"
|
|
content += "};\n\n"
|
|
|
|
# Add actual inputs
|
|
if len(inputs) < 1:
|
|
self.debug("No UBO inputs present for", self.name)
|
|
else:
|
|
if self.use_ubo:
|
|
|
|
content += "layout(shared, binding={}) uniform {}_UBO {{\n".format(
|
|
self.bind_id, self.name)
|
|
for ipt in inputs:
|
|
content += " " * 4 + ipt + "\n"
|
|
content += "} " + self.name + ";\n"
|
|
else:
|
|
content += "uniform struct {\n"
|
|
for ipt in inputs:
|
|
content += " " * 4 + ipt + "\n"
|
|
content += "} " + self.name + ";\n"
|
|
|
|
content += "\n"
|
|
return content
|