Skip to content

Commit

Permalink
#4017 scale OpenGL viewport on MacOS with 'backingScaleFactor'
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Oct 11, 2023
1 parent 1410094 commit c91d48a
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 37 deletions.
67 changes: 37 additions & 30 deletions xpra/client/gl/backing.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ def resize_fbo(self, context, oldw : int, oldh : int, bw : int, bh : int) -> Non
dy = (bh-h)-dy
#re-init our OpenGL context with the new size,
#but leave offscreen fbo with the old size
self.gl_init(True)
self.gl_init(context, True)
#copy offscreen to new tmp:
self.copy_fbo(w, h, sx, sy, dx, dy)
#make tmp the new offscreen:
Expand Down Expand Up @@ -518,7 +518,7 @@ def gl_init_shaders(self) -> None:
raise RuntimeError(f"OpenGL shader {name} setup failure: {err}")
log("%s shader initialized", name)

def gl_init(self, skip_fbo:bool=False) -> None:
def gl_init(self, context, skip_fbo:bool=False) -> None:
#must be called within a context!
#performs init if needed
if not self.debug_setup:
Expand All @@ -534,8 +534,9 @@ def gl_init(self, skip_fbo:bool=False) -> None:
self.gl_marker("Initializing GL context for window size %s, backing size %s, max texture size=%i",
self.render_size, self.size, mt)
# Initialize viewport and matrices for 2D rendering
x, _, _, y = self.offsets
glViewport(x, y, w, h)
# default is to paint to pbo, so without any scale factor or offsets
# (as those are only applied when painting the window)
glViewport(0, 0, w, h)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0.0, w, h, 0.0, -1.0, 1.0)
Expand Down Expand Up @@ -583,7 +584,7 @@ def gl_init(self, skip_fbo:bool=False) -> None:
# Bind program 0 for YUV painting by default
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, self.shaders[YUV_to_RGB_SHADER])
self.gl_setup = True
log("gl_init(%s) done", skip_fbo)
log("gl_init(%s, %s) done", context, skip_fbo)

def get_init_magfilter(self) -> IntConstant:
rw, rh = self.render_size
Expand Down Expand Up @@ -710,7 +711,7 @@ def fail(msg):
glDisable(target)
fire_paint_callbacks(callbacks, True)
if not self.draw_needs_refresh:
self.present_fbo(0, 0, bw, bh, flush)
self.present_fbo(context, 0, 0, bw, bh, flush)

def copy_fbo(self, w : int, h : int, sx : int=0, sy : int=0, dx : int=0, dy : int=0) -> None:
log("copy_fbo%s", (w, h, sx, sy, dx, dy))
Expand Down Expand Up @@ -742,28 +743,32 @@ def swap_fbos(self) -> None:
self.textures[TEX_TMP_FBO] = tmp


def present_fbo(self, x : int, y : int, w : int, h : int, flush=0) -> None:
def present_fbo(self, context, x : int, y : int, w : int, h : int, flush=0) -> None:
log("present_fbo: adding %s to pending paint list (size=%i), flush=%s, paint_screen=%s",
(x, y, w, h), len(self.pending_fbo_paint), flush, self.paint_screen)
if not context:
raise RuntimeError("missing OpenGL paint context")
self.pending_fbo_paint.append((x, y, w, h))
if not self.paint_screen:
return
#flush>0 means we should wait for the final flush=0 paint
if flush==0 or not PAINT_FLUSH:
self.record_fps_event()
self.managed_present_fbo()
self.managed_present_fbo(context)

def managed_present_fbo(self) -> None:
def managed_present_fbo(self, context) -> None:
if not context:
raise RuntimeError("missing opengl paint context")
try:
with paint_context_manager:
self.do_present_fbo()
self.do_present_fbo(context)
except Exception as e:
log.error("Error presenting FBO:")
log.estr(e)
log("Error presenting FBO", exc_info=True)
self.last_present_fbo_error = str(e)

def do_present_fbo(self) -> None:
def do_present_fbo(self, context) -> None:
bw, bh = self.size
ww, wh = self.render_size
rect_count = len(self.pending_fbo_paint)
Expand All @@ -783,10 +788,10 @@ def do_present_fbo(self) -> None:
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)

left, top, right, bottom = self.offsets

#viewport for clearing the whole window:
glViewport(0, 0, left+ww+right, top+wh+bottom)
scale = context.get_scale_factor()
left, top, right, bottom = self.offsets
glViewport(0, 0, int((left+ww+right)*scale), int((top+wh+bottom)*scale))
if self._alpha_enabled:
# transparent background:
glClearColor(0.0, 0.0, 0.0, 0.0)
Expand All @@ -796,10 +801,12 @@ def do_present_fbo(self) -> None:
if left or top or right or bottom:
self.gl_clear_color_buffer()

#from now on, take the offsets into account:
glViewport(left, top, ww, wh)
#from now on, take the offsets and scaling into account:
viewport = int(left*scale), int(top*scale), int(ww*scale), int(wh*scale)
log(f"window viewport for {self.render_size=} and {self.offsets} with scale factor {scale}: {viewport}")
glViewport(*viewport)
target = GL_TEXTURE_RECTANGLE_ARB
if ww!=bw or wh!=bh:
if ww!=bw or wh!=bh or scale!=1:
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

Expand Down Expand Up @@ -1006,7 +1013,7 @@ def set_cursor_data(self, cursor_data) -> None:
pixels = cursor_data[8]
def gl_upload_cursor(context):
if context:
self.gl_init()
self.gl_init(context)
self.upload_rgba_texture(self.textures[TEX_CURSOR], cw, ch, pixels)
self.with_gl_context(gl_upload_cursor)

Expand Down Expand Up @@ -1119,9 +1126,9 @@ def paint_nvdec(gl_context):
self.idle_add(self.do_paint_rgb, rgb_format, img.get_pixels(), x, y, w, h, width, height,
img.get_rowstride(), options, callbacks)

def cuda_buffer_to_pbo(self, cuda_buffer, rowstride:int, src_y:int, height:int, stream):
def cuda_buffer_to_pbo(self, gl_context, cuda_buffer, rowstride:int, src_y:int, height:int, stream):
#must be called with an active cuda context, and from the UI thread
self.gl_init()
self.gl_init(gl_context)
pbo = glGenBuffers(1)
size = rowstride*height
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo)
Expand Down Expand Up @@ -1169,8 +1176,8 @@ def paint_nvdec(self, gl_context, encoding, img_data, x : int, y : int, width :
strides = img.get_rowstride()
height = img.get_height()
try:
y_pbo = self.cuda_buffer_to_pbo(cuda_buffer, strides[0], 0, height, stream)
uv_pbo = self.cuda_buffer_to_pbo(cuda_buffer, strides[1], roundup(height, 2), height//2, stream)
y_pbo = self.cuda_buffer_to_pbo(gl_context, cuda_buffer, strides[0], 0, height, stream)
uv_pbo = self.cuda_buffer_to_pbo(gl_context, cuda_buffer, strides[1], roundup(height, 2), height//2, stream)
except LogicError as e:
#disable nvdec from now on:
self.nvdec_decoder = None
Expand Down Expand Up @@ -1203,7 +1210,7 @@ def paint_nvjpeg(self, gl_context, encoding, img_data, x : int, y : int, width :
raise ValueError(f"unexpected rgb format {rgb_format}")
#'pixels' is a cuda buffer:
cuda_buffer = img.get_pixels()
pbo = self.cuda_buffer_to_pbo(cuda_buffer, img.get_rowstride(), 0, img.get_height(), stream)
pbo = self.cuda_buffer_to_pbo(gl_context, cuda_buffer, img.get_rowstride(), 0, img.get_height(), stream)
cuda_buffer.free()

pformat = PIXEL_FORMAT_TO_CONSTANT[rgb_format]
Expand Down Expand Up @@ -1235,7 +1242,7 @@ def paint_nvjpeg(self, gl_context, encoding, img_data, x : int, y : int, width :
self.paint_box(encoding, x, y, width, height)
# Present update to screen
if not self.draw_needs_refresh:
self.present_fbo(x, y, width, height, options.intget("flush", 0))
self.present_fbo(gl_context, x, y, width, height, options.intget("flush", 0))
# present_fbo has reset state already
fire_paint_callbacks(callbacks)
glDeleteBuffers(1, [pbo])
Expand Down Expand Up @@ -1293,7 +1300,7 @@ def gl_paint_rgb(self, context, rgb_format:str, img_data,
try:
upload, img_data = self.pixels_for_upload(img_data)

self.gl_init()
self.gl_init(context)
scaling = width!=render_width or height!=render_height

#convert it to a GL constant:
Expand Down Expand Up @@ -1339,7 +1346,7 @@ def gl_paint_rgb(self, context, rgb_format:str, img_data,
self.paint_box(options.strget("encoding"), x, y, render_width, render_height)
# Present update to screen
if not self.draw_needs_refresh:
self.present_fbo(x, y, render_width, render_height, options.intget("flush", 0))
self.present_fbo(context, x, y, render_width, render_height, options.intget("flush", 0))
# present_fbo has reset state already
fire_paint_callbacks(callbacks)
return
Expand Down Expand Up @@ -1404,7 +1411,7 @@ def do_gl_paint_planar(self, context, shader, flush:int, encoding:str, img,
log("%s._do_paint_rgb(..) no OpenGL context!", self)
fire_paint_callbacks(callbacks, False, "failed to get a gl context")
return
self.gl_init()
self.gl_init(context)
scaling = enc_width!=width or enc_height!=height
self.update_planar_textures(enc_width, enc_height, img, pixel_format, scaling=scaling, pbo=options.get("pbo"))

Expand All @@ -1419,7 +1426,7 @@ def do_gl_paint_planar(self, context, shader, flush:int, encoding:str, img,
fire_paint_callbacks(callbacks, True)
# Present it on screen
if not self.draw_needs_refresh:
self.present_fbo(x, y, width, height, flush)
self.present_fbo(context, x, y, width, height, flush)
img.free()
return
except GLError as e:
Expand Down Expand Up @@ -1578,6 +1585,6 @@ def gl_expose_rect(self, rect=None) -> None:
rect = (0, 0, w, h)
def expose(context):
if context:
self.gl_init()
self.present_fbo(*rect)
self.gl_init(context)
self.present_fbo(context, *rect)
self.with_gl_context(expose)
6 changes: 3 additions & 3 deletions xpra/client/gl/gtk3/client_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ def magic_key(self, *args) -> None:
if self.border:
self.border.toggle()
if b:
with b.gl_context():
b.gl_init()
b.present_fbo(0, 0, *b.size)
with b.gl_context() as ctx:
b.gl_init(ctx)
b.present_fbo(ctx, 0, 0, *b.size)
self.repaint(0, 0, *self._size)
log("gl magic_key%s border=%s, backing=%s", args, self.border, b)

Expand Down
8 changes: 5 additions & 3 deletions xpra/client/gl/gtk3/drawing_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ def gl_context(self):
if not gdk_window:
raise RuntimeError(f"backing {b} does not have a gdk window!")
self.window_context = self.context.get_paint_context(gdk_window)
if not self.window_context:
raise RuntimeError(f"failed to get an OpenGL window context for {gdk_window} from {self.context}")
return self.window_context

def do_gl_show(self, rect_count) -> None:
Expand All @@ -104,6 +106,6 @@ def close_gl_config(self) -> None:

def draw_fbo(self, _context) -> None:
w, h = self.size
with self.gl_context():
self.gl_init()
self.present_fbo(0, 0, w, h)
with self.gl_context() as ctx:
self.gl_init(ctx)
self.present_fbo(ctx, 0, 0, w, h)
5 changes: 5 additions & 0 deletions xpra/platform/darwin/gl_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class AGLContext:

def __init__(self, alpha=True):
self.alpha = alpha
self.scale_factor = 1.0
self.gl_context : NSOpenGLContext | None = None
self.nsview_ptr : int = 0
self.window_context : AGLWindowContext | None = None
Expand Down Expand Up @@ -165,11 +166,15 @@ def get_paint_context(self, gdk_window) -> AGLWindowContext:
self.gl_context.setValues_forParameter_([0], NSOpenGLCPSurfaceOpacity)
enable_transparency(gdk_window)
self.window_context = AGLWindowContext(self.gl_context, nsview)
self.scale_factor = nsview.screen().backingScaleFactor()
return self.window_context

def __del__(self):
self.destroy()

def get_scale_factor(self) -> float:
return self.scale_factor

def destroy(self) -> None:
c = self.window_context
log("AGLContext.destroy() window_context=%s", c)
Expand Down
8 changes: 7 additions & 1 deletion xpra/platform/posix/gl_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from OpenGL import GLX
from OpenGL.GL import GL_VENDOR, GL_RENDERER, glGetString

from xpra.util.env import envbool
from xpra.util.env import envbool, envfloat
from xpra.client.gl.check import check_PyOpenGL_support
from xpra.x11.bindings.display_source import get_display_ptr #@UnresolvedImport
from xpra.gtk.error import xsync
Expand All @@ -19,6 +19,9 @@


DOUBLE_BUFFERED = envbool("XPRA_OPENGL_DOUBLE_BUFFERED", True)
SCALE_FACTOR = envfloat("XPRA_OPENGL_SCALE_FACTOR", 1)
if SCALE_FACTOR<=0 or SCALE_FACTOR>10:
raise ValueError(f"invalid scale factor {SCALE_FACTOR}")


GLX_ATTRIBUTES : dict[Any,str] = {
Expand Down Expand Up @@ -92,6 +95,9 @@ def swap_buffers(self) -> None:
assert self.valid, "GLX window context is no longer valid"
GLX.glXSwapBuffers(self.xdisplay, self.xid)

def get_scale_factor(self) -> float:
return SCALE_FACTOR

def __repr__(self):
return "GLXWindowContext(%#x)" % self.xid

Expand Down
4 changes: 4 additions & 0 deletions xpra/platform/win32/gl_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
def DefWndProc(hwnd, msg, wParam, lParam):
return DefWindowProcA(hwnd, msg, wParam, lParam)


class WGLWindowContext:

def __init__(self, hwnd:int, hdc:int, context):
Expand Down Expand Up @@ -77,6 +78,9 @@ def swap_buffers(self):
log("swap_buffers: calling SwapBuffers(%#x)", self.paint_hdc)
SwapBuffers(self.paint_hdc)

def get_scale_factor(self) -> float:
return 1

def __repr__(self):
return "WGLWindowContext(%#x)" % self.hwnd

Expand Down

0 comments on commit c91d48a

Please sign in to comment.