armory/blender/arm/material/shader.py

457 lines
16 KiB
Python
Raw Normal View History

2017-11-20 14:32:36 +01:00
import arm.utils
2016-12-13 01:09:17 +01:00
if arm.is_reload(__name__):
2021-08-04 22:49:38 +02:00
arm.utils = arm.reload_module(arm.utils)
else:
arm.enable_reload(__name__)
2021-08-04 22:49:38 +02:00
# Type aliases for type hints to make it easier to see which kind of
# shader data type is stored in a string
floatstr = str
vec2str = str
vec3str = str
vec4str = str
2018-10-26 19:45:07 +02:00
class ShaderData:
def __init__(self, material):
self.material = material
self.contexts = []
self.global_elems = [] # bone, weight, ipos, irot, iscl
self.sd = {}
self.data = {'shader_datas': [self.sd]}
2018-10-26 19:45:07 +02:00
self.matname = arm.utils.safesrc(arm.utils.asset_name(material))
self.sd['name'] = self.matname + '_data'
self.sd['contexts'] = []
2020-11-17 18:54:53 +01:00
def add_context(self, props) -> 'ShaderContext':
2018-10-26 19:45:07 +02:00
con = ShaderContext(self.material, self.sd, props)
if con not in self.sd['contexts']:
for elem in self.global_elems:
2018-12-14 15:27:43 +01:00
con.add_elem(elem['name'], elem['data'])
2018-10-26 19:45:07 +02:00
self.sd['contexts'].append(con.get())
return con
def get(self):
return self.data
class ShaderContext:
def __init__(self, material, shader_data, props):
self.vert = None
self.frag = None
self.geom = None
self.tesc = None
self.tese = None
self.material = material
self.matname = arm.utils.safesrc(arm.utils.asset_name(material))
self.shader_data = shader_data
self.data = {}
self.data['name'] = props['name']
self.data['depth_write'] = props['depth_write']
self.data['compare_mode'] = props['compare_mode']
self.data['cull_mode'] = props['cull_mode']
2018-12-14 15:27:43 +01:00
if 'vertex_elements' in props:
self.data['vertex_elements'] = props['vertex_elements']
2018-10-26 19:45:07 +02:00
else:
2018-12-14 15:27:43 +01:00
self.data['vertex_elements'] = [{'name': 'pos', 'data': 'short4norm'}, {'name': 'nor', 'data': 'short2norm'}] # (p.xyz, n.z), (n.xy)
2018-10-26 19:45:07 +02:00
if 'blend_source' in props:
self.data['blend_source'] = props['blend_source']
if 'blend_destination' in props:
self.data['blend_destination'] = props['blend_destination']
if 'blend_operation' in props:
self.data['blend_operation'] = props['blend_operation']
if 'alpha_blend_source' in props:
self.data['alpha_blend_source'] = props['alpha_blend_source']
if 'alpha_blend_destination' in props:
self.data['alpha_blend_destination'] = props['alpha_blend_destination']
if 'alpha_blend_operation' in props:
self.data['alpha_blend_operation'] = props['alpha_blend_operation']
if 'color_writes_red' in props:
self.data['color_writes_red'] = props['color_writes_red']
if 'color_writes_green' in props:
self.data['color_writes_green'] = props['color_writes_green']
if 'color_writes_blue' in props:
self.data['color_writes_blue'] = props['color_writes_blue']
if 'color_writes_alpha' in props:
self.data['color_writes_alpha'] = props['color_writes_alpha']
if 'color_attachments' in props:
self.data['color_attachments'] = props['color_attachments']
2018-10-26 19:45:07 +02:00
self.data['texture_units'] = []
self.tunits = self.data['texture_units']
self.data['constants'] = []
self.constants = self.data['constants']
2018-12-14 15:27:43 +01:00
def add_elem(self, name, data):
elem = { 'name': name, 'data': data }
if elem not in self.data['vertex_elements']:
self.data['vertex_elements'].append(elem)
2018-10-26 19:45:07 +02:00
self.sort_vs()
def sort_vs(self):
vs = []
2021-10-18 19:54:00 +02:00
ar = ['pos', 'nor', 'tex', 'tex1', 'morph', 'col', 'tang', 'bone', 'weight', 'ipos', 'irot', 'iscl']
for ename in ar:
2018-10-26 19:45:07 +02:00
elem = self.get_elem(ename)
if elem != None:
vs.append(elem)
2018-12-14 15:27:43 +01:00
self.data['vertex_elements'] = vs
2018-10-26 19:45:07 +02:00
def is_elem(self, name):
2018-12-14 15:27:43 +01:00
for elem in self.data['vertex_elements']:
2018-10-26 19:45:07 +02:00
if elem['name'] == name:
return True
return False
def get_elem(self, name):
2018-12-14 15:27:43 +01:00
for elem in self.data['vertex_elements']:
2018-10-26 19:45:07 +02:00
if elem['name'] == name:
return elem
return None
def get(self):
return self.data
2021-06-14 13:36:30 +02:00
def add_constant(self, ctype, name, link=None, default_value=None, is_arm_mat_param=None):
2018-10-26 19:45:07 +02:00
for c in self.constants:
if c['name'] == name:
return
2021-06-14 13:36:30 +02:00
c = { 'name': name, 'type': ctype}
if link is not None:
2018-10-26 19:45:07 +02:00
c['link'] = link
2021-06-14 13:36:30 +02:00
if default_value is not None:
if ctype == 'float':
c['float'] = default_value
if ctype == 'vec3':
c['vec3'] = default_value
if is_arm_mat_param is not None:
c['is_arm_parameter'] = 'true'
2018-10-26 19:45:07 +02:00
self.constants.append(c)
def add_texture_unit(self, name, link=None, is_image=None,
addr_u=None, addr_v=None,
2021-06-14 13:36:30 +02:00
filter_min=None, filter_mag=None, mipmap_filter=None,
default_value=None, is_arm_mat_param=None):
2018-10-26 19:45:07 +02:00
for c in self.tunits:
if c['name'] == name:
return
c = {'name': name}
if link is not None:
2018-10-26 19:45:07 +02:00
c['link'] = link
if is_image is not None:
2018-10-26 19:45:07 +02:00
c['is_image'] = is_image
if addr_u is not None:
c['addressing_u'] = addr_u
if addr_v is not None:
c['addressing_v'] = addr_v
if filter_min is not None:
c['filter_min'] = filter_min
if filter_mag is not None:
c['filter_mag'] = filter_mag
if mipmap_filter is not None:
c['mipmap_filter'] = mipmap_filter
2021-06-14 13:36:30 +02:00
if default_value is not None:
c['default_image_file'] = default_value
if is_arm_mat_param is not None:
c['is_arm_parameter'] = 'true'
2018-10-26 19:45:07 +02:00
self.tunits.append(c)
def make_vert(self, custom_name: str = None):
if custom_name is None:
self.data['vertex_shader'] = self.matname + '_' + self.data['name'] + '.vert'
else:
self.data['vertex_shader'] = custom_name + '.vert'
self.vert = Shader(self, 'vert')
2018-10-26 19:45:07 +02:00
return self.vert
def make_frag(self, custom_name: str = None):
if custom_name is None:
self.data['fragment_shader'] = self.matname + '_' + self.data['name'] + '.frag'
else:
self.data['fragment_shader'] = custom_name + '.frag'
2018-10-26 19:45:07 +02:00
self.frag = Shader(self, 'frag')
return self.frag
def make_geom(self, custom_name: str = None):
if custom_name is None:
self.data['geometry_shader'] = self.matname + '_' + self.data['name'] + '.geom'
else:
self.data['geometry_shader'] = custom_name + '.geom'
2018-10-26 19:45:07 +02:00
self.geom = Shader(self, 'geom')
return self.geom
def make_tesc(self, custom_name: str = None):
if custom_name is None:
self.data['tesscontrol_shader'] = self.matname + '_' + self.data['name'] + '.tesc'
else:
self.data['tesscontrol_shader'] = custom_name + '.tesc'
2018-10-26 19:45:07 +02:00
self.tesc = Shader(self, 'tesc')
return self.tesc
def make_tese(self, custom_name: str = None):
if custom_name is None:
self.data['tesseval_shader'] = self.matname + '_' + self.data['name'] + '.tese'
else:
self.data['tesseval_shader'] = custom_name + '.tese'
2018-10-26 19:45:07 +02:00
self.tese = Shader(self, 'tese')
return self.tese
2016-12-13 01:09:17 +01:00
class Shader:
2016-12-15 23:50:21 +01:00
def __init__(self, context, shader_type):
2016-12-13 01:09:17 +01:00
self.context = context
2016-12-15 23:50:21 +01:00
self.shader_type = shader_type
2016-12-13 01:09:17 +01:00
self.includes = []
self.ins = []
self.outs = []
Add support for shadow map atlasing With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this limitation was solved. The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a modified version of drawShadowMap(). Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial 4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth levels is added or not when compiling. the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
self.uniforms_top = []
2016-12-13 01:09:17 +01:00
self.uniforms = []
self.constants = []
2016-12-13 20:06:23 +01:00
self.functions = {}
2016-12-13 01:09:17 +01:00
self.main = ''
2018-03-27 10:10:12 +02:00
self.main_init = ''
self.main_normal = ''
2018-03-26 18:04:11 +02:00
self.main_textures = ''
2018-03-26 18:15:02 +02:00
self.main_attribs = ''
2017-02-18 20:18:38 +01:00
self.header = ''
2016-12-17 23:48:18 +01:00
self.write_pre = False
2018-03-26 18:04:11 +02:00
self.write_normal = 0
self.write_textures = 0
2016-12-13 01:09:17 +01:00
self.tab = 1
2017-12-20 15:37:58 +01:00
self.vstruct_as_vsin = True
2017-04-26 14:21:22 +02:00
self.lock = False
2017-10-26 22:13:21 +02:00
self.geom_passthrough = False
2017-12-20 15:37:58 +01:00
self.is_linked = False # Use already generated shader
2018-12-04 19:06:01 +01:00
self.noprocessing = False
2016-12-13 01:09:17 +01:00
def has_include(self, s):
return s in self.includes
2016-12-13 01:09:17 +01:00
def add_include(self, s):
if not self.has_include(s):
self.includes.append(s)
2016-12-13 01:09:17 +01:00
Add support for shadow map atlasing With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this limitation was solved. The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a modified version of drawShadowMap(). Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial 4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth levels is added or not when compiling. the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
def add_include_front(self, s):
if not self.has_include(s):
pos = 0
# make sure compiled.inc is always on top
if len(self.includes) > 0 and self.includes[0] == 'compiled.inc':
pos = 1
self.includes.insert(pos, s)
Add support for shadow map atlasing With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this limitation was solved. The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a modified version of drawShadowMap(). Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial 4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth levels is added or not when compiling. the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
2016-12-13 01:09:17 +01:00
def add_in(self, s):
if s not in self.ins:
self.ins.append(s)
2016-12-13 01:09:17 +01:00
def add_out(self, s):
if s not in self.outs:
self.outs.append(s)
2016-12-13 01:09:17 +01:00
def add_uniform(self, s, link=None, included=False, top=False,
tex_addr_u=None, tex_addr_v=None,
tex_filter_min=None, tex_filter_mag=None,
2021-06-14 13:36:30 +02:00
tex_mipmap_filter=None, default_value=None, is_arm_mat_param=None):
2016-12-13 01:09:17 +01:00
ar = s.split(' ')
2017-02-18 20:18:38 +01:00
# layout(RGBA8) image3D voxels
utype = ar[-2]
uname = ar[-1]
2017-08-03 14:01:04 +02:00
if utype.startswith('sampler') or utype.startswith('image') or utype.startswith('uimage'):
is_image = True if (utype.startswith('image') or utype.startswith('uimage')) else None
2018-12-11 23:05:18 +01:00
if uname[-1] == ']': # Array of samplers - sampler2D mySamplers[2]
# Add individual units - mySamplers[0], mySamplers[1]
for i in range(int(uname[-2])):
uname_array = uname[:-2] + str(i) + ']'
self.context.add_texture_unit(
uname_array, link, is_image,
tex_addr_u, tex_addr_v,
tex_filter_min, tex_filter_mag, tex_mipmap_filter)
2018-12-11 23:05:18 +01:00
else:
self.context.add_texture_unit(
uname, link, is_image,
tex_addr_u, tex_addr_v,
2021-06-14 13:36:30 +02:00
tex_filter_min, tex_filter_mag, tex_mipmap_filter,
default_value=default_value, is_arm_mat_param=is_arm_mat_param)
2016-12-13 01:09:17 +01:00
else:
2017-02-07 11:50:21 +01:00
# Prefer vec4[] for d3d to avoid padding
2016-12-13 01:09:17 +01:00
if ar[0] == 'float' and '[' in ar[1]:
ar[0] = 'floats'
ar[1] = ar[1].split('[', 1)[0]
2017-02-07 11:50:21 +01:00
elif ar[0] == 'vec4' and '[' in ar[1]:
2017-04-12 13:25:09 +02:00
ar[0] = 'floats'
2017-02-07 11:50:21 +01:00
ar[1] = ar[1].split('[', 1)[0]
2021-06-14 13:36:30 +02:00
self.context.add_constant(ar[0], ar[1], link=link, default_value=default_value, is_arm_mat_param=is_arm_mat_param)
Add support for shadow map atlasing With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this limitation was solved. The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a modified version of drawShadowMap(). Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial 4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth levels is added or not when compiling. the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
if top:
if not included and s not in self.uniforms_top:
self.uniforms_top.append(s)
elif not included and s not in self.uniforms:
2016-12-13 01:09:17 +01:00
self.uniforms.append(s)
def add_const(self, type_str: str, name: str, value_str: str, array_size: int = 0):
"""
Add a global constant to the shader.
Parameters
----------
2020-04-18 22:18:38 +02:00
type_str: str
The name of the type, like 'float' or 'vec3'. If the
constant is an array, there is no need to add `[]` to the
type
name: str
The name of the variable
value_str: str
The value of the constant as a string
array_size: int
If not 0 (default value), create an array with the given size
"""
if array_size == 0:
self.constants.append(f'{type_str} {name} = {value_str}')
elif array_size > 0:
self.constants.append(f'{type_str} {name}[{array_size}] = {type_str}[]({value_str})')
2016-12-13 20:06:23 +01:00
def add_function(self, s):
fname = s.split('(', 1)[0]
if fname in self.functions:
return
self.functions[fname] = s
2016-12-19 01:25:22 +01:00
def contains(self, s):
2018-03-26 19:45:15 +02:00
return s in self.main or \
2018-03-27 10:10:12 +02:00
s in self.main_init or \
s in self.main_normal or \
2018-03-26 19:45:15 +02:00
s in self.ins or \
s in self.main_textures or \
s in self.main_attribs
2016-12-19 01:25:22 +01:00
2019-02-10 20:37:38 +01:00
def replace(self, old, new):
self.main = self.main.replace(old, new)
self.main_init = self.main_init.replace(old, new)
self.main_normal = self.main_normal.replace(old, new)
self.main_textures = self.main_textures.replace(old, new)
self.main_attribs = self.main_attribs.replace(old, new)
self.uniforms = [u.replace(old, new) for u in self.uniforms]
def write_init(self, s, unique=True):
"""Prepend to the main function. If `unique` is true (default), look for other occurences first."""
if unique and self.contains(s):
return
self.main_init = '\t' + s + '\n' + self.main_init
2017-10-23 16:24:57 +02:00
2016-12-13 01:09:17 +01:00
def write(self, s):
2017-04-26 14:21:22 +02:00
if self.lock:
return
2018-03-26 18:04:11 +02:00
if self.write_textures > 0:
self.main_textures += '\t' * 1 + s + '\n'
elif self.write_normal > 0:
2018-03-27 10:10:12 +02:00
self.main_normal += '\t' * 1 + s + '\n'
2017-10-30 18:37:45 +01:00
elif self.write_pre:
2018-03-27 10:10:12 +02:00
self.main_init += '\t' * 1 + s + '\n'
2016-12-17 23:48:18 +01:00
else:
self.main += '\t' * self.tab + s + '\n'
2016-12-13 01:09:17 +01:00
2017-02-18 20:18:38 +01:00
def write_header(self, s):
self.header += s + '\n'
2018-03-26 18:15:02 +02:00
def write_attrib(self, s):
self.main_attribs += '\t' + s + '\n'
2017-03-23 12:01:25 +01:00
2017-12-20 15:37:58 +01:00
def is_equal(self, sh):
self.vstruct_to_vsin()
return self.ins == sh.ins and \
self.main == sh.main and \
2018-03-27 10:10:12 +02:00
self.main_normal == sh.main_normal and \
self.main_init == sh.main_init and \
2018-03-26 18:15:02 +02:00
self.main_textures == sh.main_textures and \
self.main_attribs == sh.main_attribs
2017-12-20 15:37:58 +01:00
2018-12-14 15:27:43 +01:00
def data_size(self, data):
if data == 'float1':
return '1'
elif data == 'float2':
return '2'
elif data == 'float3':
return '3'
elif data == 'float4':
return '4'
elif data == 'short2norm':
return '2'
elif data == 'short4norm':
return '4'
2017-12-20 15:37:58 +01:00
def vstruct_to_vsin(self):
if self.shader_type != 'vert' or self.ins != [] or not self.vstruct_as_vsin: # Vertex structure as vertex shader input
return
2018-12-14 15:27:43 +01:00
vs = self.context.data['vertex_elements']
2017-12-20 15:37:58 +01:00
for e in vs:
2018-12-14 15:27:43 +01:00
self.add_in('vec' + self.data_size(e['data']) + ' ' + e['name'])
2017-12-20 15:37:58 +01:00
2016-12-13 01:09:17 +01:00
def get(self):
2018-12-04 19:06:01 +01:00
if self.noprocessing:
return self.main
2016-12-17 15:34:43 +01:00
s = '#version 450\n'
2017-02-18 20:18:38 +01:00
s += self.header
2016-12-17 15:34:43 +01:00
in_ext = ''
2016-12-20 14:15:09 +01:00
out_ext = ''
2016-12-17 15:34:43 +01:00
2017-12-20 15:37:58 +01:00
if self.shader_type == 'vert':
self.vstruct_to_vsin()
2016-12-17 15:34:43 +01:00
elif self.shader_type == 'tesc':
in_ext = '[]'
out_ext = '[]'
s += 'layout(vertices = 3) out;\n'
# Gen outs
for sin in self.ins:
2017-02-18 20:18:38 +01:00
ar = sin.rsplit(' ', 1) # vec3 wnormal
2016-12-17 15:34:43 +01:00
tc_s = 'tc_' + ar[1]
self.add_out(ar[0] + ' ' + tc_s)
# Pass data
self.write('{0}[gl_InvocationID] = {1}[gl_InvocationID];'.format(tc_s, ar[1]))
elif self.shader_type == 'tese':
in_ext = '[]'
s += 'layout(triangles, equal_spacing, ccw) in;\n'
2017-02-18 20:18:38 +01:00
elif self.shader_type == 'geom':
in_ext = '[]'
s += 'layout(triangles) in;\n'
2017-10-26 22:13:21 +02:00
if not self.geom_passthrough:
s += 'layout(triangle_strip) out;\n'
s += 'layout(max_vertices=3) out;\n'
2017-02-18 20:18:38 +01:00
Add support for shadow map atlasing With this it is now possible to enable atlasing of shadow maps, which solves the existing limitation of 4 lights in a scene. This is done by grouping the rendering of shadow maps, that currently are drawn into their own images for each light, into one or several big textures. This was done because the openGL and webGL version Armory targets do not support dynamic indexing of shadowMapSamplers, meaning that the index that access an array of shadow maps has to be know by the compiler before hand so it can be unrolled into if/else branching. By instead simply using a big shadow map texture and moving the dynamic part to other types of array that are allowed dynamic indexing like vec4 and mat4, this limitation was solved. The premise was simple enough for the shader part, but for the Haxe part, managing and solving where lights shadow maps should go in a shadow map can be tricky. So to keep track and solve this, ShadowMapAtlas and ShadowMapTile were created. These classes have the minimally required logic to solve the basic features needed for this problem: defining some kind of abstraction to prevent overlapping of shadowmaps, finding available space, assigning such space efficiently, locking and freeing this space, etc. This functionality it is used by drawShadowMapAtlas(), which is a modified version of drawShadowMap(). Shadow map atlases are represented with perfectly balanced 4-ary trees, where each tree of the previous definition represents a "tile" or slice that results from dividing a square that represents the image into 4 slices or sub-images. The root of this "tile" it's a reference to the tile-slice, and this tile is divided in 4 slices, and the process is repeated depth-times. If depth is 1, slices are kept at just the initial 4 tiles of max size, which is the default size of the shadow map. #arm_shadowmap_atlas_lod allows controlling if code to support more depth levels is added or not when compiling. the tiles that populate atlases tile trees are simply a data structure that contains a reference to the light they are linked to, inner subtiles in case LOD is enabled, coordinates to where this tile starts in the atlas that go from 0 to Shadow Map Size, and a reference to a linked tile for LOD. This simple definition allows tiles having a theoretically small memory footprint, but in turn this simplicity might make some functionality that might be responsibility of tiles (for example knowing if they are overlapping) a responsibility of the ones that utilizes tiles instead. This decision may complicate maintenance so it is to be revised in future iterations of this feature.
2021-01-27 02:01:06 +01:00
for a in self.uniforms_top:
s += 'uniform ' + a + ';\n'
2016-12-13 01:09:17 +01:00
for a in self.includes:
s += '#include "' + a + '"\n'
2017-10-26 22:13:21 +02:00
if self.geom_passthrough:
s += 'layout(passthrough) in gl_PerVertex { vec4 gl_Position; } gl_in[];\n'
2016-12-13 01:09:17 +01:00
for a in self.ins:
2017-10-26 22:13:21 +02:00
if self.geom_passthrough:
s += 'layout(passthrough) '
2016-12-17 15:34:43 +01:00
s += 'in {0}{1};\n'.format(a, in_ext)
2016-12-13 01:09:17 +01:00
for a in self.outs:
2017-10-26 22:13:21 +02:00
if not self.geom_passthrough:
s += 'out {0}{1};\n'.format(a, out_ext)
2016-12-21 00:51:04 +01:00
for a in self.uniforms:
2016-12-13 01:09:17 +01:00
s += 'uniform ' + a + ';\n'
for c in self.constants:
s += 'const ' + c + ';\n'
2016-12-13 20:06:23 +01:00
for f in self.functions:
2020-06-28 20:26:05 +02:00
s += self.functions[f] + '\n'
2016-12-13 01:09:17 +01:00
s += 'void main() {\n'
2018-03-26 18:15:02 +02:00
s += self.main_attribs
2018-03-26 18:04:11 +02:00
s += self.main_textures
2018-03-27 10:10:12 +02:00
s += self.main_normal
s += self.main_init
2016-12-13 01:09:17 +01:00
s += self.main
s += '}\n'
return s