From c4f91ce36a33a1a4e3f662dfa0e6d8687f50cfc9 Mon Sep 17 00:00:00 2001 From: Jonas Chromik Date: Wed, 22 Feb 2017 14:14:36 +0100 Subject: [PATCH 01/31] use @not_rpython decorator instead of """NOT RPYTHON""" doc string --- rsqueakvm/model/compiled_methods.py | 4 ++-- rsqueakvm/model/numeric.py | 8 ++++---- rsqueakvm/model/variable.py | 3 ++- rsqueakvm/plugins/plugin.py | 3 ++- rsqueakvm/plugins/socket_plugin.py | 2 +- rsqueakvm/squeakimage.py | 4 ++-- rsqueakvm/util/shell.py | 3 +-- rsqueakvm/util/system.py | 6 +++--- 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/rsqueakvm/model/compiled_methods.py b/rsqueakvm/model/compiled_methods.py index c7214c99..8af2138b 100644 --- a/rsqueakvm/model/compiled_methods.py +++ b/rsqueakvm/model/compiled_methods.py @@ -3,7 +3,7 @@ from rsqueakvm.model.pointers import W_PointersObject from rpython.rlib import jit -from rpython.rlib.objectmodel import import_from_mixin, we_are_translated +from rpython.rlib.objectmodel import not_rpython, we_are_translated class CompiledMethodHeader(object): @@ -129,8 +129,8 @@ def setliteral(self, index, w_lit): if index == len(self.literals): self.compiledin_class = None + @not_rpython # Only for testing, not safe. def setliterals(self, literals): - """NOT RPYTHON""" # Only for testing, not safe. self.literals = literals self.compiledin_class = None diff --git a/rsqueakvm/model/numeric.py b/rsqueakvm/model/numeric.py index 4d566978..6c110212 100644 --- a/rsqueakvm/model/numeric.py +++ b/rsqueakvm/model/numeric.py @@ -6,7 +6,7 @@ from rpython.rlib import longlong2float, jit from rpython.rlib.rarithmetic import intmask, r_uint32, r_uint, ovfcheck, r_int64, r_ulonglong from rpython.rlib.rstruct.ieee import float_unpack, float_pack -from rpython.rlib.objectmodel import compute_hash, specialize +from rpython.rlib.objectmodel import compute_hash, not_rpython from rpython.rlib import rbigint from rpython.rlib.rerased import new_static_erasing_pair @@ -277,8 +277,8 @@ def unwrap_int64(self, space): def unwrap_rbigint(self, space): return self.value + @not_rpython def unwrap_long_untranslated(self, space): - "NOT RPYTHON" return self.value.tolong() def unwrap_float(self, space): @@ -370,8 +370,8 @@ def unwrap_rbigint(self, space): else: return rbig + @not_rpython def unwrap_long_untranslated(self, space): - "NOT RPYTHON" if self.is_positive(space): return long(self.value) else: @@ -435,8 +435,8 @@ def unwrap_int64(self, space): def unwrap_rbigint(self, space): return rbigint.rbigint.fromint(self.value) + @not_rpython def unwrap_long_untranslated(self, space): - "NOT RPYTHON" return self.value def unwrap_float(self, space): diff --git a/rsqueakvm/model/variable.py b/rsqueakvm/model/variable.py index 9a4bc5d6..05fa1ae9 100644 --- a/rsqueakvm/model/variable.py +++ b/rsqueakvm/model/variable.py @@ -3,6 +3,7 @@ from rsqueakvm.util.version import Version, elidable_for_version_iff from rpython.rlib import jit +from rpython.rlib.objectmodel import not_rpython from rpython.rlib.rarithmetic import intmask, r_uint, r_uint32, r_int64 @@ -140,8 +141,8 @@ def unwrap_rbigint(self, space): else: raise error.UnwrappingError + @not_rpython def unwrap_long_untranslated(self, space): - "NOT RPYTHON" return self.unwrap_rbigint(space).tolong() @elidable_for_version_iff(0, cond=lambda self: jit.isconstant(self)) diff --git a/rsqueakvm/plugins/plugin.py b/rsqueakvm/plugins/plugin.py index b62c62bf..2980e1a5 100644 --- a/rsqueakvm/plugins/plugin.py +++ b/rsqueakvm/plugins/plugin.py @@ -1,4 +1,5 @@ from rpython.rlib import jit +from rpython.rlib.objectmodel import not_rpython from rsqueakvm import error @@ -27,8 +28,8 @@ def call(self, name, interp, s_frame, argcount, w_method): def _find_prim(self, name): return self.primitives.get(name, None) + @not_rpython def expose_primitive(self, wrap_func=None, **kwargs): - """NOT RPYTHON""" from rsqueakvm.primitives import wrap_primitive, unwrap_alternatives if not wrap_func: if kwargs.get('unwrap_specs', None): diff --git a/rsqueakvm/plugins/socket_plugin.py b/rsqueakvm/plugins/socket_plugin.py index a544a5a5..d7f4907d 100644 --- a/rsqueakvm/plugins/socket_plugin.py +++ b/rsqueakvm/plugins/socket_plugin.py @@ -48,8 +48,8 @@ def __init__(self): self.last_lookup = Cell(None) # cannot overload call (plugins are PBCs) so we decorate the decorator + @objectmodel.not_rpython def expose_primitive(self, wrap_func=None, **kwargs): - """NOT RPYTHON""" original_decorator = Plugin.expose_primitive(self, wrap_func=wrap_func, **kwargs) def decorator(func): original_decorator(func) diff --git a/rsqueakvm/squeakimage.py b/rsqueakvm/squeakimage.py index 4ab5375d..6496e75c 100644 --- a/rsqueakvm/squeakimage.py +++ b/rsqueakvm/squeakimage.py @@ -957,14 +957,14 @@ def get_class(self): def get_hash(self): return self.chunk.hash + @objectmodel.not_rpython def as_string(self): - """NOT RPYTHON""" return "".join([chr(c) for bytes in [splitter[8,8,8,8](w) for w in self.chunk.data] for c in bytes if c != 0]) + @objectmodel.not_rpython def classname(self): - """NOT RPYTHON""" return self.g_class.pointers[6].as_string() diff --git a/rsqueakvm/util/shell.py b/rsqueakvm/util/shell.py index a5ce6495..963034f6 100644 --- a/rsqueakvm/util/shell.py +++ b/rsqueakvm/util/shell.py @@ -48,9 +48,8 @@ def cmd(func): autocompletions["!%s" % func.__name__] = None return func - +@objectmodel.not_rpython def completer(text, state, completions=None): - "NOT RPYTHON" if not completions: completions = autocompletions matches = 0 diff --git a/rsqueakvm/util/system.py b/rsqueakvm/util/system.py index 163364a5..b1e96d67 100644 --- a/rsqueakvm/util/system.py +++ b/rsqueakvm/util/system.py @@ -5,7 +5,7 @@ from rpython.config.config import OptionDescription, BoolOption, \ IntOption, StrOption, ArbitraryOption, FloatOption, DEFAULT_OPTION_NAME from rpython.config.translationoption import get_combined_translation_config - +from rpython.rlib.objectmodel import not_rpython IS_POSIX = os.name == "posix" IS_WINDOWS = os.name == "nt" @@ -24,8 +24,8 @@ def win32uname(): return uname platform.uname = win32uname +@not_rpython def translation_options(): - """NOT RPYTHON""" return OptionDescription( "rsqueak", "RSqueak Options", [ StrOption( @@ -48,8 +48,8 @@ def translation_options(): ] ) +@not_rpython def expose_options(config): - """NOT RPYTHON""" for name in translation_options().getpaths(): globals()[name] = getattr(config.rsqueak, name) globals()["translationconfig"] = config.translation From 3790e81f79a902078a660020a8d91955076c8fab Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Thu, 23 Feb 2017 13:34:35 +0100 Subject: [PATCH 02/31] attempt to call into sdl less often, and signal OutOfMemory conditions --- rsqueakvm/constants.py | 1 + rsqueakvm/display.py | 34 +++----------------------- rsqueakvm/interpreter.py | 37 ++++++++++++++++++++--------- rsqueakvm/interpreter_bytecodes.py | 10 ++++++-- rsqueakvm/model/display.py | 38 ++++++++++++++++++++---------- rsqueakvm/primitives/storage.py | 5 +++- rsqueakvm/primitives/system.py | 2 ++ 7 files changed, 69 insertions(+), 58 deletions(-) diff --git a/rsqueakvm/constants.py b/rsqueakvm/constants.py index c204eceb..e7320636 100644 --- a/rsqueakvm/constants.py +++ b/rsqueakvm/constants.py @@ -168,6 +168,7 @@ "display": SO_DISPLAY_OBJECT, "interrupt_semaphore": SO_USER_INTERRUPT_SEMAPHORE, "timerSemaphore": SO_TIMER_SEMAPHORE, + "low_space_semaphore": SO_LOW_SPACE_SEMAPHORE, "jit_hook_selector": SO_JIT_HOOK, "jit_hook_receiver": SO_JIT_HOOK_RCVR } diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index add98f96..6029cc24 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -245,37 +245,6 @@ def flip(self, force=False): return RSDL.RenderPresent(self.renderer) - # def force_32bit_texture_to_screen(self, left, right, top, bottom, pixels): - # from rsqueakvm.constants import BYTES_PER_WORD - # assert self.depth == 32 - # left = max(left - 1, 0) - # top = max(top - 1, 0) - # with lltype.scoped_alloc(RSDL.Rect) as rect: - # rect.c_x = rffi.r_int(left) - # rect.c_y = rffi.r_int(top) - # rect.c_w = rffi.r_int(right - left) - # rect.c_h = rffi.r_int(bottom - top) - # pitch = rffi.r_int(self.width * 4) # 4 bytes per pixel - # start = left + top * self.width / (BYTES_PER_WORD / 4) - # ec = RSDL.UpdateTexture( - # self.screen_texture, - # rect, - # rffi.cast(rffi.VOIDP, rffi.ptradd(pixels, start)), - # pitch # pitch includes the padding between rows - # ) - # if ec != 0: - # print RSDL.GetError() - # return - # ec = RSDL.RenderCopy( - # self.renderer, - # self.screen_texture, - # rect, - # rect) - # if ec != 0: - # print RSDL.GetError() - # return - # RSDL.RenderPresent(self.renderer) - def set_squeak_colormap(self, surface): # TODO: fix this up from the image colors = lltype.malloc(rffi.CArray(RSDL.Color), 4, flavor='raw') @@ -441,6 +410,9 @@ def get_next_key_event(self, key_event_type, time): def get_next_event(self, time=0): if len(self._deferred_events) > 0: return self._deferred_events.pop() + # we always return one None event between every event, so we poll only + # half of the time + self._deferred_events.append([EventTypeNone, 0, 0, 0, 0, 0, 0, 0]) with lltype.scoped_alloc(RSDL.Event) as event: if RSDL.PollEvent(event) == 1: event_type = r_uint(event.c_type) diff --git a/rsqueakvm/interpreter.py b/rsqueakvm/interpreter.py index 8c5bf39c..2dcb4b1c 100644 --- a/rsqueakvm/interpreter.py +++ b/rsqueakvm/interpreter.py @@ -13,6 +13,7 @@ from rsqueakvm.storage_contexts import ContextPartShadow, ActiveContext, InactiveContext, DirtyContext from rpython.rlib import jit, rstackovf, unroll, objectmodel, rsignal +from rpython.rlib.rarithmetic import ovfcheck class ReturnFromTopLevel(Exception): @@ -160,7 +161,6 @@ class Interpreter(object): "evented", "interrupts", "trace_important", - "interrupt_counter_size", "trace"] jit_driver = jit.JitDriver( @@ -197,6 +197,7 @@ def __init__(self, space, image=None, trace_important=False, self.interrupt_counter_size = int(os.environ["SPY_ICS"]) except KeyError: self.interrupt_counter_size = constants.INTERRUPT_COUNTER_SIZE + self.last_check = self.time_now() self.trace = trace # === Initialize mutable variables @@ -429,9 +430,9 @@ def jitted_check_for_interrupt(self, s_frame): return # Normally, the tick counter is decremented by 1 for every message send. # Since we don't know how many messages are called during this trace, we - # just divide by 10**2 and make sure it's always at least 1 + # just divide by 12**2 and make sure it's always at least 1 trace_length = jit.current_trace_length() - decr_by = int(trace_length >> 10 | 1) + decr_by = int(trace_length >> 12 | 1) self.quick_check_for_interrupt(s_frame, decr_by) def quick_check_for_interrupt(self, s_frame, dec=1): @@ -447,24 +448,38 @@ def check_sigusr(self, s_frame): if poll == rsignal.SIGUSR1: print s_frame.print_stack() + def signal_memory_error(self, s_frame): + w_low_space_sema = self.space.w_low_space_semaphore() + if w_low_space_sema is not self.space.w_nil: + assert isinstance(w_low_space_sema, W_PointersObject) + wrapper.SemaphoreWrapper(self.space, w_low_space_sema).signal(s_frame, forced=True) + def check_for_interrupts(self, s_frame): # parallel to Interpreter>>#checkForInterrupts - - # Profiling is skipped - # We don't adjust the check counter size - - # use the same time value as the primitive UTC_MICROSECOND_CLOCK - self.forced_interrupt_checks_count += 1 + # 1. profiling is done using rvmprof + # 2. use the same time value as the primitive UTC_MICROSECOND_CLOCK now = self.time_now() + # 3. adjust the check counter size, we want to land between 100ms and 400ms + diff = now - self.last_check + if diff < 100000 and self.interrupt_counter_size != constants.MAXINT: + try: + self.interrupt_counter_size = ovfcheck(self.interrupt_counter_size * 2) + except OverflowError: + self.interrupt_counter_size = constants.MAXINT + elif diff > 400000 and self.interrupt_counter_size > 100: + self.interrupt_counter_size = max(self.interrupt_counter_size / 2, 100) + self.last_check = now + self.forced_interrupt_checks_count += 1 - # Check for User Interrupt + # 4. check for User Interrupt if self.space.display().has_interrupts_pending(): w_interrupt_sema = self.space.w_interrupt_semaphore() if w_interrupt_sema is not self.space.w_nil: assert isinstance(w_interrupt_sema, W_PointersObject) wrapper.SemaphoreWrapper(self.space, w_interrupt_sema).signal(s_frame, forced=True) - # XXX the low space semaphore may be signaled here + # 5. the low space semaphore is signalled in ClassShadow#new + # 6. signal the timer if not self.next_wakeup_tick == 0 and now >= self.next_wakeup_tick: self.next_wakeup_tick = 0 semaphore = self.space.w_timerSemaphore() diff --git a/rsqueakvm/interpreter_bytecodes.py b/rsqueakvm/interpreter_bytecodes.py index c8a46346..12998d9e 100644 --- a/rsqueakvm/interpreter_bytecodes.py +++ b/rsqueakvm/interpreter_bytecodes.py @@ -341,8 +341,14 @@ def _sendSelector(self, w_selector, argcount, interp, receiver, pass # ignore this error and fall back to the Smalltalk version if not w_arguments: w_arguments = self.pop_and_return_n(argcount) - s_frame = w_method.create_frame(interp.space, receiver, w_arguments, - s_fallback=s_fallback) + try: + s_frame = w_method.create_frame(interp.space, receiver, w_arguments, + s_fallback=s_fallback) + except MemoryError: + interp.signal_memory_error(self) + # if the low space semaphore isn't set, we die + print "RSqueak/VM ran out of memory, and no low space semaphore was set!" + raise SystemExit self.pop() # receiver # ###################################################################### diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index ab9b589e..18914649 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -167,20 +167,32 @@ def repr_content(self): class W_32BitDisplayBitmap(W_DisplayBitmap): repr_classname = "W_32BitDisplayBitmap" - # def force_rectange_to_screen(self, left, right, top, bottom): - # if self.pixelbuffer_words > 0: - # self.display().force_32bit_texture_to_screen(left, right, top, bottom, self._real_depth_buffer) + def take_over_display(self): + if self.pixelbuffer_words == 0: + W_DisplayBitmap.take_over_display(self) + assert self.pixelbuffer_words == self.size() + lltype.free(self._real_depth_buffer, flavor='raw') + self._real_depth_buffer = self.pixelbuffer() + + def relinquish_display(self): + W_DisplayBitmap.relinquish_display(self) + assert self.pixelbuffer_words == 0 + self._real_depth_buffer = lltype.malloc(rffi.CArray(rffi.UINT), self.size(), flavor='raw') + self._copy_pixelbuffer_to_own() + + @rgc.must_be_light_finalizer + def __del__(self): + if self.pixelbuffer_words == 0: + lltype.free(self._real_depth_buffer, flavor='raw') + + def _copy_pixelbuffer_to_own(self): + for i in range(self.size()): + self.setword(i, self.pixelbuffer()[i]) + + def force_rectange_to_screen(self, left, right, top, bottom): + if self.pixelbuffer_words > 0: + self.display().flip(force=True) - def force_words(self, start, stop): - if self.is_headless(): return - if (start >= 0 and stop > start and self.size() > stop and - self.pixelbuffer_words > stop): - pixbuf = rffi.ptradd(self.display().get_pixelbuffer(), start) - realbuf = rffi.ptradd(self._real_depth_buffer, start) - rffi.c_memcpy( - rffi.cast(rffi.VOIDP, pixbuf), - rffi.cast(rffi.VOIDP, realbuf), - (stop - start) * constants.BYTES_PER_WORD) # VOIDP is char*, we want to copy word* class W_16BitDisplayBitmap(W_DisplayBitmap): diff --git a/rsqueakvm/primitives/storage.py b/rsqueakvm/primitives/storage.py index 8f332a49..4f5eb628 100644 --- a/rsqueakvm/primitives/storage.py +++ b/rsqueakvm/primitives/storage.py @@ -55,7 +55,10 @@ def func(interp, s_frame, w_cls): s_class = w_cls.as_class_get_shadow(interp.space) if s_class.isvariable(): raise PrimitiveFailedError() - return s_class.new() + try: + return s_class.new() + except MemoryError: + raise PrimitiveFailedError @expose_primitive(NEW_WITH_ARG, unwrap_spec=[object, int]) def func(interp, s_frame, w_cls, size): diff --git a/rsqueakvm/primitives/system.py b/rsqueakvm/primitives/system.py index d642c6a4..93db1295 100644 --- a/rsqueakvm/primitives/system.py +++ b/rsqueakvm/primitives/system.py @@ -129,6 +129,8 @@ def func(interp, s_frame, argcount): vm_w_params[2] = interp.space.wrap_int(1) # must be 1 for VM Stats view to work vm_w_params[8] = interp.space.wrap_int(1) # must be 1 for VM Stats view to work + vm_w_params[25] = interp.space.wrap_int(interp.interrupt_counter_size) # check for interrupts roughly every N bytecodes + vm_w_params[41] = interp.space.wrap_int(1) # We are a "stack-like" VM - number of stack tables if objectmodel.we_are_translated(): From 211cb8c1949bc71b3f3c5ee7c414e5258a7b5b42 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Sun, 26 Feb 2017 20:45:04 +0100 Subject: [PATCH 03/31] Use RSDL.LockTexture instead of RSDL.UpdateTexture --- rsqueakvm/display.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 6029cc24..1b5ee43d 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -41,6 +41,12 @@ MINIMUM_DEPTH = 8 +PIXELVOIDPP = lltype.malloc(rffi.VOIDPP.TO, 1, + flavor='raw', zero=True, immortal=True) +PITCHINTP = lltype.malloc(rffi.INTP.TO, 1, + flavor='raw', zero=True, immortal=True) + + class SqueakInterrupt(Exception): pass @@ -227,14 +233,20 @@ def defer_updates(self, flag): def flip(self, force=False): if self._defer_updates and not force: return - ec = RSDL.UpdateTexture( + + ec = RSDL.LockTexture( self.screen_texture, lltype.nullptr(RSDL.Rect), - self.screen_surface.c_pixels, - self.screen_surface.c_pitch) + PIXELVOIDPP, + PITCHINTP) if ec != 0: print RSDL.GetError() return + + nbytes = rffi.r_size_t(self.width * self.height * self.bpp) + rffi.c_memcpy(PIXELVOIDPP[0], self.screen_surface.c_pixels, nbytes) + RSDL.UnlockTexture(self.screen_texture) + ec = RSDL.RenderCopy( self.renderer, self.screen_texture, From 400ecd0b49c1c3c47d152979b6f1d2a0eb162107 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 27 Feb 2017 12:44:52 +0100 Subject: [PATCH 04/31] rework mapping bitmap code --- rsqueakvm/display.py | 4 +-- rsqueakvm/model/display.py | 59 ++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 6029cc24..087b5d7f 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -39,7 +39,7 @@ WindowEventStinks = 6 MINIMUM_DEPTH = 8 - +DEPTH_TO_MAP_TO = 32 class SqueakInterrupt(Exception): pass @@ -178,7 +178,7 @@ def set_video_mode(self, w, h, d): return assert d in [1, 2, 4, 8, 16, 32] if d < MINIMUM_DEPTH: - d = MINIMUM_DEPTH + d = DEPTH_TO_MAP_TO self.width = intmask(w) self.height = intmask(h) self.depth = intmask(d) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index 18914649..a167aefa 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -231,7 +231,7 @@ def set_pixelbuffer_word(self, n, word): (word << 24) ) -BITS = r_uint(32) + class W_MappingDisplayBitmap(W_DisplayBitmap): repr_classname = "W_MappingDisplayBitmap" @@ -245,19 +245,22 @@ def __init__(self, space, size, depth): def word_from_pixel(self, x, y): word = W_DisplayBitmap.word_from_pixel(self, x, y) if self._depth == 1: - return word / 8 + return word / 32 elif self._depth == 2: - return word / 4 + return word / 16 elif self._depth == 4: - return word / 2 + return word / 8 else: assert False + def display_words_per_word(self): + return self.display().depth / self._depth + def take_over_display(self): - pitch = r_uint(self.display().pitch) # The pitch is different from the width input to SDL! + pitch = r_uint(self.display().pitch) # The pitch may be different from the width input to SDL! self.pitch = pitch - self.bits_in_last_word = pitch % BITS - self.words_per_line = r_uint((pitch - self.bits_in_last_word) / BITS) + self.bits_in_last_word = pitch % 32 + self.words_per_line = r_uint((pitch - self.bits_in_last_word) / 32) if self.bits_in_last_word > 0: self.words_per_line += 1 W_DisplayBitmap.take_over_display(self) @@ -266,23 +269,35 @@ def take_over_display(self): def set_pixelbuffer_word(self, n, word): n = r_uint(n) if ((n+1) % self.words_per_line) == 0 and self.bits_in_last_word > 0: - # This is the last word on the line. A few bits are cut off. + # This is the last word on the line. A few bits may be cut off. bits = self.bits_in_last_word else: - bits = BITS - + bits = 32 + buf = self.pixelbuffer() word = r_uint(word) - pos = self.compute_pos(n) - buf = rffi.ptradd(self.display().get_plain_pixelbuffer(), pos) depth = r_uint(self._depth) - rshift = BITS - depth + shift = 32 - depth + display_words_per_word = 32 / depth + pixelmask = ((r_uint(1) << depth) - 1) << shift + table = PIXEL_LOOKUP_TABLE[depth - 1] + assert table is not None for i in range(bits / depth): - pixel = word >> rshift - buf[i] = rffi.cast(rffi.CHAR, pixel) - word <<= depth - - def compute_pos(self, n): - word_on_line = n % self.words_per_line - y = r_uint((n - word_on_line) / self.words_per_line) - x = word_on_line * BITS / r_uint(self._depth) - return y * self.pitch + x + pixel = (word & pixelmask) >> (shift - i) + buf[n * display_words_per_word + i] = rffi.r_uint(table[pixel]) + pixelmask >>= depth + + # def setword(self, n, word): + # W_DisplayBitmap.setword(self, n, word) + # if self.pixelbuffer_words > 0: + # self.set_pixelbuffer_word(n, word) + # self.display().flip() + + +PIXEL_LOOKUP_1BIT = [0xffffffff, 0xff000000] +PIXEL_LOOKUP_2BIT = [0xff000000, 0xff848484, 0xffc6c6c6, 0xffffffff] +PIXEL_LOOKUP_4BIT = [ + 0xff000000, 0xff000084, 0xff008400, 0xff008484, + 0xff840000, 0xff840084, 0xff848400, 0xff848484, + 0xffc6c6c6, 0xff0000ff, 0xff00ff00, 0xff00ffff, + 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff] +PIXEL_LOOKUP_TABLE = [PIXEL_LOOKUP_1BIT, PIXEL_LOOKUP_2BIT, None, PIXEL_LOOKUP_4BIT] From 6746dc2a9388486cc2e5947fc20c1a4cff223543 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 27 Feb 2017 13:53:01 +0100 Subject: [PATCH 05/31] quick hack for quick translation --- .build/build.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.build/build.py b/.build/build.py index d3ffdc5b..835f4d99 100755 --- a/.build/build.py +++ b/.build/build.py @@ -35,6 +35,10 @@ def isatty(self): if __name__ == "__main__": target = os.path.join(os.path.dirname(__file__), "..", "targetrsqueak.py") + if '--quick' in sys.argv: + assert len(sys.argv) == 2 + sys.argv.pop() + sys.argv.extend(['--no-translation-jit', '--', '--without_plugins']) if '--' in sys.argv: sys.argv[sys.argv.index('--')] = target else: From a34ff6ad87832a48594b3e81b532f92e459ddb3b Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 27 Feb 2017 14:51:19 +0100 Subject: [PATCH 06/31] buffer rendering, no rendering when deferUpdates is true --- rsqueakvm/display.py | 41 +++++++++++++++++++++------------- rsqueakvm/interpreter.py | 3 +++ rsqueakvm/model/display.py | 11 +++------ rsqueakvm/primitives/system.py | 8 +++---- 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 875c1ae1..59b929b1 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -120,7 +120,7 @@ class SDLDisplay(NullDisplay): _attrs_ = ["window", "title", "renderer", "screen_texture", "altf4quit", "screen_surface", "has_surface", "interrupt_key", "_defer_updates", "_deferred_events", "bpp", "pitch", "highdpi", - "software_renderer", "interrupt_flag"] + "software_renderer", "interrupt_flag", "_texture_dirty"] _immutable_fields_ = ["interrupt_flag"] def __init__(self, title, highdpi, software_renderer, altf4quit): @@ -139,6 +139,7 @@ def __init__(self, title, highdpi, software_renderer, altf4quit): self.interrupt_key = 15 << 8 # pushing all four meta keys, of which we support three... self._deferred_events = [] self._defer_updates = False + self._texture_dirty = True def _init_sdl(self): from rpython.rlib.objectmodel import we_are_translated @@ -205,6 +206,7 @@ def set_video_mode(self, w, h, d): if not self.screen_texture: print "Could not create screen texture" raise RuntimeError(RSDL.GetError()) + self.lock() self.screen_surface = RSDL.CreateRGBSurface(0, w, h, d, 0, 0, 0, 0) assert self.screen_surface, RSDL.GetError() self.bpp = intmask(self.screen_surface.c_format.c_BytesPerPixel) @@ -230,23 +232,18 @@ def get_plain_pixelbuffer(self): def defer_updates(self, flag): self._defer_updates = flag - def flip(self, force=False): - if self._defer_updates and not force: - return - - ec = RSDL.LockTexture( - self.screen_texture, - lltype.nullptr(RSDL.Rect), - PIXELVOIDPP, - PITCHINTP) - if ec != 0: - print RSDL.GetError() - return - + def flip(self): nbytes = rffi.r_size_t(self.width * self.height * self.bpp) rffi.c_memcpy(PIXELVOIDPP[0], self.screen_surface.c_pixels, nbytes) - RSDL.UnlockTexture(self.screen_texture) + self._texture_dirty = True + def render(self): + if self._defer_updates: + return + if not self._texture_dirty: + return + self._texture_dirty = False + self.unlock() ec = RSDL.RenderCopy( self.renderer, self.screen_texture, @@ -256,6 +253,20 @@ def flip(self, force=False): print RSDL.GetError() return RSDL.RenderPresent(self.renderer) + self.lock() + + def lock(self): + ec = RSDL.LockTexture( + self.screen_texture, + lltype.nullptr(RSDL.Rect), + PIXELVOIDPP, + PITCHINTP) + if ec != 0: + print RSDL.GetError() + return + + def unlock(self): + RSDL.UnlockTexture(self.screen_texture) def set_squeak_colormap(self, surface): # TODO: fix this up from the image diff --git a/rsqueakvm/interpreter.py b/rsqueakvm/interpreter.py index 2dcb4b1c..4d262329 100644 --- a/rsqueakvm/interpreter.py +++ b/rsqueakvm/interpreter.py @@ -455,6 +455,9 @@ def signal_memory_error(self, s_frame): wrapper.SemaphoreWrapper(self.space, w_low_space_sema).signal(s_frame, forced=True) def check_for_interrupts(self, s_frame): + display = self.space.display() + if display: + display.render() # parallel to Interpreter>>#checkForInterrupts # 1. profiling is done using rvmprof # 2. use the same time value as the primitive UTC_MICROSECOND_CLOCK diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index a167aefa..dbacb10a 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -115,6 +115,7 @@ def relinquish_display(self): def flush_to_screen(self): self.display().flip() + self.display().render() def word_from_pixel(self, x, y): return (x - 1 + (y - 1) * self.display().width) / self.pixel_per_word() @@ -126,7 +127,7 @@ def force_rectange_to_screen(self, left, right, top, bottom): if stop <= start: return self.force_words(start, stop) - self.display().flip(force=True) + self.display().flip() def force_words(self, start, stop): if self.is_headless(): return @@ -191,7 +192,7 @@ def _copy_pixelbuffer_to_own(self): def force_rectange_to_screen(self, left, right, top, bottom): if self.pixelbuffer_words > 0: - self.display().flip(force=True) + self.display().flip() @@ -286,12 +287,6 @@ def set_pixelbuffer_word(self, n, word): buf[n * display_words_per_word + i] = rffi.r_uint(table[pixel]) pixelmask >>= depth - # def setword(self, n, word): - # W_DisplayBitmap.setword(self, n, word) - # if self.pixelbuffer_words > 0: - # self.set_pixelbuffer_word(n, word) - # self.display().flip() - PIXEL_LOOKUP_1BIT = [0xffffffff, 0xff000000] PIXEL_LOOKUP_2BIT = [0xff000000, 0xff848484, 0xffc6c6c6, 0xffffffff] diff --git a/rsqueakvm/primitives/system.py b/rsqueakvm/primitives/system.py index 93db1295..ea0fc95e 100644 --- a/rsqueakvm/primitives/system.py +++ b/rsqueakvm/primitives/system.py @@ -22,10 +22,10 @@ def func(interp, s_frame, w_rcvr, time_mu_s): interp.interrupt_check_counter = 0 interp.quick_check_for_interrupt(s_frame, dec=0) -# @expose_primitive(FORCE_DISPLAY_UPDATE, unwrap_spec=[object]) -# def func(interp, s_frame, w_rcvr): -# interp.space.display().flip(force=True) -# return w_rcvr +@expose_primitive(FORCE_DISPLAY_UPDATE, unwrap_spec=[object]) +def func(interp, s_frame, w_rcvr): + interp.space.display().render() + return w_rcvr @expose_primitive(SET_FULL_SCREEN, unwrap_spec=[object, bool]) def func(interp, s_frame, w_rcvr, flag): From 3cffb72a01177727f0fd183fbdabf55cbeeddd4e Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 27 Feb 2017 15:08:15 +0100 Subject: [PATCH 07/31] real_depth_buffer is only quasi immutable --- rsqueakvm/model/display.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index dbacb10a..9cfb71cf 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -34,7 +34,7 @@ def from_words_object(w_obj, form): class W_DisplayBitmap(W_AbstractObjectWithIdentityHash): _attrs_ = ['pixelbuffer_words', '_real_depth_buffer', '_realsize', '_display', '_depth'] - _immutable_fields_ = ['pixelbuffer_words?', '_real_depth_buffer', '_realsize', '_display', '_depth'] + _immutable_fields_ = ['pixelbuffer_words?', '_real_depth_buffer?', '_realsize', '_display', '_depth'] repr_classname = "W_DisplayBitmap" def __init__(self, space, size, depth): @@ -160,7 +160,8 @@ def can_become(self, w_other): @rgc.must_be_light_finalizer def __del__(self): - lltype.free(self._real_depth_buffer, flavor='raw') + if self.pixelbuffer_words == 0: + lltype.free(self._real_depth_buffer, flavor='raw') def repr_content(self): return "len=%d depth=%d %s" % (self.size(), self._depth, self.str_content()) @@ -181,11 +182,6 @@ def relinquish_display(self): self._real_depth_buffer = lltype.malloc(rffi.CArray(rffi.UINT), self.size(), flavor='raw') self._copy_pixelbuffer_to_own() - @rgc.must_be_light_finalizer - def __del__(self): - if self.pixelbuffer_words == 0: - lltype.free(self._real_depth_buffer, flavor='raw') - def _copy_pixelbuffer_to_own(self): for i in range(self.size()): self.setword(i, self.pixelbuffer()[i]) From 120aaa4b0bbb69f55c65d6dc14264b86b31b2d9d Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 27 Feb 2017 15:59:00 +0100 Subject: [PATCH 08/31] only copy roughly the forced rectangle to the locked texture pixels --- rsqueakvm/display.py | 19 +++++++++++++++---- rsqueakvm/interpreter.py | 7 ++++--- rsqueakvm/interpreter_bytecodes.py | 10 ++-------- rsqueakvm/model/display.py | 20 ++++++++++++++------ rsqueakvm/primitives/misc.py | 4 ++-- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 59b929b1..4ecb0696 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -76,7 +76,10 @@ def get_plain_pixelbuffer(self): def get_next_event(self, time=0): return [EventTypeNone, 0, 0, 0, 0, 0, 0, 0] - def flip(self, force=False): + def flip(self, start, stop): + pass + + def render(self): pass def has_clipboard_text(self): @@ -232,9 +235,17 @@ def get_plain_pixelbuffer(self): def defer_updates(self, flag): self._defer_updates = flag - def flip(self): - nbytes = rffi.r_size_t(self.width * self.height * self.bpp) - rffi.c_memcpy(PIXELVOIDPP[0], self.screen_surface.c_pixels, nbytes) + def flip(self, start, stop): + size = self.width * self.height * self.bpp + offset = start * self.bpp + if offset >= size: + return + if start >= stop: + return + nbytes = rffi.r_size_t(min((stop - start) * self.bpp, size - offset)) + pixbuf = rffi.ptradd(PIXELVOIDPP[0], offset) + surfacebuf = rffi.ptradd(self.screen_surface.c_pixels, offset) + rffi.c_memcpy(pixbuf, surfacebuf, nbytes) self._texture_dirty = True def render(self): diff --git a/rsqueakvm/interpreter.py b/rsqueakvm/interpreter.py index 4d262329..9abf651b 100644 --- a/rsqueakvm/interpreter.py +++ b/rsqueakvm/interpreter.py @@ -458,18 +458,19 @@ def check_for_interrupts(self, s_frame): display = self.space.display() if display: display.render() + # parallel to Interpreter>>#checkForInterrupts # 1. profiling is done using rvmprof # 2. use the same time value as the primitive UTC_MICROSECOND_CLOCK now = self.time_now() - # 3. adjust the check counter size, we want to land between 100ms and 400ms + # 3. adjust the check counter size, we want to land between 20ms and 100ms diff = now - self.last_check - if diff < 100000 and self.interrupt_counter_size != constants.MAXINT: + if diff < 20000 and self.interrupt_counter_size != constants.MAXINT: try: self.interrupt_counter_size = ovfcheck(self.interrupt_counter_size * 2) except OverflowError: self.interrupt_counter_size = constants.MAXINT - elif diff > 400000 and self.interrupt_counter_size > 100: + elif diff > 100000 and self.interrupt_counter_size > 100: self.interrupt_counter_size = max(self.interrupt_counter_size / 2, 100) self.last_check = now self.forced_interrupt_checks_count += 1 diff --git a/rsqueakvm/interpreter_bytecodes.py b/rsqueakvm/interpreter_bytecodes.py index 12998d9e..c8a46346 100644 --- a/rsqueakvm/interpreter_bytecodes.py +++ b/rsqueakvm/interpreter_bytecodes.py @@ -341,14 +341,8 @@ def _sendSelector(self, w_selector, argcount, interp, receiver, pass # ignore this error and fall back to the Smalltalk version if not w_arguments: w_arguments = self.pop_and_return_n(argcount) - try: - s_frame = w_method.create_frame(interp.space, receiver, w_arguments, - s_fallback=s_fallback) - except MemoryError: - interp.signal_memory_error(self) - # if the low space semaphore isn't set, we die - print "RSqueak/VM ran out of memory, and no low space semaphore was set!" - raise SystemExit + s_frame = w_method.create_frame(interp.space, receiver, w_arguments, + s_fallback=s_fallback) self.pop() # receiver # ###################################################################### diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index 9cfb71cf..fb05260f 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -114,20 +114,24 @@ def relinquish_display(self): self.pixelbuffer_words = 0 def flush_to_screen(self): - self.display().flip() + self.display().flip(0, self.pixelbuffer_words) self.display().render() def word_from_pixel(self, x, y): - return (x - 1 + (y - 1) * self.display().width) / self.pixel_per_word() + return (x + y * self.display().width) / self.pixel_per_word() def force_rectange_to_screen(self, left, right, top, bottom): if self.pixelbuffer_words > 0: + width = self.display().width start = max(self.word_from_pixel(left, top), 0) stop = min(self.word_from_pixel(right, bottom), self.size() - 1) if stop <= start: return self.force_words(start, stop) - self.display().flip() + self.display().flip( + left + top * width, + right + bottom * width + ) def force_words(self, start, stop): if self.is_headless(): return @@ -166,6 +170,7 @@ def __del__(self): def repr_content(self): return "len=%d depth=%d %s" % (self.size(), self._depth, self.str_content()) + class W_32BitDisplayBitmap(W_DisplayBitmap): repr_classname = "W_32BitDisplayBitmap" @@ -188,12 +193,14 @@ def _copy_pixelbuffer_to_own(self): def force_rectange_to_screen(self, left, right, top, bottom): if self.pixelbuffer_words > 0: - self.display().flip() - + width = self.display().width + self.display().flip( + left + top * width, + right + bottom * width + ) class W_16BitDisplayBitmap(W_DisplayBitmap): - repr_classname = "W_16BitDisplayBitmap" def set_pixelbuffer_word(self, n, word): @@ -215,6 +222,7 @@ def set_pixelbuffer_word(self, n, word): ) self.pixelbuffer()[n] = rffi.r_uint(lsb | (msb << 16)) + class W_8BitDisplayBitmap(W_DisplayBitmap): repr_classname = "W_8BitDisplayBitmap" diff --git a/rsqueakvm/primitives/misc.py b/rsqueakvm/primitives/misc.py index f49dfd9d..6f4e34a1 100644 --- a/rsqueakvm/primitives/misc.py +++ b/rsqueakvm/primitives/misc.py @@ -4,7 +4,7 @@ from rsqueakvm.error import PrimitiveFailedError from rsqueakvm.model.display import W_DisplayBitmap from rsqueakvm.model.variable import W_BytesObject, W_WordsObject -from rsqueakvm.primitives import expose_primitive, uint +from rsqueakvm.primitives import expose_primitive, uint, index1_0 from rsqueakvm.primitives.constants import * from rsqueakvm.primitives.storage import get_instances_array @@ -45,7 +45,7 @@ def func(interp, s_frame, w_receiver, flag): sdldisplay.defer_updates(flag) return w_receiver -@expose_primitive(DRAW_RECTANGLE, unwrap_spec=[object, int, int, int, int]) +@expose_primitive(DRAW_RECTANGLE, unwrap_spec=[object, index1_0, index1_0, index1_0, index1_0]) def func(interp, s_frame, w_rcvr, left, right, top, bottom): if not interp.space.w_display().is_same_object(w_rcvr): return interp.space.w_nil From e03480692c9bac3e59494f7aecd866fa344050e9 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 27 Feb 2017 17:47:01 +0100 Subject: [PATCH 09/31] cleanup --- rsqueakvm/display.py | 9 ++++----- rsqueakvm/primitives/misc.py | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 4ecb0696..ced274b3 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -236,13 +236,12 @@ def defer_updates(self, flag): self._defer_updates = flag def flip(self, start, stop): - size = self.width * self.height * self.bpp offset = start * self.bpp - if offset >= size: + assert offset >= 0: + remaining_size = (self.width * self.height * self.bpp) - offset + if remaining_size <= 0 or start <= stop: return - if start >= stop: - return - nbytes = rffi.r_size_t(min((stop - start) * self.bpp, size - offset)) + nbytes = rffi.r_size_t(min((stop - start) * self.bpp, remaining_size)) pixbuf = rffi.ptradd(PIXELVOIDPP[0], offset) surfacebuf = rffi.ptradd(self.screen_surface.c_pixels, offset) rffi.c_memcpy(pixbuf, surfacebuf, nbytes) diff --git a/rsqueakvm/primitives/misc.py b/rsqueakvm/primitives/misc.py index 6f4e34a1..cb183fc9 100644 --- a/rsqueakvm/primitives/misc.py +++ b/rsqueakvm/primitives/misc.py @@ -52,6 +52,10 @@ def func(interp, s_frame, w_rcvr, left, right, top, bottom): if not ((left <= right) and (top <= bottom)): return interp.space.w_nil form = wrapper.FormWrapper(interp.space, w_rcvr) + left = max(0, left) + right = max(0, right) + top = max(0, top) + bottom = max(0, bottom) form.get_display_bitmap().force_rectange_to_screen(left, right, top, bottom) return w_rcvr From 340cbd0147c28b99bd993c83197946312c222ae6 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Mon, 27 Feb 2017 17:59:23 +0100 Subject: [PATCH 10/31] Syntax fix --- rsqueakvm/display.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index ced274b3..8f2d29eb 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -237,9 +237,10 @@ def defer_updates(self, flag): def flip(self, start, stop): offset = start * self.bpp - assert offset >= 0: + if offset < 0 or start <= stop: + return remaining_size = (self.width * self.height * self.bpp) - offset - if remaining_size <= 0 or start <= stop: + if remaining_size <= 0: return nbytes = rffi.r_size_t(min((stop - start) * self.bpp, remaining_size)) pixbuf = rffi.ptradd(PIXELVOIDPP[0], offset) From e5cf5128eac5bdc072963d00b3210bf628cc0503 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Mon, 27 Feb 2017 18:12:35 +0100 Subject: [PATCH 11/31] Always use PyPy on Travis for testing/compiling Use pypy-5.3.1 on Linux provided by Travis --- .travis.yml | 6 +++++- .travis/build-linux.sh | 10 +++++----- .travis/build-macos.sh | 8 ++++---- .travis/install_requirements.sh | 4 ++-- .travis/test.sh | 7 +------ 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0729cf94..b21af3f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ -language: cpp +language: python +python: pypy-5.3.1 sudo: required dist: trusty group: edge @@ -36,12 +37,15 @@ matrix: include: - os: osx osx_image: xcode7.3 + language: cpp env: BUILD_ARCH=64bit TEST_TYPE=default - os: osx osx_image: xcode7.3 + language: cpp env: BUILD_ARCH=64bit - os: osx osx_image: xcode7.3 + language: cpp env: BUILD_ARCH=64bit PLUGINS=RubyPlugin allow_failures: # - env: BUILD_ARCH=64bit PLUGINS=DatabasePlugin diff --git a/.travis/build-linux.sh b/.travis/build-linux.sh index 99e94aac..2043cbb9 100755 --- a/.travis/build-linux.sh +++ b/.travis/build-linux.sh @@ -21,21 +21,21 @@ fi case "$BUILD_ARCH" in 32bit) - python .build/build.py --batch --32bit -- $plugins + pypy .build/build.py --batch --32bit -- $plugins exitcode=$? cp rsqueak rsqueak-x86-${UNAME}$plugins_suffix-jit-$TRAVIS_COMMIT || true - python .build/jittests.py --32bit || $JITTESTS_FAIL + pypy .build/jittests.py --32bit || $JITTESTS_FAIL $EX rm -rf .build/pypy/rpython/_cache ;; 64bit) - python .build/build.py --batch --64bit -- $plugins + pypy .build/build.py --batch --64bit -- $plugins exitcode=$? cp rsqueak rsqueak-x86_64-${UNAME}$plugins_suffix-jit-$TRAVIS_COMMIT || true - python .build/jittests.py --64bit || $JITTESTS_FAIL + pypy .build/jittests.py --64bit || $JITTESTS_FAIL $EX rm -rf .build/pypy/rpython/_cache ;; lldebug) - python .build/build.py --lldebug -Ojit + pypy .build/build.py --lldebug -Ojit exitcode=$? cp rsqueak rsqueak-x86-${UNAME}-dbg-$TRAVIS_COMMIT || true $EX rm -rf .build/pypy/rpython/_cache diff --git a/.travis/build-macos.sh b/.travis/build-macos.sh index a2b0f7c8..5171ceeb 100755 --- a/.travis/build-macos.sh +++ b/.travis/build-macos.sh @@ -15,21 +15,21 @@ fi case "$BUILD_ARCH" in 32bit) - python .build/build.py $plugins + pypy .build/build.py $plugins exitcode=$? cp rsqueak rsqueak-x86-${UNAME}$plugins_suffix-jit-$TRAVIS_COMMIT || true - # python .build/jittests.py + # pypy .build/jittests.py # $EX rm -rf .build/pypy/rpython/_cache ;; 64bit) pypy .build/build.py -- $plugins exitcode=$? cp rsqueak rsqueak-x86_64-${UNAME}$plugins_suffix-jit-$TRAVIS_COMMIT || true - # python .build/jittests.py + # pypy .build/jittests.py # $EX rm -rf .build/pypy/rpython/_cache ;; lldebug) - python .build/build.py --lldebug -Ojit + pypy .build/build.py --lldebug -Ojit exitcode=$? cp rsqueak rsqueak-x86-${UNAME}-dbg-$TRAVIS_COMMIT || true # $EX rm -rf .build/pypy/rpython/_cache diff --git a/.travis/install_requirements.sh b/.travis/install_requirements.sh index 5e08f391..65d2f344 100755 --- a/.travis/install_requirements.sh +++ b/.travis/install_requirements.sh @@ -12,9 +12,9 @@ setup_osx() { 64bit) # brew update - # Use Pypy2 v5.4.0 + # Use PyPy2 v5.4.0 brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/c5a201f49c9da47d4771ebc544d10b3f9c579021/Formula/pypy.rb - + brew install sdl2 ;; esac diff --git a/.travis/test.sh b/.travis/test.sh index ebafe625..7cda1375 100755 --- a/.travis/test.sh +++ b/.travis/test.sh @@ -17,9 +17,4 @@ case "${BUILD_ARCH}" in *) ;; esac -ex="python" -if [[ "${TRAVIS_OS_NAME}" == "osx" ]] && [[ "${BUILD_ARCH}" == "64bit" ]]; then - ex="pypy" -fi - -${ex} .build/unittests.py -s ${testflag} +pypy ".build/${testscript}" -s ${testflag} From 2b566d81283e7b2e57d403eb773713e476e37991 Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Mon, 27 Feb 2017 19:48:54 +0100 Subject: [PATCH 12/31] Fix test.sh --- .travis/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/test.sh b/.travis/test.sh index 7cda1375..d2bec013 100755 --- a/.travis/test.sh +++ b/.travis/test.sh @@ -17,4 +17,4 @@ case "${BUILD_ARCH}" in *) ;; esac -pypy ".build/${testscript}" -s ${testflag} +pypy ".build/unittests.py" -s ${testflag} From 2dd3a5a1f7a84583c245fe8241d9e6e83a0f91f5 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 28 Feb 2017 09:20:03 +0100 Subject: [PATCH 13/31] record texture damage and only render the damaged rectangle --- rsqueakvm/display.py | 49 +++++++++++++++++++++++++--------- rsqueakvm/model/display.py | 13 +++------ rsqueakvm/primitives/system.py | 2 +- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index ced274b3..0e39d3c5 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -45,6 +45,8 @@ flavor='raw', zero=True, immortal=True) PITCHINTP = lltype.malloc(rffi.INTP.TO, 1, flavor='raw', zero=True, immortal=True) +FLIP_RECT = lltype.malloc(RSDL.Rect, flavor='raw', zero=True, immortal=True) +RENDER_RECT = lltype.malloc(RSDL.Rect, flavor='raw', zero=True, immortal=True) class SqueakInterrupt(Exception): @@ -76,7 +78,7 @@ def get_plain_pixelbuffer(self): def get_next_event(self, time=0): return [EventTypeNone, 0, 0, 0, 0, 0, 0, 0] - def flip(self, start, stop): + def flip(self, x, y, x2, y2): pass def render(self): @@ -216,6 +218,7 @@ def set_video_mode(self, w, h, d): if d == MINIMUM_DEPTH: self.set_squeak_colormap(self.screen_surface) self.pitch = self.width * self.bpp + self.reset_damage() def set_full_screen(self, flag): if flag: @@ -235,20 +238,13 @@ def get_plain_pixelbuffer(self): def defer_updates(self, flag): self._defer_updates = flag - def flip(self, start, stop): - offset = start * self.bpp - assert offset >= 0: - remaining_size = (self.width * self.height * self.bpp) - offset - if remaining_size <= 0 or start <= stop: - return - nbytes = rffi.r_size_t(min((stop - start) * self.bpp, remaining_size)) - pixbuf = rffi.ptradd(PIXELVOIDPP[0], offset) - surfacebuf = rffi.ptradd(self.screen_surface.c_pixels, offset) - rffi.c_memcpy(pixbuf, surfacebuf, nbytes) + def flip(self, x, y, x2, y2): + self.copy_pixels(x + y * self.width, x2 + y2 * self.width) + self.record_damage(x, y, x2 - x, y2 - y) self._texture_dirty = True - def render(self): - if self._defer_updates: + def render(self, force=False): + if self._defer_updates and not force: return if not self._texture_dirty: return @@ -257,14 +253,41 @@ def render(self): ec = RSDL.RenderCopy( self.renderer, self.screen_texture, + # RENDER_RECT, + # RENDER_RECT) lltype.nullptr(RSDL.Rect), lltype.nullptr(RSDL.Rect)) if ec != 0: print RSDL.GetError() return RSDL.RenderPresent(self.renderer) + self.reset_damage() self.lock() + def copy_pixels(self, start, stop): + offset = start * self.bpp + assert offset >= 0 + remaining_size = (self.width * self.height * self.bpp) - offset + if remaining_size <= 0 or start <= stop: + return + nbytes = rffi.r_size_t(min((stop - start) * self.bpp, remaining_size)) + pixbuf = rffi.ptradd(PIXELVOIDPP[0], offset) + surfacebuf = rffi.ptradd(self.screen_surface.c_pixels, offset) + rffi.c_memcpy(pixbuf, surfacebuf, nbytes) + + def record_damage(self, x, y, w, h): + FLIP_RECT.c_x = rffi.r_int(x) + FLIP_RECT.c_y = rffi.r_int(y) + FLIP_RECT.c_w = rffi.r_int(w) + FLIP_RECT.c_h = rffi.r_int(h) + RSDL.UnionRect(FLIP_RECT, RENDER_RECT, RENDER_RECT) + + def reset_damage(self): + RENDER_RECT.c_x = rffi.r_int(0) + RENDER_RECT.c_y = rffi.r_int(0) + RENDER_RECT.c_w = rffi.r_int(0) + RENDER_RECT.c_h = rffi.r_int(0) + def lock(self): ec = RSDL.LockTexture( self.screen_texture, diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index fb05260f..11858379 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -114,7 +114,7 @@ def relinquish_display(self): self.pixelbuffer_words = 0 def flush_to_screen(self): - self.display().flip(0, self.pixelbuffer_words) + self.display().flip(0, 0, self.display().width, self.display().height) self.display().render() def word_from_pixel(self, x, y): @@ -122,16 +122,12 @@ def word_from_pixel(self, x, y): def force_rectange_to_screen(self, left, right, top, bottom): if self.pixelbuffer_words > 0: - width = self.display().width start = max(self.word_from_pixel(left, top), 0) stop = min(self.word_from_pixel(right, bottom), self.size() - 1) if stop <= start: return self.force_words(start, stop) - self.display().flip( - left + top * width, - right + bottom * width - ) + self.display().flip(left, top, right, bottom) def force_words(self, start, stop): if self.is_headless(): return @@ -194,10 +190,7 @@ def _copy_pixelbuffer_to_own(self): def force_rectange_to_screen(self, left, right, top, bottom): if self.pixelbuffer_words > 0: width = self.display().width - self.display().flip( - left + top * width, - right + bottom * width - ) + self.display().flip(left, top, right, bottom) class W_16BitDisplayBitmap(W_DisplayBitmap): diff --git a/rsqueakvm/primitives/system.py b/rsqueakvm/primitives/system.py index ea0fc95e..82a7beab 100644 --- a/rsqueakvm/primitives/system.py +++ b/rsqueakvm/primitives/system.py @@ -24,7 +24,7 @@ def func(interp, s_frame, w_rcvr, time_mu_s): @expose_primitive(FORCE_DISPLAY_UPDATE, unwrap_spec=[object]) def func(interp, s_frame, w_rcvr): - interp.space.display().render() + interp.space.display().render(force=True) return w_rcvr @expose_primitive(SET_FULL_SCREEN, unwrap_spec=[object, bool]) From 71ea961812144a5753ef745d8b05c1fd0fb94596 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 28 Feb 2017 10:13:04 +0100 Subject: [PATCH 14/31] fix wrong comparison --- rsqueakvm/display.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 0e39d3c5..dacb9a10 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -253,10 +253,8 @@ def render(self, force=False): ec = RSDL.RenderCopy( self.renderer, self.screen_texture, - # RENDER_RECT, - # RENDER_RECT) - lltype.nullptr(RSDL.Rect), - lltype.nullptr(RSDL.Rect)) + RENDER_RECT, + RENDER_RECT) if ec != 0: print RSDL.GetError() return @@ -268,7 +266,7 @@ def copy_pixels(self, start, stop): offset = start * self.bpp assert offset >= 0 remaining_size = (self.width * self.height * self.bpp) - offset - if remaining_size <= 0 or start <= stop: + if remaining_size <= 0 or start >= stop: return nbytes = rffi.r_size_t(min((stop - start) * self.bpp, remaining_size)) pixbuf = rffi.ptradd(PIXELVOIDPP[0], offset) From 7f3c0b4408fb679ec0ee957e850543375501d0f2 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 28 Feb 2017 11:22:33 +0100 Subject: [PATCH 15/31] avoid artefacts when trying to render a rectangle larger than the screen --- rsqueakvm/display.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index dacb9a10..cc46b5ff 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -81,7 +81,7 @@ def get_next_event(self, time=0): def flip(self, x, y, x2, y2): pass - def render(self): + def render(self, force=False): pass def has_clipboard_text(self): @@ -276,8 +276,8 @@ def copy_pixels(self, start, stop): def record_damage(self, x, y, w, h): FLIP_RECT.c_x = rffi.r_int(x) FLIP_RECT.c_y = rffi.r_int(y) - FLIP_RECT.c_w = rffi.r_int(w) - FLIP_RECT.c_h = rffi.r_int(h) + FLIP_RECT.c_w = rffi.r_int(min(w + 1, self.width - x)) + FLIP_RECT.c_h = rffi.r_int(min(h + 1, self.height - y)) RSDL.UnionRect(FLIP_RECT, RENDER_RECT, RENDER_RECT) def reset_damage(self): From f49aee1879c2f38f524e532c3a7ab3517e32e6a0 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 28 Feb 2017 14:25:23 +0100 Subject: [PATCH 16/31] render twice for macOS benefit --- rsqueakvm/model/display.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index 11858379..4f2e6d83 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -114,8 +114,12 @@ def relinquish_display(self): self.pixelbuffer_words = 0 def flush_to_screen(self): - self.display().flip(0, 0, self.display().width, self.display().height) + self.make_dirty() self.display().render() + self.make_dirty() # do it twice for macOS + + def make_dirty(self): + self.display().flip(0, 0, self.display().width, self.display().height) def word_from_pixel(self, x, y): return (x + y * self.display().width) / self.pixel_per_word() From 263d9b86a99ba3225a5950c7ee6d5c854e73adee Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Tue, 28 Feb 2017 16:57:07 +0100 Subject: [PATCH 17/31] Use software renderer --- rsqueakvm/display.py | 16 ++++++---------- rsqueakvm/main.py | 6 +++--- rsqueakvm/objspace.py | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index cc46b5ff..7fcb6458 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -177,13 +177,10 @@ def create_window_and_renderer(self, x, y, width, height): flags = RSDL.WINDOW_RESIZABLE if self.highdpi: flags |= RSDL.WINDOW_ALLOW_HIGHDPI - if self.software_renderer: - flags |= RSDL.RENDERER_SOFTWARE self.window = RSDL.CreateWindow(self.title, x, y, width, height, flags) - # https://wiki.libsdl.org/SDL_CreateRenderer#flags: "Note that providing - # no flags gives priority to available SDL_RENDERER_ACCELERATED - # renderers." - self.renderer = RSDL.CreateRenderer(self.window, -1, 0) + # Use software renderer for now to avoid problems w/ duplicate buffers + self.renderer = RSDL.CreateRenderer(self.window, -1, + RSDL.RENDERER_SOFTWARE) def set_video_mode(self, w, h, d): if not (w > 0 and h > 0): @@ -244,10 +241,9 @@ def flip(self, x, y, x2, y2): self._texture_dirty = True def render(self, force=False): - if self._defer_updates and not force: - return - if not self._texture_dirty: - return + if not force: + if self._defer_updates or not self._texture_dirty: + return self._texture_dirty = False self.unlock() ec = RSDL.RenderCopy( diff --git a/rsqueakvm/main.py b/rsqueakvm/main.py index ce553a15..007d898b 100644 --- a/rsqueakvm/main.py +++ b/rsqueakvm/main.py @@ -56,7 +56,7 @@ def _usage(argv): Squeak arguments are passed on to the Squeak image rather than being processed. General: - --no-highdpi - Disable High-DPI support (default: on). + --highdpi - Enable High-DPI support (default: off). --software-renderer - Use software renderer (default: off). --no-display - Use dummy display (default: off). -h|--help - Output this message and exit. @@ -217,8 +217,8 @@ def parse_args(self, argv, skip_bad=False): elif arg in ["--git-version"]: print GIT_VERSION raise error.CleanExit() - elif arg == "--no-highdpi": - self.space.highdpi.deactivate() + elif arg == "--highdpi": + self.space.highdpi.activate() elif arg == "--software-renderer": self.space.software_renderer.activate() # -nodisplay (Linux) and -headless (macOS) are used in Cog diff --git a/rsqueakvm/objspace.py b/rsqueakvm/objspace.py index 4339606e..c864aa0a 100644 --- a/rsqueakvm/objspace.py +++ b/rsqueakvm/objspace.py @@ -35,7 +35,7 @@ def __init__(self): # This is a hack; see compile_code() in main.py self.suppress_process_switch = QuasiConstant(False) self.headless = QuasiConstant(False) - self.highdpi = QuasiConstant(True) + self.highdpi = QuasiConstant(False) self.software_renderer = QuasiConstant(False) self.no_display = QuasiConstant(False) self.silent = QuasiConstant(False) From 95d5676705ec0ad9b5072b28e8e3c60588986bcf Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 28 Feb 2017 22:02:29 +0100 Subject: [PATCH 18/31] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 641a72df..71b1bb14 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,6 +10,7 @@ RSqueak/VM includes contributions from: Jakob Reschke Jan Graichen Johannes Henning + Jonas Chromik Lars Wassermann Matthias Springer Patrick Rein From 493ac3b5928ca71237ff0af891f77881a9268b70 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 10:02:18 +0100 Subject: [PATCH 19/31] refactor textures, use texture formats to map 32bit and 16bit directly, map the rest --- rsqueakvm/constants.py | 1 + rsqueakvm/display.py | 71 +++----- rsqueakvm/model/display.py | 256 ++++++++++++--------------- rsqueakvm/primitives/input_output.py | 7 + 4 files changed, 144 insertions(+), 191 deletions(-) diff --git a/rsqueakvm/constants.py b/rsqueakvm/constants.py index e7320636..36db9c1c 100644 --- a/rsqueakvm/constants.py +++ b/rsqueakvm/constants.py @@ -66,6 +66,7 @@ COMPILED_METHOD_SMALL_FRAME_SIZE = 16 LITERAL_START = 1 # index of the first literal after the method header BYTES_PER_WORD = 4 +BITS_PER_WORD = BYTES_PER_WORD * 8 WORDS_IN_FLOAT = 2 # Fixed number of word-slots in a Squeak Float object INTERP_PROXY_MAJOR = 1 INTERP_PROXY_MINOR = 13 diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 7fcb6458..67b7313a 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -38,8 +38,8 @@ WindowEventPaint = 5 WindowEventStinks = 6 -MINIMUM_DEPTH = 8 -DEPTH_TO_MAP_TO = 32 +MINIMUM_DEPTH = 16 +BELOW_MINIMUM_DEPTH = 32 PIXELVOIDPP = lltype.malloc(rffi.VOIDPP.TO, 1, flavor='raw', zero=True, immortal=True) @@ -48,6 +48,11 @@ FLIP_RECT = lltype.malloc(RSDL.Rect, flavor='raw', zero=True, immortal=True) RENDER_RECT = lltype.malloc(RSDL.Rect, flavor='raw', zero=True, immortal=True) +DEPTH_TO_PIXELFORMAT = { + 16: RSDL.PIXELFORMAT_ARGB1555, + 32: RSDL.PIXELFORMAT_ARGB8888 +} + class SqueakInterrupt(Exception): pass @@ -78,7 +83,7 @@ def get_plain_pixelbuffer(self): def get_next_event(self, time=0): return [EventTypeNone, 0, 0, 0, 0, 0, 0, 0] - def flip(self, x, y, x2, y2): + def flip(self, pixels, x, y, x2, y2): pass def render(self, force=False): @@ -123,7 +128,7 @@ def set_video_mode(self, w, h, d): class SDLDisplay(NullDisplay): _attrs_ = ["window", "title", "renderer", "screen_texture", "altf4quit", - "screen_surface", "has_surface", "interrupt_key", + "interrupt_key", "_defer_updates", "_deferred_events", "bpp", "pitch", "highdpi", "software_renderer", "interrupt_flag", "_texture_dirty"] _immutable_fields_ = ["interrupt_flag"] @@ -139,8 +144,6 @@ def __init__(self, title, highdpi, software_renderer, altf4quit): self.window = lltype.nullptr(RSDL.WindowPtr.TO) self.renderer = lltype.nullptr(RSDL.RendererPtr.TO) self.screen_texture = lltype.nullptr(RSDL.TexturePtr.TO) - self.screen_surface = lltype.nullptr(RSDL.Surface) - self.has_surface = False self.interrupt_key = 15 << 8 # pushing all four meta keys, of which we support three... self._deferred_events = [] self._defer_updates = False @@ -185,9 +188,9 @@ def create_window_and_renderer(self, x, y, width, height): def set_video_mode(self, w, h, d): if not (w > 0 and h > 0): return - assert d in [1, 2, 4, 8, 16, 32] + assert d in (1, 2, 4, 8, 16, 32) if d < MINIMUM_DEPTH: - d = DEPTH_TO_MAP_TO + d = BELOW_MINIMUM_DEPTH self.width = intmask(w) self.height = intmask(h) self.depth = intmask(d) @@ -198,22 +201,20 @@ def set_video_mode(self, w, h, d): height=h) if self.screen_texture != lltype.nullptr(RSDL.TexturePtr.TO): RSDL.DestroyTexture(self.screen_texture) - if self.screen_surface != lltype.nullptr(RSDL.Surface): - RSDL.FreeSurface(self.screen_surface) - self.has_surface = True self.screen_texture = RSDL.CreateTexture( self.renderer, - RSDL.PIXELFORMAT_ARGB8888, RSDL.TEXTUREACCESS_STREAMING, + DEPTH_TO_PIXELFORMAT[d], RSDL.TEXTUREACCESS_STREAMING, w, h) if not self.screen_texture: print "Could not create screen texture" raise RuntimeError(RSDL.GetError()) self.lock() - self.screen_surface = RSDL.CreateRGBSurface(0, w, h, d, 0, 0, 0, 0) - assert self.screen_surface, RSDL.GetError() - self.bpp = intmask(self.screen_surface.c_format.c_BytesPerPixel) - if d == MINIMUM_DEPTH: - self.set_squeak_colormap(self.screen_surface) + if d == 16: + self.bpp = 2 + elif d == 32: + self.bpp = 4 + else: + assert False self.pitch = self.width * self.bpp self.reset_damage() @@ -226,17 +227,11 @@ def set_full_screen(self, flag): def set_title(self, title): RSDL.SetWindowTitle(self.window, title) - def get_pixelbuffer(self): - return jit.promote(rffi.cast(RSDL.Uint32P, self.get_plain_pixelbuffer())) - - def get_plain_pixelbuffer(self): - return self.screen_surface.c_pixels - def defer_updates(self, flag): self._defer_updates = flag - def flip(self, x, y, x2, y2): - self.copy_pixels(x + y * self.width, x2 + y2 * self.width) + def flip(self, pixels, x, y, x2, y2): + self.copy_pixels(pixels, x + y * self.width, x2 + y2 * self.width) self.record_damage(x, y, x2 - x, y2 - y) self._texture_dirty = True @@ -258,7 +253,7 @@ def render(self, force=False): self.reset_damage() self.lock() - def copy_pixels(self, start, stop): + def copy_pixels(self, pixels, start, stop): offset = start * self.bpp assert offset >= 0 remaining_size = (self.width * self.height * self.bpp) - offset @@ -266,7 +261,7 @@ def copy_pixels(self, start, stop): return nbytes = rffi.r_size_t(min((stop - start) * self.bpp, remaining_size)) pixbuf = rffi.ptradd(PIXELVOIDPP[0], offset) - surfacebuf = rffi.ptradd(self.screen_surface.c_pixels, offset) + surfacebuf = rffi.ptradd(rffi.cast(rffi.VOIDP, pixels), offset) rffi.c_memcpy(pixbuf, surfacebuf, nbytes) def record_damage(self, x, y, w, h): @@ -295,28 +290,6 @@ def lock(self): def unlock(self): RSDL.UnlockTexture(self.screen_texture) - def set_squeak_colormap(self, surface): - # TODO: fix this up from the image - colors = lltype.malloc(rffi.CArray(RSDL.Color), 4, flavor='raw') - colors[0].c_r = rffi.r_uchar(255) - colors[0].c_g = rffi.r_uchar(255) - colors[0].c_b = rffi.r_uchar(255) - colors[0].c_a = rffi.r_uchar(255) - colors[1].c_r = rffi.r_uchar(0) - colors[1].c_g = rffi.r_uchar(0) - colors[1].c_b = rffi.r_uchar(0) - colors[1].c_a = rffi.r_uchar(255) - colors[2].c_r = rffi.r_uchar(128) - colors[2].c_g = rffi.r_uchar(128) - colors[2].c_b = rffi.r_uchar(128) - colors[2].c_a = rffi.r_uchar(255) - colors[3].c_r = rffi.r_uchar(255) - colors[3].c_g = rffi.r_uchar(255) - colors[3].c_b = rffi.r_uchar(255) - colors[3].c_a = rffi.r_uchar(255) - RSDL.SetPaletteColors(surface.c_format.c_palette, rffi.cast(RSDL.ColorPtr, colors), 0, 4) - lltype.free(colors, flavor='raw') - def handle_mouse_button(self, c_type, event): b = rffi.cast(RSDL.MouseButtonEventPtr, event) btn = r_uint(b.c_button) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index 4f2e6d83..ecf9b26b 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -16,15 +16,12 @@ def from_words_object(w_obj, form): if not w_class.is_same_object(space.w_Bitmap): raise error.WrappingError - if depth < 8: + if depth in (1,2,4,8): w_display_bitmap = W_MappingDisplayBitmap(space, size, depth) - elif depth == 8: - w_display_bitmap = W_8BitDisplayBitmap(space, size, depth) - elif depth == 16: - w_display_bitmap = W_16BitDisplayBitmap(space, size, depth) + elif depth in (16, 32): + w_display_bitmap = W_DirectBitDisplayBitmap(space, size, depth) else: - assert depth == 32 - w_display_bitmap = W_32BitDisplayBitmap(space, size, depth) + raise error.PrimitiveFailedError for idx in range(size): w_display_bitmap.setword(idx, w_obj.getword(idx)) @@ -33,13 +30,13 @@ def from_words_object(w_obj, form): class W_DisplayBitmap(W_AbstractObjectWithIdentityHash): - _attrs_ = ['pixelbuffer_words', '_real_depth_buffer', '_realsize', '_display', '_depth'] - _immutable_fields_ = ['pixelbuffer_words?', '_real_depth_buffer?', '_realsize', '_display', '_depth'] + _attrs_ = ['is_display', '_squeak_pixel_buffer', '_realsize', '_display', '_depth'] + _immutable_fields_ = ['is_display?', '_squeak_pixel_buffer', '_realsize', '_display', '_depth'] repr_classname = "W_DisplayBitmap" def __init__(self, space, size, depth): W_AbstractObjectWithIdentityHash.__init__(self) - self._real_depth_buffer = lltype.malloc(rffi.CArray(rffi.UINT), size, flavor='raw') + self._squeak_pixel_buffer = lltype.malloc(rffi.CArray(rffi.UINT), size, flavor='raw') self._realsize = size self._depth = depth self._display = space.display() @@ -75,10 +72,10 @@ def unwrap_string(self, space): def getword(self, n): assert self.size() > n >= 0 - return r_uint(self._real_depth_buffer[n]) + return r_uint(self._squeak_pixel_buffer[n]) def setword(self, n, word): - self._real_depth_buffer[n] = rffi.r_uint(word) + self._squeak_pixel_buffer[n] = rffi.r_uint(word) def setwords(self, lst): for i in range(self.size()): @@ -95,54 +92,15 @@ def display(self): def is_headless(self): return self._display.is_headless() - def pixelbuffer(self): - return self.display().get_pixelbuffer() - - def set_pixelbuffer_word(self, n, word): - self.pixelbuffer()[n] = rffi.r_uint(word) - - @jit.elidable - def pixel_per_word(self): - return constants.BYTES_PER_WORD / (self.display().depth / 8) - def take_over_display(self): - # Make sure FrameWrapper.take_over_display() is called first for the correct Frame object. - self.pixelbuffer_words = self.display().width * self.display().height / self.pixel_per_word() - self.update_from_buffer() + self.is_display = True def relinquish_display(self): - self.pixelbuffer_words = 0 + self.is_display = False def flush_to_screen(self): - self.make_dirty() + self.force_rectange_to_screen(0, self.display().width, 0, self.display().height) self.display().render() - self.make_dirty() # do it twice for macOS - - def make_dirty(self): - self.display().flip(0, 0, self.display().width, self.display().height) - - def word_from_pixel(self, x, y): - return (x + y * self.display().width) / self.pixel_per_word() - - def force_rectange_to_screen(self, left, right, top, bottom): - if self.pixelbuffer_words > 0: - start = max(self.word_from_pixel(left, top), 0) - stop = min(self.word_from_pixel(right, bottom), self.size() - 1) - if stop <= start: - return - self.force_words(start, stop) - self.display().flip(left, top, right, bottom) - - def force_words(self, start, stop): - if self.is_headless(): return - for i in range(stop - start): - self.set_pixelbuffer_word(i + start, self.getword(i + start)) - - def update_from_buffer(self): - if self.is_headless(): return - if self.pixelbuffer_words > 0: - for i in range(self.size()): - self.set_pixelbuffer_word(i, self.getword(i)) # === Misc @@ -162,110 +120,54 @@ def can_become(self, w_other): # TODO - implement _become() for this class. Impossible due to _immutable_fields_? return False - @rgc.must_be_light_finalizer - def __del__(self): - if self.pixelbuffer_words == 0: - lltype.free(self._real_depth_buffer, flavor='raw') - def repr_content(self): return "len=%d depth=%d %s" % (self.size(), self._depth, self.str_content()) -class W_32BitDisplayBitmap(W_DisplayBitmap): - repr_classname = "W_32BitDisplayBitmap" - - def take_over_display(self): - if self.pixelbuffer_words == 0: - W_DisplayBitmap.take_over_display(self) - assert self.pixelbuffer_words == self.size() - lltype.free(self._real_depth_buffer, flavor='raw') - self._real_depth_buffer = self.pixelbuffer() - - def relinquish_display(self): - W_DisplayBitmap.relinquish_display(self) - assert self.pixelbuffer_words == 0 - self._real_depth_buffer = lltype.malloc(rffi.CArray(rffi.UINT), self.size(), flavor='raw') - self._copy_pixelbuffer_to_own() - - def _copy_pixelbuffer_to_own(self): - for i in range(self.size()): - self.setword(i, self.pixelbuffer()[i]) +class W_DirectDisplayBitmap(W_DisplayBitmap): + repr_classname = "W_DirectDisplayBitmap" def force_rectange_to_screen(self, left, right, top, bottom): - if self.pixelbuffer_words > 0: - width = self.display().width - self.display().flip(left, top, right, bottom) - + if self.is_headless: return + if self.is_display: + self.display().flip(self._squeak_pixel_buffer, left, top, right, bottom) -class W_16BitDisplayBitmap(W_DisplayBitmap): - repr_classname = "W_16BitDisplayBitmap" - - def set_pixelbuffer_word(self, n, word): - mask = 0b11111 - lsb = (r_uint(word) & r_uint(0xffff0000)) >> 16 - msb = (r_uint(word) & r_uint(0x0000ffff)) - - if not system.IS_DARWIN: - # Invert order of rgb-components - lsb = ( - ((lsb >> 10) & mask) | - (((lsb >> 5) & mask) << 6) | - ((lsb & mask) << 11) - ) - msb = ( - ((msb >> 10) & mask) | - (((msb >> 5) & mask) << 6) | - ((msb & mask) << 11) - ) - self.pixelbuffer()[n] = rffi.r_uint(lsb | (msb << 16)) - - -class W_8BitDisplayBitmap(W_DisplayBitmap): - - repr_classname = "W_8BitDisplayBitmap" - - def set_pixelbuffer_word(self, n, word): - # Invert the byte-order. - self.pixelbuffer()[n] = rffi.r_uint( - (word >> 24) | - ((word >> 8) & 0x0000ff00) | - ((word << 8) & 0x00ff0000) | - (word << 24) - ) + @rgc.must_be_light_finalizer + def __del__(self): + lltype.free(self._squeak_pixel_buffer, flavor='raw') +from rsqueakvm.display import BELOW_MINIMUM_DEPTH class W_MappingDisplayBitmap(W_DisplayBitmap): - repr_classname = "W_MappingDisplayBitmap" - _attrs_ = ['words_per_line', 'bits_in_last_word', 'pitch'] - _immutable_fields_ = ['words_per_line?', 'bits_in_last_word?', 'pitch?'] + _attrs_ = ['_sdl_pixel_buffer', 'words_per_line', 'bits_in_last_word', 'pitch'] + _immutable_fields_ = ['_sdl_pixel_buffer', 'words_per_line?', 'bits_in_last_word?', 'pitch?'] def __init__(self, space, size, depth): - assert depth in [1, 2, 4] W_DisplayBitmap.__init__(self, space, size, depth) + self.mapping_factor = BELOW_MINIMUM_DEPTH / self._depth + self._sdl_pixel_buffer = lltype.malloc(rffi.CArray(rffi.UINT), size * self.mapping_factor, flavor='raw') - def word_from_pixel(self, x, y): - word = W_DisplayBitmap.word_from_pixel(self, x, y) - if self._depth == 1: - return word / 32 - elif self._depth == 2: - return word / 16 - elif self._depth == 4: - return word / 8 - else: - assert False + @rgc.must_be_light_finalizer + def __del__(self): + lltype.free(self._squeak_pixel_buffer, flavor='raw') + lltype.free(self._sdl_pixel_buffer, flavor='raw') - def display_words_per_word(self): - return self.display().depth / self._depth + def word_from_pixel(self, x, y): + word = x + y * self.display().width + return word / self.mapping_factor def take_over_display(self): + W_DisplayBitmap.take_over_display(self) pitch = r_uint(self.display().pitch) # The pitch may be different from the width input to SDL! self.pitch = pitch - self.bits_in_last_word = pitch % 32 - self.words_per_line = r_uint((pitch - self.bits_in_last_word) / 32) + self.bits_in_last_word = pitch % BELOW_MINIMUM_DEPTH + self.words_per_line = r_uint((pitch - self.bits_in_last_word) / constants.BITS_PER_WORD) if self.bits_in_last_word > 0: self.words_per_line += 1 - W_DisplayBitmap.take_over_display(self) + if self.is_headless(): return + for i in range(self.size()): + self.set_pixelbuffer_word(i, self.getword(i)) @jit.unroll_safe def set_pixelbuffer_word(self, n, word): @@ -274,12 +176,12 @@ def set_pixelbuffer_word(self, n, word): # This is the last word on the line. A few bits may be cut off. bits = self.bits_in_last_word else: - bits = 32 - buf = self.pixelbuffer() + bits = constants.BITS_PER_WORD + buf = self._sdl_pixel_buffer word = r_uint(word) depth = r_uint(self._depth) - shift = 32 - depth - display_words_per_word = 32 / depth + shift = constants.BITS_PER_WORD - depth + display_words_per_word = BELOW_MINIMUM_DEPTH / depth pixelmask = ((r_uint(1) << depth) - 1) << shift table = PIXEL_LOOKUP_TABLE[depth - 1] assert table is not None @@ -288,6 +190,17 @@ def set_pixelbuffer_word(self, n, word): buf[n * display_words_per_word + i] = rffi.r_uint(table[pixel]) pixelmask >>= depth + def force_rectange_to_screen(self, left, right, top, bottom): + if self.is_headless(): return + if self.is_display: + start = max(self.word_from_pixel(left, top), 0) + stop = min(self.word_from_pixel(right, bottom), self.size() - 1) + if stop <= start: + return + for i in range(stop - start): + self.set_pixelbuffer_word(i + start, self.getword(i + start)) + self.display().flip(self._sdl_pixel_buffer, left, top, right, bottom) + PIXEL_LOOKUP_1BIT = [0xffffffff, 0xff000000] PIXEL_LOOKUP_2BIT = [0xff000000, 0xff848484, 0xffc6c6c6, 0xffffffff] @@ -295,5 +208,64 @@ def set_pixelbuffer_word(self, n, word): 0xff000000, 0xff000084, 0xff008400, 0xff008484, 0xff840000, 0xff840084, 0xff848400, 0xff848484, 0xffc6c6c6, 0xff0000ff, 0xff00ff00, 0xff00ffff, - 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff] -PIXEL_LOOKUP_TABLE = [PIXEL_LOOKUP_1BIT, PIXEL_LOOKUP_2BIT, None, PIXEL_LOOKUP_4BIT] + 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff +] +# String streamContents: [:s | +# Color indexedColors +# do: [:ea | s nextPutAll: '0x'; nextPutAll: ea printHtmlString] +# separatedBy: [s nextPutAll: ', ']] +PIXEL_LOOKUP_8BIT = [ + 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF7F7F7F, 0xFFFF0000, 0xFF00FF00, + 0xFF0000FF, 0xFF00FFFF, 0xFFFFFF00, 0xFFFF00FF, 0xFF1F1F1F, 0xFF3F3F3F, + 0xFF5F5F5F, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFDFDFDF, 0xFF070707, 0xFF0F0F0F, + 0xFF171717, 0xFF272727, 0xFF2F2F2F, 0xFF373737, 0xFF474747, 0xFF4F4F4F, + 0xFF575757, 0xFF676767, 0xFF6F6F6F, 0xFF777777, 0xFF878787, 0xFF8F8F8F, + 0xFF979797, 0xFFA7A7A7, 0xFFAFAFAF, 0xFFB7B7B7, 0xFFC7C7C7, 0xFFCFCFCF, + 0xFFD7D7D7, 0xFFE7E7E7, 0xFFEFEFEF, 0xFFF7F7F7, 0xFF000000, 0xFF003200, + 0xFF006500, 0xFF009800, 0xFF00CB00, 0xFF00FF00, 0xFF000032, 0xFF003232, + 0xFF006532, 0xFF009832, 0xFF00CB32, 0xFF00FF32, 0xFF000065, 0xFF003265, + 0xFF006565, 0xFF009865, 0xFF00CB65, 0xFF00FF65, 0xFF000098, 0xFF003298, + 0xFF006598, 0xFF009898, 0xFF00CB98, 0xFF00FF98, 0xFF0000CB, 0xFF0032CB, + 0xFF0065CB, 0xFF0098CB, 0xFF00CBCB, 0xFF00FFCB, 0xFF0000FF, 0xFF0032FF, + 0xFF0065FF, 0xFF0098FF, 0xFF00CBFF, 0xFF00FFFF, 0xFF320000, 0xFF323200, + 0xFF326500, 0xFF329800, 0xFF32CB00, 0xFF32FF00, 0xFF320032, 0xFF323232, + 0xFF326532, 0xFF329832, 0xFF32CB32, 0xFF32FF32, 0xFF320065, 0xFF323265, + 0xFF326565, 0xFF329865, 0xFF32CB65, 0xFF32FF65, 0xFF320098, 0xFF323298, + 0xFF326598, 0xFF329898, 0xFF32CB98, 0xFF32FF98, 0xFF3200CB, 0xFF3232CB, + 0xFF3265CB, 0xFF3298CB, 0xFF32CBCB, 0xFF32FFCB, 0xFF3200FF, 0xFF3232FF, + 0xFF3265FF, 0xFF3298FF, 0xFF32CBFF, 0xFF32FFFF, 0xFF650000, 0xFF653200, + 0xFF656500, 0xFF659800, 0xFF65CB00, 0xFF65FF00, 0xFF650032, 0xFF653232, + 0xFF656532, 0xFF659832, 0xFF65CB32, 0xFF65FF32, 0xFF650065, 0xFF653265, + 0xFF656565, 0xFF659865, 0xFF65CB65, 0xFF65FF65, 0xFF650098, 0xFF653298, + 0xFF656598, 0xFF659898, 0xFF65CB98, 0xFF65FF98, 0xFF6500CB, 0xFF6532CB, + 0xFF6565CB, 0xFF6598CB, 0xFF65CBCB, 0xFF65FFCB, 0xFF6500FF, 0xFF6532FF, + 0xFF6565FF, 0xFF6598FF, 0xFF65CBFF, 0xFF65FFFF, 0xFF980000, 0xFF983200, + 0xFF986500, 0xFF989800, 0xFF98CB00, 0xFF98FF00, 0xFF980032, 0xFF983232, + 0xFF986532, 0xFF989832, 0xFF98CB32, 0xFF98FF32, 0xFF980065, 0xFF983265, + 0xFF986565, 0xFF989865, 0xFF98CB65, 0xFF98FF65, 0xFF980098, 0xFF983298, + 0xFF986598, 0xFF989898, 0xFF98CB98, 0xFF98FF98, 0xFF9800CB, 0xFF9832CB, + 0xFF9865CB, 0xFF9898CB, 0xFF98CBCB, 0xFF98FFCB, 0xFF9800FF, 0xFF9832FF, + 0xFF9865FF, 0xFF9898FF, 0xFF98CBFF, 0xFF98FFFF, 0xFFCB0000, 0xFFCB3200, + 0xFFCB6500, 0xFFCB9800, 0xFFCBCB00, 0xFFCBFF00, 0xFFCB0032, 0xFFCB3232, + 0xFFCB6532, 0xFFCB9832, 0xFFCBCB32, 0xFFCBFF32, 0xFFCB0065, 0xFFCB3265, + 0xFFCB6565, 0xFFCB9865, 0xFFCBCB65, 0xFFCBFF65, 0xFFCB0098, 0xFFCB3298, + 0xFFCB6598, 0xFFCB9898, 0xFFCBCB98, 0xFFCBFF98, 0xFFCB00CB, 0xFFCB32CB, + 0xFFCB65CB, 0xFFCB98CB, 0xFFCBCBCB, 0xFFCBFFCB, 0xFFCB00FF, 0xFFCB32FF, + 0xFFCB65FF, 0xFFCB98FF, 0xFFCBCBFF, 0xFFCBFFFF, 0xFFFF0000, 0xFFFF3200, + 0xFFFF6500, 0xFFFF9800, 0xFFFFCB00, 0xFFFFFF00, 0xFFFF0032, 0xFFFF3232, + 0xFFFF6532, 0xFFFF9832, 0xFFFFCB32, 0xFFFFFF32, 0xFFFF0065, 0xFFFF3265, + 0xFFFF6565, 0xFFFF9865, 0xFFFFCB65, 0xFFFFFF65, 0xFFFF0098, 0xFFFF3298, + 0xFFFF6598, 0xFFFF9898, 0xFFFFCB98, 0xFFFFFF98, 0xFFFF00CB, 0xFFFF32CB, + 0xFFFF65CB, 0xFFFF98CB, 0xFFFFCBCB, 0xFFFFFFCB, 0xFFFF00FF, 0xFFFF32FF, + 0xFFFF65FF, 0xFFFF98FF, 0xFFFFCBFF, 0xFFFFFFFF +] +PIXEL_LOOKUP_TABLE = [ + PIXEL_LOOKUP_1BIT, + PIXEL_LOOKUP_2BIT, + None, + PIXEL_LOOKUP_4BIT, + None, + None, + None, + PIXEL_LOOKUP_8BIT +] diff --git a/rsqueakvm/primitives/input_output.py b/rsqueakvm/primitives/input_output.py index 9c24e7d3..c495534d 100644 --- a/rsqueakvm/primitives/input_output.py +++ b/rsqueakvm/primitives/input_output.py @@ -62,6 +62,13 @@ def func(interp, s_frame, w_rcvr): w_point.store(interp.space, 1, interp.space.wrap_int(y)) return w_point +@expose_primitive(TEST_DISPLAY_DEPTH, unwrap_spec=[object, int]) +def func(interp, s_frame, w_rcvr, depth): + if depth in [1,2,4,8,16,32]: + return interp.space.w_true + else: + return interp.space.w_false + @expose_primitive(GET_NEXT_EVENT, unwrap_spec=[object, object]) @jit.unroll_safe @jit.look_inside From 108497cb1f87a18c77cbe21da2379edf7afaee51 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 10:17:53 +0100 Subject: [PATCH 20/31] fix tests --- rsqueakvm/display.py | 7 +++++- rsqueakvm/test/test_display.py | 8 ++++++- rsqueakvm/test/test_model.py | 42 ++++++++++------------------------ 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 67b7313a..c3100f93 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -146,6 +146,7 @@ def __init__(self, title, highdpi, software_renderer, altf4quit): self.screen_texture = lltype.nullptr(RSDL.TexturePtr.TO) self.interrupt_key = 15 << 8 # pushing all four meta keys, of which we support three... self._deferred_events = [] + self.insert_padding_event() self._defer_updates = False self._texture_dirty = True @@ -430,12 +431,15 @@ def get_next_key_event(self, key_event_type, time): 0, 0] + def insert_padding_event(self): + self._deferred_events.append([EventTypeNone, 0, 0, 0, 0, 0, 0, 0]) + def get_next_event(self, time=0): if len(self._deferred_events) > 0: return self._deferred_events.pop() # we always return one None event between every event, so we poll only # half of the time - self._deferred_events.append([EventTypeNone, 0, 0, 0, 0, 0, 0, 0]) + self.insert_padding_event() with lltype.scoped_alloc(RSDL.Event) as event: if RSDL.PollEvent(event) == 1: event_type = r_uint(event.c_type) @@ -457,6 +461,7 @@ def get_next_event(self, time=0): (self.is_control_key(self.key) or RSDL.GetModState() & ~RSDL.KMOD_SHIFT != 0))): self._deferred_events.append(self.get_next_key_event(EventKeyChar, time)) + self.insert_padding_event() self.fix_key_code_case() return self.get_next_key_event(EventKeyDown, time) elif event_type == RSDL.TEXTINPUT: diff --git a/rsqueakvm/test/test_display.py b/rsqueakvm/test/test_display.py index 65647614..e0fff8cd 100644 --- a/rsqueakvm/test/test_display.py +++ b/rsqueakvm/test/test_display.py @@ -76,7 +76,13 @@ def stub_mod_state(monkeypatch): @pytest.fixture def sut(): - return display.SDLDisplay("test", True, False, False) + d = display.SDLDisplay("test", True, False, False) + def get_next_event(time=0): + # skip the none-event we always insert + display.SDLDisplay.get_next_event(d, time) + return display.SDLDisplay.get_next_event(d, time) + d.get_next_event = get_next_event + return d def assert_keyevent_array(actual, expected_char_code=None, expected_key_event_type=None, expected_modifiers=None): diff --git a/rsqueakvm/test/test_model.py b/rsqueakvm/test/test_model.py index d9750860..19fc5d30 100644 --- a/rsqueakvm/test/test_model.py +++ b/rsqueakvm/test/test_model.py @@ -413,39 +413,21 @@ def test_display_bitmap(): target.setword(0, r_uint(0xFF00FF00)) assert bin(target.getword(0)) == bin(0xFF00FF00) - buf = target.pixelbuffer() + buf = target._sdl_pixel_buffer for i in xrange(2, 8): - assert buf[i] == 0x0 + assert buf[i] == 0xffffffff target.force_rectange_to_screen(0, 31, 0, 9) - - if constants.IS_64BIT: return # XXX: The below stuff isn't working right... - buf = target.pixelbuffer() - for i in xrange(2): - assert buf[i] == 0x01010101 - for i in xrange(2, 4): - assert buf[i] == 0x0 - for i in xrange(4, 6): - assert buf[i] == 0x01010101 - for i in xrange(6, 8): - assert buf[i] == 0x0 - -def test_display_offset_computation_even(): - dbitmap = W_MappingDisplayBitmap(space, 200, 1) - dbitmap.pitch = 64 - dbitmap.words_per_line = 2 - assert dbitmap.compute_pos(0) == 0 - assert dbitmap.compute_pos(1) == 32 - assert dbitmap.compute_pos(2) == 64 - -def test_display_offset_computation_uneven(): - dbitmap = W_MappingDisplayBitmap(space, 200, 1) - dbitmap.pitch = 67 - dbitmap.words_per_line = 2 - assert dbitmap.compute_pos(0) == 0 - assert dbitmap.compute_pos(1) == 32 - assert dbitmap.compute_pos(2) == 67 - assert dbitmap.compute_pos(3) == 67 + 32 + # now we have 8 pixels black, 8 white, 8 black, 8 white + buf = target._sdl_pixel_buffer + for i in xrange(8): + assert buf[i] == 0xff000000 + for i in xrange(9, 16): + assert buf[i] == 0xffffffff + for i in xrange(17, 24): + assert buf[i] == 0xff000000 + for i in xrange(25, 32): + assert buf[i] == 0xffffffff def test_weak_pointers(): w_cls = bootstrap_class(2) From 438559e8fdd21dcc6300dda61fe7135ef492184b Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 12:08:56 +0100 Subject: [PATCH 21/31] fix translation --- rsqueakvm/model/display.py | 54 +++++++++++++++++----------- rsqueakvm/primitives/input_output.py | 7 ---- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index ecf9b26b..cb97c313 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -18,8 +18,10 @@ def from_words_object(w_obj, form): if depth in (1,2,4,8): w_display_bitmap = W_MappingDisplayBitmap(space, size, depth) - elif depth in (16, 32): - w_display_bitmap = W_DirectBitDisplayBitmap(space, size, depth) + elif depth == 16: + w_display_bitmap = W_16BitDisplayBitmap(space, size, depth) + elif depth == 32: + w_display_bitmap = W_32BitDisplayBitmap(space, size, depth) else: raise error.PrimitiveFailedError @@ -89,9 +91,6 @@ def size(self): def display(self): return jit.promote(self._display) - def is_headless(self): - return self._display.is_headless() - def take_over_display(self): self.is_display = True @@ -128,20 +127,38 @@ class W_DirectDisplayBitmap(W_DisplayBitmap): repr_classname = "W_DirectDisplayBitmap" def force_rectange_to_screen(self, left, right, top, bottom): - if self.is_headless: return - if self.is_display: - self.display().flip(self._squeak_pixel_buffer, left, top, right, bottom) + self.display().flip(self._squeak_pixel_buffer, left, top, right, bottom) @rgc.must_be_light_finalizer def __del__(self): lltype.free(self._squeak_pixel_buffer, flavor='raw') +class W_32BitDisplayBitmap(W_DirectDisplayBitmap): + repr_classname = "W_32BitDisplayBitmap" + + +class W_16BitDisplayBitmap(W_DirectDisplayBitmap): + repr_classname = "W_16BitDisplayBitmap" + + def swap_pixels(self, word): + return ( + ((word & r_uint(0xffff0000)) >> 16) | + ((word & r_uint(0x0000ffff)) << 16) + ) + + def setword(self, n, word): + W_DirectDisplayBitmap.setword(self, n, self.swap_pixels(word)) + + def getword(self, n): + return self.swap_pixels(W_DirectDisplayBitmap.getword(self, n)) + + from rsqueakvm.display import BELOW_MINIMUM_DEPTH class W_MappingDisplayBitmap(W_DisplayBitmap): repr_classname = "W_MappingDisplayBitmap" - _attrs_ = ['_sdl_pixel_buffer', 'words_per_line', 'bits_in_last_word', 'pitch'] - _immutable_fields_ = ['_sdl_pixel_buffer', 'words_per_line?', 'bits_in_last_word?', 'pitch?'] + _attrs_ = ['mapping_factor', '_sdl_pixel_buffer', 'words_per_line', 'bits_in_last_word', 'pitch'] + _immutable_fields_ = ['_sdl_pixel_buffer', 'mapping_factor', 'words_per_line?', 'bits_in_last_word?', 'pitch?'] def __init__(self, space, size, depth): W_DisplayBitmap.__init__(self, space, size, depth) @@ -165,7 +182,6 @@ def take_over_display(self): self.words_per_line = r_uint((pitch - self.bits_in_last_word) / constants.BITS_PER_WORD) if self.bits_in_last_word > 0: self.words_per_line += 1 - if self.is_headless(): return for i in range(self.size()): self.set_pixelbuffer_word(i, self.getword(i)) @@ -191,15 +207,13 @@ def set_pixelbuffer_word(self, n, word): pixelmask >>= depth def force_rectange_to_screen(self, left, right, top, bottom): - if self.is_headless(): return - if self.is_display: - start = max(self.word_from_pixel(left, top), 0) - stop = min(self.word_from_pixel(right, bottom), self.size() - 1) - if stop <= start: - return - for i in range(stop - start): - self.set_pixelbuffer_word(i + start, self.getword(i + start)) - self.display().flip(self._sdl_pixel_buffer, left, top, right, bottom) + start = max(self.word_from_pixel(left, top), 0) + stop = min(self.word_from_pixel(right, bottom), self.size() - 1) + if stop <= start: + return + for i in range(stop - start): + self.set_pixelbuffer_word(i + start, self.getword(i + start)) + self.display().flip(self._sdl_pixel_buffer, left, top, right, bottom) PIXEL_LOOKUP_1BIT = [0xffffffff, 0xff000000] diff --git a/rsqueakvm/primitives/input_output.py b/rsqueakvm/primitives/input_output.py index c495534d..9c24e7d3 100644 --- a/rsqueakvm/primitives/input_output.py +++ b/rsqueakvm/primitives/input_output.py @@ -62,13 +62,6 @@ def func(interp, s_frame, w_rcvr): w_point.store(interp.space, 1, interp.space.wrap_int(y)) return w_point -@expose_primitive(TEST_DISPLAY_DEPTH, unwrap_spec=[object, int]) -def func(interp, s_frame, w_rcvr, depth): - if depth in [1,2,4,8,16,32]: - return interp.space.w_true - else: - return interp.space.w_false - @expose_primitive(GET_NEXT_EVENT, unwrap_spec=[object, object]) @jit.unroll_safe @jit.look_inside From c011aef9e8090f468db87d8f15a9206432d57549 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 12:34:37 +0100 Subject: [PATCH 22/31] add tests for mapping bitmaps --- rsqueakvm/model/display.py | 2 +- rsqueakvm/test/test_model.py | 90 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index cb97c313..38df73ad 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -202,7 +202,7 @@ def set_pixelbuffer_word(self, n, word): table = PIXEL_LOOKUP_TABLE[depth - 1] assert table is not None for i in range(bits / depth): - pixel = (word & pixelmask) >> (shift - i) + pixel = (word & pixelmask) >> (shift - i * depth) buf[n * display_words_per_word + i] = rffi.r_uint(table[pixel]) pixelmask >>= depth diff --git a/rsqueakvm/test/test_model.py b/rsqueakvm/test/test_model.py index 19fc5d30..c9632114 100644 --- a/rsqueakvm/test/test_model.py +++ b/rsqueakvm/test/test_model.py @@ -429,6 +429,96 @@ def test_display_bitmap(): for i in xrange(25, 32): assert buf[i] == 0xffffffff +def test_display_bitmap2(): + size = 10 + space.display().set_video_mode(32, size, 2) + target = W_MappingDisplayBitmap(space, size, 2) + for idx in range(size): + target.setword(idx, r_uint(0)) + target.take_over_display() + + target.setword(0, r_uint(0xFF00)) + assert bin(target.getword(0)) == bin(0xFF00) + target.setword(0, r_uint(0x00FF00FF)) + assert bin(target.getword(0)) == bin(0x00FF00FF) + target.setword(0, r_uint(0xFF00FF00)) + assert bin(target.getword(0)) == bin(0xFF00FF00) + + buf = target._sdl_pixel_buffer + # for i in xrange(2, 8): + # assert buf[i] == 0xff000000 + + target.force_rectange_to_screen(0, 31, 0, 9) + # now we have 4 pixels white, 4 black, 4 white, 4 black + buf = target._sdl_pixel_buffer + for i in xrange(4): + assert buf[i] == 0xffffffff + for i in xrange(5, 8): + assert buf[i] == 0xff000000 + for i in xrange(9, 12): + assert buf[i] == 0xffffffff + for i in xrange(13, 16): + assert buf[i] == 0xff000000 + +def test_display_bitmap4(): + size = 10 + space.display().set_video_mode(32, size, 4) + target = W_MappingDisplayBitmap(space, size, 4) + for idx in range(size): + target.setword(idx, r_uint(0)) + target.take_over_display() + + target.setword(0, r_uint(0xFF00)) + assert bin(target.getword(0)) == bin(0xFF00) + target.setword(0, r_uint(0x00FF00FF)) + assert bin(target.getword(0)) == bin(0x00FF00FF) + target.setword(0, r_uint(0xFF00FF00)) + assert bin(target.getword(0)) == bin(0xFF00FF00) + + buf = target._sdl_pixel_buffer + # for i in xrange(2, 8): + # assert buf[i] == 0xff000000 + + target.force_rectange_to_screen(0, 31, 0, 9) + # now we have 2 pixels white, 2 black, 2 white, 2 black + buf = target._sdl_pixel_buffer + for i in xrange(2): + assert buf[i] == 0xffffffff + for i in xrange(3, 4): + assert buf[i] == 0xff000000 + for i in xrange(5, 6): + assert buf[i] == 0xffffffff + for i in xrange(7, 8): + assert buf[i] == 0xff000000 + +def test_display_bitmap8(): + size = 10 + space.display().set_video_mode(32, size, 8) + target = W_MappingDisplayBitmap(space, size, 8) + for idx in range(size): + target.setword(idx, r_uint(0)) + target.take_over_display() + + target.setword(0, r_uint(0xFF00)) + assert bin(target.getword(0)) == bin(0xFF00) + target.setword(0, r_uint(0x00FF00FF)) + assert bin(target.getword(0)) == bin(0x00FF00FF) + target.setword(0, r_uint(0xFF00FF00)) + assert bin(target.getword(0)) == bin(0xFF00FF00) + + buf = target._sdl_pixel_buffer + # for i in xrange(2, 8): + # assert buf[i] == 0xff000000 + + target.setword(0, r_uint(0xFF01FF01)) + target.force_rectange_to_screen(0, 31, 0, 9) + # now we have 1 pixels white, 1 black, 1 white, 1 black + buf = target._sdl_pixel_buffer + assert buf[0] == 0xffffffff + assert buf[1] == 0xff000000 + assert buf[2] == 0xffffffff + assert buf[3] == 0xff000000 + def test_weak_pointers(): w_cls = bootstrap_class(2) s_cls = w_cls.as_class_get_shadow(space) From eda5701390862d8f76947fb09b97b4346fbe999a Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 12:36:21 +0100 Subject: [PATCH 23/31] correct smalltalk code for 8-bit lookup table --- rsqueakvm/model/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index 38df73ad..7cec95b7 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -226,7 +226,7 @@ def force_rectange_to_screen(self, left, right, top, bottom): ] # String streamContents: [:s | # Color indexedColors -# do: [:ea | s nextPutAll: '0x'; nextPutAll: ea printHtmlString] +# do: [:ea | s nextPutAll: '0xFF'; nextPutAll: ea printHtmlString] # separatedBy: [s nextPutAll: ', ']] PIXEL_LOOKUP_8BIT = [ 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF7F7F7F, 0xFFFF0000, 0xFF00FF00, From a8de828a0afb51ee8fefc276743cbbeef4c203ad Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 14:44:53 +0100 Subject: [PATCH 24/31] update keycodes a little --- rsqueakvm/display.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index c3100f93..ff9a051b 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -338,7 +338,7 @@ def handle_keyboard_event(self, c_type, event): key = key_constants.SHIFT elif sym == RSDL.K_LCTRL or sym == RSDL.K_RCTRL: key = key_constants.CTRL - elif not system.IS_DARWIN and (sym == RSDL.K_LALT or sym == RSDL.K_RALT): + elif sym == RSDL.K_LALT or sym == RSDL.K_RALT: key = key_constants.COMMAND elif system.IS_DARWIN and (sym == RSDL.K_LGUI or sym == RSDL.K_RGUI): key = key_constants.COMMAND @@ -531,8 +531,13 @@ def get_modifier_mask(self, shift): modifier |= CtrlKeyBit if mod & RSDL.KMOD_SHIFT != 0: modifier |= ShiftKeyBit - if not system.IS_DARWIN and (mod & RSDL.KMOD_ALT != 0): - modifier |= CommandKeyBit + if mod & RSDL.KMOD_CAPS != 0: + modifier |= ShiftKeyBit + if mod & RSDL.KMOD_ALT != 0: + if not system.IS_DARWIN: + modifier |= CommandKeyBit + else: + modifier |= OptionKeyBit if mod & RSDL.KMOD_GUI != 0: modifier |= CommandKeyBit return intmask(modifier << shift) From 4ac4eef118da01b6fbe6bc15cb21cc96eee0b0b5 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 14:46:56 +0100 Subject: [PATCH 25/31] stub lock texture --- rsqueakvm/test/test_display.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rsqueakvm/test/test_display.py b/rsqueakvm/test/test_display.py index e0fff8cd..e07462db 100644 --- a/rsqueakvm/test/test_display.py +++ b/rsqueakvm/test/test_display.py @@ -11,6 +11,7 @@ def stub_sdl(monkeypatch): monkeypatch.setattr(RSDL, "PollEvent", lambda *args: 0) monkeypatch.setattr(RSDL, "Init", lambda *args: 0) monkeypatch.setattr(RSDL, "Quit", lambda: 0) + monkeypatch.setattr(RSDL, "LockTexture", lambda a,b,c,d: 0) @pytest.fixture def mocked_sdl_event_queue(monkeypatch): From 782d8ec073dd480a5959bf150007d076b02bcfea Mon Sep 17 00:00:00 2001 From: Fabio Niephaus Date: Wed, 1 Mar 2017 14:56:31 +0100 Subject: [PATCH 26/31] PEP 8 changes for display.py files [ci skip] --- rsqueakvm/display.py | 115 ++++++++++++++++++++----------------- rsqueakvm/model/display.py | 67 ++++++++++++--------- 2 files changed, 104 insertions(+), 78 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index ff9a051b..86dada45 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -1,7 +1,8 @@ +import sys + from rpython.rlib.rarithmetic import r_uint, intmask from rpython.rtyper.lltypesystem import lltype, rffi # from rpython.rlib.runicode import unicode_encode_utf_8 -from rpython.rlib import jit from rsdl import RSDL from rsqueakvm.util import system @@ -59,8 +60,8 @@ class SqueakInterrupt(Exception): class NullDisplay(object): - _attrs_ = ["button", "mouse_position", "key", "width", "height", "depth", - "pitch"] + _attrs_ = ['button', 'mouse_position', 'key', 'width', 'height', 'depth', + 'pitch'] def __init__(self): self.button = 0 @@ -75,10 +76,10 @@ def defer_updates(self, flag): pass def get_pixelbuffer(self): - raise RuntimeError("Code path should not be reached") + raise RuntimeError('Code path should not be reached') def get_plain_pixelbuffer(self): - raise RuntimeError("Code path should not be reached") + raise RuntimeError('Code path should not be reached') def get_next_event(self, time=0): return [EventTypeNone, 0, 0, 0, 0, 0, 0, 0] @@ -127,11 +128,11 @@ def set_video_mode(self, w, h, d): class SDLDisplay(NullDisplay): - _attrs_ = ["window", "title", "renderer", "screen_texture", "altf4quit", - "interrupt_key", - "_defer_updates", "_deferred_events", "bpp", "pitch", "highdpi", - "software_renderer", "interrupt_flag", "_texture_dirty"] - _immutable_fields_ = ["interrupt_flag"] + _attrs_ = ['window', 'title', 'renderer', 'screen_texture', 'altf4quit', + 'interrupt_key', + '_defer_updates', '_deferred_events', 'bpp', 'pitch', 'highdpi', + 'software_renderer', 'interrupt_flag', '_texture_dirty'] + _immutable_fields_ = ['interrupt_flag'] def __init__(self, title, highdpi, software_renderer, altf4quit): NullDisplay.__init__(self) @@ -144,26 +145,31 @@ def __init__(self, title, highdpi, software_renderer, altf4quit): self.window = lltype.nullptr(RSDL.WindowPtr.TO) self.renderer = lltype.nullptr(RSDL.RendererPtr.TO) self.screen_texture = lltype.nullptr(RSDL.TexturePtr.TO) - self.interrupt_key = 15 << 8 # pushing all four meta keys, of which we support three... + # pushing all four meta keys, of which we support three... + self.interrupt_key = 15 << 8 self._deferred_events = [] self.insert_padding_event() self._defer_updates = False self._texture_dirty = True def _init_sdl(self): - from rpython.rlib.objectmodel import we_are_translated if RSDL.Init(RSDL.INIT_VIDEO) < 0: print RSDL.GetError() assert False self.interrupt_flag = lltype.malloc(rffi.SIGNEDP.TO, 1, flavor='raw') self.interrupt_flag[0] = 0 ll_SetEventFilter(self.interrupt_flag) - RSDL.SetHint(RSDL.HINT_RENDER_VSYNC, "0") # do not wait for vsync - RSDL.SetHint(RSDL.HINT_RENDER_SCALE_QUALITY, "0") # nearest pixel sampling + # do not wait for vsync + RSDL.SetHint(RSDL.HINT_RENDER_VSYNC, '0') + # nearest pixel sampling + RSDL.SetHint(RSDL.HINT_RENDER_SCALE_QUALITY, '0') # SDL >= 2.0.4 - # RSDL.SetHint(RSDL.HINT_VIDEO_X11_NET_WM_PING, "0") # disable WM_PING, so the WM does not think we're hung - if (RSDL.SetSwapInterval(-1) < 0): # try to allow late tearing (pushes frames faster) - RSDL.SetSwapInterval(0) # at least try to disable vsync + # disable WM_PING, so the WM does not think we're hung + # RSDL.SetHint(RSDL.HINT_VIDEO_X11_NET_WM_PING, '0') + + # try to allow late tearing (pushes frames faster) + if (RSDL.SetSwapInterval(-1) < 0): + RSDL.SetSwapInterval(0) # at least try to disable vsync def has_interrupts_pending(self): if self.interrupt_flag[0] != 0: @@ -203,11 +209,11 @@ def set_video_mode(self, w, h, d): if self.screen_texture != lltype.nullptr(RSDL.TexturePtr.TO): RSDL.DestroyTexture(self.screen_texture) self.screen_texture = RSDL.CreateTexture( - self.renderer, - DEPTH_TO_PIXELFORMAT[d], RSDL.TEXTUREACCESS_STREAMING, - w, h) + self.renderer, + DEPTH_TO_PIXELFORMAT[d], RSDL.TEXTUREACCESS_STREAMING, + w, h) if not self.screen_texture: - print "Could not create screen texture" + print 'Could not create screen texture' raise RuntimeError(RSDL.GetError()) self.lock() if d == 16: @@ -221,7 +227,8 @@ def set_video_mode(self, w, h, d): def set_full_screen(self, flag): if flag: - RSDL.SetWindowFullscreen(self.window, RSDL.WINDOW_FULLSCREEN_DESKTOP) + RSDL.SetWindowFullscreen(self.window, + RSDL.WINDOW_FULLSCREEN_DESKTOP) else: RSDL.SetWindowFullscreen(self.window, 0) @@ -357,10 +364,10 @@ def handle_keyboard_event(self, c_type, event): elif sym == RSDL.K_PRINTSCREEN: key = key_constants.PRINT else: - key = rffi.cast(rffi.INT, sym) # use SDL's keycode + key = rffi.cast(rffi.INT, sym) # use SDL's keycode # this is the lowercase ascii-value for the most common keys # elif char != 0: - # chars = unicode_encode_utf_8(unichr(char), 1, "ignore") + # chars = unicode_encode_utf_8(unichr(char), 1, 'ignore') # if len(chars) == 1: # asciivalue = ord(chars[0]) # if asciivalue >= 32: @@ -370,12 +377,14 @@ def handle_keyboard_event(self, c_type, event): else: self.key = intmask(key) interrupt = self.interrupt_key - if (interrupt & 0xFF == self.key and interrupt >> 8 == self.get_modifier_mask(0)): + if (interrupt & 0xFF == self.key and + interrupt >> 8 == self.get_modifier_mask(0)): raise SqueakInterrupt def handle_textinput_event(self, event): textinput = rffi.cast(RSDL.TextInputEventPtr, event) - self.key = ord(rffi.charp2str(rffi.cast(rffi.CCHARP, textinput.c_text))[0]) + self.key = ord( + rffi.charp2str(rffi.cast(rffi.CCHARP, textinput.c_text))[0]) # XXX: textinput.c_text could contain multiple characters # so probably multiple Squeak events must be emitted # moreover, this is UTF-8 so umlauts etc. have to be decoded @@ -456,11 +465,13 @@ def get_next_event(self, time=0): if not self.is_modifier_key(self.key): # no TEXTINPUT event for this key will follow, but # Squeak needs a KeyStroke anyway - if ((system.IS_LINUX and self.is_control_key(self.key)) or + if ((system.IS_LINUX and + self.is_control_key(self.key)) or (not system.IS_LINUX and (self.is_control_key(self.key) or RSDL.GetModState() & ~RSDL.KMOD_SHIFT != 0))): - self._deferred_events.append(self.get_next_key_event(EventKeyChar, time)) + self._deferred_events.append( + self.get_next_key_event(EventKeyChar, time)) self.insert_padding_event() self.fix_key_code_case() return self.get_next_key_event(EventKeyDown, time) @@ -474,27 +485,28 @@ def get_next_event(self, time=0): elif event_type == RSDL.WINDOWEVENT: self.handle_windowevent(event_type, event) elif event_type == RSDL.QUIT: - if self.altf4quit: # we want to quit hard + if self.altf4quit: # we want to quit hard from rsqueakvm.util.dialog import ask_question - if ask_question("Quit Squeak without saving?"): + if ask_question('Quit Squeak without saving?'): raise Exception - return [EventTypeWindow, time, WindowEventClose, 0, 0, 0, 0, 0] + return [EventTypeWindow, time, WindowEventClose, + 0, 0, 0, 0, 0] return [EventTypeNone, 0, 0, 0, 0, 0, 0, 0] def is_control_key(self, key_ord): key_ord = key_ord return key_ord < 32 or key_ord in [ - key_constants.DELETE, - key_constants.NUMLOCK, - key_constants.SCROLLLOCK - ] + key_constants.DELETE, + key_constants.NUMLOCK, + key_constants.SCROLLLOCK + ] def is_modifier_key(self, key_ord): return key_ord in [ - key_constants.COMMAND, - key_constants.CTRL, - key_constants.SHIFT - ] + key_constants.COMMAND, + key_constants.CTRL, + key_constants.SHIFT + ] def fix_key_code_case(self): if self.key <= 255: @@ -504,12 +516,12 @@ def fix_key_code_case(self): # Old style event handling def pump_events(self): - event = lltype.malloc(RSDL.Event, flavor="raw") + event = lltype.malloc(RSDL.Event, flavor='raw') try: if RSDL.PollEvent(event) == 1: c_type = r_uint(event.c_type) if (c_type == r_uint(RSDL.MOUSEBUTTONDOWN) or - c_type == r_uint(RSDL.MOUSEBUTTONUP)): + c_type == r_uint(RSDL.MOUSEBUTTONUP)): self.handle_mouse_button(c_type, event) return elif c_type == r_uint(RSDL.MOUSEMOTION): @@ -519,7 +531,7 @@ def pump_events(self): return elif c_type == r_uint(RSDL.QUIT): from rsqueakvm.error import Exit - raise Exit("Window closed") + raise Exit('Window closed') finally: lltype.free(event, flavor='raw') @@ -576,7 +588,7 @@ def has_clipboard_text(self): class SDLCursorClass(object): """Cursor modification not yet implemented in RSDL2?""" - _attrs_ = ["cursor", "has_cursor", "has_display"] + _attrs_ = ['cursor', 'has_cursor', 'has_display'] instance = None @@ -602,16 +614,16 @@ def set(self, data_words, w, h, x, y, mask_words=None): self.has_cursor = True RSDL.SetCursor(self.cursor) finally: - lltype.free(mask, flavor="raw") + lltype.free(mask, flavor='raw') finally: - lltype.free(data, flavor="raw") + lltype.free(data, flavor='raw') return True def cursor_words_to_bytes(self, bytenum, words): - """In Squeak, only the upper 16bits of the cursor form seem to count (I'm - guessing because the code was ported over from 16-bit machines), so this - ignores the lower 16-bits of each word.""" - bytes = lltype.malloc(RSDL.Uint8P.TO, bytenum, flavor="raw") + """In Squeak, only the upper 16bits of the cursor form seem to count + (I'm guessing because the code was ported over from 16-bit machines), + so this ignores the lower 16-bits of each word.""" + bytes = lltype.malloc(RSDL.Uint8P.TO, bytenum, flavor='raw') if words: for idx, word in enumerate(words): bytes[idx * 2] = rffi.r_uchar((word >> 24) & 0xff) @@ -621,7 +633,6 @@ def cursor_words_to_bytes(self, bytenum, words): bytes[idx] = rffi.r_uchar(0) return bytes -import sys if 'sphinx' not in sys.modules: SDLCursor = SDLCursorClass() @@ -672,5 +683,5 @@ def cursor_words_to_bytes(self, bytenum, words): """] ).merge(get_rsdl_compilation_info()) - ll_SetEventFilter = rffi.llexternal('SetEventFilter', [rffi.SIGNEDP], rffi.INT, - compilation_info=eci) + ll_SetEventFilter = rffi.llexternal('SetEventFilter', [rffi.SIGNEDP], + rffi.INT, compilation_info=eci) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index 7cec95b7..eae5892d 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -1,11 +1,11 @@ from rsqueakvm import constants, error +from rsqueakvm.display import BELOW_MINIMUM_DEPTH from rsqueakvm.model.base import W_AbstractObjectWithIdentityHash from rsqueakvm.model.variable import W_WordsObject -from rsqueakvm.util import system from rpython.rlib import jit, rgc -from rpython.rtyper.lltypesystem import lltype, rffi from rpython.rlib.rarithmetic import r_uint +from rpython.rtyper.lltypesystem import lltype, rffi def from_words_object(w_obj, form): @@ -16,7 +16,7 @@ def from_words_object(w_obj, form): if not w_class.is_same_object(space.w_Bitmap): raise error.WrappingError - if depth in (1,2,4,8): + if depth in (1, 2, 4, 8): w_display_bitmap = W_MappingDisplayBitmap(space, size, depth) elif depth == 16: w_display_bitmap = W_16BitDisplayBitmap(space, size, depth) @@ -32,13 +32,18 @@ def from_words_object(w_obj, form): class W_DisplayBitmap(W_AbstractObjectWithIdentityHash): - _attrs_ = ['is_display', '_squeak_pixel_buffer', '_realsize', '_display', '_depth'] - _immutable_fields_ = ['is_display?', '_squeak_pixel_buffer', '_realsize', '_display', '_depth'] - repr_classname = "W_DisplayBitmap" + _attrs_ = [ + 'is_display', '_squeak_pixel_buffer', '_realsize', '_display', + '_depth'] + _immutable_fields_ = [ + 'is_display?', '_squeak_pixel_buffer', '_realsize', '_display', + '_depth'] + repr_classname = 'W_DisplayBitmap' def __init__(self, space, size, depth): W_AbstractObjectWithIdentityHash.__init__(self) - self._squeak_pixel_buffer = lltype.malloc(rffi.CArray(rffi.UINT), size, flavor='raw') + self._squeak_pixel_buffer = lltype.malloc( + rffi.CArray(rffi.UINT), size, flavor='raw') self._realsize = size self._depth = depth self._display = space.display() @@ -48,7 +53,7 @@ def getclass(self, space): return space.w_Bitmap def guess_classname(self): - return "Bitmap" + return 'Bitmap' # === Object access @@ -70,7 +75,7 @@ def unwrap_string(self, space): chr((self.getword(i) & r_uint(0x0000ff00)) >> 8), chr((self.getword(i) & r_uint(0x00ff0000)) >> 16), chr((self.getword(i) & r_uint(0xff000000)) >> 24)] - return "".join(res) + return ''.join(res) def getword(self, n): assert self.size() > n >= 0 @@ -98,7 +103,8 @@ def relinquish_display(self): self.is_display = False def flush_to_screen(self): - self.force_rectange_to_screen(0, self.display().width, 0, self.display().height) + self.force_rectange_to_screen(0, self.display().width, + 0, self.display().height) self.display().render() # === Misc @@ -116,18 +122,21 @@ def is_array_object(self): return True def can_become(self, w_other): - # TODO - implement _become() for this class. Impossible due to _immutable_fields_? + """TODO implement _become() for this class. + Impossible due to _immutable_fields_?""" return False def repr_content(self): - return "len=%d depth=%d %s" % (self.size(), self._depth, self.str_content()) + return 'len=%d depth=%d %s' % ( + self.size(), self._depth, self.str_content()) class W_DirectDisplayBitmap(W_DisplayBitmap): - repr_classname = "W_DirectDisplayBitmap" + repr_classname = 'W_DirectDisplayBitmap' def force_rectange_to_screen(self, left, right, top, bottom): - self.display().flip(self._squeak_pixel_buffer, left, top, right, bottom) + self.display().flip(self._squeak_pixel_buffer, + left, top, right, bottom) @rgc.must_be_light_finalizer def __del__(self): @@ -135,11 +144,11 @@ def __del__(self): class W_32BitDisplayBitmap(W_DirectDisplayBitmap): - repr_classname = "W_32BitDisplayBitmap" + repr_classname = 'W_32BitDisplayBitmap' class W_16BitDisplayBitmap(W_DirectDisplayBitmap): - repr_classname = "W_16BitDisplayBitmap" + repr_classname = 'W_16BitDisplayBitmap' def swap_pixels(self, word): return ( @@ -154,16 +163,20 @@ def getword(self, n): return self.swap_pixels(W_DirectDisplayBitmap.getword(self, n)) -from rsqueakvm.display import BELOW_MINIMUM_DEPTH class W_MappingDisplayBitmap(W_DisplayBitmap): - repr_classname = "W_MappingDisplayBitmap" - _attrs_ = ['mapping_factor', '_sdl_pixel_buffer', 'words_per_line', 'bits_in_last_word', 'pitch'] - _immutable_fields_ = ['_sdl_pixel_buffer', 'mapping_factor', 'words_per_line?', 'bits_in_last_word?', 'pitch?'] + repr_classname = 'W_MappingDisplayBitmap' + _attrs_ = [ + 'mapping_factor', '_sdl_pixel_buffer', 'words_per_line', + 'bits_in_last_word', 'pitch'] + _immutable_fields_ = [ + '_sdl_pixel_buffer', 'mapping_factor', 'words_per_line?', + 'bits_in_last_word?', 'pitch?'] def __init__(self, space, size, depth): W_DisplayBitmap.__init__(self, space, size, depth) self.mapping_factor = BELOW_MINIMUM_DEPTH / self._depth - self._sdl_pixel_buffer = lltype.malloc(rffi.CArray(rffi.UINT), size * self.mapping_factor, flavor='raw') + self._sdl_pixel_buffer = lltype.malloc( + rffi.CArray(rffi.UINT), size * self.mapping_factor, flavor='raw') @rgc.must_be_light_finalizer def __del__(self): @@ -176,10 +189,12 @@ def word_from_pixel(self, x, y): def take_over_display(self): W_DisplayBitmap.take_over_display(self) - pitch = r_uint(self.display().pitch) # The pitch may be different from the width input to SDL! + # The pitch may be different from the width input to SDL! + pitch = r_uint(self.display().pitch) self.pitch = pitch self.bits_in_last_word = pitch % BELOW_MINIMUM_DEPTH - self.words_per_line = r_uint((pitch - self.bits_in_last_word) / constants.BITS_PER_WORD) + self.words_per_line = r_uint( + (pitch - self.bits_in_last_word) / constants.BITS_PER_WORD) if self.bits_in_last_word > 0: self.words_per_line += 1 for i in range(self.size()): @@ -188,7 +203,7 @@ def take_over_display(self): @jit.unroll_safe def set_pixelbuffer_word(self, n, word): n = r_uint(n) - if ((n+1) % self.words_per_line) == 0 and self.bits_in_last_word > 0: + if ((n + 1) % self.words_per_line) == 0 and self.bits_in_last_word > 0: # This is the last word on the line. A few bits may be cut off. bits = self.bits_in_last_word else: @@ -197,7 +212,7 @@ def set_pixelbuffer_word(self, n, word): word = r_uint(word) depth = r_uint(self._depth) shift = constants.BITS_PER_WORD - depth - display_words_per_word = BELOW_MINIMUM_DEPTH / depth + display_words_per_word = BELOW_MINIMUM_DEPTH / depth pixelmask = ((r_uint(1) << depth) - 1) << shift table = PIXEL_LOOKUP_TABLE[depth - 1] assert table is not None @@ -226,7 +241,7 @@ def force_rectange_to_screen(self, left, right, top, bottom): ] # String streamContents: [:s | # Color indexedColors -# do: [:ea | s nextPutAll: '0xFF'; nextPutAll: ea printHtmlString] +# do: [:ea|s nextPutAll: '0xFF'; nextPutAll: ea printHtmlString] # separatedBy: [s nextPutAll: ', ']] PIXEL_LOOKUP_8BIT = [ 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF7F7F7F, 0xFFFF0000, 0xFF00FF00, From df2e4a77af28f1cd16811172981442adbdbd513c Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 15:08:22 +0100 Subject: [PATCH 27/31] always clear the event queue --- rsqueakvm/display.py | 52 +++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index ff9a051b..29f4a721 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -431,28 +431,25 @@ def get_next_key_event(self, key_event_type, time): 0, 0] - def insert_padding_event(self): - self._deferred_events.append([EventTypeNone, 0, 0, 0, 0, 0, 0, 0]) - def get_next_event(self, time=0): - if len(self._deferred_events) > 0: - return self._deferred_events.pop() - # we always return one None event between every event, so we poll only - # half of the time - self.insert_padding_event() + if self.has_queued_events(): + return self.dequeue_event() + got_event = False with lltype.scoped_alloc(RSDL.Event) as event: - if RSDL.PollEvent(event) == 1: + while RSDL.PollEvent(event) == 1: + got_event = True event_type = r_uint(event.c_type) if event_type in (RSDL.MOUSEBUTTONDOWN, RSDL.MOUSEBUTTONUP): self.handle_mouse_button(event_type, event) - return self.get_next_mouse_event(time) + self.queue_event(self.get_next_mouse_event(time)) elif event_type == RSDL.MOUSEMOTION: self.handle_mouse_move(event_type, event) - return self.get_next_mouse_event(time) + self.queue_event(self.get_next_mouse_event(time)) elif event_type == RSDL.MOUSEWHEEL: - return self.get_next_mouse_wheel_event(time, event) + self.queue_event(self.get_next_mouse_wheel_event(time, event)) elif event_type == RSDL.KEYDOWN: self.handle_keyboard_event(event_type, event) + later = None if not self.is_modifier_key(self.key): # no TEXTINPUT event for this key will follow, but # Squeak needs a KeyStroke anyway @@ -460,17 +457,19 @@ def get_next_event(self, time=0): (not system.IS_LINUX and (self.is_control_key(self.key) or RSDL.GetModState() & ~RSDL.KMOD_SHIFT != 0))): - self._deferred_events.append(self.get_next_key_event(EventKeyChar, time)) - self.insert_padding_event() + later = self.get_next_key_event(EventKeyChar, time) self.fix_key_code_case() - return self.get_next_key_event(EventKeyDown, time) + self.queue_event(self.get_next_key_event(EventKeyDown, time)) + if later: + self.insert_padding_event() + self.queue_event(later) elif event_type == RSDL.TEXTINPUT: self.handle_textinput_event(event) - return self.get_next_key_event(EventKeyChar, time) + self.queue_event(self.get_next_key_event(EventKeyChar, time)) elif event_type == RSDL.KEYUP: self.handle_keyboard_event(event_type, event) self.fix_key_code_case() - return self.get_next_key_event(EventKeyUp, time) + self.queue_event(self.get_next_key_event(EventKeyUp, time)) elif event_type == RSDL.WINDOWEVENT: self.handle_windowevent(event_type, event) elif event_type == RSDL.QUIT: @@ -478,9 +477,26 @@ def get_next_event(self, time=0): from rsqueakvm.util.dialog import ask_question if ask_question("Quit Squeak without saving?"): raise Exception - return [EventTypeWindow, time, WindowEventClose, 0, 0, 0, 0, 0] + self.queue_event([EventTypeWindow, time, WindowEventClose, 0, 0, 0, 0, 0]) + self.insert_padding_event() + if got_event: + return self.dequeue_event() return [EventTypeNone, 0, 0, 0, 0, 0, 0, 0] + def has_queued_events(self): + return len(self._deferred_events) > 0 + + def queue_event(self, evt): + self._deferred_events.append(evt) + + def dequeue_event(self): + return self._deferred_events.pop(0) + + def insert_padding_event(self): + # we always return one None event between every event, so we poll only + # half of the time + self.queue_event([EventTypeNone, 0, 0, 0, 0, 0, 0, 0]) + def is_control_key(self, key_ord): key_ord = key_ord return key_ord < 32 or key_ord in [ From d30d6f2afe0de4e297710d196095834910ae8259 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 15:43:03 +0100 Subject: [PATCH 28/31] add support for dropping files --- rsqueakvm/display.py | 30 ++++++++++++++++++++++++++++- rsqueakvm/plugins/drop_plugin.py | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 rsqueakvm/plugins/drop_plugin.py diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index 29f4a721..2914fb6a 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -27,6 +27,11 @@ EventTypeComplex = 6 EventTypeMouseWheel = 7 +EventDragTypeEnter = 1 +EventDragTypeMove = 2 +EventDragTypeLeave = 3 +EventDragTypeDrop = 4 + EventKeyChar = 0 EventKeyDown = 1 EventKeyUp = 2 @@ -92,6 +97,9 @@ def render(self, force=False): def has_clipboard_text(self): return False + def get_dropped_filename(self): + return None + def has_interrupts_pending(self): return False @@ -128,7 +136,7 @@ def set_video_mode(self, w, h, d): class SDLDisplay(NullDisplay): _attrs_ = ["window", "title", "renderer", "screen_texture", "altf4quit", - "interrupt_key", + "interrupt_key", "_dropped_file", "_defer_updates", "_deferred_events", "bpp", "pitch", "highdpi", "software_renderer", "interrupt_flag", "_texture_dirty"] _immutable_fields_ = ["interrupt_flag"] @@ -149,6 +157,7 @@ def __init__(self, title, highdpi, software_renderer, altf4quit): self.insert_padding_event() self._defer_updates = False self._texture_dirty = True + self._dropped_file = None def _init_sdl(self): from rpython.rlib.objectmodel import we_are_translated @@ -387,6 +396,23 @@ def handle_windowevent(self, c_type, event): h=intmask(window_event.c_data2), d=self.depth) + def get_dropevent(self, time, c_type, event): + drop_event = rffi.cast(RSDL.DropEvent, event) + path = rffi.charp2str(drop_event.c_file) + btn, mods = self.get_mouse_event_buttons_and_mods() + self._dropped_file = path + lltype.free(drop_event.c_file) + return [EventTypeDragDropFiles, + time, + EventDragTypeDrop, + int(self.mouse_position[0]), + int(self.mouse_position[1]), + mods, + 1] + + def get_dropped_filename(self): + return self._dropped_file + def get_mouse_event_buttons_and_mods(self): mods = self.get_modifier_mask(3) btn = self.button @@ -472,6 +498,8 @@ def get_next_event(self, time=0): self.queue_event(self.get_next_key_event(EventKeyUp, time)) elif event_type == RSDL.WINDOWEVENT: self.handle_windowevent(event_type, event) + elif event_type == RSDL.DROPFILE: + self.queue_event(self.get_dropevent(time, event_type, event)) elif event_type == RSDL.QUIT: if self.altf4quit: # we want to quit hard from rsqueakvm.util.dialog import ask_question diff --git a/rsqueakvm/plugins/drop_plugin.py b/rsqueakvm/plugins/drop_plugin.py new file mode 100644 index 00000000..c4d6a2c3 --- /dev/null +++ b/rsqueakvm/plugins/drop_plugin.py @@ -0,0 +1,33 @@ +import os + +from rsqueakvm.error import PrimitiveFailedError +from rsqueakvm.plugins.plugin import Plugin +from rsqueakvm.util.system import IS_WINDOWS + + +DropPlugin = Plugin() + + +@DropPlugin.expose_primitive(unwrap_spec=[object, int]) +def primitiveDropRequestFileName(interp, s_frame, w_rcvr, index): + path = interp.space.display().get_dropped_filename() + if path is None: + raise PrimitiveFailedError + else: + return interp.space.wrap_string(path) + + +@DropPlugin.expose_primitive(unwrap_spec=[object, int]) +def primitiveDropRequestFileHandle(interp, s_frame, w_rcvr, index): + path = interp.space.display().get_dropped_filename() + if path is None: + raise PrimitiveFailedError + else: + mode = os.O_RDONLY + if not IS_WINDOWS: + mode |= os.O_BINARY + try: + file_descriptor = os.open(path, mode, 0666) + except OSError: + raise PrimitiveFailedError + return interp.space.wrap_int(file_descriptor) From 832bdf342709fc3baee7013f26471d9f41909b87 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 15:55:59 +0100 Subject: [PATCH 29/31] fix typos --- rsqueakvm/display.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rsqueakvm/display.py b/rsqueakvm/display.py index cb790fd9..d2f7ee4c 100644 --- a/rsqueakvm/display.py +++ b/rsqueakvm/display.py @@ -406,11 +406,11 @@ def handle_windowevent(self, c_type, event): d=self.depth) def get_dropevent(self, time, c_type, event): - drop_event = rffi.cast(RSDL.DropEvent, event) + drop_event = rffi.cast(RSDL.DropEventPtr, event) path = rffi.charp2str(drop_event.c_file) btn, mods = self.get_mouse_event_buttons_and_mods() self._dropped_file = path - lltype.free(drop_event.c_file) + lltype.free(drop_event.c_file, flavor='raw') return [EventTypeDragDropFiles, time, EventDragTypeDrop, From 0517dd81dd6a51edd2579384f2a1527339a91c2e Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 17:37:20 +0100 Subject: [PATCH 30/31] fix test --- rsqueakvm/test/test_external_calls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsqueakvm/test/test_external_calls.py b/rsqueakvm/test/test_external_calls.py index 856cd6b2..ee8865e0 100644 --- a/rsqueakvm/test/test_external_calls.py +++ b/rsqueakvm/test/test_external_calls.py @@ -185,7 +185,7 @@ def write(fd, data): monkeypatch.setattr(os, "write", write) content = W_DisplayBitmap(space, 1, 32) - content._real_depth_buffer[0] = rffi.r_uint(1633837924) + content._squeak_pixel_buffer[0] = rffi.r_uint(1633837924) try: stack = [space.w(1), space.w(1), content, space.w(1), space.w(1)] w_c = external_call(space, From 99e499bc7d01ffb51e6a07047ab7ae044ba11ee7 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Wed, 1 Mar 2017 17:49:27 +0100 Subject: [PATCH 31/31] make color tables r_uints --- rsqueakvm/model/display.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rsqueakvm/model/display.py b/rsqueakvm/model/display.py index eae5892d..71221d90 100644 --- a/rsqueakvm/model/display.py +++ b/rsqueakvm/model/display.py @@ -231,19 +231,21 @@ def force_rectange_to_screen(self, left, right, top, bottom): self.display().flip(self._sdl_pixel_buffer, left, top, right, bottom) -PIXEL_LOOKUP_1BIT = [0xffffffff, 0xff000000] -PIXEL_LOOKUP_2BIT = [0xff000000, 0xff848484, 0xffc6c6c6, 0xffffffff] -PIXEL_LOOKUP_4BIT = [ +PIXEL_LOOKUP_1BIT = [r_uint(i) for i in [0xffffffff, 0xff000000]] +PIXEL_LOOKUP_2BIT = [r_uint(i) for i in [ + 0xff000000, 0xff848484, 0xffc6c6c6, 0xffffffff +]] +PIXEL_LOOKUP_4BIT = [r_uint(i) for i in [ 0xff000000, 0xff000084, 0xff008400, 0xff008484, 0xff840000, 0xff840084, 0xff848400, 0xff848484, 0xffc6c6c6, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff -] +]] # String streamContents: [:s | # Color indexedColors # do: [:ea|s nextPutAll: '0xFF'; nextPutAll: ea printHtmlString] # separatedBy: [s nextPutAll: ', ']] -PIXEL_LOOKUP_8BIT = [ +PIXEL_LOOKUP_8BIT = [r_uint(i) for i in [ 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF7F7F7F, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFF00FFFF, 0xFFFFFF00, 0xFFFF00FF, 0xFF1F1F1F, 0xFF3F3F3F, 0xFF5F5F5F, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFDFDFDF, 0xFF070707, 0xFF0F0F0F, @@ -287,7 +289,8 @@ def force_rectange_to_screen(self, left, right, top, bottom): 0xFFFF6598, 0xFFFF9898, 0xFFFFCB98, 0xFFFFFF98, 0xFFFF00CB, 0xFFFF32CB, 0xFFFF65CB, 0xFFFF98CB, 0xFFFFCBCB, 0xFFFFFFCB, 0xFFFF00FF, 0xFFFF32FF, 0xFFFF65FF, 0xFFFF98FF, 0xFFFFCBFF, 0xFFFFFFFF -] +]] + PIXEL_LOOKUP_TABLE = [ PIXEL_LOOKUP_1BIT, PIXEL_LOOKUP_2BIT,