""" RenderPipeline Copyright (c) 2014-2016 tobspr 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