@@ -674,6 +674,8 @@ class GPUCanvasContext(classes.GPUCanvasContext):
674
674
# the more cryptic Rust panics.
675
675
676
676
_surface_id = ffi .NULL
677
+ _wgpu_config = None
678
+ _skip_present_screen = False
677
679
678
680
def __init__ (self , canvas , present_methods ):
679
681
super ().__init__ (canvas , present_methods )
@@ -685,6 +687,9 @@ def __init__(self, canvas, present_methods):
685
687
else : # method == "bitmap"
686
688
self ._surface_id = ffi .NULL
687
689
690
+ # A stat for get_current_texture
691
+ self ._number_of_successive_unsuccesful_textures = 0
692
+
688
693
def _get_capabilities_screen (self , adapter ):
689
694
adapter_id = adapter ._internal
690
695
surface_id = self ._surface_id
@@ -815,6 +820,7 @@ def _configure_screen(
815
820
c_present_mode = getattr (lib , f"WGPUPresentMode_{ present_mode .capitalize ()} " )
816
821
817
822
# Prepare config object
823
+ width , height = self ._get_canvas ().get_physical_size ()
818
824
819
825
# H: nextInChain: WGPUChainedStruct *, device: WGPUDevice, format: WGPUTextureFormat, usage: WGPUTextureUsage/int, width: int, height: int, viewFormatCount: int, viewFormats: WGPUTextureFormat *, alphaMode: WGPUCompositeAlphaMode, presentMode: WGPUPresentMode
820
826
self ._wgpu_config = new_struct_p (
@@ -826,52 +832,70 @@ def _configure_screen(
826
832
viewFormatCount = len (view_formats ),
827
833
viewFormats = c_view_formats ,
828
834
alphaMode = c_alpha_mode ,
829
- width = 0 ,
830
- height = 0 ,
831
835
presentMode = c_present_mode ,
836
+ width = width , # overriden elsewhere in this class
837
+ height = height , # overriden elsewhere in this class
832
838
)
833
839
834
- def _configure_screen_real (self , width , height ):
840
+ # Configure now (if possible)
841
+ self ._configure_screen_real ()
842
+
843
+ def _configure_screen_real (self ):
835
844
# If a texture is still active, better release it first
836
845
self ._drop_texture ()
837
- # Set the size
838
- self ._wgpu_config .width = width
839
- self ._wgpu_config .height = height
840
- if width <= 0 or height <= 0 :
841
- raise RuntimeError (
842
- "Cannot configure canvas that has no pixels ({width}x{height})."
843
- )
844
846
# Configure, and store the config if we did not error out
845
- if self ._surface_id :
847
+ if (
848
+ self ._surface_id
849
+ and self ._wgpu_config .width > 0
850
+ and self ._wgpu_config .height > 0
851
+ ):
846
852
# H: void f(WGPUSurface surface, WGPUSurfaceConfiguration const * config)
847
853
libf .wgpuSurfaceConfigure (self ._surface_id , self ._wgpu_config )
854
+ else :
855
+ # Set size to zero, to trigger auto-configure later
856
+ self ._wgpu_config .width = 0
848
857
849
858
def _unconfigure_screen (self ):
850
859
if self ._surface_id :
851
860
# H: void f(WGPUSurface surface)
852
861
libf .wgpuSurfaceUnconfigure (self ._surface_id )
862
+ self ._wgpu_config = None
853
863
854
864
def _create_texture_screen (self ):
855
- surface_id = self ._surface_id
865
+ # Check
866
+ if self ._surface_id is None :
867
+ raise RuntimeError ("Looks like the CanvasContext is already destroyed." )
868
+ if self ._wgpu_config is None :
869
+ raise RuntimeError (
870
+ "Cannot get surface texture because the CanvasContext has not yet been configured."
871
+ )
856
872
857
- # Reconfigure when the canvas has resized.
858
- # On some systems (Windows+Qt) this is not necessary, because
859
- # the texture status would be Outdated below, resulting in a
860
- # reconfigure. But on others (e.g. glfwf) the texture size does
861
- # not have to match the window size, apparently. The downside
862
- # for doing this check on the former systems, is that errors
863
- # get logged, which would not be there if we did not
864
- # pre-emptively reconfigure. These log entries are harmless but
865
- # annoying, and I currently don't know how to prevent them
866
- # elegantly. See issue #352
873
+ # When the window size has changed, we need to reconfigure. If we wouldn't:
874
+ #
875
+ # * On some systems (seen on MacOS with glfw and Qt) the texture status that we get below
876
+ # will happily report 'SuccessOptimal', even when the the window has resized, and the
877
+ # texture will simply be stretched to fit the window. I believe this can be considered a bug.
878
+ # * On other systems (seen on Windows and Linux) the texture status would report 'SuccessSuboptimal',
879
+ # and the texture will either be stretched (Windows) or blitted to the window leaving either
880
+ # part of the texture invisible, or making part of the window black/transparent (Linux).
881
+ # * On some systems the texture status is 'Outdated' even if we do set the size. We deal with
882
+ # that by providing a dummy texture, and warn when this happens too often in succession.
883
+
884
+ # Get size info
867
885
old_size = (self ._wgpu_config .width , self ._wgpu_config .height )
868
886
new_size = tuple (self ._get_canvas ().get_physical_size ())
869
- if old_size != new_size :
870
- self ._configure_screen_real (* new_size )
871
-
872
- # Try to obtain a texture.
873
- # `If it fails, depending on status, we reconfigure and try again.
874
-
887
+ if new_size [0 ] <= 0 or new_size [1 ] <= 0 :
888
+ # It's the responsibility of the drawing /scheduling logic to prevent this case.
889
+ raise RuntimeError ("Cannot get texture for a canvas with zero pixels." )
890
+
891
+ # Re-configure when the size has changed.
892
+ if new_size != old_size :
893
+ self ._wgpu_config .width = new_size [0 ]
894
+ self ._wgpu_config .height = new_size [1 ]
895
+ self ._configure_screen_real ()
896
+
897
+ # Prepare for obtaining a texture.
898
+ status_str_map = enum_int2str ["SurfaceGetCurrentTextureStatus" ]
875
899
# H: nextInChain: WGPUChainedStructOut *, texture: WGPUTexture, status: WGPUSurfaceGetCurrentTextureStatus
876
900
surface_texture = new_struct_p (
877
901
"WGPUSurfaceTexture *" ,
@@ -880,41 +904,65 @@ def _create_texture_screen(self):
880
904
# not used: status
881
905
)
882
906
883
- for attempt in [1 , 2 ]:
884
- # H: void f(WGPUSurface surface, WGPUSurfaceTexture * surfaceTexture)
885
- libf .wgpuSurfaceGetCurrentTexture (surface_id , surface_texture )
886
- status_int = surface_texture .status
887
- status_str = enum_int2str ["SurfaceGetCurrentTextureStatus" ].get (
888
- status_int , "Unknown"
889
- )
890
- texture_id = surface_texture .texture
891
- if status_int == lib .WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal :
892
- break # Yay! Everything is good and we can render this frame.
893
- elif status_int == lib .WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal :
894
- # Still OK - the surface can present the frame, but in a suboptimal way. The surface may need reconfiguration.
895
- logger .warning ("The surface texture is suboptimal." )
896
- break
907
+ # Try to obtain texture
908
+ # H: void f(WGPUSurface surface, WGPUSurfaceTexture * surfaceTexture)
909
+ libf .wgpuSurfaceGetCurrentTexture (self ._surface_id , surface_texture )
910
+ status_int = surface_texture .status
911
+ status_str = status_str_map .get (status_int , "Unknown" )
912
+ texture_id = surface_texture .texture
913
+
914
+ if status_int == lib .WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal :
915
+ # Yay! Everything is good and we can render this frame.
916
+ self ._number_of_successive_unsuccesful_textures = 0
917
+ elif status_int in [
918
+ lib .WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal ,
919
+ lib .WGPUSurfaceGetCurrentTextureStatus_Timeout ,
920
+ lib .WGPUSurfaceGetCurrentTextureStatus_Outdated ,
921
+ lib .WGPUSurfaceGetCurrentTextureStatus_Lost ,
922
+ ]:
897
923
if texture_id :
898
924
# H: void f(WGPUTexture texture)
899
925
libf .wgpuTextureRelease (texture_id )
900
- if attempt == 1 and status_int in [
926
+ texture_id = 0
927
+ # Try to re-configure, if we can
928
+ self ._configure_screen_real ()
929
+ # H: void f(WGPUSurface surface, WGPUSurfaceTexture * surfaceTexture)
930
+ libf .wgpuSurfaceGetCurrentTexture (self ._surface_id , surface_texture )
931
+ status_int = surface_texture .status
932
+ status_str = status_str_map .get (status_int , "Unknown" )
933
+ texture_id = surface_texture .texture
934
+
935
+ # If still not optimal, we need to make some decisions ...
936
+ if status_int != lib .WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal :
937
+ # It's ok if we miss a sporadic frame during resizing, but warn if it becomes too much.
938
+ self ._number_of_successive_unsuccesful_textures += 1
939
+ if self ._number_of_successive_unsuccesful_textures > 5 :
940
+ n = self ._number_of_successive_unsuccesful_textures
941
+ self ._number_of_successive_unsuccesful_textures = 0
942
+ logger .warning (
943
+ f"No succesful surface texture obtained for { n } frames: { status_str !r} "
944
+ )
945
+ # Decide what to do
946
+ if status_int == lib .WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal :
947
+ # Can still use the texture
948
+ pass
949
+ elif status_int in [
901
950
lib .WGPUSurfaceGetCurrentTextureStatus_Timeout ,
902
951
lib .WGPUSurfaceGetCurrentTextureStatus_Outdated ,
903
952
lib .WGPUSurfaceGetCurrentTextureStatus_Lost ,
904
953
]:
905
- # Configure and try again.
906
- # On Window+Qt this happens e.g. when the window has resized
907
- # (status==Outdated), but also when moving the window from one
908
- # monitor to another with different scale-factor.
909
- logger .info (
910
- f"Re-configuring canvas context, because { status_str } ({ status_int } )."
911
- )
912
- self ._configure_screen_real (* new_size )
954
+ # Use a dummy texture that we cannot present
955
+ if texture_id :
956
+ # H: void f(WGPUTexture texture)
957
+ libf .wgpuTextureRelease (texture_id )
958
+ texture_id = 0
959
+ self ._skip_present_screen = True
960
+ return self ._create_texture_bitmap ()
913
961
else :
914
962
# WGPUSurfaceGetCurrentTextureStatus_OutOfMemory
915
963
# WGPUSurfaceGetCurrentTextureStatus_DeviceLost
916
964
# WGPUSurfaceGetCurrentTextureStatus_Error
917
- # Or if this is the second attempt .
965
+ # This is something we cannot recover from .
918
966
raise RuntimeError (
919
967
f"Cannot get surface texture: { status_str } ({ status_int } )."
920
968
)
@@ -959,15 +1007,17 @@ def _create_texture_screen(self):
959
1007
"format" : format ,
960
1008
"usage" : usage ,
961
1009
}
962
-
963
1010
device = self ._config ["device" ]
964
1011
return GPUTexture (label , texture_id , device , tex_info )
965
1012
966
1013
def _present_screen (self ):
967
- # H: WGPUStatus f(WGPUSurface surface)
968
- status = libf .wgpuSurfacePresent (self ._surface_id )
969
- if status != lib .WGPUStatus_Success :
970
- raise RuntimeError ("Error calling wgpuSurfacePresent" )
1014
+ if self ._skip_present_screen :
1015
+ self ._skip_present_screen = False
1016
+ else :
1017
+ # H: WGPUStatus f(WGPUSurface surface)
1018
+ status = libf .wgpuSurfacePresent (self ._surface_id )
1019
+ if status != lib .WGPUStatus_Success :
1020
+ logger .warning ("wgpuSurfacePresent failed" )
971
1021
972
1022
def _release (self ):
973
1023
self ._drop_texture ()
0 commit comments