def load_daytime_overrides(self, override_path): """ Loads an override file for the daytime settings, which contains values to override the settings with """ overrides = load_yaml_file(override_path) if not overrides: self.warn("Failed to load daytime overrides") return for plugin_id, settings in iteritems(overrides["control_points"] or {}): for setting_id, control_points in iteritems(settings): if setting_id not in self.day_settings[plugin_id]: self.warn("Unkown daytime override:", plugin_id, ":", setting_id) continue self.day_settings[plugin_id][setting_id].set_control_points(control_points)
def save_daytime_overrides(self, override_file): """ Saves all time of day overrides to the given file """ output = "\n# Render Pipeline Time Of Day Configuration\n" output += "# Instead of editing this file, prefer to use the Time Of Day Editor\n" output += "# Any formatting and comments will be lost\n\n" output += "control_points:\n" for plugin_id, settings in iteritems(self.day_settings): if settings: output += " " * 4 + plugin_id + ":\n" for setting_id, setting_handle in iteritems(settings): output += " " * 8 + setting_id + ": " output += setting_handle.serialize() + "\n" with open(override_file, "w") as handle: handle.write(output)
def load_settings(self): """ Loads all day time settings from the plugin manager and registers them to the used input buffer """ for plugin_id, settings in iteritems(self._pipeline.plugin_mgr.day_settings): for setting, handle in iteritems(settings): setting_id = "{}.{}".format(plugin_id, setting) self._input_ubo.register_pta(setting_id, handle.glsl_type) self._setting_handles[setting_id] = handle self._pipeline.stage_mgr.input_blocks.append(self._input_ubo) # Generate UBO shader code shader_code = self._input_ubo.generate_shader_code() with open("/$$rptemp/$$daytime_config.inc.glsl", "w") as handle: handle.write(shader_code)
def save_daytime_overrides(self, override_file): """ Saves all time of day overrides to the given file """ output = "\n# Render Pipeline Time Of Day Configuration\n" output += "# Instead of editing this file, prefer to use the Time Of Day Editor\n" output += "# Any formatting and comments will be lost\n\n" output += "control_points:\n" for plugin_id, settings in iteritems(self.day_settings): if settings: output += " " * 4 + plugin_id + ":\n" for setting_id, setting_handle in iteritems(settings): output += " " * 8 + setting_id + ": " output += setting_handle.serialize() + "\n" with open(override_file, "w") as handle: handle.write(output)
def load_settings(self): """ Loads all day time settings from the plugin manager and registers them to the used input buffer """ for plugin_id, settings in iteritems( self._pipeline.plugin_mgr.day_settings): for setting, handle in iteritems(settings): setting_id = "{}.{}".format(plugin_id, setting) self._input_ubo.register_pta(setting_id, handle.glsl_type) self._setting_handles[setting_id] = handle self._pipeline.stage_mgr.input_blocks.append(self._input_ubo) # Generate UBO shader code shader_code = self._input_ubo.generate_shader_code() with open("/$$rptemp/$$daytime_config.inc.glsl", "w") as handle: handle.write(shader_code)
def should_be_visible(self, settings): """ Evaluates whether the plugin should be visible, taking all display conditions into account """ for key, val in iteritems(self.display_conditions): if settings[key].value != val: return False return True
def _generate_hash(cls, filename, options): """ Generates an unique hash for the effect. The effect hash is based on the filename and the configured options, and is ensured to make the effect unique. This is important to make sure the caching works as intended. All options not present in options are set to the default value""" # Set all options which are not present in the dict to its defaults options = { k: options.get(k, v) for k, v in iteritems(cls._DEFAULT_OPTIONS) } # Hash filename, make sure it has the right format and also resolve # it to an absolute path, to make sure that relative paths are cached # correctly (otherwise, specifying a different path to the same file # will cause a cache miss) filename = Filename(filename) filename.make_absolute() file_hash = str(hash(filename.to_os_generic())) # Hash the options, that is, sort the keys to make sure the values # are always in the same order, and then convert the flags to strings using # '1' for a set flag, and '0' for a unset flag options_hash = "".join([ "1" if options[key] else "0" for key in sorted(iterkeys(options)) ]) return file_hash + "-" + options_hash
def set_options(self, options): """ Sets the effect options, overriding the default options """ for key, val in iteritems(options): if key not in self._options: self.error("Unkown option:", key) continue self._options[key] = val
def load_daytime_overrides(self, override_path): """ Loads an override file for the daytime settings, which contains values to override the settings with """ overrides = load_yaml_file(override_path) if not overrides: self.warn("Failed to load daytime overrides") return for plugin_id, settings in iteritems(overrides["control_points"] or {}): for setting_id, control_points in iteritems(settings): if setting_id not in self.day_settings[plugin_id]: self.warn("Unkown daytime override:", plugin_id, ":", setting_id) continue self.day_settings[plugin_id][setting_id].set_control_points( control_points)
def _load_plugin_list(self): """ Reloads the whole plugin list """ print("Loading plugin list") # Reset selection self._current_plugin = None self._current_plugin_instance = None self._set_settings_visible(False) # Plugins are all plugins in the plugins directory self._plugin_mgr.unload() self._plugin_mgr.load() self.lst_plugins.clear() plugins = sorted(iteritems(self._plugin_mgr.instances), key=lambda plg: plg[1].name) for plugin_id, instance in plugins: item = QtGui.QListWidgetItem() item.setText(" " + instance.name) if self._plugin_mgr.is_plugin_enabled(plugin_id): item.setCheckState(QtCore.Qt.Checked) else: item.setCheckState(QtCore.Qt.Unchecked) item._plugin_id = plugin_id self.lst_plugins.addItem(item)
def _load_plugin_list(self): """ Reloads the whole plugin list """ print("Loading plugin list") # Reset selection self._current_plugin = None self._current_plugin_instance = None self._set_settings_visible(False) # Plugins are all plugins in the plugins directory self._plugin_mgr.unload() self._plugin_mgr.load() self.lst_plugins.clear() plugins = sorted(iteritems(self._plugin_mgr.instances), key=lambda plg: plg[1].name) for plugin_id, instance in plugins: item = QtGui.QListWidgetItem() item.setText(" " + instance.name) if self._plugin_mgr.is_plugin_enabled(plugin_id): item.setCheckState(QtCore.Qt.Checked) else: item.setCheckState(QtCore.Qt.Unchecked) item._plugin_id = plugin_id self.lst_plugins.addItem(item)
def set_options(self, options): """ Sets the effect options, overriding the default options """ for key, val in iteritems(options): if key not in self._options: self.error("Unkown option:", key) continue self._options[key] = val
def load_setting_overrides(self, override_path): """ Loads an override file for the settings, which contains values to override the settings with """ overrides = load_yaml_file(override_path) if not overrides: self.warn("Failed to load overrides") return self.enabled_plugins = set(overrides["enabled"] or []) for plugin_id, pluginsettings in iteritems(overrides["overrides"] or {}): if plugin_id not in self.settings: self.warn("Unkown plugin in plugin config:", plugin_id) continue for setting_id, setting_val in iteritems(pluginsettings or {}): if setting_id not in self.settings[plugin_id]: self.warn("Unkown override:", plugin_id, ":", setting_id) continue self.settings[plugin_id][setting_id].set_value(setting_val)
def _update_settings_list(self): """ Updates the list of visible settings """ self.settings_tree.clear() self._tree_widgets = [] first_item = None for plugin_id, plugin in iteritems(self._plugin_mgr.instances): daytime_settings = self._plugin_mgr.day_settings[plugin_id] if not daytime_settings: # Skip plugins with empty settings continue plugin_head = QTreeWidgetItem(self.settings_tree) plugin_head.setText(0, plugin.name) plugin_head.setFlags(Qt.ItemIsEnabled) font = QFont() font.setBold(True) if not self._plugin_mgr.is_plugin_enabled(plugin_id): plugin_head.setText(0, plugin.name) plugin_head.setFont(0, font) # Display all settings for setting, setting_handle in iteritems(daytime_settings): setting_item = QTreeWidgetItem(plugin_head) setting_item.setText(0, setting_handle.label) if PYQT_VERSION == 4: setting_item.setTextColor(0, QColor(150, 150, 150)) else: setting_item.setForeground(0, QColor(150, 150, 150)) setting_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) setting_item._setting_id = setting setting_item._setting_handle = setting_handle setting_item._plugin_id = plugin_id setting_item.setToolTip(0, setting_handle.description) setting_item.setToolTip(1, setting_handle.description) self._tree_widgets.append((setting_handle, setting_item)) if not first_item: first_item = setting_item self.settings_tree.expandAll() if first_item: self.settings_tree.setCurrentItem(first_item)
def _update_settings_list(self): """ Updates the list of visible settings """ self.settings_tree.clear() self._tree_widgets = [] first_item = None for plugin_id, plugin in iteritems(self._plugin_mgr.instances): daytime_settings = self._plugin_mgr.day_settings[plugin_id] if not daytime_settings: # Skip plugins with empty settings continue plugin_head = QTreeWidgetItem(self.settings_tree) plugin_head.setText(0, plugin.name) plugin_head.setFlags(Qt.ItemIsEnabled) font = QFont() font.setBold(True) if not self._plugin_mgr.is_plugin_enabled(plugin_id): plugin_head.setText(0, plugin.name) plugin_head.setFont(0, font) # Display all settings for setting, setting_handle in iteritems(daytime_settings): setting_item = QTreeWidgetItem(plugin_head) setting_item.setText(0, setting_handle.label) if PYQT_VERSION == 4: setting_item.setTextColor(0, QColor(150, 150, 150)) else: setting_item.setForeground(0, QColor(150, 150, 150)) setting_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) setting_item._setting_id = setting setting_item._setting_handle = setting_handle setting_item._plugin_id = plugin_id setting_item.setToolTip(0, setting_handle.description) setting_item.setToolTip(1, setting_handle.description) self._tree_widgets.append((setting_handle, setting_item)) if not first_item: first_item = setting_item self.settings_tree.expandAll() if first_item: self.settings_tree.setCurrentItem(first_item)
def _render_current_settings(self): """ Renders the current plugin settings """ settings = self._plugin_mgr.settings[self._current_plugin] # remove all rows while self.table_plugin_settings.rowCount() > 0: self.table_plugin_settings.removeRow(0) label_font = QtGui.QFont() label_font.setPointSize(10) label_font.setFamily("Segoe UI") desc_font = QtGui.QFont() desc_font.setPointSize(8) desc_font.setFamily("Segoe UI") for index, (name, handle) in enumerate(iteritems(settings)): if not handle.should_be_visible(settings): continue row_index = self.table_plugin_settings.rowCount() # Increase row count self.table_plugin_settings.insertRow( self.table_plugin_settings.rowCount()) label = QtGui.QLabel() label.setText(handle.label) label.setWordWrap(True) label.setFont(label_font) if handle.shader_runtime or handle.runtime: # label.setBackground(QtGui.QColor(200, 255, 200, 255)) label.setStyleSheet("background: rgba(162, 204, 128, 255);") else: label.setStyleSheet("background: rgba(230, 230, 230, 255);") label.setMargin(10) self.table_plugin_settings.setCellWidget(row_index, 0, label) item_default = QtGui.QTableWidgetItem() item_default.setText(str(handle.default)) item_default.setTextAlignment(QtCore.Qt.AlignCenter) self.table_plugin_settings.setItem(row_index, 1, item_default) setting_widget = self._get_widget_for_setting(name, handle) self.table_plugin_settings.setCellWidget(row_index, 2, setting_widget) label_desc = QtGui.QLabel() label_desc.setText(handle.description) label_desc.setWordWrap(True) label_desc.setFont(desc_font) label_desc.setStyleSheet("color: #555;padding: 5px;") self.table_plugin_settings.setCellWidget(row_index, 3, label_desc)
def save_overrides(self, override_file): """ Saves all overrides to the given file """ output = "\n# Render Pipeline Plugin Configuration\n" output += "# Instead of editing this file, prefer to use the Plugin Configurator\n" output += "# Any formatting and comments will be lost\n\n" output += "enabled:\n" sort_criteria = lambda pid: ("A" if self.is_plugin_enabled(pid) else "B") + pid for plugin_id in sorted(self.settings, key=sort_criteria): output += " {}- {}\n".format( " # " if plugin_id not in self.enabled_plugins else " ", plugin_id) output += "\n\noverrides:\n" for plugin_id, pluginsettings in sorted(iteritems(self.settings)): output += " " * 4 + plugin_id + ":\n" for setting_id, setting_handle in iteritems(pluginsettings): output += " " * 8 + "{}: {}\n".format(setting_id, setting_handle.value) output += "\n" with open(override_file, "w") as handle: handle.write(output)
def load_setting_overrides(self, override_path): """ Loads an override file for the settings, which contains values to override the settings with """ overrides = load_yaml_file(override_path) if not overrides: self.warn("Failed to load overrides") return self.enabled_plugins = set(overrides["enabled"] or []) for plugin_id, pluginsettings in iteritems(overrides["overrides"] or {}): if plugin_id not in self.settings: self.warn("Unkown plugin in plugin config:", plugin_id) continue for setting_id, setting_val in iteritems(pluginsettings or {}): if setting_id not in self.settings[plugin_id]: self.warn("Unkown override:", plugin_id, ":", setting_id) continue self.settings[plugin_id][setting_id].set_value(setting_val)
def bind_to(self, target): """ Binds all inputs of this UBO to the given target, which may be either a RenderTarget or a NodePath """ for pta_name, pta_handle in iteritems(self.ptas): if self.use_ubo: target.set_shader_input(self.name + "_UBO." + pta_name, pta_handle) else: target.set_shader_input(self.name + "." + pta_name, pta_handle)
def _render_current_settings(self): """ Renders the current plugin settings """ settings = self._plugin_mgr.settings[self._current_plugin] # remove all rows while self.table_plugin_settings.rowCount() > 0: self.table_plugin_settings.removeRow(0) label_font = QFont() label_font.setPointSize(10) label_font.setFamily("Roboto") desc_font = QFont() desc_font.setPointSize(8) desc_font.setFamily("Roboto") for index, (name, handle) in enumerate(iteritems(settings)): if not handle.should_be_visible(settings): continue row_index = self.table_plugin_settings.rowCount() # Increase row count self.table_plugin_settings.insertRow( self.table_plugin_settings.rowCount()) label = QLabel() label.setText(handle.label) label.setWordWrap(True) label.setFont(label_font) if not (handle.shader_runtime or handle.runtime): label.setStyleSheet("color: #999;") if handle.display_conditions: label.setStyleSheet(label.styleSheet() + "padding-left: 10px;") label.setMargin(10) self.table_plugin_settings.setCellWidget(row_index, 0, label) item_default = QTableWidgetItem() item_default.setText(str(handle.default)) item_default.setTextAlignment(Qt.AlignCenter) self.table_plugin_settings.setItem(row_index, 1, item_default) setting_widget = self._get_widget_for_setting(name, handle) self.table_plugin_settings.setCellWidget(row_index, 2, setting_widget) label_desc = QLabel() label_desc.setText(handle.description) label_desc.setWordWrap(True) label_desc.setFont(desc_font) label_desc.setStyleSheet("color: #555;padding: 5px;") self.table_plugin_settings.setCellWidget(row_index, 3, label_desc)
def update(self): """ Internal update method which updates all day time settings """ for setting_id, handle in iteritems(self._setting_handles): # XXX: Find a better interface for this. Without this fix, colors # are in the range 0 .. 255 in the shader. if isinstance(handle, ColorType): value = handle.get_value_at(self._time) else: value = handle.get_scaled_value_at(self._time) self._input_ubo.update_input(setting_id, value)
def bind_to(self, target): """ Binds all inputs of this UBO to the given target, which may be either a RenderTarget or a NodePath """ for pta_name, pta_handle in iteritems(self.ptas): if self.use_ubo: target.set_shader_input(self.name + "_UBO." + pta_name, pta_handle) else: target.set_shader_input(self.name + "." + pta_name, pta_handle)
def remove_target(self, target): """ Removes a previously registered target. This unregisters the target, as well as removing it from the list of assigned targets. """ target.remove() target_key = None for key, value_target in iteritems(self._targets): if target == value_target: target_key = key break del self._targets[target_key]
def update(self): """ Internal update method which updates all day time settings """ for setting_id, handle in iteritems(self._setting_handles): # XXX: Find a better interface for this. Without this fix, colors # are in the range 0 .. 255 in the shader. if isinstance(handle, ColorType): value = handle.get_value_at(self._time) else: value = handle.get_scaled_value_at(self._time) self._input_ubo.update_input(setting_id, value)
def _render_current_settings(self): """ Renders the current plugin settings """ settings = self._plugin_mgr.settings[self._current_plugin] # remove all rows while self.table_plugin_settings.rowCount() > 0: self.table_plugin_settings.removeRow(0) label_font = QFont() label_font.setPointSize(10) label_font.setFamily("Roboto") desc_font = QFont() desc_font.setPointSize(8) desc_font.setFamily("Roboto") for index, (name, handle) in enumerate(iteritems(settings)): if not handle.should_be_visible(settings): continue row_index = self.table_plugin_settings.rowCount() # Increase row count self.table_plugin_settings.insertRow(self.table_plugin_settings.rowCount()) label = QLabel() label.setText(handle.label) label.setWordWrap(True) label.setFont(label_font) if not (handle.shader_runtime or handle.runtime ): label.setStyleSheet("color: #999;") if handle.display_conditions: label.setStyleSheet(label.styleSheet() + "padding-left: 10px;") label.setMargin(10) self.table_plugin_settings.setCellWidget(row_index, 0, label) item_default = QTableWidgetItem() item_default.setText(str(handle.default)) item_default.setTextAlignment(Qt.AlignCenter) self.table_plugin_settings.setItem(row_index, 1, item_default) setting_widget = self._get_widget_for_setting(name, handle) self.table_plugin_settings.setCellWidget(row_index, 2, setting_widget) label_desc = QLabel() label_desc.setText(handle.description) label_desc.setWordWrap(True) label_desc.setFont(desc_font) label_desc.setStyleSheet("color: #555;padding: 5px;") self.table_plugin_settings.setCellWidget(row_index, 3, label_desc)
def _render_current_settings(self): """ Renders the current plugin settings """ settings = self._plugin_mgr.settings[self._current_plugin] # remove all rows while self.table_plugin_settings.rowCount() > 0: self.table_plugin_settings.removeRow(0) label_font = QtGui.QFont() label_font.setPointSize(10) label_font.setFamily("Segoe UI") desc_font = QtGui.QFont() desc_font.setPointSize(8) desc_font.setFamily("Segoe UI") for index, (name, handle) in enumerate(iteritems(settings)): if not handle.should_be_visible(settings): continue row_index = self.table_plugin_settings.rowCount() # Increase row count self.table_plugin_settings.insertRow(self.table_plugin_settings.rowCount()) label = QtGui.QLabel() label.setText(handle.label) label.setWordWrap(True) label.setFont(label_font) if handle.shader_runtime or handle.runtime: # label.setBackground(QtGui.QColor(200, 255, 200, 255)) label.setStyleSheet("background: rgba(162, 204, 128, 255);") else: label.setStyleSheet("background: rgba(230, 230, 230, 255);") label.setMargin(10) self.table_plugin_settings.setCellWidget(row_index, 0, label) item_default = QtGui.QTableWidgetItem() item_default.setText(str(handle.default)) item_default.setTextAlignment(QtCore.Qt.AlignCenter) self.table_plugin_settings.setItem(row_index, 1, item_default) setting_widget = self._get_widget_for_setting(name, handle) self.table_plugin_settings.setCellWidget(row_index, 2, setting_widget) label_desc = QtGui.QLabel() label_desc.setText(handle.description) label_desc.setWordWrap(True) label_desc.setFont(desc_font) label_desc.setStyleSheet("color: #555;padding: 5px;") self.table_plugin_settings.setCellWidget(row_index, 3, label_desc)
def init_defines(self): """ Initializes all plugin settings as a define, so they can be queried in a shader """ for plugin_id in self.enabled_plugins: pluginsettings = self.settings[plugin_id] self._pipeline.stage_mgr.defines["HAVE_PLUGIN_{}".format(plugin_id)] = 1 for setting_id, setting in iteritems(pluginsettings): if setting.shader_runtime or not setting.runtime: # Only store settings which either never change, or trigger # a shader reload when they change setting.add_defines(plugin_id, setting_id, self._pipeline.stage_mgr.defines)
def _construct_shader_from_data(self, pass_id, stage, template_src, data): # noqa # pylint: disable=too-many-branches """ Constructs a shader from a given dataset """ injects = {"defines": []} for key, val in iteritems(self._options): if isinstance(val, bool): val_str = "1" if val else "0" else: val_str = str(val) injects["defines"].append("#define OPT_{} {}".format( key.upper(), val_str)) injects["defines"].append("#define IN_" + stage.upper() + "_SHADER 1") injects["defines"].append("#define IN_" + pass_id.upper() + "_SHADER 1") injects["defines"].append("#define IN_RENDERING_PASS 1") # Parse dependencies if "dependencies" in data: injects["includes"] = [] for dependency in data["dependencies"]: include_str = "#pragma include \"{}\"".format(dependency) injects["includes"].append(include_str) del data["dependencies"] # Append aditional injects for key, val in iteritems(data): if val is None: self.warn("Empty insertion: '" + key + "'") continue if isinstance(val, (list, tuple)): self.warn( "Invalid syntax, you used a list but you should have used a string:" ) self.warn(val) continue injects[key] = injects.get(key, []) + [i for i in val.split("\n")] cache_key = self.effect_name + "@" + stage + "-" + pass_id + "@" + self.effect_hash return self._process_shader_template(template_src, cache_key, injects)
def save_overrides(self, override_file): """ Saves all overrides to the given file """ output = "\n# Render Pipeline Plugin Configuration\n" output += "# Instead of editing this file, prefer to use the Plugin Configurator\n" output += "# Any formatting and comments will be lost\n\n" output += "enabled:\n" sort_criteria = lambda pid: ("A" if self.is_plugin_enabled(pid) else "B") + pid for plugin_id in sorted(self.settings, key=sort_criteria): output += " {}- {}\n".format( " # " if plugin_id not in self.enabled_plugins else " ", plugin_id) output += "\n\noverrides:\n" for plugin_id, pluginsettings in sorted(iteritems(self.settings)): output += " " * 4 + plugin_id + ":\n" for setting_id, setting_handle in iteritems(pluginsettings): output += " " * 8 + "{}: {}\n".format(setting_id, setting_handle.value) output += "\n" with open(override_file, "w") as handle: handle.write(output)
def _parse_content(self, parsed_yaml): """ Internal method to construct the effect from a yaml object """ for key, val in iteritems(parsed_yaml): self._parse_shader_template(key, val) # Create missing programs using the default options if "vertex" not in parsed_yaml: self._parse_shader_template("vertex", {}) for key in self._PASSES: if key not in parsed_yaml: self._parse_shader_template(key, {})
def _parse_content(self, parsed_yaml): """ Internal method to construct the effect from a yaml object """ for key, val in iteritems(parsed_yaml): self._parse_shader_template(key, val) # Create missing programs using the default options if "vertex" not in parsed_yaml: self._parse_shader_template("vertex", {}) for key in self._PASSES: if key not in parsed_yaml: self._parse_shader_template(key, {})
def init_defines(self): """ Initializes all plugin settings as a define, so they can be queried in a shader """ for plugin_id in self.enabled_plugins: pluginsettings = self.settings[plugin_id] self._pipeline.stage_mgr.defines["HAVE_PLUGIN_{}".format( plugin_id)] = 1 for setting_id, setting in iteritems(pluginsettings): if setting.shader_runtime or not setting.runtime: # Only store settings which either never change, or trigger # a shader reload when they change setting.add_defines(plugin_id, setting_id, self._pipeline.stage_mgr.defines)
def _register_stage_result(self, stage): """ Registers all produced pipes, inputs and defines from the given stage, so they can be used by later stages. """ for pipe_name, pipe_data in (iteritems)(stage.produced_pipes): if isinstance(pipe_data, (SimpleInputBlock, GroupedInputBlock)): self.input_blocks[pipe_name] = pipe_data continue self.pipes[pipe_name] = pipe_data for define_name, data in iteritems(stage.produced_defines): if define_name in self.defines: self.warn("Stage", stage, "overrides define", define_name) self.defines[define_name] = data for input_name, data in iteritems(stage.produced_inputs): if input_name in self.inputs: self.warn("Stage", stage, "overrides input", input_name) if isinstance(data, (SimpleInputBlock, GroupedInputBlock)): self.input_blocks[input_name] = data continue self.inputs[input_name] = data
def _register_stage_result(self, stage): """ Registers all produced pipes, inputs and defines from the given stage, so they can be used by later stages. """ for pipe_name, pipe_data in (iteritems)(stage.produced_pipes): if isinstance(pipe_data, (SimpleInputBlock, GroupedInputBlock)): self.input_blocks[pipe_name] = pipe_data continue self.pipes[pipe_name] = pipe_data for define_name, data in iteritems(stage.produced_defines): if define_name in self.defines: self.warn("Stage", stage, "overrides define", define_name) self.defines[define_name] = data for input_name, data in iteritems(stage.produced_inputs): if input_name in self.inputs: self.warn("Stage", stage, "overrides input", input_name) if isinstance(data, (SimpleInputBlock, GroupedInputBlock)): self.input_blocks[input_name] = data continue self.inputs[input_name] = data
def _construct_shader_from_data(self, pass_id, stage, template_src, data): # noqa # pylint: disable=too-many-branches """ Constructs a shader from a given dataset """ injects = {"defines": []} for key, val in iteritems(self._options): if isinstance(val, bool): val_str = "1" if val else "0" else: val_str = str(val) injects["defines"].append("#define OPT_{} {}".format(key.upper(), val_str)) injects["defines"].append("#define IN_" + stage.upper() + "_SHADER 1") injects["defines"].append("#define IN_" + pass_id.upper() + "_SHADER 1") injects["defines"].append("#define IN_RENDERING_PASS 1") # Parse dependencies if "dependencies" in data: injects["includes"] = [] for dependency in data["dependencies"]: include_str = "#pragma include \"{}\"".format(dependency) injects["includes"].append(include_str) del data["dependencies"] # Append aditional injects for key, val in iteritems(data): if val is None: self.warn("Empty insertion: '" + key + "'") continue if isinstance(val, (list, tuple)): self.warn("Invalid syntax, you used a list but you should have used a string:") self.warn(val) continue injects[key] = injects.get(key, []) + [i for i in val.split("\n")] cache_key = self.effect_name + "@" + stage + "-" + pass_id + "@" + self.effect_hash return self._process_shader_template(template_src, cache_key, injects)
def _create_previous_pipes(self): """ Creates a target for each last-frame's pipe, any pipe starting with the prefix 'Previous::' has to be stored and copied each frame. """ if self.previous_pipes: self._prev_stage = UpdatePreviousPipesStage(self.pipeline) for prev_pipe, prev_tex in iteritems(self.previous_pipes): if prev_pipe not in self.pipes: self.error("Attempted to use previous frame data from pipe", prev_pipe, "- however, that pipe was never created!") return False # Tell the stage to transfer the data from the current pipe to # the current texture self._prev_stage.add_transfer(self.pipes[prev_pipe], prev_tex) self._prev_stage.create() self.stages.append(self._prev_stage)
def _create_previous_pipes(self): """ Creates a target for each last-frame's pipe, any pipe starting with the prefix 'Previous::' has to be stored and copied each frame. """ if self.previous_pipes: self._prev_stage = UpdatePreviousPipesStage(self.pipeline) for prev_pipe, prev_tex in iteritems(self.previous_pipes): if prev_pipe not in self.pipes: self.error("Attempted to use previous frame data from pipe", prev_pipe, "- however, that pipe was never created!") return False # Tell the stage to transfer the data from the current pipe to # the current texture self._prev_stage.add_transfer(self.pipes[prev_pipe], prev_tex) self._prev_stage.create() self._prev_stage.set_dimensions() self.stages.append(self._prev_stage)
def exec_compute_shader(self, shader_obj, shader_inputs, exec_size, workgroup_size=(16, 16, 1)): """ Executes a compute shader. The shader object should be a shader loaded with Shader.load_compute, the shader inputs should be a dict where the keys are the names of the shader inputs and the values are the inputs. The workgroup_size has to match the size defined in the compute shader """ ntx = int(math.ceil(exec_size[0] / workgroup_size[0])) nty = int(math.ceil(exec_size[1] / workgroup_size[1])) ntz = int(math.ceil(exec_size[2] / workgroup_size[2])) nodepath = NodePath("shader") nodepath.set_shader(shader_obj) for key, val in iteritems(shader_inputs): nodepath.set_shader_input(key, val) attr = nodepath.get_attrib(ShaderAttrib) Globals.base.graphicsEngine.dispatch_compute( (ntx, nty, ntz), attr, Globals.base.win.gsg)
def exec_compute_shader(self, shader_obj, shader_inputs, exec_size, workgroup_size=(16, 16, 1)): """ Executes a compute shader. The shader object should be a shader loaded with Shader.load_compute, the shader inputs should be a dict where the keys are the names of the shader inputs and the values are the inputs. The workgroup_size has to match the size defined in the compute shader """ ntx = int(math.ceil(exec_size[0] / workgroup_size[0])) nty = int(math.ceil(exec_size[1] / workgroup_size[1])) ntz = int(math.ceil(exec_size[2] / workgroup_size[2])) nodepath = NodePath("shader") nodepath.set_shader(shader_obj) for key, val in iteritems(shader_inputs): nodepath.set_shader_input(key, val) attr = nodepath.get_attrib(ShaderAttrib) Globals.base.graphicsEngine.dispatch_compute( (ntx, nty, ntz), attr, Globals.base.win.get_gsg())
def write_autoconfig(self): """ Writes the shader auto config, based on the defines specified by the different stages """ self.debug("Writing shader config") # Generate autoconfig as string output = "#pragma once\n\n" output += "// Autogenerated by the render pipeline\n" output += "// Do not edit! Your changes will be lost.\n\n" for key, value in sorted(iteritems(self.defines)): if isinstance(value, bool): value = 1 if value else 0 output += "#define " + key + " " + str(value) + "\n" try: with open("/$$rptemp/$$pipeline_shader_config.inc.glsl", "w") as handle: handle.write(output) except IOError as msg: self.error("Error writing shader autoconfig:", msg)
def write_autoconfig(self): """ Writes the shader auto config, based on the defines specified by the different stages """ self.debug("Writing shader config") # Generate autoconfig as string output = "#pragma once\n\n" output += "// Autogenerated by the render pipeline\n" output += "// Do not edit! Your changes will be lost.\n\n" for key, value in sorted(iteritems(self.defines)): if isinstance(value, bool): value = 1 if value else 0 output += "#define " + key + " " + str(value) + "\n" try: with open("/$$rptemp/$$pipeline_shader_config.inc.glsl", "w") as handle: handle.write(output) except IOError as msg: self.error("Error writing shader autoconfig:", msg)
def _generate_hash(cls, filename, options): """ Generates an unique hash for the effect. The effect hash is based on the filename and the configured options, and is ensured to make the effect unique. This is important to make sure the caching works as intended. All options not present in options are set to the default value""" # Set all options which are not present in the dict to its defaults options = {k: options.get(k, v) for k, v in iteritems(cls._DEFAULT_OPTIONS)} # Hash filename, make sure it has the right format and also resolve # it to an absolute path, to make sure that relative paths are cached # correctly (otherwise, specifying a different path to the same file # will cause a cache miss) filename = Filename(filename) filename.make_absolute() file_hash = str(hash(filename.to_os_generic())) # Hash the options, that is, sort the keys to make sure the values # are always in the same order, and then convert the flags to strings using # '1' for a set flag, and '0' for a unset flag options_hash = ''.join(['1' if options[key] else '-' for key in sorted(iterkeys(options))]) return file_hash + "-" + options_hash
def set_shader_inputs(self, **inputs): set_shader_input = self.set_shader_input for args in iteritems(inputs): set_shader_input(*args)
def _construct_shader_from_data(self, shader_id, default_template, data): """ Constructs a shader from a given dataset """ injects = {} template_src = default_template # Check the template if "template" in data: data_template = data["template"] if data_template != "default": template_src = data_template # Add defines to the injects injects['defines'] = [] for key, val in iteritems(self._options): val_str = str(val) if isinstance(val, bool): val_str = "1" if val else "0" injects['defines'].append("#define OPT_" + key.upper() + " " + val_str) # Parse dependencies if "dependencies" in data: injects["includes"] = [] for dependency in data["dependencies"]: include_str = "#pragma include \"" + dependency + "\"" injects["includes"].append(include_str) # Append inouts if "inout" in data: injects["inout"] = data["inout"] # Append aditional injects if "inject" in data: data_injects = data["inject"] for key, val in iteritems(data_injects): if val is None: self.warn("Empty insertion: '" + key + "'") continue if isinstance(val, (list, tuple)): self.warn( "Invalid syntax, you used a list but you should have used a string:" ) self.warn(val) continue val = [i for i in val.split("\n")] if key in injects: injects[key] += val else: injects[key] = val # Check for unrecognized keys for key in data: if key not in ["dependencies", "inout", "inject", "template"]: self.warn("Unrecognized key:", key) shader = ShaderTemplate( template_src, self._effect_name + "@" + shader_id + "@" + self._effect_hash) for key, val in iteritems(injects): shader.register_template_value(key, val) return shader.create()
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 generate_shader_code(self): """ 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
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 _populate_content(self): # pylint: disable=too-many-branches,too-many-statements """ Reads the pipes and stages from the stage manager and renders those into the window """ self._created = True self._pipe_node = self._content_node.attach_new_node("pipes") self._pipe_node.set_scale(1, 1, -1) self._stage_node = self._content_node.attach_new_node("stages") current_pipes = [] pipe_pixel_size = 3 pipe_height = 100 # Generate stages for offs, stage in enumerate(self._STAGE_MGR.stages): node = self._content_node.attach_new_node("stage") node.set_pos(220 + offs * 200.0, 0, 20) node.set_scale(1, 1, -1) DirectFrame(parent=node, frameSize=(10, 150, 0, -3600), frameColor=(0.2, 0.2, 0.2, 1)) Text(text=str(stage.debug_name.replace("Stage", "")), parent=node, x=20, y=25, size=15) for output_pipe, pipe_tex in iteritems(stage.produced_pipes): pipe_idx = 0 r, g, b = rgb_from_string(output_pipe) if output_pipe in current_pipes: pipe_idx = current_pipes.index(output_pipe) else: current_pipes.append(output_pipe) pipe_idx = len(current_pipes) - 1 DirectFrame(parent=node, frameSize=(0, 8000, pipe_pixel_size / 2, -pipe_pixel_size / 2), frameColor=(r, g, b, 1), pos=(10, 1, -95 - pipe_idx * pipe_height)) w = 160 h = Globals.native_resolution.y /\ float(Globals.native_resolution.x) * w DirectFrame(parent=node, frameSize=(-pipe_pixel_size, w + pipe_pixel_size, h / 2 + pipe_pixel_size, -h / 2 - pipe_pixel_size), frameColor=(r, g, b, 1), pos=(0, 1, -95 - pipe_idx * pipe_height)) if isinstance(pipe_tex, (list, tuple)): pipe_tex = pipe_tex[0] if isinstance(pipe_tex, (SimpleInputBlock, GroupedInputBlock)): icon_file = "/$$rp/data/gui/icon_ubo.png" elif pipe_tex.get_z_size() > 1: icon_file = "/$$rp/data/gui/icon_texture.png" elif pipe_tex.get_texture_type() == Texture.TT_buffer_texture: icon_file = "/$$rp/data/gui/icon_buffer_texture.png" else: icon_file = None preview = Sprite( image=pipe_tex, parent=node, x=0, y=50 + pipe_idx * pipe_height, w=w, h=h, any_filter=False, transparent=False) preview_shader = DisplayShaderBuilder.build(pipe_tex, int(w), int(h)) preview.set_shader(preview_shader) preview.set_shader_inputs( mipmap=0, slice=0, brightness=1, tonemap=False) if icon_file: Sprite(image=icon_file, parent=node, x=55, y=65 + pipe_idx * pipe_height, w=48, h=48, near_filter=False, transparent=True) if isinstance(pipe_tex, (SimpleInputBlock, GroupedInputBlock)): tex_desc = "UBO" else: tex_desc = pipe_tex.format_texture_type(pipe_tex.get_texture_type()) tex_desc += " - " + pipe_tex.format_format(pipe_tex.get_format()).upper() Text(text=tex_desc, parent=node, x=55 + 48 / 2, y=130 + pipe_idx * pipe_height, color=Vec3(0.2), size=12, align="center") for input_pipe in stage.required_pipes: if input_pipe not in current_pipes: self.warn("Pipe not found:", input_pipe) continue idx = current_pipes.index(input_pipe) r, g, b = rgb_from_string(input_pipe) DirectFrame(parent=node, frameSize=(0, 10, 40, -40), frameColor=(r, g, b, 1), pos=(5, 1, -95 - idx * pipe_height)) self._pipe_descriptions = self._content_node.attach_new_node( "PipeDescriptions") self._pipe_descriptions.set_scale(1, 1, -1) DirectFrame(parent=self._pipe_descriptions, frameSize=(0, 190, 0, -5000), frameColor=(0.1, 0.1, 0.1, 1.0)) # Generate the pipe descriptions for idx, pipe in enumerate(current_pipes): r, g, b = rgb_from_string(pipe) DirectFrame(parent=self._pipe_descriptions, frameSize=(0, 180, -95, -135), frameColor=(r, g, b, 1.0), pos=(0, 1, -idx * pipe_height)) Text(parent=self._pipe_descriptions, text=pipe, x=42, y=121 + idx * pipe_height, size=15, color=Vec3(0.1)) Sprite(parent=self._pipe_descriptions, x=9, y=103 + idx * pipe_height, image="/$$rp/data/gui/icon_pipe.png", transparent=True, near_filter=False)
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 generate_shader_code(self): """ 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
def bind_to(self, target): """ Binds the UBO to a target """ for key, val in iteritems(self.inputs): target.set_shader_input(self.name + "." + key, val)
def _construct_shader_from_data(self, shader_id, default_template, data): """ Constructs a shader from a given dataset """ injects = {} template_src = default_template # Check the template if "template" in data: data_template = data["template"] if data_template != "default": template_src = data_template # Add defines to the injects injects['defines'] = [] for key, val in iteritems(self._options): val_str = str(val) if isinstance(val, bool): val_str = "1" if val else "0" injects['defines'].append("#define OPT_" + key.upper() + " " + val_str) # Parse dependencies if "dependencies" in data: injects["includes"] = [] for dependency in data["dependencies"]: include_str = "#pragma include \"" + dependency + "\"" injects["includes"].append(include_str) # Append inouts if "inout" in data: injects["inout"] = data["inout"] # Append aditional injects if "inject" in data: data_injects = data["inject"] for key, val in iteritems(data_injects): if val is None: self.warn("Empty insertion: '" + key + "'") continue if isinstance(val, (list, tuple)): self.warn("Invalid syntax, you used a list but you should have used a string:") self.warn(val) continue val = [i for i in val.split("\n")] if key in injects: injects[key] += val else: injects[key] = val # Check for unrecognized keys for key in data: if key not in ["dependencies", "inout", "inject", "template"]: self.warn("Unrecognized key:", key) shader = ShaderTemplate( template_src, self._effect_name + "@" + shader_id + "@" + self._effect_hash) for key, val in iteritems(injects): shader.register_template_value(key, val) return shader.create()
def _populate_content(self): # pylint: disable=too-many-branches,too-many-statements """ Reads the pipes and stages from the stage manager and renders those into the window """ self._created = True self._pipe_node = self._content_node.attach_new_node("pipes") self._pipe_node.set_scale(1, 1, -1) self._stage_node = self._content_node.attach_new_node("stages") current_pipes = [] pipe_pixel_size = 3 pipe_height = 100 # Generate stages for offs, stage in enumerate(self._STAGE_MGR.stages): node = self._content_node.attach_new_node("stage") node.set_pos(220 + offs * 200.0, 0, 20) node.set_scale(1, 1, -1) DirectFrame(parent=node, frameSize=(10, 150, 0, -3600), frameColor=(0.2, 0.2, 0.2, 1)) Text(text=str(stage.debug_name.replace("Stage", "")), parent=node, x=20, y=25, size=15) for output_pipe, pipe_tex in iteritems(stage.produced_pipes): pipe_idx = 0 r, g, b = rgb_from_string(output_pipe) if output_pipe in current_pipes: pipe_idx = current_pipes.index(output_pipe) else: current_pipes.append(output_pipe) pipe_idx = len(current_pipes) - 1 DirectFrame(parent=node, frameSize=(0, 8000, pipe_pixel_size / 2, -pipe_pixel_size / 2), frameColor=(r, g, b, 1), pos=(10, 1, -95 - pipe_idx * pipe_height)) w = 160 h = Globals.native_resolution.y /\ float(Globals.native_resolution.x) * w DirectFrame(parent=node, frameSize=(-pipe_pixel_size, w + pipe_pixel_size, h / 2 + pipe_pixel_size, -h / 2 - pipe_pixel_size), frameColor=(r, g, b, 1), pos=(0, 1, -95 - pipe_idx * pipe_height)) if isinstance(pipe_tex, (list, tuple)): pipe_tex = pipe_tex[0] if isinstance(pipe_tex, (SimpleInputBlock, GroupedInputBlock)): icon_file = "/$$rp/data/gui/icon_ubo.png" elif pipe_tex.get_z_size() > 1: icon_file = "/$$rp/data/gui/icon_texture.png" elif pipe_tex.get_texture_type() == Texture.TT_buffer_texture: icon_file = "/$$rp/data/gui/icon_buffer_texture.png" else: icon_file = None preview = Sprite(image=pipe_tex, parent=node, x=0, y=50 + pipe_idx * pipe_height, w=w, h=h, any_filter=False, transparent=False) preview_shader = DisplayShaderBuilder.build( pipe_tex, int(w), int(h)) preview.set_shader(preview_shader) preview.set_shader_inputs(mipmap=0, slice=0, brightness=1, tonemap=False) if icon_file: Sprite(image=icon_file, parent=node, x=55, y=65 + pipe_idx * pipe_height, w=48, h=48, near_filter=False, transparent=True) if isinstance(pipe_tex, (SimpleInputBlock, GroupedInputBlock)): tex_desc = "UBO" else: tex_desc = pipe_tex.format_texture_type( pipe_tex.get_texture_type()) tex_desc += " - " + pipe_tex.format_format( pipe_tex.get_format()).upper() Text(text=tex_desc, parent=node, x=55 + 48 / 2, y=130 + pipe_idx * pipe_height, color=Vec3(0.2), size=12, align="center") for input_pipe in stage.required_pipes: if input_pipe not in current_pipes: self.warn("Pipe not found:", input_pipe) continue idx = current_pipes.index(input_pipe) r, g, b = rgb_from_string(input_pipe) DirectFrame(parent=node, frameSize=(0, 10, 40, -40), frameColor=(r, g, b, 1), pos=(5, 1, -95 - idx * pipe_height)) self._pipe_descriptions = self._content_node.attach_new_node( "PipeDescriptions") self._pipe_descriptions.set_scale(1, 1, -1) DirectFrame(parent=self._pipe_descriptions, frameSize=(0, 190, 0, -5000), frameColor=(0.1, 0.1, 0.1, 1.0)) # Generate the pipe descriptions for idx, pipe in enumerate(current_pipes): r, g, b = rgb_from_string(pipe) DirectFrame(parent=self._pipe_descriptions, frameSize=(0, 180, -95, -135), frameColor=(r, g, b, 1.0), pos=(0, 1, -idx * pipe_height)) Text(parent=self._pipe_descriptions, text=pipe, x=42, y=121 + idx * pipe_height, size=15, color=Vec3(0.1)) Sprite(parent=self._pipe_descriptions, x=9, y=103 + idx * pipe_height, image="/$$rp/data/gui/icon_pipe.png", transparent=True, near_filter=False)
def set_shader_inputs(self, **inputs): set_shader_input = self.set_shader_input for args in iteritems(inputs): set_shader_input(*args)
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 update(self): """ Internal update method which updates all day time settings """ for setting_id, handle in iteritems(self._setting_handles): value = handle.get_scaled_value_at(self._time) self._input_ubo.update_input(setting_id, value)
def bind_to(self, target): """ Binds the UBO to a target """ for key, val in iteritems(self.inputs): target.set_shader_input(self.name + "." + key, val)