diff --git a/fast64_internal/f3d/f3d_bleed.py b/fast64_internal/f3d/f3d_bleed.py index f16efc9..16dc649 100644 --- a/fast64_internal/f3d/f3d_bleed.py +++ b/fast64_internal/f3d/f3d_bleed.py @@ -7,9 +7,25 @@ from dataclasses import dataclass, field from ..utility import create_or_get_world from .f3d_gbi import ( + DPPipelineMode, + DPSetAlphaCompare, + DPSetAlphaDither, + DPSetColorDither, + DPSetCombineKey, + DPSetCycleType, + DPSetDepthSource, + DPSetTextureConvert, + DPSetTextureDetail, + DPSetTextureFilter, + DPSetTextureLOD, + DPSetTextureLUT, + DPSetTexturePersp, + GfxMatWriteMethod, GfxTag, GfxListTag, + SPGeometryMode, SPMatrix, + SPSetOtherModeSub, SPVertex, SPViewport, SPDisplayList, @@ -36,6 +52,7 @@ from .f3d_gbi import ( DPLoadSync, DPTileSync, DPSetTile, + DPSetTileSize, DPLoadTile, FModel, FMesh, @@ -45,9 +62,61 @@ from .f3d_gbi import ( GfxList, FTriGroup, GbiMacro, + get_F3D_GBI, ) +def get_geo_cmds( + clear_modes: set[str], set_modes: set[str], is_ex2: bool, matWriteMethod: GfxMatWriteMethod +) -> tuple[ + list[SPLoadGeometryMode | SPGeometryMode | SPSetGeometryMode | SPClearGeometryMode], + list[SPGeometryMode | SPSetGeometryMode | SPClearGeometryMode], +]: + set_modes, clear_modes = set(set_modes), set(clear_modes) + if len(clear_modes) == 0 and len(set_modes) == 0: + return ([], []) + if is_ex2: + if matWriteMethod == GfxMatWriteMethod.WriteAll: + return ([SPLoadGeometryMode(set_modes)], []) + elif len(set_modes) > 0 and len(clear_modes) > 0: + return ([SPGeometryMode(clear_modes, set_modes)], [SPGeometryMode(set_modes, clear_modes)]) + material, revert = [], [] + if len(set_modes) > 0: + material.append(SPSetGeometryMode(set_modes)) + revert.append(SPClearGeometryMode(set_modes)) + if len(clear_modes) > 0: + material.append(SPClearGeometryMode(clear_modes)) + revert.append(SPSetGeometryMode(clear_modes)) + return (material, revert) + + +GEO_CMDS = (SPGeometryMode, SPSetGeometryMode, SPClearGeometryMode, SPLoadGeometryMode) +WRITE_DIFF_OTHERMODE_CMDS = (SPSetOtherModeSub, DPSetRenderMode) + + +def get_flags( + set_modes: set[str], clear_modes: set[str], cmd: GEO_CMDS, default_clear: SPClearGeometryMode | None = None +): + if type(cmd) == SPGeometryMode: + set_modes.update(cmd.setFlagList) + clear_modes.update(cmd.clearFlagList) + clear_modes.difference_update(set_modes) + set_modes.difference_update(clear_modes) + elif type(cmd) == SPSetGeometryMode: + set_modes.update(cmd.flagList) + clear_modes.difference_update(set_modes) + elif type(cmd) == SPClearGeometryMode: + clear_modes.update(cmd.flagList) + set_modes.difference_update(clear_modes) + elif type(cmd) == SPLoadGeometryMode: + clear_modes.update(set_modes) + clear_modes.difference_update(cmd.flagList) + if default_clear is not None: + clear_modes.update(default_clear.flagList - cmd.flagList) + set_modes.clear() + set_modes.update(cmd.flagList) + + class BleedGraphics: # bleed_state "enums" bleed_start = 1 @@ -57,7 +126,9 @@ class BleedGraphics: def __init__(self): self.bled_gfx_lists = dict() + self.reset_gfx_lists = set() # build world default cmds to compare against, f3d types needed for reset cmd building + self.f3d = get_F3D_GBI() self.is_f3d_old = bpy.context.scene.f3d_type == "F3D" self.is_f3dex2 = "F3DEX2" in bpy.context.scene.f3d_type self.build_default_geo() @@ -66,14 +137,14 @@ class BleedGraphics: def build_default_geo(self): defaults = create_or_get_world(bpy.context.scene).rdp_defaults - setGeo = SPSetGeometryMode([]) - clearGeo = SPClearGeometryMode([]) + setGeo = SPSetGeometryMode() + clearGeo = SPClearGeometryMode() def place_in_flaglist(flag: bool, enum: str, set_list: SPSetGeometryMode, clear_list: SPClearGeometryMode): if flag: - set_list.flagList.append(enum) + set_list.flagList.add(enum) else: - clear_list.flagList.append(enum) + clear_list.flagList.add(enum) place_in_flaglist(defaults.g_zbuffer, "G_ZBUFFER", setGeo, clearGeo) place_in_flaglist(defaults.g_shade, "G_SHADE", setGeo, clearGeo) @@ -94,32 +165,42 @@ class BleedGraphics: def build_default_othermodes(self): defaults = create_or_get_world(bpy.context.scene).rdp_defaults - othermode_H = SPSetOtherMode("G_SETOTHERMODE_H", 4, 20 - self.is_f3d_old, []) + othermode_L: dict[SPSetOtherModeSub:str] = {} + othermode_L[DPSetAlphaCompare] = defaults.g_mdsft_alpha_compare + othermode_L[DPSetDepthSource] = defaults.g_mdsft_zsrcsel + + othermode_H: dict[SPSetOtherModeSub:str] = {} + othermode_H[DPSetColorDither] = defaults.g_mdsft_rgb_dither + othermode_H[DPSetAlphaDither] = defaults.g_mdsft_alpha_dither + othermode_H[DPSetCombineKey] = defaults.g_mdsft_combkey + othermode_H[DPSetTextureConvert] = defaults.g_mdsft_textconv + othermode_H[DPSetTextureFilter] = defaults.g_mdsft_text_filt + othermode_H[DPSetTextureLUT] = defaults.g_mdsft_textlut + othermode_H[DPSetTextureLOD] = defaults.g_mdsft_textlod + othermode_H[DPSetTextureDetail] = defaults.g_mdsft_textdetail + othermode_H[DPSetTexturePersp] = defaults.g_mdsft_textpersp + othermode_H[DPSetCycleType] = defaults.g_mdsft_cycletype + othermode_H[DPPipelineMode] = defaults.g_mdsft_pipeline + self.default_othermode_dict = othermode_L | othermode_H + self.default_othermode_H = SPSetOtherMode( + "G_SETOTHERMODE_H", 4, 20 - self.is_f3d_old, set(othermode_H.values()) + ) # if the render mode is set, it will be consider non-default a priori - othermode_L = SPSetOtherMode("G_SETOTHERMODE_L", 0, 3 - self.is_f3d_old, []) - - othermode_L.flagList.append(defaults.g_mdsft_alpha_compare) - othermode_L.flagList.append(defaults.g_mdsft_zsrcsel) - - othermode_H.flagList.append(defaults.g_mdsft_rgb_dither) - othermode_H.flagList.append(defaults.g_mdsft_alpha_dither) - othermode_H.flagList.append(defaults.g_mdsft_combkey) - othermode_H.flagList.append(defaults.g_mdsft_textconv) - othermode_H.flagList.append(defaults.g_mdsft_text_filt) - othermode_H.flagList.append(defaults.g_mdsft_textlut) - othermode_H.flagList.append(defaults.g_mdsft_textlod) - othermode_H.flagList.append(defaults.g_mdsft_textdetail) - othermode_H.flagList.append(defaults.g_mdsft_textpersp) - othermode_H.flagList.append(defaults.g_mdsft_cycletype) - othermode_H.flagList.append(defaults.g_mdsft_pipeline) - - self.default_othermode_L = othermode_L - self.default_othermode_H = othermode_H + self.default_othermode_L = SPSetOtherMode("G_SETOTHERMODE_L", 0, 3 - self.is_f3d_old, set(othermode_L.values())) def bleed_fModel(self, fModel: FModel, fMeshes: dict[FMesh]): # walk fModel, no order to drawing is observed, so last_mat is not kept track of for drawLayer, fMesh in fMeshes.items(): - self.bleed_fmesh(fMesh, None, fMesh.draw, fModel.getAllMaterials().items(), fModel.getRenderMode(drawLayer)) + reset_cmd_dict = {} + self.bleed_fmesh( + None, + reset_cmd_dict, + fMesh.draw, + fModel.getAllMaterials().items(), + fModel.matWriteMethod, + fModel.getRenderMode(drawLayer), + ) + self.add_reset_cmds(fMesh.draw, reset_cmd_dict, fModel.matWriteMethod, fModel.getRenderMode(drawLayer)) self.clear_gfx_lists(fModel) # clear the gfx lists so they don't export @@ -132,28 +213,63 @@ class BleedGraphics: for tri_list in fMesh.triangleGroups: tri_list.triList.tag |= GfxListTag.NoExport + def add_reset_cmd( + self, f3d: F3D, cmd: GbiMacro, reset_cmd_dict: dict[GbiMacro], mat_write_method: GfxMatWriteMethod + ): + reset_cmd_list = (DPSetRenderMode,) + if SPGeometryMode not in reset_cmd_dict: + if mat_write_method == GfxMatWriteMethod.WriteAll: + reset_cmd_dict[SPGeometryMode] = ( + self.default_set_geo.flagList.copy(), + self.default_clear_geo.flagList.copy(), + ) + else: + reset_cmd_dict[SPGeometryMode] = set(), set() + get_flags(*reset_cmd_dict[SPGeometryMode], cmd) + if isinstance(cmd, SPSetOtherModeSub): + l: SPSetOtherMode = reset_cmd_dict.get("G_SETOTHERMODE_L") + h: SPSetOtherMode = reset_cmd_dict.get("G_SETOTHERMODE_H") + if l or h: # should never be reached, but if we reach it we are prepared + if h and cmd.is_othermodeh: + for existing_mode in [mode for mode in h.flagList if str(mode).startswith(cmd.mode_prefix)]: + h.flagList.remove(existing_mode) + h.flagList.add(cmd.mode) + if l and not cmd.is_othermodeh: + for existing_mode in [mode for mode in l.flagList if str(mode).startswith(cmd.mode_prefix)]: + l.flagList.remove(existing_mode) + l.flagList.add(cmd.mode) + else: + reset_cmd_dict[type(cmd)] = cmd + + # separate other mode H and othermode L + elif type(cmd) == SPSetOtherMode: + if cmd.cmd in reset_cmd_dict: + reset_cmd_dict[cmd.cmd].add_other(f3d, cmd) + else: + reset_cmd_dict[cmd.cmd] = copy.deepcopy(cmd) + + elif type(cmd) in reset_cmd_list: + reset_cmd_dict[type(cmd)] = cmd + def bleed_fmesh( self, - fMesh: FMesh, last_mat: FMaterial, + reset_cmd_dict: dict[type, GbiMacro], cmd_list: GfxList, fmodel_materials, - default_render_mode: list[str] = None, + mat_write_method: GfxMatWriteMethod, + default_render_mode: tuple[str] = None, ): - if bled_mat := self.bled_gfx_lists.get(cmd_list, None): + if bled_mat := self.bled_gfx_lists.get(id(cmd_list)): return bled_mat - bleed_state = self.bleed_start cur_fmat = None - reset_cmd_dict = dict() bleed_gfx_lists = BleedGfxLists() fmesh_static_cmds, fmesh_jump_cmds = self.on_bleed_start(cmd_list) + start_cmds = cmd_list.commands # commands that preceed any jump list for jump_list_cmd in fmesh_jump_cmds: # bleed mat and tex if jump_list_cmd.displayList.tag & GfxListTag.Material: - # update last_mat - if cur_fmat: - last_mat = cur_fmat _, cur_fmat = find_material_from_jump_cmd(fmodel_materials, jump_list_cmd) if not cur_fmat: # make better error msg @@ -163,22 +279,26 @@ class BleedGraphics: bleed_gfx_lists.bled_tex = self.bleed_textures(cur_fmat, last_mat, bleed_state) else: bleed_gfx_lists.bled_tex = cur_fmat.texture_DL.commands - bleed_gfx_lists.bled_mats = self.bleed_mat(cur_fmat, last_mat, bleed_state) - # some syncs may become redundant after bleeding - self.optimize_syncs(bleed_gfx_lists, bleed_state) + bleed_gfx_lists.bled_mats = self.bleed_mat( + cur_fmat, last_mat, start_cmds, mat_write_method, default_render_mode, bleed_state + ) + start_cmds = [] # bleed tri group (for large textures) and to remove other unnecessary cmds if jump_list_cmd.displayList.tag & GfxListTag.Geometry: tri_list = jump_list_cmd.displayList self.bleed_tri_group(tri_list, cur_fmat, bleed_state) - self.inline_triGroup(tri_list, bleed_gfx_lists, cmd_list, reset_cmd_dict) + self.inline_triGroup(tri_list, bleed_gfx_lists, cmd_list) self.on_tri_group_bleed_end(tri_list, cur_fmat, bleed_gfx_lists) # reset bleed gfx lists after inlining bleed_gfx_lists = BleedGfxLists() # set bleed state for cmd reverts bleed_state = self.bleed_in_progress - - last_mat = cur_fmat - self.on_bleed_end(last_mat, cmd_list, fmesh_static_cmds, reset_cmd_dict, default_render_mode) + last_mat = cur_fmat + cmd_list.commands.extend(fmesh_static_cmds) # this is troublesome + cmd_list.commands.append(SPEndDisplayList()) + self.optimize_syncs(cmd_list) # some syncs may become redundant after bleeding + [self.add_reset_cmd(self.f3d, cmd, reset_cmd_dict, mat_write_method) for cmd in cmd_list.commands] + self.bled_gfx_lists[id(cmd_list)] = cur_fmat return last_mat def build_tmem_dict(self, cmd_list: GfxList): @@ -228,7 +348,7 @@ class BleedGraphics: for j, cmd in enumerate(cur_fmat.texture_DL.commands): if not cmd: continue # some cmds are None from previous step - if self.bleed_individual_cmd(commands_bled, cmd, bleed_state, last_mat.texture_DL.commands) is True: + if self.bleed_individual_cmd(commands_bled, cmd, last_mat.texture_DL.commands) is True: commands_bled.commands[j] = None # remove Nones from list while None in commands_bled.commands: @@ -238,21 +358,98 @@ class BleedGraphics: bled_tex = cur_fmat.texture_DL return bled_tex.commands - def bleed_mat(self, cur_fmat: FMaterial, last_mat: FMaterial, bleed_state: int): + def bleed_mat( + self, + cur_fmat: FMaterial, + last_mat: FMaterial, + start_cmds: list[GbiMacro], + mat_write_method: GfxMatWriteMethod, + default_render_mode: list[str], + bleed_state: int, + ): + if mat_write_method == GfxMatWriteMethod.WriteAll: + new_sets, new_clears = self.default_set_geo.flagList.copy(), self.default_clear_geo.flagList.copy() + previous_sets, previous_clears = ( + self.default_set_geo.flagList.copy(), + self.default_clear_geo.flagList.copy(), + ) + revert_sets, revert_clears = self.default_set_geo.flagList.copy(), self.default_clear_geo.flagList.copy() + else: + new_sets, new_clears = set(), set() + previous_sets, previous_clears = set(), set() + revert_sets, revert_clears = set(), set() + revert_other_diff_cmd, revert_other_load_cmd, othermode_diff_cmds, last_cmd_list = [], [], [], [] + [get_flags(new_sets, new_clears, cmd, self.default_clear_geo) for cmd in cur_fmat.mat_only_DL.commands] + if last_mat: gfx = cur_fmat.mat_only_DL # deep copy breaks on Image objects so I will only copy the levels needed commands_bled = copy.copy(gfx) commands_bled.commands = copy.copy(gfx.commands) # copy the commands also - last_cmd_list = last_mat.mat_only_DL.commands - for j, cmd in enumerate(gfx.commands): - if self.bleed_individual_cmd(commands_bled, cmd, bleed_state, last_cmd_list): - commands_bled.commands[j] = None - # remove Nones from list - while None in commands_bled.commands: - commands_bled.commands.remove(None) + last_cmd_list = last_mat.mat_only_DL.commands + start_cmds + [get_flags(previous_sets, previous_clears, cmd, self.default_clear_geo) for cmd in last_cmd_list] + + # handle write diff reverts + othermode_diff_cmds = [c for c in commands_bled.commands if isinstance(c, WRITE_DIFF_OTHERMODE_CMDS)] + if last_mat.revert: + [get_flags(revert_sets, revert_clears, cmd, self.default_clear_geo) for cmd in last_mat.revert.commands] + revert_other_diff_cmd = [ + c for c in last_mat.revert.commands if isinstance(c, WRITE_DIFF_OTHERMODE_CMDS) + ] + revert_other_load_cmd = [ + copy.deepcopy(c) for c in last_mat.revert.commands if isinstance(c, SPSetOtherMode) + ] + # while load mode is always written, they may not set the same range of values and therefor need revert + for revert_cmd in revert_other_load_cmd: + othermode_cmd = next( + (c for c in commands_bled.commands if type(c) == type(revert_cmd) and c.cmd == revert_cmd.cmd), None + ) + if othermode_cmd is None: + commands_bled.commands.insert(0, revert_cmd) + else: + index = commands_bled.commands.index(othermode_cmd) + revert_cmd.add_other(self.f3d, othermode_cmd) + commands_bled.commands[index] = revert_cmd + commands_bled.commands = [ + cmd + for cmd in commands_bled.commands + if not self.bleed_individual_cmd(commands_bled, cmd, last_cmd_list, default_render_mode) + ] else: - commands_bled = self.bleed_cmd_list(cur_fmat.mat_only_DL, bleed_state) + [get_flags(previous_sets, previous_clears, cmd, self.default_clear_geo) for cmd in start_cmds] + commands_bled = self.bleed_cmd_list(cur_fmat.mat_only_DL, default_render_mode, bleed_state) + + # remove all geo cmds to add later + commands_bled.commands = [cmd for cmd in commands_bled.commands if not isinstance(cmd, GEO_CMDS)] + + # remove clears and sets from revert if they will be set later in start or this material + revert_clears, revert_sets = ( + revert_clears - previous_clears - new_sets, + revert_sets - previous_sets - new_clears, + ) + if mat_write_method == GfxMatWriteMethod.WriteAll: + if previous_clears != new_clears or previous_sets != new_sets: + set_modes, clear_modes = new_sets | revert_sets, new_clears | revert_clears + # add back removed geo cmds, reverts and start cmds + for cmd in get_geo_cmds(clear_modes, set_modes, self.f3d.F3DEX_GBI_2, mat_write_method)[0]: + commands_bled.commands.insert(0, cmd) + else: + # remove clears and sets from the material if set in start + new_clears, new_sets = new_clears - previous_clears, new_sets - previous_sets + # combine + set_modes, clear_modes = new_sets | revert_sets, new_clears | revert_clears + clear_modes, set_modes = clear_modes - set_modes, set_modes - clear_modes + + # add back removed geo cmds and reverts + for cmd in get_geo_cmds(clear_modes, set_modes, self.f3d.F3DEX_GBI_2, mat_write_method)[0]: + commands_bled.commands.insert(0, cmd) + + # if there is no equivelent othermode cmd, it must be using the revert + for revert_cmd in revert_other_diff_cmd: + othermode_cmd = next((cmd for cmd in othermode_diff_cmds if type(cmd) == type(revert_cmd)), None) + if othermode_cmd is None: + commands_bled.commands.insert(0, revert_cmd) + # remove SPEndDisplayList while SPEndDisplayList() in commands_bled.commands: commands_bled.commands.remove(SPEndDisplayList()) @@ -263,16 +460,16 @@ class BleedGraphics: while SPEndDisplayList() in tri_list.commands: tri_list.commands.remove(SPEndDisplayList()) if not cur_fmat or (cur_fmat.isTexLarge[0] or cur_fmat.isTexLarge[1]): - tri_list = self.bleed_cmd_list(tri_list, bleed_state) + tri_list = self.bleed_cmd_list(tri_list, None, bleed_state) # this is a little less versatile than comparing by last used material - def bleed_cmd_list(self, target_cmd_list: GfxList, bleed_state: int): + def bleed_cmd_list(self, target_cmd_list: GfxList, default_render_mode: list[str], bleed_state: int): usage_dict = dict() commands_bled = copy.copy(target_cmd_list) # copy the commands commands_bled.commands = copy.copy(target_cmd_list.commands) # copy the commands for j, cmd in enumerate(target_cmd_list.commands): # some cmds you can bleed vs world defaults, others only if they repeat within this gfx list - bleed_cmd_status = self.bleed_individual_cmd(commands_bled, cmd, bleed_state) + bleed_cmd_status = self.bleed_individual_cmd(commands_bled, cmd, default_render_mode=default_render_mode) if not bleed_cmd_status: continue last_use = usage_dict.get((type(cmd), getattr(cmd, "tile", None)), None) @@ -285,20 +482,13 @@ class BleedGraphics: return commands_bled # Put triGroup bleed gfx in the FMesh.draw object - def inline_triGroup( - self, tri_list: GfxList, bleed_gfx_lists: BleedGfxLists, cmd_list: GfxList, reset_cmd_dict: dict[GbiMacro] - ): + def inline_triGroup(self, tri_list: GfxList, bleed_gfx_lists: BleedGfxLists, cmd_list: GfxList): # add material cmd_list.commands.extend(bleed_gfx_lists.bled_mats) # add textures cmd_list.commands.extend(bleed_gfx_lists.bled_tex) # add in triangles cmd_list.commands.extend(tri_list.commands) - # skinned meshes don't draw tris sometimes, use this opportunity to save a sync - tri_cmds = [c for c in tri_list.commands if type(c) == SP1Triangle or type(c) == SP2Triangles] - if tri_cmds: - reset_cmd_dict[DPPipeSync] = DPPipeSync() - [bleed_gfx_lists.add_reset_cmd(cmd, reset_cmd_dict) for cmd in bleed_gfx_lists.bled_mats] # pre processes cmd_list and removes cmds deemed useless. subclass and override if this causes a game specific issue def on_bleed_start(self, cmd_list: GfxList): @@ -334,82 +524,104 @@ class BleedGraphics: def on_tri_group_bleed_end(self, triGroup: FTriGroup, last_mat: FMaterial, bleed_gfx_lists: BleedGfxLists): return - def on_bleed_end( + def add_reset_cmds( self, - last_mat: FMaterial, cmd_list: GfxList, - fmesh_static_cmds: list[GbiMacro], reset_cmd_dict: dict[GbiMacro], - default_render_mode: list[str] = None, + mat_write_method: GfxMatWriteMethod, + default_render_mode: tuple[str] = None, ): + if not cmd_list or not reset_cmd_dict or id(cmd_list) in self.reset_gfx_lists: + return False # revert certain cmds for extra safety - reset_cmds = self.create_reset_cmds(reset_cmd_dict, default_render_mode) - # if pipe sync in reset list, make sure it is the first cmd - if DPPipeSync in reset_cmds: - reset_cmds.remove(DPPipeSync) - reset_cmds.insert(0, DPPipeSync) + reset_cmds = self.create_reset_cmds(reset_cmd_dict, mat_write_method, default_render_mode) + while SPEndDisplayList() in cmd_list.commands: + cmd_list.commands.remove(SPEndDisplayList()) cmd_list.commands.extend(reset_cmds) - cmd_list.commands.extend(fmesh_static_cmds) # this is troublesome cmd_list.commands.append(SPEndDisplayList()) - self.bled_gfx_lists[cmd_list] = last_mat + self.optimize_syncs(cmd_list) + self.reset_gfx_lists.add(id(cmd_list)) + return True # remove syncs if first material, or if no gsDP cmds in material - def optimize_syncs(self, bleed_gfx_lists: BleedGfxLists, bleed_state: int): + def optimize_syncs(self, cmd_list: GfxList): no_syncs_needed = {"DPSetPrimColor", "DPSetPrimDepth"} # will not affect rdp - syncs_needed = {"SPSetOtherMode"} # will affect rdp - if bleed_state == self.bleed_start: - while DPPipeSync() in bleed_gfx_lists.bled_mats: - bleed_gfx_lists.bled_mats.remove(DPPipeSync()) - for cmd in (*bleed_gfx_lists.bled_mats, *bleed_gfx_lists.bled_tex): - cmd_name = type(cmd).__name__ - if cmd == DPPipeSync(): - continue - if "DP" in cmd_name and cmd_name not in no_syncs_needed: - return - if cmd_name in syncs_needed: - return - while DPPipeSync() in bleed_gfx_lists.bled_mats: - bleed_gfx_lists.bled_mats.remove(DPPipeSync()) + syncs_needed = {"SPSetOtherMode", "SPTexture"} # will affect rdp - def create_reset_cmds(self, reset_cmd_dict: dict[GbiMacro], default_render_mode: list[str]): + tri_buffered = True + last_load_sync = None + old_cmds = cmd_list.commands + new_cmds = [] + cmd_list.commands = new_cmds + + for cmd in old_cmds: + cmd_name = type(cmd).__name__ + is_dp_cmd = ("DP" in cmd_name and cmd_name not in no_syncs_needed) or cmd_name in syncs_needed + if isinstance(cmd, (DPPipeSync, DPLoadSync, DPTileSync)): + continue + elif isinstance(cmd, (DPLoadBlock, DPLoadTile, DPLoadTLUTCmd, DPSetTile, DPSetTileSize)) and tri_buffered: + last_load_sync = len(new_cmds) + new_cmds.append(DPLoadSync()) + tri_buffered = False + elif tri_buffered and is_dp_cmd: + tri_buffered = False + if last_load_sync is not None: + new_cmds[last_load_sync] = DPPipeSync() + last_load_sync = None + else: + new_cmds.append(DPPipeSync()) + elif not is_dp_cmd and isinstance(cmd, (SP2Triangles, SP1Triangle, SPLine3D, SPLineW3D)): + tri_buffered = True + last_load_sync = None + new_cmds.append(cmd) + + def create_reset_cmds( + self, reset_cmd_dict: dict[GbiMacro], mat_write_method: GfxMatWriteMethod, default_render_mode: list[str] + ): reset_cmds = [] for cmd_type, cmd_use in reset_cmd_dict.items(): - if cmd_type == DPPipeSync: - reset_cmds.append(DPPipeSync()) - - # generally either loadgeo, or a combo of set/clear is used based on microcode selected - # if you are in f3d, any selection different from the default will add a set/clear - if cmd_type == SPLoadGeometryMode and cmd_use != self.default_load_geo: - reset_cmds.append(self.default_load_geo) - - elif cmd_type == SPSetGeometryMode and cmd_use != self.default_set_geo: - reset_cmds.append(self.default_set_geo) - - elif cmd_type == SPClearGeometryMode and cmd_use != self.default_clear_geo: - reset_cmds.append(self.default_clear_geo) - + if cmd_type == SPGeometryMode: # revert cmd includes everything from the start + set_list, clear_list = cmd_use + if mat_write_method == GfxMatWriteMethod.WriteDifferingAndRevert: + clear_list = clear_list - self.default_clear_geo.flagList + set_list = set_list - self.default_set_geo.flagList + reset_cmds.extend(get_geo_cmds(clear_list, set_list, self.f3d.F3DEX_GBI_2, mat_write_method)[1]) + elif clear_list != self.default_clear_geo.flagList or set_list != self.default_set_geo.flagList: + reset_cmds.append(self.default_load_geo) elif cmd_type == "G_SETOTHERMODE_H": if cmd_use != self.default_othermode_H: reset_cmds.append(self.default_othermode_H) - # render mode takes up most bits of the lower half, so seeing high bit usage is enough to determine render mode was used - elif cmd_type == DPSetRenderMode or (cmd_type == "G_SETOTHERMODE_L" and cmd_use.length >= 31): - if default_render_mode: - reset_cmds.append( - SPSetOtherMode( - "G_SETOTHERMODE_L", - 0, - 32 - self.is_f3d_old, - [*self.default_othermode_L.flagList, *default_render_mode], - ) - ) + elif cmd_type == DPSetRenderMode: + if default_render_mode and cmd_use.flagList != default_render_mode: + reset_cmds.append(DPSetRenderMode(tuple(default_render_mode))) elif cmd_type == "G_SETOTHERMODE_L": - if cmd_use != self.default_othermode_L: - reset_cmds.append(self.default_othermode_L) + flag_list = copy.copy(self.default_othermode_L.flagList) + if cmd_use.sets_rendermode(self.f3d): + flag_list.update(default_render_mode) + default_othermode_l = SPSetOtherMode( + "G_SETOTHERMODE_L", + 0, + (32 if cmd_use.sets_rendermode(self.f3d) else 3) - self.is_f3d_old, + flag_list, + ) + if cmd_use != default_othermode_l: + reset_cmds.append(default_othermode_l) + + elif isinstance(cmd_use, SPSetOtherModeSub): + default = self.default_othermode_dict[cmd_type] + if cmd_use.mode != default: + reset_cmds.append(cmd_type(default)) return reset_cmds - def bleed_individual_cmd(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): + def bleed_individual_cmd( + self, + cmd_list: GfxList, + cmd: GbiMacro, + last_cmd_list: GfxList = None, + default_render_mode: tuple[str] = None, + ): # never bleed these cmds if type(cmd) in [ SPMatrix, @@ -434,44 +646,28 @@ class BleedGraphics: ]: return False - # if no last list then calling func will own behavior of bleeding - if not last_cmd_list: - return self.bleed_self_conflict + if last_cmd_list is None: + if isinstance(cmd, SPSetOtherModeSub): + return cmd.mode == self.default_othermode_dict[type(cmd)] + elif isinstance(cmd, DPSetRenderMode): + return cmd.flagList == default_render_mode and cmd.blender is None # apply specific logic to these cmds, see functions below, otherwise default behavior is to bleed if cmd is in the last list bleed_func = getattr(self, (f"bleed_{type(cmd).__name__}"), None) if bleed_func: - return bleed_func(cmd_list, cmd, bleed_state, last_cmd_list) + return bleed_func(cmd_list, cmd, last_cmd_list) else: - return cmd in last_cmd_list + return last_cmd_list is not None and cmd in last_cmd_list # bleed these cmds only if it is the second call and cmd was in the last use list, or if they match world defaults and it is the first call - def bleed_SPLoadGeometryMode( - self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None - ): - if bleed_state != self.bleed_start: + def bleed_SPLoadGeometryMode(self, cmd_list: GfxList, cmd: GbiMacro, last_cmd_list: GfxList = None): + if last_cmd_list is not None: return cmd in last_cmd_list else: return cmd == self.default_load_geo - def bleed_SPSetGeometryMode( - self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None - ): - if bleed_state != self.bleed_start: - return cmd in last_cmd_list - else: - return cmd == self.default_set_geo - - def bleed_SPClearGeometryMode( - self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None - ): - if bleed_state != self.bleed_start: - return cmd in last_cmd_list - else: - return cmd == self.default_clear_geo - - def bleed_SPSetOtherMode(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): - if bleed_state != self.bleed_start: + def bleed_SPSetOtherMode(self, cmd_list: GfxList, cmd: GbiMacro, last_cmd_list: GfxList = None): + if last_cmd_list is not None: return cmd in last_cmd_list else: if cmd.cmd == "G_SETOTHERMODE_H": @@ -480,43 +676,15 @@ class BleedGraphics: return cmd == self.default_othermode_L # Don“t bleed if the cmd is used for scrolling or if the last cmd's tags are not the same (those are not hashed) - def bleed_DPSetTileSize(self, _cmd_list: GfxList, cmd: GbiMacro, _bleed_state: int, last_cmd_list: GfxList = None): + def bleed_DPSetTileSize(self, _cmd_list: GfxList, cmd: GbiMacro, last_cmd_list: GfxList = None): if cmd.tags == GfxTag.TileScroll0 or cmd.tags == GfxTag.TileScroll1: return False - if cmd in last_cmd_list: + if last_cmd_list is not None and cmd in last_cmd_list: last_size_cmd = last_cmd_list[last_cmd_list.index(cmd)] if last_size_cmd.tags == cmd.tags: return True return False - # At most, only one sync is needed after drawing tris. The f3d writer should - # already have placed the appropriate sync type required. If a second sync is - # detected between drawing cmds, then remove that sync. Remove the latest sync - # not the first seen sync. - def bleed_DPTileSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): - return self.bleed_between_tris(cmd_list, cmd, bleed_state, [DPLoadSync, DPPipeSync, DPTileSync]) - - def bleed_DPPipeSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): - return self.bleed_between_tris(cmd_list, cmd, bleed_state, [DPLoadSync, DPPipeSync, DPTileSync]) - - def bleed_DPLoadSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): - return self.bleed_between_tris(cmd_list, cmd, bleed_state, [DPLoadSync, DPPipeSync, DPTileSync]) - - def bleed_between_tris(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, conflict_cmds: list[GbiMacro]): - tri_buffered = False - for parse_cmd in cmd_list.commands: - if parse_cmd is cmd: - return tri_buffered - if type(parse_cmd) in [SP2Triangles, SP1Triangle, SPLine3D, SPLineW3D]: - tri_buffered = False - continue - if type(parse_cmd) in conflict_cmds: - if not tri_buffered: - tri_buffered = True - else: - return True - return False - # small containers for data used in inline Gfx @dataclass @@ -524,20 +692,6 @@ class BleedGfxLists: bled_mats: GfxList = field(default_factory=list) bled_tex: GfxList = field(default_factory=list) - def add_reset_cmd(self, cmd: GbiMacro, reset_cmd_dict: dict[GbiMacro]): - reset_cmd_list = ( - SPLoadGeometryMode, - SPSetGeometryMode, - SPClearGeometryMode, - DPSetRenderMode, - ) - # separate other mode H and othermode L - if type(cmd) == SPSetOtherMode: - reset_cmd_dict[cmd.cmd] = cmd - - if type(cmd) in reset_cmd_list: - reset_cmd_dict[type(cmd)] = cmd - # helper function used for sm64 def find_material_from_jump_cmd( diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 111abef..a07dd13 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -3416,7 +3416,7 @@ class GbiMacro: else: return field.name if hasattr(field, "__iter__") and type(field) is not str: - return " | ".join(field) if len(field) else "0" + return " | ".join(map(str, field)) if len(field) else "0" if self._hex > 0 and isinstance(field, int): temp = field if field >= 0 else (1 << (self._hex * 4)) + field return f"{temp:#0{self._hex + 2}x}" # + 2 for the 0x part @@ -4275,9 +4275,9 @@ def gsSPGeometryMode_Non_F3DEX_GBI_2(word, f3d): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -def geoFlagListToWord(flagList, f3d): +def geoFlagListToWord(flags: tuple, f3d: F3D): word = 0 - for name in flagList: + for name in flags: if name in f3d.allGeomModeFlags: word += getattr(f3d, name) else: @@ -4291,8 +4291,8 @@ def geoFlagListToWord(flagList, f3d): @dataclass(unsafe_hash=True) class SPGeometryMode(GbiMacro): - clearFlagList: list - setFlagList: list + clearFlagList: set[str] = field(default_factory=set) + setFlagList: set[str] = field(default_factory=set) def to_binary(self, f3d, segments): if f3d.F3DEX_GBI_2: @@ -4306,7 +4306,7 @@ class SPGeometryMode(GbiMacro): @dataclass(unsafe_hash=True) class SPSetGeometryMode(GbiMacro): - flagList: list + flagList: set[str] = field(default_factory=set) def to_binary(self, f3d, segments): word = geoFlagListToWord(self.flagList, f3d) @@ -4319,7 +4319,7 @@ class SPSetGeometryMode(GbiMacro): @dataclass(unsafe_hash=True) class SPClearGeometryMode(GbiMacro): - flagList: list + flagList: set[str] = field(default_factory=set) def to_binary(self, f3d, segments): word = geoFlagListToWord(self.flagList, f3d) @@ -4332,7 +4332,7 @@ class SPClearGeometryMode(GbiMacro): @dataclass(unsafe_hash=True) class SPLoadGeometryMode(GbiMacro): - flagList: list + flagList: set[str] def to_binary(self, f3d, segments): word = geoFlagListToWord(self.flagList, f3d) @@ -4350,12 +4350,54 @@ def gsSPSetOtherMode(cmd, sft, length, data, f3d): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") +@dataclass(unsafe_hash=True) +class RendermodeBlender: + cycle1: tuple + cycle2: tuple + + def __str__(self): + return f"GBL_c1({', '.join(self.cycle1)}) | GBL_c2({', '.join(self.cycle2)})" + + def to_c(self, _static=True): + return str(self) + + def to_binary(self, f3d): + return GBL_c1(*[getattr(f3d, str(x), x) for x in self.cycle1]) | GBL_c2( + *[getattr(f3d, str(x), x) for x in self.cycle2] + ) + + @dataclass(unsafe_hash=True) class SPSetOtherMode(GbiMacro): cmd: str sft: int length: int - flagList: list + flagList: set + + def sets_rendermode(self, f3d): + return self.cmd == "G_SETOTHERMODE_L" and (self.sft + self.length) > (3 - f3d.F3D_OLD_GBI) + + def extend(self, flags: Iterable | str): + flags = {flags} if isinstance(flags, str) else set(flags) + self.flagList = self.flagList | flags + + def add_other(self, f3d, other: SPSetOtherMode): + min_max = min(self.sft, other.sft), max(self.sft + self.length, other.sft + other.length) + self.sft = min_max[0] + self.length = min_max[1] - min_max[0] + + for flag in self.flagList.copy(): # remove any flag overriden by other + value = flag + if isinstance(flag, RendermodeBlender): + value = flag.to_binary(f3d) + elif isinstance(flag, str): + value = getattr(f3d, flag, None) + if value is None: + raise ValueError(f"Flag {flag} not found in {f3d}") + if not value or value >> other.sft < (2**other.length): + self.flagList.remove(flag) + # add other's flags + self.extend(other.flagList) def to_binary(self, f3d, segments): data = 0 @@ -4367,10 +4409,27 @@ class SPSetOtherMode(GbiMacro): @dataclass(unsafe_hash=True) -class DPPipelineMode(GbiMacro): - # mode is a string +class SPSetOtherModeSub(GbiMacro): mode: str + is_othermodeh = False + @property + def mode_prefix(self): + return "_".join(self.mode.split("_")[:2]) + + +@dataclass(unsafe_hash=True) +class SPSetOtherModeLSub(SPSetOtherModeSub): + is_othermodeh = False + + +@dataclass(unsafe_hash=True) +class SPSetOtherModeHSub(SPSetOtherModeSub): + is_othermodeh = True + + +@dataclass(unsafe_hash=True) +class DPPipelineMode(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_PM_1PRIMITIVE": modeVal = f3d.G_PM_1PRIMITIVE @@ -4380,10 +4439,7 @@ class DPPipelineMode(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetCycleType(GbiMacro): - # mode is a string - mode: str - +class DPSetCycleType(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_CYC_1CYCLE": modeVal = f3d.G_CYC_1CYCLE @@ -4397,10 +4453,7 @@ class DPSetCycleType(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetTexturePersp(GbiMacro): - # mode is a string - mode: str - +class DPSetTexturePersp(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_TP_NONE": modeVal = f3d.G_TP_NONE @@ -4410,10 +4463,7 @@ class DPSetTexturePersp(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetTextureDetail(GbiMacro): - # mode is a string - mode: str - +class DPSetTextureDetail(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_TD_CLAMP": modeVal = f3d.G_TD_CLAMP @@ -4425,10 +4475,7 @@ class DPSetTextureDetail(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetTextureLOD(GbiMacro): - # mode is a string - mode: str - +class DPSetTextureLOD(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_TL_TILE": modeVal = f3d.G_TL_TILE @@ -4438,10 +4485,7 @@ class DPSetTextureLOD(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetTextureLUT(GbiMacro): - # mode is a string - mode: str - +class DPSetTextureLUT(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_TT_NONE": modeVal = f3d.G_TT_NONE @@ -4455,10 +4499,7 @@ class DPSetTextureLUT(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetTextureFilter(GbiMacro): - # mode is a string - mode: str - +class DPSetTextureFilter(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_TF_POINT": modeVal = f3d.G_TF_POINT @@ -4470,10 +4511,7 @@ class DPSetTextureFilter(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetTextureConvert(GbiMacro): - # mode is a string - mode: str - +class DPSetTextureConvert(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_TC_CONV": modeVal = f3d.G_TC_CONV @@ -4485,10 +4523,7 @@ class DPSetTextureConvert(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetCombineKey(GbiMacro): - # mode is a string - mode: str - +class DPSetCombineKey(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_CK_NONE": modeVal = f3d.G_CK_NONE @@ -4498,10 +4533,7 @@ class DPSetCombineKey(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetColorDither(GbiMacro): - # mode is a string - mode: str - +class DPSetColorDither(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_CD_MAGICSQ": modeVal = f3d.G_CD_MAGICSQ @@ -4517,10 +4549,7 @@ class DPSetColorDither(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetAlphaDither(GbiMacro): - # mode is a string - mode: str - +class DPSetAlphaDither(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_AD_PATTERN": modeVal = f3d.G_AD_PATTERN @@ -4534,10 +4563,7 @@ class DPSetAlphaDither(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetAlphaCompare(GbiMacro): - # mask is a string - mode: str - +class DPSetAlphaCompare(SPSetOtherModeLSub): def to_binary(self, f3d, segments): if self.mode == "G_AC_NONE": maskVal = f3d.G_AC_NONE @@ -4549,14 +4575,11 @@ class DPSetAlphaCompare(GbiMacro): @dataclass(unsafe_hash=True) -class DPSetDepthSource(GbiMacro): - # src is a string - src: str - +class DPSetDepthSource(SPSetOtherModeLSub): def to_binary(self, f3d, segments): - if self.src == "G_ZS_PIXEL": + if self.mode == "G_ZS_PIXEL": srcVal = f3d.G_ZS_PIXEL - elif self.src == "G_ZS_PRIM": + elif self.mode == "G_ZS_PRIM": srcVal = f3d.G_ZS_PRIM return gsSPSetOtherMode(f3d.G_SETOTHERMODE_L, f3d.G_MDSFT_ZSRCSEL, 1, srcVal, f3d) @@ -4579,37 +4602,20 @@ def GBL_c2(m1a, m1b, m2a, m2b): @dataclass(unsafe_hash=True) class DPSetRenderMode(GbiMacro): + flagList: set[str] + blender: Optional[RendermodeBlender] = None # bl0-3 are string for each blender enum - def __init__(self, flagList, blendList): - self.flagList = flagList - self.use_preset = blendList is None - if not self.use_preset: - self.bl00 = blendList[0] - self.bl01 = blendList[1] - self.bl02 = blendList[2] - self.bl03 = blendList[3] - self.bl10 = blendList[4] - self.bl11 = blendList[5] - self.bl12 = blendList[6] - self.bl13 = blendList[7] - def getGBL_c(self, f3d): - bl00 = getattr(f3d, self.bl00) - bl01 = getattr(f3d, self.bl01) - bl02 = getattr(f3d, self.bl02) - bl03 = getattr(f3d, self.bl03) - bl10 = getattr(f3d, self.bl10) - bl11 = getattr(f3d, self.bl11) - bl12 = getattr(f3d, self.bl12) - bl13 = getattr(f3d, self.bl13) - return GBL_c1(bl00, bl01, bl02, bl03) | GBL_c2(bl10, bl11, bl12, bl13) + @property + def use_preset(self): + return self.blender is None def to_binary(self, f3d, segments): flagWord = renderFlagListToWord(self.flagList, f3d) if not self.use_preset: return gsSPSetOtherMode( - f3d.G_SETOTHERMODE_L, f3d.G_MDSFT_RENDERMODE, 29, flagWord | self.getGBL_c(f3d), f3d + f3d.G_SETOTHERMODE_L, f3d.G_MDSFT_RENDERMODE, 29, flagWord | self.blender.to_binary(f3d), f3d ) else: return gsSPSetOtherMode(f3d.G_SETOTHERMODE_L, f3d.G_MDSFT_RENDERMODE, 29, flagWord, f3d) @@ -4618,25 +4624,7 @@ class DPSetRenderMode(GbiMacro): data = "gsDPSetRenderMode(" if static else "gDPSetRenderMode(glistp++, " if not self.use_preset: - data += ( - "GBL_c1(" - + self.bl00 - + ", " - + self.bl01 - + ", " - + self.bl02 - + ", " - + self.bl03 - + ") | GBL_c2(" - + self.bl10 - + ", " - + self.bl11 - + ", " - + self.bl12 - + ", " - + self.bl13 - + "), " - ) + data += self.blender.to_c(static) + ", " for name in self.flagList: data += name + " | " return data[:-3] + ")" @@ -4859,8 +4847,8 @@ class SPLightToFogColor(GbiMacro): @dataclass(unsafe_hash=True) class DPSetOtherMode(GbiMacro): - mode0: list - mode1: list + mode0: set[str] + mode1: set[str] def to_binary(self, f3d, segments): mode0 = mode1 = 0 @@ -4914,10 +4902,10 @@ class DPSetTile(GbiMacro): tmem: int tile: int palette: int - cmt: list + cmt: tuple[str, str] maskt: int shiftt: int - cms: list + cms: tuple[str, str] masks: int shifts: int @@ -4982,8 +4970,8 @@ class DPLoadTextureBlock(GbiMacro): width: int height: int pal: int - cms: list - cmt: list + cms: tuple[str, str] + cmt: tuple[str, str] masks: int maskt: int shifts: int @@ -5055,8 +5043,8 @@ class DPLoadTextureBlockYuv(GbiMacro): width: int height: int pal: int - cms: list - cmt: list + cms: tuple[str, str] + cmt: tuple[str, str] masks: int maskt: int shifts: int @@ -5134,8 +5122,8 @@ class _DPLoadTextureBlock(GbiMacro): width: int height: int pal: int - cms: list - cmt: list + cms: tuple[str, str] + cmt: tuple[str, str] masks: int maskt: int shifts: int @@ -5212,8 +5200,8 @@ class DPLoadTextureBlock_4b(GbiMacro): width: int height: int pal: int - cms: list - cmt: list + cms: tuple[str, str] + cmt: tuple[str, str] masks: int maskt: int shifts: int @@ -5287,8 +5275,8 @@ class DPLoadTextureTile(GbiMacro): lrs: int lrt: int pal: int - cms: list - cmt: list + cms: tuple[str, str] + cmt: tuple[str, str] masks: int maskt: int shifts: int @@ -5363,8 +5351,8 @@ class DPLoadTextureTile_4b(GbiMacro): lrs: int lrt: int pal: int - cms: list - cmt: list + cms: tuple[str, str] + cmt: tuple[str, str] masks: int maskt: int shifts: int diff --git a/fast64_internal/f3d/f3d_parser.py b/fast64_internal/f3d/f3d_parser.py index b911549..d206dd2 100644 --- a/fast64_internal/f3d/f3d_parser.py +++ b/fast64_internal/f3d/f3d_parser.py @@ -406,7 +406,7 @@ def getTileSize(value, f3d): def getTileClampMirror(value, f3d): data = math_eval(value, f3d) - return [(data & f3d.G_TX_CLAMP) != 0, (data & f3d.G_TX_MIRROR) != 0] + return ((data & f3d.G_TX_CLAMP) != 0, (data & f3d.G_TX_MIRROR) != 0) def getTileMask(value, f3d): @@ -496,7 +496,7 @@ class F3DContext: # This macro has all the tile setting properties, so we reuse it self.tileSettings: list[DPSetTile] = [ - DPSetTile("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 5, 0, i, 0, [False, False], 0, 0, [False, False], 0, 0) + DPSetTile("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 5, 0, i, 0, (False, False), 0, 0, (False, False), 0, 0) for i in range(8) ] self.tileSizes: list[DPSetTileSize] = [DPSetTileSize(i, 0, 0, 32, 32) for i in range(8)] @@ -579,7 +579,7 @@ class F3DContext: self.tmemDict = {} self.tileSettings = [ - DPSetTile("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 5, 0, i, 0, [False, False], 0, 0, [False, False], 0, 0) + DPSetTile("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 5, 0, i, 0, (False, False), 0, 0, (False, False), 0, 0) for i in range(8) ] diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index 556fd99..c070958 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -204,7 +204,7 @@ def maybeSaveSingleLargeTextureSetup( # SL, SH is * 2 for 4 bit and * 4 otherwise, because actually loading # 8 bit pairs of texels. Also written using f3d.G_TEXTURE_IMAGE_FRAC. sm = 2 if is4bit else 4 - nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] + nocm = ("G_TX_WRAP", "G_TX_NOMIRROR") if curImgSet != i: gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) @@ -970,7 +970,7 @@ def saveTextureLoadOnly( ): fmt = texFormatOf[texProp.tex_format] siz = texBitSizeF3D[texProp.tex_format] - nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] + nocm = ("G_TX_WRAP", "G_TX_NOMIRROR") SL, TL, SH, TH, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) # LoadTile will pad rows to 64 bit word alignment, while @@ -1040,8 +1040,8 @@ def saveTextureTile( mask_T = texProp.T.mask shift_S = texProp.S.shift shift_T = texProp.T.shift - cms = [("G_TX_CLAMP" if clamp_S else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_S else "G_TX_NOMIRROR")] - cmt = [("G_TX_CLAMP" if clamp_T else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_T else "G_TX_NOMIRROR")] + cms = (("G_TX_CLAMP" if clamp_S else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_S else "G_TX_NOMIRROR")) + cmt = (("G_TX_CLAMP" if clamp_T else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_T else "G_TX_NOMIRROR")) masks = mask_S maskt = mask_T shifts = shift_S if shift_S >= 0 else (shift_S + 16) @@ -1081,7 +1081,7 @@ def savePaletteLoad( ): assert 0 <= palAddr < 256 and (palAddr & 0xF) == 0 palFmt = texFormatOf[palFormat] - nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] + nocm = ("G_TX_WRAP", "G_TX_NOMIRROR") gfxOut.commands.extend( [ DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette), diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 6d776a0..14cc934 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -16,7 +16,7 @@ from .f3d_material import ( ) from .f3d_texture_writer import MultitexManager, TileLoad, maybeSaveSingleLargeTextureSetup from .f3d_gbi import * -from .f3d_bleed import BleedGraphics +from .f3d_bleed import BleedGraphics, get_geo_cmds from ..utility import * @@ -518,18 +518,18 @@ def addCullCommand(obj, fMesh, transformMatrix, matWriteMethod): defaults = create_or_get_world(bpy.context.scene).rdp_defaults if defaults.g_lighting: cullCommands = [ - SPClearGeometryMode(["G_LIGHTING"]), + SPClearGeometryMode({"G_LIGHTING"}), SPVertex(fMesh.cullVertexList, 0, 8, 0), - SPSetGeometryMode(["G_LIGHTING"]), + SPSetGeometryMode({"G_LIGHTING"}), SPCullDisplayList(0, 7), ] else: cullCommands = [SPVertex(fMesh.cullVertexList, 0, 8, 0), SPCullDisplayList(0, 7)] elif matWriteMethod == GfxMatWriteMethod.WriteAll: cullCommands = [ - SPClearGeometryMode(["G_LIGHTING"]), + SPClearGeometryMode({"G_LIGHTING"}), SPVertex(fMesh.cullVertexList, 0, 8, 0), - SPSetGeometryMode(["G_LIGHTING"]), + SPSetGeometryMode({"G_LIGHTING"}), SPCullDisplayList(0, 7), ] else: @@ -1056,10 +1056,10 @@ class TriangleConverter: if usesDecal: if not wroteOpaque: wroteOpaque = True - self.triList.commands.append(SPSetOtherMode("G_SETOTHERMODE_L", 10, 2, ["ZMODE_OPA"])) + self.triList.commands.append(SPSetOtherMode("G_SETOTHERMODE_L", 10, 2, {"ZMODE_OPA"})) if not wroteDecal and (darker and wroteDarker or not darker and wroteLighter): wroteDecal = True - self.triList.commands.append(SPSetOtherMode("G_SETOTHERMODE_L", 10, 2, ["ZMODE_DEC"])) + self.triList.commands.append(SPSetOtherMode("G_SETOTHERMODE_L", 10, 2, {"ZMODE_DEC"})) if darker: wroteDarker = True else: @@ -1323,10 +1323,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): useDict = all_combiner_uses(f3dMat) defaults = create_or_get_world(bpy.context.scene).rdp_defaults - if fModel.f3d.F3DEX_GBI_2: - saveGeoModeDefinitionF3DEX2(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) - else: - saveGeoModeDefinition(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) + saveGeoModeDefinition(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod, fModel.f3d.F3DEX_GBI_2) # Checking for f3dMat.rdp_settings.g_lighting here will prevent accidental exports, # There may be some edge case where this isn't desired. @@ -1585,20 +1582,12 @@ def addLightDefinition(f3d_light, fLights): ) -def saveBitGeoF3DEX2(value, defaultValue, flagName, geo, matWriteMethod): +def saveBitGeo(value, defaultValue, flagName, set_modes: list[str], clear_modes: list[str], matWriteMethod): if value != defaultValue or matWriteMethod == GfxMatWriteMethod.WriteAll: if value: - geo.setFlagList.append(flagName) + set_modes.append(flagName) else: - geo.clearFlagList.append(flagName) - - -def saveBitGeo(value, defaultValue, flagName, setGeo, clearGeo, matWriteMethod): - if value != defaultValue or matWriteMethod == GfxMatWriteMethod.WriteAll: - if value: - setGeo.flagList.append(flagName) - else: - clearGeo.flagList.append(flagName) + clear_modes.append(flagName) def saveGeoModeCommon(saveFunc: Callable, settings: RDPSettings, defaults: RDPSettings, args: Any): @@ -1625,37 +1614,15 @@ def saveGeoModeCommon(saveFunc: Callable, settings: RDPSettings, defaults: RDPSe saveFunc(settings.g_clipping, defaults.g_clipping, "G_CLIPPING", *args) -def saveGeoModeDefinitionF3DEX2(fMaterial, settings, defaults, matWriteMethod): - geo = SPGeometryMode([], []) - saveGeoModeCommon(saveBitGeoF3DEX2, settings, defaults, (geo, matWriteMethod)) +def saveGeoModeDefinition(fMaterial, settings, defaults, matWriteMethod, is_ex2: bool): + set_modes = [] + clear_modes = [] - if len(geo.clearFlagList) != 0 or len(geo.setFlagList) != 0: - if len(geo.clearFlagList) == 0: - geo.clearFlagList.append("0") - elif len(geo.setFlagList) == 0: - geo.setFlagList.append("0") + saveGeoModeCommon(saveBitGeo, settings, defaults, (set_modes, clear_modes, matWriteMethod)) - if matWriteMethod == GfxMatWriteMethod.WriteAll: - fMaterial.mat_only_DL.commands.append(SPLoadGeometryMode(geo.setFlagList)) - else: - fMaterial.mat_only_DL.commands.append(geo) - fMaterial.revert.commands.append(SPGeometryMode(geo.setFlagList, geo.clearFlagList)) - - -def saveGeoModeDefinition(fMaterial, settings, defaults, matWriteMethod): - setGeo = SPSetGeometryMode([]) - clearGeo = SPClearGeometryMode([]) - - saveGeoModeCommon(saveBitGeo, settings, defaults, (setGeo, clearGeo, matWriteMethod)) - - if len(setGeo.flagList) > 0: - fMaterial.mat_only_DL.commands.append(setGeo) - if matWriteMethod == GfxMatWriteMethod.WriteDifferingAndRevert: - fMaterial.revert.commands.append(SPClearGeometryMode(setGeo.flagList)) - if len(clearGeo.flagList) > 0: - fMaterial.mat_only_DL.commands.append(clearGeo) - if matWriteMethod == GfxMatWriteMethod.WriteDifferingAndRevert: - fMaterial.revert.commands.append(SPSetGeometryMode(clearGeo.flagList)) + material, revert = get_geo_cmds(clear_modes, set_modes, is_ex2, matWriteMethod) + fMaterial.mat_only_DL.commands.extend(material) + fMaterial.revert.commands.extend(revert) def saveModeSetting(fMaterial, value, defaultValue, cmdClass): @@ -1674,18 +1641,18 @@ def saveOtherModeHDefinition(fMaterial, settings, tlut, defaults, matWriteMethod def saveOtherModeHDefinitionAll(fMaterial, settings, tlut, defaults, f3d): - cmd = SPSetOtherMode("G_SETOTHERMODE_H", 4, 20 - f3d.F3D_OLD_GBI, []) - cmd.flagList.append(settings.g_mdsft_alpha_dither) - cmd.flagList.append(settings.g_mdsft_rgb_dither) - cmd.flagList.append(settings.g_mdsft_combkey) - cmd.flagList.append(settings.g_mdsft_textconv) - cmd.flagList.append(settings.g_mdsft_text_filt) - cmd.flagList.append(tlut) - cmd.flagList.append(settings.g_mdsft_textlod) - cmd.flagList.append(settings.g_mdsft_textdetail) - cmd.flagList.append(settings.g_mdsft_textpersp) - cmd.flagList.append(settings.g_mdsft_cycletype) - cmd.flagList.append(settings.g_mdsft_pipeline) + cmd = SPSetOtherMode("G_SETOTHERMODE_H", 4, 20 - f3d.F3D_OLD_GBI, set()) + cmd.flagList.add(settings.g_mdsft_alpha_dither) + cmd.flagList.add(settings.g_mdsft_rgb_dither) + cmd.flagList.add(settings.g_mdsft_combkey) + cmd.flagList.add(settings.g_mdsft_textconv) + cmd.flagList.add(settings.g_mdsft_text_filt) + cmd.flagList.add(tlut) + cmd.flagList.add(settings.g_mdsft_textlod) + cmd.flagList.add(settings.g_mdsft_textdetail) + cmd.flagList.add(settings.g_mdsft_textpersp) + cmd.flagList.add(settings.g_mdsft_cycletype) + cmd.flagList.add(settings.g_mdsft_pipeline) fMaterial.mat_only_DL.commands.append(cmd) @@ -1706,39 +1673,40 @@ def saveOtherModeHDefinitionIndividual(fMaterial, settings, tlut, defaults): def saveOtherModeLDefinition(fMaterial, settings, defaults, defaultRenderMode, matWriteMethod, f3d): if matWriteMethod == GfxMatWriteMethod.WriteAll: - saveOtherModeLDefinitionAll(fMaterial, settings, defaults, f3d) + saveOtherModeLDefinitionAll(fMaterial, settings, defaults, defaultRenderMode, f3d) elif matWriteMethod == GfxMatWriteMethod.WriteDifferingAndRevert: saveOtherModeLDefinitionIndividual(fMaterial, settings, defaults, defaultRenderMode) else: raise PluginError("Unhandled material write method: " + str(matWriteMethod)) -def saveOtherModeLDefinitionAll(fMaterial: FMaterial, settings, defaults, f3d): - baseLength = 3 if not settings.set_rendermode else 32 - cmd = SPSetOtherMode("G_SETOTHERMODE_L", 0, baseLength - f3d.F3D_OLD_GBI, []) - cmd.flagList.append(settings.g_mdsft_alpha_compare) - cmd.flagList.append(settings.g_mdsft_zsrcsel) - - if settings.set_rendermode: - flagList, blendList = getRenderModeFlagList(settings, fMaterial) - cmd.flagList.extend(flagList) - if blendList is not None: - cmd.flagList.extend( - [ - "GBL_c1(" + blendList[0] + ", " + blendList[1] + ", " + blendList[2] + ", " + blendList[3] + ")", - "GBL_c2(" + blendList[4] + ", " + blendList[5] + ", " + blendList[6] + ", " + blendList[7] + ")", - ] - ) - - fMaterial.mat_only_DL.commands.append(cmd) +def saveOtherModeLDefinitionAll(fMaterial: FMaterial, settings, defaults, defaultRenderMode, f3d): + cmd = SPSetOtherMode("G_SETOTHERMODE_L", 0, (32 if settings.set_rendermode else 3) - f3d.F3D_OLD_GBI, set()) + cmd.flagList.add(settings.g_mdsft_alpha_compare) + cmd.flagList.add(settings.g_mdsft_zsrcsel) if settings.g_mdsft_zsrcsel == "G_ZS_PRIM": fMaterial.mat_only_DL.commands.append(DPSetPrimDepth(z=settings.prim_depth.z, dz=settings.prim_depth.dz)) + if settings.set_rendermode: + if defaultRenderMode: + revert_cmd = SPSetOtherMode( + "G_SETOTHERMODE_L", + 0, + 32 - f3d.F3D_OLD_GBI, + {*defaultRenderMode, defaults.g_mdsft_alpha_compare, defaults.g_mdsft_zsrcsel}, + ) + fMaterial.revert.commands.append(revert_cmd) + flagList, blender = getRenderModeFlagList(settings, fMaterial) + cmd.flagList.update(flagList) + if blender is not None: + cmd.flagList.add(blender) + + fMaterial.mat_only_DL.commands.append(cmd) + def saveOtherModeLDefinitionIndividual(fMaterial, settings, defaults, defaultRenderMode): saveModeSetting(fMaterial, settings.g_mdsft_alpha_compare, defaults.g_mdsft_alpha_compare, DPSetAlphaCompare) - saveModeSetting(fMaterial, settings.g_mdsft_zsrcsel, defaults.g_mdsft_zsrcsel, DPSetDepthSource) if settings.g_mdsft_zsrcsel == "G_ZS_PRIM": @@ -1746,8 +1714,8 @@ def saveOtherModeLDefinitionIndividual(fMaterial, settings, defaults, defaultRen fMaterial.revert.commands.append(DPSetPrimDepth()) if settings.set_rendermode: - flagList, blendList = getRenderModeFlagList(settings, fMaterial) - renderModeSet = DPSetRenderMode(flagList, blendList) + flagList, blender = getRenderModeFlagList(settings, fMaterial) + renderModeSet = DPSetRenderMode(flagList, blender) fMaterial.mat_only_DL.commands.append(renderModeSet) if defaultRenderMode is not None: @@ -1756,7 +1724,7 @@ def saveOtherModeLDefinitionIndividual(fMaterial, settings, defaults, defaultRen def getRenderModeFlagList(settings, fMaterial): flagList = [] - blendList = None + blender = None # cycle independent if not settings.rendermode_advanced_enabled: @@ -1773,28 +1741,13 @@ def getRenderModeFlagList(settings, fMaterial): cycle2 = "G_RM_NOOP" flagList = [settings.rendermode_preset_cycle_1, cycle2] else: + cycle1 = (settings.blend_p1, settings.blend_a1, settings.blend_m1, settings.blend_b1) if settings.g_mdsft_cycletype == "G_CYC_2CYCLE": - blendList = [ - settings.blend_p1, - settings.blend_a1, - settings.blend_m1, - settings.blend_b1, - settings.blend_p2, - settings.blend_a2, - settings.blend_m2, - settings.blend_b2, - ] + blender = RendermodeBlender( + cycle1, (settings.blend_p2, settings.blend_a2, settings.blend_m2, settings.blend_b2) + ) else: - blendList = [ - settings.blend_p1, - settings.blend_a1, - settings.blend_m1, - settings.blend_b1, - settings.blend_p1, - settings.blend_a1, - settings.blend_m1, - settings.blend_b1, - ] + blender = RendermodeBlender(cycle1, cycle1) if settings.aa_en: flagList.append("AA_EN") @@ -1817,7 +1770,7 @@ def getRenderModeFlagList(settings, fMaterial): if settings.force_bl: flagList.append("FORCE_BL") - return flagList, blendList + return tuple(flagList), blender def saveOtherDefinition(fMaterial, material, defaults): @@ -1854,7 +1807,7 @@ def getWriteMethodFromEnum(enumVal): def exportF3DtoC(dirPath, obj, DLFormat, transformMatrix, texDir, savePNG, texSeparate, name, matWriteMethod): inline = bpy.context.scene.exportInlineF3D - fModel = FModel(name, DLFormat, matWriteMethod if not inline else GfxMatWriteMethod.WriteAll) + fModel = FModel(name, DLFormat, matWriteMethod) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, True, name, DLFormat, not savePNG) if inline: diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index ce3fe2f..d734ceb 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -121,7 +121,7 @@ class OOTModel(FModel): defaultRenderModes = create_or_get_world(bpy.context.scene).ootDefaultRenderModes cycle1 = getattr(defaultRenderModes, drawLayerUsed.lower() + "Cycle1") cycle2 = getattr(defaultRenderModes, drawLayerUsed.lower() + "Cycle2") - return [cycle1, cycle2] + return (cycle1, cycle2) def addFlipbookWithRepeatCheck(self, flipbook: TextureFlipbook): model = self.getFlipbookOwner() diff --git a/fast64_internal/sm64/settings/properties.py b/fast64_internal/sm64/settings/properties.py index 0275fb9..146700d 100644 --- a/fast64_internal/sm64/settings/properties.py +++ b/fast64_internal/sm64/settings/properties.py @@ -81,6 +81,10 @@ class SM64_Properties(PropertyGroup): name="Matstack Fix", description="Exports account for matstack fix requirements", ) + write_all: BoolProperty( + name="Write All", + description="Write single load geo and set othermode commands instead of writting the difference to defaults. Can result in smaller displaylists but may introduce issues", + ) @property def binary_export(self): @@ -90,6 +94,12 @@ class SM64_Properties(PropertyGroup): def abs_decomp_path(self) -> Path: return Path(abspath(self.decomp_path)) + @property + def gfx_write_method(self): + from ...f3d.f3d_gbi import GfxMatWriteMethod + + return GfxMatWriteMethod.WriteAll if self.write_all else GfxMatWriteMethod.WriteDifferingAndRevert + @staticmethod def upgrade_changed_props(): old_scene_props_to_new = { @@ -145,6 +155,7 @@ class SM64_Properties(PropertyGroup): data["compression_format"] = self.compression_format data["force_extended_ram"] = self.force_extended_ram data["matstack_fix"] = self.matstack_fix + data["write_all"] = self.write_all return data def from_repo_settings(self, data: dict): @@ -152,6 +163,7 @@ class SM64_Properties(PropertyGroup): set_prop_if_in_data(self, "compression_format", data, "compression_format") set_prop_if_in_data(self, "force_extended_ram", data, "force_extended_ram") set_prop_if_in_data(self, "matstack_fix", data, "matstack_fix") + set_prop_if_in_data(self, "write_all", data, "write_all") def draw_repo_settings(self, layout: UILayout): col = layout.column() @@ -161,6 +173,7 @@ class SM64_Properties(PropertyGroup): prop_split(col, self, "refresh_version", "Refresh (Function Map)") col.prop(self, "force_extended_ram") col.prop(self, "matstack_fix") + col.prop(self, "write_all") def draw_props(self, layout: UILayout, show_repo_settings: bool = True): col = layout.column() diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 43452b1..37c2556 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -119,7 +119,7 @@ class SM64Model(FModel): world = create_or_get_world(bpy.context.scene) cycle1 = getattr(world, "draw_layer_" + str(drawLayer) + "_cycle_1") cycle2 = getattr(world, "draw_layer_" + str(drawLayer) + "_cycle_2") - return [cycle1, cycle2] + return (cycle1, cycle2) class SM64GfxFormatter(GfxFormatter): @@ -320,8 +320,8 @@ def exportTexRectCommon(texProp, name, convertTextureData): saveModeSetting(fMaterial, "G_AC_THRESHOLD", defaults.g_mdsft_alpha_compare, DPSetAlphaCompare) fMaterial.mat_only_DL.commands.append(DPSetBlendColor(0xFF, 0xFF, 0xFF, 0xFF)) - fMaterial.mat_only_DL.commands.append(DPSetRenderMode(["G_RM_AA_XLU_SURF", "G_RM_AA_XLU_SURF2"], None)) - fMaterial.revert.commands.append(DPSetRenderMode(["G_RM_AA_ZB_OPA_SURF", "G_RM_AA_ZB_OPA_SURF2"], None)) + fMaterial.mat_only_DL.commands.append(DPSetRenderMode(("G_RM_AA_XLU_SURF", "G_RM_AA_XLU_SURF2"), None)) + fMaterial.revert.commands.append(DPSetRenderMode(("G_RM_AA_ZB_OPA_SURF", "G_RM_AA_ZB_OPA_SURF2"), None)) saveModeSetting(fMaterial, texProp.tlut_mode, defaults.g_mdsft_textlut, DPSetTextureLUT) ti = TexInfo() @@ -370,7 +370,7 @@ def sm64ExportF3DtoC( fModel = SM64Model( name, DLFormat, - GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, + bpy.context.scene.fast64.sm64.gfx_write_method, ) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, name, DLFormat, not savePNG) @@ -492,11 +492,7 @@ def sm64ExportF3DtoC( def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, segmentData, includeChildren): inline = bpy.context.scene.exportInlineF3D - fModel = SM64Model( - obj.name, - DLFormat.Static, - GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, - ) + fModel = SM64Model(obj.name, DLFormat, bpy.context.scene.fast64.sm64.gfx_write_method) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) if inline: @@ -522,11 +518,7 @@ def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, segmentData, i def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, RAMAddr, includeChildren): inline = bpy.context.scene.exportInlineF3D - fModel = SM64Model( - obj.name, - DLFormat.Static, - GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, - ) + fModel = SM64Model(obj.name, DLFormat, bpy.context.scene.fast64.sm64.gfx_write_method) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) if inline: @@ -554,11 +546,7 @@ def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, RAMAddr, def exportF3DtoInsertableBinary(filepath, transformMatrix, obj, includeChildren): inline = bpy.context.scene.exportInlineF3D - fModel = SM64Model( - obj.name, - DLFormat.Static, - GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, - ) + fModel = SM64Model(obj.name, DLFormat, bpy.context.scene.fast64.sm64.gfx_write_method) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) if inline: diff --git a/fast64_internal/sm64/sm64_geolayout_bone.py b/fast64_internal/sm64/sm64_geolayout_bone.py index c0ee8e1..855d168 100644 --- a/fast64_internal/sm64/sm64_geolayout_bone.py +++ b/fast64_internal/sm64/sm64_geolayout_bone.py @@ -82,6 +82,7 @@ enumMatOverrideOptions = [ def drawGeoInfo(panel: Panel, bone: Bone): + bone_props: "SM64_BoneProperties" = bone.fast64.sm64 panel.layout.box().label(text="Geolayout Inspector") if bone is None: panel.layout.label(text="Edit geolayout properties in Pose mode.") @@ -91,6 +92,15 @@ def drawGeoInfo(panel: Panel, bone: Bone): prop_split(col, bone, "geo_cmd", "Geolayout Command") + if bpy.context.scene.exportInlineF3D: + revert_split = col.split(factor=0.4) + revert_split.label(text="Revert Material") + revert_row = revert_split.row() + revert_row.prop( + bone_props, + "revert_before_func" if bone.geo_cmd in {"Function", "HeldObject"} else "revert_previous_mat", + text="Previous", + ) if bone.geo_cmd in [ "TranslateRotate", "Translate", @@ -102,6 +112,8 @@ def drawGeoInfo(panel: Panel, bone: Bone): "CustomAnimated", ]: drawLayerWarningBox(col, bone, "draw_layer") + if bpy.context.scene.exportInlineF3D: + revert_row.prop(bone_props, "revert_after_mat", text="After") if bone.geo_cmd == "Scale": prop_split(col, bone, "geo_scale", "Scale") @@ -138,9 +150,9 @@ def drawGeoInfo(panel: Panel, bone: Bone): prop_split(col, bone, "culling_radius", "Culling Radius") elif bone.geo_cmd in {"CustomAnimated", "CustomNonAnimated"}: - prop_split(col, bone.fast64.sm64, "custom_geo_cmd_macro", "Geo Command Macro") + prop_split(col, bone_props, "custom_geo_cmd_macro", "Geo Command Macro") if bone.geo_cmd == "CustomNonAnimated": - prop_split(col, bone.fast64.sm64, "custom_geo_cmd_args", "Geo Command Args") + prop_split(col, bone_props, "custom_geo_cmd_args", "Geo Command Args") else: # It's animated infobox = col.box() infobox.label(text="Command's args will be filled with layer, translate, and rotate", icon="INFO") @@ -251,10 +263,10 @@ class GeolayoutObjectPanel(Panel): prop_split(col, geo_asm, "param", "Parameter") col.prop(obj, "ignore_render") col.prop(obj, "ignore_collision") - if bpy.context.scene.f3d_type == "F3DEX3": - box.prop(obj, "is_occlusion_planes") - if obj.is_occlusion_planes and (not obj.ignore_render or not obj.ignore_collision): - box.label(icon="INFO", text="Suggest Ignore Render & Ignore Collision.") + # if bpy.context.scene.f3d_type == "F3DEX3": + # box.prop(obj, "is_occlusion_planes") + # if obj.is_occlusion_planes and (not obj.ignore_render or not obj.ignore_collision): + # box.label(icon="INFO", text="Suggest Ignore Render & Ignore Collision.") if context.scene.exportInlineF3D: col.prop(obj, "bleed_independently") if obj_scale_is_unified(obj) and len(obj.modifiers) == 0: @@ -463,6 +475,13 @@ class SM64_BoneProperties(PropertyGroup): custom_geo_cmd_macro: StringProperty(name="Geo Command Macro", default="GEO_BONE") custom_geo_cmd_args: StringProperty(name="Geo Command Args", default="") + revert_previous_mat: BoolProperty(name="Revert Previous Material", default=False) + revert_after_mat: BoolProperty( + name="Revert After Material", + default=False, + description="If disabled the last material of each layer will still be reverted at the end", + ) + revert_before_func: BoolProperty(name="Revert Before Function", default=True) sm64_bone_classes = ( diff --git a/fast64_internal/sm64/sm64_geolayout_classes.py b/fast64_internal/sm64/sm64_geolayout_classes.py index 8414e68..814fffe 100644 --- a/fast64_internal/sm64/sm64_geolayout_classes.py +++ b/fast64_internal/sm64/sm64_geolayout_classes.py @@ -2,7 +2,7 @@ from __future__ import annotations import bpy from struct import pack -from copy import copy +from copy import copy, deepcopy from ..utility import ( PluginError, @@ -19,7 +19,7 @@ from ..utility import ( geoNodeRotateOrder, ) from ..f3d.f3d_bleed import BleedGraphics -from ..f3d.f3d_gbi import FModel +from ..f3d.f3d_gbi import FMaterial, FModel, GbiMacro, GfxList from .sm64_geolayout_constants import ( nodeGroupCmds, @@ -269,7 +269,6 @@ class BaseDisplayListNode: """Base displaylist node with common helper functions dealing with displaylists""" dl_ext = "WITH_DL" # add dl_ext to geo command if command has a displaylist - bleed_independently = False # base behavior, can be changed with obj boolProp def get_dl_address(self): assert self.dlRef is None, "dlRef not implemented in binary" @@ -306,6 +305,9 @@ class TransformNode: self.parent = None self.skinned = False self.skinnedWithoutDL = False + # base behavior, can be changed with obj boolProp + self.revert_previous_mat = False + self.revert_after_mat = False def convertToDynamic(self): if self.node.hasDL: @@ -470,46 +472,93 @@ class JumpNode: return "GEO_BRANCH(" + ("1, " if self.storeReturn else "0, ") + geo_name + ")," +LastMaterials = dict[int, tuple[FMaterial | None, list[tuple[GfxList, dict[type, GbiMacro]]]]] + + class GeoLayoutBleed(BleedGraphics): def bleed_geo_layout_graph(self, fModel: FModel, geo_layout_graph: GeolayoutGraph, use_rooms: bool = False): - last_materials = dict() # last used material should be kept track of per layer + # last used material, last used cmd list and resets per layer + last_materials = {} - def walk(node, last_materials): + def copy_last(last_materials: LastMaterials) -> LastMaterials: + return {dl: [lm, [(c, deepcopy(r)) for c, r in lcr]] for dl, (lm, lcr) in last_materials.items()} + + def reset_layer(last_materials: LastMaterials, draw_layer: int) -> LastMaterials: + _, cmds_resets = last_materials.get(draw_layer, (None, [])) + for i, (cmd_list, reset_cmd_dict) in enumerate(copy(cmds_resets)): + # only discard reset if the reset was actually applied + if self.add_reset_cmds( + cmd_list, reset_cmd_dict, fModel.matWriteMethod, fModel.getRenderMode(draw_layer) + ): + cmds_resets[i] = None + cmds_resets = [cr for cr in cmds_resets if cr is not None] + if not cmds_resets: + last_materials.pop(draw_layer, 0) + return last_materials + + def reset_all_layers(last_materials: LastMaterials) -> LastMaterials: + for draw_layer in copy(list(last_materials.keys())): + last_materials = reset_layer(last_materials, draw_layer) + return {} + + def walk(node, last_materials: LastMaterials) -> LastMaterials: + last_materials = copy_last(last_materials) base_node = node.node if type(base_node) == JumpNode: if base_node.geolayout: for node in base_node.geolayout.nodes: - last_materials = ( - walk(node, last_materials if not use_rooms else dict()) if not use_rooms else dict() - ) - else: - last_materials = dict() + last_materials = walk(node, last_materials) + fMesh = getattr(base_node, "fMesh", None) + last_mat, last_cmds_resets = None, [] + if fMesh is not None: + last_mat, last_cmds_resets = last_materials.get(base_node.drawLayer, (None, [])) + + if node.revert_previous_mat: + if fMesh is not None: + # add reset commands to previous cmd lists, reset last mat and reset dict + last_materials = reset_layer(last_materials, base_node.drawLayer) + else: + last_materials = reset_all_layers(last_materials) + last_mat, last_cmds_resets = None, [] + if fMesh: + base_node: BaseDisplayListNode cmd_list = fMesh.drawMatOverrides.get(base_node.override_hash, None) or fMesh.draw - last_mat = last_materials.get(base_node.drawLayer, None) default_render_mode = fModel.getRenderMode(base_node.drawLayer) + + reset_cmd_dict = {typ: cmd for _, reset_cmds in last_cmds_resets for typ, cmd in reset_cmds.items()} last_mat = self.bleed_fmesh( - fMesh, - last_mat if not base_node.bleed_independently else None, + last_mat, + reset_cmd_dict, cmd_list, fModel.getAllMaterials().items(), + fModel.matWriteMethod, default_render_mode, ) - # if the mesh has culling, it can be culled, and create invalid combinations of f3d to represent the current full DL - if fMesh.cullVertexList: - last_materials[base_node.drawLayer] = None - else: - last_materials[base_node.drawLayer] = last_mat - # don't carry over last_mat if it is a switch node or geo asm node + last_materials[base_node.drawLayer] = [last_mat, [(cmd_list, reset_cmd_dict)]] + # if the mesh has culling, we must revert to avoid bleed issues + if fMesh.cullVertexList or node.revert_after_mat: + last_materials = reset_layer(last_materials, base_node.drawLayer) + elif node.revert_after_mat: # if no mesh but still forced revert, revert all + last_materials = reset_all_layers(last_materials) + + cur_last_materials = copy_last(last_materials) + is_switch = type(base_node) in {SwitchNode} for child in node.children: - if type(base_node) in [SwitchNode, FunctionNode]: - last_materials = dict() - last_materials = walk(child, last_materials) + if is_switch: # parent node is switch or function + new_materials = walk(child, cur_last_materials) # last material info from current switch option + # add switch option reverts, to either revert at the end or in the option itself + for draw_layer, (last_mat, cmds_resets) in new_materials.items(): + last_materials.setdefault(draw_layer, [last_mat, []])[1].extend(cmds_resets) + last_materials[draw_layer][0] = None # reset last material + else: + last_materials = walk(child, last_materials) return last_materials for node in geo_layout_graph.startGeolayout.nodes: last_materials = walk(node, last_materials) + reset_all_layers(last_materials) self.clear_gfx_lists(fModel) diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index 0fd07e6..5c039e9 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -1,4 +1,5 @@ from __future__ import annotations +import typing import bpy, mathutils, math, copy, os, shutil, re from bpy.utils import register_class, unregister_class @@ -103,6 +104,7 @@ from ..f3d.f3d_gbi import ( ) from .sm64_geolayout_classes import ( + BaseDisplayListNode, DisplayListNode, TransformNode, StartNode, @@ -136,6 +138,9 @@ from .sm64_constants import ( enumLevelNames, ) +if typing.TYPE_CHECKING: + from .sm64_geolayout_bone import SM64_BoneProperties + def appendSecondaryGeolayout(geoDirPath, geoName1, geoName2, additionalNode=""): geoPath = os.path.join(geoDirPath, "geo.inc.c") @@ -393,7 +398,7 @@ def convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, camera, fModel = SM64Model( name, DLFormat, - GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, + bpy.context.scene.fast64.sm64.gfx_write_method, ) if len(armatureObj.children) == 0: @@ -460,7 +465,7 @@ def convertObjectToGeolayout( fModel = SM64Model( name, DLFormat, - GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, + bpy.context.scene.fast64.sm64.gfx_write_method, ) # convertTransformMatrix = convertTransformMatrix @ \ @@ -1548,6 +1553,9 @@ def processMesh( additionalTransformNode = TransformNode(additionalNode) transformNode.children.append(additionalTransformNode) additionalTransformNode.parent = transformNode + additionalTransformNode.revert_previous_mat = ( + additionalTransformNode.revert_after_mat + ) = obj.bleed_independently else: triConverterInfo = TriangleConverterInfo( @@ -1577,11 +1585,11 @@ def processMesh( node.hasDL = False else: firstNodeProcessed = False + node: BaseDisplayListNode for drawLayer, fMesh in fMeshes.items(): if not firstNodeProcessed: node.DLmicrocode = fMesh.draw node.fMesh = fMesh - node.bleed_independently = obj.bleed_independently node.drawLayer = drawLayer # previous drawLayer assigments useless? firstNodeProcessed = True else: @@ -1592,13 +1600,16 @@ def processMesh( ) additionalNode.DLmicrocode = fMesh.draw additionalNode.fMesh = fMesh - additionalNode.bleed_independently = obj.bleed_independently additionalTransformNode = TransformNode(additionalNode) + additionalTransformNode.revert_previous_mat = ( + additionalTransformNode.revert_after_mat + ) = obj.bleed_independently transformNode.children.append(additionalTransformNode) additionalTransformNode.parent = transformNode parentTransformNode.children.append(transformNode) transformNode.parent = parentTransformNode + transformNode.revert_previous_mat = transformNode.revert_after_mat = obj.bleed_independently alphabeticalChildren = sorted(obj.children, key=lambda childObj: childObj.original_name.lower()) for childObj in alphabeticalChildren: @@ -1641,6 +1652,8 @@ def processBone( convertTextureData, ): bone = armatureObj.data.bones[boneName] + bone_props: "SM64_BoneProperties" = bone.fast64.sm64 + poseBone = armatureObj.pose.bones[boneName] final_transform = copy.deepcopy(transformMatrix) materialOverrides = copy.copy(materialOverrides) @@ -1862,6 +1875,16 @@ def processBone( parentTransformNode.children.append(transformNode) transformNode.parent = parentTransformNode + new_node: TransformNode + for new_node in additionalNodes + [transformNode]: + new_node.revert_previous_mat = ( + bone_props.revert_before_func + if bone.geo_cmd in {"Function", "HeldObject"} + else bone_props.revert_previous_mat + ) + if isinstance(new_node.node, BaseDisplayListNode): + new_node.revert_after_mat = bone_props.revert_after_mat + if not isinstance(transformNode.node, SwitchNode): # print(boneGroup.name if boneGroup is not None else "Offset") if len(bone.children) > 0: @@ -2167,10 +2190,15 @@ def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parent # Get skinned node bone = armatureObj.data.bones[boneName] + bone_props: "SM64_BoneProperties" = bone.fast64.sm64 skinnedNode = DisplayListNode(drawLayer) skinnedNode.fMesh = skinnedMesh skinnedNode.DLmicrocode = skinnedMesh.draw skinnedTransformNode = TransformNode(skinnedNode) + skinnedTransformNode.revert_previous_mat, skinnedTransformNode.revert_after_mat = ( + bone_props.revert_previous_mat, + bone_props.revert_after_mat, + ) # Ascend heirarchy until reaching first node before a deform parent. # We duplicate the hierarchy along the way to possibly use later. diff --git a/fast64_internal/sm64/sm64_level_writer.py b/fast64_internal/sm64/sm64_level_writer.py index dc2d740..6f923e8 100644 --- a/fast64_internal/sm64/sm64_level_writer.py +++ b/fast64_internal/sm64/sm64_level_writer.py @@ -876,11 +876,10 @@ def exportLevelC(obj, transformMatrix, level_name, exportDir, savePNG, customExp level_data = LevelData(camera_data=f"struct CameraTrigger {levelCameraVolumeName}[] = {{\n") - inline = bpy.context.scene.exportInlineF3D fModel = SM64Model( level_name + "_dl", DLFormat, - GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, + bpy.context.scene.fast64.sm64.gfx_write_method, ) childAreas = [child for child in obj.children if child.type == "EMPTY" and child.sm64_obj_type == "Area Root"] if len(childAreas) == 0: