From 255e4f500efb4696e6f3864fd65e209145f92ac3 Mon Sep 17 00:00:00 2001 From: Ricky Groenewald <16112032+ricky-groenewald@users.noreply.github.com> Date: Sat, 18 Apr 2026 23:49:06 +1000 Subject: [PATCH 1/2] feat: Per-texture filter kwarg on add_{static,dynamic,raw}_texture (#773) --- dearpygui/_dearpygui.pyi | 8 +-- dearpygui/_dearpygui_RTD.py | 5 ++ dearpygui/dearpygui.py | 17 +++--- src/dearpygui.cpp | 3 ++ src/mvAppItem.cpp | 9 ++-- src/mvAppleSpecifics.h | 10 ++-- src/mvBasicWidgets.cpp | 9 ++++ src/mvDrawings.cpp | 7 +++ src/mvGraphics_apple.mm | 102 ++++++++++++++++++++++++++++++++++++ src/mvGraphics_win32.cpp | 16 ++++++ src/mvPlotting.cpp | 5 ++ src/mvTextureItems.cpp | 20 +++++-- src/mvTextureItems.h | 2 + src/mvUtilities.h | 13 +++-- src/mvUtilities_apple.mm | 36 +++++++++++-- src/mvUtilities_linux.cpp | 29 ++++++---- src/mvUtilities_win32.cpp | 36 +++++++++++-- src/mvViewport_apple.mm | 2 + src/mvWindowsSpecifics.h | 1 + 19 files changed, 293 insertions(+), 37 deletions(-) diff --git a/dearpygui/_dearpygui.pyi b/dearpygui/_dearpygui.pyi index 456a4b845..a310dc819 100644 --- a/dearpygui/_dearpygui.pyi +++ b/dearpygui/_dearpygui.pyi @@ -178,7 +178,7 @@ def add_drawlist(width : int, height : int, *, label: str ='', user_data: Any =' """Adds a drawing canvas.""" ... -def add_dynamic_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='') -> Union[int, str]: +def add_dynamic_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', filter: int ='') -> Union[int, str]: """Adds a dynamic texture.""" ... @@ -455,7 +455,7 @@ def add_radio_button(items : Union[List[str], Tuple[str, ...]] ='', *, label: st """Adds a set of radio buttons. If items keyword is empty, nothing will be shown.""" ... -def add_raw_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', format: int ='', parent: Union[int, str] ='') -> Union[int, str]: +def add_raw_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', format: int ='', parent: Union[int, str] ='', filter: int ='') -> Union[int, str]: """Adds a raw texture.""" ... @@ -519,7 +519,7 @@ def add_stair_series(x : Union[List[float], Tuple[float, ...]], y : Union[List[f """Adds a stair series to a plot.""" ... -def add_static_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='') -> Union[int, str]: +def add_static_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', filter: int ='') -> Union[int, str]: """Adds a static texture.""" ... @@ -1553,6 +1553,8 @@ mvTreeLines_Full=0 mvTreeLines_ToNodes=0 mvFormat_Float_rgba=0 mvFormat_Float_rgb=0 +mvTextureFilter_Linear=0 +mvTextureFilter_Nearest=0 mvThemeCat_Core=0 mvThemeCat_Plots=0 mvThemeCat_Nodes=0 diff --git a/dearpygui/_dearpygui_RTD.py b/dearpygui/_dearpygui_RTD.py index 2cd741717..af2881854 100644 --- a/dearpygui/_dearpygui_RTD.py +++ b/dearpygui/_dearpygui_RTD.py @@ -4101,6 +4101,7 @@ def add_dynamic_texture(width, height, default_value, **kwargs): use_internal_label (bool, optional): Use generated internal label instead of user specified (appends ### uuid). tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + filter (int, optional): Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest). id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -5853,6 +5854,7 @@ def add_raw_texture(width, height, default_value, **kwargs): tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. format (int, optional): Data format. parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + filter (int, optional): Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest). id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -6308,6 +6310,7 @@ def add_static_texture(width, height, default_value, **kwargs): use_internal_label (bool, optional): Use generated internal label instead of user specified (appends ### uuid). tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + filter (int, optional): Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest). id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -9218,6 +9221,8 @@ def unstage(item): mvTreeLines_ToNodes=internal_dpg.mvTreeLines_ToNodes mvFormat_Float_rgba=internal_dpg.mvFormat_Float_rgba mvFormat_Float_rgb=internal_dpg.mvFormat_Float_rgb +mvTextureFilter_Linear=internal_dpg.mvTextureFilter_Linear +mvTextureFilter_Nearest=internal_dpg.mvTextureFilter_Nearest mvThemeCat_Core=internal_dpg.mvThemeCat_Core mvThemeCat_Plots=internal_dpg.mvThemeCat_Plots mvThemeCat_Nodes=internal_dpg.mvThemeCat_Nodes diff --git a/dearpygui/dearpygui.py b/dearpygui/dearpygui.py index 1e23f10c3..ea445a290 100644 --- a/dearpygui/dearpygui.py +++ b/dearpygui/dearpygui.py @@ -4536,7 +4536,7 @@ def add_drawlist(width : int, height : int, *, label: str =None, user_data: Any return internal_dpg.add_drawlist(width, height, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, before=before, callback=callback, show=show, pos=pos, filter_key=filter_key, tracked=tracked, track_offset=track_offset, **kwargs) -def add_dynamic_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =internal_dpg.mvReservedUUID_2, **kwargs) -> Union[int, str]: +def add_dynamic_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =internal_dpg.mvReservedUUID_2, filter: int =0, **kwargs) -> Union[int, str]: """ Adds a dynamic texture. Args: @@ -4548,6 +4548,7 @@ def add_dynamic_texture(width : int, height : int, default_value : Union[List[fl use_internal_label (bool, optional): Use generated internal label instead of user specified (appends ### uuid). tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + filter (int, optional): Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest). id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -4557,7 +4558,7 @@ def add_dynamic_texture(width : int, height : int, default_value : Union[List[fl warnings.warn('id keyword renamed to tag', DeprecationWarning, 2) tag=kwargs['id'] - return internal_dpg.add_dynamic_texture(width, height, default_value, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, **kwargs) + return internal_dpg.add_dynamic_texture(width, height, default_value, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, filter=filter, **kwargs) def add_error_series(x : Union[List[float], Tuple[float, ...]], y : Union[List[float], Tuple[float, ...]], negative : Union[List[float], Tuple[float, ...]], positive : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, before: Union[int, str] =0, source: Union[int, str] =0, show: bool =True, contribute_to_bounds: bool =True, horizontal: bool =False, **kwargs) -> Union[int, str]: """ Adds an error series to a plot. @@ -6639,7 +6640,7 @@ def add_radio_button(items : Union[List[str], Tuple[str, ...]] =(), *, label: st return internal_dpg.add_radio_button(items, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, indent=indent, parent=parent, before=before, source=source, payload_type=payload_type, callback=callback, drag_callback=drag_callback, drop_callback=drop_callback, show=show, enabled=enabled, pos=pos, filter_key=filter_key, tracked=tracked, track_offset=track_offset, default_value=default_value, horizontal=horizontal, **kwargs) -def add_raw_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, format: int =internal_dpg.mvFormat_Float_rgba, parent: Union[int, str] =internal_dpg.mvReservedUUID_2, **kwargs) -> Union[int, str]: +def add_raw_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, format: int =internal_dpg.mvFormat_Float_rgba, parent: Union[int, str] =internal_dpg.mvReservedUUID_2, filter: int =0, **kwargs) -> Union[int, str]: """ Adds a raw texture. Args: @@ -6652,6 +6653,7 @@ def add_raw_texture(width : int, height : int, default_value : Union[List[float] tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. format (int, optional): Data format. parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + filter (int, optional): Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest). id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -6661,7 +6663,7 @@ def add_raw_texture(width : int, height : int, default_value : Union[List[float] warnings.warn('id keyword renamed to tag', DeprecationWarning, 2) tag=kwargs['id'] - return internal_dpg.add_raw_texture(width, height, default_value, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, format=format, parent=parent, **kwargs) + return internal_dpg.add_raw_texture(width, height, default_value, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, format=format, parent=parent, filter=filter, **kwargs) def add_scatter_series(x : Union[List[float], Tuple[float, ...]], y : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, before: Union[int, str] =0, source: Union[int, str] =0, show: bool =True, no_clip: bool =False, **kwargs) -> Union[int, str]: """ Adds a scatter series to a plot. @@ -7159,7 +7161,7 @@ def add_stair_series(x : Union[List[float], Tuple[float, ...]], y : Union[List[f return internal_dpg.add_stair_series(x, y, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, before=before, source=source, show=show, pre_step=pre_step, shaded=shaded, **kwargs) -def add_static_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =internal_dpg.mvReservedUUID_2, **kwargs) -> Union[int, str]: +def add_static_texture(width : int, height : int, default_value : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =internal_dpg.mvReservedUUID_2, filter: int =0, **kwargs) -> Union[int, str]: """ Adds a static texture. Args: @@ -7171,6 +7173,7 @@ def add_static_texture(width : int, height : int, default_value : Union[List[flo use_internal_label (bool, optional): Use generated internal label instead of user specified (appends ### uuid). tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + filter (int, optional): Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest). id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -7180,7 +7183,7 @@ def add_static_texture(width : int, height : int, default_value : Union[List[flo warnings.warn('id keyword renamed to tag', DeprecationWarning, 2) tag=kwargs['id'] - return internal_dpg.add_static_texture(width, height, default_value, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, **kwargs) + return internal_dpg.add_static_texture(width, height, default_value, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, filter=filter, **kwargs) def add_stem_series(x : Union[List[float], Tuple[float, ...]], y : Union[List[float], Tuple[float, ...]], *, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, indent: int =-1, parent: Union[int, str] =0, before: Union[int, str] =0, source: Union[int, str] =0, show: bool =True, horizontal: bool =False, **kwargs) -> Union[int, str]: """ Adds a stem series to a plot. @@ -10311,6 +10314,8 @@ def unstage(item : Union[int, str], **kwargs) -> None: mvTreeLines_ToNodes=internal_dpg.mvTreeLines_ToNodes mvFormat_Float_rgba=internal_dpg.mvFormat_Float_rgba mvFormat_Float_rgb=internal_dpg.mvFormat_Float_rgb +mvTextureFilter_Linear=internal_dpg.mvTextureFilter_Linear +mvTextureFilter_Nearest=internal_dpg.mvTextureFilter_Nearest mvThemeCat_Core=internal_dpg.mvThemeCat_Core mvThemeCat_Plots=internal_dpg.mvThemeCat_Plots mvThemeCat_Nodes=internal_dpg.mvThemeCat_Nodes diff --git a/src/dearpygui.cpp b/src/dearpygui.cpp index 48e10493d..5db4caddc 100644 --- a/src/dearpygui.cpp +++ b/src/dearpygui.cpp @@ -229,6 +229,9 @@ GetModuleConstants() ModuleConstants.push_back({ "mvFormat_Float_rgba", 0L }); ModuleConstants.push_back({ "mvFormat_Float_rgb", 1L }); + ModuleConstants.push_back({ "mvTextureFilter_Linear", 0L }); + ModuleConstants.push_back({ "mvTextureFilter_Nearest", 1L }); + ModuleConstants.push_back({ "mvThemeCat_Core", 0L }); ModuleConstants.push_back({ "mvThemeCat_Plots", 1L}); ModuleConstants.push_back({ "mvThemeCat_Nodes", 2L}); diff --git a/src/mvAppItem.cpp b/src/mvAppItem.cpp index 10fd1b73d..3b58a6abe 100644 --- a/src/mvAppItem.cpp +++ b/src/mvAppItem.cpp @@ -4636,7 +4636,7 @@ DearPyGui::GetEntityParser(mvAppItemType type) setup.createContextManager = true; break; } - case mvAppItemType::mvStaticTexture: + case mvAppItemType::mvStaticTexture: { AddCommonArgs(args, (CommonParserArgs)( MV_PARSER_ARG_ID) @@ -4646,12 +4646,13 @@ DearPyGui::GetEntityParser(mvAppItemType type) args.push_back({ mvPyDataType::Integer, "height" }); args.push_back({ mvPyDataType::FloatList, "default_value" }); args.push_back({ mvPyDataType::UUID, "parent", mvArgType::KEYWORD_ARG, "internal_dpg.mvReservedUUID_2", "Parent to add this item to. (runtime adding)" }); + args.push_back({ mvPyDataType::Integer, "filter", mvArgType::KEYWORD_ARG, "0", "Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest)." }); setup.about = "Adds a static texture."; setup.category = { "Textures", "Widgets" }; break; } - case mvAppItemType::mvDynamicTexture: + case mvAppItemType::mvDynamicTexture: { AddCommonArgs(args, (CommonParserArgs)( MV_PARSER_ARG_ID) @@ -4661,6 +4662,7 @@ DearPyGui::GetEntityParser(mvAppItemType type) args.push_back({ mvPyDataType::Integer, "height" }); args.push_back({ mvPyDataType::FloatList, "default_value" }); args.push_back({ mvPyDataType::UUID, "parent", mvArgType::KEYWORD_ARG, "internal_dpg.mvReservedUUID_2", "Parent to add this item to. (runtime adding)" }); + args.push_back({ mvPyDataType::Integer, "filter", mvArgType::KEYWORD_ARG, "0", "Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest)." }); setup.about = "Adds a dynamic texture."; setup.category = { "Textures", "Widgets" }; @@ -5414,7 +5416,7 @@ DearPyGui::GetEntityParser(mvAppItemType type) setup.category = { "Widgets", "Values" }; break; } - case mvAppItemType::mvRawTexture: + case mvAppItemType::mvRawTexture: { AddCommonArgs(args, (CommonParserArgs)( MV_PARSER_ARG_ID) @@ -5425,6 +5427,7 @@ DearPyGui::GetEntityParser(mvAppItemType type) args.push_back({ mvPyDataType::FloatList, "default_value" }); args.push_back({ mvPyDataType::Integer, "format", mvArgType::KEYWORD_ARG, "internal_dpg.mvFormat_Float_rgba", "Data format." }); args.push_back({ mvPyDataType::UUID, "parent", mvArgType::KEYWORD_ARG, "internal_dpg.mvReservedUUID_2", "Parent to add this item to. (runtime adding)" }); + args.push_back({ mvPyDataType::Integer, "filter", mvArgType::KEYWORD_ARG, "0", "Texture sampling mode (mvTextureFilter_Linear or mvTextureFilter_Nearest)." }); setup.about = "Adds a raw texture."; setup.category = { "Textures", "Widgets" }; diff --git a/src/mvAppleSpecifics.h b/src/mvAppleSpecifics.h index b44a8bb2c..f3444483b 100644 --- a/src/mvAppleSpecifics.h +++ b/src/mvAppleSpecifics.h @@ -16,7 +16,11 @@ struct mvViewportData struct mvGraphics_Metal { - MTLRenderPassDescriptor* renderPassDescriptor; - id commandQueue; - id device; + MTLRenderPassDescriptor* renderPassDescriptor; + id commandQueue; + id device; + + id nearestPipelineState; + id nearestSampler; + id __unsafe_unretained currentEncoder; }; \ No newline at end of file diff --git a/src/mvBasicWidgets.cpp b/src/mvBasicWidgets.cpp index 6111f2f6c..a3b0b08f3 100644 --- a/src/mvBasicWidgets.cpp +++ b/src/mvBasicWidgets.cpp @@ -8,6 +8,7 @@ #include "mvContainers.h" #include "mvItemHandlers.h" #include "mvTextureItems.h" +#include "mvUtilities.h" #include #include @@ -5604,6 +5605,9 @@ DearPyGui::draw_image(ImDrawList* drawlist, mvAppItem& item, mvImageConfig& conf ImGuiContext& g = *GImGui; ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, (config.borderColor.a > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); ImGui::PushStyleColor(ImGuiCol_Border, config.borderColor.toVec4()); + const bool wantNearest = (static_cast(config.texture.get())->_filter == 1); + ImDrawList* windowDrawList = wantNearest ? ImGui::GetWindowDrawList() : nullptr; + if (wantNearest) EnterNearestFilterScope(windowDrawList); ImGui::ImageWithBg( texture, ImVec2((float)item.config.width, (float)item.config.height), @@ -5611,6 +5615,7 @@ DearPyGui::draw_image(ImDrawList* drawlist, mvAppItem& item, mvImageConfig& conf ImVec2(config.uv_max.x, config.uv_max.y), ImVec4(0, 0, 0, 0), config.tintColor.toVec4()); + if (wantNearest) LeaveNearestFilterScope(windowDrawList); ImGui::PopStyleColor(); ImGui::PopStyleVar(); } @@ -5717,12 +5722,16 @@ DearPyGui::draw_image_button(ImDrawList* drawlist, mvAppItem& item, mvImageButto if (config.framePadding >= 0) ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)config.framePadding, (float)config.framePadding)); + const bool wantNearest = (static_cast(config.texture.get())->_filter == 1); + ImDrawList* windowDrawList = wantNearest ? ImGui::GetWindowDrawList() : nullptr; + if (wantNearest) EnterNearestFilterScope(windowDrawList); if (ImGui::ImageButton(item.info.internalLabel.c_str(), texture, ImVec2((float)item.config.width, (float)item.config.height), ImVec2(config.uv_min.x, config.uv_min.y), ImVec2(config.uv_max.x, config.uv_max.y), config.backgroundColor, config.tintColor)) { item.submitCallback(); } + if (wantNearest) LeaveNearestFilterScope(windowDrawList); if (config.framePadding >= 0) ImGui::PopStyleVar(); diff --git a/src/mvDrawings.cpp b/src/mvDrawings.cpp index be3dc6232..39e4b9a87 100644 --- a/src/mvDrawings.cpp +++ b/src/mvDrawings.cpp @@ -8,6 +8,7 @@ #include "mvFontItems.h" #include "mvItemHandlers.h" #include "mvTextureItems.h" +#include "mvUtilities.h" #include "mvCustomTypes.h" #include @@ -561,6 +562,8 @@ void mvDrawImage::draw(ImDrawList* drawlist, float x, float y) if (mvClipPoint(drawInfo->clipViewport, tpmax)) return; } + const bool wantNearest = (static_cast(_texture.get())->_filter == 1); + if (wantNearest) EnterNearestFilterScope(drawlist); if (ImPlot::GetCurrentContext()->CurrentPlot) drawlist->AddImage(texture, ImPlot::PlotToPixels(tpmin), ImPlot::PlotToPixels(tpmax), _uv_min, _uv_max, _color); else @@ -568,6 +571,7 @@ void mvDrawImage::draw(ImDrawList* drawlist, float x, float y) mvVec2 start = { x, y }; drawlist->AddImage(texture, tpmin + start, tpmax + start, _uv_min, _uv_max, _color); } + if (wantNearest) LeaveNearestFilterScope(drawlist); } } @@ -679,6 +683,8 @@ void mvDrawImageQuad::draw(ImDrawList* drawlist, float x, float y) if (mvClipPoint(drawInfo->clipViewport, tp4)) return; } + const bool wantNearest = (static_cast(_texture.get())->_filter == 1); + if (wantNearest) EnterNearestFilterScope(drawlist); if (ImPlot::GetCurrentContext()->CurrentPlot) drawlist->AddImageQuad(texture, ImPlot::PlotToPixels(tp1), ImPlot::PlotToPixels(tp2), ImPlot::PlotToPixels(tp3), ImPlot::PlotToPixels(tp4), @@ -688,6 +694,7 @@ void mvDrawImageQuad::draw(ImDrawList* drawlist, float x, float y) mvVec2 start = { x, y }; drawlist->AddImageQuad(texture, tp1 + start, tp2 + start, tp3 + start, tp4 + start, _uv1, _uv2, _uv3, _uv4, _color); } + if (wantNearest) LeaveNearestFilterScope(drawlist); } } diff --git a/src/mvGraphics_apple.mm b/src/mvGraphics_apple.mm index b6ce2f135..bd914004c 100644 --- a/src/mvGraphics_apple.mm +++ b/src/mvGraphics_apple.mm @@ -7,6 +7,106 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_metal.h" +static void +BuildNearestFilterMetalState(mvGraphics_Metal* graphicsData, MTLPixelFormat colorPixelFormat) +{ + MTLSamplerDescriptor* samplerDesc = [MTLSamplerDescriptor new]; + samplerDesc.minFilter = MTLSamplerMinMagFilterNearest; + samplerDesc.magFilter = MTLSamplerMinMagFilterNearest; + samplerDesc.mipFilter = MTLSamplerMipFilterNearest; + samplerDesc.sAddressMode = MTLSamplerAddressModeClampToEdge; + samplerDesc.tAddressMode = MTLSamplerAddressModeClampToEdge; + samplerDesc.rAddressMode = MTLSamplerAddressModeClampToEdge; + graphicsData->nearestSampler = [graphicsData->device newSamplerStateWithDescriptor:samplerDesc]; + + // Mirrors imgui_impl_metal.mm's shader with a bindable sampler. + NSString* shaderSource = @"" + "#include \n" + "using namespace metal;\n" + "\n" + "struct Uniforms { float4x4 projectionMatrix; };\n" + "\n" + "struct VertexIn {\n" + " float2 position [[attribute(0)]];\n" + " float2 texCoords [[attribute(1)]];\n" + " uchar4 color [[attribute(2)]];\n" + "};\n" + "\n" + "struct VertexOut {\n" + " float4 position [[position]];\n" + " float2 texCoords;\n" + " float4 color;\n" + "};\n" + "\n" + "vertex VertexOut dpg_vertex_main(VertexIn in [[stage_in]],\n" + " constant Uniforms &uniforms [[buffer(1)]]) {\n" + " VertexOut out;\n" + " out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n" + " out.texCoords = in.texCoords;\n" + " out.color = float4(in.color) / float4(255.0);\n" + " return out;\n" + "}\n" + "\n" + "fragment half4 dpg_fragment_main(VertexOut in [[stage_in]],\n" + " texture2d texture [[texture(0)]],\n" + " sampler textureSampler [[sampler(0)]]) {\n" + " half4 texColor = texture.sample(textureSampler, in.texCoords);\n" + " return half4(in.color) * texColor;\n" + "}\n"; + + NSError* error = nil; + id library = [graphicsData->device newLibraryWithSource:shaderSource options:nil error:&error]; + if (library == nil) + { + NSLog(@"DPG: failed to compile nearest-filter Metal library: %@", error); + return; + } + + id vertexFunction = [library newFunctionWithName:@"dpg_vertex_main"]; + id fragmentFunction = [library newFunctionWithName:@"dpg_fragment_main"]; + if (!vertexFunction || !fragmentFunction) + { + NSLog(@"DPG: failed to resolve nearest-filter Metal functions"); + return; + } + + MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; + vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos); + vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; + vertexDescriptor.attributes[0].bufferIndex = 0; + vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv); + vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; + vertexDescriptor.attributes[1].bufferIndex = 0; + vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col); + vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; + vertexDescriptor.attributes[2].bufferIndex = 0; + vertexDescriptor.layouts[0].stepRate = 1; + vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; + vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert); + + MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineDescriptor.vertexFunction = vertexFunction; + pipelineDescriptor.fragmentFunction = fragmentFunction; + pipelineDescriptor.vertexDescriptor = vertexDescriptor; + pipelineDescriptor.rasterSampleCount = 1; + pipelineDescriptor.colorAttachments[0].pixelFormat = colorPixelFormat; + pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; + pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; + pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor= MTLBlendFactorOne; + pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.depthAttachmentPixelFormat = MTLPixelFormatInvalid; + pipelineDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatInvalid; + + graphicsData->nearestPipelineState = [graphicsData->device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error]; + if (error != nil) + NSLog(@"DPG: failed to create nearest-filter pipeline state: %@", error); + + static_assert(sizeof(ImDrawVert) == 20, "ImDrawVert layout changed; update DPG nearest-filter pipeline"); +} + mvGraphics setup_graphics(mvViewport &viewport) { @@ -30,6 +130,8 @@ graphicsData->renderPassDescriptor = [MTLRenderPassDescriptor new]; + BuildNearestFilterMetalState(graphicsData, viewportData->layer.pixelFormat); + return graphics; } diff --git a/src/mvGraphics_win32.cpp b/src/mvGraphics_win32.cpp index 24da2ec19..8268bdca2 100644 --- a/src/mvGraphics_win32.cpp +++ b/src/mvGraphics_win32.cpp @@ -124,6 +124,16 @@ setup_graphics(mvViewport& viewport) ImGui_ImplDX11_Init(graphicsData->device, graphicsData->deviceContext); + { + D3D11_SAMPLER_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + graphicsData->device->CreateSamplerState(&desc, &graphicsData->pTexSamplerNearest); + } + return graphics; } @@ -134,6 +144,12 @@ cleanup_graphics(mvGraphics& graphics) ImGui_ImplDX11_Shutdown(); + if (graphicsData->pTexSamplerNearest) + { + graphicsData->pTexSamplerNearest->Release(); + graphicsData->pTexSamplerNearest = nullptr; + } + if (graphicsData->target) { graphicsData->target->Release(); diff --git a/src/mvPlotting.cpp b/src/mvPlotting.cpp index d11375106..22cd14d64 100644 --- a/src/mvPlotting.cpp +++ b/src/mvPlotting.cpp @@ -10,6 +10,7 @@ #include "mvThemes.h" #include "mvContainers.h" #include "mvTextureItems.h" +#include "mvUtilities.h" #include "mvItemHandlers.h" #include @@ -1934,7 +1935,11 @@ DearPyGui::draw_image_series(ImDrawList* drawlist, mvAppItem& item, mvImageSerie // ImTextureRef texture = static_cast(config._texture.get())->getTexRef(); ImTextureID texture = static_cast(config._texture.get())->_texture; + const bool wantNearest = (static_cast(config._texture.get())->_filter == 1); + ImDrawList* plotDrawList = wantNearest ? ImPlot::GetPlotDrawList() : nullptr; + if (wantNearest) EnterNearestFilterScope(plotDrawList); ImPlot::PlotImage(item.info.internalLabel.c_str(), texture, config.bounds_min, config.bounds_max, config.uv_min, config.uv_max, config.tintColor, config.flags); + if (wantNearest) LeaveNearestFilterScope(plotDrawList); // Begin a popup for a legend entry. if (ImPlot::BeginLegendPopup(item.info.internalLabel.c_str(), 1)) diff --git a/src/mvTextureItems.cpp b/src/mvTextureItems.cpp index baf606a87..fd83879ac 100644 --- a/src/mvTextureItems.cpp +++ b/src/mvTextureItems.cpp @@ -157,7 +157,7 @@ void mvDynamicTexture::draw(ImDrawList* drawlist, float x, float y) if (_dirty) { - _texture = LoadTextureFromArrayDynamic(_permWidth, _permHeight, _value->data()); + _texture = LoadTextureFromArrayDynamic(_permWidth, _permHeight, _value->data(), _filter); if (_texture == ImTextureID_Invalid) state.ok = false; @@ -187,6 +187,8 @@ void mvDynamicTexture::handleSpecificKeywordArgs(PyObject* dict) if (dict == nullptr) return; + if (PyObject* item = PyDict_GetItemString(dict, "filter")) + _filter = ToInt(item); } void mvDynamicTexture::getSpecificConfiguration(PyObject* dict) @@ -233,7 +235,7 @@ void mvRawTexture::draw(ImDrawList* drawlist, float x, float y) return; if (_componentType == ComponentType::MV_FLOAT_COMPONENT) - _texture = LoadTextureFromArrayRaw(_permWidth, _permHeight, (float*)_value, _components); + _texture = LoadTextureFromArrayRaw(_permWidth, _permHeight, (float*)_value, _components, _filter); if (_texture == ImTextureID_Invalid) state.ok = false; @@ -281,6 +283,9 @@ void mvRawTexture::handleSpecificKeywordArgs(PyObject* dict) _componentType = mvRawTexture::ComponentType::MV_FLOAT_COMPONENT; } } + + if (PyObject* item = PyDict_GetItemString(dict, "filter")) + _filter = ToInt(item); } void mvRawTexture::getSpecificConfiguration(PyObject* dict) @@ -309,7 +314,7 @@ void mvStaticTexture::draw(ImDrawList* drawlist, float x, float y) if (!state.ok) return; - _texture = LoadTextureFromArray(_permWidth, _permHeight, _value->data()); + _texture = LoadTextureFromArray(_permWidth, _permHeight, _value->data(), _filter); if (_texture == ImTextureID_Invalid) { @@ -334,6 +339,15 @@ void mvStaticTexture::handleSpecificRequiredArgs(PyObject* dict) *_value = ToFloatVect(PyTuple_GetItem(dict, 2)); } +void mvStaticTexture::handleSpecificKeywordArgs(PyObject* dict) +{ + if (dict == nullptr) + return; + + if (PyObject* item = PyDict_GetItemString(dict, "filter")) + _filter = ToInt(item); +} + PyObject* mvStaticTexture::getPyValue() { return ToPyList(*_value); diff --git a/src/mvTextureItems.h b/src/mvTextureItems.h index ce827adba..82f6fb277 100644 --- a/src/mvTextureItems.h +++ b/src/mvTextureItems.h @@ -35,6 +35,7 @@ class mvTextureItem : public mvAppItem public: ImTextureID _texture = ImTextureID_Invalid; + int _filter = 0; // mvTextureFilter_* }; @@ -47,6 +48,7 @@ class mvStaticTexture : public mvTextureItem void draw(ImDrawList* drawlist, float x, float y) override; void handleSpecificRequiredArgs(PyObject* dict) override; + void handleSpecificKeywordArgs(PyObject* dict) override; // values void setDataSource(mvUUID dataSource) override; diff --git a/src/mvUtilities.h b/src/mvUtilities.h index 24aa4a747..47e7e7a82 100644 --- a/src/mvUtilities.h +++ b/src/mvUtilities.h @@ -19,21 +19,26 @@ typedef _object PyObject; #endif struct PymvBuffer; +struct ImDrawList; // general void FreeTexture(ImTextureID texture); b8 UnloadTexture(const std::string& filename); - + +// Bracket ImDrawList::AddImage calls to render with nearest-neighbor sampling. +void EnterNearestFilterScope(ImDrawList* drawlist); +void LeaveNearestFilterScope(ImDrawList* drawlist); + // static textures ImTextureID LoadTextureFromFile(const char* filename, i32& width, i32& height); -ImTextureID LoadTextureFromArray(u32 width, u32 height, f32* data); +ImTextureID LoadTextureFromArray(u32 width, u32 height, f32* data, i32 filter = 0); // dynamic textures -ImTextureID LoadTextureFromArrayDynamic(u32 width, u32 height, f32* data); +ImTextureID LoadTextureFromArrayDynamic(u32 width, u32 height, f32* data, i32 filter = 0); void UpdateTexture(ImTextureID texture, u32 width, u32 height, std::vector& data); // raw textures -ImTextureID LoadTextureFromArrayRaw(u32 width, u32 height, f32* data, i32 components); +ImTextureID LoadTextureFromArrayRaw(u32 width, u32 height, f32* data, i32 components, i32 filter = 0); void UpdateRawTexture(ImTextureID texture, u32 width, u32 height, f32* data, i32 components); // framebuffer output diff --git a/src/mvUtilities_apple.mm b/src/mvUtilities_apple.mm index f6e25f1ef..8995ccef4 100644 --- a/src/mvUtilities_apple.mm +++ b/src/mvUtilities_apple.mm @@ -4,6 +4,7 @@ #include "mvUtilities.h" #include "mvViewport.h" +#include "mvContext.h" #include "mvAppleSpecifics.h" @@ -13,6 +14,7 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" +#include #include #include #include @@ -36,8 +38,9 @@ } ImTextureID -LoadTextureFromArray(unsigned width, unsigned height, float* data) +LoadTextureFromArray(unsigned width, unsigned height, float* data, int filter) { + (void)filter; mvGraphics& graphics = GContext->graphics; auto graphicsData = (mvGraphics_Metal*)graphics.backendSpecifics; @@ -55,8 +58,9 @@ } ImTextureID -LoadTextureFromArrayDynamic(unsigned width, unsigned height, float* data) +LoadTextureFromArrayDynamic(unsigned width, unsigned height, float* data, int filter) { + (void)filter; mvGraphics& graphics = GContext->graphics; auto graphicsData = (mvGraphics_Metal*)graphics.backendSpecifics; @@ -74,8 +78,9 @@ } ImTextureID -LoadTextureFromArrayRaw(unsigned width, unsigned height, float* data, int components) +LoadTextureFromArrayRaw(unsigned width, unsigned height, float* data, int components, int filter) { + (void)filter; mvGraphics& graphics = GContext->graphics; auto graphicsData = (mvGraphics_Metal*)graphics.backendSpecifics; @@ -151,4 +156,29 @@ { id out_srv = (__bridge id )texture; [out_srv replaceRegion:MTLRegionMake2D(0, 0, width, height) mipmapLevel:0 withBytes:data bytesPerRow:width * components * 4]; +} + +static void +DpgMetalBindNearestPipeline(const ImDrawList* /*parent_list*/, const ImDrawCmd* /*cmd*/) +{ + auto* graphicsData = (mvGraphics_Metal*)GContext->graphics.backendSpecifics; + if (graphicsData == nullptr || graphicsData->currentEncoder == nil || + graphicsData->nearestPipelineState == nil || graphicsData->nearestSampler == nil) + return; + [graphicsData->currentEncoder setRenderPipelineState:graphicsData->nearestPipelineState]; + [graphicsData->currentEncoder setFragmentSamplerState:graphicsData->nearestSampler atIndex:0]; +} + +void +EnterNearestFilterScope(ImDrawList* drawlist) +{ + if (drawlist == nullptr) return; + drawlist->AddCallback(DpgMetalBindNearestPipeline, nullptr); +} + +void +LeaveNearestFilterScope(ImDrawList* drawlist) +{ + if (drawlist == nullptr) return; + drawlist->AddCallback(ImDrawCallback_ResetRenderState, nullptr); } \ No newline at end of file diff --git a/src/mvUtilities_linux.cpp b/src/mvUtilities_linux.cpp index f1e0a02eb..de730d456 100644 --- a/src/mvUtilities_linux.cpp +++ b/src/mvUtilities_linux.cpp @@ -92,8 +92,13 @@ OutputFrameBuffer(const char* filepath) free(data); } +static GLint FilterToGL(int filter) +{ + return (filter == 1) ? GL_NEAREST : GL_LINEAR; +} + ImTextureID -LoadTextureFromArray(unsigned width, unsigned height, float* data) +LoadTextureFromArray(unsigned width, unsigned height, float* data, int filter) { // Create a OpenGL texture identifier @@ -102,8 +107,9 @@ LoadTextureFromArray(unsigned width, unsigned height, float* data) glBindTexture(GL_TEXTURE_2D, image_texture); // Setup filtering parameters for display - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + GLint glFilter = FilterToGL(filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glFilter); // Upload pixels into texture glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); @@ -113,7 +119,7 @@ LoadTextureFromArray(unsigned width, unsigned height, float* data) } ImTextureID -LoadTextureFromArrayDynamic(unsigned width, unsigned height, float* data) +LoadTextureFromArrayDynamic(unsigned width, unsigned height, float* data, int filter) { // Create a OpenGL texture identifier @@ -122,8 +128,9 @@ LoadTextureFromArrayDynamic(unsigned width, unsigned height, float* data) glBindTexture(GL_TEXTURE_2D, image_texture); // Setup filtering parameters for display - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + GLint glFilter = FilterToGL(filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glFilter); // Upload pixels into texture glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); @@ -140,7 +147,7 @@ LoadTextureFromArrayDynamic(unsigned width, unsigned height, float* data) } ImTextureID -LoadTextureFromArrayRaw(unsigned width, unsigned height, float* data, int components) +LoadTextureFromArrayRaw(unsigned width, unsigned height, float* data, int components, int filter) { // Create a OpenGL texture identifier @@ -149,8 +156,9 @@ LoadTextureFromArrayRaw(unsigned width, unsigned height, float* data, int compon glBindTexture(GL_TEXTURE_2D, image_texture); // Setup filtering parameters for display - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + GLint glFilter = FilterToGL(filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glFilter); // Upload pixels into texture glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); @@ -207,6 +215,9 @@ UnloadTexture(const std::string& filename) return true; } +void EnterNearestFilterScope(ImDrawList*) {} +void LeaveNearestFilterScope(ImDrawList*) {} + void FreeTexture(ImTextureID texture) { diff --git a/src/mvUtilities_win32.cpp b/src/mvUtilities_win32.cpp index 989f1ff53..a7762c8f8 100644 --- a/src/mvUtilities_win32.cpp +++ b/src/mvUtilities_win32.cpp @@ -5,6 +5,9 @@ #include "mvWindowsSpecifics.h" #include "mvCustomTypes.h" +#include "mvContext.h" + +#include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" @@ -145,8 +148,9 @@ LoadTextureFromFile(const char* filename, int& width, int& height) } ImTextureID -LoadTextureFromArray(unsigned width, unsigned height, float* data) +LoadTextureFromArray(unsigned width, unsigned height, float* data, int filter) { + (void)filter; mvGraphics_D3D11* graphicsData = (mvGraphics_D3D11*)GContext->graphics.backendSpecifics; ID3D11ShaderResourceView* out_srv = nullptr; @@ -223,8 +227,9 @@ LoadTextureFromArray(unsigned width, unsigned height, int* data) } ImTextureID -LoadTextureFromArrayDynamic(unsigned width, unsigned height, float* data) +LoadTextureFromArrayDynamic(unsigned width, unsigned height, float* data, int filter) { + (void)filter; mvGraphics_D3D11* graphicsData = (mvGraphics_D3D11*)GContext->graphics.backendSpecifics; ID3D11ShaderResourceView* out_srv = nullptr; @@ -388,8 +393,9 @@ UpdateRawTexture(ImTextureID texture, unsigned width, unsigned height, float* da } ImTextureID -LoadTextureFromArrayRaw(unsigned width, unsigned height, float* data, int components) +LoadTextureFromArrayRaw(unsigned width, unsigned height, float* data, int components, int filter) { + (void)filter; mvGraphics_D3D11* graphicsData = (mvGraphics_D3D11*)GContext->graphics.backendSpecifics; ID3D11ShaderResourceView* out_srv = nullptr; @@ -453,4 +459,28 @@ UnloadTexture(const std::string& filename) { // TODO : decide if cleanup is necessary return true; +} + +static void +DpgD3D11BindNearestSampler(const ImDrawList* /*parent_list*/, const ImDrawCmd* /*cmd*/) +{ + auto* graphicsData = (mvGraphics_D3D11*)GContext->graphics.backendSpecifics; + if (graphicsData == nullptr || graphicsData->deviceContext == nullptr || + graphicsData->pTexSamplerNearest == nullptr) + return; + graphicsData->deviceContext->PSSetSamplers(0, 1, &graphicsData->pTexSamplerNearest); +} + +void +EnterNearestFilterScope(ImDrawList* drawlist) +{ + if (drawlist == nullptr) return; + drawlist->AddCallback(DpgD3D11BindNearestSampler, nullptr); +} + +void +LeaveNearestFilterScope(ImDrawList* drawlist) +{ + if (drawlist == nullptr) return; + drawlist->AddCallback(ImDrawCallback_ResetRenderState, nullptr); } \ No newline at end of file diff --git a/src/mvViewport_apple.mm b/src/mvViewport_apple.mm index a448ae6c8..885037164 100644 --- a/src/mvViewport_apple.mm +++ b/src/mvViewport_apple.mm @@ -241,6 +241,7 @@ graphicsData->renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; graphicsData->renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:graphicsData->renderPassDescriptor]; + graphicsData->currentEncoder = renderEncoder; [renderEncoder pushDebugGroup:@"ImGui demo"]; @@ -269,6 +270,7 @@ [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; + graphicsData->currentEncoder = nil; [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; diff --git a/src/mvWindowsSpecifics.h b/src/mvWindowsSpecifics.h index 3d887ec1c..ab8f63d16 100644 --- a/src/mvWindowsSpecifics.h +++ b/src/mvWindowsSpecifics.h @@ -32,4 +32,5 @@ struct mvGraphics_D3D11 IDXGISwapChain* swapChain = nullptr; ID3D11RenderTargetView* target = nullptr; ID3D11Texture2D* backBuffer = nullptr; + ID3D11SamplerState* pTexSamplerNearest = nullptr; }; \ No newline at end of file From 76d3c853ff1b1b25d999f29c053a8e078bcb1174 Mon Sep 17 00:00:00 2001 From: Ricky Groenewald <16112032+ricky-groenewald@users.noreply.github.com> Date: Sun, 19 Apr 2026 09:45:13 +1000 Subject: [PATCH 2/2] feat: Make texture filter mutable via configure_item (#773) --- src/dearpygui.cpp | 4 +-- src/mvBasicWidgets.cpp | 4 +-- src/mvDrawings.cpp | 4 +-- src/mvPlotting.cpp | 2 +- src/mvTextureItems.cpp | 60 +++++++++++++++++++++++++++++++++++++-- src/mvTextureItems.h | 4 ++- src/mvUtilities.h | 15 ++++++++-- src/mvUtilities_apple.mm | 6 ++++ src/mvUtilities_linux.cpp | 12 +++++++- src/mvUtilities_win32.cpp | 6 ++++ 10 files changed, 102 insertions(+), 15 deletions(-) diff --git a/src/dearpygui.cpp b/src/dearpygui.cpp index 5db4caddc..4a24a5335 100644 --- a/src/dearpygui.cpp +++ b/src/dearpygui.cpp @@ -229,8 +229,8 @@ GetModuleConstants() ModuleConstants.push_back({ "mvFormat_Float_rgba", 0L }); ModuleConstants.push_back({ "mvFormat_Float_rgb", 1L }); - ModuleConstants.push_back({ "mvTextureFilter_Linear", 0L }); - ModuleConstants.push_back({ "mvTextureFilter_Nearest", 1L }); + ModuleConstants.push_back({ "mvTextureFilter_Linear", mvTextureFilter_Linear }); + ModuleConstants.push_back({ "mvTextureFilter_Nearest", mvTextureFilter_Nearest }); ModuleConstants.push_back({ "mvThemeCat_Core", 0L }); ModuleConstants.push_back({ "mvThemeCat_Plots", 1L}); diff --git a/src/mvBasicWidgets.cpp b/src/mvBasicWidgets.cpp index a3b0b08f3..075bf8a0b 100644 --- a/src/mvBasicWidgets.cpp +++ b/src/mvBasicWidgets.cpp @@ -5605,7 +5605,7 @@ DearPyGui::draw_image(ImDrawList* drawlist, mvAppItem& item, mvImageConfig& conf ImGuiContext& g = *GImGui; ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, (config.borderColor.a > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); ImGui::PushStyleColor(ImGuiCol_Border, config.borderColor.toVec4()); - const bool wantNearest = (static_cast(config.texture.get())->_filter == 1); + const bool wantNearest = (static_cast(config.texture.get())->_filter == mvTextureFilter_Nearest); ImDrawList* windowDrawList = wantNearest ? ImGui::GetWindowDrawList() : nullptr; if (wantNearest) EnterNearestFilterScope(windowDrawList); ImGui::ImageWithBg( @@ -5722,7 +5722,7 @@ DearPyGui::draw_image_button(ImDrawList* drawlist, mvAppItem& item, mvImageButto if (config.framePadding >= 0) ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)config.framePadding, (float)config.framePadding)); - const bool wantNearest = (static_cast(config.texture.get())->_filter == 1); + const bool wantNearest = (static_cast(config.texture.get())->_filter == mvTextureFilter_Nearest); ImDrawList* windowDrawList = wantNearest ? ImGui::GetWindowDrawList() : nullptr; if (wantNearest) EnterNearestFilterScope(windowDrawList); if (ImGui::ImageButton(item.info.internalLabel.c_str(), texture, ImVec2((float)item.config.width, (float)item.config.height), diff --git a/src/mvDrawings.cpp b/src/mvDrawings.cpp index 39e4b9a87..4884b9952 100644 --- a/src/mvDrawings.cpp +++ b/src/mvDrawings.cpp @@ -562,7 +562,7 @@ void mvDrawImage::draw(ImDrawList* drawlist, float x, float y) if (mvClipPoint(drawInfo->clipViewport, tpmax)) return; } - const bool wantNearest = (static_cast(_texture.get())->_filter == 1); + const bool wantNearest = (static_cast(_texture.get())->_filter == mvTextureFilter_Nearest); if (wantNearest) EnterNearestFilterScope(drawlist); if (ImPlot::GetCurrentContext()->CurrentPlot) drawlist->AddImage(texture, ImPlot::PlotToPixels(tpmin), ImPlot::PlotToPixels(tpmax), _uv_min, _uv_max, _color); @@ -683,7 +683,7 @@ void mvDrawImageQuad::draw(ImDrawList* drawlist, float x, float y) if (mvClipPoint(drawInfo->clipViewport, tp4)) return; } - const bool wantNearest = (static_cast(_texture.get())->_filter == 1); + const bool wantNearest = (static_cast(_texture.get())->_filter == mvTextureFilter_Nearest); if (wantNearest) EnterNearestFilterScope(drawlist); if (ImPlot::GetCurrentContext()->CurrentPlot) drawlist->AddImageQuad(texture, ImPlot::PlotToPixels(tp1), diff --git a/src/mvPlotting.cpp b/src/mvPlotting.cpp index 22cd14d64..d056fecc7 100644 --- a/src/mvPlotting.cpp +++ b/src/mvPlotting.cpp @@ -1935,7 +1935,7 @@ DearPyGui::draw_image_series(ImDrawList* drawlist, mvAppItem& item, mvImageSerie // ImTextureRef texture = static_cast(config._texture.get())->getTexRef(); ImTextureID texture = static_cast(config._texture.get())->_texture; - const bool wantNearest = (static_cast(config._texture.get())->_filter == 1); + const bool wantNearest = (static_cast(config._texture.get())->_filter == mvTextureFilter_Nearest); ImDrawList* plotDrawList = wantNearest ? ImPlot::GetPlotDrawList() : nullptr; if (wantNearest) EnterNearestFilterScope(plotDrawList); ImPlot::PlotImage(item.info.internalLabel.c_str(), texture, config.bounds_min, config.bounds_max, config.uv_min, config.uv_max, config.tintColor, config.flags); diff --git a/src/mvTextureItems.cpp b/src/mvTextureItems.cpp index fd83879ac..db2fcf139 100644 --- a/src/mvTextureItems.cpp +++ b/src/mvTextureItems.cpp @@ -163,9 +163,16 @@ void mvDynamicTexture::draw(ImDrawList* drawlist, float x, float y) state.ok = false; _dirty = false; + _filterDirty = false; return; } + if (_filterDirty && _texture != ImTextureID_Invalid) + { + ApplyTextureFilter(_texture, _filter); + _filterDirty = false; + } + UpdateTexture(_texture, _permWidth, _permHeight, *_value); } @@ -188,13 +195,22 @@ void mvDynamicTexture::handleSpecificKeywordArgs(PyObject* dict) return; if (PyObject* item = PyDict_GetItemString(dict, "filter")) - _filter = ToInt(item); + { + const int v = ToInt(item); + if (v != _filter) + { + _filter = v; + _filterDirty = true; + } + } } void mvDynamicTexture::getSpecificConfiguration(PyObject* dict) { if (dict == nullptr) return; + + PyDict_SetItemString(dict, "filter", mvPyObject(ToPyInt(_filter))); } PyObject* mvRawTexture::getPyValue() @@ -241,9 +257,16 @@ void mvRawTexture::draw(ImDrawList* drawlist, float x, float y) state.ok = false; _dirty = false; + _filterDirty = false; return; } + if (_filterDirty && _texture != ImTextureID_Invalid) + { + ApplyTextureFilter(_texture, _filter); + _filterDirty = false; + } + if (_componentType == ComponentType::MV_FLOAT_COMPONENT) UpdateRawTexture(_texture, _permWidth, _permHeight, (float*)_value, _components); @@ -285,13 +308,22 @@ void mvRawTexture::handleSpecificKeywordArgs(PyObject* dict) } if (PyObject* item = PyDict_GetItemString(dict, "filter")) - _filter = ToInt(item); + { + const int v = ToInt(item); + if (v != _filter) + { + _filter = v; + _filterDirty = true; + } + } } void mvRawTexture::getSpecificConfiguration(PyObject* dict) { if (dict == nullptr) return; + + PyDict_SetItemString(dict, "filter", mvPyObject(ToPyInt(_filter))); } void mvStaticTexture::draw(ImDrawList* drawlist, float x, float y) @@ -308,6 +340,12 @@ void mvStaticTexture::draw(ImDrawList* drawlist, float x, float y) return; } + if (_filterDirty && _texture != ImTextureID_Invalid) + { + ApplyTextureFilter(_texture, _filter); + _filterDirty = false; + } + if (!_dirty) return; @@ -325,6 +363,7 @@ void mvStaticTexture::draw(ImDrawList* drawlist, float x, float y) _dirty = false; + _filterDirty = false; } void mvStaticTexture::handleSpecificRequiredArgs(PyObject* dict) @@ -345,7 +384,22 @@ void mvStaticTexture::handleSpecificKeywordArgs(PyObject* dict) return; if (PyObject* item = PyDict_GetItemString(dict, "filter")) - _filter = ToInt(item); + { + const int v = ToInt(item); + if (v != _filter) + { + _filter = v; + _filterDirty = true; + } + } +} + +void mvStaticTexture::getSpecificConfiguration(PyObject* dict) +{ + if (dict == nullptr) + return; + + PyDict_SetItemString(dict, "filter", mvPyObject(ToPyInt(_filter))); } PyObject* mvStaticTexture::getPyValue() diff --git a/src/mvTextureItems.h b/src/mvTextureItems.h index 82f6fb277..f5b7c64a6 100644 --- a/src/mvTextureItems.h +++ b/src/mvTextureItems.h @@ -35,7 +35,8 @@ class mvTextureItem : public mvAppItem public: ImTextureID _texture = ImTextureID_Invalid; - int _filter = 0; // mvTextureFilter_* + int _filter = 0; // mvTextureFilter_* + bool _filterDirty = false; }; @@ -49,6 +50,7 @@ class mvStaticTexture : public mvTextureItem void draw(ImDrawList* drawlist, float x, float y) override; void handleSpecificRequiredArgs(PyObject* dict) override; void handleSpecificKeywordArgs(PyObject* dict) override; + void getSpecificConfiguration(PyObject* dict) override; // values void setDataSource(mvUUID dataSource) override; diff --git a/src/mvUtilities.h b/src/mvUtilities.h index 47e7e7a82..f4c99ea52 100644 --- a/src/mvUtilities.h +++ b/src/mvUtilities.h @@ -21,6 +21,12 @@ typedef _object PyObject; struct PymvBuffer; struct ImDrawList; +// mvTextureFilter_* constants (also registered on the Python module). +enum { + mvTextureFilter_Linear = 0, + mvTextureFilter_Nearest = 1, +}; + // general void FreeTexture(ImTextureID texture); b8 UnloadTexture(const std::string& filename); @@ -29,16 +35,19 @@ b8 UnloadTexture(const std::string& filename); void EnterNearestFilterScope(ImDrawList* drawlist); void LeaveNearestFilterScope(ImDrawList* drawlist); +// Re-apply the filter state to an existing texture. Render-thread only. +void ApplyTextureFilter(ImTextureID texture, i32 filter); + // static textures ImTextureID LoadTextureFromFile(const char* filename, i32& width, i32& height); -ImTextureID LoadTextureFromArray(u32 width, u32 height, f32* data, i32 filter = 0); +ImTextureID LoadTextureFromArray(u32 width, u32 height, f32* data, i32 filter = mvTextureFilter_Linear); // dynamic textures -ImTextureID LoadTextureFromArrayDynamic(u32 width, u32 height, f32* data, i32 filter = 0); +ImTextureID LoadTextureFromArrayDynamic(u32 width, u32 height, f32* data, i32 filter = mvTextureFilter_Linear); void UpdateTexture(ImTextureID texture, u32 width, u32 height, std::vector& data); // raw textures -ImTextureID LoadTextureFromArrayRaw(u32 width, u32 height, f32* data, i32 components, i32 filter = 0); +ImTextureID LoadTextureFromArrayRaw(u32 width, u32 height, f32* data, i32 components, i32 filter = mvTextureFilter_Linear); void UpdateRawTexture(ImTextureID texture, u32 width, u32 height, f32* data, i32 components); // framebuffer output diff --git a/src/mvUtilities_apple.mm b/src/mvUtilities_apple.mm index 8995ccef4..bab958723 100644 --- a/src/mvUtilities_apple.mm +++ b/src/mvUtilities_apple.mm @@ -181,4 +181,10 @@ { if (drawlist == nullptr) return; drawlist->AddCallback(ImDrawCallback_ResetRenderState, nullptr); +} + +void +ApplyTextureFilter(ImTextureID, int) +{ + // No-op: the sampler is swapped per-draw via the callback above. } \ No newline at end of file diff --git a/src/mvUtilities_linux.cpp b/src/mvUtilities_linux.cpp index de730d456..b33660c20 100644 --- a/src/mvUtilities_linux.cpp +++ b/src/mvUtilities_linux.cpp @@ -94,7 +94,7 @@ OutputFrameBuffer(const char* filepath) static GLint FilterToGL(int filter) { - return (filter == 1) ? GL_NEAREST : GL_LINEAR; + return (filter == mvTextureFilter_Nearest) ? GL_NEAREST : GL_LINEAR; } ImTextureID @@ -218,6 +218,16 @@ UnloadTexture(const std::string& filename) void EnterNearestFilterScope(ImDrawList*) {} void LeaveNearestFilterScope(ImDrawList*) {} +void ApplyTextureFilter(ImTextureID texture, int filter) +{ + GLuint tex = (GLuint)(uintptr_t)texture; + if (tex == 0) return; + glBindTexture(GL_TEXTURE_2D, tex); + GLint f = FilterToGL(filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, f); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, f); +} + void FreeTexture(ImTextureID texture) { diff --git a/src/mvUtilities_win32.cpp b/src/mvUtilities_win32.cpp index a7762c8f8..10a1537ad 100644 --- a/src/mvUtilities_win32.cpp +++ b/src/mvUtilities_win32.cpp @@ -483,4 +483,10 @@ LeaveNearestFilterScope(ImDrawList* drawlist) { if (drawlist == nullptr) return; drawlist->AddCallback(ImDrawCallback_ResetRenderState, nullptr); +} + +void +ApplyTextureFilter(ImTextureID, int) +{ + // No-op: the sampler is swapped per-draw via the callback above. } \ No newline at end of file