2020-02-17 12:14:40 -08:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import moderngl
|
2020-06-29 18:17:18 -07:00
|
|
|
import numpy as np
|
|
|
|
import copy
|
2020-02-17 12:14:40 -08:00
|
|
|
|
2021-01-02 20:47:51 -08:00
|
|
|
from manimlib.utils.directories import get_shader_dir
|
2021-01-06 10:39:34 -08:00
|
|
|
from manimlib.utils.file_ops import find_file
|
2020-02-17 12:14:40 -08:00
|
|
|
|
|
|
|
# Mobjects that should be rendered with
|
|
|
|
# the same shader will be organized and
|
|
|
|
# clumped together based on keeping track
|
|
|
|
# of a dict holding all the relevant information
|
|
|
|
# to that shader
|
|
|
|
|
|
|
|
|
2020-06-29 18:17:18 -07:00
|
|
|
class ShaderWrapper(object):
|
|
|
|
def __init__(self,
|
|
|
|
vert_data=None,
|
|
|
|
vert_indices=None,
|
2021-01-05 23:14:16 -08:00
|
|
|
shader_folder=None,
|
2020-06-29 18:17:18 -07:00
|
|
|
uniforms=None, # A dictionary mapping names of uniform variables
|
|
|
|
texture_paths=None, # A dictionary mapping names to filepaths for textures.
|
|
|
|
depth_test=False,
|
2020-12-04 08:09:02 -08:00
|
|
|
render_primitive=moderngl.TRIANGLE_STRIP,
|
2020-06-29 18:17:18 -07:00
|
|
|
):
|
|
|
|
self.vert_data = vert_data
|
|
|
|
self.vert_indices = vert_indices
|
|
|
|
self.vert_attributes = vert_data.dtype.names
|
2021-01-05 23:14:16 -08:00
|
|
|
self.shader_folder = shader_folder
|
2020-06-29 18:17:18 -07:00
|
|
|
self.uniforms = uniforms or dict()
|
|
|
|
self.texture_paths = texture_paths or dict()
|
|
|
|
self.depth_test = depth_test
|
2020-12-04 08:09:02 -08:00
|
|
|
self.render_primitive = str(render_primitive)
|
2021-01-09 18:52:54 -08:00
|
|
|
self.init_program_code()
|
|
|
|
self.refresh_id()
|
2020-06-29 18:17:18 -07:00
|
|
|
|
|
|
|
def copy(self):
|
|
|
|
result = copy.copy(self)
|
|
|
|
result.vert_data = np.array(self.vert_data)
|
|
|
|
if result.vert_indices is not None:
|
|
|
|
result.vert_indices = np.array(self.vert_indices)
|
|
|
|
if self.uniforms:
|
|
|
|
result.uniforms = dict(self.uniforms)
|
|
|
|
if self.texture_paths:
|
|
|
|
result.texture_paths = dict(self.texture_paths)
|
|
|
|
return result
|
|
|
|
|
|
|
|
def is_valid(self):
|
|
|
|
return all([
|
|
|
|
self.vert_data is not None,
|
2021-01-09 18:52:54 -08:00
|
|
|
self.program_code["vertex_shader"] is not None,
|
|
|
|
self.program_code["fragment_shader"] is not None,
|
2020-06-29 18:17:18 -07:00
|
|
|
])
|
|
|
|
|
|
|
|
def get_id(self):
|
|
|
|
return self.id
|
|
|
|
|
|
|
|
def get_program_id(self):
|
|
|
|
return self.program_id
|
|
|
|
|
|
|
|
def create_id(self):
|
|
|
|
# A unique id for a shader
|
|
|
|
return "|".join(map(str, [
|
2021-01-09 18:52:54 -08:00
|
|
|
self.program_id,
|
2020-06-29 18:17:18 -07:00
|
|
|
self.uniforms,
|
|
|
|
self.texture_paths,
|
|
|
|
self.depth_test,
|
2020-12-04 08:09:02 -08:00
|
|
|
self.render_primitive,
|
2020-06-29 18:17:18 -07:00
|
|
|
]))
|
|
|
|
|
|
|
|
def refresh_id(self):
|
2021-01-09 18:52:54 -08:00
|
|
|
self.program_id = self.create_program_id()
|
2020-06-29 18:17:18 -07:00
|
|
|
self.id = self.create_id()
|
|
|
|
|
|
|
|
def create_program_id(self):
|
2021-01-09 18:52:54 -08:00
|
|
|
return hash("".join((
|
|
|
|
self.program_code[f"{name}_shader"] or ""
|
|
|
|
for name in ("vertex", "geometry", "fragment")
|
|
|
|
)))
|
2020-06-29 18:17:18 -07:00
|
|
|
|
2021-01-09 18:52:54 -08:00
|
|
|
def init_program_code(self):
|
2021-01-05 23:14:16 -08:00
|
|
|
def get_code(name):
|
|
|
|
return get_shader_code_from_file(
|
|
|
|
os.path.join(self.shader_folder, f"{name}.glsl")
|
|
|
|
)
|
|
|
|
|
2021-01-09 18:52:54 -08:00
|
|
|
self.program_code = {
|
2021-01-05 23:14:16 -08:00
|
|
|
"vertex_shader": get_code("vert"),
|
|
|
|
"geometry_shader": get_code("geom"),
|
|
|
|
"fragment_shader": get_code("frag"),
|
2020-06-29 18:17:18 -07:00
|
|
|
}
|
|
|
|
|
2021-01-09 18:52:54 -08:00
|
|
|
def get_program_code(self):
|
|
|
|
return self.program_code
|
|
|
|
|
|
|
|
def replace_code(self, old, new):
|
|
|
|
code_map = self.program_code
|
|
|
|
for (name, code) in code_map.items():
|
|
|
|
if code_map[name] is None:
|
|
|
|
continue
|
|
|
|
code_map[name] = re.sub(old, new, code_map[name])
|
|
|
|
self.refresh_id()
|
|
|
|
|
2020-06-29 18:17:18 -07:00
|
|
|
def combine_with(self, *shader_wrappers):
|
|
|
|
# Assume they are of the same type
|
|
|
|
if len(shader_wrappers) == 0:
|
|
|
|
return
|
|
|
|
if self.vert_indices is not None:
|
|
|
|
num_verts = len(self.vert_data)
|
|
|
|
indices_list = [self.vert_indices]
|
|
|
|
data_list = [self.vert_data]
|
|
|
|
for sw in shader_wrappers:
|
|
|
|
indices_list.append(sw.vert_indices + num_verts)
|
|
|
|
data_list.append(sw.vert_data)
|
|
|
|
num_verts += len(sw.vert_data)
|
|
|
|
self.vert_indices = np.hstack(indices_list)
|
|
|
|
self.vert_data = np.hstack(data_list)
|
|
|
|
else:
|
|
|
|
self.vert_data = np.hstack([self.vert_data, *[sw.vert_data for sw in shader_wrappers]])
|
|
|
|
return self
|
2020-06-14 19:01:04 -07:00
|
|
|
|
|
|
|
|
2021-01-13 00:10:17 -10:00
|
|
|
# For caching
|
|
|
|
filename_to_code_map = {}
|
|
|
|
|
2021-01-09 22:10:40 -08:00
|
|
|
|
2020-02-17 12:14:40 -08:00
|
|
|
def get_shader_code_from_file(filename):
|
|
|
|
if not filename:
|
|
|
|
return None
|
2021-01-13 00:10:17 -10:00
|
|
|
if filename in filename_to_code_map:
|
|
|
|
return filename_to_code_map[filename]
|
2020-02-17 12:14:40 -08:00
|
|
|
|
2021-01-05 23:14:16 -08:00
|
|
|
try:
|
2021-01-06 10:39:34 -08:00
|
|
|
filepath = find_file(
|
2021-01-05 23:14:16 -08:00
|
|
|
filename,
|
|
|
|
directories=[get_shader_dir(), "/"],
|
|
|
|
extensions=[],
|
|
|
|
)
|
|
|
|
except IOError:
|
|
|
|
return None
|
2020-02-17 12:14:40 -08:00
|
|
|
|
|
|
|
with open(filepath, "r") as f:
|
|
|
|
result = f.read()
|
|
|
|
|
|
|
|
# To share functionality between shaders, some functions are read in
|
|
|
|
# from other files an inserted into the relevant strings before
|
|
|
|
# passing to ctx.program for compiling
|
|
|
|
# Replace "#INSERT " lines with relevant code
|
|
|
|
insertions = re.findall(r"^#INSERT .*\.glsl$", result, flags=re.MULTILINE)
|
|
|
|
for line in insertions:
|
2021-01-05 23:14:16 -08:00
|
|
|
inserted_code = get_shader_code_from_file(
|
|
|
|
os.path.join("inserts", line.replace("#INSERT ", ""))
|
|
|
|
)
|
2020-02-17 12:14:40 -08:00
|
|
|
result = result.replace(line, inserted_code)
|
2021-01-13 00:10:17 -10:00
|
|
|
filename_to_code_map[filename] = result
|
2020-02-17 12:14:40 -08:00
|
|
|
return result
|
2021-01-09 18:52:54 -08:00
|
|
|
|
|
|
|
|
2021-01-10 14:12:15 -08:00
|
|
|
def get_colormap_code(rgb_list):
|
2021-01-10 08:35:06 -08:00
|
|
|
data = ",".join(
|
2021-01-10 14:12:15 -08:00
|
|
|
"vec3({}, {}, {})".format(*rgb)
|
|
|
|
for rgb in rgb_list
|
2021-01-09 18:52:54 -08:00
|
|
|
)
|
2021-01-10 14:12:15 -08:00
|
|
|
return f"vec3[{len(rgb_list)}]({data})"
|