From f221e640d35add68ed8a34bf810bab376f42958e Mon Sep 17 00:00:00 2001 From: Lila Date: Fri, 23 May 2025 19:48:29 +0100 Subject: [PATCH] [SM64/F3D] Use WriteDifferingAndRevert for bleed (#461) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [SM64/F3D] Use WriteDifferingAndRevert for bleed Write all logic remains unaffected, obviously. A new dict was added to map each mode cmd to its default. For the actual bleed part, we fIrst need to add geo mode reverts of the last material to the geo mode of the current material, since the current material may not set them now. For othermodes we do something different, get all reverts and add any that doesn´t get set again by the current material ignoring bleeding. For the geo mode revert, we keep track of one geo mode command for set and clear, updating it as we go. For othermodes we keep track of all of the command types and then revert them using the new dict. * remove this * scut sabotage * Port over #462 We cannot just assume that othermodeh or othermodel are always setting the same bits, so instead we write the diff of the current cmd to the one already in the reset dict, this means that if a materials sets rendermode and another sets alpha compare (for example) it will still revert the rendermode because those bits will be carried over in the reset dict * make write different optional, as it can lead to bigger dls * clean up sm64 repo settings * im gonna have to rework a bunch of othermode stuff i actually dispise fast64 so much * RendermodeBlender class * Update f3d_bleed.py * i just want to be done with this * [Repo Settings] Only set if different Fixes lag at start up from draw layers being set unnecessarily. Some clean up that I already did in #461 * Update utility.py * is_rendermode_cmd to def * fix rendermode * return RendermodeBlender instead of temp list * instead of using a revert, set when no render mode * make rendermode command hashable, check for default * pick appropriate geo cmd, make geo cmds use sets makes dls just a bit cleaner and makes ex1 work correctly * implement render mode reset fix * Update f3d_bleed.py * undo dumb change * respect write method * only revert as needed, i feel like a god * make switchs work super well * fix setting rendermode * remove repeated resets in switch options and add options for reverts in bones * use last_mat by default * comment out occlusion plane code to show bleed indep * functions already work * fix load othermodes these work more like write diffs, so we do that * someone forgot * fix prev node issue * create our own pipe syncs * Update f3d_bleed.py * fix disgusting broken late night code * Revert "fix disgusting broken late night code" This reverts commit 9ece1511595678a551fae02f906a998fbde1ffba. * Update sm64_geolayout_classes.py * something is wrong but i cant figure it out * dont remove lighting * fix DPSetRenderMode * fix write all rendermode * fix this bug once and for all * write all get_flags impl * get_flags * forgot to do diff in reverts in write all * add back prim depth, accidentally removed * comments (not ready) * not a union * not a tuple Co-authored-by: Dragorn421 * proper formatting Co-authored-by: Dragorn421 * use load sync * oopsie dasie * use pipe syncs again --------- Co-authored-by: Dragorn421 --- fast64_internal/f3d/f3d_bleed.py | 528 +++++++++++------- fast64_internal/f3d/f3d_gbi.py | 226 ++++---- fast64_internal/f3d/f3d_parser.py | 6 +- fast64_internal/f3d/f3d_texture_writer.py | 10 +- fast64_internal/f3d/f3d_writer.py | 169 ++---- fast64_internal/oot/oot_model_classes.py | 2 +- fast64_internal/sm64/settings/properties.py | 13 + fast64_internal/sm64/sm64_f3d_writer.py | 26 +- fast64_internal/sm64/sm64_geolayout_bone.py | 31 +- .../sm64/sm64_geolayout_classes.py | 93 ++- fast64_internal/sm64/sm64_geolayout_writer.py | 36 +- fast64_internal/sm64/sm64_level_writer.py | 3 +- 12 files changed, 667 insertions(+), 476 deletions(-) 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: