def __load_texture(texture): name = texture.name_full # https://numpy.org/doc/stable/reference/arrays.interface.html class ArrayInterface(object): def __init__(self, typestr, length, address): self.__array_interface__ = { 'shape': (length, ), 'data': (address, False), 'typestr': typestr, 'version': 3, } w, h = texture.size channels = int(texture.channels) size = w * h * channels sRGB = texture.colorspace_settings.name == 'sRGB' if size == 0: return True buffer = MaltPipeline.get_bridge().get_texture_buffer(size) array_interface = ArrayInterface('<f4', size, ctypes.addressof(buffer)) #np_view = np.empty(size, dtype=np.float32) np_view = np.array(array_interface, copy=False) texture.pixels.foreach_get(np_view) MaltPipeline.get_bridge().load_texture(name, (w, h), channels, sRGB) return True
def render(self, depsgraph): scene = depsgraph.scene_eval scale = scene.render.resolution_percentage / 100.0 self.size_x = int(scene.render.resolution_x * scale) self.size_y = int(scene.render.resolution_y * scale) resolution = (self.size_x, self.size_y) overrides = ['Final Render'] if self.bridge is not MaltPipeline.get_bridge(depsgraph.scene.world): self.bridge = MaltPipeline.get_bridge() self.bridge_id = self.bridge.get_viewport_id() scene = self.get_scene(None, depsgraph, True, overrides) MaltPipeline.get_bridge().render(0, resolution, scene, True) buffers = None finished = False import time while not finished: buffers, finished, read_resolution = MaltPipeline.get_bridge( ).render_result(0) time.sleep(0.1) if finished: break size = self.size_x * self.size_y result = self.begin_result(0, 0, self.size_x, self.size_y, layer=depsgraph.view_layer.name) passes = result.layers[0].passes if 'Combined' in passes: combined_pass = passes['Combined'] rect_ptr = CBlenderMalt.get_rect_ptr(combined_pass.as_pointer()) ctypes.memmove(rect_ptr, buffers['COLOR'], size * 4 * 4) if 'Depth' in passes: depth_pass = passes['Depth'] rect_ptr = CBlenderMalt.get_rect_ptr(depth_pass.as_pointer()) ctypes.memmove(rect_ptr, buffers['DEPTH'], size * 4) self.end_result(result) # Delete the scene. Otherwise we get memory leaks. # Blender never deletes RenderEngine instances ??? del self.scene
def __init__(self): self.display_draw = None self.meshes = {} self.pipeline = MaltPipeline.get_pipeline().__class__() self.view_matrix = None self.request_new_frame = False self.profiling_data = io.StringIO()
def get_parameters(self): if '_RNA_UI' not in self.keys(): return {} rna = self.get_rna() parameters = {} for key in rna.keys(): if rna[key]['active'] == False: continue if rna[key]['type'] in (Type.INT, Type.FLOAT): parameters[key] = self[key] elif rna[key]['type'] == Type.BOOL: parameters[key] = self.bools[key].boolean elif rna[key]['type'] == Type.TEXTURE: texture = self.textures[key].texture if texture: texture.gl_load() parameters[key] = texture.bindcode else: parameters[key] = None elif rna[key]['type'] == Type.GRADIENT: #TODO: Only works for materials parameters[key] = get_color_ramp_texture(self.id_data, key) elif rna[key]['type'] == Type.MATERIAL: material = self.materials[key].material extension = self.materials[key].extension if material: shader = material.malt.get_shader(extension) if shader: parameters[key] = shader[ MaltPipeline.get_pipeline().__class__.__name__] continue parameters[key] = None return parameters
def get_pipeline_graph(self, graph_type=None): if graph_type is None: graph_type = self.graph_type bridge = MaltPipeline.get_bridge() if bridge and graph_type in bridge.graphs: return bridge.graphs[graph_type] return None
def update_source(self, context): #TODO: Store source locally (for linked data and deleted files) global SHADERS self.compiler_error = '' uniforms = {} if self.shader_source != '': path = bpy.path.abspath(self.shader_source) if os.path.exists(path): pipeline_material = {} SHADERS[self.shader_source] = pipeline_material pipelines = [MaltPipeline.get_pipeline() ] #TODO: get all active pipelines for pipeline in pipelines: pipeline_name = pipeline.__class__.__name__ pipeline_material[ pipeline_name] = pipeline.compile_material(path) for pass_name, shader in pipeline_material[ pipeline_name].items(): for uniform_name, uniform in shader.uniforms.items(): uniforms[uniform_name] = uniform if shader.error: self.compiler_error += pipeline_name + " : " + pass_name + " : " + shader.error if shader.validator: self.compiler_error += pipeline_name + " : " + pass_name + " : " + shader.validator else: self.compiler_error = 'Invalid file path' self.parameters.setup(uniforms)
def execute(self, context): new_pass = context.scene.world.malt_graph_types[ self.graph_type].custom_passes.add() new_pass.name = self.name for name in MaltPipeline.get_bridge().graphs[ self.graph_type].graph_io.keys(): new_pass.io.add().name = name return {'FINISHED'}
def get_gradient(color_ramp, material_name, name): full_name = material_name + '_' + name pixels = [] if material_name not in __GRADIENTS: __GRADIENTS[material_name] = {} gradients = __GRADIENTS[material_name] if name not in gradients: for i in range(0, __GRADIENT_RESOLUTION): pixel = color_ramp.evaluate(i * (1.0 / __GRADIENT_RESOLUTION)) pixels.extend(pixel) nearest = color_ramp.interpolation == 'CONSTANT' MaltPipeline.get_bridge().load_gradient(full_name, pixels, nearest) gradients[name] = full_name return full_name
def __init__(self): self.display_draw = None self.scene = Scene.Scene() self.view_matrix = None self.request_new_frame = True self.request_scene_update = True self.profiling_data = io.StringIO() self.bridge = MaltPipeline.get_bridge() self.bridge_id = self.bridge.get_viewport_id()
def update_source(self, context): #TODO: Store source locally (for linked data and deleted files) global SHADERS self.compiler_error = '' parameters = {} if self.shader_source != '': path = find_shader_path(self.shader_source) if path: compiled_material = MaltPipeline.get_pipeline( ).compile_material(path) if compiled_material is None: self.compiler_error = 'Invalid file' elif isinstance(compiled_material, str): self.compiler_error = compiled_material else: pipeline_material = {} SHADERS[self.shader_source] = pipeline_material pipelines = [MaltPipeline.get_pipeline() ] #TODO: get all active pipelines for pipeline in pipelines: pipeline_name = pipeline.__class__.__name__ pipeline_material[pipeline_name] = compiled_material for pass_name, shader in pipeline_material[ pipeline_name].items(): for uniform_name, uniform in shader.uniforms.items( ): parameters[ uniform_name] = Parameter.from_uniform( uniform) if shader.error: self.compiler_error += pipeline_name + " : " + pass_name + " : " + shader.error if shader.validator: self.compiler_error += pipeline_name + " : " + pass_name + " : " + shader.validator else: self.compiler_error = 'Invalid file path' if self.compiler_error != '': SHADERS[self.shader_source] = None self.parameters.setup({}) else: self.parameters.setup(parameters)
def get_parameter_enums(self, context=None): types = ['None'] bridge = MaltPipeline.get_bridge() if bridge and self.graph_type in bridge.graphs: graph = bridge.graphs[self.graph_type] if self.io_type in graph.graph_io.keys(): if self.is_output: types = graph.graph_io[self.io_type].dynamic_output_types else: types = graph.graph_io[self.io_type].dynamic_input_types return [(type, type, type) for type in types]
def setup_node_trees(): graphs = MaltPipeline.get_bridge().graphs for name, graph in graphs.items(): preload_menus(graph.structs, graph.functions) track_library_changes(force_update=True, is_initial_setup=True) for tree in bpy.data.node_groups: if tree.bl_idname == 'MaltTree': tree.reload_nodes() tree.update_ext(force_track_shader_changes=False) from BlenderMalt import MaltMaterial MaltMaterial.track_shader_changes()
def update_source(self, context): #print('UPDATE SOURCE') global _SHADER_PATHS if str(self.shader_source) not in _SHADER_PATHS: _SHADER_PATHS.append(str(self.shader_source)) self.compiler_error = '' if self.shader_source != '': path = bpy.path.abspath(self.shader_source) compiled_material = MaltPipeline.get_bridge().compile_material( path) self.compiler_error = compiled_material.compiler_error self.parameters.setup(compiled_material.parameters) else: self.parameters.setup({})
def track_shader_changes(force_update=False): if bpy.context.scene.render.engine != 'MALT' and force_update == False: return 1 global INITIALIZED global __TIMESTAMP global _SHADER_PATHS try: start_time = time.time() #print('TRACK UPDATES') needs_update = [] for material in bpy.data.materials: path = bpy.path.abspath(material.malt.shader_source, library=material.library) if path not in needs_update: if os.path.exists(path): stats = os.stat(path) if path not in _SHADER_PATHS or stats.st_mtime > __TIMESTAMP: if path not in _SHADER_PATHS: _SHADER_PATHS.append(path) needs_update.append(path) #print(needs_update) if len(needs_update) > 0: compiled_materials = MaltPipeline.get_bridge().compile_materials( needs_update) for material in bpy.data.materials: path = bpy.path.abspath(material.malt.shader_source, library=material.library) if path in compiled_materials.keys(): material.malt.compiler_error = compiled_materials[ path].compiler_error material.malt.parameters.setup( compiled_materials[path].parameters) for screen in bpy.data.screens: for area in screen.areas: area.tag_redraw() __TIMESTAMP = start_time INITIALIZED = True except: import traceback, logging as log log.error(traceback.format_exc()) return 1 #Track again in 1 second
def get_parameters(self): if '_RNA_UI' not in self.keys(): return {} rna = self.get_rna() parameters = {} for key in rna.keys(): if rna[key]['active'] == False: continue if rna[key]['type'] == 'SHADER': shader = self.shaders[key].get_shader() if shader: shader = shader[MaltPipeline.get_pipeline().__class__. __name__]['SHADER'] parameters[key] = shader elif rna[key]['type'] == GL.GL_BOOL: parameters[key] = self.bools[key].boolean else: parameters[key] = self[key] return parameters
def draw(self, context): import pprint from . import MaltPipeline stats = MaltPipeline.get_bridge().get_stats() for line in stats.splitlines(): self.layout.label(text=line)
def get_pipeline(self): if self.pipeline is None or self.pipeline.__class__ != MaltPipeline.get_pipeline( ).__class__: self.pipeline = MaltPipeline.get_pipeline().__class__() return self.pipeline
def view_draw(self, context, depsgraph): profiler = cProfile.Profile() global PROFILE if PROFILE: profiler.enable() if self.request_new_frame: self.profiling_data = io.StringIO() if self.bridge is not MaltPipeline.get_bridge(): #The Bridge has been reset self.bridge.free_viewport_id(self.bridge_id) self.bridge = MaltPipeline.get_bridge() self.bridge_id = self.bridge.get_viewport_id() self.request_new_frame = True self.request_scene_update = True overrides = [] if context.space_data.shading.type == 'MATERIAL': overrides.append('Preview') scene = self.get_scene(context, depsgraph, self.request_scene_update, overrides) resolution = context.region.width, context.region.height if self.request_new_frame: self.bridge.render(self.bridge_id, resolution, scene, self.request_scene_update) self.request_new_frame = False self.request_scene_update = False buffers, finished, read_resolution = self.bridge.render_result(self.bridge_id) pixels = buffers['COLOR'] if not finished: self.tag_redraw() if pixels is None or resolution != read_resolution: # Only render if resolution is the same as read_resolution. # This avoids visual glitches when the viewport is resizing. # The alternative would be locking when writing/reading the pixel buffer. return fbo = GL.gl_buffer(GL.GL_INT, 1) GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, fbo) render_texture = Texture(resolution, GL.GL_RGBA32F, GL.GL_FLOAT, pixels) self.bind_display_space_shader(depsgraph.scene_eval) if self.display_draw is None or self.display_draw.resolution != resolution: if self.display_draw: self.display_draw.gl_delete() self.display_draw = DisplayDraw(resolution) self.display_draw.draw(fbo, render_texture) self.unbind_display_space_shader() if PROFILE: profiler.disable() stats = pstats.Stats(profiler, stream=self.profiling_data) stats.strip_dirs() stats.sort_stats(pstats.SortKey.CUMULATIVE) stats.print_stats() print('PROFILE BEGIN--------------------------------------') print(self.profiling_data.getvalue()) print('PROFILE END--------------------------------------')
def load_mesh(object, name): use_split_faces = False #Use split_faces instead of calc_normals_split (Slightly faster) m = object.data if object.type != 'MESH' or bpy.context.mode == 'EDIT_MESH': m = object.to_mesh() if m is None or len(m.polygons) == 0: return None m.calc_loop_triangles() polys_ptr = ctypes.c_void_p(m.polygons[0].as_pointer()) has_flat_polys = CBlenderMalt.has_flat_polys(polys_ptr, len(m.polygons)) needs_split_normals = m.use_auto_smooth or m.has_custom_normals or has_flat_polys if needs_split_normals: if use_split_faces: m.split_faces() else: m.calc_normals_split() verts_ptr = ctypes.c_void_p(m.vertices[0].as_pointer()) loops_ptr = ctypes.c_void_p(m.loops[0].as_pointer()) loop_tris_ptr = ctypes.c_void_p(m.loop_triangles[0].as_pointer()) loop_count = len(m.loops) loop_tri_count = len(m.loop_triangles) material_count = max(1, len(m.materials)) positions = get_load_buffer('positions', ctypes.c_float, (loop_count * 3)) normals = get_load_buffer('normals', ctypes.c_int16, (loop_count * 3)) indices = [] indices_ptrs = (ctypes.c_void_p * material_count)() for i in range(material_count): indices.append( get_load_buffer('indices' + str(i), ctypes.c_uint32, (loop_tri_count * 3))) indices_ptrs[i] = ctypes.cast(indices[i], ctypes.c_void_p) #Create a new one each time so we don't have to care about zeroing the previous results indices_lengths = (ctypes.c_uint32 * material_count)() CBlenderMalt.retrieve_mesh_data(verts_ptr, loops_ptr, loop_count, loop_tris_ptr, loop_tri_count, polys_ptr, positions, normals, indices_ptrs, indices_lengths) if needs_split_normals and use_split_faces == False: #TODO: Find a way to get a direct pointer to custom normals normals = retrieve_array(m.loops, 'normal', 'f', ctypes.c_float, [0.0, 0.0, 0.0]) uvs = [] tangents = [] for i, uv_layer in enumerate(m.uv_layers): uv_ptr = ctypes.c_void_p(uv_layer.data[0].as_pointer()) uv = get_load_buffer('uv' + str(i), ctypes.c_float, loop_count * 2) CBlenderMalt.retrieve_mesh_uv(uv_ptr, loop_count, uv) uvs.append(uv) if (object.original.data.malt_parameters.bools['precomputed_tangents']. boolean): m.calc_tangents(uvmap=uv_layer.name) #calc_tangents is so slow there's no point in optimizing this packed_tangents = [ e for l in m.loops for e in (*l.tangent, l.bitangent_sign) ] tangents.append(packed_tangents) colors = [] for i, vertex_color in enumerate(m.vertex_colors): #Colors are already contiguous in memory, so we pass them directly to OpenGL color = (ctypes.c_uint8 * (loop_count * 4)).from_address( vertex_color.data[0].as_pointer()) colors.append(color) #TODO: Optimize. Create load buffers from bytearrays and retrieve them later mesh_data = { 'positions': bytearray(positions), 'indices': [bytearray(i) for i in indices], 'indices_lengths': [l for l in indices_lengths], 'normals': bytearray(normals), 'uvs': [bytearray(u) for u in uvs], 'tangents': [bytearray(t) for t in tangents], 'colors': [bytearray(c) for c in colors], } MaltPipeline.get_bridge().load_mesh(name, mesh_data) return [name for i in range(material_count)]
def track_library_changes(force_update=False, is_initial_setup=False): if bpy.context.scene.render.engine != 'MALT' and force_update == False: return 1 bridge = MaltPipeline.get_bridge() graphs = MaltPipeline.get_bridge().graphs updated_graphs = [] if is_initial_setup == False: for name, graph in graphs.items(): if graph.needs_reload(): updated_graphs.append(name) if len(updated_graphs) > 0: bridge.reload_graphs(updated_graphs) for graph_name in updated_graphs: graph = graphs[graph_name] preload_menus(graph.structs, graph.functions) global __LIBRARIES global __TIMESTAMP start_time = time.time() #purge unused libraries new_dic = {} for tree in bpy.data.node_groups: if isinstance(tree, MaltTree): src_path = tree.get_library_path() if src_path: if src_path in __LIBRARIES: new_dic[src_path] = __LIBRARIES[src_path] else: new_dic[src_path] = None __LIBRARIES = new_dic needs_update = set() for path, library in __LIBRARIES.items(): root_dir = os.path.dirname(path) if os.path.exists(path): if library is None: needs_update.add(path) else: for sub_path in library['paths']: sub_path = os.path.join(root_dir, sub_path) if os.path.exists(sub_path): # Don't track individual files granularly since macros can completely change them if os.stat(sub_path).st_mtime > __TIMESTAMP: needs_update.add(path) break if len(needs_update) > 0: results = MaltPipeline.get_bridge().reflect_source_libraries(needs_update) for path, reflection in results.items(): __LIBRARIES[path] = reflection preload_menus(reflection['structs'], reflection['functions']) if is_initial_setup == False and max(len(needs_update), len(updated_graphs)) > 0: for tree in bpy.data.node_groups: if isinstance(tree, MaltTree): src_path = tree.get_library_path() if tree.graph_type in updated_graphs or (src_path and src_path in needs_update): tree.reload_nodes() tree.update_ext(force_track_shader_changes=False) from BlenderMalt import MaltMaterial MaltMaterial.track_shader_changes() __TIMESTAMP = start_time return 0.1